@circle-fin/app-kit 1.8.1 → 1.10.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.
Files changed (48) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +3 -3
  3. package/bridge.cjs +1102 -260
  4. package/bridge.d.cts +161 -10
  5. package/bridge.d.mts +161 -10
  6. package/bridge.d.ts +161 -10
  7. package/bridge.mjs +1102 -260
  8. package/chains.cjs +102 -2
  9. package/chains.d.cts +3 -0
  10. package/chains.d.mts +3 -0
  11. package/chains.d.ts +3 -0
  12. package/chains.mjs +102 -2
  13. package/context.cjs +1 -0
  14. package/context.d.cts +166 -12
  15. package/context.d.mts +166 -12
  16. package/context.d.ts +166 -12
  17. package/context.mjs +1 -0
  18. package/earn.cjs +1074 -454
  19. package/earn.d.cts +546 -99
  20. package/earn.d.mts +546 -99
  21. package/earn.d.ts +546 -99
  22. package/earn.mjs +1074 -455
  23. package/estimateBridge.cjs +1102 -260
  24. package/estimateBridge.d.cts +161 -10
  25. package/estimateBridge.d.mts +161 -10
  26. package/estimateBridge.d.ts +161 -10
  27. package/estimateBridge.mjs +1102 -260
  28. package/estimateSwap.cjs +915 -96
  29. package/estimateSwap.d.cts +161 -10
  30. package/estimateSwap.d.mts +161 -10
  31. package/estimateSwap.d.ts +161 -10
  32. package/estimateSwap.mjs +915 -96
  33. package/index.cjs +3029 -862
  34. package/index.d.cts +1277 -143
  35. package/index.d.mts +1277 -143
  36. package/index.d.ts +1277 -143
  37. package/index.mjs +3029 -862
  38. package/package.json +12 -6
  39. package/swap.cjs +915 -96
  40. package/swap.d.cts +161 -10
  41. package/swap.d.mts +161 -10
  42. package/swap.d.ts +161 -10
  43. package/swap.mjs +915 -96
  44. package/unifiedBalance.cjs +822 -115
  45. package/unifiedBalance.d.cts +224 -4
  46. package/unifiedBalance.d.mts +224 -4
  47. package/unifiedBalance.d.ts +224 -4
  48. package/unifiedBalance.mjs +822 -115
@@ -22,6 +22,7 @@ var zod = require('zod');
22
22
  var pino = require('pino');
23
23
  var units = require('@ethersproject/units');
24
24
  var bytes = require('@ethersproject/bytes');
25
+ require('@ethersproject/abi');
25
26
  var address = require('@ethersproject/address');
26
27
  var bs58 = require('bs58');
27
28
  var web3_js = require('@solana/web3.js');
@@ -2479,6 +2480,8 @@ class KitError extends Error {
2479
2480
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
2480
2481
  Blockchain["Codex"] = "Codex";
2481
2482
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
2483
+ Blockchain["Cronos"] = "Cronos";
2484
+ Blockchain["Cronos_Testnet"] = "Cronos_Testnet";
2482
2485
  Blockchain["Edge"] = "Edge";
2483
2486
  Blockchain["Edge_Testnet"] = "Edge_Testnet";
2484
2487
  Blockchain["Ethereum"] = "Ethereum";
@@ -2561,6 +2564,7 @@ var BridgeChain;
2561
2564
  BridgeChain["Avalanche"] = "Avalanche";
2562
2565
  BridgeChain["Base"] = "Base";
2563
2566
  BridgeChain["Codex"] = "Codex";
2567
+ BridgeChain["Cronos"] = "Cronos";
2564
2568
  BridgeChain["Edge"] = "Edge";
2565
2569
  BridgeChain["Ethereum"] = "Ethereum";
2566
2570
  BridgeChain["HyperEVM"] = "HyperEVM";
@@ -2585,6 +2589,7 @@ var BridgeChain;
2585
2589
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
2586
2590
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
2587
2591
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
2592
+ BridgeChain["Cronos_Testnet"] = "Cronos_Testnet";
2588
2593
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
2589
2594
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
2590
2595
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
@@ -3096,7 +3101,10 @@ var EarnChain;
3096
3101
  contracts: {
3097
3102
  v1: {
3098
3103
  wallet: GATEWAY_WALLET_EVM_TESTNET,
3099
- minter: GATEWAY_MINTER_EVM_TESTNET
3104
+ minter: GATEWAY_MINTER_EVM_TESTNET,
3105
+ // DepositForHandler the GenericExecutor calls to run a fast cross-chain
3106
+ // deposit into the GatewayWallet above.
3107
+ depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
3100
3108
  }
3101
3109
  },
3102
3110
  forwarderSupported: {
@@ -3630,6 +3638,96 @@ var EarnChain;
3630
3638
  }
3631
3639
  });
3632
3640
 
3641
+ /**
3642
+ * Cronos Mainnet chain definition
3643
+ * @remarks
3644
+ * This represents the official production network for the Cronos blockchain.
3645
+ * Cronos is an EVM-compatible blockchain.
3646
+ */ const Cronos = defineChain({
3647
+ type: 'evm',
3648
+ chain: Blockchain.Cronos,
3649
+ name: 'Cronos',
3650
+ title: 'Cronos Mainnet',
3651
+ nativeCurrency: {
3652
+ name: 'Cronos',
3653
+ symbol: 'CRO',
3654
+ decimals: 18
3655
+ },
3656
+ chainId: 25,
3657
+ isTestnet: false,
3658
+ explorerUrl: 'https://cronoscan.com/tx/{hash}',
3659
+ rpcEndpoints: [
3660
+ 'https://evm.cronos.org'
3661
+ ],
3662
+ eurcAddress: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
3663
+ usdcAddress: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
3664
+ usdtAddress: null,
3665
+ cctp: {
3666
+ domain: 32,
3667
+ contracts: {
3668
+ v2: {
3669
+ type: 'split',
3670
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
3671
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
3672
+ confirmations: 1,
3673
+ fastConfirmations: 1
3674
+ }
3675
+ },
3676
+ forwarderSupported: {
3677
+ source: false,
3678
+ destination: false
3679
+ }
3680
+ },
3681
+ kitContracts: {
3682
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
3683
+ }
3684
+ });
3685
+
3686
+ /**
3687
+ * Cronos Testnet chain definition
3688
+ * @remarks
3689
+ * This represents the official test network for the Cronos blockchain.
3690
+ * Cronos is an EVM-compatible blockchain.
3691
+ */ const CronosTestnet = defineChain({
3692
+ type: 'evm',
3693
+ chain: Blockchain.Cronos_Testnet,
3694
+ name: 'Cronos Testnet',
3695
+ title: 'Cronos Testnet',
3696
+ nativeCurrency: {
3697
+ name: 'CRO',
3698
+ symbol: 'tCRO',
3699
+ decimals: 18
3700
+ },
3701
+ chainId: 338,
3702
+ isTestnet: true,
3703
+ explorerUrl: 'https://explorer.cronos.org/testnet/tx/{hash}',
3704
+ rpcEndpoints: [
3705
+ 'https://evm-t3.cronos.org'
3706
+ ],
3707
+ eurcAddress: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
3708
+ usdcAddress: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
3709
+ usdtAddress: null,
3710
+ cctp: {
3711
+ domain: 32,
3712
+ contracts: {
3713
+ v2: {
3714
+ type: 'split',
3715
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
3716
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
3717
+ confirmations: 1,
3718
+ fastConfirmations: 1
3719
+ }
3720
+ },
3721
+ forwarderSupported: {
3722
+ source: false,
3723
+ destination: false
3724
+ }
3725
+ },
3726
+ kitContracts: {
3727
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
3728
+ }
3729
+ });
3730
+
3633
3731
  /**
3634
3732
  * Edge Mainnet chain definition
3635
3733
  * @remarks
@@ -5981,6 +6079,8 @@ var Chains = {
5981
6079
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
5982
6080
  Codex: Codex,
5983
6081
  CodexTestnet: CodexTestnet,
6082
+ Cronos: Cronos,
6083
+ CronosTestnet: CronosTestnet,
5984
6084
  Edge: Edge,
5985
6085
  EdgeTestnet: EdgeTestnet,
5986
6086
  Ethereum: Ethereum,
@@ -6122,7 +6222,10 @@ var Chains = {
6122
6222
  minter: zod.z.string({
6123
6223
  required_error: 'Gateway minter address is required. Please provide a valid contract address.',
6124
6224
  invalid_type_error: 'Gateway minter address must be a string.'
6125
- }).min(1, 'Gateway minter address cannot be empty.')
6225
+ }).min(1, 'Gateway minter address cannot be empty.'),
6226
+ depositForHandler: zod.z.string({
6227
+ invalid_type_error: 'Gateway depositForHandler address must be a string.'
6228
+ }).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
6126
6229
  }).strict() // Reject any additional properties not defined in the schema
6127
6230
  ;
6128
6231
  /**
@@ -6644,21 +6747,31 @@ const swapTokenEnumSchema = zod.z.enum([
6644
6747
  * returning the appropriate address based on the requested contract type.
6645
6748
  *
6646
6749
  * @param chain - The chain definition to resolve the contract address for
6647
- * @param contractType - The type of contract address to resolve ('tokenMessenger' or 'messageTransmitter')
6750
+ * @param contractType - The type of contract address to resolve ('tokenMessenger', 'messageTransmitter', or 'tokenMessengerWithFees')
6648
6751
  * @returns The contract address for the specified contract type
6649
6752
  * @throws Error when chain does not support CCTP v2 or has unsupported contract configuration
6753
+ * @throws Error when 'tokenMessengerWithFees' is requested but not configured on the chain
6650
6754
  */ const resolveCCTPV2ContractAddress = (chain, contractType)=>{
6651
6755
  // Handle custom bridge contract for tokenMessenger (burn transaction)
6652
- if (hasCustomContractSupport(chain, 'bridge') && chain.kitContracts?.bridge !== undefined) {
6756
+ if (contractType === 'tokenMessenger' && hasCustomContractSupport(chain, 'bridge') && chain.kitContracts?.bridge !== undefined) {
6653
6757
  return chain.kitContracts.bridge;
6654
6758
  }
6655
6759
  // At this point we know CCTP v2 is supported, so contracts exist
6656
6760
  const cctpConfig = chain.cctp;
6657
6761
  const contracts = cctpConfig.contracts.v2;
6762
+ // The `TokenMessengerWithFees` wrapper (prepaid FORWARD path) is an optional
6763
+ // deployment carried alongside both split and merged configurations.
6764
+ if (contractType === 'tokenMessengerWithFees') {
6765
+ const wrapper = contracts.tokenMessengerWithFees;
6766
+ if (wrapper === undefined || wrapper === '') {
6767
+ throw new Error(`TokenMessengerWithFees is not configured on chain ${chain.name}. The prepaid FORWARD path is unavailable on this chain.`);
6768
+ }
6769
+ return wrapper;
6770
+ }
6658
6771
  // Handle different contract types with explicit type checking
6659
6772
  switch(contracts.type){
6660
6773
  case 'split':
6661
- return contracts.tokenMessenger ;
6774
+ return contractType === 'tokenMessenger' ? contracts.tokenMessenger : contracts.messageTransmitter;
6662
6775
  case 'merged':
6663
6776
  return contracts.contract;
6664
6777
  default:
@@ -8046,6 +8159,7 @@ const swapTokenEnumSchema = zod.z.enum([
8046
8159
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
8047
8160
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
8048
8161
  [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
8162
+ [Blockchain.Cronos]: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
8049
8163
  [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
8050
8164
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
8051
8165
  [Blockchain.Hedera]: '0.0.456858',
@@ -8079,6 +8193,7 @@ const swapTokenEnumSchema = zod.z.enum([
8079
8193
  [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
8080
8194
  [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
8081
8195
  [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
8196
+ [Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
8082
8197
  [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
8083
8198
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
8084
8199
  [Blockchain.Hedera_Testnet]: '0.0.429274',
@@ -8151,6 +8266,7 @@ const swapTokenEnumSchema = zod.z.enum([
8151
8266
  // =========================================================================
8152
8267
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
8153
8268
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
8269
+ [Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
8154
8270
  [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
8155
8271
  [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
8156
8272
  [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
@@ -8159,6 +8275,7 @@ const swapTokenEnumSchema = zod.z.enum([
8159
8275
  // =========================================================================
8160
8276
  [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
8161
8277
  [Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
8278
+ [Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
8162
8279
  [Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
8163
8280
  }
8164
8281
  };
@@ -9509,7 +9626,7 @@ function resolveOptions(options) {
9509
9626
  }
9510
9627
 
9511
9628
  var name$2 = "@circle-fin/bridge-kit";
9512
- var version$3 = "1.11.1";
9629
+ var version$3 = "1.12.1";
9513
9630
  var pkg$3 = {
9514
9631
  name: name$2,
9515
9632
  version: version$3};
@@ -12013,6 +12130,144 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
12013
12130
  return false;
12014
12131
  };
12015
12132
 
12133
+ /**
12134
+ * The zero address, denoting a native-currency fee in a signed quote.
12135
+ */ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
12136
+ /**
12137
+ * Resolve the fee payment channel for a prepaid-FORWARD burn via `TokenMessengerWithFees`.
12138
+ *
12139
+ * Determines the native `msg.value` and the ERC-20 approvals required, honouring
12140
+ * the quote's `feeToken`:
12141
+ * - Native fee (`feeToken` is the zero address): attach exactly `feeTotalAmount`
12142
+ * as `msg.value`; approve only the burn amount.
12143
+ * - ERC-20 fee equal to the burn token (both USDC — the `isBurnTokenFee` case):
12144
+ * approve a single combined `amount + feeTotalAmount` and skip the redundant
12145
+ * second approval.
12146
+ * - ERC-20 fee different from the burn token: approve the burn amount and the fee
12147
+ * amount separately.
12148
+ *
12149
+ * This encodes only balance/allowance intent; it does not fetch balances. The
12150
+ * caller is responsible for a balance preflight against the fresh quote.
12151
+ *
12152
+ * @param params - The fee token, burn token, burn amount, and total fee amount.
12153
+ * @returns The resolved fee payment plan.
12154
+ * @throws KitError if `amount` or `feeTotalAmount` is negative.
12155
+ *
12156
+ * @example
12157
+ * ```typescript
12158
+ * // Native fee
12159
+ * resolveFeePayment({
12160
+ * feeToken: '0x0000000000000000000000000000000000000000',
12161
+ * burnToken: '0xUSDC...',
12162
+ * amount: 1_000_000n,
12163
+ * feeTotalAmount: 3_500_000n,
12164
+ * })
12165
+ * // → { isNativeFee: true, isBurnTokenFee: false, nativeValue: 3_500_000n,
12166
+ * // approvals: [{ token: '0xUSDC...', amount: 1_000_000n }] }
12167
+ * ```
12168
+ */ const resolveFeePayment = (params)=>{
12169
+ const { feeToken, burnToken, amount, feeTotalAmount } = params;
12170
+ if (typeof amount !== 'bigint' || amount < 0n) {
12171
+ throw createValidationFailedError$1('amount', amount, 'Must be a non-negative bigint');
12172
+ }
12173
+ if (typeof feeTotalAmount !== 'bigint' || feeTotalAmount < 0n) {
12174
+ throw createValidationFailedError$1('feeTotalAmount', feeTotalAmount, 'Must be a non-negative bigint');
12175
+ }
12176
+ const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS;
12177
+ const isBurnTokenFee = !isNativeFee && feeToken.toLowerCase() === burnToken.toLowerCase();
12178
+ if (isNativeFee) {
12179
+ return {
12180
+ isNativeFee: true,
12181
+ isBurnTokenFee: false,
12182
+ nativeValue: feeTotalAmount,
12183
+ approvals: [
12184
+ {
12185
+ token: burnToken,
12186
+ amount
12187
+ }
12188
+ ]
12189
+ };
12190
+ }
12191
+ if (isBurnTokenFee) {
12192
+ // Fee and burn draw on the same token — a single combined approval covers
12193
+ // both; the redundant second approval is skipped.
12194
+ return {
12195
+ isNativeFee: false,
12196
+ isBurnTokenFee: true,
12197
+ nativeValue: 0n,
12198
+ approvals: [
12199
+ {
12200
+ token: burnToken,
12201
+ amount: amount + feeTotalAmount
12202
+ }
12203
+ ]
12204
+ };
12205
+ }
12206
+ return {
12207
+ isNativeFee: false,
12208
+ isBurnTokenFee: false,
12209
+ nativeValue: 0n,
12210
+ approvals: [
12211
+ {
12212
+ token: burnToken,
12213
+ amount
12214
+ },
12215
+ {
12216
+ token: feeToken,
12217
+ amount: feeTotalAmount
12218
+ }
12219
+ ]
12220
+ };
12221
+ };
12222
+
12223
+ /**
12224
+ * The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
12225
+ * hookData must start with.
12226
+ */ const CCTP_FORWARD_MAGIC_HEX = Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
12227
+ /**
12228
+ * Determine whether a hookData blob begins with the `cctp-forward` envelope.
12229
+ *
12230
+ * The prepaid FORWARD path requires the GenericExecutor blob to be wrapped in a
12231
+ * `cctp-forward` frame; without it the wrapper reverts `ForwardFeeWithoutHook`.
12232
+ *
12233
+ * @param hookData - The 0x-prefixed hookData hex string.
12234
+ * @returns True when the hookData starts with the `cctp-forward` magic.
12235
+ *
12236
+ * @example
12237
+ * ```typescript
12238
+ * hasForwardHook('0x636374702d666f7277617264...') // true
12239
+ * hasForwardHook('0xdeadbeef') // false
12240
+ * ```
12241
+ */ const hasForwardHook = (hookData)=>{
12242
+ if (typeof hookData !== 'string') {
12243
+ return false;
12244
+ }
12245
+ const normalized = (hookData.startsWith('0x') ? hookData.slice(2) : hookData).toLowerCase();
12246
+ return normalized.startsWith(CCTP_FORWARD_MAGIC_HEX);
12247
+ };
12248
+ /**
12249
+ * Assert that a hookData blob is forward-friendly for the prepaid FORWARD path.
12250
+ *
12251
+ * The prepaid FORWARD path always requests a FORWARD fee item, so the wrapper
12252
+ * requires the hookData to start with a `cctp-forward` frame. Validating this
12253
+ * before the burn surfaces the guaranteed `ForwardFeeWithoutHook` revert as a
12254
+ * typed input error instead of an on-chain failure.
12255
+ *
12256
+ * @param hookData - The 0x-prefixed hookData hex string.
12257
+ * @throws KitError (`INPUT_VALIDATION_FAILED`) if the hookData is missing or lacks
12258
+ * the `cctp-forward` frame.
12259
+ *
12260
+ * @example
12261
+ * ```typescript
12262
+ * assertForwardHookData(geForwardHookData) // ok
12263
+ * assertForwardHookData('0xdeadbeef') // throws — would revert ForwardFeeWithoutHook
12264
+ * ```
12265
+ */ const assertForwardHookData = (hookData)=>{
12266
+ if (!hasForwardHook(hookData)) {
12267
+ throw createValidationFailedError$1('hookData', hookData, 'Prepaid FORWARD burns require a cctp-forward-wrapped hookData; without it the TokenMessengerWithFees wrapper reverts ForwardFeeWithoutHook');
12268
+ }
12269
+ };
12270
+
12016
12271
  /**
12017
12272
  * Type guard to validate the forwardFee object structure.
12018
12273
  *
@@ -12169,6 +12424,15 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
12169
12424
  ;
12170
12425
  const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
12171
12426
  ;
12427
+ // Hard execution caps: observed max + ~30% buffer, used as gasLimit overrides on
12428
+ // chains whose eth_estimateGas under-reports (e.g. Cronos EIP-7623 calldata floor).
12429
+ // Kept separate from the fee-estimate averages above.
12430
+ const APPROVE_GAS_LIMIT_EVM = 100_000n // ERC-20 approve observed max ~46k
12431
+ ;
12432
+ const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 300_000n // observed max 226_506 + ~30%
12433
+ ;
12434
+ const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839 + ~30%
12435
+ ;
12172
12436
  /**
12173
12437
  * The minimum finality threshold for CCTPv2 transfers.
12174
12438
  *
@@ -12197,6 +12461,27 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12197
12461
  'Content-Type': 'application/json'
12198
12462
  }
12199
12463
  };
12464
+ /**
12465
+ * Merges caller-provided polling overrides on top of {@link DEFAULT_CONFIG}.
12466
+ *
12467
+ * Headers are merged independently so caller-supplied headers augment the
12468
+ * defaults (such as `Content-Type`) rather than replacing them wholesale.
12469
+ *
12470
+ * @param config - Caller-provided polling configuration overrides
12471
+ * @param internalDefaults - Internal defaults applied before `config` (for example a
12472
+ * reduced `maxRetries` for one-shot requests); `config` still wins on conflict
12473
+ * @returns The effective polling configuration
12474
+ * @internal
12475
+ */ const mergeAttestationConfig = (config, internalDefaults = {})=>({
12476
+ ...DEFAULT_CONFIG,
12477
+ ...internalDefaults,
12478
+ ...config,
12479
+ headers: {
12480
+ ...DEFAULT_CONFIG.headers,
12481
+ ...internalDefaults.headers,
12482
+ ...config.headers
12483
+ }
12484
+ });
12200
12485
  /**
12201
12486
  * Type guard that verifies if an unknown value matches the AttestationMessage shape
12202
12487
  * and has all required properties.
@@ -12343,10 +12628,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12343
12628
  * ```
12344
12629
  */ const fetchAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12345
12630
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12346
- const effectiveConfig = {
12347
- ...DEFAULT_CONFIG,
12348
- ...config
12349
- };
12631
+ const effectiveConfig = mergeAttestationConfig(config);
12350
12632
  return await pollApiGet(url, isAttestationResponse, effectiveConfig);
12351
12633
  };
12352
12634
  /**
@@ -12389,11 +12671,9 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12389
12671
  */ const fetchAttestationWithoutStatusCheck = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12390
12672
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12391
12673
  // Use minimal retries since we're just fetching existing data
12392
- const effectiveConfig = {
12393
- ...DEFAULT_CONFIG,
12394
- maxRetries: 3,
12395
- ...config
12396
- };
12674
+ const effectiveConfig = mergeAttestationConfig(config, {
12675
+ maxRetries: 3
12676
+ });
12397
12677
  return await pollApiGet(url, isAttestationResponseWithoutStatusCheck, effectiveConfig);
12398
12678
  };
12399
12679
  /**
@@ -12453,10 +12733,7 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12453
12733
  * ```
12454
12734
  */ const fetchReAttestedAttestation = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12455
12735
  const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12456
- const effectiveConfig = {
12457
- ...DEFAULT_CONFIG,
12458
- ...config
12459
- };
12736
+ const effectiveConfig = mergeAttestationConfig(config);
12460
12737
  return await pollApiGet(url, isReAttestedAttestationResponse, effectiveConfig);
12461
12738
  };
12462
12739
  /**
@@ -12522,81 +12799,206 @@ const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_8
12522
12799
  */ const requestReAttestation = async (nonce, isTestnet, config = {})=>{
12523
12800
  const url = buildReAttestUrl(nonce, isTestnet);
12524
12801
  // Use minimal retries since we're just submitting a request, not polling for state
12525
- const effectiveConfig = {
12526
- ...DEFAULT_CONFIG,
12527
- maxRetries: 3,
12528
- ...config
12529
- };
12802
+ const effectiveConfig = mergeAttestationConfig(config, {
12803
+ maxRetries: 3
12804
+ });
12530
12805
  return await pollApiPost(url, {}, isReAttestationResponse, effectiveConfig);
12531
12806
  };
12532
12807
 
12533
- const assertCCTPv2WalletContextSymbol = Symbol('assertCCTPv2WalletContext');
12534
12808
  /**
12535
- * Asserts that the provided parameters match the CCTPv2 wallet context interface.
12536
- * The validation includes:
12537
- * - Basic wallet context validation (adapter, address, chain)
12538
- * - CCTPv2-specific chain validation (must be an EVM chain)
12539
- *
12540
- * @param params - The parameters to validate
12541
- * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
12542
- *
12543
- * @example
12544
- * ```typescript
12545
- * import { assertCCTPv2WalletContext } from '@circle-fin/provider-cctp-v2'
12546
- * import { Ethereum } from '@core/chains'
12809
+ * Type guard that checks if the relayer has confirmed the mint transaction.
12547
12810
  *
12548
- * // Prepare wallet context
12549
- * const context = {
12550
- * adapter: {
12551
- * prepare: async () => ({ data: 'prepared transaction' }),
12552
- * waitForTransaction: async () => ({ status: 'confirmed' })
12553
- * },
12554
- * address: '0x1234567890123456789012345678901234567890',
12555
- * chain: {
12556
- * ...Ethereum,
12557
- * usdcAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
12558
- * cctp: {
12559
- * domain: 1,
12560
- * contracts: {
12561
- * v2: {
12562
- * tokenMessenger: '0xTokenMessenger',
12563
- * messageTransmitter: '0xMessageTransmitter'
12564
- * }
12565
- * }
12566
- * }
12567
- * }
12568
- * }
12811
+ * This function validates that:
12812
+ * 1. The response has valid AttestationResponse structure
12813
+ * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
12569
12814
  *
12570
- * // This will throw if validation fails
12571
- * assertCCTPv2WalletContext(context)
12815
+ * If forwardState is 'FAILED', throws a non-retryable KitError.
12816
+ * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
12572
12817
  *
12573
- * // If we get here, context is guaranteed to be valid
12574
- * console.log('CCTPv2 wallet context is valid')
12575
- * ```
12576
- */ function assertCCTPv2WalletContext(params) {
12577
- // First validate basic wallet context
12578
- validateWithStateTracking(params, walletContextSchema, 'CCTPv2 wallet context', assertCCTPv2WalletContextSymbol);
12579
- // After validation, we know params is WalletContext
12580
- const context = params;
12581
- // Validate USDC support
12582
- if (context.chain.usdcAddress === null) {
12583
- throw createInvalidChainError(context.chain.name, 'Does not have USDC configured');
12818
+ * @param obj - The value to check, typically a parsed JSON response
12819
+ * @returns True if the relayer has confirmed the mint
12820
+ * @throws {KitError} With FATAL recoverability if structure is invalid
12821
+ * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
12822
+ * @throws {KitError} With RETRYABLE recoverability if still pending
12823
+ * @internal
12824
+ */ const isRelayerMintConfirmed = (obj)=>{
12825
+ // First check if the structure is valid
12826
+ if (!hasValidAttestationStructure(obj)) {
12827
+ throw new KitError({
12828
+ ...InputError.VALIDATION_FAILED,
12829
+ recoverability: 'FATAL',
12830
+ message: 'Invalid attestation response structure from IRIS API.'
12831
+ });
12584
12832
  }
12585
- // Validate CCTPv2 support
12586
- if (!isCCTPV2Supported(context.chain)) {
12587
- throw createInvalidChainError(context.chain.name, 'Does not support CCTPv2');
12833
+ // Find the first message (typically there's only one)
12834
+ const message = obj.messages[0];
12835
+ if (!message) {
12836
+ throw new KitError({
12837
+ ...InputError.VALIDATION_FAILED,
12838
+ recoverability: 'FATAL',
12839
+ message: 'No attestation messages found in IRIS API response.'
12840
+ });
12588
12841
  }
12589
- }
12590
-
12591
- const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
12842
+ // Check for FAILED state - this is a permanent failure
12843
+ if (message.forwardState === 'FAILED') {
12844
+ throw new KitError({
12845
+ ...NetworkError.RELAYER_FORWARD_FAILED,
12846
+ recoverability: 'RESUMABLE',
12847
+ 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.',
12848
+ cause: {
12849
+ trace: {
12850
+ eventNonce: message.eventNonce,
12851
+ attestation: message.attestation,
12852
+ message: message.message
12853
+ }
12854
+ }
12855
+ });
12856
+ }
12857
+ // Check if mint is confirmed (or complete) with a valid transaction hash
12858
+ // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
12859
+ if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
12860
+ return true;
12861
+ }
12862
+ // Still pending or not yet processed - throw RETRYABLE error to continue polling
12863
+ throw new KitError({
12864
+ ...NetworkError.RELAYER_PENDING,
12865
+ recoverability: 'RETRYABLE',
12866
+ message: 'Relayer mint not ready. Waiting for confirmation.'
12867
+ });
12868
+ };
12592
12869
  /**
12593
- * Asserts that the provided parameters match the CCTPv2 bridge parameters interface.
12594
- * The validation includes:
12595
- * - Basic parameter structure and types
12596
- * - Amount validation (non-empty numeric string \> 0)
12597
- * - Wallet address format validation (must be valid Ethereum address)
12598
- * - Chain definition validation (must be a valid chain with required properties)
12599
- * - Adapter validation (must implement required methods)
12870
+ * Polls the attestation API until the relayer's mint transaction is confirmed.
12871
+ *
12872
+ * This function is used when `useForwarder` is enabled. Instead of the user
12873
+ * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
12874
+ * This function polls until the relayer has submitted and confirmed the mint transaction.
12875
+ *
12876
+ * @remarks
12877
+ * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
12878
+ * - Throws immediately if `forwardState` is 'FAILED'
12879
+ * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
12880
+ * - Returns the attestation message with `forwardTxHash` populated
12881
+ *
12882
+ * @param sourceDomainId - The CCTP domain ID of the source chain
12883
+ * @param transactionHash - The transaction hash of the burn operation
12884
+ * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
12885
+ * @param config - Optional configuration overrides for polling behavior
12886
+ * @returns The attestation message with confirmed forwardTxHash
12887
+ * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
12888
+ * @throws {KitError} If timeout is reached while still pending
12889
+ *
12890
+ * @example
12891
+ * ```typescript
12892
+ * const attestation = await fetchRelayerMint(0, '0xabc...', false)
12893
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
12894
+ * ```
12895
+ */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
12896
+ const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
12897
+ const effectiveConfig = mergeAttestationConfig(config);
12898
+ let response;
12899
+ try {
12900
+ response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
12901
+ } catch (error) {
12902
+ // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
12903
+ if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
12904
+ throw new KitError({
12905
+ ...NetworkError.RELAYER_FORWARD_FAILED,
12906
+ recoverability: error.recoverability,
12907
+ message: error.message,
12908
+ cause: {
12909
+ ...error.cause,
12910
+ trace: {
12911
+ ...error.cause?.trace,
12912
+ burnTxHash: transactionHash
12913
+ }
12914
+ }
12915
+ });
12916
+ }
12917
+ throw error;
12918
+ }
12919
+ // Return the first message (which should have forwardTxHash)
12920
+ // Note: This check is needed for TypeScript type safety even though
12921
+ // isRelayerMintConfirmed validates messages[0] exists. The type guard
12922
+ // narrows the type at the call site, but TypeScript can't infer that
12923
+ // the array still has elements after pollApiGet returns.
12924
+ const message = response.messages[0];
12925
+ if (!message) {
12926
+ throw new KitError({
12927
+ ...InputError.VALIDATION_FAILED,
12928
+ recoverability: 'FATAL',
12929
+ message: 'No attestation messages found in response after polling.'
12930
+ });
12931
+ }
12932
+ return message;
12933
+ };
12934
+
12935
+ const assertCCTPv2WalletContextSymbol = Symbol('assertCCTPv2WalletContext');
12936
+ /**
12937
+ * Asserts that the provided parameters match the CCTPv2 wallet context interface.
12938
+ * The validation includes:
12939
+ * - Basic wallet context validation (adapter, address, chain)
12940
+ * - CCTPv2-specific chain validation (must be an EVM chain)
12941
+ *
12942
+ * @param params - The parameters to validate
12943
+ * @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
12944
+ *
12945
+ * @example
12946
+ * ```typescript
12947
+ * import { assertCCTPv2WalletContext } from '@circle-fin/provider-cctp-v2'
12948
+ * import { Ethereum } from '@core/chains'
12949
+ *
12950
+ * // Prepare wallet context
12951
+ * const context = {
12952
+ * adapter: {
12953
+ * prepare: async () => ({ data: 'prepared transaction' }),
12954
+ * waitForTransaction: async () => ({ status: 'confirmed' })
12955
+ * },
12956
+ * address: '0x1234567890123456789012345678901234567890',
12957
+ * chain: {
12958
+ * ...Ethereum,
12959
+ * usdcAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
12960
+ * cctp: {
12961
+ * domain: 1,
12962
+ * contracts: {
12963
+ * v2: {
12964
+ * tokenMessenger: '0xTokenMessenger',
12965
+ * messageTransmitter: '0xMessageTransmitter'
12966
+ * }
12967
+ * }
12968
+ * }
12969
+ * }
12970
+ * }
12971
+ *
12972
+ * // This will throw if validation fails
12973
+ * assertCCTPv2WalletContext(context)
12974
+ *
12975
+ * // If we get here, context is guaranteed to be valid
12976
+ * console.log('CCTPv2 wallet context is valid')
12977
+ * ```
12978
+ */ function assertCCTPv2WalletContext(params) {
12979
+ // First validate basic wallet context
12980
+ validateWithStateTracking(params, walletContextSchema, 'CCTPv2 wallet context', assertCCTPv2WalletContextSymbol);
12981
+ // After validation, we know params is WalletContext
12982
+ const context = params;
12983
+ // Validate USDC support
12984
+ if (context.chain.usdcAddress === null) {
12985
+ throw createInvalidChainError(context.chain.name, 'Does not have USDC configured');
12986
+ }
12987
+ // Validate CCTPv2 support
12988
+ if (!isCCTPV2Supported(context.chain)) {
12989
+ throw createInvalidChainError(context.chain.name, 'Does not support CCTPv2');
12990
+ }
12991
+ }
12992
+
12993
+ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
12994
+ /**
12995
+ * Asserts that the provided parameters match the CCTPv2 bridge parameters interface.
12996
+ * The validation includes:
12997
+ * - Basic parameter structure and types
12998
+ * - Amount validation (non-empty numeric string \> 0)
12999
+ * - Wallet address format validation (must be valid Ethereum address)
13000
+ * - Chain definition validation (must be a valid chain with required properties)
13001
+ * - Adapter validation (must implement required methods)
12600
13002
  * - Optional config validation (transfer speed and max fee)
12601
13003
  * - Network compatibility (source and destination chains must both be testnet or both mainnet)
12602
13004
  * - CCTPv2-specific wallet context validations
@@ -12797,6 +13199,109 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
12797
13199
  }
12798
13200
  }
12799
13201
 
13202
+ /**
13203
+ * Assert that `params` is a well-formed {@link BurnWithFeesParams} object.
13204
+ *
13205
+ * Validates the full public-boundary input before any field destructuring,
13206
+ * `BigInt()` coercion, or adapter preparation runs, so malformed JS-caller
13207
+ * inputs always produce typed `KitError` validation failures.
13208
+ *
13209
+ * Checks performed (in order):
13210
+ * - `params` must be a non-null plain object
13211
+ * - `source` — valid CCTP v2 wallet context (via `assertCCTPv2WalletContext`)
13212
+ * - `destinationChain` — present and supports CCTP v2
13213
+ * - source and destination chains must both be testnet or both mainnet
13214
+ * - source and destination chains must differ
13215
+ * - `executor` — non-empty string
13216
+ * - `amount` — bigint or non-empty string coercible to bigint
13217
+ * - `feeTotalAmount` — bigint or non-empty string coercible to bigint
13218
+ * - `feeToken` — valid EVM address (`0x` + 40 hex chars)
13219
+ * - `claim.signedQuote` — valid `0x`-prefixed hex string
13220
+ * - `claim.refundAddress` — valid EVM address
13221
+ * - `hookData` — valid `0x`-prefixed hex string when present
13222
+ *
13223
+ * @param params - The value to validate.
13224
+ * @throws {KitError} If any field is missing or invalid.
13225
+ *
13226
+ * @example
13227
+ * ```typescript
13228
+ * assertBurnWithFeesParams(params)
13229
+ * // params is now typed as BurnWithFeesParams and safe to use
13230
+ * const { source, destinationChain, amount } = params
13231
+ * ```
13232
+ */ function assertBurnWithFeesParams(params) {
13233
+ if (params === null || typeof params !== 'object' || Array.isArray(params)) {
13234
+ throw createValidationFailedError$1('params', params, 'Must be a non-null plain object');
13235
+ }
13236
+ const p = params;
13237
+ // Source wallet context
13238
+ assertCCTPv2WalletContext(p['source']);
13239
+ const source = p['source'];
13240
+ // destinationChain
13241
+ const destinationChain = p['destinationChain'];
13242
+ if (destinationChain === null || destinationChain === undefined || typeof destinationChain !== 'object') {
13243
+ throw createValidationFailedError$1('destinationChain', destinationChain, 'Must be a chain definition object');
13244
+ }
13245
+ if (!isCCTPV2Supported(destinationChain)) {
13246
+ throw createValidationFailedError$1('destinationChain', destinationChain, 'destinationChain must support CCTP v2');
13247
+ }
13248
+ const dest = destinationChain;
13249
+ // Testnet / mainnet mismatch
13250
+ if (source.chain.isTestnet !== dest.isTestnet) {
13251
+ throw createNetworkMismatchError(source.chain, dest);
13252
+ }
13253
+ // Same-chain guard
13254
+ if (source.chain.name === dest.name) {
13255
+ throw createUnsupportedRouteError(source.chain.name, dest.name);
13256
+ }
13257
+ // executor
13258
+ const executor = p['executor'];
13259
+ if (typeof executor !== 'string' || executor === '') {
13260
+ throw createValidationFailedError$1('executor', executor, 'A GenericExecutor address is required (used for both mintRecipient and destinationCaller)');
13261
+ }
13262
+ // amount
13263
+ const rawAmount = p['amount'];
13264
+ if (typeof rawAmount !== 'bigint' && typeof rawAmount !== 'string' || rawAmount === '') {
13265
+ throw createValidationFailedError$1('amount', rawAmount, 'Must be a bigint or a numeric string');
13266
+ }
13267
+ try {
13268
+ BigInt(rawAmount);
13269
+ } catch {
13270
+ throw createValidationFailedError$1('amount', rawAmount, 'Must be a valid integer value coercible to bigint');
13271
+ }
13272
+ // feeTotalAmount
13273
+ const rawFee = p['feeTotalAmount'];
13274
+ if (typeof rawFee !== 'bigint' && typeof rawFee !== 'string' || rawFee === '') {
13275
+ throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a bigint or a numeric string');
13276
+ }
13277
+ try {
13278
+ BigInt(rawFee);
13279
+ } catch {
13280
+ throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a valid integer value coercible to bigint');
13281
+ }
13282
+ // feeToken
13283
+ if (!evmAddressSchema.safeParse(p['feeToken']).success) {
13284
+ throw createValidationFailedError$1('feeToken', p['feeToken'], 'Must be a valid EVM address (use the zero address for a native fee)');
13285
+ }
13286
+ // claim
13287
+ const rawClaim = p['claim'];
13288
+ if (rawClaim === null || typeof rawClaim !== 'object' || Array.isArray(rawClaim)) {
13289
+ throw createValidationFailedError$1('claim', rawClaim, 'Must be an object with signedQuote and refundAddress');
13290
+ }
13291
+ const claim = rawClaim;
13292
+ if (!hexStringSchema.safeParse(claim['signedQuote']).success) {
13293
+ throw createValidationFailedError$1('claim.signedQuote', claim['signedQuote'], 'Must be a valid hex string starting with 0x');
13294
+ }
13295
+ if (!evmAddressSchema.safeParse(claim['refundAddress']).success) {
13296
+ throw createValidationFailedError$1('claim.refundAddress', claim['refundAddress'], 'Must be a valid EVM address');
13297
+ }
13298
+ // hookData (optional)
13299
+ const hookData = p['hookData'];
13300
+ if (hookData !== undefined && hookData !== '' && !hexStringSchema.safeParse(hookData).success) {
13301
+ throw createValidationFailedError$1('hookData', hookData, 'Must be a valid hex string starting with 0x');
13302
+ }
13303
+ }
13304
+
12800
13305
  /**
12801
13306
  * CCTP bridge step names that can occur in the bridging flow.
12802
13307
  *
@@ -13404,6 +13909,8 @@ function hasPendingState(analysis, result) {
13404
13909
  * - `adapter`: The adapter that will execute the transaction
13405
13910
  * - `confirmations`: The number of confirmations to wait for (defaults to 1)
13406
13911
  * - `timeout`: The timeout for the request in milliseconds
13912
+ * - `gasLimit`: Optional explicit gas limit (number) forwarded to EVM execute,
13913
+ * bypassing `eth_estimateGas`; ignored for non-EVM requests
13407
13914
  * @returns The bridge step with the transaction details and explorer URL
13408
13915
  * @throws If the transaction execution fails
13409
13916
  *
@@ -13418,7 +13925,7 @@ function hasPendingState(analysis, result) {
13418
13925
  * })
13419
13926
  * console.log('Transaction hash:', step.txHash)
13420
13927
  * ```
13421
- */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout }) {
13928
+ */ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasLimit }) {
13422
13929
  const step = {
13423
13930
  name,
13424
13931
  state: 'pending'
@@ -13431,7 +13938,9 @@ function hasPendingState(analysis, result) {
13431
13938
  step.state = 'noop';
13432
13939
  return step;
13433
13940
  }
13434
- const txHash = await request.execute();
13941
+ const txHash = request.type === 'evm' && gasLimit !== undefined ? await request.execute({
13942
+ gasLimit
13943
+ }) : await request.execute();
13435
13944
  step.txHash = txHash;
13436
13945
  const retryOptions = {
13437
13946
  isRetryable: (err)=>isRetryableError$1(parseBlockchainError(err, {
@@ -13503,7 +14012,8 @@ function hasPendingState(analysis, result) {
13503
14012
  name: 'approve',
13504
14013
  adapter: params.source.adapter,
13505
14014
  chain: params.source.chain,
13506
- request: await provider.approve(params.source, approvalAmount)
14015
+ request: await provider.approve(params.source, approvalAmount),
14016
+ gasLimit: Number(APPROVE_GAS_LIMIT_EVM)
13507
14017
  });
13508
14018
  }
13509
14019
 
@@ -13530,7 +14040,8 @@ function hasPendingState(analysis, result) {
13530
14040
  name: 'burn',
13531
14041
  adapter: params.source.adapter,
13532
14042
  chain: params.source.chain,
13533
- request: await provider.burn(params)
14043
+ request: await provider.burn(params),
14044
+ gasLimit: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
13534
14045
  });
13535
14046
  }
13536
14047
 
@@ -13616,11 +14127,18 @@ function hasPendingState(analysis, result) {
13616
14127
  */ async function bridgeMint({ params, provider }, attestation) {
13617
14128
  // Validate attestation message matches transfer params
13618
14129
  await assertCCTPv2AttestationParams(attestation, params);
14130
+ const mintRequest = await provider.mint(params.source, params.destination, attestation);
13619
14131
  const step = await executePreparedChainRequest({
13620
14132
  name: 'mint',
13621
14133
  adapter: params.destination.adapter,
13622
14134
  chain: params.destination.chain,
13623
- request: await provider.mint(params.source, params.destination, attestation)
14135
+ request: mintRequest,
14136
+ // Some chains (e.g. Cronos) enforce an EIP-7623 calldata gas floor that
14137
+ // eth_estimateGas does not account for, returning a below-floor value
14138
+ // without reverting. Pinning to a value above the observed execution max
14139
+ // (310_839) bypasses re-estimation and guarantees we clear both the floor
14140
+ // and the actual execution cost.
14141
+ gasLimit: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
13624
14142
  });
13625
14143
  // Add forwarded: false for non-relayer mints
13626
14144
  return {
@@ -13658,136 +14176,6 @@ const mockAttestationMessage = {
13658
14176
  delayReason: null
13659
14177
  };
13660
14178
 
13661
- /**
13662
- * Type guard that checks if the relayer has confirmed the mint transaction.
13663
- *
13664
- * This function validates that:
13665
- * 1. The response has valid AttestationResponse structure
13666
- * 2. At least one message has forwardState === 'CONFIRMED' (or 'COMPLETE') and a valid forwardTxHash
13667
- *
13668
- * If forwardState is 'FAILED', throws a non-retryable KitError.
13669
- * If forwardState is 'PENDING' or not present, throws a RETRYABLE KitError to continue polling.
13670
- *
13671
- * @param obj - The value to check, typically a parsed JSON response
13672
- * @returns True if the relayer has confirmed the mint
13673
- * @throws {KitError} With FATAL recoverability if structure is invalid
13674
- * @throws {KitError} With RESUMABLE recoverability if forwardState is 'FAILED'
13675
- * @throws {KitError} With RETRYABLE recoverability if still pending
13676
- * @internal
13677
- */ const isRelayerMintConfirmed = (obj)=>{
13678
- // First check if the structure is valid
13679
- if (!hasValidAttestationStructure(obj)) {
13680
- throw new KitError({
13681
- ...InputError.VALIDATION_FAILED,
13682
- recoverability: 'FATAL',
13683
- message: 'Invalid attestation response structure from IRIS API.'
13684
- });
13685
- }
13686
- // Find the first message (typically there's only one)
13687
- const message = obj.messages[0];
13688
- if (!message) {
13689
- throw new KitError({
13690
- ...InputError.VALIDATION_FAILED,
13691
- recoverability: 'FATAL',
13692
- message: 'No attestation messages found in IRIS API response.'
13693
- });
13694
- }
13695
- // Check for FAILED state - this is a permanent failure
13696
- if (message.forwardState === 'FAILED') {
13697
- throw new KitError({
13698
- ...NetworkError.RELAYER_FORWARD_FAILED,
13699
- recoverability: 'RESUMABLE',
13700
- 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.',
13701
- cause: {
13702
- trace: {
13703
- eventNonce: message.eventNonce,
13704
- attestation: message.attestation,
13705
- message: message.message
13706
- }
13707
- }
13708
- });
13709
- }
13710
- // Check if mint is confirmed (or complete) with a valid transaction hash
13711
- // We accept both CONFIRMED and COMPLETE since COMPLETE implies CONFIRMED
13712
- if ((message.forwardState === 'CONFIRMED' || message.forwardState === 'COMPLETE') && typeof message.forwardTxHash === 'string' && message.forwardTxHash.trim().length > 0) {
13713
- return true;
13714
- }
13715
- // Still pending or not yet processed - throw RETRYABLE error to continue polling
13716
- throw new KitError({
13717
- ...NetworkError.RELAYER_PENDING,
13718
- recoverability: 'RETRYABLE',
13719
- message: 'Relayer mint not ready. Waiting for confirmation.'
13720
- });
13721
- };
13722
- /**
13723
- * Polls the attestation API until the relayer's mint transaction is confirmed.
13724
- *
13725
- * This function is used when `useForwarder` is enabled. Instead of the user
13726
- * submitting the mint transaction, Circle's Orbit relayer handles it automatically.
13727
- * This function polls until the relayer has submitted and confirmed the mint transaction.
13728
- *
13729
- * @remarks
13730
- * - Uses a 20-minute timeout by default (600 retries × 2 seconds)
13731
- * - Throws immediately if `forwardState` is 'FAILED'
13732
- * - Waits for `forwardState` to be 'CONFIRMED' or 'COMPLETE' (COMPLETE implies CONFIRMED)
13733
- * - Returns the attestation message with `forwardTxHash` populated
13734
- *
13735
- * @param sourceDomainId - The CCTP domain ID of the source chain
13736
- * @param transactionHash - The transaction hash of the burn operation
13737
- * @param isTestnet - Whether this is for a testnet chain (true) or mainnet (false)
13738
- * @param config - Optional configuration overrides for polling behavior
13739
- * @returns The attestation message with confirmed forwardTxHash
13740
- * @throws {KitError} With code 'NETWORK_RELAYER_FORWARD_FAILED' if relayer failed
13741
- * @throws {KitError} If timeout is reached while still pending
13742
- *
13743
- * @example
13744
- * ```typescript
13745
- * const attestation = await fetchRelayerMint(0, '0xabc...', false)
13746
- * console.log('Relayer mint tx:', attestation.forwardTxHash)
13747
- * ```
13748
- */ const fetchRelayerMint = async (sourceDomainId, transactionHash, isTestnet, config = {})=>{
13749
- const url = buildIrisUrl(sourceDomainId, transactionHash, isTestnet);
13750
- const effectiveConfig = {
13751
- ...DEFAULT_CONFIG,
13752
- ...config
13753
- };
13754
- let response;
13755
- try {
13756
- response = await pollApiGet(url, isRelayerMintConfirmed, effectiveConfig);
13757
- } catch (error) {
13758
- // Enrich RELAYER_FORWARD_FAILED errors with the burn transaction hash
13759
- if (error instanceof KitError && error.name === 'NETWORK_RELAYER_FORWARD_FAILED') {
13760
- throw new KitError({
13761
- ...NetworkError.RELAYER_FORWARD_FAILED,
13762
- recoverability: error.recoverability,
13763
- message: error.message,
13764
- cause: {
13765
- ...error.cause,
13766
- trace: {
13767
- ...error.cause?.trace,
13768
- burnTxHash: transactionHash
13769
- }
13770
- }
13771
- });
13772
- }
13773
- throw error;
13774
- }
13775
- // Return the first message (which should have forwardTxHash)
13776
- // Note: This check is needed for TypeScript type safety even though
13777
- // isRelayerMintConfirmed validates messages[0] exists. The type guard
13778
- // narrows the type at the call site, but TypeScript can't infer that
13779
- // the array still has elements after pollApiGet returns.
13780
- const message = response.messages[0];
13781
- if (!message) {
13782
- throw new KitError({
13783
- ...InputError.VALIDATION_FAILED,
13784
- recoverability: 'FATAL',
13785
- message: 'No attestation messages found in response after polling.'
13786
- });
13787
- }
13788
- return message;
13789
- };
13790
-
13791
14179
  /**
13792
14180
  * Executes the mint step for forwarding mode where Circle's relayer handles the mint.
13793
14181
  *
@@ -13801,6 +14189,7 @@ const mockAttestationMessage = {
13801
14189
  * immediately after the API confirms the mint.
13802
14190
  *
13803
14191
  * @param params - The bridge parameters containing source, destination, amount and config
14192
+ * @param provider - The CCTP v2 bridging provider
13804
14193
  * @param context - The step context containing burnTxHash from the burn step
13805
14194
  * @returns Promise resolving to the bridge step with transaction details
13806
14195
  * @throws {KitError} If burnTxHash is not available in context
@@ -13809,10 +14198,13 @@ const mockAttestationMessage = {
13809
14198
  *
13810
14199
  * @example
13811
14200
  * ```typescript
13812
- * const step = await bridgeRelayerMint(params, { burnTxHash: '0x...' })
14201
+ * const step = await bridgeRelayerMint(
14202
+ * { params, provider },
14203
+ * { burnTxHash: '0x...' }
14204
+ * )
13813
14205
  * console.log('Relayer mint tx:', step.txHash)
13814
14206
  * ```
13815
- */ async function bridgeRelayerMint(params, context) {
14207
+ */ async function bridgeRelayerMint({ params, provider }, context) {
13816
14208
  const step = {
13817
14209
  name: 'mint',
13818
14210
  state: 'pending',
@@ -13828,7 +14220,7 @@ const mockAttestationMessage = {
13828
14220
  }
13829
14221
  try {
13830
14222
  // Poll attestation API until relayer confirms the mint
13831
- const attestation = await fetchRelayerMint(params.source.chain.cctp.domain, context.burnTxHash, params.source.chain.isTestnet ?? false);
14223
+ const attestation = await provider.fetchRelayerMint(params.source, context.burnTxHash);
13832
14224
  // Extract the relayer's mint transaction hash
13833
14225
  const forwardTxHash = attestation.forwardTxHash;
13834
14226
  if (!forwardTxHash) {
@@ -13922,7 +14314,7 @@ const mockAttestationMessage = {
13922
14314
  name: 'mint',
13923
14315
  // bridgeRelayerMint validates context.burnTxHash internally with a KitError
13924
14316
  // bridgeMint requires attestationData which is validated here
13925
- executor: useForwarder ? async (params, _provider, context)=>{
14317
+ executor: useForwarder ? async (params, provider, context)=>{
13926
14318
  if (!context) {
13927
14319
  throw new KitError({
13928
14320
  ...InputError.VALIDATION_FAILED,
@@ -13930,7 +14322,10 @@ const mockAttestationMessage = {
13930
14322
  message: 'Step context is required for relayer mint'
13931
14323
  });
13932
14324
  }
13933
- return bridgeRelayerMint(params, context);
14325
+ return bridgeRelayerMint({
14326
+ params,
14327
+ provider
14328
+ }, context);
13934
14329
  } : async (params, provider, context)=>{
13935
14330
  if (!context?.attestationData) {
13936
14331
  throw new KitError({
@@ -14115,10 +14510,15 @@ const mockAttestationMessage = {
14115
14510
  const burnCallData = burnRequest.getCallData();
14116
14511
  // batchExecute may throw before submission (wallet declined) but never
14117
14512
  // after — post-submission errors are returned as empty receipts.
14513
+ // The sender is threaded for adapters whose execution is routed through a
14514
+ // signing strategy (which has no wallet account to read it from); the
14515
+ // wallet-client path ignores it.
14118
14516
  const batchResult = await adapter.batchExecute([
14119
14517
  approveCallData,
14120
14518
  burnCallData
14121
- ], chain);
14519
+ ], chain, {
14520
+ fromAddress: params.source.address
14521
+ });
14122
14522
  const approveReceipt = batchResult.receipts[0];
14123
14523
  const burnReceipt = batchResult.receipts[1];
14124
14524
  const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
@@ -14261,7 +14661,7 @@ const mockAttestationMessage = {
14261
14661
  return step;
14262
14662
  }
14263
14663
 
14264
- var version$2 = "1.8.5";
14664
+ var version$2 = "1.10.0";
14265
14665
  var pkg$2 = {
14266
14666
  version: version$2};
14267
14667
 
@@ -14967,6 +15367,39 @@ var pkg$2 = {
14967
15367
  }
14968
15368
  }
14969
15369
 
15370
+ function isPlainObject(value) {
15371
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
15372
+ return false;
15373
+ }
15374
+ const prototype = Object.getPrototypeOf(value);
15375
+ return prototype === Object.prototype || prototype === null;
15376
+ }
15377
+ function assertHeadersConfig(headers, field) {
15378
+ if (headers === undefined) {
15379
+ return;
15380
+ }
15381
+ if (!isPlainObject(headers)) {
15382
+ throw createValidationFailedError$1(field, headers, `${field} must be a plain object with string header values when provided`);
15383
+ }
15384
+ for (const [name, value] of Object.entries(headers)){
15385
+ if (typeof value !== 'string') {
15386
+ throw createValidationFailedError$1(`${field}.${name}`, value, 'header values must be strings');
15387
+ }
15388
+ }
15389
+ }
15390
+ function assertCCTPV2Config(config) {
15391
+ if (!isPlainObject(config)) {
15392
+ throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
15393
+ }
15394
+ assertHeadersConfig(config['headers'], 'config.headers');
15395
+ const attestation = config['attestation'];
15396
+ if (attestation !== undefined) {
15397
+ if (!isPlainObject(attestation)) {
15398
+ throw createValidationFailedError$1('config.attestation', attestation, 'config.attestation must be a plain object when provided');
15399
+ }
15400
+ assertHeadersConfig(attestation['headers'], 'config.attestation.headers');
15401
+ }
15402
+ }
14970
15403
  /**
14971
15404
  * All chains that are supported by the CCTP v2 provider.
14972
15405
  *
@@ -15035,9 +15468,38 @@ var pkg$2 = {
15035
15468
  * @param config - Optional configuration overrides for the provider
15036
15469
  */ constructor(config = {}){
15037
15470
  super();
15471
+ assertCCTPV2Config(config);
15038
15472
  this.config = config;
15039
15473
  }
15040
15474
  /**
15475
+ * Resolves the effective polling configuration for an attestation request.
15476
+ *
15477
+ * Precedence (lowest to highest): provider `config.attestation`, then the
15478
+ * per-call `config`. Headers merge independently across
15479
+ * `config.attestation.headers`, the provider-level `config.headers`, and any
15480
+ * per-call `config.headers`, so a more specific header augments rather than
15481
+ * replaces the broader ones. The `headers` key is omitted entirely when no
15482
+ * headers are configured, leaving the attestation fetchers' defaults intact.
15483
+ *
15484
+ * @param config - Optional per-call polling configuration overrides
15485
+ * @returns The merged polling configuration passed to the attestation fetchers
15486
+ */ resolveAttestationConfig(config) {
15487
+ const headers = {
15488
+ ...this.config?.attestation?.headers,
15489
+ ...this.config?.headers,
15490
+ ...config?.headers
15491
+ };
15492
+ // Polling fields follow normal precedence; headers are merged separately
15493
+ // below so narrower config layers augment rather than replace broader ones.
15494
+ return {
15495
+ ...this.config?.attestation,
15496
+ ...config,
15497
+ ...Object.keys(headers).length > 0 ? {
15498
+ headers
15499
+ } : {}
15500
+ };
15501
+ }
15502
+ /**
15041
15503
  * Execute a cross-chain USDC bridge operation using the CCTP v2 protocol.
15042
15504
  *
15043
15505
  * This method orchestrates the complete CCTP v2 bridge flow including validation,
@@ -15354,7 +15816,7 @@ var pkg$2 = {
15354
15816
  throw new Error(`Failed to resolve operation context: ${error instanceof Error ? error.message : String(error)}`);
15355
15817
  }
15356
15818
  // Resolve spender address with proper error handling
15357
- const spenderAddress = resolveCCTPV2ContractAddress(chain);
15819
+ const spenderAddress = resolveCCTPV2ContractAddress(chain, 'tokenMessenger');
15358
15820
  // Prepare action parameters
15359
15821
  const actionParams = {
15360
15822
  amount: BigInt(amount),
@@ -15463,11 +15925,7 @@ var pkg$2 = {
15463
15925
  */ async fetchAttestation(source, transactionHash, config) {
15464
15926
  assertCCTPv2WalletContext(source);
15465
15927
  try {
15466
- // Merge configs: defaults <- global config <- per-call config
15467
- const effectiveConfig = {
15468
- ...this.config?.attestation,
15469
- ...config
15470
- };
15928
+ const effectiveConfig = this.resolveAttestationConfig(config);
15471
15929
  const response = await fetchAttestation(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
15472
15930
  const message = response.messages[0];
15473
15931
  if (!message) {
@@ -15484,6 +15942,49 @@ var pkg$2 = {
15484
15942
  }
15485
15943
  }
15486
15944
  /**
15945
+ * Polls attestation data until Circle's relayer mint transaction is confirmed.
15946
+ *
15947
+ * This method is used by forwarded transfers. It polls the same Iris
15948
+ * attestation endpoint as {@link CCTPV2BridgingProvider.fetchAttestation},
15949
+ * but waits for a completed relayer forward state and returns the attestation
15950
+ * message containing `forwardTxHash`.
15951
+ *
15952
+ * @typeParam TFromAdapterCapabilities - The type representing the capabilities of the source adapter
15953
+ * @param source - The source wallet context containing the chain definition and wallet address
15954
+ * @param transactionHash - The transaction hash of the burn operation
15955
+ * @param config - Optional polling configuration overrides for timeout, retries, delay, and headers
15956
+ * @returns A promise that resolves to the attestation message with `forwardTxHash`
15957
+ * @throws KitError If the relayer forward fails, the response is invalid, or polling times out
15958
+ *
15959
+ * @example
15960
+ * ```typescript
15961
+ * import { CCTPV2BridgingProvider } from '@circle-fin/provider-cctp-v2'
15962
+ * import { Chains } from '@core/chains'
15963
+ *
15964
+ * const provider = new CCTPV2BridgingProvider({
15965
+ * headers: { 'X-Partner-UUID': '00000000-0000-0000-0000-000000000000' },
15966
+ * })
15967
+ *
15968
+ * const attestation = await provider.fetchRelayerMint(
15969
+ * {
15970
+ * adapter,
15971
+ * chain: Chains.EthereumSepolia,
15972
+ * address: '0x1234...',
15973
+ * },
15974
+ * '0xabc123...',
15975
+ * )
15976
+ *
15977
+ * console.log('Relayer mint tx:', attestation.forwardTxHash)
15978
+ * ```
15979
+ */ async fetchRelayerMint(source, transactionHash, config) {
15980
+ assertCCTPv2WalletContext(source);
15981
+ if (typeof transactionHash !== 'string' || transactionHash.trim() === '') {
15982
+ throw createValidationFailedError$1('transactionHash', transactionHash, 'transactionHash must be a non-empty string');
15983
+ }
15984
+ const effectiveConfig = this.resolveAttestationConfig(config);
15985
+ return await fetchRelayerMint(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
15986
+ }
15987
+ /**
15487
15988
  * Requests a fresh attestation for an expired attestation.
15488
15989
  *
15489
15990
  * This method is used when the original attestation has expired before the mint
@@ -15537,11 +16038,7 @@ var pkg$2 = {
15537
16038
  throw new Error('Failed to re-attest: Invalid transaction hash');
15538
16039
  }
15539
16040
  try {
15540
- // Merge configs: defaults <- global config <- per-call config
15541
- const effectiveConfig = {
15542
- ...this.config?.attestation,
15543
- ...config
15544
- };
16041
+ const effectiveConfig = this.resolveAttestationConfig(config);
15545
16042
  // Step 1: Get existing attestation data to extract nonce
15546
16043
  const existingAttestation = await fetchAttestationWithoutStatusCheck(source.chain.cctp.domain, transactionHash, source.chain.isTestnet, effectiveConfig);
15547
16044
  const nonce = existingAttestation.messages[0]?.eventNonce;
@@ -15799,6 +16296,106 @@ var pkg$2 = {
15799
16296
  return await source.adapter.prepareAction('cctp.v2.depositForBurn', actionParams, resolvedContext);
15800
16297
  }
15801
16298
  /**
16299
+ * Prepare a prepaid-FORWARD burn through the `TokenMessengerWithFees` wrapper.
16300
+ *
16301
+ * Builds the source-chain `depositForBurnWithHookAndFees` call for the
16302
+ * GenericExecutor FORWARD path: fees are collected up front on the source chain
16303
+ * against a signed quote, `mintRecipient` and `destinationCaller` are both set to
16304
+ * the GenericExecutor, and the GE `hookData` is passed through unchanged.
16305
+ *
16306
+ * This is the low-level on-chain primitive behind the UBK `fastCrossChainDeposit`
16307
+ * and Bridge Kit `bridge({ deposit })` flows. The `hookData` and signed-quote
16308
+ * `claim` are produced elsewhere and passed in here:
16309
+ * - `hookData`: `buildForwardingHookDataWithPayload(version,
16310
+ * buildDepositForGenericExecutorPayload(...).hookData)` from `@core/utils`.
16311
+ * - `claim.signedQuote` / `feeToken` / `feeTotalAmount`: from `fetchFeeQuote`
16312
+ * (`@circle-fin/provider-fee-v1`), whose FORWARD item must be bound to the
16313
+ * SAME `hookData` and executor `destinationCaller` used here.
16314
+ *
16315
+ * The returned approvals and burn are NOT executed — the caller executes the
16316
+ * approvals first (in order) and then the burn. The fee payment channel matches
16317
+ * the quote's `feeToken`:
16318
+ * - Native fee: exactly `feeTotalAmount` is attached as the burn's `msg.value`;
16319
+ * only the burn amount is approved.
16320
+ * - USDC fee (same token as the burn): a single combined `amount + feeTotalAmount`
16321
+ * approval covers both; the redundant second approval is skipped.
16322
+ *
16323
+ * @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
16324
+ * @param params - The burn amount, executor, hookData, signed-quote claim, and fee.
16325
+ * @returns The prepared approvals, the prepared burn, and the resolved fee plan.
16326
+ * @throws {KitError} If the wallet context is invalid, `destinationChain` does not
16327
+ * support CCTP v2, the executor is missing, `amount` or `feeTotalAmount` is not
16328
+ * a bigint or a numeric string coercible to bigint, the hookData lacks a
16329
+ * `cctp-forward` frame (guaranteed `ForwardFeeWithoutHook`), or the operation
16330
+ * context cannot be resolved.
16331
+ *
16332
+ * @example
16333
+ * ```typescript
16334
+ * const { approvals, burn } = await provider.burnWithFees({
16335
+ * source,
16336
+ * destinationChain: Arc,
16337
+ * amount: 1_000_000n,
16338
+ * executor: genericExecutorAddress,
16339
+ * hookData: geForwardHookData,
16340
+ * claim: { signedQuote: '0x01...', refundAddress: userAddress },
16341
+ * feeToken: '0x0000000000000000000000000000000000000000', // native
16342
+ * feeTotalAmount: 3_500_000n,
16343
+ * })
16344
+ * for (const approval of approvals) await approval.execute()
16345
+ * const txHash = await burn.execute()
16346
+ * ```
16347
+ */ async burnWithFees(params) {
16348
+ assertBurnWithFeesParams(params);
16349
+ const { source, destinationChain, executor, hookData, claim, feeToken } = params;
16350
+ const amount = BigInt(params.amount);
16351
+ const feeTotalAmount = BigInt(params.feeTotalAmount);
16352
+ // Coupling guard: the prepaid FORWARD path always requests a FORWARD fee item,
16353
+ // so the hookData must carry a cctp-forward frame; otherwise the wrapper
16354
+ // reverts ForwardFeeWithoutHook. Surface it as a typed input error up front.
16355
+ assertForwardHookData(hookData);
16356
+ const burnToken = source.chain.usdcAddress;
16357
+ const feePayment = resolveFeePayment({
16358
+ feeToken,
16359
+ burnToken,
16360
+ amount,
16361
+ feeTotalAmount
16362
+ });
16363
+ // Resolve operation context from the source wallet context.
16364
+ const operationContext = this.extractOperationContext(source);
16365
+ let resolvedContext;
16366
+ try {
16367
+ resolvedContext = await resolveOperationContext(source.adapter, operationContext);
16368
+ } catch (error) {
16369
+ throw createValidationFailedError$1('source.adapter', undefined, `Failed to resolve operation context: ${error instanceof Error ? error.message : String(error)}`);
16370
+ }
16371
+ const context = resolvedContext;
16372
+ const wrapperAddress = resolveCCTPV2ContractAddress(source.chain, 'tokenMessengerWithFees');
16373
+ // Build the ERC-20 approvals to the wrapper (burn token, plus a distinct fee
16374
+ // token only when the fee is not paid in the burn token).
16375
+ const approvals = await Promise.all(feePayment.approvals.map(async (approval)=>source.adapter.prepareAction('token.approve', {
16376
+ tokenAddress: approval.token,
16377
+ delegate: wrapperAddress,
16378
+ amount: approval.amount
16379
+ }, context)));
16380
+ // Build the burn: mintRecipient AND destinationCaller are both the executor.
16381
+ const burn = await source.adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
16382
+ fromChain: source.chain,
16383
+ toChain: destinationChain,
16384
+ amount,
16385
+ mintRecipient: executor,
16386
+ destinationCaller: executor,
16387
+ hookData,
16388
+ claim,
16389
+ feeToken,
16390
+ feeTotalAmount
16391
+ }, context);
16392
+ return {
16393
+ approvals,
16394
+ burn,
16395
+ feePayment
16396
+ };
16397
+ }
16398
+ /**
15802
16399
  * Waits for a transaction to be mined and confirmed on the blockchain.
15803
16400
  *
15804
16401
  * This method should block until the transaction is confirmed on the blockchain.
@@ -15826,8 +16423,13 @@ var pkg$2 = {
15826
16423
  /**
15827
16424
  * The default providers that will be used in addition to the providers provided
15828
16425
  * to the BridgeKit constructor.
15829
- */ const getDefaultProviders = ()=>[
15830
- new CCTPV2BridgingProvider()
16426
+ *
16427
+ * @param config - Optional configuration forwarded to the default providers
16428
+ * @returns The default bridging providers
16429
+ */ const getDefaultProviders = (config = {})=>[
16430
+ new CCTPV2BridgingProvider(config.headers ? {
16431
+ headers: config.headers
16432
+ } : {})
15831
16433
  ];
15832
16434
 
15833
16435
  /**
@@ -16070,7 +16672,9 @@ var pkg$2 = {
16070
16672
  * ```
16071
16673
  */ constructor(config = {}){
16072
16674
  // Handle provider configuration
16073
- const defaultProviders = getDefaultProviders();
16675
+ const defaultProviders = getDefaultProviders(config.headers ? {
16676
+ headers: config.headers
16677
+ } : {});
16074
16678
  this.providers = [
16075
16679
  ...defaultProviders,
16076
16680
  ...config.providers ?? []
@@ -16650,6 +17254,9 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
16650
17254
  const kit = new BridgeKit({
16651
17255
  ...context.disableErrorReporting != null && {
16652
17256
  disableErrorReporting: context.disableErrorReporting
17257
+ },
17258
+ ...context.headers != null && {
17259
+ headers: context.headers
16653
17260
  }
16654
17261
  });
16655
17262
  if (hasBoth) {
@@ -16668,7 +17275,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
16668
17275
  };
16669
17276
 
16670
17277
  var name$1 = "@circle-fin/swap-kit";
16671
- var version$1 = "1.3.1";
17278
+ var version$1 = "1.4.0";
16672
17279
  var pkg$1 = {
16673
17280
  name: name$1,
16674
17281
  version: version$1};
@@ -16733,7 +17340,10 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
16733
17340
  }).min(1, 'kitKey must be a non-empty string').optional(),
16734
17341
  provider: zod.z.string({
16735
17342
  invalid_type_error: 'provider must be a string'
16736
- }).min(1, 'provider must be a non-empty string').optional()
17343
+ }).min(1, 'provider must be a non-empty string').optional(),
17344
+ batchTransactions: zod.z.boolean({
17345
+ invalid_type_error: 'batchTransactions must be a boolean'
17346
+ }).optional()
16737
17347
  });
16738
17348
  /**
16739
17349
  * Zod schema for adapter context.
@@ -17172,7 +17782,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17172
17782
  /**
17173
17783
  * Circle Stablecoin Service API Key.
17174
17784
  * Must be a valid API key format.
17175
- */ apiKey: apiKeySchema
17785
+ */ apiKey: apiKeySchema.optional()
17176
17786
  }).superRefine(requireCrossChainQuoteToAddress);
17177
17787
  /**
17178
17788
  * Zod schema for validating CreateSwapRequest parameters.
@@ -17230,7 +17840,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17230
17840
  /**
17231
17841
  * Circle Stablecoin Service API Key.
17232
17842
  * Must be a valid API key format.
17233
- */ apiKey: apiKeySchema
17843
+ */ apiKey: apiKeySchema.optional()
17234
17844
  });
17235
17845
  /**
17236
17846
  * Zod schema for validating GetSwapStatusResponse data.
@@ -17266,7 +17876,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17266
17876
  toChain: zod.z.string({
17267
17877
  invalid_type_error: 'toChain must be a string'
17268
17878
  }).min(1, 'toChain must be a non-empty string if provided').optional(),
17269
- apiKey: apiKeySchema
17879
+ apiKey: apiKeySchema.optional()
17270
17880
  });
17271
17881
  /**
17272
17882
  * Zod schema for validating CreateSwapResponse payloads.
@@ -17275,13 +17885,15 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17275
17885
  required_error: 'fee token is required',
17276
17886
  invalid_type_error: 'fee token must be a string'
17277
17887
  }).min(1, 'fee token must be a non-empty string'),
17278
- amount: feeAmountSchema
17888
+ amount: feeAmountSchema,
17889
+ decimals: zod.z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
17890
+ symbol: zod.z.string({
17891
+ invalid_type_error: 'fee token symbol must be a string'
17892
+ }).min(1, 'fee token symbol must be a non-empty string').optional()
17279
17893
  });
17280
17894
  /**
17281
17895
  * Developer fee item schema with basis field.
17282
- */ const createSwapDeveloperFeeItemSchema = zod.z.object({
17283
- token: zod.z.string().min(1, 'fee token must be a non-empty string'),
17284
- amount: feeAmountSchema,
17896
+ */ const createSwapDeveloperFeeItemSchema = createSwapFeeItemSchema.extend({
17285
17897
  basis: zod.z.enum([
17286
17898
  'inputAmount',
17287
17899
  'estimatedAmount'
@@ -17373,7 +17985,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
17373
17985
  addresses: zod.z.array(zod.z.string({
17374
17986
  invalid_type_error: 'addresses entries must be strings'
17375
17987
  }).min(1, 'addresses entries must be non-empty strings')).min(1, 'addresses must contain at least one entry when provided').max(MAX_RATE_ADDRESSES_PER_REQUEST, `addresses supports at most ${String(MAX_RATE_ADDRESSES_PER_REQUEST)} values per request`).optional(),
17376
- apiKey: apiKeySchema
17988
+ apiKey: apiKeySchema.optional()
17377
17989
  });
17378
17990
  /**
17379
17991
  * Zod schema for validating GetTokenRatesResponse payloads.
@@ -18596,7 +19208,7 @@ new Set(Object.values(Blockchain));
18596
19208
  registerKit(`${pkg$1.name}/${pkg$1.version}`);
18597
19209
 
18598
19210
  var name = "@circle-fin/earn-kit";
18599
- var version = "1.2.1";
19211
+ var version = "1.3.0";
18600
19212
  var pkg = {
18601
19213
  name: name,
18602
19214
  version: version};
@@ -18726,7 +19338,11 @@ const bridgeFeeTokenSchema = hexAddressSchema;
18726
19338
  asset: zod.z.string(),
18727
19339
  assetAddress: zod.z.string(),
18728
19340
  lltv: zod.z.number(),
18729
- supplyUsd: zod.z.number()
19341
+ supplyUsd: zod.z.number(),
19342
+ // Optional during the expand/contract window (a backend that predates the
19343
+ // field omits the key), mirroring the `.optional()` facets on the base
19344
+ // schema; `null` when the product exposes no per-market allocation (V2).
19345
+ allocationPct: zod.z.number().nullable().optional()
18730
19346
  });
18731
19347
  /**
18732
19348
  * Zod schema for a Morpho vault warning in the API response.
@@ -18740,7 +19356,74 @@ const bridgeFeeTokenSchema = hexAddressSchema;
18740
19356
  ])
18741
19357
  });
18742
19358
  /**
18743
- * Zod schema for a single vault info object in the API response.
19359
+ * Zod schema for the manager (curator) facet in the API response.
19360
+ *
19361
+ * @internal
19362
+ */ const managerSchema = zod.z.object({
19363
+ name: zod.z.string(),
19364
+ address: zod.z.string().optional(),
19365
+ // Only 'curator' is emitted today (Morpho V1/V2). Additional manager roles
19366
+ // are added here as the providers that emit them land, rather than shipped
19367
+ // speculatively.
19368
+ type: zod.z.enum([
19369
+ 'curator'
19370
+ ])
19371
+ });
19372
+ /**
19373
+ * Zod schema for the APY profile facet in the API response.
19374
+ *
19375
+ * @internal
19376
+ */ const apyProfileSchema = zod.z.object({
19377
+ current: zod.z.number(),
19378
+ native: zod.z.number().nullable(),
19379
+ d7: zod.z.number().nullable(),
19380
+ d30: zod.z.number().nullable(),
19381
+ d90: zod.z.number().nullable(),
19382
+ rewardShare: zod.z.number().nullable(),
19383
+ source: zod.z.string().optional(),
19384
+ asOf: zod.z.string().optional()
19385
+ });
19386
+ /**
19387
+ * Zod schema for the fee split facet in the API response.
19388
+ *
19389
+ * @internal
19390
+ */ const feeInfoSchema = zod.z.object({
19391
+ performance: zod.z.number().nullable(),
19392
+ management: zod.z.number().nullable()
19393
+ });
19394
+ /**
19395
+ * Zod schema for the liquidity profile facet in the API response.
19396
+ *
19397
+ * `totalSupply` is the outstanding vault share tokens (ERC4626 totalSupply);
19398
+ * it is validated as a raw JSON amount, like `totalDeposits`/`available`.
19399
+ *
19400
+ * @internal
19401
+ */ const liquidityProfileSchema = zod.z.object({
19402
+ totalDeposits: amountJsonSchema,
19403
+ available: amountJsonSchema,
19404
+ totalSupply: amountJsonSchema,
19405
+ status: zod.z.enum([
19406
+ 'active',
19407
+ 'low_liquidity'
19408
+ ])
19409
+ });
19410
+ /**
19411
+ * Zod schema for the risk signals facet in the API response.
19412
+ *
19413
+ * @internal
19414
+ */ const riskSignalsSchema = zod.z.object({
19415
+ circleSentinel: zod.z.boolean(),
19416
+ warnings: zod.z.array(vaultWarningSchema).optional(),
19417
+ earnKitWarnings: zod.z.array(zod.z.string()).optional()
19418
+ });
19419
+ /**
19420
+ * Zod schema for the universal earn-opportunity base in the API response.
19421
+ *
19422
+ * Retains every existing deprecated flat field (kept validated through the
19423
+ * expand/contract window so default-strip does not drop them) and adds the
19424
+ * new nested facets. The nested facets are `.optional()` during the
19425
+ * transition so the SDK still validates against a not-yet-fully-deployed
19426
+ * backend; they become required after Expand ships.
18744
19427
  *
18745
19428
  * @internal
18746
19429
  */ const vaultInfoResponseSchema = zod.z.object({
@@ -18765,6 +19448,96 @@ const bridgeFeeTokenSchema = hexAddressSchema;
18765
19448
  warnings: zod.z.array(vaultWarningSchema).optional(),
18766
19449
  earnKitWarnings: zod.z.array(zod.z.string()).optional()
18767
19450
  });
19451
+ /**
19452
+ * Shared base schema: existing flat fields (kept) plus the new nested
19453
+ * facets and neutral identity. Facets are `.optional()` during the
19454
+ * transition; flip to required once the backend is confirmed emitting.
19455
+ *
19456
+ * @internal
19457
+ */ const earnBaseSchema = vaultInfoResponseSchema.extend({
19458
+ address: zod.z.string().optional(),
19459
+ asOf: zod.z.string().optional(),
19460
+ manager: managerSchema.nullable().optional(),
19461
+ apyProfile: apyProfileSchema.optional(),
19462
+ fee: feeInfoSchema.optional(),
19463
+ liquidityProfile: liquidityProfileSchema.optional(),
19464
+ riskSignals: riskSignalsSchema.optional()
19465
+ });
19466
+ /**
19467
+ * Zod schema for the `vault` opportunity variant.
19468
+ *
19469
+ * @internal
19470
+ */ const vaultOpportunitySchema = earnBaseSchema.extend({
19471
+ productType: zod.z.literal('vault'),
19472
+ collateral: zod.z.array(collateralSchema)
19473
+ });
19474
+ /**
19475
+ * Discriminated union over `productType`. Add union members here as new
19476
+ * product types (e.g. `lending_market`, `rwa_token`) land.
19477
+ *
19478
+ * @internal
19479
+ */ const earnOpportunityVariants = [
19480
+ vaultOpportunitySchema
19481
+ ];
19482
+ /** @internal */ const earnOpportunitySchema = zod.z.discriminatedUnion('productType', earnOpportunityVariants);
19483
+ /** Product types this SDK version knows how to parse. */ const knownProductTypes = new Set(earnOpportunityVariants.map((variant)=>variant.shape.productType.value));
19484
+ /**
19485
+ * Tolerant list parser for earn opportunities.
19486
+ *
19487
+ * `z.discriminatedUnion` throws on an unrecognized discriminant and
19488
+ * `z.array` fails the whole array if any element fails. Two migration-window
19489
+ * cases are smoothed over here so neither breaks an already-shipped SDK:
19490
+ *
19491
+ * - A backend that predates `productType` omits it entirely. `'vault'` was the
19492
+ * only opportunity type then, so default a missing discriminant to `'vault'`
19493
+ * rather than dropping every vault the backend returns.
19494
+ * - A future backend adds a *second* `productType` this SDK version does not
19495
+ * know. Drop those elements (a present-but-unrecognized discriminant) instead
19496
+ * of rejecting the whole list.
19497
+ *
19498
+ * Only the drop above is a *tolerant* case. Anything that is not a plain object
19499
+ * with a present-but-unknown string `productType` — `null`, `undefined`,
19500
+ * primitives, or an object whose `productType` is malformed — is passed through
19501
+ * untouched so `z.array(earnOpportunitySchema)` reports it as a normal
19502
+ * validation failure. It is deliberately not silently dropped (which would hide
19503
+ * malformed backend data) and never throws here (an unguarded property read on
19504
+ * a non-object would escape `safeParse` as a raw `TypeError` instead of a
19505
+ * `ZodError`).
19506
+ *
19507
+ * @internal
19508
+ */ const earnOpportunityListSchema = zod.z.preprocess((raw)=>{
19509
+ if (!Array.isArray(raw)) {
19510
+ return raw;
19511
+ }
19512
+ // Array.isArray narrows `raw` to `any[]`; view it as `unknown[]` so the
19513
+ // map/filter chain stays type-safe and no `any` leaks into the return.
19514
+ const entries = raw;
19515
+ return entries.map((entry)=>{
19516
+ // Only touch plain objects; non-objects fall through to fail validation.
19517
+ if (typeof entry !== 'object' || entry === null) {
19518
+ return entry;
19519
+ }
19520
+ const record = entry;
19521
+ // Older backend predating productType: default to the only type then.
19522
+ return record.productType === undefined ? {
19523
+ ...record,
19524
+ productType: 'vault'
19525
+ } : record;
19526
+ }).filter((entry)=>{
19527
+ // Drop ONLY a present-but-unknown string discriminant (a future
19528
+ // productType this SDK version doesn't know). Everything else —
19529
+ // non-objects, a non-string productType — flows through to
19530
+ // z.array(earnOpportunitySchema) and fails/passes validation normally.
19531
+ if (typeof entry !== 'object' || entry === null) {
19532
+ return true;
19533
+ }
19534
+ const productType = entry.productType;
19535
+ if (typeof productType !== 'string') {
19536
+ return true;
19537
+ }
19538
+ return knownProductTypes.has(productType);
19539
+ });
19540
+ }, zod.z.array(earnOpportunitySchema));
18768
19541
  // ---------------------------------------------------------------------------
18769
19542
  // Position response schema
18770
19543
  // ---------------------------------------------------------------------------
@@ -18894,6 +19667,7 @@ const positionPnlSchema = zod.z.discriminatedUnion('status', [
18894
19667
  *
18895
19668
  * @internal
18896
19669
  */ const depositPayloadSchema = zod.z.object({
19670
+ execId: bridgeDepositExecIdSchema,
18897
19671
  executionParams: depositExecutionParamsSchema,
18898
19672
  signature: hexSignatureSchema
18899
19673
  });
@@ -18985,6 +19759,21 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
18985
19759
  amount: amountJsonSchema,
18986
19760
  vaultAddress: hexAddressSchema
18987
19761
  }).passthrough();
19762
+ /** @internal */ const bridgeQuoteExpirySchema = zod.z.discriminatedUnion('mode', [
19763
+ zod.z.object({
19764
+ mode: zod.z.literal('TIMESTAMP'),
19765
+ expiresAt: zod.z.string().datetime({
19766
+ offset: true
19767
+ })
19768
+ }),
19769
+ zod.z.object({
19770
+ mode: zod.z.literal('BLOCK_NUMBER'),
19771
+ expiresAtBlock: zod.z.number().int(),
19772
+ blockEstimatedAt: zod.z.string().datetime({
19773
+ offset: true
19774
+ }).optional()
19775
+ })
19776
+ ]).optional().catch(undefined);
18988
19777
  /**
18989
19778
  * Zod schema for the bridge deposit prepare payload.
18990
19779
  *
@@ -18996,6 +19785,10 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
18996
19785
  execId: bridgeDepositExecIdSchema,
18997
19786
  erc3009TypedData: bridgeDepositPreparedBundleSchema,
18998
19787
  expiresAt: zod.z.string().datetime(),
19788
+ quoteIssuedAt: zod.z.string().datetime({
19789
+ offset: true
19790
+ }).optional().catch(undefined),
19791
+ quoteExpiry: bridgeQuoteExpirySchema,
18999
19792
  review: bridgeDepositPrepareReviewSchema
19000
19793
  });
19001
19794
  /**
@@ -19061,6 +19854,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19061
19854
  *
19062
19855
  * @internal
19063
19856
  */ const withdrawPayloadSchema = zod.z.object({
19857
+ execId: bridgeDepositExecIdSchema,
19064
19858
  executionParams: withdrawExecutionParamsSchema,
19065
19859
  signature: hexSignatureSchema
19066
19860
  });
@@ -19074,6 +19868,27 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19074
19868
  data: withdrawPayloadSchema
19075
19869
  });
19076
19870
  // ---------------------------------------------------------------------------
19871
+ // Transaction report response schema
19872
+ // ---------------------------------------------------------------------------
19873
+ /**
19874
+ * Zod schema for the transaction report payload inside the API `data` envelope.
19875
+ *
19876
+ * The Earn Service returns an empty payload (`{"data":{}}`) on success, so the
19877
+ * schema accepts any object shape and does not require specific fields.
19878
+ *
19879
+ * @internal
19880
+ */ const transactionReportPayloadSchema = zod.z.object({}).passthrough();
19881
+ /**
19882
+ * Zod schema for the `POST /v1/earnKit/transactions/report` API response.
19883
+ *
19884
+ * The Earn Service API wraps the transaction report payload in a `data`
19885
+ * envelope.
19886
+ *
19887
+ * @internal
19888
+ */ zod.z.object({
19889
+ data: transactionReportPayloadSchema
19890
+ });
19891
+ // ---------------------------------------------------------------------------
19077
19892
  // Claim rewards response schema
19078
19893
  // ---------------------------------------------------------------------------
19079
19894
  /**
@@ -19121,10 +19936,11 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19121
19936
  * Zod schema for a fee entry in an EarnKit API response.
19122
19937
  *
19123
19938
  * Shared across deposit and withdrawal responses (and reusable for real
19124
- * charged fees, not just quote estimates). `type` identifies the fee category
19125
- * for cross-chain deposit quotes this is the kits-proxy fee-quote item type
19126
- * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). `status` qualifies the fee (e.g.
19127
- * `'estimated'` for a pre-sign cross-chain fee). Both are omitted on plain fees.
19939
+ * charged fees, not just quote estimates). `type` identifies the fee category.
19940
+ * For cross-chain deposit quotes this is the kits-proxy fee-quote item type
19941
+ * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). For withdrawal quotes, Circle fees use
19942
+ * `type: 'circle'`. `status` qualifies the fee (e.g. `'estimated'` for a
19943
+ * pre-sign cross-chain fee). Both are omitted on plain fees.
19128
19944
  *
19129
19945
  * @internal
19130
19946
  */ const feeSchema = zod.z.object({
@@ -19133,6 +19949,30 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19133
19949
  token: zod.z.string(),
19134
19950
  amount: amountJsonSchema
19135
19951
  });
19952
+ /**
19953
+ * Zod schema for a native gas-fee entry in an EarnKit quote response.
19954
+ *
19955
+ * The Earn Service backend estimates gas server-side and returns one entry per
19956
+ * action (`Approve`, `Deposit`, `Withdraw`). A successful estimate carries
19957
+ * `fees` in the SDK `EstimatedGas` shape (`{ gas, gasPrice, fee }`), each a raw
19958
+ * integer string in the chain's native base units. When the backend cannot
19959
+ * estimate an action it returns `fees: null` with an `error` message instead.
19960
+ *
19961
+ * The schema deliberately validates almost nothing beyond the envelope: `name`
19962
+ * is optional and `fees` is entirely unvalidated (`unknown`). ALL validation
19963
+ * of `fees` — that it is an object at all, and that `gas`, `gasPrice`, and
19964
+ * `fee` are parseable integer strings — is deferred to {@link toQuoteGasFees},
19965
+ * which degrades a malformed entry to a `fees: null` soft failure. This is
19966
+ * intentional: gas is best-effort, so a single unparseable gas entry (a wrong
19967
+ * type such as `fees: 123` or `fees: 'bad'`, a missing field, or a non-numeric
19968
+ * `fee`) must never fail Zod validation and reject the entire quote.
19969
+ *
19970
+ * @internal
19971
+ */ const quoteGasFeeSchema = zod.z.object({
19972
+ name: zod.z.string().optional(),
19973
+ fees: zod.z.unknown(),
19974
+ error: zod.z.string().optional()
19975
+ }).passthrough();
19136
19976
  /**
19137
19977
  * Zod schema for the inner deposit quote payload.
19138
19978
  *
@@ -19148,7 +19988,8 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19148
19988
  expectedShares: amountJsonSchema,
19149
19989
  sharePrice: zod.z.string(),
19150
19990
  currentApy: zod.z.number(),
19151
- fees: zod.z.array(feeSchema).optional()
19991
+ fees: zod.z.array(feeSchema).optional(),
19992
+ gasFees: zod.z.array(quoteGasFeeSchema).optional()
19152
19993
  });
19153
19994
  /**
19154
19995
  * Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
@@ -19175,6 +20016,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19175
20016
  sharePrice: zod.z.string(),
19176
20017
  maxWithdrawable: amountJsonSchema,
19177
20018
  fees: zod.z.array(feeSchema),
20019
+ gasFees: zod.z.array(quoteGasFeeSchema).optional(),
19178
20020
  warnings: zod.z.array(zod.z.string()).optional()
19179
20021
  });
19180
20022
  /**
@@ -19232,7 +20074,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19232
20074
  *
19233
20075
  * @internal
19234
20076
  */ const getVaultsPayloadSchema = zod.z.object({
19235
- vaults: zod.z.array(vaultInfoResponseSchema),
20077
+ vaults: earnOpportunityListSchema,
19236
20078
  errors: zod.z.array(vaultErrorSchema)
19237
20079
  });
19238
20080
  /**
@@ -19262,7 +20104,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
19262
20104
  *
19263
20105
  * @internal
19264
20106
  */ const exploreVaultsPayloadSchema = zod.z.object({
19265
- vaults: zod.z.array(vaultInfoResponseSchema),
20107
+ vaults: earnOpportunityListSchema,
19266
20108
  pagination: explorePaginationSchema
19267
20109
  });
19268
20110
  /**