@circle-fin/provider-cctp-v2 1.8.4 → 1.9.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.cjs CHANGED
@@ -35,6 +35,7 @@ var units = require('@ethersproject/units');
35
35
  var bytes = require('@ethersproject/bytes');
36
36
  var address = require('@ethersproject/address');
37
37
  var bs58 = require('bs58');
38
+ require('@ethersproject/abi');
38
39
 
39
40
  function _interopDefault (e) { return e && e.__esModule ? e.default : e; }
40
41
 
@@ -84,6 +85,8 @@ var bs58__default = /*#__PURE__*/_interopDefault(bs58);
84
85
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
85
86
  Blockchain["Codex"] = "Codex";
86
87
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
88
+ Blockchain["Cronos"] = "Cronos";
89
+ Blockchain["Cronos_Testnet"] = "Cronos_Testnet";
87
90
  Blockchain["Edge"] = "Edge";
88
91
  Blockchain["Edge_Testnet"] = "Edge_Testnet";
89
92
  Blockchain["Ethereum"] = "Ethereum";
@@ -166,6 +169,7 @@ var BridgeChain;
166
169
  BridgeChain["Avalanche"] = "Avalanche";
167
170
  BridgeChain["Base"] = "Base";
168
171
  BridgeChain["Codex"] = "Codex";
172
+ BridgeChain["Cronos"] = "Cronos";
169
173
  BridgeChain["Edge"] = "Edge";
170
174
  BridgeChain["Ethereum"] = "Ethereum";
171
175
  BridgeChain["HyperEVM"] = "HyperEVM";
@@ -190,6 +194,7 @@ var BridgeChain;
190
194
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
191
195
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
192
196
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
197
+ BridgeChain["Cronos_Testnet"] = "Cronos_Testnet";
193
198
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
194
199
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
195
200
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
@@ -1235,6 +1240,96 @@ var EarnChain;
1235
1240
  }
1236
1241
  });
1237
1242
 
1243
+ /**
1244
+ * Cronos Mainnet chain definition
1245
+ * @remarks
1246
+ * This represents the official production network for the Cronos blockchain.
1247
+ * Cronos is an EVM-compatible blockchain.
1248
+ */ const Cronos = defineChain({
1249
+ type: 'evm',
1250
+ chain: Blockchain.Cronos,
1251
+ name: 'Cronos',
1252
+ title: 'Cronos Mainnet',
1253
+ nativeCurrency: {
1254
+ name: 'Cronos',
1255
+ symbol: 'CRO',
1256
+ decimals: 18
1257
+ },
1258
+ chainId: 25,
1259
+ isTestnet: false,
1260
+ explorerUrl: 'https://cronoscan.com/tx/{hash}',
1261
+ rpcEndpoints: [
1262
+ 'https://evm.cronos.org'
1263
+ ],
1264
+ eurcAddress: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
1265
+ usdcAddress: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
1266
+ usdtAddress: null,
1267
+ cctp: {
1268
+ domain: 32,
1269
+ contracts: {
1270
+ v2: {
1271
+ type: 'split',
1272
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
1273
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
1274
+ confirmations: 1,
1275
+ fastConfirmations: 1
1276
+ }
1277
+ },
1278
+ forwarderSupported: {
1279
+ source: false,
1280
+ destination: false
1281
+ }
1282
+ },
1283
+ kitContracts: {
1284
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
1285
+ }
1286
+ });
1287
+
1288
+ /**
1289
+ * Cronos Testnet chain definition
1290
+ * @remarks
1291
+ * This represents the official test network for the Cronos blockchain.
1292
+ * Cronos is an EVM-compatible blockchain.
1293
+ */ const CronosTestnet = defineChain({
1294
+ type: 'evm',
1295
+ chain: Blockchain.Cronos_Testnet,
1296
+ name: 'Cronos Testnet',
1297
+ title: 'Cronos Testnet',
1298
+ nativeCurrency: {
1299
+ name: 'CRO',
1300
+ symbol: 'tCRO',
1301
+ decimals: 18
1302
+ },
1303
+ chainId: 338,
1304
+ isTestnet: true,
1305
+ explorerUrl: 'https://explorer.cronos.org/testnet/tx/{hash}',
1306
+ rpcEndpoints: [
1307
+ 'https://evm-t3.cronos.org'
1308
+ ],
1309
+ eurcAddress: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
1310
+ usdcAddress: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
1311
+ usdtAddress: null,
1312
+ cctp: {
1313
+ domain: 32,
1314
+ contracts: {
1315
+ v2: {
1316
+ type: 'split',
1317
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
1318
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
1319
+ confirmations: 1,
1320
+ fastConfirmations: 1
1321
+ }
1322
+ },
1323
+ forwarderSupported: {
1324
+ source: false,
1325
+ destination: false
1326
+ }
1327
+ },
1328
+ kitContracts: {
1329
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
1330
+ }
1331
+ });
1332
+
1238
1333
  /**
1239
1334
  * Edge Mainnet chain definition
1240
1335
  * @remarks
@@ -3586,6 +3681,8 @@ var Chains = {
3586
3681
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
3587
3682
  Codex: Codex,
3588
3683
  CodexTestnet: CodexTestnet,
3684
+ Cronos: Cronos,
3685
+ CronosTestnet: CronosTestnet,
3589
3686
  Edge: Edge,
3590
3687
  EdgeTestnet: EdgeTestnet,
3591
3688
  Ethereum: Ethereum,
@@ -7360,6 +7457,7 @@ class KitError extends Error {
7360
7457
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
7361
7458
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
7362
7459
  [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
7460
+ [Blockchain.Cronos]: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
7363
7461
  [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
7364
7462
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
7365
7463
  [Blockchain.Hedera]: '0.0.456858',
@@ -7393,6 +7491,7 @@ class KitError extends Error {
7393
7491
  [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
7394
7492
  [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
7395
7493
  [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
7494
+ [Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
7396
7495
  [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
7397
7496
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
7398
7497
  [Blockchain.Hedera_Testnet]: '0.0.429274',
@@ -7460,13 +7559,22 @@ class KitError extends Error {
7460
7559
  symbol: 'EURC',
7461
7560
  decimals: 6,
7462
7561
  locators: {
7562
+ // =========================================================================
7563
+ // Mainnets
7564
+ // =========================================================================
7463
7565
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
7464
7566
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
7567
+ [Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
7465
7568
  [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
7466
7569
  [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
7467
7570
  [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
7571
+ // =========================================================================
7468
7572
  // Testnets
7469
- [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a'
7573
+ // =========================================================================
7574
+ [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
7575
+ [Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
7576
+ [Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
7577
+ [Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
7470
7578
  }
7471
7579
  };
7472
7580
 
@@ -7676,8 +7784,7 @@ class KitError extends Error {
7676
7784
  * cirBTC (Circle Bitcoin) token definition with addresses and metadata.
7677
7785
  *
7678
7786
  * @remarks
7679
- * Built-in cirBTC definition for the TokenRegistry. Currently deployed
7680
- * on Arc Testnet.
7787
+ * Built-in cirBTC definition for the TokenRegistry.
7681
7788
  *
7682
7789
  * @example
7683
7790
  * ```typescript
@@ -7693,7 +7800,15 @@ class KitError extends Error {
7693
7800
  symbol: 'cirBTC',
7694
7801
  decimals: 8,
7695
7802
  locators: {
7696
- [Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
7803
+ // =========================================================================
7804
+ // Mainnets
7805
+ // =========================================================================
7806
+ [Blockchain.Ethereum]: '0x72DFB2E44f59C5AD2bAFE84314E5b99a7cd5075E',
7807
+ // =========================================================================
7808
+ // Testnets
7809
+ // =========================================================================
7810
+ [Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
7811
+ [Blockchain.Ethereum_Sepolia]: '0x3a3fe695F684Bf9b9e43CF43C2b895Ea5e392bB3'
7697
7812
  }
7698
7813
  };
7699
7814
 
@@ -9168,6 +9283,15 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
9168
9283
  ;
9169
9284
  const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
9170
9285
  ;
9286
+ // Hard execution caps: observed max + ~30% buffer, used as gasLimit overrides on
9287
+ // chains whose eth_estimateGas under-reports (e.g. Cronos EIP-7623 calldata floor).
9288
+ // Kept separate from the fee-estimate averages above.
9289
+ const APPROVE_GAS_LIMIT_EVM = 100_000n // ERC-20 approve observed max ~46k
9290
+ ;
9291
+ const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 300_000n // observed max 226_506 + ~30%
9292
+ ;
9293
+ const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839 + ~30%
9294
+ ;
9171
9295
  /**
9172
9296
  * The minimum finality threshold for CCTPv2 transfers.
9173
9297
  *
@@ -9196,6 +9320,27 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
9196
9320
  'Content-Type': 'application/json'
9197
9321
  }
9198
9322
  };
9323
+ /**
9324
+ * Merges caller-provided polling overrides on top of {@link DEFAULT_CONFIG}.
9325
+ *
9326
+ * Headers are merged independently so caller-supplied headers augment the
9327
+ * defaults (such as `Content-Type`) rather than replacing them wholesale.
9328
+ *
9329
+ * @param config - Caller-provided polling configuration overrides
9330
+ * @param internalDefaults - Internal defaults applied before `config` (for example a
9331
+ * reduced `maxRetries` for one-shot requests); `config` still wins on conflict
9332
+ * @returns The effective polling configuration
9333
+ * @internal
9334
+ */ const mergeAttestationConfig = (config, internalDefaults = {})=>({
9335
+ ...DEFAULT_CONFIG,
9336
+ ...internalDefaults,
9337
+ ...config,
9338
+ headers: {
9339
+ ...DEFAULT_CONFIG.headers,
9340
+ ...internalDefaults.headers,
9341
+ ...config.headers
9342
+ }
9343
+ });
9199
9344
  /**
9200
9345
  * Type guard that verifies if an unknown value matches the AttestationMessage shape
9201
9346
  * and has all required properties.
@@ -9342,10 +9487,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
9342
9487
  * ```
9343
9488
  */ const fetchAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
9344
9489
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
9345
- const effectiveConfig = {
9346
- ...DEFAULT_CONFIG,
9347
- ...config
9348
- };
9490
+ const effectiveConfig = mergeAttestationConfig(config);
9349
9491
  return await pollApiGet(url, isAttestationResponse, effectiveConfig);
9350
9492
  };
9351
9493
  /**
@@ -9388,11 +9530,9 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
9388
9530
  */ const fetchAttestationWithoutStatusCheck = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
9389
9531
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
9390
9532
  // Use minimal retries since we're just fetching existing data
9391
- const effectiveConfig = {
9392
- ...DEFAULT_CONFIG,
9393
- maxRetries: 3,
9394
- ...config
9395
- };
9533
+ const effectiveConfig = mergeAttestationConfig(config, {
9534
+ maxRetries: 3
9535
+ });
9396
9536
  return await pollApiGet(url, isAttestationResponseWithoutStatusCheck, effectiveConfig);
9397
9537
  };
9398
9538
  /**
@@ -9452,10 +9592,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
9452
9592
  * ```
9453
9593
  */ const fetchReAttestedAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
9454
9594
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
9455
- const effectiveConfig = {
9456
- ...DEFAULT_CONFIG,
9457
- ...config
9458
- };
9595
+ const effectiveConfig = mergeAttestationConfig(config);
9459
9596
  return await pollApiGet(url, isReAttestedAttestationResponse, effectiveConfig);
9460
9597
  };
9461
9598
  /**
@@ -9521,14 +9658,139 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
9521
9658
  */ const requestReAttestation = async (nonce, isTestnet, config = {})=>{
9522
9659
  const url = buildReAttestUrl(nonce, isTestnet);
9523
9660
  // Use minimal retries since we're just submitting a request, not polling for state
9524
- const effectiveConfig = {
9525
- ...DEFAULT_CONFIG,
9526
- maxRetries: 3,
9527
- ...config
9528
- };
9661
+ const effectiveConfig = mergeAttestationConfig(config, {
9662
+ maxRetries: 3
9663
+ });
9529
9664
  return await pollApiPost(url, {}, isReAttestationResponse, effectiveConfig);
9530
9665
  };
9531
9666
 
9667
+ /**
9668
+ * Type guard that checks if the relayer has confirmed the mint transaction.
9669
+ *
9670
+ * This function validates that:
9671
+ * 1. The response has valid AttestationResponse structure
9672
+ * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
9673
+ *
9674
+ * If forwardState is 'FAILED', throws a non-retryable KitError.
9675
+ * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
9676
+ *
9677
+ * @param obj - The value to check, typically a parsed JSON response
9678
+ * @returns True if the relayer has confirmed the mint
9679
+ * @throws {KitError} With FATAL recoverability if structure is invalid
9680
+ * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
9681
+ * @throws {KitError} With RETRYABLE recoverability if still pending
9682
+ * @internal
9683
+ */ const isRelayerMintConfirmed = (obj)=>{
9684
+ // First check if the structure is valid
9685
+ if (!hasValidAttestationStructure(obj)) {
9686
+ throw new KitError({
9687
+ ...InputError.VALIDATION_FAILED,
9688
+ recoverability: 'FATAL',
9689
+ message: 'Invalid attestation response structure from IRIS API.'
9690
+ });
9691
+ }
9692
+ // Find the first message (typically there's only one)
9693
+ const message = obj.messages[0];
9694
+ if (!message) {
9695
+ throw new KitError({
9696
+ ...InputError.VALIDATION_FAILED,
9697
+ recoverability: 'FATAL',
9698
+ message: 'No attestation messages found in IRIS API response.'
9699
+ });
9700
+ }
9701
+ // Check for FAILED state - this is a permanent failure
9702
+ if (message.forwardState === 'FAILED') {
9703
+ throw new KitError({
9704
+ ...NetworkError.RELAYER_FORWARD_FAILED,
9705
+ recoverability: 'RESUMABLE',
9706
+ message: 'Circle relayer failed to forward the mint transaction. The mint may still have succeeded if another party submitted it. Check the recipient wallet balance before retrying. If the mint did not occur, you can manually submit it using the attestation data in the error cause.',
9707
+ cause: {
9708
+ trace: {
9709
+ eventNonce: message.eventNonce,
9710
+ attestation: message.attestation,
9711
+ message: message.message
9712
+ }
9713
+ }
9714
+ });
9715
+ }
9716
+ // Check if mint is confirmed (or complete) with a valid transaction hash
9717
+ // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
9718
+ if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
9719
+ return true;
9720
+ }
9721
+ // Still pending or not yet processed - throw RETRYABLE error to continue polling
9722
+ throw new KitError({
9723
+ ...NetworkError.RELAYER_PENDING,
9724
+ recoverability: 'RETRYABLE',
9725
+ message: 'Relayer mint not ready. Waiting for confirmation.'
9726
+ });
9727
+ };
9728
+ /**
9729
+ * Polls the attestation API until the relayer's mint transaction is confirmed.
9730
+ *
9731
+ * This function is used when `useForwarder` is enabled. Instead of the user
9732
+ * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
9733
+ * This function polls until the relayer has submitted and confirmed the mint transaction.
9734
+ *
9735
+ * @remarks
9736
+ * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
9737
+ * - Throws immediately if `forwardState` is 'FAILED'
9738
+ * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
9739
+ * - Returns the attestation message with `forwardTxHash` populated
9740
+ *
9741
+ * @param sourceDomainId - The CCTP domain ID of the source chain
9742
+ * @param transactionHash - The transaction hash of the burn operation
9743
+ * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
9744
+ * @param config - Optional configuration overrides for polling behavior
9745
+ * @returns The attestation message with confirmed forwardTxHash
9746
+ * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
9747
+ * @throws {KitError} If timeout is reached while still pending
9748
+ *
9749
+ * @example
9750
+ * ```typescript
9751
+ * const attestation = await fetchRelayerMint(0, '0xabc...', false)
9752
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
9753
+ * ```
9754
+ */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
9755
+ const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
9756
+ const effectiveConfig = mergeAttestationConfig(config);
9757
+ let response;
9758
+ try {
9759
+ response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
9760
+ } catch (error) {
9761
+ // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
9762
+ if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
9763
+ throw new KitError({
9764
+ ...NetworkError.RELAYER_FORWARD_FAILED,
9765
+ recoverability: error.recoverability,
9766
+ message: error.message,
9767
+ cause: {
9768
+ ...error.cause,
9769
+ trace: {
9770
+ ...error.cause?.trace,
9771
+ burnTxHash: transactionHash
9772
+ }
9773
+ }
9774
+ });
9775
+ }
9776
+ throw error;
9777
+ }
9778
+ // Return the first message (which should have forwardTxHash)
9779
+ // Note: This check is needed for TypeScript type safety even though
9780
+ // isRelayerMintConfirmed validates messages[0] exists. The type guard
9781
+ // narrows the type at the call site, but TypeScript can't infer that
9782
+ // the array still has elements after pollApiGet returns.
9783
+ const message = response.messages[0];
9784
+ if (!message) {
9785
+ throw new KitError({
9786
+ ...InputError.VALIDATION_FAILED,
9787
+ recoverability: 'FATAL',
9788
+ message: 'No attestation messages found in response after polling.'
9789
+ });
9790
+ }
9791
+ return message;
9792
+ };
9793
+
9532
9794
  const assertCCTPv2WalletContextSymbol = Symbol('assertCCTPv2WalletContext');
9533
9795
  /**
9534
9796
  * Asserts that the provided parameters match the CCTPv2 wallet context interface.
@@ -10403,6 +10665,8 @@ function hasPendingState(analysis, result) {
10403
10665
  * - `adapter`: The adapter that will execute the transaction
10404
10666
  * - `confirmations`: The number of confirmations to wait for (defaults to 1)
10405
10667
  * - `timeout`: The timeout for the request in milliseconds
10668
+ * - `gasLimit`: Optional explicit gas limit (number) forwarded to EVM execute,
10669
+ * bypassing `eth_estimateGas`; ignored for non-EVM requests
10406
10670
  * @returns The bridge step with the transaction details and explorer URL
10407
10671
  * @throws If the transaction execution fails
10408
10672
  *
@@ -10417,7 +10681,7 @@ function hasPendingState(analysis, result) {
10417
10681
  * })
10418
10682
  * console.log('Transaction hash:', step.txHash)
10419
10683
  * ```
10420
- */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout }) {
10684
+ */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasLimit }) {
10421
10685
  const step = {
10422
10686
  name,
10423
10687
  state: 'pending'
@@ -10430,7 +10694,9 @@ function hasPendingState(analysis, result) {
10430
10694
  step.state = 'noop';
10431
10695
  return step;
10432
10696
  }
10433
- const txHash = await request.execute();
10697
+ const txHash = request.type === 'evm' && gasLimit !== undefined ? await request.execute({
10698
+ gasLimit
10699
+ }) : await request.execute();
10434
10700
  step.txHash = txHash;
10435
10701
  const retryOptions = {
10436
10702
  isRetryable: (err)=>isRetryableError$1(parseBlockchainError(err, {
@@ -10502,7 +10768,8 @@ function hasPendingState(analysis, result) {
10502
10768
  name: 'approve',
10503
10769
  adapter: params.source.adapter,
10504
10770
  chain: params.source.chain,
10505
- request: await provider.approve(params.source, approvalAmount)
10771
+ request: await provider.approve(params.source, approvalAmount),
10772
+ gasLimit: Number(APPROVE_GAS_LIMIT_EVM)
10506
10773
  });
10507
10774
  }
10508
10775
 
@@ -10529,7 +10796,8 @@ function hasPendingState(analysis, result) {
10529
10796
  name: 'burn',
10530
10797
  adapter: params.source.adapter,
10531
10798
  chain: params.source.chain,
10532
- request: await provider.burn(params)
10799
+ request: await provider.burn(params),
10800
+ gasLimit: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
10533
10801
  });
10534
10802
  }
10535
10803
 
@@ -10615,11 +10883,18 @@ function hasPendingState(analysis, result) {
10615
10883
  */ async function bridgeMint({ params, provider }, attestation) {
10616
10884
  // Validate attestation message matches transfer params
10617
10885
  await assertCCTPv2AttestationParams(attestation, params);
10886
+ const mintRequest = await provider.mint(params.source, params.destination, attestation);
10618
10887
  const step = await executePreparedChainRequest({
10619
10888
  name: 'mint',
10620
10889
  adapter: params.destination.adapter,
10621
10890
  chain: params.destination.chain,
10622
- request: await provider.mint(params.source, params.destination, attestation)
10891
+ request: mintRequest,
10892
+ // Some chains (e.g. Cronos) enforce an EIP-7623 calldata gas floor that
10893
+ // eth_estimateGas does not account for, returning a below-floor value
10894
+ // without reverting. Pinning to a value above the observed execution max
10895
+ // (310_839) bypasses re-estimation and guarantees we clear both the floor
10896
+ // and the actual execution cost.
10897
+ gasLimit: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
10623
10898
  });
10624
10899
  // Add forwarded: false for non-relayer mints
10625
10900
  return {
@@ -10657,136 +10932,6 @@ const mockAttestationMessage = {
10657
10932
  delayReason: null
10658
10933
  };
10659
10934
 
10660
- /**
10661
- * Type guard that checks if the relayer has confirmed the mint transaction.
10662
- *
10663
- * This function validates that:
10664
- * 1. The response has valid AttestationResponse structure
10665
- * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
10666
- *
10667
- * If forwardState is 'FAILED', throws a non-retryable KitError.
10668
- * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
10669
- *
10670
- * @param obj - The value to check, typically a parsed JSON response
10671
- * @returns True if the relayer has confirmed the mint
10672
- * @throws {KitError} With FATAL recoverability if structure is invalid
10673
- * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
10674
- * @throws {KitError} With RETRYABLE recoverability if still pending
10675
- * @internal
10676
- */ const isRelayerMintConfirmed = (obj)=>{
10677
- // First check if the structure is valid
10678
- if (!hasValidAttestationStructure(obj)) {
10679
- throw new KitError({
10680
- ...InputError.VALIDATION_FAILED,
10681
- recoverability: 'FATAL',
10682
- message: 'Invalid attestation response structure from IRIS API.'
10683
- });
10684
- }
10685
- // Find the first message (typically there's only one)
10686
- const message = obj.messages[0];
10687
- if (!message) {
10688
- throw new KitError({
10689
- ...InputError.VALIDATION_FAILED,
10690
- recoverability: 'FATAL',
10691
- message: 'No attestation messages found in IRIS API response.'
10692
- });
10693
- }
10694
- // Check for FAILED state - this is a permanent failure
10695
- if (message.forwardState === 'FAILED') {
10696
- throw new KitError({
10697
- ...NetworkError.RELAYER_FORWARD_FAILED,
10698
- recoverability: 'RESUMABLE',
10699
- message: 'Circle relayer failed to forward the mint transaction. The mint may still have succeeded if another party submitted it. Check the recipient wallet balance before retrying. If the mint did not occur, you can manually submit it using the attestation data in the error cause.',
10700
- cause: {
10701
- trace: {
10702
- eventNonce: message.eventNonce,
10703
- attestation: message.attestation,
10704
- message: message.message
10705
- }
10706
- }
10707
- });
10708
- }
10709
- // Check if mint is confirmed (or complete) with a valid transaction hash
10710
- // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
10711
- if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
10712
- return true;
10713
- }
10714
- // Still pending or not yet processed - throw RETRYABLE error to continue polling
10715
- throw new KitError({
10716
- ...NetworkError.RELAYER_PENDING,
10717
- recoverability: 'RETRYABLE',
10718
- message: 'Relayer mint not ready. Waiting for confirmation.'
10719
- });
10720
- };
10721
- /**
10722
- * Polls the attestation API until the relayer's mint transaction is confirmed.
10723
- *
10724
- * This function is used when `useForwarder` is enabled. Instead of the user
10725
- * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
10726
- * This function polls until the relayer has submitted and confirmed the mint transaction.
10727
- *
10728
- * @remarks
10729
- * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
10730
- * - Throws immediately if `forwardState` is 'FAILED'
10731
- * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
10732
- * - Returns the attestation message with `forwardTxHash` populated
10733
- *
10734
- * @param sourceDomainId - The CCTP domain ID of the source chain
10735
- * @param transactionHash - The transaction hash of the burn operation
10736
- * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
10737
- * @param config - Optional configuration overrides for polling behavior
10738
- * @returns The attestation message with confirmed forwardTxHash
10739
- * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
10740
- * @throws {KitError} If timeout is reached while still pending
10741
- *
10742
- * @example
10743
- * ```typescript
10744
- * const attestation = await fetchRelayerMint(0, '0xabc...', false)
10745
- * console.log('Relayer mint tx:', attestation.forwardTxHash)
10746
- * ```
10747
- */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
10748
- const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
10749
- const effectiveConfig = {
10750
- ...DEFAULT_CONFIG,
10751
- ...config
10752
- };
10753
- let response;
10754
- try {
10755
- response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
10756
- } catch (error) {
10757
- // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
10758
- if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
10759
- throw new KitError({
10760
- ...NetworkError.RELAYER_FORWARD_FAILED,
10761
- recoverability: error.recoverability,
10762
- message: error.message,
10763
- cause: {
10764
- ...error.cause,
10765
- trace: {
10766
- ...error.cause?.trace,
10767
- burnTxHash: transactionHash
10768
- }
10769
- }
10770
- });
10771
- }
10772
- throw error;
10773
- }
10774
- // Return the first message (which should have forwardTxHash)
10775
- // Note: This check is needed for TypeScript type safety even though
10776
- // isRelayerMintConfirmed validates messages[0] exists. The type guard
10777
- // narrows the type at the call site, but TypeScript can't infer that
10778
- // the array still has elements after pollApiGet returns.
10779
- const message = response.messages[0];
10780
- if (!message) {
10781
- throw new KitError({
10782
- ...InputError.VALIDATION_FAILED,
10783
- recoverability: 'FATAL',
10784
- message: 'No attestation messages found in response after polling.'
10785
- });
10786
- }
10787
- return message;
10788
- };
10789
-
10790
10935
  /**
10791
10936
  * Executes the mint step for forwarding mode where Circle's relayer handles the mint.
10792
10937
  *
@@ -10800,6 +10945,7 @@ const mockAttestationMessage = {
10800
10945
  * immediately after the API confirms the mint.
10801
10946
  *
10802
10947
  * @param params - The bridge parameters containing source, destination, amount and config
10948
+ * @param provider - The CCTP v2 bridging provider
10803
10949
  * @param context - The step context containing burnTxHash from the burn step
10804
10950
  * @returns Promise resolving to the bridge step with transaction details
10805
10951
  * @throws {KitError} If burnTxHash is not available in context
@@ -10808,10 +10954,13 @@ const mockAttestationMessage = {
10808
10954
  *
10809
10955
  * @example
10810
10956
  * ```typescript
10811
- * const step = await bridgeRelayerMint(params, { burnTxHash: '0x...' })
10957
+ * const step = await bridgeRelayerMint(
10958
+ * { params, provider },
10959
+ * { burnTxHash: '0x...' }
10960
+ * )
10812
10961
  * console.log('Relayer mint tx:', step.txHash)
10813
10962
  * ```
10814
- */ async function bridgeRelayerMint(params, context) {
10963
+ */ async function bridgeRelayerMint({ params, provider }, context) {
10815
10964
  const step = {
10816
10965
  name: 'mint',
10817
10966
  state: 'pending',
@@ -10827,7 +10976,7 @@ const mockAttestationMessage = {
10827
10976
  }
10828
10977
  try {
10829
10978
  // Poll attestation API until relayer confirms the mint
10830
- const attestation = await fetchRelayerMint(params.source.chain.cctp.domain, context.burnTxHash, params.source.chain.isTestnet ?? false);
10979
+ const attestation = await provider.fetchRelayerMint(params.source, context.burnTxHash);
10831
10980
  // Extract the relayer's mint transaction hash
10832
10981
  const forwardTxHash = attestation.forwardTxHash;
10833
10982
  if (!forwardTxHash) {
@@ -10921,7 +11070,7 @@ const mockAttestationMessage = {
10921
11070
  name: 'mint',
10922
11071
  // bridgeRelayerMint validates context.burnTxHash internally with a KitError
10923
11072
  // bridgeMint requires attestationData which is validated here
10924
- executor: useForwarder ? async (params, _provider, context)=>{
11073
+ executor: useForwarder ? async (params, provider, context)=>{
10925
11074
  if (!context) {
10926
11075
  throw new KitError({
10927
11076
  ...InputError.VALIDATION_FAILED,
@@ -10929,7 +11078,10 @@ const mockAttestationMessage = {
10929
11078
  message: 'Step context is required for relayer mint'
10930
11079
  });
10931
11080
  }
10932
- return bridgeRelayerMint(params, context);
11081
+ return bridgeRelayerMint({
11082
+ params,
11083
+ provider
11084
+ }, context);
10933
11085
  } : async (params, provider, context)=>{
10934
11086
  if (!context?.attestationData) {
10935
11087
  throw new KitError({
@@ -12138,7 +12290,7 @@ const mockAttestationMessage = {
12138
12290
  return step;
12139
12291
  }
12140
12292
 
12141
- var version = "1.8.4";
12293
+ var version = "1.9.0";
12142
12294
  var pkg = {
12143
12295
  version: version};
12144
12296
 
@@ -13164,6 +13316,39 @@ var pkg = {
13164
13316
  }
13165
13317
  }
13166
13318
 
13319
+ function isPlainObject(value) {
13320
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
13321
+ return false;
13322
+ }
13323
+ const prototype = Object.getPrototypeOf(value);
13324
+ return prototype === Object.prototype || prototype === null;
13325
+ }
13326
+ function assertHeadersConfig(headers, field) {
13327
+ if (headers === undefined) {
13328
+ return;
13329
+ }
13330
+ if (!isPlainObject(headers)) {
13331
+ throw createValidationFailedError(field, headers, `${field} must be a plain object with string header values when provided`);
13332
+ }
13333
+ for (const [name, value] of Object.entries(headers)){
13334
+ if (typeof value !== 'string') {
13335
+ throw createValidationFailedError(`${field}.${name}`, value, 'header values must be strings');
13336
+ }
13337
+ }
13338
+ }
13339
+ function assertCCTPV2Config(config) {
13340
+ if (!isPlainObject(config)) {
13341
+ throw createValidationFailedError('config', config, 'config must be a plain object when provided');
13342
+ }
13343
+ assertHeadersConfig(config['headers'], 'config.headers');
13344
+ const attestation = config['attestation'];
13345
+ if (attestation !== undefined) {
13346
+ if (!isPlainObject(attestation)) {
13347
+ throw createValidationFailedError('config.attestation', attestation, 'config.attestation must be a plain object when provided');
13348
+ }
13349
+ assertHeadersConfig(attestation['headers'], 'config.attestation.headers');
13350
+ }
13351
+ }
13167
13352
  /**
13168
13353
  * All chains that are supported by the CCTP v2 provider.
13169
13354
  *
@@ -13232,9 +13417,38 @@ var pkg = {
13232
13417
  * @param config - Optional configuration overrides for the provider
13233
13418
  */ constructor(config = {}){
13234
13419
  super();
13420
+ assertCCTPV2Config(config);
13235
13421
  this.config = config;
13236
13422
  }
13237
13423
  /**
13424
+ * Resolves the effective polling configuration for an attestation request.
13425
+ *
13426
+ * Precedence (lowest to highest): provider `config.attestation`, then the
13427
+ * per-call `config`. Headers merge independently across
13428
+ * `config.attestation.headers`, the provider-level `config.headers`, and any
13429
+ * per-call `config.headers`, so a more specific header augments rather than
13430
+ * replaces the broader ones. The `headers` key is omitted entirely when no
13431
+ * headers are configured, leaving the attestation fetchers' defaults intact.
13432
+ *
13433
+ * @param config - Optional per-call polling configuration overrides
13434
+ * @returns The merged polling configuration passed to the attestation fetchers
13435
+ */ resolveAttestationConfig(config) {
13436
+ const headers = {
13437
+ ...this.config?.attestation?.headers,
13438
+ ...this.config?.headers,
13439
+ ...config?.headers
13440
+ };
13441
+ // Polling fields follow normal precedence; headers are merged separately
13442
+ // below so narrower config layers augment rather than replace broader ones.
13443
+ return {
13444
+ ...this.config?.attestation,
13445
+ ...config,
13446
+ ...Object.keys(headers).length > 0 ? {
13447
+ headers
13448
+ } : {}
13449
+ };
13450
+ }
13451
+ /**
13238
13452
  * Execute a cross-chain USDC bridge operation using the CCTP v2 protocol.
13239
13453
  *
13240
13454
  * This method orchestrates the complete CCTP v2 bridge flow including validation,
@@ -13660,11 +13874,7 @@ var pkg = {
13660
13874
  */ async fetchAttestation(source, transactionHash, config) {
13661
13875
  assertCCTPv2WalletContext(source);
13662
13876
  try {
13663
- // Merge configs: defaults <- global config <- per-call config
13664
- const effectiveConfig = {
13665
- ...this.config?.attestation,
13666
- ...config
13667
- };
13877
+ const effectiveConfig = this.resolveAttestationConfig(config);
13668
13878
  const response = await fetchAttestation(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
13669
13879
  const message = response.messages[0];
13670
13880
  if (!message) {
@@ -13681,6 +13891,49 @@ var pkg = {
13681
13891
  }
13682
13892
  }
13683
13893
  /**
13894
+ * Polls attestation data until Circle's relayer mint transaction is confirmed.
13895
+ *
13896
+ * This method is used by forwarded transfers. It polls the same Iris
13897
+ * attestation endpoint as {@link CCTPV2BridgingProvider.fetchAttestation},
13898
+ * but waits for a completed relayer forward state and returns the attestation
13899
+ * message containing `forwardTxHash`.
13900
+ *
13901
+ * @typeParam TFromAdapterCapabilities - The type representing the capabilities of the source adapter
13902
+ * @param source - The source wallet context containing the chain definition and wallet address
13903
+ * @param transactionHash - The transaction hash of the burn operation
13904
+ * @param config - Optional polling configuration overrides for timeout, retries, delay, and headers
13905
+ * @returns A promise that resolves to the attestation message with `forwardTxHash`
13906
+ * @throws KitError If the relayer forward fails, the response is invalid, or polling times out
13907
+ *
13908
+ * @example
13909
+ * ```typescript
13910
+ * import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2'
13911
+ * import { Chains } from '@core/chains'
13912
+ *
13913
+ * const provider = new CCTPV2BridgingProvider({
13914
+ * headers: { 'X-Partner-UUID': '00000000-0000-0000-0000-000000000000' },
13915
+ * })
13916
+ *
13917
+ * const attestation = await provider.fetchRelayerMint(
13918
+ * {
13919
+ * adapter,
13920
+ * chain: Chains.EthereumSepolia,
13921
+ * address: '0x1234...',
13922
+ * },
13923
+ * '0xabc123...',
13924
+ * )
13925
+ *
13926
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
13927
+ * ```
13928
+ */ async fetchRelayerMint(source, transactionHash, config) {
13929
+ assertCCTPv2WalletContext(source);
13930
+ if (typeof transactionHash !== 'string' || transactionHash.trim() === '') {
13931
+ throw createValidationFailedError('transactionHash', transactionHash, 'transactionHash must be a non-empty string');
13932
+ }
13933
+ const effectiveConfig = this.resolveAttestationConfig(config);
13934
+ return await fetchRelayerMint(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
13935
+ }
13936
+ /**
13684
13937
  * Requests a fresh attestation for an expired attestation.
13685
13938
  *
13686
13939
  * This method is used when the original attestation has expired before the mint
@@ -13734,11 +13987,7 @@ var pkg = {
13734
13987
  throw new Error('Failed to re-attest: Invalid transaction hash');
13735
13988
  }
13736
13989
  try {
13737
- // Merge configs: defaults <- global config <- per-call config
13738
- const effectiveConfig = {
13739
- ...this.config?.attestation,
13740
- ...config
13741
- };
13990
+ const effectiveConfig = this.resolveAttestationConfig(config);
13742
13991
  // Step 1: Get existing attestation data to extract nonce
13743
13992
  const existingAttestation = await fetchAttestationWithoutStatusCheck(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
13744
13993
  const nonce = existingAttestation.messages[0]?.eventNonce;