@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.mjs CHANGED
@@ -22,6 +22,7 @@ import { parseUnits as parseUnits$1, formatUnits as formatUnits$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';
@@ -3551,7 +3552,7 @@ class KitError extends Error {
3551
3552
  *
3552
3553
  * Error code ranges:
3553
3554
  * - 1100-1105: INPUT errors — invalid inputs, unsupported configurations
3554
- * - 8100-8104: SERVICE errors — retryable backend/provider failures
3555
+ * - 8100-8105: SERVICE errors — retryable backend/provider failures
3555
3556
  *
3556
3557
  * @example
3557
3558
  * ```typescript
@@ -3627,6 +3628,11 @@ class KitError extends Error {
3627
3628
  code: 8104,
3628
3629
  name: 'EARN_PAUSED',
3629
3630
  type: 'SERVICE'
3631
+ },
3632
+ /** Position PnL is still reconciling and can be retried. */ POSITION_PNL_PENDING: {
3633
+ code: 8105,
3634
+ name: 'EARN_POSITION_PNL_PENDING',
3635
+ type: 'SERVICE'
3630
3636
  }
3631
3637
  };
3632
3638
 
@@ -3654,7 +3660,7 @@ function getOptionalString(value) {
3654
3660
  *
3655
3661
  * SERVICE errors (RETRYABLE) — try again later:
3656
3662
  * - signing-failed, provider-error, rewards-fetch-failed,
3657
- * internal-error, vault-refresh-busy, off-chain-paused,
3663
+ * internal-error, vault-refresh-busy, off-chain-paused, position-PnL-pending,
3658
3664
  * bridge failures/status lookup failures
3659
3665
  *
3660
3666
  * Unrecognized codes fall through to `parseApiError` for HTTP-status-based
@@ -3841,6 +3847,13 @@ function getOptionalString(value) {
3841
3847
  recoverability: 'FATAL'
3842
3848
  }
3843
3849
  ],
3850
+ [
3851
+ 380416,
3852
+ {
3853
+ errorDef: EarnError.POSITION_PNL_PENDING,
3854
+ recoverability: 'RETRYABLE'
3855
+ }
3856
+ ],
3844
3857
  // Bridge (380_5XX)
3845
3858
  [
3846
3859
  380500,
@@ -3985,6 +3998,8 @@ function getOptionalString(value) {
3985
3998
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
3986
3999
  Blockchain["Codex"] = "Codex";
3987
4000
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
4001
+ Blockchain["Cronos"] = "Cronos";
4002
+ Blockchain["Cronos_Testnet"] = "Cronos_Testnet";
3988
4003
  Blockchain["Edge"] = "Edge";
3989
4004
  Blockchain["Edge_Testnet"] = "Edge_Testnet";
3990
4005
  Blockchain["Ethereum"] = "Ethereum";
@@ -4067,6 +4082,7 @@ var BridgeChain;
4067
4082
  BridgeChain["Avalanche"] = "Avalanche";
4068
4083
  BridgeChain["Base"] = "Base";
4069
4084
  BridgeChain["Codex"] = "Codex";
4085
+ BridgeChain["Cronos"] = "Cronos";
4070
4086
  BridgeChain["Edge"] = "Edge";
4071
4087
  BridgeChain["Ethereum"] = "Ethereum";
4072
4088
  BridgeChain["HyperEVM"] = "HyperEVM";
@@ -4091,6 +4107,7 @@ var BridgeChain;
4091
4107
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
4092
4108
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
4093
4109
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
4110
+ BridgeChain["Cronos_Testnet"] = "Cronos_Testnet";
4094
4111
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
4095
4112
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
4096
4113
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
@@ -5157,6 +5174,96 @@ var EarnChain;
5157
5174
  }
5158
5175
  });
5159
5176
 
5177
+ /**
5178
+ * Cronos Mainnet chain definition
5179
+ * @remarks
5180
+ * This represents the official production network for the Cronos blockchain.
5181
+ * Cronos is an EVM-compatible blockchain.
5182
+ */ const Cronos = defineChain({
5183
+ type: 'evm',
5184
+ chain: Blockchain.Cronos,
5185
+ name: 'Cronos',
5186
+ title: 'Cronos Mainnet',
5187
+ nativeCurrency: {
5188
+ name: 'Cronos',
5189
+ symbol: 'CRO',
5190
+ decimals: 18
5191
+ },
5192
+ chainId: 25,
5193
+ isTestnet: false,
5194
+ explorerUrl: 'https://cronoscan.com/tx/{hash}',
5195
+ rpcEndpoints: [
5196
+ 'https://evm.cronos.org'
5197
+ ],
5198
+ eurcAddress: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
5199
+ usdcAddress: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
5200
+ usdtAddress: null,
5201
+ cctp: {
5202
+ domain: 32,
5203
+ contracts: {
5204
+ v2: {
5205
+ type: 'split',
5206
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
5207
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
5208
+ confirmations: 1,
5209
+ fastConfirmations: 1
5210
+ }
5211
+ },
5212
+ forwarderSupported: {
5213
+ source: false,
5214
+ destination: false
5215
+ }
5216
+ },
5217
+ kitContracts: {
5218
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
5219
+ }
5220
+ });
5221
+
5222
+ /**
5223
+ * Cronos Testnet chain definition
5224
+ * @remarks
5225
+ * This represents the official test network for the Cronos blockchain.
5226
+ * Cronos is an EVM-compatible blockchain.
5227
+ */ const CronosTestnet = defineChain({
5228
+ type: 'evm',
5229
+ chain: Blockchain.Cronos_Testnet,
5230
+ name: 'Cronos Testnet',
5231
+ title: 'Cronos Testnet',
5232
+ nativeCurrency: {
5233
+ name: 'CRO',
5234
+ symbol: 'tCRO',
5235
+ decimals: 18
5236
+ },
5237
+ chainId: 338,
5238
+ isTestnet: true,
5239
+ explorerUrl: 'https://explorer.cronos.org/testnet/tx/{hash}',
5240
+ rpcEndpoints: [
5241
+ 'https://evm-t3.cronos.org'
5242
+ ],
5243
+ eurcAddress: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
5244
+ usdcAddress: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
5245
+ usdtAddress: null,
5246
+ cctp: {
5247
+ domain: 32,
5248
+ contracts: {
5249
+ v2: {
5250
+ type: 'split',
5251
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
5252
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
5253
+ confirmations: 1,
5254
+ fastConfirmations: 1
5255
+ }
5256
+ },
5257
+ forwarderSupported: {
5258
+ source: false,
5259
+ destination: false
5260
+ }
5261
+ },
5262
+ kitContracts: {
5263
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
5264
+ }
5265
+ });
5266
+
5160
5267
  /**
5161
5268
  * Edge Mainnet chain definition
5162
5269
  * @remarks
@@ -7508,6 +7615,8 @@ var Chains = /*#__PURE__*/Object.freeze({
7508
7615
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
7509
7616
  Codex: Codex,
7510
7617
  CodexTestnet: CodexTestnet,
7618
+ Cronos: Cronos,
7619
+ CronosTestnet: CronosTestnet,
7511
7620
  Edge: Edge,
7512
7621
  EdgeTestnet: EdgeTestnet,
7513
7622
  Ethereum: Ethereum,
@@ -9882,6 +9991,7 @@ function parseOrThrow(value, schema, context) {
9882
9991
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
9883
9992
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
9884
9993
  [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
9994
+ [Blockchain.Cronos]: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
9885
9995
  [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
9886
9996
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
9887
9997
  [Blockchain.Hedera]: '0.0.456858',
@@ -9915,6 +10025,7 @@ function parseOrThrow(value, schema, context) {
9915
10025
  [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
9916
10026
  [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
9917
10027
  [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
10028
+ [Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
9918
10029
  [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
9919
10030
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
9920
10031
  [Blockchain.Hedera_Testnet]: '0.0.429274',
@@ -9987,6 +10098,7 @@ function parseOrThrow(value, schema, context) {
9987
10098
  // =========================================================================
9988
10099
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
9989
10100
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
10101
+ [Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
9990
10102
  [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
9991
10103
  [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
9992
10104
  [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
@@ -9995,6 +10107,7 @@ function parseOrThrow(value, schema, context) {
9995
10107
  // =========================================================================
9996
10108
  [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
9997
10109
  [Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
10110
+ [Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
9998
10111
  [Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
9999
10112
  }
10000
10113
  };
@@ -11509,11 +11622,11 @@ function resolveOptions(options) {
11509
11622
  void emitAnalyticsLog(buildPayload$1(config, stepEntry?.[1] ?? fallbackEventType, errorDetails, context));
11510
11623
  }
11511
11624
 
11512
- var name$3 = "@circle-fin/bridge-kit";
11513
- var version$4 = "1.11.1";
11514
- var pkg$4 = {
11515
- name: name$3,
11516
- version: version$4};
11625
+ var name$4 = "@circle-fin/bridge-kit";
11626
+ var version$5 = "1.12.0";
11627
+ var pkg$5 = {
11628
+ name: name$4,
11629
+ version: version$5};
11517
11630
 
11518
11631
  const assertCustomFeePolicySymbol$2 = Symbol('assertCustomFeePolicy');
11519
11632
  /**
@@ -13539,7 +13652,7 @@ var TransferSpeed;
13539
13652
  const bridgeKitCaller = {
13540
13653
  type: 'kit',
13541
13654
  name: 'BridgeKit',
13542
- version: pkg$4.version
13655
+ version: pkg$5.version
13543
13656
  };
13544
13657
  // Create default runtime and tokens for invocation context resolution
13545
13658
  const defaults = {
@@ -14209,6 +14322,15 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
14209
14322
  ;
14210
14323
  const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
14211
14324
  ;
14325
+ // Hard execution caps: observed max + ~30% buffer, used as gasLimit overrides on
14326
+ // chains whose eth_estimateGas under-reports (e.g. Cronos EIP-7623 calldata floor).
14327
+ // Kept separate from the fee-estimate averages above.
14328
+ const APPROVE_GAS_LIMIT_EVM = 100_000n // ERC-20 approve observed max ~46k
14329
+ ;
14330
+ const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 300_000n // observed max 226_506 + ~30%
14331
+ ;
14332
+ const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839 + ~30%
14333
+ ;
14212
14334
  /**
14213
14335
  * The minimum finality threshold for CCTPv2 transfers.
14214
14336
  *
@@ -14237,6 +14359,27 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14237
14359
  'Content-Type': 'application/json'
14238
14360
  }
14239
14361
  };
14362
+ /**
14363
+ * Merges caller-provided polling overrides on top of {@link DEFAULT_CONFIG}.
14364
+ *
14365
+ * Headers are merged independently so caller-supplied headers augment the
14366
+ * defaults (such as `Content-Type`) rather than replacing them wholesale.
14367
+ *
14368
+ * @param config - Caller-provided polling configuration overrides
14369
+ * @param internalDefaults - Internal defaults applied before `config` (for example a
14370
+ * reduced `maxRetries` for one-shot requests); `config` still wins on conflict
14371
+ * @returns The effective polling configuration
14372
+ * @internal
14373
+ */ const mergeAttestationConfig = (config, internalDefaults = {})=>({
14374
+ ...DEFAULT_CONFIG$2,
14375
+ ...internalDefaults,
14376
+ ...config,
14377
+ headers: {
14378
+ ...DEFAULT_CONFIG$2.headers,
14379
+ ...internalDefaults.headers,
14380
+ ...config.headers
14381
+ }
14382
+ });
14240
14383
  /**
14241
14384
  * Type guard that verifies if an unknown value matches the AttestationMessage shape
14242
14385
  * and has all required properties.
@@ -14383,10 +14526,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14383
14526
  * ```
14384
14527
  */ const fetchAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14385
14528
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14386
- const effectiveConfig = {
14387
- ...DEFAULT_CONFIG$2,
14388
- ...config
14389
- };
14529
+ const effectiveConfig = mergeAttestationConfig(config);
14390
14530
  return await pollApiGet(url, isAttestationResponse, effectiveConfig);
14391
14531
  };
14392
14532
  /**
@@ -14429,11 +14569,9 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14429
14569
  */ const fetchAttestationWithoutStatusCheck = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14430
14570
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14431
14571
  // Use minimal retries since we're just fetching existing data
14432
- const effectiveConfig = {
14433
- ...DEFAULT_CONFIG$2,
14434
- maxRetries: 3,
14435
- ...config
14436
- };
14572
+ const effectiveConfig = mergeAttestationConfig(config, {
14573
+ maxRetries: 3
14574
+ });
14437
14575
  return await pollApiGet(url, isAttestationResponseWithoutStatusCheck, effectiveConfig);
14438
14576
  };
14439
14577
  /**
@@ -14493,10 +14631,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14493
14631
  * ```
14494
14632
  */ const fetchReAttestedAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14495
14633
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14496
- const effectiveConfig = {
14497
- ...DEFAULT_CONFIG$2,
14498
- ...config
14499
- };
14634
+ const effectiveConfig = mergeAttestationConfig(config);
14500
14635
  return await pollApiGet(url, isReAttestedAttestationResponse, effectiveConfig);
14501
14636
  };
14502
14637
  /**
@@ -14562,14 +14697,139 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14562
14697
  */ const requestReAttestation = async (nonce, isTestnet, config = {})=>{
14563
14698
  const url = buildReAttestUrl(nonce, isTestnet);
14564
14699
  // Use minimal retries since we're just submitting a request, not polling for state
14565
- const effectiveConfig = {
14566
- ...DEFAULT_CONFIG$2,
14567
- maxRetries: 3,
14568
- ...config
14569
- };
14700
+ const effectiveConfig = mergeAttestationConfig(config, {
14701
+ maxRetries: 3
14702
+ });
14570
14703
  return await pollApiPost(url, {}, isReAttestationResponse, effectiveConfig);
14571
14704
  };
14572
14705
 
14706
+ /**
14707
+ * Type guard that checks if the relayer has confirmed the mint transaction.
14708
+ *
14709
+ * This function validates that:
14710
+ * 1. The response has valid AttestationResponse structure
14711
+ * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
14712
+ *
14713
+ * If forwardState is 'FAILED', throws a non-retryable KitError.
14714
+ * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
14715
+ *
14716
+ * @param obj - The value to check, typically a parsed JSON response
14717
+ * @returns True if the relayer has confirmed the mint
14718
+ * @throws {KitError} With FATAL recoverability if structure is invalid
14719
+ * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
14720
+ * @throws {KitError} With RETRYABLE recoverability if still pending
14721
+ * @internal
14722
+ */ const isRelayerMintConfirmed = (obj)=>{
14723
+ // First check if the structure is valid
14724
+ if (!hasValidAttestationStructure(obj)) {
14725
+ throw new KitError({
14726
+ ...InputError.VALIDATION_FAILED,
14727
+ recoverability: 'FATAL',
14728
+ message: 'Invalid attestation response structure from IRIS API.'
14729
+ });
14730
+ }
14731
+ // Find the first message (typically there's only one)
14732
+ const message = obj.messages[0];
14733
+ if (!message) {
14734
+ throw new KitError({
14735
+ ...InputError.VALIDATION_FAILED,
14736
+ recoverability: 'FATAL',
14737
+ message: 'No attestation messages found in IRIS API response.'
14738
+ });
14739
+ }
14740
+ // Check for FAILED state - this is a permanent failure
14741
+ if (message.forwardState === 'FAILED') {
14742
+ throw new KitError({
14743
+ ...NetworkError.RELAYER_FORWARD_FAILED,
14744
+ recoverability: 'RESUMABLE',
14745
+ 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.',
14746
+ cause: {
14747
+ trace: {
14748
+ eventNonce: message.eventNonce,
14749
+ attestation: message.attestation,
14750
+ message: message.message
14751
+ }
14752
+ }
14753
+ });
14754
+ }
14755
+ // Check if mint is confirmed (or complete) with a valid transaction hash
14756
+ // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
14757
+ if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
14758
+ return true;
14759
+ }
14760
+ // Still pending or not yet processed - throw RETRYABLE error to continue polling
14761
+ throw new KitError({
14762
+ ...NetworkError.RELAYER_PENDING,
14763
+ recoverability: 'RETRYABLE',
14764
+ message: 'Relayer mint not ready. Waiting for confirmation.'
14765
+ });
14766
+ };
14767
+ /**
14768
+ * Polls the attestation API until the relayer's mint transaction is confirmed.
14769
+ *
14770
+ * This function is used when `useForwarder` is enabled. Instead of the user
14771
+ * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
14772
+ * This function polls until the relayer has submitted and confirmed the mint transaction.
14773
+ *
14774
+ * @remarks
14775
+ * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
14776
+ * - Throws immediately if `forwardState` is 'FAILED'
14777
+ * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
14778
+ * - Returns the attestation message with `forwardTxHash` populated
14779
+ *
14780
+ * @param sourceDomainId - The CCTP domain ID of the source chain
14781
+ * @param transactionHash - The transaction hash of the burn operation
14782
+ * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
14783
+ * @param config - Optional configuration overrides for polling behavior
14784
+ * @returns The attestation message with confirmed forwardTxHash
14785
+ * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
14786
+ * @throws {KitError} If timeout is reached while still pending
14787
+ *
14788
+ * @example
14789
+ * ```typescript
14790
+ * const attestation = await fetchRelayerMint(0, '0xabc...', false)
14791
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
14792
+ * ```
14793
+ */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14794
+ const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14795
+ const effectiveConfig = mergeAttestationConfig(config);
14796
+ let response;
14797
+ try {
14798
+ response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
14799
+ } catch (error) {
14800
+ // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
14801
+ if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
14802
+ throw new KitError({
14803
+ ...NetworkError.RELAYER_FORWARD_FAILED,
14804
+ recoverability: error.recoverability,
14805
+ message: error.message,
14806
+ cause: {
14807
+ ...error.cause,
14808
+ trace: {
14809
+ ...error.cause?.trace,
14810
+ burnTxHash: transactionHash
14811
+ }
14812
+ }
14813
+ });
14814
+ }
14815
+ throw error;
14816
+ }
14817
+ // Return the first message (which should have forwardTxHash)
14818
+ // Note: This check is needed for TypeScript type safety even though
14819
+ // isRelayerMintConfirmed validates messages[0] exists. The type guard
14820
+ // narrows the type at the call site, but TypeScript can't infer that
14821
+ // the array still has elements after pollApiGet returns.
14822
+ const message = response.messages[0];
14823
+ if (!message) {
14824
+ throw new KitError({
14825
+ ...InputError.VALIDATION_FAILED,
14826
+ recoverability: 'FATAL',
14827
+ message: 'No attestation messages found in response after polling.'
14828
+ });
14829
+ }
14830
+ return message;
14831
+ };
14832
+
14573
14833
  const assertCCTPv2WalletContextSymbol = Symbol('assertCCTPv2WalletContext');
14574
14834
  /**
14575
14835
  * Asserts that the provided parameters match the CCTPv2 wallet context interface.
@@ -15444,6 +15704,8 @@ function hasPendingState(analysis, result) {
15444
15704
  * - `adapter`: The adapter that will execute the transaction
15445
15705
  * - `confirmations`: The number of confirmations to wait for (defaults to 1)
15446
15706
  * - `timeout`: The timeout for the request in milliseconds
15707
+ * - `gasLimit`: Optional explicit gas limit (number) forwarded to EVM execute,
15708
+ * bypassing `eth_estimateGas`; ignored for non-EVM requests
15447
15709
  * @returns The bridge step with the transaction details and explorer URL
15448
15710
  * @throws If the transaction execution fails
15449
15711
  *
@@ -15458,7 +15720,7 @@ function hasPendingState(analysis, result) {
15458
15720
  * })
15459
15721
  * console.log('Transaction hash:', step.txHash)
15460
15722
  * ```
15461
- */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout }) {
15723
+ */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasLimit }) {
15462
15724
  const step = {
15463
15725
  name,
15464
15726
  state: 'pending'
@@ -15471,7 +15733,9 @@ function hasPendingState(analysis, result) {
15471
15733
  step.state = 'noop';
15472
15734
  return step;
15473
15735
  }
15474
- const txHash = await request.execute();
15736
+ const txHash = request.type === 'evm' && gasLimit !== undefined ? await request.execute({
15737
+ gasLimit
15738
+ }) : await request.execute();
15475
15739
  step.txHash = txHash;
15476
15740
  const retryOptions = {
15477
15741
  isRetryable: (err)=>isRetryableError$1(parseBlockchainError(err, {
@@ -15543,7 +15807,8 @@ function hasPendingState(analysis, result) {
15543
15807
  name: 'approve',
15544
15808
  adapter: params.source.adapter,
15545
15809
  chain: params.source.chain,
15546
- request: await provider.approve(params.source, approvalAmount)
15810
+ request: await provider.approve(params.source, approvalAmount),
15811
+ gasLimit: Number(APPROVE_GAS_LIMIT_EVM)
15547
15812
  });
15548
15813
  }
15549
15814
 
@@ -15570,7 +15835,8 @@ function hasPendingState(analysis, result) {
15570
15835
  name: 'burn',
15571
15836
  adapter: params.source.adapter,
15572
15837
  chain: params.source.chain,
15573
- request: await provider.burn(params)
15838
+ request: await provider.burn(params),
15839
+ gasLimit: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
15574
15840
  });
15575
15841
  }
15576
15842
 
@@ -15656,11 +15922,18 @@ function hasPendingState(analysis, result) {
15656
15922
  */ async function bridgeMint({ params, provider }, attestation) {
15657
15923
  // Validate attestation message matches transfer params
15658
15924
  await assertCCTPv2AttestationParams(attestation, params);
15925
+ const mintRequest = await provider.mint(params.source, params.destination, attestation);
15659
15926
  const step = await executePreparedChainRequest({
15660
15927
  name: 'mint',
15661
15928
  adapter: params.destination.adapter,
15662
15929
  chain: params.destination.chain,
15663
- request: await provider.mint(params.source, params.destination, attestation)
15930
+ request: mintRequest,
15931
+ // Some chains (e.g. Cronos) enforce an EIP-7623 calldata gas floor that
15932
+ // eth_estimateGas does not account for, returning a below-floor value
15933
+ // without reverting. Pinning to a value above the observed execution max
15934
+ // (310_839) bypasses re-estimation and guarantees we clear both the floor
15935
+ // and the actual execution cost.
15936
+ gasLimit: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
15664
15937
  });
15665
15938
  // Add forwarded: false for non-relayer mints
15666
15939
  return {
@@ -15698,136 +15971,6 @@ const mockAttestationMessage = {
15698
15971
  delayReason: null
15699
15972
  };
15700
15973
 
15701
- /**
15702
- * Type guard that checks if the relayer has confirmed the mint transaction.
15703
- *
15704
- * This function validates that:
15705
- * 1. The response has valid AttestationResponse structure
15706
- * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
15707
- *
15708
- * If forwardState is 'FAILED', throws a non-retryable KitError.
15709
- * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
15710
- *
15711
- * @param obj - The value to check, typically a parsed JSON response
15712
- * @returns True if the relayer has confirmed the mint
15713
- * @throws {KitError} With FATAL recoverability if structure is invalid
15714
- * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
15715
- * @throws {KitError} With RETRYABLE recoverability if still pending
15716
- * @internal
15717
- */ const isRelayerMintConfirmed = (obj)=>{
15718
- // First check if the structure is valid
15719
- if (!hasValidAttestationStructure(obj)) {
15720
- throw new KitError({
15721
- ...InputError.VALIDATION_FAILED,
15722
- recoverability: 'FATAL',
15723
- message: 'Invalid attestation response structure from IRIS API.'
15724
- });
15725
- }
15726
- // Find the first message (typically there's only one)
15727
- const message = obj.messages[0];
15728
- if (!message) {
15729
- throw new KitError({
15730
- ...InputError.VALIDATION_FAILED,
15731
- recoverability: 'FATAL',
15732
- message: 'No attestation messages found in IRIS API response.'
15733
- });
15734
- }
15735
- // Check for FAILED state - this is a permanent failure
15736
- if (message.forwardState === 'FAILED') {
15737
- throw new KitError({
15738
- ...NetworkError.RELAYER_FORWARD_FAILED,
15739
- recoverability: 'RESUMABLE',
15740
- 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.',
15741
- cause: {
15742
- trace: {
15743
- eventNonce: message.eventNonce,
15744
- attestation: message.attestation,
15745
- message: message.message
15746
- }
15747
- }
15748
- });
15749
- }
15750
- // Check if mint is confirmed (or complete) with a valid transaction hash
15751
- // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
15752
- if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
15753
- return true;
15754
- }
15755
- // Still pending or not yet processed - throw RETRYABLE error to continue polling
15756
- throw new KitError({
15757
- ...NetworkError.RELAYER_PENDING,
15758
- recoverability: 'RETRYABLE',
15759
- message: 'Relayer mint not ready. Waiting for confirmation.'
15760
- });
15761
- };
15762
- /**
15763
- * Polls the attestation API until the relayer's mint transaction is confirmed.
15764
- *
15765
- * This function is used when `useForwarder` is enabled. Instead of the user
15766
- * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
15767
- * This function polls until the relayer has submitted and confirmed the mint transaction.
15768
- *
15769
- * @remarks
15770
- * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
15771
- * - Throws immediately if `forwardState` is 'FAILED'
15772
- * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
15773
- * - Returns the attestation message with `forwardTxHash` populated
15774
- *
15775
- * @param sourceDomainId - The CCTP domain ID of the source chain
15776
- * @param transactionHash - The transaction hash of the burn operation
15777
- * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
15778
- * @param config - Optional configuration overrides for polling behavior
15779
- * @returns The attestation message with confirmed forwardTxHash
15780
- * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
15781
- * @throws {KitError} If timeout is reached while still pending
15782
- *
15783
- * @example
15784
- * ```typescript
15785
- * const attestation = await fetchRelayerMint(0, '0xabc...', false)
15786
- * console.log('Relayer mint tx:', attestation.forwardTxHash)
15787
- * ```
15788
- */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
15789
- const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
15790
- const effectiveConfig = {
15791
- ...DEFAULT_CONFIG$2,
15792
- ...config
15793
- };
15794
- let response;
15795
- try {
15796
- response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
15797
- } catch (error) {
15798
- // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
15799
- if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
15800
- throw new KitError({
15801
- ...NetworkError.RELAYER_FORWARD_FAILED,
15802
- recoverability: error.recoverability,
15803
- message: error.message,
15804
- cause: {
15805
- ...error.cause,
15806
- trace: {
15807
- ...error.cause?.trace,
15808
- burnTxHash: transactionHash
15809
- }
15810
- }
15811
- });
15812
- }
15813
- throw error;
15814
- }
15815
- // Return the first message (which should have forwardTxHash)
15816
- // Note: This check is needed for TypeScript type safety even though
15817
- // isRelayerMintConfirmed validates messages[0] exists. The type guard
15818
- // narrows the type at the call site, but TypeScript can't infer that
15819
- // the array still has elements after pollApiGet returns.
15820
- const message = response.messages[0];
15821
- if (!message) {
15822
- throw new KitError({
15823
- ...InputError.VALIDATION_FAILED,
15824
- recoverability: 'FATAL',
15825
- message: 'No attestation messages found in response after polling.'
15826
- });
15827
- }
15828
- return message;
15829
- };
15830
-
15831
15974
  /**
15832
15975
  * Executes the mint step for forwarding mode where Circle's relayer handles the mint.
15833
15976
  *
@@ -15841,6 +15984,7 @@ const mockAttestationMessage = {
15841
15984
  * immediately after the API confirms the mint.
15842
15985
  *
15843
15986
  * @param params - The bridge parameters containing source, destination, amount and config
15987
+ * @param provider - The CCTP v2 bridging provider
15844
15988
  * @param context - The step context containing burnTxHash from the burn step
15845
15989
  * @returns Promise resolving to the bridge step with transaction details
15846
15990
  * @throws {KitError} If burnTxHash is not available in context
@@ -15849,10 +15993,13 @@ const mockAttestationMessage = {
15849
15993
  *
15850
15994
  * @example
15851
15995
  * ```typescript
15852
- * const step = await bridgeRelayerMint(params, { burnTxHash: '0x...' })
15996
+ * const step = await bridgeRelayerMint(
15997
+ * { params, provider },
15998
+ * { burnTxHash: '0x...' }
15999
+ * )
15853
16000
  * console.log('Relayer mint tx:', step.txHash)
15854
16001
  * ```
15855
- */ async function bridgeRelayerMint(params, context) {
16002
+ */ async function bridgeRelayerMint({ params, provider }, context) {
15856
16003
  const step = {
15857
16004
  name: 'mint',
15858
16005
  state: 'pending',
@@ -15868,7 +16015,7 @@ const mockAttestationMessage = {
15868
16015
  }
15869
16016
  try {
15870
16017
  // Poll attestation API until relayer confirms the mint
15871
- const attestation = await fetchRelayerMint(params.source.chain.cctp.domain, context.burnTxHash, params.source.chain.isTestnet ?? false);
16018
+ const attestation = await provider.fetchRelayerMint(params.source, context.burnTxHash);
15872
16019
  // Extract the relayer's mint transaction hash
15873
16020
  const forwardTxHash = attestation.forwardTxHash;
15874
16021
  if (!forwardTxHash) {
@@ -15962,7 +16109,7 @@ const mockAttestationMessage = {
15962
16109
  name: 'mint',
15963
16110
  // bridgeRelayerMint validates context.burnTxHash internally with a KitError
15964
16111
  // bridgeMint requires attestationData which is validated here
15965
- executor: useForwarder ? async (params, _provider, context)=>{
16112
+ executor: useForwarder ? async (params, provider, context)=>{
15966
16113
  if (!context) {
15967
16114
  throw new KitError({
15968
16115
  ...InputError.VALIDATION_FAILED,
@@ -15970,7 +16117,10 @@ const mockAttestationMessage = {
15970
16117
  message: 'Step context is required for relayer mint'
15971
16118
  });
15972
16119
  }
15973
- return bridgeRelayerMint(params, context);
16120
+ return bridgeRelayerMint({
16121
+ params,
16122
+ provider
16123
+ }, context);
15974
16124
  } : async (params, provider, context)=>{
15975
16125
  if (!context?.attestationData) {
15976
16126
  throw new KitError({
@@ -16301,16 +16451,16 @@ const mockAttestationMessage = {
16301
16451
  return step;
16302
16452
  }
16303
16453
 
16304
- var version$3 = "1.8.5";
16305
- var pkg$3 = {
16306
- version: version$3};
16454
+ var version$4 = "1.9.0";
16455
+ var pkg$4 = {
16456
+ version: version$4};
16307
16457
 
16308
16458
  /**
16309
16459
  * Provider caller component for bridge operations.
16310
16460
  */ const BRIDGE_CALLER = {
16311
16461
  type: 'provider',
16312
16462
  name: 'CCTPV2BridgingProvider.bridge',
16313
- version: pkg$3.version
16463
+ version: pkg$4.version
16314
16464
  };
16315
16465
  /**
16316
16466
  * Resolve invocation context for bridge operations.
@@ -16683,7 +16833,7 @@ var pkg$3 = {
16683
16833
  */ const RETRY_CALLER = {
16684
16834
  type: 'provider',
16685
16835
  name: 'CCTPV2BridgingProvider.retry',
16686
- version: pkg$3.version
16836
+ version: pkg$4.version
16687
16837
  };
16688
16838
  /**
16689
16839
  * Resolve invocation context for retry operations.
@@ -17007,6 +17157,39 @@ var pkg$3 = {
17007
17157
  }
17008
17158
  }
17009
17159
 
17160
+ function isPlainObject(value) {
17161
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
17162
+ return false;
17163
+ }
17164
+ const prototype = Object.getPrototypeOf(value);
17165
+ return prototype === Object.prototype || prototype === null;
17166
+ }
17167
+ function assertHeadersConfig(headers, field) {
17168
+ if (headers === undefined) {
17169
+ return;
17170
+ }
17171
+ if (!isPlainObject(headers)) {
17172
+ throw createValidationFailedError$1(field, headers, `${field} must be a plain object with string header values when provided`);
17173
+ }
17174
+ for (const [name, value] of Object.entries(headers)){
17175
+ if (typeof value !== 'string') {
17176
+ throw createValidationFailedError$1(`${field}.${name}`, value, 'header values must be strings');
17177
+ }
17178
+ }
17179
+ }
17180
+ function assertCCTPV2Config(config) {
17181
+ if (!isPlainObject(config)) {
17182
+ throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
17183
+ }
17184
+ assertHeadersConfig(config['headers'], 'config.headers');
17185
+ const attestation = config['attestation'];
17186
+ if (attestation !== undefined) {
17187
+ if (!isPlainObject(attestation)) {
17188
+ throw createValidationFailedError$1('config.attestation', attestation, 'config.attestation must be a plain object when provided');
17189
+ }
17190
+ assertHeadersConfig(attestation['headers'], 'config.attestation.headers');
17191
+ }
17192
+ }
17010
17193
  /**
17011
17194
  * All chains that are supported by the CCTP v2 provider.
17012
17195
  *
@@ -17075,9 +17258,38 @@ var pkg$3 = {
17075
17258
  * @param config - Optional configuration overrides for the provider
17076
17259
  */ constructor(config = {}){
17077
17260
  super();
17261
+ assertCCTPV2Config(config);
17078
17262
  this.config = config;
17079
17263
  }
17080
17264
  /**
17265
+ * Resolves the effective polling configuration for an attestation request.
17266
+ *
17267
+ * Precedence (lowest to highest): provider `config.attestation`, then the
17268
+ * per-call `config`. Headers merge independently across
17269
+ * `config.attestation.headers`, the provider-level `config.headers`, and any
17270
+ * per-call `config.headers`, so a more specific header augments rather than
17271
+ * replaces the broader ones. The `headers` key is omitted entirely when no
17272
+ * headers are configured, leaving the attestation fetchers' defaults intact.
17273
+ *
17274
+ * @param config - Optional per-call polling configuration overrides
17275
+ * @returns The merged polling configuration passed to the attestation fetchers
17276
+ */ resolveAttestationConfig(config) {
17277
+ const headers = {
17278
+ ...this.config?.attestation?.headers,
17279
+ ...this.config?.headers,
17280
+ ...config?.headers
17281
+ };
17282
+ // Polling fields follow normal precedence; headers are merged separately
17283
+ // below so narrower config layers augment rather than replace broader ones.
17284
+ return {
17285
+ ...this.config?.attestation,
17286
+ ...config,
17287
+ ...Object.keys(headers).length > 0 ? {
17288
+ headers
17289
+ } : {}
17290
+ };
17291
+ }
17292
+ /**
17081
17293
  * Execute a cross-chain USDC bridge operation using the CCTP v2 protocol.
17082
17294
  *
17083
17295
  * This method orchestrates the complete CCTP v2 bridge flow including validation,
@@ -17503,11 +17715,7 @@ var pkg$3 = {
17503
17715
  */ async fetchAttestation(source, transactionHash, config) {
17504
17716
  assertCCTPv2WalletContext(source);
17505
17717
  try {
17506
- // Merge configs: defaults <- global config <- per-call config
17507
- const effectiveConfig = {
17508
- ...this.config?.attestation,
17509
- ...config
17510
- };
17718
+ const effectiveConfig = this.resolveAttestationConfig(config);
17511
17719
  const response = await fetchAttestation(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17512
17720
  const message = response.messages[0];
17513
17721
  if (!message) {
@@ -17524,6 +17732,49 @@ var pkg$3 = {
17524
17732
  }
17525
17733
  }
17526
17734
  /**
17735
+ * Polls attestation data until Circle's relayer mint transaction is confirmed.
17736
+ *
17737
+ * This method is used by forwarded transfers. It polls the same Iris
17738
+ * attestation endpoint as {@link CCTPV2BridgingProvider.fetchAttestation},
17739
+ * but waits for a completed relayer forward state and returns the attestation
17740
+ * message containing `forwardTxHash`.
17741
+ *
17742
+ * @typeParam TFromAdapterCapabilities - The type representing the capabilities of the source adapter
17743
+ * @param source - The source wallet context containing the chain definition and wallet address
17744
+ * @param transactionHash - The transaction hash of the burn operation
17745
+ * @param config - Optional polling configuration overrides for timeout, retries, delay, and headers
17746
+ * @returns A promise that resolves to the attestation message with `forwardTxHash`
17747
+ * @throws KitError If the relayer forward fails, the response is invalid, or polling times out
17748
+ *
17749
+ * @example
17750
+ * ```typescript
17751
+ * import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2'
17752
+ * import { Chains } from '@core/chains'
17753
+ *
17754
+ * const provider = new CCTPV2BridgingProvider({
17755
+ * headers: { 'X-Partner-UUID': '00000000-0000-0000-0000-000000000000' },
17756
+ * })
17757
+ *
17758
+ * const attestation = await provider.fetchRelayerMint(
17759
+ * {
17760
+ * adapter,
17761
+ * chain: Chains.EthereumSepolia,
17762
+ * address: '0x1234...',
17763
+ * },
17764
+ * '0xabc123...',
17765
+ * )
17766
+ *
17767
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
17768
+ * ```
17769
+ */ async fetchRelayerMint(source, transactionHash, config) {
17770
+ assertCCTPv2WalletContext(source);
17771
+ if (typeof transactionHash !== 'string' || transactionHash.trim() === '') {
17772
+ throw createValidationFailedError$1('transactionHash', transactionHash, 'transactionHash must be a non-empty string');
17773
+ }
17774
+ const effectiveConfig = this.resolveAttestationConfig(config);
17775
+ return await fetchRelayerMint(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17776
+ }
17777
+ /**
17527
17778
  * Requests a fresh attestation for an expired attestation.
17528
17779
  *
17529
17780
  * This method is used when the original attestation has expired before the mint
@@ -17577,11 +17828,7 @@ var pkg$3 = {
17577
17828
  throw new Error('Failed to re-attest: Invalid transaction hash');
17578
17829
  }
17579
17830
  try {
17580
- // Merge configs: defaults <- global config <- per-call config
17581
- const effectiveConfig = {
17582
- ...this.config?.attestation,
17583
- ...config
17584
- };
17831
+ const effectiveConfig = this.resolveAttestationConfig(config);
17585
17832
  // Step 1: Get existing attestation data to extract nonce
17586
17833
  const existingAttestation = await fetchAttestationWithoutStatusCheck(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17587
17834
  const nonce = existingAttestation.messages[0]?.eventNonce;
@@ -17866,8 +18113,13 @@ var pkg$3 = {
17866
18113
  /**
17867
18114
  * The default providers that will be used in addition to the providers provided
17868
18115
  * to the BridgeKit constructor.
17869
- */ const getDefaultProviders$3 = ()=>[
17870
- new CCTPV2BridgingProvider()
18116
+ *
18117
+ * @param config - Optional configuration forwarded to the default providers
18118
+ * @returns The default bridging providers
18119
+ */ const getDefaultProviders$3 = (config = {})=>[
18120
+ new CCTPV2BridgingProvider(config.headers ? {
18121
+ headers: config.headers
18122
+ } : {})
17871
18123
  ];
17872
18124
 
17873
18125
  /**
@@ -17978,7 +18230,7 @@ var pkg$3 = {
17978
18230
  ]
17979
18231
  ];
17980
18232
 
17981
- /** SDK name used in telemetry payloads. */ const SDK_NAME$2 = resolveKitSdkName(pkg$4.name);
18233
+ /** SDK name used in telemetry payloads. */ const SDK_NAME$2 = resolveKitSdkName(pkg$5.name);
17982
18234
  /**
17983
18235
  * Pick the most-relevant `txHash` to attach to an error telemetry payload.
17984
18236
  *
@@ -18019,7 +18271,7 @@ var pkg$3 = {
18019
18271
  */ const BRIDGE_KIT_CALLER = {
18020
18272
  type: 'kit',
18021
18273
  name: 'BridgeKit',
18022
- version: pkg$4.version
18274
+ version: pkg$5.version
18023
18275
  };
18024
18276
  /**
18025
18277
  * Merge BridgeKit's caller into the invocation metadata for retry operations.
@@ -18110,7 +18362,9 @@ var pkg$3 = {
18110
18362
  * ```
18111
18363
  */ constructor(config = {}){
18112
18364
  // Handle provider configuration
18113
- const defaultProviders = getDefaultProviders$3();
18365
+ const defaultProviders = getDefaultProviders$3(config.headers ? {
18366
+ headers: config.headers
18367
+ } : {});
18114
18368
  this.providers = [
18115
18369
  ...defaultProviders,
18116
18370
  ...config.providers ?? []
@@ -18119,7 +18373,7 @@ var pkg$3 = {
18119
18373
  this.disableErrorReporting = config.disableErrorReporting === true;
18120
18374
  this.telemetryConfig = {
18121
18375
  sdkName: SDK_NAME$2,
18122
- sdkVersion: pkg$4.version,
18376
+ sdkVersion: pkg$5.version,
18123
18377
  disabled: this.disableErrorReporting
18124
18378
  };
18125
18379
  for (const provider of this.providers){
@@ -18641,7 +18895,7 @@ var pkg$3 = {
18641
18895
  }
18642
18896
 
18643
18897
  // Auto-register this kit for user agent tracking
18644
- registerKit(`${pkg$4.name}/${pkg$4.version}`);
18898
+ registerKit(`${pkg$5.name}/${pkg$5.version}`);
18645
18899
 
18646
18900
  /**
18647
18901
  * Create a BridgeKit instance with optional developer fee configuration.
@@ -18690,6 +18944,9 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18690
18944
  const kit = new BridgeKit({
18691
18945
  ...context.disableErrorReporting != null && {
18692
18946
  disableErrorReporting: context.disableErrorReporting
18947
+ },
18948
+ ...context.headers != null && {
18949
+ headers: context.headers
18693
18950
  }
18694
18951
  });
18695
18952
  if (hasBoth) {
@@ -18707,11 +18964,11 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18707
18964
  return kit;
18708
18965
  };
18709
18966
 
18710
- var name$2 = "@circle-fin/swap-kit";
18711
- var version$2 = "1.3.1";
18712
- var pkg$2 = {
18713
- name: name$2,
18714
- version: version$2};
18967
+ var name$3 = "@circle-fin/swap-kit";
18968
+ var version$3 = "1.3.2";
18969
+ var pkg$3 = {
18970
+ name: name$3,
18971
+ version: version$3};
18715
18972
 
18716
18973
  const chainIdentifierField = z.custom((value)=>chainIdentifierSchema.safeParse(value).success, {
18717
18974
  message: 'chain must be a valid chain identifier'
@@ -30081,7 +30338,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30081
30338
  ESTIMATE: 'swap_estimate'
30082
30339
  };
30083
30340
 
30084
- /** SDK name used in telemetry payloads. */ const SDK_NAME$1 = resolveKitSdkName(pkg$2.name);
30341
+ /** SDK name used in telemetry payloads. */ const SDK_NAME$1 = resolveKitSdkName(pkg$3.name);
30085
30342
  /**
30086
30343
  * A high-level class-based interface for same-chain and cross-chain token swap operations.
30087
30344
  *
@@ -30204,7 +30461,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30204
30461
  this.disableErrorReporting = config.disableErrorReporting === true;
30205
30462
  this.telemetryConfig = {
30206
30463
  sdkName: SDK_NAME$1,
30207
- sdkVersion: pkg$2.version,
30464
+ sdkVersion: pkg$3.version,
30208
30465
  disabled: this.disableErrorReporting
30209
30466
  };
30210
30467
  }
@@ -30660,7 +30917,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30660
30917
  }
30661
30918
 
30662
30919
  // Auto-register this kit for user agent tracking
30663
- registerKit(`${pkg$2.name}/${pkg$2.version}`);
30920
+ registerKit(`${pkg$3.name}/${pkg$3.version}`);
30664
30921
 
30665
30922
  /**
30666
30923
  * Create a SwapKit instance with optional developer fee configuration.
@@ -30755,11 +31012,11 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
30755
31012
  return kit;
30756
31013
  };
30757
31014
 
30758
- var name$1 = "@circle-fin/earn-kit";
30759
- var version$1 = "1.2.1";
30760
- var pkg$1 = {
30761
- name: name$1,
30762
- version: version$1};
31015
+ var name$2 = "@circle-fin/earn-kit";
31016
+ var version$2 = "1.2.2";
31017
+ var pkg$2 = {
31018
+ name: name$2,
31019
+ version: version$2};
30763
31020
 
30764
31021
  const EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS = [
30765
31022
  'USDC'
@@ -32331,10 +32588,11 @@ const bridgeDepositPrepareReviewSchema = z.object({
32331
32588
  * Zod schema for a fee entry in an EarnKit API response.
32332
32589
  *
32333
32590
  * Shared across deposit and withdrawal responses (and reusable for real
32334
- * charged fees, not just quote estimates). `type` identifies the fee category
32335
- * for cross-chain deposit quotes this is the kits-proxy fee-quote item type
32336
- * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). `status` qualifies the fee (e.g.
32337
- * `'estimated'` for a pre-sign cross-chain fee). Both are omitted on plain fees.
32591
+ * charged fees, not just quote estimates). `type` identifies the fee category.
32592
+ * For cross-chain deposit quotes this is the kits-proxy fee-quote item type
32593
+ * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). For withdrawal quotes, Circle fees use
32594
+ * `type: 'circle'`. `status` qualifies the fee (e.g. `'estimated'` for a
32595
+ * pre-sign cross-chain fee). Both are omitted on plain fees.
32338
32596
  *
32339
32597
  * @internal
32340
32598
  */ const feeSchema = z.object({
@@ -32606,6 +32864,60 @@ const bridgeDepositPrepareReviewSchema = z.object({
32606
32864
  return claimRewardsQuoteResponseSchema.safeParse(value).success;
32607
32865
  }
32608
32866
 
32867
+ var name$1 = "@circle-fin/provider-earn-service";
32868
+ var version$1 = "1.2.2";
32869
+ var pkg$1 = {
32870
+ name: name$1,
32871
+ version: version$1};
32872
+
32873
+ /**
32874
+ * HTTP header name used to report the EarnKit SDK version to the backend.
32875
+ *
32876
+ * @internal
32877
+ */ const SDK_VERSION_HEADER = 'X-EarnKit-SDK-Version';
32878
+ /**
32879
+ * Resolve the SDK version string from its source components.
32880
+ *
32881
+ * Resolution order:
32882
+ * 1. The registered kit identifier (preferred) — e.g. `@circle-fin/earn-kit/1.1.0`
32883
+ * becomes `earn-kit/1.1.0`.
32884
+ * 2. The provider package itself (fallback when no kit is registered) —
32885
+ * e.g. `provider-earn-service/1.1.0`.
32886
+ * 3. The literal `unknown` when neither source is available.
32887
+ *
32888
+ * Kept as a pure function (no global / module reads) so every branch is
32889
+ * unit-testable.
32890
+ *
32891
+ * @param kitId - The registered kit identifier, or `undefined`.
32892
+ * @param providerName - The provider package name (e.g. `@circle-fin/provider-earn-service`).
32893
+ * @param providerVersion - The provider package version, or `undefined`.
32894
+ * @returns The formatted SDK version string.
32895
+ *
32896
+ * @internal
32897
+ */ function formatSdkVersion(kitId, providerName, providerVersion) {
32898
+ if (kitId !== undefined && kitId !== '') {
32899
+ // e.g. '@circle-fin/earn-kit/1.1.0' -> 'earn-kit/1.1.0'
32900
+ return resolveKitSdkName(kitId);
32901
+ }
32902
+ {
32903
+ // e.g. '@circle-fin/provider-earn-service' + '1.1.0' -> 'provider-earn-service/1.1.0'
32904
+ return `${resolveKitSdkName(providerName)}/${providerVersion}`;
32905
+ }
32906
+ }
32907
+ /**
32908
+ * Resolve the value for the {@link SDK_VERSION_HEADER} header.
32909
+ *
32910
+ * Reads the kit registered at runtime via {@link createRequestContext} and
32911
+ * falls back to this provider package's own version when no kit is registered.
32912
+ *
32913
+ * @returns The SDK version string, e.g. `earn-kit/1.1.0` or
32914
+ * `provider-earn-service/1.1.0`, or `unknown`.
32915
+ *
32916
+ * @internal
32917
+ */ function resolveSdkVersionHeader() {
32918
+ return formatSdkVersion(createRequestContext().kit, pkg$1.name, pkg$1.version);
32919
+ }
32920
+
32609
32921
  /**
32610
32922
  * Build an API polling config with optional authorization header,
32611
32923
  * and resolve the base URL (configurable for testing).
@@ -32616,9 +32928,16 @@ const bridgeDepositPrepareReviewSchema = z.object({
32616
32928
  * @internal
32617
32929
  */ function buildConfig(serviceConfig) {
32618
32930
  const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
32931
+ const sdkVersion = resolveSdkVersionHeader();
32619
32932
  if (serviceConfig?.kitKey === undefined) {
32620
32933
  return {
32621
- pollingConfig: DEFAULT_CONFIG,
32934
+ pollingConfig: {
32935
+ ...DEFAULT_CONFIG,
32936
+ headers: {
32937
+ ...DEFAULT_CONFIG.headers,
32938
+ [SDK_VERSION_HEADER]: sdkVersion
32939
+ }
32940
+ },
32622
32941
  baseUrl
32623
32942
  };
32624
32943
  }
@@ -32634,6 +32953,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
32634
32953
  ...DEFAULT_CONFIG,
32635
32954
  headers: {
32636
32955
  ...DEFAULT_CONFIG.headers,
32956
+ [SDK_VERSION_HEADER]: sdkVersion,
32637
32957
  Authorization: `Bearer ${serviceConfig.kitKey}`
32638
32958
  }
32639
32959
  },
@@ -33366,9 +33686,15 @@ function toWithdrawalQuoteInfo(data) {
33366
33686
  symbol: data.asset,
33367
33687
  amount: Amount.fromJSON(data.maxWithdrawable)
33368
33688
  },
33369
- fees: data.fees.map((fee)=>({
33370
- symbol: fee.token,
33371
- amount: Amount.fromJSON(fee.amount)
33689
+ fees: data.fees.map(({ token, amount, type, status })=>({
33690
+ symbol: token,
33691
+ amount: Amount.fromJSON(amount),
33692
+ ...type !== undefined && {
33693
+ type
33694
+ },
33695
+ ...status !== undefined && {
33696
+ status
33697
+ }
33372
33698
  })),
33373
33699
  gasFees: [],
33374
33700
  // Wire format uses `warnings`, but the SDK surface uses
@@ -36587,7 +36913,7 @@ function formatRetryResult(operation, result) {
36587
36913
  }
36588
36914
 
36589
36915
  // Auto-register this kit for user agent tracking
36590
- registerKit(`${pkg$1.name}/${pkg$1.version}`);
36916
+ registerKit(`${pkg$2.name}/${pkg$2.version}`);
36591
36917
 
36592
36918
  /**
36593
36919
  * Create an EarnKit instance for AppKit earn operations.
@@ -36682,12 +37008,17 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36682
37008
  * The type annotation `readonly TokenAlias[]` ensures this array stays in sync
36683
37009
  * with the TokenAlias type definition - TypeScript will enforce any changes.
36684
37010
  *
36685
- * For swap operations, additional tokens (EURC, DAI, USDE, PYUSD) are supported
37011
+ * `USDC`, `USDT`, and `NATIVE` map to dedicated adapter transfer actions.
37012
+ * `EURC` has no dedicated action — it resolves to the chain's `eurcAddress`
37013
+ * and uses the generic token transfer action (see {@link prepareSend}).
37014
+ *
37015
+ * For swap operations, additional tokens (DAI, USDE, PYUSD) are supported
36686
37016
  * via SwapKit's SupportedToken type.
36687
37017
  */ const TOKEN_ALIASES = [
36688
37018
  'USDC',
36689
37019
  'USDT',
36690
- 'NATIVE'
37020
+ 'NATIVE',
37021
+ 'EURC'
36691
37022
  ];
36692
37023
  /**
36693
37024
  * Check if a token string is a known alias.
@@ -36714,7 +37045,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36714
37045
  * Type guard to check if a string is a valid token address for a chain.
36715
37046
  *
36716
37047
  * This function verifies that a token string is:
36717
- * 1. Not a known alias ('USDC', 'USDT', 'NATIVE')
37048
+ * 1. Not a known alias ('USDC', 'USDT', 'NATIVE', 'EURC')
36718
37049
  * 2. A valid address format for the specified chain
36719
37050
  *
36720
37051
  * Use this to narrow the type to `TokenAddress` in TypeScript.
@@ -36744,7 +37075,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36744
37075
  * Validate and classify a token identifier.
36745
37076
  *
36746
37077
  * This function determines whether a token string is:
36747
- * 1. A known alias ('USDC', 'USDT', or 'NATIVE')
37078
+ * 1. A known alias ('USDC', 'USDT', 'NATIVE', or 'EURC')
36748
37079
  * 2. A valid token address for the given chain
36749
37080
  * 3. An invalid/unrecognized token identifier
36750
37081
  *
@@ -36998,8 +37329,11 @@ const tokens = createTokenRegistry();
36998
37329
  * from `to` (explicit address string or adapter), and normalize the human‑readable
36999
37330
  * `amount` into token units.
37000
37331
  *
37001
- * For known token aliases, uses dedicated transfer actions
37002
- * 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
37332
+ * For the `USDC`, `USDT`, and `NATIVE` aliases, uses dedicated transfer actions
37333
+ * with hardcoded decimals (USDC and USDT use 6 decimals; native uses the source chain's native decimals).
37334
+ * The `EURC` alias resolves to the chain's `eurcAddress` (6 decimals from the token
37335
+ * registry) and routes through the generic token.transfer action; it is rejected on
37336
+ * chains where `eurcAddress` is not set. For custom token addresses, uses the generic token.transfer
37003
37337
  * action and fetches decimals dynamically from the contract.
37004
37338
  *
37005
37339
  * @param params - The send parameters including source context, destination, amount and token.
@@ -37092,6 +37426,21 @@ const tokens = createTokenRegistry();
37092
37426
  preparedSendRequest = await fromAdapter.prepareAction('native.transfer', requestParams, operationContext);
37093
37427
  break;
37094
37428
  }
37429
+ case 'EURC':
37430
+ {
37431
+ // EURC has no dedicated transfer action. Resolve its contract address
37432
+ // from the chain definition and route through the generic
37433
+ // token.transfer action. `eurcAddress` is null where EURC isn't deployed.
37434
+ const eurcAddress = fromChain.eurcAddress;
37435
+ if (eurcAddress === null) {
37436
+ throw createUnsupportedTokenError(token, fromChain.name);
37437
+ }
37438
+ preparedSendRequest = await fromAdapter.prepareAction('token.transfer', {
37439
+ ...requestParams,
37440
+ tokenAddress: eurcAddress
37441
+ }, operationContext);
37442
+ break;
37443
+ }
37095
37444
  default:
37096
37445
  {
37097
37446
  throw createUnsupportedTokenError(String(token), fromChain.name);
@@ -37118,12 +37467,13 @@ const tokens = createTokenRegistry();
37118
37467
  * Fully prepares and executes the send transaction to transfer the specified amount and
37119
37468
  * token to the specified destination.
37120
37469
  *
37121
- * For known token aliases ('USDC', 'USDT', 'NATIVE'), uses optimized dedicated actions.
37122
- * For custom token addresses, uses the generic token.transfer action with dynamically
37123
- * fetched decimals.
37470
+ * For the 'USDC', 'USDT', and 'NATIVE' aliases, uses optimized dedicated actions.
37471
+ * The 'EURC' alias resolves to the chain's `eurcAddress` and routes through the generic
37472
+ * token.transfer action. For custom token addresses, uses the generic token.transfer action
37473
+ * with dynamically fetched decimals.
37124
37474
  *
37125
37475
  * @param params - The send parameters: source context, destination (address or adapter),
37126
- * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE' or custom address, defaults to 'USDC').
37476
+ * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE' | 'EURC' or custom address, defaults to 'USDC').
37127
37477
  * @returns A BridgeStep object with transaction details including hash, status, and explorer URL.
37128
37478
  * @throws KitError INPUT_VALIDATION_FAILED if parameters are invalid.
37129
37479
  * @throws KitError INPUT_UNSUPPORTED_TOKEN if the token is invalid for the chain.
@@ -37141,6 +37491,17 @@ const tokens = createTokenRegistry();
37141
37491
  * console.log('Transaction hash:', step.txHash)
37142
37492
  * ```
37143
37493
  *
37494
+ * @example Sending EURC on a chain where EURC is deployed (e.g. Arc Testnet)
37495
+ * ```typescript
37496
+ * const step = await send({
37497
+ * from: { adapter: sourceAdapter, chain: 'Arc_Testnet' },
37498
+ * to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
37499
+ * amount: '5.0',
37500
+ * token: 'EURC',
37501
+ * })
37502
+ * console.log('Transaction hash:', step.txHash)
37503
+ * ```
37504
+ *
37144
37505
  * @example Sending a custom token (DAI) to an explicit address
37145
37506
  * ```typescript
37146
37507
  * const step = await send({
@@ -37182,21 +37543,22 @@ const tokens = createTokenRegistry();
37182
37543
  };
37183
37544
 
37184
37545
  /**
37185
- * Estimate the network fees to send USDC, USDT, or a supported native token.
37546
+ * Estimate the network fees to send USDC, USDT, EURC, or a supported native token.
37186
37547
  *
37187
37548
  * Prepare the send operation (including validating inputs and resolving the recipient when
37188
37549
  * provided as an adapter) and return an {@link EstimatedGas} object. This function does
37189
37550
  * not submit any transaction.
37190
37551
  *
37191
37552
  * @remarks
37192
- * - Selects the transfer handler based on `token` ('USDC' by default, 'USDT', or 'NATIVE').
37553
+ * - Selects the transfer handler based on `token` ('USDC' by default, 'USDT', 'NATIVE', or 'EURC').
37554
+ * 'EURC' is only supported on chains where `eurcAddress` is configured.
37193
37555
  * - When `to` is an {@link Adapter}, the recipient address is derived from the adapter.
37194
37556
  * - Rejects transfers where the resolved `to` equals the source `from` address.
37195
37557
  * - Interprets `amount` as a human-readable decimal string (USDC and USDT scaled to 6 decimals,
37196
37558
  * native EVM value typically scaled to 18 decimals).
37197
37559
  *
37198
37560
  * @param params - The send parameters: source context, destination (address or adapter),
37199
- * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE', defaults to 'USDC').
37561
+ * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE' | 'EURC', defaults to 'USDC').
37200
37562
  * @returns The estimated gas information including `gas`, `gasPrice`, and total `fee`.
37201
37563
  * @throws Error If parameters are invalid.
37202
37564
  * @throws Error If the token is unsupported or the send cannot be prepared.
@@ -37903,7 +38265,7 @@ async function deposit$2(context, params) {
37903
38265
  }
37904
38266
 
37905
38267
  var name = "@circle-fin/unified-balance-kit";
37906
- var version = "1.2.1";
38268
+ var version = "1.2.2";
37907
38269
  var pkg = {
37908
38270
  name: name,
37909
38271
  version: version};