@circle-fin/app-kit 1.8.1 → 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.
@@ -24,6 +24,7 @@ var units = require('@ethersproject/units');
24
24
  var bytes = require('@ethersproject/bytes');
25
25
  var address = require('@ethersproject/address');
26
26
  var bs58 = require('bs58');
27
+ require('@ethersproject/abi');
27
28
  var web3_js = require('@solana/web3.js');
28
29
  require('bn.js');
29
30
  require('@coral-xyz/anchor');
@@ -2479,6 +2480,8 @@ class KitError extends Error {
2479
2480
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
2480
2481
  Blockchain["Codex"] = "Codex";
2481
2482
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
2483
+ Blockchain["Cronos"] = "Cronos";
2484
+ Blockchain["Cronos_Testnet"] = "Cronos_Testnet";
2482
2485
  Blockchain["Edge"] = "Edge";
2483
2486
  Blockchain["Edge_Testnet"] = "Edge_Testnet";
2484
2487
  Blockchain["Ethereum"] = "Ethereum";
@@ -2561,6 +2564,7 @@ var BridgeChain;
2561
2564
  BridgeChain["Avalanche"] = "Avalanche";
2562
2565
  BridgeChain["Base"] = "Base";
2563
2566
  BridgeChain["Codex"] = "Codex";
2567
+ BridgeChain["Cronos"] = "Cronos";
2564
2568
  BridgeChain["Edge"] = "Edge";
2565
2569
  BridgeChain["Ethereum"] = "Ethereum";
2566
2570
  BridgeChain["HyperEVM"] = "HyperEVM";
@@ -2585,6 +2589,7 @@ var BridgeChain;
2585
2589
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
2586
2590
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
2587
2591
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
2592
+ BridgeChain["Cronos_Testnet"] = "Cronos_Testnet";
2588
2593
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
2589
2594
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
2590
2595
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
@@ -3630,6 +3635,96 @@ var EarnChain;
3630
3635
  }
3631
3636
  });
3632
3637
 
3638
+ /**
3639
+ * Cronos Mainnet chain definition
3640
+ * @remarks
3641
+ * This represents the official production network for the Cronos blockchain.
3642
+ * Cronos is an EVM-compatible blockchain.
3643
+ */ const Cronos = defineChain({
3644
+ type: 'evm',
3645
+ chain: Blockchain.Cronos,
3646
+ name: 'Cronos',
3647
+ title: 'Cronos Mainnet',
3648
+ nativeCurrency: {
3649
+ name: 'Cronos',
3650
+ symbol: 'CRO',
3651
+ decimals: 18
3652
+ },
3653
+ chainId: 25,
3654
+ isTestnet: false,
3655
+ explorerUrl: 'https://cronoscan.com/tx/{hash}',
3656
+ rpcEndpoints: [
3657
+ 'https://evm.cronos.org'
3658
+ ],
3659
+ eurcAddress: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
3660
+ usdcAddress: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
3661
+ usdtAddress: null,
3662
+ cctp: {
3663
+ domain: 32,
3664
+ contracts: {
3665
+ v2: {
3666
+ type: 'split',
3667
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
3668
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
3669
+ confirmations: 1,
3670
+ fastConfirmations: 1
3671
+ }
3672
+ },
3673
+ forwarderSupported: {
3674
+ source: false,
3675
+ destination: false
3676
+ }
3677
+ },
3678
+ kitContracts: {
3679
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
3680
+ }
3681
+ });
3682
+
3683
+ /**
3684
+ * Cronos Testnet chain definition
3685
+ * @remarks
3686
+ * This represents the official test network for the Cronos blockchain.
3687
+ * Cronos is an EVM-compatible blockchain.
3688
+ */ const CronosTestnet = defineChain({
3689
+ type: 'evm',
3690
+ chain: Blockchain.Cronos_Testnet,
3691
+ name: 'Cronos Testnet',
3692
+ title: 'Cronos Testnet',
3693
+ nativeCurrency: {
3694
+ name: 'CRO',
3695
+ symbol: 'tCRO',
3696
+ decimals: 18
3697
+ },
3698
+ chainId: 338,
3699
+ isTestnet: true,
3700
+ explorerUrl: 'https://explorer.cronos.org/testnet/tx/{hash}',
3701
+ rpcEndpoints: [
3702
+ 'https://evm-t3.cronos.org'
3703
+ ],
3704
+ eurcAddress: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
3705
+ usdcAddress: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
3706
+ usdtAddress: null,
3707
+ cctp: {
3708
+ domain: 32,
3709
+ contracts: {
3710
+ v2: {
3711
+ type: 'split',
3712
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
3713
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
3714
+ confirmations: 1,
3715
+ fastConfirmations: 1
3716
+ }
3717
+ },
3718
+ forwarderSupported: {
3719
+ source: false,
3720
+ destination: false
3721
+ }
3722
+ },
3723
+ kitContracts: {
3724
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
3725
+ }
3726
+ });
3727
+
3633
3728
  /**
3634
3729
  * Edge Mainnet chain definition
3635
3730
  * @remarks
@@ -5981,6 +6076,8 @@ var Chains = {
5981
6076
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
5982
6077
  Codex: Codex,
5983
6078
  CodexTestnet: CodexTestnet,
6079
+ Cronos: Cronos,
6080
+ CronosTestnet: CronosTestnet,
5984
6081
  Edge: Edge,
5985
6082
  EdgeTestnet: EdgeTestnet,
5986
6083
  Ethereum: Ethereum,
@@ -8046,6 +8143,7 @@ const swapTokenEnumSchema = zod.z.enum([
8046
8143
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
8047
8144
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
8048
8145
  [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
8146
+ [Blockchain.Cronos]: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
8049
8147
  [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
8050
8148
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
8051
8149
  [Blockchain.Hedera]: '0.0.456858',
@@ -8079,6 +8177,7 @@ const swapTokenEnumSchema = zod.z.enum([
8079
8177
  [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
8080
8178
  [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
8081
8179
  [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
8180
+ [Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
8082
8181
  [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
8083
8182
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
8084
8183
  [Blockchain.Hedera_Testnet]: '0.0.429274',
@@ -8151,6 +8250,7 @@ const swapTokenEnumSchema = zod.z.enum([
8151
8250
  // =========================================================================
8152
8251
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
8153
8252
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
8253
+ [Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
8154
8254
  [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
8155
8255
  [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
8156
8256
  [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
@@ -8159,6 +8259,7 @@ const swapTokenEnumSchema = zod.z.enum([
8159
8259
  // =========================================================================
8160
8260
  [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
8161
8261
  [Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
8262
+ [Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
8162
8263
  [Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
8163
8264
  }
8164
8265
  };
@@ -9509,7 +9610,7 @@ function resolveOptions(options) {
9509
9610
  }
9510
9611
 
9511
9612
  var name$2 = "@circle-fin/bridge-kit";
9512
- var version$3 = "1.11.1";
9613
+ var version$3 = "1.12.0";
9513
9614
  var pkg$3 = {
9514
9615
  name: name$2,
9515
9616
  version: version$3};
@@ -12169,6 +12270,15 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
12169
12270
  ;
12170
12271
  const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
12171
12272
  ;
12273
+ // Hard execution caps: observed max + ~30% buffer, used as gasLimit overrides on
12274
+ // chains whose eth_estimateGas under-reports (e.g. Cronos EIP-7623 calldata floor).
12275
+ // Kept separate from the fee-estimate averages above.
12276
+ const APPROVE_GAS_LIMIT_EVM = 100_000n // ERC-20 approve observed max ~46k
12277
+ ;
12278
+ const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 300_000n // observed max 226_506 + ~30%
12279
+ ;
12280
+ const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839 + ~30%
12281
+ ;
12172
12282
  /**
12173
12283
  * The minimum finality threshold for CCTPv2 transfers.
12174
12284
  *
@@ -12197,6 +12307,27 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12197
12307
  'Content-Type': 'application/json'
12198
12308
  }
12199
12309
  };
12310
+ /**
12311
+ * Merges caller-provided polling overrides on top of {@link DEFAULT_CONFIG}.
12312
+ *
12313
+ * Headers are merged independently so caller-supplied headers augment the
12314
+ * defaults (such as `Content-Type`) rather than replacing them wholesale.
12315
+ *
12316
+ * @param config - Caller-provided polling configuration overrides
12317
+ * @param internalDefaults - Internal defaults applied before `config` (for example a
12318
+ * reduced `maxRetries` for one-shot requests); `config` still wins on conflict
12319
+ * @returns The effective polling configuration
12320
+ * @internal
12321
+ */ const mergeAttestationConfig = (config, internalDefaults = {})=>({
12322
+ ...DEFAULT_CONFIG,
12323
+ ...internalDefaults,
12324
+ ...config,
12325
+ headers: {
12326
+ ...DEFAULT_CONFIG.headers,
12327
+ ...internalDefaults.headers,
12328
+ ...config.headers
12329
+ }
12330
+ });
12200
12331
  /**
12201
12332
  * Type guard that verifies if an unknown value matches the AttestationMessage shape
12202
12333
  * and has all required properties.
@@ -12343,10 +12474,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12343
12474
  * ```
12344
12475
  */ const fetchAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12345
12476
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12346
- const effectiveConfig = {
12347
- ...DEFAULT_CONFIG,
12348
- ...config
12349
- };
12477
+ const effectiveConfig = mergeAttestationConfig(config);
12350
12478
  return await pollApiGet(url, isAttestationResponse, effectiveConfig);
12351
12479
  };
12352
12480
  /**
@@ -12389,11 +12517,9 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12389
12517
  */ const fetchAttestationWithoutStatusCheck = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12390
12518
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12391
12519
  // Use minimal retries since we're just fetching existing data
12392
- const effectiveConfig = {
12393
- ...DEFAULT_CONFIG,
12394
- maxRetries: 3,
12395
- ...config
12396
- };
12520
+ const effectiveConfig = mergeAttestationConfig(config, {
12521
+ maxRetries: 3
12522
+ });
12397
12523
  return await pollApiGet(url, isAttestationResponseWithoutStatusCheck, effectiveConfig);
12398
12524
  };
12399
12525
  /**
@@ -12453,10 +12579,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12453
12579
  * ```
12454
12580
  */ const fetchReAttestedAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12455
12581
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12456
- const effectiveConfig = {
12457
- ...DEFAULT_CONFIG,
12458
- ...config
12459
- };
12582
+ const effectiveConfig = mergeAttestationConfig(config);
12460
12583
  return await pollApiGet(url, isReAttestedAttestationResponse, effectiveConfig);
12461
12584
  };
12462
12585
  /**
@@ -12522,14 +12645,139 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12522
12645
  */ const requestReAttestation = async (nonce, isTestnet, config = {})=>{
12523
12646
  const url = buildReAttestUrl(nonce, isTestnet);
12524
12647
  // Use minimal retries since we're just submitting a request, not polling for state
12525
- const effectiveConfig = {
12526
- ...DEFAULT_CONFIG,
12527
- maxRetries: 3,
12528
- ...config
12529
- };
12648
+ const effectiveConfig = mergeAttestationConfig(config, {
12649
+ maxRetries: 3
12650
+ });
12530
12651
  return await pollApiPost(url, {}, isReAttestationResponse, effectiveConfig);
12531
12652
  };
12532
12653
 
12654
+ /**
12655
+ * Type guard that checks if the relayer has confirmed the mint transaction.
12656
+ *
12657
+ * This function validates that:
12658
+ * 1. The response has valid AttestationResponse structure
12659
+ * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
12660
+ *
12661
+ * If forwardState is 'FAILED', throws a non-retryable KitError.
12662
+ * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
12663
+ *
12664
+ * @param obj - The value to check, typically a parsed JSON response
12665
+ * @returns True if the relayer has confirmed the mint
12666
+ * @throws {KitError} With FATAL recoverability if structure is invalid
12667
+ * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
12668
+ * @throws {KitError} With RETRYABLE recoverability if still pending
12669
+ * @internal
12670
+ */ const isRelayerMintConfirmed = (obj)=>{
12671
+ // First check if the structure is valid
12672
+ if (!hasValidAttestationStructure(obj)) {
12673
+ throw new KitError({
12674
+ ...InputError.VALIDATION_FAILED,
12675
+ recoverability: 'FATAL',
12676
+ message: 'Invalid attestation response structure from IRIS API.'
12677
+ });
12678
+ }
12679
+ // Find the first message (typically there's only one)
12680
+ const message = obj.messages[0];
12681
+ if (!message) {
12682
+ throw new KitError({
12683
+ ...InputError.VALIDATION_FAILED,
12684
+ recoverability: 'FATAL',
12685
+ message: 'No attestation messages found in IRIS API response.'
12686
+ });
12687
+ }
12688
+ // Check for FAILED state - this is a permanent failure
12689
+ if (message.forwardState === 'FAILED') {
12690
+ throw new KitError({
12691
+ ...NetworkError.RELAYER_FORWARD_FAILED,
12692
+ recoverability: 'RESUMABLE',
12693
+ 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.',
12694
+ cause: {
12695
+ trace: {
12696
+ eventNonce: message.eventNonce,
12697
+ attestation: message.attestation,
12698
+ message: message.message
12699
+ }
12700
+ }
12701
+ });
12702
+ }
12703
+ // Check if mint is confirmed (or complete) with a valid transaction hash
12704
+ // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
12705
+ if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
12706
+ return true;
12707
+ }
12708
+ // Still pending or not yet processed - throw RETRYABLE error to continue polling
12709
+ throw new KitError({
12710
+ ...NetworkError.RELAYER_PENDING,
12711
+ recoverability: 'RETRYABLE',
12712
+ message: 'Relayer mint not ready. Waiting for confirmation.'
12713
+ });
12714
+ };
12715
+ /**
12716
+ * Polls the attestation API until the relayer's mint transaction is confirmed.
12717
+ *
12718
+ * This function is used when `useForwarder` is enabled. Instead of the user
12719
+ * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
12720
+ * This function polls until the relayer has submitted and confirmed the mint transaction.
12721
+ *
12722
+ * @remarks
12723
+ * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
12724
+ * - Throws immediately if `forwardState` is 'FAILED'
12725
+ * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
12726
+ * - Returns the attestation message with `forwardTxHash` populated
12727
+ *
12728
+ * @param sourceDomainId - The CCTP domain ID of the source chain
12729
+ * @param transactionHash - The transaction hash of the burn operation
12730
+ * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
12731
+ * @param config - Optional configuration overrides for polling behavior
12732
+ * @returns The attestation message with confirmed forwardTxHash
12733
+ * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
12734
+ * @throws {KitError} If timeout is reached while still pending
12735
+ *
12736
+ * @example
12737
+ * ```typescript
12738
+ * const attestation = await fetchRelayerMint(0, '0xabc...', false)
12739
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
12740
+ * ```
12741
+ */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12742
+ const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12743
+ const effectiveConfig = mergeAttestationConfig(config);
12744
+ let response;
12745
+ try {
12746
+ response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
12747
+ } catch (error) {
12748
+ // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
12749
+ if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
12750
+ throw new KitError({
12751
+ ...NetworkError.RELAYER_FORWARD_FAILED,
12752
+ recoverability: error.recoverability,
12753
+ message: error.message,
12754
+ cause: {
12755
+ ...error.cause,
12756
+ trace: {
12757
+ ...error.cause?.trace,
12758
+ burnTxHash: transactionHash
12759
+ }
12760
+ }
12761
+ });
12762
+ }
12763
+ throw error;
12764
+ }
12765
+ // Return the first message (which should have forwardTxHash)
12766
+ // Note: This check is needed for TypeScript type safety even though
12767
+ // isRelayerMintConfirmed validates messages[0] exists. The type guard
12768
+ // narrows the type at the call site, but TypeScript can't infer that
12769
+ // the array still has elements after pollApiGet returns.
12770
+ const message = response.messages[0];
12771
+ if (!message) {
12772
+ throw new KitError({
12773
+ ...InputError.VALIDATION_FAILED,
12774
+ recoverability: 'FATAL',
12775
+ message: 'No attestation messages found in response after polling.'
12776
+ });
12777
+ }
12778
+ return message;
12779
+ };
12780
+
12533
12781
  const assertCCTPv2WalletContextSymbol = Symbol('assertCCTPv2WalletContext');
12534
12782
  /**
12535
12783
  * Asserts that the provided parameters match the CCTPv2 wallet context interface.
@@ -13404,6 +13652,8 @@ function hasPendingState(analysis, result) {
13404
13652
  * - `adapter`: The adapter that will execute the transaction
13405
13653
  * - `confirmations`: The number of confirmations to wait for (defaults to 1)
13406
13654
  * - `timeout`: The timeout for the request in milliseconds
13655
+ * - `gasLimit`: Optional explicit gas limit (number) forwarded to EVM execute,
13656
+ * bypassing `eth_estimateGas`; ignored for non-EVM requests
13407
13657
  * @returns The bridge step with the transaction details and explorer URL
13408
13658
  * @throws If the transaction execution fails
13409
13659
  *
@@ -13418,7 +13668,7 @@ function hasPendingState(analysis, result) {
13418
13668
  * })
13419
13669
  * console.log('Transaction hash:', step.txHash)
13420
13670
  * ```
13421
- */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout }) {
13671
+ */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasLimit }) {
13422
13672
  const step = {
13423
13673
  name,
13424
13674
  state: 'pending'
@@ -13431,7 +13681,9 @@ function hasPendingState(analysis, result) {
13431
13681
  step.state = 'noop';
13432
13682
  return step;
13433
13683
  }
13434
- const txHash = await request.execute();
13684
+ const txHash = request.type === 'evm' && gasLimit !== undefined ? await request.execute({
13685
+ gasLimit
13686
+ }) : await request.execute();
13435
13687
  step.txHash = txHash;
13436
13688
  const retryOptions = {
13437
13689
  isRetryable: (err)=>isRetryableError$1(parseBlockchainError(err, {
@@ -13503,7 +13755,8 @@ function hasPendingState(analysis, result) {
13503
13755
  name: 'approve',
13504
13756
  adapter: params.source.adapter,
13505
13757
  chain: params.source.chain,
13506
- request: await provider.approve(params.source, approvalAmount)
13758
+ request: await provider.approve(params.source, approvalAmount),
13759
+ gasLimit: Number(APPROVE_GAS_LIMIT_EVM)
13507
13760
  });
13508
13761
  }
13509
13762
 
@@ -13530,7 +13783,8 @@ function hasPendingState(analysis, result) {
13530
13783
  name: 'burn',
13531
13784
  adapter: params.source.adapter,
13532
13785
  chain: params.source.chain,
13533
- request: await provider.burn(params)
13786
+ request: await provider.burn(params),
13787
+ gasLimit: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
13534
13788
  });
13535
13789
  }
13536
13790
 
@@ -13616,11 +13870,18 @@ function hasPendingState(analysis, result) {
13616
13870
  */ async function bridgeMint({ params, provider }, attestation) {
13617
13871
  // Validate attestation message matches transfer params
13618
13872
  await assertCCTPv2AttestationParams(attestation, params);
13873
+ const mintRequest = await provider.mint(params.source, params.destination, attestation);
13619
13874
  const step = await executePreparedChainRequest({
13620
13875
  name: 'mint',
13621
13876
  adapter: params.destination.adapter,
13622
13877
  chain: params.destination.chain,
13623
- request: await provider.mint(params.source, params.destination, attestation)
13878
+ request: mintRequest,
13879
+ // Some chains (e.g. Cronos) enforce an EIP-7623 calldata gas floor that
13880
+ // eth_estimateGas does not account for, returning a below-floor value
13881
+ // without reverting. Pinning to a value above the observed execution max
13882
+ // (310_839) bypasses re-estimation and guarantees we clear both the floor
13883
+ // and the actual execution cost.
13884
+ gasLimit: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
13624
13885
  });
13625
13886
  // Add forwarded: false for non-relayer mints
13626
13887
  return {
@@ -13658,136 +13919,6 @@ const mockAttestationMessage = {
13658
13919
  delayReason: null
13659
13920
  };
13660
13921
 
13661
- /**
13662
- * Type guard that checks if the relayer has confirmed the mint transaction.
13663
- *
13664
- * This function validates that:
13665
- * 1. The response has valid AttestationResponse structure
13666
- * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
13667
- *
13668
- * If forwardState is 'FAILED', throws a non-retryable KitError.
13669
- * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
13670
- *
13671
- * @param obj - The value to check, typically a parsed JSON response
13672
- * @returns True if the relayer has confirmed the mint
13673
- * @throws {KitError} With FATAL recoverability if structure is invalid
13674
- * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
13675
- * @throws {KitError} With RETRYABLE recoverability if still pending
13676
- * @internal
13677
- */ const isRelayerMintConfirmed = (obj)=>{
13678
- // First check if the structure is valid
13679
- if (!hasValidAttestationStructure(obj)) {
13680
- throw new KitError({
13681
- ...InputError.VALIDATION_FAILED,
13682
- recoverability: 'FATAL',
13683
- message: 'Invalid attestation response structure from IRIS API.'
13684
- });
13685
- }
13686
- // Find the first message (typically there's only one)
13687
- const message = obj.messages[0];
13688
- if (!message) {
13689
- throw new KitError({
13690
- ...InputError.VALIDATION_FAILED,
13691
- recoverability: 'FATAL',
13692
- message: 'No attestation messages found in IRIS API response.'
13693
- });
13694
- }
13695
- // Check for FAILED state - this is a permanent failure
13696
- if (message.forwardState === 'FAILED') {
13697
- throw new KitError({
13698
- ...NetworkError.RELAYER_FORWARD_FAILED,
13699
- recoverability: 'RESUMABLE',
13700
- 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.',
13701
- cause: {
13702
- trace: {
13703
- eventNonce: message.eventNonce,
13704
- attestation: message.attestation,
13705
- message: message.message
13706
- }
13707
- }
13708
- });
13709
- }
13710
- // Check if mint is confirmed (or complete) with a valid transaction hash
13711
- // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
13712
- if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
13713
- return true;
13714
- }
13715
- // Still pending or not yet processed - throw RETRYABLE error to continue polling
13716
- throw new KitError({
13717
- ...NetworkError.RELAYER_PENDING,
13718
- recoverability: 'RETRYABLE',
13719
- message: 'Relayer mint not ready. Waiting for confirmation.'
13720
- });
13721
- };
13722
- /**
13723
- * Polls the attestation API until the relayer's mint transaction is confirmed.
13724
- *
13725
- * This function is used when `useForwarder` is enabled. Instead of the user
13726
- * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
13727
- * This function polls until the relayer has submitted and confirmed the mint transaction.
13728
- *
13729
- * @remarks
13730
- * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
13731
- * - Throws immediately if `forwardState` is 'FAILED'
13732
- * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
13733
- * - Returns the attestation message with `forwardTxHash` populated
13734
- *
13735
- * @param sourceDomainId - The CCTP domain ID of the source chain
13736
- * @param transactionHash - The transaction hash of the burn operation
13737
- * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
13738
- * @param config - Optional configuration overrides for polling behavior
13739
- * @returns The attestation message with confirmed forwardTxHash
13740
- * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
13741
- * @throws {KitError} If timeout is reached while still pending
13742
- *
13743
- * @example
13744
- * ```typescript
13745
- * const attestation = await fetchRelayerMint(0, '0xabc...', false)
13746
- * console.log('Relayer mint tx:', attestation.forwardTxHash)
13747
- * ```
13748
- */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
13749
- const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
13750
- const effectiveConfig = {
13751
- ...DEFAULT_CONFIG,
13752
- ...config
13753
- };
13754
- let response;
13755
- try {
13756
- response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
13757
- } catch (error) {
13758
- // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
13759
- if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
13760
- throw new KitError({
13761
- ...NetworkError.RELAYER_FORWARD_FAILED,
13762
- recoverability: error.recoverability,
13763
- message: error.message,
13764
- cause: {
13765
- ...error.cause,
13766
- trace: {
13767
- ...error.cause?.trace,
13768
- burnTxHash: transactionHash
13769
- }
13770
- }
13771
- });
13772
- }
13773
- throw error;
13774
- }
13775
- // Return the first message (which should have forwardTxHash)
13776
- // Note: This check is needed for TypeScript type safety even though
13777
- // isRelayerMintConfirmed validates messages[0] exists. The type guard
13778
- // narrows the type at the call site, but TypeScript can't infer that
13779
- // the array still has elements after pollApiGet returns.
13780
- const message = response.messages[0];
13781
- if (!message) {
13782
- throw new KitError({
13783
- ...InputError.VALIDATION_FAILED,
13784
- recoverability: 'FATAL',
13785
- message: 'No attestation messages found in response after polling.'
13786
- });
13787
- }
13788
- return message;
13789
- };
13790
-
13791
13922
  /**
13792
13923
  * Executes the mint step for forwarding mode where Circle's relayer handles the mint.
13793
13924
  *
@@ -13801,6 +13932,7 @@ const mockAttestationMessage = {
13801
13932
  * immediately after the API confirms the mint.
13802
13933
  *
13803
13934
  * @param params - The bridge parameters containing source, destination, amount and config
13935
+ * @param provider - The CCTP v2 bridging provider
13804
13936
  * @param context - The step context containing burnTxHash from the burn step
13805
13937
  * @returns Promise resolving to the bridge step with transaction details
13806
13938
  * @throws {KitError} If burnTxHash is not available in context
@@ -13809,10 +13941,13 @@ const mockAttestationMessage = {
13809
13941
  *
13810
13942
  * @example
13811
13943
  * ```typescript
13812
- * const step = await bridgeRelayerMint(params, { burnTxHash: '0x...' })
13944
+ * const step = await bridgeRelayerMint(
13945
+ * { params, provider },
13946
+ * { burnTxHash: '0x...' }
13947
+ * )
13813
13948
  * console.log('Relayer mint tx:', step.txHash)
13814
13949
  * ```
13815
- */ async function bridgeRelayerMint(params, context) {
13950
+ */ async function bridgeRelayerMint({ params, provider }, context) {
13816
13951
  const step = {
13817
13952
  name: 'mint',
13818
13953
  state: 'pending',
@@ -13828,7 +13963,7 @@ const mockAttestationMessage = {
13828
13963
  }
13829
13964
  try {
13830
13965
  // Poll attestation API until relayer confirms the mint
13831
- const attestation = await fetchRelayerMint(params.source.chain.cctp.domain, context.burnTxHash, params.source.chain.isTestnet ?? false);
13966
+ const attestation = await provider.fetchRelayerMint(params.source, context.burnTxHash);
13832
13967
  // Extract the relayer's mint transaction hash
13833
13968
  const forwardTxHash = attestation.forwardTxHash;
13834
13969
  if (!forwardTxHash) {
@@ -13922,7 +14057,7 @@ const mockAttestationMessage = {
13922
14057
  name: 'mint',
13923
14058
  // bridgeRelayerMint validates context.burnTxHash internally with a KitError
13924
14059
  // bridgeMint requires attestationData which is validated here
13925
- executor: useForwarder ? async (params, _provider, context)=>{
14060
+ executor: useForwarder ? async (params, provider, context)=>{
13926
14061
  if (!context) {
13927
14062
  throw new KitError({
13928
14063
  ...InputError.VALIDATION_FAILED,
@@ -13930,7 +14065,10 @@ const mockAttestationMessage = {
13930
14065
  message: 'Step context is required for relayer mint'
13931
14066
  });
13932
14067
  }
13933
- return bridgeRelayerMint(params, context);
14068
+ return bridgeRelayerMint({
14069
+ params,
14070
+ provider
14071
+ }, context);
13934
14072
  } : async (params, provider, context)=>{
13935
14073
  if (!context?.attestationData) {
13936
14074
  throw new KitError({
@@ -14261,7 +14399,7 @@ const mockAttestationMessage = {
14261
14399
  return step;
14262
14400
  }
14263
14401
 
14264
- var version$2 = "1.8.5";
14402
+ var version$2 = "1.9.0";
14265
14403
  var pkg$2 = {
14266
14404
  version: version$2};
14267
14405
 
@@ -14967,6 +15105,39 @@ var pkg$2 = {
14967
15105
  }
14968
15106
  }
14969
15107
 
15108
+ function isPlainObject(value) {
15109
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
15110
+ return false;
15111
+ }
15112
+ const prototype = Object.getPrototypeOf(value);
15113
+ return prototype === Object.prototype || prototype === null;
15114
+ }
15115
+ function assertHeadersConfig(headers, field) {
15116
+ if (headers === undefined) {
15117
+ return;
15118
+ }
15119
+ if (!isPlainObject(headers)) {
15120
+ throw createValidationFailedError$1(field, headers, `${field} must be a plain object with string header values when provided`);
15121
+ }
15122
+ for (const [name, value] of Object.entries(headers)){
15123
+ if (typeof value !== 'string') {
15124
+ throw createValidationFailedError$1(`${field}.${name}`, value, 'header values must be strings');
15125
+ }
15126
+ }
15127
+ }
15128
+ function assertCCTPV2Config(config) {
15129
+ if (!isPlainObject(config)) {
15130
+ throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
15131
+ }
15132
+ assertHeadersConfig(config['headers'], 'config.headers');
15133
+ const attestation = config['attestation'];
15134
+ if (attestation !== undefined) {
15135
+ if (!isPlainObject(attestation)) {
15136
+ throw createValidationFailedError$1('config.attestation', attestation, 'config.attestation must be a plain object when provided');
15137
+ }
15138
+ assertHeadersConfig(attestation['headers'], 'config.attestation.headers');
15139
+ }
15140
+ }
14970
15141
  /**
14971
15142
  * All chains that are supported by the CCTP v2 provider.
14972
15143
  *
@@ -15035,9 +15206,38 @@ var pkg$2 = {
15035
15206
  * @param config - Optional configuration overrides for the provider
15036
15207
  */ constructor(config = {}){
15037
15208
  super();
15209
+ assertCCTPV2Config(config);
15038
15210
  this.config = config;
15039
15211
  }
15040
15212
  /**
15213
+ * Resolves the effective polling configuration for an attestation request.
15214
+ *
15215
+ * Precedence (lowest to highest): provider `config.attestation`, then the
15216
+ * per-call `config`. Headers merge independently across
15217
+ * `config.attestation.headers`, the provider-level `config.headers`, and any
15218
+ * per-call `config.headers`, so a more specific header augments rather than
15219
+ * replaces the broader ones. The `headers` key is omitted entirely when no
15220
+ * headers are configured, leaving the attestation fetchers' defaults intact.
15221
+ *
15222
+ * @param config - Optional per-call polling configuration overrides
15223
+ * @returns The merged polling configuration passed to the attestation fetchers
15224
+ */ resolveAttestationConfig(config) {
15225
+ const headers = {
15226
+ ...this.config?.attestation?.headers,
15227
+ ...this.config?.headers,
15228
+ ...config?.headers
15229
+ };
15230
+ // Polling fields follow normal precedence; headers are merged separately
15231
+ // below so narrower config layers augment rather than replace broader ones.
15232
+ return {
15233
+ ...this.config?.attestation,
15234
+ ...config,
15235
+ ...Object.keys(headers).length > 0 ? {
15236
+ headers
15237
+ } : {}
15238
+ };
15239
+ }
15240
+ /**
15041
15241
  * Execute a cross-chain USDC bridge operation using the CCTP v2 protocol.
15042
15242
  *
15043
15243
  * This method orchestrates the complete CCTP v2 bridge flow including validation,
@@ -15463,11 +15663,7 @@ var pkg$2 = {
15463
15663
  */ async fetchAttestation(source, transactionHash, config) {
15464
15664
  assertCCTPv2WalletContext(source);
15465
15665
  try {
15466
- // Merge configs: defaults <- global config <- per-call config
15467
- const effectiveConfig = {
15468
- ...this.config?.attestation,
15469
- ...config
15470
- };
15666
+ const effectiveConfig = this.resolveAttestationConfig(config);
15471
15667
  const response = await fetchAttestation(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
15472
15668
  const message = response.messages[0];
15473
15669
  if (!message) {
@@ -15484,6 +15680,49 @@ var pkg$2 = {
15484
15680
  }
15485
15681
  }
15486
15682
  /**
15683
+ * Polls attestation data until Circle's relayer mint transaction is confirmed.
15684
+ *
15685
+ * This method is used by forwarded transfers. It polls the same Iris
15686
+ * attestation endpoint as {@link CCTPV2BridgingProvider.fetchAttestation},
15687
+ * but waits for a completed relayer forward state and returns the attestation
15688
+ * message containing `forwardTxHash`.
15689
+ *
15690
+ * @typeParam TFromAdapterCapabilities - The type representing the capabilities of the source adapter
15691
+ * @param source - The source wallet context containing the chain definition and wallet address
15692
+ * @param transactionHash - The transaction hash of the burn operation
15693
+ * @param config - Optional polling configuration overrides for timeout, retries, delay, and headers
15694
+ * @returns A promise that resolves to the attestation message with `forwardTxHash`
15695
+ * @throws KitError If the relayer forward fails, the response is invalid, or polling times out
15696
+ *
15697
+ * @example
15698
+ * ```typescript
15699
+ * import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2'
15700
+ * import { Chains } from '@core/chains'
15701
+ *
15702
+ * const provider = new CCTPV2BridgingProvider({
15703
+ * headers: { 'X-Partner-UUID': '00000000-0000-0000-0000-000000000000' },
15704
+ * })
15705
+ *
15706
+ * const attestation = await provider.fetchRelayerMint(
15707
+ * {
15708
+ * adapter,
15709
+ * chain: Chains.EthereumSepolia,
15710
+ * address: '0x1234...',
15711
+ * },
15712
+ * '0xabc123...',
15713
+ * )
15714
+ *
15715
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
15716
+ * ```
15717
+ */ async fetchRelayerMint(source, transactionHash, config) {
15718
+ assertCCTPv2WalletContext(source);
15719
+ if (typeof transactionHash !== 'string' || transactionHash.trim() === '') {
15720
+ throw createValidationFailedError$1('transactionHash', transactionHash, 'transactionHash must be a non-empty string');
15721
+ }
15722
+ const effectiveConfig = this.resolveAttestationConfig(config);
15723
+ return await fetchRelayerMint(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
15724
+ }
15725
+ /**
15487
15726
  * Requests a fresh attestation for an expired attestation.
15488
15727
  *
15489
15728
  * This method is used when the original attestation has expired before the mint
@@ -15537,11 +15776,7 @@ var pkg$2 = {
15537
15776
  throw new Error('Failed to re-attest: Invalid transaction hash');
15538
15777
  }
15539
15778
  try {
15540
- // Merge configs: defaults <- global config <- per-call config
15541
- const effectiveConfig = {
15542
- ...this.config?.attestation,
15543
- ...config
15544
- };
15779
+ const effectiveConfig = this.resolveAttestationConfig(config);
15545
15780
  // Step 1: Get existing attestation data to extract nonce
15546
15781
  const existingAttestation = await fetchAttestationWithoutStatusCheck(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
15547
15782
  const nonce = existingAttestation.messages[0]?.eventNonce;
@@ -15826,8 +16061,13 @@ var pkg$2 = {
15826
16061
  /**
15827
16062
  * The default providers that will be used in addition to the providers provided
15828
16063
  * to the BridgeKit constructor.
15829
- */ const getDefaultProviders = ()=>[
15830
- new CCTPV2BridgingProvider()
16064
+ *
16065
+ * @param config - Optional configuration forwarded to the default providers
16066
+ * @returns The default bridging providers
16067
+ */ const getDefaultProviders = (config = {})=>[
16068
+ new CCTPV2BridgingProvider(config.headers ? {
16069
+ headers: config.headers
16070
+ } : {})
15831
16071
  ];
15832
16072
 
15833
16073
  /**
@@ -16070,7 +16310,9 @@ var pkg$2 = {
16070
16310
  * ```
16071
16311
  */ constructor(config = {}){
16072
16312
  // Handle provider configuration
16073
- const defaultProviders = getDefaultProviders();
16313
+ const defaultProviders = getDefaultProviders(config.headers ? {
16314
+ headers: config.headers
16315
+ } : {});
16074
16316
  this.providers = [
16075
16317
  ...defaultProviders,
16076
16318
  ...config.providers ?? []
@@ -16650,6 +16892,9 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
16650
16892
  const kit = new BridgeKit({
16651
16893
  ...context.disableErrorReporting != null && {
16652
16894
  disableErrorReporting: context.disableErrorReporting
16895
+ },
16896
+ ...context.headers != null && {
16897
+ headers: context.headers
16653
16898
  }
16654
16899
  });
16655
16900
  if (hasBoth) {
@@ -16668,7 +16913,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
16668
16913
  };
16669
16914
 
16670
16915
  var name$1 = "@circle-fin/swap-kit";
16671
- var version$1 = "1.3.1";
16916
+ var version$1 = "1.3.2";
16672
16917
  var pkg$1 = {
16673
16918
  name: name$1,
16674
16919
  version: version$1};
@@ -18596,7 +18841,7 @@ new Set(Object.values(Blockchain));
18596
18841
  registerKit(`${pkg$1.name}/${pkg$1.version}`);
18597
18842
 
18598
18843
  var name = "@circle-fin/earn-kit";
18599
- var version = "1.2.1";
18844
+ var version = "1.2.2";
18600
18845
  var pkg = {
18601
18846
  name: name,
18602
18847
  version: version};
@@ -19121,10 +19366,11 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19121
19366
  * Zod schema for a fee entry in an EarnKit API response.
19122
19367
  *
19123
19368
  * Shared across deposit and withdrawal responses (and reusable for real
19124
- * charged fees, not just quote estimates). `type` identifies the fee category
19125
- * for cross-chain deposit quotes this is the kits-proxy fee-quote item type
19126
- * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). `status` qualifies the fee (e.g.
19127
- * `'estimated'` for a pre-sign cross-chain fee). Both are omitted on plain fees.
19369
+ * charged fees, not just quote estimates). `type` identifies the fee category.
19370
+ * For cross-chain deposit quotes this is the kits-proxy fee-quote item type
19371
+ * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). For withdrawal quotes, Circle fees use
19372
+ * `type: 'circle'`. `status` qualifies the fee (e.g. `'estimated'` for a
19373
+ * pre-sign cross-chain fee). Both are omitted on plain fees.
19128
19374
  *
19129
19375
  * @internal
19130
19376
  */ const feeSchema = zod.z.object({