diff --git a/packages/checkout/sdk/src/gasEstimate/bridgeGasEstimate.ts b/packages/checkout/sdk/src/gasEstimate/bridgeGasEstimate.ts index c25fd819df..cfc3426eae 100644 --- a/packages/checkout/sdk/src/gasEstimate/bridgeGasEstimate.ts +++ b/packages/checkout/sdk/src/gasEstimate/bridgeGasEstimate.ts @@ -3,8 +3,10 @@ import { BridgeFeeResponse, TokenBridge, } from '@imtbl/bridge-sdk'; +import { BigNumber } from 'ethers'; import { CheckoutConfiguration, getL1ChainId } from '../config'; import { ChainId } from '../types'; +import { NATIVE } from '../env/constants'; export async function getBridgeFeeEstimate( tokenBridge: TokenBridge, @@ -21,5 +23,7 @@ export async function getBridgeFeeEstimate( gasMultiplier: 1.1, sourceChainId: fromChainId.toString(), destinationChainId: toChainId.toString(), + token: NATIVE.toUpperCase(), + amount: BigNumber.from(0), }); } diff --git a/packages/checkout/sdk/src/gasEstimate/gasEstimator.test.ts b/packages/checkout/sdk/src/gasEstimate/gasEstimator.test.ts index b4c26dd88e..1d657b4edf 100644 --- a/packages/checkout/sdk/src/gasEstimate/gasEstimator.test.ts +++ b/packages/checkout/sdk/src/gasEstimate/gasEstimator.test.ts @@ -297,6 +297,7 @@ describe('gasServiceEstimator', () => { gasEstimateType: GasEstimateType.BRIDGE_TO_L2, fees: { sourceChainGas: BigNumber.from(0), + approvalFee: BigNumber.from(0), bridgeFee: BigNumber.from(0), imtblFee: BigNumber.from(0), totalFees: BigNumber.from(0), diff --git a/packages/checkout/sdk/src/gasEstimate/gasEstimator.ts b/packages/checkout/sdk/src/gasEstimate/gasEstimator.ts index c501f147a9..589a569272 100644 --- a/packages/checkout/sdk/src/gasEstimate/gasEstimator.ts +++ b/packages/checkout/sdk/src/gasEstimate/gasEstimator.ts @@ -59,6 +59,7 @@ async function bridgeToL2GasEstimator( gasEstimateType: GasEstimateType.BRIDGE_TO_L2, fees: { sourceChainGas: BigNumber.from(0), + approvalFee: BigNumber.from(0), bridgeFee: BigNumber.from(0), imtblFee: BigNumber.from(0), totalFees: BigNumber.from(0), diff --git a/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx b/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx index e79a016a49..bef0022167 100644 --- a/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx +++ b/packages/checkout/widgets-lib/src/widgets/bridge/components/BridgeForm.tsx @@ -236,6 +236,8 @@ export function BridgeForm(props: BridgeFormProps) { gasMultiplier: 1.1, sourceChainId: from?.network.toString() ?? '', destinationChainId: to?.network.toString() ?? '', + token: NATIVE.toUpperCase(), + amount: BigNumber.from(0), }); const gasEstimateResult = { diff --git a/packages/internal/bridge/sdk/src/index.ts b/packages/internal/bridge/sdk/src/index.ts index 8584d3d3d2..d398cb7ec7 100644 --- a/packages/internal/bridge/sdk/src/index.ts +++ b/packages/internal/bridge/sdk/src/index.ts @@ -19,8 +19,10 @@ export type { FungibleToken, FeeData, BridgeFeeRequest, - DepositFeeRequest, - WithdrawFeeRequest, + DepositNativeFeeRequest, + DepositERC20FeeRequest, + WithdrawNativeFeeRequest, + WithdrawERC20FeeRequest, FinaliseFeeRequest, BridgeFeeResponse, CalculateBridgeFeeResponse, diff --git a/packages/internal/bridge/sdk/src/tokenBridge.test.ts b/packages/internal/bridge/sdk/src/tokenBridge.test.ts index 191f430de6..669bbead42 100644 --- a/packages/internal/bridge/sdk/src/tokenBridge.test.ts +++ b/packages/internal/bridge/sdk/src/tokenBridge.test.ts @@ -539,6 +539,7 @@ describe('Token Bridge', () => { const originalCalculateBridgeFee = TokenBridge.prototype['calculateBridgeFee']; const sourceChainGas:ethers.BigNumber = ethers.utils.parseUnits('0.000001', 18); + const approavalGas:ethers.BigNumber = ethers.utils.parseUnits('0.000001', 18); const destinationChainGas:ethers.BigNumber = ethers.utils.parseUnits('0.000001', 18); const validatorFee:ethers.BigNumber = ethers.utils.parseUnits('0.0001', 18); const bridgeFee:ethers.BigNumber = destinationChainGas.add(validatorFee); @@ -574,7 +575,7 @@ describe('Token Bridge', () => { TokenBridge.prototype['getGasEstimates'] = originalGetGasEstimates; TokenBridge.prototype['calculateBridgeFee'] = originalCalculateBridgeFee; }); - it('returns the deposit fees', async () => { + it('returns the deposit fees for native tokens', async () => { expect.assertions(5); const result = await tokenBridge.getFee( { @@ -591,6 +592,27 @@ describe('Token Bridge', () => { expect(result.imtblFee).toStrictEqual(imtblFee); expect(result.totalFees).toStrictEqual(totalFees); }); + + it('returns the deposit fees for ERC20 tokens', async () => { + expect.assertions(6); + const result = await tokenBridge.getFee( + { + action: BridgeFeeActions.DEPOSIT, + gasMultiplier: 1.1, + sourceChainId: ETH_SEPOLIA_TO_ZKEVM_DEVNET.rootChainID, + destinationChainId: ETH_SEPOLIA_TO_ZKEVM_DEVNET.childChainID, + token: '0x40b87d235A5B010a20A241F15797C9debf1ecd01', + amount: ethers.BigNumber.from(1000), + }, + ); + + expect(result).not.toBeNull(); + expect(result.sourceChainGas).toStrictEqual(sourceChainGas); + expect(result.approvalFee).toStrictEqual(approavalGas); + expect(result.bridgeFee).toStrictEqual(bridgeFee); + expect(result.imtblFee).toStrictEqual(imtblFee); + expect(result.totalFees).toStrictEqual(totalFees.add(approavalGas)); + }); }); describe('getTransactionStatus', () => { diff --git a/packages/internal/bridge/sdk/src/tokenBridge.ts b/packages/internal/bridge/sdk/src/tokenBridge.ts index b1962f576b..201d98c93d 100644 --- a/packages/internal/bridge/sdk/src/tokenBridge.ts +++ b/packages/internal/bridge/sdk/src/tokenBridge.ts @@ -139,9 +139,17 @@ export class TokenBridge { } let sourceChainGas: ethers.BigNumber = ethers.BigNumber.from(0); + let approvalFee: ethers.BigNumber = ethers.BigNumber.from(0); let bridgeFee: ethers.BigNumber = ethers.BigNumber.from(0); const imtblFee: ethers.BigNumber = ethers.BigNumber.from(0); + if ('token' in req && req.token !== 'NATIVE') { + approvalFee = await this.getGasEstimates( + this.config.rootProvider, + BridgeMethodsGasLimit.APPROVE_TOKEN, + ); + } + if (req.action === BridgeFeeActions.FINALISE_WITHDRAWAL) { sourceChainGas = await this.getGasEstimates( this.config.rootProvider, @@ -166,10 +174,11 @@ export class TokenBridge { bridgeFee = feeResult.bridgeFee; } - const totalFees: ethers.BigNumber = sourceChainGas.add(bridgeFee).add(imtblFee); + const totalFees: ethers.BigNumber = sourceChainGas.add(approvalFee).add(bridgeFee).add(imtblFee); return { sourceChainGas, + approvalFee, bridgeFee, imtblFee, // no network fee charged currently totalFees, @@ -498,6 +507,8 @@ export class TokenBridge { gasMultiplier, sourceChainId, destinationChainId, + token, + amount, }); const canReceive:boolean = await this.checkReceiver(provider, recipient); diff --git a/packages/internal/bridge/sdk/src/types/index.ts b/packages/internal/bridge/sdk/src/types/index.ts index a35824e2df..90986ddff7 100644 --- a/packages/internal/bridge/sdk/src/types/index.ts +++ b/packages/internal/bridge/sdk/src/types/index.ts @@ -109,6 +109,7 @@ export enum BridgeMethodsGasLimit { // @TODO test methods on chain and put corre MAP_TOKEN_SOURCE = 200000, MAP_TOKEN_DESTINATION = 200000, FINALISE_WITHDRAWAL = 200000, + APPROVE_TOKEN = 55000, } export interface FeeData { @@ -123,18 +124,20 @@ export interface FeeData { * @dev Union type of DepositFeeRequest|WithdrawFeeRequest|FinaliseFeeRequest|MapTokenFeeRequest * ensures the correct params are supplied when trying to calculate the fees */ -export type BridgeFeeRequest = DepositFeeRequest -| WithdrawFeeRequest +export type BridgeFeeRequest = DepositNativeFeeRequest +| DepositERC20FeeRequest +| WithdrawNativeFeeRequest +| WithdrawERC20FeeRequest | FinaliseFeeRequest; /** - * @typedef {Object} DepositFeeRequest + * @typedef {Object} DepositNativeFeeRequest * @property {BridgeFeeActions} method - The method for which the bridge fee is being requested. * @property {number} gasMultiplier - How much buffer to add to the gas fee. * @property {string} sourceChainId - The chain ID of the source chain. * @property {string} destinationChainId - The chain ID of the destination chain. */ -export interface DepositFeeRequest { +export interface DepositNativeFeeRequest { action: BridgeFeeActions.DEPOSIT, gasMultiplier: number; sourceChainId: string; @@ -142,19 +145,55 @@ export interface DepositFeeRequest { } /** - * @typedef {Object} WithdrawFeeRequest + * @typedef {Object} DepositERC20FeeRequest + * @property {BridgeFeeActions} method - The method for which the bridge fee is being requested. + * @property {number} gasMultiplier - How much buffer to add to the gas fee. + * @property {string} sourceChainId - The chain ID of the source chain. + * @property {string} destinationChainId - The chain ID of the destination chain. + * @property {FungibleToken} token - The token to be deposited. + * @property {ethers.BigNumber} amount - The amount to be deposited. + */ +export interface DepositERC20FeeRequest { + action: BridgeFeeActions.DEPOSIT, + gasMultiplier: number; + sourceChainId: string; + destinationChainId: string; + token: FungibleToken; + amount: ethers.BigNumber; +} + +/** + * @typedef {Object} WithdrawNativeFeeRequest * @property {BridgeFeeActions} method - The method for which the bridge fee is being requested. * @property {number} gasMultiplier - How much buffer to add to the gas fee. * @property {string} sourceChainId - The chain ID of the source chain. * @property {string} destinationChainId - The chain ID of the destination chain. */ -export interface WithdrawFeeRequest { +export interface WithdrawNativeFeeRequest { action: BridgeFeeActions.WITHDRAW, gasMultiplier: number; sourceChainId: string; destinationChainId: string; } +/** + * @typedef {Object} WithdrawERC20FeeRequest + * @property {BridgeFeeActions} method - The method for which the bridge fee is being requested. + * @property {number} gasMultiplier - How much buffer to add to the gas fee. + * @property {string} sourceChainId - The chain ID of the source chain. + * @property {string} destinationChainId - The chain ID of the destination chain. + * @property {FungibleToken} token - The token to be withdrawn. + * @property {ethers.BigNumber} amount - The amount to be withdrawn. + */ +export interface WithdrawERC20FeeRequest { + action: BridgeFeeActions.WITHDRAW, + gasMultiplier: number; + sourceChainId: string; + destinationChainId: string; + token: FungibleToken; + amount: ethers.BigNumber; +} + /** * @typedef {Object} FinaliseFeeRequest * @property {BridgeFeeActions} method - The method for which the bridge fee is being requested. @@ -169,17 +208,20 @@ export interface FinaliseFeeRequest { * @typedef {Object} BridgeFeeResponse * @property {ethers.BigNumber} sourceChainGas - Gas cost to send tokens to the bridge contract on the source chain. * - priced in the source chain's native token. + * @property {ethers.BigNumber} approvalFee - Gas cost to approve bridge contract to spend tokens on the source chain. + * - priced in the source chain's native token. * @property {ethers.BigNumber} bridgeFee - destinationChainGas + validatorFee. * This will be added to the tx.value of the bridge transaction and forwarded to the Axelar Gas Service contract. * - priced in the source chain's native token. * @property {ethers.BigNumber} imtblFee - The fee charged by Immutable to facilitate the bridge. * - priced in the source chain's native token. * @property {ethers.BigNumber} totalFees - The total fees the user will be charged which is; - * sourceChainGas + bridgeFee + imtblFee. + * sourceChainGas + approvalFee + bridgeFee + imtblFee. * - priced in the source chain's native token. */ export interface BridgeFeeResponse { sourceChainGas: ethers.BigNumber, + approvalFee: ethers.BigNumber, bridgeFee: ethers.BigNumber, imtblFee: ethers.BigNumber, totalFees: ethers.BigNumber,