@circle-fin/app-kit 1.9.0 → 1.10.0
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 +26 -0
- package/bridge.cjs +622 -26
- package/bridge.d.cts +147 -10
- package/bridge.d.mts +147 -10
- package/bridge.d.ts +147 -10
- package/bridge.mjs +622 -26
- package/chains.cjs +8 -2
- package/chains.d.cts +1 -0
- package/chains.d.mts +1 -0
- package/chains.d.ts +1 -0
- package/chains.mjs +8 -2
- package/context.cjs +1 -0
- package/context.d.cts +152 -12
- package/context.d.mts +152 -12
- package/context.d.ts +152 -12
- package/context.mjs +1 -0
- package/earn.cjs +859 -420
- package/earn.d.cts +521 -94
- package/earn.d.mts +521 -94
- package/earn.d.ts +521 -94
- package/earn.mjs +859 -421
- package/estimateBridge.cjs +622 -26
- package/estimateBridge.d.cts +147 -10
- package/estimateBridge.d.mts +147 -10
- package/estimateBridge.d.ts +147 -10
- package/estimateBridge.mjs +622 -26
- package/estimateSwap.cjs +810 -92
- package/estimateSwap.d.cts +147 -10
- package/estimateSwap.d.mts +147 -10
- package/estimateSwap.d.ts +147 -10
- package/estimateSwap.mjs +810 -92
- package/index.cjs +2450 -645
- package/index.d.cts +1003 -126
- package/index.d.mts +1003 -126
- package/index.d.ts +1003 -126
- package/index.mjs +2450 -645
- package/package.json +6 -6
- package/swap.cjs +810 -92
- package/swap.d.cts +147 -10
- package/swap.d.mts +147 -10
- package/swap.d.ts +147 -10
- package/swap.mjs +810 -92
- package/unifiedBalance.cjs +722 -115
- package/unifiedBalance.d.cts +222 -4
- package/unifiedBalance.d.mts +222 -4
- package/unifiedBalance.d.ts +222 -4
- package/unifiedBalance.mjs +722 -115
package/index.cjs
CHANGED
|
@@ -22,9 +22,9 @@ var zod = require('zod');
|
|
|
22
22
|
var pino = require('pino');
|
|
23
23
|
var units = require('@ethersproject/units');
|
|
24
24
|
var bytes = require('@ethersproject/bytes');
|
|
25
|
+
require('@ethersproject/abi');
|
|
25
26
|
var address = require('@ethersproject/address');
|
|
26
27
|
var bs58 = require('bs58');
|
|
27
|
-
require('@ethersproject/abi');
|
|
28
28
|
var web3_js = require('@solana/web3.js');
|
|
29
29
|
require('bn.js');
|
|
30
30
|
require('@coral-xyz/anchor');
|
|
@@ -68,6 +68,7 @@ var bs58__default = /*#__PURE__*/_interopDefault(bs58);
|
|
|
68
68
|
...params,
|
|
69
69
|
actions: {
|
|
70
70
|
bridge: {},
|
|
71
|
+
earn: {},
|
|
71
72
|
...params.actions
|
|
72
73
|
}
|
|
73
74
|
};
|
|
@@ -3551,14 +3552,14 @@ class KitError extends Error {
|
|
|
3551
3552
|
}
|
|
3552
3553
|
|
|
3553
3554
|
/**
|
|
3554
|
-
* Standardized error definitions for Earn
|
|
3555
|
+
* Standardized error definitions for Earn operations.
|
|
3555
3556
|
*
|
|
3556
3557
|
* These error codes provide fine-grained categorization of failures
|
|
3557
|
-
* from the
|
|
3558
|
+
* from the Earn service, enabling SDK consumers to distinguish
|
|
3558
3559
|
* between input errors (fix your request) and service errors (retry later).
|
|
3559
3560
|
*
|
|
3560
3561
|
* Error code ranges:
|
|
3561
|
-
* - 1100-
|
|
3562
|
+
* - 1100-1106: INPUT errors — invalid, unsupported, or stale request state
|
|
3562
3563
|
* - 8100-8105: SERVICE errors — retryable backend/provider failures
|
|
3563
3564
|
*
|
|
3564
3565
|
* @example
|
|
@@ -3611,6 +3612,14 @@ class KitError extends Error {
|
|
|
3611
3612
|
name: 'EARN_UNSUPPORTED_BRIDGE_ROUTE',
|
|
3612
3613
|
type: 'INPUT'
|
|
3613
3614
|
},
|
|
3615
|
+
/**
|
|
3616
|
+
* The bridge quote expired. This is an INPUT error because the prepared
|
|
3617
|
+
* request is stale and must be replaced instead of retried.
|
|
3618
|
+
*/ BRIDGE_QUOTE_EXPIRED: {
|
|
3619
|
+
code: 1106,
|
|
3620
|
+
name: 'EARN_BRIDGE_QUOTE_EXPIRED',
|
|
3621
|
+
type: 'INPUT'
|
|
3622
|
+
},
|
|
3614
3623
|
/** The proxy signing call failed — retryable. */ SIGNING_FAILED: {
|
|
3615
3624
|
code: 8100,
|
|
3616
3625
|
name: 'EARN_SIGNING_FAILED',
|
|
@@ -3670,6 +3679,9 @@ function getOptionalString(value) {
|
|
|
3670
3679
|
* internal-error, vault-refresh-busy, off-chain-paused, position-PnL-pending,
|
|
3671
3680
|
* bridge failures/status lookup failures
|
|
3672
3681
|
*
|
|
3682
|
+
* Quote expiry is INPUT/FATAL because callers must start a fresh bridge prepare
|
|
3683
|
+
* flow rather than retry the stale prepared bundle.
|
|
3684
|
+
*
|
|
3673
3685
|
* Unrecognized codes fall through to `parseApiError` for HTTP-status-based
|
|
3674
3686
|
* handling.
|
|
3675
3687
|
*
|
|
@@ -3903,6 +3915,13 @@ function getOptionalString(value) {
|
|
|
3903
3915
|
errorDef: EarnError.PROVIDER_ERROR,
|
|
3904
3916
|
recoverability: 'FATAL'
|
|
3905
3917
|
}
|
|
3918
|
+
],
|
|
3919
|
+
[
|
|
3920
|
+
380506,
|
|
3921
|
+
{
|
|
3922
|
+
errorDef: EarnError.BRIDGE_QUOTE_EXPIRED,
|
|
3923
|
+
recoverability: 'FATAL'
|
|
3924
|
+
}
|
|
3906
3925
|
]
|
|
3907
3926
|
]);
|
|
3908
3927
|
/**
|
|
@@ -4647,7 +4666,10 @@ exports.EarnChain = void 0;
|
|
|
4647
4666
|
contracts: {
|
|
4648
4667
|
v1: {
|
|
4649
4668
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
4650
|
-
minter: GATEWAY_MINTER_EVM_TESTNET
|
|
4669
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
4670
|
+
// DepositForHandler the GenericExecutor calls to run a fast cross-chain
|
|
4671
|
+
// deposit into the GatewayWallet above.
|
|
4672
|
+
depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
|
|
4651
4673
|
}
|
|
4652
4674
|
},
|
|
4653
4675
|
forwarderSupported: {
|
|
@@ -7802,7 +7824,10 @@ var Chains = {
|
|
|
7802
7824
|
minter: zod.z.string({
|
|
7803
7825
|
required_error: 'Gateway minter address is required. Please provide a valid contract address.',
|
|
7804
7826
|
invalid_type_error: 'Gateway minter address must be a string.'
|
|
7805
|
-
}).min(1, 'Gateway minter address cannot be empty.')
|
|
7827
|
+
}).min(1, 'Gateway minter address cannot be empty.'),
|
|
7828
|
+
depositForHandler: zod.z.string({
|
|
7829
|
+
invalid_type_error: 'Gateway depositForHandler address must be a string.'
|
|
7830
|
+
}).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
|
|
7806
7831
|
}).strict() // Reject any additional properties not defined in the schema
|
|
7807
7832
|
;
|
|
7808
7833
|
/**
|
|
@@ -8342,21 +8367,31 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8342
8367
|
* returning the appropriate address based on the requested contract type.
|
|
8343
8368
|
*
|
|
8344
8369
|
* @param chain - The chain definition to resolve the contract address for
|
|
8345
|
-
* @param contractType - The type of contract address to resolve ('tokenMessenger' or '
|
|
8370
|
+
* @param contractType - The type of contract address to resolve ('tokenMessenger', 'messageTransmitter', or 'tokenMessengerWithFees')
|
|
8346
8371
|
* @returns The contract address for the specified contract type
|
|
8347
8372
|
* @throws Error when chain does not support CCTP v2 or has unsupported contract configuration
|
|
8373
|
+
* @throws Error when 'tokenMessengerWithFees' is requested but not configured on the chain
|
|
8348
8374
|
*/ const resolveCCTPV2ContractAddress = (chain, contractType)=>{
|
|
8349
8375
|
// Handle custom bridge contract for tokenMessenger (burn transaction)
|
|
8350
|
-
if (hasCustomContractSupport(chain, 'bridge') && chain.kitContracts?.bridge !== undefined) {
|
|
8376
|
+
if (contractType === 'tokenMessenger' && hasCustomContractSupport(chain, 'bridge') && chain.kitContracts?.bridge !== undefined) {
|
|
8351
8377
|
return chain.kitContracts.bridge;
|
|
8352
8378
|
}
|
|
8353
8379
|
// At this point we know CCTP v2 is supported, so contracts exist
|
|
8354
8380
|
const cctpConfig = chain.cctp;
|
|
8355
8381
|
const contracts = cctpConfig.contracts.v2;
|
|
8382
|
+
// The `TokenMessengerWithFees` wrapper (prepaid FORWARD path) is an optional
|
|
8383
|
+
// deployment carried alongside both split and merged configurations.
|
|
8384
|
+
if (contractType === 'tokenMessengerWithFees') {
|
|
8385
|
+
const wrapper = contracts.tokenMessengerWithFees;
|
|
8386
|
+
if (wrapper === undefined || wrapper === '') {
|
|
8387
|
+
throw new Error(`TokenMessengerWithFees is not configured on chain ${chain.name}. The prepaid FORWARD path is unavailable on this chain.`);
|
|
8388
|
+
}
|
|
8389
|
+
return wrapper;
|
|
8390
|
+
}
|
|
8356
8391
|
// Handle different contract types with explicit type checking
|
|
8357
8392
|
switch(contracts.type){
|
|
8358
8393
|
case 'split':
|
|
8359
|
-
return contracts.tokenMessenger ;
|
|
8394
|
+
return contractType === 'tokenMessenger' ? contracts.tokenMessenger : contracts.messageTransmitter;
|
|
8360
8395
|
case 'merged':
|
|
8361
8396
|
return contracts.contract;
|
|
8362
8397
|
default:
|
|
@@ -11630,7 +11665,7 @@ function resolveOptions(options) {
|
|
|
11630
11665
|
}
|
|
11631
11666
|
|
|
11632
11667
|
var name$4 = "@circle-fin/bridge-kit";
|
|
11633
|
-
var version$5 = "1.12.
|
|
11668
|
+
var version$5 = "1.12.1";
|
|
11634
11669
|
var pkg$5 = {
|
|
11635
11670
|
name: name$4,
|
|
11636
11671
|
version: version$5};
|
|
@@ -14173,6 +14208,144 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14173
14208
|
return false;
|
|
14174
14209
|
};
|
|
14175
14210
|
|
|
14211
|
+
/**
|
|
14212
|
+
* The zero address, denoting a native-currency fee in a signed quote.
|
|
14213
|
+
*/ const ZERO_ADDRESS$1 = '0x0000000000000000000000000000000000000000';
|
|
14214
|
+
/**
|
|
14215
|
+
* Resolve the fee payment channel for a prepaid-FORWARD burn via `TokenMessengerWithFees`.
|
|
14216
|
+
*
|
|
14217
|
+
* Determines the native `msg.value` and the ERC-20 approvals required, honouring
|
|
14218
|
+
* the quote's `feeToken`:
|
|
14219
|
+
* - Native fee (`feeToken` is the zero address): attach exactly `feeTotalAmount`
|
|
14220
|
+
* as `msg.value`; approve only the burn amount.
|
|
14221
|
+
* - ERC-20 fee equal to the burn token (both USDC — the `isBurnTokenFee` case):
|
|
14222
|
+
* approve a single combined `amount + feeTotalAmount` and skip the redundant
|
|
14223
|
+
* second approval.
|
|
14224
|
+
* - ERC-20 fee different from the burn token: approve the burn amount and the fee
|
|
14225
|
+
* amount separately.
|
|
14226
|
+
*
|
|
14227
|
+
* This encodes only balance/allowance intent; it does not fetch balances. The
|
|
14228
|
+
* caller is responsible for a balance preflight against the fresh quote.
|
|
14229
|
+
*
|
|
14230
|
+
* @param params - The fee token, burn token, burn amount, and total fee amount.
|
|
14231
|
+
* @returns The resolved fee payment plan.
|
|
14232
|
+
* @throws KitError if `amount` or `feeTotalAmount` is negative.
|
|
14233
|
+
*
|
|
14234
|
+
* @example
|
|
14235
|
+
* ```typescript
|
|
14236
|
+
* // Native fee
|
|
14237
|
+
* resolveFeePayment({
|
|
14238
|
+
* feeToken: '0x0000000000000000000000000000000000000000',
|
|
14239
|
+
* burnToken: '0xUSDC...',
|
|
14240
|
+
* amount: 1_000_000n,
|
|
14241
|
+
* feeTotalAmount: 3_500_000n,
|
|
14242
|
+
* })
|
|
14243
|
+
* // → { isNativeFee: true, isBurnTokenFee: false, nativeValue: 3_500_000n,
|
|
14244
|
+
* // approvals: [{ token: '0xUSDC...', amount: 1_000_000n }] }
|
|
14245
|
+
* ```
|
|
14246
|
+
*/ const resolveFeePayment = (params)=>{
|
|
14247
|
+
const { feeToken, burnToken, amount, feeTotalAmount } = params;
|
|
14248
|
+
if (typeof amount !== 'bigint' || amount < 0n) {
|
|
14249
|
+
throw createValidationFailedError$1('amount', amount, 'Must be a non-negative bigint');
|
|
14250
|
+
}
|
|
14251
|
+
if (typeof feeTotalAmount !== 'bigint' || feeTotalAmount < 0n) {
|
|
14252
|
+
throw createValidationFailedError$1('feeTotalAmount', feeTotalAmount, 'Must be a non-negative bigint');
|
|
14253
|
+
}
|
|
14254
|
+
const isNativeFee = feeToken.toLowerCase() === ZERO_ADDRESS$1;
|
|
14255
|
+
const isBurnTokenFee = !isNativeFee && feeToken.toLowerCase() === burnToken.toLowerCase();
|
|
14256
|
+
if (isNativeFee) {
|
|
14257
|
+
return {
|
|
14258
|
+
isNativeFee: true,
|
|
14259
|
+
isBurnTokenFee: false,
|
|
14260
|
+
nativeValue: feeTotalAmount,
|
|
14261
|
+
approvals: [
|
|
14262
|
+
{
|
|
14263
|
+
token: burnToken,
|
|
14264
|
+
amount
|
|
14265
|
+
}
|
|
14266
|
+
]
|
|
14267
|
+
};
|
|
14268
|
+
}
|
|
14269
|
+
if (isBurnTokenFee) {
|
|
14270
|
+
// Fee and burn draw on the same token — a single combined approval covers
|
|
14271
|
+
// both; the redundant second approval is skipped.
|
|
14272
|
+
return {
|
|
14273
|
+
isNativeFee: false,
|
|
14274
|
+
isBurnTokenFee: true,
|
|
14275
|
+
nativeValue: 0n,
|
|
14276
|
+
approvals: [
|
|
14277
|
+
{
|
|
14278
|
+
token: burnToken,
|
|
14279
|
+
amount: amount + feeTotalAmount
|
|
14280
|
+
}
|
|
14281
|
+
]
|
|
14282
|
+
};
|
|
14283
|
+
}
|
|
14284
|
+
return {
|
|
14285
|
+
isNativeFee: false,
|
|
14286
|
+
isBurnTokenFee: false,
|
|
14287
|
+
nativeValue: 0n,
|
|
14288
|
+
approvals: [
|
|
14289
|
+
{
|
|
14290
|
+
token: burnToken,
|
|
14291
|
+
amount
|
|
14292
|
+
},
|
|
14293
|
+
{
|
|
14294
|
+
token: feeToken,
|
|
14295
|
+
amount: feeTotalAmount
|
|
14296
|
+
}
|
|
14297
|
+
]
|
|
14298
|
+
};
|
|
14299
|
+
};
|
|
14300
|
+
|
|
14301
|
+
/**
|
|
14302
|
+
* The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
|
|
14303
|
+
* hookData must start with.
|
|
14304
|
+
*/ const CCTP_FORWARD_MAGIC_HEX = Buffer.from(CCTP_FORWARD_MAGIC_PREFIX, 'ascii').toString('hex');
|
|
14305
|
+
/**
|
|
14306
|
+
* Determine whether a hookData blob begins with the `cctp-forward` envelope.
|
|
14307
|
+
*
|
|
14308
|
+
* The prepaid FORWARD path requires the GenericExecutor blob to be wrapped in a
|
|
14309
|
+
* `cctp-forward` frame; without it the wrapper reverts `ForwardFeeWithoutHook`.
|
|
14310
|
+
*
|
|
14311
|
+
* @param hookData - The 0x-prefixed hookData hex string.
|
|
14312
|
+
* @returns True when the hookData starts with the `cctp-forward` magic.
|
|
14313
|
+
*
|
|
14314
|
+
* @example
|
|
14315
|
+
* ```typescript
|
|
14316
|
+
* hasForwardHook('0x636374702d666f7277617264...') // true
|
|
14317
|
+
* hasForwardHook('0xdeadbeef') // false
|
|
14318
|
+
* ```
|
|
14319
|
+
*/ const hasForwardHook = (hookData)=>{
|
|
14320
|
+
if (typeof hookData !== 'string') {
|
|
14321
|
+
return false;
|
|
14322
|
+
}
|
|
14323
|
+
const normalized = (hookData.startsWith('0x') ? hookData.slice(2) : hookData).toLowerCase();
|
|
14324
|
+
return normalized.startsWith(CCTP_FORWARD_MAGIC_HEX);
|
|
14325
|
+
};
|
|
14326
|
+
/**
|
|
14327
|
+
* Assert that a hookData blob is forward-friendly for the prepaid FORWARD path.
|
|
14328
|
+
*
|
|
14329
|
+
* The prepaid FORWARD path always requests a FORWARD fee item, so the wrapper
|
|
14330
|
+
* requires the hookData to start with a `cctp-forward` frame. Validating this
|
|
14331
|
+
* before the burn surfaces the guaranteed `ForwardFeeWithoutHook` revert as a
|
|
14332
|
+
* typed input error instead of an on-chain failure.
|
|
14333
|
+
*
|
|
14334
|
+
* @param hookData - The 0x-prefixed hookData hex string.
|
|
14335
|
+
* @throws KitError (`INPUT_VALIDATION_FAILED`) if the hookData is missing or lacks
|
|
14336
|
+
* the `cctp-forward` frame.
|
|
14337
|
+
*
|
|
14338
|
+
* @example
|
|
14339
|
+
* ```typescript
|
|
14340
|
+
* assertForwardHookData(geForwardHookData) // ok
|
|
14341
|
+
* assertForwardHookData('0xdeadbeef') // throws — would revert ForwardFeeWithoutHook
|
|
14342
|
+
* ```
|
|
14343
|
+
*/ const assertForwardHookData = (hookData)=>{
|
|
14344
|
+
if (!hasForwardHook(hookData)) {
|
|
14345
|
+
throw createValidationFailedError$1('hookData', hookData, 'Prepaid FORWARD burns require a cctp-forward-wrapped hookData; without it the TokenMessengerWithFees wrapper reverts ForwardFeeWithoutHook');
|
|
14346
|
+
}
|
|
14347
|
+
};
|
|
14348
|
+
|
|
14176
14349
|
/**
|
|
14177
14350
|
* Type guard to validate the forwardFee object structure.
|
|
14178
14351
|
*
|
|
@@ -15104,6 +15277,109 @@ const assertCCTPv2BridgeParamsSymbol = Symbol('assertCCTPv2BridgeParams');
|
|
|
15104
15277
|
}
|
|
15105
15278
|
}
|
|
15106
15279
|
|
|
15280
|
+
/**
|
|
15281
|
+
* Assert that `params` is a well-formed {@link BurnWithFeesParams} object.
|
|
15282
|
+
*
|
|
15283
|
+
* Validates the full public-boundary input before any field destructuring,
|
|
15284
|
+
* `BigInt()` coercion, or adapter preparation runs, so malformed JS-caller
|
|
15285
|
+
* inputs always produce typed `KitError` validation failures.
|
|
15286
|
+
*
|
|
15287
|
+
* Checks performed (in order):
|
|
15288
|
+
* - `params` must be a non-null plain object
|
|
15289
|
+
* - `source` — valid CCTP v2 wallet context (via `assertCCTPv2WalletContext`)
|
|
15290
|
+
* - `destinationChain` — present and supports CCTP v2
|
|
15291
|
+
* - source and destination chains must both be testnet or both mainnet
|
|
15292
|
+
* - source and destination chains must differ
|
|
15293
|
+
* - `executor` — non-empty string
|
|
15294
|
+
* - `amount` — bigint or non-empty string coercible to bigint
|
|
15295
|
+
* - `feeTotalAmount` — bigint or non-empty string coercible to bigint
|
|
15296
|
+
* - `feeToken` — valid EVM address (`0x` + 40 hex chars)
|
|
15297
|
+
* - `claim.signedQuote` — valid `0x`-prefixed hex string
|
|
15298
|
+
* - `claim.refundAddress` — valid EVM address
|
|
15299
|
+
* - `hookData` — valid `0x`-prefixed hex string when present
|
|
15300
|
+
*
|
|
15301
|
+
* @param params - The value to validate.
|
|
15302
|
+
* @throws {KitError} If any field is missing or invalid.
|
|
15303
|
+
*
|
|
15304
|
+
* @example
|
|
15305
|
+
* ```typescript
|
|
15306
|
+
* assertBurnWithFeesParams(params)
|
|
15307
|
+
* // params is now typed as BurnWithFeesParams and safe to use
|
|
15308
|
+
* const { source, destinationChain, amount } = params
|
|
15309
|
+
* ```
|
|
15310
|
+
*/ function assertBurnWithFeesParams(params) {
|
|
15311
|
+
if (params === null || typeof params !== 'object' || Array.isArray(params)) {
|
|
15312
|
+
throw createValidationFailedError$1('params', params, 'Must be a non-null plain object');
|
|
15313
|
+
}
|
|
15314
|
+
const p = params;
|
|
15315
|
+
// Source wallet context
|
|
15316
|
+
assertCCTPv2WalletContext(p['source']);
|
|
15317
|
+
const source = p['source'];
|
|
15318
|
+
// destinationChain
|
|
15319
|
+
const destinationChain = p['destinationChain'];
|
|
15320
|
+
if (destinationChain === null || destinationChain === undefined || typeof destinationChain !== 'object') {
|
|
15321
|
+
throw createValidationFailedError$1('destinationChain', destinationChain, 'Must be a chain definition object');
|
|
15322
|
+
}
|
|
15323
|
+
if (!isCCTPV2Supported(destinationChain)) {
|
|
15324
|
+
throw createValidationFailedError$1('destinationChain', destinationChain, 'destinationChain must support CCTP v2');
|
|
15325
|
+
}
|
|
15326
|
+
const dest = destinationChain;
|
|
15327
|
+
// Testnet / mainnet mismatch
|
|
15328
|
+
if (source.chain.isTestnet !== dest.isTestnet) {
|
|
15329
|
+
throw createNetworkMismatchError(source.chain, dest);
|
|
15330
|
+
}
|
|
15331
|
+
// Same-chain guard
|
|
15332
|
+
if (source.chain.name === dest.name) {
|
|
15333
|
+
throw createUnsupportedRouteError(source.chain.name, dest.name);
|
|
15334
|
+
}
|
|
15335
|
+
// executor
|
|
15336
|
+
const executor = p['executor'];
|
|
15337
|
+
if (typeof executor !== 'string' || executor === '') {
|
|
15338
|
+
throw createValidationFailedError$1('executor', executor, 'A GenericExecutor address is required (used for both mintRecipient and destinationCaller)');
|
|
15339
|
+
}
|
|
15340
|
+
// amount
|
|
15341
|
+
const rawAmount = p['amount'];
|
|
15342
|
+
if (typeof rawAmount !== 'bigint' && typeof rawAmount !== 'string' || rawAmount === '') {
|
|
15343
|
+
throw createValidationFailedError$1('amount', rawAmount, 'Must be a bigint or a numeric string');
|
|
15344
|
+
}
|
|
15345
|
+
try {
|
|
15346
|
+
BigInt(rawAmount);
|
|
15347
|
+
} catch {
|
|
15348
|
+
throw createValidationFailedError$1('amount', rawAmount, 'Must be a valid integer value coercible to bigint');
|
|
15349
|
+
}
|
|
15350
|
+
// feeTotalAmount
|
|
15351
|
+
const rawFee = p['feeTotalAmount'];
|
|
15352
|
+
if (typeof rawFee !== 'bigint' && typeof rawFee !== 'string' || rawFee === '') {
|
|
15353
|
+
throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a bigint or a numeric string');
|
|
15354
|
+
}
|
|
15355
|
+
try {
|
|
15356
|
+
BigInt(rawFee);
|
|
15357
|
+
} catch {
|
|
15358
|
+
throw createValidationFailedError$1('feeTotalAmount', rawFee, 'Must be a valid integer value coercible to bigint');
|
|
15359
|
+
}
|
|
15360
|
+
// feeToken
|
|
15361
|
+
if (!evmAddressSchema.safeParse(p['feeToken']).success) {
|
|
15362
|
+
throw createValidationFailedError$1('feeToken', p['feeToken'], 'Must be a valid EVM address (use the zero address for a native fee)');
|
|
15363
|
+
}
|
|
15364
|
+
// claim
|
|
15365
|
+
const rawClaim = p['claim'];
|
|
15366
|
+
if (rawClaim === null || typeof rawClaim !== 'object' || Array.isArray(rawClaim)) {
|
|
15367
|
+
throw createValidationFailedError$1('claim', rawClaim, 'Must be an object with signedQuote and refundAddress');
|
|
15368
|
+
}
|
|
15369
|
+
const claim = rawClaim;
|
|
15370
|
+
if (!hexStringSchema.safeParse(claim['signedQuote']).success) {
|
|
15371
|
+
throw createValidationFailedError$1('claim.signedQuote', claim['signedQuote'], 'Must be a valid hex string starting with 0x');
|
|
15372
|
+
}
|
|
15373
|
+
if (!evmAddressSchema.safeParse(claim['refundAddress']).success) {
|
|
15374
|
+
throw createValidationFailedError$1('claim.refundAddress', claim['refundAddress'], 'Must be a valid EVM address');
|
|
15375
|
+
}
|
|
15376
|
+
// hookData (optional)
|
|
15377
|
+
const hookData = p['hookData'];
|
|
15378
|
+
if (hookData !== undefined && hookData !== '' && !hexStringSchema.safeParse(hookData).success) {
|
|
15379
|
+
throw createValidationFailedError$1('hookData', hookData, 'Must be a valid hex string starting with 0x');
|
|
15380
|
+
}
|
|
15381
|
+
}
|
|
15382
|
+
|
|
15107
15383
|
/**
|
|
15108
15384
|
* CCTP bridge step names that can occur in the bridging flow.
|
|
15109
15385
|
*
|
|
@@ -16312,10 +16588,15 @@ const mockAttestationMessage = {
|
|
|
16312
16588
|
const burnCallData = burnRequest.getCallData();
|
|
16313
16589
|
// batchExecute may throw before submission (wallet declined) but never
|
|
16314
16590
|
// after — post-submission errors are returned as empty receipts.
|
|
16591
|
+
// The sender is threaded for adapters whose execution is routed through a
|
|
16592
|
+
// signing strategy (which has no wallet account to read it from); the
|
|
16593
|
+
// wallet-client path ignores it.
|
|
16315
16594
|
const batchResult = await adapter.batchExecute([
|
|
16316
16595
|
approveCallData,
|
|
16317
16596
|
burnCallData
|
|
16318
|
-
], chain
|
|
16597
|
+
], chain, {
|
|
16598
|
+
fromAddress: params.source.address
|
|
16599
|
+
});
|
|
16319
16600
|
const approveReceipt = batchResult.receipts[0];
|
|
16320
16601
|
const burnReceipt = batchResult.receipts[1];
|
|
16321
16602
|
const approveStep = await buildBatchedStep('approve', approveReceipt, batchResult.batchId, adapter, chain, batchResult.statusCode, batchResult.error);
|
|
@@ -16458,7 +16739,7 @@ const mockAttestationMessage = {
|
|
|
16458
16739
|
return step;
|
|
16459
16740
|
}
|
|
16460
16741
|
|
|
16461
|
-
var version$4 = "1.
|
|
16742
|
+
var version$4 = "1.10.0";
|
|
16462
16743
|
var pkg$4 = {
|
|
16463
16744
|
version: version$4};
|
|
16464
16745
|
|
|
@@ -17051,7 +17332,7 @@ var pkg$4 = {
|
|
|
17051
17332
|
* }
|
|
17052
17333
|
* )
|
|
17053
17334
|
* ```
|
|
17054
|
-
*/ async function retry(result, context, provider, invocationMeta) {
|
|
17335
|
+
*/ async function retry$1(result, context, provider, invocationMeta) {
|
|
17055
17336
|
const analysis = analyzeSteps(result);
|
|
17056
17337
|
// Resolve invocation context for retry operation
|
|
17057
17338
|
const resolvedInvocation = resolveRetryInvocation(invocationMeta);
|
|
@@ -17152,7 +17433,7 @@ var pkg$4 = {
|
|
|
17152
17433
|
// Continue with remaining steps.
|
|
17153
17434
|
// Recursive call handles subsequent pending states (e.g., next step may also
|
|
17154
17435
|
// be pending), allowing the retry logic to loop through all actionable steps.
|
|
17155
|
-
return await retry(result, context, provider);
|
|
17436
|
+
return await retry$1(result, context, provider);
|
|
17156
17437
|
} catch (error) {
|
|
17157
17438
|
// Re-throw FATAL validation errors - these indicate invalid input
|
|
17158
17439
|
if (isFatalError(error)) {
|
|
@@ -17366,7 +17647,7 @@ function assertCCTPV2Config(config) {
|
|
|
17366
17647
|
* const retryResult = await provider.retry(failedResult, retryContext)
|
|
17367
17648
|
* ```
|
|
17368
17649
|
*/ async retry(result, context, invocationMeta) {
|
|
17369
|
-
return retry(result, context, this, invocationMeta);
|
|
17650
|
+
return retry$1(result, context, this, invocationMeta);
|
|
17370
17651
|
}
|
|
17371
17652
|
/**
|
|
17372
17653
|
* Estimate the cost and fees for a CCTP v2 cross-chain bridge operation.
|
|
@@ -17613,7 +17894,7 @@ function assertCCTPV2Config(config) {
|
|
|
17613
17894
|
throw new Error(`Failed to resolve operation context: ${error instanceof Error ? error.message : String(error)}`);
|
|
17614
17895
|
}
|
|
17615
17896
|
// Resolve spender address with proper error handling
|
|
17616
|
-
const spenderAddress = resolveCCTPV2ContractAddress(chain);
|
|
17897
|
+
const spenderAddress = resolveCCTPV2ContractAddress(chain, 'tokenMessenger');
|
|
17617
17898
|
// Prepare action parameters
|
|
17618
17899
|
const actionParams = {
|
|
17619
17900
|
amount: BigInt(amount),
|
|
@@ -18093,6 +18374,106 @@ function assertCCTPV2Config(config) {
|
|
|
18093
18374
|
return await source.adapter.prepareAction('cctp.v2.depositForBurn', actionParams, resolvedContext);
|
|
18094
18375
|
}
|
|
18095
18376
|
/**
|
|
18377
|
+
* Prepare a prepaid-FORWARD burn through the `TokenMessengerWithFees` wrapper.
|
|
18378
|
+
*
|
|
18379
|
+
* Builds the source-chain `depositForBurnWithHookAndFees` call for the
|
|
18380
|
+
* GenericExecutor FORWARD path: fees are collected up front on the source chain
|
|
18381
|
+
* against a signed quote, `mintRecipient` and `destinationCaller` are both set to
|
|
18382
|
+
* the GenericExecutor, and the GE `hookData` is passed through unchanged.
|
|
18383
|
+
*
|
|
18384
|
+
* This is the low-level on-chain primitive behind the UBK `fastCrossChainDeposit`
|
|
18385
|
+
* and Bridge Kit `bridge({ deposit })` flows. The `hookData` and signed-quote
|
|
18386
|
+
* `claim` are produced elsewhere and passed in here:
|
|
18387
|
+
* - `hookData`: `buildForwardingHookDataWithPayload(version,
|
|
18388
|
+
* buildDepositForGenericExecutorPayload(...).hookData)` from `@core/utils`.
|
|
18389
|
+
* - `claim.signedQuote` / `feeToken` / `feeTotalAmount`: from `fetchFeeQuote`
|
|
18390
|
+
* (`@circle-fin/provider-fee-v1`), whose FORWARD item must be bound to the
|
|
18391
|
+
* SAME `hookData` and executor `destinationCaller` used here.
|
|
18392
|
+
*
|
|
18393
|
+
* The returned approvals and burn are NOT executed — the caller executes the
|
|
18394
|
+
* approvals first (in order) and then the burn. The fee payment channel matches
|
|
18395
|
+
* the quote's `feeToken`:
|
|
18396
|
+
* - Native fee: exactly `feeTotalAmount` is attached as the burn's `msg.value`;
|
|
18397
|
+
* only the burn amount is approved.
|
|
18398
|
+
* - USDC fee (same token as the burn): a single combined `amount + feeTotalAmount`
|
|
18399
|
+
* approval covers both; the redundant second approval is skipped.
|
|
18400
|
+
*
|
|
18401
|
+
* @typeParam TFromAdapterCapabilities - The source adapter's capabilities.
|
|
18402
|
+
* @param params - The burn amount, executor, hookData, signed-quote claim, and fee.
|
|
18403
|
+
* @returns The prepared approvals, the prepared burn, and the resolved fee plan.
|
|
18404
|
+
* @throws {KitError} If the wallet context is invalid, `destinationChain` does not
|
|
18405
|
+
* support CCTP v2, the executor is missing, `amount` or `feeTotalAmount` is not
|
|
18406
|
+
* a bigint or a numeric string coercible to bigint, the hookData lacks a
|
|
18407
|
+
* `cctp-forward` frame (guaranteed `ForwardFeeWithoutHook`), or the operation
|
|
18408
|
+
* context cannot be resolved.
|
|
18409
|
+
*
|
|
18410
|
+
* @example
|
|
18411
|
+
* ```typescript
|
|
18412
|
+
* const { approvals, burn } = await provider.burnWithFees({
|
|
18413
|
+
* source,
|
|
18414
|
+
* destinationChain: Arc,
|
|
18415
|
+
* amount: 1_000_000n,
|
|
18416
|
+
* executor: genericExecutorAddress,
|
|
18417
|
+
* hookData: geForwardHookData,
|
|
18418
|
+
* claim: { signedQuote: '0x01...', refundAddress: userAddress },
|
|
18419
|
+
* feeToken: '0x0000000000000000000000000000000000000000', // native
|
|
18420
|
+
* feeTotalAmount: 3_500_000n,
|
|
18421
|
+
* })
|
|
18422
|
+
* for (const approval of approvals) await approval.execute()
|
|
18423
|
+
* const txHash = await burn.execute()
|
|
18424
|
+
* ```
|
|
18425
|
+
*/ async burnWithFees(params) {
|
|
18426
|
+
assertBurnWithFeesParams(params);
|
|
18427
|
+
const { source, destinationChain, executor, hookData, claim, feeToken } = params;
|
|
18428
|
+
const amount = BigInt(params.amount);
|
|
18429
|
+
const feeTotalAmount = BigInt(params.feeTotalAmount);
|
|
18430
|
+
// Coupling guard: the prepaid FORWARD path always requests a FORWARD fee item,
|
|
18431
|
+
// so the hookData must carry a cctp-forward frame; otherwise the wrapper
|
|
18432
|
+
// reverts ForwardFeeWithoutHook. Surface it as a typed input error up front.
|
|
18433
|
+
assertForwardHookData(hookData);
|
|
18434
|
+
const burnToken = source.chain.usdcAddress;
|
|
18435
|
+
const feePayment = resolveFeePayment({
|
|
18436
|
+
feeToken,
|
|
18437
|
+
burnToken,
|
|
18438
|
+
amount,
|
|
18439
|
+
feeTotalAmount
|
|
18440
|
+
});
|
|
18441
|
+
// Resolve operation context from the source wallet context.
|
|
18442
|
+
const operationContext = this.extractOperationContext(source);
|
|
18443
|
+
let resolvedContext;
|
|
18444
|
+
try {
|
|
18445
|
+
resolvedContext = await resolveOperationContext(source.adapter, operationContext);
|
|
18446
|
+
} catch (error) {
|
|
18447
|
+
throw createValidationFailedError$1('source.adapter', undefined, `Failed to resolve operation context: ${error instanceof Error ? error.message : String(error)}`);
|
|
18448
|
+
}
|
|
18449
|
+
const context = resolvedContext;
|
|
18450
|
+
const wrapperAddress = resolveCCTPV2ContractAddress(source.chain, 'tokenMessengerWithFees');
|
|
18451
|
+
// Build the ERC-20 approvals to the wrapper (burn token, plus a distinct fee
|
|
18452
|
+
// token only when the fee is not paid in the burn token).
|
|
18453
|
+
const approvals = await Promise.all(feePayment.approvals.map(async (approval)=>source.adapter.prepareAction('token.approve', {
|
|
18454
|
+
tokenAddress: approval.token,
|
|
18455
|
+
delegate: wrapperAddress,
|
|
18456
|
+
amount: approval.amount
|
|
18457
|
+
}, context)));
|
|
18458
|
+
// Build the burn: mintRecipient AND destinationCaller are both the executor.
|
|
18459
|
+
const burn = await source.adapter.prepareAction('cctp.v2.depositForBurnWithFees', {
|
|
18460
|
+
fromChain: source.chain,
|
|
18461
|
+
toChain: destinationChain,
|
|
18462
|
+
amount,
|
|
18463
|
+
mintRecipient: executor,
|
|
18464
|
+
destinationCaller: executor,
|
|
18465
|
+
hookData,
|
|
18466
|
+
claim,
|
|
18467
|
+
feeToken,
|
|
18468
|
+
feeTotalAmount
|
|
18469
|
+
}, context);
|
|
18470
|
+
return {
|
|
18471
|
+
approvals,
|
|
18472
|
+
burn,
|
|
18473
|
+
feePayment
|
|
18474
|
+
};
|
|
18475
|
+
}
|
|
18476
|
+
/**
|
|
18096
18477
|
* Waits for a transaction to be mined and confirmed on the blockchain.
|
|
18097
18478
|
*
|
|
18098
18479
|
* This method should block until the transaction is confirmed on the blockchain.
|
|
@@ -18972,7 +19353,7 @@ registerKit(`${pkg$5.name}/${pkg$5.version}`);
|
|
|
18972
19353
|
};
|
|
18973
19354
|
|
|
18974
19355
|
var name$3 = "@circle-fin/swap-kit";
|
|
18975
|
-
var version$3 = "1.
|
|
19356
|
+
var version$3 = "1.4.0";
|
|
18976
19357
|
var pkg$3 = {
|
|
18977
19358
|
name: name$3,
|
|
18978
19359
|
version: version$3};
|
|
@@ -19037,7 +19418,10 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
19037
19418
|
}).min(1, 'kitKey must be a non-empty string').optional(),
|
|
19038
19419
|
provider: zod.z.string({
|
|
19039
19420
|
invalid_type_error: 'provider must be a string'
|
|
19040
|
-
}).min(1, 'provider must be a non-empty string').optional()
|
|
19421
|
+
}).min(1, 'provider must be a non-empty string').optional(),
|
|
19422
|
+
batchTransactions: zod.z.boolean({
|
|
19423
|
+
invalid_type_error: 'batchTransactions must be a boolean'
|
|
19424
|
+
}).optional()
|
|
19041
19425
|
});
|
|
19042
19426
|
/**
|
|
19043
19427
|
* Zod schema for adapter context.
|
|
@@ -19568,7 +19952,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
19568
19952
|
/**
|
|
19569
19953
|
* Circle Stablecoin Service API Key.
|
|
19570
19954
|
* Must be a valid API key format.
|
|
19571
|
-
*/ apiKey: apiKeySchema
|
|
19955
|
+
*/ apiKey: apiKeySchema.optional()
|
|
19572
19956
|
}).superRefine(requireCrossChainQuoteToAddress);
|
|
19573
19957
|
/**
|
|
19574
19958
|
* Zod schema for validating CreateSwapRequest parameters.
|
|
@@ -19626,7 +20010,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
19626
20010
|
/**
|
|
19627
20011
|
* Circle Stablecoin Service API Key.
|
|
19628
20012
|
* Must be a valid API key format.
|
|
19629
|
-
*/ apiKey: apiKeySchema
|
|
20013
|
+
*/ apiKey: apiKeySchema.optional()
|
|
19630
20014
|
});
|
|
19631
20015
|
/**
|
|
19632
20016
|
* Zod schema for validating GetSwapStatusResponse data.
|
|
@@ -19662,7 +20046,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
19662
20046
|
toChain: zod.z.string({
|
|
19663
20047
|
invalid_type_error: 'toChain must be a string'
|
|
19664
20048
|
}).min(1, 'toChain must be a non-empty string if provided').optional(),
|
|
19665
|
-
apiKey: apiKeySchema
|
|
20049
|
+
apiKey: apiKeySchema.optional()
|
|
19666
20050
|
});
|
|
19667
20051
|
/**
|
|
19668
20052
|
* Zod schema for validating CreateSwapResponse payloads.
|
|
@@ -19671,13 +20055,15 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
19671
20055
|
required_error: 'fee token is required',
|
|
19672
20056
|
invalid_type_error: 'fee token must be a string'
|
|
19673
20057
|
}).min(1, 'fee token must be a non-empty string'),
|
|
19674
|
-
amount: feeAmountSchema
|
|
20058
|
+
amount: feeAmountSchema,
|
|
20059
|
+
decimals: zod.z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
|
|
20060
|
+
symbol: zod.z.string({
|
|
20061
|
+
invalid_type_error: 'fee token symbol must be a string'
|
|
20062
|
+
}).min(1, 'fee token symbol must be a non-empty string').optional()
|
|
19675
20063
|
});
|
|
19676
20064
|
/**
|
|
19677
20065
|
* Developer fee item schema with basis field.
|
|
19678
|
-
*/ const createSwapDeveloperFeeItemSchema =
|
|
19679
|
-
token: zod.z.string().min(1, 'fee token must be a non-empty string'),
|
|
19680
|
-
amount: feeAmountSchema,
|
|
20066
|
+
*/ const createSwapDeveloperFeeItemSchema = createSwapFeeItemSchema.extend({
|
|
19681
20067
|
basis: zod.z.enum([
|
|
19682
20068
|
'inputAmount',
|
|
19683
20069
|
'estimatedAmount'
|
|
@@ -19769,7 +20155,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
19769
20155
|
addresses: zod.z.array(zod.z.string({
|
|
19770
20156
|
invalid_type_error: 'addresses entries must be strings'
|
|
19771
20157
|
}).min(1, 'addresses entries must be non-empty strings')).min(1, 'addresses must contain at least one entry when provided').max(MAX_RATE_ADDRESSES_PER_REQUEST, `addresses supports at most ${String(MAX_RATE_ADDRESSES_PER_REQUEST)} values per request`).optional(),
|
|
19772
|
-
apiKey: apiKeySchema
|
|
20158
|
+
apiKey: apiKeySchema.optional()
|
|
19773
20159
|
});
|
|
19774
20160
|
/**
|
|
19775
20161
|
* Zod schema for validating GetTokenRatesResponse payloads.
|
|
@@ -19986,7 +20372,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
19986
20372
|
...DEFAULT_CONFIG$1,
|
|
19987
20373
|
headers: {
|
|
19988
20374
|
...DEFAULT_CONFIG$1.headers,
|
|
19989
|
-
|
|
20375
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
20376
|
+
...apiKey !== undefined && {
|
|
20377
|
+
Authorization: `Bearer ${apiKey}`
|
|
20378
|
+
}
|
|
19990
20379
|
}
|
|
19991
20380
|
};
|
|
19992
20381
|
try {
|
|
@@ -20140,7 +20529,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20140
20529
|
...DEFAULT_CONFIG$1,
|
|
20141
20530
|
headers: {
|
|
20142
20531
|
...DEFAULT_CONFIG$1.headers,
|
|
20143
|
-
|
|
20532
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
20533
|
+
...validatedParams.apiKey !== undefined && {
|
|
20534
|
+
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
20535
|
+
}
|
|
20144
20536
|
}
|
|
20145
20537
|
};
|
|
20146
20538
|
return pollApiGet(url, isGetQuoteResponse, effectiveConfig);
|
|
@@ -20195,7 +20587,9 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20195
20587
|
const validatedParams = {
|
|
20196
20588
|
txHash: result.data.txHash,
|
|
20197
20589
|
chain: result.data.chain,
|
|
20198
|
-
|
|
20590
|
+
...result.data.apiKey !== undefined && {
|
|
20591
|
+
apiKey: result.data.apiKey
|
|
20592
|
+
},
|
|
20199
20593
|
...result.data.toChain !== undefined && {
|
|
20200
20594
|
toChain: result.data.toChain
|
|
20201
20595
|
}
|
|
@@ -20205,7 +20599,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20205
20599
|
...DEFAULT_CONFIG$1,
|
|
20206
20600
|
headers: {
|
|
20207
20601
|
...DEFAULT_CONFIG$1.headers,
|
|
20208
|
-
|
|
20602
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
20603
|
+
...validatedParams.apiKey !== undefined && {
|
|
20604
|
+
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
20605
|
+
}
|
|
20209
20606
|
}
|
|
20210
20607
|
};
|
|
20211
20608
|
return pollApiGet(url, isGetSwapStatusResponse, effectiveConfig);
|
|
@@ -20294,7 +20691,9 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20294
20691
|
}
|
|
20295
20692
|
const validatedParams = {
|
|
20296
20693
|
chain: result.data.chain,
|
|
20297
|
-
|
|
20694
|
+
...result.data.apiKey !== undefined && {
|
|
20695
|
+
apiKey: result.data.apiKey
|
|
20696
|
+
},
|
|
20298
20697
|
...result.data.addresses !== undefined && {
|
|
20299
20698
|
addresses: result.data.addresses
|
|
20300
20699
|
}
|
|
@@ -20304,7 +20703,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20304
20703
|
...DEFAULT_CONFIG$1,
|
|
20305
20704
|
headers: {
|
|
20306
20705
|
...DEFAULT_CONFIG$1.headers,
|
|
20307
|
-
|
|
20706
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
20707
|
+
...validatedParams.apiKey !== undefined && {
|
|
20708
|
+
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
20709
|
+
}
|
|
20308
20710
|
}
|
|
20309
20711
|
};
|
|
20310
20712
|
return pollApiGet(url, isGetTokenRatesResponse, effectiveConfig);
|
|
@@ -22349,6 +22751,47 @@ const S_HEX_LENGTH = 32 * HEX_CHARS_PER_BYTE$1 // 32 bytes for 's'
|
|
|
22349
22751
|
*/ function hasSignTypedData(adapter) {
|
|
22350
22752
|
return typeof adapter === 'object' && adapter !== null && 'signTypedData' in adapter && typeof adapter.signTypedData === 'function';
|
|
22351
22753
|
}
|
|
22754
|
+
/**
|
|
22755
|
+
* Type guard to check if an adapter can actually produce an EIP-712
|
|
22756
|
+
* typed-data signature.
|
|
22757
|
+
*
|
|
22758
|
+
* @remarks
|
|
22759
|
+
* Strengthens {@link hasSignTypedData}: having a `signTypedData` method
|
|
22760
|
+
* does not guarantee it can succeed. Adapters whose signer is delegated
|
|
22761
|
+
* (e.g. through a signing strategy backed by a smart contract account)
|
|
22762
|
+
* expose the method but reject typed-data payloads at runtime. Such
|
|
22763
|
+
* adapters report their real capability through an optional
|
|
22764
|
+
* `supportsSignTypedData()` method, which this guard consults when
|
|
22765
|
+
* present. Adapters without the capability method are assumed able to
|
|
22766
|
+
* sign, preserving the previous duck-typing behavior.
|
|
22767
|
+
*
|
|
22768
|
+
* @param adapter - The adapter to check
|
|
22769
|
+
* @returns True if calling `signTypedData` can be expected to succeed
|
|
22770
|
+
*
|
|
22771
|
+
* @example
|
|
22772
|
+
* ```typescript
|
|
22773
|
+
* import { canSignTypedData } from '@core/adapter-evm'
|
|
22774
|
+
*
|
|
22775
|
+
* if (canSignTypedData(adapter)) {
|
|
22776
|
+
* const signature = await adapter.signTypedData(typedData, context)
|
|
22777
|
+
* } else {
|
|
22778
|
+
* // take an on-chain approval path instead of a permit signature
|
|
22779
|
+
* }
|
|
22780
|
+
* ```
|
|
22781
|
+
*/ function canSignTypedData(adapter) {
|
|
22782
|
+
if (!hasSignTypedData(adapter)) {
|
|
22783
|
+
return false;
|
|
22784
|
+
}
|
|
22785
|
+
if (typeof adapter.supportsSignTypedData === 'function') {
|
|
22786
|
+
// The value is `boolean` per the interface, but a plain-JS adapter may
|
|
22787
|
+
// return anything; treat it as untrusted and coerce to a strict
|
|
22788
|
+
// boolean. Comparing an `unknown` (not a `boolean`) also keeps the
|
|
22789
|
+
// lint autofix from stripping this as a redundant `=== true`.
|
|
22790
|
+
const supported = adapter.supportsSignTypedData();
|
|
22791
|
+
return supported === true;
|
|
22792
|
+
}
|
|
22793
|
+
return true;
|
|
22794
|
+
}
|
|
22352
22795
|
|
|
22353
22796
|
/**
|
|
22354
22797
|
* Build EIP-2612 typed data for permit signing.
|
|
@@ -23881,10 +24324,13 @@ function writeBytes32(buffer, hex, offset) {
|
|
|
23881
24324
|
* at usage time rather than construction time.
|
|
23882
24325
|
*
|
|
23883
24326
|
* Validates:
|
|
23884
|
-
* - Kit key
|
|
24327
|
+
* - Kit key matches the required format (KIT_KEY:id:secret) when provided.
|
|
24328
|
+
* An absent or empty kit key is permitted (permissionless mode) — the swap
|
|
24329
|
+
* service now treats the key as optional.
|
|
23885
24330
|
*
|
|
23886
|
-
* @param kitKey - The inline kit key from the swap operation config
|
|
23887
|
-
* @throws KitError with VALIDATION_FAILED if kit key is
|
|
24331
|
+
* @param kitKey - The inline kit key from the swap operation config (optional)
|
|
24332
|
+
* @throws KitError with VALIDATION_FAILED if a kit key is provided but does not
|
|
24333
|
+
* match the KIT_KEY:<keyId>:<keySecret> format
|
|
23888
24334
|
*
|
|
23889
24335
|
* @example
|
|
23890
24336
|
* ```typescript
|
|
@@ -23896,9 +24342,11 @@ function writeBytes32(buffer, hex, offset) {
|
|
|
23896
24342
|
* assertKitKey(kitKey)
|
|
23897
24343
|
* ```
|
|
23898
24344
|
*/ function assertKitKey(kitKey) {
|
|
23899
|
-
//
|
|
24345
|
+
// Permissionless mode: the swap service treats the kit key as optional, so an
|
|
24346
|
+
// absent (or empty) key is valid. Only validate the format when a key is
|
|
24347
|
+
// actually provided.
|
|
23900
24348
|
if (!kitKey) {
|
|
23901
|
-
|
|
24349
|
+
return;
|
|
23902
24350
|
}
|
|
23903
24351
|
const apiKeyResult = apiKeySchema.safeParse(kitKey);
|
|
23904
24352
|
if (!apiKeyResult.success) {
|
|
@@ -24195,8 +24643,8 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
24195
24643
|
validateResolvedAddress(resolvedTokenInAddress, chain);
|
|
24196
24644
|
validateResolvedAddress(resolvedTokenOutAddress, destinationChain);
|
|
24197
24645
|
validateResolvedAddress(to, destinationChain);
|
|
24198
|
-
const kitKey = config?.kitKey
|
|
24199
|
-
//
|
|
24646
|
+
const kitKey = config?.kitKey;
|
|
24647
|
+
// Validate the kit key format when one is provided (permissionless otherwise).
|
|
24200
24648
|
assertKitKey(kitKey);
|
|
24201
24649
|
// Validate custom fee configuration if present
|
|
24202
24650
|
const customFee = config?.customFee;
|
|
@@ -24241,7 +24689,10 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
24241
24689
|
}
|
|
24242
24690
|
}
|
|
24243
24691
|
},
|
|
24244
|
-
apiKey
|
|
24692
|
+
// Map kitKey → apiKey for the service client; omitted in permissionless mode.
|
|
24693
|
+
...kitKey ? {
|
|
24694
|
+
apiKey: kitKey
|
|
24695
|
+
} : {}
|
|
24245
24696
|
};
|
|
24246
24697
|
}
|
|
24247
24698
|
|
|
@@ -24895,6 +25346,37 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
24895
25346
|
}
|
|
24896
25347
|
}
|
|
24897
25348
|
|
|
25349
|
+
/**
|
|
25350
|
+
* Determine whether an adapter can produce an EIP-2612 permit signature.
|
|
25351
|
+
*
|
|
25352
|
+
* @remarks
|
|
25353
|
+
* A gasless permit needs two adapter capabilities: fetching the token's
|
|
25354
|
+
* EIP-2612 nonce and producing an EIP-712 typed-data signature. The
|
|
25355
|
+
* typed-data check uses {@link canSignTypedData} rather than a bare
|
|
25356
|
+
* `hasSignTypedData` guard so that an adapter routed through a signing
|
|
25357
|
+
* strategy that cannot produce typed-data signatures — one whose manifest
|
|
25358
|
+
* omits `evm-typed-data`, surfaced through an optional `supportsSignTypedData()`
|
|
25359
|
+
* — is correctly excluded. Such an adapter falls back to an on-chain approval
|
|
25360
|
+
* (batched into a single submission when it supports atomic execution) instead
|
|
25361
|
+
* of attempting a permit its strategy would reject.
|
|
25362
|
+
*
|
|
25363
|
+
* @param adapter - The source adapter to inspect.
|
|
25364
|
+
* @returns `true` when the adapter can both fetch a nonce and sign typed data.
|
|
25365
|
+
*
|
|
25366
|
+
* @example
|
|
25367
|
+
* ```typescript
|
|
25368
|
+
* import { adapterSupportsPermit } from './utils'
|
|
25369
|
+
*
|
|
25370
|
+
* if (adapterSupportsPermit(adapter)) {
|
|
25371
|
+
* // gasless permit path — fold the approval into the swap transaction
|
|
25372
|
+
* } else {
|
|
25373
|
+
* // on-chain approval path (batched when supportsAtomicBatch is true)
|
|
25374
|
+
* }
|
|
25375
|
+
* ```
|
|
25376
|
+
*/ function adapterSupportsPermit(adapter) {
|
|
25377
|
+
return hasEIP2612NonceFetching(adapter) && canSignTypedData(adapter);
|
|
25378
|
+
}
|
|
25379
|
+
|
|
24898
25380
|
/**
|
|
24899
25381
|
* Generate EIP-2612 permit signature for token approval.
|
|
24900
25382
|
*
|
|
@@ -25030,8 +25512,7 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
25030
25512
|
}
|
|
25031
25513
|
// Skip permit generation if the adapter lacks the required capabilities.
|
|
25032
25514
|
// handleEvmTokenApproval will have already sent an on-chain approval in this case.
|
|
25033
|
-
|
|
25034
|
-
if (!adapterSupportsPermit) {
|
|
25515
|
+
if (!adapterSupportsPermit(adapter)) {
|
|
25035
25516
|
return [
|
|
25036
25517
|
createFallbackTokenInput(tokenInAddress, inputAmount)
|
|
25037
25518
|
];
|
|
@@ -25590,6 +26071,65 @@ const TOKEN_REGISTRY$3 = createTokenRegistry();
|
|
|
25590
26071
|
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.`;
|
|
25591
26072
|
}
|
|
25592
26073
|
|
|
26074
|
+
/**
|
|
26075
|
+
* Determine which chain a fee token should be resolved and formatted against.
|
|
26076
|
+
*
|
|
26077
|
+
* @remarks
|
|
26078
|
+
* Fees returned by the service may be denominated in either the input token
|
|
26079
|
+
* (on the source chain) or the output token (on the destination chain). A
|
|
26080
|
+
* contract address only resolves on the chain it belongs to, so formatting a
|
|
26081
|
+
* destination-denominated fee against the source chain causes
|
|
26082
|
+
* {@link resolveTokenSymbol} to miss and the amount to be returned as raw base
|
|
26083
|
+
* units (e.g. a cross-chain swap charging a fee in the destination output
|
|
26084
|
+
* token — an EURC-on-Base address shows `'13202'` instead of `'0.013202'` when
|
|
26085
|
+
* resolved against the source chain). This is the fallback for fee items that
|
|
26086
|
+
* are not self-described with their own `decimals`/`chain`.
|
|
26087
|
+
*
|
|
26088
|
+
* Prefer the source chain (covers same-chain swaps and input-denominated
|
|
26089
|
+
* fees), then fall back to the destination chain when the token only resolves
|
|
26090
|
+
* there. When neither chain recognises the token, default to the source chain
|
|
26091
|
+
* so existing on-chain decimal lookups via the source adapter still apply.
|
|
26092
|
+
*
|
|
26093
|
+
* Symbol tokens (`'USDC'`, `'NATIVE'`) resolve on either chain, so the
|
|
26094
|
+
* source-first preference keeps them on the source chain. That is correct for
|
|
26095
|
+
* registry stablecoins, and for `'NATIVE'` only when both chains share native
|
|
26096
|
+
* decimals (EVM↔EVM, 18). It does NOT honor per-chain native decimals: a
|
|
26097
|
+
* `'NATIVE'`-denominated fee on a Solana↔EVM swap (9 vs 18) would be
|
|
26098
|
+
* mis-scaled. This is latent — providers emit the address form, and
|
|
26099
|
+
* self-describing fee items carry their own `decimals` and never reach this
|
|
26100
|
+
* helper — so the gap only opens for a future `'NATIVE'` fee that arrives
|
|
26101
|
+
* without `decimals` on a cross-native-decimal route.
|
|
26102
|
+
*
|
|
26103
|
+
* @param token - The fee token identifier — a symbol (`'USDC'`) or contract address.
|
|
26104
|
+
* @param sourceChain - The chain the swap originates from.
|
|
26105
|
+
* @param destinationChain - The chain the swap settles on (equals `sourceChain` for same-chain swaps).
|
|
26106
|
+
* @returns The chain definition the fee token should be resolved against.
|
|
26107
|
+
*
|
|
26108
|
+
* @example
|
|
26109
|
+
* ```typescript
|
|
26110
|
+
* import { resolveFeeChain } from './resolveFeeChain'
|
|
26111
|
+
* import { Ethereum, Base } from '@core/chains'
|
|
26112
|
+
*
|
|
26113
|
+
* // Cross-chain swap fee charged in the destination (output) token
|
|
26114
|
+
* resolveFeeChain('0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42', Ethereum, Base)
|
|
26115
|
+
* // => Base (EURC resolves on Base, not Ethereum)
|
|
26116
|
+
*
|
|
26117
|
+
* // Symbol or source-token fees stay on the source chain
|
|
26118
|
+
* resolveFeeChain('USDC', Ethereum, Base) // => Ethereum
|
|
26119
|
+
* ```
|
|
26120
|
+
*/ function resolveFeeChain(token, sourceChain, destinationChain) {
|
|
26121
|
+
if (sourceChain.chain === destinationChain.chain) {
|
|
26122
|
+
return sourceChain;
|
|
26123
|
+
}
|
|
26124
|
+
if (resolveTokenSymbol(token, sourceChain) !== null) {
|
|
26125
|
+
return sourceChain;
|
|
26126
|
+
}
|
|
26127
|
+
if (resolveTokenSymbol(token, destinationChain) !== null) {
|
|
26128
|
+
return destinationChain;
|
|
26129
|
+
}
|
|
26130
|
+
return sourceChain;
|
|
26131
|
+
}
|
|
26132
|
+
|
|
25593
26133
|
const TOKEN_REGISTRY$2 = createTokenRegistry();
|
|
25594
26134
|
/**
|
|
25595
26135
|
* Format a raw base-unit amount into a human-readable decimal string.
|
|
@@ -25680,6 +26220,186 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
|
|
|
25680
26220
|
}
|
|
25681
26221
|
}
|
|
25682
26222
|
|
|
26223
|
+
/**
|
|
26224
|
+
* Runtime guard for {@link BatchCapableSwapAdapter}.
|
|
26225
|
+
*
|
|
26226
|
+
* @param adapter - The adapter to inspect.
|
|
26227
|
+
* @returns `true` when the adapter exposes both batch methods.
|
|
26228
|
+
*
|
|
26229
|
+
* @example
|
|
26230
|
+
* ```typescript
|
|
26231
|
+
* if (isBatchCapableSwapAdapter(adapter)) {
|
|
26232
|
+
* // adapter.supportsAtomicBatch / adapter.batchExecute are available
|
|
26233
|
+
* }
|
|
26234
|
+
* ```
|
|
26235
|
+
*/ function isBatchCapableSwapAdapter(adapter) {
|
|
26236
|
+
return typeof adapter === 'object' && adapter !== null && typeof adapter.supportsAtomicBatch === 'function' && typeof adapter.batchExecute === 'function';
|
|
26237
|
+
}
|
|
26238
|
+
/**
|
|
26239
|
+
* Decide whether the EVM swap should take the batched approve-and-swap path.
|
|
26240
|
+
*
|
|
26241
|
+
* @remarks
|
|
26242
|
+
* Batching only helps when an on-chain approval would otherwise be required, so
|
|
26243
|
+
* it is skipped for native tokens (no approval) and for the gasless permit path
|
|
26244
|
+
* (already a single transaction). USDT is skipped because its reset-to-zero
|
|
26245
|
+
* allowance flow cannot be expressed as a fixed approve+swap pair. When those
|
|
26246
|
+
* gates pass, the adapter's actual atomic-batch capability is queried; any
|
|
26247
|
+
* failure resolves to `false` so the swap falls back to the sequential path.
|
|
26248
|
+
*
|
|
26249
|
+
* @param args - The decision inputs.
|
|
26250
|
+
* @param args.adapter - The source adapter.
|
|
26251
|
+
* @param args.chain - The source chain definition.
|
|
26252
|
+
* @param args.tokenInAddress - The resolved input-token address.
|
|
26253
|
+
* @param args.allowanceStrategy - Optional allowance strategy override.
|
|
26254
|
+
* @param args.batchTransactions - Optional explicit opt-out (`false` disables).
|
|
26255
|
+
* @returns `true` when the batched approve-and-swap path should be used.
|
|
26256
|
+
*
|
|
26257
|
+
* @example
|
|
26258
|
+
* ```typescript
|
|
26259
|
+
* const useBatched = await shouldUseBatchedSwap({
|
|
26260
|
+
* adapter,
|
|
26261
|
+
* chain,
|
|
26262
|
+
* tokenInAddress: '0xA0b8...',
|
|
26263
|
+
* allowanceStrategy: config?.allowanceStrategy,
|
|
26264
|
+
* batchTransactions: config?.batchTransactions,
|
|
26265
|
+
* })
|
|
26266
|
+
* ```
|
|
26267
|
+
*/ async function shouldUseBatchedSwap({ adapter, chain, tokenInAddress, allowanceStrategy, batchTransactions }) {
|
|
26268
|
+
// Explicit opt-out.
|
|
26269
|
+
if (batchTransactions === false) {
|
|
26270
|
+
return false;
|
|
26271
|
+
}
|
|
26272
|
+
// Batching is an EVM capability (EIP-5792 or a signing strategy).
|
|
26273
|
+
if (chain.type !== 'evm') {
|
|
26274
|
+
return false;
|
|
26275
|
+
}
|
|
26276
|
+
// Native tokens need no approval — the swap is already a single transaction.
|
|
26277
|
+
if (isNativeEvmAddress(tokenInAddress)) {
|
|
26278
|
+
return false;
|
|
26279
|
+
}
|
|
26280
|
+
// A gasless permit folds the approval into the swap transaction, so there is
|
|
26281
|
+
// nothing to batch. Mirrors the permit gate in handleEvmTokenApproval.
|
|
26282
|
+
const canUsePermit = allowanceStrategy !== 'approve' && supportsEIP2612(tokenInAddress, chain) && adapterSupportsPermit(adapter);
|
|
26283
|
+
if (canUsePermit) {
|
|
26284
|
+
return false;
|
|
26285
|
+
}
|
|
26286
|
+
// USDT's reset-to-zero allowance dance cannot be expressed as a fixed
|
|
26287
|
+
// approve+swap pair; leave it on the sequential path.
|
|
26288
|
+
const usdt = chain.usdtAddress?.toLowerCase();
|
|
26289
|
+
if (usdt !== undefined && tokenInAddress.toLowerCase() === usdt) {
|
|
26290
|
+
return false;
|
|
26291
|
+
}
|
|
26292
|
+
if (!isBatchCapableSwapAdapter(adapter)) {
|
|
26293
|
+
return false;
|
|
26294
|
+
}
|
|
26295
|
+
try {
|
|
26296
|
+
return await adapter.supportsAtomicBatch(chain);
|
|
26297
|
+
} catch {
|
|
26298
|
+
return false;
|
|
26299
|
+
}
|
|
26300
|
+
}
|
|
26301
|
+
/**
|
|
26302
|
+
* Execute the approval and swap as a single atomic batch.
|
|
26303
|
+
*
|
|
26304
|
+
* @remarks
|
|
26305
|
+
* Extracts the raw call data from both prepared requests, submits them as one
|
|
26306
|
+
* batch via `adapter.batchExecute`, and maps the swap receipt back to a
|
|
26307
|
+
* transaction hash. The `fromAddress` is threaded for adapters routed through a
|
|
26308
|
+
* signing strategy (which have no wallet account to read the sender from); the
|
|
26309
|
+
* wallet-client path ignores it.
|
|
26310
|
+
*
|
|
26311
|
+
* Following the batch contract, `batchExecute` never throws once the batch is
|
|
26312
|
+
* submitted — a missing or failed swap receipt is surfaced here as a thrown
|
|
26313
|
+
* {@link KitError} (FATAL) so the caller does not resubmit an already-broadcast
|
|
26314
|
+
* batch and double-swap.
|
|
26315
|
+
*
|
|
26316
|
+
* @param args - The execution inputs.
|
|
26317
|
+
* @param args.adapter - The batch-capable source adapter.
|
|
26318
|
+
* @param args.chain - The EVM chain to execute on.
|
|
26319
|
+
* @param args.approveRequest - The prepared ERC-20 approval request.
|
|
26320
|
+
* @param args.swapRequest - The prepared swap request (pre-approval / NONE permit).
|
|
26321
|
+
* @param args.fromAddress - The address authorizing the batch.
|
|
26322
|
+
* @returns The swap transaction hash and the executed approval + swap records.
|
|
26323
|
+
* @throws {@link KitError} when the prepared requests cannot yield call data.
|
|
26324
|
+
* @throws {@link KitError} when the batch does not confirm or the swap reverts.
|
|
26325
|
+
*
|
|
26326
|
+
* @example
|
|
26327
|
+
* ```typescript
|
|
26328
|
+
* const { swapTxHash, executedTransactions } = await executeBatchedApproveAndSwap({
|
|
26329
|
+
* adapter,
|
|
26330
|
+
* chain,
|
|
26331
|
+
* approveRequest,
|
|
26332
|
+
* swapRequest,
|
|
26333
|
+
* fromAddress: '0x742d...',
|
|
26334
|
+
* })
|
|
26335
|
+
* ```
|
|
26336
|
+
*/ async function executeBatchedApproveAndSwap({ adapter, chain, approveRequest, swapRequest, fromAddress }) {
|
|
26337
|
+
if (approveRequest.type !== 'evm' || swapRequest.type !== 'evm' || !approveRequest.getCallData || !swapRequest.getCallData) {
|
|
26338
|
+
throw new KitError({
|
|
26339
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
26340
|
+
recoverability: 'FATAL',
|
|
26341
|
+
message: 'Batched swap requires EVM prepared requests with getCallData() support.'
|
|
26342
|
+
});
|
|
26343
|
+
}
|
|
26344
|
+
const approveCallData = approveRequest.getCallData();
|
|
26345
|
+
const swapCallData = swapRequest.getCallData();
|
|
26346
|
+
const batchResult = await adapter.batchExecute([
|
|
26347
|
+
approveCallData,
|
|
26348
|
+
swapCallData
|
|
26349
|
+
], chain, {
|
|
26350
|
+
fromAddress
|
|
26351
|
+
});
|
|
26352
|
+
const swapReceipt = batchResult.receipts[1];
|
|
26353
|
+
// A missing swap receipt means the batch never confirmed (polling timed out
|
|
26354
|
+
// or the wallet returned fewer receipts than calls). Re-throw the underlying
|
|
26355
|
+
// error when present (already FATAL); otherwise surface a FATAL timeout so the
|
|
26356
|
+
// caller checks the batch status rather than resubmitting.
|
|
26357
|
+
if (swapReceipt === undefined || swapReceipt.txHash === '') {
|
|
26358
|
+
if (isKitError(batchResult.error)) {
|
|
26359
|
+
throw batchResult.error;
|
|
26360
|
+
}
|
|
26361
|
+
throw new KitError({
|
|
26362
|
+
...NetworkError.TIMEOUT,
|
|
26363
|
+
recoverability: 'FATAL',
|
|
26364
|
+
message: `Batched swap did not confirm on-chain (batchId: ${batchResult.batchId}). ` + 'The batch was already submitted — check its status before retrying.',
|
|
26365
|
+
// Preserve the underlying confirmation failure when it isn't a KitError —
|
|
26366
|
+
// the signing-strategy path returns a raw viem error (e.g. a dropped or
|
|
26367
|
+
// replaced tx) — so the root cause survives behind the generic timeout.
|
|
26368
|
+
cause: {
|
|
26369
|
+
trace: {
|
|
26370
|
+
batchId: batchResult.batchId,
|
|
26371
|
+
...batchResult.error != null && {
|
|
26372
|
+
error: batchResult.error
|
|
26373
|
+
}
|
|
26374
|
+
}
|
|
26375
|
+
}
|
|
26376
|
+
});
|
|
26377
|
+
}
|
|
26378
|
+
if (swapReceipt.status !== 'success') {
|
|
26379
|
+
throw createTransactionRevertedError(chain.name, 'Batched swap transaction reverted on-chain', undefined, swapReceipt.txHash, buildExplorerUrl(chain, swapReceipt.txHash));
|
|
26380
|
+
}
|
|
26381
|
+
const executedTransactions = [];
|
|
26382
|
+
const approveReceipt = batchResult.receipts[0];
|
|
26383
|
+
// An atomic batch is a single on-chain transaction, so the approve and swap
|
|
26384
|
+
// receipts share one hash. Only surface a distinct approval record when it is
|
|
26385
|
+
// genuinely a separate transaction; otherwise the lone swap record represents
|
|
26386
|
+
// the batch, avoiding a phantom duplicate tx in executedTransactions.
|
|
26387
|
+
if (approveReceipt !== undefined && approveReceipt.txHash !== '' && approveReceipt.txHash !== swapReceipt.txHash) {
|
|
26388
|
+
executedTransactions.push({
|
|
26389
|
+
type: 'approval',
|
|
26390
|
+
txHash: approveReceipt.txHash
|
|
26391
|
+
});
|
|
26392
|
+
}
|
|
26393
|
+
executedTransactions.push({
|
|
26394
|
+
type: 'swap',
|
|
26395
|
+
txHash: swapReceipt.txHash
|
|
26396
|
+
});
|
|
26397
|
+
return {
|
|
26398
|
+
swapTxHash: swapReceipt.txHash,
|
|
26399
|
+
executedTransactions
|
|
26400
|
+
};
|
|
26401
|
+
}
|
|
26402
|
+
|
|
25683
26403
|
/**
|
|
25684
26404
|
* Safety multiplier applied to locally estimated gas for EVM swap execution.
|
|
25685
26405
|
* Derived from refund cap (max 1/5 of total gas used) plus an extra 0.1 margin,
|
|
@@ -25806,7 +26526,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
25806
26526
|
const statusResult = await getSwapStatus$2({
|
|
25807
26527
|
txHash,
|
|
25808
26528
|
chain: chain.chain,
|
|
25809
|
-
apiKey
|
|
26529
|
+
...apiKey !== undefined && {
|
|
26530
|
+
apiKey
|
|
26531
|
+
}
|
|
25810
26532
|
});
|
|
25811
26533
|
if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
|
|
25812
26534
|
return {
|
|
@@ -26397,8 +27119,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
26397
27119
|
// Note: tokenInAddress from executionCtx is already resolved (handles NATIVE alias, ETH, etc.)
|
|
26398
27120
|
const isNativeToken = isNativeEvmAddress(executionCtx.tokenInAddress);
|
|
26399
27121
|
const tokenSupportsPermit = supportsEIP2612(executionCtx.tokenInAddress, chain);
|
|
26400
|
-
const
|
|
26401
|
-
const canUsePermitFlow = tokenSupportsPermit && adapterSupportsPermit && allowanceStrategy !== 'approve';
|
|
27122
|
+
const canUsePermitFlow = tokenSupportsPermit && adapterSupportsPermit(adapter) && allowanceStrategy !== 'approve';
|
|
26402
27123
|
const needsApproval = !isNativeToken && !canUsePermitFlow;
|
|
26403
27124
|
if (!needsApproval) {
|
|
26404
27125
|
return;
|
|
@@ -26677,34 +27398,55 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
26677
27398
|
const serviceResponse = await createSwap(serviceParams);
|
|
26678
27399
|
// Track executed transactions
|
|
26679
27400
|
const executedTransactions = [];
|
|
26680
|
-
// Prepare swap action based on chain type
|
|
26681
|
-
|
|
26682
|
-
|
|
26683
|
-
|
|
26684
|
-
|
|
26685
|
-
|
|
26686
|
-
|
|
26687
|
-
|
|
26688
|
-
|
|
26689
|
-
|
|
26690
|
-
|
|
26691
|
-
|
|
26692
|
-
}
|
|
27401
|
+
// Prepare swap action(s) based on chain type and batch capability.
|
|
27402
|
+
// Returns either a single prepared action (Solana / sequential EVM) or a
|
|
27403
|
+
// batched approve+swap plan (EVM atomic-batch path).
|
|
27404
|
+
const { preparedAction, batchedSwapPlan } = await this.prepareSwapRequests({
|
|
27405
|
+
adapter,
|
|
27406
|
+
chain,
|
|
27407
|
+
serviceResponse,
|
|
27408
|
+
resolvedContext,
|
|
27409
|
+
executionCtx,
|
|
27410
|
+
config,
|
|
27411
|
+
executedTransactions
|
|
27412
|
+
});
|
|
26693
27413
|
// Execute swap transaction via adapter
|
|
26694
27414
|
// For EVM chains, use gas limit from proxy service API
|
|
26695
27415
|
let txHash;
|
|
26696
27416
|
const evmGasLimit = 'gasLimit' in serviceResponse.transaction ? serviceResponse.transaction.gasLimit : undefined;
|
|
26697
27417
|
try {
|
|
26698
|
-
|
|
26699
|
-
|
|
26700
|
-
|
|
26701
|
-
|
|
26702
|
-
|
|
26703
|
-
|
|
26704
|
-
|
|
26705
|
-
|
|
26706
|
-
|
|
26707
|
-
|
|
27418
|
+
if (batchedSwapPlan) {
|
|
27419
|
+
// Approve + swap submitted as one atomic batch. batchExecute confirms
|
|
27420
|
+
// the swap internally, so no separate waitForTransaction is needed.
|
|
27421
|
+
const batched = await executeBatchedApproveAndSwap({
|
|
27422
|
+
adapter: adapter,
|
|
27423
|
+
chain: chain,
|
|
27424
|
+
approveRequest: batchedSwapPlan.approveRequest,
|
|
27425
|
+
swapRequest: batchedSwapPlan.swapRequest,
|
|
27426
|
+
fromAddress: executionCtx.fromAddress
|
|
27427
|
+
});
|
|
27428
|
+
txHash = batched.swapTxHash;
|
|
27429
|
+
executedTransactions.push(...batched.executedTransactions);
|
|
27430
|
+
} else if (preparedAction) {
|
|
27431
|
+
txHash = await this.executeSwapTransaction(preparedAction, evmGasLimit);
|
|
27432
|
+
executedTransactions.push({
|
|
27433
|
+
type: 'swap',
|
|
27434
|
+
txHash
|
|
27435
|
+
});
|
|
27436
|
+
// Wait for transaction confirmation and verify success
|
|
27437
|
+
const txReceipt = await adapter.waitForTransaction(txHash, undefined, chain);
|
|
27438
|
+
if (txReceipt.status === 'reverted') {
|
|
27439
|
+
const explorerUrl = buildExplorerUrl(chain, txHash);
|
|
27440
|
+
throw createTransactionRevertedError(chain.name, 'Swap transaction reverted on-chain', undefined, txHash, explorerUrl);
|
|
27441
|
+
}
|
|
27442
|
+
} else {
|
|
27443
|
+
// Unreachable: the preparation step always yields either a batched plan
|
|
27444
|
+
// or a prepared action.
|
|
27445
|
+
throw new KitError({
|
|
27446
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
27447
|
+
recoverability: 'FATAL',
|
|
27448
|
+
message: 'No swap execution path was prepared.'
|
|
27449
|
+
});
|
|
26708
27450
|
}
|
|
26709
27451
|
} catch (err) {
|
|
26710
27452
|
handleSwapExecutionError(err, txHash, chain);
|
|
@@ -26723,7 +27465,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
26723
27465
|
isCrossChainSwap,
|
|
26724
27466
|
txHash,
|
|
26725
27467
|
chain,
|
|
26726
|
-
|
|
27468
|
+
...serviceParams.apiKey !== undefined && {
|
|
27469
|
+
apiKey: serviceParams.apiKey
|
|
27470
|
+
}
|
|
26727
27471
|
});
|
|
26728
27472
|
// Build and return SwapResult
|
|
26729
27473
|
return {
|
|
@@ -26748,6 +27492,79 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
26748
27492
|
};
|
|
26749
27493
|
}
|
|
26750
27494
|
/**
|
|
27495
|
+
* Prepare the swap execution request(s) for the source wallet's chain.
|
|
27496
|
+
*
|
|
27497
|
+
* Produces either a single {@link PreparedChainRequest} (Solana, or the
|
|
27498
|
+
* sequential EVM approve-then-swap path) or a `batchedSwapPlan` (the EVM
|
|
27499
|
+
* atomic approve+swap path chosen when the adapter supports EIP-5792 atomic
|
|
27500
|
+
* batching). The caller executes whichever field is populated. Any on-chain
|
|
27501
|
+
* approval sent on the sequential path is appended to `executedTransactions`.
|
|
27502
|
+
*
|
|
27503
|
+
* @typeParam TFromAdapterCapabilities - Source-adapter capability set.
|
|
27504
|
+
* @param args - Inputs derived from the validated swap request.
|
|
27505
|
+
* @param args.adapter - Source-chain wallet adapter.
|
|
27506
|
+
* @param args.chain - Source chain definition.
|
|
27507
|
+
* @param args.serviceResponse - Validated createSwap response.
|
|
27508
|
+
* @param args.resolvedContext - Resolved operation context.
|
|
27509
|
+
* @param args.executionCtx - Minimal on-chain execution context.
|
|
27510
|
+
* @param args.config - Optional swap configuration (allowance/batch flags).
|
|
27511
|
+
* @param args.executedTransactions - Array appended with any sent approval.
|
|
27512
|
+
* @returns The prepared action or the batched approve+swap plan.
|
|
27513
|
+
* @throws KitError when the EVM atomic-batch path is selected but the chain
|
|
27514
|
+
* has no configured adapter contract.
|
|
27515
|
+
*/ async prepareSwapRequests(args) {
|
|
27516
|
+
const { adapter, chain, serviceResponse, resolvedContext, executionCtx, config, executedTransactions } = args;
|
|
27517
|
+
if (chain.type === 'solana') {
|
|
27518
|
+
// Solana: No approval needed, directly prepare swap action
|
|
27519
|
+
return {
|
|
27520
|
+
preparedAction: await prepareSolanaSwapAction(adapter, serviceResponse, resolvedContext)
|
|
27521
|
+
};
|
|
27522
|
+
}
|
|
27523
|
+
const useBatch = await shouldUseBatchedSwap({
|
|
27524
|
+
adapter,
|
|
27525
|
+
chain,
|
|
27526
|
+
tokenInAddress: executionCtx.tokenInAddress,
|
|
27527
|
+
allowanceStrategy: config?.allowanceStrategy,
|
|
27528
|
+
batchTransactions: config?.batchTransactions
|
|
27529
|
+
});
|
|
27530
|
+
if (useBatch) {
|
|
27531
|
+
// EVM chains: fuse the ERC-20 approval and the swap into a single atomic
|
|
27532
|
+
// batch (one signing challenge for smart-contract wallets). Force the
|
|
27533
|
+
// swap onto the pre-approval (PermitType.NONE) path since the approval
|
|
27534
|
+
// rides in the same batch.
|
|
27535
|
+
const adapterContractAddress = chain.kitContracts?.adapter;
|
|
27536
|
+
if (!adapterContractAddress) {
|
|
27537
|
+
throw new KitError({
|
|
27538
|
+
...InputError.VALIDATION_FAILED,
|
|
27539
|
+
recoverability: 'FATAL',
|
|
27540
|
+
message: `Adapter contract not configured for chain ${chain.name}. Swap operations require an adapter contract.`,
|
|
27541
|
+
cause: {
|
|
27542
|
+
trace: {
|
|
27543
|
+
chain: chain.name
|
|
27544
|
+
}
|
|
27545
|
+
}
|
|
27546
|
+
});
|
|
27547
|
+
}
|
|
27548
|
+
const [approveRequest, swapRequest] = await Promise.all([
|
|
27549
|
+
this.approve(adapter, executionCtx.amount, executionCtx.tokenInAddress, adapterContractAddress, resolvedContext),
|
|
27550
|
+
prepareEvmSwapAction(adapter, serviceResponse, resolvedContext, 'approve')
|
|
27551
|
+
]);
|
|
27552
|
+
return {
|
|
27553
|
+
batchedSwapPlan: {
|
|
27554
|
+
approveRequest,
|
|
27555
|
+
swapRequest
|
|
27556
|
+
}
|
|
27557
|
+
};
|
|
27558
|
+
}
|
|
27559
|
+
// EVM chains: Handle token approval if needed, then prepare the swap.
|
|
27560
|
+
// prepareEvmSwapAction handles EIP-2612 permit generation; the adapter
|
|
27561
|
+
// contract address is read from chain.kitContracts.adapter.
|
|
27562
|
+
await this.handleEvmTokenApproval(adapter, chain, executionCtx, resolvedContext, executedTransactions, config?.allowanceStrategy);
|
|
27563
|
+
return {
|
|
27564
|
+
preparedAction: await prepareEvmSwapAction(adapter, serviceResponse, resolvedContext, config?.allowanceStrategy)
|
|
27565
|
+
};
|
|
27566
|
+
}
|
|
27567
|
+
/**
|
|
26751
27568
|
* Executes a swap transaction with the appropriate gas limit for the chain type.
|
|
26752
27569
|
*
|
|
26753
27570
|
* For EVM chains, performs a local eth_estimateGas call, applies a 1.3x safety
|
|
@@ -26796,8 +27613,8 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
26796
27613
|
*/ async buildFormattedFees(fees, chain, destinationChain, adapter, recipientAddress) {
|
|
26797
27614
|
if (!fees) return [];
|
|
26798
27615
|
const [providerFees, swapFees, developerFees] = await Promise.all([
|
|
26799
|
-
this.formatServiceFees(fees.provider, chain, 'provider', adapter),
|
|
26800
|
-
this.formatServiceFees(fees.swap, chain, 'swap', adapter),
|
|
27616
|
+
this.formatServiceFees(fees.provider, chain, destinationChain, 'provider', adapter),
|
|
27617
|
+
this.formatServiceFees(fees.swap, chain, destinationChain, 'swap', adapter),
|
|
26801
27618
|
recipientAddress ? this.formatDeveloperFees(fees.developer, chain, destinationChain, recipientAddress, adapter) : Promise.resolve([])
|
|
26802
27619
|
]);
|
|
26803
27620
|
return [
|
|
@@ -26807,6 +27624,45 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
26807
27624
|
];
|
|
26808
27625
|
}
|
|
26809
27626
|
/**
|
|
27627
|
+
* Resolve a single fee item to its display token and human-readable amount.
|
|
27628
|
+
*
|
|
27629
|
+
* @remarks
|
|
27630
|
+
* Prefer the self-describing metadata the service attaches to each fee:
|
|
27631
|
+
* `decimals` (and `symbol`) come straight from the provider quote, so they
|
|
27632
|
+
* are authoritative even for a token absent from the SDK registry on both
|
|
27633
|
+
* chains. That is the case {@link resolveFeeChain} cannot recover — a
|
|
27634
|
+
* destination-denominated fee token resolves on neither the source registry
|
|
27635
|
+
* nor the source-bound adapter, leaving the amount as raw base units. When
|
|
27636
|
+
* the service omits `decimals` (optional during rollout), fall back to
|
|
27637
|
+
* inferring the fee token's chain and resolving via the registry/adapter.
|
|
27638
|
+
*
|
|
27639
|
+
* Like {@link formatTokenValue}, this never throws: fee display is cosmetic
|
|
27640
|
+
* and must not fail an estimate/swap. A malformed self-describing `decimals`
|
|
27641
|
+
* (e.g. a non-numeric `amount` or invalid decimal count that makes
|
|
27642
|
+
* {@link formatUnits} throw) falls through to chain-based resolution rather
|
|
27643
|
+
* than propagating out of {@link buildFormattedFees}.
|
|
27644
|
+
*
|
|
27645
|
+
* @param fee - The fee item from the service response.
|
|
27646
|
+
* @param chain - The source chain definition.
|
|
27647
|
+
* @param destinationChain - The destination chain definition.
|
|
27648
|
+
* @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
|
|
27649
|
+
* @returns Promise resolving to the formatted amount and display token.
|
|
27650
|
+
*/ async formatFeeValue(fee, chain, destinationChain, adapter) {
|
|
27651
|
+
if (fee.decimals != null) {
|
|
27652
|
+
try {
|
|
27653
|
+
return {
|
|
27654
|
+
amount: formatUnits(fee.amount, fee.decimals),
|
|
27655
|
+
token: fee.symbol ?? fee.token
|
|
27656
|
+
};
|
|
27657
|
+
} catch {
|
|
27658
|
+
// Malformed service metadata — fall through to chain-based resolution,
|
|
27659
|
+
// which never throws (worst case: raw passthrough).
|
|
27660
|
+
}
|
|
27661
|
+
}
|
|
27662
|
+
const feeChain = resolveFeeChain(fee.token, chain, destinationChain);
|
|
27663
|
+
return formatTokenValue(fee.amount, fee.token, feeChain, adapter);
|
|
27664
|
+
}
|
|
27665
|
+
/**
|
|
26810
27666
|
* Format service fee items into the SDK's ServiceSwapFee structure.
|
|
26811
27667
|
*
|
|
26812
27668
|
* @remarks
|
|
@@ -26817,14 +27673,15 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
26817
27673
|
* - Raw passthrough only when both registry and adapter fail
|
|
26818
27674
|
*
|
|
26819
27675
|
* @param feeItems - Array of fee items from the service response.
|
|
26820
|
-
* @param chain - The chain definition for token resolution and formatting.
|
|
27676
|
+
* @param chain - The source chain definition for token resolution and formatting.
|
|
27677
|
+
* @param destinationChain - The destination chain, used to resolve fees denominated in the output token.
|
|
26821
27678
|
* @param type - The fee type to assign ('provider' or 'swap').
|
|
26822
27679
|
* @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
|
|
26823
27680
|
* @returns Promise resolving to formatted ServiceSwapFee array.
|
|
26824
|
-
*/ async formatServiceFees(feeItems, chain, type, adapter) {
|
|
27681
|
+
*/ async formatServiceFees(feeItems, chain, destinationChain, type, adapter) {
|
|
26825
27682
|
if (!feeItems) return [];
|
|
26826
27683
|
return Promise.all(feeItems.map(async (fee)=>{
|
|
26827
|
-
const formatted = await
|
|
27684
|
+
const formatted = await this.formatFeeValue(fee, chain, destinationChain, adapter);
|
|
26828
27685
|
return {
|
|
26829
27686
|
token: formatted.token,
|
|
26830
27687
|
amount: formatted.amount,
|
|
@@ -26836,16 +27693,15 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
26836
27693
|
* Format developer fee items into the SDK's ServiceSwapFee structure.
|
|
26837
27694
|
*
|
|
26838
27695
|
* @param feeItems - Array of developer fee items from the service response.
|
|
26839
|
-
* @param chain - The chain definition for token resolution and formatting.
|
|
27696
|
+
* @param chain - The source chain definition for token resolution and formatting.
|
|
27697
|
+
* @param destinationChain - The destination chain, used to resolve fees denominated in the output token.
|
|
26840
27698
|
* @param recipientAddress - The developer's fee recipient address from config.
|
|
26841
27699
|
* @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
|
|
26842
27700
|
* @returns Promise resolving to formatted ServiceSwapFee array with developer entries.
|
|
26843
27701
|
*/ async formatDeveloperFees(feeItems, chain, destinationChain, recipientAddress, adapter) {
|
|
26844
27702
|
if (!feeItems) return [];
|
|
26845
|
-
const isCrossChainSwap = destinationChain.chain !== chain.chain;
|
|
26846
27703
|
return Promise.all(feeItems.map(async (fee)=>{
|
|
26847
|
-
const
|
|
26848
|
-
const formatted = await formatTokenValue(fee.amount, fee.token, feeChain, adapter);
|
|
27704
|
+
const formatted = await this.formatFeeValue(fee, chain, destinationChain, adapter);
|
|
26849
27705
|
return {
|
|
26850
27706
|
token: formatted.token,
|
|
26851
27707
|
amount: formatted.amount,
|
|
@@ -28885,7 +29741,7 @@ const isCrossChain = (fromChain, toChain)=>toChain !== undefined && toChain.chai
|
|
|
28885
29741
|
/**
|
|
28886
29742
|
* Resolve fee recipient address from context or policy.
|
|
28887
29743
|
* @internal
|
|
28888
|
-
*/ async function resolveFeeRecipient(context, params, existingRecipient) {
|
|
29744
|
+
*/ async function resolveFeeRecipient$1(context, params, existingRecipient) {
|
|
28889
29745
|
if (existingRecipient) {
|
|
28890
29746
|
return existingRecipient;
|
|
28891
29747
|
}
|
|
@@ -28913,7 +29769,7 @@ const isCrossChain = (fromChain, toChain)=>toChain !== undefined && toChain.chai
|
|
|
28913
29769
|
* @internal
|
|
28914
29770
|
*/ async function applyExistingFeeAmount(context, params, existingAmount, existingFeeRecipient) {
|
|
28915
29771
|
await validateFeeAmount(context, params, existingAmount);
|
|
28916
|
-
const feeRecipient = await resolveFeeRecipient(context, params, existingFeeRecipient);
|
|
29772
|
+
const feeRecipient = await resolveFeeRecipient$1(context, params, existingFeeRecipient);
|
|
28917
29773
|
if (feeRecipient) {
|
|
28918
29774
|
params.config = {
|
|
28919
29775
|
...params.config,
|
|
@@ -28947,7 +29803,7 @@ const isCrossChain = (fromChain, toChain)=>toChain !== undefined && toChain.chai
|
|
|
28947
29803
|
...params,
|
|
28948
29804
|
type: 'input'
|
|
28949
29805
|
};
|
|
28950
|
-
const feeRecipient = await resolveFeeRecipient(context, params, existingFeeRecipient);
|
|
29806
|
+
const feeRecipient = await resolveFeeRecipient$1(context, params, existingFeeRecipient);
|
|
28951
29807
|
const amount = await context.customFeePolicy?.computeFee(feeContext);
|
|
28952
29808
|
if (amount !== undefined) {
|
|
28953
29809
|
await validateInputFee(context, params, BigInt(amount), amount);
|
|
@@ -29240,22 +30096,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
29240
30096
|
try {
|
|
29241
30097
|
// Step 1: Build quote params directly (no need for buildServiceParams)
|
|
29242
30098
|
// Use chain.chain (Blockchain enum value like "World_Chain") not chain.name
|
|
30099
|
+
// The kit key is optional (permissionless mode); when absent the quote is
|
|
30100
|
+
// fetched without an Authorization header.
|
|
29243
30101
|
const kitKey = params.config?.kitKey;
|
|
29244
|
-
if (!kitKey) {
|
|
29245
|
-
throw new KitError({
|
|
29246
|
-
code: 1098,
|
|
29247
|
-
name: 'INPUT_VALIDATION_FAILED',
|
|
29248
|
-
type: 'INPUT',
|
|
29249
|
-
recoverability: 'FATAL',
|
|
29250
|
-
message: 'kitKey is required in config for callback-based fees',
|
|
29251
|
-
cause: {
|
|
29252
|
-
trace: {
|
|
29253
|
-
operation: 'handleOutputFeeCallback',
|
|
29254
|
-
params
|
|
29255
|
-
}
|
|
29256
|
-
}
|
|
29257
|
-
});
|
|
29258
|
-
}
|
|
29259
30102
|
// Resolve token aliases to addresses for the quote API
|
|
29260
30103
|
// The quote endpoint requires resolved addresses, not aliases like 'USDC'
|
|
29261
30104
|
const chain = params.from.chain;
|
|
@@ -29280,7 +30123,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
29280
30123
|
...params.config?.slippageBps !== undefined && {
|
|
29281
30124
|
slippageBps: params.config.slippageBps
|
|
29282
30125
|
},
|
|
29283
|
-
|
|
30126
|
+
...kitKey ? {
|
|
30127
|
+
apiKey: kitKey
|
|
30128
|
+
} : {}
|
|
29284
30129
|
};
|
|
29285
30130
|
// Step 2: Get quote from service
|
|
29286
30131
|
const quoteResponse = await getQuote(quoteParams);
|
|
@@ -29722,7 +30567,9 @@ const sleep$2 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
29722
30567
|
...isCrossChain && {
|
|
29723
30568
|
toChain: chainOut
|
|
29724
30569
|
},
|
|
29725
|
-
|
|
30570
|
+
...params.kitKey ? {
|
|
30571
|
+
apiKey: params.kitKey
|
|
30572
|
+
} : {}
|
|
29726
30573
|
};
|
|
29727
30574
|
let raw = await getSwapStatus$2(request);
|
|
29728
30575
|
// When the service hasn't finished indexing a just-submitted swap it
|
|
@@ -29862,7 +30709,9 @@ const isResultShape = (params)=>'result' in params;
|
|
|
29862
30709
|
...chainOut !== undefined && {
|
|
29863
30710
|
chainOut
|
|
29864
30711
|
},
|
|
29865
|
-
|
|
30712
|
+
...params.kitKey ? {
|
|
30713
|
+
kitKey: params.kitKey
|
|
30714
|
+
} : {}
|
|
29866
30715
|
};
|
|
29867
30716
|
const deadline = Date.now() + timeoutMs;
|
|
29868
30717
|
let pollIndex = 0;
|
|
@@ -30023,7 +30872,9 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
30023
30872
|
const resolvedAddresses = params.tokens?.map((entry, index)=>resolveTokenEntry(entry, index, chain, chainDef, context));
|
|
30024
30873
|
return getTokenRates$2({
|
|
30025
30874
|
chain,
|
|
30026
|
-
|
|
30875
|
+
...params.kitKey ? {
|
|
30876
|
+
apiKey: params.kitKey
|
|
30877
|
+
} : {},
|
|
30027
30878
|
...resolvedAddresses !== undefined && {
|
|
30028
30879
|
addresses: resolvedAddresses
|
|
30029
30880
|
}
|
|
@@ -31020,7 +31871,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
|
31020
31871
|
};
|
|
31021
31872
|
|
|
31022
31873
|
var name$2 = "@circle-fin/earn-kit";
|
|
31023
|
-
var version$2 = "1.
|
|
31874
|
+
var version$2 = "1.3.0";
|
|
31024
31875
|
var pkg$2 = {
|
|
31025
31876
|
name: name$2,
|
|
31026
31877
|
version: version$2};
|
|
@@ -31487,7 +32338,7 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
31487
32338
|
*
|
|
31488
32339
|
* @param params - Adapter, chain, token/delegate/wallet addresses, the required
|
|
31489
32340
|
* allowance for the signed payload, and a revert message for on-chain failure.
|
|
31490
|
-
* @returns The approval transaction
|
|
32341
|
+
* @returns The approval transaction result when an approval was submitted, or
|
|
31491
32342
|
* `undefined` when the existing allowance already covers `requiredAllowance`
|
|
31492
32343
|
* (or `requiredAllowance` is zero).
|
|
31493
32344
|
* @throws {@link KitError} If the `token.allowance` response is malformed.
|
|
@@ -31495,7 +32346,7 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
31495
32346
|
*
|
|
31496
32347
|
* @example
|
|
31497
32348
|
* ```typescript
|
|
31498
|
-
* const
|
|
32349
|
+
* const approval = await approveAllowanceIfNeeded({
|
|
31499
32350
|
* adapter,
|
|
31500
32351
|
* chain,
|
|
31501
32352
|
* tokenAddress: usdcAddress,
|
|
@@ -31557,7 +32408,15 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
31557
32408
|
maxAttempts: params.allowancePropagation?.maxAttempts ?? DEFAULT_PROPAGATION_ATTEMPTS,
|
|
31558
32409
|
delayMs: params.allowancePropagation?.delayMs ?? DEFAULT_PROPAGATION_DELAY_MS
|
|
31559
32410
|
});
|
|
31560
|
-
return
|
|
32411
|
+
return {
|
|
32412
|
+
txHash: approvalTxHash,
|
|
32413
|
+
...approvalReceipt.gasUsed !== undefined && {
|
|
32414
|
+
gasUsed: approvalReceipt.gasUsed
|
|
32415
|
+
},
|
|
32416
|
+
...approvalReceipt.effectiveGasPrice !== undefined && {
|
|
32417
|
+
effectiveGasPrice: approvalReceipt.effectiveGasPrice
|
|
32418
|
+
}
|
|
32419
|
+
};
|
|
31561
32420
|
}
|
|
31562
32421
|
|
|
31563
32422
|
/** @internal */ function isSameAddress(actual, expected) {
|
|
@@ -31705,7 +32564,13 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
31705
32564
|
}
|
|
31706
32565
|
return {
|
|
31707
32566
|
txHash,
|
|
31708
|
-
explorerUrl
|
|
32567
|
+
explorerUrl,
|
|
32568
|
+
...receipt.gasUsed !== undefined && {
|
|
32569
|
+
gasUsed: receipt.gasUsed
|
|
32570
|
+
},
|
|
32571
|
+
...receipt.effectiveGasPrice !== undefined && {
|
|
32572
|
+
effectiveGasPrice: receipt.effectiveGasPrice
|
|
32573
|
+
}
|
|
31709
32574
|
};
|
|
31710
32575
|
}
|
|
31711
32576
|
|
|
@@ -32017,112 +32882,6 @@ const EARN_OPERATIONS = new Set([
|
|
|
32017
32882
|
return hasEarnServiceParamsShape(operation, candidate['params']);
|
|
32018
32883
|
}
|
|
32019
32884
|
|
|
32020
|
-
function buildGasFeeBase(name, chain) {
|
|
32021
|
-
return {
|
|
32022
|
-
name,
|
|
32023
|
-
token: chain.nativeCurrency.symbol,
|
|
32024
|
-
blockchain: chain.chain
|
|
32025
|
-
};
|
|
32026
|
-
}
|
|
32027
|
-
function buildGasFeeSuccess(name, chain, fees) {
|
|
32028
|
-
return {
|
|
32029
|
-
...buildGasFeeBase(name, chain),
|
|
32030
|
-
fees
|
|
32031
|
-
};
|
|
32032
|
-
}
|
|
32033
|
-
function buildGasFeeFailure(name, chain, error) {
|
|
32034
|
-
return {
|
|
32035
|
-
...buildGasFeeBase(name, chain),
|
|
32036
|
-
fees: null,
|
|
32037
|
-
error: getErrorMessage(error)
|
|
32038
|
-
};
|
|
32039
|
-
}
|
|
32040
|
-
async function estimatePreparedGasFee(name, chain, prepared) {
|
|
32041
|
-
try {
|
|
32042
|
-
const estimate = bufferEstimatedGas(await prepared.estimate());
|
|
32043
|
-
if (estimate.gas <= 0n) {
|
|
32044
|
-
throw createValidationFailedError$1('estimate.gas', estimate.gas.toString(), 'gas estimate must be greater than zero');
|
|
32045
|
-
}
|
|
32046
|
-
return buildGasFeeSuccess(name, chain, estimate);
|
|
32047
|
-
} catch (error) {
|
|
32048
|
-
return buildGasFeeFailure(name, chain, error);
|
|
32049
|
-
}
|
|
32050
|
-
}
|
|
32051
|
-
async function estimateApprovalGasFeeIfNeeded(params) {
|
|
32052
|
-
const { adapter, chain, address, tokenAddress, delegate, requiredAllowance } = params;
|
|
32053
|
-
if (requiredAllowance <= 0n) {
|
|
32054
|
-
return undefined;
|
|
32055
|
-
}
|
|
32056
|
-
try {
|
|
32057
|
-
const allowancePrepared = await adapter.prepareAction('token.allowance', {
|
|
32058
|
-
tokenAddress,
|
|
32059
|
-
delegate
|
|
32060
|
-
}, {
|
|
32061
|
-
chain,
|
|
32062
|
-
address
|
|
32063
|
-
});
|
|
32064
|
-
const allowanceRaw = await allowancePrepared.execute();
|
|
32065
|
-
const currentAllowance = parseAllowanceResponse(allowanceRaw);
|
|
32066
|
-
if (currentAllowance >= requiredAllowance) {
|
|
32067
|
-
return undefined;
|
|
32068
|
-
}
|
|
32069
|
-
// Reuse the execute path's approval builder so the estimate simulates the
|
|
32070
|
-
// exact approval (action, amount, and WARM_SLOT_RESIDUAL) that
|
|
32071
|
-
// approveAllowanceIfNeeded later submits.
|
|
32072
|
-
const approvalPrepared = await prepareApprovalAction({
|
|
32073
|
-
adapter,
|
|
32074
|
-
chain,
|
|
32075
|
-
address,
|
|
32076
|
-
tokenAddress,
|
|
32077
|
-
delegate,
|
|
32078
|
-
currentAllowance,
|
|
32079
|
-
requiredAllowance
|
|
32080
|
-
});
|
|
32081
|
-
return await estimatePreparedGasFee('Approve', chain, approvalPrepared);
|
|
32082
|
-
} catch (error) {
|
|
32083
|
-
return buildGasFeeFailure('Approve', chain, error);
|
|
32084
|
-
}
|
|
32085
|
-
}
|
|
32086
|
-
/**
|
|
32087
|
-
* Estimate gas fee entries for an earn quote without submitting transactions.
|
|
32088
|
-
*
|
|
32089
|
-
* Each entry is produced by simulating the prepared transaction against
|
|
32090
|
-
* current chain state. When an approval is required (allowance below the
|
|
32091
|
-
* signed payload's required amount), the subsequent action simulation runs
|
|
32092
|
-
* without that approval in place and is expected to revert — the action entry
|
|
32093
|
-
* then carries `fees: null` with the revert message while the approval entry
|
|
32094
|
-
* still estimates normally. Quote consumers must treat that as "estimate
|
|
32095
|
-
* pending approval", not a hard failure.
|
|
32096
|
-
*
|
|
32097
|
-
* @internal
|
|
32098
|
-
*/ async function estimateEarnQuoteGasFees(params) {
|
|
32099
|
-
const { adapter, chain, address, actionName, actionKey, actionParams, approval } = params;
|
|
32100
|
-
const gasFees = [];
|
|
32101
|
-
if (approval !== undefined) {
|
|
32102
|
-
const approvalEstimate = await estimateApprovalGasFeeIfNeeded({
|
|
32103
|
-
adapter,
|
|
32104
|
-
chain,
|
|
32105
|
-
address,
|
|
32106
|
-
tokenAddress: approval.token,
|
|
32107
|
-
delegate: approval.delegate,
|
|
32108
|
-
requiredAllowance: approval.requiredAllowance
|
|
32109
|
-
});
|
|
32110
|
-
if (approvalEstimate !== undefined) {
|
|
32111
|
-
gasFees.push(approvalEstimate);
|
|
32112
|
-
}
|
|
32113
|
-
}
|
|
32114
|
-
try {
|
|
32115
|
-
const actionPrepared = await adapter.prepareAction(actionKey, actionParams, {
|
|
32116
|
-
chain,
|
|
32117
|
-
address
|
|
32118
|
-
});
|
|
32119
|
-
gasFees.push(await estimatePreparedGasFee(actionName, chain, actionPrepared));
|
|
32120
|
-
} catch (error) {
|
|
32121
|
-
gasFees.push(buildGasFeeFailure(actionName, chain, error));
|
|
32122
|
-
}
|
|
32123
|
-
return gasFees;
|
|
32124
|
-
}
|
|
32125
|
-
|
|
32126
32885
|
// ---------------------------------------------------------------------------
|
|
32127
32886
|
// Shared primitives
|
|
32128
32887
|
// ---------------------------------------------------------------------------
|
|
@@ -32200,7 +32959,11 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
32200
32959
|
asset: zod.z.string(),
|
|
32201
32960
|
assetAddress: zod.z.string(),
|
|
32202
32961
|
lltv: zod.z.number(),
|
|
32203
|
-
supplyUsd: zod.z.number()
|
|
32962
|
+
supplyUsd: zod.z.number(),
|
|
32963
|
+
// Optional during the expand/contract window (a backend that predates the
|
|
32964
|
+
// field omits the key), mirroring the `.optional()` facets on the base
|
|
32965
|
+
// schema; `null` when the product exposes no per-market allocation (V2).
|
|
32966
|
+
allocationPct: zod.z.number().nullable().optional()
|
|
32204
32967
|
});
|
|
32205
32968
|
/**
|
|
32206
32969
|
* Zod schema for a Morpho vault warning in the API response.
|
|
@@ -32214,7 +32977,74 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
32214
32977
|
])
|
|
32215
32978
|
});
|
|
32216
32979
|
/**
|
|
32217
|
-
* Zod schema for
|
|
32980
|
+
* Zod schema for the manager (curator) facet in the API response.
|
|
32981
|
+
*
|
|
32982
|
+
* @internal
|
|
32983
|
+
*/ const managerSchema = zod.z.object({
|
|
32984
|
+
name: zod.z.string(),
|
|
32985
|
+
address: zod.z.string().optional(),
|
|
32986
|
+
// Only 'curator' is emitted today (Morpho V1/V2). Additional manager roles
|
|
32987
|
+
// are added here as the providers that emit them land, rather than shipped
|
|
32988
|
+
// speculatively.
|
|
32989
|
+
type: zod.z.enum([
|
|
32990
|
+
'curator'
|
|
32991
|
+
])
|
|
32992
|
+
});
|
|
32993
|
+
/**
|
|
32994
|
+
* Zod schema for the APY profile facet in the API response.
|
|
32995
|
+
*
|
|
32996
|
+
* @internal
|
|
32997
|
+
*/ const apyProfileSchema = zod.z.object({
|
|
32998
|
+
current: zod.z.number(),
|
|
32999
|
+
native: zod.z.number().nullable(),
|
|
33000
|
+
d7: zod.z.number().nullable(),
|
|
33001
|
+
d30: zod.z.number().nullable(),
|
|
33002
|
+
d90: zod.z.number().nullable(),
|
|
33003
|
+
rewardShare: zod.z.number().nullable(),
|
|
33004
|
+
source: zod.z.string().optional(),
|
|
33005
|
+
asOf: zod.z.string().optional()
|
|
33006
|
+
});
|
|
33007
|
+
/**
|
|
33008
|
+
* Zod schema for the fee split facet in the API response.
|
|
33009
|
+
*
|
|
33010
|
+
* @internal
|
|
33011
|
+
*/ const feeInfoSchema = zod.z.object({
|
|
33012
|
+
performance: zod.z.number().nullable(),
|
|
33013
|
+
management: zod.z.number().nullable()
|
|
33014
|
+
});
|
|
33015
|
+
/**
|
|
33016
|
+
* Zod schema for the liquidity profile facet in the API response.
|
|
33017
|
+
*
|
|
33018
|
+
* `totalSupply` is the outstanding vault share tokens (ERC4626 totalSupply);
|
|
33019
|
+
* it is validated as a raw JSON amount, like `totalDeposits`/`available`.
|
|
33020
|
+
*
|
|
33021
|
+
* @internal
|
|
33022
|
+
*/ const liquidityProfileSchema = zod.z.object({
|
|
33023
|
+
totalDeposits: amountJsonSchema,
|
|
33024
|
+
available: amountJsonSchema,
|
|
33025
|
+
totalSupply: amountJsonSchema,
|
|
33026
|
+
status: zod.z.enum([
|
|
33027
|
+
'active',
|
|
33028
|
+
'low_liquidity'
|
|
33029
|
+
])
|
|
33030
|
+
});
|
|
33031
|
+
/**
|
|
33032
|
+
* Zod schema for the risk signals facet in the API response.
|
|
33033
|
+
*
|
|
33034
|
+
* @internal
|
|
33035
|
+
*/ const riskSignalsSchema = zod.z.object({
|
|
33036
|
+
circleSentinel: zod.z.boolean(),
|
|
33037
|
+
warnings: zod.z.array(vaultWarningSchema).optional(),
|
|
33038
|
+
earnKitWarnings: zod.z.array(zod.z.string()).optional()
|
|
33039
|
+
});
|
|
33040
|
+
/**
|
|
33041
|
+
* Zod schema for the universal earn-opportunity base in the API response.
|
|
33042
|
+
*
|
|
33043
|
+
* Retains every existing deprecated flat field (kept validated through the
|
|
33044
|
+
* expand/contract window so default-strip does not drop them) and adds the
|
|
33045
|
+
* new nested facets. The nested facets are `.optional()` during the
|
|
33046
|
+
* transition so the SDK still validates against a not-yet-fully-deployed
|
|
33047
|
+
* backend; they become required after Expand ships.
|
|
32218
33048
|
*
|
|
32219
33049
|
* @internal
|
|
32220
33050
|
*/ const vaultInfoResponseSchema = zod.z.object({
|
|
@@ -32239,6 +33069,96 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
32239
33069
|
warnings: zod.z.array(vaultWarningSchema).optional(),
|
|
32240
33070
|
earnKitWarnings: zod.z.array(zod.z.string()).optional()
|
|
32241
33071
|
});
|
|
33072
|
+
/**
|
|
33073
|
+
* Shared base schema: existing flat fields (kept) plus the new nested
|
|
33074
|
+
* facets and neutral identity. Facets are `.optional()` during the
|
|
33075
|
+
* transition; flip to required once the backend is confirmed emitting.
|
|
33076
|
+
*
|
|
33077
|
+
* @internal
|
|
33078
|
+
*/ const earnBaseSchema = vaultInfoResponseSchema.extend({
|
|
33079
|
+
address: zod.z.string().optional(),
|
|
33080
|
+
asOf: zod.z.string().optional(),
|
|
33081
|
+
manager: managerSchema.nullable().optional(),
|
|
33082
|
+
apyProfile: apyProfileSchema.optional(),
|
|
33083
|
+
fee: feeInfoSchema.optional(),
|
|
33084
|
+
liquidityProfile: liquidityProfileSchema.optional(),
|
|
33085
|
+
riskSignals: riskSignalsSchema.optional()
|
|
33086
|
+
});
|
|
33087
|
+
/**
|
|
33088
|
+
* Zod schema for the `vault` opportunity variant.
|
|
33089
|
+
*
|
|
33090
|
+
* @internal
|
|
33091
|
+
*/ const vaultOpportunitySchema = earnBaseSchema.extend({
|
|
33092
|
+
productType: zod.z.literal('vault'),
|
|
33093
|
+
collateral: zod.z.array(collateralSchema)
|
|
33094
|
+
});
|
|
33095
|
+
/**
|
|
33096
|
+
* Discriminated union over `productType`. Add union members here as new
|
|
33097
|
+
* product types (e.g. `lending_market`, `rwa_token`) land.
|
|
33098
|
+
*
|
|
33099
|
+
* @internal
|
|
33100
|
+
*/ const earnOpportunityVariants = [
|
|
33101
|
+
vaultOpportunitySchema
|
|
33102
|
+
];
|
|
33103
|
+
/** @internal */ const earnOpportunitySchema = zod.z.discriminatedUnion('productType', earnOpportunityVariants);
|
|
33104
|
+
/** Product types this SDK version knows how to parse. */ const knownProductTypes = new Set(earnOpportunityVariants.map((variant)=>variant.shape.productType.value));
|
|
33105
|
+
/**
|
|
33106
|
+
* Tolerant list parser for earn opportunities.
|
|
33107
|
+
*
|
|
33108
|
+
* `z.discriminatedUnion` throws on an unrecognized discriminant and
|
|
33109
|
+
* `z.array` fails the whole array if any element fails. Two migration-window
|
|
33110
|
+
* cases are smoothed over here so neither breaks an already-shipped SDK:
|
|
33111
|
+
*
|
|
33112
|
+
* - A backend that predates `productType` omits it entirely. `'vault'` was the
|
|
33113
|
+
* only opportunity type then, so default a missing discriminant to `'vault'`
|
|
33114
|
+
* rather than dropping every vault the backend returns.
|
|
33115
|
+
* - A future backend adds a *second* `productType` this SDK version does not
|
|
33116
|
+
* know. Drop those elements (a present-but-unrecognized discriminant) instead
|
|
33117
|
+
* of rejecting the whole list.
|
|
33118
|
+
*
|
|
33119
|
+
* Only the drop above is a *tolerant* case. Anything that is not a plain object
|
|
33120
|
+
* with a present-but-unknown string `productType` — `null`, `undefined`,
|
|
33121
|
+
* primitives, or an object whose `productType` is malformed — is passed through
|
|
33122
|
+
* untouched so `z.array(earnOpportunitySchema)` reports it as a normal
|
|
33123
|
+
* validation failure. It is deliberately not silently dropped (which would hide
|
|
33124
|
+
* malformed backend data) and never throws here (an unguarded property read on
|
|
33125
|
+
* a non-object would escape `safeParse` as a raw `TypeError` instead of a
|
|
33126
|
+
* `ZodError`).
|
|
33127
|
+
*
|
|
33128
|
+
* @internal
|
|
33129
|
+
*/ const earnOpportunityListSchema = zod.z.preprocess((raw)=>{
|
|
33130
|
+
if (!Array.isArray(raw)) {
|
|
33131
|
+
return raw;
|
|
33132
|
+
}
|
|
33133
|
+
// Array.isArray narrows `raw` to `any[]`; view it as `unknown[]` so the
|
|
33134
|
+
// map/filter chain stays type-safe and no `any` leaks into the return.
|
|
33135
|
+
const entries = raw;
|
|
33136
|
+
return entries.map((entry)=>{
|
|
33137
|
+
// Only touch plain objects; non-objects fall through to fail validation.
|
|
33138
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
33139
|
+
return entry;
|
|
33140
|
+
}
|
|
33141
|
+
const record = entry;
|
|
33142
|
+
// Older backend predating productType: default to the only type then.
|
|
33143
|
+
return record.productType === undefined ? {
|
|
33144
|
+
...record,
|
|
33145
|
+
productType: 'vault'
|
|
33146
|
+
} : record;
|
|
33147
|
+
}).filter((entry)=>{
|
|
33148
|
+
// Drop ONLY a present-but-unknown string discriminant (a future
|
|
33149
|
+
// productType this SDK version doesn't know). Everything else —
|
|
33150
|
+
// non-objects, a non-string productType — flows through to
|
|
33151
|
+
// z.array(earnOpportunitySchema) and fails/passes validation normally.
|
|
33152
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
33153
|
+
return true;
|
|
33154
|
+
}
|
|
33155
|
+
const productType = entry.productType;
|
|
33156
|
+
if (typeof productType !== 'string') {
|
|
33157
|
+
return true;
|
|
33158
|
+
}
|
|
33159
|
+
return knownProductTypes.has(productType);
|
|
33160
|
+
});
|
|
33161
|
+
}, zod.z.array(earnOpportunitySchema));
|
|
32242
33162
|
// ---------------------------------------------------------------------------
|
|
32243
33163
|
// Position response schema
|
|
32244
33164
|
// ---------------------------------------------------------------------------
|
|
@@ -32368,6 +33288,7 @@ const positionPnlSchema = zod.z.discriminatedUnion('status', [
|
|
|
32368
33288
|
*
|
|
32369
33289
|
* @internal
|
|
32370
33290
|
*/ const depositPayloadSchema = zod.z.object({
|
|
33291
|
+
execId: bridgeDepositExecIdSchema,
|
|
32371
33292
|
executionParams: depositExecutionParamsSchema,
|
|
32372
33293
|
signature: hexSignatureSchema
|
|
32373
33294
|
});
|
|
@@ -32459,6 +33380,21 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32459
33380
|
amount: amountJsonSchema,
|
|
32460
33381
|
vaultAddress: hexAddressSchema
|
|
32461
33382
|
}).passthrough();
|
|
33383
|
+
/** @internal */ const bridgeQuoteExpirySchema = zod.z.discriminatedUnion('mode', [
|
|
33384
|
+
zod.z.object({
|
|
33385
|
+
mode: zod.z.literal('TIMESTAMP'),
|
|
33386
|
+
expiresAt: zod.z.string().datetime({
|
|
33387
|
+
offset: true
|
|
33388
|
+
})
|
|
33389
|
+
}),
|
|
33390
|
+
zod.z.object({
|
|
33391
|
+
mode: zod.z.literal('BLOCK_NUMBER'),
|
|
33392
|
+
expiresAtBlock: zod.z.number().int(),
|
|
33393
|
+
blockEstimatedAt: zod.z.string().datetime({
|
|
33394
|
+
offset: true
|
|
33395
|
+
}).optional()
|
|
33396
|
+
})
|
|
33397
|
+
]).optional().catch(undefined);
|
|
32462
33398
|
/**
|
|
32463
33399
|
* Zod schema for the bridge deposit prepare payload.
|
|
32464
33400
|
*
|
|
@@ -32470,6 +33406,10 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32470
33406
|
execId: bridgeDepositExecIdSchema,
|
|
32471
33407
|
erc3009TypedData: bridgeDepositPreparedBundleSchema,
|
|
32472
33408
|
expiresAt: zod.z.string().datetime(),
|
|
33409
|
+
quoteIssuedAt: zod.z.string().datetime({
|
|
33410
|
+
offset: true
|
|
33411
|
+
}).optional().catch(undefined),
|
|
33412
|
+
quoteExpiry: bridgeQuoteExpirySchema,
|
|
32473
33413
|
review: bridgeDepositPrepareReviewSchema
|
|
32474
33414
|
});
|
|
32475
33415
|
/**
|
|
@@ -32535,6 +33475,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32535
33475
|
*
|
|
32536
33476
|
* @internal
|
|
32537
33477
|
*/ const withdrawPayloadSchema = zod.z.object({
|
|
33478
|
+
execId: bridgeDepositExecIdSchema,
|
|
32538
33479
|
executionParams: withdrawExecutionParamsSchema,
|
|
32539
33480
|
signature: hexSignatureSchema
|
|
32540
33481
|
});
|
|
@@ -32548,6 +33489,27 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32548
33489
|
data: withdrawPayloadSchema
|
|
32549
33490
|
});
|
|
32550
33491
|
// ---------------------------------------------------------------------------
|
|
33492
|
+
// Transaction report response schema
|
|
33493
|
+
// ---------------------------------------------------------------------------
|
|
33494
|
+
/**
|
|
33495
|
+
* Zod schema for the transaction report payload inside the API `data` envelope.
|
|
33496
|
+
*
|
|
33497
|
+
* The Earn Service returns an empty payload (`{"data":{}}`) on success, so the
|
|
33498
|
+
* schema accepts any object shape and does not require specific fields.
|
|
33499
|
+
*
|
|
33500
|
+
* @internal
|
|
33501
|
+
*/ const transactionReportPayloadSchema = zod.z.object({}).passthrough();
|
|
33502
|
+
/**
|
|
33503
|
+
* Zod schema for the `POST /v1/earnKit/transactions/report` API response.
|
|
33504
|
+
*
|
|
33505
|
+
* The Earn Service API wraps the transaction report payload in a `data`
|
|
33506
|
+
* envelope.
|
|
33507
|
+
*
|
|
33508
|
+
* @internal
|
|
33509
|
+
*/ const transactionReportResponseSchema = zod.z.object({
|
|
33510
|
+
data: transactionReportPayloadSchema
|
|
33511
|
+
});
|
|
33512
|
+
// ---------------------------------------------------------------------------
|
|
32551
33513
|
// Claim rewards response schema
|
|
32552
33514
|
// ---------------------------------------------------------------------------
|
|
32553
33515
|
/**
|
|
@@ -32608,6 +33570,30 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32608
33570
|
token: zod.z.string(),
|
|
32609
33571
|
amount: amountJsonSchema
|
|
32610
33572
|
});
|
|
33573
|
+
/**
|
|
33574
|
+
* Zod schema for a native gas-fee entry in an EarnKit quote response.
|
|
33575
|
+
*
|
|
33576
|
+
* The Earn Service backend estimates gas server-side and returns one entry per
|
|
33577
|
+
* action (`Approve`, `Deposit`, `Withdraw`). A successful estimate carries
|
|
33578
|
+
* `fees` in the SDK `EstimatedGas` shape (`{ gas, gasPrice, fee }`), each a raw
|
|
33579
|
+
* integer string in the chain's native base units. When the backend cannot
|
|
33580
|
+
* estimate an action it returns `fees: null` with an `error` message instead.
|
|
33581
|
+
*
|
|
33582
|
+
* The schema deliberately validates almost nothing beyond the envelope: `name`
|
|
33583
|
+
* is optional and `fees` is entirely unvalidated (`unknown`). ALL validation
|
|
33584
|
+
* of `fees` — that it is an object at all, and that `gas`, `gasPrice`, and
|
|
33585
|
+
* `fee` are parseable integer strings — is deferred to {@link toQuoteGasFees},
|
|
33586
|
+
* which degrades a malformed entry to a `fees: null` soft failure. This is
|
|
33587
|
+
* intentional: gas is best-effort, so a single unparseable gas entry (a wrong
|
|
33588
|
+
* type such as `fees: 123` or `fees: 'bad'`, a missing field, or a non-numeric
|
|
33589
|
+
* `fee`) must never fail Zod validation and reject the entire quote.
|
|
33590
|
+
*
|
|
33591
|
+
* @internal
|
|
33592
|
+
*/ const quoteGasFeeSchema = zod.z.object({
|
|
33593
|
+
name: zod.z.string().optional(),
|
|
33594
|
+
fees: zod.z.unknown(),
|
|
33595
|
+
error: zod.z.string().optional()
|
|
33596
|
+
}).passthrough();
|
|
32611
33597
|
/**
|
|
32612
33598
|
* Zod schema for the inner deposit quote payload.
|
|
32613
33599
|
*
|
|
@@ -32623,7 +33609,8 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32623
33609
|
expectedShares: amountJsonSchema,
|
|
32624
33610
|
sharePrice: zod.z.string(),
|
|
32625
33611
|
currentApy: zod.z.number(),
|
|
32626
|
-
fees: zod.z.array(feeSchema).optional()
|
|
33612
|
+
fees: zod.z.array(feeSchema).optional(),
|
|
33613
|
+
gasFees: zod.z.array(quoteGasFeeSchema).optional()
|
|
32627
33614
|
});
|
|
32628
33615
|
/**
|
|
32629
33616
|
* Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
|
|
@@ -32650,6 +33637,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32650
33637
|
sharePrice: zod.z.string(),
|
|
32651
33638
|
maxWithdrawable: amountJsonSchema,
|
|
32652
33639
|
fees: zod.z.array(feeSchema),
|
|
33640
|
+
gasFees: zod.z.array(quoteGasFeeSchema).optional(),
|
|
32653
33641
|
warnings: zod.z.array(zod.z.string()).optional()
|
|
32654
33642
|
});
|
|
32655
33643
|
/**
|
|
@@ -32707,7 +33695,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32707
33695
|
*
|
|
32708
33696
|
* @internal
|
|
32709
33697
|
*/ const getVaultsPayloadSchema = zod.z.object({
|
|
32710
|
-
vaults:
|
|
33698
|
+
vaults: earnOpportunityListSchema,
|
|
32711
33699
|
errors: zod.z.array(vaultErrorSchema)
|
|
32712
33700
|
});
|
|
32713
33701
|
/**
|
|
@@ -32737,7 +33725,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32737
33725
|
*
|
|
32738
33726
|
* @internal
|
|
32739
33727
|
*/ const exploreVaultsPayloadSchema = zod.z.object({
|
|
32740
|
-
vaults:
|
|
33728
|
+
vaults: earnOpportunityListSchema,
|
|
32741
33729
|
pagination: explorePaginationSchema
|
|
32742
33730
|
});
|
|
32743
33731
|
/**
|
|
@@ -32830,6 +33818,16 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32830
33818
|
*/ function isWithdrawResponse(value) {
|
|
32831
33819
|
return withdrawResponseSchema.safeParse(value).success;
|
|
32832
33820
|
}
|
|
33821
|
+
/**
|
|
33822
|
+
* Type guard for the transaction report API response.
|
|
33823
|
+
*
|
|
33824
|
+
* @param value - Unknown response value to validate
|
|
33825
|
+
* @returns True when the value matches the transaction report response shape
|
|
33826
|
+
*
|
|
33827
|
+
* @internal
|
|
33828
|
+
*/ function isTransactionReportResponse(value) {
|
|
33829
|
+
return transactionReportResponseSchema.safeParse(value).success;
|
|
33830
|
+
}
|
|
32833
33831
|
/**
|
|
32834
33832
|
* Type guard for the claim rewards API response.
|
|
32835
33833
|
*
|
|
@@ -32872,7 +33870,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
32872
33870
|
}
|
|
32873
33871
|
|
|
32874
33872
|
var name$1 = "@circle-fin/provider-earn-service";
|
|
32875
|
-
var version$1 = "1.
|
|
33873
|
+
var version$1 = "1.3.0";
|
|
32876
33874
|
var pkg$1 = {
|
|
32877
33875
|
name: name$1,
|
|
32878
33876
|
version: version$1};
|
|
@@ -32992,7 +33990,7 @@ var pkg$1 = {
|
|
|
32992
33990
|
}
|
|
32993
33991
|
|
|
32994
33992
|
/**
|
|
32995
|
-
* Convert an API vault info object into the SDK {@link
|
|
33993
|
+
* Convert an API vault info object into the SDK {@link EarnOpportunity} shape.
|
|
32996
33994
|
*
|
|
32997
33995
|
* Map the API chain code back to the SDK chain identifier and hydrate the
|
|
32998
33996
|
* amount payloads into {@link Amount} instances.
|
|
@@ -33003,16 +34001,29 @@ var pkg$1 = {
|
|
|
33003
34001
|
*
|
|
33004
34002
|
* @internal
|
|
33005
34003
|
*/ function toVaultInfo(data) {
|
|
33006
|
-
const { totalDeposits, liquidity, ...vault } = data;
|
|
34004
|
+
const { totalDeposits, liquidity, liquidityProfile, ...vault } = data;
|
|
33007
34005
|
const chain = toSdkChain(vault.chain);
|
|
33008
34006
|
if (chain === undefined) {
|
|
33009
34007
|
throw createInvalidChainError(vault.chain, 'Chain returned by the Earn Service is not supported by the SDK');
|
|
33010
34008
|
}
|
|
34009
|
+
// The nested facets are `.optional()` in the schema (a backend that predates
|
|
34010
|
+
// them omits them) and are typed optional on `EarnOpportunity` to match.
|
|
34011
|
+
// Convert the nested liquidity amounts when present and pass the remaining
|
|
34012
|
+
// facets straight through; each absent facet stays absent rather than being
|
|
34013
|
+
// asserted present by a cast.
|
|
33011
34014
|
return {
|
|
33012
34015
|
...vault,
|
|
33013
34016
|
chain,
|
|
33014
34017
|
totalDeposits: Amount.fromJSON(totalDeposits),
|
|
33015
|
-
liquidity: Amount.fromJSON(liquidity)
|
|
34018
|
+
liquidity: Amount.fromJSON(liquidity),
|
|
34019
|
+
...liquidityProfile !== undefined && {
|
|
34020
|
+
liquidityProfile: {
|
|
34021
|
+
...liquidityProfile,
|
|
34022
|
+
totalDeposits: Amount.fromJSON(liquidityProfile.totalDeposits),
|
|
34023
|
+
available: Amount.fromJSON(liquidityProfile.available),
|
|
34024
|
+
totalSupply: Amount.fromJSON(liquidityProfile.totalSupply)
|
|
34025
|
+
}
|
|
34026
|
+
}
|
|
33016
34027
|
};
|
|
33017
34028
|
}
|
|
33018
34029
|
|
|
@@ -33047,8 +34058,11 @@ function toVaultError(error) {
|
|
|
33047
34058
|
}
|
|
33048
34059
|
try {
|
|
33049
34060
|
const response = await pollApiGet(url.toString(), isGetVaultsResponse, pollingConfig);
|
|
34061
|
+
// `pollApiGet` validates via a boolean guard and returns the raw JSON — it
|
|
34062
|
+
// does not run the schema's preprocess. Parse explicitly so unknown
|
|
34063
|
+
// `productType` values are dropped before `toVaultInfo`.
|
|
33050
34064
|
return {
|
|
33051
|
-
vaults: response.data.vaults.map(toVaultInfo),
|
|
34065
|
+
vaults: earnOpportunityListSchema.parse(response.data.vaults).map(toVaultInfo),
|
|
33052
34066
|
errors: response.data.errors.map(toVaultError)
|
|
33053
34067
|
};
|
|
33054
34068
|
} catch (error) {
|
|
@@ -33097,8 +34111,11 @@ function toVaultError(error) {
|
|
|
33097
34111
|
}
|
|
33098
34112
|
try {
|
|
33099
34113
|
const response = await pollApiGet(url.toString(), isExploreVaultsResponse, pollingConfig);
|
|
34114
|
+
// `pollApiGet` validates via a boolean guard and returns the raw JSON — it
|
|
34115
|
+
// does not run the schema's preprocess. Parse explicitly so unknown
|
|
34116
|
+
// `productType` values are dropped before `toVaultInfo`.
|
|
33100
34117
|
return {
|
|
33101
|
-
vaults: response.data.vaults.map(toVaultInfo),
|
|
34118
|
+
vaults: earnOpportunityListSchema.parse(response.data.vaults).map(toVaultInfo),
|
|
33102
34119
|
pagination: response.data.pagination
|
|
33103
34120
|
};
|
|
33104
34121
|
} catch (error) {
|
|
@@ -33262,6 +34279,12 @@ function toPositionInfo(data) {
|
|
|
33262
34279
|
execId: response.data.execId,
|
|
33263
34280
|
preparedBundle,
|
|
33264
34281
|
expiresAt: response.data.expiresAt,
|
|
34282
|
+
...response.data.quoteIssuedAt !== undefined && {
|
|
34283
|
+
quoteIssuedAt: response.data.quoteIssuedAt
|
|
34284
|
+
},
|
|
34285
|
+
...response.data.quoteExpiry !== undefined && {
|
|
34286
|
+
quoteExpiry: response.data.quoteExpiry
|
|
34287
|
+
},
|
|
33265
34288
|
review: response.data.review
|
|
33266
34289
|
};
|
|
33267
34290
|
} catch (error) {
|
|
@@ -33603,7 +34626,110 @@ function toClaimedAmount(reward) {
|
|
|
33603
34626
|
}
|
|
33604
34627
|
}
|
|
33605
34628
|
|
|
33606
|
-
|
|
34629
|
+
/**
|
|
34630
|
+
* Map the Earn Service's server-side quote gas estimates into the SDK
|
|
34631
|
+
* {@link EarnGasFeeEstimate} shape.
|
|
34632
|
+
*
|
|
34633
|
+
* The Earn Service estimates gas for each action (`Approve`, `Deposit`,
|
|
34634
|
+
* `Withdraw`) and returns `{ name, fees: { gas, gasPrice, fee } }` with raw
|
|
34635
|
+
* integer strings.
|
|
34636
|
+
* The SDK type additionally carries `token` (the chain's native currency
|
|
34637
|
+
* symbol) and `blockchain`, which are filled in here from the chain
|
|
34638
|
+
* definition.
|
|
34639
|
+
*
|
|
34640
|
+
* Gas reporting is best-effort: a malformed entry (e.g. a non-integer string
|
|
34641
|
+
* that fails `BigInt` parsing) degrades to a `{ fees: null, error }` estimate
|
|
34642
|
+
* rather than throwing, so one bad entry never fails the whole quote.
|
|
34643
|
+
*
|
|
34644
|
+
* @param gasFees - Backend gas-fee entries from the quote response, if any.
|
|
34645
|
+
* @param chain - Chain definition, used for the native token symbol and
|
|
34646
|
+
* blockchain identifier.
|
|
34647
|
+
* @returns One {@link EarnGasFeeEstimate} per backend entry (empty when the
|
|
34648
|
+
* backend returned none).
|
|
34649
|
+
*
|
|
34650
|
+
* @example
|
|
34651
|
+
* ```typescript
|
|
34652
|
+
* toQuoteGasFees(
|
|
34653
|
+
* [{ name: 'Deposit', fees: { gas: '364142', gasPrice: '21000000000', fee: '7646982000000000' } }],
|
|
34654
|
+
* arcTestnet,
|
|
34655
|
+
* )
|
|
34656
|
+
* // [{ name: 'Deposit', token: 'USDC', blockchain: 'Arc_Testnet',
|
|
34657
|
+
* // fees: { gas: 364142n, gasPrice: 21000000000n, fee: '7646982000000000' } }]
|
|
34658
|
+
* ```
|
|
34659
|
+
*
|
|
34660
|
+
* @internal
|
|
34661
|
+
*/ function toQuoteGasFees(gasFees, chain) {
|
|
34662
|
+
if (gasFees === undefined) {
|
|
34663
|
+
return [];
|
|
34664
|
+
}
|
|
34665
|
+
return gasFees.map((entry)=>{
|
|
34666
|
+
const base = {
|
|
34667
|
+
// `name` is optional on the wire; label an unnamed entry rather than
|
|
34668
|
+
// emitting `name: undefined`.
|
|
34669
|
+
name: entry.name ?? 'Unknown',
|
|
34670
|
+
token: chain.nativeCurrency.symbol,
|
|
34671
|
+
blockchain: chain.chain
|
|
34672
|
+
};
|
|
34673
|
+
// The Earn Service itself reports a failed estimate as `fees: null` with
|
|
34674
|
+
// an error; propagate that soft failure verbatim.
|
|
34675
|
+
if (entry.fees === null || entry.fees === undefined) {
|
|
34676
|
+
return {
|
|
34677
|
+
...base,
|
|
34678
|
+
fees: null,
|
|
34679
|
+
error: entry.error ?? 'gas estimate unavailable'
|
|
34680
|
+
};
|
|
34681
|
+
}
|
|
34682
|
+
// `fees` is `unknown` at the schema layer, so ALL validation happens here:
|
|
34683
|
+
// that it is an object at all, and that `gas`, `gasPrice`, and `fee` are
|
|
34684
|
+
// each parseable integer strings (including `fee`, which the SDK contract
|
|
34685
|
+
// requires be a numeric base-unit string). Any failure — a wrong type
|
|
34686
|
+
// (`fees: 123`), a missing field, or a non-numeric value — degrades the
|
|
34687
|
+
// whole entry to a `fees: null` soft failure rather than surfacing a
|
|
34688
|
+
// malformed "successful" estimate or rejecting the quote.
|
|
34689
|
+
try {
|
|
34690
|
+
if (typeof entry.fees !== 'object') {
|
|
34691
|
+
throw new TypeError(`gas fees must be an object (got ${typeof entry.fees})`);
|
|
34692
|
+
}
|
|
34693
|
+
const { gas, gasPrice, fee } = entry.fees;
|
|
34694
|
+
return {
|
|
34695
|
+
...base,
|
|
34696
|
+
fees: {
|
|
34697
|
+
gas: toBigInt('gas', gas),
|
|
34698
|
+
gasPrice: toBigInt('gasPrice', gasPrice),
|
|
34699
|
+
fee: toBigInt('fee', fee).toString()
|
|
34700
|
+
}
|
|
34701
|
+
};
|
|
34702
|
+
} catch (error) {
|
|
34703
|
+
return {
|
|
34704
|
+
...base,
|
|
34705
|
+
fees: null,
|
|
34706
|
+
error: getErrorMessage(error)
|
|
34707
|
+
};
|
|
34708
|
+
}
|
|
34709
|
+
});
|
|
34710
|
+
}
|
|
34711
|
+
/**
|
|
34712
|
+
* Parse an unknown value into a `bigint`, rejecting anything that is not a
|
|
34713
|
+
* non-empty integer string. `BigInt` alone is too permissive for this path —
|
|
34714
|
+
* it accepts numbers, booleans, and empty strings — so guard the type first.
|
|
34715
|
+
*
|
|
34716
|
+
* @param field - Field name, used in the thrown error message.
|
|
34717
|
+
* @param value - Raw value from the backend gas entry.
|
|
34718
|
+
* @returns The parsed `bigint`.
|
|
34719
|
+
* @throws {TypeError} When `value` is not a non-empty integer string.
|
|
34720
|
+
*/ function toBigInt(field, value) {
|
|
34721
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
34722
|
+
throw new TypeError(`gas fee field "${field}" must be an integer string`);
|
|
34723
|
+
}
|
|
34724
|
+
try {
|
|
34725
|
+
// BigInt throws on non-integer strings (e.g. "1.5", "not-a-number").
|
|
34726
|
+
return BigInt(value);
|
|
34727
|
+
} catch {
|
|
34728
|
+
throw new Error(`gas fee field "${field}" is not a valid integer string: ${value}`);
|
|
34729
|
+
}
|
|
34730
|
+
}
|
|
34731
|
+
|
|
34732
|
+
function toDepositQuoteInfo(data, chain) {
|
|
33607
34733
|
const fees = (data.fees ?? []).map(({ token: feeTokenSymbol, ...fee })=>{
|
|
33608
34734
|
// Earn Service returns fee.token as a display symbol, for example "USDC".
|
|
33609
34735
|
return {
|
|
@@ -33632,7 +34758,10 @@ function toDepositQuoteInfo(data) {
|
|
|
33632
34758
|
sharePrice: data.sharePrice,
|
|
33633
34759
|
currentApy: data.currentApy,
|
|
33634
34760
|
fees,
|
|
33635
|
-
|
|
34761
|
+
// The Earn Service estimates gas server-side; the chain fills token/blockchain.
|
|
34762
|
+
// Cross-chain quotes resolve no local chain definition, so gasFees stays
|
|
34763
|
+
// empty there (unchanged behavior).
|
|
34764
|
+
gasFees: chain === undefined ? [] : toQuoteGasFees(data.gasFees, chain)
|
|
33636
34765
|
};
|
|
33637
34766
|
}
|
|
33638
34767
|
/**
|
|
@@ -33667,7 +34796,7 @@ function toDepositQuoteInfo(data) {
|
|
|
33667
34796
|
};
|
|
33668
34797
|
try {
|
|
33669
34798
|
const response = await pollApiPost(url.toString(), requestBody, isDepositQuoteResponse, pollingConfig);
|
|
33670
|
-
return toDepositQuoteInfo(response.data);
|
|
34799
|
+
return toDepositQuoteInfo(response.data, params.chainDefinition);
|
|
33671
34800
|
} catch (error) {
|
|
33672
34801
|
throw parseEarnApiError(error, {
|
|
33673
34802
|
operation: 'getDepositQuote'
|
|
@@ -33675,7 +34804,7 @@ function toDepositQuoteInfo(data) {
|
|
|
33675
34804
|
}
|
|
33676
34805
|
}
|
|
33677
34806
|
|
|
33678
|
-
function toWithdrawalQuoteInfo(data) {
|
|
34807
|
+
function toWithdrawalQuoteInfo(data, chain) {
|
|
33679
34808
|
return {
|
|
33680
34809
|
vaultAddress: data.vaultAddress,
|
|
33681
34810
|
vaultName: data.vaultName,
|
|
@@ -33703,7 +34832,7 @@ function toWithdrawalQuoteInfo(data) {
|
|
|
33703
34832
|
status
|
|
33704
34833
|
}
|
|
33705
34834
|
})),
|
|
33706
|
-
gasFees: [],
|
|
34835
|
+
gasFees: chain === undefined ? [] : toQuoteGasFees(data.gasFees, chain),
|
|
33707
34836
|
// Wire format uses `warnings`, but the SDK surface uses
|
|
33708
34837
|
// `earnKitWarnings` to match the precedent set by `VaultInfo` —
|
|
33709
34838
|
// `warnings` is reserved for the structured `VaultWarning` shape.
|
|
@@ -33735,7 +34864,7 @@ function toWithdrawalQuoteInfo(data) {
|
|
|
33735
34864
|
};
|
|
33736
34865
|
try {
|
|
33737
34866
|
const response = await pollApiPost(url.toString(), requestBody, isWithdrawalQuoteResponse, pollingConfig);
|
|
33738
|
-
return toWithdrawalQuoteInfo(response.data);
|
|
34867
|
+
return toWithdrawalQuoteInfo(response.data, params.chainDefinition);
|
|
33739
34868
|
} catch (error) {
|
|
33740
34869
|
throw parseEarnApiError(error, {
|
|
33741
34870
|
operation: 'getWithdrawalQuote'
|
|
@@ -33771,6 +34900,8 @@ function toWithdrawalQuoteInfo(data) {
|
|
|
33771
34900
|
amount: Amount.fromJSON(r.amount),
|
|
33772
34901
|
address: r.token
|
|
33773
34902
|
})),
|
|
34903
|
+
// The claimRewards/quote response does not carry a gas estimate (unlike
|
|
34904
|
+
// deposit/withdrawal quotes), so there is nothing to surface here.
|
|
33774
34905
|
gasFees: []
|
|
33775
34906
|
};
|
|
33776
34907
|
} catch (error) {
|
|
@@ -33780,6 +34911,83 @@ function toWithdrawalQuoteInfo(data) {
|
|
|
33780
34911
|
}
|
|
33781
34912
|
}
|
|
33782
34913
|
|
|
34914
|
+
/**
|
|
34915
|
+
* Build the native gas triple the backend expects for `gasUsed`.
|
|
34916
|
+
*
|
|
34917
|
+
* Returns `undefined` unless both receipt components are present, so the
|
|
34918
|
+
* caller can omit the field entirely — the Earn Service treats a missing
|
|
34919
|
+
* triple as "skip the gas cache write, still return 200".
|
|
34920
|
+
*
|
|
34921
|
+
* @param gasUsed - Receipt gas units used.
|
|
34922
|
+
* @param effectiveGasPrice - Receipt effective gas price.
|
|
34923
|
+
* @returns The `{ gas, gasPrice, fee }` triple, or `undefined` when either
|
|
34924
|
+
* component is missing.
|
|
34925
|
+
*
|
|
34926
|
+
* @example
|
|
34927
|
+
* ```typescript
|
|
34928
|
+
* buildReportedGasUsed(362454n, 29466364605n)
|
|
34929
|
+
* // { gas: '362454', gasPrice: '29466364605', fee: '10680201716540670' }
|
|
34930
|
+
* ```
|
|
34931
|
+
*
|
|
34932
|
+
* @internal
|
|
34933
|
+
*/ function buildReportedGasUsed(gasUsed, effectiveGasPrice) {
|
|
34934
|
+
if (gasUsed === undefined || effectiveGasPrice === undefined) {
|
|
34935
|
+
return undefined;
|
|
34936
|
+
}
|
|
34937
|
+
return {
|
|
34938
|
+
gas: gasUsed.toString(),
|
|
34939
|
+
gasPrice: effectiveGasPrice.toString(),
|
|
34940
|
+
fee: (gasUsed * effectiveGasPrice).toString()
|
|
34941
|
+
};
|
|
34942
|
+
}
|
|
34943
|
+
/**
|
|
34944
|
+
* Report the outcome of an SDK-submitted same-chain Earn transaction.
|
|
34945
|
+
*
|
|
34946
|
+
* @param params - Transaction report parameters.
|
|
34947
|
+
* @throws {@link KitError} When the API call fails.
|
|
34948
|
+
*
|
|
34949
|
+
* @internal
|
|
34950
|
+
*/ async function reportEarnTransaction(params) {
|
|
34951
|
+
const { pollingConfig, baseUrl } = buildConfig(params.config);
|
|
34952
|
+
const url = new URL(`${EARN_KIT_API_PREFIX}/transactions/report`, baseUrl);
|
|
34953
|
+
// The report endpoint is not idempotent: success reports refresh the gas
|
|
34954
|
+
// cache and failure reports increment counts. If the first request succeeds
|
|
34955
|
+
// server-side but the client times out or sees a transient 5xx, retrying
|
|
34956
|
+
// would duplicate the report (double-writing an outcome or inflating failure
|
|
34957
|
+
// counts). Reporting is best-effort (see the fire-and-forget caller), so
|
|
34958
|
+
// make exactly one attempt and never retry — a single dropped report is
|
|
34959
|
+
// preferable to a duplicated one. `maxRetries` here is the total attempt
|
|
34960
|
+
// count in pollApiWithValidation (loop runs `attempt <= maxRetries`), so 1
|
|
34961
|
+
// means one request with no retry; 0 would skip the request entirely.
|
|
34962
|
+
const reportConfig = {
|
|
34963
|
+
...pollingConfig,
|
|
34964
|
+
maxRetries: 1
|
|
34965
|
+
};
|
|
34966
|
+
const gasUsed = buildReportedGasUsed(params.gasUsed, params.effectiveGasPrice);
|
|
34967
|
+
const requestBody = {
|
|
34968
|
+
execId: params.execId,
|
|
34969
|
+
chain: params.chain,
|
|
34970
|
+
status: params.status,
|
|
34971
|
+
action: params.action,
|
|
34972
|
+
...params.txHash !== undefined && {
|
|
34973
|
+
txHash: params.txHash
|
|
34974
|
+
},
|
|
34975
|
+
...gasUsed !== undefined && {
|
|
34976
|
+
gasUsed
|
|
34977
|
+
},
|
|
34978
|
+
...params.errorCode !== undefined && {
|
|
34979
|
+
errorCode: params.errorCode
|
|
34980
|
+
}
|
|
34981
|
+
};
|
|
34982
|
+
try {
|
|
34983
|
+
await pollApiPost(url.toString(), requestBody, isTransactionReportResponse, reportConfig);
|
|
34984
|
+
} catch (error) {
|
|
34985
|
+
throw parseEarnApiError(error, {
|
|
34986
|
+
operation: 'transactionReport'
|
|
34987
|
+
});
|
|
34988
|
+
}
|
|
34989
|
+
}
|
|
34990
|
+
|
|
33783
34991
|
/**
|
|
33784
34992
|
* Sum the amounts across every token input to size the allowance approval.
|
|
33785
34993
|
*
|
|
@@ -33890,6 +35098,59 @@ function toWithdrawalQuoteInfo(data) {
|
|
|
33890
35098
|
// Intentionally built-ins-only: Earn bridge support is limited to SDK-known
|
|
33891
35099
|
// token contracts plus the explicit ERC-3009 domain allowlist below.
|
|
33892
35100
|
const TOKEN_REGISTRY = createTokenRegistry();
|
|
35101
|
+
function submitTransactionReport(reportContext, action, status, details) {
|
|
35102
|
+
void reportEarnTransaction({
|
|
35103
|
+
execId: reportContext.execId,
|
|
35104
|
+
chain: reportContext.chain,
|
|
35105
|
+
config: reportContext.config,
|
|
35106
|
+
action,
|
|
35107
|
+
status,
|
|
35108
|
+
...details
|
|
35109
|
+
}).catch(()=>undefined);
|
|
35110
|
+
}
|
|
35111
|
+
function reportTransactionSuccess(reportContext, action, result) {
|
|
35112
|
+
if (result === undefined) {
|
|
35113
|
+
return;
|
|
35114
|
+
}
|
|
35115
|
+
submitTransactionReport(reportContext, action, 'success', {
|
|
35116
|
+
txHash: result.txHash,
|
|
35117
|
+
gasUsed: result.gasUsed,
|
|
35118
|
+
effectiveGasPrice: result.effectiveGasPrice
|
|
35119
|
+
});
|
|
35120
|
+
}
|
|
35121
|
+
function reportTransactionFailure(reportContext, action, error) {
|
|
35122
|
+
submitTransactionReport(reportContext, action, 'failure', {
|
|
35123
|
+
txHash: transactionReportTxHash(error),
|
|
35124
|
+
errorCode: transactionReportErrorCode(error)
|
|
35125
|
+
});
|
|
35126
|
+
}
|
|
35127
|
+
function transactionReportErrorCode(error) {
|
|
35128
|
+
if (isKitError(error)) {
|
|
35129
|
+
return error.name;
|
|
35130
|
+
}
|
|
35131
|
+
const message = getErrorMessage(error);
|
|
35132
|
+
if (/user (rejected|denied)|rejected by user/i.test(message)) {
|
|
35133
|
+
return 'USER_REJECTED';
|
|
35134
|
+
}
|
|
35135
|
+
if (/insufficient funds/i.test(message)) {
|
|
35136
|
+
return 'INSUFFICIENT_FUNDS';
|
|
35137
|
+
}
|
|
35138
|
+
if (/timeout|timed out/i.test(message)) {
|
|
35139
|
+
return 'TIMEOUT';
|
|
35140
|
+
}
|
|
35141
|
+
return 'UNKNOWN_ERROR';
|
|
35142
|
+
}
|
|
35143
|
+
function transactionReportTxHash(error) {
|
|
35144
|
+
if (!isKitError(error)) {
|
|
35145
|
+
return undefined;
|
|
35146
|
+
}
|
|
35147
|
+
const trace = error.cause?.trace;
|
|
35148
|
+
if (typeof trace !== 'object' || trace === null) {
|
|
35149
|
+
return undefined;
|
|
35150
|
+
}
|
|
35151
|
+
const txHash = trace['txHash'];
|
|
35152
|
+
return typeof txHash === 'string' && txHash !== '' ? txHash : undefined;
|
|
35153
|
+
}
|
|
33893
35154
|
/**
|
|
33894
35155
|
* Build the typed error raised when a cross-chain wait is cancelled via its
|
|
33895
35156
|
* `AbortSignal`. Mirrors `@core/adapter-base`'s `createAbortError` (same
|
|
@@ -34221,7 +35482,7 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
34221
35482
|
const adapterContractAddress = requireAdapterContract(chain);
|
|
34222
35483
|
const { adapter } = params.from;
|
|
34223
35484
|
const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
34224
|
-
const { executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
|
|
35485
|
+
const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
|
|
34225
35486
|
vaultAddress,
|
|
34226
35487
|
amount: params.amount,
|
|
34227
35488
|
address,
|
|
@@ -34229,32 +35490,55 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
34229
35490
|
config
|
|
34230
35491
|
}), ()=>undefined);
|
|
34231
35492
|
validateExecutionDeadline(executionParams);
|
|
35493
|
+
const transactionReportContext = {
|
|
35494
|
+
execId,
|
|
35495
|
+
chain: apiChain,
|
|
35496
|
+
config
|
|
35497
|
+
};
|
|
34232
35498
|
const approvalToken = resolveEarnApprovalToken(executionParams);
|
|
34233
35499
|
const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
|
|
34234
35500
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
34235
35501
|
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
|
|
34236
|
-
await this.runPhase(ctx, 'approve', 'approve', async ()=>
|
|
35502
|
+
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
35503
|
+
try {
|
|
35504
|
+
const approval = await approveAllowanceIfNeeded({
|
|
35505
|
+
adapter,
|
|
35506
|
+
chain,
|
|
35507
|
+
tokenAddress: approvalToken,
|
|
35508
|
+
delegate: adapterContractAddress,
|
|
35509
|
+
address,
|
|
35510
|
+
requiredAllowance,
|
|
35511
|
+
revertMessage: 'Earn deposit token approval reverted on-chain'
|
|
35512
|
+
});
|
|
35513
|
+
reportTransactionSuccess(transactionReportContext, 'Approve', approval);
|
|
35514
|
+
return approval;
|
|
35515
|
+
} catch (error) {
|
|
35516
|
+
reportTransactionFailure(transactionReportContext, 'Approve', error);
|
|
35517
|
+
throw error;
|
|
35518
|
+
}
|
|
35519
|
+
}, (approval)=>approval?.txHash);
|
|
35520
|
+
}
|
|
35521
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>{
|
|
35522
|
+
try {
|
|
35523
|
+
const result = await executeEarnAction({
|
|
34237
35524
|
adapter,
|
|
34238
35525
|
chain,
|
|
34239
|
-
tokenAddress: approvalToken,
|
|
34240
|
-
delegate: adapterContractAddress,
|
|
34241
35526
|
address,
|
|
34242
|
-
|
|
34243
|
-
|
|
34244
|
-
|
|
34245
|
-
|
|
34246
|
-
|
|
34247
|
-
|
|
34248
|
-
|
|
34249
|
-
|
|
34250
|
-
|
|
34251
|
-
|
|
34252
|
-
|
|
34253
|
-
|
|
34254
|
-
|
|
34255
|
-
|
|
34256
|
-
|
|
34257
|
-
}), ({ txHash })=>txHash);
|
|
35527
|
+
actionKey: 'earn.deposit',
|
|
35528
|
+
actionParams: {
|
|
35529
|
+
executeParams: executionParams,
|
|
35530
|
+
tokenInputs,
|
|
35531
|
+
signature
|
|
35532
|
+
},
|
|
35533
|
+
revertMessage: 'Earn deposit reverted on-chain'
|
|
35534
|
+
});
|
|
35535
|
+
reportTransactionSuccess(transactionReportContext, 'Deposit', result);
|
|
35536
|
+
return result;
|
|
35537
|
+
} catch (error) {
|
|
35538
|
+
reportTransactionFailure(transactionReportContext, 'Deposit', error);
|
|
35539
|
+
throw error;
|
|
35540
|
+
}
|
|
35541
|
+
}, ({ txHash })=>txHash);
|
|
34258
35542
|
return {
|
|
34259
35543
|
kind: 'same-chain',
|
|
34260
35544
|
txHash,
|
|
@@ -34334,7 +35618,13 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
34334
35618
|
amount: params.amount,
|
|
34335
35619
|
sourceChain: sourceChain.chain,
|
|
34336
35620
|
destinationChain: destinationChain.chain,
|
|
34337
|
-
expiresAt: prepared.expiresAt
|
|
35621
|
+
expiresAt: prepared.expiresAt,
|
|
35622
|
+
...prepared.quoteIssuedAt !== undefined && {
|
|
35623
|
+
quoteIssuedAt: prepared.quoteIssuedAt
|
|
35624
|
+
},
|
|
35625
|
+
...prepared.quoteExpiry !== undefined && {
|
|
35626
|
+
quoteExpiry: prepared.quoteExpiry
|
|
35627
|
+
}
|
|
34338
35628
|
};
|
|
34339
35629
|
}
|
|
34340
35630
|
/** {@inheritdoc} */ async withdraw(params) {
|
|
@@ -34355,7 +35645,7 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
34355
35645
|
const adapterContractAddress = requireAdapterContract(chain);
|
|
34356
35646
|
const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
34357
35647
|
const { adapter } = params.from;
|
|
34358
|
-
const { executionParams, signature } = await this.runPhase(ctx, 'withdraw', 'fetchParams', async ()=>fetchWithdraw({
|
|
35648
|
+
const { execId, executionParams, signature } = await this.runPhase(ctx, 'withdraw', 'fetchParams', async ()=>fetchWithdraw({
|
|
34359
35649
|
vaultAddress,
|
|
34360
35650
|
amount: params.amount,
|
|
34361
35651
|
address,
|
|
@@ -34363,32 +35653,55 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
34363
35653
|
config
|
|
34364
35654
|
}), ()=>undefined);
|
|
34365
35655
|
validateExecutionDeadline(executionParams);
|
|
35656
|
+
const transactionReportContext = {
|
|
35657
|
+
execId,
|
|
35658
|
+
chain: apiChain,
|
|
35659
|
+
config
|
|
35660
|
+
};
|
|
34366
35661
|
const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
|
|
34367
35662
|
const approvalToken = tokenInputs[0]?.token;
|
|
34368
35663
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
34369
35664
|
if (!options.skipApprove && approvalToken !== undefined) {
|
|
34370
|
-
await this.runPhase(ctx, 'approve', 'approve', async ()=>
|
|
35665
|
+
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
35666
|
+
try {
|
|
35667
|
+
const approval = await approveAllowanceIfNeeded({
|
|
35668
|
+
adapter,
|
|
35669
|
+
chain,
|
|
35670
|
+
tokenAddress: approvalToken,
|
|
35671
|
+
delegate: adapterContractAddress,
|
|
35672
|
+
address,
|
|
35673
|
+
requiredAllowance,
|
|
35674
|
+
revertMessage: 'Vault share token approval reverted on-chain'
|
|
35675
|
+
});
|
|
35676
|
+
reportTransactionSuccess(transactionReportContext, 'Approve', approval);
|
|
35677
|
+
return approval;
|
|
35678
|
+
} catch (error) {
|
|
35679
|
+
reportTransactionFailure(transactionReportContext, 'Approve', error);
|
|
35680
|
+
throw error;
|
|
35681
|
+
}
|
|
35682
|
+
}, (approval)=>approval?.txHash);
|
|
35683
|
+
}
|
|
35684
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'withdraw', 'execute', async ()=>{
|
|
35685
|
+
try {
|
|
35686
|
+
const result = await executeEarnAction({
|
|
34371
35687
|
adapter,
|
|
34372
35688
|
chain,
|
|
34373
|
-
tokenAddress: approvalToken,
|
|
34374
|
-
delegate: adapterContractAddress,
|
|
34375
35689
|
address,
|
|
34376
|
-
|
|
34377
|
-
|
|
34378
|
-
|
|
34379
|
-
|
|
34380
|
-
|
|
34381
|
-
|
|
34382
|
-
|
|
34383
|
-
|
|
34384
|
-
|
|
34385
|
-
|
|
34386
|
-
|
|
34387
|
-
|
|
34388
|
-
|
|
34389
|
-
|
|
34390
|
-
|
|
34391
|
-
}), ({ txHash })=>txHash);
|
|
35690
|
+
actionKey: 'earn.withdraw',
|
|
35691
|
+
actionParams: {
|
|
35692
|
+
executeParams: executionParams,
|
|
35693
|
+
tokenInputs,
|
|
35694
|
+
signature
|
|
35695
|
+
},
|
|
35696
|
+
revertMessage: 'Earn withdraw reverted on-chain'
|
|
35697
|
+
});
|
|
35698
|
+
reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
|
|
35699
|
+
return result;
|
|
35700
|
+
} catch (error) {
|
|
35701
|
+
reportTransactionFailure(transactionReportContext, 'Withdraw', error);
|
|
35702
|
+
throw error;
|
|
35703
|
+
}
|
|
35704
|
+
}, ({ txHash })=>txHash);
|
|
34392
35705
|
return {
|
|
34393
35706
|
txHash,
|
|
34394
35707
|
explorerUrl,
|
|
@@ -34522,141 +35835,6 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
34522
35835
|
}
|
|
34523
35836
|
}
|
|
34524
35837
|
}
|
|
34525
|
-
gasEstimateFailure(name, chain, error) {
|
|
34526
|
-
return {
|
|
34527
|
-
name,
|
|
34528
|
-
token: chain.nativeCurrency.symbol,
|
|
34529
|
-
blockchain: chain.chain,
|
|
34530
|
-
fees: null,
|
|
34531
|
-
error: getErrorMessage(error)
|
|
34532
|
-
};
|
|
34533
|
-
}
|
|
34534
|
-
async estimateDepositQuoteGasFees(params) {
|
|
34535
|
-
const { adapter, chain, apiChain, address, vaultAddress, amount, config } = params;
|
|
34536
|
-
try {
|
|
34537
|
-
const adapterContractAddress = requireAdapterContract(chain);
|
|
34538
|
-
const normalizedVaultAddress = assertHexAddress('vaultAddress', vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
34539
|
-
const { executionParams, signature } = await fetchDeposit({
|
|
34540
|
-
vaultAddress: normalizedVaultAddress,
|
|
34541
|
-
amount,
|
|
34542
|
-
address,
|
|
34543
|
-
chain: apiChain,
|
|
34544
|
-
config
|
|
34545
|
-
});
|
|
34546
|
-
validateExecutionDeadline(executionParams);
|
|
34547
|
-
const approvalToken = resolveEarnApprovalToken(executionParams);
|
|
34548
|
-
const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
|
|
34549
|
-
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
34550
|
-
return await estimateEarnQuoteGasFees({
|
|
34551
|
-
adapter,
|
|
34552
|
-
chain,
|
|
34553
|
-
address,
|
|
34554
|
-
actionName: 'Deposit',
|
|
34555
|
-
actionKey: 'earn.deposit',
|
|
34556
|
-
actionParams: {
|
|
34557
|
-
executeParams: executionParams,
|
|
34558
|
-
tokenInputs,
|
|
34559
|
-
signature
|
|
34560
|
-
},
|
|
34561
|
-
approval: approvalToken !== undefined && requiredAllowance > 0n ? {
|
|
34562
|
-
token: approvalToken,
|
|
34563
|
-
delegate: adapterContractAddress,
|
|
34564
|
-
requiredAllowance
|
|
34565
|
-
} : undefined
|
|
34566
|
-
});
|
|
34567
|
-
} catch (error) {
|
|
34568
|
-
return [
|
|
34569
|
-
this.gasEstimateFailure('Deposit', chain, error)
|
|
34570
|
-
];
|
|
34571
|
-
}
|
|
34572
|
-
}
|
|
34573
|
-
async estimateWithdrawalQuoteGasFees(params) {
|
|
34574
|
-
const { adapter, chain, apiChain, address, vaultAddress, amount, config } = params;
|
|
34575
|
-
try {
|
|
34576
|
-
const adapterContractAddress = requireAdapterContract(chain);
|
|
34577
|
-
const normalizedVaultAddress = assertHexAddress('vaultAddress', vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
34578
|
-
const { executionParams, signature } = await fetchWithdraw({
|
|
34579
|
-
vaultAddress: normalizedVaultAddress,
|
|
34580
|
-
amount,
|
|
34581
|
-
address,
|
|
34582
|
-
chain: apiChain,
|
|
34583
|
-
config
|
|
34584
|
-
});
|
|
34585
|
-
validateExecutionDeadline(executionParams);
|
|
34586
|
-
const tokenInputs = buildEarnTokenInputs(executionParams, normalizedVaultAddress);
|
|
34587
|
-
const approvalToken = tokenInputs[0]?.token;
|
|
34588
|
-
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
34589
|
-
return await estimateEarnQuoteGasFees({
|
|
34590
|
-
adapter,
|
|
34591
|
-
chain,
|
|
34592
|
-
address,
|
|
34593
|
-
actionName: 'Withdraw',
|
|
34594
|
-
actionKey: 'earn.withdraw',
|
|
34595
|
-
actionParams: {
|
|
34596
|
-
executeParams: executionParams,
|
|
34597
|
-
tokenInputs,
|
|
34598
|
-
signature
|
|
34599
|
-
},
|
|
34600
|
-
approval: approvalToken !== undefined ? {
|
|
34601
|
-
token: approvalToken,
|
|
34602
|
-
delegate: adapterContractAddress,
|
|
34603
|
-
requiredAllowance
|
|
34604
|
-
} : undefined
|
|
34605
|
-
});
|
|
34606
|
-
} catch (error) {
|
|
34607
|
-
return [
|
|
34608
|
-
this.gasEstimateFailure('Withdraw', chain, error)
|
|
34609
|
-
];
|
|
34610
|
-
}
|
|
34611
|
-
}
|
|
34612
|
-
async estimateClaimRewardsQuoteGasFees(params) {
|
|
34613
|
-
const { adapter, chain, apiChain, address, vaultAddress, config } = params;
|
|
34614
|
-
try {
|
|
34615
|
-
requireAdapterContract(chain);
|
|
34616
|
-
const { rewards, executionParams, signature } = await fetchClaimRewards({
|
|
34617
|
-
address,
|
|
34618
|
-
chain: apiChain,
|
|
34619
|
-
vaultAddress,
|
|
34620
|
-
config
|
|
34621
|
-
});
|
|
34622
|
-
if (rewards.length === 0) {
|
|
34623
|
-
return [];
|
|
34624
|
-
}
|
|
34625
|
-
const missingExecutionParams = executionParams === undefined;
|
|
34626
|
-
const missingSignature = signature === undefined;
|
|
34627
|
-
if (missingExecutionParams || missingSignature) {
|
|
34628
|
-
throw new KitError({
|
|
34629
|
-
...EarnError.INTERNAL_ERROR,
|
|
34630
|
-
recoverability: 'RETRYABLE',
|
|
34631
|
-
message: 'Claim rewards response must include executionParams and signature when rewards are claimable',
|
|
34632
|
-
cause: {
|
|
34633
|
-
trace: {
|
|
34634
|
-
rewardsCount: rewards.length,
|
|
34635
|
-
missingExecutionParams,
|
|
34636
|
-
missingSignature
|
|
34637
|
-
}
|
|
34638
|
-
}
|
|
34639
|
-
});
|
|
34640
|
-
}
|
|
34641
|
-
validateExecutionDeadline(executionParams);
|
|
34642
|
-
return await estimateEarnQuoteGasFees({
|
|
34643
|
-
adapter,
|
|
34644
|
-
chain,
|
|
34645
|
-
address,
|
|
34646
|
-
actionName: 'Claim Rewards',
|
|
34647
|
-
actionKey: 'earn.claimRewards',
|
|
34648
|
-
actionParams: {
|
|
34649
|
-
executeParams: executionParams,
|
|
34650
|
-
tokenInputs: [],
|
|
34651
|
-
signature
|
|
34652
|
-
}
|
|
34653
|
-
});
|
|
34654
|
-
} catch (error) {
|
|
34655
|
-
return [
|
|
34656
|
-
this.gasEstimateFailure('Claim Rewards', chain, error)
|
|
34657
|
-
];
|
|
34658
|
-
}
|
|
34659
|
-
}
|
|
34660
35838
|
/** {@inheritdoc} */ async getDepositQuote(params) {
|
|
34661
35839
|
const config = this.resolveConfig(params.config);
|
|
34662
35840
|
if (hasQuoteDestinationChain(params)) {
|
|
@@ -34679,96 +35857,43 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
34679
35857
|
throw createValidationFailedError$1('chain', destinationChain.chain, 'chain is only supported for cross-chain Earn deposit quotes; omit chain/address when quoting on the source chain');
|
|
34680
35858
|
}
|
|
34681
35859
|
const { address, chain, chainDefinition } = await resolveAdapterContext(params.from);
|
|
34682
|
-
//
|
|
34683
|
-
//
|
|
34684
|
-
//
|
|
34685
|
-
|
|
34686
|
-
|
|
34687
|
-
|
|
34688
|
-
|
|
34689
|
-
|
|
34690
|
-
|
|
34691
|
-
|
|
34692
|
-
|
|
34693
|
-
this.estimateDepositQuoteGasFees({
|
|
34694
|
-
adapter: params.from.adapter,
|
|
34695
|
-
chain: chainDefinition,
|
|
34696
|
-
apiChain: chain,
|
|
34697
|
-
address,
|
|
34698
|
-
vaultAddress: params.vaultAddress,
|
|
34699
|
-
amount: params.amount,
|
|
34700
|
-
config
|
|
34701
|
-
})
|
|
34702
|
-
]);
|
|
34703
|
-
return {
|
|
34704
|
-
...quote,
|
|
34705
|
-
gasFees
|
|
34706
|
-
};
|
|
35860
|
+
// Gas is estimated server-side by the Earn Service and returned on the quote, so the
|
|
35861
|
+
// SDK no longer simulates it locally. `chainDefinition` lets the fetch fill
|
|
35862
|
+
// the native token symbol / blockchain on each gas entry.
|
|
35863
|
+
return fetchDepositQuote({
|
|
35864
|
+
vaultAddress: params.vaultAddress,
|
|
35865
|
+
amount: params.amount,
|
|
35866
|
+
address,
|
|
35867
|
+
chain,
|
|
35868
|
+
config,
|
|
35869
|
+
chainDefinition
|
|
35870
|
+
});
|
|
34707
35871
|
}
|
|
34708
35872
|
/** {@inheritdoc} */ async getWithdrawalQuote(params) {
|
|
34709
35873
|
const config = this.resolveConfig(params.config);
|
|
34710
35874
|
const { address, chain, chainDefinition } = await resolveAdapterContext(params.from);
|
|
34711
|
-
//
|
|
34712
|
-
|
|
34713
|
-
|
|
34714
|
-
|
|
34715
|
-
|
|
34716
|
-
|
|
34717
|
-
|
|
34718
|
-
|
|
34719
|
-
|
|
34720
|
-
config
|
|
34721
|
-
}),
|
|
34722
|
-
this.estimateWithdrawalQuoteGasFees({
|
|
34723
|
-
adapter: params.from.adapter,
|
|
34724
|
-
chain: chainDefinition,
|
|
34725
|
-
apiChain: chain,
|
|
34726
|
-
address,
|
|
34727
|
-
vaultAddress: params.vaultAddress,
|
|
34728
|
-
amount: params.amount,
|
|
34729
|
-
config
|
|
34730
|
-
})
|
|
34731
|
-
]);
|
|
34732
|
-
return {
|
|
34733
|
-
...quote,
|
|
34734
|
-
gasFees
|
|
34735
|
-
};
|
|
35875
|
+
// Gas is estimated server-side by the Earn Service and returned on the quote.
|
|
35876
|
+
return fetchWithdrawalQuote({
|
|
35877
|
+
vaultAddress: params.vaultAddress,
|
|
35878
|
+
amount: params.amount,
|
|
35879
|
+
address,
|
|
35880
|
+
chain,
|
|
35881
|
+
config,
|
|
35882
|
+
chainDefinition
|
|
35883
|
+
});
|
|
34736
35884
|
}
|
|
34737
35885
|
/** {@inheritdoc} */ async getClaimRewardsQuote(params) {
|
|
34738
35886
|
const config = this.resolveConfig(params.config);
|
|
34739
|
-
const { address, chain
|
|
34740
|
-
|
|
35887
|
+
const { address, chain } = await resolveAdapterContext(params.from);
|
|
35888
|
+
// The claimRewards/quote response carries no gas estimate (unlike
|
|
35889
|
+
// deposit/withdrawal quotes), and the SDK no longer estimates gas locally,
|
|
35890
|
+
// so gasFees is always empty for claim rewards.
|
|
35891
|
+
return fetchClaimRewardsQuote({
|
|
34741
35892
|
vaultAddress: params.vaultAddress,
|
|
34742
35893
|
address,
|
|
34743
35894
|
chain,
|
|
34744
35895
|
config
|
|
34745
35896
|
});
|
|
34746
|
-
// No claimable rewards means there is nothing to execute, so there is no
|
|
34747
|
-
// gas to estimate. Short-circuit on the already-fetched quote rather than
|
|
34748
|
-
// calling the (heavier) claim execution endpoint again — this also keeps
|
|
34749
|
-
// `gasFees` empty as documented, instead of risking a `{ fees: null }`
|
|
34750
|
-
// estimation-error entry when the adapter/RPC is unavailable. This
|
|
34751
|
-
// short-circuit is why the claim path stays sequential instead of using
|
|
34752
|
-
// the Promise.all pattern of the deposit/withdrawal quotes: estimating in
|
|
34753
|
-
// parallel would hit the signing endpoint even when nothing is claimable.
|
|
34754
|
-
if (quote.rewards.length === 0) {
|
|
34755
|
-
return {
|
|
34756
|
-
...quote,
|
|
34757
|
-
gasFees: []
|
|
34758
|
-
};
|
|
34759
|
-
}
|
|
34760
|
-
const gasFees = await this.estimateClaimRewardsQuoteGasFees({
|
|
34761
|
-
adapter: params.from.adapter,
|
|
34762
|
-
chain: chainDefinition,
|
|
34763
|
-
apiChain: chain,
|
|
34764
|
-
address,
|
|
34765
|
-
vaultAddress: params.vaultAddress,
|
|
34766
|
-
config
|
|
34767
|
-
});
|
|
34768
|
-
return {
|
|
34769
|
-
...quote,
|
|
34770
|
-
gasFees
|
|
34771
|
-
};
|
|
34772
35897
|
}
|
|
34773
35898
|
}
|
|
34774
35899
|
function hasDepositDestination(params) {
|
|
@@ -35006,11 +36131,27 @@ function formatPositionPnL(pnl) {
|
|
|
35006
36131
|
* @param vault - Provider vault info with raw amount objects
|
|
35007
36132
|
* @returns Vault info with total deposits and liquidity formatted as strings
|
|
35008
36133
|
*/ function formatVaultInfo(vault) {
|
|
35009
|
-
|
|
36134
|
+
// The flat `totalDeposits`/`liquidity` are deprecated aliases that are
|
|
36135
|
+
// intentionally dual-read through the migration window so existing
|
|
36136
|
+
// consumers keep receiving them until Contract.
|
|
36137
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
36138
|
+
const { totalDeposits, liquidity, liquidityProfile, ...rest } = vault;
|
|
36139
|
+
// `liquidityProfile` is `.optional()` in the response schema during the
|
|
36140
|
+
// expand/contract window (an old backend that predates the nested facets
|
|
36141
|
+
// omits it), so only format and re-attach it when present — matching the
|
|
36142
|
+
// provider-side `toVaultInfo` mapper.
|
|
35010
36143
|
return {
|
|
35011
36144
|
...rest,
|
|
35012
36145
|
totalDeposits: formatAmount$1(totalDeposits),
|
|
35013
|
-
liquidity: formatAmount$1(liquidity)
|
|
36146
|
+
liquidity: formatAmount$1(liquidity),
|
|
36147
|
+
...liquidityProfile !== undefined && {
|
|
36148
|
+
liquidityProfile: {
|
|
36149
|
+
...liquidityProfile,
|
|
36150
|
+
totalDeposits: formatAmount$1(liquidityProfile.totalDeposits),
|
|
36151
|
+
available: formatAmount$1(liquidityProfile.available),
|
|
36152
|
+
totalSupply: formatAmount$1(liquidityProfile.totalSupply)
|
|
36153
|
+
}
|
|
36154
|
+
}
|
|
35014
36155
|
};
|
|
35015
36156
|
}
|
|
35016
36157
|
/**
|
|
@@ -36922,25 +38063,6 @@ function formatRetryResult(operation, result) {
|
|
|
36922
38063
|
// Auto-register this kit for user agent tracking
|
|
36923
38064
|
registerKit(`${pkg$2.name}/${pkg$2.version}`);
|
|
36924
38065
|
|
|
36925
|
-
/**
|
|
36926
|
-
* Create an EarnKit instance for AppKit earn operations.
|
|
36927
|
-
*
|
|
36928
|
-
* @remarks The context parameter is reserved for future EarnKit wiring.
|
|
36929
|
-
* EarnKit does not currently support AppKit developer fee hooks, so the
|
|
36930
|
-
* factory does not read fee callbacks from the context. Earn custom fees remain
|
|
36931
|
-
* reserved until EarnKit fee support ships.
|
|
36932
|
-
*
|
|
36933
|
-
* When EarnKit supports developer fee hooks, this factory can wire AppKit context through.
|
|
36934
|
-
*
|
|
36935
|
-
* @param context - AppKit context reserved for future EarnKit wiring
|
|
36936
|
-
* @returns A new EarnKit instance
|
|
36937
|
-
*
|
|
36938
|
-
* @example
|
|
36939
|
-
* ```typescript
|
|
36940
|
-
* const earnKit = createEarnKit(context)
|
|
36941
|
-
* ```
|
|
36942
|
-
*/ const createEarnKit = ()=>new EarnKit();
|
|
36943
|
-
|
|
36944
38066
|
/**
|
|
36945
38067
|
* Register event handlers from a context actions map to a kit instance.
|
|
36946
38068
|
*
|
|
@@ -37005,6 +38127,29 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
|
|
|
37005
38127
|
}
|
|
37006
38128
|
};
|
|
37007
38129
|
|
|
38130
|
+
/**
|
|
38131
|
+
* Create an EarnKit instance for AppKit earn operations.
|
|
38132
|
+
*
|
|
38133
|
+
* Attaches any earn event handlers previously registered on the AppKit
|
|
38134
|
+
* context (via `kit.on('earn.*', …)` or `kit.on('*', …)`) so step events
|
|
38135
|
+
* fire during the returned kit's earn operations.
|
|
38136
|
+
*
|
|
38137
|
+
* @remarks Developer fee hooks from the AppKit context are not applied.
|
|
38138
|
+
* EarnKit does not yet support custom fee policies.
|
|
38139
|
+
*
|
|
38140
|
+
* @param context - AppKit context with earn event handlers and kit options
|
|
38141
|
+
* @returns An EarnKit instance ready for AppKit earn operations
|
|
38142
|
+
*
|
|
38143
|
+
* @example
|
|
38144
|
+
* ```typescript
|
|
38145
|
+
* const earnKit = createEarnKit(context)
|
|
38146
|
+
* ```
|
|
38147
|
+
*/ const createEarnKit = (context)=>{
|
|
38148
|
+
const kit = new EarnKit();
|
|
38149
|
+
registerActionHandlers(kit, context.actions.earn, 'earn');
|
|
38150
|
+
return kit;
|
|
38151
|
+
};
|
|
38152
|
+
|
|
37008
38153
|
/**
|
|
37009
38154
|
* List of all supported token aliases for App Kit send operations.
|
|
37010
38155
|
*
|
|
@@ -37795,7 +38940,8 @@ const tokens = createTokenRegistry();
|
|
|
37795
38940
|
* polling loop yourself.
|
|
37796
38941
|
*
|
|
37797
38942
|
* @param context - AppKit context.
|
|
37798
|
-
* @param params - `txHash
|
|
38943
|
+
* @param params - `txHash` and `chainIn`, plus optional `chainOut` and
|
|
38944
|
+
* `kitKey`.
|
|
37799
38945
|
* @returns A snapshot of the swap's status at the time of the call.
|
|
37800
38946
|
*
|
|
37801
38947
|
* @example
|
|
@@ -37884,7 +39030,7 @@ const tokens = createTokenRegistry();
|
|
|
37884
39030
|
* addresses before indexing into `result.rates[chain]`.
|
|
37885
39031
|
*
|
|
37886
39032
|
* @param context - AppKit context.
|
|
37887
|
-
* @param params - `chain`, optional `tokens
|
|
39033
|
+
* @param params - `chain`, plus optional `tokens` and `kitKey`.
|
|
37888
39034
|
* @returns A nested map of `[chain][address] → { priceUSD, fetchedAt }`.
|
|
37889
39035
|
*
|
|
37890
39036
|
* @example
|
|
@@ -37979,7 +39125,7 @@ const tokens = createTokenRegistry();
|
|
|
37979
39125
|
}
|
|
37980
39126
|
// Return earn-specific chains
|
|
37981
39127
|
if (operationType === 'earn') {
|
|
37982
|
-
const earnKit = createEarnKit();
|
|
39128
|
+
const earnKit = createEarnKit(context);
|
|
37983
39129
|
return earnKit.getSupportedChains();
|
|
37984
39130
|
}
|
|
37985
39131
|
// Return unified balance chains
|
|
@@ -37990,7 +39136,7 @@ const tokens = createTokenRegistry();
|
|
|
37990
39136
|
// Reuse kit instances here if provider constructors become expensive or stateful.
|
|
37991
39137
|
const bridgeKit = createBridgeKit(context);
|
|
37992
39138
|
const swapKit = createSwapKit(context);
|
|
37993
|
-
const earnKit = createEarnKit();
|
|
39139
|
+
const earnKit = createEarnKit(context);
|
|
37994
39140
|
const bridgeChains = bridgeKit.getSupportedChains();
|
|
37995
39141
|
const swapChains = swapKit.getSupportedChains();
|
|
37996
39142
|
const earnChains = earnKit.getSupportedChains();
|
|
@@ -38012,7 +39158,7 @@ const tokens = createTokenRegistry();
|
|
|
38012
39158
|
};
|
|
38013
39159
|
|
|
38014
39160
|
async function deposit$2(context, params) {
|
|
38015
|
-
return createEarnKit().deposit(params);
|
|
39161
|
+
return createEarnKit(context).deposit(params);
|
|
38016
39162
|
}
|
|
38017
39163
|
/**
|
|
38018
39164
|
* Execute an earn withdrawal operation.
|
|
@@ -38036,7 +39182,7 @@ async function deposit$2(context, params) {
|
|
|
38036
39182
|
* })
|
|
38037
39183
|
* ```
|
|
38038
39184
|
*/ async function withdraw(context, params) {
|
|
38039
|
-
return createEarnKit().withdraw(params);
|
|
39185
|
+
return createEarnKit(context).withdraw(params);
|
|
38040
39186
|
}
|
|
38041
39187
|
/**
|
|
38042
39188
|
* Claim earn rewards.
|
|
@@ -38059,7 +39205,7 @@ async function deposit$2(context, params) {
|
|
|
38059
39205
|
* })
|
|
38060
39206
|
* ```
|
|
38061
39207
|
*/ async function claimRewards(context, params) {
|
|
38062
|
-
return createEarnKit().claimRewards(params);
|
|
39208
|
+
return createEarnKit(context).claimRewards(params);
|
|
38063
39209
|
}
|
|
38064
39210
|
/**
|
|
38065
39211
|
* Fetch vault information.
|
|
@@ -38081,7 +39227,7 @@ async function deposit$2(context, params) {
|
|
|
38081
39227
|
* })
|
|
38082
39228
|
* ```
|
|
38083
39229
|
*/ async function getVaults(context, params) {
|
|
38084
|
-
return createEarnKit().getVaults(params);
|
|
39230
|
+
return createEarnKit(context).getVaults(params);
|
|
38085
39231
|
}
|
|
38086
39232
|
/**
|
|
38087
39233
|
* Discover vaults available on a chain.
|
|
@@ -38105,7 +39251,7 @@ async function deposit$2(context, params) {
|
|
|
38105
39251
|
* })
|
|
38106
39252
|
* ```
|
|
38107
39253
|
*/ async function exploreVaults(context, params) {
|
|
38108
|
-
return createEarnKit().exploreVaults(params);
|
|
39254
|
+
return createEarnKit(context).exploreVaults(params);
|
|
38109
39255
|
}
|
|
38110
39256
|
/**
|
|
38111
39257
|
* Lazily iterate every vault available on a chain.
|
|
@@ -38129,7 +39275,7 @@ async function deposit$2(context, params) {
|
|
|
38129
39275
|
* }
|
|
38130
39276
|
* ```
|
|
38131
39277
|
*/ function exploreVaultsIterator(context, params) {
|
|
38132
|
-
return createEarnKit().exploreVaultsIterator(params);
|
|
39278
|
+
return createEarnKit(context).exploreVaultsIterator(params);
|
|
38133
39279
|
}
|
|
38134
39280
|
/**
|
|
38135
39281
|
* Fetch a wallet position in a vault.
|
|
@@ -38152,7 +39298,7 @@ async function deposit$2(context, params) {
|
|
|
38152
39298
|
* })
|
|
38153
39299
|
* ```
|
|
38154
39300
|
*/ async function getPosition(context, params) {
|
|
38155
|
-
return createEarnKit().getPosition(params);
|
|
39301
|
+
return createEarnKit(context).getPosition(params);
|
|
38156
39302
|
}
|
|
38157
39303
|
/**
|
|
38158
39304
|
* Fetch the current status of a cross-chain Earn deposit.
|
|
@@ -38174,7 +39320,7 @@ async function deposit$2(context, params) {
|
|
|
38174
39320
|
* console.log(status.status)
|
|
38175
39321
|
* ```
|
|
38176
39322
|
*/ async function getCrossChainDepositStatus(context, params) {
|
|
38177
|
-
return createEarnKit().getCrossChainDepositStatus(params);
|
|
39323
|
+
return createEarnKit(context).getCrossChainDepositStatus(params);
|
|
38178
39324
|
}
|
|
38179
39325
|
/**
|
|
38180
39326
|
* Poll a cross-chain Earn deposit until it reaches a terminal bridge state.
|
|
@@ -38197,7 +39343,7 @@ async function deposit$2(context, params) {
|
|
|
38197
39343
|
* console.log(result.outcome)
|
|
38198
39344
|
* ```
|
|
38199
39345
|
*/ async function waitForCrossChainDeposit(context, params) {
|
|
38200
|
-
return createEarnKit().waitForCrossChainDeposit(params);
|
|
39346
|
+
return createEarnKit(context).waitForCrossChainDeposit(params);
|
|
38201
39347
|
}
|
|
38202
39348
|
/**
|
|
38203
39349
|
* Fetch a deposit quote.
|
|
@@ -38221,7 +39367,7 @@ async function deposit$2(context, params) {
|
|
|
38221
39367
|
* })
|
|
38222
39368
|
* ```
|
|
38223
39369
|
*/ async function getDepositQuote(context, params) {
|
|
38224
|
-
return createEarnKit().getDepositQuote(params);
|
|
39370
|
+
return createEarnKit(context).getDepositQuote(params);
|
|
38225
39371
|
}
|
|
38226
39372
|
/**
|
|
38227
39373
|
* Fetch a withdrawal quote.
|
|
@@ -38245,7 +39391,7 @@ async function deposit$2(context, params) {
|
|
|
38245
39391
|
* })
|
|
38246
39392
|
* ```
|
|
38247
39393
|
*/ async function getWithdrawalQuote(context, params) {
|
|
38248
|
-
return createEarnKit().getWithdrawalQuote(params);
|
|
39394
|
+
return createEarnKit(context).getWithdrawalQuote(params);
|
|
38249
39395
|
}
|
|
38250
39396
|
/**
|
|
38251
39397
|
* Fetch a claim rewards quote.
|
|
@@ -38268,11 +39414,47 @@ async function deposit$2(context, params) {
|
|
|
38268
39414
|
* })
|
|
38269
39415
|
* ```
|
|
38270
39416
|
*/ async function getClaimRewardsQuote(context, params) {
|
|
38271
|
-
return createEarnKit().getClaimRewardsQuote(params);
|
|
39417
|
+
return createEarnKit(context).getClaimRewardsQuote(params);
|
|
39418
|
+
}
|
|
39419
|
+
/**
|
|
39420
|
+
* Resume a multi-phase earn operation that previously failed.
|
|
39421
|
+
*
|
|
39422
|
+
* Pass the {@link KitError} caught from `deposit`, `withdraw`, or
|
|
39423
|
+
* `claimRewards`. Completed phases can be skipped when the error carries
|
|
39424
|
+
* earn retry context. Call `isRetryableError(error)` first.
|
|
39425
|
+
*
|
|
39426
|
+
* @remarks
|
|
39427
|
+
* Retry re-fetches execution params and may re-submit the execute
|
|
39428
|
+
* transaction. Treat this as best-effort recovery if a prior execute
|
|
39429
|
+
* broadcast may still be in flight.
|
|
39430
|
+
*
|
|
39431
|
+
* @param context - AppKit context
|
|
39432
|
+
* @param error - The error caught from a previous multi-phase earn operation
|
|
39433
|
+
* @returns Promise resolving to the result of the resumed operation
|
|
39434
|
+
* @throws If the error is not retryable or lacks earn retry context
|
|
39435
|
+
*
|
|
39436
|
+
* @example
|
|
39437
|
+
* ```typescript
|
|
39438
|
+
* import { isRetryableError } from '@circle-fin/app-kit'
|
|
39439
|
+
* import { createContext } from '@circle-fin/app-kit/context'
|
|
39440
|
+
* import { retry } from '@circle-fin/app-kit/earn'
|
|
39441
|
+
*
|
|
39442
|
+
* const context = createContext()
|
|
39443
|
+
*
|
|
39444
|
+
* try {
|
|
39445
|
+
* await deposit(context, params)
|
|
39446
|
+
* } catch (error) {
|
|
39447
|
+
* if (isRetryableError(error)) {
|
|
39448
|
+
* const result = await retry(context, error)
|
|
39449
|
+
* }
|
|
39450
|
+
* }
|
|
39451
|
+
* ```
|
|
39452
|
+
*/ async function retry(context, error) {
|
|
39453
|
+
return createEarnKit(context).retry(error);
|
|
38272
39454
|
}
|
|
38273
39455
|
|
|
38274
39456
|
var name = "@circle-fin/unified-balance-kit";
|
|
38275
|
-
var version = "1.
|
|
39457
|
+
var version = "1.3.0";
|
|
38276
39458
|
var pkg = {
|
|
38277
39459
|
name: name,
|
|
38278
39460
|
version: version};
|
|
@@ -40740,29 +41922,33 @@ const CIRCLE_BPS_DIVISOR = 10_000n;
|
|
|
40740
41922
|
const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
40741
41923
|
/**
|
|
40742
41924
|
* Return the estimated Gateway gas fee for a chain in USDC atomic units.
|
|
40743
|
-
*
|
|
41925
|
+
* Prefers an entry in `overrides` (the real per-chain fee derived from a
|
|
41926
|
+
* prior estimate), then the static {@link GAS_FEE_BY_CHAIN} constant, and
|
|
41927
|
+
* finally a conservative 0.1 USDC fallback for unlisted chains.
|
|
40744
41928
|
*
|
|
40745
41929
|
* @param chain - The source blockchain.
|
|
41930
|
+
* @param overrides - Optional real per-chain gas fees keyed by chain.
|
|
40746
41931
|
* @returns Gas fee in USDC atomic units.
|
|
40747
|
-
*/ function getGasFee(chain) {
|
|
40748
|
-
return GAS_FEE_BY_CHAIN.get(chain) ?? DEFAULT_GAS_FEE;
|
|
41932
|
+
*/ function getGasFee(chain, overrides) {
|
|
41933
|
+
return overrides?.get(chain) ?? GAS_FEE_BY_CHAIN.get(chain) ?? DEFAULT_GAS_FEE;
|
|
40749
41934
|
}
|
|
40750
41935
|
/**
|
|
40751
41936
|
* Return the estimated forwarder fee for the destination chain
|
|
40752
41937
|
* (service fee + destination gas fee).
|
|
40753
41938
|
*
|
|
40754
41939
|
* @param destinationChain - The mint destination chain.
|
|
41940
|
+
* @param overrides - Optional real per-chain gas fees keyed by chain.
|
|
40755
41941
|
* @returns Forwarder fee in USDC atomic units.
|
|
40756
|
-
*/ function getForwarderFee(destinationChain) {
|
|
40757
|
-
const destGas = getGasFee(destinationChain);
|
|
41942
|
+
*/ function getForwarderFee(destinationChain, overrides) {
|
|
41943
|
+
const destGas = getGasFee(destinationChain, overrides);
|
|
40758
41944
|
return FORWARDER_SERVICE_FEE + destGas;
|
|
40759
41945
|
}
|
|
40760
41946
|
/**
|
|
40761
41947
|
* Estimate the fixed fees (gas + forwarder) and compute the maximum
|
|
40762
41948
|
* amount that can be drawn from this chain for a single intent,
|
|
40763
41949
|
* accounting for the 0.5 bps transfer fee if cross-chain.
|
|
40764
|
-
*/ function computeMaxDrawable(slot, forwarderFeeRemaining) {
|
|
40765
|
-
const gasFee = getGasFee(slot.chain);
|
|
41950
|
+
*/ function computeMaxDrawable(slot, forwarderFeeRemaining, overrides) {
|
|
41951
|
+
const gasFee = getGasFee(slot.chain, overrides);
|
|
40766
41952
|
let fixedFees = gasFee;
|
|
40767
41953
|
let forwarderFeeUsed = 0n;
|
|
40768
41954
|
if (forwarderFeeRemaining > 0n) {
|
|
@@ -40794,14 +41980,14 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
40794
41980
|
* buffer), and returns the allocations for this pass.
|
|
40795
41981
|
*
|
|
40796
41982
|
* Mutates `slot.remaining` so the next pass sees reduced balances.
|
|
40797
|
-
*/ function greedyAllocate(slots, amount, destinationChain, useForwarder) {
|
|
41983
|
+
*/ function greedyAllocate(slots, amount, destinationChain, useForwarder, overrides) {
|
|
40798
41984
|
const result = [];
|
|
40799
41985
|
let remaining = amount;
|
|
40800
|
-
let forwarderFeeRemaining = useForwarder ? getForwarderFee(destinationChain) : 0n;
|
|
41986
|
+
let forwarderFeeRemaining = useForwarder ? getForwarderFee(destinationChain, overrides) : 0n;
|
|
40801
41987
|
if (remaining <= 0n) return result;
|
|
40802
41988
|
for (const slot of slots){
|
|
40803
41989
|
if (remaining <= 0n) break;
|
|
40804
|
-
const { drawable, gasFee, forwarderFeeUsed } = computeMaxDrawable(slot, forwarderFeeRemaining);
|
|
41990
|
+
const { drawable, gasFee, forwarderFeeUsed } = computeMaxDrawable(slot, forwarderFeeRemaining, overrides);
|
|
40805
41991
|
if (drawable <= 0n) continue;
|
|
40806
41992
|
// Greedy: take as much as we can from this chain
|
|
40807
41993
|
const take = remaining < drawable ? remaining : drawable // NOSONAR: This is a false positive — Math.min() only accepts number, not bigint, so the ternary is the correct pattern here.
|
|
@@ -40900,7 +42086,7 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
40900
42086
|
// After this pass, slot.remaining reflects consumed capacity.
|
|
40901
42087
|
// -----------------------------------------------------------------------
|
|
40902
42088
|
const transferAmount = parseUnits(ctx.amountIn, USDC_DECIMALS);
|
|
40903
|
-
const allocations = greedyAllocate(slots, transferAmount, ctx.destinationChain, ctx.useForwarder);
|
|
42089
|
+
const allocations = greedyAllocate(slots, transferAmount, ctx.destinationChain, ctx.useForwarder, ctx.gasFeeOverrides);
|
|
40904
42090
|
assertFullyAllocated(allocations, transferAmount, ctx.amountIn);
|
|
40905
42091
|
// -----------------------------------------------------------------------
|
|
40906
42092
|
// 4. Phase 2 — Allocate developer fee
|
|
@@ -40908,7 +42094,7 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
40908
42094
|
// Same-chain first here too — if the destination chain still has
|
|
40909
42095
|
// capacity, use it (same-chain fee intent = cheapest gas).
|
|
40910
42096
|
// -----------------------------------------------------------------------
|
|
40911
|
-
const developerFeeAllocations = greedyAllocate(slots, devFeeAmount, ctx.destinationChain, false);
|
|
42097
|
+
const developerFeeAllocations = greedyAllocate(slots, devFeeAmount, ctx.destinationChain, false, ctx.gasFeeOverrides);
|
|
40912
42098
|
if (devFeeAmount > 0n) {
|
|
40913
42099
|
assertFullyAllocated(developerFeeAllocations, devFeeAmount, formatUnits(devFeeAmount.toString(), USDC_DECIMALS));
|
|
40914
42100
|
}
|
|
@@ -40917,7 +42103,7 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
40917
42103
|
// Again same ordering, same shared reduced balances.
|
|
40918
42104
|
// Same-chain first for the same reason.
|
|
40919
42105
|
// -----------------------------------------------------------------------
|
|
40920
|
-
const circleFeeAllocations = greedyAllocate(slots, circleFeeAmount, ctx.destinationChain, false);
|
|
42106
|
+
const circleFeeAllocations = greedyAllocate(slots, circleFeeAmount, ctx.destinationChain, false, ctx.gasFeeOverrides);
|
|
40921
42107
|
if (circleFeeAmount > 0n) {
|
|
40922
42108
|
assertFullyAllocated(circleFeeAllocations, circleFeeAmount, formatUnits(circleFeeAmount.toString(), USDC_DECIMALS));
|
|
40923
42109
|
}
|
|
@@ -41016,10 +42202,12 @@ const BPS_DIVISOR = 100_000n;
|
|
|
41016
42202
|
*
|
|
41017
42203
|
* Unlike `findChainNameByDomain` (which returns the display `name`),
|
|
41018
42204
|
* this returns `chain.chain` — the enum identifier expected by
|
|
41019
|
-
* {@link FeeAllocation}.
|
|
42205
|
+
* {@link FeeAllocation}. Returns `undefined` when no allocation covers the
|
|
42206
|
+
* domain so callers skip the intent rather than bucketing it under a
|
|
42207
|
+
* fabricated sentinel.
|
|
41020
42208
|
*/ function findBlockchainByDomain(domain, allocations) {
|
|
41021
42209
|
const alloc = allocations.find((a)=>a.chain.gateway.domain === domain);
|
|
41022
|
-
return alloc?.chain.chain
|
|
42210
|
+
return alloc?.chain.chain;
|
|
41023
42211
|
}
|
|
41024
42212
|
/**
|
|
41025
42213
|
* Normalize any address/salt format to lowercase bytes32 hex.
|
|
@@ -41143,6 +42331,33 @@ const BPS_DIVISOR = 100_000n;
|
|
|
41143
42331
|
};
|
|
41144
42332
|
});
|
|
41145
42333
|
}
|
|
42334
|
+
/**
|
|
42335
|
+
* Read an intent's transfer value as a BigInt, tolerating the string form
|
|
42336
|
+
* that can appear on estimate-response specs.
|
|
42337
|
+
*/ function intentValue(intent) {
|
|
42338
|
+
const { value } = intent.spec;
|
|
42339
|
+
return typeof value === 'bigint' ? value : safeBigInt(String(value), 'spec.value');
|
|
42340
|
+
}
|
|
42341
|
+
/**
|
|
42342
|
+
* Split a single intent's `maxFee` into its transfer-fee and gas-fee
|
|
42343
|
+
* components.
|
|
42344
|
+
*
|
|
42345
|
+
* `transferFee = value * GATEWAY_TRANSFER_FEE_SCALED_BPS / BPS_DIVISOR`
|
|
42346
|
+
* `gasFee = maxFee - transferFee`
|
|
42347
|
+
*
|
|
42348
|
+
* Same-chain transfers (withdrawals) do not incur a transfer fee, so the
|
|
42349
|
+
* whole `maxFee` is gas. See {@link aggregateFeesByIntent} for the caveats
|
|
42350
|
+
* on re-deriving the split locally.
|
|
42351
|
+
*/ function splitIntentFee(intent) {
|
|
42352
|
+
const { maxFee, spec } = intent;
|
|
42353
|
+
const isSameChain = spec.sourceDomain === spec.destinationDomain;
|
|
42354
|
+
const transferFee = isSameChain ? 0n : intentValue(intent) * GATEWAY_TRANSFER_FEE_SCALED_BPS / BPS_DIVISOR;
|
|
42355
|
+
const gasFee = maxFee > transferFee ? maxFee - transferFee : 0n;
|
|
42356
|
+
return {
|
|
42357
|
+
transferFee,
|
|
42358
|
+
gasFee
|
|
42359
|
+
};
|
|
42360
|
+
}
|
|
41146
42361
|
/**
|
|
41147
42362
|
* Decompose each intent's `maxFee` into a transfer fee and a gas fee,
|
|
41148
42363
|
* then aggregate both by source chain.
|
|
@@ -41167,7 +42382,6 @@ const BPS_DIVISOR = 100_000n;
|
|
|
41167
42382
|
* names from source domains.
|
|
41168
42383
|
* @returns Per-chain and total transfer/gas fee breakdowns.
|
|
41169
42384
|
*/ function aggregateFeesByIntent(estimatedIntents, allocations) {
|
|
41170
|
-
const transferFeeBps = GATEWAY_TRANSFER_FEE_SCALED_BPS;
|
|
41171
42385
|
const transferFeeByChain = new Map();
|
|
41172
42386
|
const gasFeeByChain = new Map();
|
|
41173
42387
|
let totalTransferFee = 0n;
|
|
@@ -41175,18 +42389,18 @@ const BPS_DIVISOR = 100_000n;
|
|
|
41175
42389
|
for (const intent of estimatedIntents){
|
|
41176
42390
|
const { maxFee, spec } = intent;
|
|
41177
42391
|
if (maxFee === 0n) continue;
|
|
41178
|
-
const
|
|
41179
|
-
|
|
41180
|
-
|
|
41181
|
-
|
|
41182
|
-
|
|
42392
|
+
const { transferFee, gasFee } = splitIntentFee(intent);
|
|
42393
|
+
totalTransferFee += transferFee;
|
|
42394
|
+
totalGasFee += gasFee;
|
|
42395
|
+
// Totals stay complete even if a domain can't be resolved; only the
|
|
42396
|
+
// per-chain breakdown skips it rather than inventing a placeholder chain.
|
|
42397
|
+
const chain = findBlockchainByDomain(spec.sourceDomain, allocations);
|
|
42398
|
+
if (chain === undefined) continue;
|
|
41183
42399
|
if (transferFee > 0n) {
|
|
41184
|
-
|
|
41185
|
-
transferFeeByChain.set(chainName, (transferFeeByChain.get(chainName) ?? 0n) + transferFee);
|
|
42400
|
+
transferFeeByChain.set(chain, (transferFeeByChain.get(chain) ?? 0n) + transferFee);
|
|
41186
42401
|
}
|
|
41187
42402
|
if (gasFee > 0n) {
|
|
41188
|
-
|
|
41189
|
-
gasFeeByChain.set(chainName, (gasFeeByChain.get(chainName) ?? 0n) + gasFee);
|
|
42403
|
+
gasFeeByChain.set(chain, (gasFeeByChain.get(chain) ?? 0n) + gasFee);
|
|
41190
42404
|
}
|
|
41191
42405
|
}
|
|
41192
42406
|
return {
|
|
@@ -41250,6 +42464,91 @@ const BPS_DIVISOR = 100_000n;
|
|
|
41250
42464
|
}
|
|
41251
42465
|
return fees;
|
|
41252
42466
|
}
|
|
42467
|
+
/**
|
|
42468
|
+
* Derive the real per-chain Gateway gas fee from estimated intents, keyed by
|
|
42469
|
+
* source {@link Blockchain}.
|
|
42470
|
+
*
|
|
42471
|
+
* The value is the maximum single-intent gas fee observed on each chain
|
|
42472
|
+
* (`maxFee − transferFee`) — the amount `computeAutoAllocation` must reserve
|
|
42473
|
+
* per burn intent on that chain. Gas is (near) amount-independent, so every
|
|
42474
|
+
* intent on a chain pays roughly the same; taking the max is a conservative
|
|
42475
|
+
* choice for the multi-intent-per-chain case.
|
|
42476
|
+
*
|
|
42477
|
+
* Intended for `AutoAllocationContext.gasFeeOverrides` so the corrective
|
|
42478
|
+
* re-allocation pass reserves the API's real fee instead of the static
|
|
42479
|
+
* {@link GAS_FEE_BY_CHAIN} constant.
|
|
42480
|
+
*
|
|
42481
|
+
* @param estimatedIntents - Intents with `maxFee` from {@link parseEstimateResponse}.
|
|
42482
|
+
* @param allocations - Normalised allocations used to resolve chain from source domain.
|
|
42483
|
+
* @returns Per-chain real gas fees in USDC atomic units.
|
|
42484
|
+
*
|
|
42485
|
+
* @example
|
|
42486
|
+
* ```typescript
|
|
42487
|
+
* import type { BurnIntent } from '../createIntent/types'
|
|
42488
|
+
* import type { NormalizedAllocation } from '../allocations'
|
|
42489
|
+
*
|
|
42490
|
+
* declare const estimatedIntents: BurnIntent[]
|
|
42491
|
+
* declare const allocations: NormalizedAllocation[]
|
|
42492
|
+
*
|
|
42493
|
+
* // Real per-chain gas, ready to pass as AutoAllocationContext.gasFeeOverrides
|
|
42494
|
+
* // to re-run computeAutoAllocation with the corrected reserve.
|
|
42495
|
+
* const overrides = deriveGasFeeOverrides(estimatedIntents, allocations)
|
|
42496
|
+
* ```
|
|
42497
|
+
*/ function deriveGasFeeOverrides(estimatedIntents, allocations) {
|
|
42498
|
+
const overrides = new Map();
|
|
42499
|
+
for (const intent of estimatedIntents){
|
|
42500
|
+
if (intent.maxFee === 0n) continue;
|
|
42501
|
+
const chain = findBlockchainByDomain(intent.spec.sourceDomain, allocations);
|
|
42502
|
+
if (chain === undefined) continue;
|
|
42503
|
+
const { gasFee } = splitIntentFee(intent);
|
|
42504
|
+
const prev = overrides.get(chain) ?? 0n;
|
|
42505
|
+
if (gasFee > prev) overrides.set(chain, gasFee);
|
|
42506
|
+
}
|
|
42507
|
+
return overrides;
|
|
42508
|
+
}
|
|
42509
|
+
/**
|
|
42510
|
+
* Sum the total balance each source chain must cover, keyed by source
|
|
42511
|
+
* {@link Blockchain}.
|
|
42512
|
+
*
|
|
42513
|
+
* Approximates the Gateway API's balance validation, which rejects a transfer
|
|
42514
|
+
* (`BALANCE_INSUFFICIENT_TOKEN`) when a depositor's confirmed balance on a
|
|
42515
|
+
* source chain is below `sum(intent.value + intent.maxFee)` for that
|
|
42516
|
+
* depositor's intents. This aggregates by chain across all sources, so it is
|
|
42517
|
+
* exact for the common single-depositor-per-chain wallet. When several
|
|
42518
|
+
* depositors hold USDC on the same chain, the chain-level sum can mask a
|
|
42519
|
+
* per-depositor shortfall (or a surplus on one depositor can hide it); the
|
|
42520
|
+
* API's own per-depositor `9001` remains the backstop for that case. Scope the
|
|
42521
|
+
* comparison per (depositor, chain) if that multi-depositor case must be caught
|
|
42522
|
+
* pre-submit.
|
|
42523
|
+
*
|
|
42524
|
+
* @param estimatedIntents - Intents with `maxFee` from {@link parseEstimateResponse}.
|
|
42525
|
+
* @param allocations - Normalised allocations used to resolve chain from source domain.
|
|
42526
|
+
* @returns Per-chain required amount (transfer value + fees) in USDC atomic units.
|
|
42527
|
+
*
|
|
42528
|
+
* @example
|
|
42529
|
+
* ```typescript
|
|
42530
|
+
* import { Blockchain } from '@core/chains'
|
|
42531
|
+
* import type { BurnIntent } from '../createIntent/types'
|
|
42532
|
+
* import type { NormalizedAllocation } from '../allocations'
|
|
42533
|
+
*
|
|
42534
|
+
* declare const estimatedIntents: BurnIntent[]
|
|
42535
|
+
* declare const allocations: NormalizedAllocation[]
|
|
42536
|
+
* declare const confirmedBalanceAtomic: bigint
|
|
42537
|
+
*
|
|
42538
|
+
* const required = sumRequiredPerChain(estimatedIntents, allocations)
|
|
42539
|
+
* const overDrawn =
|
|
42540
|
+
* (required.get(Blockchain.Ethereum) ?? 0n) > confirmedBalanceAtomic
|
|
42541
|
+
* ```
|
|
42542
|
+
*/ function sumRequiredPerChain(estimatedIntents, allocations) {
|
|
42543
|
+
const required = new Map();
|
|
42544
|
+
for (const intent of estimatedIntents){
|
|
42545
|
+
const chain = findBlockchainByDomain(intent.spec.sourceDomain, allocations);
|
|
42546
|
+
if (chain === undefined) continue;
|
|
42547
|
+
const amount = intentValue(intent) + intent.maxFee;
|
|
42548
|
+
required.set(chain, (required.get(chain) ?? 0n) + amount);
|
|
42549
|
+
}
|
|
42550
|
+
return required;
|
|
42551
|
+
}
|
|
41253
42552
|
|
|
41254
42553
|
/**
|
|
41255
42554
|
* Sign each adapter group: Solana one intent per signature, EVM batch per adapter.
|
|
@@ -41504,69 +42803,127 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
41504
42803
|
* non-forwarder transfer response is missing attestation or signature.
|
|
41505
42804
|
* @throws KitError Propagated from adapter signing if the user rejects
|
|
41506
42805
|
* or the signer is unavailable.
|
|
41507
|
-
*/
|
|
41508
|
-
|
|
41509
|
-
|
|
41510
|
-
|
|
41511
|
-
|
|
41512
|
-
|
|
41513
|
-
|
|
41514
|
-
|
|
41515
|
-
|
|
41516
|
-
|
|
41517
|
-
|
|
41518
|
-
|
|
41519
|
-
|
|
41520
|
-
|
|
41521
|
-
|
|
41522
|
-
|
|
41523
|
-
|
|
41524
|
-
|
|
41525
|
-
|
|
41526
|
-
|
|
41527
|
-
|
|
41528
|
-
|
|
41529
|
-
|
|
41530
|
-
|
|
41531
|
-
|
|
41532
|
-
|
|
41533
|
-
|
|
41534
|
-
|
|
41535
|
-
|
|
41536
|
-
|
|
41537
|
-
|
|
41538
|
-
|
|
41539
|
-
|
|
41540
|
-
|
|
41541
|
-
const breakdowns = balanceResults[i]?.breakdown[0]?.breakdown ?? [];
|
|
41542
|
-
for (const b of breakdowns){
|
|
41543
|
-
chainBalances.push({
|
|
41544
|
-
chain: b.chain,
|
|
41545
|
-
confirmedBalance: b.confirmedBalance,
|
|
41546
|
-
sourceIndex: i
|
|
41547
|
-
});
|
|
42806
|
+
*/ /**
|
|
42807
|
+
* Fetch confirmed per-chain USDC balances for every auto-allocation source.
|
|
42808
|
+
*
|
|
42809
|
+
* Used only on the `amountIn` (auto-allocation) path. Returns one
|
|
42810
|
+
* {@link ChainBalance} per (source, chain) pair so the greedy allocator — and
|
|
42811
|
+
* the corrective re-allocation pass — can reason about draw limits without a
|
|
42812
|
+
* second balance round-trip.
|
|
42813
|
+
*
|
|
42814
|
+
* @param params - Spend parameters (source(s) and token).
|
|
42815
|
+
* @param destChain - Resolved destination chain (used for network type).
|
|
42816
|
+
* @returns Confirmed balances tagged with their originating source index.
|
|
42817
|
+
*/ async function fetchChainBalances(params, destChain) {
|
|
42818
|
+
const rawSources = Array.isArray(params.from) ? params.from : [
|
|
42819
|
+
params.from
|
|
42820
|
+
];
|
|
42821
|
+
const sourcesArray = rawSources.filter((s)=>s != null);
|
|
42822
|
+
const networkType = destChain.isTestnet ? 'testnet' : 'mainnet';
|
|
42823
|
+
const balanceResults = await Promise.all(sourcesArray.map(async (source)=>{
|
|
42824
|
+
// When sourceAccount is set (delegate flow), scope the balance
|
|
42825
|
+
// query to the Gateway depositor — not the signer. Using the
|
|
42826
|
+
// address-only path bypasses adapter address resolution, which
|
|
42827
|
+
// would otherwise return the signer's balance (developer-
|
|
42828
|
+
// controlled) or reject an explicit address (user-controlled).
|
|
42829
|
+
let querySource;
|
|
42830
|
+
if (source.sourceAccount) {
|
|
42831
|
+
querySource = {
|
|
42832
|
+
address: source.sourceAccount
|
|
42833
|
+
};
|
|
42834
|
+
} else {
|
|
42835
|
+
querySource = {
|
|
42836
|
+
adapter: source.adapter
|
|
42837
|
+
};
|
|
42838
|
+
if ('address' in source && source.address) {
|
|
42839
|
+
querySource['address'] = source.address;
|
|
41548
42840
|
}
|
|
41549
42841
|
}
|
|
41550
|
-
|
|
41551
|
-
|
|
41552
|
-
|
|
41553
|
-
|
|
41554
|
-
chainBalances,
|
|
41555
|
-
useForwarder,
|
|
41556
|
-
...customFeeConfig ? {
|
|
41557
|
-
customFee: customFeeConfig
|
|
41558
|
-
} : {}
|
|
42842
|
+
return getBalances$1({
|
|
42843
|
+
token: params.token,
|
|
42844
|
+
sources: querySource,
|
|
42845
|
+
networkType
|
|
41559
42846
|
});
|
|
41560
|
-
|
|
41561
|
-
|
|
41562
|
-
|
|
41563
|
-
|
|
41564
|
-
|
|
41565
|
-
|
|
41566
|
-
|
|
42847
|
+
}));
|
|
42848
|
+
const chainBalances = [];
|
|
42849
|
+
for(let i = 0; i < balanceResults.length; i++){
|
|
42850
|
+
const breakdowns = balanceResults[i]?.breakdown[0]?.breakdown ?? [];
|
|
42851
|
+
for (const b of breakdowns){
|
|
42852
|
+
chainBalances.push({
|
|
42853
|
+
chain: b.chain,
|
|
42854
|
+
confirmedBalance: b.confirmedBalance,
|
|
42855
|
+
sourceIndex: i
|
|
42856
|
+
});
|
|
42857
|
+
}
|
|
42858
|
+
}
|
|
42859
|
+
return chainBalances;
|
|
42860
|
+
}
|
|
42861
|
+
/**
|
|
42862
|
+
* Build auto-allocated normalised allocations and burn intents from
|
|
42863
|
+
* pre-fetched balances.
|
|
42864
|
+
*
|
|
42865
|
+
* Performs no balance API call, so it can be re-invoked with
|
|
42866
|
+
* `gasFeeOverrides` (the real per-chain gas from a prior estimate) to correct
|
|
42867
|
+
* an over-draw without re-querying balances.
|
|
42868
|
+
*
|
|
42869
|
+
* @param params - Spend parameters (source(s), token, optional custom fee).
|
|
42870
|
+
* @param destChain - Resolved destination chain with Gateway v1 config.
|
|
42871
|
+
* @param recipientAddress - Resolved recipient address on the destination chain.
|
|
42872
|
+
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
42873
|
+
* @param amountIn - Human-readable USDC amount to allocate.
|
|
42874
|
+
* @param chainBalances - Confirmed balances from {@link fetchChainBalances}.
|
|
42875
|
+
* @param gasFeeOverrides - Optional real per-chain gas fees to reserve.
|
|
42876
|
+
* @returns Normalised allocations and burn intents for the estimate/transfer API.
|
|
42877
|
+
*/ async function buildAutoAllocatedFromBalances(params, destChain, recipientAddress, useForwarder, amountIn, chainBalances, gasFeeOverrides) {
|
|
42878
|
+
const rawSources = Array.isArray(params.from) ? params.from : [
|
|
42879
|
+
params.from
|
|
42880
|
+
];
|
|
42881
|
+
const sourcesArray = rawSources.filter((s)=>s != null);
|
|
42882
|
+
const customFeeConfig = params.config?.customFee;
|
|
42883
|
+
const autoAllocResult = computeAutoAllocation({
|
|
42884
|
+
amountIn,
|
|
42885
|
+
destinationChain: destChain.chain,
|
|
42886
|
+
chainBalances,
|
|
42887
|
+
useForwarder,
|
|
42888
|
+
...customFeeConfig ? {
|
|
42889
|
+
customFee: customFeeConfig
|
|
42890
|
+
} : {},
|
|
42891
|
+
...gasFeeOverrides ? {
|
|
42892
|
+
gasFeeOverrides
|
|
42893
|
+
} : {}
|
|
42894
|
+
});
|
|
42895
|
+
const normalizedAutoAllocations = await normalizeAutoAllocations(autoAllocResult, sourcesArray);
|
|
42896
|
+
const intents = buildAutoAllocatedBurnIntents(normalizedAutoAllocations, destChain, recipientAddress, params.token, params.config?.customFee);
|
|
42897
|
+
const allocations = [
|
|
42898
|
+
...normalizedAutoAllocations.user,
|
|
42899
|
+
...normalizedAutoAllocations.devFee,
|
|
42900
|
+
...normalizedAutoAllocations.circleFee
|
|
42901
|
+
];
|
|
42902
|
+
return {
|
|
42903
|
+
allocations,
|
|
42904
|
+
intents
|
|
42905
|
+
};
|
|
42906
|
+
}
|
|
42907
|
+
/**
|
|
42908
|
+
* Resolve allocations and burn intents for the spend.
|
|
42909
|
+
*
|
|
42910
|
+
* Auto-allocation (`amountIn`) fetches balances once and returns them so the
|
|
42911
|
+
* caller can detect and correct over-draw without re-querying. Explicit
|
|
42912
|
+
* allocations return no balances (they are user-authoritative).
|
|
42913
|
+
*
|
|
42914
|
+
* @param params - Spend parameters.
|
|
42915
|
+
* @param destChain - Resolved destination chain with Gateway v1 config.
|
|
42916
|
+
* @param recipientAddress - Resolved recipient address.
|
|
42917
|
+
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
42918
|
+
* @returns Allocations, intents, and (auto-allocation only) confirmed balances.
|
|
42919
|
+
*/ async function resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder) {
|
|
42920
|
+
if (params.amountIn) {
|
|
42921
|
+
const chainBalances = await fetchChainBalances(params, destChain);
|
|
42922
|
+
const { allocations, intents } = await buildAutoAllocatedFromBalances(params, destChain, recipientAddress, useForwarder, params.amountIn, chainBalances);
|
|
41567
42923
|
return {
|
|
41568
42924
|
allocations,
|
|
41569
|
-
intents
|
|
42925
|
+
intents,
|
|
42926
|
+
chainBalances
|
|
41570
42927
|
};
|
|
41571
42928
|
}
|
|
41572
42929
|
const allocations = await normalizeAllocations(params);
|
|
@@ -41608,6 +42965,148 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
41608
42965
|
forwardingFee: undefined
|
|
41609
42966
|
};
|
|
41610
42967
|
}
|
|
42968
|
+
/** Sum confirmed balances (atomic USDC) per source chain. */ function computeAvailablePerChain(chainBalances) {
|
|
42969
|
+
const available = new Map();
|
|
42970
|
+
for (const b of chainBalances){
|
|
42971
|
+
const atomic = parseUnits(b.confirmedBalance, USDC_DECIMALS$1);
|
|
42972
|
+
available.set(b.chain, (available.get(b.chain) ?? 0n) + atomic);
|
|
42973
|
+
}
|
|
42974
|
+
return available;
|
|
42975
|
+
}
|
|
42976
|
+
/**
|
|
42977
|
+
* Detect source chains whose required draw (value + maxFee across their
|
|
42978
|
+
* intents) exceeds the confirmed balance — the condition the Gateway API
|
|
42979
|
+
* rejects with `BALANCE_INSUFFICIENT_TOKEN` at `/v1/transfer`.
|
|
42980
|
+
*
|
|
42981
|
+
* Both sides are summed per chain (see {@link sumRequiredPerChain} and
|
|
42982
|
+
* {@link computeAvailablePerChain}), so detection is exact for the common
|
|
42983
|
+
* single-depositor-per-chain wallet. When multiple depositors hold USDC on the
|
|
42984
|
+
* same chain, a chain-level surplus can mask a per-depositor shortfall; the
|
|
42985
|
+
* API's own per-depositor `9001` remains the backstop in that case.
|
|
42986
|
+
*/ function findOverdrawnChains(estimatedIntents, allocations, chainBalances) {
|
|
42987
|
+
const required = sumRequiredPerChain(estimatedIntents, allocations);
|
|
42988
|
+
const available = computeAvailablePerChain(chainBalances);
|
|
42989
|
+
const overdrawn = [];
|
|
42990
|
+
for (const [chain, req] of required){
|
|
42991
|
+
const avail = available.get(chain) ?? 0n;
|
|
42992
|
+
if (req > avail) {
|
|
42993
|
+
overdrawn.push({
|
|
42994
|
+
chain,
|
|
42995
|
+
required: req,
|
|
42996
|
+
available: avail
|
|
42997
|
+
});
|
|
42998
|
+
}
|
|
42999
|
+
}
|
|
43000
|
+
return overdrawn;
|
|
43001
|
+
}
|
|
43002
|
+
/**
|
|
43003
|
+
* Build a descriptive KitError for an auto-allocation gas shortfall that
|
|
43004
|
+
* survives the corrective re-allocation, naming the per-chain gap so the
|
|
43005
|
+
* caller sees the real cause instead of the opaque API 9001 rejection.
|
|
43006
|
+
*/ function createAutoAllocationGasError(overdrawn, cause) {
|
|
43007
|
+
const detail = overdrawn.map((o)=>`${String(o.chain)} needs ${formatUnits(o.required.toString(), USDC_DECIMALS$1)} USDC ` + `(transfer + gas) but only ${formatUnits(o.available.toString(), USDC_DECIMALS$1)} USDC is available`).join('; ');
|
|
43008
|
+
return new KitError({
|
|
43009
|
+
...BalanceError.INSUFFICIENT_GAS,
|
|
43010
|
+
recoverability: 'FATAL',
|
|
43011
|
+
message: `Insufficient USDC to cover the transfer amount plus Gateway gas fees: ${detail}. ` + `Reduce the amount or add USDC on the affected chain(s).`,
|
|
43012
|
+
...cause === undefined ? {} : {
|
|
43013
|
+
cause: {
|
|
43014
|
+
trace: {
|
|
43015
|
+
cause
|
|
43016
|
+
}
|
|
43017
|
+
}
|
|
43018
|
+
}
|
|
43019
|
+
});
|
|
43020
|
+
}
|
|
43021
|
+
/**
|
|
43022
|
+
* Validate allocations against the network/forwarder rules, call the estimate
|
|
43023
|
+
* API, and return the estimated intents with any forwarding fee.
|
|
43024
|
+
*
|
|
43025
|
+
* @param allocations - Normalised allocations for the estimate.
|
|
43026
|
+
* @param intents - Burn intents to estimate.
|
|
43027
|
+
* @param destChain - Resolved destination chain with Gateway v1 config.
|
|
43028
|
+
* @param useForwarder - Whether the Forwarding Service path is active.
|
|
43029
|
+
* @returns Estimated intents (with real maxFee) and optional forwarding fee.
|
|
43030
|
+
*/ async function validateAndEstimate(allocations, intents, destChain, useForwarder) {
|
|
43031
|
+
assertNetworkCompatibility(allocations, destChain);
|
|
43032
|
+
if (useForwarder) {
|
|
43033
|
+
assertForwarderRouteSupport(destChain, allocations);
|
|
43034
|
+
}
|
|
43035
|
+
const apiBaseUrl = getGatewayApiBaseUrl(destChain.isTestnet);
|
|
43036
|
+
const estimateBody = buildEstimateRequestBody(intents);
|
|
43037
|
+
const { entries, forwardingFee } = await fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations);
|
|
43038
|
+
const estimatedIntents = parseEstimateResponse(entries, intents);
|
|
43039
|
+
return {
|
|
43040
|
+
estimatedIntents,
|
|
43041
|
+
forwardingFee
|
|
43042
|
+
};
|
|
43043
|
+
}
|
|
43044
|
+
/**
|
|
43045
|
+
* Fold newly-observed per-chain gas into the accumulated overrides, keeping the
|
|
43046
|
+
* higher fee per chain so a chain a later pass reveals is never under-reserved.
|
|
43047
|
+
*/ function mergeGasFeeOverrides(base, next) {
|
|
43048
|
+
const merged = new Map(base);
|
|
43049
|
+
for (const [chain, fee] of next){
|
|
43050
|
+
const prev = merged.get(chain);
|
|
43051
|
+
if (prev === undefined || fee > prev) {
|
|
43052
|
+
merged.set(chain, fee);
|
|
43053
|
+
}
|
|
43054
|
+
}
|
|
43055
|
+
return merged;
|
|
43056
|
+
}
|
|
43057
|
+
/**
|
|
43058
|
+
* Maximum corrective re-allocation passes before failing fast. One pass fixes
|
|
43059
|
+
* the common case; a second/third covers a chain that a spill only introduces
|
|
43060
|
+
* after gas is reserved. Bounds the worst case at this many extra estimate
|
|
43061
|
+
* round-trips (only ever reached when the balance genuinely falls short).
|
|
43062
|
+
*/ const MAX_CORRECTION_PASSES = 3;
|
|
43063
|
+
/**
|
|
43064
|
+
* Correct an auto-allocation over-draw: reserve the estimate's real per-chain
|
|
43065
|
+
* gas, re-allocate from the same balances, and re-estimate — repeating up to
|
|
43066
|
+
* {@link MAX_CORRECTION_PASSES} times, accumulating the real gas each pass
|
|
43067
|
+
* reveals.
|
|
43068
|
+
*
|
|
43069
|
+
* One pass fixes the common case, where the over-drawn chain was already in the
|
|
43070
|
+
* first estimate. A further pass covers a chain that a spill only introduces
|
|
43071
|
+
* once gas is reserved on the destination: that chain isn't in the first
|
|
43072
|
+
* estimate, so its real gas is unknown until it appears, and its first
|
|
43073
|
+
* re-allocation falls back to the static reserve. Each pass folds the newly
|
|
43074
|
+
* revealed gas into the overrides (see {@link mergeGasFeeOverrides}) so the
|
|
43075
|
+
* next pass reserves it too. Per-chain gas is ~amount-independent, so once
|
|
43076
|
+
* every drawn chain's real gas is known the allocation converges.
|
|
43077
|
+
*
|
|
43078
|
+
* When the shortfall is genuine — the re-allocation can't cover the amount, or
|
|
43079
|
+
* the passes are exhausted while still over-drawn — throws a gas-specific
|
|
43080
|
+
* {@link KitError} instead of submitting a doomed transfer.
|
|
43081
|
+
*/ async function correctOverdraw(opts) {
|
|
43082
|
+
let overrides = deriveGasFeeOverrides(opts.estimatedIntents, opts.allocations);
|
|
43083
|
+
let overdrawn = opts.overdrawn;
|
|
43084
|
+
for(let pass = 0; pass < MAX_CORRECTION_PASSES; pass++){
|
|
43085
|
+
let corrected;
|
|
43086
|
+
try {
|
|
43087
|
+
corrected = await buildAutoAllocatedFromBalances(opts.params, opts.destChain, opts.recipientAddress, opts.useForwarder, opts.amountIn, opts.chainBalances, overrides);
|
|
43088
|
+
} catch (err) {
|
|
43089
|
+
// Re-allocating with the real gas reserved can't cover the amount →
|
|
43090
|
+
// surface a gas-specific error instead of the opaque API rejection.
|
|
43091
|
+
if (err instanceof KitError && err.code === BalanceError.INSUFFICIENT_TOKEN.code) {
|
|
43092
|
+
throw createAutoAllocationGasError(overdrawn, err);
|
|
43093
|
+
}
|
|
43094
|
+
throw err;
|
|
43095
|
+
}
|
|
43096
|
+
const { estimatedIntents, forwardingFee } = await validateAndEstimate(corrected.allocations, corrected.intents, opts.destChain, opts.useForwarder);
|
|
43097
|
+
const stillOverdrawn = findOverdrawnChains(estimatedIntents, corrected.allocations, opts.chainBalances);
|
|
43098
|
+
if (stillOverdrawn.length === 0) {
|
|
43099
|
+
return {
|
|
43100
|
+
allocations: corrected.allocations,
|
|
43101
|
+
estimatedIntents,
|
|
43102
|
+
forwardingFee
|
|
43103
|
+
};
|
|
43104
|
+
}
|
|
43105
|
+
overdrawn = stillOverdrawn;
|
|
43106
|
+
overrides = mergeGasFeeOverrides(overrides, deriveGasFeeOverrides(estimatedIntents, corrected.allocations));
|
|
43107
|
+
}
|
|
43108
|
+
throw createAutoAllocationGasError(overdrawn);
|
|
43109
|
+
}
|
|
41611
43110
|
/**
|
|
41612
43111
|
* Validate allocations, call the estimate API, and return estimated intents.
|
|
41613
43112
|
*
|
|
@@ -41615,6 +43114,14 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
41615
43114
|
* path. Handles forwarder route validation, network compatibility, and the
|
|
41616
43115
|
* estimate API call.
|
|
41617
43116
|
*
|
|
43117
|
+
* For auto-allocation (`amountIn`), the greedy allocator reserves a static
|
|
43118
|
+
* per-chain gas fee that can undershoot the API's real fee, draining a source
|
|
43119
|
+
* (typically the destination chain) below `value + maxFee` and triggering a
|
|
43120
|
+
* `BALANCE_INSUFFICIENT_TOKEN` rejection. When the first estimate reveals such
|
|
43121
|
+
* an over-draw, a bounded corrective re-allocation reserves the real gas and
|
|
43122
|
+
* re-estimates until it converges or fails fast (see {@link correctOverdraw}).
|
|
43123
|
+
* Explicit allocations are user-authoritative and never re-allocated.
|
|
43124
|
+
*
|
|
41618
43125
|
* @param params - Spend parameters.
|
|
41619
43126
|
* @param destChain - Resolved destination chain with Gateway v1 config.
|
|
41620
43127
|
* @param recipientAddress - Resolved recipient address.
|
|
@@ -41624,15 +43131,24 @@ const FORWARDER_POLL_TIMEOUT_MS = 300_000;
|
|
|
41624
43131
|
if (useForwarder) {
|
|
41625
43132
|
assertForwarderRouteSupport(destChain);
|
|
41626
43133
|
}
|
|
41627
|
-
const { allocations, intents } = await resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder);
|
|
41628
|
-
|
|
41629
|
-
if (
|
|
41630
|
-
|
|
43134
|
+
const { allocations, intents, chainBalances } = await resolveAllocationsAndIntents(params, destChain, recipientAddress, useForwarder);
|
|
43135
|
+
const { estimatedIntents, forwardingFee } = await validateAndEstimate(allocations, intents, destChain, useForwarder);
|
|
43136
|
+
if (params.amountIn && chainBalances) {
|
|
43137
|
+
const overdrawn = findOverdrawnChains(estimatedIntents, allocations, chainBalances);
|
|
43138
|
+
if (overdrawn.length > 0) {
|
|
43139
|
+
return correctOverdraw({
|
|
43140
|
+
params,
|
|
43141
|
+
destChain,
|
|
43142
|
+
recipientAddress,
|
|
43143
|
+
useForwarder,
|
|
43144
|
+
amountIn: params.amountIn,
|
|
43145
|
+
chainBalances,
|
|
43146
|
+
estimatedIntents,
|
|
43147
|
+
allocations,
|
|
43148
|
+
overdrawn
|
|
43149
|
+
});
|
|
43150
|
+
}
|
|
41631
43151
|
}
|
|
41632
|
-
const apiBaseUrl = getGatewayApiBaseUrl(destChain.isTestnet);
|
|
41633
|
-
const estimateBody = buildEstimateRequestBody(intents);
|
|
41634
|
-
const { entries, forwardingFee } = await fetchEstimate(apiBaseUrl, estimateBody, useForwarder, allocations);
|
|
41635
|
-
const estimatedIntents = parseEstimateResponse(entries, intents);
|
|
41636
43152
|
return {
|
|
41637
43153
|
allocations,
|
|
41638
43154
|
estimatedIntents,
|
|
@@ -42540,8 +44056,10 @@ const assertCustomFeePolicySymbol = Symbol('assertCustomFeePolicy');
|
|
|
42540
44056
|
*
|
|
42541
44057
|
* - `computeFee` — required function that receives resolved spend params
|
|
42542
44058
|
* and returns a fee as a string (or `Promise<string>`).
|
|
42543
|
-
* - `resolveFeeRecipientAddress` —
|
|
42544
|
-
* recipient address as a string (or `Promise<string>`).
|
|
44059
|
+
* - `resolveFeeRecipientAddress` — optional function that returns a
|
|
44060
|
+
* recipient address as a string (or `Promise<string>`). Omit it when
|
|
44061
|
+
* using `setFeeRecipients()`'s declarative map instead — a policy
|
|
44062
|
+
* with neither throws at spend time.
|
|
42545
44063
|
*
|
|
42546
44064
|
* @example
|
|
42547
44065
|
* ```ts
|
|
@@ -42554,7 +44072,7 @@ const assertCustomFeePolicySymbol = Symbol('assertCustomFeePolicy');
|
|
|
42554
44072
|
* ```
|
|
42555
44073
|
*/ const customFeePolicySchema = zod.z.object({
|
|
42556
44074
|
computeFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))),
|
|
42557
|
-
resolveFeeRecipientAddress: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string())))
|
|
44075
|
+
resolveFeeRecipientAddress: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional()
|
|
42558
44076
|
}).strict();
|
|
42559
44077
|
/**
|
|
42560
44078
|
* Assert that the provided value conforms to {@link CustomFeePolicy}.
|
|
@@ -42576,6 +44094,71 @@ const assertCustomFeePolicySymbol = Symbol('assertCustomFeePolicy');
|
|
|
42576
44094
|
validateWithStateTracking(config, customFeePolicySchema, 'UnifiedBalanceKit custom fee policy', assertCustomFeePolicySymbol);
|
|
42577
44095
|
}
|
|
42578
44096
|
|
|
44097
|
+
const assertFeeRecipientsConfigSymbol = Symbol('assertFeeRecipientsConfig');
|
|
44098
|
+
/**
|
|
44099
|
+
* Schema for validating {@link FeeRecipientsConfig}.
|
|
44100
|
+
*
|
|
44101
|
+
* Requires at least one of `evm`/`solana`, non-empty string values for
|
|
44102
|
+
* whichever keys are present, and — mirroring the `depositAccount`
|
|
44103
|
+
* validation in `deposit/validate/assertions` — an address format that
|
|
44104
|
+
* matches the given chain type (EVM hex vs Solana base58).
|
|
44105
|
+
*
|
|
44106
|
+
* @example
|
|
44107
|
+
* ```ts
|
|
44108
|
+
* const config = {
|
|
44109
|
+
* evm: '0x1234567890123456789012345678901234567890',
|
|
44110
|
+
* solana: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
|
|
44111
|
+
* }
|
|
44112
|
+
* const result = feeRecipientsConfigSchema.safeParse(config)
|
|
44113
|
+
* // result.success === true
|
|
44114
|
+
* ```
|
|
44115
|
+
*/ const feeRecipientsConfigSchema = zod.z.object({
|
|
44116
|
+
evm: zod.z.string().min(1, 'Fee recipient address is required.').optional(),
|
|
44117
|
+
solana: zod.z.string().min(1, 'Fee recipient address is required.').optional()
|
|
44118
|
+
}).strict().refine((config)=>Object.keys(config).length > 0, {
|
|
44119
|
+
message: 'At least one fee recipient (evm or solana) is required.'
|
|
44120
|
+
}).superRefine((config, ctx)=>{
|
|
44121
|
+
for (const type of Object.keys(config)){
|
|
44122
|
+
const address = config[type];
|
|
44123
|
+
if (address == null) continue;
|
|
44124
|
+
// `{ name: type, type }` is a placeholder chain identifier — only
|
|
44125
|
+
// `.type` is checked by these two helpers today, `.name` is unused.
|
|
44126
|
+
// No real ChainDefinition exists here, since validation runs before
|
|
44127
|
+
// a destination chain is resolved.
|
|
44128
|
+
if (!isValidAddressForChain(address, {
|
|
44129
|
+
name: type,
|
|
44130
|
+
type
|
|
44131
|
+
})) {
|
|
44132
|
+
const { expectedAddressFormat } = extractChainInfo({
|
|
44133
|
+
name: type,
|
|
44134
|
+
type
|
|
44135
|
+
});
|
|
44136
|
+
ctx.addIssue({
|
|
44137
|
+
code: zod.z.ZodIssueCode.custom,
|
|
44138
|
+
path: [
|
|
44139
|
+
type
|
|
44140
|
+
],
|
|
44141
|
+
message: `Invalid ${type} address "${address}". Expected ${expectedAddressFormat}.`
|
|
44142
|
+
});
|
|
44143
|
+
}
|
|
44144
|
+
}
|
|
44145
|
+
});
|
|
44146
|
+
/**
|
|
44147
|
+
* Assert that the provided value conforms to {@link FeeRecipientsConfig}.
|
|
44148
|
+
*
|
|
44149
|
+
* Throws a validation error with annotated paths if the configuration is
|
|
44150
|
+
* malformed.
|
|
44151
|
+
*
|
|
44152
|
+
* @param config - The fee recipients map to validate.
|
|
44153
|
+
*
|
|
44154
|
+
* @example
|
|
44155
|
+
* ```ts
|
|
44156
|
+
* assertFeeRecipientsConfig({ evm: '0x1234567890123456789012345678901234567890' })
|
|
44157
|
+
* ```
|
|
44158
|
+
*/ function assertFeeRecipientsConfig(config) {
|
|
44159
|
+
validateWithStateTracking(config, feeRecipientsConfigSchema, 'UnifiedBalanceKit fee recipients config', assertFeeRecipientsConfigSymbol);
|
|
44160
|
+
}
|
|
44161
|
+
|
|
42579
44162
|
function sameChain(a, b) {
|
|
42580
44163
|
return a.chain !== undefined && a.chain === b.chain;
|
|
42581
44164
|
}
|
|
@@ -43556,6 +45139,105 @@ function assertSourceAccountAddresses(from) {
|
|
|
43556
45139
|
config: params.config
|
|
43557
45140
|
};
|
|
43558
45141
|
}
|
|
45142
|
+
/**
|
|
45143
|
+
* Tracks, per {@link CustomFeePolicy} instance, which chain types have
|
|
45144
|
+
* already triggered the "falling back to resolveFeeRecipientAddress"
|
|
45145
|
+
* warning, so repeated `spend()`/`estimateSpend()` calls (e.g. live
|
|
45146
|
+
* quoting) warn once per (policy, chain type) pair rather than on every
|
|
45147
|
+
* call.
|
|
45148
|
+
*/ const warnedFeeRecipientFallbacks = new WeakMap();
|
|
45149
|
+
/**
|
|
45150
|
+
* Invoke `resolveFeeRecipientAddress` and validate its return value has a
|
|
45151
|
+
* plausible address format for `destChain`, the same check
|
|
45152
|
+
* `setFeeRecipients()` already applies at config time. Unlike the map,
|
|
45153
|
+
* the callback's return value can't be validated ahead of time, so it's
|
|
45154
|
+
* checked here instead — a malformed value throws immediately rather
|
|
45155
|
+
* than silently becoming the fee recipient.
|
|
45156
|
+
*
|
|
45157
|
+
* @internal
|
|
45158
|
+
*/ async function resolveFeeRecipientFromCallback(callback, destChain, params) {
|
|
45159
|
+
const address = await callback(destChain, params);
|
|
45160
|
+
if (!isValidAddressForChain(address, destChain)) {
|
|
45161
|
+
throw new KitError({
|
|
45162
|
+
...InputError.VALIDATION_FAILED,
|
|
45163
|
+
recoverability: 'FATAL',
|
|
45164
|
+
message: `resolveFeeRecipientAddress returned an invalid address ` + `"${address}" for chain type "${destChain.type}" ` + `(resolved destination: ${destChain.name}).`
|
|
45165
|
+
});
|
|
45166
|
+
}
|
|
45167
|
+
return address;
|
|
45168
|
+
}
|
|
45169
|
+
/**
|
|
45170
|
+
* Resolve the single fee recipient address for a spend.
|
|
45171
|
+
*
|
|
45172
|
+
* Every fee burn intent in a spend mints to the same destination
|
|
45173
|
+
* chain regardless of which source chain(s) funded it, so exactly one
|
|
45174
|
+
* recipient address — valid on `destChain` — is ever needed.
|
|
45175
|
+
*
|
|
45176
|
+
* `feeRecipients` (set via `setFeeRecipients`) takes priority over the
|
|
45177
|
+
* policy's `resolveFeeRecipientAddress` callback for any chain type it
|
|
45178
|
+
* has an entry for, since it's a direct lookup and doesn't require
|
|
45179
|
+
* invoking developer code. For a chain type `feeRecipients` doesn't
|
|
45180
|
+
* cover, it falls back to `resolveFeeRecipientAddress` if one is
|
|
45181
|
+
* configured — a warning is logged once per (policy, chain type) pair
|
|
45182
|
+
* so the fallback isn't a silent surprise, without spamming repeated
|
|
45183
|
+
* `estimateSpend()` calls used for live quoting. Throws if neither
|
|
45184
|
+
* resolves `destChain`'s type, or if `resolveFeeRecipientAddress`
|
|
45185
|
+
* resolves it to a malformed address (see
|
|
45186
|
+
* {@link resolveFeeRecipientFromCallback}).
|
|
45187
|
+
*
|
|
45188
|
+
* @internal
|
|
45189
|
+
*/ async function resolveFeeRecipient(destChain, policy, feeRecipients, params) {
|
|
45190
|
+
if (feeRecipients) {
|
|
45191
|
+
// `destChain.type` is `@core/chains`' broader `ChainType` union;
|
|
45192
|
+
// `FeeRecipientChainType` is the narrower subset this map supports
|
|
45193
|
+
// today. A type not present as a key simply has no configured
|
|
45194
|
+
// recipient, which is handled below.
|
|
45195
|
+
const type = destChain.type;
|
|
45196
|
+
const recipientAddress = feeRecipients[type];
|
|
45197
|
+
if (recipientAddress) {
|
|
45198
|
+
return recipientAddress;
|
|
45199
|
+
}
|
|
45200
|
+
if (policy.resolveFeeRecipientAddress) {
|
|
45201
|
+
const warnedTypes = warnedFeeRecipientFallbacks.get(policy);
|
|
45202
|
+
if (!warnedTypes?.has(type)) {
|
|
45203
|
+
warnedFeeRecipientFallbacks.set(policy, (warnedTypes ?? new Set()).add(type));
|
|
45204
|
+
console.warn(`setFeeRecipients() is configured but has no entry for chain ` + `type "${type}" — falling back to customFeePolicy.` + `resolveFeeRecipientAddress for this chain type. Add a ` + `"${type}" entry to setFeeRecipients() to avoid this fallback.`);
|
|
45205
|
+
}
|
|
45206
|
+
return resolveFeeRecipientFromCallback(policy.resolveFeeRecipientAddress, destChain, params);
|
|
45207
|
+
}
|
|
45208
|
+
throw new KitError({
|
|
45209
|
+
...InputError.VALIDATION_FAILED,
|
|
45210
|
+
recoverability: 'FATAL',
|
|
45211
|
+
message: `No fee recipient configured for chain type "${type}" ` + `(resolved destination: ${destChain.name}). Call setFeeRecipients() ` + `with an entry for "${type}", or provide resolveFeeRecipientAddress ` + `on the custom fee policy.`
|
|
45212
|
+
});
|
|
45213
|
+
}
|
|
45214
|
+
if (!policy.resolveFeeRecipientAddress) {
|
|
45215
|
+
throw new KitError({
|
|
45216
|
+
...InputError.VALIDATION_FAILED,
|
|
45217
|
+
recoverability: 'FATAL',
|
|
45218
|
+
message: 'No fee recipient configured — call setFeeRecipients() or provide ' + 'resolveFeeRecipientAddress on the custom fee policy.'
|
|
45219
|
+
});
|
|
45220
|
+
}
|
|
45221
|
+
return resolveFeeRecipientFromCallback(policy.resolveFeeRecipientAddress, destChain, params);
|
|
45222
|
+
}
|
|
45223
|
+
/**
|
|
45224
|
+
* Guard against a common misconfiguration: a developer sets the
|
|
45225
|
+
* declarative `feeRecipients` map expecting it alone to drive fee
|
|
45226
|
+
* collection, but no fee is ever charged without a `computeFee` from
|
|
45227
|
+
* `customFeePolicy` to determine the amount. Without this check that
|
|
45228
|
+
* misconfiguration fails silently — no fee is charged and no error is
|
|
45229
|
+
* raised.
|
|
45230
|
+
*
|
|
45231
|
+
* @internal
|
|
45232
|
+
*/ function assertFeeRecipientsHasPolicy(feeRecipients) {
|
|
45233
|
+
if (feeRecipients) {
|
|
45234
|
+
throw new KitError({
|
|
45235
|
+
...InputError.VALIDATION_FAILED,
|
|
45236
|
+
recoverability: 'FATAL',
|
|
45237
|
+
message: 'setFeeRecipients() is configured but no developer fee will be ' + 'charged: setCustomFeePolicy() must also be set to provide ' + 'computeFee, which determines the fee amount. Call ' + 'setCustomFeePolicy(), or remove setFeeRecipients() if no ' + 'developer fee is intended.'
|
|
45238
|
+
});
|
|
45239
|
+
}
|
|
45240
|
+
}
|
|
43559
45241
|
/**
|
|
43560
45242
|
* Apply a {@link CustomFeePolicy} to an adapter-only spend.
|
|
43561
45243
|
*
|
|
@@ -43564,15 +45246,20 @@ function assertSourceAccountAddresses(from) {
|
|
|
43564
45246
|
* `config.customFee` so the provider sees it.
|
|
43565
45247
|
*
|
|
43566
45248
|
* @internal
|
|
43567
|
-
*/ async function mergeCustomFeePolicyForAdapterOnly(params, policy) {
|
|
43568
|
-
if (params.config?.customFee
|
|
45249
|
+
*/ async function mergeCustomFeePolicyForAdapterOnly(params, policy, feeRecipients) {
|
|
45250
|
+
if (params.config?.customFee) {
|
|
45251
|
+
return params;
|
|
45252
|
+
}
|
|
45253
|
+
if (!policy) {
|
|
45254
|
+
assertFeeRecipientsHasPolicy(feeRecipients);
|
|
43569
45255
|
return params;
|
|
43570
45256
|
}
|
|
43571
45257
|
const destChain = resolveChainIdentifier(params.to.chain);
|
|
43572
|
-
|
|
43573
|
-
|
|
43574
|
-
|
|
43575
|
-
|
|
45258
|
+
// Resolve the recipient before computing the fee: a KitError here
|
|
45259
|
+
// (missing/unresolvable recipient) shouldn't be preceded by an
|
|
45260
|
+
// otherwise-wasted computeFee call, which may be a network request.
|
|
45261
|
+
const recipientAddress = await resolveFeeRecipient(destChain, policy, feeRecipients, params);
|
|
45262
|
+
const feeValue = await policy.computeFee(params);
|
|
43576
45263
|
return {
|
|
43577
45264
|
...params,
|
|
43578
45265
|
config: {
|
|
@@ -43602,18 +45289,35 @@ function assertSourceAccountAddresses(from) {
|
|
|
43602
45289
|
});
|
|
43603
45290
|
}
|
|
43604
45291
|
}
|
|
43605
|
-
async function mergeCustomFeeConfig(resolved, policy) {
|
|
43606
|
-
if (resolved.config?.customFee
|
|
45292
|
+
async function mergeCustomFeeConfig(resolved, policy, feeRecipients) {
|
|
45293
|
+
if (resolved.config?.customFee) {
|
|
43607
45294
|
return resolved;
|
|
43608
45295
|
}
|
|
43609
|
-
|
|
43610
|
-
|
|
45296
|
+
if (!policy) {
|
|
45297
|
+
assertFeeRecipientsHasPolicy(feeRecipients);
|
|
43611
45298
|
return resolved;
|
|
43612
45299
|
}
|
|
43613
|
-
|
|
43614
|
-
|
|
43615
|
-
|
|
43616
|
-
|
|
45300
|
+
// Skip fee resolution when there's no source chain to spend from at
|
|
45301
|
+
// all. This state can't arise from validated input today — the
|
|
45302
|
+
// caller re-checks and throws "No source chain found" right after
|
|
45303
|
+
// this returns — but skipping here isn't dead code: verified that
|
|
45304
|
+
// removing it lets a degenerate zero-allocation resolved value reach
|
|
45305
|
+
// computeFee/assertDeveloperFeeWithinBounds first, which throws a
|
|
45306
|
+
// misleading "Developer fee must be less than the total spend
|
|
45307
|
+
// amount" (0 >= 0 total allocation) instead of the correct "No
|
|
45308
|
+
// source chain found" error — or, for a real developer computeFee
|
|
45309
|
+
// that assumes a non-empty allocation, an uncaught raw exception
|
|
45310
|
+
// instead of any KitError at all. This guard exists to guarantee the
|
|
45311
|
+
// caller's clear error is what actually surfaces, not for
|
|
45312
|
+
// correctness.
|
|
45313
|
+
if (collectSourceChains(resolved).length === 0) {
|
|
45314
|
+
return resolved;
|
|
45315
|
+
}
|
|
45316
|
+
// Resolve the recipient before computing the fee: a KitError here
|
|
45317
|
+
// (missing/unresolvable recipient) shouldn't be preceded by an
|
|
45318
|
+
// otherwise-wasted computeFee call, which may be a network request.
|
|
45319
|
+
const recipientAddress = await resolveFeeRecipient(resolved.to.chain, policy, feeRecipients, resolved);
|
|
45320
|
+
const feeValue = await policy.computeFee(resolved);
|
|
43617
45321
|
return {
|
|
43618
45322
|
...resolved,
|
|
43619
45323
|
config: {
|
|
@@ -43669,14 +45373,14 @@ async function mergeCustomFeeConfig(resolved, policy) {
|
|
|
43669
45373
|
}
|
|
43670
45374
|
const destChain = resolveChainIdentifier(params.to.chain);
|
|
43671
45375
|
if (!hasExplicitAllocations(params.from)) {
|
|
43672
|
-
const merged = await mergeCustomFeePolicyForAdapterOnly(params, context.customFeePolicy);
|
|
45376
|
+
const merged = await mergeCustomFeePolicyForAdapterOnly(params, context.customFeePolicy, context.feeRecipients);
|
|
43673
45377
|
assertDeveloperFeeWithinAmount(merged);
|
|
43674
45378
|
const provider = findProviderForChain(context, normalizeToken(merged.token), destChain);
|
|
43675
45379
|
return callSpend(provider, toProviderAdapterOnlyParams(merged));
|
|
43676
45380
|
}
|
|
43677
45381
|
const resolved = await resolveSpendParams(params);
|
|
43678
45382
|
assertSpendNetworkCompatibility(resolved);
|
|
43679
|
-
const withFee = await mergeCustomFeeConfig(resolved, context.customFeePolicy);
|
|
45383
|
+
const withFee = await mergeCustomFeeConfig(resolved, context.customFeePolicy, context.feeRecipients);
|
|
43680
45384
|
assertDeveloperFeeWithinBounds(withFee);
|
|
43681
45385
|
const sourceChains = collectSourceChains(withFee);
|
|
43682
45386
|
if (sourceChains.length === 0) {
|
|
@@ -43716,14 +45420,14 @@ async function mergeCustomFeeConfig(resolved, policy) {
|
|
|
43716
45420
|
assertSpendParams(params);
|
|
43717
45421
|
const destChain = resolveChainIdentifier(params.to.chain);
|
|
43718
45422
|
if (!hasExplicitAllocations(params.from)) {
|
|
43719
|
-
const merged = await mergeCustomFeePolicyForAdapterOnly(params, context.customFeePolicy);
|
|
45423
|
+
const merged = await mergeCustomFeePolicyForAdapterOnly(params, context.customFeePolicy, context.feeRecipients);
|
|
43720
45424
|
assertDeveloperFeeWithinAmount(merged);
|
|
43721
45425
|
const provider = findProviderForChain(context, normalizeToken(merged.token), destChain);
|
|
43722
45426
|
return provider.estimateSpend(toProviderAdapterOnlyParams(merged));
|
|
43723
45427
|
}
|
|
43724
45428
|
const resolved = await resolveSpendParams(params);
|
|
43725
45429
|
assertSpendNetworkCompatibility(resolved);
|
|
43726
|
-
const withFee = await mergeCustomFeeConfig(resolved, context.customFeePolicy);
|
|
45430
|
+
const withFee = await mergeCustomFeeConfig(resolved, context.customFeePolicy, context.feeRecipients);
|
|
43727
45431
|
assertDeveloperFeeWithinBounds(withFee);
|
|
43728
45432
|
const sourceChains = collectSourceChains(withFee);
|
|
43729
45433
|
if (sourceChains.length === 0) {
|
|
@@ -44698,6 +46402,46 @@ const removeFundParamsSchema = zod.z.object({
|
|
|
44698
46402
|
*/ removeCustomFeePolicy() {
|
|
44699
46403
|
delete this.context.customFeePolicy;
|
|
44700
46404
|
}
|
|
46405
|
+
/**
|
|
46406
|
+
* Set a declarative fee recipient map, keyed by chain type. Once set,
|
|
46407
|
+
* `spend()`/`estimateSpend()` resolve the fee recipient by looking up
|
|
46408
|
+
* the spend's destination chain type in this map — taking priority
|
|
46409
|
+
* over `customFeePolicy`'s `resolveFeeRecipientAddress` callback.
|
|
46410
|
+
*
|
|
46411
|
+
* @remarks
|
|
46412
|
+
* This only controls which address a fee is sent to — it does not by
|
|
46413
|
+
* itself cause any fee to be charged. You still need
|
|
46414
|
+
* {@link UnifiedBalanceKit.setCustomFeePolicy}'s `computeFee` to
|
|
46415
|
+
* determine the fee amount; calling `setFeeRecipients` without ever
|
|
46416
|
+
* calling `setCustomFeePolicy` throws at spend time (there is no
|
|
46417
|
+
* `computeFee` to determine an amount).
|
|
46418
|
+
*
|
|
46419
|
+
* @param config - Fee recipient addresses keyed by chain type (e.g.
|
|
46420
|
+
* `{ evm: '0x...', solana: 'Sol...' }`). Provide entries for every
|
|
46421
|
+
* chain type you expect to spend to; spending to a chain type with
|
|
46422
|
+
* no matching entry throws before any fee collection is attempted.
|
|
46423
|
+
*
|
|
46424
|
+
* @example
|
|
46425
|
+
* ```typescript
|
|
46426
|
+
* kit.setFeeRecipients({
|
|
46427
|
+
* evm: '0x1234567890123456789012345678901234567890',
|
|
46428
|
+
* solana: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
|
|
46429
|
+
* })
|
|
46430
|
+
* ```
|
|
46431
|
+
*/ setFeeRecipients(config) {
|
|
46432
|
+
assertFeeRecipientsConfig(config);
|
|
46433
|
+
this.context.feeRecipients = config;
|
|
46434
|
+
}
|
|
46435
|
+
/**
|
|
46436
|
+
* Remove the declarative fee recipient map for the kit.
|
|
46437
|
+
*
|
|
46438
|
+
* @example
|
|
46439
|
+
* ```typescript
|
|
46440
|
+
* kit.removeFeeRecipients()
|
|
46441
|
+
* ```
|
|
46442
|
+
*/ removeFeeRecipients() {
|
|
46443
|
+
delete this.context.feeRecipients;
|
|
46444
|
+
}
|
|
44701
46445
|
}
|
|
44702
46446
|
|
|
44703
46447
|
// Auto-register this kit for user agent tracking
|
|
@@ -45024,6 +46768,45 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
45024
46768
|
*/ removeCustomFeePolicy() {
|
|
45025
46769
|
this.kit.removeCustomFeePolicy();
|
|
45026
46770
|
}
|
|
46771
|
+
/**
|
|
46772
|
+
* Set a declarative fee recipient map, keyed by chain type.
|
|
46773
|
+
*
|
|
46774
|
+
* Once set, `spend()`/`estimateSpend()` resolve the fee recipient by
|
|
46775
|
+
* looking up the spend's destination chain type in this map — taking
|
|
46776
|
+
* priority over `customFeePolicy`'s `resolveFeeRecipientAddress`
|
|
46777
|
+
* callback.
|
|
46778
|
+
*
|
|
46779
|
+
* @remarks
|
|
46780
|
+
* This only controls which address a fee is sent to — it does not by
|
|
46781
|
+
* itself cause any fee to be charged. You still need
|
|
46782
|
+
* `setCustomFeePolicy`'s `computeFee` to determine the fee amount;
|
|
46783
|
+
* calling `setFeeRecipients` without ever calling `setCustomFeePolicy`
|
|
46784
|
+
* throws at spend time (there is no `computeFee` to determine an
|
|
46785
|
+
* amount).
|
|
46786
|
+
*
|
|
46787
|
+
* @param config - Fee recipient addresses keyed by chain type (e.g.
|
|
46788
|
+
* `{ evm: '0x...', solana: 'Sol...' }`).
|
|
46789
|
+
*
|
|
46790
|
+
* @example
|
|
46791
|
+
* ```typescript
|
|
46792
|
+
* kit.unifiedBalance.setFeeRecipients({
|
|
46793
|
+
* evm: '0x1234567890123456789012345678901234567890',
|
|
46794
|
+
* solana: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
|
|
46795
|
+
* })
|
|
46796
|
+
* ```
|
|
46797
|
+
*/ setFeeRecipients(config) {
|
|
46798
|
+
this.kit.setFeeRecipients(config);
|
|
46799
|
+
}
|
|
46800
|
+
/**
|
|
46801
|
+
* Remove the declarative fee recipient map.
|
|
46802
|
+
*
|
|
46803
|
+
* @example
|
|
46804
|
+
* ```typescript
|
|
46805
|
+
* kit.unifiedBalance.removeFeeRecipients()
|
|
46806
|
+
* ```
|
|
46807
|
+
*/ removeFeeRecipients() {
|
|
46808
|
+
this.kit.removeFeeRecipients();
|
|
46809
|
+
}
|
|
45027
46810
|
}
|
|
45028
46811
|
|
|
45029
46812
|
/**
|
|
@@ -45175,7 +46958,8 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
45175
46958
|
waitForCrossChainDeposit: async (params)=>waitForCrossChainDeposit(this.context, params),
|
|
45176
46959
|
getDepositQuote: async (params)=>getDepositQuote(this.context, params),
|
|
45177
46960
|
getWithdrawalQuote: async (params)=>getWithdrawalQuote(this.context, params),
|
|
45178
|
-
getClaimRewardsQuote: async (params)=>getClaimRewardsQuote(this.context, params)
|
|
46961
|
+
getClaimRewardsQuote: async (params)=>getClaimRewardsQuote(this.context, params),
|
|
46962
|
+
retry: async (error)=>retry(this.context, error)
|
|
45179
46963
|
};
|
|
45180
46964
|
}
|
|
45181
46965
|
/**
|
|
@@ -45434,7 +47218,8 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
45434
47218
|
* or `'NOT_FOUND'`). Use {@link AppKit.waitForSwap} if you'd rather
|
|
45435
47219
|
* not write the polling loop yourself.
|
|
45436
47220
|
*
|
|
45437
|
-
* @param params - `txHash
|
|
47221
|
+
* @param params - `txHash` and `chainIn`, plus optional `chainOut` and
|
|
47222
|
+
* `kitKey`.
|
|
45438
47223
|
* @returns A snapshot of the swap's status at the time of the call.
|
|
45439
47224
|
* @throws \{KitError\} If `chainIn` or `chainOut` is malformed.
|
|
45440
47225
|
*
|
|
@@ -45548,7 +47333,7 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
45548
47333
|
* translates to the chain's native sentinel address — `0xEee…` for EVM,
|
|
45549
47334
|
* `1111…` for Solana — before querying the service.
|
|
45550
47335
|
*
|
|
45551
|
-
* @param params - `chain`, optional `tokens
|
|
47336
|
+
* @param params - `chain`, plus optional `tokens` and `kitKey`.
|
|
45552
47337
|
* @returns A nested map of `[chain][address] → { priceUSD, fetchedAt }`.
|
|
45553
47338
|
* @throws \{KitError\} If `chain` is malformed, `tokens` exceeds 100
|
|
45554
47339
|
* entries, or any entry is neither a registered symbol nor a
|
|
@@ -45612,6 +47397,10 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
45612
47397
|
this.context.actions.bridge[action] ??= [];
|
|
45613
47398
|
this.context.actions.bridge[action].push(typedHandler);
|
|
45614
47399
|
}
|
|
47400
|
+
if (action === '*' || action.startsWith('earn.')) {
|
|
47401
|
+
this.context.actions.earn[action] ??= [];
|
|
47402
|
+
this.context.actions.earn[action].push(typedHandler);
|
|
47403
|
+
}
|
|
45615
47404
|
}
|
|
45616
47405
|
off(actionOrWildCard, handler) {
|
|
45617
47406
|
const action = actionOrWildCard;
|
|
@@ -45621,16 +47410,32 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
45621
47410
|
this.unifiedBalance.off(ubAction, typedHandler);
|
|
45622
47411
|
}
|
|
45623
47412
|
if (action === '*' || action.startsWith('bridge.')) {
|
|
45624
|
-
|
|
45625
|
-
|
|
45626
|
-
|
|
45627
|
-
|
|
45628
|
-
|
|
45629
|
-
|
|
45630
|
-
|
|
45631
|
-
|
|
45632
|
-
|
|
45633
|
-
|
|
47413
|
+
this.removeStoredActionHandler(this.context.actions.bridge, action, typedHandler);
|
|
47414
|
+
}
|
|
47415
|
+
if (action === '*' || action.startsWith('earn.')) {
|
|
47416
|
+
this.removeStoredActionHandler(this.context.actions.earn, action, typedHandler);
|
|
47417
|
+
}
|
|
47418
|
+
}
|
|
47419
|
+
/**
|
|
47420
|
+
* Remove one handler from a deferred AppKit action bucket.
|
|
47421
|
+
*
|
|
47422
|
+
* Deletes the action key when its handler list becomes empty.
|
|
47423
|
+
*
|
|
47424
|
+
* @param handlers - Action bucket to update (`bridge` or `earn`)
|
|
47425
|
+
* @param action - Stored action key, including namespace or `*`
|
|
47426
|
+
* @param handler - Handler reference previously passed to {@link on}
|
|
47427
|
+
*/ removeStoredActionHandler(handlers, action, handler) {
|
|
47428
|
+
const actionHandlers = handlers[action];
|
|
47429
|
+
if (!actionHandlers) {
|
|
47430
|
+
return;
|
|
47431
|
+
}
|
|
47432
|
+
const index = actionHandlers.indexOf(handler);
|
|
47433
|
+
if (index === -1) {
|
|
47434
|
+
return;
|
|
47435
|
+
}
|
|
47436
|
+
actionHandlers.splice(index, 1);
|
|
47437
|
+
if (actionHandlers.length === 0) {
|
|
47438
|
+
Reflect.deleteProperty(handlers, action);
|
|
45634
47439
|
}
|
|
45635
47440
|
}
|
|
45636
47441
|
}
|