{@html message}
diff --git a/packages/bridge-ui-v2/src/i18n/en.json b/packages/bridge-ui-v2/src/i18n/en.json
index 56ab85e6681..af62f785c4b 100644
--- a/packages/bridge-ui-v2/src/i18n/en.json
+++ b/packages/bridge-ui-v2/src/i18n/en.json
@@ -18,7 +18,7 @@
"processing_fee": {
"title": "Processing fee",
- "link": "Customise fee",
+ "link": "Customize fee",
"recommended": {
"label": "Recommended",
"calculating": "Calculating",
@@ -32,6 +32,10 @@
"custom": {
"label": "Custom",
"text": "Customize your processing fee"
+ },
+ "button": {
+ "confirm": "Confirm",
+ "cancel": "Cancel"
}
},
@@ -88,7 +92,10 @@
"max": "Max",
"failed_max": "Coult not estimate max amount to bridge."
},
- "error.insufficient_balance": "Insufficient balance"
+ "error": {
+ "insufficient_balance": "Insufficient balance",
+ "insufficient_allowance": "Insufficient allowance"
+ }
},
"chain_selector": {
@@ -105,6 +112,9 @@
"network": {
"switching": "Switching network",
"rejected": "Request to switch chain rejected."
+ },
+ "mint": {
+ "rejected": "Request to mint rejected."
}
},
diff --git a/packages/bridge-ui-v2/src/libs/bridge/ERC20Bridge.ts b/packages/bridge-ui-v2/src/libs/bridge/ERC20Bridge.ts
index 074bc062a27..df3a68c595c 100644
--- a/packages/bridge-ui-v2/src/libs/bridge/ERC20Bridge.ts
+++ b/packages/bridge-ui-v2/src/libs/bridge/ERC20Bridge.ts
@@ -2,7 +2,7 @@ import { getContract } from '@wagmi/core';
import { tokenVaultABI } from '$abi';
import { bridge } from '$config';
-import { getConnectedWallet } from '$libs/util/getWallet';
+import { getConnectedWallet } from '$libs/util/getConnectedWallet';
import { getLogger } from '$libs/util/logger';
import type { Bridge, ERC20BridgeArgs, SendERC20Args } from './types';
diff --git a/packages/bridge-ui-v2/src/libs/bridge/ETHBridge.ts b/packages/bridge-ui-v2/src/libs/bridge/ETHBridge.ts
index 45c54f813a9..928857a1a6b 100644
--- a/packages/bridge-ui-v2/src/libs/bridge/ETHBridge.ts
+++ b/packages/bridge-ui-v2/src/libs/bridge/ETHBridge.ts
@@ -2,7 +2,7 @@ import { getContract } from '@wagmi/core';
import { bridgeABI } from '$abi';
import { bridge } from '$config';
-import { getConnectedWallet } from '$libs/util/getWallet';
+import { getConnectedWallet } from '$libs/util/getConnectedWallet';
import { getLogger } from '$libs/util/logger';
import type { Bridge, ETHBridgeArgs, Message } from './types';
@@ -29,7 +29,7 @@ export class ETHBridge implements Bridge {
to.toLowerCase() === owner.toLowerCase() ? [amount, BigInt(0)] : [BigInt(0), amount];
// If there is a processing fee, use the specified message gas limit
- // if not called by the owner
+ // as might not be called by the owner
const gasLimit = processingFee > 0 ? bridge.noOwnerGasLimit : BigInt(0);
const message: Message = {
diff --git a/packages/bridge-ui-v2/src/libs/bridge/checkBalanceToBridge.ts b/packages/bridge-ui-v2/src/libs/bridge/checkBalanceToBridge.ts
new file mode 100644
index 00000000000..44be8a43c2b
--- /dev/null
+++ b/packages/bridge-ui-v2/src/libs/bridge/checkBalanceToBridge.ts
@@ -0,0 +1,87 @@
+import { type Address, zeroAddress } from 'viem';
+
+import { chainContractsMap } from '$libs/chain';
+import { InsufficientAllowanceError, InsufficientBalanceError } from '$libs/error';
+import { getAddress, isETH, type Token } from '$libs/token';
+import { isDeployedCrossChain } from '$libs/token/isDeployedCrossChain';
+
+import { bridges } from './bridges';
+import { estimateCostOfBridging } from './estimateCostOfBridging';
+import type { BridgeArgs, ERC20BridgeArgs, ETHBridgeArgs } from './types';
+
+type HasEnoughBalanceToBridgeArgs = {
+ to: Address;
+ token: Token;
+ amount: bigint;
+ balance: bigint;
+ srcChainId: number;
+ destChainId: number;
+ processingFee?: bigint;
+};
+
+export async function checkBalanceToBridge({
+ to,
+ token,
+ amount,
+ balance,
+ srcChainId,
+ destChainId,
+ processingFee,
+}: HasEnoughBalanceToBridgeArgs) {
+ let estimatedCost = BigInt(0);
+
+ const bridgeArgs = {
+ to,
+ amount,
+ srcChainId,
+ destChainId,
+ processingFee,
+ } as BridgeArgs;
+
+ if (isETH(token)) {
+ const { bridgeAddress } = chainContractsMap[srcChainId];
+
+ try {
+ estimatedCost = await estimateCostOfBridging(bridges.ETH, {
+ ...bridgeArgs,
+ bridgeAddress,
+ } as ETHBridgeArgs);
+ } catch (err) {
+ console.error(err);
+
+ if (`${err}`.includes('transaction exceeds the balance of the account')) {
+ throw new InsufficientBalanceError('you do not have enough balance to bridge ETH', { cause: err });
+ }
+ }
+ } else {
+ const { tokenVaultAddress } = chainContractsMap[srcChainId];
+ const tokenAddress = await getAddress({ token, srcChainId, destChainId });
+
+ if (!tokenAddress || tokenAddress === zeroAddress) return false;
+
+ const isTokenAlreadyDeployed = await isDeployedCrossChain({
+ token,
+ srcChainId: destChainId,
+ destChainId: srcChainId,
+ });
+
+ try {
+ estimatedCost = await estimateCostOfBridging(bridges.ERC20, {
+ ...bridgeArgs,
+ tokenAddress,
+ tokenVaultAddress,
+ isTokenAlreadyDeployed,
+ } as ERC20BridgeArgs);
+ } catch (err) {
+ console.error(err);
+
+ if (`${err}`.includes('insufficient allowance')) {
+ throw new InsufficientAllowanceError(`insufficient allowance for the amount ${amount}`, { cause: err });
+ }
+ }
+ }
+
+ if (estimatedCost > balance - amount) {
+ throw new InsufficientBalanceError('you do not have enough balance to bridge');
+ }
+}
diff --git a/packages/bridge-ui-v2/src/libs/bridge/getMaxToBridge.ts b/packages/bridge-ui-v2/src/libs/bridge/getMaxAmountToBridge.ts
similarity index 52%
rename from packages/bridge-ui-v2/src/libs/bridge/getMaxToBridge.ts
rename to packages/bridge-ui-v2/src/libs/bridge/getMaxAmountToBridge.ts
index 041567139a4..7b7e98d7806 100644
--- a/packages/bridge-ui-v2/src/libs/bridge/getMaxToBridge.ts
+++ b/packages/bridge-ui-v2/src/libs/bridge/getMaxAmountToBridge.ts
@@ -1,6 +1,6 @@
import type { Address } from 'viem';
-import { chainContractsMap, chains } from '$libs/chain';
+import { chainContractsMap } from '$libs/chain';
import { isETH, type Token } from '$libs/token';
import { getLogger } from '$libs/util/logger';
@@ -9,52 +9,53 @@ import { estimateCostOfBridging } from './estimateCostOfBridging';
import type { ETHBridgeArgs } from './types';
type GetMaxToBridgeArgs = {
+ to: Address;
token: Token;
balance: bigint;
- srcChainId: number;
- userAddress: Address;
- processingFee: bigint;
- destChainId?: number;
amount?: bigint;
+ srcChainId?: number;
+ destChainId?: number;
+ processingFee?: bigint;
};
-const log = getLogger('getMaxToBridge');
+const log = getLogger('bridge:getMaxAmountToBridge');
-export async function getMaxToBridge({
+export async function getMaxAmountToBridge({
+ to,
token,
+ amount,
balance,
srcChainId,
- userAddress,
- processingFee,
destChainId,
- amount,
+ processingFee,
}: GetMaxToBridgeArgs) {
+ // For ERC20 tokens, we can bridge the whole balance
+ let maxAmount = balance;
+
if (isETH(token)) {
- const to = userAddress;
- const { bridgeAddress } = chainContractsMap[srcChainId.toString()];
+ // We cannot really compute the cost of bridging ETH without
+ if (!to || !srcChainId || !destChainId) {
+ throw Error('missing required arguments to compute cost');
+ }
+
+ const { bridgeAddress } = chainContractsMap[srcChainId];
const bridgeArgs = {
to,
+ amount,
srcChainId,
+ destChainId,
bridgeAddress,
processingFee,
-
- // If no amount passed in, use whatever just to get an estimation
- amount: amount ?? BigInt(1),
-
- // If no destination chain is selected, find another chain to estimate
- // TODO: we might want to really find a compatible chain to bridge to
- // if we have multiple layers
- destChainId: destChainId ?? chains.find((chain) => chain.id !== srcChainId)?.id,
} as ETHBridgeArgs;
const estimatedCost = await estimateCostOfBridging(bridges.ETH, bridgeArgs);
log('Estimated cost of bridging', estimatedCost, 'with argument', bridgeArgs);
- return balance - processingFee - estimatedCost;
+ // We also need to take into account the processing fee if any
+ maxAmount = balance - estimatedCost - (processingFee ?? BigInt(0));
}
- // For ERC20 tokens, we can bridge the whole balance
- return balance;
+ return maxAmount;
}
diff --git a/packages/bridge-ui-v2/src/libs/bridge/index.ts b/packages/bridge-ui-v2/src/libs/bridge/index.ts
index 5187917dc4b..284f0b0ba2f 100644
--- a/packages/bridge-ui-v2/src/libs/bridge/index.ts
+++ b/packages/bridge-ui-v2/src/libs/bridge/index.ts
@@ -1,3 +1,5 @@
export { bridges } from './bridges';
+export { checkBalanceToBridge } from './checkBalanceToBridge';
export { estimateCostOfBridging } from './estimateCostOfBridging';
+export { getMaxAmountToBridge } from './getMaxAmountToBridge';
export * from './types';
diff --git a/packages/bridge-ui-v2/src/libs/error/errors.ts b/packages/bridge-ui-v2/src/libs/error/errors.ts
new file mode 100644
index 00000000000..b81a7432d57
--- /dev/null
+++ b/packages/bridge-ui-v2/src/libs/error/errors.ts
@@ -0,0 +1,3 @@
+export class TokenMintedError extends Error {}
+export class InsufficientBalanceError extends Error {}
+export class InsufficientAllowanceError extends Error {}
diff --git a/packages/bridge-ui-v2/src/libs/error/index.ts b/packages/bridge-ui-v2/src/libs/error/index.ts
new file mode 100644
index 00000000000..f72bc43e28c
--- /dev/null
+++ b/packages/bridge-ui-v2/src/libs/error/index.ts
@@ -0,0 +1 @@
+export * from './errors';
diff --git a/packages/bridge-ui-v2/src/libs/fee/recommendProcessingFee.ts b/packages/bridge-ui-v2/src/libs/fee/recommendProcessingFee.ts
index 8e0b0885858..af8793c8826 100644
--- a/packages/bridge-ui-v2/src/libs/fee/recommendProcessingFee.ts
+++ b/packages/bridge-ui-v2/src/libs/fee/recommendProcessingFee.ts
@@ -6,15 +6,13 @@ import { getAddress, isERC20, type Token } from '$libs/token';
type RecommendProcessingFeeArgs = {
token: Token;
- destChainId?: number;
+ destChainId: number;
srcChainId?: number;
};
const { ethGasLimit, erc20NotDeployedGasLimit, erc20DeployedGasLimit } = recommentProcessingFee;
export async function recommendProcessingFee({ token, destChainId, srcChainId }: RecommendProcessingFeeArgs) {
- if (!destChainId) return BigInt(0);
-
const destPublicClient = getPublicClient({ chainId: destChainId });
const gasPrice = await destPublicClient.getGasPrice();
@@ -23,9 +21,11 @@ export async function recommendProcessingFee({ token, destChainId, srcChainId }:
let gasLimit = ethGasLimit;
if (isERC20(token)) {
- if (!srcChainId) return BigInt(0);
+ if (!srcChainId) {
+ throw Error('missing required source chain for ERC20 token');
+ }
- const tokenAddress = await getAddress({ token, chainId: srcChainId, destChainId });
+ const tokenAddress = await getAddress({ token, srcChainId, destChainId });
if (!tokenAddress || tokenAddress === zeroAddress) {
// Gas limit for erc20 if not deployed on the destination chain
diff --git a/packages/bridge-ui-v2/src/libs/token/checkMintable.test.ts b/packages/bridge-ui-v2/src/libs/token/checkMintable.test.ts
index 503542c036e..6802f6332e2 100644
--- a/packages/bridge-ui-v2/src/libs/token/checkMintable.test.ts
+++ b/packages/bridge-ui-v2/src/libs/token/checkMintable.test.ts
@@ -9,10 +9,10 @@ import {
import { freeMintErc20ABI } from '$abi';
import { mainnetChain } from '$libs/chain';
+import { InsufficientBalanceError, TokenMintedError } from '$libs/error';
import { checkMintable } from './checkMintable';
import { testERC20Tokens } from './tokens';
-import { MintableError } from './types';
vi.mock('$env/static/public');
vi.mock('@wagmi/core');
@@ -56,8 +56,7 @@ describe('checkMintable', () => {
await checkMintable(BLLToken, mainnetChain.id);
expect.fail('should have thrown');
} catch (error) {
- const { cause } = error as Error;
- expect(cause).toBe(MintableError.TOKEN_MINTED);
+ expect(error).toBeInstanceOf(TokenMintedError);
expect(getContract).toHaveBeenCalledWith({
walletClient: mockWalletClient,
abi: freeMintErc20ABI,
@@ -85,8 +84,7 @@ describe('checkMintable', () => {
await checkMintable(BLLToken, mainnetChain.id);
expect.fail('should have thrown');
} catch (error) {
- const { cause } = error as Error;
- expect(cause).toBe(MintableError.INSUFFICIENT_BALANCE);
+ expect(error).toBeInstanceOf(InsufficientBalanceError);
expect(getPublicClient).toHaveBeenCalled();
expect(mockTokenContract.estimateGas.mint).toHaveBeenCalledWith([mockWalletClient.account.address]);
expect(mockPublicClient.getBalance).toHaveBeenCalledWith({ address: mockWalletClient.account.address });
diff --git a/packages/bridge-ui-v2/src/libs/token/checkMintable.ts b/packages/bridge-ui-v2/src/libs/token/checkMintable.ts
index 2c91d47a4eb..1906dd357a8 100644
--- a/packages/bridge-ui-v2/src/libs/token/checkMintable.ts
+++ b/packages/bridge-ui-v2/src/libs/token/checkMintable.ts
@@ -1,9 +1,10 @@
import { getContract, getPublicClient } from '@wagmi/core';
import { freeMintErc20ABI } from '$abi';
-import { getConnectedWallet } from '$libs/util/getWallet';
+import { InsufficientBalanceError, TokenMintedError } from '$libs/error';
+import { getConnectedWallet } from '$libs/util/getConnectedWallet';
-import { MintableError, type Token } from './types';
+import type { Token } from './types';
// Throws an error if:
// 1. User has already minted this token
@@ -22,7 +23,7 @@ export async function checkMintable(token: Token, chainId: number) {
const hasMinted = await tokenContract.read.minters([userAddress]);
if (hasMinted) {
- throw Error(`token already minted`, { cause: MintableError.TOKEN_MINTED });
+ throw new TokenMintedError();
}
// Check whether the user has enough balance to mint.
@@ -36,8 +37,6 @@ export async function checkMintable(token: Token, chainId: number) {
const userBalance = await publicClient.getBalance({ address: userAddress });
if (estimatedCost > userBalance) {
- throw Error('user has insufficient balance', {
- cause: MintableError.INSUFFICIENT_BALANCE,
- });
+ throw new InsufficientBalanceError();
}
}
diff --git a/packages/bridge-ui-v2/src/libs/token/getAddress.test.ts b/packages/bridge-ui-v2/src/libs/token/getAddress.test.ts
index 4f7d8c55eea..6818da3c2f6 100644
--- a/packages/bridge-ui-v2/src/libs/token/getAddress.test.ts
+++ b/packages/bridge-ui-v2/src/libs/token/getAddress.test.ts
@@ -1,5 +1,4 @@
import { getContract, type GetContractResult } from '@wagmi/core';
-import { zeroAddress } from 'viem';
import { tokenVaultABI } from '$abi';
import { PUBLIC_L1_CHAIN_ID, PUBLIC_L2_CHAIN_ID } from '$env/static/public';
@@ -25,29 +24,29 @@ describe('getAddress', () => {
vi.mocked(getContract).mockReturnValue(mockTokenContract);
});
- it('should return undefined if no source chain id is passed in', async () => {
- expect(await getAddress({ token: ETHToken })).toBeUndefined();
- });
-
- it('should return the address if ETH', async () => {
- expect(await getAddress({ token: ETHToken, chainId: Number(PUBLIC_L1_CHAIN_ID) })).toEqual(zeroAddress);
+ it('should return undefined if ETH', async () => {
+ expect(await getAddress({ token: ETHToken, srcChainId: Number(PUBLIC_L1_CHAIN_ID) })).toBeUndefined();
});
it('should return the address if ERC20 and has address on the source chain', async () => {
- expect(await getAddress({ token: HORSEToken, chainId: Number(PUBLIC_L1_CHAIN_ID) })).toEqual(
+ expect(await getAddress({ token: HORSEToken, srcChainId: Number(PUBLIC_L1_CHAIN_ID) })).toEqual(
HORSEToken.addresses[PUBLIC_L1_CHAIN_ID],
);
});
it('should return undefined if ERC20 and has no address on the source chain and no destination chain is is passed in', async () => {
- expect(await getAddress({ token: HORSEToken, chainId: Number(PUBLIC_L2_CHAIN_ID) })).toBeUndefined();
+ expect(await getAddress({ token: HORSEToken, srcChainId: Number(PUBLIC_L2_CHAIN_ID) })).toBeUndefined();
});
it('should return the address of deployed ERC20 token', async () => {
vi.mocked(mockTokenContract.read.canonicalToBridged).mockResolvedValue('0x456789');
expect(
- await getAddress({ token: HORSEToken, chainId: Number(PUBLIC_L2_CHAIN_ID), destChainId: +PUBLIC_L1_CHAIN_ID }),
+ await getAddress({
+ token: HORSEToken,
+ srcChainId: Number(PUBLIC_L2_CHAIN_ID),
+ destChainId: Number(PUBLIC_L1_CHAIN_ID),
+ }),
).toEqual('0x456789');
expect(mockTokenContract.read.canonicalToBridged).toHaveBeenCalledWith([
BigInt(1),
@@ -56,6 +55,7 @@ describe('getAddress', () => {
expect(getContract).toHaveBeenCalledWith({
abi: tokenVaultABI,
address: chainContractsMap[PUBLIC_L2_CHAIN_ID].tokenVaultAddress,
+ chainId: Number(PUBLIC_L2_CHAIN_ID),
});
});
});
diff --git a/packages/bridge-ui-v2/src/libs/token/getAddress.ts b/packages/bridge-ui-v2/src/libs/token/getAddress.ts
index 2417d7184b6..96765e45dc3 100644
--- a/packages/bridge-ui-v2/src/libs/token/getAddress.ts
+++ b/packages/bridge-ui-v2/src/libs/token/getAddress.ts
@@ -1,47 +1,39 @@
-import { getContract } from '@wagmi/core';
-import { zeroAddress } from 'viem';
+import { type Address, zeroAddress } from 'viem';
-import { tokenVaultABI } from '$abi';
-import { chainContractsMap } from '$libs/chain';
import { getLogger } from '$libs/util/logger';
+import { getCrossChainAddress } from './getCrossChainAddress';
import { isETH } from './tokens';
import type { Token } from './types';
type GetAddressArgs = {
token: Token;
- chainId?: number;
+ srcChainId: number;
destChainId?: number;
};
const log = getLogger('token:getAddress');
-export async function getAddress({ token, chainId, destChainId }: GetAddressArgs) {
- if (!chainId) return;
+export async function getAddress({ token, srcChainId, destChainId }: GetAddressArgs) {
+ if (isETH(token)) return; // ETH doesn't have an address
// Get the address for the token on the source chain
- let address = token.addresses[chainId];
+ let address: Maybe
= token.addresses[srcChainId];
- // If the token isn't ETH and has no address...
- if (!isETH(token) && (!address || address === zeroAddress)) {
+ if (!address || address === zeroAddress) {
+ // We need destination chain to find the address, otherwise
+ // there is nothing we can do here.
if (!destChainId) return;
// Find the address on the destination chain instead. We are
- // most likely on Taiko chain and the token hasn't yet been
- // deployed on it.
- const destChainTokenAddress = token.addresses[destChainId];
-
- // Get the TokenVault contract on the source chain. The idea is to find
- // the bridged address for the token on the destination chain if it's been
- // deployed there. This is registered in the TokenVault contract,
- // cacnonicalToBridged mapping.
- const srcTokenVaultContract = getContract({
- abi: tokenVaultABI,
- address: chainContractsMap[chainId].tokenVaultAddress,
+ // most likely on Taiko chain. We need to then query the
+ // canonicalToBridged mapping on the other chain
+ address = await getCrossChainAddress({
+ token,
+ srcChainId: destChainId,
+ destChainId: srcChainId,
});
- address = await srcTokenVaultContract.read.canonicalToBridged([BigInt(destChainId), destChainTokenAddress]);
-
log(`Bridged address for ${token.symbol} is "${address}"`);
}
diff --git a/packages/bridge-ui-v2/src/libs/token/getBalance.test.ts b/packages/bridge-ui-v2/src/libs/token/getBalance.test.ts
index 7b9d00d2e70..12db6a12687 100644
--- a/packages/bridge-ui-v2/src/libs/token/getBalance.test.ts
+++ b/packages/bridge-ui-v2/src/libs/token/getBalance.test.ts
@@ -43,7 +43,11 @@ describe('getBalance', () => {
it('should return the balance of ETH', async () => {
vi.mocked(fetchBalance).mockResolvedValueOnce(mockBalanceForETH);
- const balance = await getBalance({ token: ETHToken, userAddress: mockWalletClient.account.address });
+ const balance = await getBalance({
+ token: ETHToken,
+ userAddress: mockWalletClient.account.address,
+ srcChainId: Number(PUBLIC_L1_CHAIN_ID),
+ });
expect(balance).toEqual(mockBalanceForETH);
expect(getAddress).not.toHaveBeenCalled();
@@ -57,13 +61,13 @@ describe('getBalance', () => {
const balance = await getBalance({
token: BLLToken,
userAddress: mockWalletClient.account.address,
- chainId: Number(PUBLIC_L1_CHAIN_ID),
+ srcChainId: Number(PUBLIC_L1_CHAIN_ID),
});
expect(balance).toEqual(mockBalanceForBLL);
expect(getAddress).toHaveBeenCalledWith({
token: BLLToken,
- chainId: Number(PUBLIC_L1_CHAIN_ID),
+ srcChainId: Number(PUBLIC_L1_CHAIN_ID),
destChainId: undefined,
});
expect(fetchBalance).toHaveBeenCalledWith({
@@ -72,21 +76,30 @@ describe('getBalance', () => {
});
});
- it('should return null if the token address is not found', async () => {
+ it('should return undefined if the token address is not found', async () => {
vi.mocked(getAddress).mockResolvedValueOnce(zeroAddress);
const balance = await getBalance({
token: BLLToken,
userAddress: mockWalletClient.account.address,
- chainId: Number(PUBLIC_L1_CHAIN_ID),
+ srcChainId: Number(PUBLIC_L1_CHAIN_ID),
});
- expect(balance).toBeNull();
+ expect(balance).toBeUndefined();
expect(getAddress).toHaveBeenCalledWith({
token: BLLToken,
- chainId: Number(PUBLIC_L1_CHAIN_ID),
+ srcChainId: Number(PUBLIC_L1_CHAIN_ID),
destChainId: undefined,
});
expect(fetchBalance).not.toHaveBeenCalled();
});
+
+ it('should return undefined if ERC20 and no source chain is passed in', async () => {
+ const balance = await getBalance({
+ token: BLLToken,
+ userAddress: mockWalletClient.account.address,
+ });
+
+ expect(balance).toBeUndefined();
+ });
});
diff --git a/packages/bridge-ui-v2/src/libs/token/getBalance.ts b/packages/bridge-ui-v2/src/libs/token/getBalance.ts
index f4715362bd5..33aad51707f 100644
--- a/packages/bridge-ui-v2/src/libs/token/getBalance.ts
+++ b/packages/bridge-ui-v2/src/libs/token/getBalance.ts
@@ -8,25 +8,29 @@ import { isETH } from './tokens';
import type { Token } from './types';
type GetBalanceArgs = {
- token: Token;
userAddress: Address;
- chainId?: number;
+ token?: Token;
+ srcChainId?: number;
destChainId?: number;
};
const log = getLogger('token:getBalance');
-export async function getBalance({ token, userAddress, chainId, destChainId }: GetBalanceArgs) {
- let tokenBalance: FetchBalanceResult | null = null;
+export async function getBalance({ userAddress, token, srcChainId, destChainId }: GetBalanceArgs) {
+ let tokenBalance: FetchBalanceResult;
- if (isETH(token)) {
+ if (!token || isETH(token)) {
+ // If no token is passed in, we assume is ETH
tokenBalance = await fetchBalance({ address: userAddress });
} else {
+ // We need at least the source chain to find the address
+ if (!srcChainId) return;
+
// We are dealing with an ERC20 token. We need to first find out its address
// on the current chain in order to fetch the balance.
- const tokenAddress = await getAddress({ token, chainId, destChainId });
+ const tokenAddress = await getAddress({ token, srcChainId, destChainId });
- if (!tokenAddress || tokenAddress === zeroAddress) return null;
+ if (!tokenAddress || tokenAddress === zeroAddress) return;
// Wagmi is such an amazing library. We had to do this
// more manually before.
diff --git a/packages/bridge-ui-v2/src/libs/token/getCrossChainAddress.test.ts b/packages/bridge-ui-v2/src/libs/token/getCrossChainAddress.test.ts
new file mode 100644
index 00000000000..254c7597802
--- /dev/null
+++ b/packages/bridge-ui-v2/src/libs/token/getCrossChainAddress.test.ts
@@ -0,0 +1,5 @@
+describe('getCrossChainAddress', () => {
+ it('TODO', () => {
+ expect(true).toBeTruthy();
+ });
+});
diff --git a/packages/bridge-ui-v2/src/libs/token/getCrossChainAddress.ts b/packages/bridge-ui-v2/src/libs/token/getCrossChainAddress.ts
new file mode 100644
index 00000000000..7b30e2c2a23
--- /dev/null
+++ b/packages/bridge-ui-v2/src/libs/token/getCrossChainAddress.ts
@@ -0,0 +1,33 @@
+import { getContract } from '@wagmi/core';
+
+import { tokenVaultABI } from '$abi';
+import { chainContractsMap } from '$libs/chain';
+
+import { isETH } from './tokens';
+import type { Token } from './types';
+
+type GetCrossChainAddressArgs = {
+ token: Token;
+ srcChainId: number;
+ destChainId: number;
+};
+
+export function getCrossChainAddress({ token, srcChainId, destChainId }: GetCrossChainAddressArgs) {
+ if (isETH(token)) return; // ETH doesn't have an address
+
+ const { tokenVaultAddress } = chainContractsMap[destChainId];
+
+ const srcChainTokenAddress = token.addresses[srcChainId];
+
+ // We cannot find the address if we don't have
+ // the token address on the source chain
+ if (!srcChainTokenAddress) return;
+
+ const destTokenVaultContract = getContract({
+ abi: tokenVaultABI,
+ chainId: destChainId,
+ address: tokenVaultAddress,
+ });
+
+ return destTokenVaultContract.read.canonicalToBridged([BigInt(srcChainId), srcChainTokenAddress]);
+}
diff --git a/packages/bridge-ui-v2/src/libs/token/index.ts b/packages/bridge-ui-v2/src/libs/token/index.ts
index 92e250f0776..e13186d034f 100644
--- a/packages/bridge-ui-v2/src/libs/token/index.ts
+++ b/packages/bridge-ui-v2/src/libs/token/index.ts
@@ -1,6 +1,7 @@
export { checkMintable } from './checkMintable';
export { getAddress } from './getAddress';
export { getBalance } from './getBalance';
+export { isDeployedCrossChain } from './isDeployedCrossChain';
export { mint } from './mint';
export * from './tokens';
export * from './types';
diff --git a/packages/bridge-ui-v2/src/libs/token/isDeployedCrossChain.ts b/packages/bridge-ui-v2/src/libs/token/isDeployedCrossChain.ts
new file mode 100644
index 00000000000..8edf6a6d963
--- /dev/null
+++ b/packages/bridge-ui-v2/src/libs/token/isDeployedCrossChain.ts
@@ -0,0 +1,24 @@
+import { zeroAddress } from 'viem';
+
+import type { Token } from '$libs/token';
+
+import { getCrossChainAddress } from './getCrossChainAddress';
+
+type IsDeployedCrossChainArgs = {
+ token: Token;
+ srcChainId: number;
+ destChainId: number;
+};
+
+export async function isDeployedCrossChain({ token, srcChainId, destChainId }: IsDeployedCrossChainArgs) {
+ const destTokenAddressOnDestChain = token.addresses[destChainId];
+
+ if (!destTokenAddressOnDestChain || destTokenAddressOnDestChain === zeroAddress) {
+ // Check if token is already deployed as BridgedERC20 on destination chain
+ const bridgedTokenAddress = await getCrossChainAddress({ token, srcChainId, destChainId });
+
+ return bridgedTokenAddress && bridgedTokenAddress !== zeroAddress;
+ }
+
+ return true;
+}
diff --git a/packages/bridge-ui-v2/src/libs/token/mint.test.ts b/packages/bridge-ui-v2/src/libs/token/mint.test.ts
index 2be21d8334d..e1bf2db5ca4 100644
--- a/packages/bridge-ui-v2/src/libs/token/mint.test.ts
+++ b/packages/bridge-ui-v2/src/libs/token/mint.test.ts
@@ -13,7 +13,6 @@ const BLLToken = testERC20Tokens[0];
const mockWalletClient = {
account: { address: '0x123' },
- chain: { id: PUBLIC_L1_CHAIN_ID },
} as unknown as WalletClient;
const mockTokenContract = {
@@ -28,7 +27,12 @@ describe('mint', () => {
vi.mocked(getContract).mockReturnValue(mockTokenContract);
vi.mocked(mockTokenContract.write.mint).mockResolvedValue('0x123456');
- await expect(mint(BLLToken)).resolves.toEqual('0x123456');
+ await expect(mint(BLLToken, Number(PUBLIC_L1_CHAIN_ID))).resolves.toEqual('0x123456');
expect(mockTokenContract.write.mint).toHaveBeenCalledWith([mockWalletClient.account.address]);
+ expect(getContract).toHaveBeenCalledWith({
+ walletClient: mockWalletClient,
+ abi: expect.anything(),
+ address: BLLToken.addresses[PUBLIC_L1_CHAIN_ID],
+ });
});
});
diff --git a/packages/bridge-ui-v2/src/libs/token/mint.ts b/packages/bridge-ui-v2/src/libs/token/mint.ts
index c9585583711..c09c949a6ee 100644
--- a/packages/bridge-ui-v2/src/libs/token/mint.ts
+++ b/packages/bridge-ui-v2/src/libs/token/mint.ts
@@ -1,19 +1,18 @@
import { getContract } from '@wagmi/core';
import { freeMintErc20ABI } from '$abi';
-import { getConnectedWallet } from '$libs/util/getWallet';
+import { getConnectedWallet } from '$libs/util/getConnectedWallet';
import { getLogger } from '../util/logger';
import type { Token } from './types';
const log = getLogger('token:mint');
-export async function mint(token: Token) {
- const walletClient = await getConnectedWallet();
+export async function mint(token: Token, chainId: number) {
+ const walletClient = await getConnectedWallet(chainId);
- const tokenSymbol = token.symbol;
const userAddress = walletClient.account.address;
- const chainId = walletClient.chain.id;
+ const tokenSymbol = token.symbol;
const tokenContract = getContract({
walletClient,
diff --git a/packages/bridge-ui-v2/src/libs/token/types.ts b/packages/bridge-ui-v2/src/libs/token/types.ts
index 230e5814d1d..e68098c660b 100644
--- a/packages/bridge-ui-v2/src/libs/token/types.ts
+++ b/packages/bridge-ui-v2/src/libs/token/types.ts
@@ -12,8 +12,3 @@ export type TokenEnv = {
address: Address;
symbol: string;
};
-
-export enum MintableError {
- TOKEN_MINTED = 'TOKEN_MINTED',
- INSUFFICIENT_BALANCE = 'INSUFFICIENT_BALANCE',
-}
diff --git a/packages/bridge-ui-v2/src/libs/util/getConnectedWallet.test.ts b/packages/bridge-ui-v2/src/libs/util/getConnectedWallet.test.ts
new file mode 100644
index 00000000000..f42cda0f29b
--- /dev/null
+++ b/packages/bridge-ui-v2/src/libs/util/getConnectedWallet.test.ts
@@ -0,0 +1,5 @@
+describe('getConnectedWallet', () => {
+ it('TODO', () => {
+ expect(true).toBeTruthy();
+ });
+});
diff --git a/packages/bridge-ui-v2/src/libs/util/getWallet.ts b/packages/bridge-ui-v2/src/libs/util/getConnectedWallet.ts
similarity index 54%
rename from packages/bridge-ui-v2/src/libs/util/getWallet.ts
rename to packages/bridge-ui-v2/src/libs/util/getConnectedWallet.ts
index d609ef62344..136a6b7cc14 100644
--- a/packages/bridge-ui-v2/src/libs/util/getWallet.ts
+++ b/packages/bridge-ui-v2/src/libs/util/getConnectedWallet.ts
@@ -1,7 +1,7 @@
import { getWalletClient } from '@wagmi/core';
-export async function getConnectedWallet() {
- const walletClient = await getWalletClient();
+export async function getConnectedWallet(chainId?: number) {
+ const walletClient = await getWalletClient({ chainId });
if (!walletClient) {
throw Error('wallet is not connected');
diff --git a/packages/bridge-ui-v2/src/libs/util/truncateDecimal.test.ts b/packages/bridge-ui-v2/src/libs/util/truncateDecimal.test.ts
new file mode 100644
index 00000000000..4b5bcf09f07
--- /dev/null
+++ b/packages/bridge-ui-v2/src/libs/util/truncateDecimal.test.ts
@@ -0,0 +1,9 @@
+import { truncateDecimal } from './truncateDecimal';
+
+describe('truncateDecimal', () => {
+ it('should truncate decimals', () => {
+ expect(truncateDecimal(1.23456789, 2)).toEqual(1.23);
+ expect(truncateDecimal(12.3456789, 3)).toEqual(12.345);
+ expect(truncateDecimal(123.456789, 4)).toEqual(123.4567);
+ });
+});
diff --git a/packages/bridge-ui-v2/src/libs/util/truncateString.test.ts b/packages/bridge-ui-v2/src/libs/util/truncateString.test.ts
new file mode 100644
index 00000000000..2f8bf9db5c2
--- /dev/null
+++ b/packages/bridge-ui-v2/src/libs/util/truncateString.test.ts
@@ -0,0 +1,9 @@
+import { truncateString } from './truncateString';
+
+describe('truncateString', () => {
+ it('should truncate strings', () => {
+ expect(truncateString('123456789', 3)).toEqual('123…');
+ expect(truncateString('123456789', 5)).toEqual('12345…');
+ expect(truncateString('123456789', 6, '...')).toEqual('123456...');
+ });
+});