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