@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/earn.mjs CHANGED
@@ -698,6 +698,29 @@ class KitError extends Error {
698
698
  name: 'INPUT_VALIDATION_FAILED',
699
699
  type: 'INPUT'
700
700
  }};
701
+ /**
702
+ * Standardized error definitions for BALANCE type errors.
703
+ *
704
+ * BALANCE errors indicate insufficient funds or allowance issues
705
+ * that prevent transaction execution.
706
+ *
707
+ * @example
708
+ * ```typescript
709
+ * import { BalanceError } from '@core/errors'
710
+ *
711
+ * const error = new KitError({
712
+ * ...BalanceError.INSUFFICIENT_TOKEN,
713
+ * recoverability: 'FATAL',
714
+ * message: 'Insufficient USDC balance on Ethereum',
715
+ * cause: { trace: { required: '100', available: '50' } }
716
+ * })
717
+ * ```
718
+ */ const BalanceError = {
719
+ /** Insufficient token balance for transaction */ INSUFFICIENT_TOKEN: {
720
+ code: 9001,
721
+ name: 'BALANCE_INSUFFICIENT_TOKEN',
722
+ type: 'BALANCE'
723
+ }};
701
724
  /**
702
725
  * Standardized error definitions for ONCHAIN type errors.
703
726
  *
@@ -9129,7 +9152,7 @@ const swapTokenEnumSchema = z.enum([
9129
9152
  }
9130
9153
 
9131
9154
  var name$3 = "@circle-fin/bridge-kit";
9132
- var version$3 = "1.15.0";
9155
+ var version$3 = "1.15.1";
9133
9156
  var pkg$3 = {
9134
9157
  name: name$3,
9135
9158
  version: version$3};
@@ -9217,7 +9240,7 @@ const READ_ACTION_KEY_SET = new Set(READ_ACTION_KEYS);
9217
9240
  *
9218
9241
  * @internal
9219
9242
  */ function isReadActionKey(action) {
9220
- return READ_ACTION_KEY_SET.has(action);
9243
+ return typeof action === 'string' && READ_ACTION_KEY_SET.has(action);
9221
9244
  }
9222
9245
 
9223
9246
  /**
@@ -12541,6 +12564,86 @@ const abiParameterSchema = z.object({
12541
12564
  };
12542
12565
  }
12543
12566
 
12567
+ /**
12568
+ * EIP-7702 delegation indicator. A 7702-delegated EOA's bytecode is
12569
+ * `0xef0100` followed by the 20-byte delegate address (23 bytes total).
12570
+ * The underlying secp256k1 key still produces `ecrecover`-verifiable
12571
+ * signatures, so for Gateway's purposes a 7702-delegated address is
12572
+ * an EOA, not a contract signer.
12573
+ *
12574
+ * Spec: https://eips.ethereum.org/EIPS/eip-7702
12575
+ */ const EIP_7702_DELEGATION_PREFIX = '0xef0100';
12576
+ /**
12577
+ * Determine whether `address` on `chain` signs as a contract (ERC-1271)
12578
+ * rather than as an EOA.
12579
+ *
12580
+ * Gateway validates burn-intent signatures two ways: a static `ecrecover`
12581
+ * check for EOAs, and — for requests that carry `contractSigner: true` —
12582
+ * an offchain `isValidSignature` simulation against the signing contract
12583
+ * (ERC-1271). Gateway does not infer which one to use, so the caller must
12584
+ * declare it. This detects the contract case from on-chain bytecode.
12585
+ *
12586
+ * EIP-7702-delegated EOAs are treated as EOAs: they expose non-empty
12587
+ * bytecode (`0xef0100<delegate>`) but the underlying secp256k1 key still
12588
+ * produces `ecrecover`-verifiable signatures, so the cheaper EOA path
12589
+ * stays correct for them.
12590
+ *
12591
+ * If bytecode cannot be read (RPC failure, etc.) the address is reported
12592
+ * as an EOA and a warning is logged so the fallback is diagnosable. A
12593
+ * genuine contract signer misreported this way is rejected by Gateway with
12594
+ * an invalid-signature error rather than silently mis-attested.
12595
+ *
12596
+ * @param adapter - Anything exposing {@link EvmAdapterLike.readBytecode}.
12597
+ * @param address - Signer address to classify.
12598
+ * @param chain - EVM chain where the signer lives.
12599
+ * @returns `true` when the signer is a contract account and the transfer
12600
+ * request must set `contractSigner: true`; `false` otherwise.
12601
+ *
12602
+ * @example
12603
+ * ```typescript
12604
+ * import { isContractSigner } from '@core/adapter-evm'
12605
+ * import { Ethereum } from '@core/chains'
12606
+ *
12607
+ * const useErc1271 = await isContractSigner(adapter, '0xabc...', Ethereum)
12608
+ * ```
12609
+ */ async function isContractSigner(adapter, address, chain) {
12610
+ let code;
12611
+ try {
12612
+ code = await adapter.readBytecode(address, chain);
12613
+ } catch (err) {
12614
+ console.warn(`[gateway] isContractSigner defaulting to EOA (readBytecode failed ` + `for ${address} on ${chain.name}): ` + (err instanceof Error ? err.message : String(err)));
12615
+ return false;
12616
+ }
12617
+ if (code === undefined || code === '0x' || code.toLowerCase().startsWith(EIP_7702_DELEGATION_PREFIX)) {
12618
+ return false;
12619
+ }
12620
+ return true;
12621
+ }
12622
+
12623
+ /**
12624
+ * Duck-type test for an EVM adapter that exposes `readBytecode`.
12625
+ *
12626
+ * Used as a structural guard at package boundaries (e.g. the Circle Wallets
12627
+ * hybrid adapter calling into a chain adapter, or the Gateway sign path
12628
+ * receiving an arbitrary adapter implementation) where `instanceof EvmAdapter`
12629
+ * is unreliable because each consumer bundles its own copy of the base class.
12630
+ *
12631
+ * @param value - The value to test.
12632
+ * @returns `true` when `value` is an object exposing a callable
12633
+ * `readBytecode` method, narrowed to {@link EvmAdapterLike}.
12634
+ *
12635
+ * @example
12636
+ * ```typescript
12637
+ * import { isEvmAdapterLike } from '@core/adapter-evm'
12638
+ *
12639
+ * if (isEvmAdapterLike(adapter)) {
12640
+ * const code = await adapter.readBytecode('0xabc...', chain)
12641
+ * }
12642
+ * ```
12643
+ */ function isEvmAdapterLike(value) {
12644
+ return typeof value === 'object' && value !== null && 'readBytecode' in value && typeof value.readBytecode === 'function';
12645
+ }
12646
+
12544
12647
  const hexBytesSchema$1 = z.string().regex(/^0x([0-9a-fA-F]{2})*$/, {
12545
12648
  message: 'value must be a 0x-prefixed even-length hex string'
12546
12649
  }).transform((value)=>value);
@@ -14435,7 +14538,7 @@ new Set(Object.values(Blockchain));
14435
14538
  registerKit(`${pkg$2.name}/${pkg$2.version}`);
14436
14539
 
14437
14540
  var name$1 = "@circle-fin/earn-kit";
14438
- var version$1 = "1.7.0";
14541
+ var version$1 = "1.8.0";
14439
14542
  var pkg$1 = {
14440
14543
  name: name$1,
14441
14544
  version: version$1};
@@ -14974,6 +15077,142 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
14974
15077
  };
14975
15078
  }
14976
15079
 
15080
+ /**
15081
+ * Fallback gas reserve expressed in ERC-20 deposit token base units when no
15082
+ * quote gas fees are available. 50_000 = 0.05 USDC at the 6-decimal ERC-20
15083
+ * USDC scale used on Arc (and the scale of `balance` / `depositAmountRaw`).
15084
+ *
15085
+ * @internal
15086
+ */ const DEFAULT_GAS_RESERVE = 50_000n;
15087
+ /**
15088
+ * Standard ERC-20 decimal precision for USDC and EURC. Both tokens use 6
15089
+ * decimals on all EVM chains, regardless of how many decimals the native
15090
+ * gas-token representation uses.
15091
+ *
15092
+ * @internal
15093
+ */ const STABLECOIN_ERC20_DECIMALS = 6;
15094
+ /**
15095
+ * Compute the gas reserve in ERC-20 deposit token base units.
15096
+ *
15097
+ * `EstimatedGas.fee` is denominated in the chain's native currency base units
15098
+ * (e.g. 18-decimal USDC on Arc). The deposit token balance and
15099
+ * `depositAmountRaw` are in ERC-20 token base units (e.g. 6-decimal USDC).
15100
+ * This function converts the native fee total to the same scale so both sides
15101
+ * of the `balance < required` comparison are commensurable.
15102
+ *
15103
+ * Conversion: `fee_token_units = fee_native_units / 10^(nativeDecimals - tokenDecimals)`
15104
+ *
15105
+ * Falls back to {@link DEFAULT_GAS_RESERVE} when:
15106
+ * - `gasFees` is absent or empty
15107
+ * - all fee entries have `fees: null` (estimation failed)
15108
+ * - the converted total rounds to zero (native fee smaller than 1 token unit)
15109
+ *
15110
+ * @internal
15111
+ */ function computeGasReserve(gasFees, nativeDecimals, tokenDecimals) {
15112
+ if (gasFees === undefined || gasFees.length === 0) {
15113
+ return DEFAULT_GAS_RESERVE;
15114
+ }
15115
+ let totalNative = 0n;
15116
+ let hasNullEntry = false;
15117
+ for (const entry of gasFees){
15118
+ if (entry.fees === null) {
15119
+ hasNullEntry = true;
15120
+ } else {
15121
+ totalNative += BigInt(entry.fees.fee);
15122
+ }
15123
+ }
15124
+ const decimalDiff = nativeDecimals - tokenDecimals;
15125
+ // Use ceiling division when reducing precision so we never under-estimate.
15126
+ const scale = decimalDiff > 0 ? 10n ** BigInt(decimalDiff) : 1n;
15127
+ let knownToken;
15128
+ if (totalNative === 0n) {
15129
+ knownToken = 0n;
15130
+ } else if (decimalDiff > 0) {
15131
+ knownToken = (totalNative + scale - 1n) / scale;
15132
+ } else {
15133
+ knownToken = totalNative * 10n ** BigInt(-decimalDiff);
15134
+ }
15135
+ // When any estimate is missing the true total is unknown, so reserve at
15136
+ // least DEFAULT_GAS_RESERVE on top of whatever was already known.
15137
+ // BigInt is not supported by Math.max, so we use an explicit comparison.
15138
+ if (hasNullEntry) {
15139
+ return knownToken > DEFAULT_GAS_RESERVE ? knownToken : DEFAULT_GAS_RESERVE // NOSONAR
15140
+ ;
15141
+ }
15142
+ return knownToken > 0n ? knownToken : DEFAULT_GAS_RESERVE;
15143
+ }
15144
+ /**
15145
+ * Build a symbol → ERC-20 address map from the chain's known token addresses.
15146
+ * Used to resolve whether the native gas token corresponds to the deposit
15147
+ * token's address.
15148
+ *
15149
+ * @internal
15150
+ */ function buildSymbolAddressMap(chain) {
15151
+ const map = new Map();
15152
+ if (chain.usdcAddress != null) {
15153
+ map.set('USDC', chain.usdcAddress.toLowerCase());
15154
+ }
15155
+ if (chain.eurcAddress != null) {
15156
+ map.set('EURC', chain.eurcAddress.toLowerCase());
15157
+ }
15158
+ return map;
15159
+ }
15160
+ /**
15161
+ * Pre-broadcast balance check for EOA wallets on chains where the native gas
15162
+ * token and the deposit token are the same asset (e.g. Arc: USDC gas + USDC
15163
+ * deposit).
15164
+ *
15165
+ * When the deposit would consume the entire balance, the adapter locks gas
15166
+ * before `transferFrom`, causing an `ERC20: transfer amount exceeds balance`
15167
+ * revert on-chain. This function reads the wallet balance before broadcast and
15168
+ * throws {@link BalanceError.INSUFFICIENT_TOKEN} (code 9001) when
15169
+ * `balance < depositAmount + gasReserve`, giving callers a clean pre-broadcast
15170
+ * error instead of an opaque chain revert.
15171
+ *
15172
+ * The check is skipped when the chain's native gas currency symbol does not
15173
+ * match the deposit token symbol (derived by address lookup). Examples of
15174
+ * chains where the check is skipped: EURC deposit on Arc, any ETH-gas chain.
15175
+ *
15176
+ * Gas fees from the quote (`gasFees`) are in native-currency base units
15177
+ * (18-decimal on Arc) and are scaled down to ERC-20 token units (6-decimal)
15178
+ * before being added to `depositAmountRaw`.
15179
+ *
15180
+ * @param params - Adapter context, chain, deposit token, amount, and optional
15181
+ * quote gas fees.
15182
+ * @throws {@link KitError} (code 9001) when the balance is insufficient to
15183
+ * cover both the deposit and the gas reserve.
15184
+ *
15185
+ * @internal
15186
+ */ async function checkGasReserveForDeposit(params) {
15187
+ const { adapter, chain, address, depositTokenAddress, depositAmountRaw, gasFees } = params;
15188
+ const nativeSymbol = chain.nativeCurrency.symbol.toUpperCase();
15189
+ const symbolAddressMap = buildSymbolAddressMap(chain);
15190
+ const nativeTokenAddress = symbolAddressMap.get(nativeSymbol);
15191
+ // Only applies when native gas currency and deposit token are the same asset.
15192
+ if (nativeTokenAddress === undefined || depositTokenAddress.toLowerCase() !== nativeTokenAddress) {
15193
+ return;
15194
+ }
15195
+ const rawBalance = await executeAdapterReadAction(adapter, 'token.balanceOf', {
15196
+ tokenAddress: depositTokenAddress,
15197
+ walletAddress: address
15198
+ }, {
15199
+ chain,
15200
+ address
15201
+ });
15202
+ const balance = BigInt(String(rawBalance));
15203
+ // Both USDC and EURC use 6 ERC-20 decimals; the check only reaches here when
15204
+ // the deposit token is the chain's native gas asset (USDC or EURC).
15205
+ const gasReserve = computeGasReserve(gasFees, chain.nativeCurrency.decimals, STABLECOIN_ERC20_DECIMALS);
15206
+ const required = depositAmountRaw + gasReserve;
15207
+ if (balance < required) {
15208
+ throw new KitError({
15209
+ ...BalanceError.INSUFFICIENT_TOKEN,
15210
+ recoverability: 'FATAL',
15211
+ message: `Insufficient ${chain.nativeCurrency.symbol} balance to cover deposit and gas. Balance: ${balance.toString()}, required: ${required.toString()} (deposit: ${depositAmountRaw.toString()} + gas reserve: ${gasReserve.toString()})`
15212
+ });
15213
+ }
15214
+ }
15215
+
14977
15216
  /** @internal */ function isSameAddress(actual, expected) {
14978
15217
  return actual.toLowerCase() === expected.toLowerCase();
14979
15218
  }
@@ -16325,12 +16564,19 @@ const EARN_OPERATIONS = new Set([
16325
16564
  writable: true,
16326
16565
  configurable: true
16327
16566
  });
16567
+ // Surface the tx hash in the message so callers can look it up without
16568
+ // digging into cause.trace. Only applies to on-chain reverts — other errors
16569
+ // either have no tx hash or already embed it in their message.
16570
+ let message = base.message;
16571
+ if (base.name === 'ONCHAIN_TRANSACTION_REVERTED' && typeof existingTrace['txHash'] === 'string' && existingTrace['txHash'] !== '') {
16572
+ message = `${message} (txHash: ${existingTrace['txHash']})`;
16573
+ }
16328
16574
  return new KitError({
16329
16575
  code: base.code,
16330
16576
  name: base.name,
16331
16577
  type: base.type,
16332
16578
  recoverability,
16333
- message: base.message,
16579
+ message,
16334
16580
  cause: {
16335
16581
  trace
16336
16582
  }
@@ -16446,10 +16692,8 @@ const bridgeFeeTokenSchema = hexAddressSchema;
16446
16692
  assetAddress: z.string(),
16447
16693
  lltv: z.number(),
16448
16694
  supplyUsd: z.number(),
16449
- // Optional during the expand/contract window (a backend that predates the
16450
- // field omits the key), mirroring the `.optional()` facets on the base
16451
- // schema; `null` when the product exposes no per-market allocation (V2).
16452
- allocationPct: z.number().nullable().optional()
16695
+ // `null` when the product exposes no per-market allocation (V2).
16696
+ allocationPct: z.number().nullable()
16453
16697
  });
16454
16698
  /**
16455
16699
  * Zod schema for a Morpho vault warning in the API response.
@@ -16556,19 +16800,20 @@ const bridgeFeeTokenSchema = hexAddressSchema;
16556
16800
  earnKitWarnings: z.array(z.string()).optional()
16557
16801
  });
16558
16802
  /**
16559
- * Shared base schema: existing flat fields (kept) plus the new nested
16560
- * facets and neutral identity. Facets are `.optional()` during the
16561
- * transition; flip to required once the backend is confirmed emitting.
16803
+ * Shared base schema: existing flat fields (kept as deprecated aliases) plus
16804
+ * the nested facets. Zenith emits all facets on every response; required here
16805
+ * so a partial emission is caught at parse time rather than surfacing as a
16806
+ * runtime `undefined` access.
16562
16807
  *
16563
16808
  * @internal
16564
16809
  */ const earnBaseSchema = vaultInfoResponseSchema.extend({
16565
- address: z.string().optional(),
16566
- asOf: z.string().optional(),
16567
- manager: managerSchema.nullable().optional(),
16568
- apyProfile: apyProfileSchema.optional(),
16569
- fee: feeInfoSchema.optional(),
16570
- liquidityProfile: liquidityProfileSchema.optional(),
16571
- riskSignals: riskSignalsSchema.optional()
16810
+ address: z.string(),
16811
+ asOf: z.string(),
16812
+ manager: managerSchema.nullable(),
16813
+ apyProfile: apyProfileSchema,
16814
+ fee: feeInfoSchema,
16815
+ liquidityProfile: liquidityProfileSchema,
16816
+ riskSignals: riskSignalsSchema
16572
16817
  });
16573
16818
  /**
16574
16819
  * Zod schema for the `vault` opportunity variant.
@@ -17356,7 +17601,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
17356
17601
  }
17357
17602
 
17358
17603
  var name = "@circle-fin/provider-earn-service";
17359
- var version = "1.6.0";
17604
+ var version = "1.7.0";
17360
17605
  var pkg = {
17361
17606
  name: name,
17362
17607
  version: version};
@@ -17504,23 +17749,16 @@ var pkg = {
17504
17749
  if (chain === undefined) {
17505
17750
  throw createInvalidChainError(vault.chain, 'Chain returned by the Earn Service is not supported by the SDK');
17506
17751
  }
17507
- // The nested facets are `.optional()` in the schema (a backend that predates
17508
- // them omits them) and are typed optional on `EarnOpportunity` to match.
17509
- // Convert the nested liquidity amounts when present and pass the remaining
17510
- // facets straight through; each absent facet stays absent rather than being
17511
- // asserted present by a cast.
17512
17752
  return {
17513
17753
  ...vault,
17514
17754
  chain,
17515
17755
  totalDeposits: Amount.fromJSON(totalDeposits),
17516
17756
  liquidity: Amount.fromJSON(liquidity),
17517
- ...liquidityProfile !== undefined && {
17518
- liquidityProfile: {
17519
- ...liquidityProfile,
17520
- totalDeposits: Amount.fromJSON(liquidityProfile.totalDeposits),
17521
- available: Amount.fromJSON(liquidityProfile.available),
17522
- totalSupply: Amount.fromJSON(liquidityProfile.totalSupply)
17523
- }
17757
+ liquidityProfile: {
17758
+ ...liquidityProfile,
17759
+ totalDeposits: Amount.fromJSON(liquidityProfile.totalDeposits),
17760
+ available: Amount.fromJSON(liquidityProfile.available),
17761
+ totalSupply: Amount.fromJSON(liquidityProfile.totalSupply)
17524
17762
  }
17525
17763
  };
17526
17764
  }
@@ -19026,6 +19264,24 @@ function finishElapsedWait(lastStatus, lastError) {
19026
19264
  amount: params.amount
19027
19265
  };
19028
19266
  }
19267
+ // Only EOA wallets pay native gas from their own balance — SCAs have
19268
+ // their gas sponsored externally. Detect EOA by checking that the
19269
+ // adapter supports readBytecode and the wallet address has no on-chain
19270
+ // bytecode (empty code ⟹ EOA; non-empty ⟹ smart-contract account).
19271
+ // EIP-7702-delegated addresses are treated as EOA by isContractSigner.
19272
+ // If the adapter does not expose readBytecode (non-EVM adapter) the
19273
+ // check is skipped conservatively.
19274
+ if (approvalToken !== undefined && requiredAllowance > 0n && chain.type === 'evm' && isEvmAdapterLike(adapter) && !await isContractSigner(adapter, address, chain)) {
19275
+ const sameChainParams = params;
19276
+ await checkGasReserveForDeposit({
19277
+ adapter,
19278
+ chain,
19279
+ address,
19280
+ depositTokenAddress: approvalToken,
19281
+ depositAmountRaw: requiredAllowance,
19282
+ gasFees: sameChainParams.gasFees
19283
+ });
19284
+ }
19029
19285
  if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
19030
19286
  await this.runPhase(ctx, 'approve', 'approve', async ()=>{
19031
19287
  try {
@@ -19432,7 +19688,7 @@ function finishElapsedWait(lastStatus, lastError) {
19432
19688
  // Gas is estimated server-side by the Earn Service and returned on the quote, so the
19433
19689
  // SDK no longer simulates it locally. `chainDefinition` lets the fetch fill
19434
19690
  // the native token symbol / blockchain on each gas entry.
19435
- return fetchDepositQuote({
19691
+ const quote = await fetchDepositQuote({
19436
19692
  vaultAddress: params.vaultAddress,
19437
19693
  amount: params.amount,
19438
19694
  address,
@@ -19440,6 +19696,34 @@ function finishElapsedWait(lastStatus, lastError) {
19440
19696
  config,
19441
19697
  chainDefinition
19442
19698
  });
19699
+ // Run the same pre-broadcast gas reserve check here so callers get an early
19700
+ // warning at quote time — before they call deposit() and hit the real check.
19701
+ // Only applies on stablecoin-gas chains (e.g. Arc) where the deposit token
19702
+ // is also the native gas token, and only for EOA wallets that pay native gas
19703
+ // from their own balance. Skips silently on all other chains and wallet types.
19704
+ if (chainDefinition?.type === 'evm' && isEvmAdapterLike(params.from.adapter) && !await isContractSigner(params.from.adapter, address, chainDefinition)) {
19705
+ const symbol = quote.deposit.symbol.toUpperCase();
19706
+ let depositTokenAddress = null;
19707
+ if (symbol === 'USDC') {
19708
+ depositTokenAddress = chainDefinition.usdcAddress;
19709
+ } else if (symbol === 'EURC') {
19710
+ depositTokenAddress = chainDefinition.eurcAddress;
19711
+ }
19712
+ if (depositTokenAddress != null) {
19713
+ const amountObj = Amount.parse(params.amount, {
19714
+ decimals: 6
19715
+ });
19716
+ await checkGasReserveForDeposit({
19717
+ adapter: params.from.adapter,
19718
+ chain: chainDefinition,
19719
+ address,
19720
+ depositTokenAddress,
19721
+ depositAmountRaw: amountObj.raw,
19722
+ gasFees: quote.gasFees
19723
+ });
19724
+ }
19725
+ }
19726
+ return quote;
19443
19727
  }
19444
19728
  /** {@inheritdoc} */ async getWithdrawalQuote(params) {
19445
19729
  const config = this.resolveConfig(params.config);
@@ -19739,21 +20023,15 @@ function formatPositionPnL(pnl) {
19739
20023
  // consumers keep receiving them until Contract.
19740
20024
  // eslint-disable-next-line @typescript-eslint/no-deprecated
19741
20025
  const { totalDeposits, liquidity, liquidityProfile, ...rest } = vault;
19742
- // `liquidityProfile` is `.optional()` in the response schema during the
19743
- // expand/contract window (an old backend that predates the nested facets
19744
- // omits it), so only format and re-attach it when present — matching the
19745
- // provider-side `toVaultInfo` mapper.
19746
20026
  return {
19747
20027
  ...rest,
19748
20028
  totalDeposits: formatAmount(totalDeposits),
19749
20029
  liquidity: formatAmount(liquidity),
19750
- ...liquidityProfile !== undefined && {
19751
- liquidityProfile: {
19752
- ...liquidityProfile,
19753
- totalDeposits: formatAmount(liquidityProfile.totalDeposits),
19754
- available: formatAmount(liquidityProfile.available),
19755
- totalSupply: formatAmount(liquidityProfile.totalSupply)
19756
- }
20030
+ liquidityProfile: {
20031
+ ...liquidityProfile,
20032
+ totalDeposits: formatAmount(liquidityProfile.totalDeposits),
20033
+ available: formatAmount(liquidityProfile.available),
20034
+ totalSupply: formatAmount(liquidityProfile.totalSupply)
19757
20035
  }
19758
20036
  };
19759
20037
  }
@@ -20239,6 +20517,49 @@ const sourceAdapterContextSchema = z.object({
20239
20517
  vaultAddress: vaultAddressSchema,
20240
20518
  config: earnConfigSchema.optional()
20241
20519
  });
20520
+ /**
20521
+ * Schema for a successful gas fee entry (fees is non-null).
20522
+ *
20523
+ * Validates the kit shape — {@link EarnEstimatedGas} uses decimal strings for
20524
+ * `gas`, `gasPrice`, and `fee`. Only digits are accepted so that the deposit
20525
+ * operation can safely convert them to `bigint` before passing them to the
20526
+ * provider's `computeGasReserve`.
20527
+ *
20528
+ * @internal
20529
+ */ const earnGasFeeEstimateSuccessSchema = z.object({
20530
+ name: z.string(),
20531
+ token: z.string(),
20532
+ blockchain: z.string(),
20533
+ fees: z.object({
20534
+ gas: z.string().regex(/^\d+$/, 'gas must be a non-negative integer string'),
20535
+ gasPrice: z.string().regex(/^\d+$/, 'gasPrice must be a non-negative integer string'),
20536
+ fee: z.string().regex(/^\d+$/, 'fee must be a non-negative integer string')
20537
+ }),
20538
+ error: z.undefined().optional()
20539
+ }).strict();
20540
+ /**
20541
+ * Schema for a failed gas fee entry (fees is null, error is a string).
20542
+ *
20543
+ * @internal
20544
+ */ const earnGasFeeEstimateFailureSchema = z.object({
20545
+ name: z.string(),
20546
+ token: z.string(),
20547
+ blockchain: z.string(),
20548
+ fees: z.null(),
20549
+ error: z.string()
20550
+ }).strict();
20551
+ /**
20552
+ * Schema for a single kit-level gas fee estimate entry.
20553
+ *
20554
+ * Rejects arbitrary inputs (null, numbers, empty objects, non-numeric fee
20555
+ * strings) so callers receive a descriptive validation error instead of a
20556
+ * raw `BigInt` conversion error inside `computeGasReserve`.
20557
+ *
20558
+ * @internal
20559
+ */ const earnGasFeeEstimateSchema = z.union([
20560
+ earnGasFeeEstimateSuccessSchema,
20561
+ earnGasFeeEstimateFailureSchema
20562
+ ]);
20242
20563
  /**
20243
20564
  * Validation schema for the same-chain variant of DepositParams.
20244
20565
  *
@@ -20251,6 +20572,7 @@ const sourceAdapterContextSchema = z.object({
20251
20572
  to: z.undefined().optional(),
20252
20573
  vaultAddress: vaultAddressSchema,
20253
20574
  amount: amountSchema,
20575
+ gasFees: z.array(earnGasFeeEstimateSchema).optional(),
20254
20576
  config: earnConfigSchema.optional()
20255
20577
  }).strict();
20256
20578
  const crossChainDepositDestinationSchema = z.object({
@@ -20871,6 +21193,9 @@ async function deposit$1(context, params) {
20871
21193
  },
20872
21194
  vaultAddress: params.vaultAddress,
20873
21195
  amount: params.amount,
21196
+ ...!hasExplicitDestination && params.gasFees !== undefined && {
21197
+ gasFees: toProviderGasFees(params.gasFees)
21198
+ },
20874
21199
  ...params.config !== undefined && {
20875
21200
  config: params.config
20876
21201
  }
@@ -20893,6 +21218,21 @@ async function deposit$1(context, params) {
20893
21218
  }
20894
21219
  return provider.deposit(baseParams);
20895
21220
  }
21221
+ function toProviderGasFees(gasFees) {
21222
+ return gasFees.map((entry)=>{
21223
+ if (entry.fees === null) {
21224
+ return entry; // NOSONAR
21225
+ }
21226
+ return {
21227
+ ...entry,
21228
+ fees: {
21229
+ ...entry.fees,
21230
+ gas: BigInt(entry.fees.gas),
21231
+ gasPrice: BigInt(entry.fees.gasPrice)
21232
+ }
21233
+ };
21234
+ });
21235
+ }
20896
21236
  function hasCrossChainDestination(params) {
20897
21237
  // Runs before schema validation, so guard against non-object params from
20898
21238
  // untyped callers; assertEarnParams reports those with a structured error.