@circle-fin/app-kit 1.10.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +56 -25
- package/bridge.cjs +297 -48
- package/bridge.d.cts +891 -2
- package/bridge.d.mts +891 -2
- package/bridge.d.ts +891 -2
- package/bridge.mjs +297 -48
- package/chains.cjs +117 -1
- package/chains.d.cts +96 -2
- package/chains.d.mts +96 -2
- package/chains.d.ts +96 -2
- package/chains.mjs +116 -2
- package/context.cjs +11 -0
- package/context.d.cts +891 -2
- package/context.d.mts +891 -2
- package/context.d.ts +891 -2
- package/context.mjs +11 -0
- package/earn.cjs +2343 -175
- package/earn.d.cts +891 -2
- package/earn.d.mts +891 -2
- package/earn.d.ts +891 -2
- package/earn.mjs +2343 -175
- package/estimateBridge.cjs +297 -48
- package/estimateBridge.d.cts +891 -2
- package/estimateBridge.d.mts +891 -2
- package/estimateBridge.d.ts +891 -2
- package/estimateBridge.mjs +297 -48
- package/estimateSwap.cjs +391 -39
- package/estimateSwap.d.cts +890 -2
- package/estimateSwap.d.mts +890 -2
- package/estimateSwap.d.ts +890 -2
- package/estimateSwap.mjs +391 -39
- package/index.cjs +2591 -315
- package/index.d.cts +2382 -1632
- package/index.d.mts +2382 -1632
- package/index.d.ts +2382 -1632
- package/index.mjs +2590 -316
- package/package.json +7 -6
- package/swap.cjs +391 -39
- package/swap.d.cts +890 -2
- package/swap.d.mts +890 -2
- package/swap.d.ts +890 -2
- package/swap.mjs +391 -39
- package/unifiedBalance.cjs +411 -132
- package/unifiedBalance.d.cts +30 -5
- package/unifiedBalance.d.mts +30 -5
- package/unifiedBalance.d.ts +30 -5
- package/unifiedBalance.mjs +411 -132
package/earn.cjs
CHANGED
|
@@ -18,6 +18,17 @@
|
|
|
18
18
|
|
|
19
19
|
'use strict';
|
|
20
20
|
|
|
21
|
+
// Buffer polyfill setup - executes before any other code
|
|
22
|
+
// Ensures globalThis.Buffer is available for Solana libraries
|
|
23
|
+
const { Buffer } = require('buffer');
|
|
24
|
+
if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
|
|
25
|
+
globalThis.Buffer = Buffer;
|
|
26
|
+
}
|
|
27
|
+
if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
|
|
28
|
+
window.Buffer = Buffer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
21
32
|
var zod = require('zod');
|
|
22
33
|
require('pino');
|
|
23
34
|
require('@ethersproject/bytes');
|
|
@@ -28,8 +39,9 @@ require('bn.js');
|
|
|
28
39
|
require('@coral-xyz/anchor');
|
|
29
40
|
require('bs58');
|
|
30
41
|
require('@noble/curves/ed25519');
|
|
31
|
-
var
|
|
42
|
+
var viem = require('viem');
|
|
32
43
|
var units = require('@ethersproject/units');
|
|
44
|
+
var keccak256 = require('@ethersproject/keccak256');
|
|
33
45
|
|
|
34
46
|
// Import global type declarations
|
|
35
47
|
/**
|
|
@@ -46,6 +58,51 @@ var units = require('@ethersproject/units');
|
|
|
46
58
|
* }
|
|
47
59
|
* ```
|
|
48
60
|
*/ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
|
|
61
|
+
/**
|
|
62
|
+
* Check whether the current runtime exposes a browser DOM.
|
|
63
|
+
*
|
|
64
|
+
* @remarks
|
|
65
|
+
* This intentionally does not treat every non-Node runtime as a browser.
|
|
66
|
+
* Server-side edge runtimes such as Cloudflare Workers, Deno, and Supabase
|
|
67
|
+
* Edge Functions do not expose Node globals but can safely use server
|
|
68
|
+
* credentials. A Node.js runtime remains server-side even when a test or SSR
|
|
69
|
+
* environment provides a DOM shim.
|
|
70
|
+
*
|
|
71
|
+
* @returns `true` when running in a browser window, `false` otherwise.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* import { isBrowserEnvironment } from '@core/utils'
|
|
76
|
+
*
|
|
77
|
+
* if (isBrowserEnvironment()) {
|
|
78
|
+
* throw new Error('Server-only secrets must not be used in the browser')
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/ const isBrowserEnvironment = ()=>{
|
|
82
|
+
const browserWindow = globalThis.window;
|
|
83
|
+
return !isNodeEnvironment() && browserWindow?.document !== undefined;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Return the SDK User-Agent request header only when running in Node.js.
|
|
87
|
+
*
|
|
88
|
+
* Browsers forbid manually setting `User-Agent`, and a custom fallback header
|
|
89
|
+
* can trigger CORS preflight. Non-Node server runtimes also omit this optional
|
|
90
|
+
* attribution header because they cannot set it reliably.
|
|
91
|
+
*
|
|
92
|
+
* @returns A User-Agent header in Node.js, or an empty object otherwise.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* import { getNodeUserAgentHeader } from '@core/utils'
|
|
97
|
+
*
|
|
98
|
+
* const headers = {
|
|
99
|
+
* 'Content-Type': 'application/json',
|
|
100
|
+
* ...getNodeUserAgentHeader(),
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
|
|
104
|
+
'User-Agent': getUserAgent()
|
|
105
|
+
} : {};
|
|
49
106
|
/**
|
|
50
107
|
* Detect the runtime environment and return a shortened identifier.
|
|
51
108
|
*
|
|
@@ -623,6 +680,11 @@ class KitError extends Error {
|
|
|
623
680
|
name: 'INPUT_INSUFFICIENT_SWAP_AMOUNT',
|
|
624
681
|
type: 'INPUT'
|
|
625
682
|
},
|
|
683
|
+
/** Action not supported by this adapter / ecosystem */ UNSUPPORTED_ACTION: {
|
|
684
|
+
code: 1008,
|
|
685
|
+
name: 'INPUT_UNSUPPORTED_ACTION',
|
|
686
|
+
type: 'INPUT'
|
|
687
|
+
},
|
|
626
688
|
/** No route satisfies the slippage or minimum-output constraint */ SLIPPAGE_CONSTRAINT_NOT_MET: {
|
|
627
689
|
code: 1009,
|
|
628
690
|
name: 'INPUT_SLIPPAGE_CONSTRAINT_NOT_MET',
|
|
@@ -687,6 +749,29 @@ class KitError extends Error {
|
|
|
687
749
|
type: 'LIQUIDITY'
|
|
688
750
|
}
|
|
689
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
|
+
}};
|
|
690
775
|
/**
|
|
691
776
|
* Standardized error definitions for NETWORK type errors.
|
|
692
777
|
*
|
|
@@ -2286,6 +2371,8 @@ function getOptionalString(value) {
|
|
|
2286
2371
|
Blockchain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
2287
2372
|
Blockchain["XDC"] = "XDC";
|
|
2288
2373
|
Blockchain["XDC_Apothem"] = "XDC_Apothem";
|
|
2374
|
+
Blockchain["X_Layer"] = "X_Layer";
|
|
2375
|
+
Blockchain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
2289
2376
|
Blockchain["ZKSync_Era"] = "ZKSync_Era";
|
|
2290
2377
|
Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
|
|
2291
2378
|
})(Blockchain || (Blockchain = {}));
|
|
@@ -2339,6 +2426,7 @@ var BridgeChain;
|
|
|
2339
2426
|
BridgeChain["Unichain"] = "Unichain";
|
|
2340
2427
|
BridgeChain["World_Chain"] = "World_Chain";
|
|
2341
2428
|
BridgeChain["XDC"] = "XDC";
|
|
2429
|
+
BridgeChain["X_Layer"] = "X_Layer";
|
|
2342
2430
|
// Testnet chains with CCTPv2 support
|
|
2343
2431
|
BridgeChain["Arc_Testnet"] = "Arc_Testnet";
|
|
2344
2432
|
BridgeChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
|
|
@@ -2364,6 +2452,7 @@ var BridgeChain;
|
|
|
2364
2452
|
BridgeChain["Unichain_Sepolia"] = "Unichain_Sepolia";
|
|
2365
2453
|
BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
2366
2454
|
BridgeChain["XDC_Apothem"] = "XDC_Apothem";
|
|
2455
|
+
BridgeChain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
2367
2456
|
})(BridgeChain || (BridgeChain = {}));
|
|
2368
2457
|
var UnifiedBalanceChain;
|
|
2369
2458
|
(function(UnifiedBalanceChain) {
|
|
@@ -4901,7 +4990,8 @@ var EarnChain;
|
|
|
4901
4990
|
isTestnet: true,
|
|
4902
4991
|
explorerUrl: 'https://amoy.polygonscan.com/tx/{hash}',
|
|
4903
4992
|
rpcEndpoints: [
|
|
4904
|
-
'https://
|
|
4993
|
+
'https://polygon-amoy-bor-rpc.publicnode.com',
|
|
4994
|
+
'https://polygon-amoy.drpc.org'
|
|
4905
4995
|
],
|
|
4906
4996
|
eurcAddress: null,
|
|
4907
4997
|
usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -5766,6 +5856,104 @@ var EarnChain;
|
|
|
5766
5856
|
}
|
|
5767
5857
|
});
|
|
5768
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
|
+
|
|
5769
5957
|
/**
|
|
5770
5958
|
* ZKSync Era Mainnet chain definition
|
|
5771
5959
|
* @remarks
|
|
@@ -5885,6 +6073,8 @@ var Chains = {
|
|
|
5885
6073
|
WorldChainSepolia: WorldChainSepolia,
|
|
5886
6074
|
XDC: XDC,
|
|
5887
6075
|
XDCApothem: XDCApothem,
|
|
6076
|
+
XLayer: XLayer,
|
|
6077
|
+
XLayerTestnet: XLayerTestnet,
|
|
5888
6078
|
ZKSyncEra: ZKSyncEra,
|
|
5889
6079
|
ZKSyncEraSepolia: ZKSyncEraSepolia
|
|
5890
6080
|
};
|
|
@@ -6394,6 +6584,39 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
6394
6584
|
throw new Error(`Invalid chain identifier type: ${typeof chainIdentifier}. Expected ChainDefinition object, Blockchain enum, or string literal.`);
|
|
6395
6585
|
}
|
|
6396
6586
|
|
|
6587
|
+
/**
|
|
6588
|
+
* Resolve a chain identifier to a plain chain-name string.
|
|
6589
|
+
*
|
|
6590
|
+
* Accept a string literal (`'Ethereum'`), a `ChainDefinition`-like
|
|
6591
|
+
* object (`{ chain: 'Ethereum' }`), or `null`/`undefined` and return
|
|
6592
|
+
* the chain name as a string. Return `undefined` when the value
|
|
6593
|
+
* cannot be resolved.
|
|
6594
|
+
*
|
|
6595
|
+
* @remarks
|
|
6596
|
+
* Unlike `resolveChainIdentifier` (which returns a full `ChainDefinition`
|
|
6597
|
+
* and throws on invalid input), this helper is intentionally lenient and
|
|
6598
|
+
* never throws — it is safe to call in error-handling and telemetry paths.
|
|
6599
|
+
*
|
|
6600
|
+
* @param value - A string, chain-definition object, or nullish value.
|
|
6601
|
+
* @returns The chain name string, or `undefined`.
|
|
6602
|
+
*
|
|
6603
|
+
* @example
|
|
6604
|
+
* ```typescript
|
|
6605
|
+
* import { resolveChainName } from '@core/chains'
|
|
6606
|
+
*
|
|
6607
|
+
* resolveChainName('Ethereum') // 'Ethereum'
|
|
6608
|
+
* resolveChainName({ chain: 'Ethereum' }) // 'Ethereum'
|
|
6609
|
+
* resolveChainName(undefined) // undefined
|
|
6610
|
+
* ```
|
|
6611
|
+
*/ function resolveChainName(value) {
|
|
6612
|
+
if (value == null) return undefined;
|
|
6613
|
+
if (typeof value === 'string') return value;
|
|
6614
|
+
if (typeof value === 'object' && 'chain' in value && typeof value.chain === 'string') {
|
|
6615
|
+
return value.chain;
|
|
6616
|
+
}
|
|
6617
|
+
return undefined;
|
|
6618
|
+
}
|
|
6619
|
+
|
|
6397
6620
|
/**
|
|
6398
6621
|
* Extracts chain information including name, display name, and expected address format.
|
|
6399
6622
|
*
|
|
@@ -6778,13 +7001,12 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
6778
7001
|
headers: {
|
|
6779
7002
|
...DEFAULT_CONFIG$1.headers,
|
|
6780
7003
|
...config.headers ?? {},
|
|
6781
|
-
//
|
|
6782
|
-
//
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
}
|
|
7004
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
7005
|
+
// fallback header the SDK used instead trips CORS preflight against the
|
|
7006
|
+
// Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
|
|
7007
|
+
// blocking the request. So send the SDK user agent only in Node;
|
|
7008
|
+
// browsers omit it entirely.
|
|
7009
|
+
...getNodeUserAgentHeader()
|
|
6788
7010
|
}
|
|
6789
7011
|
};
|
|
6790
7012
|
let lastError;
|
|
@@ -7296,6 +7518,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
7296
7518
|
[Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
7297
7519
|
[Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
|
|
7298
7520
|
[Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
7521
|
+
[Blockchain.X_Layer]: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
7299
7522
|
[Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
|
|
7300
7523
|
// =========================================================================
|
|
7301
7524
|
// Testnets (alphabetically sorted)
|
|
@@ -7330,6 +7553,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
7330
7553
|
[Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
|
|
7331
7554
|
[Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
|
|
7332
7555
|
[Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
|
|
7556
|
+
[Blockchain.X_Layer_Testnet]: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
7333
7557
|
[Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853'
|
|
7334
7558
|
}
|
|
7335
7559
|
};
|
|
@@ -8044,134 +8268,544 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8044
8268
|
}
|
|
8045
8269
|
|
|
8046
8270
|
/**
|
|
8047
|
-
*
|
|
8271
|
+
* Assert that a value has type `never` (exhaustive switch helper).
|
|
8048
8272
|
*
|
|
8049
|
-
*
|
|
8050
|
-
*
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
/**
|
|
8054
|
-
* Strip the `@circle-fin/` scope from a kit package name to produce the
|
|
8055
|
-
* short SDK name used in telemetry payloads.
|
|
8273
|
+
* @remarks
|
|
8274
|
+
* Use in the `default` branch of a switch over a discriminated union.
|
|
8275
|
+
* If all union members are handled, the default is unreachable and TypeScript
|
|
8276
|
+
* narrows the parameter to `never`. If a member is missed, the compiler errors.
|
|
8056
8277
|
*
|
|
8057
|
-
* @param
|
|
8058
|
-
* @returns
|
|
8278
|
+
* @param _x - The value (typed as `never` when switch is exhaustive).
|
|
8279
|
+
* @returns Never returns; always throws.
|
|
8280
|
+
* @throws Error when the switch is not exhaustive.
|
|
8059
8281
|
*
|
|
8060
8282
|
* @example
|
|
8061
8283
|
* ```typescript
|
|
8062
|
-
*
|
|
8063
|
-
* import { resolveKitSdkName } from '@core/utils'
|
|
8284
|
+
* type Foo = { type: 'a'; x: number } | { type: 'b'; y: string }
|
|
8064
8285
|
*
|
|
8065
|
-
*
|
|
8286
|
+
* function handle(foo: Foo): string {
|
|
8287
|
+
* switch (foo.type) {
|
|
8288
|
+
* case 'a': return String(foo.x)
|
|
8289
|
+
* case 'b': return foo.y
|
|
8290
|
+
* default: return assertNever(foo)
|
|
8291
|
+
* }
|
|
8292
|
+
* }
|
|
8066
8293
|
* ```
|
|
8067
|
-
*/ function
|
|
8068
|
-
|
|
8294
|
+
*/ function assertNever$2(x) {
|
|
8295
|
+
// Plain `String(x)` collapses non-primitive union members (objects, arrays)
|
|
8296
|
+
// to `'[object Object]'`, which is useless when triaging which discriminant
|
|
8297
|
+
// was missed. Attempt `JSON.stringify` first so the thrown message preserves
|
|
8298
|
+
// the offending shape. Fall back to a minimal `typeof`-based label if
|
|
8299
|
+
// serialization fails (`BigInt` member, circular references, host objects).
|
|
8300
|
+
//
|
|
8301
|
+
// `x` is statically typed as `never` (the whole point of this helper), but
|
|
8302
|
+
// at runtime callers may still pass an unexpected value when the switch is
|
|
8303
|
+
// not actually exhaustive — that's exactly the bug we want to surface. Cast
|
|
8304
|
+
// through `unknown` so the runtime defence is not stripped by the compiler.
|
|
8305
|
+
const value = x;
|
|
8306
|
+
let stringified;
|
|
8307
|
+
try {
|
|
8308
|
+
const json = JSON.stringify(value);
|
|
8309
|
+
stringified = typeof json === 'string' ? json : `<${typeof value}>`;
|
|
8310
|
+
} catch {
|
|
8311
|
+
stringified = `<unstringifiable ${typeof value}>`;
|
|
8312
|
+
}
|
|
8313
|
+
throw new Error(`Unhandled switch case: ${stringified}`);
|
|
8069
8314
|
}
|
|
8070
8315
|
|
|
8071
|
-
var name$3 = "@circle-fin/bridge-kit";
|
|
8072
|
-
var version$3 = "1.12.1";
|
|
8073
|
-
var pkg$3 = {
|
|
8074
|
-
name: name$3,
|
|
8075
|
-
version: version$3};
|
|
8076
|
-
|
|
8077
8316
|
/**
|
|
8078
|
-
*
|
|
8317
|
+
* CCTP forwarding magic bytes prefix.
|
|
8079
8318
|
*
|
|
8080
|
-
*
|
|
8081
|
-
*
|
|
8319
|
+
* The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
|
|
8320
|
+
* This prefix is right-padded to 24 bytes in the final hookData.
|
|
8321
|
+
*/ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
|
|
8322
|
+
|
|
8323
|
+
/**
|
|
8324
|
+
* Project an arbitrary payload onto the exact set of fields the telemetry
|
|
8325
|
+
* endpoint accepts.
|
|
8082
8326
|
*
|
|
8083
|
-
*
|
|
8084
|
-
*
|
|
8085
|
-
*
|
|
8086
|
-
*
|
|
8087
|
-
* -
|
|
8088
|
-
*
|
|
8327
|
+
* @remarks
|
|
8328
|
+
* Defense-in-depth before the last network hop: rather than
|
|
8329
|
+
* `JSON.stringify`-ing the caller's object verbatim, only the
|
|
8330
|
+
* allowlisted {@link ClientLogPayload} fields (and the allowlisted
|
|
8331
|
+
* sub-fields of `errorDetails` / `clientContext`) are copied across.
|
|
8332
|
+
* A regressing upstream mapper — or a plain-JS caller that bypasses the
|
|
8333
|
+
* type — therefore cannot exfiltrate stray properties (secrets, PII,
|
|
8334
|
+
* raw error stacks) through the analytics channel. Optional fields are
|
|
8335
|
+
* only included when present so the serialised shape matches the
|
|
8336
|
+
* server's strict schema.
|
|
8089
8337
|
*
|
|
8090
|
-
*
|
|
8338
|
+
* @internal
|
|
8339
|
+
*/ function toSafePayload(payload) {
|
|
8340
|
+
const clientContext = {
|
|
8341
|
+
platform: payload.clientContext.platform,
|
|
8342
|
+
os: payload.clientContext.os,
|
|
8343
|
+
runtimeName: payload.clientContext.runtimeName
|
|
8344
|
+
};
|
|
8345
|
+
const safe = {
|
|
8346
|
+
sdkName: payload.sdkName,
|
|
8347
|
+
sdkVersion: payload.sdkVersion,
|
|
8348
|
+
eventType: payload.eventType,
|
|
8349
|
+
timestamp: payload.timestamp,
|
|
8350
|
+
clientContext
|
|
8351
|
+
};
|
|
8352
|
+
if (payload.sourceChain !== undefined) safe['sourceChain'] = payload.sourceChain;
|
|
8353
|
+
if (payload.destinationChain !== undefined) safe['destinationChain'] = payload.destinationChain;
|
|
8354
|
+
if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
|
|
8355
|
+
if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
|
|
8356
|
+
if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
|
|
8357
|
+
if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
|
|
8358
|
+
if (payload.errorDetails !== undefined) {
|
|
8359
|
+
const errorDetails = {
|
|
8360
|
+
...payload.errorDetails.errorCode !== undefined && {
|
|
8361
|
+
errorCode: payload.errorDetails.errorCode
|
|
8362
|
+
},
|
|
8363
|
+
...payload.errorDetails.errorType !== undefined && {
|
|
8364
|
+
errorType: payload.errorDetails.errorType
|
|
8365
|
+
}
|
|
8366
|
+
};
|
|
8367
|
+
safe['errorDetails'] = errorDetails;
|
|
8368
|
+
}
|
|
8369
|
+
return safe;
|
|
8370
|
+
}
|
|
8371
|
+
/**
|
|
8372
|
+
* Default telemetry endpoint.
|
|
8091
8373
|
*
|
|
8092
|
-
*
|
|
8093
|
-
*
|
|
8374
|
+
* Override via the `STABLECOIN_KITS_TELEMETRY_URL` environment variable
|
|
8375
|
+
* (e.g. for staging or local development).
|
|
8094
8376
|
*
|
|
8095
|
-
* @
|
|
8096
|
-
|
|
8097
|
-
* const config = {
|
|
8098
|
-
* computeFee: async () => '1', // 1 USDC (human-readable)
|
|
8099
|
-
* resolveFeeRecipientAddress: () => '0x1234567890123456789012345678901234567890',
|
|
8100
|
-
* }
|
|
8101
|
-
* const result = customFeePolicySchema.safeParse(config)
|
|
8102
|
-
* // result.success === true
|
|
8103
|
-
* ```
|
|
8104
|
-
*/ zod.z.object({
|
|
8105
|
-
computeFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional(),
|
|
8106
|
-
calculateFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional(),
|
|
8107
|
-
resolveFeeRecipientAddress: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string())))
|
|
8108
|
-
}).strict().refine((data)=>{
|
|
8109
|
-
const hasComputeFee = data.computeFee !== undefined;
|
|
8110
|
-
const hasCalculateFee = data.calculateFee !== undefined;
|
|
8111
|
-
// XOR: exactly one must be provided
|
|
8112
|
-
return hasComputeFee !== hasCalculateFee;
|
|
8113
|
-
}, {
|
|
8114
|
-
message: 'Provide either computeFee or calculateFee, not both. Use computeFee (recommended) for human-readable amounts.'
|
|
8115
|
-
});
|
|
8116
|
-
|
|
8377
|
+
* @internal
|
|
8378
|
+
*/ const DEFAULT_LOGS_URL = 'https://api.circle.com/v1/stablecoinKits/logs';
|
|
8117
8379
|
/**
|
|
8118
|
-
*
|
|
8380
|
+
* Resolve the telemetry endpoint URL.
|
|
8119
8381
|
*
|
|
8120
|
-
*
|
|
8121
|
-
|
|
8122
|
-
|
|
8123
|
-
|
|
8124
|
-
|
|
8382
|
+
* @internal
|
|
8383
|
+
*/ function getLogsUrl() {
|
|
8384
|
+
if (isNodeEnvironment() && typeof process.env['STABLECOIN_KITS_TELEMETRY_URL'] === 'string' && process.env['STABLECOIN_KITS_TELEMETRY_URL'].length > 0) {
|
|
8385
|
+
return process.env['STABLECOIN_KITS_TELEMETRY_URL'];
|
|
8386
|
+
}
|
|
8387
|
+
return DEFAULT_LOGS_URL;
|
|
8388
|
+
}
|
|
8389
|
+
/**
|
|
8390
|
+
* Send a telemetry event to the proxy service.
|
|
8125
8391
|
*
|
|
8126
8392
|
* @remarks
|
|
8127
|
-
*
|
|
8128
|
-
*
|
|
8393
|
+
* Fire-and-forget: the returned promise is intentionally not awaited
|
|
8394
|
+
* by the caller. A fetch failure (network error, non-2xx, timeout)
|
|
8395
|
+
* is silently swallowed so telemetry never blocks or fails user
|
|
8396
|
+
* operations.
|
|
8129
8397
|
*
|
|
8130
|
-
* @
|
|
8398
|
+
* @param payload - The structured log payload matching the server schema.
|
|
8131
8399
|
*
|
|
8132
8400
|
* @example
|
|
8133
8401
|
* ```typescript
|
|
8134
|
-
* import {
|
|
8402
|
+
* import { emitAnalyticsLog } from '@core/utils'
|
|
8135
8403
|
*
|
|
8136
|
-
*
|
|
8137
|
-
*
|
|
8138
|
-
*
|
|
8139
|
-
* const addressResult = hexStringSchema.safeParse(validAddress)
|
|
8140
|
-
* const txHashResult = hexStringSchema.safeParse(validTxHash)
|
|
8141
|
-
* console.log(addressResult.success) // true
|
|
8142
|
-
* console.log(txHashResult.success) // true
|
|
8404
|
+
* // Fire-and-forget — do not await
|
|
8405
|
+
* void emitAnalyticsLog(payload)
|
|
8143
8406
|
* ```
|
|
8144
|
-
*/
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
8407
|
+
*/ async function emitAnalyticsLog(payload) {
|
|
8408
|
+
// Hand-rolled timeout via `AbortController` + `setTimeout` rather than
|
|
8409
|
+
// `AbortSignal.timeout(...)` so we can `clearTimeout` the handle in a
|
|
8410
|
+
// `finally`. `AbortSignal.timeout` registers a timer that stays on the
|
|
8411
|
+
// event loop until it fires even if the fetch already settled, which
|
|
8412
|
+
// manifests as spurious `TimeoutError` unhandled rejections during
|
|
8413
|
+
// process teardown (notably between e2e test fork lifecycles). See
|
|
8414
|
+
// nodejs/node#48298 for the underlying issue.
|
|
8415
|
+
const controller = new AbortController();
|
|
8416
|
+
const timeoutHandle = setTimeout(()=>{
|
|
8417
|
+
controller.abort(new DOMException('Telemetry request timed out', 'TimeoutError'));
|
|
8418
|
+
}, 5_000);
|
|
8419
|
+
// Don't let the timer keep the Node event loop alive in short-lived
|
|
8420
|
+
// CLIs / test processes; telemetry is best-effort and must never
|
|
8421
|
+
// block clean process exit. `unref` only exists on Node's `Timeout`
|
|
8422
|
+
// object, not on the `number` returned by the browser's `setTimeout`,
|
|
8423
|
+
// so we feature-detect rather than call unconditionally.
|
|
8424
|
+
if (typeof timeoutHandle.unref === 'function') {
|
|
8425
|
+
timeoutHandle.unref();
|
|
8426
|
+
}
|
|
8427
|
+
try {
|
|
8428
|
+
await fetch(getLogsUrl(), {
|
|
8429
|
+
method: 'POST',
|
|
8430
|
+
headers: {
|
|
8431
|
+
'Content-Type': 'application/json',
|
|
8432
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
8433
|
+
// fallback header the SDK used instead trips CORS preflight (it isn't
|
|
8434
|
+
// in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
|
|
8435
|
+
// it only in Node; browsers omit it entirely.
|
|
8436
|
+
...getNodeUserAgentHeader()
|
|
8437
|
+
},
|
|
8438
|
+
body: JSON.stringify(toSafePayload(payload)),
|
|
8439
|
+
signal: controller.signal
|
|
8440
|
+
});
|
|
8441
|
+
} catch {
|
|
8442
|
+
// Silently swallow — telemetry must never break user operations.
|
|
8443
|
+
} finally{
|
|
8444
|
+
clearTimeout(timeoutHandle);
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
|
|
8148
8448
|
/**
|
|
8149
|
-
*
|
|
8449
|
+
* Build the `clientContext` object for telemetry payloads.
|
|
8150
8450
|
*
|
|
8151
|
-
*
|
|
8152
|
-
*
|
|
8153
|
-
*
|
|
8451
|
+
* @remarks
|
|
8452
|
+
* Use the exported `getRuntime()` and `isNodeEnvironment()` from
|
|
8453
|
+
* `@core/utils` to detect the runtime environment. The returned
|
|
8454
|
+
* string is parsed into the structured `ClientContext` fields
|
|
8455
|
+
* expected by the server schema.
|
|
8154
8456
|
*
|
|
8155
|
-
* @
|
|
8457
|
+
* @returns A {@link ClientContext} with platform, OS, and runtime name
|
|
8458
|
+
* populated from the current environment.
|
|
8156
8459
|
*
|
|
8157
8460
|
* @example
|
|
8158
8461
|
* ```typescript
|
|
8159
|
-
* import {
|
|
8160
|
-
*
|
|
8161
|
-
* const validAddress = '0x1234567890123456789012345678901234567890'
|
|
8462
|
+
* import { buildClientContext } from '@core/utils'
|
|
8162
8463
|
*
|
|
8163
|
-
* const
|
|
8164
|
-
*
|
|
8464
|
+
* const ctx = buildClientContext()
|
|
8465
|
+
* // Node: { platform: 'node', os: 'darwin', runtimeName: null }
|
|
8466
|
+
* // Browser: { platform: 'browser', os: null, runtimeName: 'chrome' }
|
|
8165
8467
|
* ```
|
|
8166
|
-
*/
|
|
8468
|
+
*/ function buildClientContext() {
|
|
8469
|
+
const runtime = getRuntime();
|
|
8470
|
+
if (runtime.startsWith('browser/')) {
|
|
8471
|
+
return {
|
|
8472
|
+
platform: 'browser',
|
|
8473
|
+
os: null,
|
|
8474
|
+
runtimeName: runtime.slice('browser/'.length).toLowerCase()
|
|
8475
|
+
};
|
|
8476
|
+
}
|
|
8477
|
+
if (runtime.startsWith('node/')) {
|
|
8478
|
+
return {
|
|
8479
|
+
platform: 'node',
|
|
8480
|
+
os: isNodeEnvironment() ? process.platform : null,
|
|
8481
|
+
runtimeName: null
|
|
8482
|
+
};
|
|
8483
|
+
}
|
|
8484
|
+
return {
|
|
8485
|
+
platform: 'node',
|
|
8486
|
+
os: null,
|
|
8487
|
+
runtimeName: null
|
|
8488
|
+
};
|
|
8489
|
+
}
|
|
8490
|
+
|
|
8167
8491
|
/**
|
|
8168
|
-
*
|
|
8492
|
+
* Extract structured error details from an unknown error value.
|
|
8169
8493
|
*
|
|
8170
|
-
*
|
|
8171
|
-
*
|
|
8172
|
-
* -
|
|
8494
|
+
* @remarks
|
|
8495
|
+
* Handle three cases:
|
|
8496
|
+
* - `KitError` — extract `code` and `name`.
|
|
8497
|
+
* - `Error` — extract `name`.
|
|
8498
|
+
* - Anything else — return empty details.
|
|
8173
8499
|
*
|
|
8174
|
-
*
|
|
8500
|
+
* Only structured, bounded fields (`errorCode`, `errorType`) are
|
|
8501
|
+
* included. Free-text fields (`message`, `stack`) are intentionally
|
|
8502
|
+
* omitted to avoid leaking secrets or PII through vendor telemetry.
|
|
8503
|
+
*
|
|
8504
|
+
* @param error - The thrown value to extract details from.
|
|
8505
|
+
* @returns A {@link ErrorDetails} object suitable for telemetry payloads.
|
|
8506
|
+
*
|
|
8507
|
+
* @example
|
|
8508
|
+
* ```typescript
|
|
8509
|
+
* import { extractErrorDetails } from '@core/utils'
|
|
8510
|
+
*
|
|
8511
|
+
* try {
|
|
8512
|
+
* await riskyOperation()
|
|
8513
|
+
* } catch (error) {
|
|
8514
|
+
* const details = extractErrorDetails(error)
|
|
8515
|
+
* // { errorCode: '1001', errorType: 'INPUT_NETWORK_MISMATCH' }
|
|
8516
|
+
* }
|
|
8517
|
+
* ```
|
|
8518
|
+
*/ function extractErrorDetails(error) {
|
|
8519
|
+
if (error instanceof KitError) {
|
|
8520
|
+
return {
|
|
8521
|
+
errorCode: String(error.code),
|
|
8522
|
+
errorType: error.name
|
|
8523
|
+
};
|
|
8524
|
+
}
|
|
8525
|
+
if (error instanceof Error) {
|
|
8526
|
+
return {
|
|
8527
|
+
errorType: error.name
|
|
8528
|
+
};
|
|
8529
|
+
}
|
|
8530
|
+
return {};
|
|
8531
|
+
}
|
|
8532
|
+
|
|
8533
|
+
/**
|
|
8534
|
+
* Strip the `@circle-fin/` scope from a kit package name to produce the
|
|
8535
|
+
* short SDK name used in telemetry payloads.
|
|
8536
|
+
*
|
|
8537
|
+
* @param pkgName - The full npm package name (e.g. `@circle-fin/bridge-kit`).
|
|
8538
|
+
* @returns The unscoped kit name (e.g. `bridge-kit`).
|
|
8539
|
+
*
|
|
8540
|
+
* @example
|
|
8541
|
+
* ```typescript
|
|
8542
|
+
* import pkg from '../../package.json'
|
|
8543
|
+
* import { resolveKitSdkName } from '@core/utils'
|
|
8544
|
+
*
|
|
8545
|
+
* const SDK_NAME = resolveKitSdkName(pkg.name) // 'bridge-kit'
|
|
8546
|
+
* ```
|
|
8547
|
+
*/ function resolveKitSdkName(pkgName) {
|
|
8548
|
+
return pkgName.replace('@circle-fin/', '');
|
|
8549
|
+
}
|
|
8550
|
+
|
|
8551
|
+
/**
|
|
8552
|
+
* Soft signal for the case where building or emitting a telemetry payload
|
|
8553
|
+
* threw — for example, a buggy `TelemetryContextResolver`, a regression in
|
|
8554
|
+
* `extractErrorDetails`, or a synchronous failure inside `emitAnalyticsLog`
|
|
8555
|
+
* before it could swallow the error itself. Logged with a stable prefix so
|
|
8556
|
+
* consumers can grep for it. We deliberately do not re-throw: the caller's
|
|
8557
|
+
* original operation error must always win.
|
|
8558
|
+
*
|
|
8559
|
+
* @internal
|
|
8560
|
+
*/ function warnTelemetryDrop(eventType, cause) {
|
|
8561
|
+
try {
|
|
8562
|
+
// Pass `cause` as the second console.warn argument rather than
|
|
8563
|
+
// string-coercing it. `String(err)` (and `err.message` alone)
|
|
8564
|
+
// discards the stack trace, nested `cause`, and any custom Error
|
|
8565
|
+
// properties — exactly the context an on-call needs when a
|
|
8566
|
+
// resolver-closure regression triggers this path.
|
|
8567
|
+
console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
|
|
8568
|
+
} catch {
|
|
8569
|
+
// console.warn itself throwing is the user's environment; nothing more we
|
|
8570
|
+
// can do without risking the original operation error.
|
|
8571
|
+
}
|
|
8572
|
+
}
|
|
8573
|
+
/**
|
|
8574
|
+
* Build a telemetry payload from common fields.
|
|
8575
|
+
*
|
|
8576
|
+
* @internal
|
|
8577
|
+
*/ function buildPayload(config, eventType, errorDetails, context) {
|
|
8578
|
+
return {
|
|
8579
|
+
sdkName: config.sdkName,
|
|
8580
|
+
sdkVersion: config.sdkVersion,
|
|
8581
|
+
eventType,
|
|
8582
|
+
timestamp: new Date().toISOString(),
|
|
8583
|
+
...errorDetails !== undefined && {
|
|
8584
|
+
errorDetails
|
|
8585
|
+
},
|
|
8586
|
+
clientContext: buildClientContext(),
|
|
8587
|
+
...context?.sourceChain != null && {
|
|
8588
|
+
sourceChain: context.sourceChain
|
|
8589
|
+
},
|
|
8590
|
+
...context?.destinationChain != null && {
|
|
8591
|
+
destinationChain: context.destinationChain
|
|
8592
|
+
},
|
|
8593
|
+
...context?.tokenIn != null && {
|
|
8594
|
+
tokenIn: context.tokenIn
|
|
8595
|
+
},
|
|
8596
|
+
...context?.tokenOut != null && {
|
|
8597
|
+
tokenOut: context.tokenOut
|
|
8598
|
+
},
|
|
8599
|
+
...context?.txHash != null && {
|
|
8600
|
+
txHash: context.txHash
|
|
8601
|
+
},
|
|
8602
|
+
...context?.correlationId != null && {
|
|
8603
|
+
correlationId: context.correlationId
|
|
8604
|
+
}
|
|
8605
|
+
};
|
|
8606
|
+
}
|
|
8607
|
+
/**
|
|
8608
|
+
* Emit telemetry for a completed operation without affecting its caller.
|
|
8609
|
+
*
|
|
8610
|
+
* No-ops when `config.disabled` is `true`. Like {@link withErrorTelemetry},
|
|
8611
|
+
* failures while constructing or submitting the telemetry payload are reported
|
|
8612
|
+
* as a soft warning and never change a completed operation's result.
|
|
8613
|
+
*
|
|
8614
|
+
* @param eventType - The telemetry event type for the completed operation.
|
|
8615
|
+
* @param config - Per-kit SDK identity and disabled flag.
|
|
8616
|
+
* @param context - Optional chain, token, and transaction context.
|
|
8617
|
+
* @returns Nothing.
|
|
8618
|
+
* @throws Never — telemetry failures are reported as warnings.
|
|
8619
|
+
*
|
|
8620
|
+
* @example
|
|
8621
|
+
* ```typescript
|
|
8622
|
+
* import { emitSuccessTelemetry } from '@core/utils'
|
|
8623
|
+
*
|
|
8624
|
+
* emitSuccessTelemetry(
|
|
8625
|
+
* 'bridge_bridge',
|
|
8626
|
+
* { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
|
|
8627
|
+
* { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
|
|
8628
|
+
* )
|
|
8629
|
+
* ```
|
|
8630
|
+
*/ function emitSuccessTelemetry(eventType, config, context) {
|
|
8631
|
+
if (config.disabled) {
|
|
8632
|
+
return;
|
|
8633
|
+
}
|
|
8634
|
+
try {
|
|
8635
|
+
void emitAnalyticsLog(buildPayload(config, eventType, undefined, context));
|
|
8636
|
+
} catch (telemetryError) {
|
|
8637
|
+
warnTelemetryDrop(eventType, telemetryError);
|
|
8638
|
+
}
|
|
8639
|
+
}
|
|
8640
|
+
/**
|
|
8641
|
+
* Wrap an async operation with error telemetry.
|
|
8642
|
+
*
|
|
8643
|
+
* Execute `fn` and, if it throws, emit an error telemetry payload
|
|
8644
|
+
* before re-throwing. No-ops when `config.disabled` is `true`.
|
|
8645
|
+
*
|
|
8646
|
+
* `context` may be a static {@link TelemetryContext} or a
|
|
8647
|
+
* {@link TelemetryContextResolver}. The resolver is invoked in the
|
|
8648
|
+
* catch branch, so it can read state — most importantly `txHash` —
|
|
8649
|
+
* that the wrapped operation set after a successful broadcast. The
|
|
8650
|
+
* resolver must close over per-call locals only; passing instance
|
|
8651
|
+
* state would break isolation between concurrent invocations.
|
|
8652
|
+
*
|
|
8653
|
+
* @param fn - The async operation to execute.
|
|
8654
|
+
* @param eventType - The telemetry event type for this operation.
|
|
8655
|
+
* @param config - Per-kit SDK identity and disabled flag.
|
|
8656
|
+
* @param context - Optional context, static or lazily resolved.
|
|
8657
|
+
* @returns The result of the operation.
|
|
8658
|
+
* @throws Re-throws any error after emitting telemetry.
|
|
8659
|
+
*
|
|
8660
|
+
* @example
|
|
8661
|
+
* ```typescript
|
|
8662
|
+
* import { withErrorTelemetry } from '@core/utils'
|
|
8663
|
+
*
|
|
8664
|
+
* let txHash: string | undefined
|
|
8665
|
+
* const result = await withErrorTelemetry(
|
|
8666
|
+
* () => provider.swap(params, h => { txHash = h }),
|
|
8667
|
+
* 'swap_swap',
|
|
8668
|
+
* { sdkName: 'swap-kit', sdkVersion: '1.0.0', disabled: false },
|
|
8669
|
+
* () => ({
|
|
8670
|
+
* sourceChain: 'Ethereum',
|
|
8671
|
+
* tokenIn: 'USDC',
|
|
8672
|
+
* tokenOut: 'EURC',
|
|
8673
|
+
* ...(txHash != null && { txHash }),
|
|
8674
|
+
* }),
|
|
8675
|
+
* )
|
|
8676
|
+
* ```
|
|
8677
|
+
*/ async function withErrorTelemetry(fn, eventType, config, context) {
|
|
8678
|
+
try {
|
|
8679
|
+
return await fn();
|
|
8680
|
+
} catch (error) {
|
|
8681
|
+
if (!config.disabled) {
|
|
8682
|
+
try {
|
|
8683
|
+
const resolved = typeof context === 'function' ? context() : context;
|
|
8684
|
+
void emitAnalyticsLog(buildPayload(config, eventType, extractErrorDetails(error), resolved));
|
|
8685
|
+
} catch (telemetryError) {
|
|
8686
|
+
// Never let telemetry emission mask the original operation error.
|
|
8687
|
+
// But surface a soft signal so silent telemetry drops are
|
|
8688
|
+
// discoverable (e.g. a regression in a resolver closure or in
|
|
8689
|
+
// `extractErrorDetails`) instead of vanishing without any trace.
|
|
8690
|
+
warnTelemetryDrop(eventType, telemetryError);
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8693
|
+
throw error;
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8696
|
+
|
|
8697
|
+
var name$3 = "@circle-fin/bridge-kit";
|
|
8698
|
+
var version$3 = "1.13.0";
|
|
8699
|
+
var pkg$3 = {
|
|
8700
|
+
name: name$3,
|
|
8701
|
+
version: version$3};
|
|
8702
|
+
|
|
8703
|
+
/**
|
|
8704
|
+
* Schema for validating BridgeKit custom fee policy.
|
|
8705
|
+
*
|
|
8706
|
+
* Validates the shape of {@link CustomFeePolicy}, which lets SDK consumers
|
|
8707
|
+
* provide custom fee calculation and fee-recipient resolution logic.
|
|
8708
|
+
*
|
|
8709
|
+
* - computeFee: optional function (recommended) that receives human-readable amounts
|
|
8710
|
+
* and returns a fee as a string (or Promise<string>).
|
|
8711
|
+
* - calculateFee: optional function (deprecated) that receives smallest-unit amounts
|
|
8712
|
+
* and returns a fee as a string (or Promise<string>).
|
|
8713
|
+
* - resolveFeeRecipientAddress: required function that returns a recipient address as a
|
|
8714
|
+
* string (or Promise<string>).
|
|
8715
|
+
*
|
|
8716
|
+
* Exactly one of `computeFee` or `calculateFee` must be provided (not both).
|
|
8717
|
+
*
|
|
8718
|
+
* This schema only ensures the presence and return types of the functions; it
|
|
8719
|
+
* does not validate their argument types.
|
|
8720
|
+
*
|
|
8721
|
+
* @example
|
|
8722
|
+
* ```ts
|
|
8723
|
+
* const config = {
|
|
8724
|
+
* computeFee: async () => '1', // 1 USDC (human-readable)
|
|
8725
|
+
* resolveFeeRecipientAddress: () => '0x1234567890123456789012345678901234567890',
|
|
8726
|
+
* }
|
|
8727
|
+
* const result = customFeePolicySchema.safeParse(config)
|
|
8728
|
+
* // result.success === true
|
|
8729
|
+
* ```
|
|
8730
|
+
*/ zod.z.object({
|
|
8731
|
+
computeFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional(),
|
|
8732
|
+
calculateFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional(),
|
|
8733
|
+
resolveFeeRecipientAddress: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string())))
|
|
8734
|
+
}).strict().superRefine((data, ctx)=>{
|
|
8735
|
+
const hasComputeFee = data.computeFee !== undefined;
|
|
8736
|
+
const hasCalculateFee = data.calculateFee !== undefined;
|
|
8737
|
+
if (hasComputeFee && hasCalculateFee) {
|
|
8738
|
+
ctx.addIssue({
|
|
8739
|
+
code: zod.z.ZodIssueCode.custom,
|
|
8740
|
+
message: 'Provide either computeFee or calculateFee, not both. Use computeFee (recommended) for human-readable amounts.'
|
|
8741
|
+
});
|
|
8742
|
+
}
|
|
8743
|
+
if (!hasComputeFee && !hasCalculateFee) {
|
|
8744
|
+
ctx.addIssue({
|
|
8745
|
+
code: zod.z.ZodIssueCode.custom,
|
|
8746
|
+
message: 'Provide either computeFee or calculateFee. Use computeFee (recommended) for human-readable amounts.'
|
|
8747
|
+
});
|
|
8748
|
+
}
|
|
8749
|
+
});
|
|
8750
|
+
|
|
8751
|
+
/**
|
|
8752
|
+
* Schema for validating hexadecimal strings with '0x' prefix.
|
|
8753
|
+
*
|
|
8754
|
+
* This schema validates that a string:
|
|
8755
|
+
* - Is a string type
|
|
8756
|
+
* - Is not empty after trimming
|
|
8757
|
+
* - Starts with '0x'
|
|
8758
|
+
* - Contains only valid hexadecimal characters (0-9, a-f, A-F) after '0x'
|
|
8759
|
+
*
|
|
8760
|
+
* @remarks
|
|
8761
|
+
* This schema does not validate length, making it suitable for various hex string types
|
|
8762
|
+
* like addresses, transaction hashes, and other hex-encoded data.
|
|
8763
|
+
*
|
|
8764
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
8765
|
+
*
|
|
8766
|
+
* @example
|
|
8767
|
+
* ```typescript
|
|
8768
|
+
* import { hexStringSchema } from '@core/adapter'
|
|
8769
|
+
*
|
|
8770
|
+
* const validAddress = '0x1234567890123456789012345678901234567890'
|
|
8771
|
+
* const validTxHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
|
|
8772
|
+
*
|
|
8773
|
+
* const addressResult = hexStringSchema.safeParse(validAddress)
|
|
8774
|
+
* const txHashResult = hexStringSchema.safeParse(validTxHash)
|
|
8775
|
+
* console.log(addressResult.success) // true
|
|
8776
|
+
* console.log(txHashResult.success) // true
|
|
8777
|
+
* ```
|
|
8778
|
+
*/ const hexStringSchema = zod.z.string().min(1, 'Hex string is required').refine((value)=>value.trim().length > 0, 'Hex string cannot be empty').refine((value)=>value.startsWith('0x'), 'Hex string must start with 0x prefix').refine((value)=>{
|
|
8779
|
+
const hexPattern = /^0x[0-9a-fA-F]+$/;
|
|
8780
|
+
return hexPattern.test(value);
|
|
8781
|
+
}, 'Hex string contains invalid characters. Only hexadecimal characters (0-9, a-f, A-F) are allowed after 0x');
|
|
8782
|
+
/**
|
|
8783
|
+
* Schema for validating EVM addresses.
|
|
8784
|
+
*
|
|
8785
|
+
* This schema validates that a string is a properly formatted EVM address:
|
|
8786
|
+
* - Must be a valid hex string with '0x' prefix
|
|
8787
|
+
* - Must be exactly 42 characters long (0x + 40 hex characters)
|
|
8788
|
+
*
|
|
8789
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
8790
|
+
*
|
|
8791
|
+
* @example
|
|
8792
|
+
* ```typescript
|
|
8793
|
+
* import { evmAddressSchema } from '@core/adapter'
|
|
8794
|
+
*
|
|
8795
|
+
* const validAddress = '0x1234567890123456789012345678901234567890'
|
|
8796
|
+
*
|
|
8797
|
+
* const result = evmAddressSchema.safeParse(validAddress)
|
|
8798
|
+
* console.log(result.success) // true
|
|
8799
|
+
* ```
|
|
8800
|
+
*/ const evmAddressSchema = hexStringSchema.refine((value)=>value.length === 42, 'EVM address must be exactly 42 characters long (0x + 40 hex characters)').transform((value)=>value);
|
|
8801
|
+
/**
|
|
8802
|
+
* Schema for validating transaction hashes.
|
|
8803
|
+
*
|
|
8804
|
+
* This schema validates that a string is a properly formatted transaction hash:
|
|
8805
|
+
* - Must be a valid hex string with '0x' prefix
|
|
8806
|
+
* - Must be exactly 66 characters long (0x + 64 hex characters)
|
|
8807
|
+
*
|
|
8808
|
+
* @throws {KitError} If validation fails with INPUT_VALIDATION_FAILED code (1098), with details about which properties failed
|
|
8175
8809
|
*
|
|
8176
8810
|
* @example
|
|
8177
8811
|
* ```typescript
|
|
@@ -8897,7 +9531,13 @@ var TransferSpeed;
|
|
|
8897
9531
|
/**
|
|
8898
9532
|
* The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
|
|
8899
9533
|
* hookData must start with.
|
|
8900
|
-
|
|
9534
|
+
*
|
|
9535
|
+
* Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
|
|
9536
|
+
* so this module-level constant does not reference the Node `Buffer` global at
|
|
9537
|
+
* import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
|
|
9538
|
+
* bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
|
|
9539
|
+
* that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
|
|
9540
|
+
*/ Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
8901
9541
|
|
|
8902
9542
|
/**
|
|
8903
9543
|
* The minimum finality threshold for CCTPv2 transfers.
|
|
@@ -8931,7 +9571,7 @@ var TransferSpeed;
|
|
|
8931
9571
|
registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
8932
9572
|
|
|
8933
9573
|
var name$2 = "@circle-fin/swap-kit";
|
|
8934
|
-
var version$2 = "1.
|
|
9574
|
+
var version$2 = "1.5.1";
|
|
8935
9575
|
var pkg$2 = {
|
|
8936
9576
|
name: name$2,
|
|
8937
9577
|
version: version$2};
|
|
@@ -9672,6 +10312,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
9672
10312
|
required_error: 'estimatedAmount is required',
|
|
9673
10313
|
invalid_type_error: 'estimatedAmount must be a string'
|
|
9674
10314
|
}).min(1, 'estimatedAmount must be a non-empty string'),
|
|
10315
|
+
// Per-swap join key echoed back verbatim on success telemetry. Optional so a
|
|
10316
|
+
// not-yet-upgraded service (no field) still validates during rollout. A
|
|
10317
|
+
// malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
|
|
10318
|
+
// than throwing: this is a telemetry-only field (stripped from the developer
|
|
10319
|
+
// result, never used for control flow), so it must not be able to abort the
|
|
10320
|
+
// swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
|
|
10321
|
+
// never-throw contract of the rest of the telemetry stack. Implemented with
|
|
10322
|
+
// `preprocess` rather than Zod's `.catch()` because static analysis misreads
|
|
10323
|
+
// `.catch` on the schema chain as an unhandled Promise (S7785).
|
|
10324
|
+
correlationId: zod.z.preprocess((value)=>zod.z.string().uuid().safeParse(value).success ? value : undefined, zod.z.string().optional()),
|
|
9675
10325
|
config: createSwapRequestBaseSchema.shape.config.optional(),
|
|
9676
10326
|
fees: createSwapFeesSchema.optional(),
|
|
9677
10327
|
transaction: createSwapTransactionSchema
|
|
@@ -9706,16 +10356,321 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
9706
10356
|
* const isValid = isValidApiKey('KIT_KEY:e84d2546d4e321b2ff427dc988c89503:f84d2548d4e322b2ff427fc989c87503')
|
|
9707
10357
|
* console.log(isValid) // true
|
|
9708
10358
|
*
|
|
9709
|
-
* ```
|
|
9710
|
-
*/ const isValidApiKey = (apiKey)=>{
|
|
9711
|
-
// Handle invalid input types
|
|
9712
|
-
if (typeof apiKey !== 'string') {
|
|
9713
|
-
return false;
|
|
10359
|
+
* ```
|
|
10360
|
+
*/ const isValidApiKey = (apiKey)=>{
|
|
10361
|
+
// Handle invalid input types
|
|
10362
|
+
if (typeof apiKey !== 'string') {
|
|
10363
|
+
return false;
|
|
10364
|
+
}
|
|
10365
|
+
const apiKeyPattern = /^KIT_KEY:[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+$/;
|
|
10366
|
+
// Validate without trimming - any whitespace will cause validation to fail
|
|
10367
|
+
return apiKeyPattern.test(apiKey);
|
|
10368
|
+
};
|
|
10369
|
+
|
|
10370
|
+
/**
|
|
10371
|
+
* IAdapter contract ABI.
|
|
10372
|
+
*
|
|
10373
|
+
* Shared ABI for the on-chain Adapter contract used by multiple kits
|
|
10374
|
+
* (swap, earn) for executing signed instruction sets. The `execute()`
|
|
10375
|
+
* function accepts EIP-712 signed execution parameters, token inputs,
|
|
10376
|
+
* and a signature, then executes the corresponding on-chain
|
|
10377
|
+
* instructions.
|
|
10378
|
+
*/ const adapterContractAbi = [
|
|
10379
|
+
{
|
|
10380
|
+
type: 'function',
|
|
10381
|
+
name: 'execute',
|
|
10382
|
+
inputs: [
|
|
10383
|
+
{
|
|
10384
|
+
name: 'params',
|
|
10385
|
+
type: 'tuple',
|
|
10386
|
+
internalType: 'struct IAdapter.ExecutionParams',
|
|
10387
|
+
components: [
|
|
10388
|
+
{
|
|
10389
|
+
name: 'instructions',
|
|
10390
|
+
type: 'tuple[]',
|
|
10391
|
+
internalType: 'struct IAdapter.Instruction[]',
|
|
10392
|
+
components: [
|
|
10393
|
+
{
|
|
10394
|
+
name: 'target',
|
|
10395
|
+
type: 'address',
|
|
10396
|
+
internalType: 'address'
|
|
10397
|
+
},
|
|
10398
|
+
{
|
|
10399
|
+
name: 'data',
|
|
10400
|
+
type: 'bytes',
|
|
10401
|
+
internalType: 'bytes'
|
|
10402
|
+
},
|
|
10403
|
+
{
|
|
10404
|
+
name: 'value',
|
|
10405
|
+
type: 'uint256',
|
|
10406
|
+
internalType: 'uint256'
|
|
10407
|
+
},
|
|
10408
|
+
{
|
|
10409
|
+
name: 'tokenIn',
|
|
10410
|
+
type: 'address',
|
|
10411
|
+
internalType: 'address'
|
|
10412
|
+
},
|
|
10413
|
+
{
|
|
10414
|
+
name: 'amountToApprove',
|
|
10415
|
+
type: 'uint256',
|
|
10416
|
+
internalType: 'uint256'
|
|
10417
|
+
},
|
|
10418
|
+
{
|
|
10419
|
+
name: 'tokenOut',
|
|
10420
|
+
type: 'address',
|
|
10421
|
+
internalType: 'address'
|
|
10422
|
+
},
|
|
10423
|
+
{
|
|
10424
|
+
name: 'minTokenOut',
|
|
10425
|
+
type: 'uint256',
|
|
10426
|
+
internalType: 'uint256'
|
|
10427
|
+
}
|
|
10428
|
+
]
|
|
10429
|
+
},
|
|
10430
|
+
{
|
|
10431
|
+
name: 'tokens',
|
|
10432
|
+
type: 'tuple[]',
|
|
10433
|
+
internalType: 'struct IAdapter.TokenRecipient[]',
|
|
10434
|
+
components: [
|
|
10435
|
+
{
|
|
10436
|
+
name: 'token',
|
|
10437
|
+
type: 'address',
|
|
10438
|
+
internalType: 'address'
|
|
10439
|
+
},
|
|
10440
|
+
{
|
|
10441
|
+
name: 'beneficiary',
|
|
10442
|
+
type: 'address',
|
|
10443
|
+
internalType: 'address'
|
|
10444
|
+
}
|
|
10445
|
+
]
|
|
10446
|
+
},
|
|
10447
|
+
{
|
|
10448
|
+
name: 'execId',
|
|
10449
|
+
type: 'uint256',
|
|
10450
|
+
internalType: 'uint256'
|
|
10451
|
+
},
|
|
10452
|
+
{
|
|
10453
|
+
name: 'deadline',
|
|
10454
|
+
type: 'uint256',
|
|
10455
|
+
internalType: 'uint256'
|
|
10456
|
+
},
|
|
10457
|
+
{
|
|
10458
|
+
name: 'metadata',
|
|
10459
|
+
type: 'bytes',
|
|
10460
|
+
internalType: 'bytes'
|
|
10461
|
+
}
|
|
10462
|
+
]
|
|
10463
|
+
},
|
|
10464
|
+
{
|
|
10465
|
+
name: 'tokenInputs',
|
|
10466
|
+
type: 'tuple[]',
|
|
10467
|
+
internalType: 'struct IAdapter.TokenInput[]',
|
|
10468
|
+
components: [
|
|
10469
|
+
{
|
|
10470
|
+
name: 'permitType',
|
|
10471
|
+
type: 'uint8',
|
|
10472
|
+
internalType: 'enum IAdapter.PermitType'
|
|
10473
|
+
},
|
|
10474
|
+
{
|
|
10475
|
+
name: 'token',
|
|
10476
|
+
type: 'address',
|
|
10477
|
+
internalType: 'address'
|
|
10478
|
+
},
|
|
10479
|
+
{
|
|
10480
|
+
name: 'amount',
|
|
10481
|
+
type: 'uint256',
|
|
10482
|
+
internalType: 'uint256'
|
|
10483
|
+
},
|
|
10484
|
+
{
|
|
10485
|
+
name: 'permitCalldata',
|
|
10486
|
+
type: 'bytes',
|
|
10487
|
+
internalType: 'bytes'
|
|
10488
|
+
}
|
|
10489
|
+
]
|
|
10490
|
+
},
|
|
10491
|
+
{
|
|
10492
|
+
name: 'signature',
|
|
10493
|
+
type: 'bytes',
|
|
10494
|
+
internalType: 'bytes'
|
|
10495
|
+
}
|
|
10496
|
+
],
|
|
10497
|
+
outputs: [],
|
|
10498
|
+
stateMutability: 'payable'
|
|
10499
|
+
}
|
|
10500
|
+
];
|
|
10501
|
+
|
|
10502
|
+
/**
|
|
10503
|
+
* Minimal ERC-4626 tokenized-vault ABI.
|
|
10504
|
+
*
|
|
10505
|
+
* Covers only the mutating methods EarnKit bundles as inner instructions inside
|
|
10506
|
+
* an Adapter `execute()` call: `deposit`, `withdraw`, and `redeem`. It exists so
|
|
10507
|
+
* clients can decode the inner instruction calldata into a human-readable
|
|
10508
|
+
* summary of what a signer is authorizing (asset amount, receiver, owner)
|
|
10509
|
+
* rather than showing opaque bytes. The 4-byte selectors match the calldata the
|
|
10510
|
+
* earn service signs (`deposit(uint256,address)` = `0x6e553f65`,
|
|
10511
|
+
* `withdraw(uint256,address,address)` = `0xb460af94`,
|
|
10512
|
+
* `redeem(uint256,address,address)` = `0xba087652`).
|
|
10513
|
+
*/ const erc4626VaultAbi = [
|
|
10514
|
+
{
|
|
10515
|
+
type: 'function',
|
|
10516
|
+
name: 'deposit',
|
|
10517
|
+
stateMutability: 'nonpayable',
|
|
10518
|
+
inputs: [
|
|
10519
|
+
{
|
|
10520
|
+
name: 'assets',
|
|
10521
|
+
type: 'uint256',
|
|
10522
|
+
internalType: 'uint256'
|
|
10523
|
+
},
|
|
10524
|
+
{
|
|
10525
|
+
name: 'receiver',
|
|
10526
|
+
type: 'address',
|
|
10527
|
+
internalType: 'address'
|
|
10528
|
+
}
|
|
10529
|
+
],
|
|
10530
|
+
outputs: [
|
|
10531
|
+
{
|
|
10532
|
+
name: 'shares',
|
|
10533
|
+
type: 'uint256',
|
|
10534
|
+
internalType: 'uint256'
|
|
10535
|
+
}
|
|
10536
|
+
]
|
|
10537
|
+
},
|
|
10538
|
+
{
|
|
10539
|
+
type: 'function',
|
|
10540
|
+
name: 'withdraw',
|
|
10541
|
+
stateMutability: 'nonpayable',
|
|
10542
|
+
inputs: [
|
|
10543
|
+
{
|
|
10544
|
+
name: 'assets',
|
|
10545
|
+
type: 'uint256',
|
|
10546
|
+
internalType: 'uint256'
|
|
10547
|
+
},
|
|
10548
|
+
{
|
|
10549
|
+
name: 'receiver',
|
|
10550
|
+
type: 'address',
|
|
10551
|
+
internalType: 'address'
|
|
10552
|
+
},
|
|
10553
|
+
{
|
|
10554
|
+
name: 'owner',
|
|
10555
|
+
type: 'address',
|
|
10556
|
+
internalType: 'address'
|
|
10557
|
+
}
|
|
10558
|
+
],
|
|
10559
|
+
outputs: [
|
|
10560
|
+
{
|
|
10561
|
+
name: 'shares',
|
|
10562
|
+
type: 'uint256',
|
|
10563
|
+
internalType: 'uint256'
|
|
10564
|
+
}
|
|
10565
|
+
]
|
|
10566
|
+
},
|
|
10567
|
+
{
|
|
10568
|
+
type: 'function',
|
|
10569
|
+
name: 'redeem',
|
|
10570
|
+
stateMutability: 'nonpayable',
|
|
10571
|
+
inputs: [
|
|
10572
|
+
{
|
|
10573
|
+
name: 'shares',
|
|
10574
|
+
type: 'uint256',
|
|
10575
|
+
internalType: 'uint256'
|
|
10576
|
+
},
|
|
10577
|
+
{
|
|
10578
|
+
name: 'receiver',
|
|
10579
|
+
type: 'address',
|
|
10580
|
+
internalType: 'address'
|
|
10581
|
+
},
|
|
10582
|
+
{
|
|
10583
|
+
name: 'owner',
|
|
10584
|
+
type: 'address',
|
|
10585
|
+
internalType: 'address'
|
|
10586
|
+
}
|
|
10587
|
+
],
|
|
10588
|
+
outputs: [
|
|
10589
|
+
{
|
|
10590
|
+
name: 'assets',
|
|
10591
|
+
type: 'uint256',
|
|
10592
|
+
internalType: 'uint256'
|
|
10593
|
+
}
|
|
10594
|
+
]
|
|
10595
|
+
}
|
|
10596
|
+
];
|
|
10597
|
+
|
|
10598
|
+
/**
|
|
10599
|
+
* Minimal FeeTaker ABI.
|
|
10600
|
+
*
|
|
10601
|
+
* The earn service appends a `takeFeeERC20` instruction to withdraw bundles
|
|
10602
|
+
* when Circle charges a withdrawal fee. This ABI decodes that inner instruction
|
|
10603
|
+
* so the fee (token, beneficiary, amount) is visible in the signing summary
|
|
10604
|
+
* instead of appearing as opaque calldata alongside the redeem/withdraw call.
|
|
10605
|
+
*/ const feeTakerAbi = [
|
|
10606
|
+
{
|
|
10607
|
+
type: 'function',
|
|
10608
|
+
name: 'takeFeeERC20',
|
|
10609
|
+
stateMutability: 'nonpayable',
|
|
10610
|
+
inputs: [
|
|
10611
|
+
{
|
|
10612
|
+
name: 'token',
|
|
10613
|
+
type: 'address',
|
|
10614
|
+
internalType: 'address'
|
|
10615
|
+
},
|
|
10616
|
+
{
|
|
10617
|
+
name: 'beneficiary',
|
|
10618
|
+
type: 'address',
|
|
10619
|
+
internalType: 'address'
|
|
10620
|
+
},
|
|
10621
|
+
{
|
|
10622
|
+
name: 'fee',
|
|
10623
|
+
type: 'uint256',
|
|
10624
|
+
internalType: 'uint256'
|
|
10625
|
+
},
|
|
10626
|
+
{
|
|
10627
|
+
name: 'kitType',
|
|
10628
|
+
type: 'bytes8',
|
|
10629
|
+
internalType: 'bytes8'
|
|
10630
|
+
}
|
|
10631
|
+
],
|
|
10632
|
+
outputs: []
|
|
10633
|
+
}
|
|
10634
|
+
];
|
|
10635
|
+
|
|
10636
|
+
/**
|
|
10637
|
+
* Minimal Merkl Distributor ABI.
|
|
10638
|
+
*
|
|
10639
|
+
* EarnKit claim-rewards bundles a single `claim` instruction targeting the
|
|
10640
|
+
* Merkl Distributor, batching one entry per reward token. This ABI decodes that
|
|
10641
|
+
* inner instruction so the claimed tokens and amounts are visible in the signing
|
|
10642
|
+
* summary. `claim` uses dynamic array arguments, which is why a real ABI decoder
|
|
10643
|
+
* (rather than fixed-word slicing) is required for the reward instruction.
|
|
10644
|
+
*/ const merklDistributorAbi = [
|
|
10645
|
+
{
|
|
10646
|
+
type: 'function',
|
|
10647
|
+
name: 'claim',
|
|
10648
|
+
stateMutability: 'nonpayable',
|
|
10649
|
+
inputs: [
|
|
10650
|
+
{
|
|
10651
|
+
name: 'users',
|
|
10652
|
+
type: 'address[]',
|
|
10653
|
+
internalType: 'address[]'
|
|
10654
|
+
},
|
|
10655
|
+
{
|
|
10656
|
+
name: 'tokens',
|
|
10657
|
+
type: 'address[]',
|
|
10658
|
+
internalType: 'address[]'
|
|
10659
|
+
},
|
|
10660
|
+
{
|
|
10661
|
+
name: 'amounts',
|
|
10662
|
+
type: 'uint256[]',
|
|
10663
|
+
internalType: 'uint256[]'
|
|
10664
|
+
},
|
|
10665
|
+
{
|
|
10666
|
+
name: 'proofs',
|
|
10667
|
+
type: 'bytes32[][]',
|
|
10668
|
+
internalType: 'bytes32[][]'
|
|
10669
|
+
}
|
|
10670
|
+
],
|
|
10671
|
+
outputs: []
|
|
9714
10672
|
}
|
|
9715
|
-
|
|
9716
|
-
// Validate without trimming - any whitespace will cause validation to fail
|
|
9717
|
-
return apiKeyPattern.test(apiKey);
|
|
9718
|
-
};
|
|
10673
|
+
];
|
|
9719
10674
|
|
|
9720
10675
|
/**
|
|
9721
10676
|
* Zod schema for validating EVM adapter capabilities.
|
|
@@ -12230,7 +13185,7 @@ new Set(Object.values(Blockchain));
|
|
|
12230
13185
|
registerKit(`${pkg$2.name}/${pkg$2.version}`);
|
|
12231
13186
|
|
|
12232
13187
|
var name$1 = "@circle-fin/earn-kit";
|
|
12233
|
-
var version$1 = "1.
|
|
13188
|
+
var version$1 = "1.5.0";
|
|
12234
13189
|
var pkg$1 = {
|
|
12235
13190
|
name: name$1,
|
|
12236
13191
|
version: version$1};
|
|
@@ -12846,31 +13801,670 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
12846
13801
|
return tokenInputs;
|
|
12847
13802
|
}
|
|
12848
13803
|
/**
|
|
12849
|
-
* Resolve the single token that needs adapter allowance from signed Earn
|
|
12850
|
-
* instructions.
|
|
13804
|
+
* Resolve the single token that needs adapter allowance from signed Earn
|
|
13805
|
+
* instructions.
|
|
13806
|
+
*
|
|
13807
|
+
* @param executionParams - Service-signed `ExecutionParams` forwarded to the adapter.
|
|
13808
|
+
* @returns The token requested by positive approval instructions, or `undefined`.
|
|
13809
|
+
* @throws {@link KitError} If positive approval instructions reference multiple tokens.
|
|
13810
|
+
*
|
|
13811
|
+
* @internal
|
|
13812
|
+
*/ function resolveEarnApprovalToken(executionParams) {
|
|
13813
|
+
let approvedToken;
|
|
13814
|
+
executionParams.instructions.forEach((instruction, index)=>{
|
|
13815
|
+
const amount = BigInt(instruction.amountToApprove);
|
|
13816
|
+
if (amount <= 0n) {
|
|
13817
|
+
return;
|
|
13818
|
+
}
|
|
13819
|
+
const { tokenIn } = instruction;
|
|
13820
|
+
if (approvedToken === undefined) {
|
|
13821
|
+
approvedToken = tokenIn;
|
|
13822
|
+
return;
|
|
13823
|
+
}
|
|
13824
|
+
if (!isSameAddress(tokenIn, approvedToken)) {
|
|
13825
|
+
throw createValidationFailedError(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
|
|
13826
|
+
}
|
|
13827
|
+
});
|
|
13828
|
+
return approvedToken;
|
|
13829
|
+
}
|
|
13830
|
+
|
|
13831
|
+
/**
|
|
13832
|
+
* Combined ABI of every inner instruction EarnKit can bundle inside an Adapter
|
|
13833
|
+
* `execute()` call. `decodeFunctionData` matches an instruction's calldata to
|
|
13834
|
+
* one of these functions by its 4-byte selector.
|
|
13835
|
+
*/ const earnInstructionAbi = [
|
|
13836
|
+
...erc4626VaultAbi,
|
|
13837
|
+
...feeTakerAbi,
|
|
13838
|
+
...merklDistributorAbi
|
|
13839
|
+
];
|
|
13840
|
+
/**
|
|
13841
|
+
* Extract and shallow-validate the `instructions` array from loosely-typed
|
|
13842
|
+
* signed execution params.
|
|
13843
|
+
*
|
|
13844
|
+
* The earn service schema validates `tokenIn`/`amountToApprove` and passes the
|
|
13845
|
+
* remaining instruction fields through untyped, so the params arrive as a plain
|
|
13846
|
+
* record; each accessed field is narrowed at runtime.
|
|
13847
|
+
*/ function requireInstructions(executionParams) {
|
|
13848
|
+
const instructions = executionParams['instructions'];
|
|
13849
|
+
if (!Array.isArray(instructions)) {
|
|
13850
|
+
throw decodeMismatchError('execution params are missing an instructions array', {
|
|
13851
|
+
instructions
|
|
13852
|
+
});
|
|
13853
|
+
}
|
|
13854
|
+
return instructions.map((instruction, index)=>{
|
|
13855
|
+
if (typeof instruction !== 'object' || instruction === null) {
|
|
13856
|
+
throw decodeMismatchError(`instructions[${index.toString()}] is not an object`, {
|
|
13857
|
+
index
|
|
13858
|
+
});
|
|
13859
|
+
}
|
|
13860
|
+
return instruction;
|
|
13861
|
+
});
|
|
13862
|
+
}
|
|
13863
|
+
/**
|
|
13864
|
+
* Build a fail-closed {@link KitError} for an earn decode or review failure.
|
|
13865
|
+
*
|
|
13866
|
+
* Marked non-recoverable: a mismatch between what would be shown and what would
|
|
13867
|
+
* be signed is never safe to retry, so the operation fails fast rather than
|
|
13868
|
+
* presenting misleading decoded data. `messagePrefix` names the failing stage
|
|
13869
|
+
* (calldata decode vs. review construction); callers bind it once and pass the
|
|
13870
|
+
* specific failure as `message`.
|
|
13871
|
+
*/ function failClosedEarnError(messagePrefix, message, trace) {
|
|
13872
|
+
return new KitError({
|
|
13873
|
+
...EarnError.INTERNAL_ERROR,
|
|
13874
|
+
recoverability: 'FATAL',
|
|
13875
|
+
message: `${messagePrefix}: ${message}`,
|
|
13876
|
+
cause: {
|
|
13877
|
+
trace
|
|
13878
|
+
}
|
|
13879
|
+
});
|
|
13880
|
+
}
|
|
13881
|
+
/**
|
|
13882
|
+
* Build a {@link KitError} for a decode or consistency failure.
|
|
13883
|
+
*
|
|
13884
|
+
* Thin wrapper over {@link failClosedEarnError} bound to the decode-stage
|
|
13885
|
+
* message prefix.
|
|
13886
|
+
*/ function decodeMismatchError(message, trace) {
|
|
13887
|
+
return failClosedEarnError('Unable to decode earn transaction', message, trace);
|
|
13888
|
+
}
|
|
13889
|
+
/**
|
|
13890
|
+
* Narrow an untyped value to a 0x-prefixed hex string, or fail fast.
|
|
13891
|
+
*/ function requireHex(value, path) {
|
|
13892
|
+
if (typeof value === 'string' && /^0x[0-9a-fA-F]*$/.test(value)) {
|
|
13893
|
+
return value;
|
|
13894
|
+
}
|
|
13895
|
+
throw decodeMismatchError(`${path} is not a hex string`, {
|
|
13896
|
+
path,
|
|
13897
|
+
value
|
|
13898
|
+
});
|
|
13899
|
+
}
|
|
13900
|
+
/**
|
|
13901
|
+
* Narrow an untyped value to a 20-byte EVM address, or fail fast.
|
|
13902
|
+
*/ function requireAddress(value, path) {
|
|
13903
|
+
if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value)) {
|
|
13904
|
+
return value;
|
|
13905
|
+
}
|
|
13906
|
+
throw decodeMismatchError(`${path} is not an address`, {
|
|
13907
|
+
path,
|
|
13908
|
+
value
|
|
13909
|
+
});
|
|
13910
|
+
}
|
|
13911
|
+
/**
|
|
13912
|
+
* Narrow an untyped `uint256`-like value (decimal string, bigint, or integer)
|
|
13913
|
+
* to a bigint, or fail fast.
|
|
13914
|
+
*/ function requireUint(value, path) {
|
|
13915
|
+
if (typeof value === 'bigint') {
|
|
13916
|
+
return value;
|
|
13917
|
+
}
|
|
13918
|
+
if (typeof value === 'string' && /^\d+$/.test(value)) {
|
|
13919
|
+
return BigInt(value);
|
|
13920
|
+
}
|
|
13921
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= 0) {
|
|
13922
|
+
return BigInt(value);
|
|
13923
|
+
}
|
|
13924
|
+
throw decodeMismatchError(`${path} is not a uint256 value`, {
|
|
13925
|
+
path,
|
|
13926
|
+
value
|
|
13927
|
+
});
|
|
13928
|
+
}
|
|
13929
|
+
/**
|
|
13930
|
+
* Decode inner instruction calldata against the earn instruction ABI, mapping a
|
|
13931
|
+
* viem decode failure (unknown selector, malformed args) to a fail-fast error.
|
|
13932
|
+
*/ function decodeEarnInstructionData(data, index) {
|
|
13933
|
+
try {
|
|
13934
|
+
return viem.decodeFunctionData({
|
|
13935
|
+
abi: earnInstructionAbi,
|
|
13936
|
+
data
|
|
13937
|
+
});
|
|
13938
|
+
} catch (error) {
|
|
13939
|
+
throw decodeMismatchError(`instructions[${index.toString()}] calldata is not a recognized earn instruction`, {
|
|
13940
|
+
index,
|
|
13941
|
+
selector: data.slice(0, 10),
|
|
13942
|
+
error: String(error)
|
|
13943
|
+
});
|
|
13944
|
+
}
|
|
13945
|
+
}
|
|
13946
|
+
/**
|
|
13947
|
+
* Decode Adapter `execute()` calldata, mapping a viem decode failure to a
|
|
13948
|
+
* fail-fast error.
|
|
13949
|
+
*/ function decodeExecuteCalldata(calldata) {
|
|
13950
|
+
try {
|
|
13951
|
+
return viem.decodeFunctionData({
|
|
13952
|
+
abi: adapterContractAbi,
|
|
13953
|
+
data: calldata
|
|
13954
|
+
});
|
|
13955
|
+
} catch (error) {
|
|
13956
|
+
throw decodeMismatchError('encoded calldata is not a valid Adapter execute() call', {
|
|
13957
|
+
error: String(error)
|
|
13958
|
+
});
|
|
13959
|
+
}
|
|
13960
|
+
}
|
|
13961
|
+
/**
|
|
13962
|
+
* Decode one inner instruction's calldata into a typed {@link
|
|
13963
|
+
* DecodedEarnInstruction}.
|
|
13964
|
+
*/ function decodeInstruction(instruction, index) {
|
|
13965
|
+
const target = requireAddress(instruction['target'], `instructions[${index.toString()}].target`);
|
|
13966
|
+
const data = requireHex(instruction['data'], `instructions[${index.toString()}].data`);
|
|
13967
|
+
const decoded = decodeEarnInstructionData(data, index);
|
|
13968
|
+
switch(decoded.functionName){
|
|
13969
|
+
case 'deposit':
|
|
13970
|
+
{
|
|
13971
|
+
const [assets, receiver] = decoded.args;
|
|
13972
|
+
return {
|
|
13973
|
+
method: 'deposit',
|
|
13974
|
+
vault: target,
|
|
13975
|
+
assets: assets.toString(),
|
|
13976
|
+
receiver
|
|
13977
|
+
};
|
|
13978
|
+
}
|
|
13979
|
+
case 'withdraw':
|
|
13980
|
+
{
|
|
13981
|
+
const [assets, receiver, owner] = decoded.args;
|
|
13982
|
+
return {
|
|
13983
|
+
method: 'withdraw',
|
|
13984
|
+
vault: target,
|
|
13985
|
+
assets: assets.toString(),
|
|
13986
|
+
receiver,
|
|
13987
|
+
owner
|
|
13988
|
+
};
|
|
13989
|
+
}
|
|
13990
|
+
case 'redeem':
|
|
13991
|
+
{
|
|
13992
|
+
const [shares, receiver, owner] = decoded.args;
|
|
13993
|
+
return {
|
|
13994
|
+
method: 'redeem',
|
|
13995
|
+
vault: target,
|
|
13996
|
+
shares: shares.toString(),
|
|
13997
|
+
receiver,
|
|
13998
|
+
owner
|
|
13999
|
+
};
|
|
14000
|
+
}
|
|
14001
|
+
case 'takeFeeERC20':
|
|
14002
|
+
{
|
|
14003
|
+
const [token, beneficiary, fee, kitType] = decoded.args;
|
|
14004
|
+
return {
|
|
14005
|
+
method: 'takeFeeERC20',
|
|
14006
|
+
feeTaker: target,
|
|
14007
|
+
token,
|
|
14008
|
+
beneficiary,
|
|
14009
|
+
fee: fee.toString(),
|
|
14010
|
+
kitType
|
|
14011
|
+
};
|
|
14012
|
+
}
|
|
14013
|
+
case 'claim':
|
|
14014
|
+
{
|
|
14015
|
+
const users = decoded.args[0];
|
|
14016
|
+
const tokens = decoded.args[1];
|
|
14017
|
+
const amounts = decoded.args[2];
|
|
14018
|
+
// Merkl claim(users, tokens, amounts, proofs) carries parallel arrays,
|
|
14019
|
+
// one entry per reward. Reject any length skew rather than padding with
|
|
14020
|
+
// zero amounts or dropping trailing entries, so the preview can never
|
|
14021
|
+
// misstate what is claimed or for whom.
|
|
14022
|
+
//
|
|
14023
|
+
// Note: Merkl `amounts` are the *cumulative lifetime* total claimable per
|
|
14024
|
+
// (user, token); the Distributor transfers only `amount - alreadyClaimed`.
|
|
14025
|
+
// This decode faithfully surfaces the signed cumulative value, which is
|
|
14026
|
+
// what `DecodedRewardClaim.amount` documents. See that type's doc.
|
|
14027
|
+
if (new Set([
|
|
14028
|
+
users.length,
|
|
14029
|
+
tokens.length,
|
|
14030
|
+
amounts.length
|
|
14031
|
+
]).size !== 1) {
|
|
14032
|
+
throw decodeMismatchError(`instructions[${index.toString()}] claim has mismatched recipient/token/amount lengths`, {
|
|
14033
|
+
index,
|
|
14034
|
+
users: users.length,
|
|
14035
|
+
tokens: tokens.length,
|
|
14036
|
+
amounts: amounts.length
|
|
14037
|
+
});
|
|
14038
|
+
}
|
|
14039
|
+
const rewards = tokens.map((token, rewardIndex)=>({
|
|
14040
|
+
recipient: requireAddress(users[rewardIndex], `instructions[${index.toString()}].claim.users[${rewardIndex.toString()}]`),
|
|
14041
|
+
address: token,
|
|
14042
|
+
amount: requireUint(amounts[rewardIndex], `instructions[${index.toString()}].claim.amounts[${rewardIndex.toString()}]`).toString()
|
|
14043
|
+
}));
|
|
14044
|
+
return {
|
|
14045
|
+
method: 'claim',
|
|
14046
|
+
distributor: target,
|
|
14047
|
+
rewards
|
|
14048
|
+
};
|
|
14049
|
+
}
|
|
14050
|
+
/* v8 ignore next 2 -- exhaustive switch; default is unreachable */ default:
|
|
14051
|
+
return assertNever$2(decoded);
|
|
14052
|
+
}
|
|
14053
|
+
}
|
|
14054
|
+
/**
|
|
14055
|
+
* Lift the primary values a wallet prompt cares about out of the decoded
|
|
14056
|
+
* instructions into a flat summary.
|
|
14057
|
+
*/ function buildSummary(instructions, envelope) {
|
|
14058
|
+
const summary = {};
|
|
14059
|
+
instructions.forEach((instruction, index)=>{
|
|
14060
|
+
switch(instruction.method){
|
|
14061
|
+
case 'deposit':
|
|
14062
|
+
case 'withdraw':
|
|
14063
|
+
case 'redeem':
|
|
14064
|
+
{
|
|
14065
|
+
// The summary lifts a single primary token movement to the top level.
|
|
14066
|
+
// An earn bundle carries exactly one deposit/withdraw/redeem today;
|
|
14067
|
+
// fail fast rather than silently overwriting an earlier one, which
|
|
14068
|
+
// would drop it from the wallet-facing preview.
|
|
14069
|
+
if (summary.token !== undefined) {
|
|
14070
|
+
throw decodeMismatchError('multiple deposit/withdraw/redeem instructions cannot be summarized into a single preview', {
|
|
14071
|
+
index
|
|
14072
|
+
});
|
|
14073
|
+
}
|
|
14074
|
+
// Pair the amount with the token it is actually denominated in so the
|
|
14075
|
+
// preview never folds two units into one entry:
|
|
14076
|
+
// - deposit: `assets` of the underlying asset pulled in (`tokenIn`)
|
|
14077
|
+
// - redeem: `shares` of the vault-share token burned (`tokenIn`)
|
|
14078
|
+
// - withdraw: `assets` of the underlying asset paid out (`tokenOut`).
|
|
14079
|
+
// `withdraw(assets)` counts the underlying received, not the shares
|
|
14080
|
+
// burned to produce it, so `tokenIn` (the share token) would misstate
|
|
14081
|
+
// the unit; the underlying is the instruction's `tokenOut`.
|
|
14082
|
+
const amount = instruction.method === 'redeem' ? instruction.shares : instruction.assets;
|
|
14083
|
+
const tokenField = instruction.method === 'withdraw' ? 'tokenOut' : 'tokenIn';
|
|
14084
|
+
summary.vault = instruction.vault;
|
|
14085
|
+
summary.receiver = instruction.receiver;
|
|
14086
|
+
summary.token = {
|
|
14087
|
+
address: requireAddress(envelope[index]?.[tokenField], `instructions[${index.toString()}].${tokenField}`),
|
|
14088
|
+
amount
|
|
14089
|
+
};
|
|
14090
|
+
break;
|
|
14091
|
+
}
|
|
14092
|
+
case 'takeFeeERC20':
|
|
14093
|
+
{
|
|
14094
|
+
// As with the vault case, a second fee would silently overwrite the
|
|
14095
|
+
// first and understate what is charged; fail fast instead.
|
|
14096
|
+
if (summary.fee !== undefined) {
|
|
14097
|
+
throw decodeMismatchError('multiple fee instructions cannot be summarized into a single preview', {
|
|
14098
|
+
index
|
|
14099
|
+
});
|
|
14100
|
+
}
|
|
14101
|
+
summary.fee = {
|
|
14102
|
+
address: instruction.token,
|
|
14103
|
+
amount: instruction.fee
|
|
14104
|
+
};
|
|
14105
|
+
break;
|
|
14106
|
+
}
|
|
14107
|
+
case 'claim':
|
|
14108
|
+
{
|
|
14109
|
+
// A second claim would silently drop the first from the preview
|
|
14110
|
+
// (rewards are already batched inside one Merkl claim); fail fast.
|
|
14111
|
+
if (summary.rewards !== undefined) {
|
|
14112
|
+
throw decodeMismatchError('multiple claim instructions cannot be summarized into a single preview', {
|
|
14113
|
+
index
|
|
14114
|
+
});
|
|
14115
|
+
}
|
|
14116
|
+
summary.rewards = instruction.rewards;
|
|
14117
|
+
break;
|
|
14118
|
+
}
|
|
14119
|
+
/* v8 ignore next 2 -- exhaustive switch; default is unreachable */ default:
|
|
14120
|
+
assertNever$2(instruction);
|
|
14121
|
+
}
|
|
14122
|
+
});
|
|
14123
|
+
return summary;
|
|
14124
|
+
}
|
|
14125
|
+
/**
|
|
14126
|
+
* Vault/claim instruction methods each declared action may decode to. The
|
|
14127
|
+
* mapping is many-to-one: a full withdrawal decodes to `redeem`, and any action
|
|
14128
|
+
* may carry an auxiliary `takeFeeERC20` alongside its primary instruction.
|
|
14129
|
+
*/ const ACTION_ALLOWED_METHODS = {
|
|
14130
|
+
deposit: new Set([
|
|
14131
|
+
'deposit'
|
|
14132
|
+
]),
|
|
14133
|
+
withdraw: new Set([
|
|
14134
|
+
'withdraw',
|
|
14135
|
+
'redeem'
|
|
14136
|
+
]),
|
|
14137
|
+
claimRewards: new Set([
|
|
14138
|
+
'claim'
|
|
14139
|
+
])
|
|
14140
|
+
};
|
|
14141
|
+
/**
|
|
14142
|
+
* Fail fast when the caller-declared `action` disagrees with the decoded
|
|
14143
|
+
* instructions, so the preview's headline can never mislabel what is signed
|
|
14144
|
+
* (e.g. a `deposit`-labeled call handed withdraw params). `takeFeeERC20` is an
|
|
14145
|
+
* auxiliary Circle-fee instruction and is allowed alongside any action.
|
|
14146
|
+
*/ function assertActionMatchesInstructions(action, instructions) {
|
|
14147
|
+
const allowed = ACTION_ALLOWED_METHODS[action];
|
|
14148
|
+
instructions.forEach((instruction, index)=>{
|
|
14149
|
+
if (instruction.method === 'takeFeeERC20') {
|
|
14150
|
+
return;
|
|
14151
|
+
}
|
|
14152
|
+
if (!allowed.has(instruction.method)) {
|
|
14153
|
+
throw decodeMismatchError(`decoded instruction method '${instruction.method}' does not match the '${action}' action`, {
|
|
14154
|
+
action,
|
|
14155
|
+
method: instruction.method,
|
|
14156
|
+
index
|
|
14157
|
+
});
|
|
14158
|
+
}
|
|
14159
|
+
});
|
|
14160
|
+
}
|
|
14161
|
+
/**
|
|
14162
|
+
* Decode a same-chain earn `execute()` bundle into a human-readable summary.
|
|
14163
|
+
*
|
|
14164
|
+
* Decodes every inner instruction in the service-signed `executionParams` — the
|
|
14165
|
+
* same object the SDK ABI-encodes into the transaction — so the returned decode
|
|
14166
|
+
* is a faithful, drift-free view of what the signer is authorizing: input token
|
|
14167
|
+
* and amount, target vault, receiver, any Circle fee, and claimed rewards. Fails
|
|
14168
|
+
* fast with a non-recoverable {@link KitError} if any instruction cannot be
|
|
14169
|
+
* decoded, rather than returning misleading data.
|
|
14170
|
+
*
|
|
14171
|
+
* @param input - Action, chain, adapter, and the signed execution params.
|
|
14172
|
+
* @returns The decoded transaction summary.
|
|
14173
|
+
* @throws {@link KitError} If an instruction's calldata cannot be decoded.
|
|
14174
|
+
*
|
|
14175
|
+
* @example
|
|
14176
|
+
* ```typescript
|
|
14177
|
+
* const decoded = decodeEarnExecute({
|
|
14178
|
+
* action: 'deposit',
|
|
14179
|
+
* chain: 'Arc_Testnet',
|
|
14180
|
+
* adapter: '0x7fb8c7260b63934d8da38af902f87ae6e284a845',
|
|
14181
|
+
* executionParams,
|
|
14182
|
+
* })
|
|
14183
|
+
* // decoded.summary -> { token: { address, amount }, vault, receiver }
|
|
14184
|
+
* ```
|
|
14185
|
+
*
|
|
14186
|
+
* @internal
|
|
14187
|
+
*/ function decodeEarnExecute(input) {
|
|
14188
|
+
const { action, chain, adapter, executionParams } = input;
|
|
14189
|
+
const envelope = requireInstructions(executionParams);
|
|
14190
|
+
const instructions = envelope.map((instruction, index)=>decodeInstruction(instruction, index));
|
|
14191
|
+
assertActionMatchesInstructions(action, instructions);
|
|
14192
|
+
return {
|
|
14193
|
+
action,
|
|
14194
|
+
chain,
|
|
14195
|
+
adapter,
|
|
14196
|
+
instructions,
|
|
14197
|
+
summary: buildSummary(instructions, envelope)
|
|
14198
|
+
};
|
|
14199
|
+
}
|
|
14200
|
+
/**
|
|
14201
|
+
* Assert that ABI-encoded Adapter `execute()` calldata encodes the same
|
|
14202
|
+
* instruction set as the service-signed execution params.
|
|
14203
|
+
*
|
|
14204
|
+
* Fail-fast preview check: the SDK encodes `execute(executeParams, ...)` locally,
|
|
14205
|
+
* so decoding those final bytes and comparing every field of each instruction
|
|
14206
|
+
* against the signed params proves the previewed instruction set matches what
|
|
14207
|
+
* will be signed. It compares `instructions[]` only — the outer `tokens`,
|
|
14208
|
+
* `execId`, `deadline`, and `metadata` are not re-compared here. The
|
|
14209
|
+
* authoritative integrity guarantee for the full signed struct is the on-chain
|
|
14210
|
+
* EIP-712 signature verification, which reverts if any signed field is altered.
|
|
14211
|
+
*
|
|
14212
|
+
* @param calldata - Encoded `execute()` calldata about to be signed.
|
|
14213
|
+
* @param executionParams - Service-signed execution params.
|
|
14214
|
+
* @throws {@link KitError} If the calldata is not an `execute()` call or any
|
|
14215
|
+
* instruction field differs from the signed params.
|
|
14216
|
+
*
|
|
14217
|
+
* @example
|
|
14218
|
+
* ```typescript
|
|
14219
|
+
* assertEarnCalldataMatchesExecuteParams(
|
|
14220
|
+
* prepared.getCallData().data,
|
|
14221
|
+
* executionParams,
|
|
14222
|
+
* )
|
|
14223
|
+
* ```
|
|
14224
|
+
*
|
|
14225
|
+
* @internal
|
|
14226
|
+
*/ function assertEarnCalldataMatchesExecuteParams(calldata, executionParams) {
|
|
14227
|
+
// adapterContractAbi declares only `execute`, so a successful decode is always
|
|
14228
|
+
// the execute() call; a non-execute selector throws inside
|
|
14229
|
+
// decodeExecuteCalldata above.
|
|
14230
|
+
const decoded = decodeExecuteCalldata(calldata);
|
|
14231
|
+
const encoded = decoded.args[0].instructions;
|
|
14232
|
+
const signed = requireInstructions(executionParams);
|
|
14233
|
+
// Compare each encoded instruction against its signed counterpart. Iterating
|
|
14234
|
+
// the encoded instructions and indexing the signed set keeps both mismatch
|
|
14235
|
+
// branches reachable: a signed set that is too short trips the guard below,
|
|
14236
|
+
// and one that is too long trips the post-loop check.
|
|
14237
|
+
encoded.forEach((instruction, index)=>{
|
|
14238
|
+
const signedInstruction = signed[index];
|
|
14239
|
+
if (signedInstruction === undefined) {
|
|
14240
|
+
throw decodeMismatchError(`signed params are missing instruction ${index.toString()}`, {
|
|
14241
|
+
index,
|
|
14242
|
+
encoded: encoded.length,
|
|
14243
|
+
signed: signed.length
|
|
14244
|
+
});
|
|
14245
|
+
}
|
|
14246
|
+
const path = `instructions[${index.toString()}]`;
|
|
14247
|
+
assertHexEqual(instruction.target, signedInstruction['target'], `${path}.target`);
|
|
14248
|
+
assertHexEqual(instruction.data, signedInstruction['data'], `${path}.data`);
|
|
14249
|
+
assertUintEqual(instruction.value, signedInstruction['value'], `${path}.value`);
|
|
14250
|
+
assertHexEqual(instruction.tokenIn, signedInstruction['tokenIn'], `${path}.tokenIn`);
|
|
14251
|
+
assertUintEqual(instruction.amountToApprove, signedInstruction['amountToApprove'], `${path}.amountToApprove`);
|
|
14252
|
+
assertHexEqual(instruction.tokenOut, signedInstruction['tokenOut'], `${path}.tokenOut`);
|
|
14253
|
+
assertUintEqual(instruction.minTokenOut, signedInstruction['minTokenOut'], `${path}.minTokenOut`);
|
|
14254
|
+
});
|
|
14255
|
+
if (signed.length > encoded.length) {
|
|
14256
|
+
throw decodeMismatchError('signed params contain more instructions than the encoded calldata', {
|
|
14257
|
+
encoded: encoded.length,
|
|
14258
|
+
signed: signed.length
|
|
14259
|
+
});
|
|
14260
|
+
}
|
|
14261
|
+
}
|
|
14262
|
+
/**
|
|
14263
|
+
* Assert two hex values are equal, case-insensitively (addresses and calldata).
|
|
14264
|
+
*/ function assertHexEqual(encoded, signed, path) {
|
|
14265
|
+
const signedHex = requireHex(signed, path);
|
|
14266
|
+
if (encoded.toLowerCase() !== signedHex.toLowerCase()) {
|
|
14267
|
+
throw decodeMismatchError(`${path} differs from signed params`, {
|
|
14268
|
+
path,
|
|
14269
|
+
encoded,
|
|
14270
|
+
signed: signedHex
|
|
14271
|
+
});
|
|
14272
|
+
}
|
|
14273
|
+
}
|
|
14274
|
+
/**
|
|
14275
|
+
* Assert an encoded bigint equals a signed `uint256`-like value.
|
|
14276
|
+
*/ function assertUintEqual(encoded, signed, path) {
|
|
14277
|
+
const signedUint = requireUint(signed, path);
|
|
14278
|
+
if (encoded !== signedUint) {
|
|
14279
|
+
throw decodeMismatchError(`${path} differs from signed params`, {
|
|
14280
|
+
path,
|
|
14281
|
+
encoded: encoded.toString(),
|
|
14282
|
+
signed: signedUint.toString()
|
|
14283
|
+
});
|
|
14284
|
+
}
|
|
14285
|
+
}
|
|
14286
|
+
|
|
14287
|
+
/**
|
|
14288
|
+
* Namespaced discriminator for the EarnKit authorization review.
|
|
14289
|
+
*
|
|
14290
|
+
* Applications match on this in an adapter `onBeforeAuthorize` hook to decide
|
|
14291
|
+
* whether the request carries EarnKit semantic data. Prefer the
|
|
14292
|
+
* {@link isEarnExecuteReview} type guard over comparing this string directly.
|
|
14293
|
+
*
|
|
14294
|
+
* @example
|
|
14295
|
+
* ```typescript
|
|
14296
|
+
* if (review?.kind === EARN_EXECUTE_REVIEW_KIND) { … }
|
|
14297
|
+
* ```
|
|
14298
|
+
*/ const EARN_EXECUTE_REVIEW_KIND = 'earn.execute';
|
|
14299
|
+
|
|
14300
|
+
/**
|
|
14301
|
+
* Build a fail-closed {@link KitError} for a review-construction failure.
|
|
14302
|
+
*
|
|
14303
|
+
* Marked non-recoverable: a review that cannot prove the calldata matches the
|
|
14304
|
+
* signed operation must abort authorization, never retry with misleading data.
|
|
14305
|
+
*/ function reviewError(message, trace) {
|
|
14306
|
+
return failClosedEarnError('Unable to build earn authorization review', message, trace);
|
|
14307
|
+
}
|
|
14308
|
+
/**
|
|
14309
|
+
* Narrow the canonical authorization payload to the single Adapter `execute()`
|
|
14310
|
+
* call a same-chain earn operation authorizes.
|
|
14311
|
+
*
|
|
14312
|
+
* Same-chain deposit, withdraw, and claim-rewards each authorize exactly one
|
|
14313
|
+
* `evm-calls` payload carrying one call. Anything else (typed data, a batch,
|
|
14314
|
+
* an empty call list) means this descriptor was attached to the wrong
|
|
14315
|
+
* authorization unit, so fail closed rather than decode misleading data.
|
|
14316
|
+
*/ function assertSingleEvmCallPayload(payload) {
|
|
14317
|
+
if (payload.type !== 'evm-calls') {
|
|
14318
|
+
throw reviewError(`expected an 'evm-calls' payload but received '${payload.type}'`, {
|
|
14319
|
+
type: payload.type
|
|
14320
|
+
});
|
|
14321
|
+
}
|
|
14322
|
+
const [call, ...rest] = payload.calls;
|
|
14323
|
+
if (call === undefined) {
|
|
14324
|
+
throw reviewError('the evm-calls payload contains no calls to review', {
|
|
14325
|
+
callCount: payload.calls.length
|
|
14326
|
+
});
|
|
14327
|
+
}
|
|
14328
|
+
if (rest.length > 0) {
|
|
14329
|
+
throw reviewError('a same-chain earn operation authorizes exactly one Adapter execute() call', {
|
|
14330
|
+
callCount: payload.calls.length
|
|
14331
|
+
});
|
|
14332
|
+
}
|
|
14333
|
+
return call;
|
|
14334
|
+
}
|
|
14335
|
+
/**
|
|
14336
|
+
* Select the final earn `execute()` call from an atomic Earn batch.
|
|
14337
|
+
*
|
|
14338
|
+
* Same-chain batched deposit/withdraw authorizes either `[execute]` when the
|
|
14339
|
+
* current allowance is sufficient, or `[approve, execute]` when a top-up is
|
|
14340
|
+
* required. Any other shape means the descriptor was attached to an
|
|
14341
|
+
* unexpected authorization unit, so fail closed.
|
|
14342
|
+
*/ function assertBatchedEarnExecuteCall(payload) {
|
|
14343
|
+
if (payload.type !== 'evm-calls') {
|
|
14344
|
+
throw reviewError(`expected an 'evm-calls' payload but received '${payload.type}'`, {
|
|
14345
|
+
type: payload.type
|
|
14346
|
+
});
|
|
14347
|
+
}
|
|
14348
|
+
if (payload.calls.length !== 1 && payload.calls.length !== 2) {
|
|
14349
|
+
throw reviewError('a batched earn operation authorizes [execute] or [approve, execute]', {
|
|
14350
|
+
callCount: payload.calls.length
|
|
14351
|
+
});
|
|
14352
|
+
}
|
|
14353
|
+
const executeCall = payload.calls.at(-1);
|
|
14354
|
+
if (executeCall === undefined) {
|
|
14355
|
+
throw reviewError('the earn batch contains no execute call to review', {
|
|
14356
|
+
callCount: payload.calls.length
|
|
14357
|
+
});
|
|
14358
|
+
}
|
|
14359
|
+
return executeCall;
|
|
14360
|
+
}
|
|
14361
|
+
/**
|
|
14362
|
+
* Map a canonical {@link EvmCall} to the {@link EarnEncodedTransaction} preview
|
|
14363
|
+
* shape, failing closed when the earn `execute()` call carries no calldata.
|
|
14364
|
+
*/ function toEarnEncodedTransaction(call) {
|
|
14365
|
+
if (call.data === undefined) {
|
|
14366
|
+
throw reviewError('the earn execute() call is missing calldata', {
|
|
14367
|
+
to: call.to
|
|
14368
|
+
});
|
|
14369
|
+
}
|
|
14370
|
+
return {
|
|
14371
|
+
to: call.to,
|
|
14372
|
+
data: call.data,
|
|
14373
|
+
...call.value !== undefined && {
|
|
14374
|
+
value: call.value
|
|
14375
|
+
}
|
|
14376
|
+
};
|
|
14377
|
+
}
|
|
14378
|
+
/**
|
|
14379
|
+
* Create an Earn authorization descriptor using the supplied canonical-payload
|
|
14380
|
+
* call selector.
|
|
14381
|
+
*
|
|
14382
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
14383
|
+
* @param selectCall - Fail-closed selector for the execute call under review.
|
|
14384
|
+
* @returns A lazy descriptor that decodes and verifies the selected call.
|
|
14385
|
+
*
|
|
14386
|
+
* @internal
|
|
14387
|
+
*/ function createEarnExecuteDescriptor(input, selectCall) {
|
|
14388
|
+
const { action, chain, executionParams } = input;
|
|
14389
|
+
const createReview = (payload)=>{
|
|
14390
|
+
const call = selectCall(payload);
|
|
14391
|
+
const encoded = toEarnEncodedTransaction(call);
|
|
14392
|
+
const decoded = decodeEarnExecute({
|
|
14393
|
+
action,
|
|
14394
|
+
chain,
|
|
14395
|
+
adapter: encoded.to,
|
|
14396
|
+
executionParams
|
|
14397
|
+
});
|
|
14398
|
+
// Prove the calldata about to be signed encodes the same instruction set as
|
|
14399
|
+
// the service-signed params. Throwing here aborts before the wallet prompt.
|
|
14400
|
+
assertEarnCalldataMatchesExecuteParams(encoded.data, executionParams);
|
|
14401
|
+
const review = {
|
|
14402
|
+
kind: EARN_EXECUTE_REVIEW_KIND,
|
|
14403
|
+
data: {
|
|
14404
|
+
encoded,
|
|
14405
|
+
decoded
|
|
14406
|
+
}
|
|
14407
|
+
};
|
|
14408
|
+
return review;
|
|
14409
|
+
};
|
|
14410
|
+
return {
|
|
14411
|
+
createReview
|
|
14412
|
+
};
|
|
14413
|
+
}
|
|
14414
|
+
/**
|
|
14415
|
+
* Build the lazy `earn.execute` authorization descriptor for a same-chain earn
|
|
14416
|
+
* action.
|
|
14417
|
+
*
|
|
14418
|
+
* The returned descriptor carries only a `createReview` factory — no intent
|
|
14419
|
+
* override, because the adapter's action system supplies the intent from the
|
|
14420
|
+
* action key. The factory is evaluated at most once, and only when the
|
|
14421
|
+
* application configured an adapter `onBeforeAuthorize` hook. When it runs it:
|
|
14422
|
+
*
|
|
14423
|
+
* 1. narrows the canonical payload to its single Adapter `execute()` call;
|
|
14424
|
+
* 2. lifts that call into an {@link EarnEncodedTransaction};
|
|
14425
|
+
* 3. decodes it into a `DecodedEarnTx`; and
|
|
14426
|
+
* 4. asserts the decoded calldata matches the service-signed params, throwing
|
|
14427
|
+
* (aborting authorization before the wallet or signer) on any mismatch.
|
|
14428
|
+
*
|
|
14429
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
14430
|
+
* @returns An authorization descriptor to pass as the fourth `prepareAction`
|
|
14431
|
+
* argument for the final earn action only (never the allowance approval).
|
|
14432
|
+
* @throws {@link KitError} From the review factory when the payload is not a
|
|
14433
|
+
* single earn `execute()` call or the calldata diverges from the signed
|
|
14434
|
+
* params. The throw surfaces through the adapter gate before authorization.
|
|
14435
|
+
*
|
|
14436
|
+
* @example
|
|
14437
|
+
* ```typescript
|
|
14438
|
+
* const descriptor = buildEarnExecuteDescriptor({
|
|
14439
|
+
* action: 'deposit',
|
|
14440
|
+
* chain: 'Arc_Testnet',
|
|
14441
|
+
* executionParams,
|
|
14442
|
+
* })
|
|
14443
|
+
* await adapter.prepareAction('earn.deposit', actionParams, ctx, {
|
|
14444
|
+
* authorization: descriptor,
|
|
14445
|
+
* })
|
|
14446
|
+
* ```
|
|
14447
|
+
*
|
|
14448
|
+
* @internal
|
|
14449
|
+
*/ function buildEarnExecuteDescriptor(input) {
|
|
14450
|
+
return createEarnExecuteDescriptor(input, assertSingleEvmCallPayload);
|
|
14451
|
+
}
|
|
14452
|
+
/**
|
|
14453
|
+
* Build a lazy `earn.execute` authorization descriptor for an atomic Earn
|
|
14454
|
+
* batch containing either `[execute]` or `[approve, execute]`.
|
|
12851
14455
|
*
|
|
12852
|
-
*
|
|
12853
|
-
*
|
|
12854
|
-
*
|
|
14456
|
+
* The review always decodes and verifies the final call against the
|
|
14457
|
+
* service-signed execution params. Unexpected payload types and call counts
|
|
14458
|
+
* fail closed before wallet authorization.
|
|
14459
|
+
*
|
|
14460
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
14461
|
+
* @returns A descriptor suitable for `batchExecute` authorization options.
|
|
14462
|
+
* @throws {@link KitError} From the lazy review factory when the batch shape or
|
|
14463
|
+
* final execute calldata cannot be verified.
|
|
12855
14464
|
*
|
|
12856
14465
|
* @internal
|
|
12857
|
-
*/ function
|
|
12858
|
-
|
|
12859
|
-
executionParams.instructions.forEach((instruction, index)=>{
|
|
12860
|
-
const amount = BigInt(instruction.amountToApprove);
|
|
12861
|
-
if (amount <= 0n) {
|
|
12862
|
-
return;
|
|
12863
|
-
}
|
|
12864
|
-
const { tokenIn } = instruction;
|
|
12865
|
-
if (approvedToken === undefined) {
|
|
12866
|
-
approvedToken = tokenIn;
|
|
12867
|
-
return;
|
|
12868
|
-
}
|
|
12869
|
-
if (!isSameAddress(tokenIn, approvedToken)) {
|
|
12870
|
-
throw createValidationFailedError(`executionParams.instructions[${index.toString()}].tokenIn`, tokenIn, 'tokenIn must match the token approved for adapter spending');
|
|
12871
|
-
}
|
|
12872
|
-
});
|
|
12873
|
-
return approvedToken;
|
|
14466
|
+
*/ function buildBatchedEarnExecuteDescriptor(input) {
|
|
14467
|
+
return createEarnExecuteDescriptor(input, assertBatchedEarnExecuteCall);
|
|
12874
14468
|
}
|
|
12875
14469
|
|
|
12876
14470
|
/**
|
|
@@ -12899,16 +14493,37 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
12899
14493
|
* address,
|
|
12900
14494
|
* actionKey: 'earn.deposit',
|
|
12901
14495
|
* actionParams: { executeParams, tokenInputs, signature },
|
|
14496
|
+
* action: 'deposit',
|
|
14497
|
+
* executionParams,
|
|
12902
14498
|
* revertMessage: 'Earn deposit reverted on-chain',
|
|
12903
14499
|
* })
|
|
12904
14500
|
* ```
|
|
12905
14501
|
*
|
|
12906
14502
|
* @internal
|
|
12907
14503
|
*/ async function executeEarnAction(params) {
|
|
12908
|
-
const { adapter, chain, address, actionKey, actionParams, revertMessage } = params;
|
|
14504
|
+
const { adapter, chain, address, actionKey, actionParams, action, executionParams, revertMessage } = params;
|
|
14505
|
+
// Attach the lazy `earn.execute` review to the final earn action only (never
|
|
14506
|
+
// the allowance approval, which runs on a separate path). The adapter's
|
|
14507
|
+
// action system supplies the intent from `actionKey`, so the descriptor
|
|
14508
|
+
// carries only the review factory. The factory is evaluated at most once,
|
|
14509
|
+
// and only when the application configured an `onBeforeAuthorize` hook.
|
|
14510
|
+
const authorization = buildEarnExecuteDescriptor({
|
|
14511
|
+
action,
|
|
14512
|
+
// The provider validates the chain is Earn-supported in
|
|
14513
|
+
// `resolveAdapterContext` before reaching execute, so the concrete chain
|
|
14514
|
+
// identifier is a valid `EarnChainIdentifier`. It is carried through to the
|
|
14515
|
+
// decoded preview's display `chain` field only.
|
|
14516
|
+
chain: chain.chain,
|
|
14517
|
+
executionParams
|
|
14518
|
+
});
|
|
14519
|
+
// The abstract `Adapter.prepareAction` is 3-arg; the fourth authorization
|
|
14520
|
+
// argument lives on the `withLegacyCompat` wrapper that produced the concrete
|
|
14521
|
+
// adapter passed here. Narrow the single seam that threads the descriptor.
|
|
12909
14522
|
const prepared = await adapter.prepareAction(actionKey, actionParams, {
|
|
12910
14523
|
chain,
|
|
12911
14524
|
address
|
|
14525
|
+
}, {
|
|
14526
|
+
authorization
|
|
12912
14527
|
});
|
|
12913
14528
|
const gasLimitOverride = await estimateBufferedGasLimit(prepared);
|
|
12914
14529
|
const txHash = prepared.type === 'evm' && gasLimitOverride !== undefined ? await prepared.execute({
|
|
@@ -12933,6 +14548,278 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
12933
14548
|
};
|
|
12934
14549
|
}
|
|
12935
14550
|
|
|
14551
|
+
/**
|
|
14552
|
+
* Decide whether a same-chain earn action should be submitted as a single
|
|
14553
|
+
* atomic batch.
|
|
14554
|
+
*
|
|
14555
|
+
* Returns `true` only when the consumer has not opted out
|
|
14556
|
+
* (`batchTransactions !== false`), the source chain is EVM, the adapter
|
|
14557
|
+
* structurally exposes the shared batch methods, and the wallet reports atomic
|
|
14558
|
+
* batch support. `address` is forwarded as `fromAddress` so developer-controlled
|
|
14559
|
+
* adapters can probe the specific wallet. Any thrown capability probe is
|
|
14560
|
+
* treated as "no support".
|
|
14561
|
+
*
|
|
14562
|
+
* @param params - Adapter, chain, address, and the resolved `batchTransactions` flag.
|
|
14563
|
+
* @returns `true` when batched execution should be attempted.
|
|
14564
|
+
*
|
|
14565
|
+
* @example
|
|
14566
|
+
* ```typescript
|
|
14567
|
+
* if (await shouldUseBatchedEarnAction({ adapter, chain, address, batchTransactions })) {
|
|
14568
|
+
* // take the batched approve + execute path
|
|
14569
|
+
* }
|
|
14570
|
+
* ```
|
|
14571
|
+
*
|
|
14572
|
+
* @internal
|
|
14573
|
+
*/ async function shouldUseBatchedEarnAction(params) {
|
|
14574
|
+
const { adapter, chain, address, batchTransactions } = params;
|
|
14575
|
+
if (batchTransactions === false) {
|
|
14576
|
+
return false;
|
|
14577
|
+
}
|
|
14578
|
+
if (chain.type !== 'evm') {
|
|
14579
|
+
return false;
|
|
14580
|
+
}
|
|
14581
|
+
const candidate = adapter;
|
|
14582
|
+
if (typeof candidate.supportsAtomicBatch !== 'function' || typeof candidate.batchExecute !== 'function') {
|
|
14583
|
+
return false;
|
|
14584
|
+
}
|
|
14585
|
+
try {
|
|
14586
|
+
return await candidate.supportsAtomicBatch(chain, {
|
|
14587
|
+
fromAddress: address
|
|
14588
|
+
});
|
|
14589
|
+
} catch {
|
|
14590
|
+
return false;
|
|
14591
|
+
}
|
|
14592
|
+
}
|
|
14593
|
+
async function buildSuccessfulBatchResult(adapter, chain, receipt, batchId, revertMessage) {
|
|
14594
|
+
const transaction = {
|
|
14595
|
+
txHash: receipt.txHash,
|
|
14596
|
+
explorerUrl: buildExplorerUrl(chain, receipt.txHash)
|
|
14597
|
+
};
|
|
14598
|
+
let confirmed;
|
|
14599
|
+
try {
|
|
14600
|
+
confirmed = await adapter.waitForTransaction(receipt.txHash, {
|
|
14601
|
+
confirmations: 1
|
|
14602
|
+
}, chain);
|
|
14603
|
+
} catch {
|
|
14604
|
+
// The batch adapter already confirmed success. Receipt enrichment is
|
|
14605
|
+
// telemetry-only, so an additional RPC failure must not turn an accepted
|
|
14606
|
+
// money-moving operation into a retryable business failure.
|
|
14607
|
+
return transaction;
|
|
14608
|
+
}
|
|
14609
|
+
if (confirmed.status === 'reverted') {
|
|
14610
|
+
throw createTransactionRevertedError(chain.name, revertMessage, {
|
|
14611
|
+
batchId
|
|
14612
|
+
}, receipt.txHash, transaction.explorerUrl);
|
|
14613
|
+
}
|
|
14614
|
+
return {
|
|
14615
|
+
...transaction,
|
|
14616
|
+
...confirmed.gasUsed !== undefined && {
|
|
14617
|
+
gasUsed: confirmed.gasUsed
|
|
14618
|
+
},
|
|
14619
|
+
...confirmed.effectiveGasPrice !== undefined && {
|
|
14620
|
+
effectiveGasPrice: confirmed.effectiveGasPrice
|
|
14621
|
+
}
|
|
14622
|
+
};
|
|
14623
|
+
}
|
|
14624
|
+
function throwBatchFailure(result, executeReceipt, chain, actionKey, revertMessage) {
|
|
14625
|
+
const cause = result.error;
|
|
14626
|
+
if (result.statusCode === 400) {
|
|
14627
|
+
throw new KitError({
|
|
14628
|
+
...RpcError.ENDPOINT_ERROR,
|
|
14629
|
+
recoverability: 'RETRYABLE',
|
|
14630
|
+
message: `Batched earn ${actionKey} failed off-chain before inclusion (batch ${result.batchId}).`,
|
|
14631
|
+
cause: {
|
|
14632
|
+
trace: {
|
|
14633
|
+
batchId: result.batchId,
|
|
14634
|
+
statusCode: result.statusCode,
|
|
14635
|
+
cause
|
|
14636
|
+
}
|
|
14637
|
+
}
|
|
14638
|
+
});
|
|
14639
|
+
}
|
|
14640
|
+
const causeTrace = cause instanceof KitError && typeof cause.cause?.trace === 'object' && cause.cause.trace !== null ? cause.cause.trace : undefined;
|
|
14641
|
+
if (cause instanceof KitError && causeTrace?.['kind'] === 'failed_offchain') {
|
|
14642
|
+
throw cause;
|
|
14643
|
+
}
|
|
14644
|
+
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 !== '';
|
|
14645
|
+
if (isConfirmedRevert) {
|
|
14646
|
+
throw createTransactionRevertedError(chain.name, revertMessage, {
|
|
14647
|
+
batchId: result.batchId,
|
|
14648
|
+
error: cause
|
|
14649
|
+
});
|
|
14650
|
+
}
|
|
14651
|
+
throw new KitError({
|
|
14652
|
+
...NetworkError.TIMEOUT,
|
|
14653
|
+
recoverability: 'FATAL',
|
|
14654
|
+
message: `Batched earn ${actionKey} was submitted (batch ${result.batchId}) but its outcome could not be confirmed; check the transaction status before retrying.`,
|
|
14655
|
+
cause: {
|
|
14656
|
+
trace: {
|
|
14657
|
+
batchId: result.batchId,
|
|
14658
|
+
cause
|
|
14659
|
+
}
|
|
14660
|
+
}
|
|
14661
|
+
});
|
|
14662
|
+
}
|
|
14663
|
+
/**
|
|
14664
|
+
* Execute the `approve` and `execute` steps of a same-chain earn action as a
|
|
14665
|
+
* single atomic batch.
|
|
14666
|
+
*
|
|
14667
|
+
* Prepare both `PreparedChainRequest` objects upfront, extract their raw call
|
|
14668
|
+
* data via `getCallData()`, then submit both through the adapter's shared
|
|
14669
|
+
* `batchExecute`. `address` is forwarded as `opts.fromAddress` so
|
|
14670
|
+
* developer-controlled adapters batch on behalf of the right wallet;
|
|
14671
|
+
* `idempotencyKey` is forwarded for adapters that deduplicate ambiguous
|
|
14672
|
+
* submissions (the Circle developer-controlled adapter reuses the Earn
|
|
14673
|
+
* execution id); other adapters may ignore either option. Reused by both the
|
|
14674
|
+
* deposit and withdraw flows via the `actionKey` parameter.
|
|
14675
|
+
*
|
|
14676
|
+
* @param params - Adapter, chain, action key, signed payload, and approval inputs.
|
|
14677
|
+
* @returns The confirmed execute transaction hash, explorer URL, and receipt
|
|
14678
|
+
* gas data when the adapter can retrieve it.
|
|
14679
|
+
* @throws {@link KitError} when the source chain is not EVM.
|
|
14680
|
+
* @throws {@link KitError} when calldata extraction (`getCallData`) is not
|
|
14681
|
+
* supported by the prepared requests.
|
|
14682
|
+
* @throws {@link KitError} when the batch reverts on-chain (a confirmed
|
|
14683
|
+
* terminal revert), carrying `batchId`.
|
|
14684
|
+
* @throws {@link KitError} RETRYABLE when EIP-5792 reports an off-chain
|
|
14685
|
+
* failure carrying `batchId` and status code `400`; no call was included.
|
|
14686
|
+
* @throws {@link KitError} FATAL `NetworkError.TIMEOUT` when the batch was
|
|
14687
|
+
* submitted but its outcome could not be confirmed (poll timeout or any
|
|
14688
|
+
* other non-revert post-submission failure); carries `batchId` so the caller
|
|
14689
|
+
* can check transaction status before retrying.
|
|
14690
|
+
* @remarks
|
|
14691
|
+
* Once the batch has been submitted this function does not fall back to the
|
|
14692
|
+
* sequential path — the batch is already on its way, so a fallback would risk
|
|
14693
|
+
* double-spend. Post-submission failures surface through the adapter's batch
|
|
14694
|
+
* result: a confirmed on-chain revert (Circle: a `TRANSACTION_REVERTED` cause;
|
|
14695
|
+
* Viem: status code `500`/`600`) is thrown as a revert error, status code `400`
|
|
14696
|
+
* is reported as a retryable off-chain failure, and any other unconfirmed
|
|
14697
|
+
* outcome is thrown as a FATAL timeout error carrying `batchId`.
|
|
14698
|
+
*
|
|
14699
|
+
* @example
|
|
14700
|
+
* ```typescript
|
|
14701
|
+
* const { txHash, explorerUrl } = await executeBatchedEarnAction({
|
|
14702
|
+
* adapter,
|
|
14703
|
+
* chain,
|
|
14704
|
+
* address,
|
|
14705
|
+
* actionKey: 'earn.deposit',
|
|
14706
|
+
* executeParams,
|
|
14707
|
+
* tokenInputs,
|
|
14708
|
+
* signature,
|
|
14709
|
+
* approvalToken: usdcAddress,
|
|
14710
|
+
* delegate: adapterContractAddress,
|
|
14711
|
+
* requiredAllowance: 1_000_000n,
|
|
14712
|
+
* idempotencyKey: '550e8400-e29b-41d4-a716-446655440000',
|
|
14713
|
+
* revertMessage: 'Earn deposit reverted on-chain',
|
|
14714
|
+
* })
|
|
14715
|
+
* ```
|
|
14716
|
+
*
|
|
14717
|
+
* @internal
|
|
14718
|
+
*/ async function executeBatchedEarnAction(params) {
|
|
14719
|
+
const { adapter, chain, address, actionKey, executeParams, tokenInputs, signature, approvalToken, delegate, requiredAllowance, idempotencyKey, revertMessage } = params;
|
|
14720
|
+
if (chain.type !== 'evm') {
|
|
14721
|
+
throw new KitError({
|
|
14722
|
+
...InputError.INVALID_CHAIN,
|
|
14723
|
+
recoverability: 'FATAL',
|
|
14724
|
+
message: 'Batched earn execution is only supported on EVM chains.'
|
|
14725
|
+
});
|
|
14726
|
+
}
|
|
14727
|
+
const evmChain = chain;
|
|
14728
|
+
const batchAdapter = adapter;
|
|
14729
|
+
// Read the current allowance so the approval tops up only the missing amount.
|
|
14730
|
+
// When the existing allowance already covers the payload, skip the approve
|
|
14731
|
+
// call and batch only the execute — this mirrors the sequential
|
|
14732
|
+
// approveAllowanceIfNeeded guard and avoids an increaseAllowance underflow
|
|
14733
|
+
// (requiredAllowance - currentAllowance would be negative, which reverts as
|
|
14734
|
+
// an out-of-range uint256).
|
|
14735
|
+
const allowancePrepared = await adapter.prepareAction('token.allowance', {
|
|
14736
|
+
tokenAddress: approvalToken,
|
|
14737
|
+
delegate
|
|
14738
|
+
}, {
|
|
14739
|
+
chain,
|
|
14740
|
+
address
|
|
14741
|
+
});
|
|
14742
|
+
const currentAllowance = parseAllowanceResponse(await allowancePrepared.execute());
|
|
14743
|
+
const approvalNeeded = currentAllowance < requiredAllowance;
|
|
14744
|
+
const executePrepared = await adapter.prepareAction(actionKey, {
|
|
14745
|
+
executeParams,
|
|
14746
|
+
tokenInputs,
|
|
14747
|
+
signature
|
|
14748
|
+
}, {
|
|
14749
|
+
chain,
|
|
14750
|
+
address
|
|
14751
|
+
});
|
|
14752
|
+
const approvePrepared = approvalNeeded ? await prepareApprovalAction({
|
|
14753
|
+
adapter,
|
|
14754
|
+
chain,
|
|
14755
|
+
address,
|
|
14756
|
+
tokenAddress: approvalToken,
|
|
14757
|
+
delegate,
|
|
14758
|
+
currentAllowance,
|
|
14759
|
+
requiredAllowance
|
|
14760
|
+
}) : undefined;
|
|
14761
|
+
if (executePrepared.type !== 'evm' || !executePrepared.getCallData) {
|
|
14762
|
+
throw new KitError({
|
|
14763
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
14764
|
+
recoverability: 'FATAL',
|
|
14765
|
+
message: 'Batched earn execution requires EVM prepared requests with getCallData() support.'
|
|
14766
|
+
});
|
|
14767
|
+
}
|
|
14768
|
+
if (approvePrepared !== undefined && (approvePrepared.type !== 'evm' || !approvePrepared.getCallData)) {
|
|
14769
|
+
throw new KitError({
|
|
14770
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
14771
|
+
recoverability: 'FATAL',
|
|
14772
|
+
message: 'Batched earn execution requires EVM prepared requests with getCallData() support.'
|
|
14773
|
+
});
|
|
14774
|
+
}
|
|
14775
|
+
const executeCallData = executePrepared.getCallData();
|
|
14776
|
+
// Prepend the approve call only when an allowance top-up is required.
|
|
14777
|
+
const calls = approvePrepared?.type === 'evm' && approvePrepared.getCallData ? [
|
|
14778
|
+
approvePrepared.getCallData(),
|
|
14779
|
+
executeCallData
|
|
14780
|
+
] : [
|
|
14781
|
+
executeCallData
|
|
14782
|
+
];
|
|
14783
|
+
const authorization = buildBatchedEarnExecuteDescriptor({
|
|
14784
|
+
action: actionKey === 'earn.deposit' ? 'deposit' : 'withdraw',
|
|
14785
|
+
chain: evmChain.chain,
|
|
14786
|
+
executionParams: executeParams
|
|
14787
|
+
});
|
|
14788
|
+
const result = await batchAdapter.batchExecute(calls, evmChain, {
|
|
14789
|
+
fromAddress: address,
|
|
14790
|
+
idempotencyKey,
|
|
14791
|
+
atomicRequired: true,
|
|
14792
|
+
authorization
|
|
14793
|
+
});
|
|
14794
|
+
// Success fans one confirmed hash across every receipt; the execute call is
|
|
14795
|
+
// the last one (approve, if present, precedes it). On failure a confirming
|
|
14796
|
+
// adapter returns no receipts, so a missing/non-success last receipt — or a
|
|
14797
|
+
// populated `result.error` — means the batch failed after submission (point
|
|
14798
|
+
// of no return). We never fall back, which would double-spend.
|
|
14799
|
+
const receiptCountMatches = result.receipts.length === calls.length;
|
|
14800
|
+
const executeReceipt = receiptCountMatches ? result.receipts[calls.length - 1] : undefined;
|
|
14801
|
+
const succeeded = receiptCountMatches && (result.statusCode === undefined || result.statusCode === 200) && result.error === undefined && executeReceipt?.status === 'success' && executeReceipt.txHash !== '';
|
|
14802
|
+
if (succeeded) {
|
|
14803
|
+
return buildSuccessfulBatchResult(adapter, evmChain, executeReceipt, result.batchId, revertMessage);
|
|
14804
|
+
}
|
|
14805
|
+
// Distinguish an off-chain rejection, a confirmed on-chain revert, and an
|
|
14806
|
+
// unknown outcome across both adapter conventions that share this contract:
|
|
14807
|
+
// - Circle SCA: no receipts + `error`; its trace kind identifies an
|
|
14808
|
+
// off-chain rejection, confirmed revert, or unconfirmed outcome.
|
|
14809
|
+
// - Viem EIP-5792: statusCode 500/600 explicitly confirms an on-chain
|
|
14810
|
+
// full/partial revert.
|
|
14811
|
+
// - Legacy/string-status wallets: a real-hash error receipt with no cause
|
|
14812
|
+
// is the best available confirmed-revert signal.
|
|
14813
|
+
// statusCode 400 is terminal but off-chain: the wallet confirms no call was
|
|
14814
|
+
// included, so it must not be labeled as a revert or unknown outcome.
|
|
14815
|
+
// Anything else — a poll timeout or any other post-submission failure with no
|
|
14816
|
+
// confirmed-revert signal — means the batch was submitted but its fate is
|
|
14817
|
+
// unconfirmed. Surface that as a FATAL (non-auto-retry) error carrying
|
|
14818
|
+
// `batchId` so the caller checks status before retrying, rather than
|
|
14819
|
+
// mislabeling it a revert.
|
|
14820
|
+
return throwBatchFailure(result, executeReceipt, evmChain, actionKey, revertMessage);
|
|
14821
|
+
}
|
|
14822
|
+
|
|
12936
14823
|
/**
|
|
12937
14824
|
* Validate that a service-signed execution payload has not expired before
|
|
12938
14825
|
* the SDK asks the wallet to broadcast a transaction.
|
|
@@ -14229,7 +16116,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
14229
16116
|
}
|
|
14230
16117
|
|
|
14231
16118
|
var name = "@circle-fin/provider-earn-service";
|
|
14232
|
-
var version = "1.
|
|
16119
|
+
var version = "1.4.0";
|
|
14233
16120
|
var pkg = {
|
|
14234
16121
|
name: name,
|
|
14235
16122
|
version: version};
|
|
@@ -14291,15 +16178,25 @@ var pkg = {
|
|
|
14291
16178
|
*
|
|
14292
16179
|
* @internal
|
|
14293
16180
|
*/ function buildConfig(serviceConfig) {
|
|
16181
|
+
// The kit key is a server-only secret. Reject it in the browser so it cannot
|
|
16182
|
+
// leak into a client bundle (no-op in Node.js). Keyless usage stays allowed.
|
|
16183
|
+
if (serviceConfig?.kitKey !== undefined && isBrowserEnvironment()) {
|
|
16184
|
+
throw createValidationFailedError('kitKey', '[redacted]', 'kitKey must not be provided in a browser environment — it is a server-only secret. Run EarnKit operations that use a kit key on your server, or omit kitKey to use the permissionless (keyless) client path');
|
|
16185
|
+
}
|
|
14294
16186
|
const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
|
|
14295
|
-
|
|
16187
|
+
// The API CORS policy does not allow this custom header. Keep the existing
|
|
16188
|
+
// per-request version attribution for Node callers, but omit it in browsers
|
|
16189
|
+
// so public EarnKit endpoints do not fail at CORS preflight.
|
|
16190
|
+
const sdkVersionHeader = isNodeEnvironment() ? {
|
|
16191
|
+
[SDK_VERSION_HEADER]: resolveSdkVersionHeader()
|
|
16192
|
+
} : {};
|
|
14296
16193
|
if (serviceConfig?.kitKey === undefined) {
|
|
14297
16194
|
return {
|
|
14298
16195
|
pollingConfig: {
|
|
14299
16196
|
...DEFAULT_CONFIG,
|
|
14300
16197
|
headers: {
|
|
14301
16198
|
...DEFAULT_CONFIG.headers,
|
|
14302
|
-
|
|
16199
|
+
...sdkVersionHeader
|
|
14303
16200
|
}
|
|
14304
16201
|
},
|
|
14305
16202
|
baseUrl
|
|
@@ -14317,7 +16214,7 @@ var pkg = {
|
|
|
14317
16214
|
...DEFAULT_CONFIG,
|
|
14318
16215
|
headers: {
|
|
14319
16216
|
...DEFAULT_CONFIG.headers,
|
|
14320
|
-
|
|
16217
|
+
...sdkVersionHeader,
|
|
14321
16218
|
Authorization: `Bearer ${serviceConfig.kitKey}`
|
|
14322
16219
|
}
|
|
14323
16220
|
},
|
|
@@ -15847,6 +17744,46 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
15847
17744
|
const approvalToken = resolveEarnApprovalToken(executionParams);
|
|
15848
17745
|
const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
|
|
15849
17746
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
17747
|
+
const approvalNeeded = !options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n;
|
|
17748
|
+
// Batch-capable wallets bundle approve + deposit into one atomic
|
|
17749
|
+
// submission. Only attempt this when an approval is actually needed.
|
|
17750
|
+
if (approvalNeeded && approvalToken !== undefined && await shouldUseBatchedEarnAction({
|
|
17751
|
+
adapter,
|
|
17752
|
+
chain,
|
|
17753
|
+
address,
|
|
17754
|
+
batchTransactions: config?.batchTransactions
|
|
17755
|
+
})) {
|
|
17756
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>{
|
|
17757
|
+
try {
|
|
17758
|
+
const result = await executeBatchedEarnAction({
|
|
17759
|
+
adapter,
|
|
17760
|
+
chain,
|
|
17761
|
+
address,
|
|
17762
|
+
actionKey: 'earn.deposit',
|
|
17763
|
+
executeParams: executionParams,
|
|
17764
|
+
tokenInputs,
|
|
17765
|
+
signature,
|
|
17766
|
+
approvalToken,
|
|
17767
|
+
delegate: adapterContractAddress,
|
|
17768
|
+
requiredAllowance,
|
|
17769
|
+
idempotencyKey: execId,
|
|
17770
|
+
revertMessage: 'Earn deposit reverted on-chain'
|
|
17771
|
+
});
|
|
17772
|
+
reportTransactionSuccess(transactionReportContext, 'Deposit', result);
|
|
17773
|
+
return result;
|
|
17774
|
+
} catch (error) {
|
|
17775
|
+
reportTransactionFailure(transactionReportContext, 'Deposit', error);
|
|
17776
|
+
throw error;
|
|
17777
|
+
}
|
|
17778
|
+
}, ({ txHash })=>txHash);
|
|
17779
|
+
return {
|
|
17780
|
+
kind: 'same-chain',
|
|
17781
|
+
txHash,
|
|
17782
|
+
explorerUrl,
|
|
17783
|
+
vaultAddress,
|
|
17784
|
+
amount: params.amount
|
|
17785
|
+
};
|
|
17786
|
+
}
|
|
15850
17787
|
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
|
|
15851
17788
|
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
15852
17789
|
try {
|
|
@@ -15879,6 +17816,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
15879
17816
|
tokenInputs,
|
|
15880
17817
|
signature
|
|
15881
17818
|
},
|
|
17819
|
+
action: 'deposit',
|
|
17820
|
+
executionParams,
|
|
15882
17821
|
revertMessage: 'Earn deposit reverted on-chain'
|
|
15883
17822
|
});
|
|
15884
17823
|
reportTransactionSuccess(transactionReportContext, 'Deposit', result);
|
|
@@ -16010,6 +17949,44 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
16010
17949
|
const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
|
|
16011
17950
|
const approvalToken = tokenInputs[0]?.token;
|
|
16012
17951
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
17952
|
+
// Batch-capable wallets bundle approve + withdraw into one atomic
|
|
17953
|
+
// submission. Only attempt this when an approval is actually needed.
|
|
17954
|
+
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n && await shouldUseBatchedEarnAction({
|
|
17955
|
+
adapter,
|
|
17956
|
+
chain,
|
|
17957
|
+
address,
|
|
17958
|
+
batchTransactions: config?.batchTransactions
|
|
17959
|
+
})) {
|
|
17960
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'withdraw', 'execute', async ()=>{
|
|
17961
|
+
try {
|
|
17962
|
+
const result = await executeBatchedEarnAction({
|
|
17963
|
+
adapter,
|
|
17964
|
+
chain,
|
|
17965
|
+
address,
|
|
17966
|
+
actionKey: 'earn.withdraw',
|
|
17967
|
+
executeParams: executionParams,
|
|
17968
|
+
tokenInputs,
|
|
17969
|
+
signature,
|
|
17970
|
+
approvalToken,
|
|
17971
|
+
delegate: adapterContractAddress,
|
|
17972
|
+
requiredAllowance,
|
|
17973
|
+
idempotencyKey: execId,
|
|
17974
|
+
revertMessage: 'Earn withdraw reverted on-chain'
|
|
17975
|
+
});
|
|
17976
|
+
reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
|
|
17977
|
+
return result;
|
|
17978
|
+
} catch (error) {
|
|
17979
|
+
reportTransactionFailure(transactionReportContext, 'Withdraw', error);
|
|
17980
|
+
throw error;
|
|
17981
|
+
}
|
|
17982
|
+
}, ({ txHash })=>txHash);
|
|
17983
|
+
return {
|
|
17984
|
+
txHash,
|
|
17985
|
+
explorerUrl,
|
|
17986
|
+
vaultAddress,
|
|
17987
|
+
amount: params.amount
|
|
17988
|
+
};
|
|
17989
|
+
}
|
|
16013
17990
|
if (!options.skipApprove && approvalToken !== undefined) {
|
|
16014
17991
|
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
16015
17992
|
try {
|
|
@@ -16042,6 +18019,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
16042
18019
|
tokenInputs,
|
|
16043
18020
|
signature
|
|
16044
18021
|
},
|
|
18022
|
+
action: 'withdraw',
|
|
18023
|
+
executionParams,
|
|
16045
18024
|
revertMessage: 'Earn withdraw reverted on-chain'
|
|
16046
18025
|
});
|
|
16047
18026
|
reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
|
|
@@ -16121,6 +18100,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
16121
18100
|
tokenInputs: [],
|
|
16122
18101
|
signature
|
|
16123
18102
|
},
|
|
18103
|
+
action: 'claimRewards',
|
|
18104
|
+
executionParams,
|
|
16124
18105
|
revertMessage: 'Earn claim rewards reverted on-chain'
|
|
16125
18106
|
}), ({ txHash })=>txHash);
|
|
16126
18107
|
return {
|
|
@@ -16421,6 +18402,12 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
|
|
|
16421
18402
|
if (config.providers !== undefined && !Array.isArray(config.providers)) {
|
|
16422
18403
|
throw createValidationFailedError('config.providers', config.providers, 'providers must be an array of earn providers when provided');
|
|
16423
18404
|
}
|
|
18405
|
+
if (config.disableAnalytics !== undefined && typeof config.disableAnalytics !== 'boolean') {
|
|
18406
|
+
throw createValidationFailedError('config.disableAnalytics', config.disableAnalytics, 'disableAnalytics must be a boolean when provided');
|
|
18407
|
+
}
|
|
18408
|
+
if (config.disableErrorReporting !== undefined && typeof config.disableErrorReporting !== 'boolean') {
|
|
18409
|
+
throw createValidationFailedError('config.disableErrorReporting', config.disableErrorReporting, 'disableErrorReporting must be a boolean when provided');
|
|
18410
|
+
}
|
|
16424
18411
|
const defaultProviders = getDefaultProviders();
|
|
16425
18412
|
const providers = [
|
|
16426
18413
|
...config.providers ?? [],
|
|
@@ -16432,6 +18419,31 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
|
|
|
16432
18419
|
return context;
|
|
16433
18420
|
}
|
|
16434
18421
|
|
|
18422
|
+
/**
|
|
18423
|
+
* Register Earn Kit telemetry event type strings with the shared registry so
|
|
18424
|
+
* error telemetry helpers remain compile-time checked.
|
|
18425
|
+
*
|
|
18426
|
+
* @internal
|
|
18427
|
+
*/ /**
|
|
18428
|
+
* Telemetry event type identifiers for Earn Kit operations.
|
|
18429
|
+
*
|
|
18430
|
+
* @internal
|
|
18431
|
+
*/ const EARN_EVENT_TYPES = {
|
|
18432
|
+
GET_VAULTS: 'earn_get_vaults',
|
|
18433
|
+
EXPLORE_VAULTS: 'earn_explore_vaults',
|
|
18434
|
+
GET_POSITION: 'earn_get_position',
|
|
18435
|
+
GET_CROSS_CHAIN_DEPOSIT_STATUS: 'earn_get_cross_chain_deposit_status',
|
|
18436
|
+
WAIT_FOR_CROSS_CHAIN_DEPOSIT: 'earn_wait_for_cross_chain_deposit',
|
|
18437
|
+
DEPOSIT: 'earn_deposit',
|
|
18438
|
+
CROSS_CHAIN_DEPOSIT: 'earn_cross_chain_deposit',
|
|
18439
|
+
WITHDRAW: 'earn_withdraw',
|
|
18440
|
+
CLAIM_REWARDS: 'earn_claim_rewards',
|
|
18441
|
+
GET_DEPOSIT_QUOTE: 'earn_get_deposit_quote',
|
|
18442
|
+
GET_WITHDRAWAL_QUOTE: 'earn_get_withdrawal_quote',
|
|
18443
|
+
GET_CLAIM_REWARDS_QUOTE: 'earn_get_claim_rewards_quote',
|
|
18444
|
+
RETRY: 'earn_retry'
|
|
18445
|
+
};
|
|
18446
|
+
|
|
16435
18447
|
/**
|
|
16436
18448
|
* Format a provider amount object as a human-readable decimal string.
|
|
16437
18449
|
*
|
|
@@ -16773,11 +18785,16 @@ const sourceAdapterContextSchema = zod.z.object({
|
|
|
16773
18785
|
*
|
|
16774
18786
|
* Validate the optional Kit Key field using the standard `apiKeySchema`
|
|
16775
18787
|
* format (`KIT_KEY:<keyId>:<keySecret>`). When omitted, the SDK
|
|
16776
|
-
* operates in permissionless mode.
|
|
18788
|
+
* operates in permissionless mode. `baseUrl` overrides the Earn Service
|
|
18789
|
+
* endpoint (e.g. staging); `batchTransactions: false` opts out of atomic
|
|
18790
|
+
* batched execution. Both are forwarded to the provider, so this `.strict()`
|
|
18791
|
+
* schema must accept them or a valid config object is rejected.
|
|
16777
18792
|
*
|
|
16778
18793
|
* @internal
|
|
16779
18794
|
*/ const earnConfigSchema = zod.z.object({
|
|
16780
|
-
kitKey: apiKeySchema.optional()
|
|
18795
|
+
kitKey: apiKeySchema.optional(),
|
|
18796
|
+
baseUrl: zod.z.string().optional(),
|
|
18797
|
+
batchTransactions: zod.z.boolean().optional()
|
|
16781
18798
|
}).strict();
|
|
16782
18799
|
/**
|
|
16783
18800
|
* Canonical decimal form: a leading digit with no leading zeros (a single
|
|
@@ -17947,6 +19964,14 @@ function hasCrossChainDestination(params) {
|
|
|
17947
19964
|
return formatClaimRewardsQuoteInfo(result);
|
|
17948
19965
|
}
|
|
17949
19966
|
|
|
19967
|
+
/** SDK name used in telemetry payloads. */ const SDK_NAME = resolveKitSdkName(pkg$1.name);
|
|
19968
|
+
/**
|
|
19969
|
+
* Determine whether deposit parameters target a destination chain.
|
|
19970
|
+
*
|
|
19971
|
+
* @internal
|
|
19972
|
+
*/ function isCrossChainDeposit(params) {
|
|
19973
|
+
return 'to' in params && params.to !== undefined;
|
|
19974
|
+
}
|
|
17950
19975
|
function formatRetryResult(operation, result) {
|
|
17951
19976
|
switch(operation){
|
|
17952
19977
|
case 'deposit':
|
|
@@ -17961,6 +19986,70 @@ function formatRetryResult(operation, result) {
|
|
|
17961
19986
|
}
|
|
17962
19987
|
}
|
|
17963
19988
|
}
|
|
19989
|
+
/**
|
|
19990
|
+
* Emit the success event corresponding to a completed retry.
|
|
19991
|
+
*
|
|
19992
|
+
* @internal
|
|
19993
|
+
*/ function emitRetrySuccessTelemetry(trace, result, config) {
|
|
19994
|
+
switch(trace.operation){
|
|
19995
|
+
case 'deposit':
|
|
19996
|
+
{
|
|
19997
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
19998
|
+
if ('to' in trace.params && trace.params.to !== undefined) {
|
|
19999
|
+
const destinationChain = resolveChainName(trace.params.to.chain);
|
|
20000
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, config, {
|
|
20001
|
+
...sourceChain != null && {
|
|
20002
|
+
sourceChain
|
|
20003
|
+
},
|
|
20004
|
+
...destinationChain != null && {
|
|
20005
|
+
destinationChain
|
|
20006
|
+
}
|
|
20007
|
+
});
|
|
20008
|
+
return;
|
|
20009
|
+
}
|
|
20010
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, config, {
|
|
20011
|
+
...sourceChain != null && {
|
|
20012
|
+
sourceChain
|
|
20013
|
+
},
|
|
20014
|
+
...'txHash' in result && {
|
|
20015
|
+
txHash: result.txHash
|
|
20016
|
+
}
|
|
20017
|
+
});
|
|
20018
|
+
return;
|
|
20019
|
+
}
|
|
20020
|
+
case 'withdraw':
|
|
20021
|
+
{
|
|
20022
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
20023
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, config, {
|
|
20024
|
+
...sourceChain != null && {
|
|
20025
|
+
sourceChain
|
|
20026
|
+
},
|
|
20027
|
+
...'txHash' in result && {
|
|
20028
|
+
txHash: result.txHash
|
|
20029
|
+
}
|
|
20030
|
+
});
|
|
20031
|
+
return;
|
|
20032
|
+
}
|
|
20033
|
+
case 'claimRewards':
|
|
20034
|
+
{
|
|
20035
|
+
if ('rewards' in result && result.status === 'claimed') {
|
|
20036
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
20037
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, config, {
|
|
20038
|
+
...sourceChain != null && {
|
|
20039
|
+
sourceChain
|
|
20040
|
+
},
|
|
20041
|
+
txHash: result.txHash
|
|
20042
|
+
});
|
|
20043
|
+
}
|
|
20044
|
+
return;
|
|
20045
|
+
}
|
|
20046
|
+
default:
|
|
20047
|
+
{
|
|
20048
|
+
const exhaustive = trace;
|
|
20049
|
+
throw createValidationFailedError('error.cause.trace', exhaustive, 'EarnKit.retry() does not support this earn operation');
|
|
20050
|
+
}
|
|
20051
|
+
}
|
|
20052
|
+
}
|
|
17964
20053
|
/**
|
|
17965
20054
|
* A high-level class-based interface for DeFi lending vault operations.
|
|
17966
20055
|
*
|
|
@@ -18019,6 +20108,8 @@ function formatRetryResult(operation, result) {
|
|
|
18019
20108
|
* ```
|
|
18020
20109
|
*/ class EarnKit {
|
|
18021
20110
|
context;
|
|
20111
|
+
/** Per-kit identity and opt-out state for error telemetry. */ telemetryConfig;
|
|
20112
|
+
/** Per-kit identity and opt-out state for success telemetry. */ analyticsTelemetryConfig;
|
|
18022
20113
|
/**
|
|
18023
20114
|
* Event dispatcher for step-level events emitted during multi-phase earn
|
|
18024
20115
|
* operations. Prefer {@link EarnKit.on} / {@link EarnKit.off} over using
|
|
@@ -18043,6 +20134,16 @@ function formatRetryResult(operation, result) {
|
|
|
18043
20134
|
*/ constructor(config = {}){
|
|
18044
20135
|
this.context = createEarnKitContext(config);
|
|
18045
20136
|
this.actionDispatcher = new Actionable();
|
|
20137
|
+
this.telemetryConfig = {
|
|
20138
|
+
sdkName: SDK_NAME,
|
|
20139
|
+
sdkVersion: pkg$1.version,
|
|
20140
|
+
disabled: config.disableErrorReporting === true
|
|
20141
|
+
};
|
|
20142
|
+
this.analyticsTelemetryConfig = {
|
|
20143
|
+
sdkName: SDK_NAME,
|
|
20144
|
+
sdkVersion: pkg$1.version,
|
|
20145
|
+
disabled: config.disableAnalytics === true
|
|
20146
|
+
};
|
|
18046
20147
|
for (const provider of this.context.providers){
|
|
18047
20148
|
provider.registerDispatcher(this.actionDispatcher);
|
|
18048
20149
|
}
|
|
@@ -18103,29 +20204,36 @@ function formatRetryResult(operation, result) {
|
|
|
18103
20204
|
* }
|
|
18104
20205
|
* ```
|
|
18105
20206
|
*/ async retry(error) {
|
|
18106
|
-
|
|
18107
|
-
|
|
18108
|
-
|
|
18109
|
-
|
|
18110
|
-
|
|
18111
|
-
|
|
18112
|
-
|
|
18113
|
-
|
|
18114
|
-
|
|
18115
|
-
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
|
|
18119
|
-
|
|
18120
|
-
|
|
18121
|
-
|
|
18122
|
-
|
|
18123
|
-
|
|
18124
|
-
|
|
18125
|
-
|
|
20207
|
+
const result = await withErrorTelemetry(async ()=>{
|
|
20208
|
+
if (!isKitError(error)) {
|
|
20209
|
+
throw createValidationFailedError('error', error, 'EarnKit.retry() requires a KitError thrown by a previous earn operation');
|
|
20210
|
+
}
|
|
20211
|
+
if (!isRetryableError$1(error)) {
|
|
20212
|
+
throw createValidationFailedError('error.recoverability', error.recoverability, 'EarnKit.retry() requires a retryable or resumable error — check isRetryableError(error) first');
|
|
20213
|
+
}
|
|
20214
|
+
const trace = error.cause?.trace;
|
|
20215
|
+
if (!isEarnErrorTrace(trace)) {
|
|
20216
|
+
throw createValidationFailedError('error.cause.trace', trace, 'EarnKit.retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
|
|
20217
|
+
}
|
|
20218
|
+
const provider = this.context.providers.find((candidate)=>candidate.name === trace.provider);
|
|
20219
|
+
if (provider === undefined) {
|
|
20220
|
+
throw createValidationFailedError('error.cause.trace.provider', trace.provider, `No earn provider named "${trace.provider}" is registered with this kit`);
|
|
20221
|
+
}
|
|
20222
|
+
const result = await provider.retry(error);
|
|
20223
|
+
// `provider.retry` returns a flat result union with no compile-time link to
|
|
20224
|
+
// `trace.operation`, so narrow the operation here to select the matching
|
|
20225
|
+
// overload. The result cast in each branch is sound: the provider always
|
|
20226
|
+
// returns the result type corresponding to the resumed operation.
|
|
20227
|
+
if (trace.operation === 'claimRewards') {
|
|
20228
|
+
return formatRetryResult(trace.operation, result);
|
|
20229
|
+
}
|
|
18126
20230
|
return formatRetryResult(trace.operation, result);
|
|
20231
|
+
}, EARN_EVENT_TYPES.RETRY, this.telemetryConfig);
|
|
20232
|
+
const trace = isKitError(error) ? error.cause?.trace : undefined;
|
|
20233
|
+
if (isEarnErrorTrace(trace)) {
|
|
20234
|
+
emitRetrySuccessTelemetry(trace, result, this.analyticsTelemetryConfig);
|
|
18127
20235
|
}
|
|
18128
|
-
return
|
|
20236
|
+
return result;
|
|
18129
20237
|
}
|
|
18130
20238
|
/**
|
|
18131
20239
|
* Return the chains supported by configured earn providers.
|
|
@@ -18159,7 +20267,9 @@ function formatRetryResult(operation, result) {
|
|
|
18159
20267
|
* result.vaults.forEach(v => console.log(`${v.name}: ${(v.currentApy * 100).toFixed(2)}% APY`))
|
|
18160
20268
|
* ```
|
|
18161
20269
|
*/ async getVaults(params) {
|
|
18162
|
-
|
|
20270
|
+
const result = await withErrorTelemetry(async ()=>getVaults$1(this.context, params), EARN_EVENT_TYPES.GET_VAULTS, this.telemetryConfig);
|
|
20271
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.GET_VAULTS, this.analyticsTelemetryConfig, {});
|
|
20272
|
+
return result;
|
|
18163
20273
|
}
|
|
18164
20274
|
/**
|
|
18165
20275
|
* Discover vaults available on a chain.
|
|
@@ -18184,7 +20294,12 @@ function formatRetryResult(operation, result) {
|
|
|
18184
20294
|
* const guarded = result.vaults.filter(v => v.circleGuarded) // Circle-guarded vaults only
|
|
18185
20295
|
* ```
|
|
18186
20296
|
*/ async exploreVaults(params) {
|
|
18187
|
-
|
|
20297
|
+
const context = {
|
|
20298
|
+
sourceChain: resolveChainName(params.chain)
|
|
20299
|
+
};
|
|
20300
|
+
const result = await withErrorTelemetry(async ()=>exploreVaults$1(this.context, params), EARN_EVENT_TYPES.EXPLORE_VAULTS, this.telemetryConfig, context);
|
|
20301
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.EXPLORE_VAULTS, this.analyticsTelemetryConfig, context);
|
|
20302
|
+
return result;
|
|
18188
20303
|
}
|
|
18189
20304
|
/**
|
|
18190
20305
|
* Lazily iterate every vault available on a chain.
|
|
@@ -18231,7 +20346,9 @@ function formatRetryResult(operation, result) {
|
|
|
18231
20346
|
* }
|
|
18232
20347
|
* ```
|
|
18233
20348
|
*/ async getPosition(params) {
|
|
18234
|
-
return getPosition$1(this.context, params)
|
|
20349
|
+
return withErrorTelemetry(async ()=>getPosition$1(this.context, params), EARN_EVENT_TYPES.GET_POSITION, this.telemetryConfig, {
|
|
20350
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
20351
|
+
});
|
|
18235
20352
|
}
|
|
18236
20353
|
/**
|
|
18237
20354
|
* Fetch the current status of a cross-chain deposit by execution ID.
|
|
@@ -18256,7 +20373,7 @@ function formatRetryResult(operation, result) {
|
|
|
18256
20373
|
* console.log(`Bridge ${status.execId} is ${status.status}`)
|
|
18257
20374
|
* ```
|
|
18258
20375
|
*/ async getCrossChainDepositStatus(params) {
|
|
18259
|
-
return getCrossChainDepositStatus$1(this.context, params);
|
|
20376
|
+
return withErrorTelemetry(async ()=>getCrossChainDepositStatus$1(this.context, params), EARN_EVENT_TYPES.GET_CROSS_CHAIN_DEPOSIT_STATUS, this.telemetryConfig);
|
|
18260
20377
|
}
|
|
18261
20378
|
/**
|
|
18262
20379
|
* Poll a cross-chain deposit until it reaches a terminal bridge state.
|
|
@@ -18283,10 +20400,30 @@ function formatRetryResult(operation, result) {
|
|
|
18283
20400
|
* console.log(`Bridge ended as ${result.outcome}`)
|
|
18284
20401
|
* ```
|
|
18285
20402
|
*/ async waitForCrossChainDeposit(params) {
|
|
18286
|
-
return waitForCrossChainDeposit$1(this.context, params);
|
|
20403
|
+
return withErrorTelemetry(async ()=>waitForCrossChainDeposit$1(this.context, params), EARN_EVENT_TYPES.WAIT_FOR_CROSS_CHAIN_DEPOSIT, this.telemetryConfig);
|
|
18287
20404
|
}
|
|
18288
20405
|
async deposit(params) {
|
|
18289
|
-
|
|
20406
|
+
const isCrossChain = isCrossChainDeposit(params);
|
|
20407
|
+
const context = {
|
|
20408
|
+
sourceChain: resolveChainName(params.from.chain),
|
|
20409
|
+
...isCrossChain && {
|
|
20410
|
+
destinationChain: resolveChainName(params.to.chain)
|
|
20411
|
+
}
|
|
20412
|
+
};
|
|
20413
|
+
const eventType = isCrossChain ? EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT : EARN_EVENT_TYPES.DEPOSIT;
|
|
20414
|
+
const result = await withErrorTelemetry(async ()=>deposit$1(this.context, params), eventType, this.telemetryConfig, context);
|
|
20415
|
+
if (result.kind === 'cross-chain') {
|
|
20416
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, this.analyticsTelemetryConfig, {
|
|
20417
|
+
sourceChain: resolveChainName(result.sourceChain),
|
|
20418
|
+
destinationChain: resolveChainName(result.destinationChain)
|
|
20419
|
+
});
|
|
20420
|
+
} else {
|
|
20421
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, this.analyticsTelemetryConfig, {
|
|
20422
|
+
...context,
|
|
20423
|
+
txHash: result.txHash
|
|
20424
|
+
});
|
|
20425
|
+
}
|
|
20426
|
+
return result;
|
|
18290
20427
|
}
|
|
18291
20428
|
/**
|
|
18292
20429
|
* Execute a withdrawal from a DeFi lending vault.
|
|
@@ -18312,7 +20449,15 @@ function formatRetryResult(operation, result) {
|
|
|
18312
20449
|
* console.log(`Withdrew ${result.amount} from ${result.vaultAddress}, tx: ${result.txHash}`)
|
|
18313
20450
|
* ```
|
|
18314
20451
|
*/ async withdraw(params) {
|
|
18315
|
-
|
|
20452
|
+
const context = {
|
|
20453
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
20454
|
+
};
|
|
20455
|
+
const result = await withErrorTelemetry(async ()=>withdraw$1(this.context, params), EARN_EVENT_TYPES.WITHDRAW, this.telemetryConfig, context);
|
|
20456
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, this.analyticsTelemetryConfig, {
|
|
20457
|
+
...context,
|
|
20458
|
+
txHash: result.txHash
|
|
20459
|
+
});
|
|
20460
|
+
return result;
|
|
18316
20461
|
}
|
|
18317
20462
|
/**
|
|
18318
20463
|
* Claim rewards from earn vaults.
|
|
@@ -18339,7 +20484,17 @@ function formatRetryResult(operation, result) {
|
|
|
18339
20484
|
*
|
|
18340
20485
|
* @internal
|
|
18341
20486
|
*/ async claimRewards(params) {
|
|
18342
|
-
|
|
20487
|
+
const context = {
|
|
20488
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
20489
|
+
};
|
|
20490
|
+
const result = await withErrorTelemetry(async ()=>claimRewards$1(this.context, params), EARN_EVENT_TYPES.CLAIM_REWARDS, this.telemetryConfig, context);
|
|
20491
|
+
if (result.status === 'claimed') {
|
|
20492
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, this.analyticsTelemetryConfig, {
|
|
20493
|
+
...context,
|
|
20494
|
+
txHash: result.txHash
|
|
20495
|
+
});
|
|
20496
|
+
}
|
|
20497
|
+
return result;
|
|
18343
20498
|
}
|
|
18344
20499
|
/**
|
|
18345
20500
|
* Get an informational quote for a deposit into a vault.
|
|
@@ -18361,7 +20516,9 @@ function formatRetryResult(operation, result) {
|
|
|
18361
20516
|
* console.log(`Expected shares: ${quote.expectedShares.amount}`)
|
|
18362
20517
|
* ```
|
|
18363
20518
|
*/ async getDepositQuote(params) {
|
|
18364
|
-
return getDepositQuote$1(this.context, params)
|
|
20519
|
+
return withErrorTelemetry(async ()=>getDepositQuote$1(this.context, params), EARN_EVENT_TYPES.GET_DEPOSIT_QUOTE, this.telemetryConfig, {
|
|
20520
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
20521
|
+
});
|
|
18365
20522
|
}
|
|
18366
20523
|
/**
|
|
18367
20524
|
* Get an informational quote for a withdrawal from a vault.
|
|
@@ -18383,7 +20540,9 @@ function formatRetryResult(operation, result) {
|
|
|
18383
20540
|
* console.log(`Shares to redeem: ${quote.sharesToRedeem.amount}`)
|
|
18384
20541
|
* ```
|
|
18385
20542
|
*/ async getWithdrawalQuote(params) {
|
|
18386
|
-
return getWithdrawalQuote$1(this.context, params)
|
|
20543
|
+
return withErrorTelemetry(async ()=>getWithdrawalQuote$1(this.context, params), EARN_EVENT_TYPES.GET_WITHDRAWAL_QUOTE, this.telemetryConfig, {
|
|
20544
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
20545
|
+
});
|
|
18387
20546
|
}
|
|
18388
20547
|
/**
|
|
18389
20548
|
* Get an informational quote for claiming rewards.
|
|
@@ -18405,7 +20564,9 @@ function formatRetryResult(operation, result) {
|
|
|
18405
20564
|
*
|
|
18406
20565
|
* @internal
|
|
18407
20566
|
*/ async getClaimRewardsQuote(params) {
|
|
18408
|
-
return getClaimRewardsQuote$1(this.context, params)
|
|
20567
|
+
return withErrorTelemetry(async ()=>getClaimRewardsQuote$1(this.context, params), EARN_EVENT_TYPES.GET_CLAIM_REWARDS_QUOTE, this.telemetryConfig, {
|
|
20568
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
20569
|
+
});
|
|
18409
20570
|
}
|
|
18410
20571
|
}
|
|
18411
20572
|
|
|
@@ -18494,7 +20655,14 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
|
|
|
18494
20655
|
* const earnKit = createEarnKit(context)
|
|
18495
20656
|
* ```
|
|
18496
20657
|
*/ const createEarnKit = (context)=>{
|
|
18497
|
-
const kit = new EarnKit(
|
|
20658
|
+
const kit = new EarnKit({
|
|
20659
|
+
...context.disableErrorReporting != null && {
|
|
20660
|
+
disableErrorReporting: context.disableErrorReporting
|
|
20661
|
+
},
|
|
20662
|
+
...context.disableAnalytics != null && {
|
|
20663
|
+
disableAnalytics: context.disableAnalytics
|
|
20664
|
+
}
|
|
20665
|
+
});
|
|
18498
20666
|
registerActionHandlers(kit, context.actions.earn, 'earn');
|
|
18499
20667
|
return kit;
|
|
18500
20668
|
};
|