@circle-fin/app-kit 1.8.0 → 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
 
@@ -3657,11 +3663,11 @@ function getOptionalString(value) {
3657
3663
  * - vault-not-found, unsupported-chain, unsupported-vault,
3658
3664
  * signature-rejected, invalid-id, invalid-batch-size, position-not-registered,
3659
3665
  * withdrawal-max-exceeded, insufficient-balance, bridge prepare idempotency
3660
- * conflicts
3666
+ * conflicts, invalid vault-asset amount precision
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
@@ -3841,6 +3847,20 @@ function getOptionalString(value) {
3841
3847
  recoverability: 'RETRYABLE'
3842
3848
  }
3843
3849
  ],
3850
+ [
3851
+ 380415,
3852
+ {
3853
+ errorDef: EarnError.INVALID_INPUT,
3854
+ recoverability: 'FATAL'
3855
+ }
3856
+ ],
3857
+ [
3858
+ 380416,
3859
+ {
3860
+ errorDef: EarnError.POSITION_PNL_PENDING,
3861
+ recoverability: 'RETRYABLE'
3862
+ }
3863
+ ],
3844
3864
  // Bridge (380_5XX)
3845
3865
  [
3846
3866
  380500,
@@ -3876,6 +3896,13 @@ function getOptionalString(value) {
3876
3896
  errorDef: EarnError.PROVIDER_ERROR,
3877
3897
  recoverability: 'RETRYABLE'
3878
3898
  }
3899
+ ],
3900
+ [
3901
+ 380505,
3902
+ {
3903
+ errorDef: EarnError.PROVIDER_ERROR,
3904
+ recoverability: 'FATAL'
3905
+ }
3879
3906
  ]
3880
3907
  ]);
3881
3908
  /**
@@ -3978,6 +4005,8 @@ function getOptionalString(value) {
3978
4005
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
3979
4006
  Blockchain["Codex"] = "Codex";
3980
4007
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
4008
+ Blockchain["Cronos"] = "Cronos";
4009
+ Blockchain["Cronos_Testnet"] = "Cronos_Testnet";
3981
4010
  Blockchain["Edge"] = "Edge";
3982
4011
  Blockchain["Edge_Testnet"] = "Edge_Testnet";
3983
4012
  Blockchain["Ethereum"] = "Ethereum";
@@ -4060,6 +4089,7 @@ exports.BridgeChain = void 0;
4060
4089
  BridgeChain["Avalanche"] = "Avalanche";
4061
4090
  BridgeChain["Base"] = "Base";
4062
4091
  BridgeChain["Codex"] = "Codex";
4092
+ BridgeChain["Cronos"] = "Cronos";
4063
4093
  BridgeChain["Edge"] = "Edge";
4064
4094
  BridgeChain["Ethereum"] = "Ethereum";
4065
4095
  BridgeChain["HyperEVM"] = "HyperEVM";
@@ -4084,6 +4114,7 @@ exports.BridgeChain = void 0;
4084
4114
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
4085
4115
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
4086
4116
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
4117
+ BridgeChain["Cronos_Testnet"] = "Cronos_Testnet";
4087
4118
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
4088
4119
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
4089
4120
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
@@ -4335,23 +4366,6 @@ exports.EarnChain = void 0;
4335
4366
  }
4336
4367
  });
4337
4368
 
4338
- /**
4339
- * Standard decimal places for tokens.
4340
- *
4341
- * These constants define the decimal precision used by different token types
4342
- * across the SDK. They are used for amount conversions between human-readable
4343
- * format and smallest units (base units).
4344
- *
4345
- * @remarks
4346
- * - Most stablecoins (USDC, EURC, USDT, PYUSD) use 6 decimal places
4347
- * - Some stablecoins (DAI, USDE) and most native tokens (ETH, POL, PLUME) use 18 decimals
4348
- * - The NATIVE alias uses chain-specific decimals which may vary (e.g., SOL uses 9)
4349
- */ /**
4350
- * Standard decimal places for 6-decimal tokens.
4351
- *
4352
- * Used by most stablecoins: USDC, EURC, USDT, PYUSD
4353
- */ const TOKEN_DECIMALS_6 = 6;
4354
-
4355
4369
  /**
4356
4370
  * @packageDocumentation
4357
4371
  * @module SwapTokenRegistry
@@ -5167,6 +5181,96 @@ exports.EarnChain = void 0;
5167
5181
  }
5168
5182
  });
5169
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
+
5170
5274
  /**
5171
5275
  * Edge Mainnet chain definition
5172
5276
  * @remarks
@@ -7518,6 +7622,8 @@ var Chains = {
7518
7622
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
7519
7623
  Codex: Codex,
7520
7624
  CodexTestnet: CodexTestnet,
7625
+ Cronos: Cronos,
7626
+ CronosTestnet: CronosTestnet,
7521
7627
  Edge: Edge,
7522
7628
  EdgeTestnet: EdgeTestnet,
7523
7629
  Ethereum: Ethereum,
@@ -9892,6 +9998,7 @@ function parseOrThrow(value, schema, context) {
9892
9998
  [exports.Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
9893
9999
  [exports.Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
9894
10000
  [exports.Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
10001
+ [exports.Blockchain.Cronos]: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
9895
10002
  [exports.Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
9896
10003
  [exports.Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
9897
10004
  [exports.Blockchain.Hedera]: '0.0.456858',
@@ -9925,6 +10032,7 @@ function parseOrThrow(value, schema, context) {
9925
10032
  [exports.Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
9926
10033
  [exports.Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
9927
10034
  [exports.Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
10035
+ [exports.Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
9928
10036
  [exports.Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
9929
10037
  [exports.Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
9930
10038
  [exports.Blockchain.Hedera_Testnet]: '0.0.429274',
@@ -9992,13 +10100,22 @@ function parseOrThrow(value, schema, context) {
9992
10100
  symbol: 'EURC',
9993
10101
  decimals: 6,
9994
10102
  locators: {
10103
+ // =========================================================================
10104
+ // Mainnets
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',
10112
+ // =========================================================================
10000
10113
  // Testnets
10001
- [exports.Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a'
10114
+ // =========================================================================
10115
+ [exports.Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
10116
+ [exports.Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
10117
+ [exports.Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
10118
+ [exports.Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
10002
10119
  }
10003
10120
  };
10004
10121
 
@@ -10208,8 +10325,7 @@ function parseOrThrow(value, schema, context) {
10208
10325
  * cirBTC (Circle Bitcoin) token definition with addresses and metadata.
10209
10326
  *
10210
10327
  * @remarks
10211
- * Built-in cirBTC definition for the TokenRegistry. Currently deployed
10212
- * on Arc Testnet.
10328
+ * Built-in cirBTC definition for the TokenRegistry.
10213
10329
  *
10214
10330
  * @example
10215
10331
  * ```typescript
@@ -10225,7 +10341,15 @@ function parseOrThrow(value, schema, context) {
10225
10341
  symbol: 'cirBTC',
10226
10342
  decimals: 8,
10227
10343
  locators: {
10228
- [exports.Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
10344
+ // =========================================================================
10345
+ // Mainnets
10346
+ // =========================================================================
10347
+ [exports.Blockchain.Ethereum]: '0x72DFB2E44f59C5AD2bAFE84314E5b99a7cd5075E',
10348
+ // =========================================================================
10349
+ // Testnets
10350
+ // =========================================================================
10351
+ [exports.Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
10352
+ [exports.Blockchain.Ethereum_Sepolia]: '0x3a3fe695F684Bf9b9e43CF43C2b895Ea5e392bB3'
10229
10353
  }
10230
10354
  };
10231
10355
 
@@ -11505,11 +11629,11 @@ function resolveOptions(options) {
11505
11629
  void emitAnalyticsLog(buildPayload$1(config, stepEntry?.[1] ?? fallbackEventType, errorDetails, context));
11506
11630
  }
11507
11631
 
11508
- var name$3 = "@circle-fin/bridge-kit";
11509
- var version$4 = "1.11.0";
11510
- var pkg$4 = {
11511
- name: name$3,
11512
- 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};
11513
11637
 
11514
11638
  const assertCustomFeePolicySymbol$2 = Symbol('assertCustomFeePolicy');
11515
11639
  /**
@@ -13535,7 +13659,7 @@ exports.TransferSpeed = void 0;
13535
13659
  const bridgeKitCaller = {
13536
13660
  type: 'kit',
13537
13661
  name: 'BridgeKit',
13538
- version: pkg$4.version
13662
+ version: pkg$5.version
13539
13663
  };
13540
13664
  // Create default runtime and tokens for invocation context resolution
13541
13665
  const defaults = {
@@ -14205,6 +14329,15 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
14205
14329
  ;
14206
14330
  const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
14207
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
+ ;
14208
14341
  /**
14209
14342
  * The minimum finality threshold for CCTPv2 transfers.
14210
14343
  *
@@ -14233,6 +14366,27 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14233
14366
  'Content-Type': 'application/json'
14234
14367
  }
14235
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
+ });
14236
14390
  /**
14237
14391
  * Type guard that verifies if an unknown value matches the AttestationMessage shape
14238
14392
  * and has all required properties.
@@ -14379,10 +14533,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14379
14533
  * ```
14380
14534
  */ const fetchAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14381
14535
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14382
- const effectiveConfig = {
14383
- ...DEFAULT_CONFIG$2,
14384
- ...config
14385
- };
14536
+ const effectiveConfig = mergeAttestationConfig(config);
14386
14537
  return await pollApiGet(url, isAttestationResponse, effectiveConfig);
14387
14538
  };
14388
14539
  /**
@@ -14425,11 +14576,9 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14425
14576
  */ const fetchAttestationWithoutStatusCheck = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14426
14577
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14427
14578
  // Use minimal retries since we're just fetching existing data
14428
- const effectiveConfig = {
14429
- ...DEFAULT_CONFIG$2,
14430
- maxRetries: 3,
14431
- ...config
14432
- };
14579
+ const effectiveConfig = mergeAttestationConfig(config, {
14580
+ maxRetries: 3
14581
+ });
14433
14582
  return await pollApiGet(url, isAttestationResponseWithoutStatusCheck, effectiveConfig);
14434
14583
  };
14435
14584
  /**
@@ -14489,10 +14638,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14489
14638
  * ```
14490
14639
  */ const fetchReAttestedAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14491
14640
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14492
- const effectiveConfig = {
14493
- ...DEFAULT_CONFIG$2,
14494
- ...config
14495
- };
14641
+ const effectiveConfig = mergeAttestationConfig(config);
14496
14642
  return await pollApiGet(url, isReAttestedAttestationResponse, effectiveConfig);
14497
14643
  };
14498
14644
  /**
@@ -14558,14 +14704,139 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14558
14704
  */ const requestReAttestation = async (nonce, isTestnet, config = {})=>{
14559
14705
  const url = buildReAttestUrl(nonce, isTestnet);
14560
14706
  // Use minimal retries since we're just submitting a request, not polling for state
14561
- const effectiveConfig = {
14562
- ...DEFAULT_CONFIG$2,
14563
- maxRetries: 3,
14564
- ...config
14565
- };
14707
+ const effectiveConfig = mergeAttestationConfig(config, {
14708
+ maxRetries: 3
14709
+ });
14566
14710
  return await pollApiPost(url, {}, isReAttestationResponse, effectiveConfig);
14567
14711
  };
14568
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
+
14569
14840
  const assertCCTPv2WalletContextSymbol = Symbol('assertCCTPv2WalletContext');
14570
14841
  /**
14571
14842
  * Asserts that the provided parameters match the CCTPv2 wallet context interface.
@@ -15440,6 +15711,8 @@ function hasPendingState(analysis, result) {
15440
15711
  * - `adapter`: The adapter that will execute the transaction
15441
15712
  * - `confirmations`: The number of confirmations to wait for (defaults to 1)
15442
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
15443
15716
  * @returns The bridge step with the transaction details and explorer URL
15444
15717
  * @throws If the transaction execution fails
15445
15718
  *
@@ -15454,7 +15727,7 @@ function hasPendingState(analysis, result) {
15454
15727
  * })
15455
15728
  * console.log('Transaction hash:', step.txHash)
15456
15729
  * ```
15457
- */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout }) {
15730
+ */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasLimit }) {
15458
15731
  const step = {
15459
15732
  name,
15460
15733
  state: 'pending'
@@ -15467,7 +15740,9 @@ function hasPendingState(analysis, result) {
15467
15740
  step.state = 'noop';
15468
15741
  return step;
15469
15742
  }
15470
- const txHash = await request.execute();
15743
+ const txHash = request.type === 'evm' && gasLimit !== undefined ? await request.execute({
15744
+ gasLimit
15745
+ }) : await request.execute();
15471
15746
  step.txHash = txHash;
15472
15747
  const retryOptions = {
15473
15748
  isRetryable: (err)=>isRetryableError$1(parseBlockchainError(err, {
@@ -15539,7 +15814,8 @@ function hasPendingState(analysis, result) {
15539
15814
  name: 'approve',
15540
15815
  adapter: params.source.adapter,
15541
15816
  chain: params.source.chain,
15542
- request: await provider.approve(params.source, approvalAmount)
15817
+ request: await provider.approve(params.source, approvalAmount),
15818
+ gasLimit: Number(APPROVE_GAS_LIMIT_EVM)
15543
15819
  });
15544
15820
  }
15545
15821
 
@@ -15566,7 +15842,8 @@ function hasPendingState(analysis, result) {
15566
15842
  name: 'burn',
15567
15843
  adapter: params.source.adapter,
15568
15844
  chain: params.source.chain,
15569
- request: await provider.burn(params)
15845
+ request: await provider.burn(params),
15846
+ gasLimit: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
15570
15847
  });
15571
15848
  }
15572
15849
 
@@ -15652,11 +15929,18 @@ function hasPendingState(analysis, result) {
15652
15929
  */ async function bridgeMint({ params, provider }, attestation) {
15653
15930
  // Validate attestation message matches transfer params
15654
15931
  await assertCCTPv2AttestationParams(attestation, params);
15932
+ const mintRequest = await provider.mint(params.source, params.destination, attestation);
15655
15933
  const step = await executePreparedChainRequest({
15656
15934
  name: 'mint',
15657
15935
  adapter: params.destination.adapter,
15658
15936
  chain: params.destination.chain,
15659
- 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)
15660
15944
  });
15661
15945
  // Add forwarded: false for non-relayer mints
15662
15946
  return {
@@ -15694,136 +15978,6 @@ const mockAttestationMessage = {
15694
15978
  delayReason: null
15695
15979
  };
15696
15980
 
15697
- /**
15698
- * Type guard that checks if the relayer has confirmed the mint transaction.
15699
- *
15700
- * This function validates that:
15701
- * 1. The response has valid AttestationResponse structure
15702
- * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
15703
- *
15704
- * If forwardState is 'FAILED', throws a non-retryable KitError.
15705
- * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
15706
- *
15707
- * @param obj - The value to check, typically a parsed JSON response
15708
- * @returns True if the relayer has confirmed the mint
15709
- * @throws {KitError} With FATAL recoverability if structure is invalid
15710
- * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
15711
- * @throws {KitError} With RETRYABLE recoverability if still pending
15712
- * @internal
15713
- */ const isRelayerMintConfirmed = (obj)=>{
15714
- // First check if the structure is valid
15715
- if (!hasValidAttestationStructure(obj)) {
15716
- throw new KitError({
15717
- ...InputError.VALIDATION_FAILED,
15718
- recoverability: 'FATAL',
15719
- message: 'Invalid attestation response structure from IRIS API.'
15720
- });
15721
- }
15722
- // Find the first message (typically there's only one)
15723
- const message = obj.messages[0];
15724
- if (!message) {
15725
- throw new KitError({
15726
- ...InputError.VALIDATION_FAILED,
15727
- recoverability: 'FATAL',
15728
- message: 'No attestation messages found in IRIS API response.'
15729
- });
15730
- }
15731
- // Check for FAILED state - this is a permanent failure
15732
- if (message.forwardState === 'FAILED') {
15733
- throw new KitError({
15734
- ...NetworkError.RELAYER_FORWARD_FAILED,
15735
- recoverability: 'RESUMABLE',
15736
- 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.',
15737
- cause: {
15738
- trace: {
15739
- eventNonce: message.eventNonce,
15740
- attestation: message.attestation,
15741
- message: message.message
15742
- }
15743
- }
15744
- });
15745
- }
15746
- // Check if mint is confirmed (or complete) with a valid transaction hash
15747
- // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
15748
- if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
15749
- return true;
15750
- }
15751
- // Still pending or not yet processed - throw RETRYABLE error to continue polling
15752
- throw new KitError({
15753
- ...NetworkError.RELAYER_PENDING,
15754
- recoverability: 'RETRYABLE',
15755
- message: 'Relayer mint not ready. Waiting for confirmation.'
15756
- });
15757
- };
15758
- /**
15759
- * Polls the attestation API until the relayer's mint transaction is confirmed.
15760
- *
15761
- * This function is used when `useForwarder` is enabled. Instead of the user
15762
- * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
15763
- * This function polls until the relayer has submitted and confirmed the mint transaction.
15764
- *
15765
- * @remarks
15766
- * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
15767
- * - Throws immediately if `forwardState` is 'FAILED'
15768
- * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
15769
- * - Returns the attestation message with `forwardTxHash` populated
15770
- *
15771
- * @param sourceDomainId - The CCTP domain ID of the source chain
15772
- * @param transactionHash - The transaction hash of the burn operation
15773
- * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
15774
- * @param config - Optional configuration overrides for polling behavior
15775
- * @returns The attestation message with confirmed forwardTxHash
15776
- * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
15777
- * @throws {KitError} If timeout is reached while still pending
15778
- *
15779
- * @example
15780
- * ```typescript
15781
- * const attestation = await fetchRelayerMint(0, '0xabc...', false)
15782
- * console.log('Relayer mint tx:', attestation.forwardTxHash)
15783
- * ```
15784
- */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
15785
- const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
15786
- const effectiveConfig = {
15787
- ...DEFAULT_CONFIG$2,
15788
- ...config
15789
- };
15790
- let response;
15791
- try {
15792
- response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
15793
- } catch (error) {
15794
- // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
15795
- if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
15796
- throw new KitError({
15797
- ...NetworkError.RELAYER_FORWARD_FAILED,
15798
- recoverability: error.recoverability,
15799
- message: error.message,
15800
- cause: {
15801
- ...error.cause,
15802
- trace: {
15803
- ...error.cause?.trace,
15804
- burnTxHash: transactionHash
15805
- }
15806
- }
15807
- });
15808
- }
15809
- throw error;
15810
- }
15811
- // Return the first message (which should have forwardTxHash)
15812
- // Note: This check is needed for TypeScript type safety even though
15813
- // isRelayerMintConfirmed validates messages[0] exists. The type guard
15814
- // narrows the type at the call site, but TypeScript can't infer that
15815
- // the array still has elements after pollApiGet returns.
15816
- const message = response.messages[0];
15817
- if (!message) {
15818
- throw new KitError({
15819
- ...InputError.VALIDATION_FAILED,
15820
- recoverability: 'FATAL',
15821
- message: 'No attestation messages found in response after polling.'
15822
- });
15823
- }
15824
- return message;
15825
- };
15826
-
15827
15981
  /**
15828
15982
  * Executes the mint step for forwarding mode where Circle's relayer handles the mint.
15829
15983
  *
@@ -15837,6 +15991,7 @@ const mockAttestationMessage = {
15837
15991
  * immediately after the API confirms the mint.
15838
15992
  *
15839
15993
  * @param params - The bridge parameters containing source, destination, amount and config
15994
+ * @param provider - The CCTP v2 bridging provider
15840
15995
  * @param context - The step context containing burnTxHash from the burn step
15841
15996
  * @returns Promise resolving to the bridge step with transaction details
15842
15997
  * @throws {KitError} If burnTxHash is not available in context
@@ -15845,10 +16000,13 @@ const mockAttestationMessage = {
15845
16000
  *
15846
16001
  * @example
15847
16002
  * ```typescript
15848
- * const step = await bridgeRelayerMint(params, { burnTxHash: '0x...' })
16003
+ * const step = await bridgeRelayerMint(
16004
+ * { params, provider },
16005
+ * { burnTxHash: '0x...' }
16006
+ * )
15849
16007
  * console.log('Relayer mint tx:', step.txHash)
15850
16008
  * ```
15851
- */ async function bridgeRelayerMint(params, context) {
16009
+ */ async function bridgeRelayerMint({ params, provider }, context) {
15852
16010
  const step = {
15853
16011
  name: 'mint',
15854
16012
  state: 'pending',
@@ -15864,7 +16022,7 @@ const mockAttestationMessage = {
15864
16022
  }
15865
16023
  try {
15866
16024
  // Poll attestation API until relayer confirms the mint
15867
- 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);
15868
16026
  // Extract the relayer's mint transaction hash
15869
16027
  const forwardTxHash = attestation.forwardTxHash;
15870
16028
  if (!forwardTxHash) {
@@ -15958,7 +16116,7 @@ const mockAttestationMessage = {
15958
16116
  name: 'mint',
15959
16117
  // bridgeRelayerMint validates context.burnTxHash internally with a KitError
15960
16118
  // bridgeMint requires attestationData which is validated here
15961
- executor: useForwarder ? async (params, _provider, context)=>{
16119
+ executor: useForwarder ? async (params, provider, context)=>{
15962
16120
  if (!context) {
15963
16121
  throw new KitError({
15964
16122
  ...InputError.VALIDATION_FAILED,
@@ -15966,7 +16124,10 @@ const mockAttestationMessage = {
15966
16124
  message: 'Step context is required for relayer mint'
15967
16125
  });
15968
16126
  }
15969
- return bridgeRelayerMint(params, context);
16127
+ return bridgeRelayerMint({
16128
+ params,
16129
+ provider
16130
+ }, context);
15970
16131
  } : async (params, provider, context)=>{
15971
16132
  if (!context?.attestationData) {
15972
16133
  throw new KitError({
@@ -16297,16 +16458,16 @@ const mockAttestationMessage = {
16297
16458
  return step;
16298
16459
  }
16299
16460
 
16300
- var version$3 = "1.8.4";
16301
- var pkg$3 = {
16302
- version: version$3};
16461
+ var version$4 = "1.9.0";
16462
+ var pkg$4 = {
16463
+ version: version$4};
16303
16464
 
16304
16465
  /**
16305
16466
  * Provider caller component for bridge operations.
16306
16467
  */ const BRIDGE_CALLER = {
16307
16468
  type: 'provider',
16308
16469
  name: 'CCTPV2BridgingProvider.bridge',
16309
- version: pkg$3.version
16470
+ version: pkg$4.version
16310
16471
  };
16311
16472
  /**
16312
16473
  * Resolve invocation context for bridge operations.
@@ -16679,7 +16840,7 @@ var pkg$3 = {
16679
16840
  */ const RETRY_CALLER = {
16680
16841
  type: 'provider',
16681
16842
  name: 'CCTPV2BridgingProvider.retry',
16682
- version: pkg$3.version
16843
+ version: pkg$4.version
16683
16844
  };
16684
16845
  /**
16685
16846
  * Resolve invocation context for retry operations.
@@ -17003,6 +17164,39 @@ var pkg$3 = {
17003
17164
  }
17004
17165
  }
17005
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
+ }
17006
17200
  /**
17007
17201
  * All chains that are supported by the CCTP v2 provider.
17008
17202
  *
@@ -17071,9 +17265,38 @@ var pkg$3 = {
17071
17265
  * @param config - Optional configuration overrides for the provider
17072
17266
  */ constructor(config = {}){
17073
17267
  super();
17268
+ assertCCTPV2Config(config);
17074
17269
  this.config = config;
17075
17270
  }
17076
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
+ /**
17077
17300
  * Execute a cross-chain USDC bridge operation using the CCTP v2 protocol.
17078
17301
  *
17079
17302
  * This method orchestrates the complete CCTP v2 bridge flow including validation,
@@ -17499,11 +17722,7 @@ var pkg$3 = {
17499
17722
  */ async fetchAttestation(source, transactionHash, config) {
17500
17723
  assertCCTPv2WalletContext(source);
17501
17724
  try {
17502
- // Merge configs: defaults <- global config <- per-call config
17503
- const effectiveConfig = {
17504
- ...this.config?.attestation,
17505
- ...config
17506
- };
17725
+ const effectiveConfig = this.resolveAttestationConfig(config);
17507
17726
  const response = await fetchAttestation(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17508
17727
  const message = response.messages[0];
17509
17728
  if (!message) {
@@ -17520,6 +17739,49 @@ var pkg$3 = {
17520
17739
  }
17521
17740
  }
17522
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
+ /**
17523
17785
  * Requests a fresh attestation for an expired attestation.
17524
17786
  *
17525
17787
  * This method is used when the original attestation has expired before the mint
@@ -17573,11 +17835,7 @@ var pkg$3 = {
17573
17835
  throw new Error('Failed to re-attest: Invalid transaction hash');
17574
17836
  }
17575
17837
  try {
17576
- // Merge configs: defaults <- global config <- per-call config
17577
- const effectiveConfig = {
17578
- ...this.config?.attestation,
17579
- ...config
17580
- };
17838
+ const effectiveConfig = this.resolveAttestationConfig(config);
17581
17839
  // Step 1: Get existing attestation data to extract nonce
17582
17840
  const existingAttestation = await fetchAttestationWithoutStatusCheck(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17583
17841
  const nonce = existingAttestation.messages[0]?.eventNonce;
@@ -17862,8 +18120,13 @@ var pkg$3 = {
17862
18120
  /**
17863
18121
  * The default providers that will be used in addition to the providers provided
17864
18122
  * to the BridgeKit constructor.
17865
- */ const getDefaultProviders$3 = ()=>[
17866
- 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
+ } : {})
17867
18130
  ];
17868
18131
 
17869
18132
  /**
@@ -17974,7 +18237,7 @@ var pkg$3 = {
17974
18237
  ]
17975
18238
  ];
17976
18239
 
17977
- /** 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);
17978
18241
  /**
17979
18242
  * Pick the most-relevant `txHash` to attach to an error telemetry payload.
17980
18243
  *
@@ -18015,7 +18278,7 @@ var pkg$3 = {
18015
18278
  */ const BRIDGE_KIT_CALLER = {
18016
18279
  type: 'kit',
18017
18280
  name: 'BridgeKit',
18018
- version: pkg$4.version
18281
+ version: pkg$5.version
18019
18282
  };
18020
18283
  /**
18021
18284
  * Merge BridgeKit's caller into the invocation metadata for retry operations.
@@ -18106,7 +18369,9 @@ var pkg$3 = {
18106
18369
  * ```
18107
18370
  */ constructor(config = {}){
18108
18371
  // Handle provider configuration
18109
- const defaultProviders = getDefaultProviders$3();
18372
+ const defaultProviders = getDefaultProviders$3(config.headers ? {
18373
+ headers: config.headers
18374
+ } : {});
18110
18375
  this.providers = [
18111
18376
  ...defaultProviders,
18112
18377
  ...config.providers ?? []
@@ -18115,7 +18380,7 @@ var pkg$3 = {
18115
18380
  this.disableErrorReporting = config.disableErrorReporting === true;
18116
18381
  this.telemetryConfig = {
18117
18382
  sdkName: SDK_NAME$2,
18118
- sdkVersion: pkg$4.version,
18383
+ sdkVersion: pkg$5.version,
18119
18384
  disabled: this.disableErrorReporting
18120
18385
  };
18121
18386
  for (const provider of this.providers){
@@ -18637,7 +18902,7 @@ var pkg$3 = {
18637
18902
  }
18638
18903
 
18639
18904
  // Auto-register this kit for user agent tracking
18640
- registerKit(`${pkg$4.name}/${pkg$4.version}`);
18905
+ registerKit(`${pkg$5.name}/${pkg$5.version}`);
18641
18906
 
18642
18907
  /**
18643
18908
  * Create a BridgeKit instance with optional developer fee configuration.
@@ -18686,6 +18951,9 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18686
18951
  const kit = new BridgeKit({
18687
18952
  ...context.disableErrorReporting != null && {
18688
18953
  disableErrorReporting: context.disableErrorReporting
18954
+ },
18955
+ ...context.headers != null && {
18956
+ headers: context.headers
18689
18957
  }
18690
18958
  });
18691
18959
  if (hasBoth) {
@@ -18703,11 +18971,11 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18703
18971
  return kit;
18704
18972
  };
18705
18973
 
18706
- var name$2 = "@circle-fin/swap-kit";
18707
- var version$2 = "1.3.0";
18708
- var pkg$2 = {
18709
- name: name$2,
18710
- 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};
18711
18979
 
18712
18980
  const chainIdentifierField = zod.z.custom((value)=>chainIdentifierSchema.safeParse(value).success, {
18713
18981
  message: 'chain must be a valid chain identifier'
@@ -23639,7 +23907,7 @@ function writeBytes32(buffer, hex, offset) {
23639
23907
  }
23640
23908
  }
23641
23909
 
23642
- const TOKEN_REGISTRY$3 = createTokenRegistry();
23910
+ const TOKEN_REGISTRY$4 = createTokenRegistry();
23643
23911
  /**
23644
23912
  * Resolve token alias to contract address for the given chain.
23645
23913
  *
@@ -23728,7 +23996,7 @@ const TOKEN_REGISTRY$3 = createTokenRegistry();
23728
23996
  }
23729
23997
  });
23730
23998
  }
23731
- const tokenDefinition = TOKEN_REGISTRY$3.get(upperToken);
23999
+ const tokenDefinition = TOKEN_REGISTRY$4.get(upperToken);
23732
24000
  if (tokenDefinition !== undefined) {
23733
24001
  const resolvedAddress = tokenDefinition.locators[chain.chain];
23734
24002
  if (!resolvedAddress) {
@@ -23745,7 +24013,7 @@ const TOKEN_REGISTRY$3 = createTokenRegistry();
23745
24013
  }
23746
24014
  });
23747
24015
  }
23748
- return TOKEN_REGISTRY$3.resolve(upperToken, chain.chain).locator;
24016
+ return TOKEN_REGISTRY$4.resolve(upperToken, chain.chain).locator;
23749
24017
  }
23750
24018
  // Not an alias - return as-is (assumed to be address)
23751
24019
  return token;
@@ -23842,11 +24110,11 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
23842
24110
  if (destinationChain.chain !== sourceChain.chain) {
23843
24111
  return sourceChain;
23844
24112
  }
23845
- const inputIsOk = isOkToken(tokenIn, sourceChain, TOKEN_REGISTRY$3, OK_TOKEN_SYMBOLS$1);
24113
+ const inputIsOk = isOkToken(tokenIn, sourceChain, TOKEN_REGISTRY$4, OK_TOKEN_SYMBOLS$1);
23846
24114
  if (inputIsOk) {
23847
24115
  return sourceChain;
23848
24116
  }
23849
- const outputIsOk = isOkToken(tokenOut, destinationChain, TOKEN_REGISTRY$3, OK_TOKEN_SYMBOLS$1);
24117
+ const outputIsOk = isOkToken(tokenOut, destinationChain, TOKEN_REGISTRY$4, OK_TOKEN_SYMBOLS$1);
23850
24118
  return outputIsOk ? destinationChain : sourceChain;
23851
24119
  }
23852
24120
  /**
@@ -25137,7 +25405,7 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
25137
25405
  }
25138
25406
  }
25139
25407
 
25140
- const TOKEN_REGISTRY$2 = createTokenRegistry();
25408
+ const TOKEN_REGISTRY$3 = createTokenRegistry();
25141
25409
  /**
25142
25410
  * Resolve a token identifier to its canonical symbol for the Stablecoin Service Swap Provider.
25143
25411
  *
@@ -25181,7 +25449,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25181
25449
  return NATIVE_TOKEN;
25182
25450
  }
25183
25451
  // For known registry aliases, return canonical symbol directly.
25184
- if (TOKEN_REGISTRY$2.get(upperToken) !== undefined) {
25452
+ if (TOKEN_REGISTRY$3.get(upperToken) !== undefined) {
25185
25453
  return upperToken;
25186
25454
  }
25187
25455
  // For EVM chains, normalize addresses to lowercase for case-insensitive comparison.
@@ -25191,7 +25459,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25191
25459
  // Native address is given
25192
25460
  if (isNativeEvmAddress(token) || isNativeSolanaAddress(token)) return NATIVE_TOKEN;
25193
25461
  try {
25194
- const { symbol } = TOKEN_REGISTRY$2.resolveByAddress(normalizedToken, chain.chain);
25462
+ const { symbol } = TOKEN_REGISTRY$3.resolveByAddress(normalizedToken, chain.chain);
25195
25463
  if (symbol === undefined) {
25196
25464
  return null;
25197
25465
  }
@@ -25322,7 +25590,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25322
25590
  return `Insufficient ${displaySymbol} balance for swap operation.\n\n` + `Wallet: ${walletAddress}\n` + `Current balance: ${currentDisplay}\n` + `Required: ${requiredDisplay}\n` + `Shortfall: ${shortfallDisplay}\n\n` + `This swap requires ${requiredSummary} to complete the transaction.\n\n` + `Action: Add at least ${actionAmount} to your wallet to complete this swap.`;
25323
25591
  }
25324
25592
 
25325
- const TOKEN_REGISTRY$1 = createTokenRegistry();
25593
+ const TOKEN_REGISTRY$2 = createTokenRegistry();
25326
25594
  /**
25327
25595
  * Format a raw base-unit amount into a human-readable decimal string.
25328
25596
  *
@@ -25382,12 +25650,12 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
25382
25650
  value,
25383
25651
  token: resolvedSymbol,
25384
25652
  chain,
25385
- tokens: TOKEN_REGISTRY$1
25653
+ tokens: TOKEN_REGISTRY$2
25386
25654
  }),
25387
25655
  token: resolvedSymbol
25388
25656
  };
25389
25657
  }
25390
- const tokenDef = TOKEN_REGISTRY$1.get(resolvedSymbol);
25658
+ const tokenDef = TOKEN_REGISTRY$2.get(resolvedSymbol);
25391
25659
  if (tokenDef) {
25392
25660
  const decimals = resolveTokenDecimals$1(tokenDef, chain.chain);
25393
25661
  return {
@@ -25418,7 +25686,7 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
25418
25686
  * i.e. 1 + 0.2 + 0.1 = 1.3. Use the greater of (local estimate × this multiplier)
25419
25687
  * or the proxy's gasLimit.
25420
25688
  */ const GAS_SAFETY_MULTIPLIER = 1.3;
25421
- const TOKEN_REGISTRY = createTokenRegistry();
25689
+ const TOKEN_REGISTRY$1 = createTokenRegistry();
25422
25690
  /**
25423
25691
  * Enhances a KitError with transaction details (txHash and explorerUrl).
25424
25692
  *
@@ -25494,6 +25762,19 @@ const TOKEN_REGISTRY = createTokenRegistry();
25494
25762
  *
25495
25763
  * @internal
25496
25764
  */ const SUPPORTED_CHAINS$1 = getSwapSupportedChains(Chains);
25765
+ /**
25766
+ * Inter-poll delays (ms) for the same-chain `amountOut` enrichment loop.
25767
+ *
25768
+ * A same-chain swap is already terminal `DONE` once its source transaction
25769
+ * confirms; this short, escalating budget (~5s total across up to four polls)
25770
+ * gives the service a brief window to resolve the output amount before
25771
+ * `swap()` returns. The loop exits the instant `amountOut` is available.
25772
+ */ const SAME_CHAIN_AMOUNT_OUT_POLL_DELAYS_MS = [
25773
+ 1_000,
25774
+ 2_000,
25775
+ 2_000
25776
+ ];
25777
+ const sleep$3 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
25497
25778
  function buildSwapProgress(statusResult) {
25498
25779
  return {
25499
25780
  status: statusResult.status,
@@ -25513,29 +25794,46 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
25513
25794
  }
25514
25795
  };
25515
25796
  }
25516
- try {
25517
- const statusResult = await getSwapStatus$2({
25518
- txHash,
25519
- chain: chain.chain,
25520
- apiKey
25521
- });
25522
- const progress = buildSwapProgress(statusResult);
25523
- if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
25797
+ // A same-chain swap completes atomically in the source transaction, which
25798
+ // has already been confirmed (and receipt-checked for revert) by the time we
25799
+ // get here. Its terminal status is therefore `DONE`. We briefly poll the
25800
+ // status endpoint to enrich `amountOut`, which the service usually resolves
25801
+ // within a few seconds. A non-DONE/erroneous peek never downgrades the
25802
+ // status — once the short budget is exhausted we return `DONE` without
25803
+ // `amountOut`, and the caller can fetch it later via `getSwapStatus`.
25804
+ for(let attempt = 0;; attempt += 1){
25805
+ try {
25806
+ const statusResult = await getSwapStatus$2({
25807
+ txHash,
25808
+ chain: chain.chain,
25809
+ apiKey
25810
+ });
25811
+ if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
25812
+ return {
25813
+ progress: buildSwapProgress(statusResult),
25814
+ amountOut: statusResult.amountOut
25815
+ };
25816
+ }
25817
+ } catch {
25818
+ // Reachability/parse failure during enrichment — non-fatal; the swap
25819
+ // already succeeded on-chain. `getSwapStatus` already retried transient
25820
+ // network errors internally, so further polling is unlikely to help.
25524
25821
  return {
25525
- progress,
25526
- amountOut: statusResult.amountOut
25822
+ progress: {
25823
+ status: 'DONE'
25824
+ }
25527
25825
  };
25528
25826
  }
25529
- return {
25530
- progress
25531
- };
25532
- } catch {
25533
- // Non-fatal — the swap already succeeded on-chain. status stays PENDING.
25534
- return {
25535
- progress: {
25536
- status: 'PENDING'
25537
- }
25538
- };
25827
+ const delayMs = SAME_CHAIN_AMOUNT_OUT_POLL_DELAYS_MS[attempt];
25828
+ if (delayMs === undefined) {
25829
+ // Budget exhausted — terminal `DONE`, `amountOut` not yet available.
25830
+ return {
25831
+ progress: {
25832
+ status: 'DONE'
25833
+ }
25834
+ };
25835
+ }
25836
+ await sleep$3(delayMs);
25539
25837
  }
25540
25838
  }
25541
25839
  /**
@@ -25816,7 +26114,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
25816
26114
  return true;
25817
26115
  }
25818
26116
  // At least one token must be supported for fee collection
25819
- return getSwapOkTokenStatus(tokenInAddress, tokenOutAddress, chain, TOKEN_REGISTRY, OK_TOKEN_SYMBOLS$1).hasOkToken;
26117
+ return getSwapOkTokenStatus(tokenInAddress, tokenOutAddress, chain, TOKEN_REGISTRY$1, OK_TOKEN_SYMBOLS$1).hasOkToken;
25820
26118
  }
25821
26119
  /**
25822
26120
  * Estimate swap costs and output amount by delegating to the Stablecoin Service quote API.
@@ -26413,14 +26711,14 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
26413
26711
  }
26414
26712
  // Build swap fees with adapter-backed formatting for unregistered tokens
26415
26713
  const swapResultFees = serviceResponse.fees ? await this.buildFormattedFees(serviceResponse.fees, chain, destinationChain, adapter, config?.customFee?.recipientAddress) : undefined;
26416
- // Best-effort enrichment: fetch amountOut and provider-reported status.
26417
- // Only performed for same-chain swaps they typically reach DONE within
26418
- // a few seconds of source-tx confirmation, so the peek is useful UX and
26419
- // adds minimal latency. Cross-chain swaps are skipped because the
26420
- // destination mint takes minutes (CCTPv2 attestation), meaning the peek
26421
- // would always return PENDING/WAIT_DESTINATION_TRANSACTION while still
26422
- // burning up to 30s of the load balancer's server-side wait. Callers
26423
- // should use `kit.getSwapStatus()` to poll cross-chain swaps to DONE.
26714
+ // Best-effort enrichment: briefly poll for amountOut on same-chain swaps.
26715
+ // They complete atomically in the (already-confirmed) source tx, so the
26716
+ // result is terminal `DONE` regardless; the short poll only adds amountOut
26717
+ // once the service has resolved it. Cross-chain swaps are skipped because
26718
+ // the destination mint takes minutes (CCTPv2 attestation), so polling would
26719
+ // always return PENDING/WAIT_DESTINATION_TRANSACTION while burning the
26720
+ // budget. Callers should use `kit.getSwapStatus()` to poll cross-chain
26721
+ // swaps to DONE.
26424
26722
  const statusSnapshot = await fetchSameChainStatusSnapshot({
26425
26723
  isCrossChainSwap,
26426
26724
  txHash,
@@ -30047,7 +30345,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30047
30345
  ESTIMATE: 'swap_estimate'
30048
30346
  };
30049
30347
 
30050
- /** 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);
30051
30349
  /**
30052
30350
  * A high-level class-based interface for same-chain and cross-chain token swap operations.
30053
30351
  *
@@ -30170,7 +30468,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30170
30468
  this.disableErrorReporting = config.disableErrorReporting === true;
30171
30469
  this.telemetryConfig = {
30172
30470
  sdkName: SDK_NAME$1,
30173
- sdkVersion: pkg$2.version,
30471
+ sdkVersion: pkg$3.version,
30174
30472
  disabled: this.disableErrorReporting
30175
30473
  };
30176
30474
  }
@@ -30626,7 +30924,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30626
30924
  }
30627
30925
 
30628
30926
  // Auto-register this kit for user agent tracking
30629
- registerKit(`${pkg$2.name}/${pkg$2.version}`);
30927
+ registerKit(`${pkg$3.name}/${pkg$3.version}`);
30630
30928
 
30631
30929
  /**
30632
30930
  * Create a SwapKit instance with optional developer fee configuration.
@@ -30721,12 +31019,15 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
30721
31019
  return kit;
30722
31020
  };
30723
31021
 
30724
- var name$1 = "@circle-fin/earn-kit";
30725
- var version$1 = "1.2.0";
30726
- var pkg$1 = {
30727
- name: name$1,
30728
- 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};
30729
31027
 
31028
+ const EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS = [
31029
+ 'USDC'
31030
+ ];
30730
31031
  /**
30731
31032
  * Default base URL for the Earn Service API.
30732
31033
  *
@@ -30771,20 +31072,7 @@ var pkg$1 = {
30771
31072
  */ const EARN_BRIDGE_DESTINATION_CHAIN_TO_API = {
30772
31073
  [exports.Blockchain.Arc_Testnet]: CHAIN_TO_API[exports.Blockchain.Arc_Testnet]
30773
31074
  };
30774
- /**
30775
- * Expected EIP-712 domain of the ERC-3009 authorization token per source
30776
- * chain.
30777
- *
30778
- * Cross-chain Earn deposits are USDC-only, but USDC deployments differ on the
30779
- * EIP-712 domain name: newer deployments use `USDC` while older ones (for
30780
- * example Arbitrum Sepolia) kept `USD Coin` from the FiatToken V2 upgrade.
30781
- * These values mirror the bridge service's per-chain domain table and the
30782
- * on-chain `DOMAIN_SEPARATOR` inputs. Asserted before signing so a tampered
30783
- * prepare response cannot point the signature at another token. The
30784
- * `satisfies` check forces a domain entry whenever a source chain is added.
30785
- *
30786
- * @internal
30787
- */ const EARN_BRIDGE_ERC3009_DOMAINS = {
31075
+ const EARN_BRIDGE_USDC_ERC3009_DOMAINS = {
30788
31076
  [exports.Blockchain.Arbitrum_Sepolia]: {
30789
31077
  name: 'USD Coin',
30790
31078
  version: '2'
@@ -30798,6 +31086,21 @@ var pkg$1 = {
30798
31086
  version: '2'
30799
31087
  }
30800
31088
  };
31089
+ /**
31090
+ * Expected EIP-712 domain of the ERC-3009 authorization token per
31091
+ * cross-chain-supported source token and source chain.
31092
+ *
31093
+ * The bridge service prepares an ERC-3009 payload for the deposited token. We
31094
+ * keep this table as an explicit SDK allowlist because token deployments can
31095
+ * differ by source chain and by domain name/version. These values mirror the
31096
+ * bridge service's per-chain domain table and the on-chain DOMAIN_SEPARATOR
31097
+ * inputs. Asserted before signing so a tampered prepare response cannot point
31098
+ * the signature at another token or unexpected token domain.
31099
+ *
31100
+ * @internal
31101
+ */ const EARN_BRIDGE_ERC3009_DOMAINS = {
31102
+ USDC: EARN_BRIDGE_USDC_ERC3009_DOMAINS
31103
+ };
30801
31104
  /**
30802
31105
  * Maximum tolerated remaining lifetime of a prepared ERC-3009 authorization.
30803
31106
  *
@@ -31257,6 +31560,16 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31257
31560
  return approvalTxHash;
31258
31561
  }
31259
31562
 
31563
+ /** @internal */ function isSameAddress(actual, expected) {
31564
+ return actual.toLowerCase() === expected.toLowerCase();
31565
+ }
31566
+ /** @internal */ function assertDecimalPlaces(field, value, tokenDecimals, reason) {
31567
+ const decimals = value.split('.')[1]?.length ?? 0;
31568
+ if (decimals > tokenDecimals) {
31569
+ throw createValidationFailedError$1(field, value, reason);
31570
+ }
31571
+ }
31572
+
31260
31573
  /**
31261
31574
  * Build the `tokenInputs` array forwarded to the adapter contract's
31262
31575
  * `execute(executeParams, tokenInputs, signature)` call.
@@ -31280,15 +31593,17 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31280
31593
  *
31281
31594
  * @example
31282
31595
  * ```typescript
31283
- * const approvedToken = assertHexAddress(
31284
- * 'chain.usdcAddress',
31285
- * chain.usdcAddress,
31286
- * )
31287
- *
31288
- * const tokenInputs = buildEarnTokenInputs(
31596
+ * const approvalToken = resolveEarnApprovalToken(
31289
31597
  * executionPayload.executionParams,
31290
- * approvedToken,
31291
31598
  * )
31599
+ *
31600
+ * const tokenInputs =
31601
+ * approvalToken === undefined
31602
+ * ? []
31603
+ * : buildEarnTokenInputs(
31604
+ * executionPayload.executionParams,
31605
+ * approvalToken,
31606
+ * )
31292
31607
  * ```
31293
31608
  *
31294
31609
  * @internal
@@ -31300,7 +31615,7 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31300
31615
  return;
31301
31616
  }
31302
31617
  const { tokenIn } = instruction;
31303
- if (tokenIn.toLowerCase() !== approvedToken.toLowerCase()) {
31618
+ if (!isSameAddress(tokenIn, approvedToken)) {
31304
31619
  throw createValidationFailedError$1(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
31305
31620
  }
31306
31621
  tokenInputs.push({
@@ -31312,6 +31627,33 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31312
31627
  });
31313
31628
  return tokenInputs;
31314
31629
  }
31630
+ /**
31631
+ * Resolve the single token that needs adapter allowance from signed Earn
31632
+ * instructions.
31633
+ *
31634
+ * @param executionParams - Service-signed `ExecutionParams` forwarded to the adapter.
31635
+ * @returns The token requested by positive approval instructions, or `undefined`.
31636
+ * @throws {@link KitError} If positive approval instructions reference multiple tokens.
31637
+ *
31638
+ * @internal
31639
+ */ function resolveEarnApprovalToken(executionParams) {
31640
+ let approvedToken;
31641
+ executionParams.instructions.forEach((instruction, index)=>{
31642
+ const amount = BigInt(instruction.amountToApprove);
31643
+ if (amount <= 0n) {
31644
+ return;
31645
+ }
31646
+ const { tokenIn } = instruction;
31647
+ if (approvedToken === undefined) {
31648
+ approvedToken = tokenIn;
31649
+ return;
31650
+ }
31651
+ if (!isSameAddress(tokenIn, approvedToken)) {
31652
+ throw createValidationFailedError$1(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
31653
+ }
31654
+ });
31655
+ return approvedToken;
31656
+ }
31315
31657
 
31316
31658
  /**
31317
31659
  * Prepare an earn adapter action, execute it, wait for confirmation, and
@@ -31803,6 +32145,7 @@ const hexSignatureSchema = evmSignatureSchema;
31803
32145
  const hexAddressSchema = evmAddressSchema;
31804
32146
  // '0x' prefix + 32 bytes * 2 hex chars.
31805
32147
  const BYTES32_HEX_LENGTH = 66;
32148
+ const bridgeFeeTokenSchema = hexAddressSchema;
31806
32149
  /**
31807
32150
  * Zod schema for a non-negative uint256-like value.
31808
32151
  *
@@ -32089,14 +32432,12 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32089
32432
  *
32090
32433
  * The bridge prepare path returns one item per collected source fee (e.g.
32091
32434
  * `PRE_FINALITY` for the fast-burn fee and `FORWARD` for the destination
32092
- * forward fee), all denominated in the shared `feeToken`. The signed
32093
- * authorization `value` is `principal + sum(items[].amount)`, so the pre-sign
32094
- * guard parses these to recompute the expected value. Each item's `amount` is
32095
- * a base-unit decimal string in `feeToken` units.
32435
+ * forward fee), all denominated in the shared `feeToken`. Each item's `amount`
32436
+ * is a base-unit decimal string in `feeToken` units.
32096
32437
  *
32097
32438
  * @internal
32098
32439
  */ const bridgeFeeQuoteSchema = zod.z.object({
32099
- feeToken: hexAddressSchema,
32440
+ feeToken: bridgeFeeTokenSchema,
32100
32441
  items: zod.z.array(zod.z.object({
32101
32442
  type: zod.z.string(),
32102
32443
  amount: uint256LikeSchema
@@ -32112,6 +32453,12 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32112
32453
  erc3009TypedData: erc3009TypedDataSchema,
32113
32454
  feeQuote: bridgeFeeQuoteSchema
32114
32455
  }).passthrough();
32456
+ const bridgeDepositPrepareReviewSchema = zod.z.object({
32457
+ sourceChain: zod.z.string(),
32458
+ destinationChain: zod.z.string(),
32459
+ amount: amountJsonSchema,
32460
+ vaultAddress: hexAddressSchema
32461
+ }).passthrough();
32115
32462
  /**
32116
32463
  * Zod schema for the bridge deposit prepare payload.
32117
32464
  *
@@ -32123,7 +32470,7 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32123
32470
  execId: bridgeDepositExecIdSchema,
32124
32471
  erc3009TypedData: bridgeDepositPreparedBundleSchema,
32125
32472
  expiresAt: zod.z.string().datetime(),
32126
- review: zod.z.unknown().optional()
32473
+ review: bridgeDepositPrepareReviewSchema
32127
32474
  });
32128
32475
  /**
32129
32476
  * Zod schema for the `POST /v1/earnKit/bridge/deposit/prepare` API response.
@@ -32248,10 +32595,11 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32248
32595
  * Zod schema for a fee entry in an EarnKit API response.
32249
32596
  *
32250
32597
  * Shared across deposit and withdrawal responses (and reusable for real
32251
- * charged fees, not just quote estimates). `type` identifies the fee category
32252
- * for cross-chain deposit quotes this is the kits-proxy fee-quote item type
32253
- * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). `status` qualifies the fee (e.g.
32254
- * `'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.
32255
32603
  *
32256
32604
  * @internal
32257
32605
  */ const feeSchema = zod.z.object({
@@ -32523,6 +32871,60 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32523
32871
  return claimRewardsQuoteResponseSchema.safeParse(value).success;
32524
32872
  }
32525
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
+
32526
32928
  /**
32527
32929
  * Build an API polling config with optional authorization header,
32528
32930
  * and resolve the base URL (configurable for testing).
@@ -32533,9 +32935,16 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32533
32935
  * @internal
32534
32936
  */ function buildConfig(serviceConfig) {
32535
32937
  const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
32938
+ const sdkVersion = resolveSdkVersionHeader();
32536
32939
  if (serviceConfig?.kitKey === undefined) {
32537
32940
  return {
32538
- pollingConfig: DEFAULT_CONFIG,
32941
+ pollingConfig: {
32942
+ ...DEFAULT_CONFIG,
32943
+ headers: {
32944
+ ...DEFAULT_CONFIG.headers,
32945
+ [SDK_VERSION_HEADER]: sdkVersion
32946
+ }
32947
+ },
32539
32948
  baseUrl
32540
32949
  };
32541
32950
  }
@@ -32551,6 +32960,7 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32551
32960
  ...DEFAULT_CONFIG,
32552
32961
  headers: {
32553
32962
  ...DEFAULT_CONFIG.headers,
32963
+ [SDK_VERSION_HEADER]: sdkVersion,
32554
32964
  Authorization: `Bearer ${serviceConfig.kitKey}`
32555
32965
  }
32556
32966
  },
@@ -32851,7 +33261,8 @@ function toPositionInfo(data) {
32851
33261
  return {
32852
33262
  execId: response.data.execId,
32853
33263
  preparedBundle,
32854
- expiresAt: response.data.expiresAt
33264
+ expiresAt: response.data.expiresAt,
33265
+ review: response.data.review
32855
33266
  };
32856
33267
  } catch (error) {
32857
33268
  throw parseEarnApiError(error, {
@@ -32962,47 +33373,34 @@ const VALID_AFTER_CLOCK_SKEW_SECONDS = 60n;
32962
33373
  * @throws If any typed-data field does not match the expected bridge deposit intent.
32963
33374
  * @internal
32964
33375
  */ function assertBridgeDepositTypedDataMatchesIntent(intent) {
32965
- const { preparedBundle, sourceChain, sourceAddress, sourceTokenAddress, authorizationRecipient, amount, maxFee, tokenDecimals, nowSeconds = BigInt(Math.floor(Date.now() / 1000)) } = intent;
33376
+ const { preparedBundle, sourceChain, sourceAddress, sourceTokenAddress, expectedDomain, authorizationRecipient, amount, maxFee, tokenDecimals, amountRaw, nowSeconds = BigInt(Math.floor(Date.now() / 1000)) } = intent;
32966
33377
  const { domain, message } = preparedBundle.erc3009TypedData;
32967
- // USDC deployments differ on the EIP-712 domain name per chain, so the
32968
- // expected domain is looked up for the source chain being signed on.
32969
- const expectedDomain = EARN_BRIDGE_ERC3009_DOMAINS[sourceChain.chain];
32970
- if (expectedDomain === undefined) {
32971
- throw createValidationFailedError$1('sourceChain.chain', sourceChain.chain, 'no expected ERC-3009 domain is configured for the source chain');
32972
- }
32973
- if (domain.name !== expectedDomain.name) {
32974
- throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.name', domain.name, `domain name must be ${expectedDomain.name}`);
32975
- }
32976
- if (domain.version !== expectedDomain.version) {
32977
- throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.version', domain.version, `domain version must be ${expectedDomain.version}`);
32978
- }
32979
33378
  assertSameAddress('preparedBundle.erc3009TypedData.domain.verifyingContract', domain.verifyingContract, sourceTokenAddress, 'verifyingContract must match the source token contract');
32980
33379
  assertSameBigInt('preparedBundle.erc3009TypedData.domain.chainId', domain.chainId, BigInt(sourceChain.chainId), 'chainId must match the source chain');
33380
+ assertSameString('preparedBundle.erc3009TypedData.domain.name', domain.name, expectedDomain.name, 'domain name must match the source token');
33381
+ assertSameString('preparedBundle.erc3009TypedData.domain.version', domain.version, expectedDomain.version, 'domain version must match the source token');
32981
33382
  assertSameAddress('preparedBundle.erc3009TypedData.message.from', message.from, sourceAddress, 'from must match the source wallet');
32982
33383
  assertSameAddress('preparedBundle.erc3009TypedData.message.to', message.to, authorizationRecipient, 'to must match the configured authorization recipient');
32983
- // The prepare path folds the source fee into the signed authorization:
32984
- // message.value = principal + sum(feeQuote.items[].amount), all denominated
32985
- // in the source token. The fee items only add up to a meaningful total when
32986
- // they are priced in the same token as the principal, so first pin the fee
32987
- // token to the source token contract before summing.
32988
- assertSameAddress('preparedBundle.feeQuote.feeToken', preparedBundle.feeQuote.feeToken, sourceTokenAddress, 'feeQuote.feeToken must match the source token contract');
32989
- // Recompute the expected value from locally trusted inputs (the caller
32990
- // principal) plus the bundle's own fee items so the guard accepts fee-bearing
32991
- // deposits while still rejecting any other value tampering. The fee quote is
32992
- // service-supplied, so bound it to the caller-accepted quote-derived cap
32993
- // before including it in the signed value.
32994
- const principal = Amount.parse(amount, {
33384
+ // Recompute the expected ERC-3009 value from locally trusted inputs. Bridge
33385
+ // source fees are paid in the source ERC-20 and are part of the authorization.
33386
+ assertDecimalPlaces('amount', amount, tokenDecimals, `amount must have at most ${String(tokenDecimals)} decimal places for the cross-chain Earn deposit source token`);
33387
+ const parsedAmount = Amount.parse(amount, {
32995
33388
  decimals: tokenDecimals
32996
33389
  }).raw;
33390
+ const principal = amountRaw === undefined ? parsedAmount : parseReviewedAmount(amountRaw, parsedAmount);
33391
+ const feeQuote = preparedBundle.feeQuote;
32997
33392
  const feeTotal = sumBridgeFeeItems(preparedBundle.feeQuote);
33393
+ assertFeeTokenMatchesSourceToken(feeQuote.feeToken, sourceTokenAddress);
33394
+ const feeDecimals = tokenDecimals;
33395
+ assertDecimalPlaces('maxFee', maxFee, feeDecimals, `maxFee must have at most ${String(feeDecimals)} decimal places for the cross-chain Earn deposit fee token`);
32998
33396
  const maxFeeRaw = Amount.parse(maxFee, {
32999
- decimals: tokenDecimals
33397
+ decimals: feeDecimals
33000
33398
  }).raw;
33001
33399
  if (feeTotal > maxFeeRaw) {
33002
33400
  throw createValidationFailedError$1('preparedBundle.feeQuote.items', feeTotal.toString(), 'fee total must not exceed maxFee in base units');
33003
33401
  }
33004
33402
  const expectedValue = principal + feeTotal;
33005
- assertSameBigInt('preparedBundle.erc3009TypedData.message.value', message.value, expectedValue, 'value must match the requested amount plus the bundle fee in base units');
33403
+ assertSameBigInt('preparedBundle.erc3009TypedData.message.value', message.value, expectedValue, 'value must match the requested amount plus source-token fees in base units');
33006
33404
  const window = checkErc3009ValidityWindow({
33007
33405
  validAfter: BigInt(message.validAfter),
33008
33406
  validBefore: BigInt(message.validBefore),
@@ -33076,14 +33474,19 @@ const VALID_AFTER_CLOCK_SKEW_SECONDS = 60n;
33076
33474
  /**
33077
33475
  * Sum the source-collected fee items in a prepared bundle's fee quote.
33078
33476
  *
33079
- * Each item amount is a base-unit value in the shared `feeToken`; the total is
33080
- * what the prepare path adds to the principal when building `message.value`.
33477
+ * Each item amount is a base-unit value in the shared `feeToken`.
33081
33478
  *
33082
33479
  * @param feeQuote - Fee quote parsed from the prepared bundle.
33083
- * @returns Total fee in source token base units (0n when there are no items).
33480
+ * @returns Total fee in fee-token base units (0n when there are no items).
33084
33481
  */ function sumBridgeFeeItems(feeQuote) {
33085
33482
  return feeQuote.items.reduce((total, item)=>total + BigInt(item.amount), 0n);
33086
33483
  }
33484
+ function assertFeeTokenMatchesSourceToken(feeToken, sourceTokenAddress) {
33485
+ if (isSameAddress(feeToken, sourceTokenAddress)) {
33486
+ return;
33487
+ }
33488
+ throw createValidationFailedError$1('preparedBundle.feeQuote.feeToken', feeToken, 'fee token must match the cross-chain Earn deposit source token');
33489
+ }
33087
33490
  function assertNever(value) {
33088
33491
  throw new KitError({
33089
33492
  ...EarnError.INTERNAL_ERROR,
@@ -33091,8 +33494,23 @@ function assertNever(value) {
33091
33494
  message: `Unexpected validity window reason: ${String(value)}`
33092
33495
  });
33093
33496
  }
33497
+ function parseReviewedAmount(amountRaw, parsedAmount) {
33498
+ let reviewedAmount;
33499
+ try {
33500
+ reviewedAmount = BigInt(amountRaw);
33501
+ } catch {
33502
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'amount must be a non-negative integer string');
33503
+ }
33504
+ if (reviewedAmount < 0n) {
33505
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'amount must be a non-negative integer string');
33506
+ }
33507
+ if (reviewedAmount !== parsedAmount) {
33508
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'review amount must match the requested amount');
33509
+ }
33510
+ return reviewedAmount;
33511
+ }
33094
33512
  function assertSameAddress(field, actual, expected, reason) {
33095
- if (actual.toLowerCase() !== expected.toLowerCase()) {
33513
+ if (!isSameAddress(actual, expected)) {
33096
33514
  throw createValidationFailedError$1(field, actual, reason);
33097
33515
  }
33098
33516
  }
@@ -33101,6 +33519,11 @@ function assertSameBigInt(field, actual, expected, reason) {
33101
33519
  throw createValidationFailedError$1(field, actual, reason);
33102
33520
  }
33103
33521
  }
33522
+ function assertSameString(field, actual, expected, reason) {
33523
+ if (actual !== expected) {
33524
+ throw createValidationFailedError$1(field, actual, reason);
33525
+ }
33526
+ }
33104
33527
 
33105
33528
  /**
33106
33529
  * Build signed withdrawal instructions via the Earn Service API.
@@ -33270,9 +33693,15 @@ function toWithdrawalQuoteInfo(data) {
33270
33693
  symbol: data.asset,
33271
33694
  amount: Amount.fromJSON(data.maxWithdrawable)
33272
33695
  },
33273
- fees: data.fees.map((fee)=>({
33274
- symbol: fee.token,
33275
- 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
+ }
33276
33705
  })),
33277
33706
  gasFees: [],
33278
33707
  // Wire format uses `warnings`, but the SDK surface uses
@@ -33458,6 +33887,9 @@ function toWithdrawalQuoteInfo(data) {
33458
33887
  'CONFIRMED',
33459
33888
  'COMPLETE'
33460
33889
  ]);
33890
+ // Intentionally built-ins-only: Earn bridge support is limited to SDK-known
33891
+ // token contracts plus the explicit ERC-3009 domain allowlist below.
33892
+ const TOKEN_REGISTRY = createTokenRegistry();
33461
33893
  /**
33462
33894
  * Build the typed error raised when a cross-chain wait is cancelled via its
33463
33895
  * `AbortSignal`. Mirrors `@core/adapter-base`'s `createAbortError` (same
@@ -33787,24 +34219,20 @@ function finishElapsedWait(lastStatus, lastError) {
33787
34219
  }
33788
34220
  const { address, chain: apiChain, chainDefinition: chain } = await resolveAdapterContext(params.from);
33789
34221
  const adapterContractAddress = requireAdapterContract(chain);
33790
- const rawUsdcAddress = chain.usdcAddress;
33791
- if (rawUsdcAddress === null) {
33792
- throw createUnsupportedTokenError('USDC', chain.name);
33793
- }
33794
- const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
33795
34222
  const { adapter } = params.from;
34223
+ const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
33796
34224
  const { executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
33797
- vaultAddress: params.vaultAddress,
34225
+ vaultAddress,
33798
34226
  amount: params.amount,
33799
34227
  address,
33800
34228
  chain: apiChain,
33801
34229
  config
33802
34230
  }), ()=>undefined);
33803
34231
  validateExecutionDeadline(executionParams);
33804
- const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
33805
- const approvalToken = tokenInputs[0]?.token;
34232
+ const approvalToken = resolveEarnApprovalToken(executionParams);
34233
+ const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
33806
34234
  const requiredAllowance = sumTokenInputAmounts(tokenInputs);
33807
- if (!options.skipApprove && approvalToken !== undefined) {
34235
+ if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
33808
34236
  await this.runPhase(ctx, 'approve', 'approve', async ()=>approveAllowanceIfNeeded({
33809
34237
  adapter,
33810
34238
  chain,
@@ -33812,7 +34240,7 @@ function finishElapsedWait(lastStatus, lastError) {
33812
34240
  delegate: adapterContractAddress,
33813
34241
  address,
33814
34242
  requiredAllowance,
33815
- revertMessage: 'USDC approval reverted on-chain'
34243
+ revertMessage: 'Earn deposit token approval reverted on-chain'
33816
34244
  }), (txHash)=>txHash);
33817
34245
  }
33818
34246
  const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>executeEarnAction({
@@ -33831,7 +34259,7 @@ function finishElapsedWait(lastStatus, lastError) {
33831
34259
  kind: 'same-chain',
33832
34260
  txHash,
33833
34261
  explorerUrl,
33834
- vaultAddress: params.vaultAddress,
34262
+ vaultAddress,
33835
34263
  amount: params.amount
33836
34264
  };
33837
34265
  } catch (error) {
@@ -33853,13 +34281,6 @@ function finishElapsedWait(lastStatus, lastError) {
33853
34281
  const sourceAddress = assertHexAddress('from.address', await resolveCrossChainSourceAddress(params.from, sourceChain), `Source address for chain ${sourceChain.name} must be a 0x-prefixed 20-byte hex address.`);
33854
34282
  const destinationAddress = assertHexAddress('to.recipientAddress', params.to.recipientAddress, `Destination address for chain ${destinationChain.name} must be a 0x-prefixed 20-byte hex address.`);
33855
34283
  const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
33856
- // The signed ERC-3009 value is denominated in USDC's 6 decimals. More
33857
- // precise inputs would be silently truncated by Amount.parse, so the
33858
- // result would claim a larger deposit than was actually authorized.
33859
- const amountDecimals = params.amount.split('.')[1]?.length ?? 0;
33860
- if (amountDecimals > TOKEN_DECIMALS_6) {
33861
- throw createValidationFailedError$1('amount', params.amount, `amount must have at most ${String(TOKEN_DECIMALS_6)} decimal places for cross-chain Earn deposits`);
33862
- }
33863
34284
  const execId = params.idempotencyKey;
33864
34285
  const prepared = await this.runPhase(ctx, 'crossChainDeposit', 'prepare', async ()=>fetchBridgeDepositPrepare({
33865
34286
  execId,
@@ -33879,17 +34300,19 @@ function finishElapsedWait(lastStatus, lastError) {
33879
34300
  // the error trace, instead of ending the event stream unannounced
33880
34301
  // after the prepare success event.
33881
34302
  assertPreparedBundleMatchesTypedDataNonce(prepared.preparedBundle);
34303
+ const sourceToken = resolvePreparedBridgeSourceToken(prepared.preparedBundle, route.sourceChain);
34304
+ validatePreparedReviewAmountDecimals(prepared.review.amount.decimals, sourceToken.decimals);
33882
34305
  assertBridgeDepositTypedDataMatchesIntent({
33883
34306
  preparedBundle: prepared.preparedBundle,
33884
34307
  sourceChain: route.sourceChain,
33885
34308
  sourceAddress,
33886
- sourceTokenAddress: route.sourceTokenAddress,
34309
+ sourceTokenAddress: sourceToken.address,
34310
+ expectedDomain: sourceToken.erc3009Domain,
33887
34311
  authorizationRecipient: route.authorizationRecipient,
33888
34312
  amount: params.amount,
33889
34313
  maxFee: params.maxFee,
33890
- // Cross-chain Earn is USDC-only today. USDC uses 6 decimals on all
33891
- // configured source chains.
33892
- tokenDecimals: TOKEN_DECIMALS_6
34314
+ tokenDecimals: sourceToken.decimals,
34315
+ amountRaw: prepared.review.amount.raw
33893
34316
  });
33894
34317
  return signBridgeDepositAuthorization({
33895
34318
  adapter: bridgeSigningAdapter,
@@ -34112,21 +34535,17 @@ function finishElapsedWait(lastStatus, lastError) {
34112
34535
  const { adapter, chain, apiChain, address, vaultAddress, amount, config } = params;
34113
34536
  try {
34114
34537
  const adapterContractAddress = requireAdapterContract(chain);
34115
- const rawUsdcAddress = chain.usdcAddress;
34116
- if (rawUsdcAddress === null) {
34117
- throw createUnsupportedTokenError('USDC', chain.name);
34118
- }
34119
- const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
34538
+ const normalizedVaultAddress = assertHexAddress('vaultAddress', vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
34120
34539
  const { executionParams, signature } = await fetchDeposit({
34121
- vaultAddress,
34540
+ vaultAddress: normalizedVaultAddress,
34122
34541
  amount,
34123
34542
  address,
34124
34543
  chain: apiChain,
34125
34544
  config
34126
34545
  });
34127
34546
  validateExecutionDeadline(executionParams);
34128
- const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
34129
- const approvalToken = tokenInputs[0]?.token;
34547
+ const approvalToken = resolveEarnApprovalToken(executionParams);
34548
+ const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
34130
34549
  const requiredAllowance = sumTokenInputAmounts(tokenInputs);
34131
34550
  return await estimateEarnQuoteGasFees({
34132
34551
  adapter,
@@ -34139,7 +34558,7 @@ function finishElapsedWait(lastStatus, lastError) {
34139
34558
  tokenInputs,
34140
34559
  signature
34141
34560
  },
34142
- approval: approvalToken !== undefined ? {
34561
+ approval: approvalToken !== undefined && requiredAllowance > 0n ? {
34143
34562
  token: approvalToken,
34144
34563
  delegate: adapterContractAddress,
34145
34564
  requiredAllowance
@@ -34383,11 +34802,6 @@ function validateCrossChainDepositRoute(sourceChain, destinationChain) {
34383
34802
  if (destinationApiChain === undefined) {
34384
34803
  throw createUnsupportedCrossChainDepositError(destinationChain, 'destination chain is not enabled for cross-chain Earn deposits');
34385
34804
  }
34386
- const sourceTokenAddressRaw = sourceChain.usdcAddress;
34387
- if (sourceTokenAddressRaw === null) {
34388
- throw createUnsupportedTokenError('USDC', sourceChain.name);
34389
- }
34390
- const sourceTokenAddress = assertHexAddress('sourceChain.usdcAddress', sourceTokenAddressRaw, `USDC address for chain ${sourceChain.name} must be a 0x-prefixed 20-byte hex address.`);
34391
34805
  if (sourceChain.cctp == null) {
34392
34806
  throw createBridgeRouteNotConfiguredError(sourceChain, 'source chain does not support CCTP');
34393
34807
  }
@@ -34411,10 +34825,50 @@ function validateCrossChainDepositRoute(sourceChain, destinationChain) {
34411
34825
  destinationApiChain,
34412
34826
  destinationDomain: destinationChain.cctp.domain,
34413
34827
  sourceChain,
34414
- sourceTokenAddress,
34415
34828
  authorizationRecipient
34416
34829
  };
34417
34830
  }
34831
+ function resolvePreparedBridgeSourceToken(preparedBundle, sourceChain) {
34832
+ const sourceTokenAddress = assertHexAddress('preparedBundle.erc3009TypedData.domain.verifyingContract', preparedBundle.erc3009TypedData.domain.verifyingContract, `Source token for chain ${sourceChain.name} must be a 0x-prefixed 20-byte hex address.`);
34833
+ let sourceToken;
34834
+ try {
34835
+ sourceToken = TOKEN_REGISTRY.resolveByAddress(sourceTokenAddress, sourceChain.chain);
34836
+ } catch {
34837
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token ${sourceTokenAddress} is not configured on source chain ${sourceChain.name} for cross-chain Earn deposits`);
34838
+ }
34839
+ const registryAddress = assertHexAddress('sourceToken.locator', sourceToken.locator, `Source token ${sourceToken.symbol ?? sourceTokenAddress} for chain ${sourceChain.name} must be a 0x-prefixed 20-byte hex address.`);
34840
+ if (!isSameAddress(sourceTokenAddress, registryAddress)) {
34841
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token must match the configured ${sourceToken.symbol ?? 'token'} address on source chain ${sourceChain.name}`);
34842
+ }
34843
+ const { symbol } = sourceToken;
34844
+ if (symbol === undefined) {
34845
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token ${sourceTokenAddress} is not a registered Earn bridge token on source chain ${sourceChain.name}`);
34846
+ }
34847
+ return {
34848
+ address: sourceTokenAddress,
34849
+ erc3009Domain: resolveBridgeErc3009Domain(symbol, sourceChain),
34850
+ decimals: sourceToken.decimals
34851
+ };
34852
+ }
34853
+ function validatePreparedReviewAmountDecimals(reviewedDecimals, tokenDecimals) {
34854
+ if (reviewedDecimals !== tokenDecimals) {
34855
+ throw createValidationFailedError$1('prepared.review.amount.decimals', reviewedDecimals, `review amount decimals must match the cross-chain Earn deposit source token decimals ${String(tokenDecimals)}`);
34856
+ }
34857
+ }
34858
+ function resolveBridgeErc3009Domain(tokenSymbol, sourceChain) {
34859
+ if (!isEarnBridgeErc3009TokenSymbol(tokenSymbol)) {
34860
+ throw createValidationFailedError$1('sourceToken.symbol', tokenSymbol, `ERC-3009 domain is not configured for ${tokenSymbol} on source chain ${sourceChain.name}`);
34861
+ }
34862
+ const domainsByChain = EARN_BRIDGE_ERC3009_DOMAINS[tokenSymbol];
34863
+ const domain = domainsByChain[sourceChain.chain];
34864
+ if (domain === undefined) {
34865
+ throw createValidationFailedError$1('sourceToken.symbol', tokenSymbol, `ERC-3009 domain is not configured for ${tokenSymbol} on source chain ${sourceChain.name}`);
34866
+ }
34867
+ return domain;
34868
+ }
34869
+ function isEarnBridgeErc3009TokenSymbol(tokenSymbol) {
34870
+ return EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS.includes(tokenSymbol);
34871
+ }
34418
34872
  function createUnsupportedCrossChainDepositError(chain, reason) {
34419
34873
  return new KitError({
34420
34874
  ...EarnError.UNSUPPORTED_CHAIN,
@@ -34487,7 +34941,6 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
34487
34941
  // Untyped callers can pass anything here; without these guards a null
34488
34942
  // config crashes with a raw TypeError on `config.providers` instead of
34489
34943
  // the structured validation error every other public surface throws.
34490
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JS consumers may pass null or a non-object config
34491
34944
  if (typeof config !== 'object' || config === null || Array.isArray(config)) {
34492
34945
  throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
34493
34946
  }
@@ -34854,10 +35307,9 @@ const sourceAdapterContextSchema = zod.z.object({
34854
35307
  * Schema for validating human-readable decimal amount strings.
34855
35308
  *
34856
35309
  * Accept positive decimal strings like '100', '100.50', '0.001'. Reject
34857
- * zero, negative, non-numeric, and non-canonical strings. EarnKit
34858
- * same-chain amounts are USDC/EURC (6 decimals), so precision is capped at
34859
- * 6 to match the Earn Service and fail fast locally instead of leaking
34860
- * 7+ decimal-place inputs to the server.
35310
+ * zero, negative, non-numeric, and non-canonical strings. Same-chain
35311
+ * EarnKit vaults can use different asset precisions, so the service enforces
35312
+ * the vault-specific decimal cap after resolving the vault asset.
34861
35313
  *
34862
35314
  * @internal
34863
35315
  */ const amountSchema$1 = zod.z.string({
@@ -34866,32 +35318,15 @@ const sourceAdapterContextSchema = zod.z.object({
34866
35318
  allowZero: false,
34867
35319
  regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34868
35320
  attributeName: 'amount',
34869
- maxDecimals: 6
34870
- })(zod.z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34871
- /**
34872
- * Schema for cross-chain deposit amount strings.
34873
- *
34874
- * Cross-chain Earn deposits are USDC-only, and the signed ERC-3009 value is
34875
- * denominated in USDC's 6 decimals. Reject more precise inputs up front so
34876
- * the signed value always equals the requested amount instead of a silent
34877
- * truncation. Non-canonical forms (leading dot, leading zeros) are rejected
34878
- * too, matching the Earn Service.
34879
- *
34880
- * @internal
34881
- */ const crossChainAmountSchema = zod.z.string({
34882
- required_error: 'amount is required'
34883
- }).min(1, 'amount is required').pipe(createDecimalStringValidator({
34884
- allowZero: false,
34885
- regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34886
- attributeName: 'amount',
34887
- maxDecimals: 6
35321
+ maxDecimals: 18
34888
35322
  })(zod.z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34889
35323
  /**
34890
35324
  * Schema for cross-chain source fee caps.
34891
35325
  *
34892
- * Cross-chain source fees are denominated in USDC base units. Zero is valid
34893
- * when the quote contains no source-collected fees; otherwise the cap should be
34894
- * the caller-accepted total from a recent quote.
35326
+ * Cross-chain source fees are denominated in the quote's fee token. Zero is
35327
+ * valid when the quote contains no source-collected fees; otherwise the cap
35328
+ * should be the caller-accepted total from a recent quote. The provider
35329
+ * enforces the prepared bundle's fee-token precision before signing.
34895
35330
  *
34896
35331
  * @internal
34897
35332
  */ const crossChainMaxFeeSchema = zod.z.string({
@@ -34900,7 +35335,7 @@ const sourceAdapterContextSchema = zod.z.object({
34900
35335
  allowZero: true,
34901
35336
  regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34902
35337
  attributeName: 'maxFee',
34903
- maxDecimals: 6
35338
+ maxDecimals: 18
34904
35339
  })(zod.z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34905
35340
  /**
34906
35341
  * Build a schema for an optional non-negative decimal filter string.
@@ -35061,7 +35496,7 @@ const crossChainDepositDestinationSchema = zod.z.object({
35061
35496
  from: sourceAdapterContextSchema,
35062
35497
  to: crossChainDepositDestinationSchema,
35063
35498
  vaultAddress: vaultAddressSchema,
35064
- amount: crossChainAmountSchema,
35499
+ amount: amountSchema$1,
35065
35500
  maxFee: crossChainMaxFeeSchema,
35066
35501
  transferSpeed: zod.z.enum([
35067
35502
  'FAST',
@@ -36485,7 +36920,7 @@ function formatRetryResult(operation, result) {
36485
36920
  }
36486
36921
 
36487
36922
  // Auto-register this kit for user agent tracking
36488
- registerKit(`${pkg$1.name}/${pkg$1.version}`);
36923
+ registerKit(`${pkg$2.name}/${pkg$2.version}`);
36489
36924
 
36490
36925
  /**
36491
36926
  * Create an EarnKit instance for AppKit earn operations.
@@ -36580,12 +37015,17 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36580
37015
  * The type annotation `readonly TokenAlias[]` ensures this array stays in sync
36581
37016
  * with the TokenAlias type definition - TypeScript will enforce any changes.
36582
37017
  *
36583
- * 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
36584
37023
  * via SwapKit's SupportedToken type.
36585
37024
  */ const TOKEN_ALIASES = [
36586
37025
  'USDC',
36587
37026
  'USDT',
36588
- 'NATIVE'
37027
+ 'NATIVE',
37028
+ 'EURC'
36589
37029
  ];
36590
37030
  /**
36591
37031
  * Check if a token string is a known alias.
@@ -36612,7 +37052,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36612
37052
  * Type guard to check if a string is a valid token address for a chain.
36613
37053
  *
36614
37054
  * This function verifies that a token string is:
36615
- * 1. Not a known alias ('USDC', 'USDT', 'NATIVE')
37055
+ * 1. Not a known alias ('USDC', 'USDT', 'NATIVE', 'EURC')
36616
37056
  * 2. A valid address format for the specified chain
36617
37057
  *
36618
37058
  * Use this to narrow the type to `TokenAddress` in TypeScript.
@@ -36642,7 +37082,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36642
37082
  * Validate and classify a token identifier.
36643
37083
  *
36644
37084
  * This function determines whether a token string is:
36645
- * 1. A known alias ('USDC', 'USDT', or 'NATIVE')
37085
+ * 1. A known alias ('USDC', 'USDT', 'NATIVE', or 'EURC')
36646
37086
  * 2. A valid token address for the given chain
36647
37087
  * 3. An invalid/unrecognized token identifier
36648
37088
  *
@@ -36896,8 +37336,11 @@ const tokens = createTokenRegistry();
36896
37336
  * from `to` (explicit address string or adapter), and normalize the human‑readable
36897
37337
  * `amount` into token units.
36898
37338
  *
36899
- * For known token aliases, uses dedicated transfer actions
36900
- * 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
36901
37344
  * action and fetches decimals dynamically from the contract.
36902
37345
  *
36903
37346
  * @param params - The send parameters including source context, destination, amount and token.
@@ -36990,6 +37433,21 @@ const tokens = createTokenRegistry();
36990
37433
  preparedSendRequest = await fromAdapter.prepareAction('native.transfer', requestParams, operationContext);
36991
37434
  break;
36992
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
+ }
36993
37451
  default:
36994
37452
  {
36995
37453
  throw createUnsupportedTokenError(String(token), fromChain.name);
@@ -37016,12 +37474,13 @@ const tokens = createTokenRegistry();
37016
37474
  * Fully prepares and executes the send transaction to transfer the specified amount and
37017
37475
  * token to the specified destination.
37018
37476
  *
37019
- * For known token aliases ('USDC', 'USDT', 'NATIVE'), uses optimized dedicated actions.
37020
- * For custom token addresses, uses the generic token.transfer action with dynamically
37021
- * 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.
37022
37481
  *
37023
37482
  * @param params - The send parameters: source context, destination (address or adapter),
37024
- * 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').
37025
37484
  * @returns A BridgeStep object with transaction details including hash, status, and explorer URL.
37026
37485
  * @throws KitError INPUT_VALIDATION_FAILED if parameters are invalid.
37027
37486
  * @throws KitError INPUT_UNSUPPORTED_TOKEN if the token is invalid for the chain.
@@ -37039,6 +37498,17 @@ const tokens = createTokenRegistry();
37039
37498
  * console.log('Transaction hash:', step.txHash)
37040
37499
  * ```
37041
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
+ *
37042
37512
  * @example Sending a custom token (DAI) to an explicit address
37043
37513
  * ```typescript
37044
37514
  * const step = await send({
@@ -37080,21 +37550,22 @@ const tokens = createTokenRegistry();
37080
37550
  };
37081
37551
 
37082
37552
  /**
37083
- * 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.
37084
37554
  *
37085
37555
  * Prepare the send operation (including validating inputs and resolving the recipient when
37086
37556
  * provided as an adapter) and return an {@link EstimatedGas} object. This function does
37087
37557
  * not submit any transaction.
37088
37558
  *
37089
37559
  * @remarks
37090
- * - 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.
37091
37562
  * - When `to` is an {@link Adapter}, the recipient address is derived from the adapter.
37092
37563
  * - Rejects transfers where the resolved `to` equals the source `from` address.
37093
37564
  * - Interprets `amount` as a human-readable decimal string (USDC and USDT scaled to 6 decimals,
37094
37565
  * native EVM value typically scaled to 18 decimals).
37095
37566
  *
37096
37567
  * @param params - The send parameters: source context, destination (address or adapter),
37097
- * 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').
37098
37569
  * @returns The estimated gas information including `gas`, `gasPrice`, and total `fee`.
37099
37570
  * @throws Error If parameters are invalid.
37100
37571
  * @throws Error If the token is unsupported or the send cannot be prepared.
@@ -37801,7 +38272,7 @@ async function deposit$2(context, params) {
37801
38272
  }
37802
38273
 
37803
38274
  var name = "@circle-fin/unified-balance-kit";
37804
- var version = "1.2.0";
38275
+ var version = "1.2.2";
37805
38276
  var pkg = {
37806
38277
  name: name,
37807
38278
  version: version};