@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/earn.cjs CHANGED
@@ -20,6 +20,9 @@
20
20
 
21
21
  var zod = require('zod');
22
22
  require('pino');
23
+ require('@ethersproject/bytes');
24
+ require('@ethersproject/abi');
25
+ require('@ethersproject/address');
23
26
  var web3_js = require('@solana/web3.js');
24
27
  require('bn.js');
25
28
  require('@coral-xyz/anchor');
@@ -1761,15 +1764,15 @@ class KitError extends Error {
1761
1764
  }
1762
1765
 
1763
1766
  /**
1764
- * Standardized error definitions for Earn/Zenith operations.
1767
+ * Standardized error definitions for Earn operations.
1765
1768
  *
1766
1769
  * These error codes provide fine-grained categorization of failures
1767
- * from the Zenith earn service, enabling SDK consumers to distinguish
1770
+ * from the Earn service, enabling SDK consumers to distinguish
1768
1771
  * between input errors (fix your request) and service errors (retry later).
1769
1772
  *
1770
1773
  * Error code ranges:
1771
- * - 1100-1105: INPUT errors — invalid inputs, unsupported configurations
1772
- * - 8100-8104: SERVICE errors — retryable backend/provider failures
1774
+ * - 1100-1106: INPUT errors — invalid, unsupported, or stale request state
1775
+ * - 8100-8105: SERVICE errors — retryable backend/provider failures
1773
1776
  *
1774
1777
  * @example
1775
1778
  * ```typescript
@@ -1821,6 +1824,14 @@ class KitError extends Error {
1821
1824
  name: 'EARN_UNSUPPORTED_BRIDGE_ROUTE',
1822
1825
  type: 'INPUT'
1823
1826
  },
1827
+ /**
1828
+ * The bridge quote expired. This is an INPUT error because the prepared
1829
+ * request is stale and must be replaced instead of retried.
1830
+ */ BRIDGE_QUOTE_EXPIRED: {
1831
+ code: 1106,
1832
+ name: 'EARN_BRIDGE_QUOTE_EXPIRED',
1833
+ type: 'INPUT'
1834
+ },
1824
1835
  /** The proxy signing call failed — retryable. */ SIGNING_FAILED: {
1825
1836
  code: 8100,
1826
1837
  name: 'EARN_SIGNING_FAILED',
@@ -1845,6 +1856,11 @@ class KitError extends Error {
1845
1856
  code: 8104,
1846
1857
  name: 'EARN_PAUSED',
1847
1858
  type: 'SERVICE'
1859
+ },
1860
+ /** Position PnL is still reconciling and can be retried. */ POSITION_PNL_PENDING: {
1861
+ code: 8105,
1862
+ name: 'EARN_POSITION_PNL_PENDING',
1863
+ type: 'SERVICE'
1848
1864
  }
1849
1865
  };
1850
1866
 
@@ -1872,9 +1888,12 @@ function getOptionalString(value) {
1872
1888
  *
1873
1889
  * SERVICE errors (RETRYABLE) — try again later:
1874
1890
  * - signing-failed, provider-error, rewards-fetch-failed,
1875
- * internal-error, vault-refresh-busy, off-chain-paused,
1891
+ * internal-error, vault-refresh-busy, off-chain-paused, position-PnL-pending,
1876
1892
  * bridge failures/status lookup failures
1877
1893
  *
1894
+ * Quote expiry is INPUT/FATAL because callers must start a fresh bridge prepare
1895
+ * flow rather than retry the stale prepared bundle.
1896
+ *
1878
1897
  * Unrecognized codes fall through to `parseApiError` for HTTP-status-based
1879
1898
  * handling.
1880
1899
  *
@@ -2059,6 +2078,13 @@ function getOptionalString(value) {
2059
2078
  recoverability: 'FATAL'
2060
2079
  }
2061
2080
  ],
2081
+ [
2082
+ 380416,
2083
+ {
2084
+ errorDef: EarnError.POSITION_PNL_PENDING,
2085
+ recoverability: 'RETRYABLE'
2086
+ }
2087
+ ],
2062
2088
  // Bridge (380_5XX)
2063
2089
  [
2064
2090
  380500,
@@ -2101,6 +2127,13 @@ function getOptionalString(value) {
2101
2127
  errorDef: EarnError.PROVIDER_ERROR,
2102
2128
  recoverability: 'FATAL'
2103
2129
  }
2130
+ ],
2131
+ [
2132
+ 380506,
2133
+ {
2134
+ errorDef: EarnError.BRIDGE_QUOTE_EXPIRED,
2135
+ recoverability: 'FATAL'
2136
+ }
2104
2137
  ]
2105
2138
  ]);
2106
2139
  /**
@@ -2203,6 +2236,8 @@ function getOptionalString(value) {
2203
2236
  Blockchain["Celo_Alfajores_Testnet"] = "Celo_Alfajores_Testnet";
2204
2237
  Blockchain["Codex"] = "Codex";
2205
2238
  Blockchain["Codex_Testnet"] = "Codex_Testnet";
2239
+ Blockchain["Cronos"] = "Cronos";
2240
+ Blockchain["Cronos_Testnet"] = "Cronos_Testnet";
2206
2241
  Blockchain["Edge"] = "Edge";
2207
2242
  Blockchain["Edge_Testnet"] = "Edge_Testnet";
2208
2243
  Blockchain["Ethereum"] = "Ethereum";
@@ -2285,6 +2320,7 @@ var BridgeChain;
2285
2320
  BridgeChain["Avalanche"] = "Avalanche";
2286
2321
  BridgeChain["Base"] = "Base";
2287
2322
  BridgeChain["Codex"] = "Codex";
2323
+ BridgeChain["Cronos"] = "Cronos";
2288
2324
  BridgeChain["Edge"] = "Edge";
2289
2325
  BridgeChain["Ethereum"] = "Ethereum";
2290
2326
  BridgeChain["HyperEVM"] = "HyperEVM";
@@ -2309,6 +2345,7 @@ var BridgeChain;
2309
2345
  BridgeChain["Avalanche_Fuji"] = "Avalanche_Fuji";
2310
2346
  BridgeChain["Base_Sepolia"] = "Base_Sepolia";
2311
2347
  BridgeChain["Codex_Testnet"] = "Codex_Testnet";
2348
+ BridgeChain["Cronos_Testnet"] = "Cronos_Testnet";
2312
2349
  BridgeChain["Edge_Testnet"] = "Edge_Testnet";
2313
2350
  BridgeChain["Ethereum_Sepolia"] = "Ethereum_Sepolia";
2314
2351
  BridgeChain["HyperEVM_Testnet"] = "HyperEVM_Testnet";
@@ -2820,7 +2857,10 @@ var EarnChain;
2820
2857
  contracts: {
2821
2858
  v1: {
2822
2859
  wallet: GATEWAY_WALLET_EVM_TESTNET,
2823
- minter: GATEWAY_MINTER_EVM_TESTNET
2860
+ minter: GATEWAY_MINTER_EVM_TESTNET,
2861
+ // DepositForHandler the GenericExecutor calls to run a fast cross-chain
2862
+ // deposit into the GatewayWallet above.
2863
+ depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
2824
2864
  }
2825
2865
  },
2826
2866
  forwarderSupported: {
@@ -3354,6 +3394,96 @@ var EarnChain;
3354
3394
  }
3355
3395
  });
3356
3396
 
3397
+ /**
3398
+ * Cronos Mainnet chain definition
3399
+ * @remarks
3400
+ * This represents the official production network for the Cronos blockchain.
3401
+ * Cronos is an EVM-compatible blockchain.
3402
+ */ const Cronos = defineChain({
3403
+ type: 'evm',
3404
+ chain: Blockchain.Cronos,
3405
+ name: 'Cronos',
3406
+ title: 'Cronos Mainnet',
3407
+ nativeCurrency: {
3408
+ name: 'Cronos',
3409
+ symbol: 'CRO',
3410
+ decimals: 18
3411
+ },
3412
+ chainId: 25,
3413
+ isTestnet: false,
3414
+ explorerUrl: 'https://cronoscan.com/tx/{hash}',
3415
+ rpcEndpoints: [
3416
+ 'https://evm.cronos.org'
3417
+ ],
3418
+ eurcAddress: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
3419
+ usdcAddress: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
3420
+ usdtAddress: null,
3421
+ cctp: {
3422
+ domain: 32,
3423
+ contracts: {
3424
+ v2: {
3425
+ type: 'split',
3426
+ tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
3427
+ messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
3428
+ confirmations: 1,
3429
+ fastConfirmations: 1
3430
+ }
3431
+ },
3432
+ forwarderSupported: {
3433
+ source: false,
3434
+ destination: false
3435
+ }
3436
+ },
3437
+ kitContracts: {
3438
+ bridge: BRIDGE_CONTRACT_EVM_MAINNET
3439
+ }
3440
+ });
3441
+
3442
+ /**
3443
+ * Cronos Testnet chain definition
3444
+ * @remarks
3445
+ * This represents the official test network for the Cronos blockchain.
3446
+ * Cronos is an EVM-compatible blockchain.
3447
+ */ const CronosTestnet = defineChain({
3448
+ type: 'evm',
3449
+ chain: Blockchain.Cronos_Testnet,
3450
+ name: 'Cronos Testnet',
3451
+ title: 'Cronos Testnet',
3452
+ nativeCurrency: {
3453
+ name: 'CRO',
3454
+ symbol: 'tCRO',
3455
+ decimals: 18
3456
+ },
3457
+ chainId: 338,
3458
+ isTestnet: true,
3459
+ explorerUrl: 'https://explorer.cronos.org/testnet/tx/{hash}',
3460
+ rpcEndpoints: [
3461
+ 'https://evm-t3.cronos.org'
3462
+ ],
3463
+ eurcAddress: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
3464
+ usdcAddress: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
3465
+ usdtAddress: null,
3466
+ cctp: {
3467
+ domain: 32,
3468
+ contracts: {
3469
+ v2: {
3470
+ type: 'split',
3471
+ tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
3472
+ messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
3473
+ confirmations: 1,
3474
+ fastConfirmations: 1
3475
+ }
3476
+ },
3477
+ forwarderSupported: {
3478
+ source: false,
3479
+ destination: false
3480
+ }
3481
+ },
3482
+ kitContracts: {
3483
+ bridge: BRIDGE_CONTRACT_EVM_TESTNET
3484
+ }
3485
+ });
3486
+
3357
3487
  /**
3358
3488
  * Edge Mainnet chain definition
3359
3489
  * @remarks
@@ -5705,6 +5835,8 @@ var Chains = {
5705
5835
  CeloAlfajoresTestnet: CeloAlfajoresTestnet,
5706
5836
  Codex: Codex,
5707
5837
  CodexTestnet: CodexTestnet,
5838
+ Cronos: Cronos,
5839
+ CronosTestnet: CronosTestnet,
5708
5840
  Edge: Edge,
5709
5841
  EdgeTestnet: EdgeTestnet,
5710
5842
  Ethereum: Ethereum,
@@ -5796,7 +5928,10 @@ var Chains = {
5796
5928
  minter: zod.z.string({
5797
5929
  required_error: 'Gateway minter address is required. Please provide a valid contract address.',
5798
5930
  invalid_type_error: 'Gateway minter address must be a string.'
5799
- }).min(1, 'Gateway minter address cannot be empty.')
5931
+ }).min(1, 'Gateway minter address cannot be empty.'),
5932
+ depositForHandler: zod.z.string({
5933
+ invalid_type_error: 'Gateway depositForHandler address must be a string.'
5934
+ }).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
5800
5935
  }).strict() // Reject any additional properties not defined in the schema
5801
5936
  ;
5802
5937
  /**
@@ -7136,6 +7271,7 @@ const swapTokenEnumSchema = zod.z.enum([
7136
7271
  [Blockchain.Base]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
7137
7272
  [Blockchain.Celo]: '0xcebA9300f2b948710d2653dD7B07f33A8B32118C',
7138
7273
  [Blockchain.Codex]: '0xd996633a415985DBd7D6D12f4A4343E31f5037cf',
7274
+ [Blockchain.Cronos]: '0x3D7F2C478aAfdB65542BCB44bCeeC05849999d2D',
7139
7275
  [Blockchain.Edge]: '0x98d2919b9A214E6Fa5384AC81E6864bA686Ad74c',
7140
7276
  [Blockchain.Ethereum]: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
7141
7277
  [Blockchain.Hedera]: '0.0.456858',
@@ -7169,6 +7305,7 @@ const swapTokenEnumSchema = zod.z.enum([
7169
7305
  [Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
7170
7306
  [Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
7171
7307
  [Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
7308
+ [Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
7172
7309
  [Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
7173
7310
  [Blockchain.Ethereum_Sepolia]: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
7174
7311
  [Blockchain.Hedera_Testnet]: '0.0.429274',
@@ -7241,6 +7378,7 @@ const swapTokenEnumSchema = zod.z.enum([
7241
7378
  // =========================================================================
7242
7379
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
7243
7380
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
7381
+ [Blockchain.Cronos]: '0xA6dE01a2d62C6B5f3525d768f34d276652C554c8',
7244
7382
  [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
7245
7383
  [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
7246
7384
  [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
@@ -7249,6 +7387,7 @@ const swapTokenEnumSchema = zod.z.enum([
7249
7387
  // =========================================================================
7250
7388
  [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
7251
7389
  [Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
7390
+ [Blockchain.Cronos_Testnet]: '0x31f7538adb53cF16350e6B0c89d03D91b7D12c46',
7252
7391
  [Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
7253
7392
  }
7254
7393
  };
@@ -7904,6 +8043,13 @@ const swapTokenEnumSchema = zod.z.enum([
7904
8043
  return explorerUrl;
7905
8044
  }
7906
8045
 
8046
+ /**
8047
+ * CCTP forwarding magic bytes prefix.
8048
+ *
8049
+ * The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
8050
+ * This prefix is right-padded to 24 bytes in the final hookData.
8051
+ */ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
8052
+
7907
8053
  /**
7908
8054
  * Strip the `@circle-fin/` scope from a kit package name to produce the
7909
8055
  * short SDK name used in telemetry payloads.
@@ -7922,11 +8068,11 @@ const swapTokenEnumSchema = zod.z.enum([
7922
8068
  return pkgName.replace('@circle-fin/', '');
7923
8069
  }
7924
8070
 
7925
- var name$2 = "@circle-fin/bridge-kit";
7926
- var version$2 = "1.11.1";
7927
- var pkg$2 = {
7928
- name: name$2,
7929
- version: version$2};
8071
+ var name$3 = "@circle-fin/bridge-kit";
8072
+ var version$3 = "1.12.1";
8073
+ var pkg$3 = {
8074
+ name: name$3,
8075
+ version: version$3};
7930
8076
 
7931
8077
  /**
7932
8078
  * Schema for validating BridgeKit custom fee policy.
@@ -8748,6 +8894,11 @@ var TransferSpeed;
8748
8894
  clock: zod.z.any().optional()
8749
8895
  }).passthrough();
8750
8896
 
8897
+ /**
8898
+ * The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
8899
+ * hookData must start with.
8900
+ */ Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
8901
+
8751
8902
  /**
8752
8903
  * The minimum finality threshold for CCTPv2 transfers.
8753
8904
  *
@@ -8774,16 +8925,16 @@ var TransferSpeed;
8774
8925
  * @internal
8775
8926
  */ Object.values(Chains).filter((chain)=>isCCTPV2Supported(chain));
8776
8927
 
8777
- /** SDK name used in telemetry payloads. */ resolveKitSdkName(pkg$2.name);
8928
+ /** SDK name used in telemetry payloads. */ resolveKitSdkName(pkg$3.name);
8778
8929
 
8779
8930
  // Auto-register this kit for user agent tracking
8780
- registerKit(`${pkg$2.name}/${pkg$2.version}`);
8931
+ registerKit(`${pkg$3.name}/${pkg$3.version}`);
8781
8932
 
8782
- var name$1 = "@circle-fin/swap-kit";
8783
- var version$1 = "1.3.1";
8784
- var pkg$1 = {
8785
- name: name$1,
8786
- version: version$1};
8933
+ var name$2 = "@circle-fin/swap-kit";
8934
+ var version$2 = "1.4.0";
8935
+ var pkg$2 = {
8936
+ name: name$2,
8937
+ version: version$2};
8787
8938
 
8788
8939
  const chainIdentifierField = zod.z.custom((value)=>chainIdentifierSchema.safeParse(value).success, {
8789
8940
  message: 'chain must be a valid chain identifier'
@@ -8845,7 +8996,10 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
8845
8996
  }).min(1, 'kitKey must be a non-empty string').optional(),
8846
8997
  provider: zod.z.string({
8847
8998
  invalid_type_error: 'provider must be a string'
8848
- }).min(1, 'provider must be a non-empty string').optional()
8999
+ }).min(1, 'provider must be a non-empty string').optional(),
9000
+ batchTransactions: zod.z.boolean({
9001
+ invalid_type_error: 'batchTransactions must be a boolean'
9002
+ }).optional()
8849
9003
  });
8850
9004
  /**
8851
9005
  * Zod schema for adapter context.
@@ -9284,7 +9438,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
9284
9438
  /**
9285
9439
  * Circle Stablecoin Service API Key.
9286
9440
  * Must be a valid API key format.
9287
- */ apiKey: apiKeySchema
9441
+ */ apiKey: apiKeySchema.optional()
9288
9442
  }).superRefine(requireCrossChainQuoteToAddress);
9289
9443
  /**
9290
9444
  * Zod schema for validating CreateSwapRequest parameters.
@@ -9342,7 +9496,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
9342
9496
  /**
9343
9497
  * Circle Stablecoin Service API Key.
9344
9498
  * Must be a valid API key format.
9345
- */ apiKey: apiKeySchema
9499
+ */ apiKey: apiKeySchema.optional()
9346
9500
  });
9347
9501
  /**
9348
9502
  * Zod schema for validating GetSwapStatusResponse data.
@@ -9378,7 +9532,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
9378
9532
  toChain: zod.z.string({
9379
9533
  invalid_type_error: 'toChain must be a string'
9380
9534
  }).min(1, 'toChain must be a non-empty string if provided').optional(),
9381
- apiKey: apiKeySchema
9535
+ apiKey: apiKeySchema.optional()
9382
9536
  });
9383
9537
  /**
9384
9538
  * Zod schema for validating CreateSwapResponse payloads.
@@ -9387,13 +9541,15 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
9387
9541
  required_error: 'fee token is required',
9388
9542
  invalid_type_error: 'fee token must be a string'
9389
9543
  }).min(1, 'fee token must be a non-empty string'),
9390
- amount: feeAmountSchema
9544
+ amount: feeAmountSchema,
9545
+ decimals: zod.z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
9546
+ symbol: zod.z.string({
9547
+ invalid_type_error: 'fee token symbol must be a string'
9548
+ }).min(1, 'fee token symbol must be a non-empty string').optional()
9391
9549
  });
9392
9550
  /**
9393
9551
  * Developer fee item schema with basis field.
9394
- */ const createSwapDeveloperFeeItemSchema = zod.z.object({
9395
- token: zod.z.string().min(1, 'fee token must be a non-empty string'),
9396
- amount: feeAmountSchema,
9552
+ */ const createSwapDeveloperFeeItemSchema = createSwapFeeItemSchema.extend({
9397
9553
  basis: zod.z.enum([
9398
9554
  'inputAmount',
9399
9555
  'estimatedAmount'
@@ -9485,7 +9641,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
9485
9641
  addresses: zod.z.array(zod.z.string({
9486
9642
  invalid_type_error: 'addresses entries must be strings'
9487
9643
  }).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(),
9488
- apiKey: apiKeySchema
9644
+ apiKey: apiKeySchema.optional()
9489
9645
  });
9490
9646
  /**
9491
9647
  * Zod schema for validating GetTokenRatesResponse payloads.
@@ -12068,16 +12224,16 @@ new Set(Object.values(Blockchain));
12068
12224
 
12069
12225
  new Set(Object.values(Blockchain));
12070
12226
 
12071
- /** SDK name used in telemetry payloads. */ resolveKitSdkName(pkg$1.name);
12227
+ /** SDK name used in telemetry payloads. */ resolveKitSdkName(pkg$2.name);
12072
12228
 
12073
12229
  // Auto-register this kit for user agent tracking
12074
- registerKit(`${pkg$1.name}/${pkg$1.version}`);
12230
+ registerKit(`${pkg$2.name}/${pkg$2.version}`);
12075
12231
 
12076
- var name = "@circle-fin/earn-kit";
12077
- var version = "1.2.1";
12078
- var pkg = {
12079
- name: name,
12080
- version: version};
12232
+ var name$1 = "@circle-fin/earn-kit";
12233
+ var version$1 = "1.3.0";
12234
+ var pkg$1 = {
12235
+ name: name$1,
12236
+ version: version$1};
12081
12237
 
12082
12238
  const EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS = [
12083
12239
  'USDC'
@@ -12541,7 +12697,7 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
12541
12697
  *
12542
12698
  * @param params - Adapter, chain, token/delegate/wallet addresses, the required
12543
12699
  * allowance for the signed payload, and a revert message for on-chain failure.
12544
- * @returns The approval transaction hash when an approval was submitted, or
12700
+ * @returns The approval transaction result when an approval was submitted, or
12545
12701
  * `undefined` when the existing allowance already covers `requiredAllowance`
12546
12702
  * (or `requiredAllowance` is zero).
12547
12703
  * @throws {@link KitError} If the `token.allowance` response is malformed.
@@ -12549,7 +12705,7 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
12549
12705
  *
12550
12706
  * @example
12551
12707
  * ```typescript
12552
- * const txHash = await approveAllowanceIfNeeded({
12708
+ * const approval = await approveAllowanceIfNeeded({
12553
12709
  * adapter,
12554
12710
  * chain,
12555
12711
  * tokenAddress: usdcAddress,
@@ -12611,7 +12767,15 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
12611
12767
  maxAttempts: params.allowancePropagation?.maxAttempts ?? DEFAULT_PROPAGATION_ATTEMPTS,
12612
12768
  delayMs: params.allowancePropagation?.delayMs ?? DEFAULT_PROPAGATION_DELAY_MS
12613
12769
  });
12614
- return approvalTxHash;
12770
+ return {
12771
+ txHash: approvalTxHash,
12772
+ ...approvalReceipt.gasUsed !== undefined && {
12773
+ gasUsed: approvalReceipt.gasUsed
12774
+ },
12775
+ ...approvalReceipt.effectiveGasPrice !== undefined && {
12776
+ effectiveGasPrice: approvalReceipt.effectiveGasPrice
12777
+ }
12778
+ };
12615
12779
  }
12616
12780
 
12617
12781
  /** @internal */ function isSameAddress(actual, expected) {
@@ -12759,7 +12923,13 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
12759
12923
  }
12760
12924
  return {
12761
12925
  txHash,
12762
- explorerUrl
12926
+ explorerUrl,
12927
+ ...receipt.gasUsed !== undefined && {
12928
+ gasUsed: receipt.gasUsed
12929
+ },
12930
+ ...receipt.effectiveGasPrice !== undefined && {
12931
+ effectiveGasPrice: receipt.effectiveGasPrice
12932
+ }
12763
12933
  };
12764
12934
  }
12765
12935
 
@@ -13071,112 +13241,6 @@ const EARN_OPERATIONS = new Set([
13071
13241
  return hasEarnServiceParamsShape(operation, candidate['params']);
13072
13242
  }
13073
13243
 
13074
- function buildGasFeeBase(name, chain) {
13075
- return {
13076
- name,
13077
- token: chain.nativeCurrency.symbol,
13078
- blockchain: chain.chain
13079
- };
13080
- }
13081
- function buildGasFeeSuccess(name, chain, fees) {
13082
- return {
13083
- ...buildGasFeeBase(name, chain),
13084
- fees
13085
- };
13086
- }
13087
- function buildGasFeeFailure(name, chain, error) {
13088
- return {
13089
- ...buildGasFeeBase(name, chain),
13090
- fees: null,
13091
- error: getErrorMessage(error)
13092
- };
13093
- }
13094
- async function estimatePreparedGasFee(name, chain, prepared) {
13095
- try {
13096
- const estimate = bufferEstimatedGas(await prepared.estimate());
13097
- if (estimate.gas <= 0n) {
13098
- throw createValidationFailedError('estimate.gas', estimate.gas.toString(), 'gas estimate must be greater than zero');
13099
- }
13100
- return buildGasFeeSuccess(name, chain, estimate);
13101
- } catch (error) {
13102
- return buildGasFeeFailure(name, chain, error);
13103
- }
13104
- }
13105
- async function estimateApprovalGasFeeIfNeeded(params) {
13106
- const { adapter, chain, address, tokenAddress, delegate, requiredAllowance } = params;
13107
- if (requiredAllowance <= 0n) {
13108
- return undefined;
13109
- }
13110
- try {
13111
- const allowancePrepared = await adapter.prepareAction('token.allowance', {
13112
- tokenAddress,
13113
- delegate
13114
- }, {
13115
- chain,
13116
- address
13117
- });
13118
- const allowanceRaw = await allowancePrepared.execute();
13119
- const currentAllowance = parseAllowanceResponse(allowanceRaw);
13120
- if (currentAllowance >= requiredAllowance) {
13121
- return undefined;
13122
- }
13123
- // Reuse the execute path's approval builder so the estimate simulates the
13124
- // exact approval (action, amount, and WARM_SLOT_RESIDUAL) that
13125
- // approveAllowanceIfNeeded later submits.
13126
- const approvalPrepared = await prepareApprovalAction({
13127
- adapter,
13128
- chain,
13129
- address,
13130
- tokenAddress,
13131
- delegate,
13132
- currentAllowance,
13133
- requiredAllowance
13134
- });
13135
- return await estimatePreparedGasFee('Approve', chain, approvalPrepared);
13136
- } catch (error) {
13137
- return buildGasFeeFailure('Approve', chain, error);
13138
- }
13139
- }
13140
- /**
13141
- * Estimate gas fee entries for an earn quote without submitting transactions.
13142
- *
13143
- * Each entry is produced by simulating the prepared transaction against
13144
- * current chain state. When an approval is required (allowance below the
13145
- * signed payload's required amount), the subsequent action simulation runs
13146
- * without that approval in place and is expected to revert — the action entry
13147
- * then carries `fees: null` with the revert message while the approval entry
13148
- * still estimates normally. Quote consumers must treat that as "estimate
13149
- * pending approval", not a hard failure.
13150
- *
13151
- * @internal
13152
- */ async function estimateEarnQuoteGasFees(params) {
13153
- const { adapter, chain, address, actionName, actionKey, actionParams, approval } = params;
13154
- const gasFees = [];
13155
- if (approval !== undefined) {
13156
- const approvalEstimate = await estimateApprovalGasFeeIfNeeded({
13157
- adapter,
13158
- chain,
13159
- address,
13160
- tokenAddress: approval.token,
13161
- delegate: approval.delegate,
13162
- requiredAllowance: approval.requiredAllowance
13163
- });
13164
- if (approvalEstimate !== undefined) {
13165
- gasFees.push(approvalEstimate);
13166
- }
13167
- }
13168
- try {
13169
- const actionPrepared = await adapter.prepareAction(actionKey, actionParams, {
13170
- chain,
13171
- address
13172
- });
13173
- gasFees.push(await estimatePreparedGasFee(actionName, chain, actionPrepared));
13174
- } catch (error) {
13175
- gasFees.push(buildGasFeeFailure(actionName, chain, error));
13176
- }
13177
- return gasFees;
13178
- }
13179
-
13180
13244
  // ---------------------------------------------------------------------------
13181
13245
  // Shared primitives
13182
13246
  // ---------------------------------------------------------------------------
@@ -13254,7 +13318,11 @@ const bridgeFeeTokenSchema = hexAddressSchema;
13254
13318
  asset: zod.z.string(),
13255
13319
  assetAddress: zod.z.string(),
13256
13320
  lltv: zod.z.number(),
13257
- supplyUsd: zod.z.number()
13321
+ supplyUsd: zod.z.number(),
13322
+ // Optional during the expand/contract window (a backend that predates the
13323
+ // field omits the key), mirroring the `.optional()` facets on the base
13324
+ // schema; `null` when the product exposes no per-market allocation (V2).
13325
+ allocationPct: zod.z.number().nullable().optional()
13258
13326
  });
13259
13327
  /**
13260
13328
  * Zod schema for a Morpho vault warning in the API response.
@@ -13268,7 +13336,74 @@ const bridgeFeeTokenSchema = hexAddressSchema;
13268
13336
  ])
13269
13337
  });
13270
13338
  /**
13271
- * Zod schema for a single vault info object in the API response.
13339
+ * Zod schema for the manager (curator) facet in the API response.
13340
+ *
13341
+ * @internal
13342
+ */ const managerSchema = zod.z.object({
13343
+ name: zod.z.string(),
13344
+ address: zod.z.string().optional(),
13345
+ // Only 'curator' is emitted today (Morpho V1/V2). Additional manager roles
13346
+ // are added here as the providers that emit them land, rather than shipped
13347
+ // speculatively.
13348
+ type: zod.z.enum([
13349
+ 'curator'
13350
+ ])
13351
+ });
13352
+ /**
13353
+ * Zod schema for the APY profile facet in the API response.
13354
+ *
13355
+ * @internal
13356
+ */ const apyProfileSchema = zod.z.object({
13357
+ current: zod.z.number(),
13358
+ native: zod.z.number().nullable(),
13359
+ d7: zod.z.number().nullable(),
13360
+ d30: zod.z.number().nullable(),
13361
+ d90: zod.z.number().nullable(),
13362
+ rewardShare: zod.z.number().nullable(),
13363
+ source: zod.z.string().optional(),
13364
+ asOf: zod.z.string().optional()
13365
+ });
13366
+ /**
13367
+ * Zod schema for the fee split facet in the API response.
13368
+ *
13369
+ * @internal
13370
+ */ const feeInfoSchema = zod.z.object({
13371
+ performance: zod.z.number().nullable(),
13372
+ management: zod.z.number().nullable()
13373
+ });
13374
+ /**
13375
+ * Zod schema for the liquidity profile facet in the API response.
13376
+ *
13377
+ * `totalSupply` is the outstanding vault share tokens (ERC4626 totalSupply);
13378
+ * it is validated as a raw JSON amount, like `totalDeposits`/`available`.
13379
+ *
13380
+ * @internal
13381
+ */ const liquidityProfileSchema = zod.z.object({
13382
+ totalDeposits: amountJsonSchema,
13383
+ available: amountJsonSchema,
13384
+ totalSupply: amountJsonSchema,
13385
+ status: zod.z.enum([
13386
+ 'active',
13387
+ 'low_liquidity'
13388
+ ])
13389
+ });
13390
+ /**
13391
+ * Zod schema for the risk signals facet in the API response.
13392
+ *
13393
+ * @internal
13394
+ */ const riskSignalsSchema = zod.z.object({
13395
+ circleSentinel: zod.z.boolean(),
13396
+ warnings: zod.z.array(vaultWarningSchema).optional(),
13397
+ earnKitWarnings: zod.z.array(zod.z.string()).optional()
13398
+ });
13399
+ /**
13400
+ * Zod schema for the universal earn-opportunity base in the API response.
13401
+ *
13402
+ * Retains every existing deprecated flat field (kept validated through the
13403
+ * expand/contract window so default-strip does not drop them) and adds the
13404
+ * new nested facets. The nested facets are `.optional()` during the
13405
+ * transition so the SDK still validates against a not-yet-fully-deployed
13406
+ * backend; they become required after Expand ships.
13272
13407
  *
13273
13408
  * @internal
13274
13409
  */ const vaultInfoResponseSchema = zod.z.object({
@@ -13293,6 +13428,96 @@ const bridgeFeeTokenSchema = hexAddressSchema;
13293
13428
  warnings: zod.z.array(vaultWarningSchema).optional(),
13294
13429
  earnKitWarnings: zod.z.array(zod.z.string()).optional()
13295
13430
  });
13431
+ /**
13432
+ * Shared base schema: existing flat fields (kept) plus the new nested
13433
+ * facets and neutral identity. Facets are `.optional()` during the
13434
+ * transition; flip to required once the backend is confirmed emitting.
13435
+ *
13436
+ * @internal
13437
+ */ const earnBaseSchema = vaultInfoResponseSchema.extend({
13438
+ address: zod.z.string().optional(),
13439
+ asOf: zod.z.string().optional(),
13440
+ manager: managerSchema.nullable().optional(),
13441
+ apyProfile: apyProfileSchema.optional(),
13442
+ fee: feeInfoSchema.optional(),
13443
+ liquidityProfile: liquidityProfileSchema.optional(),
13444
+ riskSignals: riskSignalsSchema.optional()
13445
+ });
13446
+ /**
13447
+ * Zod schema for the `vault` opportunity variant.
13448
+ *
13449
+ * @internal
13450
+ */ const vaultOpportunitySchema = earnBaseSchema.extend({
13451
+ productType: zod.z.literal('vault'),
13452
+ collateral: zod.z.array(collateralSchema)
13453
+ });
13454
+ /**
13455
+ * Discriminated union over `productType`. Add union members here as new
13456
+ * product types (e.g. `lending_market`, `rwa_token`) land.
13457
+ *
13458
+ * @internal
13459
+ */ const earnOpportunityVariants = [
13460
+ vaultOpportunitySchema
13461
+ ];
13462
+ /** @internal */ const earnOpportunitySchema = zod.z.discriminatedUnion('productType', earnOpportunityVariants);
13463
+ /** Product types this SDK version knows how to parse. */ const knownProductTypes = new Set(earnOpportunityVariants.map((variant)=>variant.shape.productType.value));
13464
+ /**
13465
+ * Tolerant list parser for earn opportunities.
13466
+ *
13467
+ * `z.discriminatedUnion` throws on an unrecognized discriminant and
13468
+ * `z.array` fails the whole array if any element fails. Two migration-window
13469
+ * cases are smoothed over here so neither breaks an already-shipped SDK:
13470
+ *
13471
+ * - A backend that predates `productType` omits it entirely. `'vault'` was the
13472
+ * only opportunity type then, so default a missing discriminant to `'vault'`
13473
+ * rather than dropping every vault the backend returns.
13474
+ * - A future backend adds a *second* `productType` this SDK version does not
13475
+ * know. Drop those elements (a present-but-unrecognized discriminant) instead
13476
+ * of rejecting the whole list.
13477
+ *
13478
+ * Only the drop above is a *tolerant* case. Anything that is not a plain object
13479
+ * with a present-but-unknown string `productType` — `null`, `undefined`,
13480
+ * primitives, or an object whose `productType` is malformed — is passed through
13481
+ * untouched so `z.array(earnOpportunitySchema)` reports it as a normal
13482
+ * validation failure. It is deliberately not silently dropped (which would hide
13483
+ * malformed backend data) and never throws here (an unguarded property read on
13484
+ * a non-object would escape `safeParse` as a raw `TypeError` instead of a
13485
+ * `ZodError`).
13486
+ *
13487
+ * @internal
13488
+ */ const earnOpportunityListSchema = zod.z.preprocess((raw)=>{
13489
+ if (!Array.isArray(raw)) {
13490
+ return raw;
13491
+ }
13492
+ // Array.isArray narrows `raw` to `any[]`; view it as `unknown[]` so the
13493
+ // map/filter chain stays type-safe and no `any` leaks into the return.
13494
+ const entries = raw;
13495
+ return entries.map((entry)=>{
13496
+ // Only touch plain objects; non-objects fall through to fail validation.
13497
+ if (typeof entry !== 'object' || entry === null) {
13498
+ return entry;
13499
+ }
13500
+ const record = entry;
13501
+ // Older backend predating productType: default to the only type then.
13502
+ return record.productType === undefined ? {
13503
+ ...record,
13504
+ productType: 'vault'
13505
+ } : record;
13506
+ }).filter((entry)=>{
13507
+ // Drop ONLY a present-but-unknown string discriminant (a future
13508
+ // productType this SDK version doesn't know). Everything else —
13509
+ // non-objects, a non-string productType — flows through to
13510
+ // z.array(earnOpportunitySchema) and fails/passes validation normally.
13511
+ if (typeof entry !== 'object' || entry === null) {
13512
+ return true;
13513
+ }
13514
+ const productType = entry.productType;
13515
+ if (typeof productType !== 'string') {
13516
+ return true;
13517
+ }
13518
+ return knownProductTypes.has(productType);
13519
+ });
13520
+ }, zod.z.array(earnOpportunitySchema));
13296
13521
  // ---------------------------------------------------------------------------
13297
13522
  // Position response schema
13298
13523
  // ---------------------------------------------------------------------------
@@ -13422,6 +13647,7 @@ const positionPnlSchema = zod.z.discriminatedUnion('status', [
13422
13647
  *
13423
13648
  * @internal
13424
13649
  */ const depositPayloadSchema = zod.z.object({
13650
+ execId: bridgeDepositExecIdSchema,
13425
13651
  executionParams: depositExecutionParamsSchema,
13426
13652
  signature: hexSignatureSchema
13427
13653
  });
@@ -13513,6 +13739,21 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13513
13739
  amount: amountJsonSchema,
13514
13740
  vaultAddress: hexAddressSchema
13515
13741
  }).passthrough();
13742
+ /** @internal */ const bridgeQuoteExpirySchema = zod.z.discriminatedUnion('mode', [
13743
+ zod.z.object({
13744
+ mode: zod.z.literal('TIMESTAMP'),
13745
+ expiresAt: zod.z.string().datetime({
13746
+ offset: true
13747
+ })
13748
+ }),
13749
+ zod.z.object({
13750
+ mode: zod.z.literal('BLOCK_NUMBER'),
13751
+ expiresAtBlock: zod.z.number().int(),
13752
+ blockEstimatedAt: zod.z.string().datetime({
13753
+ offset: true
13754
+ }).optional()
13755
+ })
13756
+ ]).optional().catch(undefined);
13516
13757
  /**
13517
13758
  * Zod schema for the bridge deposit prepare payload.
13518
13759
  *
@@ -13524,6 +13765,10 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13524
13765
  execId: bridgeDepositExecIdSchema,
13525
13766
  erc3009TypedData: bridgeDepositPreparedBundleSchema,
13526
13767
  expiresAt: zod.z.string().datetime(),
13768
+ quoteIssuedAt: zod.z.string().datetime({
13769
+ offset: true
13770
+ }).optional().catch(undefined),
13771
+ quoteExpiry: bridgeQuoteExpirySchema,
13527
13772
  review: bridgeDepositPrepareReviewSchema
13528
13773
  });
13529
13774
  /**
@@ -13589,6 +13834,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13589
13834
  *
13590
13835
  * @internal
13591
13836
  */ const withdrawPayloadSchema = zod.z.object({
13837
+ execId: bridgeDepositExecIdSchema,
13592
13838
  executionParams: withdrawExecutionParamsSchema,
13593
13839
  signature: hexSignatureSchema
13594
13840
  });
@@ -13602,6 +13848,27 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13602
13848
  data: withdrawPayloadSchema
13603
13849
  });
13604
13850
  // ---------------------------------------------------------------------------
13851
+ // Transaction report response schema
13852
+ // ---------------------------------------------------------------------------
13853
+ /**
13854
+ * Zod schema for the transaction report payload inside the API `data` envelope.
13855
+ *
13856
+ * The Earn Service returns an empty payload (`{"data":{}}`) on success, so the
13857
+ * schema accepts any object shape and does not require specific fields.
13858
+ *
13859
+ * @internal
13860
+ */ const transactionReportPayloadSchema = zod.z.object({}).passthrough();
13861
+ /**
13862
+ * Zod schema for the `POST /v1/earnKit/transactions/report` API response.
13863
+ *
13864
+ * The Earn Service API wraps the transaction report payload in a `data`
13865
+ * envelope.
13866
+ *
13867
+ * @internal
13868
+ */ const transactionReportResponseSchema = zod.z.object({
13869
+ data: transactionReportPayloadSchema
13870
+ });
13871
+ // ---------------------------------------------------------------------------
13605
13872
  // Claim rewards response schema
13606
13873
  // ---------------------------------------------------------------------------
13607
13874
  /**
@@ -13649,10 +13916,11 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13649
13916
  * Zod schema for a fee entry in an EarnKit API response.
13650
13917
  *
13651
13918
  * Shared across deposit and withdrawal responses (and reusable for real
13652
- * charged fees, not just quote estimates). `type` identifies the fee category
13653
- * for cross-chain deposit quotes this is the kits-proxy fee-quote item type
13654
- * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). `status` qualifies the fee (e.g.
13655
- * `'estimated'` for a pre-sign cross-chain fee). Both are omitted on plain fees.
13919
+ * charged fees, not just quote estimates). `type` identifies the fee category.
13920
+ * For cross-chain deposit quotes this is the kits-proxy fee-quote item type
13921
+ * (e.g. `'FORWARD'`, `'PRE_FINALITY'`). For withdrawal quotes, Circle fees use
13922
+ * `type: 'circle'`. `status` qualifies the fee (e.g. `'estimated'` for a
13923
+ * pre-sign cross-chain fee). Both are omitted on plain fees.
13656
13924
  *
13657
13925
  * @internal
13658
13926
  */ const feeSchema = zod.z.object({
@@ -13661,6 +13929,30 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13661
13929
  token: zod.z.string(),
13662
13930
  amount: amountJsonSchema
13663
13931
  });
13932
+ /**
13933
+ * Zod schema for a native gas-fee entry in an EarnKit quote response.
13934
+ *
13935
+ * The Earn Service backend estimates gas server-side and returns one entry per
13936
+ * action (`Approve`, `Deposit`, `Withdraw`). A successful estimate carries
13937
+ * `fees` in the SDK `EstimatedGas` shape (`{ gas, gasPrice, fee }`), each a raw
13938
+ * integer string in the chain's native base units. When the backend cannot
13939
+ * estimate an action it returns `fees: null` with an `error` message instead.
13940
+ *
13941
+ * The schema deliberately validates almost nothing beyond the envelope: `name`
13942
+ * is optional and `fees` is entirely unvalidated (`unknown`). ALL validation
13943
+ * of `fees` — that it is an object at all, and that `gas`, `gasPrice`, and
13944
+ * `fee` are parseable integer strings — is deferred to {@link toQuoteGasFees},
13945
+ * which degrades a malformed entry to a `fees: null` soft failure. This is
13946
+ * intentional: gas is best-effort, so a single unparseable gas entry (a wrong
13947
+ * type such as `fees: 123` or `fees: 'bad'`, a missing field, or a non-numeric
13948
+ * `fee`) must never fail Zod validation and reject the entire quote.
13949
+ *
13950
+ * @internal
13951
+ */ const quoteGasFeeSchema = zod.z.object({
13952
+ name: zod.z.string().optional(),
13953
+ fees: zod.z.unknown(),
13954
+ error: zod.z.string().optional()
13955
+ }).passthrough();
13664
13956
  /**
13665
13957
  * Zod schema for the inner deposit quote payload.
13666
13958
  *
@@ -13676,7 +13968,8 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13676
13968
  expectedShares: amountJsonSchema,
13677
13969
  sharePrice: zod.z.string(),
13678
13970
  currentApy: zod.z.number(),
13679
- fees: zod.z.array(feeSchema).optional()
13971
+ fees: zod.z.array(feeSchema).optional(),
13972
+ gasFees: zod.z.array(quoteGasFeeSchema).optional()
13680
13973
  });
13681
13974
  /**
13682
13975
  * Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
@@ -13703,6 +13996,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13703
13996
  sharePrice: zod.z.string(),
13704
13997
  maxWithdrawable: amountJsonSchema,
13705
13998
  fees: zod.z.array(feeSchema),
13999
+ gasFees: zod.z.array(quoteGasFeeSchema).optional(),
13706
14000
  warnings: zod.z.array(zod.z.string()).optional()
13707
14001
  });
13708
14002
  /**
@@ -13760,7 +14054,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13760
14054
  *
13761
14055
  * @internal
13762
14056
  */ const getVaultsPayloadSchema = zod.z.object({
13763
- vaults: zod.z.array(vaultInfoResponseSchema),
14057
+ vaults: earnOpportunityListSchema,
13764
14058
  errors: zod.z.array(vaultErrorSchema)
13765
14059
  });
13766
14060
  /**
@@ -13790,7 +14084,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13790
14084
  *
13791
14085
  * @internal
13792
14086
  */ const exploreVaultsPayloadSchema = zod.z.object({
13793
- vaults: zod.z.array(vaultInfoResponseSchema),
14087
+ vaults: earnOpportunityListSchema,
13794
14088
  pagination: explorePaginationSchema
13795
14089
  });
13796
14090
  /**
@@ -13883,6 +14177,16 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13883
14177
  */ function isWithdrawResponse(value) {
13884
14178
  return withdrawResponseSchema.safeParse(value).success;
13885
14179
  }
14180
+ /**
14181
+ * Type guard for the transaction report API response.
14182
+ *
14183
+ * @param value - Unknown response value to validate
14184
+ * @returns True when the value matches the transaction report response shape
14185
+ *
14186
+ * @internal
14187
+ */ function isTransactionReportResponse(value) {
14188
+ return transactionReportResponseSchema.safeParse(value).success;
14189
+ }
13886
14190
  /**
13887
14191
  * Type guard for the claim rewards API response.
13888
14192
  *
@@ -13924,19 +14228,80 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13924
14228
  return claimRewardsQuoteResponseSchema.safeParse(value).success;
13925
14229
  }
13926
14230
 
14231
+ var name = "@circle-fin/provider-earn-service";
14232
+ var version = "1.3.0";
14233
+ var pkg = {
14234
+ name: name,
14235
+ version: version};
14236
+
13927
14237
  /**
13928
- * Build an API polling config with optional authorization header,
13929
- * and resolve the base URL (configurable for testing).
13930
- *
13931
- * @param serviceConfig - Optional earn service configuration
13932
- * @returns Resolved polling config and base URL
14238
+ * HTTP header name used to report the EarnKit SDK version to the backend.
13933
14239
  *
13934
14240
  * @internal
13935
- */ function buildConfig(serviceConfig) {
13936
- const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
13937
- if (serviceConfig?.kitKey === undefined) {
14241
+ */ const SDK_VERSION_HEADER = 'X-EarnKit-SDK-Version';
14242
+ /**
14243
+ * Resolve the SDK version string from its source components.
14244
+ *
14245
+ * Resolution order:
14246
+ * 1. The registered kit identifier (preferred) — e.g. `@circle-fin/earn-kit/1.1.0`
14247
+ * becomes `earn-kit/1.1.0`.
14248
+ * 2. The provider package itself (fallback when no kit is registered) —
14249
+ * e.g. `provider-earn-service/1.1.0`.
14250
+ * 3. The literal `unknown` when neither source is available.
14251
+ *
14252
+ * Kept as a pure function (no global / module reads) so every branch is
14253
+ * unit-testable.
14254
+ *
14255
+ * @param kitId - The registered kit identifier, or `undefined`.
14256
+ * @param providerName - The provider package name (e.g. `@circle-fin/provider-earn-service`).
14257
+ * @param providerVersion - The provider package version, or `undefined`.
14258
+ * @returns The formatted SDK version string.
14259
+ *
14260
+ * @internal
14261
+ */ function formatSdkVersion(kitId, providerName, providerVersion) {
14262
+ if (kitId !== undefined && kitId !== '') {
14263
+ // e.g. '@circle-fin/earn-kit/1.1.0' -> 'earn-kit/1.1.0'
14264
+ return resolveKitSdkName(kitId);
14265
+ }
14266
+ {
14267
+ // e.g. '@circle-fin/provider-earn-service' + '1.1.0' -> 'provider-earn-service/1.1.0'
14268
+ return `${resolveKitSdkName(providerName)}/${providerVersion}`;
14269
+ }
14270
+ }
14271
+ /**
14272
+ * Resolve the value for the {@link SDK_VERSION_HEADER} header.
14273
+ *
14274
+ * Reads the kit registered at runtime via {@link createRequestContext} and
14275
+ * falls back to this provider package's own version when no kit is registered.
14276
+ *
14277
+ * @returns The SDK version string, e.g. `earn-kit/1.1.0` or
14278
+ * `provider-earn-service/1.1.0`, or `unknown`.
14279
+ *
14280
+ * @internal
14281
+ */ function resolveSdkVersionHeader() {
14282
+ return formatSdkVersion(createRequestContext().kit, pkg.name, pkg.version);
14283
+ }
14284
+
14285
+ /**
14286
+ * Build an API polling config with optional authorization header,
14287
+ * and resolve the base URL (configurable for testing).
14288
+ *
14289
+ * @param serviceConfig - Optional earn service configuration
14290
+ * @returns Resolved polling config and base URL
14291
+ *
14292
+ * @internal
14293
+ */ function buildConfig(serviceConfig) {
14294
+ const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
14295
+ const sdkVersion = resolveSdkVersionHeader();
14296
+ if (serviceConfig?.kitKey === undefined) {
13938
14297
  return {
13939
- pollingConfig: DEFAULT_CONFIG,
14298
+ pollingConfig: {
14299
+ ...DEFAULT_CONFIG,
14300
+ headers: {
14301
+ ...DEFAULT_CONFIG.headers,
14302
+ [SDK_VERSION_HEADER]: sdkVersion
14303
+ }
14304
+ },
13940
14305
  baseUrl
13941
14306
  };
13942
14307
  }
@@ -13952,6 +14317,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13952
14317
  ...DEFAULT_CONFIG,
13953
14318
  headers: {
13954
14319
  ...DEFAULT_CONFIG.headers,
14320
+ [SDK_VERSION_HEADER]: sdkVersion,
13955
14321
  Authorization: `Bearer ${serviceConfig.kitKey}`
13956
14322
  }
13957
14323
  },
@@ -13983,7 +14349,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13983
14349
  }
13984
14350
 
13985
14351
  /**
13986
- * Convert an API vault info object into the SDK {@link VaultInfo} shape.
14352
+ * Convert an API vault info object into the SDK {@link EarnOpportunity} shape.
13987
14353
  *
13988
14354
  * Map the API chain code back to the SDK chain identifier and hydrate the
13989
14355
  * amount payloads into {@link Amount} instances.
@@ -13994,16 +14360,29 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
13994
14360
  *
13995
14361
  * @internal
13996
14362
  */ function toVaultInfo(data) {
13997
- const { totalDeposits, liquidity, ...vault } = data;
14363
+ const { totalDeposits, liquidity, liquidityProfile, ...vault } = data;
13998
14364
  const chain = toSdkChain(vault.chain);
13999
14365
  if (chain === undefined) {
14000
14366
  throw createInvalidChainError(vault.chain, 'Chain returned by the Earn Service is not supported by the SDK');
14001
14367
  }
14368
+ // The nested facets are `.optional()` in the schema (a backend that predates
14369
+ // them omits them) and are typed optional on `EarnOpportunity` to match.
14370
+ // Convert the nested liquidity amounts when present and pass the remaining
14371
+ // facets straight through; each absent facet stays absent rather than being
14372
+ // asserted present by a cast.
14002
14373
  return {
14003
14374
  ...vault,
14004
14375
  chain,
14005
14376
  totalDeposits: Amount.fromJSON(totalDeposits),
14006
- liquidity: Amount.fromJSON(liquidity)
14377
+ liquidity: Amount.fromJSON(liquidity),
14378
+ ...liquidityProfile !== undefined && {
14379
+ liquidityProfile: {
14380
+ ...liquidityProfile,
14381
+ totalDeposits: Amount.fromJSON(liquidityProfile.totalDeposits),
14382
+ available: Amount.fromJSON(liquidityProfile.available),
14383
+ totalSupply: Amount.fromJSON(liquidityProfile.totalSupply)
14384
+ }
14385
+ }
14007
14386
  };
14008
14387
  }
14009
14388
 
@@ -14038,8 +14417,11 @@ function toVaultError(error) {
14038
14417
  }
14039
14418
  try {
14040
14419
  const response = await pollApiGet(url.toString(), isGetVaultsResponse, pollingConfig);
14420
+ // `pollApiGet` validates via a boolean guard and returns the raw JSON — it
14421
+ // does not run the schema's preprocess. Parse explicitly so unknown
14422
+ // `productType` values are dropped before `toVaultInfo`.
14041
14423
  return {
14042
- vaults: response.data.vaults.map(toVaultInfo),
14424
+ vaults: earnOpportunityListSchema.parse(response.data.vaults).map(toVaultInfo),
14043
14425
  errors: response.data.errors.map(toVaultError)
14044
14426
  };
14045
14427
  } catch (error) {
@@ -14088,8 +14470,11 @@ function toVaultError(error) {
14088
14470
  }
14089
14471
  try {
14090
14472
  const response = await pollApiGet(url.toString(), isExploreVaultsResponse, pollingConfig);
14473
+ // `pollApiGet` validates via a boolean guard and returns the raw JSON — it
14474
+ // does not run the schema's preprocess. Parse explicitly so unknown
14475
+ // `productType` values are dropped before `toVaultInfo`.
14091
14476
  return {
14092
- vaults: response.data.vaults.map(toVaultInfo),
14477
+ vaults: earnOpportunityListSchema.parse(response.data.vaults).map(toVaultInfo),
14093
14478
  pagination: response.data.pagination
14094
14479
  };
14095
14480
  } catch (error) {
@@ -14253,6 +14638,12 @@ function toPositionInfo(data) {
14253
14638
  execId: response.data.execId,
14254
14639
  preparedBundle,
14255
14640
  expiresAt: response.data.expiresAt,
14641
+ ...response.data.quoteIssuedAt !== undefined && {
14642
+ quoteIssuedAt: response.data.quoteIssuedAt
14643
+ },
14644
+ ...response.data.quoteExpiry !== undefined && {
14645
+ quoteExpiry: response.data.quoteExpiry
14646
+ },
14256
14647
  review: response.data.review
14257
14648
  };
14258
14649
  } catch (error) {
@@ -14594,7 +14985,110 @@ function toClaimedAmount(reward) {
14594
14985
  }
14595
14986
  }
14596
14987
 
14597
- function toDepositQuoteInfo(data) {
14988
+ /**
14989
+ * Map the Earn Service's server-side quote gas estimates into the SDK
14990
+ * {@link EarnGasFeeEstimate} shape.
14991
+ *
14992
+ * The Earn Service estimates gas for each action (`Approve`, `Deposit`,
14993
+ * `Withdraw`) and returns `{ name, fees: { gas, gasPrice, fee } }` with raw
14994
+ * integer strings.
14995
+ * The SDK type additionally carries `token` (the chain's native currency
14996
+ * symbol) and `blockchain`, which are filled in here from the chain
14997
+ * definition.
14998
+ *
14999
+ * Gas reporting is best-effort: a malformed entry (e.g. a non-integer string
15000
+ * that fails `BigInt` parsing) degrades to a `{ fees: null, error }` estimate
15001
+ * rather than throwing, so one bad entry never fails the whole quote.
15002
+ *
15003
+ * @param gasFees - Backend gas-fee entries from the quote response, if any.
15004
+ * @param chain - Chain definition, used for the native token symbol and
15005
+ * blockchain identifier.
15006
+ * @returns One {@link EarnGasFeeEstimate} per backend entry (empty when the
15007
+ * backend returned none).
15008
+ *
15009
+ * @example
15010
+ * ```typescript
15011
+ * toQuoteGasFees(
15012
+ * [{ name: 'Deposit', fees: { gas: '364142', gasPrice: '21000000000', fee: '7646982000000000' } }],
15013
+ * arcTestnet,
15014
+ * )
15015
+ * // [{ name: 'Deposit', token: 'USDC', blockchain: 'Arc_Testnet',
15016
+ * // fees: { gas: 364142n, gasPrice: 21000000000n, fee: '7646982000000000' } }]
15017
+ * ```
15018
+ *
15019
+ * @internal
15020
+ */ function toQuoteGasFees(gasFees, chain) {
15021
+ if (gasFees === undefined) {
15022
+ return [];
15023
+ }
15024
+ return gasFees.map((entry)=>{
15025
+ const base = {
15026
+ // `name` is optional on the wire; label an unnamed entry rather than
15027
+ // emitting `name: undefined`.
15028
+ name: entry.name ?? 'Unknown',
15029
+ token: chain.nativeCurrency.symbol,
15030
+ blockchain: chain.chain
15031
+ };
15032
+ // The Earn Service itself reports a failed estimate as `fees: null` with
15033
+ // an error; propagate that soft failure verbatim.
15034
+ if (entry.fees === null || entry.fees === undefined) {
15035
+ return {
15036
+ ...base,
15037
+ fees: null,
15038
+ error: entry.error ?? 'gas estimate unavailable'
15039
+ };
15040
+ }
15041
+ // `fees` is `unknown` at the schema layer, so ALL validation happens here:
15042
+ // that it is an object at all, and that `gas`, `gasPrice`, and `fee` are
15043
+ // each parseable integer strings (including `fee`, which the SDK contract
15044
+ // requires be a numeric base-unit string). Any failure — a wrong type
15045
+ // (`fees: 123`), a missing field, or a non-numeric value — degrades the
15046
+ // whole entry to a `fees: null` soft failure rather than surfacing a
15047
+ // malformed "successful" estimate or rejecting the quote.
15048
+ try {
15049
+ if (typeof entry.fees !== 'object') {
15050
+ throw new TypeError(`gas fees must be an object (got ${typeof entry.fees})`);
15051
+ }
15052
+ const { gas, gasPrice, fee } = entry.fees;
15053
+ return {
15054
+ ...base,
15055
+ fees: {
15056
+ gas: toBigInt('gas', gas),
15057
+ gasPrice: toBigInt('gasPrice', gasPrice),
15058
+ fee: toBigInt('fee', fee).toString()
15059
+ }
15060
+ };
15061
+ } catch (error) {
15062
+ return {
15063
+ ...base,
15064
+ fees: null,
15065
+ error: getErrorMessage(error)
15066
+ };
15067
+ }
15068
+ });
15069
+ }
15070
+ /**
15071
+ * Parse an unknown value into a `bigint`, rejecting anything that is not a
15072
+ * non-empty integer string. `BigInt` alone is too permissive for this path —
15073
+ * it accepts numbers, booleans, and empty strings — so guard the type first.
15074
+ *
15075
+ * @param field - Field name, used in the thrown error message.
15076
+ * @param value - Raw value from the backend gas entry.
15077
+ * @returns The parsed `bigint`.
15078
+ * @throws {TypeError} When `value` is not a non-empty integer string.
15079
+ */ function toBigInt(field, value) {
15080
+ if (typeof value !== 'string' || value.trim() === '') {
15081
+ throw new TypeError(`gas fee field "${field}" must be an integer string`);
15082
+ }
15083
+ try {
15084
+ // BigInt throws on non-integer strings (e.g. "1.5", "not-a-number").
15085
+ return BigInt(value);
15086
+ } catch {
15087
+ throw new Error(`gas fee field "${field}" is not a valid integer string: ${value}`);
15088
+ }
15089
+ }
15090
+
15091
+ function toDepositQuoteInfo(data, chain) {
14598
15092
  const fees = (data.fees ?? []).map(({ token: feeTokenSymbol, ...fee })=>{
14599
15093
  // Earn Service returns fee.token as a display symbol, for example "USDC".
14600
15094
  return {
@@ -14623,7 +15117,10 @@ function toDepositQuoteInfo(data) {
14623
15117
  sharePrice: data.sharePrice,
14624
15118
  currentApy: data.currentApy,
14625
15119
  fees,
14626
- gasFees: []
15120
+ // The Earn Service estimates gas server-side; the chain fills token/blockchain.
15121
+ // Cross-chain quotes resolve no local chain definition, so gasFees stays
15122
+ // empty there (unchanged behavior).
15123
+ gasFees: chain === undefined ? [] : toQuoteGasFees(data.gasFees, chain)
14627
15124
  };
14628
15125
  }
14629
15126
  /**
@@ -14658,7 +15155,7 @@ function toDepositQuoteInfo(data) {
14658
15155
  };
14659
15156
  try {
14660
15157
  const response = await pollApiPost(url.toString(), requestBody, isDepositQuoteResponse, pollingConfig);
14661
- return toDepositQuoteInfo(response.data);
15158
+ return toDepositQuoteInfo(response.data, params.chainDefinition);
14662
15159
  } catch (error) {
14663
15160
  throw parseEarnApiError(error, {
14664
15161
  operation: 'getDepositQuote'
@@ -14666,7 +15163,7 @@ function toDepositQuoteInfo(data) {
14666
15163
  }
14667
15164
  }
14668
15165
 
14669
- function toWithdrawalQuoteInfo(data) {
15166
+ function toWithdrawalQuoteInfo(data, chain) {
14670
15167
  return {
14671
15168
  vaultAddress: data.vaultAddress,
14672
15169
  vaultName: data.vaultName,
@@ -14684,11 +15181,17 @@ function toWithdrawalQuoteInfo(data) {
14684
15181
  symbol: data.asset,
14685
15182
  amount: Amount.fromJSON(data.maxWithdrawable)
14686
15183
  },
14687
- fees: data.fees.map((fee)=>({
14688
- symbol: fee.token,
14689
- amount: Amount.fromJSON(fee.amount)
15184
+ fees: data.fees.map(({ token, amount, type, status })=>({
15185
+ symbol: token,
15186
+ amount: Amount.fromJSON(amount),
15187
+ ...type !== undefined && {
15188
+ type
15189
+ },
15190
+ ...status !== undefined && {
15191
+ status
15192
+ }
14690
15193
  })),
14691
- gasFees: [],
15194
+ gasFees: chain === undefined ? [] : toQuoteGasFees(data.gasFees, chain),
14692
15195
  // Wire format uses `warnings`, but the SDK surface uses
14693
15196
  // `earnKitWarnings` to match the precedent set by `VaultInfo` —
14694
15197
  // `warnings` is reserved for the structured `VaultWarning` shape.
@@ -14720,7 +15223,7 @@ function toWithdrawalQuoteInfo(data) {
14720
15223
  };
14721
15224
  try {
14722
15225
  const response = await pollApiPost(url.toString(), requestBody, isWithdrawalQuoteResponse, pollingConfig);
14723
- return toWithdrawalQuoteInfo(response.data);
15226
+ return toWithdrawalQuoteInfo(response.data, params.chainDefinition);
14724
15227
  } catch (error) {
14725
15228
  throw parseEarnApiError(error, {
14726
15229
  operation: 'getWithdrawalQuote'
@@ -14756,6 +15259,8 @@ function toWithdrawalQuoteInfo(data) {
14756
15259
  amount: Amount.fromJSON(r.amount),
14757
15260
  address: r.token
14758
15261
  })),
15262
+ // The claimRewards/quote response does not carry a gas estimate (unlike
15263
+ // deposit/withdrawal quotes), so there is nothing to surface here.
14759
15264
  gasFees: []
14760
15265
  };
14761
15266
  } catch (error) {
@@ -14765,6 +15270,83 @@ function toWithdrawalQuoteInfo(data) {
14765
15270
  }
14766
15271
  }
14767
15272
 
15273
+ /**
15274
+ * Build the native gas triple the backend expects for `gasUsed`.
15275
+ *
15276
+ * Returns `undefined` unless both receipt components are present, so the
15277
+ * caller can omit the field entirely — the Earn Service treats a missing
15278
+ * triple as "skip the gas cache write, still return 200".
15279
+ *
15280
+ * @param gasUsed - Receipt gas units used.
15281
+ * @param effectiveGasPrice - Receipt effective gas price.
15282
+ * @returns The `{ gas, gasPrice, fee }` triple, or `undefined` when either
15283
+ * component is missing.
15284
+ *
15285
+ * @example
15286
+ * ```typescript
15287
+ * buildReportedGasUsed(362454n, 29466364605n)
15288
+ * // { gas: '362454', gasPrice: '29466364605', fee: '10680201716540670' }
15289
+ * ```
15290
+ *
15291
+ * @internal
15292
+ */ function buildReportedGasUsed(gasUsed, effectiveGasPrice) {
15293
+ if (gasUsed === undefined || effectiveGasPrice === undefined) {
15294
+ return undefined;
15295
+ }
15296
+ return {
15297
+ gas: gasUsed.toString(),
15298
+ gasPrice: effectiveGasPrice.toString(),
15299
+ fee: (gasUsed * effectiveGasPrice).toString()
15300
+ };
15301
+ }
15302
+ /**
15303
+ * Report the outcome of an SDK-submitted same-chain Earn transaction.
15304
+ *
15305
+ * @param params - Transaction report parameters.
15306
+ * @throws {@link KitError} When the API call fails.
15307
+ *
15308
+ * @internal
15309
+ */ async function reportEarnTransaction(params) {
15310
+ const { pollingConfig, baseUrl } = buildConfig(params.config);
15311
+ const url = new URL(`${EARN_KIT_API_PREFIX}/transactions/report`, baseUrl);
15312
+ // The report endpoint is not idempotent: success reports refresh the gas
15313
+ // cache and failure reports increment counts. If the first request succeeds
15314
+ // server-side but the client times out or sees a transient 5xx, retrying
15315
+ // would duplicate the report (double-writing an outcome or inflating failure
15316
+ // counts). Reporting is best-effort (see the fire-and-forget caller), so
15317
+ // make exactly one attempt and never retry — a single dropped report is
15318
+ // preferable to a duplicated one. `maxRetries` here is the total attempt
15319
+ // count in pollApiWithValidation (loop runs `attempt <= maxRetries`), so 1
15320
+ // means one request with no retry; 0 would skip the request entirely.
15321
+ const reportConfig = {
15322
+ ...pollingConfig,
15323
+ maxRetries: 1
15324
+ };
15325
+ const gasUsed = buildReportedGasUsed(params.gasUsed, params.effectiveGasPrice);
15326
+ const requestBody = {
15327
+ execId: params.execId,
15328
+ chain: params.chain,
15329
+ status: params.status,
15330
+ action: params.action,
15331
+ ...params.txHash !== undefined && {
15332
+ txHash: params.txHash
15333
+ },
15334
+ ...gasUsed !== undefined && {
15335
+ gasUsed
15336
+ },
15337
+ ...params.errorCode !== undefined && {
15338
+ errorCode: params.errorCode
15339
+ }
15340
+ };
15341
+ try {
15342
+ await pollApiPost(url.toString(), requestBody, isTransactionReportResponse, reportConfig);
15343
+ } catch (error) {
15344
+ throw parseEarnApiError(error, {
15345
+ operation: 'transactionReport'
15346
+ });
15347
+ }
15348
+ }
15349
+
14768
15350
  /**
14769
15351
  * Sum the amounts across every token input to size the allowance approval.
14770
15352
  *
@@ -14875,6 +15457,59 @@ function toWithdrawalQuoteInfo(data) {
14875
15457
  // Intentionally built-ins-only: Earn bridge support is limited to SDK-known
14876
15458
  // token contracts plus the explicit ERC-3009 domain allowlist below.
14877
15459
  const TOKEN_REGISTRY = createTokenRegistry();
15460
+ function submitTransactionReport(reportContext, action, status, details) {
15461
+ void reportEarnTransaction({
15462
+ execId: reportContext.execId,
15463
+ chain: reportContext.chain,
15464
+ config: reportContext.config,
15465
+ action,
15466
+ status,
15467
+ ...details
15468
+ }).catch(()=>undefined);
15469
+ }
15470
+ function reportTransactionSuccess(reportContext, action, result) {
15471
+ if (result === undefined) {
15472
+ return;
15473
+ }
15474
+ submitTransactionReport(reportContext, action, 'success', {
15475
+ txHash: result.txHash,
15476
+ gasUsed: result.gasUsed,
15477
+ effectiveGasPrice: result.effectiveGasPrice
15478
+ });
15479
+ }
15480
+ function reportTransactionFailure(reportContext, action, error) {
15481
+ submitTransactionReport(reportContext, action, 'failure', {
15482
+ txHash: transactionReportTxHash(error),
15483
+ errorCode: transactionReportErrorCode(error)
15484
+ });
15485
+ }
15486
+ function transactionReportErrorCode(error) {
15487
+ if (isKitError(error)) {
15488
+ return error.name;
15489
+ }
15490
+ const message = getErrorMessage(error);
15491
+ if (/user (rejected|denied)|rejected by user/i.test(message)) {
15492
+ return 'USER_REJECTED';
15493
+ }
15494
+ if (/insufficient funds/i.test(message)) {
15495
+ return 'INSUFFICIENT_FUNDS';
15496
+ }
15497
+ if (/timeout|timed out/i.test(message)) {
15498
+ return 'TIMEOUT';
15499
+ }
15500
+ return 'UNKNOWN_ERROR';
15501
+ }
15502
+ function transactionReportTxHash(error) {
15503
+ if (!isKitError(error)) {
15504
+ return undefined;
15505
+ }
15506
+ const trace = error.cause?.trace;
15507
+ if (typeof trace !== 'object' || trace === null) {
15508
+ return undefined;
15509
+ }
15510
+ const txHash = trace['txHash'];
15511
+ return typeof txHash === 'string' && txHash !== '' ? txHash : undefined;
15512
+ }
14878
15513
  /**
14879
15514
  * Build the typed error raised when a cross-chain wait is cancelled via its
14880
15515
  * `AbortSignal`. Mirrors `@core/adapter-base`'s `createAbortError` (same
@@ -15196,7 +15831,7 @@ function finishElapsedWait(lastStatus, lastError) {
15196
15831
  const adapterContractAddress = requireAdapterContract(chain);
15197
15832
  const { adapter } = params.from;
15198
15833
  const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
15199
- const { executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
15834
+ const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
15200
15835
  vaultAddress,
15201
15836
  amount: params.amount,
15202
15837
  address,
@@ -15204,32 +15839,55 @@ function finishElapsedWait(lastStatus, lastError) {
15204
15839
  config
15205
15840
  }), ()=>undefined);
15206
15841
  validateExecutionDeadline(executionParams);
15842
+ const transactionReportContext = {
15843
+ execId,
15844
+ chain: apiChain,
15845
+ config
15846
+ };
15207
15847
  const approvalToken = resolveEarnApprovalToken(executionParams);
15208
15848
  const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
15209
15849
  const requiredAllowance = sumTokenInputAmounts(tokenInputs);
15210
15850
  if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
15211
- await this.runPhase(ctx, 'approve', 'approve', async ()=>approveAllowanceIfNeeded({
15851
+ await this.runPhase(ctx, 'approve', 'approve', async ()=>{
15852
+ try {
15853
+ const approval = await approveAllowanceIfNeeded({
15854
+ adapter,
15855
+ chain,
15856
+ tokenAddress: approvalToken,
15857
+ delegate: adapterContractAddress,
15858
+ address,
15859
+ requiredAllowance,
15860
+ revertMessage: 'Earn deposit token approval reverted on-chain'
15861
+ });
15862
+ reportTransactionSuccess(transactionReportContext, 'Approve', approval);
15863
+ return approval;
15864
+ } catch (error) {
15865
+ reportTransactionFailure(transactionReportContext, 'Approve', error);
15866
+ throw error;
15867
+ }
15868
+ }, (approval)=>approval?.txHash);
15869
+ }
15870
+ const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>{
15871
+ try {
15872
+ const result = await executeEarnAction({
15212
15873
  adapter,
15213
15874
  chain,
15214
- tokenAddress: approvalToken,
15215
- delegate: adapterContractAddress,
15216
15875
  address,
15217
- requiredAllowance,
15218
- revertMessage: 'Earn deposit token approval reverted on-chain'
15219
- }), (txHash)=>txHash);
15220
- }
15221
- const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>executeEarnAction({
15222
- adapter,
15223
- chain,
15224
- address,
15225
- actionKey: 'earn.deposit',
15226
- actionParams: {
15227
- executeParams: executionParams,
15228
- tokenInputs,
15229
- signature
15230
- },
15231
- revertMessage: 'Earn deposit reverted on-chain'
15232
- }), ({ txHash })=>txHash);
15876
+ actionKey: 'earn.deposit',
15877
+ actionParams: {
15878
+ executeParams: executionParams,
15879
+ tokenInputs,
15880
+ signature
15881
+ },
15882
+ revertMessage: 'Earn deposit reverted on-chain'
15883
+ });
15884
+ reportTransactionSuccess(transactionReportContext, 'Deposit', result);
15885
+ return result;
15886
+ } catch (error) {
15887
+ reportTransactionFailure(transactionReportContext, 'Deposit', error);
15888
+ throw error;
15889
+ }
15890
+ }, ({ txHash })=>txHash);
15233
15891
  return {
15234
15892
  kind: 'same-chain',
15235
15893
  txHash,
@@ -15309,7 +15967,13 @@ function finishElapsedWait(lastStatus, lastError) {
15309
15967
  amount: params.amount,
15310
15968
  sourceChain: sourceChain.chain,
15311
15969
  destinationChain: destinationChain.chain,
15312
- expiresAt: prepared.expiresAt
15970
+ expiresAt: prepared.expiresAt,
15971
+ ...prepared.quoteIssuedAt !== undefined && {
15972
+ quoteIssuedAt: prepared.quoteIssuedAt
15973
+ },
15974
+ ...prepared.quoteExpiry !== undefined && {
15975
+ quoteExpiry: prepared.quoteExpiry
15976
+ }
15313
15977
  };
15314
15978
  }
15315
15979
  /** {@inheritdoc} */ async withdraw(params) {
@@ -15330,7 +15994,7 @@ function finishElapsedWait(lastStatus, lastError) {
15330
15994
  const adapterContractAddress = requireAdapterContract(chain);
15331
15995
  const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
15332
15996
  const { adapter } = params.from;
15333
- const { executionParams, signature } = await this.runPhase(ctx, 'withdraw', 'fetchParams', async ()=>fetchWithdraw({
15997
+ const { execId, executionParams, signature } = await this.runPhase(ctx, 'withdraw', 'fetchParams', async ()=>fetchWithdraw({
15334
15998
  vaultAddress,
15335
15999
  amount: params.amount,
15336
16000
  address,
@@ -15338,32 +16002,55 @@ function finishElapsedWait(lastStatus, lastError) {
15338
16002
  config
15339
16003
  }), ()=>undefined);
15340
16004
  validateExecutionDeadline(executionParams);
16005
+ const transactionReportContext = {
16006
+ execId,
16007
+ chain: apiChain,
16008
+ config
16009
+ };
15341
16010
  const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
15342
16011
  const approvalToken = tokenInputs[0]?.token;
15343
16012
  const requiredAllowance = sumTokenInputAmounts(tokenInputs);
15344
16013
  if (!options.skipApprove && approvalToken !== undefined) {
15345
- await this.runPhase(ctx, 'approve', 'approve', async ()=>approveAllowanceIfNeeded({
16014
+ await this.runPhase(ctx, 'approve', 'approve', async ()=>{
16015
+ try {
16016
+ const approval = await approveAllowanceIfNeeded({
16017
+ adapter,
16018
+ chain,
16019
+ tokenAddress: approvalToken,
16020
+ delegate: adapterContractAddress,
16021
+ address,
16022
+ requiredAllowance,
16023
+ revertMessage: 'Vault share token approval reverted on-chain'
16024
+ });
16025
+ reportTransactionSuccess(transactionReportContext, 'Approve', approval);
16026
+ return approval;
16027
+ } catch (error) {
16028
+ reportTransactionFailure(transactionReportContext, 'Approve', error);
16029
+ throw error;
16030
+ }
16031
+ }, (approval)=>approval?.txHash);
16032
+ }
16033
+ const { txHash, explorerUrl } = await this.runPhase(ctx, 'withdraw', 'execute', async ()=>{
16034
+ try {
16035
+ const result = await executeEarnAction({
15346
16036
  adapter,
15347
16037
  chain,
15348
- tokenAddress: approvalToken,
15349
- delegate: adapterContractAddress,
15350
16038
  address,
15351
- requiredAllowance,
15352
- revertMessage: 'Vault share token approval reverted on-chain'
15353
- }), (txHash)=>txHash);
15354
- }
15355
- const { txHash, explorerUrl } = await this.runPhase(ctx, 'withdraw', 'execute', async ()=>executeEarnAction({
15356
- adapter,
15357
- chain,
15358
- address,
15359
- actionKey: 'earn.withdraw',
15360
- actionParams: {
15361
- executeParams: executionParams,
15362
- tokenInputs,
15363
- signature
15364
- },
15365
- revertMessage: 'Earn withdraw reverted on-chain'
15366
- }), ({ txHash })=>txHash);
16039
+ actionKey: 'earn.withdraw',
16040
+ actionParams: {
16041
+ executeParams: executionParams,
16042
+ tokenInputs,
16043
+ signature
16044
+ },
16045
+ revertMessage: 'Earn withdraw reverted on-chain'
16046
+ });
16047
+ reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
16048
+ return result;
16049
+ } catch (error) {
16050
+ reportTransactionFailure(transactionReportContext, 'Withdraw', error);
16051
+ throw error;
16052
+ }
16053
+ }, ({ txHash })=>txHash);
15367
16054
  return {
15368
16055
  txHash,
15369
16056
  explorerUrl,
@@ -15497,141 +16184,6 @@ function finishElapsedWait(lastStatus, lastError) {
15497
16184
  }
15498
16185
  }
15499
16186
  }
15500
- gasEstimateFailure(name, chain, error) {
15501
- return {
15502
- name,
15503
- token: chain.nativeCurrency.symbol,
15504
- blockchain: chain.chain,
15505
- fees: null,
15506
- error: getErrorMessage(error)
15507
- };
15508
- }
15509
- async estimateDepositQuoteGasFees(params) {
15510
- const { adapter, chain, apiChain, address, vaultAddress, amount, config } = params;
15511
- try {
15512
- const adapterContractAddress = requireAdapterContract(chain);
15513
- const normalizedVaultAddress = assertHexAddress('vaultAddress', vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
15514
- const { executionParams, signature } = await fetchDeposit({
15515
- vaultAddress: normalizedVaultAddress,
15516
- amount,
15517
- address,
15518
- chain: apiChain,
15519
- config
15520
- });
15521
- validateExecutionDeadline(executionParams);
15522
- const approvalToken = resolveEarnApprovalToken(executionParams);
15523
- const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
15524
- const requiredAllowance = sumTokenInputAmounts(tokenInputs);
15525
- return await estimateEarnQuoteGasFees({
15526
- adapter,
15527
- chain,
15528
- address,
15529
- actionName: 'Deposit',
15530
- actionKey: 'earn.deposit',
15531
- actionParams: {
15532
- executeParams: executionParams,
15533
- tokenInputs,
15534
- signature
15535
- },
15536
- approval: approvalToken !== undefined && requiredAllowance > 0n ? {
15537
- token: approvalToken,
15538
- delegate: adapterContractAddress,
15539
- requiredAllowance
15540
- } : undefined
15541
- });
15542
- } catch (error) {
15543
- return [
15544
- this.gasEstimateFailure('Deposit', chain, error)
15545
- ];
15546
- }
15547
- }
15548
- async estimateWithdrawalQuoteGasFees(params) {
15549
- const { adapter, chain, apiChain, address, vaultAddress, amount, config } = params;
15550
- try {
15551
- const adapterContractAddress = requireAdapterContract(chain);
15552
- const normalizedVaultAddress = assertHexAddress('vaultAddress', vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
15553
- const { executionParams, signature } = await fetchWithdraw({
15554
- vaultAddress: normalizedVaultAddress,
15555
- amount,
15556
- address,
15557
- chain: apiChain,
15558
- config
15559
- });
15560
- validateExecutionDeadline(executionParams);
15561
- const tokenInputs = buildEarnTokenInputs(executionParams, normalizedVaultAddress);
15562
- const approvalToken = tokenInputs[0]?.token;
15563
- const requiredAllowance = sumTokenInputAmounts(tokenInputs);
15564
- return await estimateEarnQuoteGasFees({
15565
- adapter,
15566
- chain,
15567
- address,
15568
- actionName: 'Withdraw',
15569
- actionKey: 'earn.withdraw',
15570
- actionParams: {
15571
- executeParams: executionParams,
15572
- tokenInputs,
15573
- signature
15574
- },
15575
- approval: approvalToken !== undefined ? {
15576
- token: approvalToken,
15577
- delegate: adapterContractAddress,
15578
- requiredAllowance
15579
- } : undefined
15580
- });
15581
- } catch (error) {
15582
- return [
15583
- this.gasEstimateFailure('Withdraw', chain, error)
15584
- ];
15585
- }
15586
- }
15587
- async estimateClaimRewardsQuoteGasFees(params) {
15588
- const { adapter, chain, apiChain, address, vaultAddress, config } = params;
15589
- try {
15590
- requireAdapterContract(chain);
15591
- const { rewards, executionParams, signature } = await fetchClaimRewards({
15592
- address,
15593
- chain: apiChain,
15594
- vaultAddress,
15595
- config
15596
- });
15597
- if (rewards.length === 0) {
15598
- return [];
15599
- }
15600
- const missingExecutionParams = executionParams === undefined;
15601
- const missingSignature = signature === undefined;
15602
- if (missingExecutionParams || missingSignature) {
15603
- throw new KitError({
15604
- ...EarnError.INTERNAL_ERROR,
15605
- recoverability: 'RETRYABLE',
15606
- message: 'Claim rewards response must include executionParams and signature when rewards are claimable',
15607
- cause: {
15608
- trace: {
15609
- rewardsCount: rewards.length,
15610
- missingExecutionParams,
15611
- missingSignature
15612
- }
15613
- }
15614
- });
15615
- }
15616
- validateExecutionDeadline(executionParams);
15617
- return await estimateEarnQuoteGasFees({
15618
- adapter,
15619
- chain,
15620
- address,
15621
- actionName: 'Claim Rewards',
15622
- actionKey: 'earn.claimRewards',
15623
- actionParams: {
15624
- executeParams: executionParams,
15625
- tokenInputs: [],
15626
- signature
15627
- }
15628
- });
15629
- } catch (error) {
15630
- return [
15631
- this.gasEstimateFailure('Claim Rewards', chain, error)
15632
- ];
15633
- }
15634
- }
15635
16187
  /** {@inheritdoc} */ async getDepositQuote(params) {
15636
16188
  const config = this.resolveConfig(params.config);
15637
16189
  if (hasQuoteDestinationChain(params)) {
@@ -15654,96 +16206,43 @@ function finishElapsedWait(lastStatus, lastError) {
15654
16206
  throw createValidationFailedError('chain', destinationChain.chain, 'chain is only supported for cross-chain Earn deposit quotes; omit chain/address when quoting on the source chain');
15655
16207
  }
15656
16208
  const { address, chain, chainDefinition } = await resolveAdapterContext(params.from);
15657
- // The quote fetch and the gas estimation share no data, so run them
15658
- // concurrently. The estimator never rejects (failures fold into
15659
- // `{ fees: null }` entries), so only a quote failure can throw here.
15660
- const [quote, gasFees] = await Promise.all([
15661
- fetchDepositQuote({
15662
- vaultAddress: params.vaultAddress,
15663
- amount: params.amount,
15664
- address,
15665
- chain,
15666
- config
15667
- }),
15668
- this.estimateDepositQuoteGasFees({
15669
- adapter: params.from.adapter,
15670
- chain: chainDefinition,
15671
- apiChain: chain,
15672
- address,
15673
- vaultAddress: params.vaultAddress,
15674
- amount: params.amount,
15675
- config
15676
- })
15677
- ]);
15678
- return {
15679
- ...quote,
15680
- gasFees
15681
- };
16209
+ // Gas is estimated server-side by the Earn Service and returned on the quote, so the
16210
+ // SDK no longer simulates it locally. `chainDefinition` lets the fetch fill
16211
+ // the native token symbol / blockchain on each gas entry.
16212
+ return fetchDepositQuote({
16213
+ vaultAddress: params.vaultAddress,
16214
+ amount: params.amount,
16215
+ address,
16216
+ chain,
16217
+ config,
16218
+ chainDefinition
16219
+ });
15682
16220
  }
15683
16221
  /** {@inheritdoc} */ async getWithdrawalQuote(params) {
15684
16222
  const config = this.resolveConfig(params.config);
15685
16223
  const { address, chain, chainDefinition } = await resolveAdapterContext(params.from);
15686
- // The quote fetch and the gas estimation share no data, so run them
15687
- // concurrently. The estimator never rejects (failures fold into
15688
- // `{ fees: null }` entries), so only a quote failure can throw here.
15689
- const [quote, gasFees] = await Promise.all([
15690
- fetchWithdrawalQuote({
15691
- vaultAddress: params.vaultAddress,
15692
- amount: params.amount,
15693
- address,
15694
- chain,
15695
- config
15696
- }),
15697
- this.estimateWithdrawalQuoteGasFees({
15698
- adapter: params.from.adapter,
15699
- chain: chainDefinition,
15700
- apiChain: chain,
15701
- address,
15702
- vaultAddress: params.vaultAddress,
15703
- amount: params.amount,
15704
- config
15705
- })
15706
- ]);
15707
- return {
15708
- ...quote,
15709
- gasFees
15710
- };
16224
+ // Gas is estimated server-side by the Earn Service and returned on the quote.
16225
+ return fetchWithdrawalQuote({
16226
+ vaultAddress: params.vaultAddress,
16227
+ amount: params.amount,
16228
+ address,
16229
+ chain,
16230
+ config,
16231
+ chainDefinition
16232
+ });
15711
16233
  }
15712
16234
  /** {@inheritdoc} */ async getClaimRewardsQuote(params) {
15713
16235
  const config = this.resolveConfig(params.config);
15714
- const { address, chain, chainDefinition } = await resolveAdapterContext(params.from);
15715
- const quote = await fetchClaimRewardsQuote({
16236
+ const { address, chain } = await resolveAdapterContext(params.from);
16237
+ // The claimRewards/quote response carries no gas estimate (unlike
16238
+ // deposit/withdrawal quotes), and the SDK no longer estimates gas locally,
16239
+ // so gasFees is always empty for claim rewards.
16240
+ return fetchClaimRewardsQuote({
15716
16241
  vaultAddress: params.vaultAddress,
15717
16242
  address,
15718
16243
  chain,
15719
16244
  config
15720
16245
  });
15721
- // No claimable rewards means there is nothing to execute, so there is no
15722
- // gas to estimate. Short-circuit on the already-fetched quote rather than
15723
- // calling the (heavier) claim execution endpoint again — this also keeps
15724
- // `gasFees` empty as documented, instead of risking a `{ fees: null }`
15725
- // estimation-error entry when the adapter/RPC is unavailable. This
15726
- // short-circuit is why the claim path stays sequential instead of using
15727
- // the Promise.all pattern of the deposit/withdrawal quotes: estimating in
15728
- // parallel would hit the signing endpoint even when nothing is claimable.
15729
- if (quote.rewards.length === 0) {
15730
- return {
15731
- ...quote,
15732
- gasFees: []
15733
- };
15734
- }
15735
- const gasFees = await this.estimateClaimRewardsQuoteGasFees({
15736
- adapter: params.from.adapter,
15737
- chain: chainDefinition,
15738
- apiChain: chain,
15739
- address,
15740
- vaultAddress: params.vaultAddress,
15741
- config
15742
- });
15743
- return {
15744
- ...quote,
15745
- gasFees
15746
- };
15747
16246
  }
15748
16247
  }
15749
16248
  function hasDepositDestination(params) {
@@ -15981,11 +16480,27 @@ function formatPositionPnL(pnl) {
15981
16480
  * @param vault - Provider vault info with raw amount objects
15982
16481
  * @returns Vault info with total deposits and liquidity formatted as strings
15983
16482
  */ function formatVaultInfo(vault) {
15984
- const { totalDeposits, liquidity, ...rest } = vault;
16483
+ // The flat `totalDeposits`/`liquidity` are deprecated aliases that are
16484
+ // intentionally dual-read through the migration window so existing
16485
+ // consumers keep receiving them until Contract.
16486
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
16487
+ const { totalDeposits, liquidity, liquidityProfile, ...rest } = vault;
16488
+ // `liquidityProfile` is `.optional()` in the response schema during the
16489
+ // expand/contract window (an old backend that predates the nested facets
16490
+ // omits it), so only format and re-attach it when present — matching the
16491
+ // provider-side `toVaultInfo` mapper.
15985
16492
  return {
15986
16493
  ...rest,
15987
16494
  totalDeposits: formatAmount(totalDeposits),
15988
- liquidity: formatAmount(liquidity)
16495
+ liquidity: formatAmount(liquidity),
16496
+ ...liquidityProfile !== undefined && {
16497
+ liquidityProfile: {
16498
+ ...liquidityProfile,
16499
+ totalDeposits: formatAmount(liquidityProfile.totalDeposits),
16500
+ available: formatAmount(liquidityProfile.available),
16501
+ totalSupply: formatAmount(liquidityProfile.totalSupply)
16502
+ }
16503
+ }
15989
16504
  };
15990
16505
  }
15991
16506
  /**
@@ -17895,29 +18410,97 @@ function formatRetryResult(operation, result) {
17895
18410
  }
17896
18411
 
17897
18412
  // Auto-register this kit for user agent tracking
17898
- registerKit(`${pkg.name}/${pkg.version}`);
18413
+ registerKit(`${pkg$1.name}/${pkg$1.version}`);
18414
+
18415
+ /**
18416
+ * Register event handlers from a context actions map to a kit instance.
18417
+ *
18418
+ * This utility function registers event handlers stored in a context actions map
18419
+ * with a kit instance that supports event handling via an `on` method. It handles
18420
+ * wildcard handlers ('*') and prefixed action handlers, stripping the prefix
18421
+ * before registration.
18422
+ *
18423
+ * The function is designed to be reusable across different operation types
18424
+ * (bridge, swap, stake, etc.) by accepting a configurable prefix parameter.
18425
+ *
18426
+ * @param kit - The kit instance to register handlers with (must have an `on` method)
18427
+ * @param handlers - Map of action names to arrays of handler functions
18428
+ * @param prefix - Optional prefix to strip from action names (e.g., 'bridge.')
18429
+ *
18430
+ * @example
18431
+ * ```typescript
18432
+ * import { registerActionHandlers } from '@circle-fin/app-kit/utils'
18433
+ * import { BridgeKit } from '@circle-fin/bridge-kit'
18434
+ *
18435
+ * const kit = new BridgeKit()
18436
+ * const handlers = {
18437
+ * '*': [(payload) => console.log('All actions:', payload)],
18438
+ * 'bridge.approve': [(payload) => console.log('Approved:', payload)],
18439
+ * 'bridge.burn': [(payload) => console.log('Burned:', payload)],
18440
+ * }
18441
+ *
18442
+ * registerActionHandlers(kit, handlers, 'bridge.')
18443
+ * ```
18444
+ *
18445
+ * @example
18446
+ * ```typescript
18447
+ * import { registerActionHandlers } from '@circle-fin/app-kit/utils'
18448
+ * import { SwapKit } from '@circle-fin/swap-kit'
18449
+ *
18450
+ * const kit = new SwapKit()
18451
+ * const handlers = {
18452
+ * 'swap.initiate': [(payload) => console.log('Swap initiated:', payload)],
18453
+ * }
18454
+ *
18455
+ * registerActionHandlers(kit, handlers, 'swap.')
18456
+ * ```
18457
+ */ const registerActionHandlers = (kit, handlers, prefix = '')=>{
18458
+ for (const [action, handlerArray] of Object.entries(handlers)){
18459
+ // Register all handlers for this action
18460
+ for (const handler of handlerArray){
18461
+ if (action === '*') {
18462
+ // Wildcard handlers are registered as-is
18463
+ kit.on('*', handler);
18464
+ } else if (prefix && action.startsWith(prefix)) {
18465
+ // Remove prefix to get the actual kit action name
18466
+ const kitAction = action.split('.').at(1);
18467
+ if (kitAction) {
18468
+ kit.on(kitAction, handler);
18469
+ }
18470
+ } else if (!prefix) {
18471
+ // No prefix configured, register action as-is
18472
+ kit.on(action, handler);
18473
+ }
18474
+ // Actions that don't match the prefix are silently ignored
18475
+ }
18476
+ }
18477
+ };
17899
18478
 
17900
18479
  /**
17901
18480
  * Create an EarnKit instance for AppKit earn operations.
17902
18481
  *
17903
- * @remarks The context parameter is reserved for future EarnKit wiring.
17904
- * EarnKit does not currently support AppKit developer fee hooks, so the
17905
- * factory does not read fee callbacks from the context. Earn custom fees remain
17906
- * reserved until EarnKit fee support ships.
18482
+ * Attaches any earn event handlers previously registered on the AppKit
18483
+ * context (via `kit.on('earn.*', …)` or `kit.on('*', …)`) so step events
18484
+ * fire during the returned kit's earn operations.
17907
18485
  *
17908
- * When EarnKit supports developer fee hooks, this factory can wire AppKit context through.
18486
+ * @remarks Developer fee hooks from the AppKit context are not applied.
18487
+ * EarnKit does not yet support custom fee policies.
17909
18488
  *
17910
- * @param context - AppKit context reserved for future EarnKit wiring
17911
- * @returns A new EarnKit instance
18489
+ * @param context - AppKit context with earn event handlers and kit options
18490
+ * @returns An EarnKit instance ready for AppKit earn operations
17912
18491
  *
17913
18492
  * @example
17914
18493
  * ```typescript
17915
18494
  * const earnKit = createEarnKit(context)
17916
18495
  * ```
17917
- */ const createEarnKit = ()=>new EarnKit();
18496
+ */ const createEarnKit = (context)=>{
18497
+ const kit = new EarnKit();
18498
+ registerActionHandlers(kit, context.actions.earn, 'earn');
18499
+ return kit;
18500
+ };
17918
18501
 
17919
18502
  async function deposit(context, params) {
17920
- return createEarnKit().deposit(params);
18503
+ return createEarnKit(context).deposit(params);
17921
18504
  }
17922
18505
  /**
17923
18506
  * Execute an earn withdrawal operation.
@@ -17941,7 +18524,7 @@ async function deposit(context, params) {
17941
18524
  * })
17942
18525
  * ```
17943
18526
  */ async function withdraw(context, params) {
17944
- return createEarnKit().withdraw(params);
18527
+ return createEarnKit(context).withdraw(params);
17945
18528
  }
17946
18529
  /**
17947
18530
  * Claim earn rewards.
@@ -17964,7 +18547,7 @@ async function deposit(context, params) {
17964
18547
  * })
17965
18548
  * ```
17966
18549
  */ async function claimRewards(context, params) {
17967
- return createEarnKit().claimRewards(params);
18550
+ return createEarnKit(context).claimRewards(params);
17968
18551
  }
17969
18552
  /**
17970
18553
  * Fetch vault information.
@@ -17986,7 +18569,7 @@ async function deposit(context, params) {
17986
18569
  * })
17987
18570
  * ```
17988
18571
  */ async function getVaults(context, params) {
17989
- return createEarnKit().getVaults(params);
18572
+ return createEarnKit(context).getVaults(params);
17990
18573
  }
17991
18574
  /**
17992
18575
  * Discover vaults available on a chain.
@@ -18010,7 +18593,7 @@ async function deposit(context, params) {
18010
18593
  * })
18011
18594
  * ```
18012
18595
  */ async function exploreVaults(context, params) {
18013
- return createEarnKit().exploreVaults(params);
18596
+ return createEarnKit(context).exploreVaults(params);
18014
18597
  }
18015
18598
  /**
18016
18599
  * Lazily iterate every vault available on a chain.
@@ -18034,7 +18617,7 @@ async function deposit(context, params) {
18034
18617
  * }
18035
18618
  * ```
18036
18619
  */ function exploreVaultsIterator(context, params) {
18037
- return createEarnKit().exploreVaultsIterator(params);
18620
+ return createEarnKit(context).exploreVaultsIterator(params);
18038
18621
  }
18039
18622
  /**
18040
18623
  * Fetch a wallet position in a vault.
@@ -18057,7 +18640,7 @@ async function deposit(context, params) {
18057
18640
  * })
18058
18641
  * ```
18059
18642
  */ async function getPosition(context, params) {
18060
- return createEarnKit().getPosition(params);
18643
+ return createEarnKit(context).getPosition(params);
18061
18644
  }
18062
18645
  /**
18063
18646
  * Fetch the current status of a cross-chain Earn deposit.
@@ -18079,7 +18662,7 @@ async function deposit(context, params) {
18079
18662
  * console.log(status.status)
18080
18663
  * ```
18081
18664
  */ async function getCrossChainDepositStatus(context, params) {
18082
- return createEarnKit().getCrossChainDepositStatus(params);
18665
+ return createEarnKit(context).getCrossChainDepositStatus(params);
18083
18666
  }
18084
18667
  /**
18085
18668
  * Poll a cross-chain Earn deposit until it reaches a terminal bridge state.
@@ -18102,7 +18685,7 @@ async function deposit(context, params) {
18102
18685
  * console.log(result.outcome)
18103
18686
  * ```
18104
18687
  */ async function waitForCrossChainDeposit(context, params) {
18105
- return createEarnKit().waitForCrossChainDeposit(params);
18688
+ return createEarnKit(context).waitForCrossChainDeposit(params);
18106
18689
  }
18107
18690
  /**
18108
18691
  * Fetch a deposit quote.
@@ -18126,7 +18709,7 @@ async function deposit(context, params) {
18126
18709
  * })
18127
18710
  * ```
18128
18711
  */ async function getDepositQuote(context, params) {
18129
- return createEarnKit().getDepositQuote(params);
18712
+ return createEarnKit(context).getDepositQuote(params);
18130
18713
  }
18131
18714
  /**
18132
18715
  * Fetch a withdrawal quote.
@@ -18150,7 +18733,7 @@ async function deposit(context, params) {
18150
18733
  * })
18151
18734
  * ```
18152
18735
  */ async function getWithdrawalQuote(context, params) {
18153
- return createEarnKit().getWithdrawalQuote(params);
18736
+ return createEarnKit(context).getWithdrawalQuote(params);
18154
18737
  }
18155
18738
  /**
18156
18739
  * Fetch a claim rewards quote.
@@ -18173,7 +18756,43 @@ async function deposit(context, params) {
18173
18756
  * })
18174
18757
  * ```
18175
18758
  */ async function getClaimRewardsQuote(context, params) {
18176
- return createEarnKit().getClaimRewardsQuote(params);
18759
+ return createEarnKit(context).getClaimRewardsQuote(params);
18760
+ }
18761
+ /**
18762
+ * Resume a multi-phase earn operation that previously failed.
18763
+ *
18764
+ * Pass the {@link KitError} caught from `deposit`, `withdraw`, or
18765
+ * `claimRewards`. Completed phases can be skipped when the error carries
18766
+ * earn retry context. Call `isRetryableError(error)` first.
18767
+ *
18768
+ * @remarks
18769
+ * Retry re-fetches execution params and may re-submit the execute
18770
+ * transaction. Treat this as best-effort recovery if a prior execute
18771
+ * broadcast may still be in flight.
18772
+ *
18773
+ * @param context - AppKit context
18774
+ * @param error - The error caught from a previous multi-phase earn operation
18775
+ * @returns Promise resolving to the result of the resumed operation
18776
+ * @throws If the error is not retryable or lacks earn retry context
18777
+ *
18778
+ * @example
18779
+ * ```typescript
18780
+ * import { isRetryableError } from '@circle-fin/app-kit'
18781
+ * import { createContext } from '@circle-fin/app-kit/context'
18782
+ * import { retry } from '@circle-fin/app-kit/earn'
18783
+ *
18784
+ * const context = createContext()
18785
+ *
18786
+ * try {
18787
+ * await deposit(context, params)
18788
+ * } catch (error) {
18789
+ * if (isRetryableError(error)) {
18790
+ * const result = await retry(context, error)
18791
+ * }
18792
+ * }
18793
+ * ```
18794
+ */ async function retry(context, error) {
18795
+ return createEarnKit(context).retry(error);
18177
18796
  }
18178
18797
 
18179
18798
  exports.claimRewards = claimRewards;
@@ -18186,6 +18805,7 @@ exports.getDepositQuote = getDepositQuote;
18186
18805
  exports.getPosition = getPosition;
18187
18806
  exports.getVaults = getVaults;
18188
18807
  exports.getWithdrawalQuote = getWithdrawalQuote;
18808
+ exports.retry = retry;
18189
18809
  exports.waitForCrossChainDeposit = waitForCrossChainDeposit;
18190
18810
  exports.withdraw = withdraw;
18191
18811
  //# sourceMappingURL=earn.cjs.map