diff --git a/components/common/input/TransactionWithdraw.vue b/components/common/input/TransactionWithdraw.vue index 42244bdf..b80e8fbb 100644 --- a/components/common/input/TransactionWithdraw.vue +++ b/components/common/input/TransactionWithdraw.vue @@ -53,6 +53,9 @@ Insufficient balance + + {{ totalAmountPrice }} @@ -144,6 +148,10 @@ const props = defineProps({ type: Boolean, default: false, }, + mergeLimitExceeds: { + type: Boolean, + default: false + } }); const emit = defineEmits<{ @@ -215,6 +223,9 @@ const setMaxAmount = () => { }; const amountError = computed(() => { + if(props.mergeLimitExceeds) { + return 'exceeds_merge_limit' + } if (!selectedToken.value) return; if (tokenBalance.value && totalComputeAmount.value.gt(tokenBalance.value.amount)) { return "exceeds_balance"; diff --git a/components/transaction/summary/AddressEntry.vue b/components/transaction/summary/AddressEntry.vue index 33c7662f..533fee9d 100644 --- a/components/transaction/summary/AddressEntry.vue +++ b/components/transaction/summary/AddressEntry.vue @@ -46,11 +46,18 @@ const props = defineProps({ type: Object as PropType, required: true, }, + addressLabel: { + type: String, + required: false + } }); const { account } = storeToRefs(useOnboardStore()); const accountLabel = computed(() => { + if(props.addressLabel) { + return props.addressLabel; + } if (props.address === account.value.address) { return `Your ${props.destination.label} account`; } diff --git a/composables/transaction/useMergeToken.ts b/composables/transaction/useMergeToken.ts new file mode 100644 index 00000000..f18309ed --- /dev/null +++ b/composables/transaction/useMergeToken.ts @@ -0,0 +1,65 @@ +import MergeTokenPortal from "@/zksync-web3-nova/abi/MergeTokenPortal.json"; +import { useOnboardStore } from "@/store/onboard"; + +import type { Hash } from "@/types"; +import type { BigNumberish } from "ethers"; +import type { PublicClient } from "viem"; +import type { Ref } from "vue"; + +const nodeType = process.env.NODE_TYPE; + +export type SourceTokenInfo = { + isSupported: boolean; + isLocked: boolean; + mergeToken: string; + balance: bigint; + depositLimit: bigint; +}; +const NOVA_CHAIN_ID = nodeType === "nexus" ? 810180 : 810182; +const MERGE_TOKEN_PORTAL_ADDRESSES = + nodeType === "nexus" ? "0x83FD59FD58C6A5E6eA449e5400D02803875e1104" : "0x83FD59FD58C6A5E6eA449e5400D02803875e1104"; +export default (tokenL2Address: Ref) => { + const onboardStore = useOnboardStore(); + const { + result, + inProgress, + error, + execute: getMergeTokenInfo, + reset, + } = usePromise( + async () => { + const publicClient = onboardStore.getPublicClient(NOVA_CHAIN_ID); + const info = (await publicClient!.readContract({ + address: MERGE_TOKEN_PORTAL_ADDRESSES, + abi: MergeTokenPortal, + functionName: "getSourceTokenInfos", + args: [tokenL2Address.value], + })) as SourceTokenInfo; + return info; + }, + { cache: false } + ); + + const requestMergeTokenInfo = async () => { + if (tokenL2Address.value) { + await getMergeTokenInfo(); + } else { + reset(); + } + }; + + watch( + [tokenL2Address], + () => { + requestMergeTokenInfo(); + }, + { immediate: true } + ); + + return { + result: computed(() => result.value), + inProgress: computed(() => inProgress.value), + error: computed(() => error.value), + requestMergeTokenInfo, + }; +}; diff --git a/composables/zksync/deposit/useTransaction.ts b/composables/zksync/deposit/useTransaction.ts index 69131ef1..09b854f2 100644 --- a/composables/zksync/deposit/useTransaction.ts +++ b/composables/zksync/deposit/useTransaction.ts @@ -20,6 +20,7 @@ export default (getL1Signer: () => Promise) => { to: string; tokenAddress: string; amount: BigNumberish; + toMerge?: boolean; }, fee: DepositFeeValues ) => { @@ -48,6 +49,7 @@ export default (getL1Signer: () => Promise) => { to: transaction.to, token: transaction.tokenAddress, amount: transaction.amount, + toMerge: transaction.toMerge, l2GasLimit: fee.l2GasLimit, overrides, }); diff --git a/public/img/Shape.svg b/public/img/Shape.svg new file mode 100644 index 00000000..d39ed105 --- /dev/null +++ b/public/img/Shape.svg @@ -0,0 +1,3 @@ + + + diff --git a/store/zksync/ethereumBalance.ts b/store/zksync/ethereumBalance.ts index ae8716eb..8170be17 100644 --- a/store/zksync/ethereumBalance.ts +++ b/store/zksync/ethereumBalance.ts @@ -1,9 +1,9 @@ -import { getBalance } from "@wagmi/core"; +import { getBalance, getPublicClient } from "@wagmi/core"; import type { Hash, TokenAmount } from "@/types"; import type { Config } from "@wagmi/core"; import type { Address } from "viem"; - +import { erc20Abi } from "viem"; import { l1Networks } from "@/data/networks"; import { useEthereumBalanceStore } from "@/store/ethereumBalance"; import { useNetworkStore } from "@/store/network"; @@ -17,7 +17,7 @@ export const useZkSyncEthereumBalanceStore = defineStore("zkSyncEthereumBalances const tokensStore = useZkSyncTokensStore(); const { l1Network, selectedNetwork } = storeToRefs(useNetworkStore()); const wagmiConfig = onboardStore.wagmiConfig; - const { account } = storeToRefs(onboardStore); + const { account, network } = storeToRefs(onboardStore); const { balance: ethereumBalance } = storeToRefs(ethereumBalancesStore); const { l1Tokens } = storeToRefs(tokensStore); const searchToken = useSearchtokenStore(); @@ -62,32 +62,49 @@ export const useZkSyncEthereumBalanceStore = defineStore("zkSyncEthereumBalances nativeToken!.price = wmntToken?.price ?? 0; nativeToken!.iconUrl = "/img/mantle.svg"; } - return await Promise.all([ - ...filterL1tokens.map(async (token) => { - const amount = await getBalance(wagmiConfig as Config, { - address: account.value.address!, - chainId: l1Network.value!.id, - token: token.address === ETH_TOKEN.l1Address ? undefined : (token.address! as Hash), - }); - return { - ...token, - amount: amount.value.toString(), - }; - }), - ...(searchTokenBalance.value ?? []) - .filter((token) => !Object.values(l1Tokens.value ?? []).find((e) => e.address === token.address)) - .map(async (e) => { - const amount = await getBalance(wagmiConfig as Config, { - address: account.value.address!, - chainId: l1Network.value!.id, - token: e.address === ETH_TOKEN.l1Address ? undefined : (e.address! as Hash), - }); - return { - ...e, - amount: amount.value.toString(), - }; - }), - ]); + const publicClient = getPublicClient(wagmiConfig as Config, { chainId: l1Network.value?.id }); + + const ethBalance = await getBalance(wagmiConfig as Config, { + address: account.value.address!, + chainId: l1Network.value!.id, + token: undefined, + }); + const erc20Tokens = filterL1tokens.filter((t) => t.address !== ETH_TOKEN.l1Address); + const filterTokenBalances = + (await publicClient?.multicall({ + contracts: erc20Tokens.map((item) => ({ + address: item.address as Hash, + abi: erc20Abi, + functionName: "balanceOf", + args: [account.value.address!], + })), + })) ?? []; + + const searchTokens = (searchTokenBalance.value ?? []).filter( + (token) => !Object.values(l1Tokens.value ?? []).find((e) => e.address === token.address) + ); + const searchTokenBalances = + (await publicClient?.multicall({ + contracts: searchTokens.map((item) => ({ + address: item.address as Hash, + abi: erc20Abi, + functionName: "balanceOf", + args: [account.value.address!], + })), + })) ?? []; + + const ethToken = filterL1tokens.find((item) => item.address === ETH_TOKEN.l1Address); + return [ + { ...ethToken!, amount: ethBalance.value.toString() }, + ...filterTokenBalances.map((item, index) => ({ + ...erc20Tokens[index], + amount: item.result?.toString() ?? "0", + })), + ...searchTokenBalances.map((item, index) => ({ + ...searchTokens[index], + amount: item.result?.toString() ?? "0", + })), + ]; }; let isSaveToken = false, oldBalance: TokenAmount[]; diff --git a/store/zksync/tokens.ts b/store/zksync/tokens.ts index c8a40135..bf5fcd89 100644 --- a/store/zksync/tokens.ts +++ b/store/zksync/tokens.ts @@ -44,7 +44,10 @@ export const useZkSyncTokensStore = defineStore("zkSyncTokens", () => { return Object.fromEntries( tokensRaw.value .filter((e) => e.l1Address) - .map((token) => [token.l1Address!, { ...token, l1Address: undefined, address: token.l1Address! }]) + .map((token) => [ + token.l1Address!, + { ...token, l1Address: undefined, address: token.l1Address!, l2Address: token.l2Address }, + ]) ); }); diff --git a/types/index.d.ts b/types/index.d.ts index 21aad246..220b403e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -10,6 +10,7 @@ export type Token = { iconUrl?: string; price?: TokenPrice; networkKey?: string; + l2Address: string; }; export type TokenAmount = Token & { amount: BigNumberish }; diff --git a/utils/mappers.ts b/utils/mappers.ts index abbde89e..e634dc9d 100644 --- a/utils/mappers.ts +++ b/utils/mappers.ts @@ -44,6 +44,7 @@ export const mapApiToken = (token: Api.Response.Token): Token => { } return { + l2Address: token.l2Address, l1Address: token.l1Address || undefined, address: token.l2Address, symbol: token.symbol || "unknown", diff --git a/views/transactions/Deposit.vue b/views/transactions/Deposit.vue index 0bf884f6..011bd22a 100644 --- a/views/transactions/Deposit.vue +++ b/views/transactions/Deposit.vue @@ -7,10 +7,10 @@ OKX Cryptopedia - +

@@ -72,6 +72,7 @@ :max-amount="maxAmount" :approve-required="!enoughAllowance" :loading="tokensRequestInProgress || balanceInProgress" + :merge-limit-exceeds="mergeLimitExceeds" class="mb-block-padding-1/2 sm:mb-block-gap" >