@avalabs/bridge-unified 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ import { isAddress } from 'viem';
2
+
3
+ import { InvalidParamsError } from '../../../errors';
4
+ import { getClientForChain } from '../../../utils/client';
5
+ import { ErrorReason, type BridgeService, type TransferParams } from '../../../types';
6
+
7
+ import { ERC20_ABI } from '../abis/erc20';
8
+ import { getTransferData } from '../utils/transfer-data';
9
+ import { ChainDomain } from '../types/chain';
10
+
11
+ const ETH_APPROVAL_TX_GAS_ESTIMATE = 60_000n; // 55.5k gas on average (+/- 200 units)
12
+ const ETH_TRANSFER_TX_GAS_ESTIMATE = 175_000n; // 161.5k gas on average (+/- 200 units)
13
+ const AVAX_APPROVAL_TX_GAS_ESTIMATE = 60_000n; // 55.5k gas on average (+/- 200 units)
14
+ const AVAX_TRANSFER_TX_GAS_ESTIMATE = 215_000n; // 203k gas on average (+/- 200 units)
15
+ /**
16
+ * The CCTP bridging consists of up to two transactions:
17
+ *
18
+ * 1. Token spend approval (technically optional, but realistically performed basically every time)
19
+ * 2. Token transfer (required)
20
+ *
21
+ * Since the 2nd one needs the first transaction to be complete, calling .estimateGas() is not possible.
22
+ * The RPC call raises an error since it cannot execute the transfer transaction locally without
23
+ * the allowance being set. For that reason, we're using hard-coded estimates here, with small buffers added.
24
+ *
25
+ * NOTE: These estimates are only supposed to be used to approximate the network fees in the UI.
26
+ * DO NOT use them as `gasLimit` prop on the transactions!
27
+ */
28
+ export async function estimateGas(bridge: BridgeService, params: TransferParams): Promise<bigint> {
29
+ await bridge.ensureHasConfig();
30
+
31
+ const { sourceChain, targetChain, asset, amount, fromAddress, toAddress: maybeToAddress, sourceProvider } = params;
32
+ const toAddress = maybeToAddress ?? fromAddress;
33
+
34
+ if (!isAddress(fromAddress) || !isAddress(toAddress)) {
35
+ throw new InvalidParamsError(ErrorReason.INCORRECT_ADDRESS_PROVIDED);
36
+ }
37
+
38
+ const { sourceChainData, burnToken } = getTransferData({ sourceChain, targetChain, asset, amount }, bridge.config!);
39
+
40
+ const client = getClientForChain({ chain: sourceChain, provider: sourceProvider });
41
+
42
+ const allowance = await client.readContract({
43
+ address: burnToken.address,
44
+ abi: ERC20_ABI,
45
+ functionName: 'allowance',
46
+ args: [fromAddress, sourceChainData.tokenRouterAddress],
47
+ });
48
+
49
+ const isOffboarding = sourceChainData.domain === ChainDomain.Avalanche;
50
+
51
+ if (allowance >= amount) {
52
+ return isOffboarding ? AVAX_TRANSFER_TX_GAS_ESTIMATE : ETH_TRANSFER_TX_GAS_ESTIMATE;
53
+ }
54
+
55
+ return isOffboarding
56
+ ? AVAX_APPROVAL_TX_GAS_ESTIMATE + AVAX_TRANSFER_TX_GAS_ESTIMATE
57
+ : ETH_APPROVAL_TX_GAS_ESTIMATE + ETH_TRANSFER_TX_GAS_ESTIMATE;
58
+ }
@@ -1,4 +1,4 @@
1
- import { encodeFunctionData, isAddress, type PublicClient } from 'viem';
1
+ import { isAddress, type PublicClient } from 'viem';
2
2
  import {
3
3
  ErrorReason,
4
4
  type BridgeService,
@@ -13,6 +13,7 @@ import { ERC20_ABI } from '../abis/erc20';
13
13
  import { getTransferData } from '../utils/transfer-data';
14
14
  import { TOKEN_ROUTER_ABI } from '../abis/token-router';
15
15
  import { InvalidParamsError } from '../../../errors';
16
+ import { buildApprovalTxData, buildTransferTxData } from '../utils/build-tx';
16
17
 
17
18
  const approveAndTransfer = async (bridge: BridgeService, params: TransferParams) => {
18
19
  const {
@@ -56,12 +57,10 @@ const approveAndTransfer = async (bridge: BridgeService, params: TransferParams)
56
57
  });
57
58
 
58
59
  if (sign) {
59
- const data = encodeFunctionData({
60
- abi: ERC20_ABI,
61
- functionName: 'approve',
62
- args: [sourceChainData.tokenRouterAddress, amount],
60
+ const data = buildApprovalTxData({
61
+ amount,
62
+ sourceChainData,
63
63
  });
64
-
65
64
  const txHash = await sign(
66
65
  {
67
66
  from: fromAddress,
@@ -93,10 +92,11 @@ const approveAndTransfer = async (bridge: BridgeService, params: TransferParams)
93
92
  });
94
93
 
95
94
  if (sign) {
96
- const data = encodeFunctionData({
97
- abi: TOKEN_ROUTER_ABI,
98
- functionName: 'transferTokens',
99
- args: [amount, targetChainData.domain, toAddress, burnToken.address],
95
+ const data = buildTransferTxData({
96
+ amount,
97
+ burnToken,
98
+ targetChainData,
99
+ toAddress,
100
100
  });
101
101
 
102
102
  return sign(
@@ -2,3 +2,8 @@ export enum AvalancheChainIds {
2
2
  FUJI = 'eip155:43113',
3
3
  MAINNET = 'eip155:43114',
4
4
  }
5
+
6
+ export enum ChainDomain {
7
+ Ethereum = 0,
8
+ Avalanche = 1,
9
+ }
@@ -1,15 +1,16 @@
1
1
  import type { Address } from 'viem';
2
+ import type { ChainDomain } from './chain';
2
3
 
3
- type Token = {
4
+ export type Token = {
4
5
  address: Address;
5
6
  name: string;
6
7
  symbol: string;
7
8
  decimals: number;
8
9
  };
9
10
 
10
- type ChainData = {
11
+ export type ChainData = {
11
12
  chainId: string;
12
- domain: number;
13
+ domain: ChainDomain;
13
14
  tokenRouterAddress: Address;
14
15
  messageTransmitterAddress: Address;
15
16
  tokens: Token[];
@@ -0,0 +1,30 @@
1
+ import { encodeFunctionData } from 'viem';
2
+ import type { ChainData, Token } from '../types/config';
3
+ import { TOKEN_ROUTER_ABI } from '../abis/token-router';
4
+ import { ERC20_ABI } from '../abis/erc20';
5
+
6
+ export function buildTransferTxData({
7
+ amount,
8
+ burnToken,
9
+ targetChainData,
10
+ toAddress,
11
+ }: {
12
+ targetChainData: ChainData;
13
+ toAddress: `0x${string}`;
14
+ burnToken: Token;
15
+ amount: bigint;
16
+ }) {
17
+ return encodeFunctionData({
18
+ abi: TOKEN_ROUTER_ABI,
19
+ functionName: 'transferTokens',
20
+ args: [amount, targetChainData.domain, toAddress, burnToken.address],
21
+ });
22
+ }
23
+
24
+ export function buildApprovalTxData({ amount, sourceChainData }: { amount: bigint; sourceChainData: ChainData }) {
25
+ return encodeFunctionData({
26
+ abi: ERC20_ABI,
27
+ functionName: 'approve',
28
+ args: [sourceChainData.tokenRouterAddress, amount],
29
+ });
30
+ }
@@ -54,6 +54,7 @@ export type BridgeService = {
54
54
  config: BridgeConfig | null;
55
55
  ensureHasConfig: () => Promise<void>;
56
56
  updateConfig: () => Promise<void>;
57
+ estimateGas: (params: TransferParams) => Promise<bigint>;
57
58
  getAssets: () => Promise<ChainAssetMap>;
58
59
  getFees: (params: FeeParams) => Promise<AssetFeeMap>;
59
60
  transferAsset: (params: TransferParams) => Promise<BridgeTransfer>;
@@ -48,6 +48,7 @@ describe('unified-bridge-service', () => {
48
48
  bridges: enabledBridges,
49
49
  init: expect.any(Function),
50
50
  updateConfigs: expect.any(Function),
51
+ estimateGas: expect.any(Function),
51
52
  getAssets: expect.any(Function),
52
53
  getFees: expect.any(Function),
53
54
  transferAsset: expect.any(Function),
@@ -76,6 +76,12 @@ export const createUnifiedBridgeService = ({ environment, disabledBridgeTypes }:
76
76
  return bridge.trackTransfer(params);
77
77
  };
78
78
 
79
+ const estimateGas = async (params: TransferParams) => {
80
+ const { bridge } = getBridgeForTransfer(enabledBridgeServices, params.asset, params.targetChain.chainId);
81
+
82
+ return bridge.estimateGas(params);
83
+ };
84
+
79
85
  return {
80
86
  environment,
81
87
  bridges: enabledBridgeServices,
@@ -83,6 +89,7 @@ export const createUnifiedBridgeService = ({ environment, disabledBridgeTypes }:
83
89
  updateConfigs,
84
90
  getAssets,
85
91
  getFees,
92
+ estimateGas,
86
93
  canTransferAsset,
87
94
  transferAsset,
88
95
  trackTransfer,