@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
package/estimateSwap.mjs CHANGED
@@ -18,13 +18,14 @@
18
18
 
19
19
  import { z } from 'zod';
20
20
  import 'pino';
21
+ import { hexlify, hexZeroPad } from '@ethersproject/bytes';
22
+ import '@ethersproject/abi';
23
+ import { getAddress } from '@ethersproject/address';
21
24
  import { PublicKey } from '@solana/web3.js';
22
25
  import 'bn.js';
23
26
  import '@coral-xyz/anchor';
24
27
  import bs58 from 'bs58';
25
28
  import '@noble/curves/ed25519';
26
- import { hexlify, hexZeroPad } from '@ethersproject/bytes';
27
- import { getAddress } from '@ethersproject/address';
28
29
  import { formatUnits as formatUnits$1 } from '@ethersproject/units';
29
30
  import { keccak256 } from '@ethersproject/keccak256';
30
31
 
@@ -2895,6 +2896,8 @@ class KitError extends Error {
2895
2896
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
2896
2897
  Blockchain["Codex"] = "Codex";
2897
2898
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
2899
+ Blockchain["Cronos"] = "Cronos";
2900
+ Blockchain["Cronos_Testnet"] = "Cronos_Testnet";
2898
2901
  Blockchain["Edge"] = "Edge";
2899
2902
  Blockchain["Edge_Testnet"] = "Edge_Testnet";
2900
2903
  Blockchain["Ethereum"] = "Ethereum";
@@ -2977,6 +2980,7 @@ var BridgeChain;
2977
2980
  BridgeChain["Avalanche"] = "Avalanche";
2978
2981
  BridgeChain["Base"] = "Base";
2979
2982
  BridgeChain["Codex"] = "Codex";
2983
+ BridgeChain["Cronos"] = "Cronos";
2980
2984
  BridgeChain["Edge"] = "Edge";
2981
2985
  BridgeChain["Ethereum"] = "Ethereum";
2982
2986
  BridgeChain["HyperEVM"] = "HyperEVM";
@@ -3001,6 +3005,7 @@ var BridgeChain;
3001
3005
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
3002
3006
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
3003
3007
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
3008
+ BridgeChain["Cronos_Testnet"] = "Cronos_Testnet";
3004
3009
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
3005
3010
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
3006
3011
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
@@ -3523,7 +3528,10 @@ var EarnChain;
3523
3528
  contracts: {
3524
3529
  v1: {
3525
3530
  wallet: GATEWAY_WALLET_EVM_TESTNET,
3526
- minter: GATEWAY_MINTER_EVM_TESTNET
3531
+ minter: GATEWAY_MINTER_EVM_TESTNET,
3532
+ // DepositForHandler the GenericExecutor calls to run a fast cross-chain
3533
+ // deposit into the GatewayWallet above.
3534
+ depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
3527
3535
  }
3528
3536
  },
3529
3537
  forwarderSupported: {
@@ -4057,6 +4065,96 @@ var EarnChain;
4057
4065
  }
4058
4066
  });
4059
4067
 
4068
+ /**
4069
+ * Cronos Mainnet chain definition
4070
+ * @remarks
4071
+ * This represents the official production network for the Cronos blockchain.
4072
+ * Cronos is an EVM-compatible blockchain.
4073
+ */ const Cronos = defineChain({
4074
+ type: 'evm',
4075
+ chain: Blockchain.Cronos,
4076
+ name: 'Cronos',
4077
+ title: 'Cronos Mainnet',
4078
+ nativeCurrency: {
4079
+ name: 'Cronos',
4080
+ symbol: 'CRO',
4081
+ decimals: 18
4082
+ },
4083
+ chainId: 25,
4084
+ isTestnet: false,
4085
+ explorerUrl: 'https://cronoscan.com/tx/{hash}',
4086
+ rpcEndpoints: [
4087
+ 'https://evm.cronos.org'
4088
+ ],
4089
+ eurcAddress: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
4090
+ usdcAddress: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
4091
+ usdtAddress: null,
4092
+ cctp: {
4093
+ domain: 32,
4094
+ contracts: {
4095
+ v2: {
4096
+ type: 'split',
4097
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
4098
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
4099
+ confirmations: 1,
4100
+ fastConfirmations: 1
4101
+ }
4102
+ },
4103
+ forwarderSupported: {
4104
+ source: false,
4105
+ destination: false
4106
+ }
4107
+ },
4108
+ kitContracts: {
4109
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
4110
+ }
4111
+ });
4112
+
4113
+ /**
4114
+ * Cronos Testnet chain definition
4115
+ * @remarks
4116
+ * This represents the official test network for the Cronos blockchain.
4117
+ * Cronos is an EVM-compatible blockchain.
4118
+ */ const CronosTestnet = defineChain({
4119
+ type: 'evm',
4120
+ chain: Blockchain.Cronos_Testnet,
4121
+ name: 'Cronos Testnet',
4122
+ title: 'Cronos Testnet',
4123
+ nativeCurrency: {
4124
+ name: 'CRO',
4125
+ symbol: 'tCRO',
4126
+ decimals: 18
4127
+ },
4128
+ chainId: 338,
4129
+ isTestnet: true,
4130
+ explorerUrl: 'https://explorer.cronos.org/testnet/tx/{hash}',
4131
+ rpcEndpoints: [
4132
+ 'https://evm-t3.cronos.org'
4133
+ ],
4134
+ eurcAddress: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
4135
+ usdcAddress: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
4136
+ usdtAddress: null,
4137
+ cctp: {
4138
+ domain: 32,
4139
+ contracts: {
4140
+ v2: {
4141
+ type: 'split',
4142
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
4143
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
4144
+ confirmations: 1,
4145
+ fastConfirmations: 1
4146
+ }
4147
+ },
4148
+ forwarderSupported: {
4149
+ source: false,
4150
+ destination: false
4151
+ }
4152
+ },
4153
+ kitContracts: {
4154
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
4155
+ }
4156
+ });
4157
+
4060
4158
  /**
4061
4159
  * Edge Mainnet chain definition
4062
4160
  * @remarks
@@ -6408,6 +6506,8 @@ var Chains = /*#__PURE__*/Object.freeze({
6408
6506
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
6409
6507
  Codex: Codex,
6410
6508
  CodexTestnet: CodexTestnet,
6509
+ Cronos: Cronos,
6510
+ CronosTestnet: CronosTestnet,
6411
6511
  Edge: Edge,
6412
6512
  EdgeTestnet: EdgeTestnet,
6413
6513
  Ethereum: Ethereum,
@@ -6499,7 +6599,10 @@ var Chains = /*#__PURE__*/Object.freeze({
6499
6599
  minter: z.string({
6500
6600
  required_error: 'Gateway minter address is required. Please provide a valid contract address.',
6501
6601
  invalid_type_error: 'Gateway minter address must be a string.'
6502
- }).min(1, 'Gateway minter address cannot be empty.')
6602
+ }).min(1, 'Gateway minter address cannot be empty.'),
6603
+ depositForHandler: z.string({
6604
+ invalid_type_error: 'Gateway depositForHandler address must be a string.'
6605
+ }).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
6503
6606
  }).strict() // Reject any additional properties not defined in the schema
6504
6607
  ;
6505
6608
  /**
@@ -8457,6 +8560,7 @@ const swapTokenEnumSchema = z.enum([
8457
8560
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
8458
8561
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
8459
8562
  [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
8563
+ [Blockchain.Cronos]: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
8460
8564
  [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
8461
8565
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
8462
8566
  [Blockchain.Hedera]: '0.0.456858',
@@ -8490,6 +8594,7 @@ const swapTokenEnumSchema = z.enum([
8490
8594
  [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
8491
8595
  [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
8492
8596
  [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
8597
+ [Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
8493
8598
  [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
8494
8599
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
8495
8600
  [Blockchain.Hedera_Testnet]: '0.0.429274',
@@ -8562,6 +8667,7 @@ const swapTokenEnumSchema = z.enum([
8562
8667
  // =========================================================================
8563
8668
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
8564
8669
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
8670
+ [Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
8565
8671
  [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
8566
8672
  [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
8567
8673
  [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
@@ -8570,6 +8676,7 @@ const swapTokenEnumSchema = z.enum([
8570
8676
  // =========================================================================
8571
8677
  [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
8572
8678
  [Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
8679
+ [Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
8573
8680
  [Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
8574
8681
  }
8575
8682
  };
@@ -9380,6 +9487,13 @@ const swapTokenEnumSchema = z.enum([
9380
9487
  return explorerUrl;
9381
9488
  }
9382
9489
 
9490
+ /**
9491
+ * CCTP forwarding magic bytes prefix.
9492
+ *
9493
+ * The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
9494
+ * This prefix is right-padded to 24 bytes in the final hookData.
9495
+ */ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
9496
+
9383
9497
  /**
9384
9498
  * Project an arbitrary payload onto the exact set of fields the telemetry
9385
9499
  * endpoint accepts.
@@ -9756,7 +9870,7 @@ const swapTokenEnumSchema = z.enum([
9756
9870
  }
9757
9871
 
9758
9872
  var name$2 = "@circle-fin/bridge-kit";
9759
- var version$2 = "1.11.1";
9873
+ var version$2 = "1.12.1";
9760
9874
  var pkg$2 = {
9761
9875
  name: name$2,
9762
9876
  version: version$2};
@@ -10614,6 +10728,11 @@ var TransferSpeed;
10614
10728
  clock: z.any().optional()
10615
10729
  }).passthrough();
10616
10730
 
10731
+ /**
10732
+ * The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
10733
+ * hookData must start with.
10734
+ */ Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
10735
+
10617
10736
  /**
10618
10737
  * The minimum finality threshold for CCTPv2 transfers.
10619
10738
  *
@@ -10646,7 +10765,7 @@ var TransferSpeed;
10646
10765
  registerKit(`${pkg$2.name}/${pkg$2.version}`);
10647
10766
 
10648
10767
  var name$1 = "@circle-fin/swap-kit";
10649
- var version$1 = "1.3.1";
10768
+ var version$1 = "1.4.0";
10650
10769
  var pkg$1 = {
10651
10770
  name: name$1,
10652
10771
  version: version$1};
@@ -10711,7 +10830,10 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
10711
10830
  }).min(1, 'kitKey must be a non-empty string').optional(),
10712
10831
  provider: z.string({
10713
10832
  invalid_type_error: 'provider must be a string'
10714
- }).min(1, 'provider must be a non-empty string').optional()
10833
+ }).min(1, 'provider must be a non-empty string').optional(),
10834
+ batchTransactions: z.boolean({
10835
+ invalid_type_error: 'batchTransactions must be a boolean'
10836
+ }).optional()
10715
10837
  });
10716
10838
  /**
10717
10839
  * Zod schema for adapter context.
@@ -11242,7 +11364,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11242
11364
  /**
11243
11365
  * Circle Stablecoin Service API Key.
11244
11366
  * Must be a valid API key format.
11245
- */ apiKey: apiKeySchema
11367
+ */ apiKey: apiKeySchema.optional()
11246
11368
  }).superRefine(requireCrossChainQuoteToAddress);
11247
11369
  /**
11248
11370
  * Zod schema for validating CreateSwapRequest parameters.
@@ -11300,7 +11422,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11300
11422
  /**
11301
11423
  * Circle Stablecoin Service API Key.
11302
11424
  * Must be a valid API key format.
11303
- */ apiKey: apiKeySchema
11425
+ */ apiKey: apiKeySchema.optional()
11304
11426
  });
11305
11427
  /**
11306
11428
  * Zod schema for validating GetSwapStatusResponse data.
@@ -11336,7 +11458,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11336
11458
  toChain: z.string({
11337
11459
  invalid_type_error: 'toChain must be a string'
11338
11460
  }).min(1, 'toChain must be a non-empty string if provided').optional(),
11339
- apiKey: apiKeySchema
11461
+ apiKey: apiKeySchema.optional()
11340
11462
  });
11341
11463
  /**
11342
11464
  * Zod schema for validating CreateSwapResponse payloads.
@@ -11345,13 +11467,15 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11345
11467
  required_error: 'fee token is required',
11346
11468
  invalid_type_error: 'fee token must be a string'
11347
11469
  }).min(1, 'fee token must be a non-empty string'),
11348
- amount: feeAmountSchema
11470
+ amount: feeAmountSchema,
11471
+ decimals: z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
11472
+ symbol: z.string({
11473
+ invalid_type_error: 'fee token symbol must be a string'
11474
+ }).min(1, 'fee token symbol must be a non-empty string').optional()
11349
11475
  });
11350
11476
  /**
11351
11477
  * Developer fee item schema with basis field.
11352
- */ const createSwapDeveloperFeeItemSchema = z.object({
11353
- token: z.string().min(1, 'fee token must be a non-empty string'),
11354
- amount: feeAmountSchema,
11478
+ */ const createSwapDeveloperFeeItemSchema = createSwapFeeItemSchema.extend({
11355
11479
  basis: z.enum([
11356
11480
  'inputAmount',
11357
11481
  'estimatedAmount'
@@ -11443,7 +11567,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11443
11567
  addresses: z.array(z.string({
11444
11568
  invalid_type_error: 'addresses entries must be strings'
11445
11569
  }).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(),
11446
- apiKey: apiKeySchema
11570
+ apiKey: apiKeySchema.optional()
11447
11571
  });
11448
11572
  /**
11449
11573
  * Zod schema for validating GetTokenRatesResponse payloads.
@@ -11621,7 +11745,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11621
11745
  ...DEFAULT_CONFIG,
11622
11746
  headers: {
11623
11747
  ...DEFAULT_CONFIG.headers,
11624
- Authorization: `Bearer ${apiKey}`
11748
+ // Permissionless mode: no Authorization header when the kit key is absent.
11749
+ ...apiKey !== undefined && {
11750
+ Authorization: `Bearer ${apiKey}`
11751
+ }
11625
11752
  }
11626
11753
  };
11627
11754
  try {
@@ -11775,7 +11902,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11775
11902
  ...DEFAULT_CONFIG,
11776
11903
  headers: {
11777
11904
  ...DEFAULT_CONFIG.headers,
11778
- Authorization: `Bearer ${validatedParams.apiKey}`
11905
+ // Permissionless mode: no Authorization header when the kit key is absent.
11906
+ ...validatedParams.apiKey !== undefined && {
11907
+ Authorization: `Bearer ${validatedParams.apiKey}`
11908
+ }
11779
11909
  }
11780
11910
  };
11781
11911
  return pollApiGet(url, isGetQuoteResponse, effectiveConfig);
@@ -11830,7 +11960,9 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11830
11960
  const validatedParams = {
11831
11961
  txHash: result.data.txHash,
11832
11962
  chain: result.data.chain,
11833
- apiKey: result.data.apiKey,
11963
+ ...result.data.apiKey !== undefined && {
11964
+ apiKey: result.data.apiKey
11965
+ },
11834
11966
  ...result.data.toChain !== undefined && {
11835
11967
  toChain: result.data.toChain
11836
11968
  }
@@ -11840,7 +11972,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11840
11972
  ...DEFAULT_CONFIG,
11841
11973
  headers: {
11842
11974
  ...DEFAULT_CONFIG.headers,
11843
- Authorization: `Bearer ${validatedParams.apiKey}`
11975
+ // Permissionless mode: no Authorization header when the kit key is absent.
11976
+ ...validatedParams.apiKey !== undefined && {
11977
+ Authorization: `Bearer ${validatedParams.apiKey}`
11978
+ }
11844
11979
  }
11845
11980
  };
11846
11981
  return pollApiGet(url, isGetSwapStatusResponse, effectiveConfig);
@@ -11929,7 +12064,9 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11929
12064
  }
11930
12065
  const validatedParams = {
11931
12066
  chain: result.data.chain,
11932
- apiKey: result.data.apiKey,
12067
+ ...result.data.apiKey !== undefined && {
12068
+ apiKey: result.data.apiKey
12069
+ },
11933
12070
  ...result.data.addresses !== undefined && {
11934
12071
  addresses: result.data.addresses
11935
12072
  }
@@ -11939,7 +12076,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
11939
12076
  ...DEFAULT_CONFIG,
11940
12077
  headers: {
11941
12078
  ...DEFAULT_CONFIG.headers,
11942
- Authorization: `Bearer ${validatedParams.apiKey}`
12079
+ // Permissionless mode: no Authorization header when the kit key is absent.
12080
+ ...validatedParams.apiKey !== undefined && {
12081
+ Authorization: `Bearer ${validatedParams.apiKey}`
12082
+ }
11943
12083
  }
11944
12084
  };
11945
12085
  return pollApiGet(url, isGetTokenRatesResponse, effectiveConfig);
@@ -12749,6 +12889,47 @@ const S_HEX_LENGTH = 32 * HEX_CHARS_PER_BYTE$1 // 32 bytes for 's'
12749
12889
  */ function hasSignTypedData(adapter) {
12750
12890
  return typeof adapter === 'object' && adapter !== null && 'signTypedData' in adapter && typeof adapter.signTypedData === 'function';
12751
12891
  }
12892
+ /**
12893
+ * Type guard to check if an adapter can actually produce an EIP-712
12894
+ * typed-data signature.
12895
+ *
12896
+ * @remarks
12897
+ * Strengthens {@link hasSignTypedData}: having a `signTypedData` method
12898
+ * does not guarantee it can succeed. Adapters whose signer is delegated
12899
+ * (e.g. through a signing strategy backed by a smart contract account)
12900
+ * expose the method but reject typed-data payloads at runtime. Such
12901
+ * adapters report their real capability through an optional
12902
+ * `supportsSignTypedData()` method, which this guard consults when
12903
+ * present. Adapters without the capability method are assumed able to
12904
+ * sign, preserving the previous duck-typing behavior.
12905
+ *
12906
+ * @param adapter - The adapter to check
12907
+ * @returns True if calling `signTypedData` can be expected to succeed
12908
+ *
12909
+ * @example
12910
+ * ```typescript
12911
+ * import { canSignTypedData } from '@core/adapter-evm'
12912
+ *
12913
+ * if (canSignTypedData(adapter)) {
12914
+ * const signature = await adapter.signTypedData(typedData, context)
12915
+ * } else {
12916
+ * // take an on-chain approval path instead of a permit signature
12917
+ * }
12918
+ * ```
12919
+ */ function canSignTypedData(adapter) {
12920
+ if (!hasSignTypedData(adapter)) {
12921
+ return false;
12922
+ }
12923
+ if (typeof adapter.supportsSignTypedData === 'function') {
12924
+ // The value is `boolean` per the interface, but a plain-JS adapter may
12925
+ // return anything; treat it as untrusted and coerce to a strict
12926
+ // boolean. Comparing an `unknown` (not a `boolean`) also keeps the
12927
+ // lint autofix from stripping this as a redundant `=== true`.
12928
+ const supported = adapter.supportsSignTypedData();
12929
+ return supported === true;
12930
+ }
12931
+ return true;
12932
+ }
12752
12933
 
12753
12934
  /**
12754
12935
  * Build EIP-2612 typed data for permit signing.
@@ -13171,10 +13352,13 @@ enc.encode('used_transfer_spec_hash');
13171
13352
  * at usage time rather than construction time.
13172
13353
  *
13173
13354
  * Validates:
13174
- * - Kit key is present and matches required format (KIT_KEY:id:secret)
13355
+ * - Kit key matches the required format (KIT_KEY:id:secret) when provided.
13356
+ * An absent or empty kit key is permitted (permissionless mode) — the swap
13357
+ * service now treats the key as optional.
13175
13358
  *
13176
- * @param kitKey - The inline kit key from the swap operation config
13177
- * @throws KitError with VALIDATION_FAILED if kit key is invalid or missing
13359
+ * @param kitKey - The inline kit key from the swap operation config (optional)
13360
+ * @throws KitError with VALIDATION_FAILED if a kit key is provided but does not
13361
+ * match the KIT_KEY:<keyId>:<keySecret> format
13178
13362
  *
13179
13363
  * @example
13180
13364
  * ```typescript
@@ -13186,9 +13370,11 @@ enc.encode('used_transfer_spec_hash');
13186
13370
  * assertKitKey(kitKey)
13187
13371
  * ```
13188
13372
  */ function assertKitKey(kitKey) {
13189
- // Validate API key format using existing schema from service-client
13373
+ // Permissionless mode: the swap service treats the kit key as optional, so an
13374
+ // absent (or empty) key is valid. Only validate the format when a key is
13375
+ // actually provided.
13190
13376
  if (!kitKey) {
13191
- throw createValidationFailedError$1('kitKey', kitKey, 'Kit key is required. Expected format: KIT_KEY:<keyId>:<keySecret>. Provide it inline via config.kitKey parameter. Get your free Kit Key at: https://developers.circle.com/w3s/keys#kit-keys');
13377
+ return;
13192
13378
  }
13193
13379
  const apiKeyResult = apiKeySchema.safeParse(kitKey);
13194
13380
  if (!apiKeyResult.success) {
@@ -13485,8 +13671,8 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
13485
13671
  validateResolvedAddress(resolvedTokenInAddress, chain);
13486
13672
  validateResolvedAddress(resolvedTokenOutAddress, destinationChain);
13487
13673
  validateResolvedAddress(to, destinationChain);
13488
- const kitKey = config?.kitKey ?? '';
13489
- // Validates the kit key
13674
+ const kitKey = config?.kitKey;
13675
+ // Validate the kit key format when one is provided (permissionless otherwise).
13490
13676
  assertKitKey(kitKey);
13491
13677
  // Validate custom fee configuration if present
13492
13678
  const customFee = config?.customFee;
@@ -13531,7 +13717,10 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
13531
13717
  }
13532
13718
  }
13533
13719
  },
13534
- apiKey: kitKey
13720
+ // Map kitKey → apiKey for the service client; omitted in permissionless mode.
13721
+ ...kitKey ? {
13722
+ apiKey: kitKey
13723
+ } : {}
13535
13724
  };
13536
13725
  }
13537
13726
 
@@ -14185,6 +14374,37 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
14185
14374
  }
14186
14375
  }
14187
14376
 
14377
+ /**
14378
+ * Determine whether an adapter can produce an EIP-2612 permit signature.
14379
+ *
14380
+ * @remarks
14381
+ * A gasless permit needs two adapter capabilities: fetching the token's
14382
+ * EIP-2612 nonce and producing an EIP-712 typed-data signature. The
14383
+ * typed-data check uses {@link canSignTypedData} rather than a bare
14384
+ * `hasSignTypedData` guard so that an adapter routed through a signing
14385
+ * strategy that cannot produce typed-data signatures — one whose manifest
14386
+ * omits `evm-typed-data`, surfaced through an optional `supportsSignTypedData()`
14387
+ * — is correctly excluded. Such an adapter falls back to an on-chain approval
14388
+ * (batched into a single submission when it supports atomic execution) instead
14389
+ * of attempting a permit its strategy would reject.
14390
+ *
14391
+ * @param adapter - The source adapter to inspect.
14392
+ * @returns `true` when the adapter can both fetch a nonce and sign typed data.
14393
+ *
14394
+ * @example
14395
+ * ```typescript
14396
+ * import { adapterSupportsPermit } from './utils'
14397
+ *
14398
+ * if (adapterSupportsPermit(adapter)) {
14399
+ * // gasless permit path — fold the approval into the swap transaction
14400
+ * } else {
14401
+ * // on-chain approval path (batched when supportsAtomicBatch is true)
14402
+ * }
14403
+ * ```
14404
+ */ function adapterSupportsPermit(adapter) {
14405
+ return hasEIP2612NonceFetching(adapter) && canSignTypedData(adapter);
14406
+ }
14407
+
14188
14408
  /**
14189
14409
  * Generate EIP-2612 permit signature for token approval.
14190
14410
  *
@@ -14320,8 +14540,7 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
14320
14540
  }
14321
14541
  // Skip permit generation if the adapter lacks the required capabilities.
14322
14542
  // handleEvmTokenApproval will have already sent an on-chain approval in this case.
14323
- const adapterSupportsPermit = hasEIP2612NonceFetching(adapter) && hasSignTypedData(adapter);
14324
- if (!adapterSupportsPermit) {
14543
+ if (!adapterSupportsPermit(adapter)) {
14325
14544
  return [
14326
14545
  createFallbackTokenInput(tokenInAddress, inputAmount)
14327
14546
  ];
@@ -14880,6 +15099,65 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
14880
15099
  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.`;
14881
15100
  }
14882
15101
 
15102
+ /**
15103
+ * Determine which chain a fee token should be resolved and formatted against.
15104
+ *
15105
+ * @remarks
15106
+ * Fees returned by the service may be denominated in either the input token
15107
+ * (on the source chain) or the output token (on the destination chain). A
15108
+ * contract address only resolves on the chain it belongs to, so formatting a
15109
+ * destination-denominated fee against the source chain causes
15110
+ * {@link resolveTokenSymbol} to miss and the amount to be returned as raw base
15111
+ * units (e.g. a cross-chain swap charging a fee in the destination output
15112
+ * token — an EURC-on-Base address shows `'13202'` instead of `'0.013202'` when
15113
+ * resolved against the source chain). This is the fallback for fee items that
15114
+ * are not self-described with their own `decimals`/`chain`.
15115
+ *
15116
+ * Prefer the source chain (covers same-chain swaps and input-denominated
15117
+ * fees), then fall back to the destination chain when the token only resolves
15118
+ * there. When neither chain recognises the token, default to the source chain
15119
+ * so existing on-chain decimal lookups via the source adapter still apply.
15120
+ *
15121
+ * Symbol tokens (`'USDC'`, `'NATIVE'`) resolve on either chain, so the
15122
+ * source-first preference keeps them on the source chain. That is correct for
15123
+ * registry stablecoins, and for `'NATIVE'` only when both chains share native
15124
+ * decimals (EVM↔EVM, 18). It does NOT honor per-chain native decimals: a
15125
+ * `'NATIVE'`-denominated fee on a Solana↔EVM swap (9 vs 18) would be
15126
+ * mis-scaled. This is latent — providers emit the address form, and
15127
+ * self-describing fee items carry their own `decimals` and never reach this
15128
+ * helper — so the gap only opens for a future `'NATIVE'` fee that arrives
15129
+ * without `decimals` on a cross-native-decimal route.
15130
+ *
15131
+ * @param token - The fee token identifier — a symbol (`'USDC'`) or contract address.
15132
+ * @param sourceChain - The chain the swap originates from.
15133
+ * @param destinationChain - The chain the swap settles on (equals `sourceChain` for same-chain swaps).
15134
+ * @returns The chain definition the fee token should be resolved against.
15135
+ *
15136
+ * @example
15137
+ * ```typescript
15138
+ * import { resolveFeeChain } from './resolveFeeChain'
15139
+ * import { Ethereum, Base } from '@core/chains'
15140
+ *
15141
+ * // Cross-chain swap fee charged in the destination (output) token
15142
+ * resolveFeeChain('0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42', Ethereum, Base)
15143
+ * // => Base (EURC resolves on Base, not Ethereum)
15144
+ *
15145
+ * // Symbol or source-token fees stay on the source chain
15146
+ * resolveFeeChain('USDC', Ethereum, Base) // => Ethereum
15147
+ * ```
15148
+ */ function resolveFeeChain(token, sourceChain, destinationChain) {
15149
+ if (sourceChain.chain === destinationChain.chain) {
15150
+ return sourceChain;
15151
+ }
15152
+ if (resolveTokenSymbol(token, sourceChain) !== null) {
15153
+ return sourceChain;
15154
+ }
15155
+ if (resolveTokenSymbol(token, destinationChain) !== null) {
15156
+ return destinationChain;
15157
+ }
15158
+ return sourceChain;
15159
+ }
15160
+
14883
15161
  const TOKEN_REGISTRY$1 = createTokenRegistry();
14884
15162
  /**
14885
15163
  * Format a raw base-unit amount into a human-readable decimal string.
@@ -14970,6 +15248,186 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
14970
15248
  }
14971
15249
  }
14972
15250
 
15251
+ /**
15252
+ * Runtime guard for {@link BatchCapableSwapAdapter}.
15253
+ *
15254
+ * @param adapter - The adapter to inspect.
15255
+ * @returns `true` when the adapter exposes both batch methods.
15256
+ *
15257
+ * @example
15258
+ * ```typescript
15259
+ * if (isBatchCapableSwapAdapter(adapter)) {
15260
+ * // adapter.supportsAtomicBatch / adapter.batchExecute are available
15261
+ * }
15262
+ * ```
15263
+ */ function isBatchCapableSwapAdapter(adapter) {
15264
+ return typeof adapter === 'object' && adapter !== null && typeof adapter.supportsAtomicBatch === 'function' && typeof adapter.batchExecute === 'function';
15265
+ }
15266
+ /**
15267
+ * Decide whether the EVM swap should take the batched approve-and-swap path.
15268
+ *
15269
+ * @remarks
15270
+ * Batching only helps when an on-chain approval would otherwise be required, so
15271
+ * it is skipped for native tokens (no approval) and for the gasless permit path
15272
+ * (already a single transaction). USDT is skipped because its reset-to-zero
15273
+ * allowance flow cannot be expressed as a fixed approve+swap pair. When those
15274
+ * gates pass, the adapter's actual atomic-batch capability is queried; any
15275
+ * failure resolves to `false` so the swap falls back to the sequential path.
15276
+ *
15277
+ * @param args - The decision inputs.
15278
+ * @param args.adapter - The source adapter.
15279
+ * @param args.chain - The source chain definition.
15280
+ * @param args.tokenInAddress - The resolved input-token address.
15281
+ * @param args.allowanceStrategy - Optional allowance strategy override.
15282
+ * @param args.batchTransactions - Optional explicit opt-out (`false` disables).
15283
+ * @returns `true` when the batched approve-and-swap path should be used.
15284
+ *
15285
+ * @example
15286
+ * ```typescript
15287
+ * const useBatched = await shouldUseBatchedSwap({
15288
+ * adapter,
15289
+ * chain,
15290
+ * tokenInAddress: '0xA0b8...',
15291
+ * allowanceStrategy: config?.allowanceStrategy,
15292
+ * batchTransactions: config?.batchTransactions,
15293
+ * })
15294
+ * ```
15295
+ */ async function shouldUseBatchedSwap({ adapter, chain, tokenInAddress, allowanceStrategy, batchTransactions }) {
15296
+ // Explicit opt-out.
15297
+ if (batchTransactions === false) {
15298
+ return false;
15299
+ }
15300
+ // Batching is an EVM capability (EIP-5792 or a signing strategy).
15301
+ if (chain.type !== 'evm') {
15302
+ return false;
15303
+ }
15304
+ // Native tokens need no approval — the swap is already a single transaction.
15305
+ if (isNativeEvmAddress(tokenInAddress)) {
15306
+ return false;
15307
+ }
15308
+ // A gasless permit folds the approval into the swap transaction, so there is
15309
+ // nothing to batch. Mirrors the permit gate in handleEvmTokenApproval.
15310
+ const canUsePermit = allowanceStrategy !== 'approve' && supportsEIP2612(tokenInAddress, chain) && adapterSupportsPermit(adapter);
15311
+ if (canUsePermit) {
15312
+ return false;
15313
+ }
15314
+ // USDT's reset-to-zero allowance dance cannot be expressed as a fixed
15315
+ // approve+swap pair; leave it on the sequential path.
15316
+ const usdt = chain.usdtAddress?.toLowerCase();
15317
+ if (usdt !== undefined && tokenInAddress.toLowerCase() === usdt) {
15318
+ return false;
15319
+ }
15320
+ if (!isBatchCapableSwapAdapter(adapter)) {
15321
+ return false;
15322
+ }
15323
+ try {
15324
+ return await adapter.supportsAtomicBatch(chain);
15325
+ } catch {
15326
+ return false;
15327
+ }
15328
+ }
15329
+ /**
15330
+ * Execute the approval and swap as a single atomic batch.
15331
+ *
15332
+ * @remarks
15333
+ * Extracts the raw call data from both prepared requests, submits them as one
15334
+ * batch via `adapter.batchExecute`, and maps the swap receipt back to a
15335
+ * transaction hash. The `fromAddress` is threaded for adapters routed through a
15336
+ * signing strategy (which have no wallet account to read the sender from); the
15337
+ * wallet-client path ignores it.
15338
+ *
15339
+ * Following the batch contract, `batchExecute` never throws once the batch is
15340
+ * submitted — a missing or failed swap receipt is surfaced here as a thrown
15341
+ * {@link KitError} (FATAL) so the caller does not resubmit an already-broadcast
15342
+ * batch and double-swap.
15343
+ *
15344
+ * @param args - The execution inputs.
15345
+ * @param args.adapter - The batch-capable source adapter.
15346
+ * @param args.chain - The EVM chain to execute on.
15347
+ * @param args.approveRequest - The prepared ERC-20 approval request.
15348
+ * @param args.swapRequest - The prepared swap request (pre-approval / NONE permit).
15349
+ * @param args.fromAddress - The address authorizing the batch.
15350
+ * @returns The swap transaction hash and the executed approval + swap records.
15351
+ * @throws {@link KitError} when the prepared requests cannot yield call data.
15352
+ * @throws {@link KitError} when the batch does not confirm or the swap reverts.
15353
+ *
15354
+ * @example
15355
+ * ```typescript
15356
+ * const { swapTxHash, executedTransactions } = await executeBatchedApproveAndSwap({
15357
+ * adapter,
15358
+ * chain,
15359
+ * approveRequest,
15360
+ * swapRequest,
15361
+ * fromAddress: '0x742d...',
15362
+ * })
15363
+ * ```
15364
+ */ async function executeBatchedApproveAndSwap({ adapter, chain, approveRequest, swapRequest, fromAddress }) {
15365
+ if (approveRequest.type !== 'evm' || swapRequest.type !== 'evm' || !approveRequest.getCallData || !swapRequest.getCallData) {
15366
+ throw new KitError({
15367
+ ...InputError.UNSUPPORTED_ACTION,
15368
+ recoverability: 'FATAL',
15369
+ message: 'Batched swap requires EVM prepared requests with getCallData() support.'
15370
+ });
15371
+ }
15372
+ const approveCallData = approveRequest.getCallData();
15373
+ const swapCallData = swapRequest.getCallData();
15374
+ const batchResult = await adapter.batchExecute([
15375
+ approveCallData,
15376
+ swapCallData
15377
+ ], chain, {
15378
+ fromAddress
15379
+ });
15380
+ const swapReceipt = batchResult.receipts[1];
15381
+ // A missing swap receipt means the batch never confirmed (polling timed out
15382
+ // or the wallet returned fewer receipts than calls). Re-throw the underlying
15383
+ // error when present (already FATAL); otherwise surface a FATAL timeout so the
15384
+ // caller checks the batch status rather than resubmitting.
15385
+ if (swapReceipt === undefined || swapReceipt.txHash === '') {
15386
+ if (isKitError(batchResult.error)) {
15387
+ throw batchResult.error;
15388
+ }
15389
+ throw new KitError({
15390
+ ...NetworkError.TIMEOUT,
15391
+ recoverability: 'FATAL',
15392
+ message: `Batched swap did not confirm on-chain (batchId: ${batchResult.batchId}). ` + 'The batch was already submitted — check its status before retrying.',
15393
+ // Preserve the underlying confirmation failure when it isn't a KitError —
15394
+ // the signing-strategy path returns a raw viem error (e.g. a dropped or
15395
+ // replaced tx) — so the root cause survives behind the generic timeout.
15396
+ cause: {
15397
+ trace: {
15398
+ batchId: batchResult.batchId,
15399
+ ...batchResult.error != null && {
15400
+ error: batchResult.error
15401
+ }
15402
+ }
15403
+ }
15404
+ });
15405
+ }
15406
+ if (swapReceipt.status !== 'success') {
15407
+ throw createTransactionRevertedError(chain.name, 'Batched swap transaction reverted on-chain', undefined, swapReceipt.txHash, buildExplorerUrl(chain, swapReceipt.txHash));
15408
+ }
15409
+ const executedTransactions = [];
15410
+ const approveReceipt = batchResult.receipts[0];
15411
+ // An atomic batch is a single on-chain transaction, so the approve and swap
15412
+ // receipts share one hash. Only surface a distinct approval record when it is
15413
+ // genuinely a separate transaction; otherwise the lone swap record represents
15414
+ // the batch, avoiding a phantom duplicate tx in executedTransactions.
15415
+ if (approveReceipt !== undefined && approveReceipt.txHash !== '' && approveReceipt.txHash !== swapReceipt.txHash) {
15416
+ executedTransactions.push({
15417
+ type: 'approval',
15418
+ txHash: approveReceipt.txHash
15419
+ });
15420
+ }
15421
+ executedTransactions.push({
15422
+ type: 'swap',
15423
+ txHash: swapReceipt.txHash
15424
+ });
15425
+ return {
15426
+ swapTxHash: swapReceipt.txHash,
15427
+ executedTransactions
15428
+ };
15429
+ }
15430
+
14973
15431
  /**
14974
15432
  * Safety multiplier applied to locally estimated gas for EVM swap execution.
14975
15433
  * Derived from refund cap (max 1/5 of total gas used) plus an extra 0.1 margin,
@@ -15096,7 +15554,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
15096
15554
  const statusResult = await getSwapStatus$1({
15097
15555
  txHash,
15098
15556
  chain: chain.chain,
15099
- apiKey
15557
+ ...apiKey !== undefined && {
15558
+ apiKey
15559
+ }
15100
15560
  });
15101
15561
  if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
15102
15562
  return {
@@ -15687,8 +16147,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
15687
16147
  // Note: tokenInAddress from executionCtx is already resolved (handles NATIVE alias, ETH, etc.)
15688
16148
  const isNativeToken = isNativeEvmAddress(executionCtx.tokenInAddress);
15689
16149
  const tokenSupportsPermit = supportsEIP2612(executionCtx.tokenInAddress, chain);
15690
- const adapterSupportsPermit = hasEIP2612NonceFetching(adapter) && hasSignTypedData(adapter);
15691
- const canUsePermitFlow = tokenSupportsPermit && adapterSupportsPermit && allowanceStrategy !== 'approve';
16150
+ const canUsePermitFlow = tokenSupportsPermit && adapterSupportsPermit(adapter) && allowanceStrategy !== 'approve';
15692
16151
  const needsApproval = !isNativeToken && !canUsePermitFlow;
15693
16152
  if (!needsApproval) {
15694
16153
  return;
@@ -15967,34 +16426,55 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
15967
16426
  const serviceResponse = await createSwap(serviceParams);
15968
16427
  // Track executed transactions
15969
16428
  const executedTransactions = [];
15970
- // Prepare swap action based on chain type
15971
- let preparedAction;
15972
- if (chain.type === 'solana') {
15973
- // Solana: No approval needed, directly prepare swap action
15974
- preparedAction = await prepareSolanaSwapAction(adapter, serviceResponse, resolvedContext);
15975
- } else {
15976
- // EVM chains: Handle token approval if needed
15977
- await this.handleEvmTokenApproval(adapter, chain, executionCtx, resolvedContext, executedTransactions, config?.allowanceStrategy);
15978
- // EVM chains: prepareEvmSwapAction handles EIP-2612 permit generation
15979
- // Adapter contract address is read from chain.kitContracts.adapter
15980
- // Use the already-resolved context from above
15981
- preparedAction = await prepareEvmSwapAction(adapter, serviceResponse, resolvedContext, config?.allowanceStrategy);
15982
- }
16429
+ // Prepare swap action(s) based on chain type and batch capability.
16430
+ // Returns either a single prepared action (Solana / sequential EVM) or a
16431
+ // batched approve+swap plan (EVM atomic-batch path).
16432
+ const { preparedAction, batchedSwapPlan } = await this.prepareSwapRequests({
16433
+ adapter,
16434
+ chain,
16435
+ serviceResponse,
16436
+ resolvedContext,
16437
+ executionCtx,
16438
+ config,
16439
+ executedTransactions
16440
+ });
15983
16441
  // Execute swap transaction via adapter
15984
16442
  // For EVM chains, use gas limit from proxy service API
15985
16443
  let txHash;
15986
16444
  const evmGasLimit = 'gasLimit' in serviceResponse.transaction ? serviceResponse.transaction.gasLimit : undefined;
15987
16445
  try {
15988
- txHash = await this.executeSwapTransaction(preparedAction, evmGasLimit);
15989
- executedTransactions.push({
15990
- type: 'swap',
15991
- txHash
15992
- });
15993
- // Wait for transaction confirmation and verify success
15994
- const txReceipt = await adapter.waitForTransaction(txHash, undefined, chain);
15995
- if (txReceipt.status === 'reverted') {
15996
- const explorerUrl = buildExplorerUrl(chain, txHash);
15997
- throw createTransactionRevertedError(chain.name, 'Swap transaction reverted on-chain', undefined, txHash, explorerUrl);
16446
+ if (batchedSwapPlan) {
16447
+ // Approve + swap submitted as one atomic batch. batchExecute confirms
16448
+ // the swap internally, so no separate waitForTransaction is needed.
16449
+ const batched = await executeBatchedApproveAndSwap({
16450
+ adapter: adapter,
16451
+ chain: chain,
16452
+ approveRequest: batchedSwapPlan.approveRequest,
16453
+ swapRequest: batchedSwapPlan.swapRequest,
16454
+ fromAddress: executionCtx.fromAddress
16455
+ });
16456
+ txHash = batched.swapTxHash;
16457
+ executedTransactions.push(...batched.executedTransactions);
16458
+ } else if (preparedAction) {
16459
+ txHash = await this.executeSwapTransaction(preparedAction, evmGasLimit);
16460
+ executedTransactions.push({
16461
+ type: 'swap',
16462
+ txHash
16463
+ });
16464
+ // Wait for transaction confirmation and verify success
16465
+ const txReceipt = await adapter.waitForTransaction(txHash, undefined, chain);
16466
+ if (txReceipt.status === 'reverted') {
16467
+ const explorerUrl = buildExplorerUrl(chain, txHash);
16468
+ throw createTransactionRevertedError(chain.name, 'Swap transaction reverted on-chain', undefined, txHash, explorerUrl);
16469
+ }
16470
+ } else {
16471
+ // Unreachable: the preparation step always yields either a batched plan
16472
+ // or a prepared action.
16473
+ throw new KitError({
16474
+ ...InputError.UNSUPPORTED_ACTION,
16475
+ recoverability: 'FATAL',
16476
+ message: 'No swap execution path was prepared.'
16477
+ });
15998
16478
  }
15999
16479
  } catch (err) {
16000
16480
  handleSwapExecutionError(err, txHash, chain);
@@ -16013,7 +16493,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
16013
16493
  isCrossChainSwap,
16014
16494
  txHash,
16015
16495
  chain,
16016
- apiKey: serviceParams.apiKey
16496
+ ...serviceParams.apiKey !== undefined && {
16497
+ apiKey: serviceParams.apiKey
16498
+ }
16017
16499
  });
16018
16500
  // Build and return SwapResult
16019
16501
  return {
@@ -16038,6 +16520,79 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
16038
16520
  };
16039
16521
  }
16040
16522
  /**
16523
+ * Prepare the swap execution request(s) for the source wallet's chain.
16524
+ *
16525
+ * Produces either a single {@link PreparedChainRequest} (Solana, or the
16526
+ * sequential EVM approve-then-swap path) or a `batchedSwapPlan` (the EVM
16527
+ * atomic approve+swap path chosen when the adapter supports EIP-5792 atomic
16528
+ * batching). The caller executes whichever field is populated. Any on-chain
16529
+ * approval sent on the sequential path is appended to `executedTransactions`.
16530
+ *
16531
+ * @typeParam TFromAdapterCapabilities - Source-adapter capability set.
16532
+ * @param args - Inputs derived from the validated swap request.
16533
+ * @param args.adapter - Source-chain wallet adapter.
16534
+ * @param args.chain - Source chain definition.
16535
+ * @param args.serviceResponse - Validated createSwap response.
16536
+ * @param args.resolvedContext - Resolved operation context.
16537
+ * @param args.executionCtx - Minimal on-chain execution context.
16538
+ * @param args.config - Optional swap configuration (allowance/batch flags).
16539
+ * @param args.executedTransactions - Array appended with any sent approval.
16540
+ * @returns The prepared action or the batched approve+swap plan.
16541
+ * @throws KitError when the EVM atomic-batch path is selected but the chain
16542
+ * has no configured adapter contract.
16543
+ */ async prepareSwapRequests(args) {
16544
+ const { adapter, chain, serviceResponse, resolvedContext, executionCtx, config, executedTransactions } = args;
16545
+ if (chain.type === 'solana') {
16546
+ // Solana: No approval needed, directly prepare swap action
16547
+ return {
16548
+ preparedAction: await prepareSolanaSwapAction(adapter, serviceResponse, resolvedContext)
16549
+ };
16550
+ }
16551
+ const useBatch = await shouldUseBatchedSwap({
16552
+ adapter,
16553
+ chain,
16554
+ tokenInAddress: executionCtx.tokenInAddress,
16555
+ allowanceStrategy: config?.allowanceStrategy,
16556
+ batchTransactions: config?.batchTransactions
16557
+ });
16558
+ if (useBatch) {
16559
+ // EVM chains: fuse the ERC-20 approval and the swap into a single atomic
16560
+ // batch (one signing challenge for smart-contract wallets). Force the
16561
+ // swap onto the pre-approval (PermitType.NONE) path since the approval
16562
+ // rides in the same batch.
16563
+ const adapterContractAddress = chain.kitContracts?.adapter;
16564
+ if (!adapterContractAddress) {
16565
+ throw new KitError({
16566
+ ...InputError.VALIDATION_FAILED,
16567
+ recoverability: 'FATAL',
16568
+ message: `Adapter contract not configured for chain ${chain.name}. Swap operations require an adapter contract.`,
16569
+ cause: {
16570
+ trace: {
16571
+ chain: chain.name
16572
+ }
16573
+ }
16574
+ });
16575
+ }
16576
+ const [approveRequest, swapRequest] = await Promise.all([
16577
+ this.approve(adapter, executionCtx.amount, executionCtx.tokenInAddress, adapterContractAddress, resolvedContext),
16578
+ prepareEvmSwapAction(adapter, serviceResponse, resolvedContext, 'approve')
16579
+ ]);
16580
+ return {
16581
+ batchedSwapPlan: {
16582
+ approveRequest,
16583
+ swapRequest
16584
+ }
16585
+ };
16586
+ }
16587
+ // EVM chains: Handle token approval if needed, then prepare the swap.
16588
+ // prepareEvmSwapAction handles EIP-2612 permit generation; the adapter
16589
+ // contract address is read from chain.kitContracts.adapter.
16590
+ await this.handleEvmTokenApproval(adapter, chain, executionCtx, resolvedContext, executedTransactions, config?.allowanceStrategy);
16591
+ return {
16592
+ preparedAction: await prepareEvmSwapAction(adapter, serviceResponse, resolvedContext, config?.allowanceStrategy)
16593
+ };
16594
+ }
16595
+ /**
16041
16596
  * Executes a swap transaction with the appropriate gas limit for the chain type.
16042
16597
  *
16043
16598
  * For EVM chains, performs a local eth_estimateGas call, applies a 1.3x safety
@@ -16086,8 +16641,8 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
16086
16641
  */ async buildFormattedFees(fees, chain, destinationChain, adapter, recipientAddress) {
16087
16642
  if (!fees) return [];
16088
16643
  const [providerFees, swapFees, developerFees] = await Promise.all([
16089
- this.formatServiceFees(fees.provider, chain, 'provider', adapter),
16090
- this.formatServiceFees(fees.swap, chain, 'swap', adapter),
16644
+ this.formatServiceFees(fees.provider, chain, destinationChain, 'provider', adapter),
16645
+ this.formatServiceFees(fees.swap, chain, destinationChain, 'swap', adapter),
16091
16646
  recipientAddress ? this.formatDeveloperFees(fees.developer, chain, destinationChain, recipientAddress, adapter) : Promise.resolve([])
16092
16647
  ]);
16093
16648
  return [
@@ -16097,6 +16652,45 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
16097
16652
  ];
16098
16653
  }
16099
16654
  /**
16655
+ * Resolve a single fee item to its display token and human-readable amount.
16656
+ *
16657
+ * @remarks
16658
+ * Prefer the self-describing metadata the service attaches to each fee:
16659
+ * `decimals` (and `symbol`) come straight from the provider quote, so they
16660
+ * are authoritative even for a token absent from the SDK registry on both
16661
+ * chains. That is the case {@link resolveFeeChain} cannot recover — a
16662
+ * destination-denominated fee token resolves on neither the source registry
16663
+ * nor the source-bound adapter, leaving the amount as raw base units. When
16664
+ * the service omits `decimals` (optional during rollout), fall back to
16665
+ * inferring the fee token's chain and resolving via the registry/adapter.
16666
+ *
16667
+ * Like {@link formatTokenValue}, this never throws: fee display is cosmetic
16668
+ * and must not fail an estimate/swap. A malformed self-describing `decimals`
16669
+ * (e.g. a non-numeric `amount` or invalid decimal count that makes
16670
+ * {@link formatUnits} throw) falls through to chain-based resolution rather
16671
+ * than propagating out of {@link buildFormattedFees}.
16672
+ *
16673
+ * @param fee - The fee item from the service response.
16674
+ * @param chain - The source chain definition.
16675
+ * @param destinationChain - The destination chain definition.
16676
+ * @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
16677
+ * @returns Promise resolving to the formatted amount and display token.
16678
+ */ async formatFeeValue(fee, chain, destinationChain, adapter) {
16679
+ if (fee.decimals != null) {
16680
+ try {
16681
+ return {
16682
+ amount: formatUnits(fee.amount, fee.decimals),
16683
+ token: fee.symbol ?? fee.token
16684
+ };
16685
+ } catch {
16686
+ // Malformed service metadata — fall through to chain-based resolution,
16687
+ // which never throws (worst case: raw passthrough).
16688
+ }
16689
+ }
16690
+ const feeChain = resolveFeeChain(fee.token, chain, destinationChain);
16691
+ return formatTokenValue(fee.amount, fee.token, feeChain, adapter);
16692
+ }
16693
+ /**
16100
16694
  * Format service fee items into the SDK's ServiceSwapFee structure.
16101
16695
  *
16102
16696
  * @remarks
@@ -16107,14 +16701,15 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
16107
16701
  * - Raw passthrough only when both registry and adapter fail
16108
16702
  *
16109
16703
  * @param feeItems - Array of fee items from the service response.
16110
- * @param chain - The chain definition for token resolution and formatting.
16704
+ * @param chain - The source chain definition for token resolution and formatting.
16705
+ * @param destinationChain - The destination chain, used to resolve fees denominated in the output token.
16111
16706
  * @param type - The fee type to assign ('provider' or 'swap').
16112
16707
  * @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
16113
16708
  * @returns Promise resolving to formatted ServiceSwapFee array.
16114
- */ async formatServiceFees(feeItems, chain, type, adapter) {
16709
+ */ async formatServiceFees(feeItems, chain, destinationChain, type, adapter) {
16115
16710
  if (!feeItems) return [];
16116
16711
  return Promise.all(feeItems.map(async (fee)=>{
16117
- const formatted = await formatTokenValue(fee.amount, fee.token, chain, adapter);
16712
+ const formatted = await this.formatFeeValue(fee, chain, destinationChain, adapter);
16118
16713
  return {
16119
16714
  token: formatted.token,
16120
16715
  amount: formatted.amount,
@@ -16126,16 +16721,15 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
16126
16721
  * Format developer fee items into the SDK's ServiceSwapFee structure.
16127
16722
  *
16128
16723
  * @param feeItems - Array of developer fee items from the service response.
16129
- * @param chain - The chain definition for token resolution and formatting.
16724
+ * @param chain - The source chain definition for token resolution and formatting.
16725
+ * @param destinationChain - The destination chain, used to resolve fees denominated in the output token.
16130
16726
  * @param recipientAddress - The developer's fee recipient address from config.
16131
16727
  * @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
16132
16728
  * @returns Promise resolving to formatted ServiceSwapFee array with developer entries.
16133
16729
  */ async formatDeveloperFees(feeItems, chain, destinationChain, recipientAddress, adapter) {
16134
16730
  if (!feeItems) return [];
16135
- const isCrossChainSwap = destinationChain.chain !== chain.chain;
16136
16731
  return Promise.all(feeItems.map(async (fee)=>{
16137
- const feeChain = !isCrossChainSwap && fee.basis === 'estimatedAmount' ? destinationChain : chain;
16138
- const formatted = await formatTokenValue(fee.amount, fee.token, feeChain, adapter);
16732
+ const formatted = await this.formatFeeValue(fee, chain, destinationChain, adapter);
16139
16733
  return {
16140
16734
  token: formatted.token,
16141
16735
  amount: formatted.amount,
@@ -18530,22 +19124,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
18530
19124
  try {
18531
19125
  // Step 1: Build quote params directly (no need for buildServiceParams)
18532
19126
  // Use chain.chain (Blockchain enum value like "World_Chain") not chain.name
19127
+ // The kit key is optional (permissionless mode); when absent the quote is
19128
+ // fetched without an Authorization header.
18533
19129
  const kitKey = params.config?.kitKey;
18534
- if (!kitKey) {
18535
- throw new KitError({
18536
- code: 1098,
18537
- name: 'INPUT_VALIDATION_FAILED',
18538
- type: 'INPUT',
18539
- recoverability: 'FATAL',
18540
- message: 'kitKey is required in config for callback-based fees',
18541
- cause: {
18542
- trace: {
18543
- operation: 'handleOutputFeeCallback',
18544
- params
18545
- }
18546
- }
18547
- });
18548
- }
18549
19130
  // Resolve token aliases to addresses for the quote API
18550
19131
  // The quote endpoint requires resolved addresses, not aliases like 'USDC'
18551
19132
  const chain = params.from.chain;
@@ -18570,7 +19151,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
18570
19151
  ...params.config?.slippageBps !== undefined && {
18571
19152
  slippageBps: params.config.slippageBps
18572
19153
  },
18573
- apiKey: kitKey
19154
+ ...kitKey ? {
19155
+ apiKey: kitKey
19156
+ } : {}
18574
19157
  };
18575
19158
  // Step 2: Get quote from service
18576
19159
  const quoteResponse = await getQuote(quoteParams);
@@ -19012,7 +19595,9 @@ const sleep$1 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
19012
19595
  ...isCrossChain && {
19013
19596
  toChain: chainOut
19014
19597
  },
19015
- apiKey: params.kitKey
19598
+ ...params.kitKey ? {
19599
+ apiKey: params.kitKey
19600
+ } : {}
19016
19601
  };
19017
19602
  let raw = await getSwapStatus$1(request);
19018
19603
  // When the service hasn't finished indexing a just-submitted swap it
@@ -19152,7 +19737,9 @@ const isResultShape = (params)=>'result' in params;
19152
19737
  ...chainOut !== undefined && {
19153
19738
  chainOut
19154
19739
  },
19155
- kitKey: params.kitKey
19740
+ ...params.kitKey ? {
19741
+ kitKey: params.kitKey
19742
+ } : {}
19156
19743
  };
19157
19744
  const deadline = Date.now() + timeoutMs;
19158
19745
  let pollIndex = 0;
@@ -19313,7 +19900,9 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
19313
19900
  const resolvedAddresses = params.tokens?.map((entry, index)=>resolveTokenEntry(entry, index, chain, chainDef, context));
19314
19901
  return getTokenRates$1({
19315
19902
  chain,
19316
- apiKey: params.kitKey,
19903
+ ...params.kitKey ? {
19904
+ apiKey: params.kitKey
19905
+ } : {},
19317
19906
  ...resolvedAddresses !== undefined && {
19318
19907
  addresses: resolvedAddresses
19319
19908
  }
@@ -20310,7 +20899,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
20310
20899
  };
20311
20900
 
20312
20901
  var name = "@circle-fin/earn-kit";
20313
- var version = "1.2.1";
20902
+ var version = "1.3.0";
20314
20903
  var pkg = {
20315
20904
  name: name,
20316
20905
  version: version};
@@ -20440,7 +21029,11 @@ const bridgeFeeTokenSchema = hexAddressSchema;
20440
21029
  asset: z.string(),
20441
21030
  assetAddress: z.string(),
20442
21031
  lltv: z.number(),
20443
- supplyUsd: z.number()
21032
+ supplyUsd: z.number(),
21033
+ // Optional during the expand/contract window (a backend that predates the
21034
+ // field omits the key), mirroring the `.optional()` facets on the base
21035
+ // schema; `null` when the product exposes no per-market allocation (V2).
21036
+ allocationPct: z.number().nullable().optional()
20444
21037
  });
20445
21038
  /**
20446
21039
  * Zod schema for a Morpho vault warning in the API response.
@@ -20454,7 +21047,74 @@ const bridgeFeeTokenSchema = hexAddressSchema;
20454
21047
  ])
20455
21048
  });
20456
21049
  /**
20457
- * Zod schema for a single vault info object in the API response.
21050
+ * Zod schema for the manager (curator) facet in the API response.
21051
+ *
21052
+ * @internal
21053
+ */ const managerSchema = z.object({
21054
+ name: z.string(),
21055
+ address: z.string().optional(),
21056
+ // Only 'curator' is emitted today (Morpho V1/V2). Additional manager roles
21057
+ // are added here as the providers that emit them land, rather than shipped
21058
+ // speculatively.
21059
+ type: z.enum([
21060
+ 'curator'
21061
+ ])
21062
+ });
21063
+ /**
21064
+ * Zod schema for the APY profile facet in the API response.
21065
+ *
21066
+ * @internal
21067
+ */ const apyProfileSchema = z.object({
21068
+ current: z.number(),
21069
+ native: z.number().nullable(),
21070
+ d7: z.number().nullable(),
21071
+ d30: z.number().nullable(),
21072
+ d90: z.number().nullable(),
21073
+ rewardShare: z.number().nullable(),
21074
+ source: z.string().optional(),
21075
+ asOf: z.string().optional()
21076
+ });
21077
+ /**
21078
+ * Zod schema for the fee split facet in the API response.
21079
+ *
21080
+ * @internal
21081
+ */ const feeInfoSchema = z.object({
21082
+ performance: z.number().nullable(),
21083
+ management: z.number().nullable()
21084
+ });
21085
+ /**
21086
+ * Zod schema for the liquidity profile facet in the API response.
21087
+ *
21088
+ * `totalSupply` is the outstanding vault share tokens (ERC4626 totalSupply);
21089
+ * it is validated as a raw JSON amount, like `totalDeposits`/`available`.
21090
+ *
21091
+ * @internal
21092
+ */ const liquidityProfileSchema = z.object({
21093
+ totalDeposits: amountJsonSchema,
21094
+ available: amountJsonSchema,
21095
+ totalSupply: amountJsonSchema,
21096
+ status: z.enum([
21097
+ 'active',
21098
+ 'low_liquidity'
21099
+ ])
21100
+ });
21101
+ /**
21102
+ * Zod schema for the risk signals facet in the API response.
21103
+ *
21104
+ * @internal
21105
+ */ const riskSignalsSchema = z.object({
21106
+ circleSentinel: z.boolean(),
21107
+ warnings: z.array(vaultWarningSchema).optional(),
21108
+ earnKitWarnings: z.array(z.string()).optional()
21109
+ });
21110
+ /**
21111
+ * Zod schema for the universal earn-opportunity base in the API response.
21112
+ *
21113
+ * Retains every existing deprecated flat field (kept validated through the
21114
+ * expand/contract window so default-strip does not drop them) and adds the
21115
+ * new nested facets. The nested facets are `.optional()` during the
21116
+ * transition so the SDK still validates against a not-yet-fully-deployed
21117
+ * backend; they become required after Expand ships.
20458
21118
  *
20459
21119
  * @internal
20460
21120
  */ const vaultInfoResponseSchema = z.object({
@@ -20479,6 +21139,96 @@ const bridgeFeeTokenSchema = hexAddressSchema;
20479
21139
  warnings: z.array(vaultWarningSchema).optional(),
20480
21140
  earnKitWarnings: z.array(z.string()).optional()
20481
21141
  });
21142
+ /**
21143
+ * Shared base schema: existing flat fields (kept) plus the new nested
21144
+ * facets and neutral identity. Facets are `.optional()` during the
21145
+ * transition; flip to required once the backend is confirmed emitting.
21146
+ *
21147
+ * @internal
21148
+ */ const earnBaseSchema = vaultInfoResponseSchema.extend({
21149
+ address: z.string().optional(),
21150
+ asOf: z.string().optional(),
21151
+ manager: managerSchema.nullable().optional(),
21152
+ apyProfile: apyProfileSchema.optional(),
21153
+ fee: feeInfoSchema.optional(),
21154
+ liquidityProfile: liquidityProfileSchema.optional(),
21155
+ riskSignals: riskSignalsSchema.optional()
21156
+ });
21157
+ /**
21158
+ * Zod schema for the `vault` opportunity variant.
21159
+ *
21160
+ * @internal
21161
+ */ const vaultOpportunitySchema = earnBaseSchema.extend({
21162
+ productType: z.literal('vault'),
21163
+ collateral: z.array(collateralSchema)
21164
+ });
21165
+ /**
21166
+ * Discriminated union over `productType`. Add union members here as new
21167
+ * product types (e.g. `lending_market`, `rwa_token`) land.
21168
+ *
21169
+ * @internal
21170
+ */ const earnOpportunityVariants = [
21171
+ vaultOpportunitySchema
21172
+ ];
21173
+ /** @internal */ const earnOpportunitySchema = z.discriminatedUnion('productType', earnOpportunityVariants);
21174
+ /** Product types this SDK version knows how to parse. */ const knownProductTypes = new Set(earnOpportunityVariants.map((variant)=>variant.shape.productType.value));
21175
+ /**
21176
+ * Tolerant list parser for earn opportunities.
21177
+ *
21178
+ * `z.discriminatedUnion` throws on an unrecognized discriminant and
21179
+ * `z.array` fails the whole array if any element fails. Two migration-window
21180
+ * cases are smoothed over here so neither breaks an already-shipped SDK:
21181
+ *
21182
+ * - A backend that predates `productType` omits it entirely. `'vault'` was the
21183
+ * only opportunity type then, so default a missing discriminant to `'vault'`
21184
+ * rather than dropping every vault the backend returns.
21185
+ * - A future backend adds a *second* `productType` this SDK version does not
21186
+ * know. Drop those elements (a present-but-unrecognized discriminant) instead
21187
+ * of rejecting the whole list.
21188
+ *
21189
+ * Only the drop above is a *tolerant* case. Anything that is not a plain object
21190
+ * with a present-but-unknown string `productType` — `null`, `undefined`,
21191
+ * primitives, or an object whose `productType` is malformed — is passed through
21192
+ * untouched so `z.array(earnOpportunitySchema)` reports it as a normal
21193
+ * validation failure. It is deliberately not silently dropped (which would hide
21194
+ * malformed backend data) and never throws here (an unguarded property read on
21195
+ * a non-object would escape `safeParse` as a raw `TypeError` instead of a
21196
+ * `ZodError`).
21197
+ *
21198
+ * @internal
21199
+ */ const earnOpportunityListSchema = z.preprocess((raw)=>{
21200
+ if (!Array.isArray(raw)) {
21201
+ return raw;
21202
+ }
21203
+ // Array.isArray narrows `raw` to `any[]`; view it as `unknown[]` so the
21204
+ // map/filter chain stays type-safe and no `any` leaks into the return.
21205
+ const entries = raw;
21206
+ return entries.map((entry)=>{
21207
+ // Only touch plain objects; non-objects fall through to fail validation.
21208
+ if (typeof entry !== 'object' || entry === null) {
21209
+ return entry;
21210
+ }
21211
+ const record = entry;
21212
+ // Older backend predating productType: default to the only type then.
21213
+ return record.productType === undefined ? {
21214
+ ...record,
21215
+ productType: 'vault'
21216
+ } : record;
21217
+ }).filter((entry)=>{
21218
+ // Drop ONLY a present-but-unknown string discriminant (a future
21219
+ // productType this SDK version doesn't know). Everything else —
21220
+ // non-objects, a non-string productType — flows through to
21221
+ // z.array(earnOpportunitySchema) and fails/passes validation normally.
21222
+ if (typeof entry !== 'object' || entry === null) {
21223
+ return true;
21224
+ }
21225
+ const productType = entry.productType;
21226
+ if (typeof productType !== 'string') {
21227
+ return true;
21228
+ }
21229
+ return knownProductTypes.has(productType);
21230
+ });
21231
+ }, z.array(earnOpportunitySchema));
20482
21232
  // ---------------------------------------------------------------------------
20483
21233
  // Position response schema
20484
21234
  // ---------------------------------------------------------------------------
@@ -20608,6 +21358,7 @@ const positionPnlSchema = z.discriminatedUnion('status', [
20608
21358
  *
20609
21359
  * @internal
20610
21360
  */ const depositPayloadSchema = z.object({
21361
+ execId: bridgeDepositExecIdSchema,
20611
21362
  executionParams: depositExecutionParamsSchema,
20612
21363
  signature: hexSignatureSchema
20613
21364
  });
@@ -20699,6 +21450,21 @@ const bridgeDepositPrepareReviewSchema = z.object({
20699
21450
  amount: amountJsonSchema,
20700
21451
  vaultAddress: hexAddressSchema
20701
21452
  }).passthrough();
21453
+ /** @internal */ const bridgeQuoteExpirySchema = z.discriminatedUnion('mode', [
21454
+ z.object({
21455
+ mode: z.literal('TIMESTAMP'),
21456
+ expiresAt: z.string().datetime({
21457
+ offset: true
21458
+ })
21459
+ }),
21460
+ z.object({
21461
+ mode: z.literal('BLOCK_NUMBER'),
21462
+ expiresAtBlock: z.number().int(),
21463
+ blockEstimatedAt: z.string().datetime({
21464
+ offset: true
21465
+ }).optional()
21466
+ })
21467
+ ]).optional().catch(undefined);
20702
21468
  /**
20703
21469
  * Zod schema for the bridge deposit prepare payload.
20704
21470
  *
@@ -20710,6 +21476,10 @@ const bridgeDepositPrepareReviewSchema = z.object({
20710
21476
  execId: bridgeDepositExecIdSchema,
20711
21477
  erc3009TypedData: bridgeDepositPreparedBundleSchema,
20712
21478
  expiresAt: z.string().datetime(),
21479
+ quoteIssuedAt: z.string().datetime({
21480
+ offset: true
21481
+ }).optional().catch(undefined),
21482
+ quoteExpiry: bridgeQuoteExpirySchema,
20713
21483
  review: bridgeDepositPrepareReviewSchema
20714
21484
  });
20715
21485
  /**
@@ -20775,6 +21545,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
20775
21545
  *
20776
21546
  * @internal
20777
21547
  */ const withdrawPayloadSchema = z.object({
21548
+ execId: bridgeDepositExecIdSchema,
20778
21549
  executionParams: withdrawExecutionParamsSchema,
20779
21550
  signature: hexSignatureSchema
20780
21551
  });
@@ -20788,6 +21559,27 @@ const bridgeDepositPrepareReviewSchema = z.object({
20788
21559
  data: withdrawPayloadSchema
20789
21560
  });
20790
21561
  // ---------------------------------------------------------------------------
21562
+ // Transaction report response schema
21563
+ // ---------------------------------------------------------------------------
21564
+ /**
21565
+ * Zod schema for the transaction report payload inside the API `data` envelope.
21566
+ *
21567
+ * The Earn Service returns an empty payload (`{"data":{}}`) on success, so the
21568
+ * schema accepts any object shape and does not require specific fields.
21569
+ *
21570
+ * @internal
21571
+ */ const transactionReportPayloadSchema = z.object({}).passthrough();
21572
+ /**
21573
+ * Zod schema for the `POST /v1/earnKit/transactions/report` API response.
21574
+ *
21575
+ * The Earn Service API wraps the transaction report payload in a `data`
21576
+ * envelope.
21577
+ *
21578
+ * @internal
21579
+ */ z.object({
21580
+ data: transactionReportPayloadSchema
21581
+ });
21582
+ // ---------------------------------------------------------------------------
20791
21583
  // Claim rewards response schema
20792
21584
  // ---------------------------------------------------------------------------
20793
21585
  /**
@@ -20835,10 +21627,11 @@ const bridgeDepositPrepareReviewSchema = z.object({
20835
21627
  * Zod schema for a fee entry in an EarnKit API response.
20836
21628
  *
20837
21629
  * Shared across deposit and withdrawal responses (and reusable for real
20838
- * charged fees, not just quote estimates). `type` identifies the fee category
20839
- * for cross-chain deposit quotes this is the kits-proxy fee-quote item type
20840
- * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). `status` qualifies the fee (e.g.
20841
- * `'estimated'` for a pre-sign cross-chain fee). Both are omitted on plain fees.
21630
+ * charged fees, not just quote estimates). `type` identifies the fee category.
21631
+ * For cross-chain deposit quotes this is the kits-proxy fee-quote item type
21632
+ * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). For withdrawal quotes, Circle fees use
21633
+ * `type: 'circle'`. `status` qualifies the fee (e.g. `'estimated'` for a
21634
+ * pre-sign cross-chain fee). Both are omitted on plain fees.
20842
21635
  *
20843
21636
  * @internal
20844
21637
  */ const feeSchema = z.object({
@@ -20847,6 +21640,30 @@ const bridgeDepositPrepareReviewSchema = z.object({
20847
21640
  token: z.string(),
20848
21641
  amount: amountJsonSchema
20849
21642
  });
21643
+ /**
21644
+ * Zod schema for a native gas-fee entry in an EarnKit quote response.
21645
+ *
21646
+ * The Earn Service backend estimates gas server-side and returns one entry per
21647
+ * action (`Approve`, `Deposit`, `Withdraw`). A successful estimate carries
21648
+ * `fees` in the SDK `EstimatedGas` shape (`{ gas, gasPrice, fee }`), each a raw
21649
+ * integer string in the chain's native base units. When the backend cannot
21650
+ * estimate an action it returns `fees: null` with an `error` message instead.
21651
+ *
21652
+ * The schema deliberately validates almost nothing beyond the envelope: `name`
21653
+ * is optional and `fees` is entirely unvalidated (`unknown`). ALL validation
21654
+ * of `fees` — that it is an object at all, and that `gas`, `gasPrice`, and
21655
+ * `fee` are parseable integer strings — is deferred to {@link toQuoteGasFees},
21656
+ * which degrades a malformed entry to a `fees: null` soft failure. This is
21657
+ * intentional: gas is best-effort, so a single unparseable gas entry (a wrong
21658
+ * type such as `fees: 123` or `fees: 'bad'`, a missing field, or a non-numeric
21659
+ * `fee`) must never fail Zod validation and reject the entire quote.
21660
+ *
21661
+ * @internal
21662
+ */ const quoteGasFeeSchema = z.object({
21663
+ name: z.string().optional(),
21664
+ fees: z.unknown(),
21665
+ error: z.string().optional()
21666
+ }).passthrough();
20850
21667
  /**
20851
21668
  * Zod schema for the inner deposit quote payload.
20852
21669
  *
@@ -20862,7 +21679,8 @@ const bridgeDepositPrepareReviewSchema = z.object({
20862
21679
  expectedShares: amountJsonSchema,
20863
21680
  sharePrice: z.string(),
20864
21681
  currentApy: z.number(),
20865
- fees: z.array(feeSchema).optional()
21682
+ fees: z.array(feeSchema).optional(),
21683
+ gasFees: z.array(quoteGasFeeSchema).optional()
20866
21684
  });
20867
21685
  /**
20868
21686
  * Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
@@ -20889,6 +21707,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
20889
21707
  sharePrice: z.string(),
20890
21708
  maxWithdrawable: amountJsonSchema,
20891
21709
  fees: z.array(feeSchema),
21710
+ gasFees: z.array(quoteGasFeeSchema).optional(),
20892
21711
  warnings: z.array(z.string()).optional()
20893
21712
  });
20894
21713
  /**
@@ -20946,7 +21765,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
20946
21765
  *
20947
21766
  * @internal
20948
21767
  */ const getVaultsPayloadSchema = z.object({
20949
- vaults: z.array(vaultInfoResponseSchema),
21768
+ vaults: earnOpportunityListSchema,
20950
21769
  errors: z.array(vaultErrorSchema)
20951
21770
  });
20952
21771
  /**
@@ -20976,7 +21795,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
20976
21795
  *
20977
21796
  * @internal
20978
21797
  */ const exploreVaultsPayloadSchema = z.object({
20979
- vaults: z.array(vaultInfoResponseSchema),
21798
+ vaults: earnOpportunityListSchema,
20980
21799
  pagination: explorePaginationSchema
20981
21800
  });
20982
21801
  /**