@circle-fin/app-kit 1.8.0 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.cjs CHANGED
@@ -3657,7 +3657,7 @@ function getOptionalString(value) {
3657
3657
  * - vault-not-found, unsupported-chain, unsupported-vault,
3658
3658
  * signature-rejected, invalid-id, invalid-batch-size, position-not-registered,
3659
3659
  * withdrawal-max-exceeded, insufficient-balance, bridge prepare idempotency
3660
- * conflicts
3660
+ * conflicts, invalid vault-asset amount precision
3661
3661
  *
3662
3662
  * SERVICE errors (RETRYABLE) — try again later:
3663
3663
  * - signing-failed, provider-error, rewards-fetch-failed,
@@ -3841,6 +3841,13 @@ function getOptionalString(value) {
3841
3841
  recoverability: 'RETRYABLE'
3842
3842
  }
3843
3843
  ],
3844
+ [
3845
+ 380415,
3846
+ {
3847
+ errorDef: EarnError.INVALID_INPUT,
3848
+ recoverability: 'FATAL'
3849
+ }
3850
+ ],
3844
3851
  // Bridge (380_5XX)
3845
3852
  [
3846
3853
  380500,
@@ -3876,6 +3883,13 @@ function getOptionalString(value) {
3876
3883
  errorDef: EarnError.PROVIDER_ERROR,
3877
3884
  recoverability: 'RETRYABLE'
3878
3885
  }
3886
+ ],
3887
+ [
3888
+ 380505,
3889
+ {
3890
+ errorDef: EarnError.PROVIDER_ERROR,
3891
+ recoverability: 'FATAL'
3892
+ }
3879
3893
  ]
3880
3894
  ]);
3881
3895
  /**
@@ -4335,23 +4349,6 @@ exports.EarnChain = void 0;
4335
4349
  }
4336
4350
  });
4337
4351
 
4338
- /**
4339
- * Standard decimal places for tokens.
4340
- *
4341
- * These constants define the decimal precision used by different token types
4342
- * across the SDK. They are used for amount conversions between human-readable
4343
- * format and smallest units (base units).
4344
- *
4345
- * @remarks
4346
- * - Most stablecoins (USDC, EURC, USDT, PYUSD) use 6 decimal places
4347
- * - Some stablecoins (DAI, USDE) and most native tokens (ETH, POL, PLUME) use 18 decimals
4348
- * - The NATIVE alias uses chain-specific decimals which may vary (e.g., SOL uses 9)
4349
- */ /**
4350
- * Standard decimal places for 6-decimal tokens.
4351
- *
4352
- * Used by most stablecoins: USDC, EURC, USDT, PYUSD
4353
- */ const TOKEN_DECIMALS_6 = 6;
4354
-
4355
4352
  /**
4356
4353
  * @packageDocumentation
4357
4354
  * @module SwapTokenRegistry
@@ -9992,13 +9989,20 @@ function parseOrThrow(value, schema, context) {
9992
9989
  symbol: 'EURC',
9993
9990
  decimals: 6,
9994
9991
  locators: {
9992
+ // =========================================================================
9993
+ // Mainnets
9994
+ // =========================================================================
9995
9995
  [exports.Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
9996
9996
  [exports.Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
9997
9997
  [exports.Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
9998
9998
  [exports.Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
9999
9999
  [exports.Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
10000
+ // =========================================================================
10000
10001
  // Testnets
10001
- [exports.Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a'
10002
+ // =========================================================================
10003
+ [exports.Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
10004
+ [exports.Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
10005
+ [exports.Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
10002
10006
  }
10003
10007
  };
10004
10008
 
@@ -10208,8 +10212,7 @@ function parseOrThrow(value, schema, context) {
10208
10212
  * cirBTC (Circle Bitcoin) token definition with addresses and metadata.
10209
10213
  *
10210
10214
  * @remarks
10211
- * Built-in cirBTC definition for the TokenRegistry. Currently deployed
10212
- * on Arc Testnet.
10215
+ * Built-in cirBTC definition for the TokenRegistry.
10213
10216
  *
10214
10217
  * @example
10215
10218
  * ```typescript
@@ -10225,7 +10228,15 @@ function parseOrThrow(value, schema, context) {
10225
10228
  symbol: 'cirBTC',
10226
10229
  decimals: 8,
10227
10230
  locators: {
10228
- [exports.Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
10231
+ // =========================================================================
10232
+ // Mainnets
10233
+ // =========================================================================
10234
+ [exports.Blockchain.Ethereum]: '0x72DFB2E44f59C5AD2bAFE84314E5b99a7cd5075E',
10235
+ // =========================================================================
10236
+ // Testnets
10237
+ // =========================================================================
10238
+ [exports.Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
10239
+ [exports.Blockchain.Ethereum_Sepolia]: '0x3a3fe695F684Bf9b9e43CF43C2b895Ea5e392bB3'
10229
10240
  }
10230
10241
  };
10231
10242
 
@@ -11506,7 +11517,7 @@ function resolveOptions(options) {
11506
11517
  }
11507
11518
 
11508
11519
  var name$3 = "@circle-fin/bridge-kit";
11509
- var version$4 = "1.11.0";
11520
+ var version$4 = "1.11.1";
11510
11521
  var pkg$4 = {
11511
11522
  name: name$3,
11512
11523
  version: version$4};
@@ -16297,7 +16308,7 @@ const mockAttestationMessage = {
16297
16308
  return step;
16298
16309
  }
16299
16310
 
16300
- var version$3 = "1.8.4";
16311
+ var version$3 = "1.8.5";
16301
16312
  var pkg$3 = {
16302
16313
  version: version$3};
16303
16314
 
@@ -18704,7 +18715,7 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18704
18715
  };
18705
18716
 
18706
18717
  var name$2 = "@circle-fin/swap-kit";
18707
- var version$2 = "1.3.0";
18718
+ var version$2 = "1.3.1";
18708
18719
  var pkg$2 = {
18709
18720
  name: name$2,
18710
18721
  version: version$2};
@@ -23639,7 +23650,7 @@ function writeBytes32(buffer, hex, offset) {
23639
23650
  }
23640
23651
  }
23641
23652
 
23642
- const TOKEN_REGISTRY$3 = createTokenRegistry();
23653
+ const TOKEN_REGISTRY$4 = createTokenRegistry();
23643
23654
  /**
23644
23655
  * Resolve token alias to contract address for the given chain.
23645
23656
  *
@@ -23728,7 +23739,7 @@ const TOKEN_REGISTRY$3 = createTokenRegistry();
23728
23739
  }
23729
23740
  });
23730
23741
  }
23731
- const tokenDefinition = TOKEN_REGISTRY$3.get(upperToken);
23742
+ const tokenDefinition = TOKEN_REGISTRY$4.get(upperToken);
23732
23743
  if (tokenDefinition !== undefined) {
23733
23744
  const resolvedAddress = tokenDefinition.locators[chain.chain];
23734
23745
  if (!resolvedAddress) {
@@ -23745,7 +23756,7 @@ const TOKEN_REGISTRY$3 = createTokenRegistry();
23745
23756
  }
23746
23757
  });
23747
23758
  }
23748
- return TOKEN_REGISTRY$3.resolve(upperToken, chain.chain).locator;
23759
+ return TOKEN_REGISTRY$4.resolve(upperToken, chain.chain).locator;
23749
23760
  }
23750
23761
  // Not an alias - return as-is (assumed to be address)
23751
23762
  return token;
@@ -23842,11 +23853,11 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
23842
23853
  if (destinationChain.chain !== sourceChain.chain) {
23843
23854
  return sourceChain;
23844
23855
  }
23845
- const inputIsOk = isOkToken(tokenIn, sourceChain, TOKEN_REGISTRY$3, OK_TOKEN_SYMBOLS$1);
23856
+ const inputIsOk = isOkToken(tokenIn, sourceChain, TOKEN_REGISTRY$4, OK_TOKEN_SYMBOLS$1);
23846
23857
  if (inputIsOk) {
23847
23858
  return sourceChain;
23848
23859
  }
23849
- const outputIsOk = isOkToken(tokenOut, destinationChain, TOKEN_REGISTRY$3, OK_TOKEN_SYMBOLS$1);
23860
+ const outputIsOk = isOkToken(tokenOut, destinationChain, TOKEN_REGISTRY$4, OK_TOKEN_SYMBOLS$1);
23850
23861
  return outputIsOk ? destinationChain : sourceChain;
23851
23862
  }
23852
23863
  /**
@@ -25137,7 +25148,7 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
25137
25148
  }
25138
25149
  }
25139
25150
 
25140
- const TOKEN_REGISTRY$2 = createTokenRegistry();
25151
+ const TOKEN_REGISTRY$3 = createTokenRegistry();
25141
25152
  /**
25142
25153
  * Resolve a token identifier to its canonical symbol for the Stablecoin Service Swap Provider.
25143
25154
  *
@@ -25181,7 +25192,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25181
25192
  return NATIVE_TOKEN;
25182
25193
  }
25183
25194
  // For known registry aliases, return canonical symbol directly.
25184
- if (TOKEN_REGISTRY$2.get(upperToken) !== undefined) {
25195
+ if (TOKEN_REGISTRY$3.get(upperToken) !== undefined) {
25185
25196
  return upperToken;
25186
25197
  }
25187
25198
  // For EVM chains, normalize addresses to lowercase for case-insensitive comparison.
@@ -25191,7 +25202,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25191
25202
  // Native address is given
25192
25203
  if (isNativeEvmAddress(token) || isNativeSolanaAddress(token)) return NATIVE_TOKEN;
25193
25204
  try {
25194
- const { symbol } = TOKEN_REGISTRY$2.resolveByAddress(normalizedToken, chain.chain);
25205
+ const { symbol } = TOKEN_REGISTRY$3.resolveByAddress(normalizedToken, chain.chain);
25195
25206
  if (symbol === undefined) {
25196
25207
  return null;
25197
25208
  }
@@ -25322,7 +25333,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25322
25333
  return `Insufficient ${displaySymbol} balance for swap operation.\n\n` + `Wallet: ${walletAddress}\n` + `Current balance: ${currentDisplay}\n` + `Required: ${requiredDisplay}\n` + `Shortfall: ${shortfallDisplay}\n\n` + `This swap requires ${requiredSummary} to complete the transaction.\n\n` + `Action: Add at least ${actionAmount} to your wallet to complete this swap.`;
25323
25334
  }
25324
25335
 
25325
- const TOKEN_REGISTRY$1 = createTokenRegistry();
25336
+ const TOKEN_REGISTRY$2 = createTokenRegistry();
25326
25337
  /**
25327
25338
  * Format a raw base-unit amount into a human-readable decimal string.
25328
25339
  *
@@ -25382,12 +25393,12 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
25382
25393
  value,
25383
25394
  token: resolvedSymbol,
25384
25395
  chain,
25385
- tokens: TOKEN_REGISTRY$1
25396
+ tokens: TOKEN_REGISTRY$2
25386
25397
  }),
25387
25398
  token: resolvedSymbol
25388
25399
  };
25389
25400
  }
25390
- const tokenDef = TOKEN_REGISTRY$1.get(resolvedSymbol);
25401
+ const tokenDef = TOKEN_REGISTRY$2.get(resolvedSymbol);
25391
25402
  if (tokenDef) {
25392
25403
  const decimals = resolveTokenDecimals$1(tokenDef, chain.chain);
25393
25404
  return {
@@ -25418,7 +25429,7 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
25418
25429
  * i.e. 1 + 0.2 + 0.1 = 1.3. Use the greater of (local estimate × this multiplier)
25419
25430
  * or the proxy's gasLimit.
25420
25431
  */ const GAS_SAFETY_MULTIPLIER = 1.3;
25421
- const TOKEN_REGISTRY = createTokenRegistry();
25432
+ const TOKEN_REGISTRY$1 = createTokenRegistry();
25422
25433
  /**
25423
25434
  * Enhances a KitError with transaction details (txHash and explorerUrl).
25424
25435
  *
@@ -25494,6 +25505,19 @@ const TOKEN_REGISTRY = createTokenRegistry();
25494
25505
  *
25495
25506
  * @internal
25496
25507
  */ const SUPPORTED_CHAINS$1 = getSwapSupportedChains(Chains);
25508
+ /**
25509
+ * Inter-poll delays (ms) for the same-chain `amountOut` enrichment loop.
25510
+ *
25511
+ * A same-chain swap is already terminal `DONE` once its source transaction
25512
+ * confirms; this short, escalating budget (~5s total across up to four polls)
25513
+ * gives the service a brief window to resolve the output amount before
25514
+ * `swap()` returns. The loop exits the instant `amountOut` is available.
25515
+ */ const SAME_CHAIN_AMOUNT_OUT_POLL_DELAYS_MS = [
25516
+ 1_000,
25517
+ 2_000,
25518
+ 2_000
25519
+ ];
25520
+ const sleep$3 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
25497
25521
  function buildSwapProgress(statusResult) {
25498
25522
  return {
25499
25523
  status: statusResult.status,
@@ -25513,29 +25537,46 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
25513
25537
  }
25514
25538
  };
25515
25539
  }
25516
- try {
25517
- const statusResult = await getSwapStatus$2({
25518
- txHash,
25519
- chain: chain.chain,
25520
- apiKey
25521
- });
25522
- const progress = buildSwapProgress(statusResult);
25523
- if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
25540
+ // A same-chain swap completes atomically in the source transaction, which
25541
+ // has already been confirmed (and receipt-checked for revert) by the time we
25542
+ // get here. Its terminal status is therefore `DONE`. We briefly poll the
25543
+ // status endpoint to enrich `amountOut`, which the service usually resolves
25544
+ // within a few seconds. A non-DONE/erroneous peek never downgrades the
25545
+ // status — once the short budget is exhausted we return `DONE` without
25546
+ // `amountOut`, and the caller can fetch it later via `getSwapStatus`.
25547
+ for(let attempt = 0;; attempt += 1){
25548
+ try {
25549
+ const statusResult = await getSwapStatus$2({
25550
+ txHash,
25551
+ chain: chain.chain,
25552
+ apiKey
25553
+ });
25554
+ if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
25555
+ return {
25556
+ progress: buildSwapProgress(statusResult),
25557
+ amountOut: statusResult.amountOut
25558
+ };
25559
+ }
25560
+ } catch {
25561
+ // Reachability/parse failure during enrichment — non-fatal; the swap
25562
+ // already succeeded on-chain. `getSwapStatus` already retried transient
25563
+ // network errors internally, so further polling is unlikely to help.
25524
25564
  return {
25525
- progress,
25526
- amountOut: statusResult.amountOut
25565
+ progress: {
25566
+ status: 'DONE'
25567
+ }
25527
25568
  };
25528
25569
  }
25529
- return {
25530
- progress
25531
- };
25532
- } catch {
25533
- // Non-fatal — the swap already succeeded on-chain. status stays PENDING.
25534
- return {
25535
- progress: {
25536
- status: 'PENDING'
25537
- }
25538
- };
25570
+ const delayMs = SAME_CHAIN_AMOUNT_OUT_POLL_DELAYS_MS[attempt];
25571
+ if (delayMs === undefined) {
25572
+ // Budget exhausted — terminal `DONE`, `amountOut` not yet available.
25573
+ return {
25574
+ progress: {
25575
+ status: 'DONE'
25576
+ }
25577
+ };
25578
+ }
25579
+ await sleep$3(delayMs);
25539
25580
  }
25540
25581
  }
25541
25582
  /**
@@ -25816,7 +25857,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
25816
25857
  return true;
25817
25858
  }
25818
25859
  // At least one token must be supported for fee collection
25819
- return getSwapOkTokenStatus(tokenInAddress, tokenOutAddress, chain, TOKEN_REGISTRY, OK_TOKEN_SYMBOLS$1).hasOkToken;
25860
+ return getSwapOkTokenStatus(tokenInAddress, tokenOutAddress, chain, TOKEN_REGISTRY$1, OK_TOKEN_SYMBOLS$1).hasOkToken;
25820
25861
  }
25821
25862
  /**
25822
25863
  * Estimate swap costs and output amount by delegating to the Stablecoin Service quote API.
@@ -26413,14 +26454,14 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
26413
26454
  }
26414
26455
  // Build swap fees with adapter-backed formatting for unregistered tokens
26415
26456
  const swapResultFees = serviceResponse.fees ? await this.buildFormattedFees(serviceResponse.fees, chain, destinationChain, adapter, config?.customFee?.recipientAddress) : undefined;
26416
- // Best-effort enrichment: fetch amountOut and provider-reported status.
26417
- // Only performed for same-chain swaps they typically reach DONE within
26418
- // a few seconds of source-tx confirmation, so the peek is useful UX and
26419
- // adds minimal latency. Cross-chain swaps are skipped because the
26420
- // destination mint takes minutes (CCTPv2 attestation), meaning the peek
26421
- // would always return PENDING/WAIT_DESTINATION_TRANSACTION while still
26422
- // burning up to 30s of the load balancer's server-side wait. Callers
26423
- // should use `kit.getSwapStatus()` to poll cross-chain swaps to DONE.
26457
+ // Best-effort enrichment: briefly poll for amountOut on same-chain swaps.
26458
+ // They complete atomically in the (already-confirmed) source tx, so the
26459
+ // result is terminal `DONE` regardless; the short poll only adds amountOut
26460
+ // once the service has resolved it. Cross-chain swaps are skipped because
26461
+ // the destination mint takes minutes (CCTPv2 attestation), so polling would
26462
+ // always return PENDING/WAIT_DESTINATION_TRANSACTION while burning the
26463
+ // budget. Callers should use `kit.getSwapStatus()` to poll cross-chain
26464
+ // swaps to DONE.
26424
26465
  const statusSnapshot = await fetchSameChainStatusSnapshot({
26425
26466
  isCrossChainSwap,
26426
26467
  txHash,
@@ -30722,11 +30763,14 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
30722
30763
  };
30723
30764
 
30724
30765
  var name$1 = "@circle-fin/earn-kit";
30725
- var version$1 = "1.2.0";
30766
+ var version$1 = "1.2.1";
30726
30767
  var pkg$1 = {
30727
30768
  name: name$1,
30728
30769
  version: version$1};
30729
30770
 
30771
+ const EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS = [
30772
+ 'USDC'
30773
+ ];
30730
30774
  /**
30731
30775
  * Default base URL for the Earn Service API.
30732
30776
  *
@@ -30771,20 +30815,7 @@ var pkg$1 = {
30771
30815
  */ const EARN_BRIDGE_DESTINATION_CHAIN_TO_API = {
30772
30816
  [exports.Blockchain.Arc_Testnet]: CHAIN_TO_API[exports.Blockchain.Arc_Testnet]
30773
30817
  };
30774
- /**
30775
- * Expected EIP-712 domain of the ERC-3009 authorization token per source
30776
- * chain.
30777
- *
30778
- * Cross-chain Earn deposits are USDC-only, but USDC deployments differ on the
30779
- * EIP-712 domain name: newer deployments use `USDC` while older ones (for
30780
- * example Arbitrum Sepolia) kept `USD Coin` from the FiatToken V2 upgrade.
30781
- * These values mirror the bridge service's per-chain domain table and the
30782
- * on-chain `DOMAIN_SEPARATOR` inputs. Asserted before signing so a tampered
30783
- * prepare response cannot point the signature at another token. The
30784
- * `satisfies` check forces a domain entry whenever a source chain is added.
30785
- *
30786
- * @internal
30787
- */ const EARN_BRIDGE_ERC3009_DOMAINS = {
30818
+ const EARN_BRIDGE_USDC_ERC3009_DOMAINS = {
30788
30819
  [exports.Blockchain.Arbitrum_Sepolia]: {
30789
30820
  name: 'USD Coin',
30790
30821
  version: '2'
@@ -30798,6 +30829,21 @@ var pkg$1 = {
30798
30829
  version: '2'
30799
30830
  }
30800
30831
  };
30832
+ /**
30833
+ * Expected EIP-712 domain of the ERC-3009 authorization token per
30834
+ * cross-chain-supported source token and source chain.
30835
+ *
30836
+ * The bridge service prepares an ERC-3009 payload for the deposited token. We
30837
+ * keep this table as an explicit SDK allowlist because token deployments can
30838
+ * differ by source chain and by domain name/version. These values mirror the
30839
+ * bridge service's per-chain domain table and the on-chain DOMAIN_SEPARATOR
30840
+ * inputs. Asserted before signing so a tampered prepare response cannot point
30841
+ * the signature at another token or unexpected token domain.
30842
+ *
30843
+ * @internal
30844
+ */ const EARN_BRIDGE_ERC3009_DOMAINS = {
30845
+ USDC: EARN_BRIDGE_USDC_ERC3009_DOMAINS
30846
+ };
30801
30847
  /**
30802
30848
  * Maximum tolerated remaining lifetime of a prepared ERC-3009 authorization.
30803
30849
  *
@@ -31257,6 +31303,16 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31257
31303
  return approvalTxHash;
31258
31304
  }
31259
31305
 
31306
+ /** @internal */ function isSameAddress(actual, expected) {
31307
+ return actual.toLowerCase() === expected.toLowerCase();
31308
+ }
31309
+ /** @internal */ function assertDecimalPlaces(field, value, tokenDecimals, reason) {
31310
+ const decimals = value.split('.')[1]?.length ?? 0;
31311
+ if (decimals > tokenDecimals) {
31312
+ throw createValidationFailedError$1(field, value, reason);
31313
+ }
31314
+ }
31315
+
31260
31316
  /**
31261
31317
  * Build the `tokenInputs` array forwarded to the adapter contract's
31262
31318
  * `execute(executeParams, tokenInputs, signature)` call.
@@ -31280,15 +31336,17 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31280
31336
  *
31281
31337
  * @example
31282
31338
  * ```typescript
31283
- * const approvedToken = assertHexAddress(
31284
- * 'chain.usdcAddress',
31285
- * chain.usdcAddress,
31286
- * )
31287
- *
31288
- * const tokenInputs = buildEarnTokenInputs(
31339
+ * const approvalToken = resolveEarnApprovalToken(
31289
31340
  * executionPayload.executionParams,
31290
- * approvedToken,
31291
31341
  * )
31342
+ *
31343
+ * const tokenInputs =
31344
+ * approvalToken === undefined
31345
+ * ? []
31346
+ * : buildEarnTokenInputs(
31347
+ * executionPayload.executionParams,
31348
+ * approvalToken,
31349
+ * )
31292
31350
  * ```
31293
31351
  *
31294
31352
  * @internal
@@ -31300,7 +31358,7 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31300
31358
  return;
31301
31359
  }
31302
31360
  const { tokenIn } = instruction;
31303
- if (tokenIn.toLowerCase() !== approvedToken.toLowerCase()) {
31361
+ if (!isSameAddress(tokenIn, approvedToken)) {
31304
31362
  throw createValidationFailedError$1(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
31305
31363
  }
31306
31364
  tokenInputs.push({
@@ -31312,6 +31370,33 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31312
31370
  });
31313
31371
  return tokenInputs;
31314
31372
  }
31373
+ /**
31374
+ * Resolve the single token that needs adapter allowance from signed Earn
31375
+ * instructions.
31376
+ *
31377
+ * @param executionParams - Service-signed `ExecutionParams` forwarded to the adapter.
31378
+ * @returns The token requested by positive approval instructions, or `undefined`.
31379
+ * @throws {@link KitError} If positive approval instructions reference multiple tokens.
31380
+ *
31381
+ * @internal
31382
+ */ function resolveEarnApprovalToken(executionParams) {
31383
+ let approvedToken;
31384
+ executionParams.instructions.forEach((instruction, index)=>{
31385
+ const amount = BigInt(instruction.amountToApprove);
31386
+ if (amount <= 0n) {
31387
+ return;
31388
+ }
31389
+ const { tokenIn } = instruction;
31390
+ if (approvedToken === undefined) {
31391
+ approvedToken = tokenIn;
31392
+ return;
31393
+ }
31394
+ if (!isSameAddress(tokenIn, approvedToken)) {
31395
+ throw createValidationFailedError$1(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
31396
+ }
31397
+ });
31398
+ return approvedToken;
31399
+ }
31315
31400
 
31316
31401
  /**
31317
31402
  * Prepare an earn adapter action, execute it, wait for confirmation, and
@@ -31803,6 +31888,7 @@ const hexSignatureSchema = evmSignatureSchema;
31803
31888
  const hexAddressSchema = evmAddressSchema;
31804
31889
  // '0x' prefix + 32 bytes * 2 hex chars.
31805
31890
  const BYTES32_HEX_LENGTH = 66;
31891
+ const bridgeFeeTokenSchema = hexAddressSchema;
31806
31892
  /**
31807
31893
  * Zod schema for a non-negative uint256-like value.
31808
31894
  *
@@ -32089,14 +32175,12 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32089
32175
  *
32090
32176
  * The bridge prepare path returns one item per collected source fee (e.g.
32091
32177
  * `PRE_FINALITY` for the fast-burn fee and `FORWARD` for the destination
32092
- * forward fee), all denominated in the shared `feeToken`. The signed
32093
- * authorization `value` is `principal + sum(items[].amount)`, so the pre-sign
32094
- * guard parses these to recompute the expected value. Each item's `amount` is
32095
- * a base-unit decimal string in `feeToken` units.
32178
+ * forward fee), all denominated in the shared `feeToken`. Each item's `amount`
32179
+ * is a base-unit decimal string in `feeToken` units.
32096
32180
  *
32097
32181
  * @internal
32098
32182
  */ const bridgeFeeQuoteSchema = zod.z.object({
32099
- feeToken: hexAddressSchema,
32183
+ feeToken: bridgeFeeTokenSchema,
32100
32184
  items: zod.z.array(zod.z.object({
32101
32185
  type: zod.z.string(),
32102
32186
  amount: uint256LikeSchema
@@ -32112,6 +32196,12 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32112
32196
  erc3009TypedData: erc3009TypedDataSchema,
32113
32197
  feeQuote: bridgeFeeQuoteSchema
32114
32198
  }).passthrough();
32199
+ const bridgeDepositPrepareReviewSchema = zod.z.object({
32200
+ sourceChain: zod.z.string(),
32201
+ destinationChain: zod.z.string(),
32202
+ amount: amountJsonSchema,
32203
+ vaultAddress: hexAddressSchema
32204
+ }).passthrough();
32115
32205
  /**
32116
32206
  * Zod schema for the bridge deposit prepare payload.
32117
32207
  *
@@ -32123,7 +32213,7 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32123
32213
  execId: bridgeDepositExecIdSchema,
32124
32214
  erc3009TypedData: bridgeDepositPreparedBundleSchema,
32125
32215
  expiresAt: zod.z.string().datetime(),
32126
- review: zod.z.unknown().optional()
32216
+ review: bridgeDepositPrepareReviewSchema
32127
32217
  });
32128
32218
  /**
32129
32219
  * Zod schema for the `POST /v1/earnKit/bridge/deposit/prepare` API response.
@@ -32851,7 +32941,8 @@ function toPositionInfo(data) {
32851
32941
  return {
32852
32942
  execId: response.data.execId,
32853
32943
  preparedBundle,
32854
- expiresAt: response.data.expiresAt
32944
+ expiresAt: response.data.expiresAt,
32945
+ review: response.data.review
32855
32946
  };
32856
32947
  } catch (error) {
32857
32948
  throw parseEarnApiError(error, {
@@ -32962,47 +33053,34 @@ const VALID_AFTER_CLOCK_SKEW_SECONDS = 60n;
32962
33053
  * @throws If any typed-data field does not match the expected bridge deposit intent.
32963
33054
  * @internal
32964
33055
  */ function assertBridgeDepositTypedDataMatchesIntent(intent) {
32965
- const { preparedBundle, sourceChain, sourceAddress, sourceTokenAddress, authorizationRecipient, amount, maxFee, tokenDecimals, nowSeconds = BigInt(Math.floor(Date.now() / 1000)) } = intent;
33056
+ const { preparedBundle, sourceChain, sourceAddress, sourceTokenAddress, expectedDomain, authorizationRecipient, amount, maxFee, tokenDecimals, amountRaw, nowSeconds = BigInt(Math.floor(Date.now() / 1000)) } = intent;
32966
33057
  const { domain, message } = preparedBundle.erc3009TypedData;
32967
- // USDC deployments differ on the EIP-712 domain name per chain, so the
32968
- // expected domain is looked up for the source chain being signed on.
32969
- const expectedDomain = EARN_BRIDGE_ERC3009_DOMAINS[sourceChain.chain];
32970
- if (expectedDomain === undefined) {
32971
- throw createValidationFailedError$1('sourceChain.chain', sourceChain.chain, 'no expected ERC-3009 domain is configured for the source chain');
32972
- }
32973
- if (domain.name !== expectedDomain.name) {
32974
- throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.name', domain.name, `domain name must be ${expectedDomain.name}`);
32975
- }
32976
- if (domain.version !== expectedDomain.version) {
32977
- throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.version', domain.version, `domain version must be ${expectedDomain.version}`);
32978
- }
32979
33058
  assertSameAddress('preparedBundle.erc3009TypedData.domain.verifyingContract', domain.verifyingContract, sourceTokenAddress, 'verifyingContract must match the source token contract');
32980
33059
  assertSameBigInt('preparedBundle.erc3009TypedData.domain.chainId', domain.chainId, BigInt(sourceChain.chainId), 'chainId must match the source chain');
33060
+ assertSameString('preparedBundle.erc3009TypedData.domain.name', domain.name, expectedDomain.name, 'domain name must match the source token');
33061
+ assertSameString('preparedBundle.erc3009TypedData.domain.version', domain.version, expectedDomain.version, 'domain version must match the source token');
32981
33062
  assertSameAddress('preparedBundle.erc3009TypedData.message.from', message.from, sourceAddress, 'from must match the source wallet');
32982
33063
  assertSameAddress('preparedBundle.erc3009TypedData.message.to', message.to, authorizationRecipient, 'to must match the configured authorization recipient');
32983
- // The prepare path folds the source fee into the signed authorization:
32984
- // message.value = principal + sum(feeQuote.items[].amount), all denominated
32985
- // in the source token. The fee items only add up to a meaningful total when
32986
- // they are priced in the same token as the principal, so first pin the fee
32987
- // token to the source token contract before summing.
32988
- assertSameAddress('preparedBundle.feeQuote.feeToken', preparedBundle.feeQuote.feeToken, sourceTokenAddress, 'feeQuote.feeToken must match the source token contract');
32989
- // Recompute the expected value from locally trusted inputs (the caller
32990
- // principal) plus the bundle's own fee items so the guard accepts fee-bearing
32991
- // deposits while still rejecting any other value tampering. The fee quote is
32992
- // service-supplied, so bound it to the caller-accepted quote-derived cap
32993
- // before including it in the signed value.
32994
- const principal = Amount.parse(amount, {
33064
+ // Recompute the expected ERC-3009 value from locally trusted inputs. Bridge
33065
+ // source fees are paid in the source ERC-20 and are part of the authorization.
33066
+ assertDecimalPlaces('amount', amount, tokenDecimals, `amount must have at most ${String(tokenDecimals)} decimal places for the cross-chain Earn deposit source token`);
33067
+ const parsedAmount = Amount.parse(amount, {
32995
33068
  decimals: tokenDecimals
32996
33069
  }).raw;
33070
+ const principal = amountRaw === undefined ? parsedAmount : parseReviewedAmount(amountRaw, parsedAmount);
33071
+ const feeQuote = preparedBundle.feeQuote;
32997
33072
  const feeTotal = sumBridgeFeeItems(preparedBundle.feeQuote);
33073
+ assertFeeTokenMatchesSourceToken(feeQuote.feeToken, sourceTokenAddress);
33074
+ const feeDecimals = tokenDecimals;
33075
+ assertDecimalPlaces('maxFee', maxFee, feeDecimals, `maxFee must have at most ${String(feeDecimals)} decimal places for the cross-chain Earn deposit fee token`);
32998
33076
  const maxFeeRaw = Amount.parse(maxFee, {
32999
- decimals: tokenDecimals
33077
+ decimals: feeDecimals
33000
33078
  }).raw;
33001
33079
  if (feeTotal > maxFeeRaw) {
33002
33080
  throw createValidationFailedError$1('preparedBundle.feeQuote.items', feeTotal.toString(), 'fee total must not exceed maxFee in base units');
33003
33081
  }
33004
33082
  const expectedValue = principal + feeTotal;
33005
- assertSameBigInt('preparedBundle.erc3009TypedData.message.value', message.value, expectedValue, 'value must match the requested amount plus the bundle fee in base units');
33083
+ assertSameBigInt('preparedBundle.erc3009TypedData.message.value', message.value, expectedValue, 'value must match the requested amount plus source-token fees in base units');
33006
33084
  const window = checkErc3009ValidityWindow({
33007
33085
  validAfter: BigInt(message.validAfter),
33008
33086
  validBefore: BigInt(message.validBefore),
@@ -33076,14 +33154,19 @@ const VALID_AFTER_CLOCK_SKEW_SECONDS = 60n;
33076
33154
  /**
33077
33155
  * Sum the source-collected fee items in a prepared bundle's fee quote.
33078
33156
  *
33079
- * Each item amount is a base-unit value in the shared `feeToken`; the total is
33080
- * what the prepare path adds to the principal when building `message.value`.
33157
+ * Each item amount is a base-unit value in the shared `feeToken`.
33081
33158
  *
33082
33159
  * @param feeQuote - Fee quote parsed from the prepared bundle.
33083
- * @returns Total fee in source token base units (0n when there are no items).
33160
+ * @returns Total fee in fee-token base units (0n when there are no items).
33084
33161
  */ function sumBridgeFeeItems(feeQuote) {
33085
33162
  return feeQuote.items.reduce((total, item)=>total + BigInt(item.amount), 0n);
33086
33163
  }
33164
+ function assertFeeTokenMatchesSourceToken(feeToken, sourceTokenAddress) {
33165
+ if (isSameAddress(feeToken, sourceTokenAddress)) {
33166
+ return;
33167
+ }
33168
+ throw createValidationFailedError$1('preparedBundle.feeQuote.feeToken', feeToken, 'fee token must match the cross-chain Earn deposit source token');
33169
+ }
33087
33170
  function assertNever(value) {
33088
33171
  throw new KitError({
33089
33172
  ...EarnError.INTERNAL_ERROR,
@@ -33091,8 +33174,23 @@ function assertNever(value) {
33091
33174
  message: `Unexpected validity window reason: ${String(value)}`
33092
33175
  });
33093
33176
  }
33177
+ function parseReviewedAmount(amountRaw, parsedAmount) {
33178
+ let reviewedAmount;
33179
+ try {
33180
+ reviewedAmount = BigInt(amountRaw);
33181
+ } catch {
33182
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'amount must be a non-negative integer string');
33183
+ }
33184
+ if (reviewedAmount < 0n) {
33185
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'amount must be a non-negative integer string');
33186
+ }
33187
+ if (reviewedAmount !== parsedAmount) {
33188
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'review amount must match the requested amount');
33189
+ }
33190
+ return reviewedAmount;
33191
+ }
33094
33192
  function assertSameAddress(field, actual, expected, reason) {
33095
- if (actual.toLowerCase() !== expected.toLowerCase()) {
33193
+ if (!isSameAddress(actual, expected)) {
33096
33194
  throw createValidationFailedError$1(field, actual, reason);
33097
33195
  }
33098
33196
  }
@@ -33101,6 +33199,11 @@ function assertSameBigInt(field, actual, expected, reason) {
33101
33199
  throw createValidationFailedError$1(field, actual, reason);
33102
33200
  }
33103
33201
  }
33202
+ function assertSameString(field, actual, expected, reason) {
33203
+ if (actual !== expected) {
33204
+ throw createValidationFailedError$1(field, actual, reason);
33205
+ }
33206
+ }
33104
33207
 
33105
33208
  /**
33106
33209
  * Build signed withdrawal instructions via the Earn Service API.
@@ -33458,6 +33561,9 @@ function toWithdrawalQuoteInfo(data) {
33458
33561
  'CONFIRMED',
33459
33562
  'COMPLETE'
33460
33563
  ]);
33564
+ // Intentionally built-ins-only: Earn bridge support is limited to SDK-known
33565
+ // token contracts plus the explicit ERC-3009 domain allowlist below.
33566
+ const TOKEN_REGISTRY = createTokenRegistry();
33461
33567
  /**
33462
33568
  * Build the typed error raised when a cross-chain wait is cancelled via its
33463
33569
  * `AbortSignal`. Mirrors `@core/adapter-base`'s `createAbortError` (same
@@ -33787,24 +33893,20 @@ function finishElapsedWait(lastStatus, lastError) {
33787
33893
  }
33788
33894
  const { address, chain: apiChain, chainDefinition: chain } = await resolveAdapterContext(params.from);
33789
33895
  const adapterContractAddress = requireAdapterContract(chain);
33790
- const rawUsdcAddress = chain.usdcAddress;
33791
- if (rawUsdcAddress === null) {
33792
- throw createUnsupportedTokenError('USDC', chain.name);
33793
- }
33794
- const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
33795
33896
  const { adapter } = params.from;
33897
+ const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
33796
33898
  const { executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
33797
- vaultAddress: params.vaultAddress,
33899
+ vaultAddress,
33798
33900
  amount: params.amount,
33799
33901
  address,
33800
33902
  chain: apiChain,
33801
33903
  config
33802
33904
  }), ()=>undefined);
33803
33905
  validateExecutionDeadline(executionParams);
33804
- const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
33805
- const approvalToken = tokenInputs[0]?.token;
33906
+ const approvalToken = resolveEarnApprovalToken(executionParams);
33907
+ const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
33806
33908
  const requiredAllowance = sumTokenInputAmounts(tokenInputs);
33807
- if (!options.skipApprove && approvalToken !== undefined) {
33909
+ if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
33808
33910
  await this.runPhase(ctx, 'approve', 'approve', async ()=>approveAllowanceIfNeeded({
33809
33911
  adapter,
33810
33912
  chain,
@@ -33812,7 +33914,7 @@ function finishElapsedWait(lastStatus, lastError) {
33812
33914
  delegate: adapterContractAddress,
33813
33915
  address,
33814
33916
  requiredAllowance,
33815
- revertMessage: 'USDC approval reverted on-chain'
33917
+ revertMessage: 'Earn deposit token approval reverted on-chain'
33816
33918
  }), (txHash)=>txHash);
33817
33919
  }
33818
33920
  const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>executeEarnAction({
@@ -33831,7 +33933,7 @@ function finishElapsedWait(lastStatus, lastError) {
33831
33933
  kind: 'same-chain',
33832
33934
  txHash,
33833
33935
  explorerUrl,
33834
- vaultAddress: params.vaultAddress,
33936
+ vaultAddress,
33835
33937
  amount: params.amount
33836
33938
  };
33837
33939
  } catch (error) {
@@ -33853,13 +33955,6 @@ function finishElapsedWait(lastStatus, lastError) {
33853
33955
  const sourceAddress = assertHexAddress('from.address', await resolveCrossChainSourceAddress(params.from, sourceChain), `Source address for chain ${sourceChain.name} must be a 0x-prefixed 20-byte hex address.`);
33854
33956
  const destinationAddress = assertHexAddress('to.recipientAddress', params.to.recipientAddress, `Destination address for chain ${destinationChain.name} must be a 0x-prefixed 20-byte hex address.`);
33855
33957
  const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
33856
- // The signed ERC-3009 value is denominated in USDC's 6 decimals. More
33857
- // precise inputs would be silently truncated by Amount.parse, so the
33858
- // result would claim a larger deposit than was actually authorized.
33859
- const amountDecimals = params.amount.split('.')[1]?.length ?? 0;
33860
- if (amountDecimals > TOKEN_DECIMALS_6) {
33861
- throw createValidationFailedError$1('amount', params.amount, `amount must have at most ${String(TOKEN_DECIMALS_6)} decimal places for cross-chain Earn deposits`);
33862
- }
33863
33958
  const execId = params.idempotencyKey;
33864
33959
  const prepared = await this.runPhase(ctx, 'crossChainDeposit', 'prepare', async ()=>fetchBridgeDepositPrepare({
33865
33960
  execId,
@@ -33879,17 +33974,19 @@ function finishElapsedWait(lastStatus, lastError) {
33879
33974
  // the error trace, instead of ending the event stream unannounced
33880
33975
  // after the prepare success event.
33881
33976
  assertPreparedBundleMatchesTypedDataNonce(prepared.preparedBundle);
33977
+ const sourceToken = resolvePreparedBridgeSourceToken(prepared.preparedBundle, route.sourceChain);
33978
+ validatePreparedReviewAmountDecimals(prepared.review.amount.decimals, sourceToken.decimals);
33882
33979
  assertBridgeDepositTypedDataMatchesIntent({
33883
33980
  preparedBundle: prepared.preparedBundle,
33884
33981
  sourceChain: route.sourceChain,
33885
33982
  sourceAddress,
33886
- sourceTokenAddress: route.sourceTokenAddress,
33983
+ sourceTokenAddress: sourceToken.address,
33984
+ expectedDomain: sourceToken.erc3009Domain,
33887
33985
  authorizationRecipient: route.authorizationRecipient,
33888
33986
  amount: params.amount,
33889
33987
  maxFee: params.maxFee,
33890
- // Cross-chain Earn is USDC-only today. USDC uses 6 decimals on all
33891
- // configured source chains.
33892
- tokenDecimals: TOKEN_DECIMALS_6
33988
+ tokenDecimals: sourceToken.decimals,
33989
+ amountRaw: prepared.review.amount.raw
33893
33990
  });
33894
33991
  return signBridgeDepositAuthorization({
33895
33992
  adapter: bridgeSigningAdapter,
@@ -34112,21 +34209,17 @@ function finishElapsedWait(lastStatus, lastError) {
34112
34209
  const { adapter, chain, apiChain, address, vaultAddress, amount, config } = params;
34113
34210
  try {
34114
34211
  const adapterContractAddress = requireAdapterContract(chain);
34115
- const rawUsdcAddress = chain.usdcAddress;
34116
- if (rawUsdcAddress === null) {
34117
- throw createUnsupportedTokenError('USDC', chain.name);
34118
- }
34119
- const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
34212
+ const normalizedVaultAddress = assertHexAddress('vaultAddress', vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
34120
34213
  const { executionParams, signature } = await fetchDeposit({
34121
- vaultAddress,
34214
+ vaultAddress: normalizedVaultAddress,
34122
34215
  amount,
34123
34216
  address,
34124
34217
  chain: apiChain,
34125
34218
  config
34126
34219
  });
34127
34220
  validateExecutionDeadline(executionParams);
34128
- const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
34129
- const approvalToken = tokenInputs[0]?.token;
34221
+ const approvalToken = resolveEarnApprovalToken(executionParams);
34222
+ const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
34130
34223
  const requiredAllowance = sumTokenInputAmounts(tokenInputs);
34131
34224
  return await estimateEarnQuoteGasFees({
34132
34225
  adapter,
@@ -34139,7 +34232,7 @@ function finishElapsedWait(lastStatus, lastError) {
34139
34232
  tokenInputs,
34140
34233
  signature
34141
34234
  },
34142
- approval: approvalToken !== undefined ? {
34235
+ approval: approvalToken !== undefined && requiredAllowance > 0n ? {
34143
34236
  token: approvalToken,
34144
34237
  delegate: adapterContractAddress,
34145
34238
  requiredAllowance
@@ -34383,11 +34476,6 @@ function validateCrossChainDepositRoute(sourceChain, destinationChain) {
34383
34476
  if (destinationApiChain === undefined) {
34384
34477
  throw createUnsupportedCrossChainDepositError(destinationChain, 'destination chain is not enabled for cross-chain Earn deposits');
34385
34478
  }
34386
- const sourceTokenAddressRaw = sourceChain.usdcAddress;
34387
- if (sourceTokenAddressRaw === null) {
34388
- throw createUnsupportedTokenError('USDC', sourceChain.name);
34389
- }
34390
- const sourceTokenAddress = assertHexAddress('sourceChain.usdcAddress', sourceTokenAddressRaw, `USDC address for chain ${sourceChain.name} must be a 0x-prefixed 20-byte hex address.`);
34391
34479
  if (sourceChain.cctp == null) {
34392
34480
  throw createBridgeRouteNotConfiguredError(sourceChain, 'source chain does not support CCTP');
34393
34481
  }
@@ -34411,10 +34499,50 @@ function validateCrossChainDepositRoute(sourceChain, destinationChain) {
34411
34499
  destinationApiChain,
34412
34500
  destinationDomain: destinationChain.cctp.domain,
34413
34501
  sourceChain,
34414
- sourceTokenAddress,
34415
34502
  authorizationRecipient
34416
34503
  };
34417
34504
  }
34505
+ function resolvePreparedBridgeSourceToken(preparedBundle, sourceChain) {
34506
+ const sourceTokenAddress = assertHexAddress('preparedBundle.erc3009TypedData.domain.verifyingContract', preparedBundle.erc3009TypedData.domain.verifyingContract, `Source token for chain ${sourceChain.name} must be a 0x-prefixed 20-byte hex address.`);
34507
+ let sourceToken;
34508
+ try {
34509
+ sourceToken = TOKEN_REGISTRY.resolveByAddress(sourceTokenAddress, sourceChain.chain);
34510
+ } catch {
34511
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token ${sourceTokenAddress} is not configured on source chain ${sourceChain.name} for cross-chain Earn deposits`);
34512
+ }
34513
+ const registryAddress = assertHexAddress('sourceToken.locator', sourceToken.locator, `Source token ${sourceToken.symbol ?? sourceTokenAddress} for chain ${sourceChain.name} must be a 0x-prefixed 20-byte hex address.`);
34514
+ if (!isSameAddress(sourceTokenAddress, registryAddress)) {
34515
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token must match the configured ${sourceToken.symbol ?? 'token'} address on source chain ${sourceChain.name}`);
34516
+ }
34517
+ const { symbol } = sourceToken;
34518
+ if (symbol === undefined) {
34519
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token ${sourceTokenAddress} is not a registered Earn bridge token on source chain ${sourceChain.name}`);
34520
+ }
34521
+ return {
34522
+ address: sourceTokenAddress,
34523
+ erc3009Domain: resolveBridgeErc3009Domain(symbol, sourceChain),
34524
+ decimals: sourceToken.decimals
34525
+ };
34526
+ }
34527
+ function validatePreparedReviewAmountDecimals(reviewedDecimals, tokenDecimals) {
34528
+ if (reviewedDecimals !== tokenDecimals) {
34529
+ throw createValidationFailedError$1('prepared.review.amount.decimals', reviewedDecimals, `review amount decimals must match the cross-chain Earn deposit source token decimals ${String(tokenDecimals)}`);
34530
+ }
34531
+ }
34532
+ function resolveBridgeErc3009Domain(tokenSymbol, sourceChain) {
34533
+ if (!isEarnBridgeErc3009TokenSymbol(tokenSymbol)) {
34534
+ throw createValidationFailedError$1('sourceToken.symbol', tokenSymbol, `ERC-3009 domain is not configured for ${tokenSymbol} on source chain ${sourceChain.name}`);
34535
+ }
34536
+ const domainsByChain = EARN_BRIDGE_ERC3009_DOMAINS[tokenSymbol];
34537
+ const domain = domainsByChain[sourceChain.chain];
34538
+ if (domain === undefined) {
34539
+ throw createValidationFailedError$1('sourceToken.symbol', tokenSymbol, `ERC-3009 domain is not configured for ${tokenSymbol} on source chain ${sourceChain.name}`);
34540
+ }
34541
+ return domain;
34542
+ }
34543
+ function isEarnBridgeErc3009TokenSymbol(tokenSymbol) {
34544
+ return EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS.includes(tokenSymbol);
34545
+ }
34418
34546
  function createUnsupportedCrossChainDepositError(chain, reason) {
34419
34547
  return new KitError({
34420
34548
  ...EarnError.UNSUPPORTED_CHAIN,
@@ -34487,7 +34615,6 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
34487
34615
  // Untyped callers can pass anything here; without these guards a null
34488
34616
  // config crashes with a raw TypeError on `config.providers` instead of
34489
34617
  // the structured validation error every other public surface throws.
34490
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JS consumers may pass null or a non-object config
34491
34618
  if (typeof config !== 'object' || config === null || Array.isArray(config)) {
34492
34619
  throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
34493
34620
  }
@@ -34854,10 +34981,9 @@ const sourceAdapterContextSchema = zod.z.object({
34854
34981
  * Schema for validating human-readable decimal amount strings.
34855
34982
  *
34856
34983
  * Accept positive decimal strings like '100', '100.50', '0.001'. Reject
34857
- * zero, negative, non-numeric, and non-canonical strings. EarnKit
34858
- * same-chain amounts are USDC/EURC (6 decimals), so precision is capped at
34859
- * 6 to match the Earn Service and fail fast locally instead of leaking
34860
- * 7+ decimal-place inputs to the server.
34984
+ * zero, negative, non-numeric, and non-canonical strings. Same-chain
34985
+ * EarnKit vaults can use different asset precisions, so the service enforces
34986
+ * the vault-specific decimal cap after resolving the vault asset.
34861
34987
  *
34862
34988
  * @internal
34863
34989
  */ const amountSchema$1 = zod.z.string({
@@ -34866,32 +34992,15 @@ const sourceAdapterContextSchema = zod.z.object({
34866
34992
  allowZero: false,
34867
34993
  regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34868
34994
  attributeName: 'amount',
34869
- maxDecimals: 6
34870
- })(zod.z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34871
- /**
34872
- * Schema for cross-chain deposit amount strings.
34873
- *
34874
- * Cross-chain Earn deposits are USDC-only, and the signed ERC-3009 value is
34875
- * denominated in USDC's 6 decimals. Reject more precise inputs up front so
34876
- * the signed value always equals the requested amount instead of a silent
34877
- * truncation. Non-canonical forms (leading dot, leading zeros) are rejected
34878
- * too, matching the Earn Service.
34879
- *
34880
- * @internal
34881
- */ const crossChainAmountSchema = zod.z.string({
34882
- required_error: 'amount is required'
34883
- }).min(1, 'amount is required').pipe(createDecimalStringValidator({
34884
- allowZero: false,
34885
- regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34886
- attributeName: 'amount',
34887
- maxDecimals: 6
34995
+ maxDecimals: 18
34888
34996
  })(zod.z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34889
34997
  /**
34890
34998
  * Schema for cross-chain source fee caps.
34891
34999
  *
34892
- * Cross-chain source fees are denominated in USDC base units. Zero is valid
34893
- * when the quote contains no source-collected fees; otherwise the cap should be
34894
- * the caller-accepted total from a recent quote.
35000
+ * Cross-chain source fees are denominated in the quote's fee token. Zero is
35001
+ * valid when the quote contains no source-collected fees; otherwise the cap
35002
+ * should be the caller-accepted total from a recent quote. The provider
35003
+ * enforces the prepared bundle's fee-token precision before signing.
34895
35004
  *
34896
35005
  * @internal
34897
35006
  */ const crossChainMaxFeeSchema = zod.z.string({
@@ -34900,7 +35009,7 @@ const sourceAdapterContextSchema = zod.z.object({
34900
35009
  allowZero: true,
34901
35010
  regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34902
35011
  attributeName: 'maxFee',
34903
- maxDecimals: 6
35012
+ maxDecimals: 18
34904
35013
  })(zod.z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34905
35014
  /**
34906
35015
  * Build a schema for an optional non-negative decimal filter string.
@@ -35061,7 +35170,7 @@ const crossChainDepositDestinationSchema = zod.z.object({
35061
35170
  from: sourceAdapterContextSchema,
35062
35171
  to: crossChainDepositDestinationSchema,
35063
35172
  vaultAddress: vaultAddressSchema,
35064
- amount: crossChainAmountSchema,
35173
+ amount: amountSchema$1,
35065
35174
  maxFee: crossChainMaxFeeSchema,
35066
35175
  transferSpeed: zod.z.enum([
35067
35176
  'FAST',
@@ -37801,7 +37910,7 @@ async function deposit$2(context, params) {
37801
37910
  }
37802
37911
 
37803
37912
  var name = "@circle-fin/unified-balance-kit";
37804
- var version = "1.2.0";
37913
+ var version = "1.2.1";
37805
37914
  var pkg = {
37806
37915
  name: name,
37807
37916
  version: version};