@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.mjs CHANGED
@@ -22,6 +22,7 @@ import { parseUnits as parseUnits$1, formatUnits as formatUnits$1 } from '@ether
22
22
  import { hexlify, hexZeroPad } from '@ethersproject/bytes';
23
23
  import { getAddress } from '@ethersproject/address';
24
24
  import bs58 from 'bs58';
25
+ import '@ethersproject/abi';
25
26
  import { PublicKey } from '@solana/web3.js';
26
27
  import 'bn.js';
27
28
  import '@coral-xyz/anchor';
@@ -3551,7 +3552,7 @@ class KitError extends Error {
3551
3552
  *
3552
3553
  * Error code ranges:
3553
3554
  * - 1100-1105: INPUT errors — invalid inputs, unsupported configurations
3554
- * - 8100-8104: SERVICE errors — retryable backend/provider failures
3555
+ * - 8100-8105: SERVICE errors — retryable backend/provider failures
3555
3556
  *
3556
3557
  * @example
3557
3558
  * ```typescript
@@ -3627,6 +3628,11 @@ class KitError extends Error {
3627
3628
  code: 8104,
3628
3629
  name: 'EARN_PAUSED',
3629
3630
  type: 'SERVICE'
3631
+ },
3632
+ /** Position PnL is still reconciling and can be retried. */ POSITION_PNL_PENDING: {
3633
+ code: 8105,
3634
+ name: 'EARN_POSITION_PNL_PENDING',
3635
+ type: 'SERVICE'
3630
3636
  }
3631
3637
  };
3632
3638
 
@@ -3650,11 +3656,11 @@ function getOptionalString(value) {
3650
3656
  * - vault-not-found, unsupported-chain, unsupported-vault,
3651
3657
  * signature-rejected, invalid-id, invalid-batch-size, position-not-registered,
3652
3658
  * withdrawal-max-exceeded, insufficient-balance, bridge prepare idempotency
3653
- * conflicts
3659
+ * conflicts, invalid vault-asset amount precision
3654
3660
  *
3655
3661
  * SERVICE errors (RETRYABLE) — try again later:
3656
3662
  * - signing-failed, provider-error, rewards-fetch-failed,
3657
- * internal-error, vault-refresh-busy, off-chain-paused,
3663
+ * internal-error, vault-refresh-busy, off-chain-paused, position-PnL-pending,
3658
3664
  * bridge failures/status lookup failures
3659
3665
  *
3660
3666
  * Unrecognized codes fall through to `parseApiError` for HTTP-status-based
@@ -3834,6 +3840,20 @@ function getOptionalString(value) {
3834
3840
  recoverability: 'RETRYABLE'
3835
3841
  }
3836
3842
  ],
3843
+ [
3844
+ 380415,
3845
+ {
3846
+ errorDef: EarnError.INVALID_INPUT,
3847
+ recoverability: 'FATAL'
3848
+ }
3849
+ ],
3850
+ [
3851
+ 380416,
3852
+ {
3853
+ errorDef: EarnError.POSITION_PNL_PENDING,
3854
+ recoverability: 'RETRYABLE'
3855
+ }
3856
+ ],
3837
3857
  // Bridge (380_5XX)
3838
3858
  [
3839
3859
  380500,
@@ -3869,6 +3889,13 @@ function getOptionalString(value) {
3869
3889
  errorDef: EarnError.PROVIDER_ERROR,
3870
3890
  recoverability: 'RETRYABLE'
3871
3891
  }
3892
+ ],
3893
+ [
3894
+ 380505,
3895
+ {
3896
+ errorDef: EarnError.PROVIDER_ERROR,
3897
+ recoverability: 'FATAL'
3898
+ }
3872
3899
  ]
3873
3900
  ]);
3874
3901
  /**
@@ -3971,6 +3998,8 @@ function getOptionalString(value) {
3971
3998
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
3972
3999
  Blockchain["Codex"] = "Codex";
3973
4000
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
4001
+ Blockchain["Cronos"] = "Cronos";
4002
+ Blockchain["Cronos_Testnet"] = "Cronos_Testnet";
3974
4003
  Blockchain["Edge"] = "Edge";
3975
4004
  Blockchain["Edge_Testnet"] = "Edge_Testnet";
3976
4005
  Blockchain["Ethereum"] = "Ethereum";
@@ -4053,6 +4082,7 @@ var BridgeChain;
4053
4082
  BridgeChain["Avalanche"] = "Avalanche";
4054
4083
  BridgeChain["Base"] = "Base";
4055
4084
  BridgeChain["Codex"] = "Codex";
4085
+ BridgeChain["Cronos"] = "Cronos";
4056
4086
  BridgeChain["Edge"] = "Edge";
4057
4087
  BridgeChain["Ethereum"] = "Ethereum";
4058
4088
  BridgeChain["HyperEVM"] = "HyperEVM";
@@ -4077,6 +4107,7 @@ var BridgeChain;
4077
4107
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
4078
4108
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
4079
4109
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
4110
+ BridgeChain["Cronos_Testnet"] = "Cronos_Testnet";
4080
4111
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
4081
4112
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
4082
4113
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
@@ -4328,23 +4359,6 @@ var EarnChain;
4328
4359
  }
4329
4360
  });
4330
4361
 
4331
- /**
4332
- * Standard decimal places for tokens.
4333
- *
4334
- * These constants define the decimal precision used by different token types
4335
- * across the SDK. They are used for amount conversions between human-readable
4336
- * format and smallest units (base units).
4337
- *
4338
- * @remarks
4339
- * - Most stablecoins (USDC, EURC, USDT, PYUSD) use 6 decimal places
4340
- * - Some stablecoins (DAI, USDE) and most native tokens (ETH, POL, PLUME) use 18 decimals
4341
- * - The NATIVE alias uses chain-specific decimals which may vary (e.g., SOL uses 9)
4342
- */ /**
4343
- * Standard decimal places for 6-decimal tokens.
4344
- *
4345
- * Used by most stablecoins: USDC, EURC, USDT, PYUSD
4346
- */ const TOKEN_DECIMALS_6 = 6;
4347
-
4348
4362
  /**
4349
4363
  * @packageDocumentation
4350
4364
  * @module SwapTokenRegistry
@@ -5160,6 +5174,96 @@ var EarnChain;
5160
5174
  }
5161
5175
  });
5162
5176
 
5177
+ /**
5178
+ * Cronos Mainnet chain definition
5179
+ * @remarks
5180
+ * This represents the official production network for the Cronos blockchain.
5181
+ * Cronos is an EVM-compatible blockchain.
5182
+ */ const Cronos = defineChain({
5183
+ type: 'evm',
5184
+ chain: Blockchain.Cronos,
5185
+ name: 'Cronos',
5186
+ title: 'Cronos Mainnet',
5187
+ nativeCurrency: {
5188
+ name: 'Cronos',
5189
+ symbol: 'CRO',
5190
+ decimals: 18
5191
+ },
5192
+ chainId: 25,
5193
+ isTestnet: false,
5194
+ explorerUrl: 'https://cronoscan.com/tx/{hash}',
5195
+ rpcEndpoints: [
5196
+ 'https://evm.cronos.org'
5197
+ ],
5198
+ eurcAddress: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
5199
+ usdcAddress: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
5200
+ usdtAddress: null,
5201
+ cctp: {
5202
+ domain: 32,
5203
+ contracts: {
5204
+ v2: {
5205
+ type: 'split',
5206
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
5207
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
5208
+ confirmations: 1,
5209
+ fastConfirmations: 1
5210
+ }
5211
+ },
5212
+ forwarderSupported: {
5213
+ source: false,
5214
+ destination: false
5215
+ }
5216
+ },
5217
+ kitContracts: {
5218
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
5219
+ }
5220
+ });
5221
+
5222
+ /**
5223
+ * Cronos Testnet chain definition
5224
+ * @remarks
5225
+ * This represents the official test network for the Cronos blockchain.
5226
+ * Cronos is an EVM-compatible blockchain.
5227
+ */ const CronosTestnet = defineChain({
5228
+ type: 'evm',
5229
+ chain: Blockchain.Cronos_Testnet,
5230
+ name: 'Cronos Testnet',
5231
+ title: 'Cronos Testnet',
5232
+ nativeCurrency: {
5233
+ name: 'CRO',
5234
+ symbol: 'tCRO',
5235
+ decimals: 18
5236
+ },
5237
+ chainId: 338,
5238
+ isTestnet: true,
5239
+ explorerUrl: 'https://explorer.cronos.org/testnet/tx/{hash}',
5240
+ rpcEndpoints: [
5241
+ 'https://evm-t3.cronos.org'
5242
+ ],
5243
+ eurcAddress: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
5244
+ usdcAddress: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
5245
+ usdtAddress: null,
5246
+ cctp: {
5247
+ domain: 32,
5248
+ contracts: {
5249
+ v2: {
5250
+ type: 'split',
5251
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
5252
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
5253
+ confirmations: 1,
5254
+ fastConfirmations: 1
5255
+ }
5256
+ },
5257
+ forwarderSupported: {
5258
+ source: false,
5259
+ destination: false
5260
+ }
5261
+ },
5262
+ kitContracts: {
5263
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
5264
+ }
5265
+ });
5266
+
5163
5267
  /**
5164
5268
  * Edge Mainnet chain definition
5165
5269
  * @remarks
@@ -7511,6 +7615,8 @@ var Chains = /*#__PURE__*/Object.freeze({
7511
7615
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
7512
7616
  Codex: Codex,
7513
7617
  CodexTestnet: CodexTestnet,
7618
+ Cronos: Cronos,
7619
+ CronosTestnet: CronosTestnet,
7514
7620
  Edge: Edge,
7515
7621
  EdgeTestnet: EdgeTestnet,
7516
7622
  Ethereum: Ethereum,
@@ -9885,6 +9991,7 @@ function parseOrThrow(value, schema, context) {
9885
9991
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
9886
9992
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
9887
9993
  [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
9994
+ [Blockchain.Cronos]: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
9888
9995
  [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
9889
9996
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
9890
9997
  [Blockchain.Hedera]: '0.0.456858',
@@ -9918,6 +10025,7 @@ function parseOrThrow(value, schema, context) {
9918
10025
  [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
9919
10026
  [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
9920
10027
  [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
10028
+ [Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
9921
10029
  [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
9922
10030
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
9923
10031
  [Blockchain.Hedera_Testnet]: '0.0.429274',
@@ -9985,13 +10093,22 @@ function parseOrThrow(value, schema, context) {
9985
10093
  symbol: 'EURC',
9986
10094
  decimals: 6,
9987
10095
  locators: {
10096
+ // =========================================================================
10097
+ // Mainnets
10098
+ // =========================================================================
9988
10099
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
9989
10100
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
10101
+ [Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
9990
10102
  [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
9991
10103
  [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
9992
10104
  [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
10105
+ // =========================================================================
9993
10106
  // Testnets
9994
- [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a'
10107
+ // =========================================================================
10108
+ [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
10109
+ [Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
10110
+ [Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
10111
+ [Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
9995
10112
  }
9996
10113
  };
9997
10114
 
@@ -10201,8 +10318,7 @@ function parseOrThrow(value, schema, context) {
10201
10318
  * cirBTC (Circle Bitcoin) token definition with addresses and metadata.
10202
10319
  *
10203
10320
  * @remarks
10204
- * Built-in cirBTC definition for the TokenRegistry. Currently deployed
10205
- * on Arc Testnet.
10321
+ * Built-in cirBTC definition for the TokenRegistry.
10206
10322
  *
10207
10323
  * @example
10208
10324
  * ```typescript
@@ -10218,7 +10334,15 @@ function parseOrThrow(value, schema, context) {
10218
10334
  symbol: 'cirBTC',
10219
10335
  decimals: 8,
10220
10336
  locators: {
10221
- [Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
10337
+ // =========================================================================
10338
+ // Mainnets
10339
+ // =========================================================================
10340
+ [Blockchain.Ethereum]: '0x72DFB2E44f59C5AD2bAFE84314E5b99a7cd5075E',
10341
+ // =========================================================================
10342
+ // Testnets
10343
+ // =========================================================================
10344
+ [Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
10345
+ [Blockchain.Ethereum_Sepolia]: '0x3a3fe695F684Bf9b9e43CF43C2b895Ea5e392bB3'
10222
10346
  }
10223
10347
  };
10224
10348
 
@@ -11498,11 +11622,11 @@ function resolveOptions(options) {
11498
11622
  void emitAnalyticsLog(buildPayload$1(config, stepEntry?.[1] ?? fallbackEventType, errorDetails, context));
11499
11623
  }
11500
11624
 
11501
- var name$3 = "@circle-fin/bridge-kit";
11502
- var version$4 = "1.11.0";
11503
- var pkg$4 = {
11504
- name: name$3,
11505
- version: version$4};
11625
+ var name$4 = "@circle-fin/bridge-kit";
11626
+ var version$5 = "1.12.0";
11627
+ var pkg$5 = {
11628
+ name: name$4,
11629
+ version: version$5};
11506
11630
 
11507
11631
  const assertCustomFeePolicySymbol$2 = Symbol('assertCustomFeePolicy');
11508
11632
  /**
@@ -13528,7 +13652,7 @@ var TransferSpeed;
13528
13652
  const bridgeKitCaller = {
13529
13653
  type: 'kit',
13530
13654
  name: 'BridgeKit',
13531
- version: pkg$4.version
13655
+ version: pkg$5.version
13532
13656
  };
13533
13657
  // Create default runtime and tokens for invocation context resolution
13534
13658
  const defaults = {
@@ -14198,6 +14322,15 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
14198
14322
  ;
14199
14323
  const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
14200
14324
  ;
14325
+ // Hard execution caps: observed max + ~30% buffer, used as gasLimit overrides on
14326
+ // chains whose eth_estimateGas under-reports (e.g. Cronos EIP-7623 calldata floor).
14327
+ // Kept separate from the fee-estimate averages above.
14328
+ const APPROVE_GAS_LIMIT_EVM = 100_000n // ERC-20 approve observed max ~46k
14329
+ ;
14330
+ const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 300_000n // observed max 226_506 + ~30%
14331
+ ;
14332
+ const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839 + ~30%
14333
+ ;
14201
14334
  /**
14202
14335
  * The minimum finality threshold for CCTPv2 transfers.
14203
14336
  *
@@ -14226,6 +14359,27 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14226
14359
  'Content-Type': 'application/json'
14227
14360
  }
14228
14361
  };
14362
+ /**
14363
+ * Merges caller-provided polling overrides on top of {@link DEFAULT_CONFIG}.
14364
+ *
14365
+ * Headers are merged independently so caller-supplied headers augment the
14366
+ * defaults (such as `Content-Type`) rather than replacing them wholesale.
14367
+ *
14368
+ * @param config - Caller-provided polling configuration overrides
14369
+ * @param internalDefaults - Internal defaults applied before `config` (for example a
14370
+ * reduced `maxRetries` for one-shot requests); `config` still wins on conflict
14371
+ * @returns The effective polling configuration
14372
+ * @internal
14373
+ */ const mergeAttestationConfig = (config, internalDefaults = {})=>({
14374
+ ...DEFAULT_CONFIG$2,
14375
+ ...internalDefaults,
14376
+ ...config,
14377
+ headers: {
14378
+ ...DEFAULT_CONFIG$2.headers,
14379
+ ...internalDefaults.headers,
14380
+ ...config.headers
14381
+ }
14382
+ });
14229
14383
  /**
14230
14384
  * Type guard that verifies if an unknown value matches the AttestationMessage shape
14231
14385
  * and has all required properties.
@@ -14372,10 +14526,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14372
14526
  * ```
14373
14527
  */ const fetchAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14374
14528
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14375
- const effectiveConfig = {
14376
- ...DEFAULT_CONFIG$2,
14377
- ...config
14378
- };
14529
+ const effectiveConfig = mergeAttestationConfig(config);
14379
14530
  return await pollApiGet(url, isAttestationResponse, effectiveConfig);
14380
14531
  };
14381
14532
  /**
@@ -14418,11 +14569,9 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14418
14569
  */ const fetchAttestationWithoutStatusCheck = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14419
14570
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14420
14571
  // Use minimal retries since we're just fetching existing data
14421
- const effectiveConfig = {
14422
- ...DEFAULT_CONFIG$2,
14423
- maxRetries: 3,
14424
- ...config
14425
- };
14572
+ const effectiveConfig = mergeAttestationConfig(config, {
14573
+ maxRetries: 3
14574
+ });
14426
14575
  return await pollApiGet(url, isAttestationResponseWithoutStatusCheck, effectiveConfig);
14427
14576
  };
14428
14577
  /**
@@ -14482,10 +14631,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14482
14631
  * ```
14483
14632
  */ const fetchReAttestedAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14484
14633
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14485
- const effectiveConfig = {
14486
- ...DEFAULT_CONFIG$2,
14487
- ...config
14488
- };
14634
+ const effectiveConfig = mergeAttestationConfig(config);
14489
14635
  return await pollApiGet(url, isReAttestedAttestationResponse, effectiveConfig);
14490
14636
  };
14491
14637
  /**
@@ -14551,14 +14697,139 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
14551
14697
  */ const requestReAttestation = async (nonce, isTestnet, config = {})=>{
14552
14698
  const url = buildReAttestUrl(nonce, isTestnet);
14553
14699
  // Use minimal retries since we're just submitting a request, not polling for state
14554
- const effectiveConfig = {
14555
- ...DEFAULT_CONFIG$2,
14556
- maxRetries: 3,
14557
- ...config
14558
- };
14700
+ const effectiveConfig = mergeAttestationConfig(config, {
14701
+ maxRetries: 3
14702
+ });
14559
14703
  return await pollApiPost(url, {}, isReAttestationResponse, effectiveConfig);
14560
14704
  };
14561
14705
 
14706
+ /**
14707
+ * Type guard that checks if the relayer has confirmed the mint transaction.
14708
+ *
14709
+ * This function validates that:
14710
+ * 1. The response has valid AttestationResponse structure
14711
+ * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
14712
+ *
14713
+ * If forwardState is 'FAILED', throws a non-retryable KitError.
14714
+ * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
14715
+ *
14716
+ * @param obj - The value to check, typically a parsed JSON response
14717
+ * @returns True if the relayer has confirmed the mint
14718
+ * @throws {KitError} With FATAL recoverability if structure is invalid
14719
+ * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
14720
+ * @throws {KitError} With RETRYABLE recoverability if still pending
14721
+ * @internal
14722
+ */ const isRelayerMintConfirmed = (obj)=>{
14723
+ // First check if the structure is valid
14724
+ if (!hasValidAttestationStructure(obj)) {
14725
+ throw new KitError({
14726
+ ...InputError.VALIDATION_FAILED,
14727
+ recoverability: 'FATAL',
14728
+ message: 'Invalid attestation response structure from IRIS API.'
14729
+ });
14730
+ }
14731
+ // Find the first message (typically there's only one)
14732
+ const message = obj.messages[0];
14733
+ if (!message) {
14734
+ throw new KitError({
14735
+ ...InputError.VALIDATION_FAILED,
14736
+ recoverability: 'FATAL',
14737
+ message: 'No attestation messages found in IRIS API response.'
14738
+ });
14739
+ }
14740
+ // Check for FAILED state - this is a permanent failure
14741
+ if (message.forwardState === 'FAILED') {
14742
+ throw new KitError({
14743
+ ...NetworkError.RELAYER_FORWARD_FAILED,
14744
+ recoverability: 'RESUMABLE',
14745
+ message: 'Circle relayer failed to forward the mint transaction. The mint may still have succeeded if another party submitted it. Check the recipient wallet balance before retrying. If the mint did not occur, you can manually submit it using the attestation data in the error cause.',
14746
+ cause: {
14747
+ trace: {
14748
+ eventNonce: message.eventNonce,
14749
+ attestation: message.attestation,
14750
+ message: message.message
14751
+ }
14752
+ }
14753
+ });
14754
+ }
14755
+ // Check if mint is confirmed (or complete) with a valid transaction hash
14756
+ // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
14757
+ if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
14758
+ return true;
14759
+ }
14760
+ // Still pending or not yet processed - throw RETRYABLE error to continue polling
14761
+ throw new KitError({
14762
+ ...NetworkError.RELAYER_PENDING,
14763
+ recoverability: 'RETRYABLE',
14764
+ message: 'Relayer mint not ready. Waiting for confirmation.'
14765
+ });
14766
+ };
14767
+ /**
14768
+ * Polls the attestation API until the relayer's mint transaction is confirmed.
14769
+ *
14770
+ * This function is used when `useForwarder` is enabled. Instead of the user
14771
+ * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
14772
+ * This function polls until the relayer has submitted and confirmed the mint transaction.
14773
+ *
14774
+ * @remarks
14775
+ * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
14776
+ * - Throws immediately if `forwardState` is 'FAILED'
14777
+ * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
14778
+ * - Returns the attestation message with `forwardTxHash` populated
14779
+ *
14780
+ * @param sourceDomainId - The CCTP domain ID of the source chain
14781
+ * @param transactionHash - The transaction hash of the burn operation
14782
+ * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
14783
+ * @param config - Optional configuration overrides for polling behavior
14784
+ * @returns The attestation message with confirmed forwardTxHash
14785
+ * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
14786
+ * @throws {KitError} If timeout is reached while still pending
14787
+ *
14788
+ * @example
14789
+ * ```typescript
14790
+ * const attestation = await fetchRelayerMint(0, '0xabc...', false)
14791
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
14792
+ * ```
14793
+ */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
14794
+ const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
14795
+ const effectiveConfig = mergeAttestationConfig(config);
14796
+ let response;
14797
+ try {
14798
+ response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
14799
+ } catch (error) {
14800
+ // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
14801
+ if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
14802
+ throw new KitError({
14803
+ ...NetworkError.RELAYER_FORWARD_FAILED,
14804
+ recoverability: error.recoverability,
14805
+ message: error.message,
14806
+ cause: {
14807
+ ...error.cause,
14808
+ trace: {
14809
+ ...error.cause?.trace,
14810
+ burnTxHash: transactionHash
14811
+ }
14812
+ }
14813
+ });
14814
+ }
14815
+ throw error;
14816
+ }
14817
+ // Return the first message (which should have forwardTxHash)
14818
+ // Note: This check is needed for TypeScript type safety even though
14819
+ // isRelayerMintConfirmed validates messages[0] exists. The type guard
14820
+ // narrows the type at the call site, but TypeScript can't infer that
14821
+ // the array still has elements after pollApiGet returns.
14822
+ const message = response.messages[0];
14823
+ if (!message) {
14824
+ throw new KitError({
14825
+ ...InputError.VALIDATION_FAILED,
14826
+ recoverability: 'FATAL',
14827
+ message: 'No attestation messages found in response after polling.'
14828
+ });
14829
+ }
14830
+ return message;
14831
+ };
14832
+
14562
14833
  const assertCCTPv2WalletContextSymbol = Symbol('assertCCTPv2WalletContext');
14563
14834
  /**
14564
14835
  * Asserts that the provided parameters match the CCTPv2 wallet context interface.
@@ -15433,6 +15704,8 @@ function hasPendingState(analysis, result) {
15433
15704
  * - `adapter`: The adapter that will execute the transaction
15434
15705
  * - `confirmations`: The number of confirmations to wait for (defaults to 1)
15435
15706
  * - `timeout`: The timeout for the request in milliseconds
15707
+ * - `gasLimit`: Optional explicit gas limit (number) forwarded to EVM execute,
15708
+ * bypassing `eth_estimateGas`; ignored for non-EVM requests
15436
15709
  * @returns The bridge step with the transaction details and explorer URL
15437
15710
  * @throws If the transaction execution fails
15438
15711
  *
@@ -15447,7 +15720,7 @@ function hasPendingState(analysis, result) {
15447
15720
  * })
15448
15721
  * console.log('Transaction hash:', step.txHash)
15449
15722
  * ```
15450
- */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout }) {
15723
+ */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasLimit }) {
15451
15724
  const step = {
15452
15725
  name,
15453
15726
  state: 'pending'
@@ -15460,7 +15733,9 @@ function hasPendingState(analysis, result) {
15460
15733
  step.state = 'noop';
15461
15734
  return step;
15462
15735
  }
15463
- const txHash = await request.execute();
15736
+ const txHash = request.type === 'evm' && gasLimit !== undefined ? await request.execute({
15737
+ gasLimit
15738
+ }) : await request.execute();
15464
15739
  step.txHash = txHash;
15465
15740
  const retryOptions = {
15466
15741
  isRetryable: (err)=>isRetryableError$1(parseBlockchainError(err, {
@@ -15532,7 +15807,8 @@ function hasPendingState(analysis, result) {
15532
15807
  name: 'approve',
15533
15808
  adapter: params.source.adapter,
15534
15809
  chain: params.source.chain,
15535
- request: await provider.approve(params.source, approvalAmount)
15810
+ request: await provider.approve(params.source, approvalAmount),
15811
+ gasLimit: Number(APPROVE_GAS_LIMIT_EVM)
15536
15812
  });
15537
15813
  }
15538
15814
 
@@ -15559,7 +15835,8 @@ function hasPendingState(analysis, result) {
15559
15835
  name: 'burn',
15560
15836
  adapter: params.source.adapter,
15561
15837
  chain: params.source.chain,
15562
- request: await provider.burn(params)
15838
+ request: await provider.burn(params),
15839
+ gasLimit: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
15563
15840
  });
15564
15841
  }
15565
15842
 
@@ -15645,11 +15922,18 @@ function hasPendingState(analysis, result) {
15645
15922
  */ async function bridgeMint({ params, provider }, attestation) {
15646
15923
  // Validate attestation message matches transfer params
15647
15924
  await assertCCTPv2AttestationParams(attestation, params);
15925
+ const mintRequest = await provider.mint(params.source, params.destination, attestation);
15648
15926
  const step = await executePreparedChainRequest({
15649
15927
  name: 'mint',
15650
15928
  adapter: params.destination.adapter,
15651
15929
  chain: params.destination.chain,
15652
- request: await provider.mint(params.source, params.destination, attestation)
15930
+ request: mintRequest,
15931
+ // Some chains (e.g. Cronos) enforce an EIP-7623 calldata gas floor that
15932
+ // eth_estimateGas does not account for, returning a below-floor value
15933
+ // without reverting. Pinning to a value above the observed execution max
15934
+ // (310_839) bypasses re-estimation and guarantees we clear both the floor
15935
+ // and the actual execution cost.
15936
+ gasLimit: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
15653
15937
  });
15654
15938
  // Add forwarded: false for non-relayer mints
15655
15939
  return {
@@ -15687,136 +15971,6 @@ const mockAttestationMessage = {
15687
15971
  delayReason: null
15688
15972
  };
15689
15973
 
15690
- /**
15691
- * Type guard that checks if the relayer has confirmed the mint transaction.
15692
- *
15693
- * This function validates that:
15694
- * 1. The response has valid AttestationResponse structure
15695
- * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
15696
- *
15697
- * If forwardState is 'FAILED', throws a non-retryable KitError.
15698
- * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
15699
- *
15700
- * @param obj - The value to check, typically a parsed JSON response
15701
- * @returns True if the relayer has confirmed the mint
15702
- * @throws {KitError} With FATAL recoverability if structure is invalid
15703
- * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
15704
- * @throws {KitError} With RETRYABLE recoverability if still pending
15705
- * @internal
15706
- */ const isRelayerMintConfirmed = (obj)=>{
15707
- // First check if the structure is valid
15708
- if (!hasValidAttestationStructure(obj)) {
15709
- throw new KitError({
15710
- ...InputError.VALIDATION_FAILED,
15711
- recoverability: 'FATAL',
15712
- message: 'Invalid attestation response structure from IRIS API.'
15713
- });
15714
- }
15715
- // Find the first message (typically there's only one)
15716
- const message = obj.messages[0];
15717
- if (!message) {
15718
- throw new KitError({
15719
- ...InputError.VALIDATION_FAILED,
15720
- recoverability: 'FATAL',
15721
- message: 'No attestation messages found in IRIS API response.'
15722
- });
15723
- }
15724
- // Check for FAILED state - this is a permanent failure
15725
- if (message.forwardState === 'FAILED') {
15726
- throw new KitError({
15727
- ...NetworkError.RELAYER_FORWARD_FAILED,
15728
- recoverability: 'RESUMABLE',
15729
- 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.',
15730
- cause: {
15731
- trace: {
15732
- eventNonce: message.eventNonce,
15733
- attestation: message.attestation,
15734
- message: message.message
15735
- }
15736
- }
15737
- });
15738
- }
15739
- // Check if mint is confirmed (or complete) with a valid transaction hash
15740
- // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
15741
- if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
15742
- return true;
15743
- }
15744
- // Still pending or not yet processed - throw RETRYABLE error to continue polling
15745
- throw new KitError({
15746
- ...NetworkError.RELAYER_PENDING,
15747
- recoverability: 'RETRYABLE',
15748
- message: 'Relayer mint not ready. Waiting for confirmation.'
15749
- });
15750
- };
15751
- /**
15752
- * Polls the attestation API until the relayer's mint transaction is confirmed.
15753
- *
15754
- * This function is used when `useForwarder` is enabled. Instead of the user
15755
- * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
15756
- * This function polls until the relayer has submitted and confirmed the mint transaction.
15757
- *
15758
- * @remarks
15759
- * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
15760
- * - Throws immediately if `forwardState` is 'FAILED'
15761
- * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
15762
- * - Returns the attestation message with `forwardTxHash` populated
15763
- *
15764
- * @param sourceDomainId - The CCTP domain ID of the source chain
15765
- * @param transactionHash - The transaction hash of the burn operation
15766
- * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
15767
- * @param config - Optional configuration overrides for polling behavior
15768
- * @returns The attestation message with confirmed forwardTxHash
15769
- * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
15770
- * @throws {KitError} If timeout is reached while still pending
15771
- *
15772
- * @example
15773
- * ```typescript
15774
- * const attestation = await fetchRelayerMint(0, '0xabc...', false)
15775
- * console.log('Relayer mint tx:', attestation.forwardTxHash)
15776
- * ```
15777
- */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
15778
- const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
15779
- const effectiveConfig = {
15780
- ...DEFAULT_CONFIG$2,
15781
- ...config
15782
- };
15783
- let response;
15784
- try {
15785
- response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
15786
- } catch (error) {
15787
- // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
15788
- if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
15789
- throw new KitError({
15790
- ...NetworkError.RELAYER_FORWARD_FAILED,
15791
- recoverability: error.recoverability,
15792
- message: error.message,
15793
- cause: {
15794
- ...error.cause,
15795
- trace: {
15796
- ...error.cause?.trace,
15797
- burnTxHash: transactionHash
15798
- }
15799
- }
15800
- });
15801
- }
15802
- throw error;
15803
- }
15804
- // Return the first message (which should have forwardTxHash)
15805
- // Note: This check is needed for TypeScript type safety even though
15806
- // isRelayerMintConfirmed validates messages[0] exists. The type guard
15807
- // narrows the type at the call site, but TypeScript can't infer that
15808
- // the array still has elements after pollApiGet returns.
15809
- const message = response.messages[0];
15810
- if (!message) {
15811
- throw new KitError({
15812
- ...InputError.VALIDATION_FAILED,
15813
- recoverability: 'FATAL',
15814
- message: 'No attestation messages found in response after polling.'
15815
- });
15816
- }
15817
- return message;
15818
- };
15819
-
15820
15974
  /**
15821
15975
  * Executes the mint step for forwarding mode where Circle's relayer handles the mint.
15822
15976
  *
@@ -15830,6 +15984,7 @@ const mockAttestationMessage = {
15830
15984
  * immediately after the API confirms the mint.
15831
15985
  *
15832
15986
  * @param params - The bridge parameters containing source, destination, amount and config
15987
+ * @param provider - The CCTP v2 bridging provider
15833
15988
  * @param context - The step context containing burnTxHash from the burn step
15834
15989
  * @returns Promise resolving to the bridge step with transaction details
15835
15990
  * @throws {KitError} If burnTxHash is not available in context
@@ -15838,10 +15993,13 @@ const mockAttestationMessage = {
15838
15993
  *
15839
15994
  * @example
15840
15995
  * ```typescript
15841
- * const step = await bridgeRelayerMint(params, { burnTxHash: '0x...' })
15996
+ * const step = await bridgeRelayerMint(
15997
+ * { params, provider },
15998
+ * { burnTxHash: '0x...' }
15999
+ * )
15842
16000
  * console.log('Relayer mint tx:', step.txHash)
15843
16001
  * ```
15844
- */ async function bridgeRelayerMint(params, context) {
16002
+ */ async function bridgeRelayerMint({ params, provider }, context) {
15845
16003
  const step = {
15846
16004
  name: 'mint',
15847
16005
  state: 'pending',
@@ -15857,7 +16015,7 @@ const mockAttestationMessage = {
15857
16015
  }
15858
16016
  try {
15859
16017
  // Poll attestation API until relayer confirms the mint
15860
- const attestation = await fetchRelayerMint(params.source.chain.cctp.domain, context.burnTxHash, params.source.chain.isTestnet ?? false);
16018
+ const attestation = await provider.fetchRelayerMint(params.source, context.burnTxHash);
15861
16019
  // Extract the relayer's mint transaction hash
15862
16020
  const forwardTxHash = attestation.forwardTxHash;
15863
16021
  if (!forwardTxHash) {
@@ -15951,7 +16109,7 @@ const mockAttestationMessage = {
15951
16109
  name: 'mint',
15952
16110
  // bridgeRelayerMint validates context.burnTxHash internally with a KitError
15953
16111
  // bridgeMint requires attestationData which is validated here
15954
- executor: useForwarder ? async (params, _provider, context)=>{
16112
+ executor: useForwarder ? async (params, provider, context)=>{
15955
16113
  if (!context) {
15956
16114
  throw new KitError({
15957
16115
  ...InputError.VALIDATION_FAILED,
@@ -15959,7 +16117,10 @@ const mockAttestationMessage = {
15959
16117
  message: 'Step context is required for relayer mint'
15960
16118
  });
15961
16119
  }
15962
- return bridgeRelayerMint(params, context);
16120
+ return bridgeRelayerMint({
16121
+ params,
16122
+ provider
16123
+ }, context);
15963
16124
  } : async (params, provider, context)=>{
15964
16125
  if (!context?.attestationData) {
15965
16126
  throw new KitError({
@@ -16290,16 +16451,16 @@ const mockAttestationMessage = {
16290
16451
  return step;
16291
16452
  }
16292
16453
 
16293
- var version$3 = "1.8.4";
16294
- var pkg$3 = {
16295
- version: version$3};
16454
+ var version$4 = "1.9.0";
16455
+ var pkg$4 = {
16456
+ version: version$4};
16296
16457
 
16297
16458
  /**
16298
16459
  * Provider caller component for bridge operations.
16299
16460
  */ const BRIDGE_CALLER = {
16300
16461
  type: 'provider',
16301
16462
  name: 'CCTPV2BridgingProvider.bridge',
16302
- version: pkg$3.version
16463
+ version: pkg$4.version
16303
16464
  };
16304
16465
  /**
16305
16466
  * Resolve invocation context for bridge operations.
@@ -16672,7 +16833,7 @@ var pkg$3 = {
16672
16833
  */ const RETRY_CALLER = {
16673
16834
  type: 'provider',
16674
16835
  name: 'CCTPV2BridgingProvider.retry',
16675
- version: pkg$3.version
16836
+ version: pkg$4.version
16676
16837
  };
16677
16838
  /**
16678
16839
  * Resolve invocation context for retry operations.
@@ -16996,6 +17157,39 @@ var pkg$3 = {
16996
17157
  }
16997
17158
  }
16998
17159
 
17160
+ function isPlainObject(value) {
17161
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
17162
+ return false;
17163
+ }
17164
+ const prototype = Object.getPrototypeOf(value);
17165
+ return prototype === Object.prototype || prototype === null;
17166
+ }
17167
+ function assertHeadersConfig(headers, field) {
17168
+ if (headers === undefined) {
17169
+ return;
17170
+ }
17171
+ if (!isPlainObject(headers)) {
17172
+ throw createValidationFailedError$1(field, headers, `${field} must be a plain object with string header values when provided`);
17173
+ }
17174
+ for (const [name, value] of Object.entries(headers)){
17175
+ if (typeof value !== 'string') {
17176
+ throw createValidationFailedError$1(`${field}.${name}`, value, 'header values must be strings');
17177
+ }
17178
+ }
17179
+ }
17180
+ function assertCCTPV2Config(config) {
17181
+ if (!isPlainObject(config)) {
17182
+ throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
17183
+ }
17184
+ assertHeadersConfig(config['headers'], 'config.headers');
17185
+ const attestation = config['attestation'];
17186
+ if (attestation !== undefined) {
17187
+ if (!isPlainObject(attestation)) {
17188
+ throw createValidationFailedError$1('config.attestation', attestation, 'config.attestation must be a plain object when provided');
17189
+ }
17190
+ assertHeadersConfig(attestation['headers'], 'config.attestation.headers');
17191
+ }
17192
+ }
16999
17193
  /**
17000
17194
  * All chains that are supported by the CCTP v2 provider.
17001
17195
  *
@@ -17064,9 +17258,38 @@ var pkg$3 = {
17064
17258
  * @param config - Optional configuration overrides for the provider
17065
17259
  */ constructor(config = {}){
17066
17260
  super();
17261
+ assertCCTPV2Config(config);
17067
17262
  this.config = config;
17068
17263
  }
17069
17264
  /**
17265
+ * Resolves the effective polling configuration for an attestation request.
17266
+ *
17267
+ * Precedence (lowest to highest): provider `config.attestation`, then the
17268
+ * per-call `config`. Headers merge independently across
17269
+ * `config.attestation.headers`, the provider-level `config.headers`, and any
17270
+ * per-call `config.headers`, so a more specific header augments rather than
17271
+ * replaces the broader ones. The `headers` key is omitted entirely when no
17272
+ * headers are configured, leaving the attestation fetchers' defaults intact.
17273
+ *
17274
+ * @param config - Optional per-call polling configuration overrides
17275
+ * @returns The merged polling configuration passed to the attestation fetchers
17276
+ */ resolveAttestationConfig(config) {
17277
+ const headers = {
17278
+ ...this.config?.attestation?.headers,
17279
+ ...this.config?.headers,
17280
+ ...config?.headers
17281
+ };
17282
+ // Polling fields follow normal precedence; headers are merged separately
17283
+ // below so narrower config layers augment rather than replace broader ones.
17284
+ return {
17285
+ ...this.config?.attestation,
17286
+ ...config,
17287
+ ...Object.keys(headers).length > 0 ? {
17288
+ headers
17289
+ } : {}
17290
+ };
17291
+ }
17292
+ /**
17070
17293
  * Execute a cross-chain USDC bridge operation using the CCTP v2 protocol.
17071
17294
  *
17072
17295
  * This method orchestrates the complete CCTP v2 bridge flow including validation,
@@ -17492,11 +17715,7 @@ var pkg$3 = {
17492
17715
  */ async fetchAttestation(source, transactionHash, config) {
17493
17716
  assertCCTPv2WalletContext(source);
17494
17717
  try {
17495
- // Merge configs: defaults <- global config <- per-call config
17496
- const effectiveConfig = {
17497
- ...this.config?.attestation,
17498
- ...config
17499
- };
17718
+ const effectiveConfig = this.resolveAttestationConfig(config);
17500
17719
  const response = await fetchAttestation(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17501
17720
  const message = response.messages[0];
17502
17721
  if (!message) {
@@ -17513,6 +17732,49 @@ var pkg$3 = {
17513
17732
  }
17514
17733
  }
17515
17734
  /**
17735
+ * Polls attestation data until Circle's relayer mint transaction is confirmed.
17736
+ *
17737
+ * This method is used by forwarded transfers. It polls the same Iris
17738
+ * attestation endpoint as {@link CCTPV2BridgingProvider.fetchAttestation},
17739
+ * but waits for a completed relayer forward state and returns the attestation
17740
+ * message containing `forwardTxHash`.
17741
+ *
17742
+ * @typeParam TFromAdapterCapabilities - The type representing the capabilities of the source adapter
17743
+ * @param source - The source wallet context containing the chain definition and wallet address
17744
+ * @param transactionHash - The transaction hash of the burn operation
17745
+ * @param config - Optional polling configuration overrides for timeout, retries, delay, and headers
17746
+ * @returns A promise that resolves to the attestation message with `forwardTxHash`
17747
+ * @throws KitError If the relayer forward fails, the response is invalid, or polling times out
17748
+ *
17749
+ * @example
17750
+ * ```typescript
17751
+ * import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2'
17752
+ * import { Chains } from '@core/chains'
17753
+ *
17754
+ * const provider = new CCTPV2BridgingProvider({
17755
+ * headers: { 'X-Partner-UUID': '00000000-0000-0000-0000-000000000000' },
17756
+ * })
17757
+ *
17758
+ * const attestation = await provider.fetchRelayerMint(
17759
+ * {
17760
+ * adapter,
17761
+ * chain: Chains.EthereumSepolia,
17762
+ * address: '0x1234...',
17763
+ * },
17764
+ * '0xabc123...',
17765
+ * )
17766
+ *
17767
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
17768
+ * ```
17769
+ */ async fetchRelayerMint(source, transactionHash, config) {
17770
+ assertCCTPv2WalletContext(source);
17771
+ if (typeof transactionHash !== 'string' || transactionHash.trim() === '') {
17772
+ throw createValidationFailedError$1('transactionHash', transactionHash, 'transactionHash must be a non-empty string');
17773
+ }
17774
+ const effectiveConfig = this.resolveAttestationConfig(config);
17775
+ return await fetchRelayerMint(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17776
+ }
17777
+ /**
17516
17778
  * Requests a fresh attestation for an expired attestation.
17517
17779
  *
17518
17780
  * This method is used when the original attestation has expired before the mint
@@ -17566,11 +17828,7 @@ var pkg$3 = {
17566
17828
  throw new Error('Failed to re-attest: Invalid transaction hash');
17567
17829
  }
17568
17830
  try {
17569
- // Merge configs: defaults <- global config <- per-call config
17570
- const effectiveConfig = {
17571
- ...this.config?.attestation,
17572
- ...config
17573
- };
17831
+ const effectiveConfig = this.resolveAttestationConfig(config);
17574
17832
  // Step 1: Get existing attestation data to extract nonce
17575
17833
  const existingAttestation = await fetchAttestationWithoutStatusCheck(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
17576
17834
  const nonce = existingAttestation.messages[0]?.eventNonce;
@@ -17855,8 +18113,13 @@ var pkg$3 = {
17855
18113
  /**
17856
18114
  * The default providers that will be used in addition to the providers provided
17857
18115
  * to the BridgeKit constructor.
17858
- */ const getDefaultProviders$3 = ()=>[
17859
- new CCTPV2BridgingProvider()
18116
+ *
18117
+ * @param config - Optional configuration forwarded to the default providers
18118
+ * @returns The default bridging providers
18119
+ */ const getDefaultProviders$3 = (config = {})=>[
18120
+ new CCTPV2BridgingProvider(config.headers ? {
18121
+ headers: config.headers
18122
+ } : {})
17860
18123
  ];
17861
18124
 
17862
18125
  /**
@@ -17967,7 +18230,7 @@ var pkg$3 = {
17967
18230
  ]
17968
18231
  ];
17969
18232
 
17970
- /** SDK name used in telemetry payloads. */ const SDK_NAME$2 = resolveKitSdkName(pkg$4.name);
18233
+ /** SDK name used in telemetry payloads. */ const SDK_NAME$2 = resolveKitSdkName(pkg$5.name);
17971
18234
  /**
17972
18235
  * Pick the most-relevant `txHash` to attach to an error telemetry payload.
17973
18236
  *
@@ -18008,7 +18271,7 @@ var pkg$3 = {
18008
18271
  */ const BRIDGE_KIT_CALLER = {
18009
18272
  type: 'kit',
18010
18273
  name: 'BridgeKit',
18011
- version: pkg$4.version
18274
+ version: pkg$5.version
18012
18275
  };
18013
18276
  /**
18014
18277
  * Merge BridgeKit's caller into the invocation metadata for retry operations.
@@ -18099,7 +18362,9 @@ var pkg$3 = {
18099
18362
  * ```
18100
18363
  */ constructor(config = {}){
18101
18364
  // Handle provider configuration
18102
- const defaultProviders = getDefaultProviders$3();
18365
+ const defaultProviders = getDefaultProviders$3(config.headers ? {
18366
+ headers: config.headers
18367
+ } : {});
18103
18368
  this.providers = [
18104
18369
  ...defaultProviders,
18105
18370
  ...config.providers ?? []
@@ -18108,7 +18373,7 @@ var pkg$3 = {
18108
18373
  this.disableErrorReporting = config.disableErrorReporting === true;
18109
18374
  this.telemetryConfig = {
18110
18375
  sdkName: SDK_NAME$2,
18111
- sdkVersion: pkg$4.version,
18376
+ sdkVersion: pkg$5.version,
18112
18377
  disabled: this.disableErrorReporting
18113
18378
  };
18114
18379
  for (const provider of this.providers){
@@ -18630,7 +18895,7 @@ var pkg$3 = {
18630
18895
  }
18631
18896
 
18632
18897
  // Auto-register this kit for user agent tracking
18633
- registerKit(`${pkg$4.name}/${pkg$4.version}`);
18898
+ registerKit(`${pkg$5.name}/${pkg$5.version}`);
18634
18899
 
18635
18900
  /**
18636
18901
  * Create a BridgeKit instance with optional developer fee configuration.
@@ -18679,6 +18944,9 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18679
18944
  const kit = new BridgeKit({
18680
18945
  ...context.disableErrorReporting != null && {
18681
18946
  disableErrorReporting: context.disableErrorReporting
18947
+ },
18948
+ ...context.headers != null && {
18949
+ headers: context.headers
18682
18950
  }
18683
18951
  });
18684
18952
  if (hasBoth) {
@@ -18696,11 +18964,11 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18696
18964
  return kit;
18697
18965
  };
18698
18966
 
18699
- var name$2 = "@circle-fin/swap-kit";
18700
- var version$2 = "1.3.0";
18701
- var pkg$2 = {
18702
- name: name$2,
18703
- version: version$2};
18967
+ var name$3 = "@circle-fin/swap-kit";
18968
+ var version$3 = "1.3.2";
18969
+ var pkg$3 = {
18970
+ name: name$3,
18971
+ version: version$3};
18704
18972
 
18705
18973
  const chainIdentifierField = z.custom((value)=>chainIdentifierSchema.safeParse(value).success, {
18706
18974
  message: 'chain must be a valid chain identifier'
@@ -23632,7 +23900,7 @@ function writeBytes32(buffer, hex, offset) {
23632
23900
  }
23633
23901
  }
23634
23902
 
23635
- const TOKEN_REGISTRY$3 = createTokenRegistry();
23903
+ const TOKEN_REGISTRY$4 = createTokenRegistry();
23636
23904
  /**
23637
23905
  * Resolve token alias to contract address for the given chain.
23638
23906
  *
@@ -23721,7 +23989,7 @@ const TOKEN_REGISTRY$3 = createTokenRegistry();
23721
23989
  }
23722
23990
  });
23723
23991
  }
23724
- const tokenDefinition = TOKEN_REGISTRY$3.get(upperToken);
23992
+ const tokenDefinition = TOKEN_REGISTRY$4.get(upperToken);
23725
23993
  if (tokenDefinition !== undefined) {
23726
23994
  const resolvedAddress = tokenDefinition.locators[chain.chain];
23727
23995
  if (!resolvedAddress) {
@@ -23738,7 +24006,7 @@ const TOKEN_REGISTRY$3 = createTokenRegistry();
23738
24006
  }
23739
24007
  });
23740
24008
  }
23741
- return TOKEN_REGISTRY$3.resolve(upperToken, chain.chain).locator;
24009
+ return TOKEN_REGISTRY$4.resolve(upperToken, chain.chain).locator;
23742
24010
  }
23743
24011
  // Not an alias - return as-is (assumed to be address)
23744
24012
  return token;
@@ -23835,11 +24103,11 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
23835
24103
  if (destinationChain.chain !== sourceChain.chain) {
23836
24104
  return sourceChain;
23837
24105
  }
23838
- const inputIsOk = isOkToken(tokenIn, sourceChain, TOKEN_REGISTRY$3, OK_TOKEN_SYMBOLS$1);
24106
+ const inputIsOk = isOkToken(tokenIn, sourceChain, TOKEN_REGISTRY$4, OK_TOKEN_SYMBOLS$1);
23839
24107
  if (inputIsOk) {
23840
24108
  return sourceChain;
23841
24109
  }
23842
- const outputIsOk = isOkToken(tokenOut, destinationChain, TOKEN_REGISTRY$3, OK_TOKEN_SYMBOLS$1);
24110
+ const outputIsOk = isOkToken(tokenOut, destinationChain, TOKEN_REGISTRY$4, OK_TOKEN_SYMBOLS$1);
23843
24111
  return outputIsOk ? destinationChain : sourceChain;
23844
24112
  }
23845
24113
  /**
@@ -25130,7 +25398,7 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
25130
25398
  }
25131
25399
  }
25132
25400
 
25133
- const TOKEN_REGISTRY$2 = createTokenRegistry();
25401
+ const TOKEN_REGISTRY$3 = createTokenRegistry();
25134
25402
  /**
25135
25403
  * Resolve a token identifier to its canonical symbol for the Stablecoin Service Swap Provider.
25136
25404
  *
@@ -25174,7 +25442,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25174
25442
  return NATIVE_TOKEN;
25175
25443
  }
25176
25444
  // For known registry aliases, return canonical symbol directly.
25177
- if (TOKEN_REGISTRY$2.get(upperToken) !== undefined) {
25445
+ if (TOKEN_REGISTRY$3.get(upperToken) !== undefined) {
25178
25446
  return upperToken;
25179
25447
  }
25180
25448
  // For EVM chains, normalize addresses to lowercase for case-insensitive comparison.
@@ -25184,7 +25452,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25184
25452
  // Native address is given
25185
25453
  if (isNativeEvmAddress(token) || isNativeSolanaAddress(token)) return NATIVE_TOKEN;
25186
25454
  try {
25187
- const { symbol } = TOKEN_REGISTRY$2.resolveByAddress(normalizedToken, chain.chain);
25455
+ const { symbol } = TOKEN_REGISTRY$3.resolveByAddress(normalizedToken, chain.chain);
25188
25456
  if (symbol === undefined) {
25189
25457
  return null;
25190
25458
  }
@@ -25315,7 +25583,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25315
25583
  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.`;
25316
25584
  }
25317
25585
 
25318
- const TOKEN_REGISTRY$1 = createTokenRegistry();
25586
+ const TOKEN_REGISTRY$2 = createTokenRegistry();
25319
25587
  /**
25320
25588
  * Format a raw base-unit amount into a human-readable decimal string.
25321
25589
  *
@@ -25375,12 +25643,12 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
25375
25643
  value,
25376
25644
  token: resolvedSymbol,
25377
25645
  chain,
25378
- tokens: TOKEN_REGISTRY$1
25646
+ tokens: TOKEN_REGISTRY$2
25379
25647
  }),
25380
25648
  token: resolvedSymbol
25381
25649
  };
25382
25650
  }
25383
- const tokenDef = TOKEN_REGISTRY$1.get(resolvedSymbol);
25651
+ const tokenDef = TOKEN_REGISTRY$2.get(resolvedSymbol);
25384
25652
  if (tokenDef) {
25385
25653
  const decimals = resolveTokenDecimals$1(tokenDef, chain.chain);
25386
25654
  return {
@@ -25411,7 +25679,7 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
25411
25679
  * i.e. 1 + 0.2 + 0.1 = 1.3. Use the greater of (local estimate × this multiplier)
25412
25680
  * or the proxy's gasLimit.
25413
25681
  */ const GAS_SAFETY_MULTIPLIER = 1.3;
25414
- const TOKEN_REGISTRY = createTokenRegistry();
25682
+ const TOKEN_REGISTRY$1 = createTokenRegistry();
25415
25683
  /**
25416
25684
  * Enhances a KitError with transaction details (txHash and explorerUrl).
25417
25685
  *
@@ -25487,6 +25755,19 @@ const TOKEN_REGISTRY = createTokenRegistry();
25487
25755
  *
25488
25756
  * @internal
25489
25757
  */ const SUPPORTED_CHAINS$1 = getSwapSupportedChains(Chains);
25758
+ /**
25759
+ * Inter-poll delays (ms) for the same-chain `amountOut` enrichment loop.
25760
+ *
25761
+ * A same-chain swap is already terminal `DONE` once its source transaction
25762
+ * confirms; this short, escalating budget (~5s total across up to four polls)
25763
+ * gives the service a brief window to resolve the output amount before
25764
+ * `swap()` returns. The loop exits the instant `amountOut` is available.
25765
+ */ const SAME_CHAIN_AMOUNT_OUT_POLL_DELAYS_MS = [
25766
+ 1_000,
25767
+ 2_000,
25768
+ 2_000
25769
+ ];
25770
+ const sleep$3 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
25490
25771
  function buildSwapProgress(statusResult) {
25491
25772
  return {
25492
25773
  status: statusResult.status,
@@ -25506,29 +25787,46 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
25506
25787
  }
25507
25788
  };
25508
25789
  }
25509
- try {
25510
- const statusResult = await getSwapStatus$2({
25511
- txHash,
25512
- chain: chain.chain,
25513
- apiKey
25514
- });
25515
- const progress = buildSwapProgress(statusResult);
25516
- if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
25790
+ // A same-chain swap completes atomically in the source transaction, which
25791
+ // has already been confirmed (and receipt-checked for revert) by the time we
25792
+ // get here. Its terminal status is therefore `DONE`. We briefly poll the
25793
+ // status endpoint to enrich `amountOut`, which the service usually resolves
25794
+ // within a few seconds. A non-DONE/erroneous peek never downgrades the
25795
+ // status — once the short budget is exhausted we return `DONE` without
25796
+ // `amountOut`, and the caller can fetch it later via `getSwapStatus`.
25797
+ for(let attempt = 0;; attempt += 1){
25798
+ try {
25799
+ const statusResult = await getSwapStatus$2({
25800
+ txHash,
25801
+ chain: chain.chain,
25802
+ apiKey
25803
+ });
25804
+ if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
25805
+ return {
25806
+ progress: buildSwapProgress(statusResult),
25807
+ amountOut: statusResult.amountOut
25808
+ };
25809
+ }
25810
+ } catch {
25811
+ // Reachability/parse failure during enrichment — non-fatal; the swap
25812
+ // already succeeded on-chain. `getSwapStatus` already retried transient
25813
+ // network errors internally, so further polling is unlikely to help.
25517
25814
  return {
25518
- progress,
25519
- amountOut: statusResult.amountOut
25815
+ progress: {
25816
+ status: 'DONE'
25817
+ }
25520
25818
  };
25521
25819
  }
25522
- return {
25523
- progress
25524
- };
25525
- } catch {
25526
- // Non-fatal — the swap already succeeded on-chain. status stays PENDING.
25527
- return {
25528
- progress: {
25529
- status: 'PENDING'
25530
- }
25531
- };
25820
+ const delayMs = SAME_CHAIN_AMOUNT_OUT_POLL_DELAYS_MS[attempt];
25821
+ if (delayMs === undefined) {
25822
+ // Budget exhausted — terminal `DONE`, `amountOut` not yet available.
25823
+ return {
25824
+ progress: {
25825
+ status: 'DONE'
25826
+ }
25827
+ };
25828
+ }
25829
+ await sleep$3(delayMs);
25532
25830
  }
25533
25831
  }
25534
25832
  /**
@@ -25809,7 +26107,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
25809
26107
  return true;
25810
26108
  }
25811
26109
  // At least one token must be supported for fee collection
25812
- return getSwapOkTokenStatus(tokenInAddress, tokenOutAddress, chain, TOKEN_REGISTRY, OK_TOKEN_SYMBOLS$1).hasOkToken;
26110
+ return getSwapOkTokenStatus(tokenInAddress, tokenOutAddress, chain, TOKEN_REGISTRY$1, OK_TOKEN_SYMBOLS$1).hasOkToken;
25813
26111
  }
25814
26112
  /**
25815
26113
  * Estimate swap costs and output amount by delegating to the Stablecoin Service quote API.
@@ -26406,14 +26704,14 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
26406
26704
  }
26407
26705
  // Build swap fees with adapter-backed formatting for unregistered tokens
26408
26706
  const swapResultFees = serviceResponse.fees ? await this.buildFormattedFees(serviceResponse.fees, chain, destinationChain, adapter, config?.customFee?.recipientAddress) : undefined;
26409
- // Best-effort enrichment: fetch amountOut and provider-reported status.
26410
- // Only performed for same-chain swaps they typically reach DONE within
26411
- // a few seconds of source-tx confirmation, so the peek is useful UX and
26412
- // adds minimal latency. Cross-chain swaps are skipped because the
26413
- // destination mint takes minutes (CCTPv2 attestation), meaning the peek
26414
- // would always return PENDING/WAIT_DESTINATION_TRANSACTION while still
26415
- // burning up to 30s of the load balancer's server-side wait. Callers
26416
- // should use `kit.getSwapStatus()` to poll cross-chain swaps to DONE.
26707
+ // Best-effort enrichment: briefly poll for amountOut on same-chain swaps.
26708
+ // They complete atomically in the (already-confirmed) source tx, so the
26709
+ // result is terminal `DONE` regardless; the short poll only adds amountOut
26710
+ // once the service has resolved it. Cross-chain swaps are skipped because
26711
+ // the destination mint takes minutes (CCTPv2 attestation), so polling would
26712
+ // always return PENDING/WAIT_DESTINATION_TRANSACTION while burning the
26713
+ // budget. Callers should use `kit.getSwapStatus()` to poll cross-chain
26714
+ // swaps to DONE.
26417
26715
  const statusSnapshot = await fetchSameChainStatusSnapshot({
26418
26716
  isCrossChainSwap,
26419
26717
  txHash,
@@ -30040,7 +30338,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30040
30338
  ESTIMATE: 'swap_estimate'
30041
30339
  };
30042
30340
 
30043
- /** SDK name used in telemetry payloads. */ const SDK_NAME$1 = resolveKitSdkName(pkg$2.name);
30341
+ /** SDK name used in telemetry payloads. */ const SDK_NAME$1 = resolveKitSdkName(pkg$3.name);
30044
30342
  /**
30045
30343
  * A high-level class-based interface for same-chain and cross-chain token swap operations.
30046
30344
  *
@@ -30163,7 +30461,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30163
30461
  this.disableErrorReporting = config.disableErrorReporting === true;
30164
30462
  this.telemetryConfig = {
30165
30463
  sdkName: SDK_NAME$1,
30166
- sdkVersion: pkg$2.version,
30464
+ sdkVersion: pkg$3.version,
30167
30465
  disabled: this.disableErrorReporting
30168
30466
  };
30169
30467
  }
@@ -30619,7 +30917,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
30619
30917
  }
30620
30918
 
30621
30919
  // Auto-register this kit for user agent tracking
30622
- registerKit(`${pkg$2.name}/${pkg$2.version}`);
30920
+ registerKit(`${pkg$3.name}/${pkg$3.version}`);
30623
30921
 
30624
30922
  /**
30625
30923
  * Create a SwapKit instance with optional developer fee configuration.
@@ -30714,12 +31012,15 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
30714
31012
  return kit;
30715
31013
  };
30716
31014
 
30717
- var name$1 = "@circle-fin/earn-kit";
30718
- var version$1 = "1.2.0";
30719
- var pkg$1 = {
30720
- name: name$1,
30721
- version: version$1};
31015
+ var name$2 = "@circle-fin/earn-kit";
31016
+ var version$2 = "1.2.2";
31017
+ var pkg$2 = {
31018
+ name: name$2,
31019
+ version: version$2};
30722
31020
 
31021
+ const EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS = [
31022
+ 'USDC'
31023
+ ];
30723
31024
  /**
30724
31025
  * Default base URL for the Earn Service API.
30725
31026
  *
@@ -30764,20 +31065,7 @@ var pkg$1 = {
30764
31065
  */ const EARN_BRIDGE_DESTINATION_CHAIN_TO_API = {
30765
31066
  [Blockchain.Arc_Testnet]: CHAIN_TO_API[Blockchain.Arc_Testnet]
30766
31067
  };
30767
- /**
30768
- * Expected EIP-712 domain of the ERC-3009 authorization token per source
30769
- * chain.
30770
- *
30771
- * Cross-chain Earn deposits are USDC-only, but USDC deployments differ on the
30772
- * EIP-712 domain name: newer deployments use `USDC` while older ones (for
30773
- * example Arbitrum Sepolia) kept `USD Coin` from the FiatToken V2 upgrade.
30774
- * These values mirror the bridge service's per-chain domain table and the
30775
- * on-chain `DOMAIN_SEPARATOR` inputs. Asserted before signing so a tampered
30776
- * prepare response cannot point the signature at another token. The
30777
- * `satisfies` check forces a domain entry whenever a source chain is added.
30778
- *
30779
- * @internal
30780
- */ const EARN_BRIDGE_ERC3009_DOMAINS = {
31068
+ const EARN_BRIDGE_USDC_ERC3009_DOMAINS = {
30781
31069
  [Blockchain.Arbitrum_Sepolia]: {
30782
31070
  name: 'USD Coin',
30783
31071
  version: '2'
@@ -30791,6 +31079,21 @@ var pkg$1 = {
30791
31079
  version: '2'
30792
31080
  }
30793
31081
  };
31082
+ /**
31083
+ * Expected EIP-712 domain of the ERC-3009 authorization token per
31084
+ * cross-chain-supported source token and source chain.
31085
+ *
31086
+ * The bridge service prepares an ERC-3009 payload for the deposited token. We
31087
+ * keep this table as an explicit SDK allowlist because token deployments can
31088
+ * differ by source chain and by domain name/version. These values mirror the
31089
+ * bridge service's per-chain domain table and the on-chain DOMAIN_SEPARATOR
31090
+ * inputs. Asserted before signing so a tampered prepare response cannot point
31091
+ * the signature at another token or unexpected token domain.
31092
+ *
31093
+ * @internal
31094
+ */ const EARN_BRIDGE_ERC3009_DOMAINS = {
31095
+ USDC: EARN_BRIDGE_USDC_ERC3009_DOMAINS
31096
+ };
30794
31097
  /**
30795
31098
  * Maximum tolerated remaining lifetime of a prepared ERC-3009 authorization.
30796
31099
  *
@@ -31250,6 +31553,16 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31250
31553
  return approvalTxHash;
31251
31554
  }
31252
31555
 
31556
+ /** @internal */ function isSameAddress(actual, expected) {
31557
+ return actual.toLowerCase() === expected.toLowerCase();
31558
+ }
31559
+ /** @internal */ function assertDecimalPlaces(field, value, tokenDecimals, reason) {
31560
+ const decimals = value.split('.')[1]?.length ?? 0;
31561
+ if (decimals > tokenDecimals) {
31562
+ throw createValidationFailedError$1(field, value, reason);
31563
+ }
31564
+ }
31565
+
31253
31566
  /**
31254
31567
  * Build the `tokenInputs` array forwarded to the adapter contract's
31255
31568
  * `execute(executeParams, tokenInputs, signature)` call.
@@ -31273,15 +31586,17 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31273
31586
  *
31274
31587
  * @example
31275
31588
  * ```typescript
31276
- * const approvedToken = assertHexAddress(
31277
- * 'chain.usdcAddress',
31278
- * chain.usdcAddress,
31279
- * )
31280
- *
31281
- * const tokenInputs = buildEarnTokenInputs(
31589
+ * const approvalToken = resolveEarnApprovalToken(
31282
31590
  * executionPayload.executionParams,
31283
- * approvedToken,
31284
31591
  * )
31592
+ *
31593
+ * const tokenInputs =
31594
+ * approvalToken === undefined
31595
+ * ? []
31596
+ * : buildEarnTokenInputs(
31597
+ * executionPayload.executionParams,
31598
+ * approvalToken,
31599
+ * )
31285
31600
  * ```
31286
31601
  *
31287
31602
  * @internal
@@ -31293,7 +31608,7 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31293
31608
  return;
31294
31609
  }
31295
31610
  const { tokenIn } = instruction;
31296
- if (tokenIn.toLowerCase() !== approvedToken.toLowerCase()) {
31611
+ if (!isSameAddress(tokenIn, approvedToken)) {
31297
31612
  throw createValidationFailedError$1(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
31298
31613
  }
31299
31614
  tokenInputs.push({
@@ -31305,6 +31620,33 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31305
31620
  });
31306
31621
  return tokenInputs;
31307
31622
  }
31623
+ /**
31624
+ * Resolve the single token that needs adapter allowance from signed Earn
31625
+ * instructions.
31626
+ *
31627
+ * @param executionParams - Service-signed `ExecutionParams` forwarded to the adapter.
31628
+ * @returns The token requested by positive approval instructions, or `undefined`.
31629
+ * @throws {@link KitError} If positive approval instructions reference multiple tokens.
31630
+ *
31631
+ * @internal
31632
+ */ function resolveEarnApprovalToken(executionParams) {
31633
+ let approvedToken;
31634
+ executionParams.instructions.forEach((instruction, index)=>{
31635
+ const amount = BigInt(instruction.amountToApprove);
31636
+ if (amount <= 0n) {
31637
+ return;
31638
+ }
31639
+ const { tokenIn } = instruction;
31640
+ if (approvedToken === undefined) {
31641
+ approvedToken = tokenIn;
31642
+ return;
31643
+ }
31644
+ if (!isSameAddress(tokenIn, approvedToken)) {
31645
+ throw createValidationFailedError$1(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
31646
+ }
31647
+ });
31648
+ return approvedToken;
31649
+ }
31308
31650
 
31309
31651
  /**
31310
31652
  * Prepare an earn adapter action, execute it, wait for confirmation, and
@@ -31796,6 +32138,7 @@ const hexSignatureSchema = evmSignatureSchema;
31796
32138
  const hexAddressSchema = evmAddressSchema;
31797
32139
  // '0x' prefix + 32 bytes * 2 hex chars.
31798
32140
  const BYTES32_HEX_LENGTH = 66;
32141
+ const bridgeFeeTokenSchema = hexAddressSchema;
31799
32142
  /**
31800
32143
  * Zod schema for a non-negative uint256-like value.
31801
32144
  *
@@ -32082,14 +32425,12 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32082
32425
  *
32083
32426
  * The bridge prepare path returns one item per collected source fee (e.g.
32084
32427
  * `PRE_FINALITY` for the fast-burn fee and `FORWARD` for the destination
32085
- * forward fee), all denominated in the shared `feeToken`. The signed
32086
- * authorization `value` is `principal + sum(items[].amount)`, so the pre-sign
32087
- * guard parses these to recompute the expected value. Each item's `amount` is
32088
- * a base-unit decimal string in `feeToken` units.
32428
+ * forward fee), all denominated in the shared `feeToken`. Each item's `amount`
32429
+ * is a base-unit decimal string in `feeToken` units.
32089
32430
  *
32090
32431
  * @internal
32091
32432
  */ const bridgeFeeQuoteSchema = z.object({
32092
- feeToken: hexAddressSchema,
32433
+ feeToken: bridgeFeeTokenSchema,
32093
32434
  items: z.array(z.object({
32094
32435
  type: z.string(),
32095
32436
  amount: uint256LikeSchema
@@ -32105,6 +32446,12 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32105
32446
  erc3009TypedData: erc3009TypedDataSchema,
32106
32447
  feeQuote: bridgeFeeQuoteSchema
32107
32448
  }).passthrough();
32449
+ const bridgeDepositPrepareReviewSchema = z.object({
32450
+ sourceChain: z.string(),
32451
+ destinationChain: z.string(),
32452
+ amount: amountJsonSchema,
32453
+ vaultAddress: hexAddressSchema
32454
+ }).passthrough();
32108
32455
  /**
32109
32456
  * Zod schema for the bridge deposit prepare payload.
32110
32457
  *
@@ -32116,7 +32463,7 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32116
32463
  execId: bridgeDepositExecIdSchema,
32117
32464
  erc3009TypedData: bridgeDepositPreparedBundleSchema,
32118
32465
  expiresAt: z.string().datetime(),
32119
- review: z.unknown().optional()
32466
+ review: bridgeDepositPrepareReviewSchema
32120
32467
  });
32121
32468
  /**
32122
32469
  * Zod schema for the `POST /v1/earnKit/bridge/deposit/prepare` API response.
@@ -32241,10 +32588,11 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32241
32588
  * Zod schema for a fee entry in an EarnKit API response.
32242
32589
  *
32243
32590
  * Shared across deposit and withdrawal responses (and reusable for real
32244
- * charged fees, not just quote estimates). `type` identifies the fee category
32245
- * for cross-chain deposit quotes this is the kits-proxy fee-quote item type
32246
- * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). `status` qualifies the fee (e.g.
32247
- * `'estimated'` for a pre-sign cross-chain fee). Both are omitted on plain fees.
32591
+ * charged fees, not just quote estimates). `type` identifies the fee category.
32592
+ * For cross-chain deposit quotes this is the kits-proxy fee-quote item type
32593
+ * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). For withdrawal quotes, Circle fees use
32594
+ * `type: 'circle'`. `status` qualifies the fee (e.g. `'estimated'` for a
32595
+ * pre-sign cross-chain fee). Both are omitted on plain fees.
32248
32596
  *
32249
32597
  * @internal
32250
32598
  */ const feeSchema = z.object({
@@ -32516,6 +32864,60 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32516
32864
  return claimRewardsQuoteResponseSchema.safeParse(value).success;
32517
32865
  }
32518
32866
 
32867
+ var name$1 = "@circle-fin/provider-earn-service";
32868
+ var version$1 = "1.2.2";
32869
+ var pkg$1 = {
32870
+ name: name$1,
32871
+ version: version$1};
32872
+
32873
+ /**
32874
+ * HTTP header name used to report the EarnKit SDK version to the backend.
32875
+ *
32876
+ * @internal
32877
+ */ const SDK_VERSION_HEADER = 'X-EarnKit-SDK-Version';
32878
+ /**
32879
+ * Resolve the SDK version string from its source components.
32880
+ *
32881
+ * Resolution order:
32882
+ * 1. The registered kit identifier (preferred) — e.g. `@circle-fin/earn-kit/1.1.0`
32883
+ * becomes `earn-kit/1.1.0`.
32884
+ * 2. The provider package itself (fallback when no kit is registered) —
32885
+ * e.g. `provider-earn-service/1.1.0`.
32886
+ * 3. The literal `unknown` when neither source is available.
32887
+ *
32888
+ * Kept as a pure function (no global / module reads) so every branch is
32889
+ * unit-testable.
32890
+ *
32891
+ * @param kitId - The registered kit identifier, or `undefined`.
32892
+ * @param providerName - The provider package name (e.g. `@circle-fin/provider-earn-service`).
32893
+ * @param providerVersion - The provider package version, or `undefined`.
32894
+ * @returns The formatted SDK version string.
32895
+ *
32896
+ * @internal
32897
+ */ function formatSdkVersion(kitId, providerName, providerVersion) {
32898
+ if (kitId !== undefined && kitId !== '') {
32899
+ // e.g. '@circle-fin/earn-kit/1.1.0' -> 'earn-kit/1.1.0'
32900
+ return resolveKitSdkName(kitId);
32901
+ }
32902
+ {
32903
+ // e.g. '@circle-fin/provider-earn-service' + '1.1.0' -> 'provider-earn-service/1.1.0'
32904
+ return `${resolveKitSdkName(providerName)}/${providerVersion}`;
32905
+ }
32906
+ }
32907
+ /**
32908
+ * Resolve the value for the {@link SDK_VERSION_HEADER} header.
32909
+ *
32910
+ * Reads the kit registered at runtime via {@link createRequestContext} and
32911
+ * falls back to this provider package's own version when no kit is registered.
32912
+ *
32913
+ * @returns The SDK version string, e.g. `earn-kit/1.1.0` or
32914
+ * `provider-earn-service/1.1.0`, or `unknown`.
32915
+ *
32916
+ * @internal
32917
+ */ function resolveSdkVersionHeader() {
32918
+ return formatSdkVersion(createRequestContext().kit, pkg$1.name, pkg$1.version);
32919
+ }
32920
+
32519
32921
  /**
32520
32922
  * Build an API polling config with optional authorization header,
32521
32923
  * and resolve the base URL (configurable for testing).
@@ -32526,9 +32928,16 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32526
32928
  * @internal
32527
32929
  */ function buildConfig(serviceConfig) {
32528
32930
  const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
32931
+ const sdkVersion = resolveSdkVersionHeader();
32529
32932
  if (serviceConfig?.kitKey === undefined) {
32530
32933
  return {
32531
- pollingConfig: DEFAULT_CONFIG,
32934
+ pollingConfig: {
32935
+ ...DEFAULT_CONFIG,
32936
+ headers: {
32937
+ ...DEFAULT_CONFIG.headers,
32938
+ [SDK_VERSION_HEADER]: sdkVersion
32939
+ }
32940
+ },
32532
32941
  baseUrl
32533
32942
  };
32534
32943
  }
@@ -32544,6 +32953,7 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32544
32953
  ...DEFAULT_CONFIG,
32545
32954
  headers: {
32546
32955
  ...DEFAULT_CONFIG.headers,
32956
+ [SDK_VERSION_HEADER]: sdkVersion,
32547
32957
  Authorization: `Bearer ${serviceConfig.kitKey}`
32548
32958
  }
32549
32959
  },
@@ -32844,7 +33254,8 @@ function toPositionInfo(data) {
32844
33254
  return {
32845
33255
  execId: response.data.execId,
32846
33256
  preparedBundle,
32847
- expiresAt: response.data.expiresAt
33257
+ expiresAt: response.data.expiresAt,
33258
+ review: response.data.review
32848
33259
  };
32849
33260
  } catch (error) {
32850
33261
  throw parseEarnApiError(error, {
@@ -32955,47 +33366,34 @@ const VALID_AFTER_CLOCK_SKEW_SECONDS = 60n;
32955
33366
  * @throws If any typed-data field does not match the expected bridge deposit intent.
32956
33367
  * @internal
32957
33368
  */ function assertBridgeDepositTypedDataMatchesIntent(intent) {
32958
- const { preparedBundle, sourceChain, sourceAddress, sourceTokenAddress, authorizationRecipient, amount, maxFee, tokenDecimals, nowSeconds = BigInt(Math.floor(Date.now() / 1000)) } = intent;
33369
+ const { preparedBundle, sourceChain, sourceAddress, sourceTokenAddress, expectedDomain, authorizationRecipient, amount, maxFee, tokenDecimals, amountRaw, nowSeconds = BigInt(Math.floor(Date.now() / 1000)) } = intent;
32959
33370
  const { domain, message } = preparedBundle.erc3009TypedData;
32960
- // USDC deployments differ on the EIP-712 domain name per chain, so the
32961
- // expected domain is looked up for the source chain being signed on.
32962
- const expectedDomain = EARN_BRIDGE_ERC3009_DOMAINS[sourceChain.chain];
32963
- if (expectedDomain === undefined) {
32964
- throw createValidationFailedError$1('sourceChain.chain', sourceChain.chain, 'no expected ERC-3009 domain is configured for the source chain');
32965
- }
32966
- if (domain.name !== expectedDomain.name) {
32967
- throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.name', domain.name, `domain name must be ${expectedDomain.name}`);
32968
- }
32969
- if (domain.version !== expectedDomain.version) {
32970
- throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.version', domain.version, `domain version must be ${expectedDomain.version}`);
32971
- }
32972
33371
  assertSameAddress('preparedBundle.erc3009TypedData.domain.verifyingContract', domain.verifyingContract, sourceTokenAddress, 'verifyingContract must match the source token contract');
32973
33372
  assertSameBigInt('preparedBundle.erc3009TypedData.domain.chainId', domain.chainId, BigInt(sourceChain.chainId), 'chainId must match the source chain');
33373
+ assertSameString('preparedBundle.erc3009TypedData.domain.name', domain.name, expectedDomain.name, 'domain name must match the source token');
33374
+ assertSameString('preparedBundle.erc3009TypedData.domain.version', domain.version, expectedDomain.version, 'domain version must match the source token');
32974
33375
  assertSameAddress('preparedBundle.erc3009TypedData.message.from', message.from, sourceAddress, 'from must match the source wallet');
32975
33376
  assertSameAddress('preparedBundle.erc3009TypedData.message.to', message.to, authorizationRecipient, 'to must match the configured authorization recipient');
32976
- // The prepare path folds the source fee into the signed authorization:
32977
- // message.value = principal + sum(feeQuote.items[].amount), all denominated
32978
- // in the source token. The fee items only add up to a meaningful total when
32979
- // they are priced in the same token as the principal, so first pin the fee
32980
- // token to the source token contract before summing.
32981
- assertSameAddress('preparedBundle.feeQuote.feeToken', preparedBundle.feeQuote.feeToken, sourceTokenAddress, 'feeQuote.feeToken must match the source token contract');
32982
- // Recompute the expected value from locally trusted inputs (the caller
32983
- // principal) plus the bundle's own fee items so the guard accepts fee-bearing
32984
- // deposits while still rejecting any other value tampering. The fee quote is
32985
- // service-supplied, so bound it to the caller-accepted quote-derived cap
32986
- // before including it in the signed value.
32987
- const principal = Amount.parse(amount, {
33377
+ // Recompute the expected ERC-3009 value from locally trusted inputs. Bridge
33378
+ // source fees are paid in the source ERC-20 and are part of the authorization.
33379
+ assertDecimalPlaces('amount', amount, tokenDecimals, `amount must have at most ${String(tokenDecimals)} decimal places for the cross-chain Earn deposit source token`);
33380
+ const parsedAmount = Amount.parse(amount, {
32988
33381
  decimals: tokenDecimals
32989
33382
  }).raw;
33383
+ const principal = amountRaw === undefined ? parsedAmount : parseReviewedAmount(amountRaw, parsedAmount);
33384
+ const feeQuote = preparedBundle.feeQuote;
32990
33385
  const feeTotal = sumBridgeFeeItems(preparedBundle.feeQuote);
33386
+ assertFeeTokenMatchesSourceToken(feeQuote.feeToken, sourceTokenAddress);
33387
+ const feeDecimals = tokenDecimals;
33388
+ assertDecimalPlaces('maxFee', maxFee, feeDecimals, `maxFee must have at most ${String(feeDecimals)} decimal places for the cross-chain Earn deposit fee token`);
32991
33389
  const maxFeeRaw = Amount.parse(maxFee, {
32992
- decimals: tokenDecimals
33390
+ decimals: feeDecimals
32993
33391
  }).raw;
32994
33392
  if (feeTotal > maxFeeRaw) {
32995
33393
  throw createValidationFailedError$1('preparedBundle.feeQuote.items', feeTotal.toString(), 'fee total must not exceed maxFee in base units');
32996
33394
  }
32997
33395
  const expectedValue = principal + feeTotal;
32998
- assertSameBigInt('preparedBundle.erc3009TypedData.message.value', message.value, expectedValue, 'value must match the requested amount plus the bundle fee in base units');
33396
+ assertSameBigInt('preparedBundle.erc3009TypedData.message.value', message.value, expectedValue, 'value must match the requested amount plus source-token fees in base units');
32999
33397
  const window = checkErc3009ValidityWindow({
33000
33398
  validAfter: BigInt(message.validAfter),
33001
33399
  validBefore: BigInt(message.validBefore),
@@ -33069,14 +33467,19 @@ const VALID_AFTER_CLOCK_SKEW_SECONDS = 60n;
33069
33467
  /**
33070
33468
  * Sum the source-collected fee items in a prepared bundle's fee quote.
33071
33469
  *
33072
- * Each item amount is a base-unit value in the shared `feeToken`; the total is
33073
- * what the prepare path adds to the principal when building `message.value`.
33470
+ * Each item amount is a base-unit value in the shared `feeToken`.
33074
33471
  *
33075
33472
  * @param feeQuote - Fee quote parsed from the prepared bundle.
33076
- * @returns Total fee in source token base units (0n when there are no items).
33473
+ * @returns Total fee in fee-token base units (0n when there are no items).
33077
33474
  */ function sumBridgeFeeItems(feeQuote) {
33078
33475
  return feeQuote.items.reduce((total, item)=>total + BigInt(item.amount), 0n);
33079
33476
  }
33477
+ function assertFeeTokenMatchesSourceToken(feeToken, sourceTokenAddress) {
33478
+ if (isSameAddress(feeToken, sourceTokenAddress)) {
33479
+ return;
33480
+ }
33481
+ throw createValidationFailedError$1('preparedBundle.feeQuote.feeToken', feeToken, 'fee token must match the cross-chain Earn deposit source token');
33482
+ }
33080
33483
  function assertNever(value) {
33081
33484
  throw new KitError({
33082
33485
  ...EarnError.INTERNAL_ERROR,
@@ -33084,8 +33487,23 @@ function assertNever(value) {
33084
33487
  message: `Unexpected validity window reason: ${String(value)}`
33085
33488
  });
33086
33489
  }
33490
+ function parseReviewedAmount(amountRaw, parsedAmount) {
33491
+ let reviewedAmount;
33492
+ try {
33493
+ reviewedAmount = BigInt(amountRaw);
33494
+ } catch {
33495
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'amount must be a non-negative integer string');
33496
+ }
33497
+ if (reviewedAmount < 0n) {
33498
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'amount must be a non-negative integer string');
33499
+ }
33500
+ if (reviewedAmount !== parsedAmount) {
33501
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'review amount must match the requested amount');
33502
+ }
33503
+ return reviewedAmount;
33504
+ }
33087
33505
  function assertSameAddress(field, actual, expected, reason) {
33088
- if (actual.toLowerCase() !== expected.toLowerCase()) {
33506
+ if (!isSameAddress(actual, expected)) {
33089
33507
  throw createValidationFailedError$1(field, actual, reason);
33090
33508
  }
33091
33509
  }
@@ -33094,6 +33512,11 @@ function assertSameBigInt(field, actual, expected, reason) {
33094
33512
  throw createValidationFailedError$1(field, actual, reason);
33095
33513
  }
33096
33514
  }
33515
+ function assertSameString(field, actual, expected, reason) {
33516
+ if (actual !== expected) {
33517
+ throw createValidationFailedError$1(field, actual, reason);
33518
+ }
33519
+ }
33097
33520
 
33098
33521
  /**
33099
33522
  * Build signed withdrawal instructions via the Earn Service API.
@@ -33263,9 +33686,15 @@ function toWithdrawalQuoteInfo(data) {
33263
33686
  symbol: data.asset,
33264
33687
  amount: Amount.fromJSON(data.maxWithdrawable)
33265
33688
  },
33266
- fees: data.fees.map((fee)=>({
33267
- symbol: fee.token,
33268
- amount: Amount.fromJSON(fee.amount)
33689
+ fees: data.fees.map(({ token, amount, type, status })=>({
33690
+ symbol: token,
33691
+ amount: Amount.fromJSON(amount),
33692
+ ...type !== undefined && {
33693
+ type
33694
+ },
33695
+ ...status !== undefined && {
33696
+ status
33697
+ }
33269
33698
  })),
33270
33699
  gasFees: [],
33271
33700
  // Wire format uses `warnings`, but the SDK surface uses
@@ -33451,6 +33880,9 @@ function toWithdrawalQuoteInfo(data) {
33451
33880
  'CONFIRMED',
33452
33881
  'COMPLETE'
33453
33882
  ]);
33883
+ // Intentionally built-ins-only: Earn bridge support is limited to SDK-known
33884
+ // token contracts plus the explicit ERC-3009 domain allowlist below.
33885
+ const TOKEN_REGISTRY = createTokenRegistry();
33454
33886
  /**
33455
33887
  * Build the typed error raised when a cross-chain wait is cancelled via its
33456
33888
  * `AbortSignal`. Mirrors `@core/adapter-base`'s `createAbortError` (same
@@ -33780,24 +34212,20 @@ function finishElapsedWait(lastStatus, lastError) {
33780
34212
  }
33781
34213
  const { address, chain: apiChain, chainDefinition: chain } = await resolveAdapterContext(params.from);
33782
34214
  const adapterContractAddress = requireAdapterContract(chain);
33783
- const rawUsdcAddress = chain.usdcAddress;
33784
- if (rawUsdcAddress === null) {
33785
- throw createUnsupportedTokenError('USDC', chain.name);
33786
- }
33787
- const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
33788
34215
  const { adapter } = params.from;
34216
+ const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
33789
34217
  const { executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
33790
- vaultAddress: params.vaultAddress,
34218
+ vaultAddress,
33791
34219
  amount: params.amount,
33792
34220
  address,
33793
34221
  chain: apiChain,
33794
34222
  config
33795
34223
  }), ()=>undefined);
33796
34224
  validateExecutionDeadline(executionParams);
33797
- const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
33798
- const approvalToken = tokenInputs[0]?.token;
34225
+ const approvalToken = resolveEarnApprovalToken(executionParams);
34226
+ const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
33799
34227
  const requiredAllowance = sumTokenInputAmounts(tokenInputs);
33800
- if (!options.skipApprove && approvalToken !== undefined) {
34228
+ if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
33801
34229
  await this.runPhase(ctx, 'approve', 'approve', async ()=>approveAllowanceIfNeeded({
33802
34230
  adapter,
33803
34231
  chain,
@@ -33805,7 +34233,7 @@ function finishElapsedWait(lastStatus, lastError) {
33805
34233
  delegate: adapterContractAddress,
33806
34234
  address,
33807
34235
  requiredAllowance,
33808
- revertMessage: 'USDC approval reverted on-chain'
34236
+ revertMessage: 'Earn deposit token approval reverted on-chain'
33809
34237
  }), (txHash)=>txHash);
33810
34238
  }
33811
34239
  const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>executeEarnAction({
@@ -33824,7 +34252,7 @@ function finishElapsedWait(lastStatus, lastError) {
33824
34252
  kind: 'same-chain',
33825
34253
  txHash,
33826
34254
  explorerUrl,
33827
- vaultAddress: params.vaultAddress,
34255
+ vaultAddress,
33828
34256
  amount: params.amount
33829
34257
  };
33830
34258
  } catch (error) {
@@ -33846,13 +34274,6 @@ function finishElapsedWait(lastStatus, lastError) {
33846
34274
  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.`);
33847
34275
  const destinationAddress = assertHexAddress('to.recipientAddress', params.to.recipientAddress, `Destination address for chain ${destinationChain.name} must be a 0x-prefixed 20-byte hex address.`);
33848
34276
  const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
33849
- // The signed ERC-3009 value is denominated in USDC's 6 decimals. More
33850
- // precise inputs would be silently truncated by Amount.parse, so the
33851
- // result would claim a larger deposit than was actually authorized.
33852
- const amountDecimals = params.amount.split('.')[1]?.length ?? 0;
33853
- if (amountDecimals > TOKEN_DECIMALS_6) {
33854
- throw createValidationFailedError$1('amount', params.amount, `amount must have at most ${String(TOKEN_DECIMALS_6)} decimal places for cross-chain Earn deposits`);
33855
- }
33856
34277
  const execId = params.idempotencyKey;
33857
34278
  const prepared = await this.runPhase(ctx, 'crossChainDeposit', 'prepare', async ()=>fetchBridgeDepositPrepare({
33858
34279
  execId,
@@ -33872,17 +34293,19 @@ function finishElapsedWait(lastStatus, lastError) {
33872
34293
  // the error trace, instead of ending the event stream unannounced
33873
34294
  // after the prepare success event.
33874
34295
  assertPreparedBundleMatchesTypedDataNonce(prepared.preparedBundle);
34296
+ const sourceToken = resolvePreparedBridgeSourceToken(prepared.preparedBundle, route.sourceChain);
34297
+ validatePreparedReviewAmountDecimals(prepared.review.amount.decimals, sourceToken.decimals);
33875
34298
  assertBridgeDepositTypedDataMatchesIntent({
33876
34299
  preparedBundle: prepared.preparedBundle,
33877
34300
  sourceChain: route.sourceChain,
33878
34301
  sourceAddress,
33879
- sourceTokenAddress: route.sourceTokenAddress,
34302
+ sourceTokenAddress: sourceToken.address,
34303
+ expectedDomain: sourceToken.erc3009Domain,
33880
34304
  authorizationRecipient: route.authorizationRecipient,
33881
34305
  amount: params.amount,
33882
34306
  maxFee: params.maxFee,
33883
- // Cross-chain Earn is USDC-only today. USDC uses 6 decimals on all
33884
- // configured source chains.
33885
- tokenDecimals: TOKEN_DECIMALS_6
34307
+ tokenDecimals: sourceToken.decimals,
34308
+ amountRaw: prepared.review.amount.raw
33886
34309
  });
33887
34310
  return signBridgeDepositAuthorization({
33888
34311
  adapter: bridgeSigningAdapter,
@@ -34105,21 +34528,17 @@ function finishElapsedWait(lastStatus, lastError) {
34105
34528
  const { adapter, chain, apiChain, address, vaultAddress, amount, config } = params;
34106
34529
  try {
34107
34530
  const adapterContractAddress = requireAdapterContract(chain);
34108
- const rawUsdcAddress = chain.usdcAddress;
34109
- if (rawUsdcAddress === null) {
34110
- throw createUnsupportedTokenError('USDC', chain.name);
34111
- }
34112
- const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
34531
+ const normalizedVaultAddress = assertHexAddress('vaultAddress', vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
34113
34532
  const { executionParams, signature } = await fetchDeposit({
34114
- vaultAddress,
34533
+ vaultAddress: normalizedVaultAddress,
34115
34534
  amount,
34116
34535
  address,
34117
34536
  chain: apiChain,
34118
34537
  config
34119
34538
  });
34120
34539
  validateExecutionDeadline(executionParams);
34121
- const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
34122
- const approvalToken = tokenInputs[0]?.token;
34540
+ const approvalToken = resolveEarnApprovalToken(executionParams);
34541
+ const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
34123
34542
  const requiredAllowance = sumTokenInputAmounts(tokenInputs);
34124
34543
  return await estimateEarnQuoteGasFees({
34125
34544
  adapter,
@@ -34132,7 +34551,7 @@ function finishElapsedWait(lastStatus, lastError) {
34132
34551
  tokenInputs,
34133
34552
  signature
34134
34553
  },
34135
- approval: approvalToken !== undefined ? {
34554
+ approval: approvalToken !== undefined && requiredAllowance > 0n ? {
34136
34555
  token: approvalToken,
34137
34556
  delegate: adapterContractAddress,
34138
34557
  requiredAllowance
@@ -34376,11 +34795,6 @@ function validateCrossChainDepositRoute(sourceChain, destinationChain) {
34376
34795
  if (destinationApiChain === undefined) {
34377
34796
  throw createUnsupportedCrossChainDepositError(destinationChain, 'destination chain is not enabled for cross-chain Earn deposits');
34378
34797
  }
34379
- const sourceTokenAddressRaw = sourceChain.usdcAddress;
34380
- if (sourceTokenAddressRaw === null) {
34381
- throw createUnsupportedTokenError('USDC', sourceChain.name);
34382
- }
34383
- const sourceTokenAddress = assertHexAddress('sourceChain.usdcAddress', sourceTokenAddressRaw, `USDC address for chain ${sourceChain.name} must be a 0x-prefixed 20-byte hex address.`);
34384
34798
  if (sourceChain.cctp == null) {
34385
34799
  throw createBridgeRouteNotConfiguredError(sourceChain, 'source chain does not support CCTP');
34386
34800
  }
@@ -34404,10 +34818,50 @@ function validateCrossChainDepositRoute(sourceChain, destinationChain) {
34404
34818
  destinationApiChain,
34405
34819
  destinationDomain: destinationChain.cctp.domain,
34406
34820
  sourceChain,
34407
- sourceTokenAddress,
34408
34821
  authorizationRecipient
34409
34822
  };
34410
34823
  }
34824
+ function resolvePreparedBridgeSourceToken(preparedBundle, sourceChain) {
34825
+ 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.`);
34826
+ let sourceToken;
34827
+ try {
34828
+ sourceToken = TOKEN_REGISTRY.resolveByAddress(sourceTokenAddress, sourceChain.chain);
34829
+ } catch {
34830
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token ${sourceTokenAddress} is not configured on source chain ${sourceChain.name} for cross-chain Earn deposits`);
34831
+ }
34832
+ 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.`);
34833
+ if (!isSameAddress(sourceTokenAddress, registryAddress)) {
34834
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token must match the configured ${sourceToken.symbol ?? 'token'} address on source chain ${sourceChain.name}`);
34835
+ }
34836
+ const { symbol } = sourceToken;
34837
+ if (symbol === undefined) {
34838
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token ${sourceTokenAddress} is not a registered Earn bridge token on source chain ${sourceChain.name}`);
34839
+ }
34840
+ return {
34841
+ address: sourceTokenAddress,
34842
+ erc3009Domain: resolveBridgeErc3009Domain(symbol, sourceChain),
34843
+ decimals: sourceToken.decimals
34844
+ };
34845
+ }
34846
+ function validatePreparedReviewAmountDecimals(reviewedDecimals, tokenDecimals) {
34847
+ if (reviewedDecimals !== tokenDecimals) {
34848
+ throw createValidationFailedError$1('prepared.review.amount.decimals', reviewedDecimals, `review amount decimals must match the cross-chain Earn deposit source token decimals ${String(tokenDecimals)}`);
34849
+ }
34850
+ }
34851
+ function resolveBridgeErc3009Domain(tokenSymbol, sourceChain) {
34852
+ if (!isEarnBridgeErc3009TokenSymbol(tokenSymbol)) {
34853
+ throw createValidationFailedError$1('sourceToken.symbol', tokenSymbol, `ERC-3009 domain is not configured for ${tokenSymbol} on source chain ${sourceChain.name}`);
34854
+ }
34855
+ const domainsByChain = EARN_BRIDGE_ERC3009_DOMAINS[tokenSymbol];
34856
+ const domain = domainsByChain[sourceChain.chain];
34857
+ if (domain === undefined) {
34858
+ throw createValidationFailedError$1('sourceToken.symbol', tokenSymbol, `ERC-3009 domain is not configured for ${tokenSymbol} on source chain ${sourceChain.name}`);
34859
+ }
34860
+ return domain;
34861
+ }
34862
+ function isEarnBridgeErc3009TokenSymbol(tokenSymbol) {
34863
+ return EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS.includes(tokenSymbol);
34864
+ }
34411
34865
  function createUnsupportedCrossChainDepositError(chain, reason) {
34412
34866
  return new KitError({
34413
34867
  ...EarnError.UNSUPPORTED_CHAIN,
@@ -34480,7 +34934,6 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
34480
34934
  // Untyped callers can pass anything here; without these guards a null
34481
34935
  // config crashes with a raw TypeError on `config.providers` instead of
34482
34936
  // the structured validation error every other public surface throws.
34483
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JS consumers may pass null or a non-object config
34484
34937
  if (typeof config !== 'object' || config === null || Array.isArray(config)) {
34485
34938
  throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
34486
34939
  }
@@ -34847,10 +35300,9 @@ const sourceAdapterContextSchema = z.object({
34847
35300
  * Schema for validating human-readable decimal amount strings.
34848
35301
  *
34849
35302
  * Accept positive decimal strings like '100', '100.50', '0.001'. Reject
34850
- * zero, negative, non-numeric, and non-canonical strings. EarnKit
34851
- * same-chain amounts are USDC/EURC (6 decimals), so precision is capped at
34852
- * 6 to match the Earn Service and fail fast locally instead of leaking
34853
- * 7+ decimal-place inputs to the server.
35303
+ * zero, negative, non-numeric, and non-canonical strings. Same-chain
35304
+ * EarnKit vaults can use different asset precisions, so the service enforces
35305
+ * the vault-specific decimal cap after resolving the vault asset.
34854
35306
  *
34855
35307
  * @internal
34856
35308
  */ const amountSchema$1 = z.string({
@@ -34859,32 +35311,15 @@ const sourceAdapterContextSchema = z.object({
34859
35311
  allowZero: false,
34860
35312
  regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34861
35313
  attributeName: 'amount',
34862
- maxDecimals: 6
34863
- })(z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34864
- /**
34865
- * Schema for cross-chain deposit amount strings.
34866
- *
34867
- * Cross-chain Earn deposits are USDC-only, and the signed ERC-3009 value is
34868
- * denominated in USDC's 6 decimals. Reject more precise inputs up front so
34869
- * the signed value always equals the requested amount instead of a silent
34870
- * truncation. Non-canonical forms (leading dot, leading zeros) are rejected
34871
- * too, matching the Earn Service.
34872
- *
34873
- * @internal
34874
- */ const crossChainAmountSchema = z.string({
34875
- required_error: 'amount is required'
34876
- }).min(1, 'amount is required').pipe(createDecimalStringValidator({
34877
- allowZero: false,
34878
- regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34879
- attributeName: 'amount',
34880
- maxDecimals: 6
35314
+ maxDecimals: 18
34881
35315
  })(z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34882
35316
  /**
34883
35317
  * Schema for cross-chain source fee caps.
34884
35318
  *
34885
- * Cross-chain source fees are denominated in USDC base units. Zero is valid
34886
- * when the quote contains no source-collected fees; otherwise the cap should be
34887
- * the caller-accepted total from a recent quote.
35319
+ * Cross-chain source fees are denominated in the quote's fee token. Zero is
35320
+ * valid when the quote contains no source-collected fees; otherwise the cap
35321
+ * should be the caller-accepted total from a recent quote. The provider
35322
+ * enforces the prepared bundle's fee-token precision before signing.
34888
35323
  *
34889
35324
  * @internal
34890
35325
  */ const crossChainMaxFeeSchema = z.string({
@@ -34893,7 +35328,7 @@ const sourceAdapterContextSchema = z.object({
34893
35328
  allowZero: true,
34894
35329
  regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34895
35330
  attributeName: 'maxFee',
34896
- maxDecimals: 6
35331
+ maxDecimals: 18
34897
35332
  })(z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34898
35333
  /**
34899
35334
  * Build a schema for an optional non-negative decimal filter string.
@@ -35054,7 +35489,7 @@ const crossChainDepositDestinationSchema = z.object({
35054
35489
  from: sourceAdapterContextSchema,
35055
35490
  to: crossChainDepositDestinationSchema,
35056
35491
  vaultAddress: vaultAddressSchema,
35057
- amount: crossChainAmountSchema,
35492
+ amount: amountSchema$1,
35058
35493
  maxFee: crossChainMaxFeeSchema,
35059
35494
  transferSpeed: z.enum([
35060
35495
  'FAST',
@@ -36478,7 +36913,7 @@ function formatRetryResult(operation, result) {
36478
36913
  }
36479
36914
 
36480
36915
  // Auto-register this kit for user agent tracking
36481
- registerKit(`${pkg$1.name}/${pkg$1.version}`);
36916
+ registerKit(`${pkg$2.name}/${pkg$2.version}`);
36482
36917
 
36483
36918
  /**
36484
36919
  * Create an EarnKit instance for AppKit earn operations.
@@ -36573,12 +37008,17 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36573
37008
  * The type annotation `readonly TokenAlias[]` ensures this array stays in sync
36574
37009
  * with the TokenAlias type definition - TypeScript will enforce any changes.
36575
37010
  *
36576
- * For swap operations, additional tokens (EURC, DAI, USDE, PYUSD) are supported
37011
+ * `USDC`, `USDT`, and `NATIVE` map to dedicated adapter transfer actions.
37012
+ * `EURC` has no dedicated action — it resolves to the chain's `eurcAddress`
37013
+ * and uses the generic token transfer action (see {@link prepareSend}).
37014
+ *
37015
+ * For swap operations, additional tokens (DAI, USDE, PYUSD) are supported
36577
37016
  * via SwapKit's SupportedToken type.
36578
37017
  */ const TOKEN_ALIASES = [
36579
37018
  'USDC',
36580
37019
  'USDT',
36581
- 'NATIVE'
37020
+ 'NATIVE',
37021
+ 'EURC'
36582
37022
  ];
36583
37023
  /**
36584
37024
  * Check if a token string is a known alias.
@@ -36605,7 +37045,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36605
37045
  * Type guard to check if a string is a valid token address for a chain.
36606
37046
  *
36607
37047
  * This function verifies that a token string is:
36608
- * 1. Not a known alias ('USDC', 'USDT', 'NATIVE')
37048
+ * 1. Not a known alias ('USDC', 'USDT', 'NATIVE', 'EURC')
36609
37049
  * 2. A valid address format for the specified chain
36610
37050
  *
36611
37051
  * Use this to narrow the type to `TokenAddress` in TypeScript.
@@ -36635,7 +37075,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
36635
37075
  * Validate and classify a token identifier.
36636
37076
  *
36637
37077
  * This function determines whether a token string is:
36638
- * 1. A known alias ('USDC', 'USDT', or 'NATIVE')
37078
+ * 1. A known alias ('USDC', 'USDT', 'NATIVE', or 'EURC')
36639
37079
  * 2. A valid token address for the given chain
36640
37080
  * 3. An invalid/unrecognized token identifier
36641
37081
  *
@@ -36889,8 +37329,11 @@ const tokens = createTokenRegistry();
36889
37329
  * from `to` (explicit address string or adapter), and normalize the human‑readable
36890
37330
  * `amount` into token units.
36891
37331
  *
36892
- * For known token aliases, uses dedicated transfer actions
36893
- * with hardcoded decimals (USDC and USDT use 6 decimals; native uses the source chain's native decimals). For custom token addresses, uses the generic token.transfer
37332
+ * For the `USDC`, `USDT`, and `NATIVE` aliases, uses dedicated transfer actions
37333
+ * with hardcoded decimals (USDC and USDT use 6 decimals; native uses the source chain's native decimals).
37334
+ * The `EURC` alias resolves to the chain's `eurcAddress` (6 decimals from the token
37335
+ * registry) and routes through the generic token.transfer action; it is rejected on
37336
+ * chains where `eurcAddress` is not set. For custom token addresses, uses the generic token.transfer
36894
37337
  * action and fetches decimals dynamically from the contract.
36895
37338
  *
36896
37339
  * @param params - The send parameters including source context, destination, amount and token.
@@ -36983,6 +37426,21 @@ const tokens = createTokenRegistry();
36983
37426
  preparedSendRequest = await fromAdapter.prepareAction('native.transfer', requestParams, operationContext);
36984
37427
  break;
36985
37428
  }
37429
+ case 'EURC':
37430
+ {
37431
+ // EURC has no dedicated transfer action. Resolve its contract address
37432
+ // from the chain definition and route through the generic
37433
+ // token.transfer action. `eurcAddress` is null where EURC isn't deployed.
37434
+ const eurcAddress = fromChain.eurcAddress;
37435
+ if (eurcAddress === null) {
37436
+ throw createUnsupportedTokenError(token, fromChain.name);
37437
+ }
37438
+ preparedSendRequest = await fromAdapter.prepareAction('token.transfer', {
37439
+ ...requestParams,
37440
+ tokenAddress: eurcAddress
37441
+ }, operationContext);
37442
+ break;
37443
+ }
36986
37444
  default:
36987
37445
  {
36988
37446
  throw createUnsupportedTokenError(String(token), fromChain.name);
@@ -37009,12 +37467,13 @@ const tokens = createTokenRegistry();
37009
37467
  * Fully prepares and executes the send transaction to transfer the specified amount and
37010
37468
  * token to the specified destination.
37011
37469
  *
37012
- * For known token aliases ('USDC', 'USDT', 'NATIVE'), uses optimized dedicated actions.
37013
- * For custom token addresses, uses the generic token.transfer action with dynamically
37014
- * fetched decimals.
37470
+ * For the 'USDC', 'USDT', and 'NATIVE' aliases, uses optimized dedicated actions.
37471
+ * The 'EURC' alias resolves to the chain's `eurcAddress` and routes through the generic
37472
+ * token.transfer action. For custom token addresses, uses the generic token.transfer action
37473
+ * with dynamically fetched decimals.
37015
37474
  *
37016
37475
  * @param params - The send parameters: source context, destination (address or adapter),
37017
- * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE' or custom address, defaults to 'USDC').
37476
+ * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE' | 'EURC' or custom address, defaults to 'USDC').
37018
37477
  * @returns A BridgeStep object with transaction details including hash, status, and explorer URL.
37019
37478
  * @throws KitError INPUT_VALIDATION_FAILED if parameters are invalid.
37020
37479
  * @throws KitError INPUT_UNSUPPORTED_TOKEN if the token is invalid for the chain.
@@ -37032,6 +37491,17 @@ const tokens = createTokenRegistry();
37032
37491
  * console.log('Transaction hash:', step.txHash)
37033
37492
  * ```
37034
37493
  *
37494
+ * @example Sending EURC on a chain where EURC is deployed (e.g. Arc Testnet)
37495
+ * ```typescript
37496
+ * const step = await send({
37497
+ * from: { adapter: sourceAdapter, chain: 'Arc_Testnet' },
37498
+ * to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
37499
+ * amount: '5.0',
37500
+ * token: 'EURC',
37501
+ * })
37502
+ * console.log('Transaction hash:', step.txHash)
37503
+ * ```
37504
+ *
37035
37505
  * @example Sending a custom token (DAI) to an explicit address
37036
37506
  * ```typescript
37037
37507
  * const step = await send({
@@ -37073,21 +37543,22 @@ const tokens = createTokenRegistry();
37073
37543
  };
37074
37544
 
37075
37545
  /**
37076
- * Estimate the network fees to send USDC, USDT, or a supported native token.
37546
+ * Estimate the network fees to send USDC, USDT, EURC, or a supported native token.
37077
37547
  *
37078
37548
  * Prepare the send operation (including validating inputs and resolving the recipient when
37079
37549
  * provided as an adapter) and return an {@link EstimatedGas} object. This function does
37080
37550
  * not submit any transaction.
37081
37551
  *
37082
37552
  * @remarks
37083
- * - Selects the transfer handler based on `token` ('USDC' by default, 'USDT', or 'NATIVE').
37553
+ * - Selects the transfer handler based on `token` ('USDC' by default, 'USDT', 'NATIVE', or 'EURC').
37554
+ * 'EURC' is only supported on chains where `eurcAddress` is configured.
37084
37555
  * - When `to` is an {@link Adapter}, the recipient address is derived from the adapter.
37085
37556
  * - Rejects transfers where the resolved `to` equals the source `from` address.
37086
37557
  * - Interprets `amount` as a human-readable decimal string (USDC and USDT scaled to 6 decimals,
37087
37558
  * native EVM value typically scaled to 18 decimals).
37088
37559
  *
37089
37560
  * @param params - The send parameters: source context, destination (address or adapter),
37090
- * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE', defaults to 'USDC').
37561
+ * human-readable `amount`, and optional `token` ('USDC' | 'USDT' | 'NATIVE' | 'EURC', defaults to 'USDC').
37091
37562
  * @returns The estimated gas information including `gas`, `gasPrice`, and total `fee`.
37092
37563
  * @throws Error If parameters are invalid.
37093
37564
  * @throws Error If the token is unsupported or the send cannot be prepared.
@@ -37794,7 +38265,7 @@ async function deposit$2(context, params) {
37794
38265
  }
37795
38266
 
37796
38267
  var name = "@circle-fin/unified-balance-kit";
37797
- var version = "1.2.0";
38268
+ var version = "1.2.2";
37798
38269
  var pkg = {
37799
38270
  name: name,
37800
38271
  version: version};