@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/CHANGELOG.md +12 -0
- package/bridge.cjs +61 -18
- package/bridge.d.cts +71 -0
- package/bridge.d.mts +71 -0
- package/bridge.d.ts +71 -0
- package/bridge.mjs +61 -18
- package/context.d.cts +71 -0
- package/context.d.mts +71 -0
- package/context.d.ts +71 -0
- package/earn.cjs +383 -43
- package/earn.d.cts +19 -26
- package/earn.d.mts +19 -26
- package/earn.d.ts +19 -26
- package/earn.mjs +383 -43
- package/estimateBridge.cjs +61 -18
- package/estimateBridge.d.cts +71 -0
- package/estimateBridge.d.mts +71 -0
- package/estimateBridge.d.ts +71 -0
- package/estimateBridge.mjs +61 -18
- package/estimateSwap.cjs +59 -16
- package/estimateSwap.d.cts +71 -0
- package/estimateSwap.d.mts +71 -0
- package/estimateSwap.d.ts +71 -0
- package/estimateSwap.mjs +59 -16
- package/index.cjs +291 -63
- package/index.d.cts +184 -91
- package/index.d.mts +184 -91
- package/index.d.ts +184 -91
- package/index.mjs +291 -63
- package/package.json +4 -4
- package/server.cjs +13 -17
- package/server.d.cts +10 -9
- package/server.d.mts +10 -9
- package/server.d.ts +10 -9
- package/server.mjs +13 -17
- package/swap.cjs +59 -16
- package/swap.d.cts +71 -0
- package/swap.d.mts +71 -0
- package/swap.d.ts +71 -0
- package/swap.mjs +59 -16
package/earn.cjs
CHANGED
|
@@ -704,6 +704,29 @@ class KitError extends Error {
|
|
|
704
704
|
name: 'INPUT_VALIDATION_FAILED',
|
|
705
705
|
type: 'INPUT'
|
|
706
706
|
}};
|
|
707
|
+
/**
|
|
708
|
+
* Standardized error definitions for BALANCE type errors.
|
|
709
|
+
*
|
|
710
|
+
* BALANCE errors indicate insufficient funds or allowance issues
|
|
711
|
+
* that prevent transaction execution.
|
|
712
|
+
*
|
|
713
|
+
* @example
|
|
714
|
+
* ```typescript
|
|
715
|
+
* import { BalanceError } from '@core/errors'
|
|
716
|
+
*
|
|
717
|
+
* const error = new KitError({
|
|
718
|
+
* ...BalanceError.INSUFFICIENT_TOKEN,
|
|
719
|
+
* recoverability: 'FATAL',
|
|
720
|
+
* message: 'Insufficient USDC balance on Ethereum',
|
|
721
|
+
* cause: { trace: { required: '100', available: '50' } }
|
|
722
|
+
* })
|
|
723
|
+
* ```
|
|
724
|
+
*/ const BalanceError = {
|
|
725
|
+
/** Insufficient token balance for transaction */ INSUFFICIENT_TOKEN: {
|
|
726
|
+
code: 9001,
|
|
727
|
+
name: 'BALANCE_INSUFFICIENT_TOKEN',
|
|
728
|
+
type: 'BALANCE'
|
|
729
|
+
}};
|
|
707
730
|
/**
|
|
708
731
|
* Standardized error definitions for ONCHAIN type errors.
|
|
709
732
|
*
|
|
@@ -9135,7 +9158,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9135
9158
|
}
|
|
9136
9159
|
|
|
9137
9160
|
var name$3 = "@circle-fin/bridge-kit";
|
|
9138
|
-
var version$3 = "1.15.
|
|
9161
|
+
var version$3 = "1.15.1";
|
|
9139
9162
|
var pkg$3 = {
|
|
9140
9163
|
name: name$3,
|
|
9141
9164
|
version: version$3};
|
|
@@ -9223,7 +9246,7 @@ const READ_ACTION_KEY_SET = new Set(READ_ACTION_KEYS);
|
|
|
9223
9246
|
*
|
|
9224
9247
|
* @internal
|
|
9225
9248
|
*/ function isReadActionKey(action) {
|
|
9226
|
-
return READ_ACTION_KEY_SET.has(action);
|
|
9249
|
+
return typeof action === 'string' && READ_ACTION_KEY_SET.has(action);
|
|
9227
9250
|
}
|
|
9228
9251
|
|
|
9229
9252
|
/**
|
|
@@ -12547,6 +12570,86 @@ const abiParameterSchema = zod.z.object({
|
|
|
12547
12570
|
};
|
|
12548
12571
|
}
|
|
12549
12572
|
|
|
12573
|
+
/**
|
|
12574
|
+
* EIP-7702 delegation indicator. A 7702-delegated EOA's bytecode is
|
|
12575
|
+
* `0xef0100` followed by the 20-byte delegate address (23 bytes total).
|
|
12576
|
+
* The underlying secp256k1 key still produces `ecrecover`-verifiable
|
|
12577
|
+
* signatures, so for Gateway's purposes a 7702-delegated address is
|
|
12578
|
+
* an EOA, not a contract signer.
|
|
12579
|
+
*
|
|
12580
|
+
* Spec: https://eips.ethereum.org/EIPS/eip-7702
|
|
12581
|
+
*/ const EIP_7702_DELEGATION_PREFIX = '0xef0100';
|
|
12582
|
+
/**
|
|
12583
|
+
* Determine whether `address` on `chain` signs as a contract (ERC-1271)
|
|
12584
|
+
* rather than as an EOA.
|
|
12585
|
+
*
|
|
12586
|
+
* Gateway validates burn-intent signatures two ways: a static `ecrecover`
|
|
12587
|
+
* check for EOAs, and — for requests that carry `contractSigner: true` —
|
|
12588
|
+
* an offchain `isValidSignature` simulation against the signing contract
|
|
12589
|
+
* (ERC-1271). Gateway does not infer which one to use, so the caller must
|
|
12590
|
+
* declare it. This detects the contract case from on-chain bytecode.
|
|
12591
|
+
*
|
|
12592
|
+
* EIP-7702-delegated EOAs are treated as EOAs: they expose non-empty
|
|
12593
|
+
* bytecode (`0xef0100<delegate>`) but the underlying secp256k1 key still
|
|
12594
|
+
* produces `ecrecover`-verifiable signatures, so the cheaper EOA path
|
|
12595
|
+
* stays correct for them.
|
|
12596
|
+
*
|
|
12597
|
+
* If bytecode cannot be read (RPC failure, etc.) the address is reported
|
|
12598
|
+
* as an EOA and a warning is logged so the fallback is diagnosable. A
|
|
12599
|
+
* genuine contract signer misreported this way is rejected by Gateway with
|
|
12600
|
+
* an invalid-signature error rather than silently mis-attested.
|
|
12601
|
+
*
|
|
12602
|
+
* @param adapter - Anything exposing {@link EvmAdapterLike.readBytecode}.
|
|
12603
|
+
* @param address - Signer address to classify.
|
|
12604
|
+
* @param chain - EVM chain where the signer lives.
|
|
12605
|
+
* @returns `true` when the signer is a contract account and the transfer
|
|
12606
|
+
* request must set `contractSigner: true`; `false` otherwise.
|
|
12607
|
+
*
|
|
12608
|
+
* @example
|
|
12609
|
+
* ```typescript
|
|
12610
|
+
* import { isContractSigner } from '@core/adapter-evm'
|
|
12611
|
+
* import { Ethereum } from '@core/chains'
|
|
12612
|
+
*
|
|
12613
|
+
* const useErc1271 = await isContractSigner(adapter, '0xabc...', Ethereum)
|
|
12614
|
+
* ```
|
|
12615
|
+
*/ async function isContractSigner(adapter, address, chain) {
|
|
12616
|
+
let code;
|
|
12617
|
+
try {
|
|
12618
|
+
code = await adapter.readBytecode(address, chain);
|
|
12619
|
+
} catch (err) {
|
|
12620
|
+
console.warn(`[gateway] isContractSigner defaulting to EOA (readBytecode failed ` + `for ${address} on ${chain.name}): ` + (err instanceof Error ? err.message : String(err)));
|
|
12621
|
+
return false;
|
|
12622
|
+
}
|
|
12623
|
+
if (code === undefined || code === '0x' || code.toLowerCase().startsWith(EIP_7702_DELEGATION_PREFIX)) {
|
|
12624
|
+
return false;
|
|
12625
|
+
}
|
|
12626
|
+
return true;
|
|
12627
|
+
}
|
|
12628
|
+
|
|
12629
|
+
/**
|
|
12630
|
+
* Duck-type test for an EVM adapter that exposes `readBytecode`.
|
|
12631
|
+
*
|
|
12632
|
+
* Used as a structural guard at package boundaries (e.g. the Circle Wallets
|
|
12633
|
+
* hybrid adapter calling into a chain adapter, or the Gateway sign path
|
|
12634
|
+
* receiving an arbitrary adapter implementation) where `instanceof EvmAdapter`
|
|
12635
|
+
* is unreliable because each consumer bundles its own copy of the base class.
|
|
12636
|
+
*
|
|
12637
|
+
* @param value - The value to test.
|
|
12638
|
+
* @returns `true` when `value` is an object exposing a callable
|
|
12639
|
+
* `readBytecode` method, narrowed to {@link EvmAdapterLike}.
|
|
12640
|
+
*
|
|
12641
|
+
* @example
|
|
12642
|
+
* ```typescript
|
|
12643
|
+
* import { isEvmAdapterLike } from '@core/adapter-evm'
|
|
12644
|
+
*
|
|
12645
|
+
* if (isEvmAdapterLike(adapter)) {
|
|
12646
|
+
* const code = await adapter.readBytecode('0xabc...', chain)
|
|
12647
|
+
* }
|
|
12648
|
+
* ```
|
|
12649
|
+
*/ function isEvmAdapterLike(value) {
|
|
12650
|
+
return typeof value === 'object' && value !== null && 'readBytecode' in value && typeof value.readBytecode === 'function';
|
|
12651
|
+
}
|
|
12652
|
+
|
|
12550
12653
|
const hexBytesSchema$1 = zod.z.string().regex(/^0x([0-9a-fA-F]{2})*$/, {
|
|
12551
12654
|
message: 'value must be a 0x-prefixed even-length hex string'
|
|
12552
12655
|
}).transform((value)=>value);
|
|
@@ -14441,7 +14544,7 @@ new Set(Object.values(Blockchain));
|
|
|
14441
14544
|
registerKit(`${pkg$2.name}/${pkg$2.version}`);
|
|
14442
14545
|
|
|
14443
14546
|
var name$1 = "@circle-fin/earn-kit";
|
|
14444
|
-
var version$1 = "1.
|
|
14547
|
+
var version$1 = "1.8.0";
|
|
14445
14548
|
var pkg$1 = {
|
|
14446
14549
|
name: name$1,
|
|
14447
14550
|
version: version$1};
|
|
@@ -14980,6 +15083,142 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
14980
15083
|
};
|
|
14981
15084
|
}
|
|
14982
15085
|
|
|
15086
|
+
/**
|
|
15087
|
+
* Fallback gas reserve expressed in ERC-20 deposit token base units when no
|
|
15088
|
+
* quote gas fees are available. 50_000 = 0.05 USDC at the 6-decimal ERC-20
|
|
15089
|
+
* USDC scale used on Arc (and the scale of `balance` / `depositAmountRaw`).
|
|
15090
|
+
*
|
|
15091
|
+
* @internal
|
|
15092
|
+
*/ const DEFAULT_GAS_RESERVE = 50_000n;
|
|
15093
|
+
/**
|
|
15094
|
+
* Standard ERC-20 decimal precision for USDC and EURC. Both tokens use 6
|
|
15095
|
+
* decimals on all EVM chains, regardless of how many decimals the native
|
|
15096
|
+
* gas-token representation uses.
|
|
15097
|
+
*
|
|
15098
|
+
* @internal
|
|
15099
|
+
*/ const STABLECOIN_ERC20_DECIMALS = 6;
|
|
15100
|
+
/**
|
|
15101
|
+
* Compute the gas reserve in ERC-20 deposit token base units.
|
|
15102
|
+
*
|
|
15103
|
+
* `EstimatedGas.fee` is denominated in the chain's native currency base units
|
|
15104
|
+
* (e.g. 18-decimal USDC on Arc). The deposit token balance and
|
|
15105
|
+
* `depositAmountRaw` are in ERC-20 token base units (e.g. 6-decimal USDC).
|
|
15106
|
+
* This function converts the native fee total to the same scale so both sides
|
|
15107
|
+
* of the `balance < required` comparison are commensurable.
|
|
15108
|
+
*
|
|
15109
|
+
* Conversion: `fee_token_units = fee_native_units / 10^(nativeDecimals - tokenDecimals)`
|
|
15110
|
+
*
|
|
15111
|
+
* Falls back to {@link DEFAULT_GAS_RESERVE} when:
|
|
15112
|
+
* - `gasFees` is absent or empty
|
|
15113
|
+
* - all fee entries have `fees: null` (estimation failed)
|
|
15114
|
+
* - the converted total rounds to zero (native fee smaller than 1 token unit)
|
|
15115
|
+
*
|
|
15116
|
+
* @internal
|
|
15117
|
+
*/ function computeGasReserve(gasFees, nativeDecimals, tokenDecimals) {
|
|
15118
|
+
if (gasFees === undefined || gasFees.length === 0) {
|
|
15119
|
+
return DEFAULT_GAS_RESERVE;
|
|
15120
|
+
}
|
|
15121
|
+
let totalNative = 0n;
|
|
15122
|
+
let hasNullEntry = false;
|
|
15123
|
+
for (const entry of gasFees){
|
|
15124
|
+
if (entry.fees === null) {
|
|
15125
|
+
hasNullEntry = true;
|
|
15126
|
+
} else {
|
|
15127
|
+
totalNative += BigInt(entry.fees.fee);
|
|
15128
|
+
}
|
|
15129
|
+
}
|
|
15130
|
+
const decimalDiff = nativeDecimals - tokenDecimals;
|
|
15131
|
+
// Use ceiling division when reducing precision so we never under-estimate.
|
|
15132
|
+
const scale = decimalDiff > 0 ? 10n ** BigInt(decimalDiff) : 1n;
|
|
15133
|
+
let knownToken;
|
|
15134
|
+
if (totalNative === 0n) {
|
|
15135
|
+
knownToken = 0n;
|
|
15136
|
+
} else if (decimalDiff > 0) {
|
|
15137
|
+
knownToken = (totalNative + scale - 1n) / scale;
|
|
15138
|
+
} else {
|
|
15139
|
+
knownToken = totalNative * 10n ** BigInt(-decimalDiff);
|
|
15140
|
+
}
|
|
15141
|
+
// When any estimate is missing the true total is unknown, so reserve at
|
|
15142
|
+
// least DEFAULT_GAS_RESERVE on top of whatever was already known.
|
|
15143
|
+
// BigInt is not supported by Math.max, so we use an explicit comparison.
|
|
15144
|
+
if (hasNullEntry) {
|
|
15145
|
+
return knownToken > DEFAULT_GAS_RESERVE ? knownToken : DEFAULT_GAS_RESERVE // NOSONAR
|
|
15146
|
+
;
|
|
15147
|
+
}
|
|
15148
|
+
return knownToken > 0n ? knownToken : DEFAULT_GAS_RESERVE;
|
|
15149
|
+
}
|
|
15150
|
+
/**
|
|
15151
|
+
* Build a symbol → ERC-20 address map from the chain's known token addresses.
|
|
15152
|
+
* Used to resolve whether the native gas token corresponds to the deposit
|
|
15153
|
+
* token's address.
|
|
15154
|
+
*
|
|
15155
|
+
* @internal
|
|
15156
|
+
*/ function buildSymbolAddressMap(chain) {
|
|
15157
|
+
const map = new Map();
|
|
15158
|
+
if (chain.usdcAddress != null) {
|
|
15159
|
+
map.set('USDC', chain.usdcAddress.toLowerCase());
|
|
15160
|
+
}
|
|
15161
|
+
if (chain.eurcAddress != null) {
|
|
15162
|
+
map.set('EURC', chain.eurcAddress.toLowerCase());
|
|
15163
|
+
}
|
|
15164
|
+
return map;
|
|
15165
|
+
}
|
|
15166
|
+
/**
|
|
15167
|
+
* Pre-broadcast balance check for EOA wallets on chains where the native gas
|
|
15168
|
+
* token and the deposit token are the same asset (e.g. Arc: USDC gas + USDC
|
|
15169
|
+
* deposit).
|
|
15170
|
+
*
|
|
15171
|
+
* When the deposit would consume the entire balance, the adapter locks gas
|
|
15172
|
+
* before `transferFrom`, causing an `ERC20: transfer amount exceeds balance`
|
|
15173
|
+
* revert on-chain. This function reads the wallet balance before broadcast and
|
|
15174
|
+
* throws {@link BalanceError.INSUFFICIENT_TOKEN} (code 9001) when
|
|
15175
|
+
* `balance < depositAmount + gasReserve`, giving callers a clean pre-broadcast
|
|
15176
|
+
* error instead of an opaque chain revert.
|
|
15177
|
+
*
|
|
15178
|
+
* The check is skipped when the chain's native gas currency symbol does not
|
|
15179
|
+
* match the deposit token symbol (derived by address lookup). Examples of
|
|
15180
|
+
* chains where the check is skipped: EURC deposit on Arc, any ETH-gas chain.
|
|
15181
|
+
*
|
|
15182
|
+
* Gas fees from the quote (`gasFees`) are in native-currency base units
|
|
15183
|
+
* (18-decimal on Arc) and are scaled down to ERC-20 token units (6-decimal)
|
|
15184
|
+
* before being added to `depositAmountRaw`.
|
|
15185
|
+
*
|
|
15186
|
+
* @param params - Adapter context, chain, deposit token, amount, and optional
|
|
15187
|
+
* quote gas fees.
|
|
15188
|
+
* @throws {@link KitError} (code 9001) when the balance is insufficient to
|
|
15189
|
+
* cover both the deposit and the gas reserve.
|
|
15190
|
+
*
|
|
15191
|
+
* @internal
|
|
15192
|
+
*/ async function checkGasReserveForDeposit(params) {
|
|
15193
|
+
const { adapter, chain, address, depositTokenAddress, depositAmountRaw, gasFees } = params;
|
|
15194
|
+
const nativeSymbol = chain.nativeCurrency.symbol.toUpperCase();
|
|
15195
|
+
const symbolAddressMap = buildSymbolAddressMap(chain);
|
|
15196
|
+
const nativeTokenAddress = symbolAddressMap.get(nativeSymbol);
|
|
15197
|
+
// Only applies when native gas currency and deposit token are the same asset.
|
|
15198
|
+
if (nativeTokenAddress === undefined || depositTokenAddress.toLowerCase() !== nativeTokenAddress) {
|
|
15199
|
+
return;
|
|
15200
|
+
}
|
|
15201
|
+
const rawBalance = await executeAdapterReadAction(adapter, 'token.balanceOf', {
|
|
15202
|
+
tokenAddress: depositTokenAddress,
|
|
15203
|
+
walletAddress: address
|
|
15204
|
+
}, {
|
|
15205
|
+
chain,
|
|
15206
|
+
address
|
|
15207
|
+
});
|
|
15208
|
+
const balance = BigInt(String(rawBalance));
|
|
15209
|
+
// Both USDC and EURC use 6 ERC-20 decimals; the check only reaches here when
|
|
15210
|
+
// the deposit token is the chain's native gas asset (USDC or EURC).
|
|
15211
|
+
const gasReserve = computeGasReserve(gasFees, chain.nativeCurrency.decimals, STABLECOIN_ERC20_DECIMALS);
|
|
15212
|
+
const required = depositAmountRaw + gasReserve;
|
|
15213
|
+
if (balance < required) {
|
|
15214
|
+
throw new KitError({
|
|
15215
|
+
...BalanceError.INSUFFICIENT_TOKEN,
|
|
15216
|
+
recoverability: 'FATAL',
|
|
15217
|
+
message: `Insufficient ${chain.nativeCurrency.symbol} balance to cover deposit and gas. Balance: ${balance.toString()}, required: ${required.toString()} (deposit: ${depositAmountRaw.toString()} + gas reserve: ${gasReserve.toString()})`
|
|
15218
|
+
});
|
|
15219
|
+
}
|
|
15220
|
+
}
|
|
15221
|
+
|
|
14983
15222
|
/** @internal */ function isSameAddress(actual, expected) {
|
|
14984
15223
|
return actual.toLowerCase() === expected.toLowerCase();
|
|
14985
15224
|
}
|
|
@@ -16331,12 +16570,19 @@ const EARN_OPERATIONS = new Set([
|
|
|
16331
16570
|
writable: true,
|
|
16332
16571
|
configurable: true
|
|
16333
16572
|
});
|
|
16573
|
+
// Surface the tx hash in the message so callers can look it up without
|
|
16574
|
+
// digging into cause.trace. Only applies to on-chain reverts — other errors
|
|
16575
|
+
// either have no tx hash or already embed it in their message.
|
|
16576
|
+
let message = base.message;
|
|
16577
|
+
if (base.name === 'ONCHAIN_TRANSACTION_REVERTED' && typeof existingTrace['txHash'] === 'string' && existingTrace['txHash'] !== '') {
|
|
16578
|
+
message = `${message} (txHash: ${existingTrace['txHash']})`;
|
|
16579
|
+
}
|
|
16334
16580
|
return new KitError({
|
|
16335
16581
|
code: base.code,
|
|
16336
16582
|
name: base.name,
|
|
16337
16583
|
type: base.type,
|
|
16338
16584
|
recoverability,
|
|
16339
|
-
message
|
|
16585
|
+
message,
|
|
16340
16586
|
cause: {
|
|
16341
16587
|
trace
|
|
16342
16588
|
}
|
|
@@ -16452,10 +16698,8 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
16452
16698
|
assetAddress: zod.z.string(),
|
|
16453
16699
|
lltv: zod.z.number(),
|
|
16454
16700
|
supplyUsd: zod.z.number(),
|
|
16455
|
-
//
|
|
16456
|
-
|
|
16457
|
-
// schema; `null` when the product exposes no per-market allocation (V2).
|
|
16458
|
-
allocationPct: zod.z.number().nullable().optional()
|
|
16701
|
+
// `null` when the product exposes no per-market allocation (V2).
|
|
16702
|
+
allocationPct: zod.z.number().nullable()
|
|
16459
16703
|
});
|
|
16460
16704
|
/**
|
|
16461
16705
|
* Zod schema for a Morpho vault warning in the API response.
|
|
@@ -16562,19 +16806,20 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
16562
16806
|
earnKitWarnings: zod.z.array(zod.z.string()).optional()
|
|
16563
16807
|
});
|
|
16564
16808
|
/**
|
|
16565
|
-
* Shared base schema: existing flat fields (kept
|
|
16566
|
-
* facets
|
|
16567
|
-
*
|
|
16809
|
+
* Shared base schema: existing flat fields (kept as deprecated aliases) plus
|
|
16810
|
+
* the nested facets. Zenith emits all facets on every response; required here
|
|
16811
|
+
* so a partial emission is caught at parse time rather than surfacing as a
|
|
16812
|
+
* runtime `undefined` access.
|
|
16568
16813
|
*
|
|
16569
16814
|
* @internal
|
|
16570
16815
|
*/ const earnBaseSchema = vaultInfoResponseSchema.extend({
|
|
16571
|
-
address: zod.z.string()
|
|
16572
|
-
asOf: zod.z.string()
|
|
16573
|
-
manager: managerSchema.nullable()
|
|
16574
|
-
apyProfile: apyProfileSchema
|
|
16575
|
-
fee: feeInfoSchema
|
|
16576
|
-
liquidityProfile: liquidityProfileSchema
|
|
16577
|
-
riskSignals: riskSignalsSchema
|
|
16816
|
+
address: zod.z.string(),
|
|
16817
|
+
asOf: zod.z.string(),
|
|
16818
|
+
manager: managerSchema.nullable(),
|
|
16819
|
+
apyProfile: apyProfileSchema,
|
|
16820
|
+
fee: feeInfoSchema,
|
|
16821
|
+
liquidityProfile: liquidityProfileSchema,
|
|
16822
|
+
riskSignals: riskSignalsSchema
|
|
16578
16823
|
});
|
|
16579
16824
|
/**
|
|
16580
16825
|
* Zod schema for the `vault` opportunity variant.
|
|
@@ -17362,7 +17607,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
17362
17607
|
}
|
|
17363
17608
|
|
|
17364
17609
|
var name = "@circle-fin/provider-earn-service";
|
|
17365
|
-
var version = "1.
|
|
17610
|
+
var version = "1.7.0";
|
|
17366
17611
|
var pkg = {
|
|
17367
17612
|
name: name,
|
|
17368
17613
|
version: version};
|
|
@@ -17510,23 +17755,16 @@ var pkg = {
|
|
|
17510
17755
|
if (chain === undefined) {
|
|
17511
17756
|
throw createInvalidChainError(vault.chain, 'Chain returned by the Earn Service is not supported by the SDK');
|
|
17512
17757
|
}
|
|
17513
|
-
// The nested facets are `.optional()` in the schema (a backend that predates
|
|
17514
|
-
// them omits them) and are typed optional on `EarnOpportunity` to match.
|
|
17515
|
-
// Convert the nested liquidity amounts when present and pass the remaining
|
|
17516
|
-
// facets straight through; each absent facet stays absent rather than being
|
|
17517
|
-
// asserted present by a cast.
|
|
17518
17758
|
return {
|
|
17519
17759
|
...vault,
|
|
17520
17760
|
chain,
|
|
17521
17761
|
totalDeposits: Amount.fromJSON(totalDeposits),
|
|
17522
17762
|
liquidity: Amount.fromJSON(liquidity),
|
|
17523
|
-
|
|
17524
|
-
liquidityProfile
|
|
17525
|
-
|
|
17526
|
-
|
|
17527
|
-
|
|
17528
|
-
totalSupply: Amount.fromJSON(liquidityProfile.totalSupply)
|
|
17529
|
-
}
|
|
17763
|
+
liquidityProfile: {
|
|
17764
|
+
...liquidityProfile,
|
|
17765
|
+
totalDeposits: Amount.fromJSON(liquidityProfile.totalDeposits),
|
|
17766
|
+
available: Amount.fromJSON(liquidityProfile.available),
|
|
17767
|
+
totalSupply: Amount.fromJSON(liquidityProfile.totalSupply)
|
|
17530
17768
|
}
|
|
17531
17769
|
};
|
|
17532
17770
|
}
|
|
@@ -19032,6 +19270,24 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
19032
19270
|
amount: params.amount
|
|
19033
19271
|
};
|
|
19034
19272
|
}
|
|
19273
|
+
// Only EOA wallets pay native gas from their own balance — SCAs have
|
|
19274
|
+
// their gas sponsored externally. Detect EOA by checking that the
|
|
19275
|
+
// adapter supports readBytecode and the wallet address has no on-chain
|
|
19276
|
+
// bytecode (empty code ⟹ EOA; non-empty ⟹ smart-contract account).
|
|
19277
|
+
// EIP-7702-delegated addresses are treated as EOA by isContractSigner.
|
|
19278
|
+
// If the adapter does not expose readBytecode (non-EVM adapter) the
|
|
19279
|
+
// check is skipped conservatively.
|
|
19280
|
+
if (approvalToken !== undefined && requiredAllowance > 0n && chain.type === 'evm' && isEvmAdapterLike(adapter) && !await isContractSigner(adapter, address, chain)) {
|
|
19281
|
+
const sameChainParams = params;
|
|
19282
|
+
await checkGasReserveForDeposit({
|
|
19283
|
+
adapter,
|
|
19284
|
+
chain,
|
|
19285
|
+
address,
|
|
19286
|
+
depositTokenAddress: approvalToken,
|
|
19287
|
+
depositAmountRaw: requiredAllowance,
|
|
19288
|
+
gasFees: sameChainParams.gasFees
|
|
19289
|
+
});
|
|
19290
|
+
}
|
|
19035
19291
|
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
|
|
19036
19292
|
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
19037
19293
|
try {
|
|
@@ -19438,7 +19694,7 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
19438
19694
|
// Gas is estimated server-side by the Earn Service and returned on the quote, so the
|
|
19439
19695
|
// SDK no longer simulates it locally. `chainDefinition` lets the fetch fill
|
|
19440
19696
|
// the native token symbol / blockchain on each gas entry.
|
|
19441
|
-
|
|
19697
|
+
const quote = await fetchDepositQuote({
|
|
19442
19698
|
vaultAddress: params.vaultAddress,
|
|
19443
19699
|
amount: params.amount,
|
|
19444
19700
|
address,
|
|
@@ -19446,6 +19702,34 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
19446
19702
|
config,
|
|
19447
19703
|
chainDefinition
|
|
19448
19704
|
});
|
|
19705
|
+
// Run the same pre-broadcast gas reserve check here so callers get an early
|
|
19706
|
+
// warning at quote time — before they call deposit() and hit the real check.
|
|
19707
|
+
// Only applies on stablecoin-gas chains (e.g. Arc) where the deposit token
|
|
19708
|
+
// is also the native gas token, and only for EOA wallets that pay native gas
|
|
19709
|
+
// from their own balance. Skips silently on all other chains and wallet types.
|
|
19710
|
+
if (chainDefinition?.type === 'evm' && isEvmAdapterLike(params.from.adapter) && !await isContractSigner(params.from.adapter, address, chainDefinition)) {
|
|
19711
|
+
const symbol = quote.deposit.symbol.toUpperCase();
|
|
19712
|
+
let depositTokenAddress = null;
|
|
19713
|
+
if (symbol === 'USDC') {
|
|
19714
|
+
depositTokenAddress = chainDefinition.usdcAddress;
|
|
19715
|
+
} else if (symbol === 'EURC') {
|
|
19716
|
+
depositTokenAddress = chainDefinition.eurcAddress;
|
|
19717
|
+
}
|
|
19718
|
+
if (depositTokenAddress != null) {
|
|
19719
|
+
const amountObj = Amount.parse(params.amount, {
|
|
19720
|
+
decimals: 6
|
|
19721
|
+
});
|
|
19722
|
+
await checkGasReserveForDeposit({
|
|
19723
|
+
adapter: params.from.adapter,
|
|
19724
|
+
chain: chainDefinition,
|
|
19725
|
+
address,
|
|
19726
|
+
depositTokenAddress,
|
|
19727
|
+
depositAmountRaw: amountObj.raw,
|
|
19728
|
+
gasFees: quote.gasFees
|
|
19729
|
+
});
|
|
19730
|
+
}
|
|
19731
|
+
}
|
|
19732
|
+
return quote;
|
|
19449
19733
|
}
|
|
19450
19734
|
/** {@inheritdoc} */ async getWithdrawalQuote(params) {
|
|
19451
19735
|
const config = this.resolveConfig(params.config);
|
|
@@ -19745,21 +20029,15 @@ function formatPositionPnL(pnl) {
|
|
|
19745
20029
|
// consumers keep receiving them until Contract.
|
|
19746
20030
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
19747
20031
|
const { totalDeposits, liquidity, liquidityProfile, ...rest } = vault;
|
|
19748
|
-
// `liquidityProfile` is `.optional()` in the response schema during the
|
|
19749
|
-
// expand/contract window (an old backend that predates the nested facets
|
|
19750
|
-
// omits it), so only format and re-attach it when present — matching the
|
|
19751
|
-
// provider-side `toVaultInfo` mapper.
|
|
19752
20032
|
return {
|
|
19753
20033
|
...rest,
|
|
19754
20034
|
totalDeposits: formatAmount(totalDeposits),
|
|
19755
20035
|
liquidity: formatAmount(liquidity),
|
|
19756
|
-
|
|
19757
|
-
liquidityProfile
|
|
19758
|
-
|
|
19759
|
-
|
|
19760
|
-
|
|
19761
|
-
totalSupply: formatAmount(liquidityProfile.totalSupply)
|
|
19762
|
-
}
|
|
20036
|
+
liquidityProfile: {
|
|
20037
|
+
...liquidityProfile,
|
|
20038
|
+
totalDeposits: formatAmount(liquidityProfile.totalDeposits),
|
|
20039
|
+
available: formatAmount(liquidityProfile.available),
|
|
20040
|
+
totalSupply: formatAmount(liquidityProfile.totalSupply)
|
|
19763
20041
|
}
|
|
19764
20042
|
};
|
|
19765
20043
|
}
|
|
@@ -20245,6 +20523,49 @@ const sourceAdapterContextSchema = zod.z.object({
|
|
|
20245
20523
|
vaultAddress: vaultAddressSchema,
|
|
20246
20524
|
config: earnConfigSchema.optional()
|
|
20247
20525
|
});
|
|
20526
|
+
/**
|
|
20527
|
+
* Schema for a successful gas fee entry (fees is non-null).
|
|
20528
|
+
*
|
|
20529
|
+
* Validates the kit shape — {@link EarnEstimatedGas} uses decimal strings for
|
|
20530
|
+
* `gas`, `gasPrice`, and `fee`. Only digits are accepted so that the deposit
|
|
20531
|
+
* operation can safely convert them to `bigint` before passing them to the
|
|
20532
|
+
* provider's `computeGasReserve`.
|
|
20533
|
+
*
|
|
20534
|
+
* @internal
|
|
20535
|
+
*/ const earnGasFeeEstimateSuccessSchema = zod.z.object({
|
|
20536
|
+
name: zod.z.string(),
|
|
20537
|
+
token: zod.z.string(),
|
|
20538
|
+
blockchain: zod.z.string(),
|
|
20539
|
+
fees: zod.z.object({
|
|
20540
|
+
gas: zod.z.string().regex(/^\d+$/, 'gas must be a non-negative integer string'),
|
|
20541
|
+
gasPrice: zod.z.string().regex(/^\d+$/, 'gasPrice must be a non-negative integer string'),
|
|
20542
|
+
fee: zod.z.string().regex(/^\d+$/, 'fee must be a non-negative integer string')
|
|
20543
|
+
}),
|
|
20544
|
+
error: zod.z.undefined().optional()
|
|
20545
|
+
}).strict();
|
|
20546
|
+
/**
|
|
20547
|
+
* Schema for a failed gas fee entry (fees is null, error is a string).
|
|
20548
|
+
*
|
|
20549
|
+
* @internal
|
|
20550
|
+
*/ const earnGasFeeEstimateFailureSchema = zod.z.object({
|
|
20551
|
+
name: zod.z.string(),
|
|
20552
|
+
token: zod.z.string(),
|
|
20553
|
+
blockchain: zod.z.string(),
|
|
20554
|
+
fees: zod.z.null(),
|
|
20555
|
+
error: zod.z.string()
|
|
20556
|
+
}).strict();
|
|
20557
|
+
/**
|
|
20558
|
+
* Schema for a single kit-level gas fee estimate entry.
|
|
20559
|
+
*
|
|
20560
|
+
* Rejects arbitrary inputs (null, numbers, empty objects, non-numeric fee
|
|
20561
|
+
* strings) so callers receive a descriptive validation error instead of a
|
|
20562
|
+
* raw `BigInt` conversion error inside `computeGasReserve`.
|
|
20563
|
+
*
|
|
20564
|
+
* @internal
|
|
20565
|
+
*/ const earnGasFeeEstimateSchema = zod.z.union([
|
|
20566
|
+
earnGasFeeEstimateSuccessSchema,
|
|
20567
|
+
earnGasFeeEstimateFailureSchema
|
|
20568
|
+
]);
|
|
20248
20569
|
/**
|
|
20249
20570
|
* Validation schema for the same-chain variant of DepositParams.
|
|
20250
20571
|
*
|
|
@@ -20257,6 +20578,7 @@ const sourceAdapterContextSchema = zod.z.object({
|
|
|
20257
20578
|
to: zod.z.undefined().optional(),
|
|
20258
20579
|
vaultAddress: vaultAddressSchema,
|
|
20259
20580
|
amount: amountSchema,
|
|
20581
|
+
gasFees: zod.z.array(earnGasFeeEstimateSchema).optional(),
|
|
20260
20582
|
config: earnConfigSchema.optional()
|
|
20261
20583
|
}).strict();
|
|
20262
20584
|
const crossChainDepositDestinationSchema = zod.z.object({
|
|
@@ -20877,6 +21199,9 @@ async function deposit$1(context, params) {
|
|
|
20877
21199
|
},
|
|
20878
21200
|
vaultAddress: params.vaultAddress,
|
|
20879
21201
|
amount: params.amount,
|
|
21202
|
+
...!hasExplicitDestination && params.gasFees !== undefined && {
|
|
21203
|
+
gasFees: toProviderGasFees(params.gasFees)
|
|
21204
|
+
},
|
|
20880
21205
|
...params.config !== undefined && {
|
|
20881
21206
|
config: params.config
|
|
20882
21207
|
}
|
|
@@ -20899,6 +21224,21 @@ async function deposit$1(context, params) {
|
|
|
20899
21224
|
}
|
|
20900
21225
|
return provider.deposit(baseParams);
|
|
20901
21226
|
}
|
|
21227
|
+
function toProviderGasFees(gasFees) {
|
|
21228
|
+
return gasFees.map((entry)=>{
|
|
21229
|
+
if (entry.fees === null) {
|
|
21230
|
+
return entry; // NOSONAR
|
|
21231
|
+
}
|
|
21232
|
+
return {
|
|
21233
|
+
...entry,
|
|
21234
|
+
fees: {
|
|
21235
|
+
...entry.fees,
|
|
21236
|
+
gas: BigInt(entry.fees.gas),
|
|
21237
|
+
gasPrice: BigInt(entry.fees.gasPrice)
|
|
21238
|
+
}
|
|
21239
|
+
};
|
|
21240
|
+
});
|
|
21241
|
+
}
|
|
20902
21242
|
function hasCrossChainDestination(params) {
|
|
20903
21243
|
// Runs before schema validation, so guard against non-object params from
|
|
20904
21244
|
// untyped callers; assertEarnParams reports those with a structured error.
|
package/earn.d.cts
CHANGED
|
@@ -6872,12 +6872,9 @@ interface CollateralInfo {
|
|
|
6872
6872
|
* (e.g., 0.6 = 60%).
|
|
6873
6873
|
*
|
|
6874
6874
|
* Populated for Morpho V1 vaults; `null` when the underlying product
|
|
6875
|
-
* exposes no per-market allocation (e.g. Morpho V2).
|
|
6876
|
-
* backend that predates this field omits it entirely, matching the other
|
|
6877
|
-
* optional facets on {@link EarnOpportunityBase}; a future release makes it
|
|
6878
|
-
* required once every backend emits it.
|
|
6875
|
+
* exposes no per-market allocation (e.g. Morpho V2).
|
|
6879
6876
|
*/
|
|
6880
|
-
readonly allocationPct
|
|
6877
|
+
readonly allocationPct: number | null;
|
|
6881
6878
|
}
|
|
6882
6879
|
/**
|
|
6883
6880
|
* Vault warning from the underlying earn protocol.
|
|
@@ -7047,19 +7044,19 @@ interface EarnOpportunityBase {
|
|
|
7047
7044
|
/** Reward tokens distributed by this opportunity. */
|
|
7048
7045
|
readonly rewards: readonly VaultRewardInfo[];
|
|
7049
7046
|
/** Primary on-chain address (protocol-neutral; replaces vaultAddress). */
|
|
7050
|
-
readonly address
|
|
7047
|
+
readonly address: string;
|
|
7051
7048
|
/** RFC3339 freshness timestamp: provider state ts, else cache sync time. */
|
|
7052
|
-
readonly asOf
|
|
7049
|
+
readonly asOf: string;
|
|
7053
7050
|
/** Manager/curator identity; `null` when the product has no manager. */
|
|
7054
|
-
readonly manager
|
|
7051
|
+
readonly manager: ManagerInfo | null;
|
|
7055
7052
|
/** Yield profile including trailing averages. */
|
|
7056
|
-
readonly apyProfile
|
|
7053
|
+
readonly apyProfile: ApyProfile;
|
|
7057
7054
|
/** Fee split. */
|
|
7058
|
-
readonly fee
|
|
7055
|
+
readonly fee: FeeInfo;
|
|
7059
7056
|
/** Liquidity profile. */
|
|
7060
|
-
readonly liquidityProfile
|
|
7057
|
+
readonly liquidityProfile: LiquidityProfile;
|
|
7061
7058
|
/** Risk signals. */
|
|
7062
|
-
readonly riskSignals
|
|
7059
|
+
readonly riskSignals: RiskSignals;
|
|
7063
7060
|
/** @deprecated use {@link EarnOpportunityBase.address} */
|
|
7064
7061
|
readonly vaultAddress: string;
|
|
7065
7062
|
/** @deprecated use {@link ApyProfile.current} via apyProfile */
|
|
@@ -7126,13 +7123,9 @@ interface VaultOpportunity extends EarnOpportunityBase {
|
|
|
7126
7123
|
/**
|
|
7127
7124
|
* Universal discriminator identifying the opportunity shape.
|
|
7128
7125
|
*
|
|
7129
|
-
*
|
|
7130
|
-
* the other optional facets — so this release stays source-compatible for
|
|
7131
|
-
* code that constructs the type. A future release makes it required once
|
|
7132
|
-
* every backend emits it. Always present on responses from an emitting
|
|
7133
|
-
* backend; narrow on it before reading product-specific fields.
|
|
7126
|
+
* Narrow on `productType` before reading product-specific fields.
|
|
7134
7127
|
*/
|
|
7135
|
-
readonly productType
|
|
7128
|
+
readonly productType: 'vault';
|
|
7136
7129
|
/** Collateral markets backing this vault. */
|
|
7137
7130
|
readonly collateral: readonly CollateralInfo[];
|
|
7138
7131
|
}
|
|
@@ -8158,6 +8151,12 @@ interface SameChainDepositParams<TFromAdapterCapabilities extends AdapterCapabil
|
|
|
8158
8151
|
* @example '100.50' for 100.50 USDC
|
|
8159
8152
|
*/
|
|
8160
8153
|
readonly amount: string;
|
|
8154
|
+
/**
|
|
8155
|
+
* Gas fee estimates from a prior {@link getDepositQuote} call. When provided,
|
|
8156
|
+
* the pre-broadcast gas reserve check uses the actual estimated cost instead
|
|
8157
|
+
* of the conservative fallback reserve.
|
|
8158
|
+
*/
|
|
8159
|
+
readonly gasFees?: readonly EarnGasFeeEstimate[] | undefined;
|
|
8161
8160
|
/** Optional earn configuration. */
|
|
8162
8161
|
readonly config?: EarnConfig | undefined;
|
|
8163
8162
|
}
|
|
@@ -8488,14 +8487,8 @@ type EarnVaultInfo = DistributiveOmit<EarnOpportunity, 'totalDeposits' | 'liquid
|
|
|
8488
8487
|
readonly totalDeposits: string;
|
|
8489
8488
|
/** Available liquidity in the vault in human-readable decimal format. */
|
|
8490
8489
|
readonly liquidity: string;
|
|
8491
|
-
/**
|
|
8492
|
-
|
|
8493
|
-
*
|
|
8494
|
-
* Optional during the expand/contract migration window: a backend that
|
|
8495
|
-
* predates the nested facets omits it, so it is absent until the response
|
|
8496
|
-
* carries it.
|
|
8497
|
-
*/
|
|
8498
|
-
readonly liquidityProfile?: EarnLiquidityProfile;
|
|
8490
|
+
/** Liquidity profile with amounts as human-readable decimal strings. */
|
|
8491
|
+
readonly liquidityProfile: EarnLiquidityProfile;
|
|
8499
8492
|
};
|
|
8500
8493
|
/** Result of a batch vault lookup. */
|
|
8501
8494
|
type EarnGetVaultsResult = Omit<GetVaultsResult, 'vaults'> & {
|