@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.mjs CHANGED
@@ -3650,7 +3650,7 @@ function getOptionalString(value) {
3650
3650
  * - vault-not-found, unsupported-chain, unsupported-vault,
3651
3651
  * signature-rejected, invalid-id, invalid-batch-size, position-not-registered,
3652
3652
  * withdrawal-max-exceeded, insufficient-balance, bridge prepare idempotency
3653
- * conflicts
3653
+ * conflicts, invalid vault-asset amount precision
3654
3654
  *
3655
3655
  * SERVICE errors (RETRYABLE) — try again later:
3656
3656
  * - signing-failed, provider-error, rewards-fetch-failed,
@@ -3834,6 +3834,13 @@ function getOptionalString(value) {
3834
3834
  recoverability: 'RETRYABLE'
3835
3835
  }
3836
3836
  ],
3837
+ [
3838
+ 380415,
3839
+ {
3840
+ errorDef: EarnError.INVALID_INPUT,
3841
+ recoverability: 'FATAL'
3842
+ }
3843
+ ],
3837
3844
  // Bridge (380_5XX)
3838
3845
  [
3839
3846
  380500,
@@ -3869,6 +3876,13 @@ function getOptionalString(value) {
3869
3876
  errorDef: EarnError.PROVIDER_ERROR,
3870
3877
  recoverability: 'RETRYABLE'
3871
3878
  }
3879
+ ],
3880
+ [
3881
+ 380505,
3882
+ {
3883
+ errorDef: EarnError.PROVIDER_ERROR,
3884
+ recoverability: 'FATAL'
3885
+ }
3872
3886
  ]
3873
3887
  ]);
3874
3888
  /**
@@ -4328,23 +4342,6 @@ var EarnChain;
4328
4342
  }
4329
4343
  });
4330
4344
 
4331
- /**
4332
- * Standard decimal places for tokens.
4333
- *
4334
- * These constants define the decimal precision used by different token types
4335
- * across the SDK. They are used for amount conversions between human-readable
4336
- * format and smallest units (base units).
4337
- *
4338
- * @remarks
4339
- * - Most stablecoins (USDC, EURC, USDT, PYUSD) use 6 decimal places
4340
- * - Some stablecoins (DAI, USDE) and most native tokens (ETH, POL, PLUME) use 18 decimals
4341
- * - The NATIVE alias uses chain-specific decimals which may vary (e.g., SOL uses 9)
4342
- */ /**
4343
- * Standard decimal places for 6-decimal tokens.
4344
- *
4345
- * Used by most stablecoins: USDC, EURC, USDT, PYUSD
4346
- */ const TOKEN_DECIMALS_6 = 6;
4347
-
4348
4345
  /**
4349
4346
  * @packageDocumentation
4350
4347
  * @module SwapTokenRegistry
@@ -9985,13 +9982,20 @@ function parseOrThrow(value, schema, context) {
9985
9982
  symbol: 'EURC',
9986
9983
  decimals: 6,
9987
9984
  locators: {
9985
+ // =========================================================================
9986
+ // Mainnets
9987
+ // =========================================================================
9988
9988
  [Blockchain.Avalanche]: '0xc891EB4cbdEFf6e073e859e987815Ed1505c2ACD',
9989
9989
  [Blockchain.Base]: '0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42',
9990
9990
  [Blockchain.Ethereum]: '0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c',
9991
9991
  [Blockchain.Solana]: 'HzwqbKZw8HxMN6bF2yFZNrht3c2iXXzpKcFu7uBEDKtr',
9992
9992
  [Blockchain.World_Chain]: '0x1C60ba0A0eD1019e8Eb035E6daF4155A5cE2380B',
9993
+ // =========================================================================
9993
9994
  // Testnets
9994
- [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a'
9995
+ // =========================================================================
9996
+ [Blockchain.Arc_Testnet]: '0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a',
9997
+ [Blockchain.Base_Sepolia]: '0x808456652fdb597867f38412077A9182bf77359F',
9998
+ [Blockchain.Ethereum_Sepolia]: '0x08210F9170F89Ab7658F0B5E3fF39b0E03C594D4'
9995
9999
  }
9996
10000
  };
9997
10001
 
@@ -10201,8 +10205,7 @@ function parseOrThrow(value, schema, context) {
10201
10205
  * cirBTC (Circle Bitcoin) token definition with addresses and metadata.
10202
10206
  *
10203
10207
  * @remarks
10204
- * Built-in cirBTC definition for the TokenRegistry. Currently deployed
10205
- * on Arc Testnet.
10208
+ * Built-in cirBTC definition for the TokenRegistry.
10206
10209
  *
10207
10210
  * @example
10208
10211
  * ```typescript
@@ -10218,7 +10221,15 @@ function parseOrThrow(value, schema, context) {
10218
10221
  symbol: 'cirBTC',
10219
10222
  decimals: 8,
10220
10223
  locators: {
10221
- [Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF'
10224
+ // =========================================================================
10225
+ // Mainnets
10226
+ // =========================================================================
10227
+ [Blockchain.Ethereum]: '0x72DFB2E44f59C5AD2bAFE84314E5b99a7cd5075E',
10228
+ // =========================================================================
10229
+ // Testnets
10230
+ // =========================================================================
10231
+ [Blockchain.Arc_Testnet]: '0xf0C4a4CE82A5746AbAAd9425360Ab04fbBA432BF',
10232
+ [Blockchain.Ethereum_Sepolia]: '0x3a3fe695F684Bf9b9e43CF43C2b895Ea5e392bB3'
10222
10233
  }
10223
10234
  };
10224
10235
 
@@ -11499,7 +11510,7 @@ function resolveOptions(options) {
11499
11510
  }
11500
11511
 
11501
11512
  var name$3 = "@circle-fin/bridge-kit";
11502
- var version$4 = "1.11.0";
11513
+ var version$4 = "1.11.1";
11503
11514
  var pkg$4 = {
11504
11515
  name: name$3,
11505
11516
  version: version$4};
@@ -16290,7 +16301,7 @@ const mockAttestationMessage = {
16290
16301
  return step;
16291
16302
  }
16292
16303
 
16293
- var version$3 = "1.8.4";
16304
+ var version$3 = "1.8.5";
16294
16305
  var pkg$3 = {
16295
16306
  version: version$3};
16296
16307
 
@@ -18697,7 +18708,7 @@ registerKit(`${pkg$4.name}/${pkg$4.version}`);
18697
18708
  };
18698
18709
 
18699
18710
  var name$2 = "@circle-fin/swap-kit";
18700
- var version$2 = "1.3.0";
18711
+ var version$2 = "1.3.1";
18701
18712
  var pkg$2 = {
18702
18713
  name: name$2,
18703
18714
  version: version$2};
@@ -23632,7 +23643,7 @@ function writeBytes32(buffer, hex, offset) {
23632
23643
  }
23633
23644
  }
23634
23645
 
23635
- const TOKEN_REGISTRY$3 = createTokenRegistry();
23646
+ const TOKEN_REGISTRY$4 = createTokenRegistry();
23636
23647
  /**
23637
23648
  * Resolve token alias to contract address for the given chain.
23638
23649
  *
@@ -23721,7 +23732,7 @@ const TOKEN_REGISTRY$3 = createTokenRegistry();
23721
23732
  }
23722
23733
  });
23723
23734
  }
23724
- const tokenDefinition = TOKEN_REGISTRY$3.get(upperToken);
23735
+ const tokenDefinition = TOKEN_REGISTRY$4.get(upperToken);
23725
23736
  if (tokenDefinition !== undefined) {
23726
23737
  const resolvedAddress = tokenDefinition.locators[chain.chain];
23727
23738
  if (!resolvedAddress) {
@@ -23738,7 +23749,7 @@ const TOKEN_REGISTRY$3 = createTokenRegistry();
23738
23749
  }
23739
23750
  });
23740
23751
  }
23741
- return TOKEN_REGISTRY$3.resolve(upperToken, chain.chain).locator;
23752
+ return TOKEN_REGISTRY$4.resolve(upperToken, chain.chain).locator;
23742
23753
  }
23743
23754
  // Not an alias - return as-is (assumed to be address)
23744
23755
  return token;
@@ -23835,11 +23846,11 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
23835
23846
  if (destinationChain.chain !== sourceChain.chain) {
23836
23847
  return sourceChain;
23837
23848
  }
23838
- const inputIsOk = isOkToken(tokenIn, sourceChain, TOKEN_REGISTRY$3, OK_TOKEN_SYMBOLS$1);
23849
+ const inputIsOk = isOkToken(tokenIn, sourceChain, TOKEN_REGISTRY$4, OK_TOKEN_SYMBOLS$1);
23839
23850
  if (inputIsOk) {
23840
23851
  return sourceChain;
23841
23852
  }
23842
- const outputIsOk = isOkToken(tokenOut, destinationChain, TOKEN_REGISTRY$3, OK_TOKEN_SYMBOLS$1);
23853
+ const outputIsOk = isOkToken(tokenOut, destinationChain, TOKEN_REGISTRY$4, OK_TOKEN_SYMBOLS$1);
23843
23854
  return outputIsOk ? destinationChain : sourceChain;
23844
23855
  }
23845
23856
  /**
@@ -25130,7 +25141,7 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
25130
25141
  }
25131
25142
  }
25132
25143
 
25133
- const TOKEN_REGISTRY$2 = createTokenRegistry();
25144
+ const TOKEN_REGISTRY$3 = createTokenRegistry();
25134
25145
  /**
25135
25146
  * Resolve a token identifier to its canonical symbol for the Stablecoin Service Swap Provider.
25136
25147
  *
@@ -25174,7 +25185,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25174
25185
  return NATIVE_TOKEN;
25175
25186
  }
25176
25187
  // For known registry aliases, return canonical symbol directly.
25177
- if (TOKEN_REGISTRY$2.get(upperToken) !== undefined) {
25188
+ if (TOKEN_REGISTRY$3.get(upperToken) !== undefined) {
25178
25189
  return upperToken;
25179
25190
  }
25180
25191
  // For EVM chains, normalize addresses to lowercase for case-insensitive comparison.
@@ -25184,7 +25195,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25184
25195
  // Native address is given
25185
25196
  if (isNativeEvmAddress(token) || isNativeSolanaAddress(token)) return NATIVE_TOKEN;
25186
25197
  try {
25187
- const { symbol } = TOKEN_REGISTRY$2.resolveByAddress(normalizedToken, chain.chain);
25198
+ const { symbol } = TOKEN_REGISTRY$3.resolveByAddress(normalizedToken, chain.chain);
25188
25199
  if (symbol === undefined) {
25189
25200
  return null;
25190
25201
  }
@@ -25315,7 +25326,7 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
25315
25326
  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.`;
25316
25327
  }
25317
25328
 
25318
- const TOKEN_REGISTRY$1 = createTokenRegistry();
25329
+ const TOKEN_REGISTRY$2 = createTokenRegistry();
25319
25330
  /**
25320
25331
  * Format a raw base-unit amount into a human-readable decimal string.
25321
25332
  *
@@ -25375,12 +25386,12 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
25375
25386
  value,
25376
25387
  token: resolvedSymbol,
25377
25388
  chain,
25378
- tokens: TOKEN_REGISTRY$1
25389
+ tokens: TOKEN_REGISTRY$2
25379
25390
  }),
25380
25391
  token: resolvedSymbol
25381
25392
  };
25382
25393
  }
25383
- const tokenDef = TOKEN_REGISTRY$1.get(resolvedSymbol);
25394
+ const tokenDef = TOKEN_REGISTRY$2.get(resolvedSymbol);
25384
25395
  if (tokenDef) {
25385
25396
  const decimals = resolveTokenDecimals$1(tokenDef, chain.chain);
25386
25397
  return {
@@ -25411,7 +25422,7 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
25411
25422
  * i.e. 1 + 0.2 + 0.1 = 1.3. Use the greater of (local estimate × this multiplier)
25412
25423
  * or the proxy's gasLimit.
25413
25424
  */ const GAS_SAFETY_MULTIPLIER = 1.3;
25414
- const TOKEN_REGISTRY = createTokenRegistry();
25425
+ const TOKEN_REGISTRY$1 = createTokenRegistry();
25415
25426
  /**
25416
25427
  * Enhances a KitError with transaction details (txHash and explorerUrl).
25417
25428
  *
@@ -25487,6 +25498,19 @@ const TOKEN_REGISTRY = createTokenRegistry();
25487
25498
  *
25488
25499
  * @internal
25489
25500
  */ const SUPPORTED_CHAINS$1 = getSwapSupportedChains(Chains);
25501
+ /**
25502
+ * Inter-poll delays (ms) for the same-chain `amountOut` enrichment loop.
25503
+ *
25504
+ * A same-chain swap is already terminal `DONE` once its source transaction
25505
+ * confirms; this short, escalating budget (~5s total across up to four polls)
25506
+ * gives the service a brief window to resolve the output amount before
25507
+ * `swap()` returns. The loop exits the instant `amountOut` is available.
25508
+ */ const SAME_CHAIN_AMOUNT_OUT_POLL_DELAYS_MS = [
25509
+ 1_000,
25510
+ 2_000,
25511
+ 2_000
25512
+ ];
25513
+ const sleep$3 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
25490
25514
  function buildSwapProgress(statusResult) {
25491
25515
  return {
25492
25516
  status: statusResult.status,
@@ -25506,29 +25530,46 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
25506
25530
  }
25507
25531
  };
25508
25532
  }
25509
- try {
25510
- const statusResult = await getSwapStatus$2({
25511
- txHash,
25512
- chain: chain.chain,
25513
- apiKey
25514
- });
25515
- const progress = buildSwapProgress(statusResult);
25516
- if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
25533
+ // A same-chain swap completes atomically in the source transaction, which
25534
+ // has already been confirmed (and receipt-checked for revert) by the time we
25535
+ // get here. Its terminal status is therefore `DONE`. We briefly poll the
25536
+ // status endpoint to enrich `amountOut`, which the service usually resolves
25537
+ // within a few seconds. A non-DONE/erroneous peek never downgrades the
25538
+ // status — once the short budget is exhausted we return `DONE` without
25539
+ // `amountOut`, and the caller can fetch it later via `getSwapStatus`.
25540
+ for(let attempt = 0;; attempt += 1){
25541
+ try {
25542
+ const statusResult = await getSwapStatus$2({
25543
+ txHash,
25544
+ chain: chain.chain,
25545
+ apiKey
25546
+ });
25547
+ if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
25548
+ return {
25549
+ progress: buildSwapProgress(statusResult),
25550
+ amountOut: statusResult.amountOut
25551
+ };
25552
+ }
25553
+ } catch {
25554
+ // Reachability/parse failure during enrichment — non-fatal; the swap
25555
+ // already succeeded on-chain. `getSwapStatus` already retried transient
25556
+ // network errors internally, so further polling is unlikely to help.
25517
25557
  return {
25518
- progress,
25519
- amountOut: statusResult.amountOut
25558
+ progress: {
25559
+ status: 'DONE'
25560
+ }
25520
25561
  };
25521
25562
  }
25522
- return {
25523
- progress
25524
- };
25525
- } catch {
25526
- // Non-fatal — the swap already succeeded on-chain. status stays PENDING.
25527
- return {
25528
- progress: {
25529
- status: 'PENDING'
25530
- }
25531
- };
25563
+ const delayMs = SAME_CHAIN_AMOUNT_OUT_POLL_DELAYS_MS[attempt];
25564
+ if (delayMs === undefined) {
25565
+ // Budget exhausted — terminal `DONE`, `amountOut` not yet available.
25566
+ return {
25567
+ progress: {
25568
+ status: 'DONE'
25569
+ }
25570
+ };
25571
+ }
25572
+ await sleep$3(delayMs);
25532
25573
  }
25533
25574
  }
25534
25575
  /**
@@ -25809,7 +25850,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
25809
25850
  return true;
25810
25851
  }
25811
25852
  // At least one token must be supported for fee collection
25812
- return getSwapOkTokenStatus(tokenInAddress, tokenOutAddress, chain, TOKEN_REGISTRY, OK_TOKEN_SYMBOLS$1).hasOkToken;
25853
+ return getSwapOkTokenStatus(tokenInAddress, tokenOutAddress, chain, TOKEN_REGISTRY$1, OK_TOKEN_SYMBOLS$1).hasOkToken;
25813
25854
  }
25814
25855
  /**
25815
25856
  * Estimate swap costs and output amount by delegating to the Stablecoin Service quote API.
@@ -26406,14 +26447,14 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
26406
26447
  }
26407
26448
  // Build swap fees with adapter-backed formatting for unregistered tokens
26408
26449
  const swapResultFees = serviceResponse.fees ? await this.buildFormattedFees(serviceResponse.fees, chain, destinationChain, adapter, config?.customFee?.recipientAddress) : undefined;
26409
- // Best-effort enrichment: fetch amountOut and provider-reported status.
26410
- // Only performed for same-chain swaps they typically reach DONE within
26411
- // a few seconds of source-tx confirmation, so the peek is useful UX and
26412
- // adds minimal latency. Cross-chain swaps are skipped because the
26413
- // destination mint takes minutes (CCTPv2 attestation), meaning the peek
26414
- // would always return PENDING/WAIT_DESTINATION_TRANSACTION while still
26415
- // burning up to 30s of the load balancer's server-side wait. Callers
26416
- // should use `kit.getSwapStatus()` to poll cross-chain swaps to DONE.
26450
+ // Best-effort enrichment: briefly poll for amountOut on same-chain swaps.
26451
+ // They complete atomically in the (already-confirmed) source tx, so the
26452
+ // result is terminal `DONE` regardless; the short poll only adds amountOut
26453
+ // once the service has resolved it. Cross-chain swaps are skipped because
26454
+ // the destination mint takes minutes (CCTPv2 attestation), so polling would
26455
+ // always return PENDING/WAIT_DESTINATION_TRANSACTION while burning the
26456
+ // budget. Callers should use `kit.getSwapStatus()` to poll cross-chain
26457
+ // swaps to DONE.
26417
26458
  const statusSnapshot = await fetchSameChainStatusSnapshot({
26418
26459
  isCrossChainSwap,
26419
26460
  txHash,
@@ -30715,11 +30756,14 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
30715
30756
  };
30716
30757
 
30717
30758
  var name$1 = "@circle-fin/earn-kit";
30718
- var version$1 = "1.2.0";
30759
+ var version$1 = "1.2.1";
30719
30760
  var pkg$1 = {
30720
30761
  name: name$1,
30721
30762
  version: version$1};
30722
30763
 
30764
+ const EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS = [
30765
+ 'USDC'
30766
+ ];
30723
30767
  /**
30724
30768
  * Default base URL for the Earn Service API.
30725
30769
  *
@@ -30764,20 +30808,7 @@ var pkg$1 = {
30764
30808
  */ const EARN_BRIDGE_DESTINATION_CHAIN_TO_API = {
30765
30809
  [Blockchain.Arc_Testnet]: CHAIN_TO_API[Blockchain.Arc_Testnet]
30766
30810
  };
30767
- /**
30768
- * Expected EIP-712 domain of the ERC-3009 authorization token per source
30769
- * chain.
30770
- *
30771
- * Cross-chain Earn deposits are USDC-only, but USDC deployments differ on the
30772
- * EIP-712 domain name: newer deployments use `USDC` while older ones (for
30773
- * example Arbitrum Sepolia) kept `USD Coin` from the FiatToken V2 upgrade.
30774
- * These values mirror the bridge service's per-chain domain table and the
30775
- * on-chain `DOMAIN_SEPARATOR` inputs. Asserted before signing so a tampered
30776
- * prepare response cannot point the signature at another token. The
30777
- * `satisfies` check forces a domain entry whenever a source chain is added.
30778
- *
30779
- * @internal
30780
- */ const EARN_BRIDGE_ERC3009_DOMAINS = {
30811
+ const EARN_BRIDGE_USDC_ERC3009_DOMAINS = {
30781
30812
  [Blockchain.Arbitrum_Sepolia]: {
30782
30813
  name: 'USD Coin',
30783
30814
  version: '2'
@@ -30791,6 +30822,21 @@ var pkg$1 = {
30791
30822
  version: '2'
30792
30823
  }
30793
30824
  };
30825
+ /**
30826
+ * Expected EIP-712 domain of the ERC-3009 authorization token per
30827
+ * cross-chain-supported source token and source chain.
30828
+ *
30829
+ * The bridge service prepares an ERC-3009 payload for the deposited token. We
30830
+ * keep this table as an explicit SDK allowlist because token deployments can
30831
+ * differ by source chain and by domain name/version. These values mirror the
30832
+ * bridge service's per-chain domain table and the on-chain DOMAIN_SEPARATOR
30833
+ * inputs. Asserted before signing so a tampered prepare response cannot point
30834
+ * the signature at another token or unexpected token domain.
30835
+ *
30836
+ * @internal
30837
+ */ const EARN_BRIDGE_ERC3009_DOMAINS = {
30838
+ USDC: EARN_BRIDGE_USDC_ERC3009_DOMAINS
30839
+ };
30794
30840
  /**
30795
30841
  * Maximum tolerated remaining lifetime of a prepared ERC-3009 authorization.
30796
30842
  *
@@ -31250,6 +31296,16 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31250
31296
  return approvalTxHash;
31251
31297
  }
31252
31298
 
31299
+ /** @internal */ function isSameAddress(actual, expected) {
31300
+ return actual.toLowerCase() === expected.toLowerCase();
31301
+ }
31302
+ /** @internal */ function assertDecimalPlaces(field, value, tokenDecimals, reason) {
31303
+ const decimals = value.split('.')[1]?.length ?? 0;
31304
+ if (decimals > tokenDecimals) {
31305
+ throw createValidationFailedError$1(field, value, reason);
31306
+ }
31307
+ }
31308
+
31253
31309
  /**
31254
31310
  * Build the `tokenInputs` array forwarded to the adapter contract's
31255
31311
  * `execute(executeParams, tokenInputs, signature)` call.
@@ -31273,15 +31329,17 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31273
31329
  *
31274
31330
  * @example
31275
31331
  * ```typescript
31276
- * const approvedToken = assertHexAddress(
31277
- * 'chain.usdcAddress',
31278
- * chain.usdcAddress,
31279
- * )
31280
- *
31281
- * const tokenInputs = buildEarnTokenInputs(
31332
+ * const approvalToken = resolveEarnApprovalToken(
31282
31333
  * executionPayload.executionParams,
31283
- * approvedToken,
31284
31334
  * )
31335
+ *
31336
+ * const tokenInputs =
31337
+ * approvalToken === undefined
31338
+ * ? []
31339
+ * : buildEarnTokenInputs(
31340
+ * executionPayload.executionParams,
31341
+ * approvalToken,
31342
+ * )
31285
31343
  * ```
31286
31344
  *
31287
31345
  * @internal
@@ -31293,7 +31351,7 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31293
31351
  return;
31294
31352
  }
31295
31353
  const { tokenIn } = instruction;
31296
- if (tokenIn.toLowerCase() !== approvedToken.toLowerCase()) {
31354
+ if (!isSameAddress(tokenIn, approvedToken)) {
31297
31355
  throw createValidationFailedError$1(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
31298
31356
  }
31299
31357
  tokenInputs.push({
@@ -31305,6 +31363,33 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
31305
31363
  });
31306
31364
  return tokenInputs;
31307
31365
  }
31366
+ /**
31367
+ * Resolve the single token that needs adapter allowance from signed Earn
31368
+ * instructions.
31369
+ *
31370
+ * @param executionParams - Service-signed `ExecutionParams` forwarded to the adapter.
31371
+ * @returns The token requested by positive approval instructions, or `undefined`.
31372
+ * @throws {@link KitError} If positive approval instructions reference multiple tokens.
31373
+ *
31374
+ * @internal
31375
+ */ function resolveEarnApprovalToken(executionParams) {
31376
+ let approvedToken;
31377
+ executionParams.instructions.forEach((instruction, index)=>{
31378
+ const amount = BigInt(instruction.amountToApprove);
31379
+ if (amount <= 0n) {
31380
+ return;
31381
+ }
31382
+ const { tokenIn } = instruction;
31383
+ if (approvedToken === undefined) {
31384
+ approvedToken = tokenIn;
31385
+ return;
31386
+ }
31387
+ if (!isSameAddress(tokenIn, approvedToken)) {
31388
+ throw createValidationFailedError$1(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
31389
+ }
31390
+ });
31391
+ return approvedToken;
31392
+ }
31308
31393
 
31309
31394
  /**
31310
31395
  * Prepare an earn adapter action, execute it, wait for confirmation, and
@@ -31796,6 +31881,7 @@ const hexSignatureSchema = evmSignatureSchema;
31796
31881
  const hexAddressSchema = evmAddressSchema;
31797
31882
  // '0x' prefix + 32 bytes * 2 hex chars.
31798
31883
  const BYTES32_HEX_LENGTH = 66;
31884
+ const bridgeFeeTokenSchema = hexAddressSchema;
31799
31885
  /**
31800
31886
  * Zod schema for a non-negative uint256-like value.
31801
31887
  *
@@ -32082,14 +32168,12 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32082
32168
  *
32083
32169
  * The bridge prepare path returns one item per collected source fee (e.g.
32084
32170
  * `PRE_FINALITY` for the fast-burn fee and `FORWARD` for the destination
32085
- * forward fee), all denominated in the shared `feeToken`. The signed
32086
- * authorization `value` is `principal + sum(items[].amount)`, so the pre-sign
32087
- * guard parses these to recompute the expected value. Each item's `amount` is
32088
- * a base-unit decimal string in `feeToken` units.
32171
+ * forward fee), all denominated in the shared `feeToken`. Each item's `amount`
32172
+ * is a base-unit decimal string in `feeToken` units.
32089
32173
  *
32090
32174
  * @internal
32091
32175
  */ const bridgeFeeQuoteSchema = z.object({
32092
- feeToken: hexAddressSchema,
32176
+ feeToken: bridgeFeeTokenSchema,
32093
32177
  items: z.array(z.object({
32094
32178
  type: z.string(),
32095
32179
  amount: uint256LikeSchema
@@ -32105,6 +32189,12 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32105
32189
  erc3009TypedData: erc3009TypedDataSchema,
32106
32190
  feeQuote: bridgeFeeQuoteSchema
32107
32191
  }).passthrough();
32192
+ const bridgeDepositPrepareReviewSchema = z.object({
32193
+ sourceChain: z.string(),
32194
+ destinationChain: z.string(),
32195
+ amount: amountJsonSchema,
32196
+ vaultAddress: hexAddressSchema
32197
+ }).passthrough();
32108
32198
  /**
32109
32199
  * Zod schema for the bridge deposit prepare payload.
32110
32200
  *
@@ -32116,7 +32206,7 @@ const eip712DomainSchema = eip712DomainSchema$1.extend({
32116
32206
  execId: bridgeDepositExecIdSchema,
32117
32207
  erc3009TypedData: bridgeDepositPreparedBundleSchema,
32118
32208
  expiresAt: z.string().datetime(),
32119
- review: z.unknown().optional()
32209
+ review: bridgeDepositPrepareReviewSchema
32120
32210
  });
32121
32211
  /**
32122
32212
  * Zod schema for the `POST /v1/earnKit/bridge/deposit/prepare` API response.
@@ -32844,7 +32934,8 @@ function toPositionInfo(data) {
32844
32934
  return {
32845
32935
  execId: response.data.execId,
32846
32936
  preparedBundle,
32847
- expiresAt: response.data.expiresAt
32937
+ expiresAt: response.data.expiresAt,
32938
+ review: response.data.review
32848
32939
  };
32849
32940
  } catch (error) {
32850
32941
  throw parseEarnApiError(error, {
@@ -32955,47 +33046,34 @@ const VALID_AFTER_CLOCK_SKEW_SECONDS = 60n;
32955
33046
  * @throws If any typed-data field does not match the expected bridge deposit intent.
32956
33047
  * @internal
32957
33048
  */ function assertBridgeDepositTypedDataMatchesIntent(intent) {
32958
- const { preparedBundle, sourceChain, sourceAddress, sourceTokenAddress, authorizationRecipient, amount, maxFee, tokenDecimals, nowSeconds = BigInt(Math.floor(Date.now() / 1000)) } = intent;
33049
+ const { preparedBundle, sourceChain, sourceAddress, sourceTokenAddress, expectedDomain, authorizationRecipient, amount, maxFee, tokenDecimals, amountRaw, nowSeconds = BigInt(Math.floor(Date.now() / 1000)) } = intent;
32959
33050
  const { domain, message } = preparedBundle.erc3009TypedData;
32960
- // USDC deployments differ on the EIP-712 domain name per chain, so the
32961
- // expected domain is looked up for the source chain being signed on.
32962
- const expectedDomain = EARN_BRIDGE_ERC3009_DOMAINS[sourceChain.chain];
32963
- if (expectedDomain === undefined) {
32964
- throw createValidationFailedError$1('sourceChain.chain', sourceChain.chain, 'no expected ERC-3009 domain is configured for the source chain');
32965
- }
32966
- if (domain.name !== expectedDomain.name) {
32967
- throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.name', domain.name, `domain name must be ${expectedDomain.name}`);
32968
- }
32969
- if (domain.version !== expectedDomain.version) {
32970
- throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.version', domain.version, `domain version must be ${expectedDomain.version}`);
32971
- }
32972
33051
  assertSameAddress('preparedBundle.erc3009TypedData.domain.verifyingContract', domain.verifyingContract, sourceTokenAddress, 'verifyingContract must match the source token contract');
32973
33052
  assertSameBigInt('preparedBundle.erc3009TypedData.domain.chainId', domain.chainId, BigInt(sourceChain.chainId), 'chainId must match the source chain');
33053
+ assertSameString('preparedBundle.erc3009TypedData.domain.name', domain.name, expectedDomain.name, 'domain name must match the source token');
33054
+ assertSameString('preparedBundle.erc3009TypedData.domain.version', domain.version, expectedDomain.version, 'domain version must match the source token');
32974
33055
  assertSameAddress('preparedBundle.erc3009TypedData.message.from', message.from, sourceAddress, 'from must match the source wallet');
32975
33056
  assertSameAddress('preparedBundle.erc3009TypedData.message.to', message.to, authorizationRecipient, 'to must match the configured authorization recipient');
32976
- // The prepare path folds the source fee into the signed authorization:
32977
- // message.value = principal + sum(feeQuote.items[].amount), all denominated
32978
- // in the source token. The fee items only add up to a meaningful total when
32979
- // they are priced in the same token as the principal, so first pin the fee
32980
- // token to the source token contract before summing.
32981
- assertSameAddress('preparedBundle.feeQuote.feeToken', preparedBundle.feeQuote.feeToken, sourceTokenAddress, 'feeQuote.feeToken must match the source token contract');
32982
- // Recompute the expected value from locally trusted inputs (the caller
32983
- // principal) plus the bundle's own fee items so the guard accepts fee-bearing
32984
- // deposits while still rejecting any other value tampering. The fee quote is
32985
- // service-supplied, so bound it to the caller-accepted quote-derived cap
32986
- // before including it in the signed value.
32987
- const principal = Amount.parse(amount, {
33057
+ // Recompute the expected ERC-3009 value from locally trusted inputs. Bridge
33058
+ // source fees are paid in the source ERC-20 and are part of the authorization.
33059
+ assertDecimalPlaces('amount', amount, tokenDecimals, `amount must have at most ${String(tokenDecimals)} decimal places for the cross-chain Earn deposit source token`);
33060
+ const parsedAmount = Amount.parse(amount, {
32988
33061
  decimals: tokenDecimals
32989
33062
  }).raw;
33063
+ const principal = amountRaw === undefined ? parsedAmount : parseReviewedAmount(amountRaw, parsedAmount);
33064
+ const feeQuote = preparedBundle.feeQuote;
32990
33065
  const feeTotal = sumBridgeFeeItems(preparedBundle.feeQuote);
33066
+ assertFeeTokenMatchesSourceToken(feeQuote.feeToken, sourceTokenAddress);
33067
+ const feeDecimals = tokenDecimals;
33068
+ assertDecimalPlaces('maxFee', maxFee, feeDecimals, `maxFee must have at most ${String(feeDecimals)} decimal places for the cross-chain Earn deposit fee token`);
32991
33069
  const maxFeeRaw = Amount.parse(maxFee, {
32992
- decimals: tokenDecimals
33070
+ decimals: feeDecimals
32993
33071
  }).raw;
32994
33072
  if (feeTotal > maxFeeRaw) {
32995
33073
  throw createValidationFailedError$1('preparedBundle.feeQuote.items', feeTotal.toString(), 'fee total must not exceed maxFee in base units');
32996
33074
  }
32997
33075
  const expectedValue = principal + feeTotal;
32998
- assertSameBigInt('preparedBundle.erc3009TypedData.message.value', message.value, expectedValue, 'value must match the requested amount plus the bundle fee in base units');
33076
+ assertSameBigInt('preparedBundle.erc3009TypedData.message.value', message.value, expectedValue, 'value must match the requested amount plus source-token fees in base units');
32999
33077
  const window = checkErc3009ValidityWindow({
33000
33078
  validAfter: BigInt(message.validAfter),
33001
33079
  validBefore: BigInt(message.validBefore),
@@ -33069,14 +33147,19 @@ const VALID_AFTER_CLOCK_SKEW_SECONDS = 60n;
33069
33147
  /**
33070
33148
  * Sum the source-collected fee items in a prepared bundle's fee quote.
33071
33149
  *
33072
- * Each item amount is a base-unit value in the shared `feeToken`; the total is
33073
- * what the prepare path adds to the principal when building `message.value`.
33150
+ * Each item amount is a base-unit value in the shared `feeToken`.
33074
33151
  *
33075
33152
  * @param feeQuote - Fee quote parsed from the prepared bundle.
33076
- * @returns Total fee in source token base units (0n when there are no items).
33153
+ * @returns Total fee in fee-token base units (0n when there are no items).
33077
33154
  */ function sumBridgeFeeItems(feeQuote) {
33078
33155
  return feeQuote.items.reduce((total, item)=>total + BigInt(item.amount), 0n);
33079
33156
  }
33157
+ function assertFeeTokenMatchesSourceToken(feeToken, sourceTokenAddress) {
33158
+ if (isSameAddress(feeToken, sourceTokenAddress)) {
33159
+ return;
33160
+ }
33161
+ throw createValidationFailedError$1('preparedBundle.feeQuote.feeToken', feeToken, 'fee token must match the cross-chain Earn deposit source token');
33162
+ }
33080
33163
  function assertNever(value) {
33081
33164
  throw new KitError({
33082
33165
  ...EarnError.INTERNAL_ERROR,
@@ -33084,8 +33167,23 @@ function assertNever(value) {
33084
33167
  message: `Unexpected validity window reason: ${String(value)}`
33085
33168
  });
33086
33169
  }
33170
+ function parseReviewedAmount(amountRaw, parsedAmount) {
33171
+ let reviewedAmount;
33172
+ try {
33173
+ reviewedAmount = BigInt(amountRaw);
33174
+ } catch {
33175
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'amount must be a non-negative integer string');
33176
+ }
33177
+ if (reviewedAmount < 0n) {
33178
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'amount must be a non-negative integer string');
33179
+ }
33180
+ if (reviewedAmount !== parsedAmount) {
33181
+ throw createValidationFailedError$1('prepared.review.amount.raw', amountRaw, 'review amount must match the requested amount');
33182
+ }
33183
+ return reviewedAmount;
33184
+ }
33087
33185
  function assertSameAddress(field, actual, expected, reason) {
33088
- if (actual.toLowerCase() !== expected.toLowerCase()) {
33186
+ if (!isSameAddress(actual, expected)) {
33089
33187
  throw createValidationFailedError$1(field, actual, reason);
33090
33188
  }
33091
33189
  }
@@ -33094,6 +33192,11 @@ function assertSameBigInt(field, actual, expected, reason) {
33094
33192
  throw createValidationFailedError$1(field, actual, reason);
33095
33193
  }
33096
33194
  }
33195
+ function assertSameString(field, actual, expected, reason) {
33196
+ if (actual !== expected) {
33197
+ throw createValidationFailedError$1(field, actual, reason);
33198
+ }
33199
+ }
33097
33200
 
33098
33201
  /**
33099
33202
  * Build signed withdrawal instructions via the Earn Service API.
@@ -33451,6 +33554,9 @@ function toWithdrawalQuoteInfo(data) {
33451
33554
  'CONFIRMED',
33452
33555
  'COMPLETE'
33453
33556
  ]);
33557
+ // Intentionally built-ins-only: Earn bridge support is limited to SDK-known
33558
+ // token contracts plus the explicit ERC-3009 domain allowlist below.
33559
+ const TOKEN_REGISTRY = createTokenRegistry();
33454
33560
  /**
33455
33561
  * Build the typed error raised when a cross-chain wait is cancelled via its
33456
33562
  * `AbortSignal`. Mirrors `@core/adapter-base`'s `createAbortError` (same
@@ -33780,24 +33886,20 @@ function finishElapsedWait(lastStatus, lastError) {
33780
33886
  }
33781
33887
  const { address, chain: apiChain, chainDefinition: chain } = await resolveAdapterContext(params.from);
33782
33888
  const adapterContractAddress = requireAdapterContract(chain);
33783
- const rawUsdcAddress = chain.usdcAddress;
33784
- if (rawUsdcAddress === null) {
33785
- throw createUnsupportedTokenError('USDC', chain.name);
33786
- }
33787
- const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
33788
33889
  const { adapter } = params.from;
33890
+ const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
33789
33891
  const { executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
33790
- vaultAddress: params.vaultAddress,
33892
+ vaultAddress,
33791
33893
  amount: params.amount,
33792
33894
  address,
33793
33895
  chain: apiChain,
33794
33896
  config
33795
33897
  }), ()=>undefined);
33796
33898
  validateExecutionDeadline(executionParams);
33797
- const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
33798
- const approvalToken = tokenInputs[0]?.token;
33899
+ const approvalToken = resolveEarnApprovalToken(executionParams);
33900
+ const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
33799
33901
  const requiredAllowance = sumTokenInputAmounts(tokenInputs);
33800
- if (!options.skipApprove && approvalToken !== undefined) {
33902
+ if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
33801
33903
  await this.runPhase(ctx, 'approve', 'approve', async ()=>approveAllowanceIfNeeded({
33802
33904
  adapter,
33803
33905
  chain,
@@ -33805,7 +33907,7 @@ function finishElapsedWait(lastStatus, lastError) {
33805
33907
  delegate: adapterContractAddress,
33806
33908
  address,
33807
33909
  requiredAllowance,
33808
- revertMessage: 'USDC approval reverted on-chain'
33910
+ revertMessage: 'Earn deposit token approval reverted on-chain'
33809
33911
  }), (txHash)=>txHash);
33810
33912
  }
33811
33913
  const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>executeEarnAction({
@@ -33824,7 +33926,7 @@ function finishElapsedWait(lastStatus, lastError) {
33824
33926
  kind: 'same-chain',
33825
33927
  txHash,
33826
33928
  explorerUrl,
33827
- vaultAddress: params.vaultAddress,
33929
+ vaultAddress,
33828
33930
  amount: params.amount
33829
33931
  };
33830
33932
  } catch (error) {
@@ -33846,13 +33948,6 @@ function finishElapsedWait(lastStatus, lastError) {
33846
33948
  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.`);
33847
33949
  const destinationAddress = assertHexAddress('to.recipientAddress', params.to.recipientAddress, `Destination address for chain ${destinationChain.name} must be a 0x-prefixed 20-byte hex address.`);
33848
33950
  const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
33849
- // The signed ERC-3009 value is denominated in USDC's 6 decimals. More
33850
- // precise inputs would be silently truncated by Amount.parse, so the
33851
- // result would claim a larger deposit than was actually authorized.
33852
- const amountDecimals = params.amount.split('.')[1]?.length ?? 0;
33853
- if (amountDecimals > TOKEN_DECIMALS_6) {
33854
- throw createValidationFailedError$1('amount', params.amount, `amount must have at most ${String(TOKEN_DECIMALS_6)} decimal places for cross-chain Earn deposits`);
33855
- }
33856
33951
  const execId = params.idempotencyKey;
33857
33952
  const prepared = await this.runPhase(ctx, 'crossChainDeposit', 'prepare', async ()=>fetchBridgeDepositPrepare({
33858
33953
  execId,
@@ -33872,17 +33967,19 @@ function finishElapsedWait(lastStatus, lastError) {
33872
33967
  // the error trace, instead of ending the event stream unannounced
33873
33968
  // after the prepare success event.
33874
33969
  assertPreparedBundleMatchesTypedDataNonce(prepared.preparedBundle);
33970
+ const sourceToken = resolvePreparedBridgeSourceToken(prepared.preparedBundle, route.sourceChain);
33971
+ validatePreparedReviewAmountDecimals(prepared.review.amount.decimals, sourceToken.decimals);
33875
33972
  assertBridgeDepositTypedDataMatchesIntent({
33876
33973
  preparedBundle: prepared.preparedBundle,
33877
33974
  sourceChain: route.sourceChain,
33878
33975
  sourceAddress,
33879
- sourceTokenAddress: route.sourceTokenAddress,
33976
+ sourceTokenAddress: sourceToken.address,
33977
+ expectedDomain: sourceToken.erc3009Domain,
33880
33978
  authorizationRecipient: route.authorizationRecipient,
33881
33979
  amount: params.amount,
33882
33980
  maxFee: params.maxFee,
33883
- // Cross-chain Earn is USDC-only today. USDC uses 6 decimals on all
33884
- // configured source chains.
33885
- tokenDecimals: TOKEN_DECIMALS_6
33981
+ tokenDecimals: sourceToken.decimals,
33982
+ amountRaw: prepared.review.amount.raw
33886
33983
  });
33887
33984
  return signBridgeDepositAuthorization({
33888
33985
  adapter: bridgeSigningAdapter,
@@ -34105,21 +34202,17 @@ function finishElapsedWait(lastStatus, lastError) {
34105
34202
  const { adapter, chain, apiChain, address, vaultAddress, amount, config } = params;
34106
34203
  try {
34107
34204
  const adapterContractAddress = requireAdapterContract(chain);
34108
- const rawUsdcAddress = chain.usdcAddress;
34109
- if (rawUsdcAddress === null) {
34110
- throw createUnsupportedTokenError('USDC', chain.name);
34111
- }
34112
- const usdcAddress = assertHexAddress('chain.usdcAddress', rawUsdcAddress, `USDC address for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
34205
+ const normalizedVaultAddress = assertHexAddress('vaultAddress', vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
34113
34206
  const { executionParams, signature } = await fetchDeposit({
34114
- vaultAddress,
34207
+ vaultAddress: normalizedVaultAddress,
34115
34208
  amount,
34116
34209
  address,
34117
34210
  chain: apiChain,
34118
34211
  config
34119
34212
  });
34120
34213
  validateExecutionDeadline(executionParams);
34121
- const tokenInputs = buildEarnTokenInputs(executionParams, usdcAddress);
34122
- const approvalToken = tokenInputs[0]?.token;
34214
+ const approvalToken = resolveEarnApprovalToken(executionParams);
34215
+ const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
34123
34216
  const requiredAllowance = sumTokenInputAmounts(tokenInputs);
34124
34217
  return await estimateEarnQuoteGasFees({
34125
34218
  adapter,
@@ -34132,7 +34225,7 @@ function finishElapsedWait(lastStatus, lastError) {
34132
34225
  tokenInputs,
34133
34226
  signature
34134
34227
  },
34135
- approval: approvalToken !== undefined ? {
34228
+ approval: approvalToken !== undefined && requiredAllowance > 0n ? {
34136
34229
  token: approvalToken,
34137
34230
  delegate: adapterContractAddress,
34138
34231
  requiredAllowance
@@ -34376,11 +34469,6 @@ function validateCrossChainDepositRoute(sourceChain, destinationChain) {
34376
34469
  if (destinationApiChain === undefined) {
34377
34470
  throw createUnsupportedCrossChainDepositError(destinationChain, 'destination chain is not enabled for cross-chain Earn deposits');
34378
34471
  }
34379
- const sourceTokenAddressRaw = sourceChain.usdcAddress;
34380
- if (sourceTokenAddressRaw === null) {
34381
- throw createUnsupportedTokenError('USDC', sourceChain.name);
34382
- }
34383
- const sourceTokenAddress = assertHexAddress('sourceChain.usdcAddress', sourceTokenAddressRaw, `USDC address for chain ${sourceChain.name} must be a 0x-prefixed 20-byte hex address.`);
34384
34472
  if (sourceChain.cctp == null) {
34385
34473
  throw createBridgeRouteNotConfiguredError(sourceChain, 'source chain does not support CCTP');
34386
34474
  }
@@ -34404,10 +34492,50 @@ function validateCrossChainDepositRoute(sourceChain, destinationChain) {
34404
34492
  destinationApiChain,
34405
34493
  destinationDomain: destinationChain.cctp.domain,
34406
34494
  sourceChain,
34407
- sourceTokenAddress,
34408
34495
  authorizationRecipient
34409
34496
  };
34410
34497
  }
34498
+ function resolvePreparedBridgeSourceToken(preparedBundle, sourceChain) {
34499
+ 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.`);
34500
+ let sourceToken;
34501
+ try {
34502
+ sourceToken = TOKEN_REGISTRY.resolveByAddress(sourceTokenAddress, sourceChain.chain);
34503
+ } catch {
34504
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token ${sourceTokenAddress} is not configured on source chain ${sourceChain.name} for cross-chain Earn deposits`);
34505
+ }
34506
+ 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.`);
34507
+ if (!isSameAddress(sourceTokenAddress, registryAddress)) {
34508
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token must match the configured ${sourceToken.symbol ?? 'token'} address on source chain ${sourceChain.name}`);
34509
+ }
34510
+ const { symbol } = sourceToken;
34511
+ if (symbol === undefined) {
34512
+ throw createValidationFailedError$1('preparedBundle.erc3009TypedData.domain.verifyingContract', sourceTokenAddress, `source token ${sourceTokenAddress} is not a registered Earn bridge token on source chain ${sourceChain.name}`);
34513
+ }
34514
+ return {
34515
+ address: sourceTokenAddress,
34516
+ erc3009Domain: resolveBridgeErc3009Domain(symbol, sourceChain),
34517
+ decimals: sourceToken.decimals
34518
+ };
34519
+ }
34520
+ function validatePreparedReviewAmountDecimals(reviewedDecimals, tokenDecimals) {
34521
+ if (reviewedDecimals !== tokenDecimals) {
34522
+ throw createValidationFailedError$1('prepared.review.amount.decimals', reviewedDecimals, `review amount decimals must match the cross-chain Earn deposit source token decimals ${String(tokenDecimals)}`);
34523
+ }
34524
+ }
34525
+ function resolveBridgeErc3009Domain(tokenSymbol, sourceChain) {
34526
+ if (!isEarnBridgeErc3009TokenSymbol(tokenSymbol)) {
34527
+ throw createValidationFailedError$1('sourceToken.symbol', tokenSymbol, `ERC-3009 domain is not configured for ${tokenSymbol} on source chain ${sourceChain.name}`);
34528
+ }
34529
+ const domainsByChain = EARN_BRIDGE_ERC3009_DOMAINS[tokenSymbol];
34530
+ const domain = domainsByChain[sourceChain.chain];
34531
+ if (domain === undefined) {
34532
+ throw createValidationFailedError$1('sourceToken.symbol', tokenSymbol, `ERC-3009 domain is not configured for ${tokenSymbol} on source chain ${sourceChain.name}`);
34533
+ }
34534
+ return domain;
34535
+ }
34536
+ function isEarnBridgeErc3009TokenSymbol(tokenSymbol) {
34537
+ return EARN_BRIDGE_ERC3009_TOKEN_SYMBOLS.includes(tokenSymbol);
34538
+ }
34411
34539
  function createUnsupportedCrossChainDepositError(chain, reason) {
34412
34540
  return new KitError({
34413
34541
  ...EarnError.UNSUPPORTED_CHAIN,
@@ -34480,7 +34608,6 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
34480
34608
  // Untyped callers can pass anything here; without these guards a null
34481
34609
  // config crashes with a raw TypeError on `config.providers` instead of
34482
34610
  // the structured validation error every other public surface throws.
34483
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- JS consumers may pass null or a non-object config
34484
34611
  if (typeof config !== 'object' || config === null || Array.isArray(config)) {
34485
34612
  throw createValidationFailedError$1('config', config, 'config must be a plain object when provided');
34486
34613
  }
@@ -34847,10 +34974,9 @@ const sourceAdapterContextSchema = z.object({
34847
34974
  * Schema for validating human-readable decimal amount strings.
34848
34975
  *
34849
34976
  * Accept positive decimal strings like '100', '100.50', '0.001'. Reject
34850
- * zero, negative, non-numeric, and non-canonical strings. EarnKit
34851
- * same-chain amounts are USDC/EURC (6 decimals), so precision is capped at
34852
- * 6 to match the Earn Service and fail fast locally instead of leaking
34853
- * 7+ decimal-place inputs to the server.
34977
+ * zero, negative, non-numeric, and non-canonical strings. Same-chain
34978
+ * EarnKit vaults can use different asset precisions, so the service enforces
34979
+ * the vault-specific decimal cap after resolving the vault asset.
34854
34980
  *
34855
34981
  * @internal
34856
34982
  */ const amountSchema$1 = z.string({
@@ -34859,32 +34985,15 @@ const sourceAdapterContextSchema = z.object({
34859
34985
  allowZero: false,
34860
34986
  regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34861
34987
  attributeName: 'amount',
34862
- maxDecimals: 6
34863
- })(z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34864
- /**
34865
- * Schema for cross-chain deposit amount strings.
34866
- *
34867
- * Cross-chain Earn deposits are USDC-only, and the signed ERC-3009 value is
34868
- * denominated in USDC's 6 decimals. Reject more precise inputs up front so
34869
- * the signed value always equals the requested amount instead of a silent
34870
- * truncation. Non-canonical forms (leading dot, leading zeros) are rejected
34871
- * too, matching the Earn Service.
34872
- *
34873
- * @internal
34874
- */ const crossChainAmountSchema = z.string({
34875
- required_error: 'amount is required'
34876
- }).min(1, 'amount is required').pipe(createDecimalStringValidator({
34877
- allowZero: false,
34878
- regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34879
- attributeName: 'amount',
34880
- maxDecimals: 6
34988
+ maxDecimals: 18
34881
34989
  })(z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34882
34990
  /**
34883
34991
  * Schema for cross-chain source fee caps.
34884
34992
  *
34885
- * Cross-chain source fees are denominated in USDC base units. Zero is valid
34886
- * when the quote contains no source-collected fees; otherwise the cap should be
34887
- * the caller-accepted total from a recent quote.
34993
+ * Cross-chain source fees are denominated in the quote's fee token. Zero is
34994
+ * valid when the quote contains no source-collected fees; otherwise the cap
34995
+ * should be the caller-accepted total from a recent quote. The provider
34996
+ * enforces the prepared bundle's fee-token precision before signing.
34888
34997
  *
34889
34998
  * @internal
34890
34999
  */ const crossChainMaxFeeSchema = z.string({
@@ -34893,7 +35002,7 @@ const sourceAdapterContextSchema = z.object({
34893
35002
  allowZero: true,
34894
35003
  regexMessage: AMOUNT_FORMAT_ERROR_MESSAGE,
34895
35004
  attributeName: 'maxFee',
34896
- maxDecimals: 6
35005
+ maxDecimals: 18
34897
35006
  })(z.string())).refine((value)=>CANONICAL_AMOUNT_REGEX.test(value), AMOUNT_FORMAT_ERROR_MESSAGE);
34898
35007
  /**
34899
35008
  * Build a schema for an optional non-negative decimal filter string.
@@ -35054,7 +35163,7 @@ const crossChainDepositDestinationSchema = z.object({
35054
35163
  from: sourceAdapterContextSchema,
35055
35164
  to: crossChainDepositDestinationSchema,
35056
35165
  vaultAddress: vaultAddressSchema,
35057
- amount: crossChainAmountSchema,
35166
+ amount: amountSchema$1,
35058
35167
  maxFee: crossChainMaxFeeSchema,
35059
35168
  transferSpeed: z.enum([
35060
35169
  'FAST',
@@ -37794,7 +37903,7 @@ async function deposit$2(context, params) {
37794
37903
  }
37795
37904
 
37796
37905
  var name = "@circle-fin/unified-balance-kit";
37797
- var version = "1.2.0";
37906
+ var version = "1.2.1";
37798
37907
  var pkg = {
37799
37908
  name: name,
37800
37909
  version: version};