@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.
package/bridge.mjs CHANGED
@@ -22,6 +22,7 @@ import { formatUnits as formatUnits$1, parseUnits as parseUnits$1 } from '@ether
22
22
  import { hexlify, hexZeroPad } from '@ethersproject/bytes';
23
23
  import { getAddress } from '@ethersproject/address';
24
24
  import bs58 from 'bs58';
25
+ import '@ethersproject/abi';
25
26
  import { PublicKey } from '@solana/web3.js';
26
27
  import 'bn.js';
27
28
  import '@coral-xyz/anchor';
@@ -2472,6 +2473,8 @@ class KitError extends Error {
2472
2473
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
2473
2474
  Blockchain["Codex"] = "Codex";
2474
2475
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
2476
+ Blockchain["Cronos"] = "Cronos";
2477
+ Blockchain["Cronos_Testnet"] = "Cronos_Testnet";
2475
2478
  Blockchain["Edge"] = "Edge";
2476
2479
  Blockchain["Edge_Testnet"] = "Edge_Testnet";
2477
2480
  Blockchain["Ethereum"] = "Ethereum";
@@ -2554,6 +2557,7 @@ var BridgeChain;
2554
2557
  BridgeChain["Avalanche"] = "Avalanche";
2555
2558
  BridgeChain["Base"] = "Base";
2556
2559
  BridgeChain["Codex"] = "Codex";
2560
+ BridgeChain["Cronos"] = "Cronos";
2557
2561
  BridgeChain["Edge"] = "Edge";
2558
2562
  BridgeChain["Ethereum"] = "Ethereum";
2559
2563
  BridgeChain["HyperEVM"] = "HyperEVM";
@@ -2578,6 +2582,7 @@ var BridgeChain;
2578
2582
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
2579
2583
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
2580
2584
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
2585
+ BridgeChain["Cronos_Testnet"] = "Cronos_Testnet";
2581
2586
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
2582
2587
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
2583
2588
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
@@ -3623,6 +3628,96 @@ var EarnChain;
3623
3628
  }
3624
3629
  });
3625
3630
 
3631
+ /**
3632
+ * Cronos Mainnet chain definition
3633
+ * @remarks
3634
+ * This represents the official production network for the Cronos blockchain.
3635
+ * Cronos is an EVM-compatible blockchain.
3636
+ */ const Cronos = defineChain({
3637
+ type: 'evm',
3638
+ chain: Blockchain.Cronos,
3639
+ name: 'Cronos',
3640
+ title: 'Cronos Mainnet',
3641
+ nativeCurrency: {
3642
+ name: 'Cronos',
3643
+ symbol: 'CRO',
3644
+ decimals: 18
3645
+ },
3646
+ chainId: 25,
3647
+ isTestnet: false,
3648
+ explorerUrl: 'https://cronoscan.com/tx/{hash}',
3649
+ rpcEndpoints: [
3650
+ 'https://evm.cronos.org'
3651
+ ],
3652
+ eurcAddress: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
3653
+ usdcAddress: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
3654
+ usdtAddress: null,
3655
+ cctp: {
3656
+ domain: 32,
3657
+ contracts: {
3658
+ v2: {
3659
+ type: 'split',
3660
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
3661
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
3662
+ confirmations: 1,
3663
+ fastConfirmations: 1
3664
+ }
3665
+ },
3666
+ forwarderSupported: {
3667
+ source: false,
3668
+ destination: false
3669
+ }
3670
+ },
3671
+ kitContracts: {
3672
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
3673
+ }
3674
+ });
3675
+
3676
+ /**
3677
+ * Cronos Testnet chain definition
3678
+ * @remarks
3679
+ * This represents the official test network for the Cronos blockchain.
3680
+ * Cronos is an EVM-compatible blockchain.
3681
+ */ const CronosTestnet = defineChain({
3682
+ type: 'evm',
3683
+ chain: Blockchain.Cronos_Testnet,
3684
+ name: 'Cronos Testnet',
3685
+ title: 'Cronos Testnet',
3686
+ nativeCurrency: {
3687
+ name: 'CRO',
3688
+ symbol: 'tCRO',
3689
+ decimals: 18
3690
+ },
3691
+ chainId: 338,
3692
+ isTestnet: true,
3693
+ explorerUrl: 'https://explorer.cronos.org/testnet/tx/{hash}',
3694
+ rpcEndpoints: [
3695
+ 'https://evm-t3.cronos.org'
3696
+ ],
3697
+ eurcAddress: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
3698
+ usdcAddress: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
3699
+ usdtAddress: null,
3700
+ cctp: {
3701
+ domain: 32,
3702
+ contracts: {
3703
+ v2: {
3704
+ type: 'split',
3705
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
3706
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
3707
+ confirmations: 1,
3708
+ fastConfirmations: 1
3709
+ }
3710
+ },
3711
+ forwarderSupported: {
3712
+ source: false,
3713
+ destination: false
3714
+ }
3715
+ },
3716
+ kitContracts: {
3717
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
3718
+ }
3719
+ });
3720
+
3626
3721
  /**
3627
3722
  * Edge Mainnet chain definition
3628
3723
  * @remarks
@@ -5974,6 +6069,8 @@ var Chains = /*#__PURE__*/Object.freeze({
5974
6069
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
5975
6070
  Codex: Codex,
5976
6071
  CodexTestnet: CodexTestnet,
6072
+ Cronos: Cronos,
6073
+ CronosTestnet: CronosTestnet,
5977
6074
  Edge: Edge,
5978
6075
  EdgeTestnet: EdgeTestnet,
5979
6076
  Ethereum: Ethereum,
@@ -8039,6 +8136,7 @@ const swapTokenEnumSchema = z.enum([
8039
8136
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
8040
8137
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
8041
8138
  [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
8139
+ [Blockchain.Cronos]: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
8042
8140
  [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
8043
8141
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
8044
8142
  [Blockchain.Hedera]: '0.0.456858',
@@ -8072,6 +8170,7 @@ const swapTokenEnumSchema = z.enum([
8072
8170
  [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
8073
8171
  [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
8074
8172
  [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
8173
+ [Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
8075
8174
  [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
8076
8175
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
8077
8176
  [Blockchain.Hedera_Testnet]: '0.0.429274',
@@ -8144,6 +8243,7 @@ const swapTokenEnumSchema = z.enum([
8144
8243
  // =========================================================================
8145
8244
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
8146
8245
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
8246
+ [Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
8147
8247
  [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
8148
8248
  [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
8149
8249
  [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
@@ -8152,6 +8252,7 @@ const swapTokenEnumSchema = z.enum([
8152
8252
  // =========================================================================
8153
8253
  [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
8154
8254
  [Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
8255
+ [Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
8155
8256
  [Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
8156
8257
  }
8157
8258
  };
@@ -9502,7 +9603,7 @@ function resolveOptions(options) {
9502
9603
  }
9503
9604
 
9504
9605
  var name$2 = "@circle-fin/bridge-kit";
9505
- var version$3 = "1.11.1";
9606
+ var version$3 = "1.12.0";
9506
9607
  var pkg$3 = {
9507
9608
  name: name$2,
9508
9609
  version: version$3};
@@ -12162,6 +12263,15 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
12162
12263
  ;
12163
12264
  const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
12164
12265
  ;
12266
+ // Hard execution caps: observed max + ~30% buffer, used as gasLimit overrides on
12267
+ // chains whose eth_estimateGas under-reports (e.g. Cronos EIP-7623 calldata floor).
12268
+ // Kept separate from the fee-estimate averages above.
12269
+ const APPROVE_GAS_LIMIT_EVM = 100_000n // ERC-20 approve observed max ~46k
12270
+ ;
12271
+ const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 300_000n // observed max 226_506 + ~30%
12272
+ ;
12273
+ const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839 + ~30%
12274
+ ;
12165
12275
  /**
12166
12276
  * The minimum finality threshold for CCTPv2 transfers.
12167
12277
  *
@@ -12190,6 +12300,27 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12190
12300
  'Content-Type': 'application/json'
12191
12301
  }
12192
12302
  };
12303
+ /**
12304
+ * Merges caller-provided polling overrides on top of {@link DEFAULT_CONFIG}.
12305
+ *
12306
+ * Headers are merged independently so caller-supplied headers augment the
12307
+ * defaults (such as `Content-Type`) rather than replacing them wholesale.
12308
+ *
12309
+ * @param config - Caller-provided polling configuration overrides
12310
+ * @param internalDefaults - Internal defaults applied before `config` (for example a
12311
+ * reduced `maxRetries` for one-shot requests); `config` still wins on conflict
12312
+ * @returns The effective polling configuration
12313
+ * @internal
12314
+ */ const mergeAttestationConfig = (config, internalDefaults = {})=>({
12315
+ ...DEFAULT_CONFIG,
12316
+ ...internalDefaults,
12317
+ ...config,
12318
+ headers: {
12319
+ ...DEFAULT_CONFIG.headers,
12320
+ ...internalDefaults.headers,
12321
+ ...config.headers
12322
+ }
12323
+ });
12193
12324
  /**
12194
12325
  * Type guard that verifies if an unknown value matches the AttestationMessage shape
12195
12326
  * and has all required properties.
@@ -12336,10 +12467,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12336
12467
  * ```
12337
12468
  */ const fetchAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12338
12469
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12339
- const effectiveConfig = {
12340
- ...DEFAULT_CONFIG,
12341
- ...config
12342
- };
12470
+ const effectiveConfig = mergeAttestationConfig(config);
12343
12471
  return await pollApiGet(url, isAttestationResponse, effectiveConfig);
12344
12472
  };
12345
12473
  /**
@@ -12382,11 +12510,9 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12382
12510
  */ const fetchAttestationWithoutStatusCheck = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12383
12511
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12384
12512
  // Use minimal retries since we're just fetching existing data
12385
- const effectiveConfig = {
12386
- ...DEFAULT_CONFIG,
12387
- maxRetries: 3,
12388
- ...config
12389
- };
12513
+ const effectiveConfig = mergeAttestationConfig(config, {
12514
+ maxRetries: 3
12515
+ });
12390
12516
  return await pollApiGet(url, isAttestationResponseWithoutStatusCheck, effectiveConfig);
12391
12517
  };
12392
12518
  /**
@@ -12446,10 +12572,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12446
12572
  * ```
12447
12573
  */ const fetchReAttestedAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12448
12574
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12449
- const effectiveConfig = {
12450
- ...DEFAULT_CONFIG,
12451
- ...config
12452
- };
12575
+ const effectiveConfig = mergeAttestationConfig(config);
12453
12576
  return await pollApiGet(url, isReAttestedAttestationResponse, effectiveConfig);
12454
12577
  };
12455
12578
  /**
@@ -12515,14 +12638,139 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12515
12638
  */ const requestReAttestation = async (nonce, isTestnet, config = {})=>{
12516
12639
  const url = buildReAttestUrl(nonce, isTestnet);
12517
12640
  // Use minimal retries since we're just submitting a request, not polling for state
12518
- const effectiveConfig = {
12519
- ...DEFAULT_CONFIG,
12520
- maxRetries: 3,
12521
- ...config
12522
- };
12641
+ const effectiveConfig = mergeAttestationConfig(config, {
12642
+ maxRetries: 3
12643
+ });
12523
12644
  return await pollApiPost(url, {}, isReAttestationResponse, effectiveConfig);
12524
12645
  };
12525
12646
 
12647
+ /**
12648
+ * Type guard that checks if the relayer has confirmed the mint transaction.
12649
+ *
12650
+ * This function validates that:
12651
+ * 1. The response has valid AttestationResponse structure
12652
+ * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
12653
+ *
12654
+ * If forwardState is 'FAILED', throws a non-retryable KitError.
12655
+ * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
12656
+ *
12657
+ * @param obj - The value to check, typically a parsed JSON response
12658
+ * @returns True if the relayer has confirmed the mint
12659
+ * @throws {KitError} With FATAL recoverability if structure is invalid
12660
+ * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
12661
+ * @throws {KitError} With RETRYABLE recoverability if still pending
12662
+ * @internal
12663
+ */ const isRelayerMintConfirmed = (obj)=>{
12664
+ // First check if the structure is valid
12665
+ if (!hasValidAttestationStructure(obj)) {
12666
+ throw new KitError({
12667
+ ...InputError.VALIDATION_FAILED,
12668
+ recoverability: 'FATAL',
12669
+ message: 'Invalid attestation response structure from IRIS API.'
12670
+ });
12671
+ }
12672
+ // Find the first message (typically there's only one)
12673
+ const message = obj.messages[0];
12674
+ if (!message) {
12675
+ throw new KitError({
12676
+ ...InputError.VALIDATION_FAILED,
12677
+ recoverability: 'FATAL',
12678
+ message: 'No attestation messages found in IRIS API response.'
12679
+ });
12680
+ }
12681
+ // Check for FAILED state - this is a permanent failure
12682
+ if (message.forwardState === 'FAILED') {
12683
+ throw new KitError({
12684
+ ...NetworkError.RELAYER_FORWARD_FAILED,
12685
+ recoverability: 'RESUMABLE',
12686
+ 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.',
12687
+ cause: {
12688
+ trace: {
12689
+ eventNonce: message.eventNonce,
12690
+ attestation: message.attestation,
12691
+ message: message.message
12692
+ }
12693
+ }
12694
+ });
12695
+ }
12696
+ // Check if mint is confirmed (or complete) with a valid transaction hash
12697
+ // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
12698
+ if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
12699
+ return true;
12700
+ }
12701
+ // Still pending or not yet processed - throw RETRYABLE error to continue polling
12702
+ throw new KitError({
12703
+ ...NetworkError.RELAYER_PENDING,
12704
+ recoverability: 'RETRYABLE',
12705
+ message: 'Relayer mint not ready. Waiting for confirmation.'
12706
+ });
12707
+ };
12708
+ /**
12709
+ * Polls the attestation API until the relayer's mint transaction is confirmed.
12710
+ *
12711
+ * This function is used when `useForwarder` is enabled. Instead of the user
12712
+ * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
12713
+ * This function polls until the relayer has submitted and confirmed the mint transaction.
12714
+ *
12715
+ * @remarks
12716
+ * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
12717
+ * - Throws immediately if `forwardState` is 'FAILED'
12718
+ * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
12719
+ * - Returns the attestation message with `forwardTxHash` populated
12720
+ *
12721
+ * @param sourceDomainId - The CCTP domain ID of the source chain
12722
+ * @param transactionHash - The transaction hash of the burn operation
12723
+ * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
12724
+ * @param config - Optional configuration overrides for polling behavior
12725
+ * @returns The attestation message with confirmed forwardTxHash
12726
+ * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
12727
+ * @throws {KitError} If timeout is reached while still pending
12728
+ *
12729
+ * @example
12730
+ * ```typescript
12731
+ * const attestation = await fetchRelayerMint(0, '0xabc...', false)
12732
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
12733
+ * ```
12734
+ */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12735
+ const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12736
+ const effectiveConfig = mergeAttestationConfig(config);
12737
+ let response;
12738
+ try {
12739
+ response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
12740
+ } catch (error) {
12741
+ // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
12742
+ if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
12743
+ throw new KitError({
12744
+ ...NetworkError.RELAYER_FORWARD_FAILED,
12745
+ recoverability: error.recoverability,
12746
+ message: error.message,
12747
+ cause: {
12748
+ ...error.cause,
12749
+ trace: {
12750
+ ...error.cause?.trace,
12751
+ burnTxHash: transactionHash
12752
+ }
12753
+ }
12754
+ });
12755
+ }
12756
+ throw error;
12757
+ }
12758
+ // Return the first message (which should have forwardTxHash)
12759
+ // Note: This check is needed for TypeScript type safety even though
12760
+ // isRelayerMintConfirmed validates messages[0] exists. The type guard
12761
+ // narrows the type at the call site, but TypeScript can't infer that
12762
+ // the array still has elements after pollApiGet returns.
12763
+ const message = response.messages[0];
12764
+ if (!message) {
12765
+ throw new KitError({
12766
+ ...InputError.VALIDATION_FAILED,
12767
+ recoverability: 'FATAL',
12768
+ message: 'No attestation messages found in response after polling.'
12769
+ });
12770
+ }
12771
+ return message;
12772
+ };
12773
+
12526
12774
  const assertCCTPv2WalletContextSymbol = Symbol('assertCCTPv2WalletContext');
12527
12775
  /**
12528
12776
  * Asserts that the provided parameters match the CCTPv2 wallet context interface.
@@ -13397,6 +13645,8 @@ function hasPendingState(analysis, result) {
13397
13645
  * - `adapter`: The adapter that will execute the transaction
13398
13646
  * - `confirmations`: The number of confirmations to wait for (defaults to 1)
13399
13647
  * - `timeout`: The timeout for the request in milliseconds
13648
+ * - `gasLimit`: Optional explicit gas limit (number) forwarded to EVM execute,
13649
+ * bypassing `eth_estimateGas`; ignored for non-EVM requests
13400
13650
  * @returns The bridge step with the transaction details and explorer URL
13401
13651
  * @throws If the transaction execution fails
13402
13652
  *
@@ -13411,7 +13661,7 @@ function hasPendingState(analysis, result) {
13411
13661
  * })
13412
13662
  * console.log('Transaction hash:', step.txHash)
13413
13663
  * ```
13414
- */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout }) {
13664
+ */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasLimit }) {
13415
13665
  const step = {
13416
13666
  name,
13417
13667
  state: 'pending'
@@ -13424,7 +13674,9 @@ function hasPendingState(analysis, result) {
13424
13674
  step.state = 'noop';
13425
13675
  return step;
13426
13676
  }
13427
- const txHash = await request.execute();
13677
+ const txHash = request.type === 'evm' && gasLimit !== undefined ? await request.execute({
13678
+ gasLimit
13679
+ }) : await request.execute();
13428
13680
  step.txHash = txHash;
13429
13681
  const retryOptions = {
13430
13682
  isRetryable: (err)=>isRetryableError$1(parseBlockchainError(err, {
@@ -13496,7 +13748,8 @@ function hasPendingState(analysis, result) {
13496
13748
  name: 'approve',
13497
13749
  adapter: params.source.adapter,
13498
13750
  chain: params.source.chain,
13499
- request: await provider.approve(params.source, approvalAmount)
13751
+ request: await provider.approve(params.source, approvalAmount),
13752
+ gasLimit: Number(APPROVE_GAS_LIMIT_EVM)
13500
13753
  });
13501
13754
  }
13502
13755
 
@@ -13523,7 +13776,8 @@ function hasPendingState(analysis, result) {
13523
13776
  name: 'burn',
13524
13777
  adapter: params.source.adapter,
13525
13778
  chain: params.source.chain,
13526
- request: await provider.burn(params)
13779
+ request: await provider.burn(params),
13780
+ gasLimit: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
13527
13781
  });
13528
13782
  }
13529
13783
 
@@ -13609,11 +13863,18 @@ function hasPendingState(analysis, result) {
13609
13863
  */ async function bridgeMint({ params, provider }, attestation) {
13610
13864
  // Validate attestation message matches transfer params
13611
13865
  await assertCCTPv2AttestationParams(attestation, params);
13866
+ const mintRequest = await provider.mint(params.source, params.destination, attestation);
13612
13867
  const step = await executePreparedChainRequest({
13613
13868
  name: 'mint',
13614
13869
  adapter: params.destination.adapter,
13615
13870
  chain: params.destination.chain,
13616
- request: await provider.mint(params.source, params.destination, attestation)
13871
+ request: mintRequest,
13872
+ // Some chains (e.g. Cronos) enforce an EIP-7623 calldata gas floor that
13873
+ // eth_estimateGas does not account for, returning a below-floor value
13874
+ // without reverting. Pinning to a value above the observed execution max
13875
+ // (310_839) bypasses re-estimation and guarantees we clear both the floor
13876
+ // and the actual execution cost.
13877
+ gasLimit: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
13617
13878
  });
13618
13879
  // Add forwarded: false for non-relayer mints
13619
13880
  return {
@@ -13651,136 +13912,6 @@ const mockAttestationMessage = {
13651
13912
  delayReason: null
13652
13913
  };
13653
13914
 
13654
- /**
13655
- * Type guard that checks if the relayer has confirmed the mint transaction.
13656
- *
13657
- * This function validates that:
13658
- * 1. The response has valid AttestationResponse structure
13659
- * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
13660
- *
13661
- * If forwardState is 'FAILED', throws a non-retryable KitError.
13662
- * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
13663
- *
13664
- * @param obj - The value to check, typically a parsed JSON response
13665
- * @returns True if the relayer has confirmed the mint
13666
- * @throws {KitError} With FATAL recoverability if structure is invalid
13667
- * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
13668
- * @throws {KitError} With RETRYABLE recoverability if still pending
13669
- * @internal
13670
- */ const isRelayerMintConfirmed = (obj)=>{
13671
- // First check if the structure is valid
13672
- if (!hasValidAttestationStructure(obj)) {
13673
- throw new KitError({
13674
- ...InputError.VALIDATION_FAILED,
13675
- recoverability: 'FATAL',
13676
- message: 'Invalid attestation response structure from IRIS API.'
13677
- });
13678
- }
13679
- // Find the first message (typically there's only one)
13680
- const message = obj.messages[0];
13681
- if (!message) {
13682
- throw new KitError({
13683
- ...InputError.VALIDATION_FAILED,
13684
- recoverability: 'FATAL',
13685
- message: 'No attestation messages found in IRIS API response.'
13686
- });
13687
- }
13688
- // Check for FAILED state - this is a permanent failure
13689
- if (message.forwardState === 'FAILED') {
13690
- throw new KitError({
13691
- ...NetworkError.RELAYER_FORWARD_FAILED,
13692
- recoverability: 'RESUMABLE',
13693
- 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.',
13694
- cause: {
13695
- trace: {
13696
- eventNonce: message.eventNonce,
13697
- attestation: message.attestation,
13698
- message: message.message
13699
- }
13700
- }
13701
- });
13702
- }
13703
- // Check if mint is confirmed (or complete) with a valid transaction hash
13704
- // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
13705
- if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
13706
- return true;
13707
- }
13708
- // Still pending or not yet processed - throw RETRYABLE error to continue polling
13709
- throw new KitError({
13710
- ...NetworkError.RELAYER_PENDING,
13711
- recoverability: 'RETRYABLE',
13712
- message: 'Relayer mint not ready. Waiting for confirmation.'
13713
- });
13714
- };
13715
- /**
13716
- * Polls the attestation API until the relayer's mint transaction is confirmed.
13717
- *
13718
- * This function is used when `useForwarder` is enabled. Instead of the user
13719
- * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
13720
- * This function polls until the relayer has submitted and confirmed the mint transaction.
13721
- *
13722
- * @remarks
13723
- * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
13724
- * - Throws immediately if `forwardState` is 'FAILED'
13725
- * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
13726
- * - Returns the attestation message with `forwardTxHash` populated
13727
- *
13728
- * @param sourceDomainId - The CCTP domain ID of the source chain
13729
- * @param transactionHash - The transaction hash of the burn operation
13730
- * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
13731
- * @param config - Optional configuration overrides for polling behavior
13732
- * @returns The attestation message with confirmed forwardTxHash
13733
- * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
13734
- * @throws {KitError} If timeout is reached while still pending
13735
- *
13736
- * @example
13737
- * ```typescript
13738
- * const attestation = await fetchRelayerMint(0, '0xabc...', false)
13739
- * console.log('Relayer mint tx:', attestation.forwardTxHash)
13740
- * ```
13741
- */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
13742
- const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
13743
- const effectiveConfig = {
13744
- ...DEFAULT_CONFIG,
13745
- ...config
13746
- };
13747
- let response;
13748
- try {
13749
- response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
13750
- } catch (error) {
13751
- // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
13752
- if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
13753
- throw new KitError({
13754
- ...NetworkError.RELAYER_FORWARD_FAILED,
13755
- recoverability: error.recoverability,
13756
- message: error.message,
13757
- cause: {
13758
- ...error.cause,
13759
- trace: {
13760
- ...error.cause?.trace,
13761
- burnTxHash: transactionHash
13762
- }
13763
- }
13764
- });
13765
- }
13766
- throw error;
13767
- }
13768
- // Return the first message (which should have forwardTxHash)
13769
- // Note: This check is needed for TypeScript type safety even though
13770
- // isRelayerMintConfirmed validates messages[0] exists. The type guard
13771
- // narrows the type at the call site, but TypeScript can't infer that
13772
- // the array still has elements after pollApiGet returns.
13773
- const message = response.messages[0];
13774
- if (!message) {
13775
- throw new KitError({
13776
- ...InputError.VALIDATION_FAILED,
13777
- recoverability: 'FATAL',
13778
- message: 'No attestation messages found in response after polling.'
13779
- });
13780
- }
13781
- return message;
13782
- };
13783
-
13784
13915
  /**
13785
13916
  * Executes the mint step for forwarding mode where Circle's relayer handles the mint.
13786
13917
  *
@@ -13794,6 +13925,7 @@ const mockAttestationMessage = {
13794
13925
  * immediately after the API confirms the mint.
13795
13926
  *
13796
13927
  * @param params - The bridge parameters containing source, destination, amount and config
13928
+ * @param provider - The CCTP v2 bridging provider
13797
13929
  * @param context - The step context containing burnTxHash from the burn step
13798
13930
  * @returns Promise resolving to the bridge step with transaction details
13799
13931
  * @throws {KitError} If burnTxHash is not available in context
@@ -13802,10 +13934,13 @@ const mockAttestationMessage = {
13802
13934
  *
13803
13935
  * @example
13804
13936
  * ```typescript
13805
- * const step = await bridgeRelayerMint(params, { burnTxHash: '0x...' })
13937
+ * const step = await bridgeRelayerMint(
13938
+ * { params, provider },
13939
+ * { burnTxHash: '0x...' }
13940
+ * )
13806
13941
  * console.log('Relayer mint tx:', step.txHash)
13807
13942
  * ```
13808
- */ async function bridgeRelayerMint(params, context) {
13943
+ */ async function bridgeRelayerMint({ params, provider }, context) {
13809
13944
  const step = {
13810
13945
  name: 'mint',
13811
13946
  state: 'pending',
@@ -13821,7 +13956,7 @@ const mockAttestationMessage = {
13821
13956
  }
13822
13957
  try {
13823
13958
  // Poll attestation API until relayer confirms the mint
13824
- const attestation = await fetchRelayerMint(params.source.chain.cctp.domain, context.burnTxHash, params.source.chain.isTestnet ?? false);
13959
+ const attestation = await provider.fetchRelayerMint(params.source, context.burnTxHash);
13825
13960
  // Extract the relayer's mint transaction hash
13826
13961
  const forwardTxHash = attestation.forwardTxHash;
13827
13962
  if (!forwardTxHash) {
@@ -13915,7 +14050,7 @@ const mockAttestationMessage = {
13915
14050
  name: 'mint',
13916
14051
  // bridgeRelayerMint validates context.burnTxHash internally with a KitError
13917
14052
  // bridgeMint requires attestationData which is validated here
13918
- executor: useForwarder ? async (params, _provider, context)=>{
14053
+ executor: useForwarder ? async (params, provider, context)=>{
13919
14054
  if (!context) {
13920
14055
  throw new KitError({
13921
14056
  ...InputError.VALIDATION_FAILED,
@@ -13923,7 +14058,10 @@ const mockAttestationMessage = {
13923
14058
  message: 'Step context is required for relayer mint'
13924
14059
  });
13925
14060
  }
13926
- return bridgeRelayerMint(params, context);
14061
+ return bridgeRelayerMint({
14062
+ params,
14063
+ provider
14064
+ }, context);
13927
14065
  } : async (params, provider, context)=>{
13928
14066
  if (!context?.attestationData) {
13929
14067
  throw new KitError({
@@ -14254,7 +14392,7 @@ const mockAttestationMessage = {
14254
14392
  return step;
14255
14393
  }
14256
14394
 
14257
- var version$2 = "1.8.5";
14395
+ var version$2 = "1.9.0";
14258
14396
  var pkg$2 = {
14259
14397
  version: version$2};
14260
14398
 
@@ -14960,6 +15098,39 @@ var pkg$2 = {
14960
15098
  }
14961
15099
  }
14962
15100
 
15101
+ function isPlainObject(value) {
15102
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
15103
+ return false;
15104
+ }
15105
+ const prototype = Object.getPrototypeOf(value);
15106
+ return prototype === Object.prototype || prototype === null;
15107
+ }
15108
+ function assertHeadersConfig(headers, field) {
15109
+ if (headers === undefined) {
15110
+ return;
15111
+ }
15112
+ if (!isPlainObject(headers)) {
15113
+ throw createValidationFailedError$1(field, headers, `${field} must be a plain object with string header values when provided`);
15114
+ }
15115
+ for (const [name, value] of Object.entries(headers)){
15116
+ if (typeof value !== 'string') {
15117
+ throw createValidationFailedError$1(`${field}.${name}`, value, 'header values must be strings');
15118
+ }
15119
+ }
15120
+ }
15121
+ function assertCCTPV2Config(config) {
15122
+ if (!isPlainObject(config)) {
15123
+ throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
15124
+ }
15125
+ assertHeadersConfig(config['headers'], 'config.headers');
15126
+ const attestation = config['attestation'];
15127
+ if (attestation !== undefined) {
15128
+ if (!isPlainObject(attestation)) {
15129
+ throw createValidationFailedError$1('config.attestation', attestation, 'config.attestation must be a plain object when provided');
15130
+ }
15131
+ assertHeadersConfig(attestation['headers'], 'config.attestation.headers');
15132
+ }
15133
+ }
14963
15134
  /**
14964
15135
  * All chains that are supported by the CCTP v2 provider.
14965
15136
  *
@@ -15028,9 +15199,38 @@ var pkg$2 = {
15028
15199
  * @param config - Optional configuration overrides for the provider
15029
15200
  */ constructor(config = {}){
15030
15201
  super();
15202
+ assertCCTPV2Config(config);
15031
15203
  this.config = config;
15032
15204
  }
15033
15205
  /**
15206
+ * Resolves the effective polling configuration for an attestation request.
15207
+ *
15208
+ * Precedence (lowest to highest): provider `config.attestation`, then the
15209
+ * per-call `config`. Headers merge independently across
15210
+ * `config.attestation.headers`, the provider-level `config.headers`, and any
15211
+ * per-call `config.headers`, so a more specific header augments rather than
15212
+ * replaces the broader ones. The `headers` key is omitted entirely when no
15213
+ * headers are configured, leaving the attestation fetchers' defaults intact.
15214
+ *
15215
+ * @param config - Optional per-call polling configuration overrides
15216
+ * @returns The merged polling configuration passed to the attestation fetchers
15217
+ */ resolveAttestationConfig(config) {
15218
+ const headers = {
15219
+ ...this.config?.attestation?.headers,
15220
+ ...this.config?.headers,
15221
+ ...config?.headers
15222
+ };
15223
+ // Polling fields follow normal precedence; headers are merged separately
15224
+ // below so narrower config layers augment rather than replace broader ones.
15225
+ return {
15226
+ ...this.config?.attestation,
15227
+ ...config,
15228
+ ...Object.keys(headers).length > 0 ? {
15229
+ headers
15230
+ } : {}
15231
+ };
15232
+ }
15233
+ /**
15034
15234
  * Execute a cross-chain USDC bridge operation using the CCTP v2 protocol.
15035
15235
  *
15036
15236
  * This method orchestrates the complete CCTP v2 bridge flow including validation,
@@ -15456,11 +15656,7 @@ var pkg$2 = {
15456
15656
  */ async fetchAttestation(source, transactionHash, config) {
15457
15657
  assertCCTPv2WalletContext(source);
15458
15658
  try {
15459
- // Merge configs: defaults <- global config <- per-call config
15460
- const effectiveConfig = {
15461
- ...this.config?.attestation,
15462
- ...config
15463
- };
15659
+ const effectiveConfig = this.resolveAttestationConfig(config);
15464
15660
  const response = await fetchAttestation(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
15465
15661
  const message = response.messages[0];
15466
15662
  if (!message) {
@@ -15477,6 +15673,49 @@ var pkg$2 = {
15477
15673
  }
15478
15674
  }
15479
15675
  /**
15676
+ * Polls attestation data until Circle's relayer mint transaction is confirmed.
15677
+ *
15678
+ * This method is used by forwarded transfers. It polls the same Iris
15679
+ * attestation endpoint as {@link CCTPV2BridgingProvider.fetchAttestation},
15680
+ * but waits for a completed relayer forward state and returns the attestation
15681
+ * message containing `forwardTxHash`.
15682
+ *
15683
+ * @typeParam TFromAdapterCapabilities - The type representing the capabilities of the source adapter
15684
+ * @param source - The source wallet context containing the chain definition and wallet address
15685
+ * @param transactionHash - The transaction hash of the burn operation
15686
+ * @param config - Optional polling configuration overrides for timeout, retries, delay, and headers
15687
+ * @returns A promise that resolves to the attestation message with `forwardTxHash`
15688
+ * @throws KitError If the relayer forward fails, the response is invalid, or polling times out
15689
+ *
15690
+ * @example
15691
+ * ```typescript
15692
+ * import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2'
15693
+ * import { Chains } from '@core/chains'
15694
+ *
15695
+ * const provider = new CCTPV2BridgingProvider({
15696
+ * headers: { 'X-Partner-UUID': '00000000-0000-0000-0000-000000000000' },
15697
+ * })
15698
+ *
15699
+ * const attestation = await provider.fetchRelayerMint(
15700
+ * {
15701
+ * adapter,
15702
+ * chain: Chains.EthereumSepolia,
15703
+ * address: '0x1234...',
15704
+ * },
15705
+ * '0xabc123...',
15706
+ * )
15707
+ *
15708
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
15709
+ * ```
15710
+ */ async fetchRelayerMint(source, transactionHash, config) {
15711
+ assertCCTPv2WalletContext(source);
15712
+ if (typeof transactionHash !== 'string' || transactionHash.trim() === '') {
15713
+ throw createValidationFailedError$1('transactionHash', transactionHash, 'transactionHash must be a non-empty string');
15714
+ }
15715
+ const effectiveConfig = this.resolveAttestationConfig(config);
15716
+ return await fetchRelayerMint(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
15717
+ }
15718
+ /**
15480
15719
  * Requests a fresh attestation for an expired attestation.
15481
15720
  *
15482
15721
  * This method is used when the original attestation has expired before the mint
@@ -15530,11 +15769,7 @@ var pkg$2 = {
15530
15769
  throw new Error('Failed to re-attest: Invalid transaction hash');
15531
15770
  }
15532
15771
  try {
15533
- // Merge configs: defaults <- global config <- per-call config
15534
- const effectiveConfig = {
15535
- ...this.config?.attestation,
15536
- ...config
15537
- };
15772
+ const effectiveConfig = this.resolveAttestationConfig(config);
15538
15773
  // Step 1: Get existing attestation data to extract nonce
15539
15774
  const existingAttestation = await fetchAttestationWithoutStatusCheck(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
15540
15775
  const nonce = existingAttestation.messages[0]?.eventNonce;
@@ -15819,8 +16054,13 @@ var pkg$2 = {
15819
16054
  /**
15820
16055
  * The default providers that will be used in addition to the providers provided
15821
16056
  * to the BridgeKit constructor.
15822
- */ const getDefaultProviders = ()=>[
15823
- new CCTPV2BridgingProvider()
16057
+ *
16058
+ * @param config - Optional configuration forwarded to the default providers
16059
+ * @returns The default bridging providers
16060
+ */ const getDefaultProviders = (config = {})=>[
16061
+ new CCTPV2BridgingProvider(config.headers ? {
16062
+ headers: config.headers
16063
+ } : {})
15824
16064
  ];
15825
16065
 
15826
16066
  /**
@@ -16063,7 +16303,9 @@ var pkg$2 = {
16063
16303
  * ```
16064
16304
  */ constructor(config = {}){
16065
16305
  // Handle provider configuration
16066
- const defaultProviders = getDefaultProviders();
16306
+ const defaultProviders = getDefaultProviders(config.headers ? {
16307
+ headers: config.headers
16308
+ } : {});
16067
16309
  this.providers = [
16068
16310
  ...defaultProviders,
16069
16311
  ...config.providers ?? []
@@ -16643,6 +16885,9 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
16643
16885
  const kit = new BridgeKit({
16644
16886
  ...context.disableErrorReporting != null && {
16645
16887
  disableErrorReporting: context.disableErrorReporting
16888
+ },
16889
+ ...context.headers != null && {
16890
+ headers: context.headers
16646
16891
  }
16647
16892
  });
16648
16893
  if (hasBoth) {
@@ -16661,7 +16906,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
16661
16906
  };
16662
16907
 
16663
16908
  var name$1 = "@circle-fin/swap-kit";
16664
- var version$1 = "1.3.1";
16909
+ var version$1 = "1.3.2";
16665
16910
  var pkg$1 = {
16666
16911
  name: name$1,
16667
16912
  version: version$1};
@@ -18589,7 +18834,7 @@ new Set(Object.values(Blockchain));
18589
18834
  registerKit(`${pkg$1.name}/${pkg$1.version}`);
18590
18835
 
18591
18836
  var name = "@circle-fin/earn-kit";
18592
- var version = "1.2.1";
18837
+ var version = "1.2.2";
18593
18838
  var pkg = {
18594
18839
  name: name,
18595
18840
  version: version};
@@ -19114,10 +19359,11 @@ const bridgeDepositPrepareReviewSchema = z.object({
19114
19359
  * Zod schema for a fee entry in an EarnKit API response.
19115
19360
  *
19116
19361
  * Shared across deposit and withdrawal responses (and reusable for real
19117
- * charged fees, not just quote estimates). `type` identifies the fee category
19118
- * for cross-chain deposit quotes this is the kits-proxy fee-quote item type
19119
- * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). `status` qualifies the fee (e.g.
19120
- * `'estimated'` for a pre-sign cross-chain fee). Both are omitted on plain fees.
19362
+ * charged fees, not just quote estimates). `type` identifies the fee category.
19363
+ * For cross-chain deposit quotes this is the kits-proxy fee-quote item type
19364
+ * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). For withdrawal quotes, Circle fees use
19365
+ * `type: 'circle'`. `status` qualifies the fee (e.g. `'estimated'` for a
19366
+ * pre-sign cross-chain fee). Both are omitted on plain fees.
19121
19367
  *
19122
19368
  * @internal
19123
19369
  */ const feeSchema = z.object({