From 37d585cea0b9f7920c171f83240e3b67de46dcf1 Mon Sep 17 00:00:00 2001 From: Alexander Keating Date: Tue, 4 Apr 2023 11:44:53 -0400 Subject: [PATCH] feat: include chain id as a param in the payment link (#474) * Add chain to payment link * Remove console logs * Fix param * Handle case where wallet is not connected * Make changes based on feedback * Add back accidental deletion * Fix redirect * Remove comment * Add redirect to base layout connect * Make sure user is connected to trigger wallet --- frontend/src/components/ConnectWallet.vue | 15 ++++++++--- frontend/src/layouts/BaseLayout.vue | 19 ++++++++++++-- frontend/src/pages/AccountSend.vue | 31 ++++++++++++++++++++--- frontend/src/store/wallet.ts | 4 +++ frontend/src/utils/payment-links.ts | 12 ++++++++- frontend/src/utils/utils.ts | 8 ++++++ 6 files changed, 78 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/ConnectWallet.vue b/frontend/src/components/ConnectWallet.vue index 5eaadb2f3..9e9038414 100644 --- a/frontend/src/components/ConnectWallet.vue +++ b/frontend/src/components/ConnectWallet.vue @@ -8,15 +8,17 @@ import { defineComponent } from 'vue'; import { Router, useRouter } from 'vue-router'; import useWalletStore from 'src/store/wallet'; +import { paramsToObject } from 'src/utils/utils'; -function useWallet(to: string, router: Router) { +function useWallet(to: string, router: Router, params?: string) { const { connectWallet, userAddress } = useWalletStore(); async function connectWalletWithRedirect() { // If user already connected wallet, continue (this branch is used when clicking e.g. the "Send" box // from the home page) + const parsedParams = paramsToObject(new URLSearchParams(params || '').entries()); if (userAddress.value && to) { - await router.push({ name: to }); + await router.push({ name: to, query: parsedParams }); return; } else if (userAddress.value) { return; @@ -24,7 +26,7 @@ function useWallet(to: string, router: Router) { await connectWallet(); - if (to) await router.push({ name: to }); // redirect to specified page + if (to) await router.push({ name: to, query: parsedParams }); // redirect to specified page } return { connectWalletWithRedirect }; @@ -40,11 +42,16 @@ export default defineComponent({ required: false, default: undefined, }, + params: { + type: String, + required: false, + default: undefined, + }, }, setup(props) { const router = useRouter(); - return { ...useWallet(props.to || 'home', router) }; + return { ...useWallet(props.to || 'home', router, props.params) }; }, }); diff --git a/frontend/src/layouts/BaseLayout.vue b/frontend/src/layouts/BaseLayout.vue index 475b628cc..b1f5de83d 100644 --- a/frontend/src/layouts/BaseLayout.vue +++ b/frontend/src/layouts/BaseLayout.vue @@ -87,9 +87,9 @@ - +
- + isArgent.value && !argentModalDismissed.value); + const redirectPath = computed(() => { + if (window.location.pathname === '/send') { + return 'send'; + } + return undefined; + }); + const redirectParams = computed(() => { + if (window.location.pathname === '/send') { + return window.location.search; + } + return undefined; + }); + const version = window.logger.version; const changeLanguage = (language: Language) => { @@ -313,6 +326,8 @@ export default defineComponent({ language, network, changeLanguage, + redirectPath, + redirectParams, showArgentModal, supportedLanguages, toggleAdvancedMode, diff --git a/frontend/src/pages/AccountSend.vue b/frontend/src/pages/AccountSend.vue index 84e7f74b3..8d2abf785 100644 --- a/frontend/src/pages/AccountSend.vue +++ b/frontend/src/pages/AccountSend.vue @@ -82,7 +82,7 @@

{{ $t('Send.connect-your-wallet') }}

- +
@@ -233,7 +233,7 @@ type="submit" /> (Zero); const sendMax = ref(false); + const paymentLinkParams = ref(window.location.search); + const attemptedNetworkChange = ref(false); + const connectRedirectTo = ref('send'); // Computed form parameters. const showAdvancedWarning = computed(() => advancedAcknowledged.value === false && useNormalPubKey.value === true); @@ -400,7 +405,7 @@ function useSendForm() { }); async function setPaymentLinkData() { - const { to, token: paymentToken, amount } = await parsePaymentLink(NATIVE_TOKEN.value); + const { to, token: paymentToken, amount, chainId: linkChainId } = await parsePaymentLink(NATIVE_TOKEN.value); if (to) recipientId.value = to; if (amount) humanAmount.value = amount; @@ -410,6 +415,22 @@ function useSendForm() { // Validate the form await sendFormRef.value?.validate(); + + // Switch chain + if (linkChainId) { + const chain = supportedChains.filter((chain) => chain.chainId === hexValue(BigNumber.from(linkChainId))); + + if ( + chain.length === 1 && + !isLoading.value && + chainId.value !== Number(linkChainId) && + !attemptedNetworkChange.value && + userAddress.value + ) { + attemptedNetworkChange.value = true; + await setNetwork(chain[0]); + } + } } // Validators @@ -631,6 +652,7 @@ function useSendForm() { advancedAcknowledged, advancedMode, balances, + connectRedirectTo, chainId, currentChain, humanAmount, @@ -644,6 +666,7 @@ function useSendForm() { isValidTokenAmount, NATIVE_TOKEN, onFormSubmit, + paymentLinkParams, recipientId, recipientIdBaseInputRef, sendAdvancedButton, diff --git a/frontend/src/store/wallet.ts b/frontend/src/store/wallet.ts index 8931d0284..fcf53a8b1 100644 --- a/frontend/src/store/wallet.ts +++ b/frontend/src/store/wallet.ts @@ -399,6 +399,10 @@ export default function useWalletStore() { */ async function setNetwork(chain: Chain) { setLoading(true); + if (!provider.value) { + setLoading(false); + return; + } try { await provider.value?.send('wallet_switchEthereumChain', [{ chainId: chain.chainId }]); diff --git a/frontend/src/utils/payment-links.ts b/frontend/src/utils/payment-links.ts index bc3254232..080ec9047 100644 --- a/frontend/src/utils/payment-links.ts +++ b/frontend/src/utils/payment-links.ts @@ -40,10 +40,12 @@ export async function generatePaymentLink({ to = undefined, token = undefined, amount = undefined, + chainId = undefined, }: { to: string | undefined; token: TokenInfoExtended | undefined; amount: string | undefined; + chainId: number | undefined; }) { // Ensure at least one form field was provided if (!to && !token && !amount) throw new Error('Please complete at least one field to generate a payment link'); @@ -56,6 +58,7 @@ export async function generatePaymentLink({ if (to) url.searchParams.set('to', to); if (token) url.searchParams.set('token', token.symbol); if (amount) url.searchParams.set('amount', amount); + if (chainId) url.searchParams.set('chainId', BigNumber.from(chainId).toString()); await copyToClipboard(url.toString()); notifyUser('success', 'Payment link copied to clipboard'); @@ -66,16 +69,23 @@ export async function generatePaymentLink({ */ export async function parsePaymentLink(nativeToken: TokenInfoExtended) { // Setup output object - const paymentData: { to: string | null; token: TokenInfoExtended | null; amount: string | null } = { + const paymentData: { + to: string | null; + token: TokenInfoExtended | null; + amount: string | null; + chainId: string | null; + } = { to: null, token: null, amount: null, + chainId: null, }; // First we assign the `to` and `amount` fields. const params = new URLSearchParams(window.location.search); paymentData['to'] = params.get('to'); paymentData['amount'] = params.get('amount'); + paymentData['chainId'] = params.get('chainId'); // If no `token` symbol was given, we can return with the payment data. let tokenSymbol = params.get('token')?.toLowerCase(); diff --git a/frontend/src/utils/utils.ts b/frontend/src/utils/utils.ts index 727a8eea6..38b09e921 100644 --- a/frontend/src/utils/utils.ts +++ b/frontend/src/utils/utils.ts @@ -199,3 +199,11 @@ export function openInEtherscan(hash: string, provider: Provider, chainId: numbe // Assume mainnet if we don't have a provider with a valid chainId window.open(getEtherscanUrl(hash, chainId || 1)); } + +export function paramsToObject(entries: IterableIterator<[string, string]>) { + const result = {} as { [key: string]: string }; + for (const [key, value] of entries) { + result[key] = value; + } + return result; +}