Skip to content

Commit

Permalink
feat: properly support depposits of custom base token (#215)
Browse files Browse the repository at this point in the history
Co-authored-by: bxpana <[email protected]>
Co-authored-by: Dustin Brickwood <[email protected]>
  • Loading branch information
3 people authored Jan 30, 2025
1 parent f528a2f commit ef0f1c5
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 71 deletions.
108 changes: 71 additions & 37 deletions composables/transaction/useAllowance.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { utils } from "zksync-ethers";
import { L1Signer, utils } from "zksync-ethers";
import IERC20 from "zksync-ethers/abi/IERC20.json";

import type { Hash } from "@/types";
import type { DepositFeeValues } from "../zksync/deposit/useFee";
import type { Hash, TokenAllowance } from "@/types";
import type { BigNumberish } from "ethers";

export default (
accountAddress: Ref<string | undefined>,
tokenAddress: Ref<string | undefined>,
getContractAddress: () => Promise<string | undefined>
getContractAddress: () => Promise<string | undefined>,
getL1Signer: () => Promise<L1Signer | undefined>
) => {
const { getPublicClient, getWallet } = useOnboardStore();
const { getPublicClient } = useOnboardStore();
const {
result,
inProgress,
Expand Down Expand Up @@ -43,13 +45,14 @@ export default (
}
};

let approvalAmount: BigNumberish | undefined;
let approvalAmounts: TokenAllowance[] = [];
const setAllowanceStatus = ref<"not-started" | "processing" | "waiting-for-signature" | "sending" | "done">(
"not-started"
);
const setAllowanceTransactionHash = ref<Hash | undefined>();
const setAllowanceTransactionHashes = ref<(Hash | undefined)[]>([]);

const {
result: setAllowanceReceipt,
result: setAllowanceReceipts,
inProgress: setAllowanceInProgress,
error: setAllowanceError,
execute: executeSetAllowance,
Expand All @@ -63,49 +66,79 @@ export default (
const contractAddress = await getContractAddress();
if (!contractAddress) throw new Error("Contract address is not available");

const wallet = await getWallet();

const wallet = await getL1Signer();
setAllowanceStatus.value = "waiting-for-signature";
setAllowanceTransactionHash.value = await wallet.writeContract({
address: tokenAddress.value as Hash,
abi: IERC20,
functionName: "approve",
args: [contractAddress, approvalAmount!.toString()],
});

setAllowanceStatus.value = "sending";
const receipt = await retry(
() =>
getPublicClient().waitForTransactionReceipt({
hash: setAllowanceTransactionHash.value!,
onReplaced: (replacement) => {
setAllowanceTransactionHash.value = replacement.transaction.hash;
},
}),
{
retries: 3,
delay: 5_000,
}
);

const receipts = [];

for (let i = 0; i < approvalAmounts.length; i++) {
const txResponse = await wallet?.approveERC20(approvalAmounts[i].token, approvalAmounts[i].allowance);

setAllowanceTransactionHashes.value.push(txResponse?.hash as Hash);

setAllowanceStatus.value = "sending";

const receipt = await retry(
() =>
getPublicClient().waitForTransactionReceipt({
hash: setAllowanceTransactionHashes.value[i]!,
onReplaced: (replacement) => {
setAllowanceTransactionHashes.value[i] = replacement.transaction.hash;
},
}),
{
retries: 3,
delay: 5_000,
}
);

receipts.push(receipt);
}

await requestAllowance();

setAllowanceStatus.value = "done";
return receipt;
return receipts;
} catch (err) {
setAllowanceStatus.value = "not-started";
throw err;
}
},
{ cache: false }
);
const setAllowance = async (amount: BigNumberish) => {
approvalAmount = amount;
const getApprovalAmounts = async (amount: BigNumberish, fee: DepositFeeValues) => {
const wallet = await getL1Signer();
if (!wallet) throw new Error("Wallet is not available");

// We need to pass the overrides in order to get the correct deposits allowance params
const overrides = {
gasPrice: fee.gasPrice,
gasLimit: fee.l1GasLimit,
maxFeePerGas: fee.maxFeePerGas,
maxPriorityFeePerGas: fee.maxPriorityFeePerGas,
};
if (overrides.gasPrice && overrides.maxFeePerGas) {
overrides.gasPrice = undefined;
}

approvalAmounts = (await wallet.getDepositAllowanceParams(
tokenAddress.value!,
amount,
overrides
)) as TokenAllowance[];

return approvalAmounts;
};

const setAllowance = async (amount: BigNumberish, fee: DepositFeeValues) => {
await getApprovalAmounts(amount, fee);
await executeSetAllowance();
};

const resetSetAllowance = () => {
approvalAmount = undefined;
approvalAmounts = [];
setAllowanceStatus.value = "not-started";
setAllowanceTransactionHash.value = undefined;
setAllowanceTransactionHashes.value = [];
resetExecuteSetAllowance();
};

Expand All @@ -124,12 +157,13 @@ export default (
error: computed(() => error.value),
requestAllowance,

setAllowanceTransactionHash,
setAllowanceReceipt,
setAllowanceTransactionHashes,
setAllowanceReceipts,
setAllowanceStatus,
setAllowanceInProgress,
setAllowanceError,
setAllowance,
resetSetAllowance,
getApprovalAmounts,
};
};
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"vite": "^3.0.0",
"vue-tippy": "^6.0.0",
"web3-avatar-vue": "^1.0.0",
"zksync-ethers": "^6.15.3"
"zksync-ethers": "^6.16.0"
},
"overrides": {
"vue": "latest"
Expand Down
4 changes: 4 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { type Config as HyperchainsConfig } from "@/scripts/hyperchains/common";
import type { BigNumberish } from "ethers";
import type { Address } from "zksync-ethers/build/types";

export type Hash = `0x${string}`;

Expand All @@ -15,6 +17,8 @@ export type Token = {
};
export type TokenAmount = Token & { amount: BigNumberish };

export type TokenAllowance = { token: Address; allowance: bigint };

export declare namespace Api {
namespace Response {
type Collection<T> = {
Expand Down
71 changes: 42 additions & 29 deletions views/transactions/Deposit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -218,25 +218,27 @@
</CommonErrorBlock>
<CommonHeightTransition
v-if="step === 'form'"
:opened="(!enoughAllowance && !continueButtonDisabled) || !!setAllowanceReceipt"
:opened="(!enoughAllowance && !continueButtonDisabled) || !!setAllowanceReceipts?.length"
>
<CommonCardWithLineButtons class="mt-4">
<DestinationItem
v-if="enoughAllowance && setAllowanceReceipt"
v-if="enoughAllowance && setAllowanceReceipts?.length"
as="div"
:description="`You can now proceed to deposit`"
>
<template #label>
{{ selectedToken?.symbol }} allowance approved
<a
v-if="l1BlockExplorerUrl"
:href="`${l1BlockExplorerUrl}/tx/${setAllowanceReceipt.transactionHash}`"
target="_blank"
class="inline-flex items-center gap-1 underline underline-offset-2"
>
View on Explorer
<ArrowTopRightOnSquareIcon class="h-6 w-6" aria-hidden="true" />
</a>
<template v-for="allowanceReceipt in setAllowanceReceipts" :key="allowanceReceipt.transactionHash">
<a
v-if="l1BlockExplorerUrl"
:href="`${l1BlockExplorerUrl}/tx/${allowanceReceipt.transactionHash}`"
target="_blank"
class="inline-flex items-center gap-1 underline underline-offset-2"
>
View on Explorer
<ArrowTopRightOnSquareIcon class="h-6 w-6" aria-hidden="true" />
</a>
</template>
</template>
<template #image>
<div class="aspect-square h-full w-full rounded-full bg-success-400 p-3 text-black">
Expand All @@ -247,20 +249,25 @@
<DestinationItem v-else as="div">
<template #label>
Approve {{ selectedToken?.symbol }} allowance
<a
v-if="l1BlockExplorerUrl && setAllowanceTransactionHash"
:href="`${l1BlockExplorerUrl}/tx/${setAllowanceTransactionHash}`"
target="_blank"
class="inline-flex items-center gap-1 underline underline-offset-2"
<template
v-for="allowanceTransactionHash in setAllowanceTransactionHashes"
:key="allowanceTransactionHash"
>
View on Explorer
<ArrowTopRightOnSquareIcon class="h-6 w-6" aria-hidden="true" />
</a>
<a
v-if="l1BlockExplorerUrl && allowanceTransactionHash"
:href="`${l1BlockExplorerUrl}/tx/${allowanceTransactionHash}`"
target="_blank"
class="inline-flex items-center gap-1 underline underline-offset-2"
>
View on Explorer
<ArrowTopRightOnSquareIcon class="h-6 w-6" aria-hidden="true" />
</a>
</template>
</template>
<template #underline>
Before depositing you need to give our bridge permission to spend specified amount of
{{ selectedToken?.symbol }}.
<span v-if="allowance && !allowance.isZero()"
<span v-if="allowance && allowance !== 0n"
>You can deposit up to
<CommonButtonLabel variant="light" @click="setAmountToCurrentAllowance()">
{{ parseTokenAmount(allowance!, selectedToken!.decimals) }}
Expand Down Expand Up @@ -370,6 +377,7 @@ import {
ExclamationTriangleIcon,
LockClosedIcon,
} from "@heroicons/vue/24/outline";
import { computedAsync } from "@vueuse/core";
import { useRouteQuery } from "@vueuse/router";
import { isAddress } from "ethers";
Expand Down Expand Up @@ -469,32 +477,37 @@ const {
error: allowanceRequestError,
requestAllowance,
setAllowanceTransactionHash,
setAllowanceReceipt,
setAllowanceTransactionHashes,
setAllowanceReceipts,
setAllowanceStatus,
setAllowanceInProgress,
setAllowanceError,
setAllowance,
resetSetAllowance,
getApprovalAmounts,
} = useAllowance(
computed(() => account.value.address),
computed(() => selectedToken.value?.address),
async () => (await providerStore.requestProvider().getDefaultBridgeAddresses()).sharedL1
async () => (await providerStore.requestProvider().getDefaultBridgeAddresses()).sharedL1,
eraWalletStore.getL1Signer
);
const enoughAllowance = computed(() => {
if (!allowance.value || !selectedToken.value) {
const enoughAllowance = computedAsync(async () => {
if (allowance?.value === undefined || !selectedToken.value) {
return true;
}
return !allowance.value.isZero() && allowance.value.gte(totalComputeAmount.value);
});
const approvalAmounts = await getApprovalAmounts(totalComputeAmount.value, feeValues.value!);
const approvalAllowance = approvalAmounts.length ? approvalAmounts[0]?.allowance : 0;
return allowance.value !== 0n && allowance?.value >= BigInt(approvalAllowance);
}, false);
const setAmountToCurrentAllowance = () => {
if (!allowance.value || !selectedToken.value) {
return;
}
amount.value = parseTokenAmount(allowance.value, selectedToken.value.decimals);
};
const setTokenAllowance = async () => {
await setAllowance(totalComputeAmount.value);
await setAllowance(totalComputeAmount.value, feeValues.value!);
await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait for balances to be updated on API side
await fetchBalances(true);
};
Expand Down Expand Up @@ -697,7 +710,7 @@ const makeTransaction = async () => {
if (tx) {
zkSyncEthereumBalance.deductBalance(feeToken.value!.address!, fee.value!);
zkSyncEthereumBalance.deductBalance(transaction.value!.token.address!, transaction.value!.token.amount);
zkSyncEthereumBalance.deductBalance(transaction.value!.token.address!, String(transaction.value!.token.amount));
transactionInfo.value = {
type: "deposit",
transactionHash: tx.hash,
Expand Down

0 comments on commit ef0f1c5

Please sign in to comment.