@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/index.cjs CHANGED
@@ -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');
@@ -3558,7 +3559,7 @@ class KitError extends Error {
3558
3559
  *
3559
3560
  * Error code ranges:
3560
3561
  * - 1100-1105: INPUT errors — invalid inputs, unsupported configurations
3561
- * - 8100-8104: SERVICE errors — retryable backend/provider failures
3562
+ * - 8100-8105: SERVICE errors — retryable backend/provider failures
3562
3563
  *
3563
3564
  * @example
3564
3565
  * ```typescript
@@ -3634,6 +3635,11 @@ class KitError extends Error {
3634
3635
  code: 8104,
3635
3636
  name: 'EARN_PAUSED',
3636
3637
  type: 'SERVICE'
3638
+ },
3639
+ /** Position PnL is still reconciling and can be retried. */ POSITION_PNL_PENDING: {
3640
+ code: 8105,
3641
+ name: 'EARN_POSITION_PNL_PENDING',
3642
+ type: 'SERVICE'
3637
3643
  }
3638
3644
  };
3639
3645
 
@@ -3661,7 +3667,7 @@ function getOptionalString(value) {
3661
3667
  *
3662
3668
  * SERVICE errors (RETRYABLE) — try again later:
3663
3669
  * - signing-failed, provider-error, rewards-fetch-failed,
3664
- * internal-error, vault-refresh-busy, off-chain-paused,
3670
+ * internal-error, vault-refresh-busy, off-chain-paused, position-PnL-pending,
3665
3671
  * bridge failures/status lookup failures
3666
3672
  *
3667
3673
  * Unrecognized codes fall through to `parseApiError` for HTTP-status-based
@@ -3848,6 +3854,13 @@ function getOptionalString(value) {
3848
3854
  recoverability: 'FATAL'
3849
3855
  }
3850
3856
  ],
3857
+ [
3858
+ 380416,
3859
+ {
3860
+ errorDef: EarnError.POSITION_PNL_PENDING,
3861
+ recoverability: 'RETRYABLE'
3862
+ }
3863
+ ],
3851
3864
  // Bridge (380_5XX)
3852
3865
  [
3853
3866
  380500,
@@ -3992,6 +4005,8 @@ function getOptionalString(value) {
3992
4005
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
3993
4006
  Blockchain["Codex"] = "Codex";
3994
4007
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
4008
+ Blockchain["Cronos"] = "Cronos";
4009
+ Blockchain["Cronos_Testnet"] = "Cronos_Testnet";
3995
4010
  Blockchain["Edge"] = "Edge";
3996
4011
  Blockchain["Edge_Testnet"] = "Edge_Testnet";
3997
4012
  Blockchain["Ethereum"] = "Ethereum";
@@ -4074,6 +4089,7 @@ exports.BridgeChain = void 0;
4074
4089
  BridgeChain["Avalanche"] = "Avalanche";
4075
4090
  BridgeChain["Base"] = "Base";
4076
4091
  BridgeChain["Codex"] = "Codex";
4092
+ BridgeChain["Cronos"] = "Cronos";
4077
4093
  BridgeChain["Edge"] = "Edge";
4078
4094
  BridgeChain["Ethereum"] = "Ethereum";
4079
4095
  BridgeChain["HyperEVM"] = "HyperEVM";
@@ -4098,6 +4114,7 @@ exports.BridgeChain = void 0;
4098
4114
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
4099
4115
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
4100
4116
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
4117
+ BridgeChain["Cronos_Testnet"] = "Cronos_Testnet";
4101
4118
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
4102
4119
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
4103
4120
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
@@ -5164,6 +5181,96 @@ exports.EarnChain = void 0;
5164
5181
  }
5165
5182
  });
5166
5183
 
5184
+ /**
5185
+ * Cronos Mainnet chain definition
5186
+ * @remarks
5187
+ * This represents the official production network for the Cronos blockchain.
5188
+ * Cronos is an EVM-compatible blockchain.
5189
+ */ const Cronos = defineChain({
5190
+ type: 'evm',
5191
+ chain: exports.Blockchain.Cronos,
5192
+ name: 'Cronos',
5193
+ title: 'Cronos Mainnet',
5194
+ nativeCurrency: {
5195
+ name: 'Cronos',
5196
+ symbol: 'CRO',
5197
+ decimals: 18
5198
+ },
5199
+ chainId: 25,
5200
+ isTestnet: false,
5201
+ explorerUrl: 'https://cronoscan.com/tx/{hash}',
5202
+ rpcEndpoints: [
5203
+ 'https://evm.cronos.org'
5204
+ ],
5205
+ eurcAddress: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
5206
+ usdcAddress: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
5207
+ usdtAddress: null,
5208
+ cctp: {
5209
+ domain: 32,
5210
+ contracts: {
5211
+ v2: {
5212
+ type: 'split',
5213
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
5214
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
5215
+ confirmations: 1,
5216
+ fastConfirmations: 1
5217
+ }
5218
+ },
5219
+ forwarderSupported: {
5220
+ source: false,
5221
+ destination: false
5222
+ }
5223
+ },
5224
+ kitContracts: {
5225
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
5226
+ }
5227
+ });
5228
+
5229
+ /**
5230
+ * Cronos Testnet chain definition
5231
+ * @remarks
5232
+ * This represents the official test network for the Cronos blockchain.
5233
+ * Cronos is an EVM-compatible blockchain.
5234
+ */ const CronosTestnet = defineChain({
5235
+ type: 'evm',
5236
+ chain: exports.Blockchain.Cronos_Testnet,
5237
+ name: 'Cronos Testnet',
5238
+ title: 'Cronos Testnet',
5239
+ nativeCurrency: {
5240
+ name: 'CRO',
5241
+ symbol: 'tCRO',
5242
+ decimals: 18
5243
+ },
5244
+ chainId: 338,
5245
+ isTestnet: true,
5246
+ explorerUrl: 'https://explorer.cronos.org/testnet/tx/{hash}',
5247
+ rpcEndpoints: [
5248
+ 'https://evm-t3.cronos.org'
5249
+ ],
5250
+ eurcAddress: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
5251
+ usdcAddress: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
5252
+ usdtAddress: null,
5253
+ cctp: {
5254
+ domain: 32,
5255
+ contracts: {
5256
+ v2: {
5257
+ type: 'split',
5258
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
5259
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
5260
+ confirmations: 1,
5261
+ fastConfirmations: 1
5262
+ }
5263
+ },
5264
+ forwarderSupported: {
5265
+ source: false,
5266
+ destination: false
5267
+ }
5268
+ },
5269
+ kitContracts: {
5270
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
5271
+ }
5272
+ });
5273
+
5167
5274
  /**
5168
5275
  * Edge Mainnet chain definition
5169
5276
  * @remarks
@@ -7515,6 +7622,8 @@ var Chains = {
7515
7622
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
7516
7623
  Codex: Codex,
7517
7624
  CodexTestnet: CodexTestnet,
7625
+ Cronos: Cronos,
7626
+ CronosTestnet: CronosTestnet,
7518
7627
  Edge: Edge,
7519
7628
  EdgeTestnet: EdgeTestnet,
7520
7629
  Ethereum: Ethereum,
@@ -9889,6 +9998,7 @@ function parseOrThrow(value, schema, context) {
9889
9998
  [exports.Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
9890
9999
  [exports.Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
9891
10000
  [exports.Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
10001
+ [exports.Blockchain.Cronos]: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
9892
10002
  [exports.Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
9893
10003
  [exports.Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
9894
10004
  [exports.Blockchain.Hedera]: '0.0.456858',
@@ -9922,6 +10032,7 @@ function parseOrThrow(value, schema, context) {
9922
10032
  [exports.Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
9923
10033
  [exports.Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
9924
10034
  [exports.Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
10035
+ [exports.Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
9925
10036
  [exports.Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
9926
10037
  [exports.Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
9927
10038
  [exports.Blockchain.Hedera_Testnet]: '0.0.429274',
@@ -9994,6 +10105,7 @@ function parseOrThrow(value, schema, context) {
9994
10105
  // =========================================================================
9995
10106
  [exports.Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
9996
10107
  [exports.Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
10108
+ [exports.Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
9997
10109
  [exports.Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
9998
10110
  [exports.Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
9999
10111
  [exports.Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
@@ -10002,6 +10114,7 @@ function parseOrThrow(value, schema, context) {
10002
10114
  // =========================================================================
10003
10115
  [exports.Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
10004
10116
  [exports.Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
10117
+ [exports.Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
10005
10118
  [exports.Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
10006
10119
  }
10007
10120
  };
@@ -11516,11 +11629,11 @@ function resolveOptions(options) {
11516
11629
  void emitAnalyticsLog(buildPayload$1(config, stepEntry?.[1] ?? fallbackEventType, errorDetails, context));
11517
11630
  }
11518
11631
 
11519
- var name$3 = "@circle-fin/bridge-kit";
11520
- var version$4 = "1.11.1";
11521
- var pkg$4 = {
11522
- name: name$3,
11523
- version: version$4};
11632
+ var name$4 = "@circle-fin/bridge-kit";
11633
+ var version$5 = "1.12.0";
11634
+ var pkg$5 = {
11635
+ name: name$4,
11636
+ version: version$5};
11524
11637
 
11525
11638
  const assertCustomFeePolicySymbol$2 = Symbol('assertCustomFeePolicy');
11526
11639
  /**
@@ -13546,7 +13659,7 @@ exports.TransferSpeed = void 0;
13546
13659
  const bridgeKitCaller = {
13547
13660
  type: 'kit',
13548
13661
  name: 'BridgeKit',
13549
- version: pkg$4.version
13662
+ version: pkg$5.version
13550
13663
  };
13551
13664
  // Create default runtime and tokens for invocation context resolution
13552
13665
  const defaults = {
@@ -14216,6 +14329,15 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
14216
14329
  ;
14217
14330
  const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
14218
14331
  ;
14332
+ // Hard execution caps: observed max + ~30% buffer, used as gasLimit overrides on
14333
+ // chains whose eth_estimateGas under-reports (e.g. Cronos EIP-7623 calldata floor).
14334
+ // Kept separate from the fee-estimate averages above.
14335
+ const APPROVE_GAS_LIMIT_EVM = 100_000n // ERC-20 approve observed max ~46k
14336
+ ;
14337
+ const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 300_000n // observed max 226_506 + ~30%
14338
+ ;
14339
+ const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839 + ~30%
14340
+ ;
14219
14341
  /**
14220
14342
  * The minimum finality threshold for CCTPv2 transfers.
14221
14343
  *
@@ -14244,6 +14366,27 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14244
14366
  'Content-Type': 'application/json'
14245
14367
  }
14246
14368
  };
14369
+ /**
14370
+ * Merges caller-provided polling overrides on top of {@link DEFAULT_CONFIG}.
14371
+ *
14372
+ * Headers are merged independently so caller-supplied headers augment the
14373
+ * defaults (such as `Content-Type`) rather than replacing them wholesale.
14374
+ *
14375
+ * @param config - Caller-provided polling configuration overrides
14376
+ * @param internalDefaults - Internal defaults applied before `config` (for example a
14377
+ * reduced `maxRetries` for one-shot requests); `config` still wins on conflict
14378
+ * @returns The effective polling configuration
14379
+ * @internal
14380
+ */ const mergeAttestationConfig = (config, internalDefaults = {})=>({
14381
+ ...DEFAULT_CONFIG$2,
14382
+ ...internalDefaults,
14383
+ ...config,
14384
+ headers: {
14385
+ ...DEFAULT_CONFIG$2.headers,
14386
+ ...internalDefaults.headers,
14387
+ ...config.headers
14388
+ }
14389
+ });
14247
14390
  /**
14248
14391
  * Type guard that verifies if an unknown value matches the AttestationMessage shape
14249
14392
  * and has all required properties.
@@ -14390,10 +14533,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14390
14533
  * ```
14391
14534
  */ const fetchAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14392
14535
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14393
- const effectiveConfig = {
14394
- ...DEFAULT_CONFIG$2,
14395
- ...config
14396
- };
14536
+ const effectiveConfig = mergeAttestationConfig(config);
14397
14537
  return await pollApiGet(url, isAttestationResponse, effectiveConfig);
14398
14538
  };
14399
14539
  /**
@@ -14436,11 +14576,9 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14436
14576
  */ const fetchAttestationWithoutStatusCheck = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14437
14577
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14438
14578
  // Use minimal retries since we're just fetching existing data
14439
- const effectiveConfig = {
14440
- ...DEFAULT_CONFIG$2,
14441
- maxRetries: 3,
14442
- ...config
14443
- };
14579
+ const effectiveConfig = mergeAttestationConfig(config, {
14580
+ maxRetries: 3
14581
+ });
14444
14582
  return await pollApiGet(url, isAttestationResponseWithoutStatusCheck, effectiveConfig);
14445
14583
  };
14446
14584
  /**
@@ -14500,10 +14638,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14500
14638
  * ```
14501
14639
  */ const fetchReAttestedAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14502
14640
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14503
- const effectiveConfig = {
14504
- ...DEFAULT_CONFIG$2,
14505
- ...config
14506
- };
14641
+ const effectiveConfig = mergeAttestationConfig(config);
14507
14642
  return await pollApiGet(url, isReAttestedAttestationResponse, effectiveConfig);
14508
14643
  };
14509
14644
  /**
@@ -14569,14 +14704,139 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14569
14704
  */ const requestReAttestation = async (nonce, isTestnet, config = {})=>{
14570
14705
  const url = buildReAttestUrl(nonce, isTestnet);
14571
14706
  // Use minimal retries since we're just submitting a request, not polling for state
14572
- const effectiveConfig = {
14573
- ...DEFAULT_CONFIG$2,
14574
- maxRetries: 3,
14575
- ...config
14576
- };
14707
+ const effectiveConfig = mergeAttestationConfig(config, {
14708
+ maxRetries: 3
14709
+ });
14577
14710
  return await pollApiPost(url, {}, isReAttestationResponse, effectiveConfig);
14578
14711
  };
14579
14712
 
14713
+ /**
14714
+ * Type guard that checks if the relayer has confirmed the mint transaction.
14715
+ *
14716
+ * This function validates that:
14717
+ * 1. The response has valid AttestationResponse structure
14718
+ * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
14719
+ *
14720
+ * If forwardState is 'FAILED', throws a non-retryable KitError.
14721
+ * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
14722
+ *
14723
+ * @param obj - The value to check, typically a parsed JSON response
14724
+ * @returns True if the relayer has confirmed the mint
14725
+ * @throws {KitError} With FATAL recoverability if structure is invalid
14726
+ * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
14727
+ * @throws {KitError} With RETRYABLE recoverability if still pending
14728
+ * @internal
14729
+ */ const isRelayerMintConfirmed = (obj)=>{
14730
+ // First check if the structure is valid
14731
+ if (!hasValidAttestationStructure(obj)) {
14732
+ throw new KitError({
14733
+ ...InputError.VALIDATION_FAILED,
14734
+ recoverability: 'FATAL',
14735
+ message: 'Invalid attestation response structure from IRIS API.'
14736
+ });
14737
+ }
14738
+ // Find the first message (typically there's only one)
14739
+ const message = obj.messages[0];
14740
+ if (!message) {
14741
+ throw new KitError({
14742
+ ...InputError.VALIDATION_FAILED,
14743
+ recoverability: 'FATAL',
14744
+ message: 'No attestation messages found in IRIS API response.'
14745
+ });
14746
+ }
14747
+ // Check for FAILED state - this is a permanent failure
14748
+ if (message.forwardState === 'FAILED') {
14749
+ throw new KitError({
14750
+ ...NetworkError.RELAYER_FORWARD_FAILED,
14751
+ recoverability: 'RESUMABLE',
14752
+ 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.',
14753
+ cause: {
14754
+ trace: {
14755
+ eventNonce: message.eventNonce,
14756
+ attestation: message.attestation,
14757
+ message: message.message
14758
+ }
14759
+ }
14760
+ });
14761
+ }
14762
+ // Check if mint is confirmed (or complete) with a valid transaction hash
14763
+ // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
14764
+ if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
14765
+ return true;
14766
+ }
14767
+ // Still pending or not yet processed - throw RETRYABLE error to continue polling
14768
+ throw new KitError({
14769
+ ...NetworkError.RELAYER_PENDING,
14770
+ recoverability: 'RETRYABLE',
14771
+ message: 'Relayer mint not ready. Waiting for confirmation.'
14772
+ });
14773
+ };
14774
+ /**
14775
+ * Polls the attestation API until the relayer's mint transaction is confirmed.
14776
+ *
14777
+ * This function is used when `useForwarder` is enabled. Instead of the user
14778
+ * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
14779
+ * This function polls until the relayer has submitted and confirmed the mint transaction.
14780
+ *
14781
+ * @remarks
14782
+ * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
14783
+ * - Throws immediately if `forwardState` is 'FAILED'
14784
+ * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
14785
+ * - Returns the attestation message with `forwardTxHash` populated
14786
+ *
14787
+ * @param sourceDomainId - The CCTP domain ID of the source chain
14788
+ * @param transactionHash - The transaction hash of the burn operation
14789
+ * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
14790
+ * @param config - Optional configuration overrides for polling behavior
14791
+ * @returns The attestation message with confirmed forwardTxHash
14792
+ * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
14793
+ * @throws {KitError} If timeout is reached while still pending
14794
+ *
14795
+ * @example
14796
+ * ```typescript
14797
+ * const attestation = await fetchRelayerMint(0, '0xabc...', false)
14798
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
14799
+ * ```
14800
+ */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14801
+ const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14802
+ const effectiveConfig = mergeAttestationConfig(config);
14803
+ let response;
14804
+ try {
14805
+ response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
14806
+ } catch (error) {
14807
+ // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
14808
+ if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
14809
+ throw new KitError({
14810
+ ...NetworkError.RELAYER_FORWARD_FAILED,
14811
+ recoverability: error.recoverability,
14812
+ message: error.message,
14813
+ cause: {
14814
+ ...error.cause,
14815
+ trace: {
14816
+ ...error.cause?.trace,
14817
+ burnTxHash: transactionHash
14818
+ }
14819
+ }
14820
+ });
14821
+ }
14822
+ throw error;
14823
+ }
14824
+ // Return the first message (which should have forwardTxHash)
14825
+ // Note: This check is needed for TypeScript type safety even though
14826
+ // isRelayerMintConfirmed validates messages[0] exists. The type guard
14827
+ // narrows the type at the call site, but TypeScript can't infer that
14828
+ // the array still has elements after pollApiGet returns.
14829
+ const message = response.messages[0];
14830
+ if (!message) {
14831
+ throw new KitError({
14832
+ ...InputError.VALIDATION_FAILED,
14833
+ recoverability: 'FATAL',
14834
+ message: 'No attestation messages found in response after polling.'
14835
+ });
14836
+ }
14837
+ return message;
14838
+ };
14839
+
14580
14840
  const assertCCTPv2WalletContextSymbol = Symbol('assertCCTPv2WalletContext');
14581
14841
  /**
14582
14842
  * Asserts that the provided parameters match the CCTPv2 wallet context interface.
@@ -15451,6 +15711,8 @@ function hasPendingState(analysis, result) {
15451
15711
  * - `adapter`: The adapter that will execute the transaction
15452
15712
  * - `confirmations`: The number of confirmations to wait for (defaults to 1)
15453
15713
  * - `timeout`: The timeout for the request in milliseconds
15714
+ * - `gasLimit`: Optional explicit gas limit (number) forwarded to EVM execute,
15715
+ * bypassing `eth_estimateGas`; ignored for non-EVM requests
15454
15716
  * @returns The bridge step with the transaction details and explorer URL
15455
15717
  * @throws If the transaction execution fails
15456
15718
  *
@@ -15465,7 +15727,7 @@ function hasPendingState(analysis, result) {
15465
15727
  * })
15466
15728
  * console.log('Transaction hash:', step.txHash)
15467
15729
  * ```
15468
- */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout }) {
15730
+ */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasLimit }) {
15469
15731
  const step = {
15470
15732
  name,
15471
15733
  state: 'pending'
@@ -15478,7 +15740,9 @@ function hasPendingState(analysis, result) {
15478
15740
  step.state = 'noop';
15479
15741
  return step;
15480
15742
  }
15481
- const txHash = await request.execute();
15743
+ const txHash = request.type === 'evm' && gasLimit !== undefined ? await request.execute({
15744
+ gasLimit
15745
+ }) : await request.execute();
15482
15746
  step.txHash = txHash;
15483
15747
  const retryOptions = {
15484
15748
  isRetryable: (err)=>isRetryableError$1(parseBlockchainError(err, {
@@ -15550,7 +15814,8 @@ function hasPendingState(analysis, result) {
15550
15814
  name: 'approve',
15551
15815
  adapter: params.source.adapter,
15552
15816
  chain: params.source.chain,
15553
- request: await provider.approve(params.source, approvalAmount)
15817
+ request: await provider.approve(params.source, approvalAmount),
15818
+ gasLimit: Number(APPROVE_GAS_LIMIT_EVM)
15554
15819
  });
15555
15820
  }
15556
15821
 
@@ -15577,7 +15842,8 @@ function hasPendingState(analysis, result) {
15577
15842
  name: 'burn',
15578
15843
  adapter: params.source.adapter,
15579
15844
  chain: params.source.chain,
15580
- request: await provider.burn(params)
15845
+ request: await provider.burn(params),
15846
+ gasLimit: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
15581
15847
  });
15582
15848
  }
15583
15849
 
@@ -15663,11 +15929,18 @@ function hasPendingState(analysis, result) {
15663
15929
  */ async function bridgeMint({ params, provider }, attestation) {
15664
15930
  // Validate attestation message matches transfer params
15665
15931
  await assertCCTPv2AttestationParams(attestation, params);
15932
+ const mintRequest = await provider.mint(params.source, params.destination, attestation);
15666
15933
  const step = await executePreparedChainRequest({
15667
15934
  name: 'mint',
15668
15935
  adapter: params.destination.adapter,
15669
15936
  chain: params.destination.chain,
15670
- request: await provider.mint(params.source, params.destination, attestation)
15937
+ request: mintRequest,
15938
+ // Some chains (e.g. Cronos) enforce an EIP-7623 calldata gas floor that
15939
+ // eth_estimateGas does not account for, returning a below-floor value
15940
+ // without reverting. Pinning to a value above the observed execution max
15941
+ // (310_839) bypasses re-estimation and guarantees we clear both the floor
15942
+ // and the actual execution cost.
15943
+ gasLimit: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
15671
15944
  });
15672
15945
  // Add forwarded: false for non-relayer mints
15673
15946
  return {
@@ -15705,136 +15978,6 @@ const mockAttestationMessage = {
15705
15978
  delayReason: null
15706
15979
  };
15707
15980
 
15708
- /**
15709
- * Type guard that checks if the relayer has confirmed the mint transaction.
15710
- *
15711
- * This function validates that:
15712
- * 1. The response has valid AttestationResponse structure
15713
- * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
15714
- *
15715
- * If forwardState is 'FAILED', throws a non-retryable KitError.
15716
- * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
15717
- *
15718
- * @param obj - The value to check, typically a parsed JSON response
15719
- * @returns True if the relayer has confirmed the mint
15720
- * @throws {KitError} With FATAL recoverability if structure is invalid
15721
- * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
15722
- * @throws {KitError} With RETRYABLE recoverability if still pending
15723
- * @internal
15724
- */ const isRelayerMintConfirmed = (obj)=>{
15725
- // First check if the structure is valid
15726
- if (!hasValidAttestationStructure(obj)) {
15727
- throw new KitError({
15728
- ...InputError.VALIDATION_FAILED,
15729
- recoverability: 'FATAL',
15730
- message: 'Invalid attestation response structure from IRIS API.'
15731
- });
15732
- }
15733
- // Find the first message (typically there's only one)
15734
- const message = obj.messages[0];
15735
- if (!message) {
15736
- throw new KitError({
15737
- ...InputError.VALIDATION_FAILED,
15738
- recoverability: 'FATAL',
15739
- message: 'No attestation messages found in IRIS API response.'
15740
- });
15741
- }
15742
- // Check for FAILED state - this is a permanent failure
15743
- if (message.forwardState === 'FAILED') {
15744
- throw new KitError({
15745
- ...NetworkError.RELAYER_FORWARD_FAILED,
15746
- recoverability: 'RESUMABLE',
15747
- 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.',
15748
- cause: {
15749
- trace: {
15750
- eventNonce: message.eventNonce,
15751
- attestation: message.attestation,
15752
- message: message.message
15753
- }
15754
- }
15755
- });
15756
- }
15757
- // Check if mint is confirmed (or complete) with a valid transaction hash
15758
- // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
15759
- if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
15760
- return true;
15761
- }
15762
- // Still pending or not yet processed - throw RETRYABLE error to continue polling
15763
- throw new KitError({
15764
- ...NetworkError.RELAYER_PENDING,
15765
- recoverability: 'RETRYABLE',
15766
- message: 'Relayer mint not ready. Waiting for confirmation.'
15767
- });
15768
- };
15769
- /**
15770
- * Polls the attestation API until the relayer's mint transaction is confirmed.
15771
- *
15772
- * This function is used when `useForwarder` is enabled. Instead of the user
15773
- * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
15774
- * This function polls until the relayer has submitted and confirmed the mint transaction.
15775
- *
15776
- * @remarks
15777
- * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
15778
- * - Throws immediately if `forwardState` is 'FAILED'
15779
- * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
15780
- * - Returns the attestation message with `forwardTxHash` populated
15781
- *
15782
- * @param sourceDomainId - The CCTP domain ID of the source chain
15783
- * @param transactionHash - The transaction hash of the burn operation
15784
- * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
15785
- * @param config - Optional configuration overrides for polling behavior
15786
- * @returns The attestation message with confirmed forwardTxHash
15787
- * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
15788
- * @throws {KitError} If timeout is reached while still pending
15789
- *
15790
- * @example
15791
- * ```typescript
15792
- * const attestation = await fetchRelayerMint(0, '0xabc...', false)
15793
- * console.log('Relayer mint tx:', attestation.forwardTxHash)
15794
- * ```
15795
- */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
15796
- const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
15797
- const effectiveConfig = {
15798
- ...DEFAULT_CONFIG$2,
15799
- ...config
15800
- };
15801
- let response;
15802
- try {
15803
- response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
15804
- } catch (error) {
15805
- // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
15806
- if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
15807
- throw new KitError({
15808
- ...NetworkError.RELAYER_FORWARD_FAILED,
15809
- recoverability: error.recoverability,
15810
- message: error.message,
15811
- cause: {
15812
- ...error.cause,
15813
- trace: {
15814
- ...error.cause?.trace,
15815
- burnTxHash: transactionHash
15816
- }
15817
- }
15818
- });
15819
- }
15820
- throw error;
15821
- }
15822
- // Return the first message (which should have forwardTxHash)
15823
- // Note: This check is needed for TypeScript type safety even though
15824
- // isRelayerMintConfirmed validates messages[0] exists. The type guard
15825
- // narrows the type at the call site, but TypeScript can't infer that
15826
- // the array still has elements after pollApiGet returns.
15827
- const message = response.messages[0];
15828
- if (!message) {
15829
- throw new KitError({
15830
- ...InputError.VALIDATION_FAILED,
15831
- recoverability: 'FATAL',
15832
- message: 'No attestation messages found in response after polling.'
15833
- });
15834
- }
15835
- return message;
15836
- };
15837
-
15838
15981
  /**
15839
15982
  * Executes the mint step for forwarding mode where Circle's relayer handles the mint.
15840
15983
  *
@@ -15848,6 +15991,7 @@ const mockAttestationMessage = {
15848
15991
  * immediately after the API confirms the mint.
15849
15992
  *
15850
15993
  * @param params - The bridge parameters containing source, destination, amount and config
15994
+ * @param provider - The CCTP v2 bridging provider
15851
15995
  * @param context - The step context containing burnTxHash from the burn step
15852
15996
  * @returns Promise resolving to the bridge step with transaction details
15853
15997
  * @throws {KitError} If burnTxHash is not available in context
@@ -15856,10 +16000,13 @@ const mockAttestationMessage = {
15856
16000
  *
15857
16001
  * @example
15858
16002
  * ```typescript
15859
- * const step = await bridgeRelayerMint(params, { burnTxHash: '0x...' })
16003
+ * const step = await bridgeRelayerMint(
16004
+ * { params, provider },
16005
+ * { burnTxHash: '0x...' }
16006
+ * )
15860
16007
  * console.log('Relayer mint tx:', step.txHash)
15861
16008
  * ```
15862
- */ async function bridgeRelayerMint(params, context) {
16009
+ */ async function bridgeRelayerMint({ params, provider }, context) {
15863
16010
  const step = {
15864
16011
  name: 'mint',
15865
16012
  state: 'pending',
@@ -15875,7 +16022,7 @@ const mockAttestationMessage = {
15875
16022
  }
15876
16023
  try {
15877
16024
  // Poll attestation API until relayer confirms the mint
15878
- const attestation = await fetchRelayerMint(params.source.chain.cctp.domain, context.burnTxHash, params.source.chain.isTestnet ?? false);
16025
+ const attestation = await provider.fetchRelayerMint(params.source, context.burnTxHash);
15879
16026
  // Extract the relayer's mint transaction hash
15880
16027
  const forwardTxHash = attestation.forwardTxHash;
15881
16028
  if (!forwardTxHash) {
@@ -15969,7 +16116,7 @@ const mockAttestationMessage = {
15969
16116
  name: 'mint',
15970
16117
  // bridgeRelayerMint validates context.burnTxHash internally with a KitError
15971
16118
  // bridgeMint requires attestationData which is validated here
15972
- executor: useForwarder ? async (params, _provider, context)=>{
16119
+ executor: useForwarder ? async (params, provider, context)=>{
15973
16120
  if (!context) {
15974
16121
  throw new KitError({
15975
16122
  ...InputError.VALIDATION_FAILED,
@@ -15977,7 +16124,10 @@ const mockAttestationMessage = {
15977
16124
  message: 'Step context is required for relayer mint'
15978
16125
  });
15979
16126
  }
15980
- return bridgeRelayerMint(params, context);
16127
+ return bridgeRelayerMint({
16128
+ params,
16129
+ provider
16130
+ }, context);
15981
16131
  } : async (params, provider, context)=>{
15982
16132
  if (!context?.attestationData) {
15983
16133
  throw new KitError({
@@ -16308,16 +16458,16 @@ const mockAttestationMessage = {
16308
16458
  return step;
16309
16459
  }
16310
16460
 
16311
- var version$3 = "1.8.5";
16312
- var pkg$3 = {
16313
- version: version$3};
16461
+ var version$4 = "1.9.0";
16462
+ var pkg$4 = {
16463
+ version: version$4};
16314
16464
 
16315
16465
  /**
16316
16466
  * Provider caller component for bridge operations.
16317
16467
  */ const BRIDGE_CALLER = {
16318
16468
  type: 'provider',
16319
16469
  name: 'CCTPV2BridgingProvider.bridge',
16320
- version: pkg$3.version
16470
+ version: pkg$4.version
16321
16471
  };
16322
16472
  /**
16323
16473
  * Resolve invocation context for bridge operations.
@@ -16690,7 +16840,7 @@ var pkg$3 = {
16690
16840
  */ const RETRY_CALLER = {
16691
16841
  type: 'provider',
16692
16842
  name: 'CCTPV2BridgingProvider.retry',
16693
- version: pkg$3.version
16843
+ version: pkg$4.version
16694
16844
  };
16695
16845
  /**
16696
16846
  * Resolve invocation context for retry operations.
@@ -17014,6 +17164,39 @@ var pkg$3 = {
17014
17164
  }
17015
17165
  }
17016
17166
 
17167
+ function isPlainObject(value) {
17168
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
17169
+ return false;
17170
+ }
17171
+ const prototype = Object.getPrototypeOf(value);
17172
+ return prototype === Object.prototype || prototype === null;
17173
+ }
17174
+ function assertHeadersConfig(headers, field) {
17175
+ if (headers === undefined) {
17176
+ return;
17177
+ }
17178
+ if (!isPlainObject(headers)) {
17179
+ throw createValidationFailedError$1(field, headers, `${field} must be a plain object with string header values when provided`);
17180
+ }
17181
+ for (const [name, value] of Object.entries(headers)){
17182
+ if (typeof value !== 'string') {
17183
+ throw createValidationFailedError$1(`${field}.${name}`, value, 'header values must be strings');
17184
+ }
17185
+ }
17186
+ }
17187
+ function assertCCTPV2Config(config) {
17188
+ if (!isPlainObject(config)) {
17189
+ throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
17190
+ }
17191
+ assertHeadersConfig(config['headers'], 'config.headers');
17192
+ const attestation = config['attestation'];
17193
+ if (attestation !== undefined) {
17194
+ if (!isPlainObject(attestation)) {
17195
+ throw createValidationFailedError$1('config.attestation', attestation, 'config.attestation must be a plain object when provided');
17196
+ }
17197
+ assertHeadersConfig(attestation['headers'], 'config.attestation.headers');
17198
+ }
17199
+ }
17017
17200
  /**
17018
17201
  * All chains that are supported by the CCTP v2 provider.
17019
17202
  *
@@ -17082,9 +17265,38 @@ var pkg$3 = {
17082
17265
  * @param config - Optional configuration overrides for the provider
17083
17266
  */ constructor(config = {}){
17084
17267
  super();
17268
+ assertCCTPV2Config(config);
17085
17269
  this.config = config;
17086
17270
  }
17087
17271
  /**
17272
+ * Resolves the effective polling configuration for an attestation request.
17273
+ *
17274
+ * Precedence (lowest to highest): provider `config.attestation`, then the
17275
+ * per-call `config`. Headers merge independently across
17276
+ * `config.attestation.headers`, the provider-level `config.headers`, and any
17277
+ * per-call `config.headers`, so a more specific header augments rather than
17278
+ * replaces the broader ones. The `headers` key is omitted entirely when no
17279
+ * headers are configured, leaving the attestation fetchers' defaults intact.
17280
+ *
17281
+ * @param config - Optional per-call polling configuration overrides
17282
+ * @returns The merged polling configuration passed to the attestation fetchers
17283
+ */ resolveAttestationConfig(config) {
17284
+ const headers = {
17285
+ ...this.config?.attestation?.headers,
17286
+ ...this.config?.headers,
17287
+ ...config?.headers
17288
+ };
17289
+ // Polling fields follow normal precedence; headers are merged separately
17290
+ // below so narrower config layers augment rather than replace broader ones.
17291
+ return {
17292
+ ...this.config?.attestation,
17293
+ ...config,
17294
+ ...Object.keys(headers).length > 0 ? {
17295
+ headers
17296
+ } : {}
17297
+ };
17298
+ }
17299
+ /**
17088
17300
  * Execute a cross-chain USDC bridge operation using the CCTP v2 protocol.
17089
17301
  *
17090
17302
  * This method orchestrates the complete CCTP v2 bridge flow including validation,
@@ -17510,11 +17722,7 @@ var pkg$3 = {
17510
17722
  */ async fetchAttestation(source, transactionHash, config) {
17511
17723
  assertCCTPv2WalletContext(source);
17512
17724
  try {
17513
- // Merge configs: defaults <- global config <- per-call config
17514
- const effectiveConfig = {
17515
- ...this.config?.attestation,
17516
- ...config
17517
- };
17725
+ const effectiveConfig = this.resolveAttestationConfig(config);
17518
17726
  const response = await fetchAttestation(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17519
17727
  const message = response.messages[0];
17520
17728
  if (!message) {
@@ -17531,6 +17739,49 @@ var pkg$3 = {
17531
17739
  }
17532
17740
  }
17533
17741
  /**
17742
+ * Polls attestation data until Circle's relayer mint transaction is confirmed.
17743
+ *
17744
+ * This method is used by forwarded transfers. It polls the same Iris
17745
+ * attestation endpoint as {@link CCTPV2BridgingProvider.fetchAttestation},
17746
+ * but waits for a completed relayer forward state and returns the attestation
17747
+ * message containing `forwardTxHash`.
17748
+ *
17749
+ * @typeParam TFromAdapterCapabilities - The type representing the capabilities of the source adapter
17750
+ * @param source - The source wallet context containing the chain definition and wallet address
17751
+ * @param transactionHash - The transaction hash of the burn operation
17752
+ * @param config - Optional polling configuration overrides for timeout, retries, delay, and headers
17753
+ * @returns A promise that resolves to the attestation message with `forwardTxHash`
17754
+ * @throws KitError If the relayer forward fails, the response is invalid, or polling times out
17755
+ *
17756
+ * @example
17757
+ * ```typescript
17758
+ * import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2'
17759
+ * import { Chains } from '@core/chains'
17760
+ *
17761
+ * const provider = new CCTPV2BridgingProvider({
17762
+ * headers: { 'X-Partner-UUID': '00000000-0000-0000-0000-000000000000' },
17763
+ * })
17764
+ *
17765
+ * const attestation = await provider.fetchRelayerMint(
17766
+ * {
17767
+ * adapter,
17768
+ * chain: Chains.EthereumSepolia,
17769
+ * address: '0x1234...',
17770
+ * },
17771
+ * '0xabc123...',
17772
+ * )
17773
+ *
17774
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
17775
+ * ```
17776
+ */ async fetchRelayerMint(source, transactionHash, config) {
17777
+ assertCCTPv2WalletContext(source);
17778
+ if (typeof transactionHash !== 'string' || transactionHash.trim() === '') {
17779
+ throw createValidationFailedError$1('transactionHash', transactionHash, 'transactionHash must be a non-empty string');
17780
+ }
17781
+ const effectiveConfig = this.resolveAttestationConfig(config);
17782
+ return await fetchRelayerMint(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17783
+ }
17784
+ /**
17534
17785
  * Requests a fresh attestation for an expired attestation.
17535
17786
  *
17536
17787
  * This method is used when the original attestation has expired before the mint
@@ -17584,11 +17835,7 @@ var pkg$3 = {
17584
17835
  throw new Error('Failed to re-attest: Invalid transaction hash');
17585
17836
  }
17586
17837
  try {
17587
- // Merge configs: defaults <- global config <- per-call config
17588
- const effectiveConfig = {
17589
- ...this.config?.attestation,
17590
- ...config
17591
- };
17838
+ const effectiveConfig = this.resolveAttestationConfig(config);
17592
17839
  // Step 1: Get existing attestation data to extract nonce
17593
17840
  const existingAttestation = await fetchAttestationWithoutStatusCheck(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17594
17841
  const nonce = existingAttestation.messages[0]?.eventNonce;
@@ -17873,8 +18120,13 @@ var pkg$3 = {
17873
18120
  /**
17874
18121
  * The default providers that will be used in addition to the providers provided
17875
18122
  * to the BridgeKit constructor.
17876
- */ const getDefaultProviders$3 = ()=>[
17877
- new CCTPV2BridgingProvider()
18123
+ *
18124
+ * @param config - Optional configuration forwarded to the default providers
18125
+ * @returns The default bridging providers
18126
+ */ const getDefaultProviders$3 = (config = {})=>[
18127
+ new CCTPV2BridgingProvider(config.headers ? {
18128
+ headers: config.headers
18129
+ } : {})
17878
18130
  ];
17879
18131
 
17880
18132
  /**
@@ -17985,7 +18237,7 @@ var pkg$3 = {
17985
18237
  ]
17986
18238
  ];
17987
18239
 
17988
- /** SDK name used in telemetry payloads. */ const SDK_NAME$2 = resolveKitSdkName(pkg$4.name);
18240
+ /** SDK name used in telemetry payloads. */ const SDK_NAME$2 = resolveKitSdkName(pkg$5.name);
17989
18241
  /**
17990
18242
  * Pick the most-relevant `txHash` to attach to an error telemetry payload.
17991
18243
  *
@@ -18026,7 +18278,7 @@ var pkg$3 = {
18026
18278
  */ const BRIDGE_KIT_CALLER = {
18027
18279
  type: 'kit',
18028
18280
  name: 'BridgeKit',
18029
- version: pkg$4.version
18281
+ version: pkg$5.version
18030
18282
  };
18031
18283
  /**
18032
18284
  * Merge BridgeKit's caller into the invocation metadata for retry operations.
@@ -18117,7 +18369,9 @@ var pkg$3 = {
18117
18369
  * ```
18118
18370
  */ constructor(config = {}){
18119
18371
  // Handle provider configuration
18120
- const defaultProviders = getDefaultProviders$3();
18372
+ const defaultProviders = getDefaultProviders$3(config.headers ? {
18373
+ headers: config.headers
18374
+ } : {});
18121
18375
  this.providers = [
18122
18376
  ...defaultProviders,
18123
18377
  ...config.providers ?? []
@@ -18126,7 +18380,7 @@ var pkg$3 = {
18126
18380
  this.disableErrorReporting = config.disableErrorReporting === true;
18127
18381
  this.telemetryConfig = {
18128
18382
  sdkName: SDK_NAME$2,
18129
- sdkVersion: pkg$4.version,
18383
+ sdkVersion: pkg$5.version,
18130
18384
  disabled: this.disableErrorReporting
18131
18385
  };
18132
18386
  for (const provider of this.providers){
@@ -18648,7 +18902,7 @@ var pkg$3 = {
18648
18902
  }
18649
18903
 
18650
18904
  // Auto-register this kit for user agent tracking
18651
- registerKit(`${pkg$4.name}/${pkg$4.version}`);
18905
+ registerKit(`${pkg$5.name}/${pkg$5.version}`);
18652
18906
 
18653
18907
  /**
18654
18908
  * Create a BridgeKit instance with optional developer fee configuration.
@@ -18697,6 +18951,9 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18697
18951
  const kit = new BridgeKit({
18698
18952
  ...context.disableErrorReporting != null && {
18699
18953
  disableErrorReporting: context.disableErrorReporting
18954
+ },
18955
+ ...context.headers != null && {
18956
+ headers: context.headers
18700
18957
  }
18701
18958
  });
18702
18959
  if (hasBoth) {
@@ -18714,11 +18971,11 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18714
18971
  return kit;
18715
18972
  };
18716
18973
 
18717
- var name$2 = "@circle-fin/swap-kit";
18718
- var version$2 = "1.3.1";
18719
- var pkg$2 = {
18720
- name: name$2,
18721
- version: version$2};
18974
+ var name$3 = "@circle-fin/swap-kit";
18975
+ var version$3 = "1.3.2";
18976
+ var pkg$3 = {
18977
+ name: name$3,
18978
+ version: version$3};
18722
18979
 
18723
18980
  const chainIdentifierField = zod.z.custom((value)=>chainIdentifierSchema.safeParse(value).success, {
18724
18981
  message: 'chain must be a valid chain identifier'
@@ -30088,7 +30345,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30088
30345
  ESTIMATE: 'swap_estimate'
30089
30346
  };
30090
30347
 
30091
- /** SDK name used in telemetry payloads. */ const SDK_NAME$1 = resolveKitSdkName(pkg$2.name);
30348
+ /** SDK name used in telemetry payloads. */ const SDK_NAME$1 = resolveKitSdkName(pkg$3.name);
30092
30349
  /**
30093
30350
  * A high-level class-based interface for same-chain and cross-chain token swap operations.
30094
30351
  *
@@ -30211,7 +30468,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30211
30468
  this.disableErrorReporting = config.disableErrorReporting === true;
30212
30469
  this.telemetryConfig = {
30213
30470
  sdkName: SDK_NAME$1,
30214
- sdkVersion: pkg$2.version,
30471
+ sdkVersion: pkg$3.version,
30215
30472
  disabled: this.disableErrorReporting
30216
30473
  };
30217
30474
  }
@@ -30667,7 +30924,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30667
30924
  }
30668
30925
 
30669
30926
  // Auto-register this kit for user agent tracking
30670
- registerKit(`${pkg$2.name}/${pkg$2.version}`);
30927
+ registerKit(`${pkg$3.name}/${pkg$3.version}`);
30671
30928
 
30672
30929
  /**
30673
30930
  * Create a SwapKit instance with optional developer fee configuration.
@@ -30762,11 +31019,11 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
30762
31019
  return kit;
30763
31020
  };
30764
31021
 
30765
- var name$1 = "@circle-fin/earn-kit";
30766
- var version$1 = "1.2.1";
30767
- var pkg$1 = {
30768
- name: name$1,
30769
- version: version$1};
31022
+ var name$2 = "@circle-fin/earn-kit";
31023
+ var version$2 = "1.2.2";
31024
+ var pkg$2 = {
31025
+ name: name$2,
31026
+ version: version$2};
30770
31027
 
30771
31028
  const EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS = [
30772
31029
  'USDC'
@@ -32338,10 +32595,11 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
32338
32595
  * Zod schema for a fee entry in an EarnKit API response.
32339
32596
  *
32340
32597
  * Shared across deposit and withdrawal responses (and reusable for real
32341
- * charged fees, not just quote estimates). `type` identifies the fee category
32342
- * for cross-chain deposit quotes this is the kits-proxy fee-quote item type
32343
- * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). `status` qualifies the fee (e.g.
32344
- * `'estimated'` for a pre-sign cross-chain fee). Both are omitted on plain fees.
32598
+ * charged fees, not just quote estimates). `type` identifies the fee category.
32599
+ * For cross-chain deposit quotes this is the kits-proxy fee-quote item type
32600
+ * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). For withdrawal quotes, Circle fees use
32601
+ * `type: 'circle'`. `status` qualifies the fee (e.g. `'estimated'` for a
32602
+ * pre-sign cross-chain fee). Both are omitted on plain fees.
32345
32603
  *
32346
32604
  * @internal
32347
32605
  */ const feeSchema = zod.z.object({
@@ -32613,6 +32871,60 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
32613
32871
  return claimRewardsQuoteResponseSchema.safeParse(value).success;
32614
32872
  }
32615
32873
 
32874
+ var name$1 = "@circle-fin/provider-earn-service";
32875
+ var version$1 = "1.2.2";
32876
+ var pkg$1 = {
32877
+ name: name$1,
32878
+ version: version$1};
32879
+
32880
+ /**
32881
+ * HTTP header name used to report the EarnKit SDK version to the backend.
32882
+ *
32883
+ * @internal
32884
+ */ const SDK_VERSION_HEADER = 'X-EarnKit-SDK-Version';
32885
+ /**
32886
+ * Resolve the SDK version string from its source components.
32887
+ *
32888
+ * Resolution order:
32889
+ * 1. The registered kit identifier (preferred) — e.g. `@circle-fin/earn-kit/1.1.0`
32890
+ * becomes `earn-kit/1.1.0`.
32891
+ * 2. The provider package itself (fallback when no kit is registered) —
32892
+ * e.g. `provider-earn-service/1.1.0`.
32893
+ * 3. The literal `unknown` when neither source is available.
32894
+ *
32895
+ * Kept as a pure function (no global / module reads) so every branch is
32896
+ * unit-testable.
32897
+ *
32898
+ * @param kitId - The registered kit identifier, or `undefined`.
32899
+ * @param providerName - The provider package name (e.g. `@circle-fin/provider-earn-service`).
32900
+ * @param providerVersion - The provider package version, or `undefined`.
32901
+ * @returns The formatted SDK version string.
32902
+ *
32903
+ * @internal
32904
+ */ function formatSdkVersion(kitId, providerName, providerVersion) {
32905
+ if (kitId !== undefined && kitId !== '') {
32906
+ // e.g. '@circle-fin/earn-kit/1.1.0' -> 'earn-kit/1.1.0'
32907
+ return resolveKitSdkName(kitId);
32908
+ }
32909
+ {
32910
+ // e.g. '@circle-fin/provider-earn-service' + '1.1.0' -> 'provider-earn-service/1.1.0'
32911
+ return `${resolveKitSdkName(providerName)}/${providerVersion}`;
32912
+ }
32913
+ }
32914
+ /**
32915
+ * Resolve the value for the {@link SDK_VERSION_HEADER} header.
32916
+ *
32917
+ * Reads the kit registered at runtime via {@link createRequestContext} and
32918
+ * falls back to this provider package's own version when no kit is registered.
32919
+ *
32920
+ * @returns The SDK version string, e.g. `earn-kit/1.1.0` or
32921
+ * `provider-earn-service/1.1.0`, or `unknown`.
32922
+ *
32923
+ * @internal
32924
+ */ function resolveSdkVersionHeader() {
32925
+ return formatSdkVersion(createRequestContext().kit, pkg$1.name, pkg$1.version);
32926
+ }
32927
+
32616
32928
  /**
32617
32929
  * Build an API polling config with optional authorization header,
32618
32930
  * and resolve the base URL (configurable for testing).
@@ -32623,9 +32935,16 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
32623
32935
  * @internal
32624
32936
  */ function buildConfig(serviceConfig) {
32625
32937
  const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
32938
+ const sdkVersion = resolveSdkVersionHeader();
32626
32939
  if (serviceConfig?.kitKey === undefined) {
32627
32940
  return {
32628
- pollingConfig: DEFAULT_CONFIG,
32941
+ pollingConfig: {
32942
+ ...DEFAULT_CONFIG,
32943
+ headers: {
32944
+ ...DEFAULT_CONFIG.headers,
32945
+ [SDK_VERSION_HEADER]: sdkVersion
32946
+ }
32947
+ },
32629
32948
  baseUrl
32630
32949
  };
32631
32950
  }
@@ -32641,6 +32960,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
32641
32960
  ...DEFAULT_CONFIG,
32642
32961
  headers: {
32643
32962
  ...DEFAULT_CONFIG.headers,
32963
+ [SDK_VERSION_HEADER]: sdkVersion,
32644
32964
  Authorization: `Bearer ${serviceConfig.kitKey}`
32645
32965
  }
32646
32966
  },
@@ -33373,9 +33693,15 @@ function toWithdrawalQuoteInfo(data) {
33373
33693
  symbol: data.asset,
33374
33694
  amount: Amount.fromJSON(data.maxWithdrawable)
33375
33695
  },
33376
- fees: data.fees.map((fee)=>({
33377
- symbol: fee.token,
33378
- amount: Amount.fromJSON(fee.amount)
33696
+ fees: data.fees.map(({ token, amount, type, status })=>({
33697
+ symbol: token,
33698
+ amount: Amount.fromJSON(amount),
33699
+ ...type !== undefined && {
33700
+ type
33701
+ },
33702
+ ...status !== undefined && {
33703
+ status
33704
+ }
33379
33705
  })),
33380
33706
  gasFees: [],
33381
33707
  // Wire format uses `warnings`, but the SDK surface uses
@@ -36594,7 +36920,7 @@ function formatRetryResult(operation, result) {
36594
36920
  }
36595
36921
 
36596
36922
  // Auto-register this kit for user agent tracking
36597
- registerKit(`${pkg$1.name}/${pkg$1.version}`);
36923
+ registerKit(`${pkg$2.name}/${pkg$2.version}`);
36598
36924
 
36599
36925
  /**
36600
36926
  * Create an EarnKit instance for AppKit earn operations.
@@ -36689,12 +37015,17 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36689
37015
  * The type annotation `readonly TokenAlias[]` ensures this array stays in sync
36690
37016
  * with the TokenAlias type definition - TypeScript will enforce any changes.
36691
37017
  *
36692
- * For swap operations, additional tokens (EURC, DAI, USDE, PYUSD) are supported
37018
+ * `USDC`, `USDT`, and `NATIVE` map to dedicated adapter transfer actions.
37019
+ * `EURC` has no dedicated action — it resolves to the chain's `eurcAddress`
37020
+ * and uses the generic token transfer action (see {@link prepareSend}).
37021
+ *
37022
+ * For swap operations, additional tokens (DAI, USDE, PYUSD) are supported
36693
37023
  * via SwapKit's SupportedToken type.
36694
37024
  */ const TOKEN_ALIASES = [
36695
37025
  'USDC',
36696
37026
  'USDT',
36697
- 'NATIVE'
37027
+ 'NATIVE',
37028
+ 'EURC'
36698
37029
  ];
36699
37030
  /**
36700
37031
  * Check if a token string is a known alias.
@@ -36721,7 +37052,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36721
37052
  * Type guard to check if a string is a valid token address for a chain.
36722
37053
  *
36723
37054
  * This function verifies that a token string is:
36724
- * 1. Not a known alias ('USDC', 'USDT', 'NATIVE')
37055
+ * 1. Not a known alias ('USDC', 'USDT', 'NATIVE', 'EURC')
36725
37056
  * 2. A valid address format for the specified chain
36726
37057
  *
36727
37058
  * Use this to narrow the type to `TokenAddress` in TypeScript.
@@ -36751,7 +37082,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36751
37082
  * Validate and classify a token identifier.
36752
37083
  *
36753
37084
  * This function determines whether a token string is:
36754
- * 1. A known alias ('USDC', 'USDT', or 'NATIVE')
37085
+ * 1. A known alias ('USDC', 'USDT', 'NATIVE', or 'EURC')
36755
37086
  * 2. A valid token address for the given chain
36756
37087
  * 3. An invalid/unrecognized token identifier
36757
37088
  *
@@ -37005,8 +37336,11 @@ const tokens = createTokenRegistry();
37005
37336
  * from `to` (explicit address string or adapter), and normalize the human‑readable
37006
37337
  * `amount` into token units.
37007
37338
  *
37008
- * For known token aliases, uses dedicated transfer actions
37009
- * with hardcoded decimals (USDC and USDT use 6 decimals; native uses the source chain's native decimals). For custom token addresses, uses the generic token.transfer
37339
+ * For the `USDC`, `USDT`, and `NATIVE` aliases, uses dedicated transfer actions
37340
+ * with hardcoded decimals (USDC and USDT use 6 decimals; native uses the source chain's native decimals).
37341
+ * The `EURC` alias resolves to the chain's `eurcAddress` (6 decimals from the token
37342
+ * registry) and routes through the generic token.transfer action; it is rejected on
37343
+ * chains where `eurcAddress` is not set. For custom token addresses, uses the generic token.transfer
37010
37344
  * action and fetches decimals dynamically from the contract.
37011
37345
  *
37012
37346
  * @param params - The send parameters including source context, destination, amount and token.
@@ -37099,6 +37433,21 @@ const tokens = createTokenRegistry();
37099
37433
  preparedSendRequest = await fromAdapter.prepareAction('native.transfer', requestParams, operationContext);
37100
37434
  break;
37101
37435
  }
37436
+ case 'EURC':
37437
+ {
37438
+ // EURC has no dedicated transfer action. Resolve its contract address
37439
+ // from the chain definition and route through the generic
37440
+ // token.transfer action. `eurcAddress` is null where EURC isn't deployed.
37441
+ const eurcAddress = fromChain.eurcAddress;
37442
+ if (eurcAddress === null) {
37443
+ throw createUnsupportedTokenError(token, fromChain.name);
37444
+ }
37445
+ preparedSendRequest = await fromAdapter.prepareAction('token.transfer', {
37446
+ ...requestParams,
37447
+ tokenAddress: eurcAddress
37448
+ }, operationContext);
37449
+ break;
37450
+ }
37102
37451
  default:
37103
37452
  {
37104
37453
  throw createUnsupportedTokenError(String(token), fromChain.name);
@@ -37125,12 +37474,13 @@ const tokens = createTokenRegistry();
37125
37474
  * Fully prepares and executes the send transaction to transfer the specified amount and
37126
37475
  * token to the specified destination.
37127
37476
  *
37128
- * For known token aliases ('USDC', 'USDT', 'NATIVE'), uses optimized dedicated actions.
37129
- * For custom token addresses, uses the generic token.transfer action with dynamically
37130
- * fetched decimals.
37477
+ * For the 'USDC', 'USDT', and 'NATIVE' aliases, uses optimized dedicated actions.
37478
+ * The 'EURC' alias resolves to the chain's `eurcAddress` and routes through the generic
37479
+ * token.transfer action. For custom token addresses, uses the generic token.transfer action
37480
+ * with dynamically fetched decimals.
37131
37481
  *
37132
37482
  * @param params - The send parameters: source context, destination (address or adapter),
37133
- * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE' or custom address, defaults to 'USDC').
37483
+ * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE' | 'EURC' or custom address, defaults to 'USDC').
37134
37484
  * @returns A BridgeStep object with transaction details including hash, status, and explorer URL.
37135
37485
  * @throws KitError INPUT_VALIDATION_FAILED if parameters are invalid.
37136
37486
  * @throws KitError INPUT_UNSUPPORTED_TOKEN if the token is invalid for the chain.
@@ -37148,6 +37498,17 @@ const tokens = createTokenRegistry();
37148
37498
  * console.log('Transaction hash:', step.txHash)
37149
37499
  * ```
37150
37500
  *
37501
+ * @example Sending EURC on a chain where EURC is deployed (e.g. Arc Testnet)
37502
+ * ```typescript
37503
+ * const step = await send({
37504
+ * from: { adapter: sourceAdapter, chain: 'Arc_Testnet' },
37505
+ * to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
37506
+ * amount: '5.0',
37507
+ * token: 'EURC',
37508
+ * })
37509
+ * console.log('Transaction hash:', step.txHash)
37510
+ * ```
37511
+ *
37151
37512
  * @example Sending a custom token (DAI) to an explicit address
37152
37513
  * ```typescript
37153
37514
  * const step = await send({
@@ -37189,21 +37550,22 @@ const tokens = createTokenRegistry();
37189
37550
  };
37190
37551
 
37191
37552
  /**
37192
- * Estimate the network fees to send USDC, USDT, or a supported native token.
37553
+ * Estimate the network fees to send USDC, USDT, EURC, or a supported native token.
37193
37554
  *
37194
37555
  * Prepare the send operation (including validating inputs and resolving the recipient when
37195
37556
  * provided as an adapter) and return an {@link EstimatedGas} object. This function does
37196
37557
  * not submit any transaction.
37197
37558
  *
37198
37559
  * @remarks
37199
- * - Selects the transfer handler based on `token` ('USDC' by default, 'USDT', or 'NATIVE').
37560
+ * - Selects the transfer handler based on `token` ('USDC' by default, 'USDT', 'NATIVE', or 'EURC').
37561
+ * 'EURC' is only supported on chains where `eurcAddress` is configured.
37200
37562
  * - When `to` is an {@link Adapter}, the recipient address is derived from the adapter.
37201
37563
  * - Rejects transfers where the resolved `to` equals the source `from` address.
37202
37564
  * - Interprets `amount` as a human-readable decimal string (USDC and USDT scaled to 6 decimals,
37203
37565
  * native EVM value typically scaled to 18 decimals).
37204
37566
  *
37205
37567
  * @param params - The send parameters: source context, destination (address or adapter),
37206
- * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE', defaults to 'USDC').
37568
+ * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE' | 'EURC', defaults to 'USDC').
37207
37569
  * @returns The estimated gas information including `gas`, `gasPrice`, and total `fee`.
37208
37570
  * @throws Error If parameters are invalid.
37209
37571
  * @throws Error If the token is unsupported or the send cannot be prepared.
@@ -37910,7 +38272,7 @@ async function deposit$2(context, params) {
37910
38272
  }
37911
38273
 
37912
38274
  var name = "@circle-fin/unified-balance-kit";
37913
- var version = "1.2.1";
38275
+ var version = "1.2.2";
37914
38276
  var pkg = {
37915
38277
  name: name,
37916
38278
  version: version};