@circle-fin/app-kit 1.15.1 → 1.15.3

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
@@ -13607,7 +13607,7 @@ function resolveOptions(options) {
13607
13607
  }
13608
13608
 
13609
13609
  var name$4 = "@circle-fin/bridge-kit";
13610
- var version$5 = "1.15.0";
13610
+ var version$5 = "1.15.1";
13611
13611
  var pkg$5 = {
13612
13612
  name: name$4,
13613
13613
  version: version$5};
@@ -14799,7 +14799,7 @@ function createBridgeParamsSchema(tokenSchema, config) {
14799
14799
  /**
14800
14800
  * `POST` — returns a server-signed fee quote for a CCTPx transfer of the
14801
14801
  * given `tokenId` between the named source and destination domains.
14802
- */ const CCTPX_FEE_QUOTE_PATH = '/v1/quote/cctpx/{tokenId}/{sourceDomain}/{destinationDomain}';
14802
+ */ const CCTPX_FEE_QUOTE_PATH = '/v2/quote/cctpx/{tokenId}/{sourceDomain}/{destinationDomain}';
14803
14803
  /**
14804
14804
  * `GET` — returns the remaining fast-burn allowance for the given
14805
14805
  * `tokenIds` (a comma-separated query param of bytes32 token ids). Backs
@@ -16330,7 +16330,7 @@ function createNoopLogger() {
16330
16330
  })
16331
16331
  ]);
16332
16332
  /**
16333
- * Schema for `POST /v1/quote/cctpx/{tokenId}/{src}/{dst}`.
16333
+ * Schema for `POST /v2/quote/cctpx/{tokenId}/{src}/{dst}`.
16334
16334
  *
16335
16335
  * Use {@link parseOrThrow} from `@core/utils` to validate payloads
16336
16336
  * against this schema.
@@ -44521,7 +44521,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
44521
44521
  };
44522
44522
 
44523
44523
  var name$2 = "@circle-fin/earn-kit";
44524
- var version$2 = "1.7.0";
44524
+ var version$2 = "1.8.0";
44525
44525
  var pkg$2 = {
44526
44526
  name: name$2,
44527
44527
  version: version$2};
@@ -45060,6 +45060,142 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
45060
45060
  };
45061
45061
  }
45062
45062
 
45063
+ /**
45064
+ * Fallback gas reserve expressed in ERC-20 deposit token base units when no
45065
+ * quote gas fees are available. 50_000 = 0.05 USDC at the 6-decimal ERC-20
45066
+ * USDC scale used on Arc (and the scale of `balance` / `depositAmountRaw`).
45067
+ *
45068
+ * @internal
45069
+ */ const DEFAULT_GAS_RESERVE = 50_000n;
45070
+ /**
45071
+ * Standard ERC-20 decimal precision for USDC and EURC. Both tokens use 6
45072
+ * decimals on all EVM chains, regardless of how many decimals the native
45073
+ * gas-token representation uses.
45074
+ *
45075
+ * @internal
45076
+ */ const STABLECOIN_ERC20_DECIMALS = 6;
45077
+ /**
45078
+ * Compute the gas reserve in ERC-20 deposit token base units.
45079
+ *
45080
+ * `EstimatedGas.fee` is denominated in the chain's native currency base units
45081
+ * (e.g. 18-decimal USDC on Arc). The deposit token balance and
45082
+ * `depositAmountRaw` are in ERC-20 token base units (e.g. 6-decimal USDC).
45083
+ * This function converts the native fee total to the same scale so both sides
45084
+ * of the `balance < required` comparison are commensurable.
45085
+ *
45086
+ * Conversion: `fee_token_units = fee_native_units / 10^(nativeDecimals - tokenDecimals)`
45087
+ *
45088
+ * Falls back to {@link DEFAULT_GAS_RESERVE} when:
45089
+ * - `gasFees` is absent or empty
45090
+ * - all fee entries have `fees: null` (estimation failed)
45091
+ * - the converted total rounds to zero (native fee smaller than 1 token unit)
45092
+ *
45093
+ * @internal
45094
+ */ function computeGasReserve(gasFees, nativeDecimals, tokenDecimals) {
45095
+ if (gasFees === undefined || gasFees.length === 0) {
45096
+ return DEFAULT_GAS_RESERVE;
45097
+ }
45098
+ let totalNative = 0n;
45099
+ let hasNullEntry = false;
45100
+ for (const entry of gasFees){
45101
+ if (entry.fees === null) {
45102
+ hasNullEntry = true;
45103
+ } else {
45104
+ totalNative += BigInt(entry.fees.fee);
45105
+ }
45106
+ }
45107
+ const decimalDiff = nativeDecimals - tokenDecimals;
45108
+ // Use ceiling division when reducing precision so we never under-estimate.
45109
+ const scale = decimalDiff > 0 ? 10n ** BigInt(decimalDiff) : 1n;
45110
+ let knownToken;
45111
+ if (totalNative === 0n) {
45112
+ knownToken = 0n;
45113
+ } else if (decimalDiff > 0) {
45114
+ knownToken = (totalNative + scale - 1n) / scale;
45115
+ } else {
45116
+ knownToken = totalNative * 10n ** BigInt(-decimalDiff);
45117
+ }
45118
+ // When any estimate is missing the true total is unknown, so reserve at
45119
+ // least DEFAULT_GAS_RESERVE on top of whatever was already known.
45120
+ // BigInt is not supported by Math.max, so we use an explicit comparison.
45121
+ if (hasNullEntry) {
45122
+ return knownToken > DEFAULT_GAS_RESERVE ? knownToken : DEFAULT_GAS_RESERVE // NOSONAR
45123
+ ;
45124
+ }
45125
+ return knownToken > 0n ? knownToken : DEFAULT_GAS_RESERVE;
45126
+ }
45127
+ /**
45128
+ * Build a symbol → ERC-20 address map from the chain's known token addresses.
45129
+ * Used to resolve whether the native gas token corresponds to the deposit
45130
+ * token's address.
45131
+ *
45132
+ * @internal
45133
+ */ function buildSymbolAddressMap(chain) {
45134
+ const map = new Map();
45135
+ if (chain.usdcAddress != null) {
45136
+ map.set('USDC', chain.usdcAddress.toLowerCase());
45137
+ }
45138
+ if (chain.eurcAddress != null) {
45139
+ map.set('EURC', chain.eurcAddress.toLowerCase());
45140
+ }
45141
+ return map;
45142
+ }
45143
+ /**
45144
+ * Pre-broadcast balance check for EOA wallets on chains where the native gas
45145
+ * token and the deposit token are the same asset (e.g. Arc: USDC gas + USDC
45146
+ * deposit).
45147
+ *
45148
+ * When the deposit would consume the entire balance, the adapter locks gas
45149
+ * before `transferFrom`, causing an `ERC20: transfer amount exceeds balance`
45150
+ * revert on-chain. This function reads the wallet balance before broadcast and
45151
+ * throws {@link BalanceError.INSUFFICIENT_TOKEN} (code 9001) when
45152
+ * `balance < depositAmount + gasReserve`, giving callers a clean pre-broadcast
45153
+ * error instead of an opaque chain revert.
45154
+ *
45155
+ * The check is skipped when the chain's native gas currency symbol does not
45156
+ * match the deposit token symbol (derived by address lookup). Examples of
45157
+ * chains where the check is skipped: EURC deposit on Arc, any ETH-gas chain.
45158
+ *
45159
+ * Gas fees from the quote (`gasFees`) are in native-currency base units
45160
+ * (18-decimal on Arc) and are scaled down to ERC-20 token units (6-decimal)
45161
+ * before being added to `depositAmountRaw`.
45162
+ *
45163
+ * @param params - Adapter context, chain, deposit token, amount, and optional
45164
+ * quote gas fees.
45165
+ * @throws {@link KitError} (code 9001) when the balance is insufficient to
45166
+ * cover both the deposit and the gas reserve.
45167
+ *
45168
+ * @internal
45169
+ */ async function checkGasReserveForDeposit(params) {
45170
+ const { adapter, chain, address, depositTokenAddress, depositAmountRaw, gasFees } = params;
45171
+ const nativeSymbol = chain.nativeCurrency.symbol.toUpperCase();
45172
+ const symbolAddressMap = buildSymbolAddressMap(chain);
45173
+ const nativeTokenAddress = symbolAddressMap.get(nativeSymbol);
45174
+ // Only applies when native gas currency and deposit token are the same asset.
45175
+ if (nativeTokenAddress === undefined || depositTokenAddress.toLowerCase() !== nativeTokenAddress) {
45176
+ return;
45177
+ }
45178
+ const rawBalance = await executeAdapterReadAction(adapter, 'token.balanceOf', {
45179
+ tokenAddress: depositTokenAddress,
45180
+ walletAddress: address
45181
+ }, {
45182
+ chain,
45183
+ address
45184
+ });
45185
+ const balance = BigInt(String(rawBalance));
45186
+ // Both USDC and EURC use 6 ERC-20 decimals; the check only reaches here when
45187
+ // the deposit token is the chain's native gas asset (USDC or EURC).
45188
+ const gasReserve = computeGasReserve(gasFees, chain.nativeCurrency.decimals, STABLECOIN_ERC20_DECIMALS);
45189
+ const required = depositAmountRaw + gasReserve;
45190
+ if (balance < required) {
45191
+ throw new KitError({
45192
+ ...BalanceError.INSUFFICIENT_TOKEN,
45193
+ recoverability: 'FATAL',
45194
+ message: `Insufficient ${chain.nativeCurrency.symbol} balance to cover deposit and gas. Balance: ${balance.toString()}, required: ${required.toString()} (deposit: ${depositAmountRaw.toString()} + gas reserve: ${gasReserve.toString()})`
45195
+ });
45196
+ }
45197
+ }
45198
+
45063
45199
  /** @internal */ function isSameAddress(actual, expected) {
45064
45200
  return actual.toLowerCase() === expected.toLowerCase();
45065
45201
  }
@@ -46449,12 +46585,19 @@ const EARN_OPERATIONS = new Set([
46449
46585
  writable: true,
46450
46586
  configurable: true
46451
46587
  });
46588
+ // Surface the tx hash in the message so callers can look it up without
46589
+ // digging into cause.trace. Only applies to on-chain reverts — other errors
46590
+ // either have no tx hash or already embed it in their message.
46591
+ let message = base.message;
46592
+ if (base.name === 'ONCHAIN_TRANSACTION_REVERTED' && typeof existingTrace['txHash'] === 'string' && existingTrace['txHash'] !== '') {
46593
+ message = `${message} (txHash: ${existingTrace['txHash']})`;
46594
+ }
46452
46595
  return new KitError({
46453
46596
  code: base.code,
46454
46597
  name: base.name,
46455
46598
  type: base.type,
46456
46599
  recoverability,
46457
- message: base.message,
46600
+ message,
46458
46601
  cause: {
46459
46602
  trace
46460
46603
  }
@@ -46570,10 +46713,8 @@ const bridgeFeeTokenSchema = hexAddressSchema;
46570
46713
  assetAddress: zod.z.string(),
46571
46714
  lltv: zod.z.number(),
46572
46715
  supplyUsd: zod.z.number(),
46573
- // Optional during the expand/contract window (a backend that predates the
46574
- // field omits the key), mirroring the `.optional()` facets on the base
46575
- // schema; `null` when the product exposes no per-market allocation (V2).
46576
- allocationPct: zod.z.number().nullable().optional()
46716
+ // `null` when the product exposes no per-market allocation (V2).
46717
+ allocationPct: zod.z.number().nullable()
46577
46718
  });
46578
46719
  /**
46579
46720
  * Zod schema for a Morpho vault warning in the API response.
@@ -46680,19 +46821,20 @@ const bridgeFeeTokenSchema = hexAddressSchema;
46680
46821
  earnKitWarnings: zod.z.array(zod.z.string()).optional()
46681
46822
  });
46682
46823
  /**
46683
- * Shared base schema: existing flat fields (kept) plus the new nested
46684
- * facets and neutral identity. Facets are `.optional()` during the
46685
- * transition; flip to required once the backend is confirmed emitting.
46824
+ * Shared base schema: existing flat fields (kept as deprecated aliases) plus
46825
+ * the nested facets. Zenith emits all facets on every response; required here
46826
+ * so a partial emission is caught at parse time rather than surfacing as a
46827
+ * runtime `undefined` access.
46686
46828
  *
46687
46829
  * @internal
46688
46830
  */ const earnBaseSchema = vaultInfoResponseSchema.extend({
46689
- address: zod.z.string().optional(),
46690
- asOf: zod.z.string().optional(),
46691
- manager: managerSchema.nullable().optional(),
46692
- apyProfile: apyProfileSchema.optional(),
46693
- fee: feeInfoSchema.optional(),
46694
- liquidityProfile: liquidityProfileSchema.optional(),
46695
- riskSignals: riskSignalsSchema.optional()
46831
+ address: zod.z.string(),
46832
+ asOf: zod.z.string(),
46833
+ manager: managerSchema.nullable(),
46834
+ apyProfile: apyProfileSchema,
46835
+ fee: feeInfoSchema,
46836
+ liquidityProfile: liquidityProfileSchema,
46837
+ riskSignals: riskSignalsSchema
46696
46838
  });
46697
46839
  /**
46698
46840
  * Zod schema for the `vault` opportunity variant.
@@ -47480,7 +47622,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
47480
47622
  }
47481
47623
 
47482
47624
  var name$1 = "@circle-fin/provider-earn-service";
47483
- var version$1 = "1.6.0";
47625
+ var version$1 = "1.7.0";
47484
47626
  var pkg$1 = {
47485
47627
  name: name$1,
47486
47628
  version: version$1};
@@ -47628,23 +47770,16 @@ var pkg$1 = {
47628
47770
  if (chain === undefined) {
47629
47771
  throw createInvalidChainError(vault.chain, 'Chain returned by the Earn Service is not supported by the SDK');
47630
47772
  }
47631
- // The nested facets are `.optional()` in the schema (a backend that predates
47632
- // them omits them) and are typed optional on `EarnOpportunity` to match.
47633
- // Convert the nested liquidity amounts when present and pass the remaining
47634
- // facets straight through; each absent facet stays absent rather than being
47635
- // asserted present by a cast.
47636
47773
  return {
47637
47774
  ...vault,
47638
47775
  chain,
47639
47776
  totalDeposits: Amount.fromJSON(totalDeposits),
47640
47777
  liquidity: Amount.fromJSON(liquidity),
47641
- ...liquidityProfile !== undefined && {
47642
- liquidityProfile: {
47643
- ...liquidityProfile,
47644
- totalDeposits: Amount.fromJSON(liquidityProfile.totalDeposits),
47645
- available: Amount.fromJSON(liquidityProfile.available),
47646
- totalSupply: Amount.fromJSON(liquidityProfile.totalSupply)
47647
- }
47778
+ liquidityProfile: {
47779
+ ...liquidityProfile,
47780
+ totalDeposits: Amount.fromJSON(liquidityProfile.totalDeposits),
47781
+ available: Amount.fromJSON(liquidityProfile.available),
47782
+ totalSupply: Amount.fromJSON(liquidityProfile.totalSupply)
47648
47783
  }
47649
47784
  };
47650
47785
  }
@@ -49160,6 +49295,24 @@ function finishElapsedWait(lastStatus, lastError) {
49160
49295
  amount: params.amount
49161
49296
  };
49162
49297
  }
49298
+ // Only EOA wallets pay native gas from their own balance — SCAs have
49299
+ // their gas sponsored externally. Detect EOA by checking that the
49300
+ // adapter supports readBytecode and the wallet address has no on-chain
49301
+ // bytecode (empty code ⟹ EOA; non-empty ⟹ smart-contract account).
49302
+ // EIP-7702-delegated addresses are treated as EOA by isContractSigner.
49303
+ // If the adapter does not expose readBytecode (non-EVM adapter) the
49304
+ // check is skipped conservatively.
49305
+ if (approvalToken !== undefined && requiredAllowance > 0n && chain.type === 'evm' && isEvmAdapterLike(adapter) && !await isContractSigner(adapter, address, chain)) {
49306
+ const sameChainParams = params;
49307
+ await checkGasReserveForDeposit({
49308
+ adapter,
49309
+ chain,
49310
+ address,
49311
+ depositTokenAddress: approvalToken,
49312
+ depositAmountRaw: requiredAllowance,
49313
+ gasFees: sameChainParams.gasFees
49314
+ });
49315
+ }
49163
49316
  if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
49164
49317
  await this.runPhase(ctx, 'approve', 'approve', async ()=>{
49165
49318
  try {
@@ -49566,7 +49719,7 @@ function finishElapsedWait(lastStatus, lastError) {
49566
49719
  // Gas is estimated server-side by the Earn Service and returned on the quote, so the
49567
49720
  // SDK no longer simulates it locally. `chainDefinition` lets the fetch fill
49568
49721
  // the native token symbol / blockchain on each gas entry.
49569
- return fetchDepositQuote({
49722
+ const quote = await fetchDepositQuote({
49570
49723
  vaultAddress: params.vaultAddress,
49571
49724
  amount: params.amount,
49572
49725
  address,
@@ -49574,6 +49727,34 @@ function finishElapsedWait(lastStatus, lastError) {
49574
49727
  config,
49575
49728
  chainDefinition
49576
49729
  });
49730
+ // Run the same pre-broadcast gas reserve check here so callers get an early
49731
+ // warning at quote time — before they call deposit() and hit the real check.
49732
+ // Only applies on stablecoin-gas chains (e.g. Arc) where the deposit token
49733
+ // is also the native gas token, and only for EOA wallets that pay native gas
49734
+ // from their own balance. Skips silently on all other chains and wallet types.
49735
+ if (chainDefinition?.type === 'evm' && isEvmAdapterLike(params.from.adapter) && !await isContractSigner(params.from.adapter, address, chainDefinition)) {
49736
+ const symbol = quote.deposit.symbol.toUpperCase();
49737
+ let depositTokenAddress = null;
49738
+ if (symbol === 'USDC') {
49739
+ depositTokenAddress = chainDefinition.usdcAddress;
49740
+ } else if (symbol === 'EURC') {
49741
+ depositTokenAddress = chainDefinition.eurcAddress;
49742
+ }
49743
+ if (depositTokenAddress != null) {
49744
+ const amountObj = Amount.parse(params.amount, {
49745
+ decimals: 6
49746
+ });
49747
+ await checkGasReserveForDeposit({
49748
+ adapter: params.from.adapter,
49749
+ chain: chainDefinition,
49750
+ address,
49751
+ depositTokenAddress,
49752
+ depositAmountRaw: amountObj.raw,
49753
+ gasFees: quote.gasFees
49754
+ });
49755
+ }
49756
+ }
49757
+ return quote;
49577
49758
  }
49578
49759
  /** {@inheritdoc} */ async getWithdrawalQuote(params) {
49579
49760
  const config = this.resolveConfig(params.config);
@@ -49873,21 +50054,15 @@ function formatPositionPnL(pnl) {
49873
50054
  // consumers keep receiving them until Contract.
49874
50055
  // eslint-disable-next-line @typescript-eslint/no-deprecated
49875
50056
  const { totalDeposits, liquidity, liquidityProfile, ...rest } = vault;
49876
- // `liquidityProfile` is `.optional()` in the response schema during the
49877
- // expand/contract window (an old backend that predates the nested facets
49878
- // omits it), so only format and re-attach it when present — matching the
49879
- // provider-side `toVaultInfo` mapper.
49880
50057
  return {
49881
50058
  ...rest,
49882
50059
  totalDeposits: formatAmount$1(totalDeposits),
49883
50060
  liquidity: formatAmount$1(liquidity),
49884
- ...liquidityProfile !== undefined && {
49885
- liquidityProfile: {
49886
- ...liquidityProfile,
49887
- totalDeposits: formatAmount$1(liquidityProfile.totalDeposits),
49888
- available: formatAmount$1(liquidityProfile.available),
49889
- totalSupply: formatAmount$1(liquidityProfile.totalSupply)
49890
- }
50061
+ liquidityProfile: {
50062
+ ...liquidityProfile,
50063
+ totalDeposits: formatAmount$1(liquidityProfile.totalDeposits),
50064
+ available: formatAmount$1(liquidityProfile.available),
50065
+ totalSupply: formatAmount$1(liquidityProfile.totalSupply)
49891
50066
  }
49892
50067
  };
49893
50068
  }
@@ -50373,6 +50548,49 @@ const sourceAdapterContextSchema = zod.z.object({
50373
50548
  vaultAddress: vaultAddressSchema,
50374
50549
  config: earnConfigSchema.optional()
50375
50550
  });
50551
+ /**
50552
+ * Schema for a successful gas fee entry (fees is non-null).
50553
+ *
50554
+ * Validates the kit shape — {@link EarnEstimatedGas} uses decimal strings for
50555
+ * `gas`, `gasPrice`, and `fee`. Only digits are accepted so that the deposit
50556
+ * operation can safely convert them to `bigint` before passing them to the
50557
+ * provider's `computeGasReserve`.
50558
+ *
50559
+ * @internal
50560
+ */ const earnGasFeeEstimateSuccessSchema = zod.z.object({
50561
+ name: zod.z.string(),
50562
+ token: zod.z.string(),
50563
+ blockchain: zod.z.string(),
50564
+ fees: zod.z.object({
50565
+ gas: zod.z.string().regex(/^\d+$/, 'gas must be a non-negative integer string'),
50566
+ gasPrice: zod.z.string().regex(/^\d+$/, 'gasPrice must be a non-negative integer string'),
50567
+ fee: zod.z.string().regex(/^\d+$/, 'fee must be a non-negative integer string')
50568
+ }),
50569
+ error: zod.z.undefined().optional()
50570
+ }).strict();
50571
+ /**
50572
+ * Schema for a failed gas fee entry (fees is null, error is a string).
50573
+ *
50574
+ * @internal
50575
+ */ const earnGasFeeEstimateFailureSchema = zod.z.object({
50576
+ name: zod.z.string(),
50577
+ token: zod.z.string(),
50578
+ blockchain: zod.z.string(),
50579
+ fees: zod.z.null(),
50580
+ error: zod.z.string()
50581
+ }).strict();
50582
+ /**
50583
+ * Schema for a single kit-level gas fee estimate entry.
50584
+ *
50585
+ * Rejects arbitrary inputs (null, numbers, empty objects, non-numeric fee
50586
+ * strings) so callers receive a descriptive validation error instead of a
50587
+ * raw `BigInt` conversion error inside `computeGasReserve`.
50588
+ *
50589
+ * @internal
50590
+ */ const earnGasFeeEstimateSchema = zod.z.union([
50591
+ earnGasFeeEstimateSuccessSchema,
50592
+ earnGasFeeEstimateFailureSchema
50593
+ ]);
50376
50594
  /**
50377
50595
  * Validation schema for the same-chain variant of DepositParams.
50378
50596
  *
@@ -50385,6 +50603,7 @@ const sourceAdapterContextSchema = zod.z.object({
50385
50603
  to: zod.z.undefined().optional(),
50386
50604
  vaultAddress: vaultAddressSchema,
50387
50605
  amount: amountSchema$1,
50606
+ gasFees: zod.z.array(earnGasFeeEstimateSchema).optional(),
50388
50607
  config: earnConfigSchema.optional()
50389
50608
  }).strict();
50390
50609
  const crossChainDepositDestinationSchema = zod.z.object({
@@ -51005,6 +51224,9 @@ async function deposit$3(context, params) {
51005
51224
  },
51006
51225
  vaultAddress: params.vaultAddress,
51007
51226
  amount: params.amount,
51227
+ ...!hasExplicitDestination && params.gasFees !== undefined && {
51228
+ gasFees: toProviderGasFees(params.gasFees)
51229
+ },
51008
51230
  ...params.config !== undefined && {
51009
51231
  config: params.config
51010
51232
  }
@@ -51027,6 +51249,21 @@ async function deposit$3(context, params) {
51027
51249
  }
51028
51250
  return provider.deposit(baseParams);
51029
51251
  }
51252
+ function toProviderGasFees(gasFees) {
51253
+ return gasFees.map((entry)=>{
51254
+ if (entry.fees === null) {
51255
+ return entry; // NOSONAR
51256
+ }
51257
+ return {
51258
+ ...entry,
51259
+ fees: {
51260
+ ...entry.fees,
51261
+ gas: BigInt(entry.fees.gas),
51262
+ gasPrice: BigInt(entry.fees.gasPrice)
51263
+ }
51264
+ };
51265
+ });
51266
+ }
51030
51267
  function hasCrossChainDestination(params) {
51031
51268
  // Runs before schema validation, so guard against non-object params from
51032
51269
  // untyped callers; assertEarnParams reports those with a structured error.
@@ -52502,10 +52739,10 @@ function readUserAgent(hostWindow) {
52502
52739
  * @param widgetPath - Path appended to `widgetBaseUrl`. Leading `/`
52503
52740
  * is required by the URL semantics below — see remarks.
52504
52741
  * @param params - Additional query parameters to set on the launch URL
52505
- * (e.g. `destinationWallet`, `hostApp`). Entries whose value is
52506
- * `undefined` or empty are skipped. The widget reads these from the
52507
- * query string — `destinationWallet` in particular is **required** by
52508
- * the widget, so callers that have it must pass it here.
52742
+ * (e.g. the asset-scoping `chains` / `tokens` / `pairs`). Entries whose
52743
+ * value is `undefined` or empty are skipped. Launch URLs end up in
52744
+ * browser history and access logs, so never pass user identifiers
52745
+ * such as wallet addresses here.
52509
52746
  * @returns A fully-qualified launch URL.
52510
52747
  *
52511
52748
  * @remarks
@@ -52529,9 +52766,9 @@ function readUserAgent(hostWindow) {
52529
52766
  * @example
52530
52767
  * ```ts
52531
52768
  * composeWidgetUrl('https://onramp.arc.io', 'tok-1', '', {
52532
- * destinationWallet: '0xabc',
52769
+ * tokens: 'USDC',
52533
52770
  * })
52534
- * // → 'https://onramp.arc.io/?sessionToken=tok-1&destinationWallet=0xabc'
52771
+ * // → 'https://onramp.arc.io/?sessionToken=tok-1&tokens=USDC'
52535
52772
  * ```
52536
52773
  *
52537
52774
  * @internal Implementation detail shared by the server kit
@@ -52540,13 +52777,6 @@ function readUserAgent(hostWindow) {
52540
52777
  */ function composeWidgetUrl(widgetBaseUrl, sessionToken, widgetPath, params) {
52541
52778
  const url = new URL(widgetPath, widgetBaseUrl);
52542
52779
  url.searchParams.set('sessionToken', sessionToken);
52543
- if (params !== undefined) {
52544
- for (const [key, value] of Object.entries(params)){
52545
- if (typeof value === 'string' && value.length > 0) {
52546
- url.searchParams.set(key, value);
52547
- }
52548
- }
52549
- }
52550
52780
  return url.toString();
52551
52781
  }
52552
52782
 
@@ -52756,12 +52986,10 @@ function readUserAgent(hostWindow) {
52756
52986
  assertSafeWidgetUrl(session.widgetUrl, expectedOrigin);
52757
52987
  return session.widgetUrl;
52758
52988
  }
52759
- // Fallback path: compose from a bare session token. The widget reads
52760
- // the destination wallet from the URL, so forward it when the session
52761
- // carries one (server-minted sessions always do).
52762
- return composeWidgetUrl(widgetBaseUrl, session.sessionToken, ONRAMP_WIDGET_PATH, {
52763
- destinationWallet: typeof session.destinationWallet === 'string' ? session.destinationWallet : undefined
52764
- });
52989
+ // Fallback path: compose from a bare session token. The widget reads the
52990
+ // destination wallet from the token's `walletAddress` claim, so
52991
+ // `session.destinationWallet` must not be added to the URL.
52992
+ return composeWidgetUrl(widgetBaseUrl, session.sessionToken, ONRAMP_WIDGET_PATH);
52765
52993
  }
52766
52994
 
52767
52995
  const ALLOWED_TRANSITIONS = {