Skip to content

Commit

Permalink
feat: include chain id as a param in the payment link (#474)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
alexkeating authored Apr 4, 2023
1 parent ef14ad2 commit 37d585c
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 11 deletions.
15 changes: 11 additions & 4 deletions frontend/src/components/ConnectWallet.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,25 @@
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;
}
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 };
Expand All @@ -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) };
},
});
</script>
19 changes: 17 additions & 2 deletions frontend/src/layouts/BaseLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@
</div>
<network-dropdown />
</div>
<connect-wallet v-else-if="!isLoading">
<connect-wallet v-else-if="!isLoading" :to="redirectPath" :params="redirectParams">
<div class="row justify-end items-center">
<connect-wallet>
<connect-wallet :to="redirectPath" :params="redirectParams">
<base-button
class="cursor-pointer"
color="primary"
Expand Down Expand Up @@ -294,6 +294,19 @@ export default defineComponent({
const currentLanguage = ref({ label: language.value.label, value: language.value.value });
const argentModalDismissed = ref(false);
const showArgentModal = computed(() => 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) => {
Expand All @@ -313,6 +326,8 @@ export default defineComponent({
language,
network,
changeLanguage,
redirectPath,
redirectParams,
showArgentModal,
supportedLanguages,
toggleAdvancedMode,
Expand Down
31 changes: 27 additions & 4 deletions frontend/src/pages/AccountSend.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
<div v-if="!userAddress">
<p class="text-center">{{ $t('Send.connect-your-wallet') }}</p>
<div class="row justify-center">
<connect-wallet>
<connect-wallet :to="connectRedirectTo" :params="paymentLinkParams">
<base-button class="text-center" :label="$t('Send.connect-wallet')" />
</connect-wallet>
</div>
Expand Down Expand Up @@ -233,7 +233,7 @@
type="submit"
/>
<base-button
@click="generatePaymentLink({ to: recipientId, token, amount: humanAmount })"
@click="generatePaymentLink({ to: recipientId, token, amount: humanAmount, chainId: chainId })"
:disable="isSending"
:flat="true"
:full-width="true"
Expand Down Expand Up @@ -271,14 +271,15 @@ import {
Contract,
formatUnits,
getAddress,
hexValue,
MaxUint256,
parseUnits,
TransactionResponse,
Zero,
} from 'src/utils/ethers';
import { humanizeTokenAmount, humanizeMinSendAmount, humanizeArithmeticResult } from 'src/utils/utils';
import { generatePaymentLink, parsePaymentLink } from 'src/utils/payment-links';
import { Provider, TokenInfoExtended } from 'components/models';
import { Provider, TokenInfoExtended, supportedChains } from 'components/models';
import { ERC20_ABI } from 'src/utils/constants';
import { toAddress } from 'src/utils/address';
Expand All @@ -292,6 +293,7 @@ function useSendForm() {
isLoading,
NATIVE_TOKEN,
provider,
setNetwork,
signer,
tokens: tokenList,
umbra,
Expand Down Expand Up @@ -319,6 +321,9 @@ function useSendForm() {
const isValidRecipientId = ref(true); // for showing/hiding bottom space (error message div) under input field
const toll = ref<BigNumber>(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);
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -631,6 +652,7 @@ function useSendForm() {
advancedAcknowledged,
advancedMode,
balances,
connectRedirectTo,
chainId,
currentChain,
humanAmount,
Expand All @@ -644,6 +666,7 @@ function useSendForm() {
isValidTokenAmount,
NATIVE_TOKEN,
onFormSubmit,
paymentLinkParams,
recipientId,
recipientIdBaseInputRef,
sendAdvancedButton,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/store/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }]);
Expand Down
12 changes: 11 additions & 1 deletion frontend/src/utils/payment-links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -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();
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit 37d585c

Please sign in to comment.