@circle-fin/provider-cctp-v2 1.4.1 → 1.6.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.
package/index.mjs CHANGED
@@ -66,6 +66,8 @@ var Blockchain;
66
66
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
67
67
  Blockchain["Codex"] = "Codex";
68
68
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
69
+ Blockchain["Edge"] = "Edge";
70
+ Blockchain["Edge_Testnet"] = "Edge_Testnet";
69
71
  Blockchain["Ethereum"] = "Ethereum";
70
72
  Blockchain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
71
73
  Blockchain["Hedera"] = "Hedera";
@@ -78,6 +80,8 @@ var Blockchain;
78
80
  Blockchain["Linea_Sepolia"] = "Linea_Sepolia";
79
81
  Blockchain["Monad"] = "Monad";
80
82
  Blockchain["Monad_Testnet"] = "Monad_Testnet";
83
+ Blockchain["Morph"] = "Morph";
84
+ Blockchain["Morph_Testnet"] = "Morph_Testnet";
81
85
  Blockchain["NEAR"] = "NEAR";
82
86
  Blockchain["NEAR_Testnet"] = "NEAR_Testnet";
83
87
  Blockchain["Noble"] = "Noble";
@@ -281,11 +285,13 @@ var BridgeChain;
281
285
  BridgeChain["Avalanche"] = "Avalanche";
282
286
  BridgeChain["Base"] = "Base";
283
287
  BridgeChain["Codex"] = "Codex";
288
+ BridgeChain["Edge"] = "Edge";
284
289
  BridgeChain["Ethereum"] = "Ethereum";
285
290
  BridgeChain["HyperEVM"] = "HyperEVM";
286
291
  BridgeChain["Ink"] = "Ink";
287
292
  BridgeChain["Linea"] = "Linea";
288
293
  BridgeChain["Monad"] = "Monad";
294
+ BridgeChain["Morph"] = "Morph";
289
295
  BridgeChain["Optimism"] = "Optimism";
290
296
  BridgeChain["Plume"] = "Plume";
291
297
  BridgeChain["Polygon"] = "Polygon";
@@ -301,11 +307,13 @@ var BridgeChain;
301
307
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
302
308
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
303
309
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
310
+ BridgeChain["Edge_Testnet"] = "Edge_Testnet";
304
311
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
305
312
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
306
313
  BridgeChain["Ink_Testnet"] = "Ink_Testnet";
307
314
  BridgeChain["Linea_Sepolia"] = "Linea_Sepolia";
308
315
  BridgeChain["Monad_Testnet"] = "Monad_Testnet";
316
+ BridgeChain["Morph_Testnet"] = "Morph_Testnet";
309
317
  BridgeChain["Optimism_Sepolia"] = "Optimism_Sepolia";
310
318
  BridgeChain["Plume_Testnet"] = "Plume_Testnet";
311
319
  BridgeChain["Polygon_Amoy_Testnet"] = "Polygon_Amoy_Testnet";
@@ -1051,7 +1059,7 @@ const Codex = defineChain({
1051
1059
  },
1052
1060
  forwarderSupported: {
1053
1061
  source: true,
1054
- destination: false,
1062
+ destination: true,
1055
1063
  },
1056
1064
  },
1057
1065
  kitContracts: {
@@ -1094,7 +1102,95 @@ const CodexTestnet = defineChain({
1094
1102
  },
1095
1103
  forwarderSupported: {
1096
1104
  source: true,
1097
- destination: false,
1105
+ destination: true,
1106
+ },
1107
+ },
1108
+ kitContracts: {
1109
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1110
+ },
1111
+ });
1112
+
1113
+ /**
1114
+ * Edge Mainnet chain definition
1115
+ * @remarks
1116
+ * This represents the official production network for the Edge blockchain.
1117
+ * Edge is an EVM-compatible blockchain.
1118
+ */
1119
+ const Edge = defineChain({
1120
+ type: 'evm',
1121
+ chain: Blockchain.Edge,
1122
+ name: 'Edge',
1123
+ title: 'Edge Mainnet',
1124
+ nativeCurrency: {
1125
+ name: 'Ether',
1126
+ symbol: 'ETH',
1127
+ decimals: 18,
1128
+ },
1129
+ chainId: 3343,
1130
+ isTestnet: false,
1131
+ explorerUrl: 'https://pro.edgex.exchange/en-US/explorer/tx/{hash}',
1132
+ rpcEndpoints: ['https://edge-mainnet.g.alchemy.com/public'],
1133
+ eurcAddress: null,
1134
+ usdcAddress: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
1135
+ usdtAddress: null,
1136
+ cctp: {
1137
+ domain: 28,
1138
+ contracts: {
1139
+ v2: {
1140
+ type: 'split',
1141
+ tokenMessenger: '0x98706A006bc632Df31CAdFCBD43F38887ce2ca5c',
1142
+ messageTransmitter: '0x5b61381Fc9e58E70EfC13a4A97516997019198ee',
1143
+ confirmations: 65,
1144
+ fastConfirmations: 1,
1145
+ },
1146
+ },
1147
+ forwarderSupported: {
1148
+ source: true,
1149
+ destination: true,
1150
+ },
1151
+ },
1152
+ kitContracts: {
1153
+ bridge: '0x6D1AaE1c34Aeb582022916a67f2A655C6f4eDFF2', //Unique bridge address as CCTP address for Edge is also unique
1154
+ },
1155
+ });
1156
+
1157
+ /**
1158
+ * Edge Testnet chain definition
1159
+ * @remarks
1160
+ * This represents the official test network for the Edge blockchain.
1161
+ * Edge is an EVM-compatible blockchain.
1162
+ */
1163
+ const EdgeTestnet = defineChain({
1164
+ type: 'evm',
1165
+ chain: Blockchain.Edge_Testnet,
1166
+ name: 'Edge Testnet',
1167
+ title: 'Edge Testnet',
1168
+ nativeCurrency: {
1169
+ name: 'Ether',
1170
+ symbol: 'ETH',
1171
+ decimals: 18,
1172
+ },
1173
+ chainId: 33431,
1174
+ isTestnet: true,
1175
+ explorerUrl: 'https://edge-testnet.explorer.alchemy.com/tx/{hash}',
1176
+ rpcEndpoints: ['https://edge-testnet.g.alchemy.com/public'],
1177
+ eurcAddress: null,
1178
+ usdcAddress: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
1179
+ usdtAddress: null,
1180
+ cctp: {
1181
+ domain: 28,
1182
+ contracts: {
1183
+ v2: {
1184
+ type: 'split',
1185
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
1186
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
1187
+ confirmations: 65,
1188
+ fastConfirmations: 1,
1189
+ },
1190
+ },
1191
+ forwarderSupported: {
1192
+ source: true,
1193
+ destination: true,
1098
1194
  },
1099
1195
  },
1100
1196
  kitContracts: {
@@ -1268,7 +1364,7 @@ const HyperEVM = defineChain({
1268
1364
  },
1269
1365
  chainId: 999,
1270
1366
  isTestnet: false,
1271
- explorerUrl: 'https://hyperevmscan.io/tx/{hash}',
1367
+ explorerUrl: 'https://app.hyperliquid.xyz/explorer/tx/{hash}',
1272
1368
  rpcEndpoints: ['https://rpc.hyperliquid.xyz/evm'],
1273
1369
  eurcAddress: null,
1274
1370
  usdcAddress: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
@@ -1313,7 +1409,7 @@ const HyperEVMTestnet = defineChain({
1313
1409
  },
1314
1410
  chainId: 998,
1315
1411
  isTestnet: true,
1316
- explorerUrl: 'https://testnet.hyperliquid.xyz/explorer/tx/{hash}',
1412
+ explorerUrl: 'https://app.hyperliquid-testnet.xyz/explorer/tx/{hash}',
1317
1413
  rpcEndpoints: ['https://rpc.hyperliquid-testnet.xyz/evm'],
1318
1414
  eurcAddress: null,
1319
1415
  usdcAddress: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
@@ -1613,6 +1709,94 @@ const MonadTestnet = defineChain({
1613
1709
  },
1614
1710
  });
1615
1711
 
1712
+ /**
1713
+ * Morph Mainnet chain definition
1714
+ * @remarks
1715
+ * This represents the official production network for the Morph blockchain.
1716
+ * Morph is an EVM-compatible Layer-2 blockchain built on the OP Stack.
1717
+ */
1718
+ const Morph = defineChain({
1719
+ type: 'evm',
1720
+ chain: Blockchain.Morph,
1721
+ name: 'Morph',
1722
+ title: 'Morph Mainnet',
1723
+ nativeCurrency: {
1724
+ name: 'Ether',
1725
+ symbol: 'ETH',
1726
+ decimals: 18,
1727
+ },
1728
+ chainId: 2818,
1729
+ isTestnet: false,
1730
+ explorerUrl: 'https://explorer.morph.network/tx/{hash}',
1731
+ rpcEndpoints: ['https://rpc.morphl2.io'],
1732
+ eurcAddress: null,
1733
+ usdcAddress: '0xCfb1186F4e93D60E60a8bDd997427D1F33bc372B',
1734
+ usdtAddress: null,
1735
+ cctp: {
1736
+ domain: 30,
1737
+ contracts: {
1738
+ v2: {
1739
+ type: 'split',
1740
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
1741
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
1742
+ confirmations: 64,
1743
+ fastConfirmations: 1,
1744
+ },
1745
+ },
1746
+ forwarderSupported: {
1747
+ source: false,
1748
+ destination: false,
1749
+ },
1750
+ },
1751
+ kitContracts: {
1752
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET,
1753
+ },
1754
+ });
1755
+
1756
+ /**
1757
+ * Morph Hoodi Testnet chain definition
1758
+ * @remarks
1759
+ * This represents the official test network for the Morph blockchain.
1760
+ * Morph is an EVM-compatible Layer-2 blockchain built on the OP Stack.
1761
+ */
1762
+ const MorphTestnet = defineChain({
1763
+ type: 'evm',
1764
+ chain: Blockchain.Morph_Testnet,
1765
+ name: 'Morph Hoodi',
1766
+ title: 'Morph Hoodi Testnet',
1767
+ nativeCurrency: {
1768
+ name: 'Ether',
1769
+ symbol: 'ETH',
1770
+ decimals: 18,
1771
+ },
1772
+ chainId: 2910,
1773
+ isTestnet: true,
1774
+ explorerUrl: 'https://explorer-hoodi.morphl2.io/tx/{hash}',
1775
+ rpcEndpoints: ['https://rpc-hoodi.morphl2.io'],
1776
+ eurcAddress: null,
1777
+ usdcAddress: '0x7433b41C6c5e1d58D4Da99483609520255ab661B',
1778
+ usdtAddress: null,
1779
+ cctp: {
1780
+ domain: 30,
1781
+ contracts: {
1782
+ v2: {
1783
+ type: 'split',
1784
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
1785
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
1786
+ confirmations: 64,
1787
+ fastConfirmations: 1,
1788
+ },
1789
+ },
1790
+ forwarderSupported: {
1791
+ source: false,
1792
+ destination: false,
1793
+ },
1794
+ },
1795
+ kitContracts: {
1796
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET,
1797
+ },
1798
+ });
1799
+
1616
1800
  /**
1617
1801
  * NEAR Protocol Mainnet chain definition
1618
1802
  * @remarks
@@ -1871,7 +2055,7 @@ const Plume = defineChain({
1871
2055
  },
1872
2056
  forwarderSupported: {
1873
2057
  source: true,
1874
- destination: false,
2058
+ destination: true,
1875
2059
  },
1876
2060
  },
1877
2061
  kitContracts: {
@@ -1916,7 +2100,7 @@ const PlumeTestnet = defineChain({
1916
2100
  },
1917
2101
  forwarderSupported: {
1918
2102
  source: true,
1919
- destination: false,
2103
+ destination: true,
1920
2104
  },
1921
2105
  },
1922
2106
  kitContracts: {
@@ -2694,7 +2878,7 @@ const XDC = defineChain({
2694
2878
  },
2695
2879
  forwarderSupported: {
2696
2880
  source: true,
2697
- destination: false,
2881
+ destination: true,
2698
2882
  },
2699
2883
  },
2700
2884
  kitContracts: {
@@ -2738,7 +2922,7 @@ const XDCApothem = defineChain({
2738
2922
  },
2739
2923
  forwarderSupported: {
2740
2924
  source: true,
2741
- destination: false,
2925
+ destination: true,
2742
2926
  },
2743
2927
  },
2744
2928
  kitContracts: {
@@ -2813,6 +2997,8 @@ var Chains = /*#__PURE__*/Object.freeze({
2813
2997
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
2814
2998
  Codex: Codex,
2815
2999
  CodexTestnet: CodexTestnet,
3000
+ Edge: Edge,
3001
+ EdgeTestnet: EdgeTestnet,
2816
3002
  Ethereum: Ethereum,
2817
3003
  EthereumSepolia: EthereumSepolia,
2818
3004
  Hedera: Hedera,
@@ -2825,6 +3011,8 @@ var Chains = /*#__PURE__*/Object.freeze({
2825
3011
  LineaSepolia: LineaSepolia,
2826
3012
  Monad: Monad,
2827
3013
  MonadTestnet: MonadTestnet,
3014
+ Morph: Morph,
3015
+ MorphTestnet: MorphTestnet,
2828
3016
  NEAR: NEAR,
2829
3017
  NEARTestnet: NEARTestnet,
2830
3018
  Noble: Noble,
@@ -5416,12 +5604,14 @@ const USDC = {
5416
5604
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
5417
5605
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
5418
5606
  [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
5607
+ [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
5419
5608
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
5420
5609
  [Blockchain.Hedera]: '0.0.456858',
5421
5610
  [Blockchain.HyperEVM]: '0xb88339CB7199b77E23DB6E890353E22632Ba630f',
5422
5611
  [Blockchain.Ink]: '0x2D270e6886d130D724215A266106e6832161EAEd',
5423
5612
  [Blockchain.Linea]: '0x176211869ca2b568f2a7d4ee941e073a821ee1ff',
5424
5613
  [Blockchain.Monad]: '0x754704Bc059F8C67012fEd69BC8A327a5aafb603',
5614
+ [Blockchain.Morph]: '0xCfb1186F4e93D60E60a8bDd997427D1F33bc372B',
5425
5615
  [Blockchain.NEAR]: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1',
5426
5616
  [Blockchain.Noble]: 'uusdc',
5427
5617
  [Blockchain.Optimism]: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
@@ -5444,12 +5634,14 @@ const USDC = {
5444
5634
  [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
5445
5635
  [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
5446
5636
  [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
5637
+ [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
5447
5638
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
5448
5639
  [Blockchain.Hedera_Testnet]: '0.0.429274',
5449
5640
  [Blockchain.HyperEVM_Testnet]: '0x2B3370eE501B4a559b57D449569354196457D8Ab',
5450
5641
  [Blockchain.Ink_Testnet]: '0xFabab97dCE620294D2B0b0e46C68964e326300Ac',
5451
5642
  [Blockchain.Linea_Sepolia]: '0xfece4462d57bd51a6a552365a011b95f0e16d9b7',
5452
5643
  [Blockchain.Monad_Testnet]: '0x534b2f3A21130d7a60830c2Df862319e593943A3',
5644
+ [Blockchain.Morph_Testnet]: '0x7433b41C6c5e1d58D4Da99483609520255ab661B',
5453
5645
  [Blockchain.NEAR_Testnet]: '3e2210e1184b45b64c8a434c0a7e7b23cc04ea7eb7a6c3c32520d03d4afcb8af',
5454
5646
  [Blockchain.Noble_Testnet]: 'uusdc',
5455
5647
  [Blockchain.Optimism_Sepolia]: '0x5fd84259d66Cd46123540766Be93DFE6D43130D7',
@@ -9310,7 +9502,184 @@ z
9310
9502
  })
9311
9503
  .passthrough();
9312
9504
 
9313
- var version = "1.4.1";
9505
+ /**
9506
+ * Check whether the source adapter supports EIP-5792 atomic batching and
9507
+ * the consumer has not explicitly opted out via `config.batchTransactions`.
9508
+ *
9509
+ * @param params - Bridge parameters (used for adapter and config access).
9510
+ * @returns `true` when batched execution should be attempted.
9511
+ *
9512
+ * @example
9513
+ * ```typescript
9514
+ * const useBatched = await shouldUseBatchedExecution(params)
9515
+ * if (useBatched) {
9516
+ * // take the batched approve + burn path
9517
+ * }
9518
+ * ```
9519
+ */
9520
+ async function shouldUseBatchedExecution(params) {
9521
+ if (params.config?.batchTransactions === false) {
9522
+ return false;
9523
+ }
9524
+ const { chain } = params.source;
9525
+ if (chain.type !== 'evm') {
9526
+ return false;
9527
+ }
9528
+ const adapter = params.source
9529
+ .adapter;
9530
+ if (typeof adapter.supportsAtomicBatch !== 'function' ||
9531
+ typeof adapter.batchExecute !== 'function') {
9532
+ return false;
9533
+ }
9534
+ try {
9535
+ return await adapter.supportsAtomicBatch(chain);
9536
+ }
9537
+ catch {
9538
+ return false;
9539
+ }
9540
+ }
9541
+ /**
9542
+ * Execute the approve and burn steps as a single EIP-5792 batched call.
9543
+ *
9544
+ * Prepare both `PreparedChainRequest` objects upfront, extract their raw
9545
+ * call data via `getCallData()`, submit both via `adapter.batchExecute()`,
9546
+ * then map the individual receipts back to standard {@link BridgeStep}
9547
+ * objects so downstream consumers (event callbacks, result tracking) are
9548
+ * unaffected.
9549
+ *
9550
+ * @param params - The CCTP v2 bridge parameters.
9551
+ * @param provider - The CCTP v2 bridging provider.
9552
+ * @returns Approve and burn steps with a shared context containing the burn tx hash.
9553
+ * @throws {@link KitError} when the source chain is not EVM.
9554
+ * @throws {@link KitError} when calldata extraction (`getCallData`) is not supported
9555
+ * by the prepared requests.
9556
+ * @remarks
9557
+ * Errors that occur after the batch has been submitted to the wallet
9558
+ * (e.g. polling timeout, insufficient receipts) are **not thrown** — they
9559
+ * are captured as `state: 'error'` on the returned steps to prevent
9560
+ * accidental double-spend on retry.
9561
+ *
9562
+ * @example
9563
+ * ```typescript
9564
+ * const { approveStep, burnStep, context } = await executeBatchedApproveAndBurn(
9565
+ * params,
9566
+ * provider,
9567
+ * )
9568
+ * result.steps.push(approveStep, burnStep)
9569
+ * ```
9570
+ */
9571
+ async function executeBatchedApproveAndBurn(params, provider) {
9572
+ // Double-unknown cast: Adapter<TFrom> has no structural overlap with
9573
+ // BatchCapableAdapter (a duck-typed interface for EIP-5792 methods).
9574
+ // A direct cast fails because TS cannot prove the intersection; the
9575
+ // runtime capability check below guards against unsupported adapters.
9576
+ const adapter = params.source.adapter;
9577
+ const sourceChain = params.source.chain;
9578
+ if (sourceChain.type !== 'evm') {
9579
+ throw new KitError({
9580
+ ...InputError.INVALID_CHAIN,
9581
+ recoverability: 'FATAL',
9582
+ message: 'Batched execution is only supported on EVM chains.',
9583
+ });
9584
+ }
9585
+ const chain = sourceChain;
9586
+ // customFee.value is in base units (integer string) at this point.
9587
+ const customFee = BigInt(params.config?.customFee?.value ?? '0');
9588
+ const amountBigInt = BigInt(params.amount);
9589
+ const approvalAmount = (amountBigInt + customFee).toString();
9590
+ const [approveRequest, burnRequest] = await Promise.all([
9591
+ provider.approve(params.source, approvalAmount),
9592
+ provider.burn(params),
9593
+ ]);
9594
+ if (approveRequest.type !== 'evm' ||
9595
+ burnRequest.type !== 'evm' ||
9596
+ !approveRequest.getCallData ||
9597
+ !burnRequest.getCallData) {
9598
+ throw new KitError({
9599
+ ...InputError.UNSUPPORTED_ACTION,
9600
+ recoverability: 'FATAL',
9601
+ message: 'Batched execution requires EVM prepared requests with getCallData() support.',
9602
+ });
9603
+ }
9604
+ const approveCallData = approveRequest.getCallData();
9605
+ const burnCallData = burnRequest.getCallData();
9606
+ // batchExecute may throw before submission (wallet declined) but never
9607
+ // after — post-submission errors are returned as empty receipts.
9608
+ const batchResult = await adapter.batchExecute([approveCallData, burnCallData], chain);
9609
+ const approveReceipt = batchResult.receipts[0];
9610
+ const burnReceipt = batchResult.receipts[1];
9611
+ const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain);
9612
+ const burnStep = await buildBatchedStep('burn', burnReceipt, batchResult.batchId, adapter, chain);
9613
+ if (burnStep.state !== 'error' && !burnStep.txHash) {
9614
+ burnStep.state = 'error';
9615
+ burnStep.errorMessage =
9616
+ 'Batched burn step completed but no transaction hash was returned.';
9617
+ }
9618
+ const context = { burnTxHash: burnStep.txHash ?? '' };
9619
+ return { approveStep, burnStep, context };
9620
+ }
9621
+ /**
9622
+ * Build a {@link BridgeStep} from a single receipt within a batch.
9623
+ *
9624
+ * Map the raw receipt from `batchExecute` into a standard `BridgeStep`,
9625
+ * waiting for on-chain confirmation via `adapter.waitForTransaction`.
9626
+ * All errors are captured on the step (never thrown) so the caller
9627
+ * can inspect each step independently.
9628
+ *
9629
+ * @param name - Human-readable step name (e.g. `'approve'`, `'burn'`).
9630
+ * @param receipt - Per-call receipt from `batchExecute`, or `undefined`
9631
+ * when the wallet returned fewer receipts than submitted calls.
9632
+ * @param batchId - Wallet-assigned batch identifier.
9633
+ * @param adapter - The batch-capable adapter (used for confirmation).
9634
+ * @param chain - The EVM chain the batch was executed on.
9635
+ * @returns A fully-populated bridge step with state, tx hash and explorer URL.
9636
+ *
9637
+ * @internal
9638
+ */
9639
+ async function buildBatchedStep(name, receipt, batchId, adapter, chain) {
9640
+ const step = {
9641
+ name,
9642
+ state: 'pending',
9643
+ batched: true,
9644
+ batchId,
9645
+ };
9646
+ if (!receipt) {
9647
+ step.state = 'error';
9648
+ step.errorMessage = `No receipt returned for ${name} in batch ${batchId}.`;
9649
+ return step;
9650
+ }
9651
+ step.txHash = receipt.txHash;
9652
+ if (receipt.txHash) {
9653
+ step.explorerUrl = buildExplorerUrl(chain, receipt.txHash);
9654
+ }
9655
+ if (receipt.status !== 'success') {
9656
+ step.state = 'error';
9657
+ step.errorMessage = `${name} call failed within batch ${batchId}.`;
9658
+ return step;
9659
+ }
9660
+ if (!receipt.txHash) {
9661
+ step.state = 'error';
9662
+ step.errorMessage = `${name} succeeded in batch but returned an empty transaction hash.`;
9663
+ return step;
9664
+ }
9665
+ try {
9666
+ const transaction = await adapter.waitForTransaction(receipt.txHash, { confirmations: 1 }, chain);
9667
+ step.state = transaction.blockNumber === undefined ? 'error' : 'success';
9668
+ step.data = transaction;
9669
+ if (transaction.blockNumber === undefined) {
9670
+ step.errorMessage = 'Transaction was not confirmed on-chain.';
9671
+ }
9672
+ }
9673
+ catch (err) {
9674
+ step.state = 'error';
9675
+ step.error = err;
9676
+ step.errorMessage =
9677
+ err instanceof Error ? err.message : 'Unknown error during confirmation.';
9678
+ }
9679
+ return step;
9680
+ }
9681
+
9682
+ var version = "1.6.0";
9314
9683
  var pkg = {
9315
9684
  version: version};
9316
9685
 
@@ -9339,6 +9708,67 @@ function resolveBridgeInvocation(invocationMeta) {
9339
9708
  };
9340
9709
  return extendInvocationContext(resolveInvocationContext(invocationMeta, defaults), BRIDGE_CALLER);
9341
9710
  }
9711
+ /**
9712
+ * Execute the batched approve + burn path via EIP-5792.
9713
+ *
9714
+ * Mutate `result` with step data and error state as needed. Return the
9715
+ * batch context on success, or `undefined` when the batch failed (in
9716
+ * which case `result.state` is set to `'error'`).
9717
+ *
9718
+ * @internal
9719
+ * @param params - Bridge parameters (read-only).
9720
+ * @param provider - The CCTP v2 bridging provider (read-only).
9721
+ * @param result - Bridge result object — **mutated in place** with step
9722
+ * data and, on failure, `state: 'error'` plus an `error` payload.
9723
+ * @param invocation - Invocation context for telemetry (read-only).
9724
+ * @returns The step context on success, or `undefined` when the batch failed.
9725
+ */
9726
+ async function executeBatchedPath(params, provider, result, invocation) {
9727
+ // IMPORTANT: once executeBatchedApproveAndBurn is called, we NEVER
9728
+ // fall back to sequential. The wallet may have already signed &
9729
+ // submitted the batch; retrying as individual txs would double-spend.
9730
+ try {
9731
+ const { approveStep, burnStep, context: batchContext, } = await executeBatchedApproveAndBurn(params, provider);
9732
+ for (const step of [approveStep, burnStep]) {
9733
+ const stepName = step.name;
9734
+ if (step.state === 'error') {
9735
+ ensureStepErrorMessage(step.name, step);
9736
+ result.steps.push(step);
9737
+ result.state = 'error';
9738
+ dispatchStepEvent(stepName, step, provider, invocation);
9739
+ return undefined;
9740
+ }
9741
+ dispatchStepEvent(stepName, step, provider, invocation);
9742
+ result.steps.push(step);
9743
+ }
9744
+ return batchContext;
9745
+ }
9746
+ catch (error_) {
9747
+ // Only handles pre-submission failures (prepare rejected, wallet
9748
+ // declined, etc.). batchExecute never throws after sendCalls succeeds.
9749
+ result.state = 'error';
9750
+ result.steps.push({
9751
+ name: 'batch',
9752
+ state: 'error',
9753
+ batched: true,
9754
+ error: error_,
9755
+ errorMessage: error_ instanceof Error
9756
+ ? error_.message
9757
+ : 'Batched approve + burn failed.',
9758
+ });
9759
+ return undefined;
9760
+ }
9761
+ }
9762
+ /**
9763
+ * Ensure `step.errorMessage` is populated when an error object exists.
9764
+ *
9765
+ * @internal
9766
+ */
9767
+ function ensureStepErrorMessage(name, step) {
9768
+ if (!step.errorMessage && step.error) {
9769
+ step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
9770
+ }
9771
+ }
9342
9772
  /**
9343
9773
  * Execute a cross-chain USDC bridge using the CCTP v2 protocol.
9344
9774
  *
@@ -9372,9 +9802,7 @@ function resolveBridgeInvocation(invocationMeta) {
9372
9802
  * ```
9373
9803
  */
9374
9804
  async function bridge(params, provider) {
9375
- // Check if forwarder is enabled (on destination)
9376
9805
  const useForwarder = params.destination.useForwarder === true;
9377
- // Resolve invocation metadata to full context for event dispatching
9378
9806
  const invocation = resolveBridgeInvocation(params.invocationMeta);
9379
9807
  const result = {
9380
9808
  state: 'pending',
@@ -9387,11 +9815,9 @@ async function bridge(params, provider) {
9387
9815
  destination: {
9388
9816
  address: params.destination.address,
9389
9817
  chain: params.destination.chain,
9390
- // Preserve recipientAddress
9391
9818
  ...(params.destination.recipientAddress && {
9392
9819
  recipientAddress: params.destination.recipientAddress,
9393
9820
  }),
9394
- // Preserve useForwarder
9395
9821
  ...(useForwarder && {
9396
9822
  useForwarder: true,
9397
9823
  }),
@@ -9400,29 +9826,38 @@ async function bridge(params, provider) {
9400
9826
  config: params.config,
9401
9827
  provider: provider.name,
9402
9828
  };
9403
- // Context shared between steps
9404
9829
  let context = undefined;
9405
- // Create step executors based on useForwarder config
9830
+ let useBatched = false;
9831
+ try {
9832
+ useBatched = await shouldUseBatchedExecution(params);
9833
+ }
9834
+ catch {
9835
+ // Silently fall back to sequential
9836
+ }
9406
9837
  const executors = createStepExecutors(useForwarder);
9407
- // Execute each step in sequence
9408
- for (const { name, executor, updateContext } of executors) {
9838
+ if (useBatched) {
9839
+ const batchContext = await executeBatchedPath(params, provider, result, invocation);
9840
+ if (result.state === 'error') {
9841
+ return result;
9842
+ }
9843
+ context = batchContext;
9844
+ }
9845
+ const stepsToRun = useBatched
9846
+ ? executors.filter(({ name }) => name !== 'approve' && name !== 'burn')
9847
+ : executors;
9848
+ for (const { name, executor, updateContext } of stepsToRun) {
9409
9849
  try {
9410
9850
  const step = await executor(params, provider, context);
9411
9851
  if (step.state === 'error') {
9412
- // Ensure errorMessage is set with proper formatting if not already present
9413
- if (!step.errorMessage && step.error) {
9414
- step.errorMessage = `${name} step failed: ${getErrorMessage(step.error)}`;
9415
- }
9852
+ ensureStepErrorMessage(name, step);
9416
9853
  result.steps.push(step);
9417
9854
  result.state = 'error';
9418
- // Dispatch event even for error steps
9419
9855
  dispatchStepEvent(name, step, provider, invocation);
9420
9856
  return result;
9421
9857
  }
9422
- // Merge new context with existing context to preserve data from previous steps
9423
9858
  const newContext = updateContext?.(step);
9424
9859
  if (newContext) {
9425
- context = { ...(context ?? {}), ...newContext };
9860
+ context = { ...context, ...newContext };
9426
9861
  }
9427
9862
  dispatchStepEvent(name, step, provider, invocation);
9428
9863
  result.steps.push(step);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@circle-fin/provider-cctp-v2",
3
- "version": "1.4.1",
3
+ "version": "1.6.0",
4
4
  "description": "Circle's official Cross-Chain Transfer Protocol v2 provider for native USDC bridging",
5
5
  "keywords": [
6
6
  "circle",