@circle-fin/app-kit 1.11.0 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/README.md +56 -25
- package/bridge.cjs +1696 -1348
- package/bridge.d.cts +979 -2
- package/bridge.d.mts +979 -2
- package/bridge.d.ts +979 -2
- package/bridge.mjs +1696 -1348
- package/chains.cjs +106 -1
- package/chains.d.cts +96 -2
- package/chains.d.mts +96 -2
- package/chains.d.ts +96 -2
- package/chains.mjs +105 -2
- package/context.d.cts +978 -1
- package/context.d.mts +978 -1
- package/context.d.ts +978 -1
- package/earn.cjs +1728 -65
- package/earn.d.cts +978 -1
- package/earn.d.mts +978 -1
- package/earn.d.ts +978 -1
- package/earn.mjs +1728 -65
- package/estimateBridge.cjs +1695 -1347
- package/estimateBridge.d.cts +978 -1
- package/estimateBridge.d.mts +978 -1
- package/estimateBridge.d.ts +978 -1
- package/estimateBridge.mjs +1695 -1347
- package/estimateSwap.cjs +312 -17
- package/estimateSwap.d.cts +977 -1
- package/estimateSwap.d.mts +977 -1
- package/estimateSwap.d.ts +977 -1
- package/estimateSwap.mjs +312 -17
- package/index.cjs +3608 -1603
- package/index.d.cts +4181 -3426
- package/index.d.mts +4181 -3426
- package/index.d.ts +4181 -3426
- package/index.mjs +3607 -1604
- package/package.json +6 -6
- package/swap.cjs +312 -17
- package/swap.d.cts +977 -1
- package/swap.d.mts +977 -1
- package/swap.d.ts +977 -1
- package/swap.mjs +312 -17
- package/unifiedBalance.cjs +436 -122
- package/unifiedBalance.d.cts +132 -31
- package/unifiedBalance.d.mts +132 -31
- package/unifiedBalance.d.ts +132 -31
- package/unifiedBalance.mjs +436 -122
package/earn.cjs
CHANGED
|
@@ -39,6 +39,7 @@ require('bn.js');
|
|
|
39
39
|
require('@coral-xyz/anchor');
|
|
40
40
|
require('bs58');
|
|
41
41
|
require('@noble/curves/ed25519');
|
|
42
|
+
var viem = require('viem');
|
|
42
43
|
var units = require('@ethersproject/units');
|
|
43
44
|
var keccak256 = require('@ethersproject/keccak256');
|
|
44
45
|
|
|
@@ -679,6 +680,11 @@ class KitError extends Error {
|
|
|
679
680
|
name: 'INPUT_INSUFFICIENT_SWAP_AMOUNT',
|
|
680
681
|
type: 'INPUT'
|
|
681
682
|
},
|
|
683
|
+
/** Action not supported by this adapter / ecosystem */ UNSUPPORTED_ACTION: {
|
|
684
|
+
code: 1008,
|
|
685
|
+
name: 'INPUT_UNSUPPORTED_ACTION',
|
|
686
|
+
type: 'INPUT'
|
|
687
|
+
},
|
|
682
688
|
/** No route satisfies the slippage or minimum-output constraint */ SLIPPAGE_CONSTRAINT_NOT_MET: {
|
|
683
689
|
code: 1009,
|
|
684
690
|
name: 'INPUT_SLIPPAGE_CONSTRAINT_NOT_MET',
|
|
@@ -743,6 +749,29 @@ class KitError extends Error {
|
|
|
743
749
|
type: 'LIQUIDITY'
|
|
744
750
|
}
|
|
745
751
|
};
|
|
752
|
+
/**
|
|
753
|
+
* Standardized error definitions for RPC type errors.
|
|
754
|
+
*
|
|
755
|
+
* RPC errors occur when communicating with blockchain RPC providers,
|
|
756
|
+
* including endpoint failures, invalid responses, and provider-specific issues.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```typescript
|
|
760
|
+
* import { RpcError } from '@core/errors'
|
|
761
|
+
*
|
|
762
|
+
* const error = new KitError({
|
|
763
|
+
* ...RpcError.ENDPOINT_ERROR,
|
|
764
|
+
* recoverability: 'RETRYABLE',
|
|
765
|
+
* message: 'RPC endpoint unavailable on Ethereum',
|
|
766
|
+
* cause: { trace: { endpoint: 'https://mainnet.infura.io' } }
|
|
767
|
+
* })
|
|
768
|
+
* ```
|
|
769
|
+
*/ const RpcError = {
|
|
770
|
+
/** RPC endpoint returned error or is unavailable */ ENDPOINT_ERROR: {
|
|
771
|
+
code: 4001,
|
|
772
|
+
name: 'RPC_ENDPOINT_ERROR',
|
|
773
|
+
type: 'RPC'
|
|
774
|
+
}};
|
|
746
775
|
/**
|
|
747
776
|
* Standardized error definitions for NETWORK type errors.
|
|
748
777
|
*
|
|
@@ -2342,6 +2371,8 @@ function getOptionalString(value) {
|
|
|
2342
2371
|
Blockchain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
2343
2372
|
Blockchain["XDC"] = "XDC";
|
|
2344
2373
|
Blockchain["XDC_Apothem"] = "XDC_Apothem";
|
|
2374
|
+
Blockchain["X_Layer"] = "X_Layer";
|
|
2375
|
+
Blockchain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
2345
2376
|
Blockchain["ZKSync_Era"] = "ZKSync_Era";
|
|
2346
2377
|
Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
|
|
2347
2378
|
})(Blockchain || (Blockchain = {}));
|
|
@@ -2395,6 +2426,7 @@ var BridgeChain;
|
|
|
2395
2426
|
BridgeChain["Unichain"] = "Unichain";
|
|
2396
2427
|
BridgeChain["World_Chain"] = "World_Chain";
|
|
2397
2428
|
BridgeChain["XDC"] = "XDC";
|
|
2429
|
+
BridgeChain["X_Layer"] = "X_Layer";
|
|
2398
2430
|
// Testnet chains with CCTPv2 support
|
|
2399
2431
|
BridgeChain["Arc_Testnet"] = "Arc_Testnet";
|
|
2400
2432
|
BridgeChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
|
|
@@ -2420,6 +2452,7 @@ var BridgeChain;
|
|
|
2420
2452
|
BridgeChain["Unichain_Sepolia"] = "Unichain_Sepolia";
|
|
2421
2453
|
BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
2422
2454
|
BridgeChain["XDC_Apothem"] = "XDC_Apothem";
|
|
2455
|
+
BridgeChain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
2423
2456
|
})(BridgeChain || (BridgeChain = {}));
|
|
2424
2457
|
var UnifiedBalanceChain;
|
|
2425
2458
|
(function(UnifiedBalanceChain) {
|
|
@@ -4957,7 +4990,8 @@ var EarnChain;
|
|
|
4957
4990
|
isTestnet: true,
|
|
4958
4991
|
explorerUrl: 'https://amoy.polygonscan.com/tx/{hash}',
|
|
4959
4992
|
rpcEndpoints: [
|
|
4960
|
-
'https://
|
|
4993
|
+
'https://polygon-amoy-bor-rpc.publicnode.com',
|
|
4994
|
+
'https://polygon-amoy.drpc.org'
|
|
4961
4995
|
],
|
|
4962
4996
|
eurcAddress: null,
|
|
4963
4997
|
usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -5822,6 +5856,104 @@ var EarnChain;
|
|
|
5822
5856
|
}
|
|
5823
5857
|
});
|
|
5824
5858
|
|
|
5859
|
+
/**
|
|
5860
|
+
* X Layer Mainnet chain definition
|
|
5861
|
+
* @remarks
|
|
5862
|
+
* This represents the official production network for the X Layer blockchain.
|
|
5863
|
+
* X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
|
|
5864
|
+
* using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
|
|
5865
|
+
* OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
|
|
5866
|
+
*/ const XLayer = defineChain({
|
|
5867
|
+
type: 'evm',
|
|
5868
|
+
chain: Blockchain.X_Layer,
|
|
5869
|
+
name: 'X Layer',
|
|
5870
|
+
title: 'X Layer Mainnet',
|
|
5871
|
+
nativeCurrency: {
|
|
5872
|
+
name: 'OKB',
|
|
5873
|
+
symbol: 'OKB',
|
|
5874
|
+
decimals: 18
|
|
5875
|
+
},
|
|
5876
|
+
chainId: 196,
|
|
5877
|
+
isTestnet: false,
|
|
5878
|
+
explorerUrl: 'https://www.oklink.com/xlayer/tx/{hash}',
|
|
5879
|
+
rpcEndpoints: [
|
|
5880
|
+
'https://xlayerrpc.okx.com'
|
|
5881
|
+
],
|
|
5882
|
+
eurcAddress: null,
|
|
5883
|
+
usdcAddress: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
5884
|
+
usdtAddress: null,
|
|
5885
|
+
cctp: {
|
|
5886
|
+
domain: 37,
|
|
5887
|
+
contracts: {
|
|
5888
|
+
v2: {
|
|
5889
|
+
type: 'split',
|
|
5890
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
5891
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
5892
|
+
confirmations: 65,
|
|
5893
|
+
fastConfirmations: 1
|
|
5894
|
+
}
|
|
5895
|
+
},
|
|
5896
|
+
forwarderSupported: {
|
|
5897
|
+
source: false,
|
|
5898
|
+
destination: false
|
|
5899
|
+
}
|
|
5900
|
+
},
|
|
5901
|
+
kitContracts: {
|
|
5902
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
5903
|
+
}
|
|
5904
|
+
});
|
|
5905
|
+
|
|
5906
|
+
/**
|
|
5907
|
+
* X Layer Testnet chain definition
|
|
5908
|
+
* @remarks
|
|
5909
|
+
* This represents the official test network for the X Layer blockchain.
|
|
5910
|
+
* X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
|
|
5911
|
+
* using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
|
|
5912
|
+
* OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
|
|
5913
|
+
*/ const XLayerTestnet = defineChain({
|
|
5914
|
+
type: 'evm',
|
|
5915
|
+
chain: Blockchain.X_Layer_Testnet,
|
|
5916
|
+
name: 'X Layer Testnet',
|
|
5917
|
+
title: 'X Layer Testnet',
|
|
5918
|
+
nativeCurrency: {
|
|
5919
|
+
name: 'OKB',
|
|
5920
|
+
symbol: 'OKB',
|
|
5921
|
+
decimals: 18
|
|
5922
|
+
},
|
|
5923
|
+
chainId: 1952,
|
|
5924
|
+
isTestnet: true,
|
|
5925
|
+
// Deliberately not oklink.com (used for mainnet): viem's bundled OKLink
|
|
5926
|
+
// testnet URL targets the deprecated pre-rebrand chain ID 195, not this
|
|
5927
|
+
// chain's ID (1952). Verified against the internal chain-expansion-scripts
|
|
5928
|
+
// config (`v2config.sandbox.yml`) — do not "normalize" this to match mainnet.
|
|
5929
|
+
explorerUrl: 'https://web3.okx.com/explorer/x-layer-testnet/tx/{hash}',
|
|
5930
|
+
rpcEndpoints: [
|
|
5931
|
+
'https://testrpc.xlayer.tech'
|
|
5932
|
+
],
|
|
5933
|
+
eurcAddress: null,
|
|
5934
|
+
usdcAddress: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
5935
|
+
usdtAddress: null,
|
|
5936
|
+
cctp: {
|
|
5937
|
+
domain: 37,
|
|
5938
|
+
contracts: {
|
|
5939
|
+
v2: {
|
|
5940
|
+
type: 'split',
|
|
5941
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
5942
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
5943
|
+
confirmations: 65,
|
|
5944
|
+
fastConfirmations: 1
|
|
5945
|
+
}
|
|
5946
|
+
},
|
|
5947
|
+
forwarderSupported: {
|
|
5948
|
+
source: false,
|
|
5949
|
+
destination: false
|
|
5950
|
+
}
|
|
5951
|
+
},
|
|
5952
|
+
kitContracts: {
|
|
5953
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET
|
|
5954
|
+
}
|
|
5955
|
+
});
|
|
5956
|
+
|
|
5825
5957
|
/**
|
|
5826
5958
|
* ZKSync Era Mainnet chain definition
|
|
5827
5959
|
* @remarks
|
|
@@ -5941,6 +6073,8 @@ var Chains = {
|
|
|
5941
6073
|
WorldChainSepolia: WorldChainSepolia,
|
|
5942
6074
|
XDC: XDC,
|
|
5943
6075
|
XDCApothem: XDCApothem,
|
|
6076
|
+
XLayer: XLayer,
|
|
6077
|
+
XLayerTestnet: XLayerTestnet,
|
|
5944
6078
|
ZKSyncEra: ZKSyncEra,
|
|
5945
6079
|
ZKSyncEraSepolia: ZKSyncEraSepolia
|
|
5946
6080
|
};
|
|
@@ -7384,6 +7518,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
7384
7518
|
[Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
7385
7519
|
[Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
|
|
7386
7520
|
[Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
7521
|
+
[Blockchain.X_Layer]: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
7387
7522
|
[Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
|
|
7388
7523
|
// =========================================================================
|
|
7389
7524
|
// Testnets (alphabetically sorted)
|
|
@@ -7392,6 +7527,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
7392
7527
|
[Blockchain.Arbitrum_Sepolia]: '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d',
|
|
7393
7528
|
[Blockchain.Avalanche_Fuji]: '0x5425890298aed601595a70AB815c96711a31Bc65',
|
|
7394
7529
|
[Blockchain.Base_Sepolia]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
7530
|
+
[Blockchain.Celo_Alfajores_Testnet]: '0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B',
|
|
7395
7531
|
[Blockchain.Codex_Testnet]: '0x6d7f141b6819C2c9CC2f818e6ad549E7Ca090F8f',
|
|
7396
7532
|
[Blockchain.Cronos_Testnet]: '0xEb33dc5fac03833e132593659e1dE7256aB59794',
|
|
7397
7533
|
[Blockchain.Edge_Testnet]: '0x2d9F7CAD728051AA35Ecdc472a14cf8cDF5CFD6B',
|
|
@@ -7418,6 +7554,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
7418
7554
|
[Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
|
|
7419
7555
|
[Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
|
|
7420
7556
|
[Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
|
|
7557
|
+
[Blockchain.X_Layer_Testnet]: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
7421
7558
|
[Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853'
|
|
7422
7559
|
}
|
|
7423
7560
|
};
|
|
@@ -8131,6 +8268,52 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8131
8268
|
return explorerUrl;
|
|
8132
8269
|
}
|
|
8133
8270
|
|
|
8271
|
+
/**
|
|
8272
|
+
* Assert that a value has type `never` (exhaustive switch helper).
|
|
8273
|
+
*
|
|
8274
|
+
* @remarks
|
|
8275
|
+
* Use in the `default` branch of a switch over a discriminated union.
|
|
8276
|
+
* If all union members are handled, the default is unreachable and TypeScript
|
|
8277
|
+
* narrows the parameter to `never`. If a member is missed, the compiler errors.
|
|
8278
|
+
*
|
|
8279
|
+
* @param _x - The value (typed as `never` when switch is exhaustive).
|
|
8280
|
+
* @returns Never returns; always throws.
|
|
8281
|
+
* @throws Error when the switch is not exhaustive.
|
|
8282
|
+
*
|
|
8283
|
+
* @example
|
|
8284
|
+
* ```typescript
|
|
8285
|
+
* type Foo = { type: 'a'; x: number } | { type: 'b'; y: string }
|
|
8286
|
+
*
|
|
8287
|
+
* function handle(foo: Foo): string {
|
|
8288
|
+
* switch (foo.type) {
|
|
8289
|
+
* case 'a': return String(foo.x)
|
|
8290
|
+
* case 'b': return foo.y
|
|
8291
|
+
* default: return assertNever(foo)
|
|
8292
|
+
* }
|
|
8293
|
+
* }
|
|
8294
|
+
* ```
|
|
8295
|
+
*/ function assertNever$2(x) {
|
|
8296
|
+
// Plain `String(x)` collapses non-primitive union members (objects, arrays)
|
|
8297
|
+
// to `'[object Object]'`, which is useless when triaging which discriminant
|
|
8298
|
+
// was missed. Attempt `JSON.stringify` first so the thrown message preserves
|
|
8299
|
+
// the offending shape. Fall back to a minimal `typeof`-based label if
|
|
8300
|
+
// serialization fails (`BigInt` member, circular references, host objects).
|
|
8301
|
+
//
|
|
8302
|
+
// `x` is statically typed as `never` (the whole point of this helper), but
|
|
8303
|
+
// at runtime callers may still pass an unexpected value when the switch is
|
|
8304
|
+
// not actually exhaustive — that's exactly the bug we want to surface. Cast
|
|
8305
|
+
// through `unknown` so the runtime defence is not stripped by the compiler.
|
|
8306
|
+
const value = x;
|
|
8307
|
+
let stringified;
|
|
8308
|
+
try {
|
|
8309
|
+
const json = JSON.stringify(value);
|
|
8310
|
+
stringified = typeof json === 'string' ? json : `<${typeof value}>`;
|
|
8311
|
+
} catch {
|
|
8312
|
+
stringified = `<unstringifiable ${typeof value}>`;
|
|
8313
|
+
}
|
|
8314
|
+
throw new Error(`Unhandled switch case: ${stringified}`);
|
|
8315
|
+
}
|
|
8316
|
+
|
|
8134
8317
|
/**
|
|
8135
8318
|
* CCTP forwarding magic bytes prefix.
|
|
8136
8319
|
*
|
|
@@ -8513,7 +8696,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8513
8696
|
}
|
|
8514
8697
|
|
|
8515
8698
|
var name$3 = "@circle-fin/bridge-kit";
|
|
8516
|
-
var version$3 = "1.
|
|
8699
|
+
var version$3 = "1.13.0";
|
|
8517
8700
|
var pkg$3 = {
|
|
8518
8701
|
name: name$3,
|
|
8519
8702
|
version: version$3};
|
|
@@ -8549,15 +8732,196 @@ var pkg$3 = {
|
|
|
8549
8732
|
computeFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional(),
|
|
8550
8733
|
calculateFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional(),
|
|
8551
8734
|
resolveFeeRecipientAddress: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string())))
|
|
8552
|
-
}).strict().
|
|
8735
|
+
}).strict().superRefine((data, ctx)=>{
|
|
8553
8736
|
const hasComputeFee = data.computeFee !== undefined;
|
|
8554
8737
|
const hasCalculateFee = data.calculateFee !== undefined;
|
|
8555
|
-
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8738
|
+
if (hasComputeFee && hasCalculateFee) {
|
|
8739
|
+
ctx.addIssue({
|
|
8740
|
+
code: zod.z.ZodIssueCode.custom,
|
|
8741
|
+
message: 'Provide either computeFee or calculateFee, not both. Use computeFee (recommended) for human-readable amounts.'
|
|
8742
|
+
});
|
|
8743
|
+
}
|
|
8744
|
+
if (!hasComputeFee && !hasCalculateFee) {
|
|
8745
|
+
ctx.addIssue({
|
|
8746
|
+
code: zod.z.ZodIssueCode.custom,
|
|
8747
|
+
message: 'Provide either computeFee or calculateFee. Use computeFee (recommended) for human-readable amounts.'
|
|
8748
|
+
});
|
|
8749
|
+
}
|
|
8559
8750
|
});
|
|
8560
8751
|
|
|
8752
|
+
/**
|
|
8753
|
+
* Canonical list of actions that do not prepare or submit transactions.
|
|
8754
|
+
*
|
|
8755
|
+
* @internal
|
|
8756
|
+
*/ const READ_ACTION_KEYS = [
|
|
8757
|
+
'token.allowance',
|
|
8758
|
+
'token.balanceOf',
|
|
8759
|
+
'token.name',
|
|
8760
|
+
'native.balanceOf',
|
|
8761
|
+
'usdc.allowance',
|
|
8762
|
+
'usdc.balanceOf',
|
|
8763
|
+
'usdc.name',
|
|
8764
|
+
'gateway.v1.isDelegate',
|
|
8765
|
+
'gateway.v1.withdrawingBalance',
|
|
8766
|
+
'gateway.v1.withdrawalBlock',
|
|
8767
|
+
'gateway.v1.signBurnIntents'
|
|
8768
|
+
];
|
|
8769
|
+
const READ_ACTION_KEY_SET = new Set(READ_ACTION_KEYS);
|
|
8770
|
+
/**
|
|
8771
|
+
* Check whether a runtime value identifies a read action.
|
|
8772
|
+
*
|
|
8773
|
+
* @param action - The value to classify.
|
|
8774
|
+
* @returns Whether the value is a registered read-action key.
|
|
8775
|
+
*
|
|
8776
|
+
* @example
|
|
8777
|
+
* ```typescript
|
|
8778
|
+
* import { isReadActionKey } from '@core/adapter'
|
|
8779
|
+
*
|
|
8780
|
+
* if (isReadActionKey(value)) {
|
|
8781
|
+
* await adapter.readAction(value, params, context)
|
|
8782
|
+
* }
|
|
8783
|
+
* ```
|
|
8784
|
+
*
|
|
8785
|
+
* @internal
|
|
8786
|
+
*/ function isReadActionKey(action) {
|
|
8787
|
+
return READ_ACTION_KEY_SET.has(action);
|
|
8788
|
+
}
|
|
8789
|
+
|
|
8790
|
+
/**
|
|
8791
|
+
* Create the standard error for a missing or non-read action.
|
|
8792
|
+
*
|
|
8793
|
+
* @param action - The unsupported action value.
|
|
8794
|
+
* @returns A fatal unsupported-action error.
|
|
8795
|
+
*
|
|
8796
|
+
* @internal
|
|
8797
|
+
*/ function createUnsupportedReadActionError(action) {
|
|
8798
|
+
return new KitError({
|
|
8799
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
8800
|
+
recoverability: 'FATAL',
|
|
8801
|
+
message: `Read action "${String(action)}" is not registered in this adapter.`
|
|
8802
|
+
});
|
|
8803
|
+
}
|
|
8804
|
+
/**
|
|
8805
|
+
* Execute a read through the adapter's dedicated read seam when available.
|
|
8806
|
+
*
|
|
8807
|
+
* @remarks
|
|
8808
|
+
* Fall back to the legacy `prepareAction().execute()` contract so providers
|
|
8809
|
+
* remain runtime-compatible with adapter versions released before `readAction`.
|
|
8810
|
+
* Consumers must upgrade their adapter package for reads to bypass custom
|
|
8811
|
+
* `prepareAction` wrappers.
|
|
8812
|
+
*
|
|
8813
|
+
* @typeParam TAdapterCapabilities - The adapter capabilities type.
|
|
8814
|
+
* @typeParam TActionKey - The read action key.
|
|
8815
|
+
* @param adapter - The adapter that owns the read action.
|
|
8816
|
+
* @param action - The read action to execute.
|
|
8817
|
+
* @param params - The parameters for the read action.
|
|
8818
|
+
* @param ctx - The operation context.
|
|
8819
|
+
* @returns The raw read-action result.
|
|
8820
|
+
* @throws {KitError} When `action` is not a supported read-action key.
|
|
8821
|
+
*
|
|
8822
|
+
* @example
|
|
8823
|
+
* ```typescript
|
|
8824
|
+
* import { executeAdapterReadAction } from '@core/adapter'
|
|
8825
|
+
* import { Ethereum } from '@core/chains'
|
|
8826
|
+
*
|
|
8827
|
+
* const allowance = await executeAdapterReadAction(
|
|
8828
|
+
* adapter,
|
|
8829
|
+
* 'token.allowance',
|
|
8830
|
+
* { tokenAddress, delegate },
|
|
8831
|
+
* { chain: Ethereum },
|
|
8832
|
+
* )
|
|
8833
|
+
* ```
|
|
8834
|
+
*
|
|
8835
|
+
* @internal
|
|
8836
|
+
*/ async function executeAdapterReadAction(adapter, action, params, ctx) {
|
|
8837
|
+
if (!isReadActionKey(action)) {
|
|
8838
|
+
throw createUnsupportedReadActionError(action);
|
|
8839
|
+
}
|
|
8840
|
+
const runtimeAdapter = adapter;
|
|
8841
|
+
if (typeof runtimeAdapter.readAction === 'function') {
|
|
8842
|
+
return runtimeAdapter.readAction(action, params, ctx);
|
|
8843
|
+
}
|
|
8844
|
+
let request;
|
|
8845
|
+
try {
|
|
8846
|
+
request = await adapter.prepareAction(action, params, ctx);
|
|
8847
|
+
} catch (error) {
|
|
8848
|
+
if (error instanceof Error && error.message === `Action ${action} is not supported`) {
|
|
8849
|
+
throw createUnsupportedReadActionError(action);
|
|
8850
|
+
}
|
|
8851
|
+
throw error;
|
|
8852
|
+
}
|
|
8853
|
+
return request.execute();
|
|
8854
|
+
}
|
|
8855
|
+
/**
|
|
8856
|
+
* Read and parse a token allowance while supporting older adapter versions.
|
|
8857
|
+
*
|
|
8858
|
+
* @remarks
|
|
8859
|
+
* Prefer the adapter's dedicated read seam and fall back to the legacy
|
|
8860
|
+
* `prepareAction().execute()` contract when the runtime adapter predates it.
|
|
8861
|
+
*
|
|
8862
|
+
* @typeParam TAdapterCapabilities - The adapter capabilities type.
|
|
8863
|
+
* @param adapter - The adapter that owns the allowance action.
|
|
8864
|
+
* @param params - The token and delegate whose allowance is being read.
|
|
8865
|
+
* @param ctx - The operation context.
|
|
8866
|
+
* @returns The non-negative allowance in token base units.
|
|
8867
|
+
* @throws {KitError} When the action is unsupported or its response is malformed.
|
|
8868
|
+
*
|
|
8869
|
+
* @example
|
|
8870
|
+
* ```typescript
|
|
8871
|
+
* import { readTokenAllowance } from '@core/adapter'
|
|
8872
|
+
* import { Ethereum } from '@core/chains'
|
|
8873
|
+
*
|
|
8874
|
+
* const allowance = await readTokenAllowance(
|
|
8875
|
+
* adapter,
|
|
8876
|
+
* { tokenAddress, delegate },
|
|
8877
|
+
* { chain: Ethereum },
|
|
8878
|
+
* )
|
|
8879
|
+
* ```
|
|
8880
|
+
*
|
|
8881
|
+
* @internal
|
|
8882
|
+
*/ async function readTokenAllowance(adapter, params, ctx) {
|
|
8883
|
+
return parseAllowanceResponse(await executeAdapterReadAction(adapter, 'token.allowance', params, ctx));
|
|
8884
|
+
}
|
|
8885
|
+
/**
|
|
8886
|
+
* Parse a raw token allowance response into base units.
|
|
8887
|
+
*
|
|
8888
|
+
* @param allowanceRaw - The adapter response, optionally wrapped as an Amount output.
|
|
8889
|
+
* @returns The non-negative allowance as a bigint, or zero for a missing value.
|
|
8890
|
+
* @throws {KitError} When the response cannot represent a non-negative bigint.
|
|
8891
|
+
*
|
|
8892
|
+
* @example
|
|
8893
|
+
* ```typescript
|
|
8894
|
+
* import { parseAllowanceResponse } from '@core/adapter'
|
|
8895
|
+
*
|
|
8896
|
+
* const allowance = parseAllowanceResponse({ amount: { raw: 1000000n } })
|
|
8897
|
+
* ```
|
|
8898
|
+
*/ function parseAllowanceResponse(allowanceRaw) {
|
|
8899
|
+
let value = allowanceRaw;
|
|
8900
|
+
if (typeof value === 'object' && value !== null && 'amount' in value) {
|
|
8901
|
+
const amount = value.amount;
|
|
8902
|
+
if (typeof amount === 'object' && amount !== null && 'raw' in amount) {
|
|
8903
|
+
value = amount.raw;
|
|
8904
|
+
}
|
|
8905
|
+
}
|
|
8906
|
+
if (value === undefined || value === null) {
|
|
8907
|
+
return 0n;
|
|
8908
|
+
}
|
|
8909
|
+
let allowance;
|
|
8910
|
+
if (typeof value === 'bigint') {
|
|
8911
|
+
allowance = value;
|
|
8912
|
+
} else if (typeof value === 'string') {
|
|
8913
|
+
try {
|
|
8914
|
+
allowance = BigInt(value);
|
|
8915
|
+
} catch {
|
|
8916
|
+
allowance = undefined;
|
|
8917
|
+
}
|
|
8918
|
+
}
|
|
8919
|
+
if (allowance === undefined || allowance < 0n) {
|
|
8920
|
+
throw createValidationFailedError('token.allowance', value, 'token.allowance response must be a non-negative bigint-compatible string or bigint');
|
|
8921
|
+
}
|
|
8922
|
+
return allowance;
|
|
8923
|
+
}
|
|
8924
|
+
|
|
8561
8925
|
/**
|
|
8562
8926
|
* Schema for validating hexadecimal strings with '0x' prefix.
|
|
8563
8927
|
*
|
|
@@ -9381,7 +9745,7 @@ var TransferSpeed;
|
|
|
9381
9745
|
registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
9382
9746
|
|
|
9383
9747
|
var name$2 = "@circle-fin/swap-kit";
|
|
9384
|
-
var version$2 = "1.5.
|
|
9748
|
+
var version$2 = "1.5.2";
|
|
9385
9749
|
var pkg$2 = {
|
|
9386
9750
|
name: name$2,
|
|
9387
9751
|
version: version$2};
|
|
@@ -10178,25 +10542,330 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
10178
10542
|
};
|
|
10179
10543
|
|
|
10180
10544
|
/**
|
|
10181
|
-
*
|
|
10182
|
-
*
|
|
10183
|
-
* This schema ensures that EVM adapter capabilities meet the requirements for the
|
|
10184
|
-
* OperationContext pattern by validating the addressContext property and ensuring
|
|
10185
|
-
* the supportedChains property contains only EVM-compatible chains.
|
|
10186
|
-
*
|
|
10187
|
-
* @remarks
|
|
10188
|
-
* This schema enforces that all supported chains are EVM type, making it specific
|
|
10189
|
-
* to EVM adapters (Ethers, Viem, etc.). For non-EVM adapters, a different validation
|
|
10190
|
-
* schema should be used.
|
|
10545
|
+
* IAdapter contract ABI.
|
|
10191
10546
|
*
|
|
10192
|
-
*
|
|
10193
|
-
*
|
|
10194
|
-
*
|
|
10547
|
+
* Shared ABI for the on-chain Adapter contract used by multiple kits
|
|
10548
|
+
* (swap, earn) for executing signed instruction sets. The `execute()`
|
|
10549
|
+
* function accepts EIP-712 signed execution parameters, token inputs,
|
|
10550
|
+
* and a signature, then executes the corresponding on-chain
|
|
10551
|
+
* instructions.
|
|
10552
|
+
*/ const adapterContractAbi = [
|
|
10553
|
+
{
|
|
10554
|
+
type: 'function',
|
|
10555
|
+
name: 'execute',
|
|
10556
|
+
inputs: [
|
|
10557
|
+
{
|
|
10558
|
+
name: 'params',
|
|
10559
|
+
type: 'tuple',
|
|
10560
|
+
internalType: 'struct IAdapter.ExecutionParams',
|
|
10561
|
+
components: [
|
|
10562
|
+
{
|
|
10563
|
+
name: 'instructions',
|
|
10564
|
+
type: 'tuple[]',
|
|
10565
|
+
internalType: 'struct IAdapter.Instruction[]',
|
|
10566
|
+
components: [
|
|
10567
|
+
{
|
|
10568
|
+
name: 'target',
|
|
10569
|
+
type: 'address',
|
|
10570
|
+
internalType: 'address'
|
|
10571
|
+
},
|
|
10572
|
+
{
|
|
10573
|
+
name: 'data',
|
|
10574
|
+
type: 'bytes',
|
|
10575
|
+
internalType: 'bytes'
|
|
10576
|
+
},
|
|
10577
|
+
{
|
|
10578
|
+
name: 'value',
|
|
10579
|
+
type: 'uint256',
|
|
10580
|
+
internalType: 'uint256'
|
|
10581
|
+
},
|
|
10582
|
+
{
|
|
10583
|
+
name: 'tokenIn',
|
|
10584
|
+
type: 'address',
|
|
10585
|
+
internalType: 'address'
|
|
10586
|
+
},
|
|
10587
|
+
{
|
|
10588
|
+
name: 'amountToApprove',
|
|
10589
|
+
type: 'uint256',
|
|
10590
|
+
internalType: 'uint256'
|
|
10591
|
+
},
|
|
10592
|
+
{
|
|
10593
|
+
name: 'tokenOut',
|
|
10594
|
+
type: 'address',
|
|
10595
|
+
internalType: 'address'
|
|
10596
|
+
},
|
|
10597
|
+
{
|
|
10598
|
+
name: 'minTokenOut',
|
|
10599
|
+
type: 'uint256',
|
|
10600
|
+
internalType: 'uint256'
|
|
10601
|
+
}
|
|
10602
|
+
]
|
|
10603
|
+
},
|
|
10604
|
+
{
|
|
10605
|
+
name: 'tokens',
|
|
10606
|
+
type: 'tuple[]',
|
|
10607
|
+
internalType: 'struct IAdapter.TokenRecipient[]',
|
|
10608
|
+
components: [
|
|
10609
|
+
{
|
|
10610
|
+
name: 'token',
|
|
10611
|
+
type: 'address',
|
|
10612
|
+
internalType: 'address'
|
|
10613
|
+
},
|
|
10614
|
+
{
|
|
10615
|
+
name: 'beneficiary',
|
|
10616
|
+
type: 'address',
|
|
10617
|
+
internalType: 'address'
|
|
10618
|
+
}
|
|
10619
|
+
]
|
|
10620
|
+
},
|
|
10621
|
+
{
|
|
10622
|
+
name: 'execId',
|
|
10623
|
+
type: 'uint256',
|
|
10624
|
+
internalType: 'uint256'
|
|
10625
|
+
},
|
|
10626
|
+
{
|
|
10627
|
+
name: 'deadline',
|
|
10628
|
+
type: 'uint256',
|
|
10629
|
+
internalType: 'uint256'
|
|
10630
|
+
},
|
|
10631
|
+
{
|
|
10632
|
+
name: 'metadata',
|
|
10633
|
+
type: 'bytes',
|
|
10634
|
+
internalType: 'bytes'
|
|
10635
|
+
}
|
|
10636
|
+
]
|
|
10637
|
+
},
|
|
10638
|
+
{
|
|
10639
|
+
name: 'tokenInputs',
|
|
10640
|
+
type: 'tuple[]',
|
|
10641
|
+
internalType: 'struct IAdapter.TokenInput[]',
|
|
10642
|
+
components: [
|
|
10643
|
+
{
|
|
10644
|
+
name: 'permitType',
|
|
10645
|
+
type: 'uint8',
|
|
10646
|
+
internalType: 'enum IAdapter.PermitType'
|
|
10647
|
+
},
|
|
10648
|
+
{
|
|
10649
|
+
name: 'token',
|
|
10650
|
+
type: 'address',
|
|
10651
|
+
internalType: 'address'
|
|
10652
|
+
},
|
|
10653
|
+
{
|
|
10654
|
+
name: 'amount',
|
|
10655
|
+
type: 'uint256',
|
|
10656
|
+
internalType: 'uint256'
|
|
10657
|
+
},
|
|
10658
|
+
{
|
|
10659
|
+
name: 'permitCalldata',
|
|
10660
|
+
type: 'bytes',
|
|
10661
|
+
internalType: 'bytes'
|
|
10662
|
+
}
|
|
10663
|
+
]
|
|
10664
|
+
},
|
|
10665
|
+
{
|
|
10666
|
+
name: 'signature',
|
|
10667
|
+
type: 'bytes',
|
|
10668
|
+
internalType: 'bytes'
|
|
10669
|
+
}
|
|
10670
|
+
],
|
|
10671
|
+
outputs: [],
|
|
10672
|
+
stateMutability: 'payable'
|
|
10673
|
+
}
|
|
10674
|
+
];
|
|
10675
|
+
|
|
10676
|
+
/**
|
|
10677
|
+
* Minimal ERC-4626 tokenized-vault ABI.
|
|
10678
|
+
*
|
|
10679
|
+
* Covers only the mutating methods EarnKit bundles as inner instructions inside
|
|
10680
|
+
* an Adapter `execute()` call: `deposit`, `withdraw`, and `redeem`. It exists so
|
|
10681
|
+
* clients can decode the inner instruction calldata into a human-readable
|
|
10682
|
+
* summary of what a signer is authorizing (asset amount, receiver, owner)
|
|
10683
|
+
* rather than showing opaque bytes. The 4-byte selectors match the calldata the
|
|
10684
|
+
* earn service signs (`deposit(uint256,address)` = `0x6e553f65`,
|
|
10685
|
+
* `withdraw(uint256,address,address)` = `0xb460af94`,
|
|
10686
|
+
* `redeem(uint256,address,address)` = `0xba087652`).
|
|
10687
|
+
*/ const erc4626VaultAbi = [
|
|
10688
|
+
{
|
|
10689
|
+
type: 'function',
|
|
10690
|
+
name: 'deposit',
|
|
10691
|
+
stateMutability: 'nonpayable',
|
|
10692
|
+
inputs: [
|
|
10693
|
+
{
|
|
10694
|
+
name: 'assets',
|
|
10695
|
+
type: 'uint256',
|
|
10696
|
+
internalType: 'uint256'
|
|
10697
|
+
},
|
|
10698
|
+
{
|
|
10699
|
+
name: 'receiver',
|
|
10700
|
+
type: 'address',
|
|
10701
|
+
internalType: 'address'
|
|
10702
|
+
}
|
|
10703
|
+
],
|
|
10704
|
+
outputs: [
|
|
10705
|
+
{
|
|
10706
|
+
name: 'shares',
|
|
10707
|
+
type: 'uint256',
|
|
10708
|
+
internalType: 'uint256'
|
|
10709
|
+
}
|
|
10710
|
+
]
|
|
10711
|
+
},
|
|
10712
|
+
{
|
|
10713
|
+
type: 'function',
|
|
10714
|
+
name: 'withdraw',
|
|
10715
|
+
stateMutability: 'nonpayable',
|
|
10716
|
+
inputs: [
|
|
10717
|
+
{
|
|
10718
|
+
name: 'assets',
|
|
10719
|
+
type: 'uint256',
|
|
10720
|
+
internalType: 'uint256'
|
|
10721
|
+
},
|
|
10722
|
+
{
|
|
10723
|
+
name: 'receiver',
|
|
10724
|
+
type: 'address',
|
|
10725
|
+
internalType: 'address'
|
|
10726
|
+
},
|
|
10727
|
+
{
|
|
10728
|
+
name: 'owner',
|
|
10729
|
+
type: 'address',
|
|
10730
|
+
internalType: 'address'
|
|
10731
|
+
}
|
|
10732
|
+
],
|
|
10733
|
+
outputs: [
|
|
10734
|
+
{
|
|
10735
|
+
name: 'shares',
|
|
10736
|
+
type: 'uint256',
|
|
10737
|
+
internalType: 'uint256'
|
|
10738
|
+
}
|
|
10739
|
+
]
|
|
10740
|
+
},
|
|
10741
|
+
{
|
|
10742
|
+
type: 'function',
|
|
10743
|
+
name: 'redeem',
|
|
10744
|
+
stateMutability: 'nonpayable',
|
|
10745
|
+
inputs: [
|
|
10746
|
+
{
|
|
10747
|
+
name: 'shares',
|
|
10748
|
+
type: 'uint256',
|
|
10749
|
+
internalType: 'uint256'
|
|
10750
|
+
},
|
|
10751
|
+
{
|
|
10752
|
+
name: 'receiver',
|
|
10753
|
+
type: 'address',
|
|
10754
|
+
internalType: 'address'
|
|
10755
|
+
},
|
|
10756
|
+
{
|
|
10757
|
+
name: 'owner',
|
|
10758
|
+
type: 'address',
|
|
10759
|
+
internalType: 'address'
|
|
10760
|
+
}
|
|
10761
|
+
],
|
|
10762
|
+
outputs: [
|
|
10763
|
+
{
|
|
10764
|
+
name: 'assets',
|
|
10765
|
+
type: 'uint256',
|
|
10766
|
+
internalType: 'uint256'
|
|
10767
|
+
}
|
|
10768
|
+
]
|
|
10769
|
+
}
|
|
10770
|
+
];
|
|
10771
|
+
|
|
10772
|
+
/**
|
|
10773
|
+
* Minimal FeeTaker ABI.
|
|
10195
10774
|
*
|
|
10196
|
-
*
|
|
10197
|
-
*
|
|
10198
|
-
*
|
|
10199
|
-
*
|
|
10775
|
+
* The earn service appends a `takeFeeERC20` instruction to withdraw bundles
|
|
10776
|
+
* when Circle charges a withdrawal fee. This ABI decodes that inner instruction
|
|
10777
|
+
* so the fee (token, beneficiary, amount) is visible in the signing summary
|
|
10778
|
+
* instead of appearing as opaque calldata alongside the redeem/withdraw call.
|
|
10779
|
+
*/ const feeTakerAbi = [
|
|
10780
|
+
{
|
|
10781
|
+
type: 'function',
|
|
10782
|
+
name: 'takeFeeERC20',
|
|
10783
|
+
stateMutability: 'nonpayable',
|
|
10784
|
+
inputs: [
|
|
10785
|
+
{
|
|
10786
|
+
name: 'token',
|
|
10787
|
+
type: 'address',
|
|
10788
|
+
internalType: 'address'
|
|
10789
|
+
},
|
|
10790
|
+
{
|
|
10791
|
+
name: 'beneficiary',
|
|
10792
|
+
type: 'address',
|
|
10793
|
+
internalType: 'address'
|
|
10794
|
+
},
|
|
10795
|
+
{
|
|
10796
|
+
name: 'fee',
|
|
10797
|
+
type: 'uint256',
|
|
10798
|
+
internalType: 'uint256'
|
|
10799
|
+
},
|
|
10800
|
+
{
|
|
10801
|
+
name: 'kitType',
|
|
10802
|
+
type: 'bytes8',
|
|
10803
|
+
internalType: 'bytes8'
|
|
10804
|
+
}
|
|
10805
|
+
],
|
|
10806
|
+
outputs: []
|
|
10807
|
+
}
|
|
10808
|
+
];
|
|
10809
|
+
|
|
10810
|
+
/**
|
|
10811
|
+
* Minimal Merkl Distributor ABI.
|
|
10812
|
+
*
|
|
10813
|
+
* EarnKit claim-rewards bundles a single `claim` instruction targeting the
|
|
10814
|
+
* Merkl Distributor, batching one entry per reward token. This ABI decodes that
|
|
10815
|
+
* inner instruction so the claimed tokens and amounts are visible in the signing
|
|
10816
|
+
* summary. `claim` uses dynamic array arguments, which is why a real ABI decoder
|
|
10817
|
+
* (rather than fixed-word slicing) is required for the reward instruction.
|
|
10818
|
+
*/ const merklDistributorAbi = [
|
|
10819
|
+
{
|
|
10820
|
+
type: 'function',
|
|
10821
|
+
name: 'claim',
|
|
10822
|
+
stateMutability: 'nonpayable',
|
|
10823
|
+
inputs: [
|
|
10824
|
+
{
|
|
10825
|
+
name: 'users',
|
|
10826
|
+
type: 'address[]',
|
|
10827
|
+
internalType: 'address[]'
|
|
10828
|
+
},
|
|
10829
|
+
{
|
|
10830
|
+
name: 'tokens',
|
|
10831
|
+
type: 'address[]',
|
|
10832
|
+
internalType: 'address[]'
|
|
10833
|
+
},
|
|
10834
|
+
{
|
|
10835
|
+
name: 'amounts',
|
|
10836
|
+
type: 'uint256[]',
|
|
10837
|
+
internalType: 'uint256[]'
|
|
10838
|
+
},
|
|
10839
|
+
{
|
|
10840
|
+
name: 'proofs',
|
|
10841
|
+
type: 'bytes32[][]',
|
|
10842
|
+
internalType: 'bytes32[][]'
|
|
10843
|
+
}
|
|
10844
|
+
],
|
|
10845
|
+
outputs: []
|
|
10846
|
+
}
|
|
10847
|
+
];
|
|
10848
|
+
|
|
10849
|
+
/**
|
|
10850
|
+
* Zod schema for validating EVM adapter capabilities.
|
|
10851
|
+
*
|
|
10852
|
+
* This schema ensures that EVM adapter capabilities meet the requirements for the
|
|
10853
|
+
* OperationContext pattern by validating the addressContext property and ensuring
|
|
10854
|
+
* the supportedChains property contains only EVM-compatible chains.
|
|
10855
|
+
*
|
|
10856
|
+
* @remarks
|
|
10857
|
+
* This schema enforces that all supported chains are EVM type, making it specific
|
|
10858
|
+
* to EVM adapters (Ethers, Viem, etc.). For non-EVM adapters, a different validation
|
|
10859
|
+
* schema should be used.
|
|
10860
|
+
*
|
|
10861
|
+
* When used with the validate() function from @core/utils, this throws KitError
|
|
10862
|
+
* with INPUT_VALIDATION_FAILED code if validation fails, with details about which
|
|
10863
|
+
* properties failed.
|
|
10864
|
+
*
|
|
10865
|
+
* @example
|
|
10866
|
+
* ```typescript
|
|
10867
|
+
* import { AdapterCapabilitiesSchema } from '@core/adapter-evm/validation'
|
|
10868
|
+
* import { Ethereum, Base } from '@core/chains'
|
|
10200
10869
|
*
|
|
10201
10870
|
* // Valid user-controlled capabilities
|
|
10202
10871
|
* const userCapabilities = {
|
|
@@ -12690,7 +13359,7 @@ new Set(Object.values(Blockchain));
|
|
|
12690
13359
|
registerKit(`${pkg$2.name}/${pkg$2.version}`);
|
|
12691
13360
|
|
|
12692
13361
|
var name$1 = "@circle-fin/earn-kit";
|
|
12693
|
-
var version$1 = "1.
|
|
13362
|
+
var version$1 = "1.5.1";
|
|
12694
13363
|
var pkg$1 = {
|
|
12695
13364
|
name: name$1,
|
|
12696
13365
|
version: version$1};
|
|
@@ -12937,30 +13606,6 @@ function toSdkChain(chain) {
|
|
|
12937
13606
|
return assertHexAddress('chain.kitContracts.adapter', adapterContractAddress, `Adapter contract for chain ${chain.name} must be a 0x-prefixed 20-byte hex address.`);
|
|
12938
13607
|
}
|
|
12939
13608
|
|
|
12940
|
-
/**
|
|
12941
|
-
* Parse the raw `token.allowance` adapter response into a bigint.
|
|
12942
|
-
*
|
|
12943
|
-
* @internal
|
|
12944
|
-
*/ function parseAllowanceResponse(allowanceRaw) {
|
|
12945
|
-
if (allowanceRaw === undefined || allowanceRaw === null) {
|
|
12946
|
-
return 0n;
|
|
12947
|
-
}
|
|
12948
|
-
let allowance;
|
|
12949
|
-
if (typeof allowanceRaw === 'bigint') {
|
|
12950
|
-
allowance = allowanceRaw;
|
|
12951
|
-
} else if (typeof allowanceRaw === 'string') {
|
|
12952
|
-
try {
|
|
12953
|
-
allowance = BigInt(allowanceRaw);
|
|
12954
|
-
} catch {
|
|
12955
|
-
allowance = undefined;
|
|
12956
|
-
}
|
|
12957
|
-
}
|
|
12958
|
-
if (allowance === undefined || allowance < 0n) {
|
|
12959
|
-
throw createValidationFailedError('token.allowance', allowanceRaw, 'token.allowance response must be a non-negative bigint-compatible string or bigint');
|
|
12960
|
-
}
|
|
12961
|
-
return allowance;
|
|
12962
|
-
}
|
|
12963
|
-
|
|
12964
13609
|
/**
|
|
12965
13610
|
* Safety multiplier applied to locally estimated gas for earn transactions.
|
|
12966
13611
|
*
|
|
@@ -13182,17 +13827,15 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
13182
13827
|
if (requiredAllowance <= 0n) {
|
|
13183
13828
|
return undefined;
|
|
13184
13829
|
}
|
|
13185
|
-
|
|
13186
|
-
|
|
13187
|
-
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
// propagation polls.
|
|
13195
|
-
const readAllowance = async ()=>parseAllowanceResponse(await allowancePrepared.execute());
|
|
13830
|
+
// Each call is a fresh allowance read at `latest`, so the same function serves
|
|
13831
|
+
// both the pre-approval decision and post-approval propagation polls.
|
|
13832
|
+
const readAllowance = async ()=>readTokenAllowance(adapter, {
|
|
13833
|
+
tokenAddress,
|
|
13834
|
+
delegate
|
|
13835
|
+
}, {
|
|
13836
|
+
chain,
|
|
13837
|
+
address
|
|
13838
|
+
});
|
|
13196
13839
|
const currentAllowance = await readAllowance();
|
|
13197
13840
|
if (currentAllowance >= requiredAllowance) {
|
|
13198
13841
|
return undefined;
|
|
@@ -13333,6 +13976,645 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
13333
13976
|
return approvedToken;
|
|
13334
13977
|
}
|
|
13335
13978
|
|
|
13979
|
+
/**
|
|
13980
|
+
* Combined ABI of every inner instruction EarnKit can bundle inside an Adapter
|
|
13981
|
+
* `execute()` call. `decodeFunctionData` matches an instruction's calldata to
|
|
13982
|
+
* one of these functions by its 4-byte selector.
|
|
13983
|
+
*/ const earnInstructionAbi = [
|
|
13984
|
+
...erc4626VaultAbi,
|
|
13985
|
+
...feeTakerAbi,
|
|
13986
|
+
...merklDistributorAbi
|
|
13987
|
+
];
|
|
13988
|
+
/**
|
|
13989
|
+
* Extract and shallow-validate the `instructions` array from loosely-typed
|
|
13990
|
+
* signed execution params.
|
|
13991
|
+
*
|
|
13992
|
+
* The earn service schema validates `tokenIn`/`amountToApprove` and passes the
|
|
13993
|
+
* remaining instruction fields through untyped, so the params arrive as a plain
|
|
13994
|
+
* record; each accessed field is narrowed at runtime.
|
|
13995
|
+
*/ function requireInstructions(executionParams) {
|
|
13996
|
+
const instructions = executionParams['instructions'];
|
|
13997
|
+
if (!Array.isArray(instructions)) {
|
|
13998
|
+
throw decodeMismatchError('execution params are missing an instructions array', {
|
|
13999
|
+
instructions
|
|
14000
|
+
});
|
|
14001
|
+
}
|
|
14002
|
+
return instructions.map((instruction, index)=>{
|
|
14003
|
+
if (typeof instruction !== 'object' || instruction === null) {
|
|
14004
|
+
throw decodeMismatchError(`instructions[${index.toString()}] is not an object`, {
|
|
14005
|
+
index
|
|
14006
|
+
});
|
|
14007
|
+
}
|
|
14008
|
+
return instruction;
|
|
14009
|
+
});
|
|
14010
|
+
}
|
|
14011
|
+
/**
|
|
14012
|
+
* Build a fail-closed {@link KitError} for an earn decode or review failure.
|
|
14013
|
+
*
|
|
14014
|
+
* Marked non-recoverable: a mismatch between what would be shown and what would
|
|
14015
|
+
* be signed is never safe to retry, so the operation fails fast rather than
|
|
14016
|
+
* presenting misleading decoded data. `messagePrefix` names the failing stage
|
|
14017
|
+
* (calldata decode vs. review construction); callers bind it once and pass the
|
|
14018
|
+
* specific failure as `message`.
|
|
14019
|
+
*/ function failClosedEarnError(messagePrefix, message, trace) {
|
|
14020
|
+
return new KitError({
|
|
14021
|
+
...EarnError.INTERNAL_ERROR,
|
|
14022
|
+
recoverability: 'FATAL',
|
|
14023
|
+
message: `${messagePrefix}: ${message}`,
|
|
14024
|
+
cause: {
|
|
14025
|
+
trace
|
|
14026
|
+
}
|
|
14027
|
+
});
|
|
14028
|
+
}
|
|
14029
|
+
/**
|
|
14030
|
+
* Build a {@link KitError} for a decode or consistency failure.
|
|
14031
|
+
*
|
|
14032
|
+
* Thin wrapper over {@link failClosedEarnError} bound to the decode-stage
|
|
14033
|
+
* message prefix.
|
|
14034
|
+
*/ function decodeMismatchError(message, trace) {
|
|
14035
|
+
return failClosedEarnError('Unable to decode earn transaction', message, trace);
|
|
14036
|
+
}
|
|
14037
|
+
/**
|
|
14038
|
+
* Narrow an untyped value to a 0x-prefixed hex string, or fail fast.
|
|
14039
|
+
*/ function requireHex(value, path) {
|
|
14040
|
+
if (typeof value === 'string' && /^0x[0-9a-fA-F]*$/.test(value)) {
|
|
14041
|
+
return value;
|
|
14042
|
+
}
|
|
14043
|
+
throw decodeMismatchError(`${path} is not a hex string`, {
|
|
14044
|
+
path,
|
|
14045
|
+
value
|
|
14046
|
+
});
|
|
14047
|
+
}
|
|
14048
|
+
/**
|
|
14049
|
+
* Narrow an untyped value to a 20-byte EVM address, or fail fast.
|
|
14050
|
+
*/ function requireAddress(value, path) {
|
|
14051
|
+
if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value)) {
|
|
14052
|
+
return value;
|
|
14053
|
+
}
|
|
14054
|
+
throw decodeMismatchError(`${path} is not an address`, {
|
|
14055
|
+
path,
|
|
14056
|
+
value
|
|
14057
|
+
});
|
|
14058
|
+
}
|
|
14059
|
+
/**
|
|
14060
|
+
* Narrow an untyped `uint256`-like value (decimal string, bigint, or integer)
|
|
14061
|
+
* to a bigint, or fail fast.
|
|
14062
|
+
*/ function requireUint(value, path) {
|
|
14063
|
+
if (typeof value === 'bigint') {
|
|
14064
|
+
return value;
|
|
14065
|
+
}
|
|
14066
|
+
if (typeof value === 'string' && /^\d+$/.test(value)) {
|
|
14067
|
+
return BigInt(value);
|
|
14068
|
+
}
|
|
14069
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= 0) {
|
|
14070
|
+
return BigInt(value);
|
|
14071
|
+
}
|
|
14072
|
+
throw decodeMismatchError(`${path} is not a uint256 value`, {
|
|
14073
|
+
path,
|
|
14074
|
+
value
|
|
14075
|
+
});
|
|
14076
|
+
}
|
|
14077
|
+
/**
|
|
14078
|
+
* Decode inner instruction calldata against the earn instruction ABI, mapping a
|
|
14079
|
+
* viem decode failure (unknown selector, malformed args) to a fail-fast error.
|
|
14080
|
+
*/ function decodeEarnInstructionData(data, index) {
|
|
14081
|
+
try {
|
|
14082
|
+
return viem.decodeFunctionData({
|
|
14083
|
+
abi: earnInstructionAbi,
|
|
14084
|
+
data
|
|
14085
|
+
});
|
|
14086
|
+
} catch (error) {
|
|
14087
|
+
throw decodeMismatchError(`instructions[${index.toString()}] calldata is not a recognized earn instruction`, {
|
|
14088
|
+
index,
|
|
14089
|
+
selector: data.slice(0, 10),
|
|
14090
|
+
error: String(error)
|
|
14091
|
+
});
|
|
14092
|
+
}
|
|
14093
|
+
}
|
|
14094
|
+
/**
|
|
14095
|
+
* Decode Adapter `execute()` calldata, mapping a viem decode failure to a
|
|
14096
|
+
* fail-fast error.
|
|
14097
|
+
*/ function decodeExecuteCalldata(calldata) {
|
|
14098
|
+
try {
|
|
14099
|
+
return viem.decodeFunctionData({
|
|
14100
|
+
abi: adapterContractAbi,
|
|
14101
|
+
data: calldata
|
|
14102
|
+
});
|
|
14103
|
+
} catch (error) {
|
|
14104
|
+
throw decodeMismatchError('encoded calldata is not a valid Adapter execute() call', {
|
|
14105
|
+
error: String(error)
|
|
14106
|
+
});
|
|
14107
|
+
}
|
|
14108
|
+
}
|
|
14109
|
+
/**
|
|
14110
|
+
* Decode one inner instruction's calldata into a typed {@link
|
|
14111
|
+
* DecodedEarnInstruction}.
|
|
14112
|
+
*/ function decodeInstruction(instruction, index) {
|
|
14113
|
+
const target = requireAddress(instruction['target'], `instructions[${index.toString()}].target`);
|
|
14114
|
+
const data = requireHex(instruction['data'], `instructions[${index.toString()}].data`);
|
|
14115
|
+
const decoded = decodeEarnInstructionData(data, index);
|
|
14116
|
+
switch(decoded.functionName){
|
|
14117
|
+
case 'deposit':
|
|
14118
|
+
{
|
|
14119
|
+
const [assets, receiver] = decoded.args;
|
|
14120
|
+
return {
|
|
14121
|
+
method: 'deposit',
|
|
14122
|
+
vault: target,
|
|
14123
|
+
assets: assets.toString(),
|
|
14124
|
+
receiver
|
|
14125
|
+
};
|
|
14126
|
+
}
|
|
14127
|
+
case 'withdraw':
|
|
14128
|
+
{
|
|
14129
|
+
const [assets, receiver, owner] = decoded.args;
|
|
14130
|
+
return {
|
|
14131
|
+
method: 'withdraw',
|
|
14132
|
+
vault: target,
|
|
14133
|
+
assets: assets.toString(),
|
|
14134
|
+
receiver,
|
|
14135
|
+
owner
|
|
14136
|
+
};
|
|
14137
|
+
}
|
|
14138
|
+
case 'redeem':
|
|
14139
|
+
{
|
|
14140
|
+
const [shares, receiver, owner] = decoded.args;
|
|
14141
|
+
return {
|
|
14142
|
+
method: 'redeem',
|
|
14143
|
+
vault: target,
|
|
14144
|
+
shares: shares.toString(),
|
|
14145
|
+
receiver,
|
|
14146
|
+
owner
|
|
14147
|
+
};
|
|
14148
|
+
}
|
|
14149
|
+
case 'takeFeeERC20':
|
|
14150
|
+
{
|
|
14151
|
+
const [token, beneficiary, fee, kitType] = decoded.args;
|
|
14152
|
+
return {
|
|
14153
|
+
method: 'takeFeeERC20',
|
|
14154
|
+
feeTaker: target,
|
|
14155
|
+
token,
|
|
14156
|
+
beneficiary,
|
|
14157
|
+
fee: fee.toString(),
|
|
14158
|
+
kitType
|
|
14159
|
+
};
|
|
14160
|
+
}
|
|
14161
|
+
case 'claim':
|
|
14162
|
+
{
|
|
14163
|
+
const users = decoded.args[0];
|
|
14164
|
+
const tokens = decoded.args[1];
|
|
14165
|
+
const amounts = decoded.args[2];
|
|
14166
|
+
// Merkl claim(users, tokens, amounts, proofs) carries parallel arrays,
|
|
14167
|
+
// one entry per reward. Reject any length skew rather than padding with
|
|
14168
|
+
// zero amounts or dropping trailing entries, so the preview can never
|
|
14169
|
+
// misstate what is claimed or for whom.
|
|
14170
|
+
//
|
|
14171
|
+
// Note: Merkl `amounts` are the *cumulative lifetime* total claimable per
|
|
14172
|
+
// (user, token); the Distributor transfers only `amount - alreadyClaimed`.
|
|
14173
|
+
// This decode faithfully surfaces the signed cumulative value, which is
|
|
14174
|
+
// what `DecodedRewardClaim.amount` documents. See that type's doc.
|
|
14175
|
+
if (new Set([
|
|
14176
|
+
users.length,
|
|
14177
|
+
tokens.length,
|
|
14178
|
+
amounts.length
|
|
14179
|
+
]).size !== 1) {
|
|
14180
|
+
throw decodeMismatchError(`instructions[${index.toString()}] claim has mismatched recipient/token/amount lengths`, {
|
|
14181
|
+
index,
|
|
14182
|
+
users: users.length,
|
|
14183
|
+
tokens: tokens.length,
|
|
14184
|
+
amounts: amounts.length
|
|
14185
|
+
});
|
|
14186
|
+
}
|
|
14187
|
+
const rewards = tokens.map((token, rewardIndex)=>({
|
|
14188
|
+
recipient: requireAddress(users[rewardIndex], `instructions[${index.toString()}].claim.users[${rewardIndex.toString()}]`),
|
|
14189
|
+
address: token,
|
|
14190
|
+
amount: requireUint(amounts[rewardIndex], `instructions[${index.toString()}].claim.amounts[${rewardIndex.toString()}]`).toString()
|
|
14191
|
+
}));
|
|
14192
|
+
return {
|
|
14193
|
+
method: 'claim',
|
|
14194
|
+
distributor: target,
|
|
14195
|
+
rewards
|
|
14196
|
+
};
|
|
14197
|
+
}
|
|
14198
|
+
/* v8 ignore next 2 -- exhaustive switch; default is unreachable */ default:
|
|
14199
|
+
return assertNever$2(decoded);
|
|
14200
|
+
}
|
|
14201
|
+
}
|
|
14202
|
+
/**
|
|
14203
|
+
* Lift the primary values a wallet prompt cares about out of the decoded
|
|
14204
|
+
* instructions into a flat summary.
|
|
14205
|
+
*/ function buildSummary(instructions, envelope) {
|
|
14206
|
+
const summary = {};
|
|
14207
|
+
instructions.forEach((instruction, index)=>{
|
|
14208
|
+
switch(instruction.method){
|
|
14209
|
+
case 'deposit':
|
|
14210
|
+
case 'withdraw':
|
|
14211
|
+
case 'redeem':
|
|
14212
|
+
{
|
|
14213
|
+
// The summary lifts a single primary token movement to the top level.
|
|
14214
|
+
// An earn bundle carries exactly one deposit/withdraw/redeem today;
|
|
14215
|
+
// fail fast rather than silently overwriting an earlier one, which
|
|
14216
|
+
// would drop it from the wallet-facing preview.
|
|
14217
|
+
if (summary.token !== undefined) {
|
|
14218
|
+
throw decodeMismatchError('multiple deposit/withdraw/redeem instructions cannot be summarized into a single preview', {
|
|
14219
|
+
index
|
|
14220
|
+
});
|
|
14221
|
+
}
|
|
14222
|
+
// Pair the amount with the token it is actually denominated in so the
|
|
14223
|
+
// preview never folds two units into one entry:
|
|
14224
|
+
// - deposit: `assets` of the underlying asset pulled in (`tokenIn`)
|
|
14225
|
+
// - redeem: `shares` of the vault-share token burned (`tokenIn`)
|
|
14226
|
+
// - withdraw: `assets` of the underlying asset paid out (`tokenOut`).
|
|
14227
|
+
// `withdraw(assets)` counts the underlying received, not the shares
|
|
14228
|
+
// burned to produce it, so `tokenIn` (the share token) would misstate
|
|
14229
|
+
// the unit; the underlying is the instruction's `tokenOut`.
|
|
14230
|
+
const amount = instruction.method === 'redeem' ? instruction.shares : instruction.assets;
|
|
14231
|
+
const tokenField = instruction.method === 'withdraw' ? 'tokenOut' : 'tokenIn';
|
|
14232
|
+
summary.vault = instruction.vault;
|
|
14233
|
+
summary.receiver = instruction.receiver;
|
|
14234
|
+
summary.token = {
|
|
14235
|
+
address: requireAddress(envelope[index]?.[tokenField], `instructions[${index.toString()}].${tokenField}`),
|
|
14236
|
+
amount
|
|
14237
|
+
};
|
|
14238
|
+
break;
|
|
14239
|
+
}
|
|
14240
|
+
case 'takeFeeERC20':
|
|
14241
|
+
{
|
|
14242
|
+
// As with the vault case, a second fee would silently overwrite the
|
|
14243
|
+
// first and understate what is charged; fail fast instead.
|
|
14244
|
+
if (summary.fee !== undefined) {
|
|
14245
|
+
throw decodeMismatchError('multiple fee instructions cannot be summarized into a single preview', {
|
|
14246
|
+
index
|
|
14247
|
+
});
|
|
14248
|
+
}
|
|
14249
|
+
summary.fee = {
|
|
14250
|
+
address: instruction.token,
|
|
14251
|
+
amount: instruction.fee
|
|
14252
|
+
};
|
|
14253
|
+
break;
|
|
14254
|
+
}
|
|
14255
|
+
case 'claim':
|
|
14256
|
+
{
|
|
14257
|
+
// A second claim would silently drop the first from the preview
|
|
14258
|
+
// (rewards are already batched inside one Merkl claim); fail fast.
|
|
14259
|
+
if (summary.rewards !== undefined) {
|
|
14260
|
+
throw decodeMismatchError('multiple claim instructions cannot be summarized into a single preview', {
|
|
14261
|
+
index
|
|
14262
|
+
});
|
|
14263
|
+
}
|
|
14264
|
+
summary.rewards = instruction.rewards;
|
|
14265
|
+
break;
|
|
14266
|
+
}
|
|
14267
|
+
/* v8 ignore next 2 -- exhaustive switch; default is unreachable */ default:
|
|
14268
|
+
assertNever$2(instruction);
|
|
14269
|
+
}
|
|
14270
|
+
});
|
|
14271
|
+
return summary;
|
|
14272
|
+
}
|
|
14273
|
+
/**
|
|
14274
|
+
* Vault/claim instruction methods each declared action may decode to. The
|
|
14275
|
+
* mapping is many-to-one: a full withdrawal decodes to `redeem`, and any action
|
|
14276
|
+
* may carry an auxiliary `takeFeeERC20` alongside its primary instruction.
|
|
14277
|
+
*/ const ACTION_ALLOWED_METHODS = {
|
|
14278
|
+
deposit: new Set([
|
|
14279
|
+
'deposit'
|
|
14280
|
+
]),
|
|
14281
|
+
withdraw: new Set([
|
|
14282
|
+
'withdraw',
|
|
14283
|
+
'redeem'
|
|
14284
|
+
]),
|
|
14285
|
+
claimRewards: new Set([
|
|
14286
|
+
'claim'
|
|
14287
|
+
])
|
|
14288
|
+
};
|
|
14289
|
+
/**
|
|
14290
|
+
* Fail fast when the caller-declared `action` disagrees with the decoded
|
|
14291
|
+
* instructions, so the preview's headline can never mislabel what is signed
|
|
14292
|
+
* (e.g. a `deposit`-labeled call handed withdraw params). `takeFeeERC20` is an
|
|
14293
|
+
* auxiliary Circle-fee instruction and is allowed alongside any action.
|
|
14294
|
+
*/ function assertActionMatchesInstructions(action, instructions) {
|
|
14295
|
+
const allowed = ACTION_ALLOWED_METHODS[action];
|
|
14296
|
+
instructions.forEach((instruction, index)=>{
|
|
14297
|
+
if (instruction.method === 'takeFeeERC20') {
|
|
14298
|
+
return;
|
|
14299
|
+
}
|
|
14300
|
+
if (!allowed.has(instruction.method)) {
|
|
14301
|
+
throw decodeMismatchError(`decoded instruction method '${instruction.method}' does not match the '${action}' action`, {
|
|
14302
|
+
action,
|
|
14303
|
+
method: instruction.method,
|
|
14304
|
+
index
|
|
14305
|
+
});
|
|
14306
|
+
}
|
|
14307
|
+
});
|
|
14308
|
+
}
|
|
14309
|
+
/**
|
|
14310
|
+
* Decode a same-chain earn `execute()` bundle into a human-readable summary.
|
|
14311
|
+
*
|
|
14312
|
+
* Decodes every inner instruction in the service-signed `executionParams` — the
|
|
14313
|
+
* same object the SDK ABI-encodes into the transaction — so the returned decode
|
|
14314
|
+
* is a faithful, drift-free view of what the signer is authorizing: input token
|
|
14315
|
+
* and amount, target vault, receiver, any Circle fee, and claimed rewards. Fails
|
|
14316
|
+
* fast with a non-recoverable {@link KitError} if any instruction cannot be
|
|
14317
|
+
* decoded, rather than returning misleading data.
|
|
14318
|
+
*
|
|
14319
|
+
* @param input - Action, chain, adapter, and the signed execution params.
|
|
14320
|
+
* @returns The decoded transaction summary.
|
|
14321
|
+
* @throws {@link KitError} If an instruction's calldata cannot be decoded.
|
|
14322
|
+
*
|
|
14323
|
+
* @example
|
|
14324
|
+
* ```typescript
|
|
14325
|
+
* const decoded = decodeEarnExecute({
|
|
14326
|
+
* action: 'deposit',
|
|
14327
|
+
* chain: 'Arc_Testnet',
|
|
14328
|
+
* adapter: '0x7fb8c7260b63934d8da38af902f87ae6e284a845',
|
|
14329
|
+
* executionParams,
|
|
14330
|
+
* })
|
|
14331
|
+
* // decoded.summary -> { token: { address, amount }, vault, receiver }
|
|
14332
|
+
* ```
|
|
14333
|
+
*
|
|
14334
|
+
* @internal
|
|
14335
|
+
*/ function decodeEarnExecute(input) {
|
|
14336
|
+
const { action, chain, adapter, executionParams } = input;
|
|
14337
|
+
const envelope = requireInstructions(executionParams);
|
|
14338
|
+
const instructions = envelope.map((instruction, index)=>decodeInstruction(instruction, index));
|
|
14339
|
+
assertActionMatchesInstructions(action, instructions);
|
|
14340
|
+
return {
|
|
14341
|
+
action,
|
|
14342
|
+
chain,
|
|
14343
|
+
adapter,
|
|
14344
|
+
instructions,
|
|
14345
|
+
summary: buildSummary(instructions, envelope)
|
|
14346
|
+
};
|
|
14347
|
+
}
|
|
14348
|
+
/**
|
|
14349
|
+
* Assert that ABI-encoded Adapter `execute()` calldata encodes the same
|
|
14350
|
+
* instruction set as the service-signed execution params.
|
|
14351
|
+
*
|
|
14352
|
+
* Fail-fast preview check: the SDK encodes `execute(executeParams, ...)` locally,
|
|
14353
|
+
* so decoding those final bytes and comparing every field of each instruction
|
|
14354
|
+
* against the signed params proves the previewed instruction set matches what
|
|
14355
|
+
* will be signed. It compares `instructions[]` only — the outer `tokens`,
|
|
14356
|
+
* `execId`, `deadline`, and `metadata` are not re-compared here. The
|
|
14357
|
+
* authoritative integrity guarantee for the full signed struct is the on-chain
|
|
14358
|
+
* EIP-712 signature verification, which reverts if any signed field is altered.
|
|
14359
|
+
*
|
|
14360
|
+
* @param calldata - Encoded `execute()` calldata about to be signed.
|
|
14361
|
+
* @param executionParams - Service-signed execution params.
|
|
14362
|
+
* @throws {@link KitError} If the calldata is not an `execute()` call or any
|
|
14363
|
+
* instruction field differs from the signed params.
|
|
14364
|
+
*
|
|
14365
|
+
* @example
|
|
14366
|
+
* ```typescript
|
|
14367
|
+
* assertEarnCalldataMatchesExecuteParams(
|
|
14368
|
+
* prepared.getCallData().data,
|
|
14369
|
+
* executionParams,
|
|
14370
|
+
* )
|
|
14371
|
+
* ```
|
|
14372
|
+
*
|
|
14373
|
+
* @internal
|
|
14374
|
+
*/ function assertEarnCalldataMatchesExecuteParams(calldata, executionParams) {
|
|
14375
|
+
// adapterContractAbi declares only `execute`, so a successful decode is always
|
|
14376
|
+
// the execute() call; a non-execute selector throws inside
|
|
14377
|
+
// decodeExecuteCalldata above.
|
|
14378
|
+
const decoded = decodeExecuteCalldata(calldata);
|
|
14379
|
+
const encoded = decoded.args[0].instructions;
|
|
14380
|
+
const signed = requireInstructions(executionParams);
|
|
14381
|
+
// Compare each encoded instruction against its signed counterpart. Iterating
|
|
14382
|
+
// the encoded instructions and indexing the signed set keeps both mismatch
|
|
14383
|
+
// branches reachable: a signed set that is too short trips the guard below,
|
|
14384
|
+
// and one that is too long trips the post-loop check.
|
|
14385
|
+
encoded.forEach((instruction, index)=>{
|
|
14386
|
+
const signedInstruction = signed[index];
|
|
14387
|
+
if (signedInstruction === undefined) {
|
|
14388
|
+
throw decodeMismatchError(`signed params are missing instruction ${index.toString()}`, {
|
|
14389
|
+
index,
|
|
14390
|
+
encoded: encoded.length,
|
|
14391
|
+
signed: signed.length
|
|
14392
|
+
});
|
|
14393
|
+
}
|
|
14394
|
+
const path = `instructions[${index.toString()}]`;
|
|
14395
|
+
assertHexEqual(instruction.target, signedInstruction['target'], `${path}.target`);
|
|
14396
|
+
assertHexEqual(instruction.data, signedInstruction['data'], `${path}.data`);
|
|
14397
|
+
assertUintEqual(instruction.value, signedInstruction['value'], `${path}.value`);
|
|
14398
|
+
assertHexEqual(instruction.tokenIn, signedInstruction['tokenIn'], `${path}.tokenIn`);
|
|
14399
|
+
assertUintEqual(instruction.amountToApprove, signedInstruction['amountToApprove'], `${path}.amountToApprove`);
|
|
14400
|
+
assertHexEqual(instruction.tokenOut, signedInstruction['tokenOut'], `${path}.tokenOut`);
|
|
14401
|
+
assertUintEqual(instruction.minTokenOut, signedInstruction['minTokenOut'], `${path}.minTokenOut`);
|
|
14402
|
+
});
|
|
14403
|
+
if (signed.length > encoded.length) {
|
|
14404
|
+
throw decodeMismatchError('signed params contain more instructions than the encoded calldata', {
|
|
14405
|
+
encoded: encoded.length,
|
|
14406
|
+
signed: signed.length
|
|
14407
|
+
});
|
|
14408
|
+
}
|
|
14409
|
+
}
|
|
14410
|
+
/**
|
|
14411
|
+
* Assert two hex values are equal, case-insensitively (addresses and calldata).
|
|
14412
|
+
*/ function assertHexEqual(encoded, signed, path) {
|
|
14413
|
+
const signedHex = requireHex(signed, path);
|
|
14414
|
+
if (encoded.toLowerCase() !== signedHex.toLowerCase()) {
|
|
14415
|
+
throw decodeMismatchError(`${path} differs from signed params`, {
|
|
14416
|
+
path,
|
|
14417
|
+
encoded,
|
|
14418
|
+
signed: signedHex
|
|
14419
|
+
});
|
|
14420
|
+
}
|
|
14421
|
+
}
|
|
14422
|
+
/**
|
|
14423
|
+
* Assert an encoded bigint equals a signed `uint256`-like value.
|
|
14424
|
+
*/ function assertUintEqual(encoded, signed, path) {
|
|
14425
|
+
const signedUint = requireUint(signed, path);
|
|
14426
|
+
if (encoded !== signedUint) {
|
|
14427
|
+
throw decodeMismatchError(`${path} differs from signed params`, {
|
|
14428
|
+
path,
|
|
14429
|
+
encoded: encoded.toString(),
|
|
14430
|
+
signed: signedUint.toString()
|
|
14431
|
+
});
|
|
14432
|
+
}
|
|
14433
|
+
}
|
|
14434
|
+
|
|
14435
|
+
/**
|
|
14436
|
+
* Namespaced discriminator for the EarnKit authorization review.
|
|
14437
|
+
*
|
|
14438
|
+
* Applications match on this in an adapter `onBeforeAuthorize` hook to decide
|
|
14439
|
+
* whether the request carries EarnKit semantic data. Prefer the
|
|
14440
|
+
* {@link isEarnExecuteReview} type guard over comparing this string directly.
|
|
14441
|
+
*
|
|
14442
|
+
* @example
|
|
14443
|
+
* ```typescript
|
|
14444
|
+
* if (review?.kind === EARN_EXECUTE_REVIEW_KIND) { … }
|
|
14445
|
+
* ```
|
|
14446
|
+
*/ const EARN_EXECUTE_REVIEW_KIND = 'earn.execute';
|
|
14447
|
+
|
|
14448
|
+
/**
|
|
14449
|
+
* Build a fail-closed {@link KitError} for a review-construction failure.
|
|
14450
|
+
*
|
|
14451
|
+
* Marked non-recoverable: a review that cannot prove the calldata matches the
|
|
14452
|
+
* signed operation must abort authorization, never retry with misleading data.
|
|
14453
|
+
*/ function reviewError(message, trace) {
|
|
14454
|
+
return failClosedEarnError('Unable to build earn authorization review', message, trace);
|
|
14455
|
+
}
|
|
14456
|
+
/**
|
|
14457
|
+
* Narrow the canonical authorization payload to the single Adapter `execute()`
|
|
14458
|
+
* call a same-chain earn operation authorizes.
|
|
14459
|
+
*
|
|
14460
|
+
* Same-chain deposit, withdraw, and claim-rewards each authorize exactly one
|
|
14461
|
+
* `evm-calls` payload carrying one call. Anything else (typed data, a batch,
|
|
14462
|
+
* an empty call list) means this descriptor was attached to the wrong
|
|
14463
|
+
* authorization unit, so fail closed rather than decode misleading data.
|
|
14464
|
+
*/ function assertSingleEvmCallPayload(payload) {
|
|
14465
|
+
if (payload.type !== 'evm-calls') {
|
|
14466
|
+
throw reviewError(`expected an 'evm-calls' payload but received '${payload.type}'`, {
|
|
14467
|
+
type: payload.type
|
|
14468
|
+
});
|
|
14469
|
+
}
|
|
14470
|
+
const [call, ...rest] = payload.calls;
|
|
14471
|
+
if (call === undefined) {
|
|
14472
|
+
throw reviewError('the evm-calls payload contains no calls to review', {
|
|
14473
|
+
callCount: payload.calls.length
|
|
14474
|
+
});
|
|
14475
|
+
}
|
|
14476
|
+
if (rest.length > 0) {
|
|
14477
|
+
throw reviewError('a same-chain earn operation authorizes exactly one Adapter execute() call', {
|
|
14478
|
+
callCount: payload.calls.length
|
|
14479
|
+
});
|
|
14480
|
+
}
|
|
14481
|
+
return call;
|
|
14482
|
+
}
|
|
14483
|
+
/**
|
|
14484
|
+
* Select the final earn `execute()` call from an atomic Earn batch.
|
|
14485
|
+
*
|
|
14486
|
+
* Same-chain batched deposit/withdraw authorizes either `[execute]` when the
|
|
14487
|
+
* current allowance is sufficient, or `[approve, execute]` when a top-up is
|
|
14488
|
+
* required. Any other shape means the descriptor was attached to an
|
|
14489
|
+
* unexpected authorization unit, so fail closed.
|
|
14490
|
+
*/ function assertBatchedEarnExecuteCall(payload) {
|
|
14491
|
+
if (payload.type !== 'evm-calls') {
|
|
14492
|
+
throw reviewError(`expected an 'evm-calls' payload but received '${payload.type}'`, {
|
|
14493
|
+
type: payload.type
|
|
14494
|
+
});
|
|
14495
|
+
}
|
|
14496
|
+
if (payload.calls.length !== 1 && payload.calls.length !== 2) {
|
|
14497
|
+
throw reviewError('a batched earn operation authorizes [execute] or [approve, execute]', {
|
|
14498
|
+
callCount: payload.calls.length
|
|
14499
|
+
});
|
|
14500
|
+
}
|
|
14501
|
+
const executeCall = payload.calls.at(-1);
|
|
14502
|
+
if (executeCall === undefined) {
|
|
14503
|
+
throw reviewError('the earn batch contains no execute call to review', {
|
|
14504
|
+
callCount: payload.calls.length
|
|
14505
|
+
});
|
|
14506
|
+
}
|
|
14507
|
+
return executeCall;
|
|
14508
|
+
}
|
|
14509
|
+
/**
|
|
14510
|
+
* Map a canonical {@link EvmCall} to the {@link EarnEncodedTransaction} preview
|
|
14511
|
+
* shape, failing closed when the earn `execute()` call carries no calldata.
|
|
14512
|
+
*/ function toEarnEncodedTransaction(call) {
|
|
14513
|
+
if (call.data === undefined) {
|
|
14514
|
+
throw reviewError('the earn execute() call is missing calldata', {
|
|
14515
|
+
to: call.to
|
|
14516
|
+
});
|
|
14517
|
+
}
|
|
14518
|
+
return {
|
|
14519
|
+
to: call.to,
|
|
14520
|
+
data: call.data,
|
|
14521
|
+
...call.value !== undefined && {
|
|
14522
|
+
value: call.value
|
|
14523
|
+
}
|
|
14524
|
+
};
|
|
14525
|
+
}
|
|
14526
|
+
/**
|
|
14527
|
+
* Create an Earn authorization descriptor using the supplied canonical-payload
|
|
14528
|
+
* call selector.
|
|
14529
|
+
*
|
|
14530
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
14531
|
+
* @param selectCall - Fail-closed selector for the execute call under review.
|
|
14532
|
+
* @returns A lazy descriptor that decodes and verifies the selected call.
|
|
14533
|
+
*
|
|
14534
|
+
* @internal
|
|
14535
|
+
*/ function createEarnExecuteDescriptor(input, selectCall) {
|
|
14536
|
+
const { action, chain, executionParams } = input;
|
|
14537
|
+
const createReview = (payload)=>{
|
|
14538
|
+
const call = selectCall(payload);
|
|
14539
|
+
const encoded = toEarnEncodedTransaction(call);
|
|
14540
|
+
const decoded = decodeEarnExecute({
|
|
14541
|
+
action,
|
|
14542
|
+
chain,
|
|
14543
|
+
adapter: encoded.to,
|
|
14544
|
+
executionParams
|
|
14545
|
+
});
|
|
14546
|
+
// Prove the calldata about to be signed encodes the same instruction set as
|
|
14547
|
+
// the service-signed params. Throwing here aborts before the wallet prompt.
|
|
14548
|
+
assertEarnCalldataMatchesExecuteParams(encoded.data, executionParams);
|
|
14549
|
+
const review = {
|
|
14550
|
+
kind: EARN_EXECUTE_REVIEW_KIND,
|
|
14551
|
+
data: {
|
|
14552
|
+
encoded,
|
|
14553
|
+
decoded
|
|
14554
|
+
}
|
|
14555
|
+
};
|
|
14556
|
+
return review;
|
|
14557
|
+
};
|
|
14558
|
+
return {
|
|
14559
|
+
createReview
|
|
14560
|
+
};
|
|
14561
|
+
}
|
|
14562
|
+
/**
|
|
14563
|
+
* Build the lazy `earn.execute` authorization descriptor for a same-chain earn
|
|
14564
|
+
* action.
|
|
14565
|
+
*
|
|
14566
|
+
* The returned descriptor carries only a `createReview` factory — no intent
|
|
14567
|
+
* override, because the adapter's action system supplies the intent from the
|
|
14568
|
+
* action key. The factory is evaluated at most once, and only when the
|
|
14569
|
+
* application configured an adapter `onBeforeAuthorize` hook. When it runs it:
|
|
14570
|
+
*
|
|
14571
|
+
* 1. narrows the canonical payload to its single Adapter `execute()` call;
|
|
14572
|
+
* 2. lifts that call into an {@link EarnEncodedTransaction};
|
|
14573
|
+
* 3. decodes it into a `DecodedEarnTx`; and
|
|
14574
|
+
* 4. asserts the decoded calldata matches the service-signed params, throwing
|
|
14575
|
+
* (aborting authorization before the wallet or signer) on any mismatch.
|
|
14576
|
+
*
|
|
14577
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
14578
|
+
* @returns An authorization descriptor to pass as the fourth `prepareAction`
|
|
14579
|
+
* argument for the final earn action only (never the allowance approval).
|
|
14580
|
+
* @throws {@link KitError} From the review factory when the payload is not a
|
|
14581
|
+
* single earn `execute()` call or the calldata diverges from the signed
|
|
14582
|
+
* params. The throw surfaces through the adapter gate before authorization.
|
|
14583
|
+
*
|
|
14584
|
+
* @example
|
|
14585
|
+
* ```typescript
|
|
14586
|
+
* const descriptor = buildEarnExecuteDescriptor({
|
|
14587
|
+
* action: 'deposit',
|
|
14588
|
+
* chain: 'Arc_Testnet',
|
|
14589
|
+
* executionParams,
|
|
14590
|
+
* })
|
|
14591
|
+
* await adapter.prepareAction('earn.deposit', actionParams, ctx, {
|
|
14592
|
+
* authorization: descriptor,
|
|
14593
|
+
* })
|
|
14594
|
+
* ```
|
|
14595
|
+
*
|
|
14596
|
+
* @internal
|
|
14597
|
+
*/ function buildEarnExecuteDescriptor(input) {
|
|
14598
|
+
return createEarnExecuteDescriptor(input, assertSingleEvmCallPayload);
|
|
14599
|
+
}
|
|
14600
|
+
/**
|
|
14601
|
+
* Build a lazy `earn.execute` authorization descriptor for an atomic Earn
|
|
14602
|
+
* batch containing either `[execute]` or `[approve, execute]`.
|
|
14603
|
+
*
|
|
14604
|
+
* The review always decodes and verifies the final call against the
|
|
14605
|
+
* service-signed execution params. Unexpected payload types and call counts
|
|
14606
|
+
* fail closed before wallet authorization.
|
|
14607
|
+
*
|
|
14608
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
14609
|
+
* @returns A descriptor suitable for `batchExecute` authorization options.
|
|
14610
|
+
* @throws {@link KitError} From the lazy review factory when the batch shape or
|
|
14611
|
+
* final execute calldata cannot be verified.
|
|
14612
|
+
*
|
|
14613
|
+
* @internal
|
|
14614
|
+
*/ function buildBatchedEarnExecuteDescriptor(input) {
|
|
14615
|
+
return createEarnExecuteDescriptor(input, assertBatchedEarnExecuteCall);
|
|
14616
|
+
}
|
|
14617
|
+
|
|
13336
14618
|
/**
|
|
13337
14619
|
* Prepare an earn adapter action, execute it, wait for confirmation, and
|
|
13338
14620
|
* throw a structured revert error if the receipt status is `'reverted'`.
|
|
@@ -13359,16 +14641,37 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
13359
14641
|
* address,
|
|
13360
14642
|
* actionKey: 'earn.deposit',
|
|
13361
14643
|
* actionParams: { executeParams, tokenInputs, signature },
|
|
14644
|
+
* action: 'deposit',
|
|
14645
|
+
* executionParams,
|
|
13362
14646
|
* revertMessage: 'Earn deposit reverted on-chain',
|
|
13363
14647
|
* })
|
|
13364
14648
|
* ```
|
|
13365
14649
|
*
|
|
13366
14650
|
* @internal
|
|
13367
14651
|
*/ async function executeEarnAction(params) {
|
|
13368
|
-
const { adapter, chain, address, actionKey, actionParams, revertMessage } = params;
|
|
14652
|
+
const { adapter, chain, address, actionKey, actionParams, action, executionParams, revertMessage } = params;
|
|
14653
|
+
// Attach the lazy `earn.execute` review to the final earn action only (never
|
|
14654
|
+
// the allowance approval, which runs on a separate path). The adapter's
|
|
14655
|
+
// action system supplies the intent from `actionKey`, so the descriptor
|
|
14656
|
+
// carries only the review factory. The factory is evaluated at most once,
|
|
14657
|
+
// and only when the application configured an `onBeforeAuthorize` hook.
|
|
14658
|
+
const authorization = buildEarnExecuteDescriptor({
|
|
14659
|
+
action,
|
|
14660
|
+
// The provider validates the chain is Earn-supported in
|
|
14661
|
+
// `resolveAdapterContext` before reaching execute, so the concrete chain
|
|
14662
|
+
// identifier is a valid `EarnChainIdentifier`. It is carried through to the
|
|
14663
|
+
// decoded preview's display `chain` field only.
|
|
14664
|
+
chain: chain.chain,
|
|
14665
|
+
executionParams
|
|
14666
|
+
});
|
|
14667
|
+
// The abstract `Adapter.prepareAction` is 3-arg; the fourth authorization
|
|
14668
|
+
// argument lives on the `withLegacyCompat` wrapper that produced the concrete
|
|
14669
|
+
// adapter passed here. Narrow the single seam that threads the descriptor.
|
|
13369
14670
|
const prepared = await adapter.prepareAction(actionKey, actionParams, {
|
|
13370
14671
|
chain,
|
|
13371
14672
|
address
|
|
14673
|
+
}, {
|
|
14674
|
+
authorization
|
|
13372
14675
|
});
|
|
13373
14676
|
const gasLimitOverride = await estimateBufferedGasLimit(prepared);
|
|
13374
14677
|
const txHash = prepared.type === 'evm' && gasLimitOverride !== undefined ? await prepared.execute({
|
|
@@ -13393,6 +14696,277 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
13393
14696
|
};
|
|
13394
14697
|
}
|
|
13395
14698
|
|
|
14699
|
+
/**
|
|
14700
|
+
* Decide whether a same-chain earn action should be submitted as a single
|
|
14701
|
+
* atomic batch.
|
|
14702
|
+
*
|
|
14703
|
+
* Returns `true` only when the consumer has not opted out
|
|
14704
|
+
* (`batchTransactions !== false`), the source chain is EVM, the adapter
|
|
14705
|
+
* structurally exposes the shared batch methods, and the wallet reports atomic
|
|
14706
|
+
* batch support. `address` is forwarded as `fromAddress` so developer-controlled
|
|
14707
|
+
* adapters can probe the specific wallet. Any thrown capability probe is
|
|
14708
|
+
* treated as "no support".
|
|
14709
|
+
*
|
|
14710
|
+
* @param params - Adapter, chain, address, and the resolved `batchTransactions` flag.
|
|
14711
|
+
* @returns `true` when batched execution should be attempted.
|
|
14712
|
+
*
|
|
14713
|
+
* @example
|
|
14714
|
+
* ```typescript
|
|
14715
|
+
* if (await shouldUseBatchedEarnAction({ adapter, chain, address, batchTransactions })) {
|
|
14716
|
+
* // take the batched approve + execute path
|
|
14717
|
+
* }
|
|
14718
|
+
* ```
|
|
14719
|
+
*
|
|
14720
|
+
* @internal
|
|
14721
|
+
*/ async function shouldUseBatchedEarnAction(params) {
|
|
14722
|
+
const { adapter, chain, address, batchTransactions } = params;
|
|
14723
|
+
if (batchTransactions === false) {
|
|
14724
|
+
return false;
|
|
14725
|
+
}
|
|
14726
|
+
if (chain.type !== 'evm') {
|
|
14727
|
+
return false;
|
|
14728
|
+
}
|
|
14729
|
+
const candidate = adapter;
|
|
14730
|
+
if (typeof candidate.supportsAtomicBatch !== 'function' || typeof candidate.batchExecute !== 'function') {
|
|
14731
|
+
return false;
|
|
14732
|
+
}
|
|
14733
|
+
try {
|
|
14734
|
+
return await candidate.supportsAtomicBatch(chain, {
|
|
14735
|
+
fromAddress: address
|
|
14736
|
+
});
|
|
14737
|
+
} catch {
|
|
14738
|
+
return false;
|
|
14739
|
+
}
|
|
14740
|
+
}
|
|
14741
|
+
async function buildSuccessfulBatchResult(adapter, chain, receipt, batchId, revertMessage) {
|
|
14742
|
+
const transaction = {
|
|
14743
|
+
txHash: receipt.txHash,
|
|
14744
|
+
explorerUrl: buildExplorerUrl(chain, receipt.txHash)
|
|
14745
|
+
};
|
|
14746
|
+
let confirmed;
|
|
14747
|
+
try {
|
|
14748
|
+
confirmed = await adapter.waitForTransaction(receipt.txHash, {
|
|
14749
|
+
confirmations: 1
|
|
14750
|
+
}, chain);
|
|
14751
|
+
} catch {
|
|
14752
|
+
// The batch adapter already confirmed success. Receipt enrichment is
|
|
14753
|
+
// telemetry-only, so an additional RPC failure must not turn an accepted
|
|
14754
|
+
// money-moving operation into a retryable business failure.
|
|
14755
|
+
return transaction;
|
|
14756
|
+
}
|
|
14757
|
+
if (confirmed.status === 'reverted') {
|
|
14758
|
+
throw createTransactionRevertedError(chain.name, revertMessage, {
|
|
14759
|
+
batchId
|
|
14760
|
+
}, receipt.txHash, transaction.explorerUrl);
|
|
14761
|
+
}
|
|
14762
|
+
return {
|
|
14763
|
+
...transaction,
|
|
14764
|
+
...confirmed.gasUsed !== undefined && {
|
|
14765
|
+
gasUsed: confirmed.gasUsed
|
|
14766
|
+
},
|
|
14767
|
+
...confirmed.effectiveGasPrice !== undefined && {
|
|
14768
|
+
effectiveGasPrice: confirmed.effectiveGasPrice
|
|
14769
|
+
}
|
|
14770
|
+
};
|
|
14771
|
+
}
|
|
14772
|
+
function throwBatchFailure(result, executeReceipt, chain, actionKey, revertMessage) {
|
|
14773
|
+
const cause = result.error;
|
|
14774
|
+
if (result.statusCode === 400) {
|
|
14775
|
+
throw new KitError({
|
|
14776
|
+
...RpcError.ENDPOINT_ERROR,
|
|
14777
|
+
recoverability: 'RETRYABLE',
|
|
14778
|
+
message: `Batched earn ${actionKey} failed off-chain before inclusion (batch ${result.batchId}).`,
|
|
14779
|
+
cause: {
|
|
14780
|
+
trace: {
|
|
14781
|
+
batchId: result.batchId,
|
|
14782
|
+
statusCode: result.statusCode,
|
|
14783
|
+
cause
|
|
14784
|
+
}
|
|
14785
|
+
}
|
|
14786
|
+
});
|
|
14787
|
+
}
|
|
14788
|
+
const causeTrace = cause instanceof KitError && typeof cause.cause?.trace === 'object' && cause.cause.trace !== null ? cause.cause.trace : undefined;
|
|
14789
|
+
if (cause instanceof KitError && causeTrace?.['kind'] === 'failed_offchain') {
|
|
14790
|
+
throw cause;
|
|
14791
|
+
}
|
|
14792
|
+
const isConfirmedRevert = cause instanceof KitError && cause.name === OnchainError.TRANSACTION_REVERTED.name || result.statusCode === 500 || result.statusCode === 600 || result.statusCode === undefined && cause === undefined && executeReceipt?.status === 'error' && executeReceipt.txHash !== '';
|
|
14793
|
+
if (isConfirmedRevert) {
|
|
14794
|
+
throw createTransactionRevertedError(chain.name, revertMessage, {
|
|
14795
|
+
batchId: result.batchId,
|
|
14796
|
+
error: cause
|
|
14797
|
+
});
|
|
14798
|
+
}
|
|
14799
|
+
throw new KitError({
|
|
14800
|
+
...NetworkError.TIMEOUT,
|
|
14801
|
+
recoverability: 'FATAL',
|
|
14802
|
+
message: `Batched earn ${actionKey} was submitted (batch ${result.batchId}) but its outcome could not be confirmed; check the transaction status before retrying.`,
|
|
14803
|
+
cause: {
|
|
14804
|
+
trace: {
|
|
14805
|
+
batchId: result.batchId,
|
|
14806
|
+
cause
|
|
14807
|
+
}
|
|
14808
|
+
}
|
|
14809
|
+
});
|
|
14810
|
+
}
|
|
14811
|
+
/**
|
|
14812
|
+
* Execute the `approve` and `execute` steps of a same-chain earn action as a
|
|
14813
|
+
* single atomic batch.
|
|
14814
|
+
*
|
|
14815
|
+
* Prepare both `PreparedChainRequest` objects upfront, extract their raw call
|
|
14816
|
+
* data via `getCallData()`, then submit both through the adapter's shared
|
|
14817
|
+
* `batchExecute`. `address` is forwarded as `opts.fromAddress` so
|
|
14818
|
+
* developer-controlled adapters batch on behalf of the right wallet;
|
|
14819
|
+
* `idempotencyKey` is forwarded for adapters that deduplicate ambiguous
|
|
14820
|
+
* submissions (the Circle developer-controlled adapter reuses the Earn
|
|
14821
|
+
* execution id); other adapters may ignore either option. Reused by both the
|
|
14822
|
+
* deposit and withdraw flows via the `actionKey` parameter.
|
|
14823
|
+
*
|
|
14824
|
+
* @param params - Adapter, chain, action key, signed payload, and approval inputs.
|
|
14825
|
+
* @returns The confirmed execute transaction hash, explorer URL, and receipt
|
|
14826
|
+
* gas data when the adapter can retrieve it.
|
|
14827
|
+
* @throws {@link KitError} when the source chain is not EVM.
|
|
14828
|
+
* @throws {@link KitError} when calldata extraction (`getCallData`) is not
|
|
14829
|
+
* supported by the prepared requests.
|
|
14830
|
+
* @throws {@link KitError} when the batch reverts on-chain (a confirmed
|
|
14831
|
+
* terminal revert), carrying `batchId`.
|
|
14832
|
+
* @throws {@link KitError} RETRYABLE when EIP-5792 reports an off-chain
|
|
14833
|
+
* failure carrying `batchId` and status code `400`; no call was included.
|
|
14834
|
+
* @throws {@link KitError} FATAL `NetworkError.TIMEOUT` when the batch was
|
|
14835
|
+
* submitted but its outcome could not be confirmed (poll timeout or any
|
|
14836
|
+
* other non-revert post-submission failure); carries `batchId` so the caller
|
|
14837
|
+
* can check transaction status before retrying.
|
|
14838
|
+
* @remarks
|
|
14839
|
+
* Once the batch has been submitted this function does not fall back to the
|
|
14840
|
+
* sequential path — the batch is already on its way, so a fallback would risk
|
|
14841
|
+
* double-spend. Post-submission failures surface through the adapter's batch
|
|
14842
|
+
* result: a confirmed on-chain revert (Circle: a `TRANSACTION_REVERTED` cause;
|
|
14843
|
+
* Viem: status code `500`/`600`) is thrown as a revert error, status code `400`
|
|
14844
|
+
* is reported as a retryable off-chain failure, and any other unconfirmed
|
|
14845
|
+
* outcome is thrown as a FATAL timeout error carrying `batchId`.
|
|
14846
|
+
*
|
|
14847
|
+
* @example
|
|
14848
|
+
* ```typescript
|
|
14849
|
+
* const { txHash, explorerUrl } = await executeBatchedEarnAction({
|
|
14850
|
+
* adapter,
|
|
14851
|
+
* chain,
|
|
14852
|
+
* address,
|
|
14853
|
+
* actionKey: 'earn.deposit',
|
|
14854
|
+
* executeParams,
|
|
14855
|
+
* tokenInputs,
|
|
14856
|
+
* signature,
|
|
14857
|
+
* approvalToken: usdcAddress,
|
|
14858
|
+
* delegate: adapterContractAddress,
|
|
14859
|
+
* requiredAllowance: 1_000_000n,
|
|
14860
|
+
* idempotencyKey: '550e8400-e29b-41d4-a716-446655440000',
|
|
14861
|
+
* revertMessage: 'Earn deposit reverted on-chain',
|
|
14862
|
+
* })
|
|
14863
|
+
* ```
|
|
14864
|
+
*
|
|
14865
|
+
* @internal
|
|
14866
|
+
*/ async function executeBatchedEarnAction(params) {
|
|
14867
|
+
const { adapter, chain, address, actionKey, executeParams, tokenInputs, signature, approvalToken, delegate, requiredAllowance, idempotencyKey, revertMessage } = params;
|
|
14868
|
+
if (chain.type !== 'evm') {
|
|
14869
|
+
throw new KitError({
|
|
14870
|
+
...InputError.INVALID_CHAIN,
|
|
14871
|
+
recoverability: 'FATAL',
|
|
14872
|
+
message: 'Batched earn execution is only supported on EVM chains.'
|
|
14873
|
+
});
|
|
14874
|
+
}
|
|
14875
|
+
const evmChain = chain;
|
|
14876
|
+
const batchAdapter = adapter;
|
|
14877
|
+
// Read the current allowance so the approval tops up only the missing amount.
|
|
14878
|
+
// When the existing allowance already covers the payload, skip the approve
|
|
14879
|
+
// call and batch only the execute — this mirrors the sequential
|
|
14880
|
+
// approveAllowanceIfNeeded guard and avoids an increaseAllowance underflow
|
|
14881
|
+
// (requiredAllowance - currentAllowance would be negative, which reverts as
|
|
14882
|
+
// an out-of-range uint256).
|
|
14883
|
+
const currentAllowance = await readTokenAllowance(adapter, {
|
|
14884
|
+
tokenAddress: approvalToken,
|
|
14885
|
+
delegate
|
|
14886
|
+
}, {
|
|
14887
|
+
chain,
|
|
14888
|
+
address
|
|
14889
|
+
});
|
|
14890
|
+
const approvalNeeded = currentAllowance < requiredAllowance;
|
|
14891
|
+
const executePrepared = await adapter.prepareAction(actionKey, {
|
|
14892
|
+
executeParams,
|
|
14893
|
+
tokenInputs,
|
|
14894
|
+
signature
|
|
14895
|
+
}, {
|
|
14896
|
+
chain,
|
|
14897
|
+
address
|
|
14898
|
+
});
|
|
14899
|
+
const approvePrepared = approvalNeeded ? await prepareApprovalAction({
|
|
14900
|
+
adapter,
|
|
14901
|
+
chain,
|
|
14902
|
+
address,
|
|
14903
|
+
tokenAddress: approvalToken,
|
|
14904
|
+
delegate,
|
|
14905
|
+
currentAllowance,
|
|
14906
|
+
requiredAllowance
|
|
14907
|
+
}) : undefined;
|
|
14908
|
+
if (executePrepared.type !== 'evm' || !executePrepared.getCallData) {
|
|
14909
|
+
throw new KitError({
|
|
14910
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
14911
|
+
recoverability: 'FATAL',
|
|
14912
|
+
message: 'Batched earn execution requires EVM prepared requests with getCallData() support.'
|
|
14913
|
+
});
|
|
14914
|
+
}
|
|
14915
|
+
if (approvePrepared !== undefined && (approvePrepared.type !== 'evm' || !approvePrepared.getCallData)) {
|
|
14916
|
+
throw new KitError({
|
|
14917
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
14918
|
+
recoverability: 'FATAL',
|
|
14919
|
+
message: 'Batched earn execution requires EVM prepared requests with getCallData() support.'
|
|
14920
|
+
});
|
|
14921
|
+
}
|
|
14922
|
+
const executeCallData = executePrepared.getCallData();
|
|
14923
|
+
// Prepend the approve call only when an allowance top-up is required.
|
|
14924
|
+
const calls = approvePrepared?.type === 'evm' && approvePrepared.getCallData ? [
|
|
14925
|
+
approvePrepared.getCallData(),
|
|
14926
|
+
executeCallData
|
|
14927
|
+
] : [
|
|
14928
|
+
executeCallData
|
|
14929
|
+
];
|
|
14930
|
+
const authorization = buildBatchedEarnExecuteDescriptor({
|
|
14931
|
+
action: actionKey === 'earn.deposit' ? 'deposit' : 'withdraw',
|
|
14932
|
+
chain: evmChain.chain,
|
|
14933
|
+
executionParams: executeParams
|
|
14934
|
+
});
|
|
14935
|
+
const result = await batchAdapter.batchExecute(calls, evmChain, {
|
|
14936
|
+
fromAddress: address,
|
|
14937
|
+
idempotencyKey,
|
|
14938
|
+
atomicRequired: true,
|
|
14939
|
+
authorization
|
|
14940
|
+
});
|
|
14941
|
+
// Success fans one confirmed hash across every receipt; the execute call is
|
|
14942
|
+
// the last one (approve, if present, precedes it). On failure a confirming
|
|
14943
|
+
// adapter returns no receipts, so a missing/non-success last receipt — or a
|
|
14944
|
+
// populated `result.error` — means the batch failed after submission (point
|
|
14945
|
+
// of no return). We never fall back, which would double-spend.
|
|
14946
|
+
const receiptCountMatches = result.receipts.length === calls.length;
|
|
14947
|
+
const executeReceipt = receiptCountMatches ? result.receipts[calls.length - 1] : undefined;
|
|
14948
|
+
const succeeded = receiptCountMatches && (result.statusCode === undefined || result.statusCode === 200) && result.error === undefined && executeReceipt?.status === 'success' && executeReceipt.txHash !== '';
|
|
14949
|
+
if (succeeded) {
|
|
14950
|
+
return buildSuccessfulBatchResult(adapter, evmChain, executeReceipt, result.batchId, revertMessage);
|
|
14951
|
+
}
|
|
14952
|
+
// Distinguish an off-chain rejection, a confirmed on-chain revert, and an
|
|
14953
|
+
// unknown outcome across both adapter conventions that share this contract:
|
|
14954
|
+
// - Circle SCA: no receipts + `error`; its trace kind identifies an
|
|
14955
|
+
// off-chain rejection, confirmed revert, or unconfirmed outcome.
|
|
14956
|
+
// - Viem EIP-5792: statusCode 500/600 explicitly confirms an on-chain
|
|
14957
|
+
// full/partial revert.
|
|
14958
|
+
// - Legacy/string-status wallets: a real-hash error receipt with no cause
|
|
14959
|
+
// is the best available confirmed-revert signal.
|
|
14960
|
+
// statusCode 400 is terminal but off-chain: the wallet confirms no call was
|
|
14961
|
+
// included, so it must not be labeled as a revert or unknown outcome.
|
|
14962
|
+
// Anything else — a poll timeout or any other post-submission failure with no
|
|
14963
|
+
// confirmed-revert signal — means the batch was submitted but its fate is
|
|
14964
|
+
// unconfirmed. Surface that as a FATAL (non-auto-retry) error carrying
|
|
14965
|
+
// `batchId` so the caller checks status before retrying, rather than
|
|
14966
|
+
// mislabeling it a revert.
|
|
14967
|
+
return throwBatchFailure(result, executeReceipt, evmChain, actionKey, revertMessage);
|
|
14968
|
+
}
|
|
14969
|
+
|
|
13396
14970
|
/**
|
|
13397
14971
|
* Validate that a service-signed execution payload has not expired before
|
|
13398
14972
|
* the SDK asks the wallet to broadcast a transaction.
|
|
@@ -14689,7 +16263,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
14689
16263
|
}
|
|
14690
16264
|
|
|
14691
16265
|
var name = "@circle-fin/provider-earn-service";
|
|
14692
|
-
var version = "1.
|
|
16266
|
+
var version = "1.4.1";
|
|
14693
16267
|
var pkg = {
|
|
14694
16268
|
name: name,
|
|
14695
16269
|
version: version};
|
|
@@ -16317,6 +17891,46 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
16317
17891
|
const approvalToken = resolveEarnApprovalToken(executionParams);
|
|
16318
17892
|
const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
|
|
16319
17893
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
17894
|
+
const approvalNeeded = !options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n;
|
|
17895
|
+
// Batch-capable wallets bundle approve + deposit into one atomic
|
|
17896
|
+
// submission. Only attempt this when an approval is actually needed.
|
|
17897
|
+
if (approvalNeeded && approvalToken !== undefined && await shouldUseBatchedEarnAction({
|
|
17898
|
+
adapter,
|
|
17899
|
+
chain,
|
|
17900
|
+
address,
|
|
17901
|
+
batchTransactions: config?.batchTransactions
|
|
17902
|
+
})) {
|
|
17903
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>{
|
|
17904
|
+
try {
|
|
17905
|
+
const result = await executeBatchedEarnAction({
|
|
17906
|
+
adapter,
|
|
17907
|
+
chain,
|
|
17908
|
+
address,
|
|
17909
|
+
actionKey: 'earn.deposit',
|
|
17910
|
+
executeParams: executionParams,
|
|
17911
|
+
tokenInputs,
|
|
17912
|
+
signature,
|
|
17913
|
+
approvalToken,
|
|
17914
|
+
delegate: adapterContractAddress,
|
|
17915
|
+
requiredAllowance,
|
|
17916
|
+
idempotencyKey: execId,
|
|
17917
|
+
revertMessage: 'Earn deposit reverted on-chain'
|
|
17918
|
+
});
|
|
17919
|
+
reportTransactionSuccess(transactionReportContext, 'Deposit', result);
|
|
17920
|
+
return result;
|
|
17921
|
+
} catch (error) {
|
|
17922
|
+
reportTransactionFailure(transactionReportContext, 'Deposit', error);
|
|
17923
|
+
throw error;
|
|
17924
|
+
}
|
|
17925
|
+
}, ({ txHash })=>txHash);
|
|
17926
|
+
return {
|
|
17927
|
+
kind: 'same-chain',
|
|
17928
|
+
txHash,
|
|
17929
|
+
explorerUrl,
|
|
17930
|
+
vaultAddress,
|
|
17931
|
+
amount: params.amount
|
|
17932
|
+
};
|
|
17933
|
+
}
|
|
16320
17934
|
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
|
|
16321
17935
|
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
16322
17936
|
try {
|
|
@@ -16349,6 +17963,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
16349
17963
|
tokenInputs,
|
|
16350
17964
|
signature
|
|
16351
17965
|
},
|
|
17966
|
+
action: 'deposit',
|
|
17967
|
+
executionParams,
|
|
16352
17968
|
revertMessage: 'Earn deposit reverted on-chain'
|
|
16353
17969
|
});
|
|
16354
17970
|
reportTransactionSuccess(transactionReportContext, 'Deposit', result);
|
|
@@ -16480,6 +18096,44 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
16480
18096
|
const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
|
|
16481
18097
|
const approvalToken = tokenInputs[0]?.token;
|
|
16482
18098
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
18099
|
+
// Batch-capable wallets bundle approve + withdraw into one atomic
|
|
18100
|
+
// submission. Only attempt this when an approval is actually needed.
|
|
18101
|
+
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n && await shouldUseBatchedEarnAction({
|
|
18102
|
+
adapter,
|
|
18103
|
+
chain,
|
|
18104
|
+
address,
|
|
18105
|
+
batchTransactions: config?.batchTransactions
|
|
18106
|
+
})) {
|
|
18107
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'withdraw', 'execute', async ()=>{
|
|
18108
|
+
try {
|
|
18109
|
+
const result = await executeBatchedEarnAction({
|
|
18110
|
+
adapter,
|
|
18111
|
+
chain,
|
|
18112
|
+
address,
|
|
18113
|
+
actionKey: 'earn.withdraw',
|
|
18114
|
+
executeParams: executionParams,
|
|
18115
|
+
tokenInputs,
|
|
18116
|
+
signature,
|
|
18117
|
+
approvalToken,
|
|
18118
|
+
delegate: adapterContractAddress,
|
|
18119
|
+
requiredAllowance,
|
|
18120
|
+
idempotencyKey: execId,
|
|
18121
|
+
revertMessage: 'Earn withdraw reverted on-chain'
|
|
18122
|
+
});
|
|
18123
|
+
reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
|
|
18124
|
+
return result;
|
|
18125
|
+
} catch (error) {
|
|
18126
|
+
reportTransactionFailure(transactionReportContext, 'Withdraw', error);
|
|
18127
|
+
throw error;
|
|
18128
|
+
}
|
|
18129
|
+
}, ({ txHash })=>txHash);
|
|
18130
|
+
return {
|
|
18131
|
+
txHash,
|
|
18132
|
+
explorerUrl,
|
|
18133
|
+
vaultAddress,
|
|
18134
|
+
amount: params.amount
|
|
18135
|
+
};
|
|
18136
|
+
}
|
|
16483
18137
|
if (!options.skipApprove && approvalToken !== undefined) {
|
|
16484
18138
|
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
16485
18139
|
try {
|
|
@@ -16512,6 +18166,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
16512
18166
|
tokenInputs,
|
|
16513
18167
|
signature
|
|
16514
18168
|
},
|
|
18169
|
+
action: 'withdraw',
|
|
18170
|
+
executionParams,
|
|
16515
18171
|
revertMessage: 'Earn withdraw reverted on-chain'
|
|
16516
18172
|
});
|
|
16517
18173
|
reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
|
|
@@ -16591,6 +18247,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
16591
18247
|
tokenInputs: [],
|
|
16592
18248
|
signature
|
|
16593
18249
|
},
|
|
18250
|
+
action: 'claimRewards',
|
|
18251
|
+
executionParams,
|
|
16594
18252
|
revertMessage: 'Earn claim rewards reverted on-chain'
|
|
16595
18253
|
}), ({ txHash })=>txHash);
|
|
16596
18254
|
return {
|
|
@@ -17274,11 +18932,16 @@ const sourceAdapterContextSchema = zod.z.object({
|
|
|
17274
18932
|
*
|
|
17275
18933
|
* Validate the optional Kit Key field using the standard `apiKeySchema`
|
|
17276
18934
|
* format (`KIT_KEY:<keyId>:<keySecret>`). When omitted, the SDK
|
|
17277
|
-
* operates in permissionless mode.
|
|
18935
|
+
* operates in permissionless mode. `baseUrl` overrides the Earn Service
|
|
18936
|
+
* endpoint (e.g. staging); `batchTransactions: false` opts out of atomic
|
|
18937
|
+
* batched execution. Both are forwarded to the provider, so this `.strict()`
|
|
18938
|
+
* schema must accept them or a valid config object is rejected.
|
|
17278
18939
|
*
|
|
17279
18940
|
* @internal
|
|
17280
18941
|
*/ const earnConfigSchema = zod.z.object({
|
|
17281
|
-
kitKey: apiKeySchema.optional()
|
|
18942
|
+
kitKey: apiKeySchema.optional(),
|
|
18943
|
+
baseUrl: zod.z.string().optional(),
|
|
18944
|
+
batchTransactions: zod.z.boolean().optional()
|
|
17282
18945
|
}).strict();
|
|
17283
18946
|
/**
|
|
17284
18947
|
* Canonical decimal form: a leading digit with no leading zeros (a single
|