@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/index.mjs
CHANGED
|
@@ -16,6 +16,17 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
// Buffer polyfill setup - executes before any other code
|
|
20
|
+
// Ensures globalThis.Buffer is available for Solana libraries
|
|
21
|
+
import { Buffer } from 'buffer';
|
|
22
|
+
if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
|
|
23
|
+
globalThis.Buffer = Buffer;
|
|
24
|
+
}
|
|
25
|
+
if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
|
|
26
|
+
window.Buffer = Buffer;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
19
30
|
import { z } from 'zod';
|
|
20
31
|
import pino from 'pino';
|
|
21
32
|
import { parseUnits as parseUnits$1, formatUnits as formatUnits$1 } from '@ethersproject/units';
|
|
@@ -27,61 +38,69 @@ import { PublicKey } from '@solana/web3.js';
|
|
|
27
38
|
import 'bn.js';
|
|
28
39
|
import '@coral-xyz/anchor';
|
|
29
40
|
import '@noble/curves/ed25519';
|
|
41
|
+
import { decodeFunctionData } from 'viem';
|
|
30
42
|
import { keccak256 } from '@ethersproject/keccak256';
|
|
31
43
|
|
|
44
|
+
// Import global type declarations
|
|
32
45
|
/**
|
|
33
|
-
*
|
|
46
|
+
* Check whether the current runtime is Node.js.
|
|
34
47
|
*
|
|
35
|
-
*
|
|
36
|
-
* used for event handlers, and merges in any custom implementations provided
|
|
37
|
-
* via params.
|
|
48
|
+
* @returns `true` when running in Node.js, `false` otherwise.
|
|
38
49
|
*
|
|
39
|
-
* @
|
|
40
|
-
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { isNodeEnvironment } from '@core/utils'
|
|
53
|
+
*
|
|
54
|
+
* if (isNodeEnvironment()) {
|
|
55
|
+
* console.log('Running in Node.js')
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
|
|
59
|
+
/**
|
|
60
|
+
* Check whether the current runtime exposes a browser DOM.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* This intentionally does not treat every non-Node runtime as a browser.
|
|
64
|
+
* Server-side edge runtimes such as Cloudflare Workers, Deno, and Supabase
|
|
65
|
+
* Edge Functions do not expose Node globals but can safely use server
|
|
66
|
+
* credentials. A Node.js runtime remains server-side even when a test or SSR
|
|
67
|
+
* environment provides a DOM shim.
|
|
68
|
+
*
|
|
69
|
+
* @returns `true` when running in a browser window, `false` otherwise.
|
|
41
70
|
*
|
|
42
71
|
* @example
|
|
43
72
|
* ```typescript
|
|
44
|
-
*
|
|
45
|
-
* const defaultContext = createContext()
|
|
73
|
+
* import { isBrowserEnvironment } from '@core/utils'
|
|
46
74
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* if (type === 'bridge') {
|
|
51
|
-
* // Custom bridge fee logic
|
|
52
|
-
* return await calculateBridgeFee(params)
|
|
53
|
-
* }
|
|
54
|
-
* // Use default for other types
|
|
55
|
-
* return defaultFeeCalculation(type, params)
|
|
56
|
-
* }
|
|
57
|
-
* })
|
|
75
|
+
* if (isBrowserEnvironment()) {
|
|
76
|
+
* throw new Error('Server-only secrets must not be used in the browser')
|
|
77
|
+
* }
|
|
58
78
|
* ```
|
|
59
|
-
*/ const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
actions: {
|
|
63
|
-
bridge: {},
|
|
64
|
-
earn: {},
|
|
65
|
-
...params.actions
|
|
66
|
-
}
|
|
67
|
-
};
|
|
79
|
+
*/ const isBrowserEnvironment = ()=>{
|
|
80
|
+
const browserWindow = globalThis.window;
|
|
81
|
+
return !isNodeEnvironment() && browserWindow?.document !== undefined;
|
|
68
82
|
};
|
|
69
|
-
|
|
70
|
-
// Import global type declarations
|
|
71
83
|
/**
|
|
72
|
-
*
|
|
84
|
+
* Return the SDK User-Agent request header only when running in Node.js.
|
|
73
85
|
*
|
|
74
|
-
*
|
|
86
|
+
* Browsers forbid manually setting `User-Agent`, and a custom fallback header
|
|
87
|
+
* can trigger CORS preflight. Non-Node server runtimes also omit this optional
|
|
88
|
+
* attribution header because they cannot set it reliably.
|
|
89
|
+
*
|
|
90
|
+
* @returns A User-Agent header in Node.js, or an empty object otherwise.
|
|
75
91
|
*
|
|
76
92
|
* @example
|
|
77
93
|
* ```typescript
|
|
78
|
-
* import {
|
|
94
|
+
* import { getNodeUserAgentHeader } from '@core/utils'
|
|
79
95
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
96
|
+
* const headers = {
|
|
97
|
+
* 'Content-Type': 'application/json',
|
|
98
|
+
* ...getNodeUserAgentHeader(),
|
|
82
99
|
* }
|
|
83
100
|
* ```
|
|
84
|
-
*/ const
|
|
101
|
+
*/ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
|
|
102
|
+
'User-Agent': getUserAgent()
|
|
103
|
+
} : {};
|
|
85
104
|
/**
|
|
86
105
|
* Detect the runtime environment and return a shortened identifier.
|
|
87
106
|
*
|
|
@@ -4067,6 +4086,8 @@ function getOptionalString(value) {
|
|
|
4067
4086
|
Blockchain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
4068
4087
|
Blockchain["XDC"] = "XDC";
|
|
4069
4088
|
Blockchain["XDC_Apothem"] = "XDC_Apothem";
|
|
4089
|
+
Blockchain["X_Layer"] = "X_Layer";
|
|
4090
|
+
Blockchain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
4070
4091
|
Blockchain["ZKSync_Era"] = "ZKSync_Era";
|
|
4071
4092
|
Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
|
|
4072
4093
|
})(Blockchain || (Blockchain = {}));
|
|
@@ -4120,6 +4141,7 @@ var BridgeChain;
|
|
|
4120
4141
|
BridgeChain["Unichain"] = "Unichain";
|
|
4121
4142
|
BridgeChain["World_Chain"] = "World_Chain";
|
|
4122
4143
|
BridgeChain["XDC"] = "XDC";
|
|
4144
|
+
BridgeChain["X_Layer"] = "X_Layer";
|
|
4123
4145
|
// Testnet chains with CCTPv2 support
|
|
4124
4146
|
BridgeChain["Arc_Testnet"] = "Arc_Testnet";
|
|
4125
4147
|
BridgeChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
|
|
@@ -4145,6 +4167,7 @@ var BridgeChain;
|
|
|
4145
4167
|
BridgeChain["Unichain_Sepolia"] = "Unichain_Sepolia";
|
|
4146
4168
|
BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
4147
4169
|
BridgeChain["XDC_Apothem"] = "XDC_Apothem";
|
|
4170
|
+
BridgeChain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
4148
4171
|
})(BridgeChain || (BridgeChain = {}));
|
|
4149
4172
|
var UnifiedBalanceChain;
|
|
4150
4173
|
(function(UnifiedBalanceChain) {
|
|
@@ -6703,7 +6726,8 @@ var EarnChain;
|
|
|
6703
6726
|
isTestnet: true,
|
|
6704
6727
|
explorerUrl: 'https://amoy.polygonscan.com/tx/{hash}',
|
|
6705
6728
|
rpcEndpoints: [
|
|
6706
|
-
'https://
|
|
6729
|
+
'https://polygon-amoy-bor-rpc.publicnode.com',
|
|
6730
|
+
'https://polygon-amoy.drpc.org'
|
|
6707
6731
|
],
|
|
6708
6732
|
eurcAddress: null,
|
|
6709
6733
|
usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -7568,6 +7592,104 @@ var EarnChain;
|
|
|
7568
7592
|
}
|
|
7569
7593
|
});
|
|
7570
7594
|
|
|
7595
|
+
/**
|
|
7596
|
+
* X Layer Mainnet chain definition
|
|
7597
|
+
* @remarks
|
|
7598
|
+
* This represents the official production network for the X Layer blockchain.
|
|
7599
|
+
* X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
|
|
7600
|
+
* using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
|
|
7601
|
+
* OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
|
|
7602
|
+
*/ const XLayer = defineChain({
|
|
7603
|
+
type: 'evm',
|
|
7604
|
+
chain: Blockchain.X_Layer,
|
|
7605
|
+
name: 'X Layer',
|
|
7606
|
+
title: 'X Layer Mainnet',
|
|
7607
|
+
nativeCurrency: {
|
|
7608
|
+
name: 'OKB',
|
|
7609
|
+
symbol: 'OKB',
|
|
7610
|
+
decimals: 18
|
|
7611
|
+
},
|
|
7612
|
+
chainId: 196,
|
|
7613
|
+
isTestnet: false,
|
|
7614
|
+
explorerUrl: 'https://www.oklink.com/xlayer/tx/{hash}',
|
|
7615
|
+
rpcEndpoints: [
|
|
7616
|
+
'https://xlayerrpc.okx.com'
|
|
7617
|
+
],
|
|
7618
|
+
eurcAddress: null,
|
|
7619
|
+
usdcAddress: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
7620
|
+
usdtAddress: null,
|
|
7621
|
+
cctp: {
|
|
7622
|
+
domain: 37,
|
|
7623
|
+
contracts: {
|
|
7624
|
+
v2: {
|
|
7625
|
+
type: 'split',
|
|
7626
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
7627
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
7628
|
+
confirmations: 65,
|
|
7629
|
+
fastConfirmations: 1
|
|
7630
|
+
}
|
|
7631
|
+
},
|
|
7632
|
+
forwarderSupported: {
|
|
7633
|
+
source: false,
|
|
7634
|
+
destination: false
|
|
7635
|
+
}
|
|
7636
|
+
},
|
|
7637
|
+
kitContracts: {
|
|
7638
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
7639
|
+
}
|
|
7640
|
+
});
|
|
7641
|
+
|
|
7642
|
+
/**
|
|
7643
|
+
* X Layer Testnet chain definition
|
|
7644
|
+
* @remarks
|
|
7645
|
+
* This represents the official test network for the X Layer blockchain.
|
|
7646
|
+
* X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
|
|
7647
|
+
* using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
|
|
7648
|
+
* OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
|
|
7649
|
+
*/ const XLayerTestnet = defineChain({
|
|
7650
|
+
type: 'evm',
|
|
7651
|
+
chain: Blockchain.X_Layer_Testnet,
|
|
7652
|
+
name: 'X Layer Testnet',
|
|
7653
|
+
title: 'X Layer Testnet',
|
|
7654
|
+
nativeCurrency: {
|
|
7655
|
+
name: 'OKB',
|
|
7656
|
+
symbol: 'OKB',
|
|
7657
|
+
decimals: 18
|
|
7658
|
+
},
|
|
7659
|
+
chainId: 1952,
|
|
7660
|
+
isTestnet: true,
|
|
7661
|
+
// Deliberately not oklink.com (used for mainnet): viem's bundled OKLink
|
|
7662
|
+
// testnet URL targets the deprecated pre-rebrand chain ID 195, not this
|
|
7663
|
+
// chain's ID (1952). Verified against the internal chain-expansion-scripts
|
|
7664
|
+
// config (`v2config.sandbox.yml`) — do not "normalize" this to match mainnet.
|
|
7665
|
+
explorerUrl: 'https://web3.okx.com/explorer/x-layer-testnet/tx/{hash}',
|
|
7666
|
+
rpcEndpoints: [
|
|
7667
|
+
'https://testrpc.xlayer.tech'
|
|
7668
|
+
],
|
|
7669
|
+
eurcAddress: null,
|
|
7670
|
+
usdcAddress: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
7671
|
+
usdtAddress: null,
|
|
7672
|
+
cctp: {
|
|
7673
|
+
domain: 37,
|
|
7674
|
+
contracts: {
|
|
7675
|
+
v2: {
|
|
7676
|
+
type: 'split',
|
|
7677
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
7678
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
7679
|
+
confirmations: 65,
|
|
7680
|
+
fastConfirmations: 1
|
|
7681
|
+
}
|
|
7682
|
+
},
|
|
7683
|
+
forwarderSupported: {
|
|
7684
|
+
source: false,
|
|
7685
|
+
destination: false
|
|
7686
|
+
}
|
|
7687
|
+
},
|
|
7688
|
+
kitContracts: {
|
|
7689
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET
|
|
7690
|
+
}
|
|
7691
|
+
});
|
|
7692
|
+
|
|
7571
7693
|
/**
|
|
7572
7694
|
* ZKSync Era Mainnet chain definition
|
|
7573
7695
|
* @remarks
|
|
@@ -7687,6 +7809,8 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
7687
7809
|
WorldChainSepolia: WorldChainSepolia,
|
|
7688
7810
|
XDC: XDC,
|
|
7689
7811
|
XDCApothem: XDCApothem,
|
|
7812
|
+
XLayer: XLayer,
|
|
7813
|
+
XLayerTestnet: XLayerTestnet,
|
|
7690
7814
|
ZKSyncEra: ZKSyncEra,
|
|
7691
7815
|
ZKSyncEraSepolia: ZKSyncEraSepolia
|
|
7692
7816
|
});
|
|
@@ -9282,13 +9406,12 @@ const swapTokenEnumSchema = z.enum([
|
|
|
9282
9406
|
headers: {
|
|
9283
9407
|
...DEFAULT_CONFIG$3.headers,
|
|
9284
9408
|
...config.headers ?? {},
|
|
9285
|
-
//
|
|
9286
|
-
//
|
|
9287
|
-
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
}
|
|
9409
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
9410
|
+
// fallback header the SDK used instead trips CORS preflight against the
|
|
9411
|
+
// Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
|
|
9412
|
+
// blocking the request. So send the SDK user agent only in Node;
|
|
9413
|
+
// browsers omit it entirely.
|
|
9414
|
+
...getNodeUserAgentHeader()
|
|
9292
9415
|
}
|
|
9293
9416
|
};
|
|
9294
9417
|
let lastError;
|
|
@@ -10051,6 +10174,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
10051
10174
|
[Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
10052
10175
|
[Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
|
|
10053
10176
|
[Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
10177
|
+
[Blockchain.X_Layer]: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
10054
10178
|
[Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
|
|
10055
10179
|
// =========================================================================
|
|
10056
10180
|
// Testnets (alphabetically sorted)
|
|
@@ -10085,6 +10209,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
10085
10209
|
[Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
|
|
10086
10210
|
[Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
|
|
10087
10211
|
[Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
|
|
10212
|
+
[Blockchain.X_Layer_Testnet]: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
10088
10213
|
[Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853'
|
|
10089
10214
|
}
|
|
10090
10215
|
};
|
|
@@ -11034,6 +11159,52 @@ function parseOrThrow(value, schema, context) {
|
|
|
11034
11159
|
return explorerUrl;
|
|
11035
11160
|
}
|
|
11036
11161
|
|
|
11162
|
+
/**
|
|
11163
|
+
* Assert that a value has type `never` (exhaustive switch helper).
|
|
11164
|
+
*
|
|
11165
|
+
* @remarks
|
|
11166
|
+
* Use in the `default` branch of a switch over a discriminated union.
|
|
11167
|
+
* If all union members are handled, the default is unreachable and TypeScript
|
|
11168
|
+
* narrows the parameter to `never`. If a member is missed, the compiler errors.
|
|
11169
|
+
*
|
|
11170
|
+
* @param _x - The value (typed as `never` when switch is exhaustive).
|
|
11171
|
+
* @returns Never returns; always throws.
|
|
11172
|
+
* @throws Error when the switch is not exhaustive.
|
|
11173
|
+
*
|
|
11174
|
+
* @example
|
|
11175
|
+
* ```typescript
|
|
11176
|
+
* type Foo = { type: 'a'; x: number } | { type: 'b'; y: string }
|
|
11177
|
+
*
|
|
11178
|
+
* function handle(foo: Foo): string {
|
|
11179
|
+
* switch (foo.type) {
|
|
11180
|
+
* case 'a': return String(foo.x)
|
|
11181
|
+
* case 'b': return foo.y
|
|
11182
|
+
* default: return assertNever(foo)
|
|
11183
|
+
* }
|
|
11184
|
+
* }
|
|
11185
|
+
* ```
|
|
11186
|
+
*/ function assertNever$2(x) {
|
|
11187
|
+
// Plain `String(x)` collapses non-primitive union members (objects, arrays)
|
|
11188
|
+
// to `'[object Object]'`, which is useless when triaging which discriminant
|
|
11189
|
+
// was missed. Attempt `JSON.stringify` first so the thrown message preserves
|
|
11190
|
+
// the offending shape. Fall back to a minimal `typeof`-based label if
|
|
11191
|
+
// serialization fails (`BigInt` member, circular references, host objects).
|
|
11192
|
+
//
|
|
11193
|
+
// `x` is statically typed as `never` (the whole point of this helper), but
|
|
11194
|
+
// at runtime callers may still pass an unexpected value when the switch is
|
|
11195
|
+
// not actually exhaustive — that's exactly the bug we want to surface. Cast
|
|
11196
|
+
// through `unknown` so the runtime defence is not stripped by the compiler.
|
|
11197
|
+
const value = x;
|
|
11198
|
+
let stringified;
|
|
11199
|
+
try {
|
|
11200
|
+
const json = JSON.stringify(value);
|
|
11201
|
+
stringified = typeof json === 'string' ? json : `<${typeof value}>`;
|
|
11202
|
+
} catch {
|
|
11203
|
+
stringified = `<unstringifiable ${typeof value}>`;
|
|
11204
|
+
}
|
|
11205
|
+
throw new Error(`Unhandled switch case: ${stringified}`);
|
|
11206
|
+
}
|
|
11207
|
+
|
|
11037
11208
|
/**
|
|
11038
11209
|
* CCTP forwarding magic bytes prefix.
|
|
11039
11210
|
*
|
|
@@ -11219,6 +11390,7 @@ function resolveOptions(options) {
|
|
|
11219
11390
|
if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
|
|
11220
11391
|
if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
|
|
11221
11392
|
if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
|
|
11393
|
+
if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
|
|
11222
11394
|
if (payload.errorDetails !== undefined) {
|
|
11223
11395
|
const errorDetails = {
|
|
11224
11396
|
...payload.errorDetails.errorCode !== undefined && {
|
|
@@ -11289,18 +11461,15 @@ function resolveOptions(options) {
|
|
|
11289
11461
|
timeoutHandle.unref();
|
|
11290
11462
|
}
|
|
11291
11463
|
try {
|
|
11292
|
-
const isNode = isNodeEnvironment();
|
|
11293
|
-
const userAgent = getUserAgent();
|
|
11294
11464
|
await fetch(getLogsUrl(), {
|
|
11295
11465
|
method: 'POST',
|
|
11296
11466
|
headers: {
|
|
11297
11467
|
'Content-Type': 'application/json',
|
|
11298
|
-
//
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
11302
|
-
|
|
11303
|
-
}
|
|
11468
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
11469
|
+
// fallback header the SDK used instead trips CORS preflight (it isn't
|
|
11470
|
+
// in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
|
|
11471
|
+
// it only in Node; browsers omit it entirely.
|
|
11472
|
+
...getNodeUserAgentHeader()
|
|
11304
11473
|
},
|
|
11305
11474
|
body: JSON.stringify(toSafePayload(payload)),
|
|
11306
11475
|
signal: controller.signal
|
|
@@ -11513,7 +11682,7 @@ function resolveOptions(options) {
|
|
|
11513
11682
|
// discards the stack trace, nested `cause`, and any custom Error
|
|
11514
11683
|
// properties — exactly the context an on-call needs when a
|
|
11515
11684
|
// resolver-closure regression triggers this path.
|
|
11516
|
-
console.warn(`[stablecoin-kits telemetry] dropped
|
|
11685
|
+
console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
|
|
11517
11686
|
} catch {
|
|
11518
11687
|
// console.warn itself throwing is the user's environment; nothing more we
|
|
11519
11688
|
// can do without risking the original operation error.
|
|
@@ -11529,7 +11698,9 @@ function resolveOptions(options) {
|
|
|
11529
11698
|
sdkVersion: config.sdkVersion,
|
|
11530
11699
|
eventType,
|
|
11531
11700
|
timestamp: new Date().toISOString(),
|
|
11532
|
-
errorDetails
|
|
11701
|
+
...errorDetails !== undefined && {
|
|
11702
|
+
errorDetails
|
|
11703
|
+
},
|
|
11533
11704
|
clientContext: buildClientContext(),
|
|
11534
11705
|
...context?.sourceChain != null && {
|
|
11535
11706
|
sourceChain: context.sourceChain
|
|
@@ -11545,9 +11716,45 @@ function resolveOptions(options) {
|
|
|
11545
11716
|
},
|
|
11546
11717
|
...context?.txHash != null && {
|
|
11547
11718
|
txHash: context.txHash
|
|
11719
|
+
},
|
|
11720
|
+
...context?.correlationId != null && {
|
|
11721
|
+
correlationId: context.correlationId
|
|
11548
11722
|
}
|
|
11549
11723
|
};
|
|
11550
11724
|
}
|
|
11725
|
+
/**
|
|
11726
|
+
* Emit telemetry for a completed operation without affecting its caller.
|
|
11727
|
+
*
|
|
11728
|
+
* No-ops when `config.disabled` is `true`. Like {@link withErrorTelemetry},
|
|
11729
|
+
* failures while constructing or submitting the telemetry payload are reported
|
|
11730
|
+
* as a soft warning and never change a completed operation's result.
|
|
11731
|
+
*
|
|
11732
|
+
* @param eventType - The telemetry event type for the completed operation.
|
|
11733
|
+
* @param config - Per-kit SDK identity and disabled flag.
|
|
11734
|
+
* @param context - Optional chain, token, and transaction context.
|
|
11735
|
+
* @returns Nothing.
|
|
11736
|
+
* @throws Never — telemetry failures are reported as warnings.
|
|
11737
|
+
*
|
|
11738
|
+
* @example
|
|
11739
|
+
* ```typescript
|
|
11740
|
+
* import { emitSuccessTelemetry } from '@core/utils'
|
|
11741
|
+
*
|
|
11742
|
+
* emitSuccessTelemetry(
|
|
11743
|
+
* 'bridge_bridge',
|
|
11744
|
+
* { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
|
|
11745
|
+
* { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
|
|
11746
|
+
* )
|
|
11747
|
+
* ```
|
|
11748
|
+
*/ function emitSuccessTelemetry(eventType, config, context) {
|
|
11749
|
+
if (config.disabled) {
|
|
11750
|
+
return;
|
|
11751
|
+
}
|
|
11752
|
+
try {
|
|
11753
|
+
void emitAnalyticsLog(buildPayload$1(config, eventType, undefined, context));
|
|
11754
|
+
} catch (telemetryError) {
|
|
11755
|
+
warnTelemetryDrop(eventType, telemetryError);
|
|
11756
|
+
}
|
|
11757
|
+
}
|
|
11551
11758
|
/**
|
|
11552
11759
|
* Wrap an async operation with error telemetry.
|
|
11553
11760
|
*
|
|
@@ -11658,7 +11865,7 @@ function resolveOptions(options) {
|
|
|
11658
11865
|
}
|
|
11659
11866
|
|
|
11660
11867
|
var name$4 = "@circle-fin/bridge-kit";
|
|
11661
|
-
var version$5 = "1.
|
|
11868
|
+
var version$5 = "1.13.0";
|
|
11662
11869
|
var pkg$5 = {
|
|
11663
11870
|
name: name$4,
|
|
11664
11871
|
version: version$5};
|
|
@@ -11695,13 +11902,21 @@ const assertCustomFeePolicySymbol$2 = Symbol('assertCustomFeePolicy');
|
|
|
11695
11902
|
computeFee: z.function().returns(z.string().or(z.promise(z.string()))).optional(),
|
|
11696
11903
|
calculateFee: z.function().returns(z.string().or(z.promise(z.string()))).optional(),
|
|
11697
11904
|
resolveFeeRecipientAddress: z.function().returns(z.string().or(z.promise(z.string())))
|
|
11698
|
-
}).strict().
|
|
11905
|
+
}).strict().superRefine((data, ctx)=>{
|
|
11699
11906
|
const hasComputeFee = data.computeFee !== undefined;
|
|
11700
11907
|
const hasCalculateFee = data.calculateFee !== undefined;
|
|
11701
|
-
|
|
11702
|
-
|
|
11703
|
-
|
|
11704
|
-
|
|
11908
|
+
if (hasComputeFee && hasCalculateFee) {
|
|
11909
|
+
ctx.addIssue({
|
|
11910
|
+
code: z.ZodIssueCode.custom,
|
|
11911
|
+
message: 'Provide either computeFee or calculateFee, not both. Use computeFee (recommended) for human-readable amounts.'
|
|
11912
|
+
});
|
|
11913
|
+
}
|
|
11914
|
+
if (!hasComputeFee && !hasCalculateFee) {
|
|
11915
|
+
ctx.addIssue({
|
|
11916
|
+
code: z.ZodIssueCode.custom,
|
|
11917
|
+
message: 'Provide either computeFee or calculateFee. Use computeFee (recommended) for human-readable amounts.'
|
|
11918
|
+
});
|
|
11919
|
+
}
|
|
11705
11920
|
});
|
|
11706
11921
|
/**
|
|
11707
11922
|
* Assert that the provided value conforms to {@link CustomFeePolicy}.
|
|
@@ -14294,7 +14509,13 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14294
14509
|
/**
|
|
14295
14510
|
* The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
|
|
14296
14511
|
* hookData must start with.
|
|
14297
|
-
|
|
14512
|
+
*
|
|
14513
|
+
* Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
|
|
14514
|
+
* so this module-level constant does not reference the Node `Buffer` global at
|
|
14515
|
+
* import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
|
|
14516
|
+
* bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
|
|
14517
|
+
* that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
|
|
14518
|
+
*/ const CCTP_FORWARD_MAGIC_HEX = Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
14298
14519
|
/**
|
|
14299
14520
|
* Determine whether a hookData blob begins with the `cctp-forward` envelope.
|
|
14300
14521
|
*
|
|
@@ -14495,14 +14716,32 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
|
|
|
14495
14716
|
;
|
|
14496
14717
|
const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
|
|
14497
14718
|
;
|
|
14498
|
-
//
|
|
14499
|
-
//
|
|
14500
|
-
//
|
|
14501
|
-
|
|
14719
|
+
// Gas FLOORS, not ceilings — kept separate from the fee-estimate averages
|
|
14720
|
+
// above. `executePreparedChainRequest` submits
|
|
14721
|
+
// max(estimate * buffer, floor), so a chain whose real cost exceeds the floor
|
|
14722
|
+
// is covered by its own estimate, and a chain whose estimator under-reports
|
|
14723
|
+
// (Cronos: returns 30_600 where the EIP-7623 calldata floor is 45_000) is
|
|
14724
|
+
// covered by the floor.
|
|
14725
|
+
//
|
|
14726
|
+
// Two distinct chain surcharges drive these numbers, both measured live:
|
|
14727
|
+
// Sei — ~+51_500 per NEWLY CREATED storage slot (73_595 vs vanilla 22_100);
|
|
14728
|
+
// no flat per-tx surcharge (31_535, identical to Base).
|
|
14729
|
+
// Edge — ~+53_200 flat on EVERY tx (84_751 vs Base 31_535); storage priced
|
|
14730
|
+
// normally. Edge therefore fails warm as well as cold.
|
|
14731
|
+
// A floor must clear the worst COLD cost, since a slot that exists at estimate
|
|
14732
|
+
// time can be consumed before inclusion and cost a full step more on execution.
|
|
14733
|
+
// Each floor is therefore derived from the worst observed estimate *after* the
|
|
14734
|
+
// 1.25x buffer, plus headroom — sizing it below the buffered value would leave
|
|
14735
|
+
// the estimate governing and defeat the point of the floor.
|
|
14736
|
+
//
|
|
14737
|
+
// The `*_GAS_LIMIT_EVM` names are kept despite these being floors: they are
|
|
14738
|
+
// exported, so renaming to `*_GAS_FLOOR_EVM` would be a breaking change for
|
|
14739
|
+
// consumers. Read "LIMIT" here as "the limit we submit", never as a ceiling.
|
|
14740
|
+
const APPROVE_GAS_LIMIT_EVM = 150_000n // buffered worst cold 149_355 (Edge Testnet 119_484 x 1.25) + drift headroom
|
|
14502
14741
|
;
|
|
14503
|
-
const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM =
|
|
14742
|
+
const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 500_000n // buffered worst 474_078 (Sei 379_263 x 1.25) + ~26k headroom
|
|
14504
14743
|
;
|
|
14505
|
-
const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839
|
|
14744
|
+
const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839; clears Cronos' calldata floor ~10x
|
|
14506
14745
|
;
|
|
14507
14746
|
/**
|
|
14508
14747
|
* The minimum finality threshold for CCTPv2 transfers.
|
|
@@ -15967,6 +16206,63 @@ function hasPendingState(analysis, result) {
|
|
|
15967
16206
|
return waitForPendingTransaction(pendingStep, adapter, chain);
|
|
15968
16207
|
}
|
|
15969
16208
|
|
|
16209
|
+
/**
|
|
16210
|
+
* Multiplier applied to a successful gas estimate before it is submitted.
|
|
16211
|
+
*
|
|
16212
|
+
* Estimates are exact, not padded: Sei returns 109_739 for an approve that
|
|
16213
|
+
* consumes 107_717 (1.9% headroom). Chains that price storage in large steps
|
|
16214
|
+
* can exceed the estimate if state changes between estimation and inclusion,
|
|
16215
|
+
* so the estimate is padded before use.
|
|
16216
|
+
*
|
|
16217
|
+
* @remarks
|
|
16218
|
+
* This buffer alone does NOT cover Sei's ~51_500 per-new-slot step at approve
|
|
16219
|
+
* scale (25% of ~110_000 is only ~27_500). For approve, the FLOOR is what
|
|
16220
|
+
* covers a slot that exists at estimation time and is consumed before
|
|
16221
|
+
* inclusion — so do not lower `APPROVE_GAS_LIMIT_EVM` on the reasoning that
|
|
16222
|
+
* the estimate covers it. For burn the buffer does cover a step (25% of
|
|
16223
|
+
* ~300_000 exceeds 51_500).
|
|
16224
|
+
*/ const GAS_ESTIMATE_BUFFER_PERCENT = 125n;
|
|
16225
|
+
/**
|
|
16226
|
+
* Resolve the gas limit for an EVM request as `max(estimate * buffer, floor)`.
|
|
16227
|
+
*
|
|
16228
|
+
* Estimates first so chains whose real cost exceeds the floor are covered by
|
|
16229
|
+
* their own measurement, and falls back to the floor whenever estimation is
|
|
16230
|
+
* unavailable or under-reports. Estimation failure is never fatal here: before
|
|
16231
|
+
* floors existed these requests were submitted with a pinned limit and no
|
|
16232
|
+
* estimate at all, so degrading to the floor is never worse than the previous
|
|
16233
|
+
* behaviour.
|
|
16234
|
+
*
|
|
16235
|
+
* @param request - The prepared EVM request to size a gas limit for
|
|
16236
|
+
* @param gasFloor - The minimum gas limit to submit, in gas units
|
|
16237
|
+
* @returns The gas limit to submit, in gas units
|
|
16238
|
+
* @throws Never — estimation failures degrade to `gasFloor`
|
|
16239
|
+
*
|
|
16240
|
+
* @example
|
|
16241
|
+
* ```typescript
|
|
16242
|
+
* const gasLimit = await resolveGasLimit(request, 150_000)
|
|
16243
|
+
* ```
|
|
16244
|
+
*/ const resolveGasLimit = async (request, gasFloor)=>{
|
|
16245
|
+
try {
|
|
16246
|
+
// Deliberately called without a `fallback`: both the viem and ethers
|
|
16247
|
+
// adapters *return* the supplied fallback object when estimation reverts
|
|
16248
|
+
// rather than throwing, which would set the estimate to the floor and then
|
|
16249
|
+
// multiply it by the buffer below. Omitting it routes reverts through the
|
|
16250
|
+
// catch, so a failed estimate degrades to exactly the floor.
|
|
16251
|
+
const estimate = await request.estimate();
|
|
16252
|
+
// The arithmetic stays inside the try on purpose. `EstimatedGas.gas` is
|
|
16253
|
+
// typed `bigint`, but adapters are a public extension point and may be
|
|
16254
|
+
// implemented in plain JS, so a non-bigint `gas` would throw here
|
|
16255
|
+
// ("Cannot mix BigInt and other types"). Guarding it keeps the documented
|
|
16256
|
+
// contract — estimation never aborts a step, it degrades to the floor.
|
|
16257
|
+
const buffered = estimate.gas * GAS_ESTIMATE_BUFFER_PERCENT / 100n;
|
|
16258
|
+
// Convert before comparing: Math.max throws on BigInt operands, and gas
|
|
16259
|
+
// units are far below Number.MAX_SAFE_INTEGER so the narrowing is lossless.
|
|
16260
|
+
return Math.max(Number(buffered), gasFloor);
|
|
16261
|
+
} catch {
|
|
16262
|
+
// Estimation is best-effort; the floor is the known-safe value.
|
|
16263
|
+
return gasFloor;
|
|
16264
|
+
}
|
|
16265
|
+
};
|
|
15970
16266
|
/**
|
|
15971
16267
|
* Executes a prepared chain request and returns the result as a bridge step.
|
|
15972
16268
|
*
|
|
@@ -15980,8 +16276,8 @@ function hasPendingState(analysis, result) {
|
|
|
15980
16276
|
* - `adapter`: The adapter that will execute the transaction
|
|
15981
16277
|
* - `confirmations`: The number of confirmations to wait for (defaults to 1)
|
|
15982
16278
|
* - `timeout`: The timeout for the request in milliseconds
|
|
15983
|
-
* - `
|
|
15984
|
-
*
|
|
16279
|
+
* - `gasFloor`: Optional minimum gas limit (number); the request is submitted
|
|
16280
|
+
* with `max(estimate * 1.25, gasFloor)`. Ignored for non-EVM requests
|
|
15985
16281
|
* @returns The bridge step with the transaction details and explorer URL
|
|
15986
16282
|
* @throws If the transaction execution fails
|
|
15987
16283
|
*
|
|
@@ -15996,7 +16292,7 @@ function hasPendingState(analysis, result) {
|
|
|
15996
16292
|
* })
|
|
15997
16293
|
* console.log('Transaction hash:', step.txHash)
|
|
15998
16294
|
* ```
|
|
15999
|
-
*/ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout,
|
|
16295
|
+
*/ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasFloor }) {
|
|
16000
16296
|
const step = {
|
|
16001
16297
|
name,
|
|
16002
16298
|
state: 'pending'
|
|
@@ -16009,8 +16305,8 @@ function hasPendingState(analysis, result) {
|
|
|
16009
16305
|
step.state = 'noop';
|
|
16010
16306
|
return step;
|
|
16011
16307
|
}
|
|
16012
|
-
const txHash = request.type === 'evm' &&
|
|
16013
|
-
gasLimit
|
|
16308
|
+
const txHash = request.type === 'evm' && gasFloor !== undefined ? await request.execute({
|
|
16309
|
+
gasLimit: await resolveGasLimit(request, gasFloor)
|
|
16014
16310
|
}) : await request.execute();
|
|
16015
16311
|
step.txHash = txHash;
|
|
16016
16312
|
const retryOptions = {
|
|
@@ -16084,7 +16380,7 @@ function hasPendingState(analysis, result) {
|
|
|
16084
16380
|
adapter: params.source.adapter,
|
|
16085
16381
|
chain: params.source.chain,
|
|
16086
16382
|
request: await provider.approve(params.source, approvalAmount),
|
|
16087
|
-
|
|
16383
|
+
gasFloor: Number(APPROVE_GAS_LIMIT_EVM)
|
|
16088
16384
|
});
|
|
16089
16385
|
}
|
|
16090
16386
|
|
|
@@ -16112,7 +16408,7 @@ function hasPendingState(analysis, result) {
|
|
|
16112
16408
|
adapter: params.source.adapter,
|
|
16113
16409
|
chain: params.source.chain,
|
|
16114
16410
|
request: await provider.burn(params),
|
|
16115
|
-
|
|
16411
|
+
gasFloor: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
|
|
16116
16412
|
});
|
|
16117
16413
|
}
|
|
16118
16414
|
|
|
@@ -16206,10 +16502,9 @@ function hasPendingState(analysis, result) {
|
|
|
16206
16502
|
request: mintRequest,
|
|
16207
16503
|
// Some chains (e.g. Cronos) enforce an EIP-7623 calldata gas floor that
|
|
16208
16504
|
// eth_estimateGas does not account for, returning a below-floor value
|
|
16209
|
-
// without reverting.
|
|
16210
|
-
//
|
|
16211
|
-
|
|
16212
|
-
gasLimit: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
|
|
16505
|
+
// without reverting. The floor covers those; chains that cost more than the
|
|
16506
|
+
// floor are covered by their own estimate.
|
|
16507
|
+
gasFloor: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
|
|
16213
16508
|
});
|
|
16214
16509
|
// Add forwarded: false for non-relayer mints
|
|
16215
16510
|
return {
|
|
@@ -16732,7 +17027,7 @@ const mockAttestationMessage = {
|
|
|
16732
17027
|
return step;
|
|
16733
17028
|
}
|
|
16734
17029
|
|
|
16735
|
-
var version$4 = "1.10.
|
|
17030
|
+
var version$4 = "1.10.2";
|
|
16736
17031
|
var pkg$4 = {
|
|
16737
17032
|
version: version$4};
|
|
16738
17033
|
|
|
@@ -18611,7 +18906,7 @@ function assertCCTPV2Config(config) {
|
|
|
18611
18906
|
]
|
|
18612
18907
|
];
|
|
18613
18908
|
|
|
18614
|
-
/** SDK name used in telemetry payloads. */ const SDK_NAME$
|
|
18909
|
+
/** SDK name used in telemetry payloads. */ const SDK_NAME$3 = resolveKitSdkName(pkg$5.name);
|
|
18615
18910
|
/**
|
|
18616
18911
|
* Pick the most-relevant `txHash` to attach to an error telemetry payload.
|
|
18617
18912
|
*
|
|
@@ -18753,7 +19048,7 @@ function assertCCTPV2Config(config) {
|
|
|
18753
19048
|
this.actionDispatcher = new Actionable();
|
|
18754
19049
|
this.disableErrorReporting = config.disableErrorReporting === true;
|
|
18755
19050
|
this.telemetryConfig = {
|
|
18756
|
-
sdkName: SDK_NAME$
|
|
19051
|
+
sdkName: SDK_NAME$3,
|
|
18757
19052
|
sdkVersion: pkg$5.version,
|
|
18758
19053
|
disabled: this.disableErrorReporting
|
|
18759
19054
|
};
|
|
@@ -19278,75 +19573,8 @@ function assertCCTPV2Config(config) {
|
|
|
19278
19573
|
// Auto-register this kit for user agent tracking
|
|
19279
19574
|
registerKit(`${pkg$5.name}/${pkg$5.version}`);
|
|
19280
19575
|
|
|
19281
|
-
/**
|
|
19282
|
-
* Create a BridgeKit instance with optional developer fee configuration.
|
|
19283
|
-
*
|
|
19284
|
-
* This utility creates a BridgeKit instance that optionally includes developer
|
|
19285
|
-
* fee configuration based on the provided AppKit context. If the context
|
|
19286
|
-
* provides both `getFee` and `getFeeRecipient` methods, they will be configured
|
|
19287
|
-
* as developer fees in the BridgeKit instance using the `setCustomFeePolicy` method.
|
|
19288
|
-
*
|
|
19289
|
-
* The fee integration transforms string-based fees from the context into the
|
|
19290
|
-
* format expected by BridgeKit, enabling seamless fee calculation across both kits.
|
|
19291
|
-
*
|
|
19292
|
-
* @param context - The AppKit context containing optional fee methods
|
|
19293
|
-
* @returns A configured BridgeKit instance with or without developer fees
|
|
19294
|
-
*
|
|
19295
|
-
* @example
|
|
19296
|
-
* ```typescript
|
|
19297
|
-
* import { createBridgeKit } from '@circle-fin/app-kit/utils'
|
|
19298
|
-
* import { createContext } from '@circle-fin/app-kit/context'
|
|
19299
|
-
*
|
|
19300
|
-
* // Create context with fee methods
|
|
19301
|
-
* const context = createContext({
|
|
19302
|
-
* getFee: async (type, params) => '1000000', // 1 USDC in micro-units
|
|
19303
|
-
* getFeeRecipient: async (type, info) => '0x742d35Cc4634C0532925a3b8D1d7'
|
|
19304
|
-
* })
|
|
19305
|
-
*
|
|
19306
|
-
* // Create BridgeKit with developer fees
|
|
19307
|
-
* const bridgeKit = createBridgeKit(context)
|
|
19308
|
-
* ```
|
|
19309
|
-
*
|
|
19310
|
-
* @example
|
|
19311
|
-
* ```typescript
|
|
19312
|
-
* import { createBridgeKit } from '@circle-fin/app-kit/utils'
|
|
19313
|
-
* import { createContext } from '@circle-fin/app-kit/context'
|
|
19314
|
-
*
|
|
19315
|
-
* // Create context without fee methods
|
|
19316
|
-
* const context = createContext()
|
|
19317
|
-
*
|
|
19318
|
-
* // Create standard BridgeKit instance
|
|
19319
|
-
* const bridgeKit = createBridgeKit(context)
|
|
19320
|
-
* ```
|
|
19321
|
-
*/ const createBridgeKit = (context)=>{
|
|
19322
|
-
const getFee = context.getFee?.bind(context);
|
|
19323
|
-
const getFeeRecipient = context.getFeeRecipient?.bind(context);
|
|
19324
|
-
const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
|
|
19325
|
-
const kit = new BridgeKit({
|
|
19326
|
-
...context.disableErrorReporting != null && {
|
|
19327
|
-
disableErrorReporting: context.disableErrorReporting
|
|
19328
|
-
},
|
|
19329
|
-
...context.headers != null && {
|
|
19330
|
-
headers: context.headers
|
|
19331
|
-
}
|
|
19332
|
-
});
|
|
19333
|
-
if (hasBoth) {
|
|
19334
|
-
kit.setCustomFeePolicy({
|
|
19335
|
-
calculateFee: async (params)=>{
|
|
19336
|
-
const feeStr = await getFee('bridge', params);
|
|
19337
|
-
return feeStr;
|
|
19338
|
-
},
|
|
19339
|
-
resolveFeeRecipientAddress: async (chain, params)=>await getFeeRecipient('bridge', {
|
|
19340
|
-
chain,
|
|
19341
|
-
params: params || {}
|
|
19342
|
-
})
|
|
19343
|
-
});
|
|
19344
|
-
}
|
|
19345
|
-
return kit;
|
|
19346
|
-
};
|
|
19347
|
-
|
|
19348
19576
|
var name$3 = "@circle-fin/swap-kit";
|
|
19349
|
-
var version$3 = "1.
|
|
19577
|
+
var version$3 = "1.5.1";
|
|
19350
19578
|
var pkg$3 = {
|
|
19351
19579
|
name: name$3,
|
|
19352
19580
|
version: version$3};
|
|
@@ -20179,6 +20407,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20179
20407
|
required_error: 'estimatedAmount is required',
|
|
20180
20408
|
invalid_type_error: 'estimatedAmount must be a string'
|
|
20181
20409
|
}).min(1, 'estimatedAmount must be a non-empty string'),
|
|
20410
|
+
// Per-swap join key echoed back verbatim on success telemetry. Optional so a
|
|
20411
|
+
// not-yet-upgraded service (no field) still validates during rollout. A
|
|
20412
|
+
// malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
|
|
20413
|
+
// than throwing: this is a telemetry-only field (stripped from the developer
|
|
20414
|
+
// result, never used for control flow), so it must not be able to abort the
|
|
20415
|
+
// swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
|
|
20416
|
+
// never-throw contract of the rest of the telemetry stack. Implemented with
|
|
20417
|
+
// `preprocess` rather than Zod's `.catch()` because static analysis misreads
|
|
20418
|
+
// `.catch` on the schema chain as an unhandled Promise (S7785).
|
|
20419
|
+
correlationId: z.preprocess((value)=>z.string().uuid().safeParse(value).success ? value : undefined, z.string().optional()),
|
|
20182
20420
|
config: createSwapRequestBaseSchema.shape.config.optional(),
|
|
20183
20421
|
fees: createSwapFeesSchema.optional(),
|
|
20184
20422
|
transaction: createSwapTransactionSchema
|
|
@@ -20304,6 +20542,45 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20304
20542
|
// Validate without trimming - any whitespace will cause validation to fail
|
|
20305
20543
|
return apiKeyPattern.test(apiKey);
|
|
20306
20544
|
};
|
|
20545
|
+
/**
|
|
20546
|
+
* Assert that a Stablecoin Service kit key is not being supplied from a browser.
|
|
20547
|
+
*
|
|
20548
|
+
* The kit key (`KIT_KEY:<id>:<secret>`) is a server-only secret. Every
|
|
20549
|
+
* Stablecoin Service request that attaches an `Authorization: Bearer` header
|
|
20550
|
+
* funnels through this package, so calling this guard before that header is
|
|
20551
|
+
* built prevents the secret from being sent from — and thus bundled into — a
|
|
20552
|
+
* client application. In Node.js the check is a no-op, preserving the
|
|
20553
|
+
* legitimate "hold the kit key on the server, forward the prepared transaction
|
|
20554
|
+
* to the client" flow. When no kit key is supplied the permissionless (keyless)
|
|
20555
|
+
* client path remains fully allowed.
|
|
20556
|
+
*
|
|
20557
|
+
* @param apiKey - The inline kit key for the request, or `undefined` when none
|
|
20558
|
+
* was supplied (permissionless mode).
|
|
20559
|
+
* @returns Nothing.
|
|
20560
|
+
* @throws KitError with VALIDATION_FAILED when a kit key is supplied while
|
|
20561
|
+
* running in a browser environment. The secret value is never echoed.
|
|
20562
|
+
*
|
|
20563
|
+
* @example
|
|
20564
|
+
* ```typescript
|
|
20565
|
+
* import { assertBrowserSafeApiKey } from '@core/service-client'
|
|
20566
|
+
*
|
|
20567
|
+
* // Server (Node.js): no-op, request proceeds with the Authorization header.
|
|
20568
|
+
* assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
|
|
20569
|
+
*
|
|
20570
|
+
* // Browser: throws to stop the secret from leaking into the client bundle.
|
|
20571
|
+
* assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
|
|
20572
|
+
*
|
|
20573
|
+
* // Browser, permissionless: allowed.
|
|
20574
|
+
* assertBrowserSafeApiKey(undefined)
|
|
20575
|
+
* ```
|
|
20576
|
+
*/ const assertBrowserSafeApiKey = (apiKey)=>{
|
|
20577
|
+
if (apiKey === undefined) {
|
|
20578
|
+
return;
|
|
20579
|
+
}
|
|
20580
|
+
if (isBrowserEnvironment()) {
|
|
20581
|
+
throw createValidationFailedError$1('kitKey', '[redacted]', 'kitKey must not be provided in a browser environment — it is a server-only secret. Run kit operations that use a kit key on your server, or omit kitKey to use the permissionless (keyless) client path');
|
|
20582
|
+
}
|
|
20583
|
+
};
|
|
20307
20584
|
|
|
20308
20585
|
/**
|
|
20309
20586
|
* Create a cross-chain bridge and swap transaction through the Stablecoin Service.
|
|
@@ -20361,6 +20638,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20361
20638
|
const url = new URL('/v1/stablecoinKits/swap', STABLECOIN_SERVICE_BASE_URL).toString();
|
|
20362
20639
|
// Remove the API key from the request body
|
|
20363
20640
|
const { apiKey, ...requestBody } = validatedParams;
|
|
20641
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
20642
|
+
assertBrowserSafeApiKey(apiKey);
|
|
20364
20643
|
const effectiveConfig = {
|
|
20365
20644
|
...DEFAULT_CONFIG$1,
|
|
20366
20645
|
headers: {
|
|
@@ -20515,6 +20794,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20515
20794
|
}
|
|
20516
20795
|
// Use validated data
|
|
20517
20796
|
const validatedParams = result.data;
|
|
20797
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
20798
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
20518
20799
|
// Build the API URL
|
|
20519
20800
|
const url = buildQuoteUrl(validatedParams);
|
|
20520
20801
|
// Merge default config with Authorization header
|
|
@@ -20587,6 +20868,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20587
20868
|
toChain: result.data.toChain
|
|
20588
20869
|
}
|
|
20589
20870
|
};
|
|
20871
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
20872
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
20590
20873
|
const url = buildSwapStatusUrl(validatedParams);
|
|
20591
20874
|
const effectiveConfig = {
|
|
20592
20875
|
...DEFAULT_CONFIG$1,
|
|
@@ -20691,6 +20974,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20691
20974
|
addresses: result.data.addresses
|
|
20692
20975
|
}
|
|
20693
20976
|
};
|
|
20977
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
20978
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
20694
20979
|
const url = buildTokenRatesUrl(validatedParams);
|
|
20695
20980
|
const effectiveConfig = {
|
|
20696
20981
|
...DEFAULT_CONFIG$1,
|
|
@@ -20705,6 +20990,138 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20705
20990
|
return pollApiGet(url, isGetTokenRatesResponse, effectiveConfig);
|
|
20706
20991
|
};
|
|
20707
20992
|
|
|
20993
|
+
/**
|
|
20994
|
+
* IAdapter contract ABI.
|
|
20995
|
+
*
|
|
20996
|
+
* Shared ABI for the on-chain Adapter contract used by multiple kits
|
|
20997
|
+
* (swap, earn) for executing signed instruction sets. The `execute()`
|
|
20998
|
+
* function accepts EIP-712 signed execution parameters, token inputs,
|
|
20999
|
+
* and a signature, then executes the corresponding on-chain
|
|
21000
|
+
* instructions.
|
|
21001
|
+
*/ const adapterContractAbi = [
|
|
21002
|
+
{
|
|
21003
|
+
type: 'function',
|
|
21004
|
+
name: 'execute',
|
|
21005
|
+
inputs: [
|
|
21006
|
+
{
|
|
21007
|
+
name: 'params',
|
|
21008
|
+
type: 'tuple',
|
|
21009
|
+
internalType: 'struct IAdapter.ExecutionParams',
|
|
21010
|
+
components: [
|
|
21011
|
+
{
|
|
21012
|
+
name: 'instructions',
|
|
21013
|
+
type: 'tuple[]',
|
|
21014
|
+
internalType: 'struct IAdapter.Instruction[]',
|
|
21015
|
+
components: [
|
|
21016
|
+
{
|
|
21017
|
+
name: 'target',
|
|
21018
|
+
type: 'address',
|
|
21019
|
+
internalType: 'address'
|
|
21020
|
+
},
|
|
21021
|
+
{
|
|
21022
|
+
name: 'data',
|
|
21023
|
+
type: 'bytes',
|
|
21024
|
+
internalType: 'bytes'
|
|
21025
|
+
},
|
|
21026
|
+
{
|
|
21027
|
+
name: 'value',
|
|
21028
|
+
type: 'uint256',
|
|
21029
|
+
internalType: 'uint256'
|
|
21030
|
+
},
|
|
21031
|
+
{
|
|
21032
|
+
name: 'tokenIn',
|
|
21033
|
+
type: 'address',
|
|
21034
|
+
internalType: 'address'
|
|
21035
|
+
},
|
|
21036
|
+
{
|
|
21037
|
+
name: 'amountToApprove',
|
|
21038
|
+
type: 'uint256',
|
|
21039
|
+
internalType: 'uint256'
|
|
21040
|
+
},
|
|
21041
|
+
{
|
|
21042
|
+
name: 'tokenOut',
|
|
21043
|
+
type: 'address',
|
|
21044
|
+
internalType: 'address'
|
|
21045
|
+
},
|
|
21046
|
+
{
|
|
21047
|
+
name: 'minTokenOut',
|
|
21048
|
+
type: 'uint256',
|
|
21049
|
+
internalType: 'uint256'
|
|
21050
|
+
}
|
|
21051
|
+
]
|
|
21052
|
+
},
|
|
21053
|
+
{
|
|
21054
|
+
name: 'tokens',
|
|
21055
|
+
type: 'tuple[]',
|
|
21056
|
+
internalType: 'struct IAdapter.TokenRecipient[]',
|
|
21057
|
+
components: [
|
|
21058
|
+
{
|
|
21059
|
+
name: 'token',
|
|
21060
|
+
type: 'address',
|
|
21061
|
+
internalType: 'address'
|
|
21062
|
+
},
|
|
21063
|
+
{
|
|
21064
|
+
name: 'beneficiary',
|
|
21065
|
+
type: 'address',
|
|
21066
|
+
internalType: 'address'
|
|
21067
|
+
}
|
|
21068
|
+
]
|
|
21069
|
+
},
|
|
21070
|
+
{
|
|
21071
|
+
name: 'execId',
|
|
21072
|
+
type: 'uint256',
|
|
21073
|
+
internalType: 'uint256'
|
|
21074
|
+
},
|
|
21075
|
+
{
|
|
21076
|
+
name: 'deadline',
|
|
21077
|
+
type: 'uint256',
|
|
21078
|
+
internalType: 'uint256'
|
|
21079
|
+
},
|
|
21080
|
+
{
|
|
21081
|
+
name: 'metadata',
|
|
21082
|
+
type: 'bytes',
|
|
21083
|
+
internalType: 'bytes'
|
|
21084
|
+
}
|
|
21085
|
+
]
|
|
21086
|
+
},
|
|
21087
|
+
{
|
|
21088
|
+
name: 'tokenInputs',
|
|
21089
|
+
type: 'tuple[]',
|
|
21090
|
+
internalType: 'struct IAdapter.TokenInput[]',
|
|
21091
|
+
components: [
|
|
21092
|
+
{
|
|
21093
|
+
name: 'permitType',
|
|
21094
|
+
type: 'uint8',
|
|
21095
|
+
internalType: 'enum IAdapter.PermitType'
|
|
21096
|
+
},
|
|
21097
|
+
{
|
|
21098
|
+
name: 'token',
|
|
21099
|
+
type: 'address',
|
|
21100
|
+
internalType: 'address'
|
|
21101
|
+
},
|
|
21102
|
+
{
|
|
21103
|
+
name: 'amount',
|
|
21104
|
+
type: 'uint256',
|
|
21105
|
+
internalType: 'uint256'
|
|
21106
|
+
},
|
|
21107
|
+
{
|
|
21108
|
+
name: 'permitCalldata',
|
|
21109
|
+
type: 'bytes',
|
|
21110
|
+
internalType: 'bytes'
|
|
21111
|
+
}
|
|
21112
|
+
]
|
|
21113
|
+
},
|
|
21114
|
+
{
|
|
21115
|
+
name: 'signature',
|
|
21116
|
+
type: 'bytes',
|
|
21117
|
+
internalType: 'bytes'
|
|
21118
|
+
}
|
|
21119
|
+
],
|
|
21120
|
+
outputs: [],
|
|
21121
|
+
stateMutability: 'payable'
|
|
21122
|
+
}
|
|
21123
|
+
];
|
|
21124
|
+
|
|
20708
21125
|
/**
|
|
20709
21126
|
* USDC ABI
|
|
20710
21127
|
*
|
|
@@ -21940,6 +22357,179 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21940
22357
|
}
|
|
21941
22358
|
];
|
|
21942
22359
|
|
|
22360
|
+
/**
|
|
22361
|
+
* Minimal ERC-4626 tokenized-vault ABI.
|
|
22362
|
+
*
|
|
22363
|
+
* Covers only the mutating methods EarnKit bundles as inner instructions inside
|
|
22364
|
+
* an Adapter `execute()` call: `deposit`, `withdraw`, and `redeem`. It exists so
|
|
22365
|
+
* clients can decode the inner instruction calldata into a human-readable
|
|
22366
|
+
* summary of what a signer is authorizing (asset amount, receiver, owner)
|
|
22367
|
+
* rather than showing opaque bytes. The 4-byte selectors match the calldata the
|
|
22368
|
+
* earn service signs (`deposit(uint256,address)` = `0x6e553f65`,
|
|
22369
|
+
* `withdraw(uint256,address,address)` = `0xb460af94`,
|
|
22370
|
+
* `redeem(uint256,address,address)` = `0xba087652`).
|
|
22371
|
+
*/ const erc4626VaultAbi = [
|
|
22372
|
+
{
|
|
22373
|
+
type: 'function',
|
|
22374
|
+
name: 'deposit',
|
|
22375
|
+
stateMutability: 'nonpayable',
|
|
22376
|
+
inputs: [
|
|
22377
|
+
{
|
|
22378
|
+
name: 'assets',
|
|
22379
|
+
type: 'uint256',
|
|
22380
|
+
internalType: 'uint256'
|
|
22381
|
+
},
|
|
22382
|
+
{
|
|
22383
|
+
name: 'receiver',
|
|
22384
|
+
type: 'address',
|
|
22385
|
+
internalType: 'address'
|
|
22386
|
+
}
|
|
22387
|
+
],
|
|
22388
|
+
outputs: [
|
|
22389
|
+
{
|
|
22390
|
+
name: 'shares',
|
|
22391
|
+
type: 'uint256',
|
|
22392
|
+
internalType: 'uint256'
|
|
22393
|
+
}
|
|
22394
|
+
]
|
|
22395
|
+
},
|
|
22396
|
+
{
|
|
22397
|
+
type: 'function',
|
|
22398
|
+
name: 'withdraw',
|
|
22399
|
+
stateMutability: 'nonpayable',
|
|
22400
|
+
inputs: [
|
|
22401
|
+
{
|
|
22402
|
+
name: 'assets',
|
|
22403
|
+
type: 'uint256',
|
|
22404
|
+
internalType: 'uint256'
|
|
22405
|
+
},
|
|
22406
|
+
{
|
|
22407
|
+
name: 'receiver',
|
|
22408
|
+
type: 'address',
|
|
22409
|
+
internalType: 'address'
|
|
22410
|
+
},
|
|
22411
|
+
{
|
|
22412
|
+
name: 'owner',
|
|
22413
|
+
type: 'address',
|
|
22414
|
+
internalType: 'address'
|
|
22415
|
+
}
|
|
22416
|
+
],
|
|
22417
|
+
outputs: [
|
|
22418
|
+
{
|
|
22419
|
+
name: 'shares',
|
|
22420
|
+
type: 'uint256',
|
|
22421
|
+
internalType: 'uint256'
|
|
22422
|
+
}
|
|
22423
|
+
]
|
|
22424
|
+
},
|
|
22425
|
+
{
|
|
22426
|
+
type: 'function',
|
|
22427
|
+
name: 'redeem',
|
|
22428
|
+
stateMutability: 'nonpayable',
|
|
22429
|
+
inputs: [
|
|
22430
|
+
{
|
|
22431
|
+
name: 'shares',
|
|
22432
|
+
type: 'uint256',
|
|
22433
|
+
internalType: 'uint256'
|
|
22434
|
+
},
|
|
22435
|
+
{
|
|
22436
|
+
name: 'receiver',
|
|
22437
|
+
type: 'address',
|
|
22438
|
+
internalType: 'address'
|
|
22439
|
+
},
|
|
22440
|
+
{
|
|
22441
|
+
name: 'owner',
|
|
22442
|
+
type: 'address',
|
|
22443
|
+
internalType: 'address'
|
|
22444
|
+
}
|
|
22445
|
+
],
|
|
22446
|
+
outputs: [
|
|
22447
|
+
{
|
|
22448
|
+
name: 'assets',
|
|
22449
|
+
type: 'uint256',
|
|
22450
|
+
internalType: 'uint256'
|
|
22451
|
+
}
|
|
22452
|
+
]
|
|
22453
|
+
}
|
|
22454
|
+
];
|
|
22455
|
+
|
|
22456
|
+
/**
|
|
22457
|
+
* Minimal FeeTaker ABI.
|
|
22458
|
+
*
|
|
22459
|
+
* The earn service appends a `takeFeeERC20` instruction to withdraw bundles
|
|
22460
|
+
* when Circle charges a withdrawal fee. This ABI decodes that inner instruction
|
|
22461
|
+
* so the fee (token, beneficiary, amount) is visible in the signing summary
|
|
22462
|
+
* instead of appearing as opaque calldata alongside the redeem/withdraw call.
|
|
22463
|
+
*/ const feeTakerAbi = [
|
|
22464
|
+
{
|
|
22465
|
+
type: 'function',
|
|
22466
|
+
name: 'takeFeeERC20',
|
|
22467
|
+
stateMutability: 'nonpayable',
|
|
22468
|
+
inputs: [
|
|
22469
|
+
{
|
|
22470
|
+
name: 'token',
|
|
22471
|
+
type: 'address',
|
|
22472
|
+
internalType: 'address'
|
|
22473
|
+
},
|
|
22474
|
+
{
|
|
22475
|
+
name: 'beneficiary',
|
|
22476
|
+
type: 'address',
|
|
22477
|
+
internalType: 'address'
|
|
22478
|
+
},
|
|
22479
|
+
{
|
|
22480
|
+
name: 'fee',
|
|
22481
|
+
type: 'uint256',
|
|
22482
|
+
internalType: 'uint256'
|
|
22483
|
+
},
|
|
22484
|
+
{
|
|
22485
|
+
name: 'kitType',
|
|
22486
|
+
type: 'bytes8',
|
|
22487
|
+
internalType: 'bytes8'
|
|
22488
|
+
}
|
|
22489
|
+
],
|
|
22490
|
+
outputs: []
|
|
22491
|
+
}
|
|
22492
|
+
];
|
|
22493
|
+
|
|
22494
|
+
/**
|
|
22495
|
+
* Minimal Merkl Distributor ABI.
|
|
22496
|
+
*
|
|
22497
|
+
* EarnKit claim-rewards bundles a single `claim` instruction targeting the
|
|
22498
|
+
* Merkl Distributor, batching one entry per reward token. This ABI decodes that
|
|
22499
|
+
* inner instruction so the claimed tokens and amounts are visible in the signing
|
|
22500
|
+
* summary. `claim` uses dynamic array arguments, which is why a real ABI decoder
|
|
22501
|
+
* (rather than fixed-word slicing) is required for the reward instruction.
|
|
22502
|
+
*/ const merklDistributorAbi = [
|
|
22503
|
+
{
|
|
22504
|
+
type: 'function',
|
|
22505
|
+
name: 'claim',
|
|
22506
|
+
stateMutability: 'nonpayable',
|
|
22507
|
+
inputs: [
|
|
22508
|
+
{
|
|
22509
|
+
name: 'users',
|
|
22510
|
+
type: 'address[]',
|
|
22511
|
+
internalType: 'address[]'
|
|
22512
|
+
},
|
|
22513
|
+
{
|
|
22514
|
+
name: 'tokens',
|
|
22515
|
+
type: 'address[]',
|
|
22516
|
+
internalType: 'address[]'
|
|
22517
|
+
},
|
|
22518
|
+
{
|
|
22519
|
+
name: 'amounts',
|
|
22520
|
+
type: 'uint256[]',
|
|
22521
|
+
internalType: 'uint256[]'
|
|
22522
|
+
},
|
|
22523
|
+
{
|
|
22524
|
+
name: 'proofs',
|
|
22525
|
+
type: 'bytes32[][]',
|
|
22526
|
+
internalType: 'bytes32[][]'
|
|
22527
|
+
}
|
|
22528
|
+
],
|
|
22529
|
+
outputs: []
|
|
22530
|
+
}
|
|
22531
|
+
];
|
|
22532
|
+
|
|
21943
22533
|
/**
|
|
21944
22534
|
* Zod schema for validating EVM adapter capabilities.
|
|
21945
22535
|
*
|
|
@@ -23448,72 +24038,55 @@ function evmSigningData(burnIntent) {
|
|
|
23448
24038
|
* `0xef0100` followed by the 20-byte delegate address (23 bytes total).
|
|
23449
24039
|
* The underlying secp256k1 key still produces `ecrecover`-verifiable
|
|
23450
24040
|
* signatures, so for Gateway's purposes a 7702-delegated address is
|
|
23451
|
-
* an EOA, not
|
|
24041
|
+
* an EOA, not a contract signer.
|
|
23452
24042
|
*
|
|
23453
24043
|
* Spec: https://eips.ethereum.org/EIPS/eip-7702
|
|
23454
24044
|
*/ const EIP_7702_DELEGATION_PREFIX = '0xef0100';
|
|
23455
24045
|
/**
|
|
23456
|
-
*
|
|
23457
|
-
*
|
|
23458
|
-
* Gateway verifies burn-intent signatures with plain `ecrecover` (see
|
|
23459
|
-
* `evm-gateway-contracts/src/lib/EIP712Domain.sol`). Smart-contract
|
|
23460
|
-
* accounts (SCAs) produce signatures over wrapped hashes (ERC-1271 /
|
|
23461
|
-
* ERC-6492 / ERC-6900 replay-safe hashes) that Gateway cannot verify.
|
|
23462
|
-
* Additionally, the Circle Wallets backend rejects SCA typed-data signing
|
|
23463
|
-
* against Gateway's chainId-less domain with an opaque
|
|
23464
|
-
* `invalid integer value <nil>/<nil> for type uint256` error.
|
|
24046
|
+
* Determine whether `address` on `chain` signs as a contract (ERC-1271)
|
|
24047
|
+
* rather than as an EOA.
|
|
23465
24048
|
*
|
|
23466
|
-
*
|
|
23467
|
-
*
|
|
23468
|
-
* `
|
|
24049
|
+
* Gateway validates burn-intent signatures two ways: a static `ecrecover`
|
|
24050
|
+
* check for EOAs, and — for requests that carry `contractSigner: true` —
|
|
24051
|
+
* an offchain `isValidSignature` simulation against the signing contract
|
|
24052
|
+
* (ERC-1271). Gateway does not infer which one to use, so the caller must
|
|
24053
|
+
* declare it. This detects the contract case from on-chain bytecode.
|
|
23469
24054
|
*
|
|
23470
|
-
*
|
|
23471
|
-
*
|
|
23472
|
-
*
|
|
23473
|
-
*
|
|
23474
|
-
* docs for the exact API.
|
|
24055
|
+
* EIP-7702-delegated EOAs are treated as EOAs: they expose non-empty
|
|
24056
|
+
* bytecode (`0xef0100<delegate>`) but the underlying secp256k1 key still
|
|
24057
|
+
* produces `ecrecover`-verifiable signatures, so the cheaper EOA path
|
|
24058
|
+
* stays correct for them.
|
|
23475
24059
|
*
|
|
23476
|
-
* If bytecode cannot be read (RPC failure, etc.) the
|
|
23477
|
-
*
|
|
23478
|
-
*
|
|
24060
|
+
* If bytecode cannot be read (RPC failure, etc.) the address is reported
|
|
24061
|
+
* as an EOA and a warning is logged so the fallback is diagnosable. A
|
|
24062
|
+
* genuine contract signer misreported this way is rejected by Gateway with
|
|
24063
|
+
* an invalid-signature error rather than silently mis-attested.
|
|
23479
24064
|
*
|
|
23480
24065
|
* @param adapter - Anything exposing {@link EvmAdapterLike.readBytecode}.
|
|
23481
|
-
* @param address - Signer address to
|
|
24066
|
+
* @param address - Signer address to classify.
|
|
23482
24067
|
* @param chain - EVM chain where the signer lives.
|
|
23483
|
-
* @
|
|
24068
|
+
* @returns `true` when the signer is a contract account and the transfer
|
|
24069
|
+
* request must set `contractSigner: true`; `false` otherwise.
|
|
23484
24070
|
*
|
|
23485
24071
|
* @example
|
|
23486
24072
|
* ```typescript
|
|
23487
|
-
* import {
|
|
24073
|
+
* import { isContractSigner } from '@core/adapter-evm'
|
|
23488
24074
|
* import { Ethereum } from '@core/chains'
|
|
23489
24075
|
*
|
|
23490
|
-
* await
|
|
24076
|
+
* const useErc1271 = await isContractSigner(adapter, '0xabc...', Ethereum)
|
|
23491
24077
|
* ```
|
|
23492
|
-
*/ async function
|
|
24078
|
+
*/ async function isContractSigner(adapter, address, chain) {
|
|
23493
24079
|
let code;
|
|
23494
24080
|
try {
|
|
23495
24081
|
code = await adapter.readBytecode(address, chain);
|
|
23496
24082
|
} catch (err) {
|
|
23497
|
-
console.warn(`[gateway]
|
|
23498
|
-
return;
|
|
24083
|
+
console.warn(`[gateway] isContractSigner defaulting to EOA (readBytecode failed ` + `for ${address} on ${chain.name}): ` + (err instanceof Error ? err.message : String(err)));
|
|
24084
|
+
return false;
|
|
23499
24085
|
}
|
|
23500
24086
|
if (code === undefined || code === '0x' || code.toLowerCase().startsWith(EIP_7702_DELEGATION_PREFIX)) {
|
|
23501
|
-
return;
|
|
24087
|
+
return false;
|
|
23502
24088
|
}
|
|
23503
|
-
|
|
23504
|
-
...InputError.UNSUPPORTED_ACTION,
|
|
23505
|
-
recoverability: 'FATAL',
|
|
23506
|
-
message: `Gateway burn-intent signing requires an EOA signer (Gateway ` + `verifies signatures with ecrecover and does not support ERC-1271). ` + `The signer ${address} on ${chain.name} has on-chain bytecode, ` + `indicating it is a smart-contract account (SCA). Register an EOA ` + `delegate against the SCA, then submit the spend with the delegate ` + `EOA as the signer and the SCA as the source account. See DEVX-2774.`,
|
|
23507
|
-
cause: {
|
|
23508
|
-
trace: {
|
|
23509
|
-
operation: 'signEvmIntentGroup.assertSignerIsEoa',
|
|
23510
|
-
address,
|
|
23511
|
-
chain: chain.name,
|
|
23512
|
-
bytecodeBytes: (code.length - 2) / 2,
|
|
23513
|
-
bytecodePrefix: code.slice(0, 12)
|
|
23514
|
-
}
|
|
23515
|
-
}
|
|
23516
|
-
});
|
|
24089
|
+
return true;
|
|
23517
24090
|
}
|
|
23518
24091
|
|
|
23519
24092
|
/**
|
|
@@ -23540,78 +24113,177 @@ function evmSigningData(burnIntent) {
|
|
|
23540
24113
|
return typeof value === 'object' && value !== null && 'readBytecode' in value && typeof value.readBytecode === 'function';
|
|
23541
24114
|
}
|
|
23542
24115
|
|
|
24116
|
+
function resolveIntentChain(group, intent) {
|
|
24117
|
+
const sourceDomain = intent.spec.sourceDomain;
|
|
24118
|
+
const chain = group.chainsByDomain.get(sourceDomain);
|
|
24119
|
+
if (chain !== undefined) return chain;
|
|
24120
|
+
throw createValidationFailedError$1('intent.spec.sourceDomain', sourceDomain, `No source chain found for Gateway domain ${String(sourceDomain)}`);
|
|
24121
|
+
}
|
|
24122
|
+
function normalizeSignatureResult(result) {
|
|
24123
|
+
if (typeof result === 'string') {
|
|
24124
|
+
return {
|
|
24125
|
+
signature: result,
|
|
24126
|
+
contractSigner: false
|
|
24127
|
+
};
|
|
24128
|
+
}
|
|
24129
|
+
if (typeof result === 'object' && result !== null && 'signature' in result && typeof result.signature === 'string') {
|
|
24130
|
+
return {
|
|
24131
|
+
signature: result.signature,
|
|
24132
|
+
contractSigner: 'contractSigner' in result && result.contractSigner === true
|
|
24133
|
+
};
|
|
24134
|
+
}
|
|
24135
|
+
throw createValidationFailedError$1('signature', result, 'must be a signature string or an object containing a signature string');
|
|
24136
|
+
}
|
|
24137
|
+
function validateGroupIntents(intents) {
|
|
24138
|
+
evmSigningData(intents);
|
|
24139
|
+
}
|
|
24140
|
+
function collectChainsByDomain(group) {
|
|
24141
|
+
const chainsByDomain = new Map();
|
|
24142
|
+
for (const intent of group.intents){
|
|
24143
|
+
chainsByDomain.set(intent.spec.sourceDomain, resolveIntentChain(group, intent));
|
|
24144
|
+
}
|
|
24145
|
+
return chainsByDomain;
|
|
24146
|
+
}
|
|
24147
|
+
async function classifySignerTypes(group, chainsByDomain) {
|
|
24148
|
+
const { adapter, address } = group;
|
|
24149
|
+
// Duck-typed on readBytecode rather than `instanceof EvmAdapter` because
|
|
24150
|
+
// each consumer package bundles its own copy of the base class and the
|
|
24151
|
+
// `instanceof` identity check fails across package boundaries.
|
|
24152
|
+
// Empty strings are rejected to avoid calling eth_getCode('') on the RPC.
|
|
24153
|
+
const hasResolvedSigner = typeof address === 'string' && address.length > 0;
|
|
24154
|
+
const signerTypes = await Promise.all([
|
|
24155
|
+
...chainsByDomain
|
|
24156
|
+
].map(async ([sourceDomain, sourceChain])=>{
|
|
24157
|
+
const contractSigner = hasResolvedSigner && sourceChain.type === 'evm' && isEvmAdapterLike(adapter) ? await isContractSigner(adapter, address, sourceChain) : false;
|
|
24158
|
+
return [
|
|
24159
|
+
sourceDomain,
|
|
24160
|
+
contractSigner
|
|
24161
|
+
];
|
|
24162
|
+
}));
|
|
24163
|
+
return new Map(signerTypes);
|
|
24164
|
+
}
|
|
24165
|
+
function createSigningUnits(group, signerTypeByDomain) {
|
|
24166
|
+
const contractUnitsByDomain = new Map();
|
|
24167
|
+
let eoaUnit;
|
|
24168
|
+
for (const [index, intent] of group.intents.entries()){
|
|
24169
|
+
const sourceDomain = intent.spec.sourceDomain;
|
|
24170
|
+
const contractSigner = signerTypeByDomain.get(sourceDomain) ?? false;
|
|
24171
|
+
if (contractSigner) {
|
|
24172
|
+
const existingUnit = contractUnitsByDomain.get(sourceDomain);
|
|
24173
|
+
if (existingUnit === undefined) {
|
|
24174
|
+
contractUnitsByDomain.set(sourceDomain, {
|
|
24175
|
+
intents: [
|
|
24176
|
+
intent
|
|
24177
|
+
],
|
|
24178
|
+
chain: resolveIntentChain(group, intent),
|
|
24179
|
+
contractSigner: true,
|
|
24180
|
+
firstIntentIndex: index
|
|
24181
|
+
});
|
|
24182
|
+
} else {
|
|
24183
|
+
existingUnit.intents.push(intent);
|
|
24184
|
+
}
|
|
24185
|
+
} else {
|
|
24186
|
+
eoaUnit ??= {
|
|
24187
|
+
intents: [],
|
|
24188
|
+
chain: resolveIntentChain(group, intent),
|
|
24189
|
+
contractSigner: false,
|
|
24190
|
+
firstIntentIndex: index
|
|
24191
|
+
};
|
|
24192
|
+
eoaUnit.intents.push(intent);
|
|
24193
|
+
}
|
|
24194
|
+
}
|
|
24195
|
+
const signingUnits = [
|
|
24196
|
+
...contractUnitsByDomain.values()
|
|
24197
|
+
];
|
|
24198
|
+
if (eoaUnit !== undefined) signingUnits.push(eoaUnit);
|
|
24199
|
+
signingUnits.sort((a, b)=>a.firstIntentIndex - b.firstIntentIndex);
|
|
24200
|
+
return signingUnits;
|
|
24201
|
+
}
|
|
24202
|
+
async function signUnit(group, unit) {
|
|
24203
|
+
const { adapter, address } = group;
|
|
24204
|
+
const firstIntent = unit.intents[0];
|
|
24205
|
+
const typedData = unit.intents.length === 1 && firstIntent !== undefined ? evmSigningData(firstIntent) : evmSigningData(unit.intents);
|
|
24206
|
+
const operationContext = address === undefined ? {
|
|
24207
|
+
chain: unit.chain
|
|
24208
|
+
} : {
|
|
24209
|
+
chain: unit.chain,
|
|
24210
|
+
address
|
|
24211
|
+
};
|
|
24212
|
+
const signRequest = await adapter.prepareAction('gateway.v1.signBurnIntents', {
|
|
24213
|
+
typedData,
|
|
24214
|
+
chain: unit.chain
|
|
24215
|
+
}, operationContext);
|
|
24216
|
+
const result = normalizeSignatureResult(await signRequest.execute());
|
|
24217
|
+
return {
|
|
24218
|
+
intents: unit.intents,
|
|
24219
|
+
signature: result.signature,
|
|
24220
|
+
contractSigner: result.contractSigner || unit.contractSigner
|
|
24221
|
+
};
|
|
24222
|
+
}
|
|
24223
|
+
async function signUnits(group, signingUnits) {
|
|
24224
|
+
const signedSets = [];
|
|
24225
|
+
// Keep wallet prompts deterministic. Multiple adapter groups can still sign
|
|
24226
|
+
// in parallel, but one signer is asked for its chain-bound signatures in
|
|
24227
|
+
// source-intent order.
|
|
24228
|
+
for (const unit of signingUnits){
|
|
24229
|
+
signedSets.push(await signUnit(group, unit));
|
|
24230
|
+
}
|
|
24231
|
+
return signedSets;
|
|
24232
|
+
}
|
|
23543
24233
|
/**
|
|
23544
|
-
* Sign an EVM adapter group
|
|
23545
|
-
* EIP-712 ECDSA signature.
|
|
24234
|
+
* Sign an EVM adapter group.
|
|
23546
24235
|
*
|
|
23547
|
-
*
|
|
23548
|
-
*
|
|
24236
|
+
* EOA intents remain batched into one EIP-712 `BurnIntentSet`. ERC-1271
|
|
24237
|
+
* intents are grouped and signed per source chain because smart accounts
|
|
24238
|
+
* commonly include `chainId` in their replay-safe signature hash.
|
|
24239
|
+
* All returned entries can still be submitted together in one atomic Gateway
|
|
24240
|
+
* transfer request.
|
|
23549
24241
|
*
|
|
23550
|
-
* Before signing,
|
|
23551
|
-
*
|
|
23552
|
-
*
|
|
23553
|
-
*
|
|
23554
|
-
*
|
|
24242
|
+
* Before signing, classifies the signer as an EOA or a contract account.
|
|
24243
|
+
* Gateway validates EOA signatures with `ecrecover` and contract-account
|
|
24244
|
+
* signatures with ERC-1271, but it does not infer which one applies — the
|
|
24245
|
+
* transfer request has to declare it. The returned `contractSigner` flag
|
|
24246
|
+
* carries that decision through to `buildTransferRequestBody`.
|
|
23555
24247
|
*
|
|
23556
24248
|
* @param group - The adapter group containing the adapter, chain, and
|
|
23557
24249
|
* burn intents to sign.
|
|
23558
|
-
* @returns
|
|
24250
|
+
* @returns Signed entries with their intents, signatures, and Gateway signer
|
|
24251
|
+
* validation mode.
|
|
24252
|
+
* @throws KitError when an intent has no source-chain mapping or a signing
|
|
24253
|
+
* action returns an invalid signature shape.
|
|
23559
24254
|
*
|
|
23560
24255
|
* @example
|
|
23561
24256
|
* ```typescript
|
|
23562
24257
|
* import { signEvmIntentGroup } from '@core/adapter-evm'
|
|
23563
24258
|
*
|
|
23564
|
-
* const
|
|
24259
|
+
* const signedSets = await signEvmIntentGroup({
|
|
23565
24260
|
* adapter: evmAdapter,
|
|
23566
24261
|
* chain: ethereumChain,
|
|
23567
24262
|
* intents: [burnIntent1, burnIntent2],
|
|
24263
|
+
* chainsByDomain: new Map([
|
|
24264
|
+
* [0, ethereumChain],
|
|
24265
|
+
* [6, baseChain],
|
|
24266
|
+
* ]),
|
|
23568
24267
|
* address: '0x...',
|
|
23569
24268
|
* })
|
|
23570
|
-
* console.log(
|
|
24269
|
+
* console.log(signedSets)
|
|
23571
24270
|
* ```
|
|
23572
24271
|
*/ async function signEvmIntentGroup(group) {
|
|
23573
|
-
|
|
23574
|
-
|
|
23575
|
-
|
|
23576
|
-
|
|
23577
|
-
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
// Gateway verifies burn-intent signatures with plain ecrecover. An SCA
|
|
23581
|
-
// signer silently produces a signature over a wrapped hash that Gateway
|
|
23582
|
-
// cannot verify, and Circle Wallets' KMS rejects the typed data up front
|
|
23583
|
-
// with an opaque `<nil>/<nil>` error. Short-circuit with a clear message
|
|
23584
|
-
// when we can detect bytecode at the signer address. See DEVX-2774.
|
|
23585
|
-
//
|
|
23586
|
-
// Duck-typed on readBytecode rather than `instanceof EvmAdapter` because
|
|
23587
|
-
// each consumer package bundles its own copy of the base class and the
|
|
23588
|
-
// `instanceof` identity check fails across package boundaries.
|
|
23589
|
-
//
|
|
23590
|
-
// Empty string is defended against because assertSignerIsEoa would
|
|
23591
|
-
// otherwise call eth_getCode('') on the RPC.
|
|
23592
|
-
const hasResolvedSigner = typeof address === 'string' && address.length > 0;
|
|
23593
|
-
if (hasResolvedSigner && chain.type === 'evm' && isEvmAdapterLike(adapter)) {
|
|
23594
|
-
await assertSignerIsEoa(adapter, address, chain);
|
|
23595
|
-
}
|
|
23596
|
-
const firstIntent = groupIntents[0];
|
|
23597
|
-
const typedData = groupIntents.length === 1 && firstIntent ? evmSigningData(firstIntent) : evmSigningData(groupIntents);
|
|
23598
|
-
const signRequest = await adapter.prepareAction('gateway.v1.signBurnIntents', {
|
|
23599
|
-
typedData,
|
|
23600
|
-
chain
|
|
23601
|
-
}, operationContext);
|
|
23602
|
-
const sig = await signRequest.execute();
|
|
23603
|
-
return {
|
|
23604
|
-
intents: groupIntents,
|
|
23605
|
-
signature: sig
|
|
23606
|
-
};
|
|
24272
|
+
// Validate the collection before doing bytecode reads or asking a wallet
|
|
24273
|
+
// to sign. evmSigningData owns the canonical BurnIntent validation.
|
|
24274
|
+
validateGroupIntents(group.intents);
|
|
24275
|
+
const chainsByDomain = collectChainsByDomain(group);
|
|
24276
|
+
const signerTypeByDomain = await classifySignerTypes(group, chainsByDomain);
|
|
24277
|
+
const signingUnits = createSigningUnits(group, signerTypeByDomain);
|
|
24278
|
+
return await signUnits(group, signingUnits);
|
|
23607
24279
|
}
|
|
23608
24280
|
|
|
23609
24281
|
/**
|
|
23610
24282
|
* Add an EVM intent into the batched EVM group map.
|
|
23611
24283
|
*
|
|
23612
24284
|
* On EVM, all intents for the same adapter are batched into a single
|
|
23613
|
-
* group
|
|
23614
|
-
*
|
|
24285
|
+
* group. The signing step uses `chainsByDomain` to preserve EOA batching
|
|
24286
|
+
* while signing ERC-1271 intents separately on their source chains.
|
|
23615
24287
|
*
|
|
23616
24288
|
* @param intent - The burn intent to group.
|
|
23617
24289
|
* @param alloc - The allocation that resolved to this intent.
|
|
@@ -23628,6 +24300,7 @@ function evmSigningData(burnIntent) {
|
|
|
23628
24300
|
const existing = evmGroups.get(alloc.adapter);
|
|
23629
24301
|
if (existing) {
|
|
23630
24302
|
existing.intents.push(intent);
|
|
24303
|
+
existing.chainsByDomain.set(alloc.chain.gateway.domain, alloc.chain);
|
|
23631
24304
|
} else {
|
|
23632
24305
|
evmGroups.set(alloc.adapter, {
|
|
23633
24306
|
adapter: alloc.adapter,
|
|
@@ -23635,6 +24308,12 @@ function evmSigningData(burnIntent) {
|
|
|
23635
24308
|
intents: [
|
|
23636
24309
|
intent
|
|
23637
24310
|
],
|
|
24311
|
+
chainsByDomain: new Map([
|
|
24312
|
+
[
|
|
24313
|
+
alloc.chain.gateway.domain,
|
|
24314
|
+
alloc.chain
|
|
24315
|
+
]
|
|
24316
|
+
]),
|
|
23638
24317
|
address: alloc.sourceSigner
|
|
23639
24318
|
});
|
|
23640
24319
|
}
|
|
@@ -27462,6 +28141,11 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
27462
28141
|
apiKey: serviceParams.apiKey
|
|
27463
28142
|
}
|
|
27464
28143
|
});
|
|
28144
|
+
// Per-swap correlation id returned by the service as a top-level response
|
|
28145
|
+
// field for every chain (EVM + Solana). Attached to success telemetry so a
|
|
28146
|
+
// swap can be correlated across records; never used for control flow.
|
|
28147
|
+
// Undefined only against a not-yet-upgraded service that omits it.
|
|
28148
|
+
const correlationId = serviceResponse.correlationId;
|
|
27465
28149
|
// Build and return SwapResult
|
|
27466
28150
|
return {
|
|
27467
28151
|
tokenIn,
|
|
@@ -27471,6 +28155,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
27471
28155
|
fromAddress: serviceParams.fromAddress,
|
|
27472
28156
|
toAddress: serviceParams.toAddress,
|
|
27473
28157
|
txHash,
|
|
28158
|
+
...correlationId !== undefined && {
|
|
28159
|
+
correlationId
|
|
28160
|
+
},
|
|
27474
28161
|
executedTransactions,
|
|
27475
28162
|
...config !== undefined && {
|
|
27476
28163
|
config
|
|
@@ -30370,7 +31057,13 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
30370
31057
|
* amountIn: '50.00'
|
|
30371
31058
|
* })
|
|
30372
31059
|
* ```
|
|
30373
|
-
*/ async function swap$1(context, params, /**
|
|
31060
|
+
*/ async function swap$1(context, params, /**
|
|
31061
|
+
* @internal
|
|
31062
|
+
* Invoked after a successful broadcast with the on-chain `txHash` and the
|
|
31063
|
+
* service-issued `correlationId` (join key for success telemetry). The
|
|
31064
|
+
* service returns `correlationId` for every chain (EVM + Solana); it is
|
|
31065
|
+
* undefined only against a not-yet-upgraded service that omits the field.
|
|
31066
|
+
*/ onBroadcast) {
|
|
30374
31067
|
// Step 1: Validate parameters using schema
|
|
30375
31068
|
assertSwapParams(params, swapParamsSchema);
|
|
30376
31069
|
// Step 2: Resolve parameters (chain definitions, wallet addresses, token aliases preserved)
|
|
@@ -30390,13 +31083,16 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
30390
31083
|
// Step 5: Execute swap via provider
|
|
30391
31084
|
const swapParams = buildServiceSwapParams(resolvedParams, paramsWithFees);
|
|
30392
31085
|
const providerResult = await provider.swap(swapParams);
|
|
31086
|
+
// `correlationId` is an internal telemetry join key, not part of the public
|
|
31087
|
+
// SwapResult — strip it here so it never leaks into the formatted result.
|
|
31088
|
+
const { correlationId, ...providerResultPublic } = providerResult;
|
|
30393
31089
|
// A throwing `onBroadcast` must never strand the caller after a
|
|
30394
31090
|
// successful swap broadcast — the chain has moved. `safeInvokeCallback`
|
|
30395
31091
|
// swallows the error and surfaces a `console.warn` prefixed with
|
|
30396
31092
|
// `[stablecoin-kits swap-kit] callback threw and was swallowed:` so a
|
|
30397
31093
|
// kit-side closure bug stays debuggable rather than vanishing.
|
|
30398
31094
|
safeInvokeCallback('swap-kit', ()=>{
|
|
30399
|
-
onBroadcast?.(
|
|
31095
|
+
onBroadcast?.(providerResultPublic.txHash, correlationId);
|
|
30400
31096
|
});
|
|
30401
31097
|
const { tokenInDecimals, tokenOutDecimals } = await resolveTokenDecimals(context, resolvedParams);
|
|
30402
31098
|
// Step 6: Compose chain identity (owned by the kit, derived from the
|
|
@@ -30405,10 +31101,10 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
30405
31101
|
// resolves `progress` — the provider's snapshot, or a terminal `'DONE'` for
|
|
30406
31102
|
// a provider that omits it (a synchronous same-chain completion).
|
|
30407
31103
|
const composedResult = {
|
|
30408
|
-
...
|
|
31104
|
+
...providerResultPublic,
|
|
30409
31105
|
chainIn: resolvedParams.from.chain,
|
|
30410
31106
|
chainOut: resolvedParams.toChain ?? resolvedParams.from.chain,
|
|
30411
|
-
progress:
|
|
31107
|
+
progress: providerResultPublic.progress ?? {
|
|
30412
31108
|
status: 'DONE'
|
|
30413
31109
|
}
|
|
30414
31110
|
};
|
|
@@ -31189,7 +31885,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31189
31885
|
ESTIMATE: 'swap_estimate'
|
|
31190
31886
|
};
|
|
31191
31887
|
|
|
31192
|
-
/** SDK name used in telemetry payloads. */ const SDK_NAME$
|
|
31888
|
+
/** SDK name used in telemetry payloads. */ const SDK_NAME$2 = resolveKitSdkName(pkg$3.name);
|
|
31193
31889
|
/**
|
|
31194
31890
|
* A high-level class-based interface for same-chain and cross-chain token swap operations.
|
|
31195
31891
|
*
|
|
@@ -31261,7 +31957,13 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31261
31957
|
*/ class SwapKit {
|
|
31262
31958
|
context;
|
|
31263
31959
|
/** Whether error telemetry is disabled. */ disableErrorReporting;
|
|
31264
|
-
/** Per-kit telemetry identity for
|
|
31960
|
+
/** Per-kit telemetry identity for error reporting. */ telemetryConfig;
|
|
31961
|
+
/**
|
|
31962
|
+
* Per-kit telemetry identity for success/analytics events. Gated by
|
|
31963
|
+
* `disableAnalytics` (independent of `disableErrorReporting`) so a developer
|
|
31964
|
+
* can opt out of volume analytics without also silencing error reports —
|
|
31965
|
+
* mirrors EarnKit.
|
|
31966
|
+
*/ analyticsTelemetryConfig;
|
|
31265
31967
|
/**
|
|
31266
31968
|
* Create a new SwapKit instance.
|
|
31267
31969
|
*
|
|
@@ -31311,10 +32013,15 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31311
32013
|
this.context = createSwapKitContext(config);
|
|
31312
32014
|
this.disableErrorReporting = config.disableErrorReporting === true;
|
|
31313
32015
|
this.telemetryConfig = {
|
|
31314
|
-
sdkName: SDK_NAME$
|
|
32016
|
+
sdkName: SDK_NAME$2,
|
|
31315
32017
|
sdkVersion: pkg$3.version,
|
|
31316
32018
|
disabled: this.disableErrorReporting
|
|
31317
32019
|
};
|
|
32020
|
+
this.analyticsTelemetryConfig = {
|
|
32021
|
+
sdkName: SDK_NAME$2,
|
|
32022
|
+
sdkVersion: pkg$3.version,
|
|
32023
|
+
disabled: config.disableAnalytics === true
|
|
32024
|
+
};
|
|
31318
32025
|
}
|
|
31319
32026
|
/**
|
|
31320
32027
|
* Estimate the output amount and fees for a swap operation.
|
|
@@ -31355,8 +32062,12 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31355
32062
|
* console.log(`Fees:`, quote.fees)
|
|
31356
32063
|
* ```
|
|
31357
32064
|
*/ async estimate(params) {
|
|
32065
|
+
const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
|
|
31358
32066
|
return withErrorTelemetry(async ()=>estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
|
|
31359
32067
|
sourceChain: resolveChainName(params.from.chain),
|
|
32068
|
+
...destinationChain != null && {
|
|
32069
|
+
destinationChain
|
|
32070
|
+
},
|
|
31360
32071
|
tokenIn: params.tokenIn,
|
|
31361
32072
|
tokenOut: params.tokenOut
|
|
31362
32073
|
});
|
|
@@ -31415,16 +32126,54 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31415
32126
|
* ```
|
|
31416
32127
|
*/ async swap(params) {
|
|
31417
32128
|
let txHash;
|
|
31418
|
-
|
|
31419
|
-
|
|
31420
|
-
|
|
32129
|
+
let correlationId;
|
|
32130
|
+
// Shared context builder so the error resolver and the success emit stay in
|
|
32131
|
+
// lockstep — a field added here reaches both call sites. Reads the per-call
|
|
32132
|
+
// locals lazily, so txHash/correlationId (set during the swap) are captured
|
|
32133
|
+
// whenever it is invoked.
|
|
32134
|
+
// Destination chain is the primary attribution dimension for cross-chain
|
|
32135
|
+
// swaps; resolved once from the (static) params. Omitted for same-chain
|
|
32136
|
+
// swaps that leave `to.chain` unset (destination == source).
|
|
32137
|
+
const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
|
|
32138
|
+
const buildTelemetryContext = ()=>({
|
|
31421
32139
|
sourceChain: resolveChainName(params.from.chain),
|
|
32140
|
+
...destinationChain != null && {
|
|
32141
|
+
destinationChain
|
|
32142
|
+
},
|
|
31422
32143
|
tokenIn: params.tokenIn,
|
|
31423
32144
|
tokenOut: params.tokenOut,
|
|
31424
32145
|
...txHash != null && {
|
|
31425
32146
|
txHash
|
|
32147
|
+
},
|
|
32148
|
+
...correlationId != null && {
|
|
32149
|
+
correlationId
|
|
31426
32150
|
}
|
|
31427
|
-
})
|
|
32151
|
+
});
|
|
32152
|
+
const result = await withErrorTelemetry(async ()=>swap$1(this.context, params, (h, cId)=>{
|
|
32153
|
+
txHash = h;
|
|
32154
|
+
correlationId = cId;
|
|
32155
|
+
}), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, buildTelemetryContext);
|
|
32156
|
+
// withErrorTelemetry only emits on failure. Record the successful swap here
|
|
32157
|
+
// so the backend can attribute swap volume to a developer: the client event
|
|
32158
|
+
// carries the (burn) txHash + correlationId, which joins to the
|
|
32159
|
+
// server-emitted event carrying entity_id. Best-effort; never throws.
|
|
32160
|
+
//
|
|
32161
|
+
// Emit whenever a broadcast happened, i.e. we have the source/burn txHash.
|
|
32162
|
+
// For a cross-chain swap that is the source-chain burn (progress is still
|
|
32163
|
+
// PENDING while the destination mint settles) — we intentionally attribute
|
|
32164
|
+
// at broadcast using the burn txHash rather than tracking the destination
|
|
32165
|
+
// leg, which keeps the capture simple and self-contained in swap().
|
|
32166
|
+
//
|
|
32167
|
+
// Guard against a terminal-failure result: the EVM provider throws on
|
|
32168
|
+
// revert today, but the kit is provider-agnostic, so a provider that
|
|
32169
|
+
// returns a FAILED/NOT_FOUND result without throwing must not be recorded
|
|
32170
|
+
// as a successful swap. Routed through analyticsTelemetryConfig so it is
|
|
32171
|
+
// gated by disableAnalytics, independent of error reporting.
|
|
32172
|
+
const status = result.progress?.status;
|
|
32173
|
+
if (txHash != null && status !== 'FAILED' && status !== 'NOT_FOUND') {
|
|
32174
|
+
emitSuccessTelemetry(SWAP_EVENT_TYPES.SWAP, this.analyticsTelemetryConfig, buildTelemetryContext());
|
|
32175
|
+
}
|
|
32176
|
+
return result;
|
|
31428
32177
|
}
|
|
31429
32178
|
/**
|
|
31430
32179
|
* Fetch the current status of a swap from the Stablecoin Service.
|
|
@@ -31770,6 +32519,113 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31770
32519
|
// Auto-register this kit for user agent tracking
|
|
31771
32520
|
registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
31772
32521
|
|
|
32522
|
+
/**
|
|
32523
|
+
* Creates a AppKit context.
|
|
32524
|
+
*
|
|
32525
|
+
* This function constructs a context object, initializes the actions registry
|
|
32526
|
+
* used for event handlers, and merges in any custom implementations provided
|
|
32527
|
+
* via params.
|
|
32528
|
+
*
|
|
32529
|
+
* @param params - Optional custom implementations to override defaults
|
|
32530
|
+
* @returns A AppKitContext
|
|
32531
|
+
*
|
|
32532
|
+
* @example
|
|
32533
|
+
* ```typescript
|
|
32534
|
+
* // Create context with all defaults
|
|
32535
|
+
* const defaultContext = createContext()
|
|
32536
|
+
*
|
|
32537
|
+
* // Create context with custom fee calculation
|
|
32538
|
+
* const customContext = createContext({
|
|
32539
|
+
* getFee: async (type, params) => {
|
|
32540
|
+
* if (type === 'bridge') {
|
|
32541
|
+
* // Custom bridge fee logic
|
|
32542
|
+
* return await calculateBridgeFee(params)
|
|
32543
|
+
* }
|
|
32544
|
+
* // Use default for other types
|
|
32545
|
+
* return defaultFeeCalculation(type, params)
|
|
32546
|
+
* }
|
|
32547
|
+
* })
|
|
32548
|
+
* ```
|
|
32549
|
+
*/ const createContext = (params = {})=>{
|
|
32550
|
+
return {
|
|
32551
|
+
...params,
|
|
32552
|
+
actions: {
|
|
32553
|
+
bridge: {},
|
|
32554
|
+
earn: {},
|
|
32555
|
+
...params.actions
|
|
32556
|
+
}
|
|
32557
|
+
};
|
|
32558
|
+
};
|
|
32559
|
+
|
|
32560
|
+
/**
|
|
32561
|
+
* Create a BridgeKit instance with optional developer fee configuration.
|
|
32562
|
+
*
|
|
32563
|
+
* This utility creates a BridgeKit instance that optionally includes developer
|
|
32564
|
+
* fee configuration based on the provided AppKit context. If the context
|
|
32565
|
+
* provides both `getFee` and `getFeeRecipient` methods, they will be configured
|
|
32566
|
+
* as developer fees in the BridgeKit instance using the `setCustomFeePolicy` method.
|
|
32567
|
+
*
|
|
32568
|
+
* The fee integration transforms string-based fees from the context into the
|
|
32569
|
+
* format expected by BridgeKit, enabling seamless fee calculation across both kits.
|
|
32570
|
+
*
|
|
32571
|
+
* @param context - The AppKit context containing optional fee methods
|
|
32572
|
+
* @returns A configured BridgeKit instance with or without developer fees
|
|
32573
|
+
*
|
|
32574
|
+
* @example
|
|
32575
|
+
* ```typescript
|
|
32576
|
+
* import { createBridgeKit } from '@circle-fin/app-kit/utils'
|
|
32577
|
+
* import { createContext } from '@circle-fin/app-kit/context'
|
|
32578
|
+
*
|
|
32579
|
+
* // Create context with fee methods
|
|
32580
|
+
* const context = createContext({
|
|
32581
|
+
* getFee: async (type, params) => '1000000', // 1 USDC in micro-units
|
|
32582
|
+
* getFeeRecipient: async (type, info) => '0x742d35Cc4634C0532925a3b8D1d7'
|
|
32583
|
+
* })
|
|
32584
|
+
*
|
|
32585
|
+
* // Create BridgeKit with developer fees
|
|
32586
|
+
* const bridgeKit = createBridgeKit(context)
|
|
32587
|
+
* ```
|
|
32588
|
+
*
|
|
32589
|
+
* @example
|
|
32590
|
+
* ```typescript
|
|
32591
|
+
* import { createBridgeKit } from '@circle-fin/app-kit/utils'
|
|
32592
|
+
* import { createContext } from '@circle-fin/app-kit/context'
|
|
32593
|
+
*
|
|
32594
|
+
* // Create context without fee methods
|
|
32595
|
+
* const context = createContext()
|
|
32596
|
+
*
|
|
32597
|
+
* // Create standard BridgeKit instance
|
|
32598
|
+
* const bridgeKit = createBridgeKit(context)
|
|
32599
|
+
* ```
|
|
32600
|
+
*/ const createBridgeKit = (context)=>{
|
|
32601
|
+
const getFee = context.getFee?.bind(context);
|
|
32602
|
+
const getFeeRecipient = context.getFeeRecipient?.bind(context);
|
|
32603
|
+
const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
|
|
32604
|
+
const kit = new BridgeKit({
|
|
32605
|
+
...context.disableErrorReporting != null && {
|
|
32606
|
+
disableErrorReporting: context.disableErrorReporting
|
|
32607
|
+
},
|
|
32608
|
+
...context.headers != null && {
|
|
32609
|
+
headers: context.headers
|
|
32610
|
+
}
|
|
32611
|
+
});
|
|
32612
|
+
if (context.customFeePolicy?.bridge != null) {
|
|
32613
|
+
kit.setCustomFeePolicy(context.customFeePolicy.bridge);
|
|
32614
|
+
} else if (hasBoth) {
|
|
32615
|
+
kit.setCustomFeePolicy({
|
|
32616
|
+
calculateFee: async (params)=>{
|
|
32617
|
+
const feeStr = await getFee('bridge', params);
|
|
32618
|
+
return feeStr;
|
|
32619
|
+
},
|
|
32620
|
+
resolveFeeRecipientAddress: async (chain, params)=>await getFeeRecipient('bridge', {
|
|
32621
|
+
chain,
|
|
32622
|
+
params: params || {}
|
|
32623
|
+
})
|
|
32624
|
+
});
|
|
32625
|
+
}
|
|
32626
|
+
return kit;
|
|
32627
|
+
};
|
|
32628
|
+
|
|
31773
32629
|
/**
|
|
31774
32630
|
* Create a SwapKit instance with optional developer fee configuration.
|
|
31775
32631
|
*
|
|
@@ -31803,9 +32659,14 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
|
31803
32659
|
const kit = new SwapKit({
|
|
31804
32660
|
...context.disableErrorReporting != null && {
|
|
31805
32661
|
disableErrorReporting: context.disableErrorReporting
|
|
32662
|
+
},
|
|
32663
|
+
...context.disableAnalytics != null && {
|
|
32664
|
+
disableAnalytics: context.disableAnalytics
|
|
31806
32665
|
}
|
|
31807
32666
|
});
|
|
31808
|
-
if (
|
|
32667
|
+
if (context.customFeePolicy?.swap != null) {
|
|
32668
|
+
kit.setCustomFeePolicy(context.customFeePolicy.swap);
|
|
32669
|
+
} else if (hasBoth) {
|
|
31809
32670
|
kit.setCustomFeePolicy({
|
|
31810
32671
|
computeFee: async (params)=>{
|
|
31811
32672
|
// Adapt provider-level params (with tokenIn/tokenOut)
|
|
@@ -31864,7 +32725,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
|
31864
32725
|
};
|
|
31865
32726
|
|
|
31866
32727
|
var name$2 = "@circle-fin/earn-kit";
|
|
31867
|
-
var version$2 = "1.
|
|
32728
|
+
var version$2 = "1.5.0";
|
|
31868
32729
|
var pkg$2 = {
|
|
31869
32730
|
name: name$2,
|
|
31870
32731
|
version: version$2};
|
|
@@ -32507,6 +33368,683 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
32507
33368
|
return approvedToken;
|
|
32508
33369
|
}
|
|
32509
33370
|
|
|
33371
|
+
/**
|
|
33372
|
+
* Combined ABI of every inner instruction EarnKit can bundle inside an Adapter
|
|
33373
|
+
* `execute()` call. `decodeFunctionData` matches an instruction's calldata to
|
|
33374
|
+
* one of these functions by its 4-byte selector.
|
|
33375
|
+
*/ const earnInstructionAbi = [
|
|
33376
|
+
...erc4626VaultAbi,
|
|
33377
|
+
...feeTakerAbi,
|
|
33378
|
+
...merklDistributorAbi
|
|
33379
|
+
];
|
|
33380
|
+
/**
|
|
33381
|
+
* Extract and shallow-validate the `instructions` array from loosely-typed
|
|
33382
|
+
* signed execution params.
|
|
33383
|
+
*
|
|
33384
|
+
* The earn service schema validates `tokenIn`/`amountToApprove` and passes the
|
|
33385
|
+
* remaining instruction fields through untyped, so the params arrive as a plain
|
|
33386
|
+
* record; each accessed field is narrowed at runtime.
|
|
33387
|
+
*/ function requireInstructions(executionParams) {
|
|
33388
|
+
const instructions = executionParams['instructions'];
|
|
33389
|
+
if (!Array.isArray(instructions)) {
|
|
33390
|
+
throw decodeMismatchError('execution params are missing an instructions array', {
|
|
33391
|
+
instructions
|
|
33392
|
+
});
|
|
33393
|
+
}
|
|
33394
|
+
return instructions.map((instruction, index)=>{
|
|
33395
|
+
if (typeof instruction !== 'object' || instruction === null) {
|
|
33396
|
+
throw decodeMismatchError(`instructions[${index.toString()}] is not an object`, {
|
|
33397
|
+
index
|
|
33398
|
+
});
|
|
33399
|
+
}
|
|
33400
|
+
return instruction;
|
|
33401
|
+
});
|
|
33402
|
+
}
|
|
33403
|
+
/**
|
|
33404
|
+
* Build a fail-closed {@link KitError} for an earn decode or review failure.
|
|
33405
|
+
*
|
|
33406
|
+
* Marked non-recoverable: a mismatch between what would be shown and what would
|
|
33407
|
+
* be signed is never safe to retry, so the operation fails fast rather than
|
|
33408
|
+
* presenting misleading decoded data. `messagePrefix` names the failing stage
|
|
33409
|
+
* (calldata decode vs. review construction); callers bind it once and pass the
|
|
33410
|
+
* specific failure as `message`.
|
|
33411
|
+
*/ function failClosedEarnError(messagePrefix, message, trace) {
|
|
33412
|
+
return new KitError({
|
|
33413
|
+
...EarnError.INTERNAL_ERROR,
|
|
33414
|
+
recoverability: 'FATAL',
|
|
33415
|
+
message: `${messagePrefix}: ${message}`,
|
|
33416
|
+
cause: {
|
|
33417
|
+
trace
|
|
33418
|
+
}
|
|
33419
|
+
});
|
|
33420
|
+
}
|
|
33421
|
+
/**
|
|
33422
|
+
* Build a {@link KitError} for a decode or consistency failure.
|
|
33423
|
+
*
|
|
33424
|
+
* Thin wrapper over {@link failClosedEarnError} bound to the decode-stage
|
|
33425
|
+
* message prefix.
|
|
33426
|
+
*/ function decodeMismatchError(message, trace) {
|
|
33427
|
+
return failClosedEarnError('Unable to decode earn transaction', message, trace);
|
|
33428
|
+
}
|
|
33429
|
+
/**
|
|
33430
|
+
* Narrow an untyped value to a 0x-prefixed hex string, or fail fast.
|
|
33431
|
+
*/ function requireHex(value, path) {
|
|
33432
|
+
if (typeof value === 'string' && /^0x[0-9a-fA-F]*$/.test(value)) {
|
|
33433
|
+
return value;
|
|
33434
|
+
}
|
|
33435
|
+
throw decodeMismatchError(`${path} is not a hex string`, {
|
|
33436
|
+
path,
|
|
33437
|
+
value
|
|
33438
|
+
});
|
|
33439
|
+
}
|
|
33440
|
+
/**
|
|
33441
|
+
* Narrow an untyped value to a 20-byte EVM address, or fail fast.
|
|
33442
|
+
*/ function requireAddress(value, path) {
|
|
33443
|
+
if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value)) {
|
|
33444
|
+
return value;
|
|
33445
|
+
}
|
|
33446
|
+
throw decodeMismatchError(`${path} is not an address`, {
|
|
33447
|
+
path,
|
|
33448
|
+
value
|
|
33449
|
+
});
|
|
33450
|
+
}
|
|
33451
|
+
/**
|
|
33452
|
+
* Narrow an untyped `uint256`-like value (decimal string, bigint, or integer)
|
|
33453
|
+
* to a bigint, or fail fast.
|
|
33454
|
+
*/ function requireUint(value, path) {
|
|
33455
|
+
if (typeof value === 'bigint') {
|
|
33456
|
+
return value;
|
|
33457
|
+
}
|
|
33458
|
+
if (typeof value === 'string' && /^\d+$/.test(value)) {
|
|
33459
|
+
return BigInt(value);
|
|
33460
|
+
}
|
|
33461
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= 0) {
|
|
33462
|
+
return BigInt(value);
|
|
33463
|
+
}
|
|
33464
|
+
throw decodeMismatchError(`${path} is not a uint256 value`, {
|
|
33465
|
+
path,
|
|
33466
|
+
value
|
|
33467
|
+
});
|
|
33468
|
+
}
|
|
33469
|
+
/**
|
|
33470
|
+
* Decode inner instruction calldata against the earn instruction ABI, mapping a
|
|
33471
|
+
* viem decode failure (unknown selector, malformed args) to a fail-fast error.
|
|
33472
|
+
*/ function decodeEarnInstructionData(data, index) {
|
|
33473
|
+
try {
|
|
33474
|
+
return decodeFunctionData({
|
|
33475
|
+
abi: earnInstructionAbi,
|
|
33476
|
+
data
|
|
33477
|
+
});
|
|
33478
|
+
} catch (error) {
|
|
33479
|
+
throw decodeMismatchError(`instructions[${index.toString()}] calldata is not a recognized earn instruction`, {
|
|
33480
|
+
index,
|
|
33481
|
+
selector: data.slice(0, 10),
|
|
33482
|
+
error: String(error)
|
|
33483
|
+
});
|
|
33484
|
+
}
|
|
33485
|
+
}
|
|
33486
|
+
/**
|
|
33487
|
+
* Decode Adapter `execute()` calldata, mapping a viem decode failure to a
|
|
33488
|
+
* fail-fast error.
|
|
33489
|
+
*/ function decodeExecuteCalldata(calldata) {
|
|
33490
|
+
try {
|
|
33491
|
+
return decodeFunctionData({
|
|
33492
|
+
abi: adapterContractAbi,
|
|
33493
|
+
data: calldata
|
|
33494
|
+
});
|
|
33495
|
+
} catch (error) {
|
|
33496
|
+
throw decodeMismatchError('encoded calldata is not a valid Adapter execute() call', {
|
|
33497
|
+
error: String(error)
|
|
33498
|
+
});
|
|
33499
|
+
}
|
|
33500
|
+
}
|
|
33501
|
+
/**
|
|
33502
|
+
* Decode one inner instruction's calldata into a typed {@link
|
|
33503
|
+
* DecodedEarnInstruction}.
|
|
33504
|
+
*/ function decodeInstruction(instruction, index) {
|
|
33505
|
+
const target = requireAddress(instruction['target'], `instructions[${index.toString()}].target`);
|
|
33506
|
+
const data = requireHex(instruction['data'], `instructions[${index.toString()}].data`);
|
|
33507
|
+
const decoded = decodeEarnInstructionData(data, index);
|
|
33508
|
+
switch(decoded.functionName){
|
|
33509
|
+
case 'deposit':
|
|
33510
|
+
{
|
|
33511
|
+
const [assets, receiver] = decoded.args;
|
|
33512
|
+
return {
|
|
33513
|
+
method: 'deposit',
|
|
33514
|
+
vault: target,
|
|
33515
|
+
assets: assets.toString(),
|
|
33516
|
+
receiver
|
|
33517
|
+
};
|
|
33518
|
+
}
|
|
33519
|
+
case 'withdraw':
|
|
33520
|
+
{
|
|
33521
|
+
const [assets, receiver, owner] = decoded.args;
|
|
33522
|
+
return {
|
|
33523
|
+
method: 'withdraw',
|
|
33524
|
+
vault: target,
|
|
33525
|
+
assets: assets.toString(),
|
|
33526
|
+
receiver,
|
|
33527
|
+
owner
|
|
33528
|
+
};
|
|
33529
|
+
}
|
|
33530
|
+
case 'redeem':
|
|
33531
|
+
{
|
|
33532
|
+
const [shares, receiver, owner] = decoded.args;
|
|
33533
|
+
return {
|
|
33534
|
+
method: 'redeem',
|
|
33535
|
+
vault: target,
|
|
33536
|
+
shares: shares.toString(),
|
|
33537
|
+
receiver,
|
|
33538
|
+
owner
|
|
33539
|
+
};
|
|
33540
|
+
}
|
|
33541
|
+
case 'takeFeeERC20':
|
|
33542
|
+
{
|
|
33543
|
+
const [token, beneficiary, fee, kitType] = decoded.args;
|
|
33544
|
+
return {
|
|
33545
|
+
method: 'takeFeeERC20',
|
|
33546
|
+
feeTaker: target,
|
|
33547
|
+
token,
|
|
33548
|
+
beneficiary,
|
|
33549
|
+
fee: fee.toString(),
|
|
33550
|
+
kitType
|
|
33551
|
+
};
|
|
33552
|
+
}
|
|
33553
|
+
case 'claim':
|
|
33554
|
+
{
|
|
33555
|
+
const users = decoded.args[0];
|
|
33556
|
+
const tokens = decoded.args[1];
|
|
33557
|
+
const amounts = decoded.args[2];
|
|
33558
|
+
// Merkl claim(users, tokens, amounts, proofs) carries parallel arrays,
|
|
33559
|
+
// one entry per reward. Reject any length skew rather than padding with
|
|
33560
|
+
// zero amounts or dropping trailing entries, so the preview can never
|
|
33561
|
+
// misstate what is claimed or for whom.
|
|
33562
|
+
//
|
|
33563
|
+
// Note: Merkl `amounts` are the *cumulative lifetime* total claimable per
|
|
33564
|
+
// (user, token); the Distributor transfers only `amount - alreadyClaimed`.
|
|
33565
|
+
// This decode faithfully surfaces the signed cumulative value, which is
|
|
33566
|
+
// what `DecodedRewardClaim.amount` documents. See that type's doc.
|
|
33567
|
+
if (new Set([
|
|
33568
|
+
users.length,
|
|
33569
|
+
tokens.length,
|
|
33570
|
+
amounts.length
|
|
33571
|
+
]).size !== 1) {
|
|
33572
|
+
throw decodeMismatchError(`instructions[${index.toString()}] claim has mismatched recipient/token/amount lengths`, {
|
|
33573
|
+
index,
|
|
33574
|
+
users: users.length,
|
|
33575
|
+
tokens: tokens.length,
|
|
33576
|
+
amounts: amounts.length
|
|
33577
|
+
});
|
|
33578
|
+
}
|
|
33579
|
+
const rewards = tokens.map((token, rewardIndex)=>({
|
|
33580
|
+
recipient: requireAddress(users[rewardIndex], `instructions[${index.toString()}].claim.users[${rewardIndex.toString()}]`),
|
|
33581
|
+
address: token,
|
|
33582
|
+
amount: requireUint(amounts[rewardIndex], `instructions[${index.toString()}].claim.amounts[${rewardIndex.toString()}]`).toString()
|
|
33583
|
+
}));
|
|
33584
|
+
return {
|
|
33585
|
+
method: 'claim',
|
|
33586
|
+
distributor: target,
|
|
33587
|
+
rewards
|
|
33588
|
+
};
|
|
33589
|
+
}
|
|
33590
|
+
/* v8 ignore next 2 -- exhaustive switch; default is unreachable */ default:
|
|
33591
|
+
return assertNever$2(decoded);
|
|
33592
|
+
}
|
|
33593
|
+
}
|
|
33594
|
+
/**
|
|
33595
|
+
* Lift the primary values a wallet prompt cares about out of the decoded
|
|
33596
|
+
* instructions into a flat summary.
|
|
33597
|
+
*/ function buildSummary(instructions, envelope) {
|
|
33598
|
+
const summary = {};
|
|
33599
|
+
instructions.forEach((instruction, index)=>{
|
|
33600
|
+
switch(instruction.method){
|
|
33601
|
+
case 'deposit':
|
|
33602
|
+
case 'withdraw':
|
|
33603
|
+
case 'redeem':
|
|
33604
|
+
{
|
|
33605
|
+
// The summary lifts a single primary token movement to the top level.
|
|
33606
|
+
// An earn bundle carries exactly one deposit/withdraw/redeem today;
|
|
33607
|
+
// fail fast rather than silently overwriting an earlier one, which
|
|
33608
|
+
// would drop it from the wallet-facing preview.
|
|
33609
|
+
if (summary.token !== undefined) {
|
|
33610
|
+
throw decodeMismatchError('multiple deposit/withdraw/redeem instructions cannot be summarized into a single preview', {
|
|
33611
|
+
index
|
|
33612
|
+
});
|
|
33613
|
+
}
|
|
33614
|
+
// Pair the amount with the token it is actually denominated in so the
|
|
33615
|
+
// preview never folds two units into one entry:
|
|
33616
|
+
// - deposit: `assets` of the underlying asset pulled in (`tokenIn`)
|
|
33617
|
+
// - redeem: `shares` of the vault-share token burned (`tokenIn`)
|
|
33618
|
+
// - withdraw: `assets` of the underlying asset paid out (`tokenOut`).
|
|
33619
|
+
// `withdraw(assets)` counts the underlying received, not the shares
|
|
33620
|
+
// burned to produce it, so `tokenIn` (the share token) would misstate
|
|
33621
|
+
// the unit; the underlying is the instruction's `tokenOut`.
|
|
33622
|
+
const amount = instruction.method === 'redeem' ? instruction.shares : instruction.assets;
|
|
33623
|
+
const tokenField = instruction.method === 'withdraw' ? 'tokenOut' : 'tokenIn';
|
|
33624
|
+
summary.vault = instruction.vault;
|
|
33625
|
+
summary.receiver = instruction.receiver;
|
|
33626
|
+
summary.token = {
|
|
33627
|
+
address: requireAddress(envelope[index]?.[tokenField], `instructions[${index.toString()}].${tokenField}`),
|
|
33628
|
+
amount
|
|
33629
|
+
};
|
|
33630
|
+
break;
|
|
33631
|
+
}
|
|
33632
|
+
case 'takeFeeERC20':
|
|
33633
|
+
{
|
|
33634
|
+
// As with the vault case, a second fee would silently overwrite the
|
|
33635
|
+
// first and understate what is charged; fail fast instead.
|
|
33636
|
+
if (summary.fee !== undefined) {
|
|
33637
|
+
throw decodeMismatchError('multiple fee instructions cannot be summarized into a single preview', {
|
|
33638
|
+
index
|
|
33639
|
+
});
|
|
33640
|
+
}
|
|
33641
|
+
summary.fee = {
|
|
33642
|
+
address: instruction.token,
|
|
33643
|
+
amount: instruction.fee
|
|
33644
|
+
};
|
|
33645
|
+
break;
|
|
33646
|
+
}
|
|
33647
|
+
case 'claim':
|
|
33648
|
+
{
|
|
33649
|
+
// A second claim would silently drop the first from the preview
|
|
33650
|
+
// (rewards are already batched inside one Merkl claim); fail fast.
|
|
33651
|
+
if (summary.rewards !== undefined) {
|
|
33652
|
+
throw decodeMismatchError('multiple claim instructions cannot be summarized into a single preview', {
|
|
33653
|
+
index
|
|
33654
|
+
});
|
|
33655
|
+
}
|
|
33656
|
+
summary.rewards = instruction.rewards;
|
|
33657
|
+
break;
|
|
33658
|
+
}
|
|
33659
|
+
/* v8 ignore next 2 -- exhaustive switch; default is unreachable */ default:
|
|
33660
|
+
assertNever$2(instruction);
|
|
33661
|
+
}
|
|
33662
|
+
});
|
|
33663
|
+
return summary;
|
|
33664
|
+
}
|
|
33665
|
+
/**
|
|
33666
|
+
* Vault/claim instruction methods each declared action may decode to. The
|
|
33667
|
+
* mapping is many-to-one: a full withdrawal decodes to `redeem`, and any action
|
|
33668
|
+
* may carry an auxiliary `takeFeeERC20` alongside its primary instruction.
|
|
33669
|
+
*/ const ACTION_ALLOWED_METHODS = {
|
|
33670
|
+
deposit: new Set([
|
|
33671
|
+
'deposit'
|
|
33672
|
+
]),
|
|
33673
|
+
withdraw: new Set([
|
|
33674
|
+
'withdraw',
|
|
33675
|
+
'redeem'
|
|
33676
|
+
]),
|
|
33677
|
+
claimRewards: new Set([
|
|
33678
|
+
'claim'
|
|
33679
|
+
])
|
|
33680
|
+
};
|
|
33681
|
+
/**
|
|
33682
|
+
* Fail fast when the caller-declared `action` disagrees with the decoded
|
|
33683
|
+
* instructions, so the preview's headline can never mislabel what is signed
|
|
33684
|
+
* (e.g. a `deposit`-labeled call handed withdraw params). `takeFeeERC20` is an
|
|
33685
|
+
* auxiliary Circle-fee instruction and is allowed alongside any action.
|
|
33686
|
+
*/ function assertActionMatchesInstructions(action, instructions) {
|
|
33687
|
+
const allowed = ACTION_ALLOWED_METHODS[action];
|
|
33688
|
+
instructions.forEach((instruction, index)=>{
|
|
33689
|
+
if (instruction.method === 'takeFeeERC20') {
|
|
33690
|
+
return;
|
|
33691
|
+
}
|
|
33692
|
+
if (!allowed.has(instruction.method)) {
|
|
33693
|
+
throw decodeMismatchError(`decoded instruction method '${instruction.method}' does not match the '${action}' action`, {
|
|
33694
|
+
action,
|
|
33695
|
+
method: instruction.method,
|
|
33696
|
+
index
|
|
33697
|
+
});
|
|
33698
|
+
}
|
|
33699
|
+
});
|
|
33700
|
+
}
|
|
33701
|
+
/**
|
|
33702
|
+
* Decode a same-chain earn `execute()` bundle into a human-readable summary.
|
|
33703
|
+
*
|
|
33704
|
+
* Decodes every inner instruction in the service-signed `executionParams` — the
|
|
33705
|
+
* same object the SDK ABI-encodes into the transaction — so the returned decode
|
|
33706
|
+
* is a faithful, drift-free view of what the signer is authorizing: input token
|
|
33707
|
+
* and amount, target vault, receiver, any Circle fee, and claimed rewards. Fails
|
|
33708
|
+
* fast with a non-recoverable {@link KitError} if any instruction cannot be
|
|
33709
|
+
* decoded, rather than returning misleading data.
|
|
33710
|
+
*
|
|
33711
|
+
* @param input - Action, chain, adapter, and the signed execution params.
|
|
33712
|
+
* @returns The decoded transaction summary.
|
|
33713
|
+
* @throws {@link KitError} If an instruction's calldata cannot be decoded.
|
|
33714
|
+
*
|
|
33715
|
+
* @example
|
|
33716
|
+
* ```typescript
|
|
33717
|
+
* const decoded = decodeEarnExecute({
|
|
33718
|
+
* action: 'deposit',
|
|
33719
|
+
* chain: 'Arc_Testnet',
|
|
33720
|
+
* adapter: '0x7fb8c7260b63934d8da38af902f87ae6e284a845',
|
|
33721
|
+
* executionParams,
|
|
33722
|
+
* })
|
|
33723
|
+
* // decoded.summary -> { token: { address, amount }, vault, receiver }
|
|
33724
|
+
* ```
|
|
33725
|
+
*
|
|
33726
|
+
* @internal
|
|
33727
|
+
*/ function decodeEarnExecute(input) {
|
|
33728
|
+
const { action, chain, adapter, executionParams } = input;
|
|
33729
|
+
const envelope = requireInstructions(executionParams);
|
|
33730
|
+
const instructions = envelope.map((instruction, index)=>decodeInstruction(instruction, index));
|
|
33731
|
+
assertActionMatchesInstructions(action, instructions);
|
|
33732
|
+
return {
|
|
33733
|
+
action,
|
|
33734
|
+
chain,
|
|
33735
|
+
adapter,
|
|
33736
|
+
instructions,
|
|
33737
|
+
summary: buildSummary(instructions, envelope)
|
|
33738
|
+
};
|
|
33739
|
+
}
|
|
33740
|
+
/**
|
|
33741
|
+
* Assert that ABI-encoded Adapter `execute()` calldata encodes the same
|
|
33742
|
+
* instruction set as the service-signed execution params.
|
|
33743
|
+
*
|
|
33744
|
+
* Fail-fast preview check: the SDK encodes `execute(executeParams, ...)` locally,
|
|
33745
|
+
* so decoding those final bytes and comparing every field of each instruction
|
|
33746
|
+
* against the signed params proves the previewed instruction set matches what
|
|
33747
|
+
* will be signed. It compares `instructions[]` only — the outer `tokens`,
|
|
33748
|
+
* `execId`, `deadline`, and `metadata` are not re-compared here. The
|
|
33749
|
+
* authoritative integrity guarantee for the full signed struct is the on-chain
|
|
33750
|
+
* EIP-712 signature verification, which reverts if any signed field is altered.
|
|
33751
|
+
*
|
|
33752
|
+
* @param calldata - Encoded `execute()` calldata about to be signed.
|
|
33753
|
+
* @param executionParams - Service-signed execution params.
|
|
33754
|
+
* @throws {@link KitError} If the calldata is not an `execute()` call or any
|
|
33755
|
+
* instruction field differs from the signed params.
|
|
33756
|
+
*
|
|
33757
|
+
* @example
|
|
33758
|
+
* ```typescript
|
|
33759
|
+
* assertEarnCalldataMatchesExecuteParams(
|
|
33760
|
+
* prepared.getCallData().data,
|
|
33761
|
+
* executionParams,
|
|
33762
|
+
* )
|
|
33763
|
+
* ```
|
|
33764
|
+
*
|
|
33765
|
+
* @internal
|
|
33766
|
+
*/ function assertEarnCalldataMatchesExecuteParams(calldata, executionParams) {
|
|
33767
|
+
// adapterContractAbi declares only `execute`, so a successful decode is always
|
|
33768
|
+
// the execute() call; a non-execute selector throws inside
|
|
33769
|
+
// decodeExecuteCalldata above.
|
|
33770
|
+
const decoded = decodeExecuteCalldata(calldata);
|
|
33771
|
+
const encoded = decoded.args[0].instructions;
|
|
33772
|
+
const signed = requireInstructions(executionParams);
|
|
33773
|
+
// Compare each encoded instruction against its signed counterpart. Iterating
|
|
33774
|
+
// the encoded instructions and indexing the signed set keeps both mismatch
|
|
33775
|
+
// branches reachable: a signed set that is too short trips the guard below,
|
|
33776
|
+
// and one that is too long trips the post-loop check.
|
|
33777
|
+
encoded.forEach((instruction, index)=>{
|
|
33778
|
+
const signedInstruction = signed[index];
|
|
33779
|
+
if (signedInstruction === undefined) {
|
|
33780
|
+
throw decodeMismatchError(`signed params are missing instruction ${index.toString()}`, {
|
|
33781
|
+
index,
|
|
33782
|
+
encoded: encoded.length,
|
|
33783
|
+
signed: signed.length
|
|
33784
|
+
});
|
|
33785
|
+
}
|
|
33786
|
+
const path = `instructions[${index.toString()}]`;
|
|
33787
|
+
assertHexEqual(instruction.target, signedInstruction['target'], `${path}.target`);
|
|
33788
|
+
assertHexEqual(instruction.data, signedInstruction['data'], `${path}.data`);
|
|
33789
|
+
assertUintEqual(instruction.value, signedInstruction['value'], `${path}.value`);
|
|
33790
|
+
assertHexEqual(instruction.tokenIn, signedInstruction['tokenIn'], `${path}.tokenIn`);
|
|
33791
|
+
assertUintEqual(instruction.amountToApprove, signedInstruction['amountToApprove'], `${path}.amountToApprove`);
|
|
33792
|
+
assertHexEqual(instruction.tokenOut, signedInstruction['tokenOut'], `${path}.tokenOut`);
|
|
33793
|
+
assertUintEqual(instruction.minTokenOut, signedInstruction['minTokenOut'], `${path}.minTokenOut`);
|
|
33794
|
+
});
|
|
33795
|
+
if (signed.length > encoded.length) {
|
|
33796
|
+
throw decodeMismatchError('signed params contain more instructions than the encoded calldata', {
|
|
33797
|
+
encoded: encoded.length,
|
|
33798
|
+
signed: signed.length
|
|
33799
|
+
});
|
|
33800
|
+
}
|
|
33801
|
+
}
|
|
33802
|
+
/**
|
|
33803
|
+
* Assert two hex values are equal, case-insensitively (addresses and calldata).
|
|
33804
|
+
*/ function assertHexEqual(encoded, signed, path) {
|
|
33805
|
+
const signedHex = requireHex(signed, path);
|
|
33806
|
+
if (encoded.toLowerCase() !== signedHex.toLowerCase()) {
|
|
33807
|
+
throw decodeMismatchError(`${path} differs from signed params`, {
|
|
33808
|
+
path,
|
|
33809
|
+
encoded,
|
|
33810
|
+
signed: signedHex
|
|
33811
|
+
});
|
|
33812
|
+
}
|
|
33813
|
+
}
|
|
33814
|
+
/**
|
|
33815
|
+
* Assert an encoded bigint equals a signed `uint256`-like value.
|
|
33816
|
+
*/ function assertUintEqual(encoded, signed, path) {
|
|
33817
|
+
const signedUint = requireUint(signed, path);
|
|
33818
|
+
if (encoded !== signedUint) {
|
|
33819
|
+
throw decodeMismatchError(`${path} differs from signed params`, {
|
|
33820
|
+
path,
|
|
33821
|
+
encoded: encoded.toString(),
|
|
33822
|
+
signed: signedUint.toString()
|
|
33823
|
+
});
|
|
33824
|
+
}
|
|
33825
|
+
}
|
|
33826
|
+
|
|
33827
|
+
/**
|
|
33828
|
+
* Namespaced discriminator for the EarnKit authorization review.
|
|
33829
|
+
*
|
|
33830
|
+
* Applications match on this in an adapter `onBeforeAuthorize` hook to decide
|
|
33831
|
+
* whether the request carries EarnKit semantic data. Prefer the
|
|
33832
|
+
* {@link isEarnExecuteReview} type guard over comparing this string directly.
|
|
33833
|
+
*
|
|
33834
|
+
* @example
|
|
33835
|
+
* ```typescript
|
|
33836
|
+
* if (review?.kind === EARN_EXECUTE_REVIEW_KIND) { … }
|
|
33837
|
+
* ```
|
|
33838
|
+
*/ const EARN_EXECUTE_REVIEW_KIND = 'earn.execute';
|
|
33839
|
+
|
|
33840
|
+
/**
|
|
33841
|
+
* Build a fail-closed {@link KitError} for a review-construction failure.
|
|
33842
|
+
*
|
|
33843
|
+
* Marked non-recoverable: a review that cannot prove the calldata matches the
|
|
33844
|
+
* signed operation must abort authorization, never retry with misleading data.
|
|
33845
|
+
*/ function reviewError(message, trace) {
|
|
33846
|
+
return failClosedEarnError('Unable to build earn authorization review', message, trace);
|
|
33847
|
+
}
|
|
33848
|
+
/**
|
|
33849
|
+
* Narrow the canonical authorization payload to the single Adapter `execute()`
|
|
33850
|
+
* call a same-chain earn operation authorizes.
|
|
33851
|
+
*
|
|
33852
|
+
* Same-chain deposit, withdraw, and claim-rewards each authorize exactly one
|
|
33853
|
+
* `evm-calls` payload carrying one call. Anything else (typed data, a batch,
|
|
33854
|
+
* an empty call list) means this descriptor was attached to the wrong
|
|
33855
|
+
* authorization unit, so fail closed rather than decode misleading data.
|
|
33856
|
+
*/ function assertSingleEvmCallPayload(payload) {
|
|
33857
|
+
if (payload.type !== 'evm-calls') {
|
|
33858
|
+
throw reviewError(`expected an 'evm-calls' payload but received '${payload.type}'`, {
|
|
33859
|
+
type: payload.type
|
|
33860
|
+
});
|
|
33861
|
+
}
|
|
33862
|
+
const [call, ...rest] = payload.calls;
|
|
33863
|
+
if (call === undefined) {
|
|
33864
|
+
throw reviewError('the evm-calls payload contains no calls to review', {
|
|
33865
|
+
callCount: payload.calls.length
|
|
33866
|
+
});
|
|
33867
|
+
}
|
|
33868
|
+
if (rest.length > 0) {
|
|
33869
|
+
throw reviewError('a same-chain earn operation authorizes exactly one Adapter execute() call', {
|
|
33870
|
+
callCount: payload.calls.length
|
|
33871
|
+
});
|
|
33872
|
+
}
|
|
33873
|
+
return call;
|
|
33874
|
+
}
|
|
33875
|
+
/**
|
|
33876
|
+
* Select the final earn `execute()` call from an atomic Earn batch.
|
|
33877
|
+
*
|
|
33878
|
+
* Same-chain batched deposit/withdraw authorizes either `[execute]` when the
|
|
33879
|
+
* current allowance is sufficient, or `[approve, execute]` when a top-up is
|
|
33880
|
+
* required. Any other shape means the descriptor was attached to an
|
|
33881
|
+
* unexpected authorization unit, so fail closed.
|
|
33882
|
+
*/ function assertBatchedEarnExecuteCall(payload) {
|
|
33883
|
+
if (payload.type !== 'evm-calls') {
|
|
33884
|
+
throw reviewError(`expected an 'evm-calls' payload but received '${payload.type}'`, {
|
|
33885
|
+
type: payload.type
|
|
33886
|
+
});
|
|
33887
|
+
}
|
|
33888
|
+
if (payload.calls.length !== 1 && payload.calls.length !== 2) {
|
|
33889
|
+
throw reviewError('a batched earn operation authorizes [execute] or [approve, execute]', {
|
|
33890
|
+
callCount: payload.calls.length
|
|
33891
|
+
});
|
|
33892
|
+
}
|
|
33893
|
+
const executeCall = payload.calls.at(-1);
|
|
33894
|
+
if (executeCall === undefined) {
|
|
33895
|
+
throw reviewError('the earn batch contains no execute call to review', {
|
|
33896
|
+
callCount: payload.calls.length
|
|
33897
|
+
});
|
|
33898
|
+
}
|
|
33899
|
+
return executeCall;
|
|
33900
|
+
}
|
|
33901
|
+
/**
|
|
33902
|
+
* Map a canonical {@link EvmCall} to the {@link EarnEncodedTransaction} preview
|
|
33903
|
+
* shape, failing closed when the earn `execute()` call carries no calldata.
|
|
33904
|
+
*/ function toEarnEncodedTransaction(call) {
|
|
33905
|
+
if (call.data === undefined) {
|
|
33906
|
+
throw reviewError('the earn execute() call is missing calldata', {
|
|
33907
|
+
to: call.to
|
|
33908
|
+
});
|
|
33909
|
+
}
|
|
33910
|
+
return {
|
|
33911
|
+
to: call.to,
|
|
33912
|
+
data: call.data,
|
|
33913
|
+
...call.value !== undefined && {
|
|
33914
|
+
value: call.value
|
|
33915
|
+
}
|
|
33916
|
+
};
|
|
33917
|
+
}
|
|
33918
|
+
/**
|
|
33919
|
+
* Create an Earn authorization descriptor using the supplied canonical-payload
|
|
33920
|
+
* call selector.
|
|
33921
|
+
*
|
|
33922
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
33923
|
+
* @param selectCall - Fail-closed selector for the execute call under review.
|
|
33924
|
+
* @returns A lazy descriptor that decodes and verifies the selected call.
|
|
33925
|
+
*
|
|
33926
|
+
* @internal
|
|
33927
|
+
*/ function createEarnExecuteDescriptor(input, selectCall) {
|
|
33928
|
+
const { action, chain, executionParams } = input;
|
|
33929
|
+
const createReview = (payload)=>{
|
|
33930
|
+
const call = selectCall(payload);
|
|
33931
|
+
const encoded = toEarnEncodedTransaction(call);
|
|
33932
|
+
const decoded = decodeEarnExecute({
|
|
33933
|
+
action,
|
|
33934
|
+
chain,
|
|
33935
|
+
adapter: encoded.to,
|
|
33936
|
+
executionParams
|
|
33937
|
+
});
|
|
33938
|
+
// Prove the calldata about to be signed encodes the same instruction set as
|
|
33939
|
+
// the service-signed params. Throwing here aborts before the wallet prompt.
|
|
33940
|
+
assertEarnCalldataMatchesExecuteParams(encoded.data, executionParams);
|
|
33941
|
+
const review = {
|
|
33942
|
+
kind: EARN_EXECUTE_REVIEW_KIND,
|
|
33943
|
+
data: {
|
|
33944
|
+
encoded,
|
|
33945
|
+
decoded
|
|
33946
|
+
}
|
|
33947
|
+
};
|
|
33948
|
+
return review;
|
|
33949
|
+
};
|
|
33950
|
+
return {
|
|
33951
|
+
createReview
|
|
33952
|
+
};
|
|
33953
|
+
}
|
|
33954
|
+
/**
|
|
33955
|
+
* Build the lazy `earn.execute` authorization descriptor for a same-chain earn
|
|
33956
|
+
* action.
|
|
33957
|
+
*
|
|
33958
|
+
* The returned descriptor carries only a `createReview` factory — no intent
|
|
33959
|
+
* override, because the adapter's action system supplies the intent from the
|
|
33960
|
+
* action key. The factory is evaluated at most once, and only when the
|
|
33961
|
+
* application configured an adapter `onBeforeAuthorize` hook. When it runs it:
|
|
33962
|
+
*
|
|
33963
|
+
* 1. narrows the canonical payload to its single Adapter `execute()` call;
|
|
33964
|
+
* 2. lifts that call into an {@link EarnEncodedTransaction};
|
|
33965
|
+
* 3. decodes it into a `DecodedEarnTx`; and
|
|
33966
|
+
* 4. asserts the decoded calldata matches the service-signed params, throwing
|
|
33967
|
+
* (aborting authorization before the wallet or signer) on any mismatch.
|
|
33968
|
+
*
|
|
33969
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
33970
|
+
* @returns An authorization descriptor to pass as the fourth `prepareAction`
|
|
33971
|
+
* argument for the final earn action only (never the allowance approval).
|
|
33972
|
+
* @throws {@link KitError} From the review factory when the payload is not a
|
|
33973
|
+
* single earn `execute()` call or the calldata diverges from the signed
|
|
33974
|
+
* params. The throw surfaces through the adapter gate before authorization.
|
|
33975
|
+
*
|
|
33976
|
+
* @example
|
|
33977
|
+
* ```typescript
|
|
33978
|
+
* const descriptor = buildEarnExecuteDescriptor({
|
|
33979
|
+
* action: 'deposit',
|
|
33980
|
+
* chain: 'Arc_Testnet',
|
|
33981
|
+
* executionParams,
|
|
33982
|
+
* })
|
|
33983
|
+
* await adapter.prepareAction('earn.deposit', actionParams, ctx, {
|
|
33984
|
+
* authorization: descriptor,
|
|
33985
|
+
* })
|
|
33986
|
+
* ```
|
|
33987
|
+
*
|
|
33988
|
+
* @internal
|
|
33989
|
+
*/ function buildEarnExecuteDescriptor(input) {
|
|
33990
|
+
return createEarnExecuteDescriptor(input, assertSingleEvmCallPayload);
|
|
33991
|
+
}
|
|
33992
|
+
/**
|
|
33993
|
+
* Build a lazy `earn.execute` authorization descriptor for an atomic Earn
|
|
33994
|
+
* batch containing either `[execute]` or `[approve, execute]`.
|
|
33995
|
+
*
|
|
33996
|
+
* The review always decodes and verifies the final call against the
|
|
33997
|
+
* service-signed execution params. Unexpected payload types and call counts
|
|
33998
|
+
* fail closed before wallet authorization.
|
|
33999
|
+
*
|
|
34000
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
34001
|
+
* @returns A descriptor suitable for `batchExecute` authorization options.
|
|
34002
|
+
* @throws {@link KitError} From the lazy review factory when the batch shape or
|
|
34003
|
+
* final execute calldata cannot be verified.
|
|
34004
|
+
*
|
|
34005
|
+
* @internal
|
|
34006
|
+
*/ function buildBatchedEarnExecuteDescriptor(input) {
|
|
34007
|
+
return createEarnExecuteDescriptor(input, assertBatchedEarnExecuteCall);
|
|
34008
|
+
}
|
|
34009
|
+
/**
|
|
34010
|
+
* Type guard: narrow an adapter authorization review to an EarnKit
|
|
34011
|
+
* `earn.execute` review.
|
|
34012
|
+
*
|
|
34013
|
+
* Use this inside an adapter `onBeforeAuthorize` hook to detect whether the
|
|
34014
|
+
* request carries EarnKit semantic data before reading it, instead of
|
|
34015
|
+
* comparing `review.kind` by hand. Robust against plain-JavaScript callers:
|
|
34016
|
+
* accepts `unknown` and checks the shape at runtime.
|
|
34017
|
+
*
|
|
34018
|
+
* @param review - The `review` field from an `AuthorizationRequest`, or any
|
|
34019
|
+
* value.
|
|
34020
|
+
* @returns `true` when `review` is an `earn.execute` review with the expected
|
|
34021
|
+
* `{ encoded, decoded }` data shape.
|
|
34022
|
+
*
|
|
34023
|
+
* @example
|
|
34024
|
+
* ```typescript
|
|
34025
|
+
* onBeforeAuthorize: async ({ review }) => {
|
|
34026
|
+
* if (isEarnExecuteReview(review)) {
|
|
34027
|
+
* await showEarnConfirmation(review.data.decoded.summary)
|
|
34028
|
+
* }
|
|
34029
|
+
* return 'approve'
|
|
34030
|
+
* }
|
|
34031
|
+
* ```
|
|
34032
|
+
*/ function isEarnExecuteReview(review) {
|
|
34033
|
+
if (typeof review !== 'object' || review === null) {
|
|
34034
|
+
return false;
|
|
34035
|
+
}
|
|
34036
|
+
const candidate = review;
|
|
34037
|
+
if (candidate.kind !== EARN_EXECUTE_REVIEW_KIND) {
|
|
34038
|
+
return false;
|
|
34039
|
+
}
|
|
34040
|
+
const data = candidate.data;
|
|
34041
|
+
if (typeof data !== 'object' || data === null) {
|
|
34042
|
+
return false;
|
|
34043
|
+
}
|
|
34044
|
+
const { encoded, decoded } = data;
|
|
34045
|
+
return typeof encoded === 'object' && encoded !== null && typeof decoded === 'object' && decoded !== null;
|
|
34046
|
+
}
|
|
34047
|
+
|
|
32510
34048
|
/**
|
|
32511
34049
|
* Prepare an earn adapter action, execute it, wait for confirmation, and
|
|
32512
34050
|
* throw a structured revert error if the receipt status is `'reverted'`.
|
|
@@ -32533,16 +34071,37 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
32533
34071
|
* address,
|
|
32534
34072
|
* actionKey: 'earn.deposit',
|
|
32535
34073
|
* actionParams: { executeParams, tokenInputs, signature },
|
|
34074
|
+
* action: 'deposit',
|
|
34075
|
+
* executionParams,
|
|
32536
34076
|
* revertMessage: 'Earn deposit reverted on-chain',
|
|
32537
34077
|
* })
|
|
32538
34078
|
* ```
|
|
32539
34079
|
*
|
|
32540
34080
|
* @internal
|
|
32541
34081
|
*/ async function executeEarnAction(params) {
|
|
32542
|
-
const { adapter, chain, address, actionKey, actionParams, revertMessage } = params;
|
|
34082
|
+
const { adapter, chain, address, actionKey, actionParams, action, executionParams, revertMessage } = params;
|
|
34083
|
+
// Attach the lazy `earn.execute` review to the final earn action only (never
|
|
34084
|
+
// the allowance approval, which runs on a separate path). The adapter's
|
|
34085
|
+
// action system supplies the intent from `actionKey`, so the descriptor
|
|
34086
|
+
// carries only the review factory. The factory is evaluated at most once,
|
|
34087
|
+
// and only when the application configured an `onBeforeAuthorize` hook.
|
|
34088
|
+
const authorization = buildEarnExecuteDescriptor({
|
|
34089
|
+
action,
|
|
34090
|
+
// The provider validates the chain is Earn-supported in
|
|
34091
|
+
// `resolveAdapterContext` before reaching execute, so the concrete chain
|
|
34092
|
+
// identifier is a valid `EarnChainIdentifier`. It is carried through to the
|
|
34093
|
+
// decoded preview's display `chain` field only.
|
|
34094
|
+
chain: chain.chain,
|
|
34095
|
+
executionParams
|
|
34096
|
+
});
|
|
34097
|
+
// The abstract `Adapter.prepareAction` is 3-arg; the fourth authorization
|
|
34098
|
+
// argument lives on the `withLegacyCompat` wrapper that produced the concrete
|
|
34099
|
+
// adapter passed here. Narrow the single seam that threads the descriptor.
|
|
32543
34100
|
const prepared = await adapter.prepareAction(actionKey, actionParams, {
|
|
32544
34101
|
chain,
|
|
32545
34102
|
address
|
|
34103
|
+
}, {
|
|
34104
|
+
authorization
|
|
32546
34105
|
});
|
|
32547
34106
|
const gasLimitOverride = await estimateBufferedGasLimit(prepared);
|
|
32548
34107
|
const txHash = prepared.type === 'evm' && gasLimitOverride !== undefined ? await prepared.execute({
|
|
@@ -32567,6 +34126,278 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
32567
34126
|
};
|
|
32568
34127
|
}
|
|
32569
34128
|
|
|
34129
|
+
/**
|
|
34130
|
+
* Decide whether a same-chain earn action should be submitted as a single
|
|
34131
|
+
* atomic batch.
|
|
34132
|
+
*
|
|
34133
|
+
* Returns `true` only when the consumer has not opted out
|
|
34134
|
+
* (`batchTransactions !== false`), the source chain is EVM, the adapter
|
|
34135
|
+
* structurally exposes the shared batch methods, and the wallet reports atomic
|
|
34136
|
+
* batch support. `address` is forwarded as `fromAddress` so developer-controlled
|
|
34137
|
+
* adapters can probe the specific wallet. Any thrown capability probe is
|
|
34138
|
+
* treated as "no support".
|
|
34139
|
+
*
|
|
34140
|
+
* @param params - Adapter, chain, address, and the resolved `batchTransactions` flag.
|
|
34141
|
+
* @returns `true` when batched execution should be attempted.
|
|
34142
|
+
*
|
|
34143
|
+
* @example
|
|
34144
|
+
* ```typescript
|
|
34145
|
+
* if (await shouldUseBatchedEarnAction({ adapter, chain, address, batchTransactions })) {
|
|
34146
|
+
* // take the batched approve + execute path
|
|
34147
|
+
* }
|
|
34148
|
+
* ```
|
|
34149
|
+
*
|
|
34150
|
+
* @internal
|
|
34151
|
+
*/ async function shouldUseBatchedEarnAction(params) {
|
|
34152
|
+
const { adapter, chain, address, batchTransactions } = params;
|
|
34153
|
+
if (batchTransactions === false) {
|
|
34154
|
+
return false;
|
|
34155
|
+
}
|
|
34156
|
+
if (chain.type !== 'evm') {
|
|
34157
|
+
return false;
|
|
34158
|
+
}
|
|
34159
|
+
const candidate = adapter;
|
|
34160
|
+
if (typeof candidate.supportsAtomicBatch !== 'function' || typeof candidate.batchExecute !== 'function') {
|
|
34161
|
+
return false;
|
|
34162
|
+
}
|
|
34163
|
+
try {
|
|
34164
|
+
return await candidate.supportsAtomicBatch(chain, {
|
|
34165
|
+
fromAddress: address
|
|
34166
|
+
});
|
|
34167
|
+
} catch {
|
|
34168
|
+
return false;
|
|
34169
|
+
}
|
|
34170
|
+
}
|
|
34171
|
+
async function buildSuccessfulBatchResult(adapter, chain, receipt, batchId, revertMessage) {
|
|
34172
|
+
const transaction = {
|
|
34173
|
+
txHash: receipt.txHash,
|
|
34174
|
+
explorerUrl: buildExplorerUrl(chain, receipt.txHash)
|
|
34175
|
+
};
|
|
34176
|
+
let confirmed;
|
|
34177
|
+
try {
|
|
34178
|
+
confirmed = await adapter.waitForTransaction(receipt.txHash, {
|
|
34179
|
+
confirmations: 1
|
|
34180
|
+
}, chain);
|
|
34181
|
+
} catch {
|
|
34182
|
+
// The batch adapter already confirmed success. Receipt enrichment is
|
|
34183
|
+
// telemetry-only, so an additional RPC failure must not turn an accepted
|
|
34184
|
+
// money-moving operation into a retryable business failure.
|
|
34185
|
+
return transaction;
|
|
34186
|
+
}
|
|
34187
|
+
if (confirmed.status === 'reverted') {
|
|
34188
|
+
throw createTransactionRevertedError(chain.name, revertMessage, {
|
|
34189
|
+
batchId
|
|
34190
|
+
}, receipt.txHash, transaction.explorerUrl);
|
|
34191
|
+
}
|
|
34192
|
+
return {
|
|
34193
|
+
...transaction,
|
|
34194
|
+
...confirmed.gasUsed !== undefined && {
|
|
34195
|
+
gasUsed: confirmed.gasUsed
|
|
34196
|
+
},
|
|
34197
|
+
...confirmed.effectiveGasPrice !== undefined && {
|
|
34198
|
+
effectiveGasPrice: confirmed.effectiveGasPrice
|
|
34199
|
+
}
|
|
34200
|
+
};
|
|
34201
|
+
}
|
|
34202
|
+
function throwBatchFailure(result, executeReceipt, chain, actionKey, revertMessage) {
|
|
34203
|
+
const cause = result.error;
|
|
34204
|
+
if (result.statusCode === 400) {
|
|
34205
|
+
throw new KitError({
|
|
34206
|
+
...RpcError.ENDPOINT_ERROR,
|
|
34207
|
+
recoverability: 'RETRYABLE',
|
|
34208
|
+
message: `Batched earn ${actionKey} failed off-chain before inclusion (batch ${result.batchId}).`,
|
|
34209
|
+
cause: {
|
|
34210
|
+
trace: {
|
|
34211
|
+
batchId: result.batchId,
|
|
34212
|
+
statusCode: result.statusCode,
|
|
34213
|
+
cause
|
|
34214
|
+
}
|
|
34215
|
+
}
|
|
34216
|
+
});
|
|
34217
|
+
}
|
|
34218
|
+
const causeTrace = cause instanceof KitError && typeof cause.cause?.trace === 'object' && cause.cause.trace !== null ? cause.cause.trace : undefined;
|
|
34219
|
+
if (cause instanceof KitError && causeTrace?.['kind'] === 'failed_offchain') {
|
|
34220
|
+
throw cause;
|
|
34221
|
+
}
|
|
34222
|
+
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 !== '';
|
|
34223
|
+
if (isConfirmedRevert) {
|
|
34224
|
+
throw createTransactionRevertedError(chain.name, revertMessage, {
|
|
34225
|
+
batchId: result.batchId,
|
|
34226
|
+
error: cause
|
|
34227
|
+
});
|
|
34228
|
+
}
|
|
34229
|
+
throw new KitError({
|
|
34230
|
+
...NetworkError.TIMEOUT,
|
|
34231
|
+
recoverability: 'FATAL',
|
|
34232
|
+
message: `Batched earn ${actionKey} was submitted (batch ${result.batchId}) but its outcome could not be confirmed; check the transaction status before retrying.`,
|
|
34233
|
+
cause: {
|
|
34234
|
+
trace: {
|
|
34235
|
+
batchId: result.batchId,
|
|
34236
|
+
cause
|
|
34237
|
+
}
|
|
34238
|
+
}
|
|
34239
|
+
});
|
|
34240
|
+
}
|
|
34241
|
+
/**
|
|
34242
|
+
* Execute the `approve` and `execute` steps of a same-chain earn action as a
|
|
34243
|
+
* single atomic batch.
|
|
34244
|
+
*
|
|
34245
|
+
* Prepare both `PreparedChainRequest` objects upfront, extract their raw call
|
|
34246
|
+
* data via `getCallData()`, then submit both through the adapter's shared
|
|
34247
|
+
* `batchExecute`. `address` is forwarded as `opts.fromAddress` so
|
|
34248
|
+
* developer-controlled adapters batch on behalf of the right wallet;
|
|
34249
|
+
* `idempotencyKey` is forwarded for adapters that deduplicate ambiguous
|
|
34250
|
+
* submissions (the Circle developer-controlled adapter reuses the Earn
|
|
34251
|
+
* execution id); other adapters may ignore either option. Reused by both the
|
|
34252
|
+
* deposit and withdraw flows via the `actionKey` parameter.
|
|
34253
|
+
*
|
|
34254
|
+
* @param params - Adapter, chain, action key, signed payload, and approval inputs.
|
|
34255
|
+
* @returns The confirmed execute transaction hash, explorer URL, and receipt
|
|
34256
|
+
* gas data when the adapter can retrieve it.
|
|
34257
|
+
* @throws {@link KitError} when the source chain is not EVM.
|
|
34258
|
+
* @throws {@link KitError} when calldata extraction (`getCallData`) is not
|
|
34259
|
+
* supported by the prepared requests.
|
|
34260
|
+
* @throws {@link KitError} when the batch reverts on-chain (a confirmed
|
|
34261
|
+
* terminal revert), carrying `batchId`.
|
|
34262
|
+
* @throws {@link KitError} RETRYABLE when EIP-5792 reports an off-chain
|
|
34263
|
+
* failure carrying `batchId` and status code `400`; no call was included.
|
|
34264
|
+
* @throws {@link KitError} FATAL `NetworkError.TIMEOUT` when the batch was
|
|
34265
|
+
* submitted but its outcome could not be confirmed (poll timeout or any
|
|
34266
|
+
* other non-revert post-submission failure); carries `batchId` so the caller
|
|
34267
|
+
* can check transaction status before retrying.
|
|
34268
|
+
* @remarks
|
|
34269
|
+
* Once the batch has been submitted this function does not fall back to the
|
|
34270
|
+
* sequential path — the batch is already on its way, so a fallback would risk
|
|
34271
|
+
* double-spend. Post-submission failures surface through the adapter's batch
|
|
34272
|
+
* result: a confirmed on-chain revert (Circle: a `TRANSACTION_REVERTED` cause;
|
|
34273
|
+
* Viem: status code `500`/`600`) is thrown as a revert error, status code `400`
|
|
34274
|
+
* is reported as a retryable off-chain failure, and any other unconfirmed
|
|
34275
|
+
* outcome is thrown as a FATAL timeout error carrying `batchId`.
|
|
34276
|
+
*
|
|
34277
|
+
* @example
|
|
34278
|
+
* ```typescript
|
|
34279
|
+
* const { txHash, explorerUrl } = await executeBatchedEarnAction({
|
|
34280
|
+
* adapter,
|
|
34281
|
+
* chain,
|
|
34282
|
+
* address,
|
|
34283
|
+
* actionKey: 'earn.deposit',
|
|
34284
|
+
* executeParams,
|
|
34285
|
+
* tokenInputs,
|
|
34286
|
+
* signature,
|
|
34287
|
+
* approvalToken: usdcAddress,
|
|
34288
|
+
* delegate: adapterContractAddress,
|
|
34289
|
+
* requiredAllowance: 1_000_000n,
|
|
34290
|
+
* idempotencyKey: '550e8400-e29b-41d4-a716-446655440000',
|
|
34291
|
+
* revertMessage: 'Earn deposit reverted on-chain',
|
|
34292
|
+
* })
|
|
34293
|
+
* ```
|
|
34294
|
+
*
|
|
34295
|
+
* @internal
|
|
34296
|
+
*/ async function executeBatchedEarnAction(params) {
|
|
34297
|
+
const { adapter, chain, address, actionKey, executeParams, tokenInputs, signature, approvalToken, delegate, requiredAllowance, idempotencyKey, revertMessage } = params;
|
|
34298
|
+
if (chain.type !== 'evm') {
|
|
34299
|
+
throw new KitError({
|
|
34300
|
+
...InputError.INVALID_CHAIN,
|
|
34301
|
+
recoverability: 'FATAL',
|
|
34302
|
+
message: 'Batched earn execution is only supported on EVM chains.'
|
|
34303
|
+
});
|
|
34304
|
+
}
|
|
34305
|
+
const evmChain = chain;
|
|
34306
|
+
const batchAdapter = adapter;
|
|
34307
|
+
// Read the current allowance so the approval tops up only the missing amount.
|
|
34308
|
+
// When the existing allowance already covers the payload, skip the approve
|
|
34309
|
+
// call and batch only the execute — this mirrors the sequential
|
|
34310
|
+
// approveAllowanceIfNeeded guard and avoids an increaseAllowance underflow
|
|
34311
|
+
// (requiredAllowance - currentAllowance would be negative, which reverts as
|
|
34312
|
+
// an out-of-range uint256).
|
|
34313
|
+
const allowancePrepared = await adapter.prepareAction('token.allowance', {
|
|
34314
|
+
tokenAddress: approvalToken,
|
|
34315
|
+
delegate
|
|
34316
|
+
}, {
|
|
34317
|
+
chain,
|
|
34318
|
+
address
|
|
34319
|
+
});
|
|
34320
|
+
const currentAllowance = parseAllowanceResponse(await allowancePrepared.execute());
|
|
34321
|
+
const approvalNeeded = currentAllowance < requiredAllowance;
|
|
34322
|
+
const executePrepared = await adapter.prepareAction(actionKey, {
|
|
34323
|
+
executeParams,
|
|
34324
|
+
tokenInputs,
|
|
34325
|
+
signature
|
|
34326
|
+
}, {
|
|
34327
|
+
chain,
|
|
34328
|
+
address
|
|
34329
|
+
});
|
|
34330
|
+
const approvePrepared = approvalNeeded ? await prepareApprovalAction({
|
|
34331
|
+
adapter,
|
|
34332
|
+
chain,
|
|
34333
|
+
address,
|
|
34334
|
+
tokenAddress: approvalToken,
|
|
34335
|
+
delegate,
|
|
34336
|
+
currentAllowance,
|
|
34337
|
+
requiredAllowance
|
|
34338
|
+
}) : undefined;
|
|
34339
|
+
if (executePrepared.type !== 'evm' || !executePrepared.getCallData) {
|
|
34340
|
+
throw new KitError({
|
|
34341
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
34342
|
+
recoverability: 'FATAL',
|
|
34343
|
+
message: 'Batched earn execution requires EVM prepared requests with getCallData() support.'
|
|
34344
|
+
});
|
|
34345
|
+
}
|
|
34346
|
+
if (approvePrepared !== undefined && (approvePrepared.type !== 'evm' || !approvePrepared.getCallData)) {
|
|
34347
|
+
throw new KitError({
|
|
34348
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
34349
|
+
recoverability: 'FATAL',
|
|
34350
|
+
message: 'Batched earn execution requires EVM prepared requests with getCallData() support.'
|
|
34351
|
+
});
|
|
34352
|
+
}
|
|
34353
|
+
const executeCallData = executePrepared.getCallData();
|
|
34354
|
+
// Prepend the approve call only when an allowance top-up is required.
|
|
34355
|
+
const calls = approvePrepared?.type === 'evm' && approvePrepared.getCallData ? [
|
|
34356
|
+
approvePrepared.getCallData(),
|
|
34357
|
+
executeCallData
|
|
34358
|
+
] : [
|
|
34359
|
+
executeCallData
|
|
34360
|
+
];
|
|
34361
|
+
const authorization = buildBatchedEarnExecuteDescriptor({
|
|
34362
|
+
action: actionKey === 'earn.deposit' ? 'deposit' : 'withdraw',
|
|
34363
|
+
chain: evmChain.chain,
|
|
34364
|
+
executionParams: executeParams
|
|
34365
|
+
});
|
|
34366
|
+
const result = await batchAdapter.batchExecute(calls, evmChain, {
|
|
34367
|
+
fromAddress: address,
|
|
34368
|
+
idempotencyKey,
|
|
34369
|
+
atomicRequired: true,
|
|
34370
|
+
authorization
|
|
34371
|
+
});
|
|
34372
|
+
// Success fans one confirmed hash across every receipt; the execute call is
|
|
34373
|
+
// the last one (approve, if present, precedes it). On failure a confirming
|
|
34374
|
+
// adapter returns no receipts, so a missing/non-success last receipt — or a
|
|
34375
|
+
// populated `result.error` — means the batch failed after submission (point
|
|
34376
|
+
// of no return). We never fall back, which would double-spend.
|
|
34377
|
+
const receiptCountMatches = result.receipts.length === calls.length;
|
|
34378
|
+
const executeReceipt = receiptCountMatches ? result.receipts[calls.length - 1] : undefined;
|
|
34379
|
+
const succeeded = receiptCountMatches && (result.statusCode === undefined || result.statusCode === 200) && result.error === undefined && executeReceipt?.status === 'success' && executeReceipt.txHash !== '';
|
|
34380
|
+
if (succeeded) {
|
|
34381
|
+
return buildSuccessfulBatchResult(adapter, evmChain, executeReceipt, result.batchId, revertMessage);
|
|
34382
|
+
}
|
|
34383
|
+
// Distinguish an off-chain rejection, a confirmed on-chain revert, and an
|
|
34384
|
+
// unknown outcome across both adapter conventions that share this contract:
|
|
34385
|
+
// - Circle SCA: no receipts + `error`; its trace kind identifies an
|
|
34386
|
+
// off-chain rejection, confirmed revert, or unconfirmed outcome.
|
|
34387
|
+
// - Viem EIP-5792: statusCode 500/600 explicitly confirms an on-chain
|
|
34388
|
+
// full/partial revert.
|
|
34389
|
+
// - Legacy/string-status wallets: a real-hash error receipt with no cause
|
|
34390
|
+
// is the best available confirmed-revert signal.
|
|
34391
|
+
// statusCode 400 is terminal but off-chain: the wallet confirms no call was
|
|
34392
|
+
// included, so it must not be labeled as a revert or unknown outcome.
|
|
34393
|
+
// Anything else — a poll timeout or any other post-submission failure with no
|
|
34394
|
+
// confirmed-revert signal — means the batch was submitted but its fate is
|
|
34395
|
+
// unconfirmed. Surface that as a FATAL (non-auto-retry) error carrying
|
|
34396
|
+
// `batchId` so the caller checks status before retrying, rather than
|
|
34397
|
+
// mislabeling it a revert.
|
|
34398
|
+
return throwBatchFailure(result, executeReceipt, evmChain, actionKey, revertMessage);
|
|
34399
|
+
}
|
|
34400
|
+
|
|
32570
34401
|
/**
|
|
32571
34402
|
* Validate that a service-signed execution payload has not expired before
|
|
32572
34403
|
* the SDK asks the wallet to broadcast a transaction.
|
|
@@ -33863,7 +35694,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
33863
35694
|
}
|
|
33864
35695
|
|
|
33865
35696
|
var name$1 = "@circle-fin/provider-earn-service";
|
|
33866
|
-
var version$1 = "1.
|
|
35697
|
+
var version$1 = "1.4.0";
|
|
33867
35698
|
var pkg$1 = {
|
|
33868
35699
|
name: name$1,
|
|
33869
35700
|
version: version$1};
|
|
@@ -33925,15 +35756,25 @@ var pkg$1 = {
|
|
|
33925
35756
|
*
|
|
33926
35757
|
* @internal
|
|
33927
35758
|
*/ function buildConfig(serviceConfig) {
|
|
35759
|
+
// The kit key is a server-only secret. Reject it in the browser so it cannot
|
|
35760
|
+
// leak into a client bundle (no-op in Node.js). Keyless usage stays allowed.
|
|
35761
|
+
if (serviceConfig?.kitKey !== undefined && isBrowserEnvironment()) {
|
|
35762
|
+
throw createValidationFailedError$1('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');
|
|
35763
|
+
}
|
|
33928
35764
|
const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
|
|
33929
|
-
|
|
35765
|
+
// The API CORS policy does not allow this custom header. Keep the existing
|
|
35766
|
+
// per-request version attribution for Node callers, but omit it in browsers
|
|
35767
|
+
// so public EarnKit endpoints do not fail at CORS preflight.
|
|
35768
|
+
const sdkVersionHeader = isNodeEnvironment() ? {
|
|
35769
|
+
[SDK_VERSION_HEADER]: resolveSdkVersionHeader()
|
|
35770
|
+
} : {};
|
|
33930
35771
|
if (serviceConfig?.kitKey === undefined) {
|
|
33931
35772
|
return {
|
|
33932
35773
|
pollingConfig: {
|
|
33933
35774
|
...DEFAULT_CONFIG,
|
|
33934
35775
|
headers: {
|
|
33935
35776
|
...DEFAULT_CONFIG.headers,
|
|
33936
|
-
|
|
35777
|
+
...sdkVersionHeader
|
|
33937
35778
|
}
|
|
33938
35779
|
},
|
|
33939
35780
|
baseUrl
|
|
@@ -33951,7 +35792,7 @@ var pkg$1 = {
|
|
|
33951
35792
|
...DEFAULT_CONFIG,
|
|
33952
35793
|
headers: {
|
|
33953
35794
|
...DEFAULT_CONFIG.headers,
|
|
33954
|
-
|
|
35795
|
+
...sdkVersionHeader,
|
|
33955
35796
|
Authorization: `Bearer ${serviceConfig.kitKey}`
|
|
33956
35797
|
}
|
|
33957
35798
|
},
|
|
@@ -35491,6 +37332,46 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
35491
37332
|
const approvalToken = resolveEarnApprovalToken(executionParams);
|
|
35492
37333
|
const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
|
|
35493
37334
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
37335
|
+
const approvalNeeded = !options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n;
|
|
37336
|
+
// Batch-capable wallets bundle approve + deposit into one atomic
|
|
37337
|
+
// submission. Only attempt this when an approval is actually needed.
|
|
37338
|
+
if (approvalNeeded && approvalToken !== undefined && await shouldUseBatchedEarnAction({
|
|
37339
|
+
adapter,
|
|
37340
|
+
chain,
|
|
37341
|
+
address,
|
|
37342
|
+
batchTransactions: config?.batchTransactions
|
|
37343
|
+
})) {
|
|
37344
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>{
|
|
37345
|
+
try {
|
|
37346
|
+
const result = await executeBatchedEarnAction({
|
|
37347
|
+
adapter,
|
|
37348
|
+
chain,
|
|
37349
|
+
address,
|
|
37350
|
+
actionKey: 'earn.deposit',
|
|
37351
|
+
executeParams: executionParams,
|
|
37352
|
+
tokenInputs,
|
|
37353
|
+
signature,
|
|
37354
|
+
approvalToken,
|
|
37355
|
+
delegate: adapterContractAddress,
|
|
37356
|
+
requiredAllowance,
|
|
37357
|
+
idempotencyKey: execId,
|
|
37358
|
+
revertMessage: 'Earn deposit reverted on-chain'
|
|
37359
|
+
});
|
|
37360
|
+
reportTransactionSuccess(transactionReportContext, 'Deposit', result);
|
|
37361
|
+
return result;
|
|
37362
|
+
} catch (error) {
|
|
37363
|
+
reportTransactionFailure(transactionReportContext, 'Deposit', error);
|
|
37364
|
+
throw error;
|
|
37365
|
+
}
|
|
37366
|
+
}, ({ txHash })=>txHash);
|
|
37367
|
+
return {
|
|
37368
|
+
kind: 'same-chain',
|
|
37369
|
+
txHash,
|
|
37370
|
+
explorerUrl,
|
|
37371
|
+
vaultAddress,
|
|
37372
|
+
amount: params.amount
|
|
37373
|
+
};
|
|
37374
|
+
}
|
|
35494
37375
|
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
|
|
35495
37376
|
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
35496
37377
|
try {
|
|
@@ -35523,6 +37404,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
35523
37404
|
tokenInputs,
|
|
35524
37405
|
signature
|
|
35525
37406
|
},
|
|
37407
|
+
action: 'deposit',
|
|
37408
|
+
executionParams,
|
|
35526
37409
|
revertMessage: 'Earn deposit reverted on-chain'
|
|
35527
37410
|
});
|
|
35528
37411
|
reportTransactionSuccess(transactionReportContext, 'Deposit', result);
|
|
@@ -35654,6 +37537,44 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
35654
37537
|
const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
|
|
35655
37538
|
const approvalToken = tokenInputs[0]?.token;
|
|
35656
37539
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
37540
|
+
// Batch-capable wallets bundle approve + withdraw into one atomic
|
|
37541
|
+
// submission. Only attempt this when an approval is actually needed.
|
|
37542
|
+
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n && await shouldUseBatchedEarnAction({
|
|
37543
|
+
adapter,
|
|
37544
|
+
chain,
|
|
37545
|
+
address,
|
|
37546
|
+
batchTransactions: config?.batchTransactions
|
|
37547
|
+
})) {
|
|
37548
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'withdraw', 'execute', async ()=>{
|
|
37549
|
+
try {
|
|
37550
|
+
const result = await executeBatchedEarnAction({
|
|
37551
|
+
adapter,
|
|
37552
|
+
chain,
|
|
37553
|
+
address,
|
|
37554
|
+
actionKey: 'earn.withdraw',
|
|
37555
|
+
executeParams: executionParams,
|
|
37556
|
+
tokenInputs,
|
|
37557
|
+
signature,
|
|
37558
|
+
approvalToken,
|
|
37559
|
+
delegate: adapterContractAddress,
|
|
37560
|
+
requiredAllowance,
|
|
37561
|
+
idempotencyKey: execId,
|
|
37562
|
+
revertMessage: 'Earn withdraw reverted on-chain'
|
|
37563
|
+
});
|
|
37564
|
+
reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
|
|
37565
|
+
return result;
|
|
37566
|
+
} catch (error) {
|
|
37567
|
+
reportTransactionFailure(transactionReportContext, 'Withdraw', error);
|
|
37568
|
+
throw error;
|
|
37569
|
+
}
|
|
37570
|
+
}, ({ txHash })=>txHash);
|
|
37571
|
+
return {
|
|
37572
|
+
txHash,
|
|
37573
|
+
explorerUrl,
|
|
37574
|
+
vaultAddress,
|
|
37575
|
+
amount: params.amount
|
|
37576
|
+
};
|
|
37577
|
+
}
|
|
35657
37578
|
if (!options.skipApprove && approvalToken !== undefined) {
|
|
35658
37579
|
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
35659
37580
|
try {
|
|
@@ -35686,6 +37607,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
35686
37607
|
tokenInputs,
|
|
35687
37608
|
signature
|
|
35688
37609
|
},
|
|
37610
|
+
action: 'withdraw',
|
|
37611
|
+
executionParams,
|
|
35689
37612
|
revertMessage: 'Earn withdraw reverted on-chain'
|
|
35690
37613
|
});
|
|
35691
37614
|
reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
|
|
@@ -35765,6 +37688,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
35765
37688
|
tokenInputs: [],
|
|
35766
37689
|
signature
|
|
35767
37690
|
},
|
|
37691
|
+
action: 'claimRewards',
|
|
37692
|
+
executionParams,
|
|
35768
37693
|
revertMessage: 'Earn claim rewards reverted on-chain'
|
|
35769
37694
|
}), ({ txHash })=>txHash);
|
|
35770
37695
|
return {
|
|
@@ -36065,6 +37990,12 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
|
|
|
36065
37990
|
if (config.providers !== undefined && !Array.isArray(config.providers)) {
|
|
36066
37991
|
throw createValidationFailedError$1('config.providers', config.providers, 'providers must be an array of earn providers when provided');
|
|
36067
37992
|
}
|
|
37993
|
+
if (config.disableAnalytics !== undefined && typeof config.disableAnalytics !== 'boolean') {
|
|
37994
|
+
throw createValidationFailedError$1('config.disableAnalytics', config.disableAnalytics, 'disableAnalytics must be a boolean when provided');
|
|
37995
|
+
}
|
|
37996
|
+
if (config.disableErrorReporting !== undefined && typeof config.disableErrorReporting !== 'boolean') {
|
|
37997
|
+
throw createValidationFailedError$1('config.disableErrorReporting', config.disableErrorReporting, 'disableErrorReporting must be a boolean when provided');
|
|
37998
|
+
}
|
|
36068
37999
|
const defaultProviders = getDefaultProviders$1();
|
|
36069
38000
|
const providers = [
|
|
36070
38001
|
...config.providers ?? [],
|
|
@@ -36076,6 +38007,31 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
|
|
|
36076
38007
|
return context;
|
|
36077
38008
|
}
|
|
36078
38009
|
|
|
38010
|
+
/**
|
|
38011
|
+
* Register Earn Kit telemetry event type strings with the shared registry so
|
|
38012
|
+
* error telemetry helpers remain compile-time checked.
|
|
38013
|
+
*
|
|
38014
|
+
* @internal
|
|
38015
|
+
*/ /**
|
|
38016
|
+
* Telemetry event type identifiers for Earn Kit operations.
|
|
38017
|
+
*
|
|
38018
|
+
* @internal
|
|
38019
|
+
*/ const EARN_EVENT_TYPES = {
|
|
38020
|
+
GET_VAULTS: 'earn_get_vaults',
|
|
38021
|
+
EXPLORE_VAULTS: 'earn_explore_vaults',
|
|
38022
|
+
GET_POSITION: 'earn_get_position',
|
|
38023
|
+
GET_CROSS_CHAIN_DEPOSIT_STATUS: 'earn_get_cross_chain_deposit_status',
|
|
38024
|
+
WAIT_FOR_CROSS_CHAIN_DEPOSIT: 'earn_wait_for_cross_chain_deposit',
|
|
38025
|
+
DEPOSIT: 'earn_deposit',
|
|
38026
|
+
CROSS_CHAIN_DEPOSIT: 'earn_cross_chain_deposit',
|
|
38027
|
+
WITHDRAW: 'earn_withdraw',
|
|
38028
|
+
CLAIM_REWARDS: 'earn_claim_rewards',
|
|
38029
|
+
GET_DEPOSIT_QUOTE: 'earn_get_deposit_quote',
|
|
38030
|
+
GET_WITHDRAWAL_QUOTE: 'earn_get_withdrawal_quote',
|
|
38031
|
+
GET_CLAIM_REWARDS_QUOTE: 'earn_get_claim_rewards_quote',
|
|
38032
|
+
RETRY: 'earn_retry'
|
|
38033
|
+
};
|
|
38034
|
+
|
|
36079
38035
|
/**
|
|
36080
38036
|
* Format a provider amount object as a human-readable decimal string.
|
|
36081
38037
|
*
|
|
@@ -36417,11 +38373,16 @@ const sourceAdapterContextSchema = z.object({
|
|
|
36417
38373
|
*
|
|
36418
38374
|
* Validate the optional Kit Key field using the standard `apiKeySchema`
|
|
36419
38375
|
* format (`KIT_KEY:<keyId>:<keySecret>`). When omitted, the SDK
|
|
36420
|
-
* operates in permissionless mode.
|
|
38376
|
+
* operates in permissionless mode. `baseUrl` overrides the Earn Service
|
|
38377
|
+
* endpoint (e.g. staging); `batchTransactions: false` opts out of atomic
|
|
38378
|
+
* batched execution. Both are forwarded to the provider, so this `.strict()`
|
|
38379
|
+
* schema must accept them or a valid config object is rejected.
|
|
36421
38380
|
*
|
|
36422
38381
|
* @internal
|
|
36423
38382
|
*/ const earnConfigSchema = z.object({
|
|
36424
|
-
kitKey: apiKeySchema.optional()
|
|
38383
|
+
kitKey: apiKeySchema.optional(),
|
|
38384
|
+
baseUrl: z.string().optional(),
|
|
38385
|
+
batchTransactions: z.boolean().optional()
|
|
36425
38386
|
}).strict();
|
|
36426
38387
|
/**
|
|
36427
38388
|
* Canonical decimal form: a leading digit with no leading zeros (a single
|
|
@@ -37591,6 +39552,14 @@ function hasCrossChainDestination(params) {
|
|
|
37591
39552
|
return formatClaimRewardsQuoteInfo(result);
|
|
37592
39553
|
}
|
|
37593
39554
|
|
|
39555
|
+
/** SDK name used in telemetry payloads. */ const SDK_NAME$1 = resolveKitSdkName(pkg$2.name);
|
|
39556
|
+
/**
|
|
39557
|
+
* Determine whether deposit parameters target a destination chain.
|
|
39558
|
+
*
|
|
39559
|
+
* @internal
|
|
39560
|
+
*/ function isCrossChainDeposit(params) {
|
|
39561
|
+
return 'to' in params && params.to !== undefined;
|
|
39562
|
+
}
|
|
37594
39563
|
function formatRetryResult(operation, result) {
|
|
37595
39564
|
switch(operation){
|
|
37596
39565
|
case 'deposit':
|
|
@@ -37605,6 +39574,70 @@ function formatRetryResult(operation, result) {
|
|
|
37605
39574
|
}
|
|
37606
39575
|
}
|
|
37607
39576
|
}
|
|
39577
|
+
/**
|
|
39578
|
+
* Emit the success event corresponding to a completed retry.
|
|
39579
|
+
*
|
|
39580
|
+
* @internal
|
|
39581
|
+
*/ function emitRetrySuccessTelemetry(trace, result, config) {
|
|
39582
|
+
switch(trace.operation){
|
|
39583
|
+
case 'deposit':
|
|
39584
|
+
{
|
|
39585
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
39586
|
+
if ('to' in trace.params && trace.params.to !== undefined) {
|
|
39587
|
+
const destinationChain = resolveChainName(trace.params.to.chain);
|
|
39588
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, config, {
|
|
39589
|
+
...sourceChain != null && {
|
|
39590
|
+
sourceChain
|
|
39591
|
+
},
|
|
39592
|
+
...destinationChain != null && {
|
|
39593
|
+
destinationChain
|
|
39594
|
+
}
|
|
39595
|
+
});
|
|
39596
|
+
return;
|
|
39597
|
+
}
|
|
39598
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, config, {
|
|
39599
|
+
...sourceChain != null && {
|
|
39600
|
+
sourceChain
|
|
39601
|
+
},
|
|
39602
|
+
...'txHash' in result && {
|
|
39603
|
+
txHash: result.txHash
|
|
39604
|
+
}
|
|
39605
|
+
});
|
|
39606
|
+
return;
|
|
39607
|
+
}
|
|
39608
|
+
case 'withdraw':
|
|
39609
|
+
{
|
|
39610
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
39611
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, config, {
|
|
39612
|
+
...sourceChain != null && {
|
|
39613
|
+
sourceChain
|
|
39614
|
+
},
|
|
39615
|
+
...'txHash' in result && {
|
|
39616
|
+
txHash: result.txHash
|
|
39617
|
+
}
|
|
39618
|
+
});
|
|
39619
|
+
return;
|
|
39620
|
+
}
|
|
39621
|
+
case 'claimRewards':
|
|
39622
|
+
{
|
|
39623
|
+
if ('rewards' in result && result.status === 'claimed') {
|
|
39624
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
39625
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, config, {
|
|
39626
|
+
...sourceChain != null && {
|
|
39627
|
+
sourceChain
|
|
39628
|
+
},
|
|
39629
|
+
txHash: result.txHash
|
|
39630
|
+
});
|
|
39631
|
+
}
|
|
39632
|
+
return;
|
|
39633
|
+
}
|
|
39634
|
+
default:
|
|
39635
|
+
{
|
|
39636
|
+
const exhaustive = trace;
|
|
39637
|
+
throw createValidationFailedError$1('error.cause.trace', exhaustive, 'EarnKit.retry() does not support this earn operation');
|
|
39638
|
+
}
|
|
39639
|
+
}
|
|
39640
|
+
}
|
|
37608
39641
|
/**
|
|
37609
39642
|
* A high-level class-based interface for DeFi lending vault operations.
|
|
37610
39643
|
*
|
|
@@ -37663,6 +39696,8 @@ function formatRetryResult(operation, result) {
|
|
|
37663
39696
|
* ```
|
|
37664
39697
|
*/ class EarnKit {
|
|
37665
39698
|
context;
|
|
39699
|
+
/** Per-kit identity and opt-out state for error telemetry. */ telemetryConfig;
|
|
39700
|
+
/** Per-kit identity and opt-out state for success telemetry. */ analyticsTelemetryConfig;
|
|
37666
39701
|
/**
|
|
37667
39702
|
* Event dispatcher for step-level events emitted during multi-phase earn
|
|
37668
39703
|
* operations. Prefer {@link EarnKit.on} / {@link EarnKit.off} over using
|
|
@@ -37687,6 +39722,16 @@ function formatRetryResult(operation, result) {
|
|
|
37687
39722
|
*/ constructor(config = {}){
|
|
37688
39723
|
this.context = createEarnKitContext(config);
|
|
37689
39724
|
this.actionDispatcher = new Actionable();
|
|
39725
|
+
this.telemetryConfig = {
|
|
39726
|
+
sdkName: SDK_NAME$1,
|
|
39727
|
+
sdkVersion: pkg$2.version,
|
|
39728
|
+
disabled: config.disableErrorReporting === true
|
|
39729
|
+
};
|
|
39730
|
+
this.analyticsTelemetryConfig = {
|
|
39731
|
+
sdkName: SDK_NAME$1,
|
|
39732
|
+
sdkVersion: pkg$2.version,
|
|
39733
|
+
disabled: config.disableAnalytics === true
|
|
39734
|
+
};
|
|
37690
39735
|
for (const provider of this.context.providers){
|
|
37691
39736
|
provider.registerDispatcher(this.actionDispatcher);
|
|
37692
39737
|
}
|
|
@@ -37747,29 +39792,36 @@ function formatRetryResult(operation, result) {
|
|
|
37747
39792
|
* }
|
|
37748
39793
|
* ```
|
|
37749
39794
|
*/ async retry(error) {
|
|
37750
|
-
|
|
37751
|
-
|
|
37752
|
-
|
|
37753
|
-
|
|
37754
|
-
|
|
37755
|
-
|
|
37756
|
-
|
|
37757
|
-
|
|
37758
|
-
|
|
37759
|
-
|
|
37760
|
-
|
|
37761
|
-
|
|
37762
|
-
|
|
37763
|
-
|
|
37764
|
-
|
|
37765
|
-
|
|
37766
|
-
|
|
37767
|
-
|
|
37768
|
-
|
|
37769
|
-
|
|
39795
|
+
const result = await withErrorTelemetry(async ()=>{
|
|
39796
|
+
if (!isKitError(error)) {
|
|
39797
|
+
throw createValidationFailedError$1('error', error, 'EarnKit.retry() requires a KitError thrown by a previous earn operation');
|
|
39798
|
+
}
|
|
39799
|
+
if (!isRetryableError$1(error)) {
|
|
39800
|
+
throw createValidationFailedError$1('error.recoverability', error.recoverability, 'EarnKit.retry() requires a retryable or resumable error — check isRetryableError(error) first');
|
|
39801
|
+
}
|
|
39802
|
+
const trace = error.cause?.trace;
|
|
39803
|
+
if (!isEarnErrorTrace(trace)) {
|
|
39804
|
+
throw createValidationFailedError$1('error.cause.trace', trace, 'EarnKit.retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
|
|
39805
|
+
}
|
|
39806
|
+
const provider = this.context.providers.find((candidate)=>candidate.name === trace.provider);
|
|
39807
|
+
if (provider === undefined) {
|
|
39808
|
+
throw createValidationFailedError$1('error.cause.trace.provider', trace.provider, `No earn provider named "${trace.provider}" is registered with this kit`);
|
|
39809
|
+
}
|
|
39810
|
+
const result = await provider.retry(error);
|
|
39811
|
+
// `provider.retry` returns a flat result union with no compile-time link to
|
|
39812
|
+
// `trace.operation`, so narrow the operation here to select the matching
|
|
39813
|
+
// overload. The result cast in each branch is sound: the provider always
|
|
39814
|
+
// returns the result type corresponding to the resumed operation.
|
|
39815
|
+
if (trace.operation === 'claimRewards') {
|
|
39816
|
+
return formatRetryResult(trace.operation, result);
|
|
39817
|
+
}
|
|
37770
39818
|
return formatRetryResult(trace.operation, result);
|
|
39819
|
+
}, EARN_EVENT_TYPES.RETRY, this.telemetryConfig);
|
|
39820
|
+
const trace = isKitError(error) ? error.cause?.trace : undefined;
|
|
39821
|
+
if (isEarnErrorTrace(trace)) {
|
|
39822
|
+
emitRetrySuccessTelemetry(trace, result, this.analyticsTelemetryConfig);
|
|
37771
39823
|
}
|
|
37772
|
-
return
|
|
39824
|
+
return result;
|
|
37773
39825
|
}
|
|
37774
39826
|
/**
|
|
37775
39827
|
* Return the chains supported by configured earn providers.
|
|
@@ -37803,7 +39855,9 @@ function formatRetryResult(operation, result) {
|
|
|
37803
39855
|
* result.vaults.forEach(v => console.log(`${v.name}: ${(v.currentApy * 100).toFixed(2)}% APY`))
|
|
37804
39856
|
* ```
|
|
37805
39857
|
*/ async getVaults(params) {
|
|
37806
|
-
|
|
39858
|
+
const result = await withErrorTelemetry(async ()=>getVaults$1(this.context, params), EARN_EVENT_TYPES.GET_VAULTS, this.telemetryConfig);
|
|
39859
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.GET_VAULTS, this.analyticsTelemetryConfig, {});
|
|
39860
|
+
return result;
|
|
37807
39861
|
}
|
|
37808
39862
|
/**
|
|
37809
39863
|
* Discover vaults available on a chain.
|
|
@@ -37828,7 +39882,12 @@ function formatRetryResult(operation, result) {
|
|
|
37828
39882
|
* const guarded = result.vaults.filter(v => v.circleGuarded) // Circle-guarded vaults only
|
|
37829
39883
|
* ```
|
|
37830
39884
|
*/ async exploreVaults(params) {
|
|
37831
|
-
|
|
39885
|
+
const context = {
|
|
39886
|
+
sourceChain: resolveChainName(params.chain)
|
|
39887
|
+
};
|
|
39888
|
+
const result = await withErrorTelemetry(async ()=>exploreVaults$1(this.context, params), EARN_EVENT_TYPES.EXPLORE_VAULTS, this.telemetryConfig, context);
|
|
39889
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.EXPLORE_VAULTS, this.analyticsTelemetryConfig, context);
|
|
39890
|
+
return result;
|
|
37832
39891
|
}
|
|
37833
39892
|
/**
|
|
37834
39893
|
* Lazily iterate every vault available on a chain.
|
|
@@ -37875,7 +39934,9 @@ function formatRetryResult(operation, result) {
|
|
|
37875
39934
|
* }
|
|
37876
39935
|
* ```
|
|
37877
39936
|
*/ async getPosition(params) {
|
|
37878
|
-
return getPosition$1(this.context, params)
|
|
39937
|
+
return withErrorTelemetry(async ()=>getPosition$1(this.context, params), EARN_EVENT_TYPES.GET_POSITION, this.telemetryConfig, {
|
|
39938
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
39939
|
+
});
|
|
37879
39940
|
}
|
|
37880
39941
|
/**
|
|
37881
39942
|
* Fetch the current status of a cross-chain deposit by execution ID.
|
|
@@ -37900,7 +39961,7 @@ function formatRetryResult(operation, result) {
|
|
|
37900
39961
|
* console.log(`Bridge ${status.execId} is ${status.status}`)
|
|
37901
39962
|
* ```
|
|
37902
39963
|
*/ async getCrossChainDepositStatus(params) {
|
|
37903
|
-
return getCrossChainDepositStatus$1(this.context, params);
|
|
39964
|
+
return withErrorTelemetry(async ()=>getCrossChainDepositStatus$1(this.context, params), EARN_EVENT_TYPES.GET_CROSS_CHAIN_DEPOSIT_STATUS, this.telemetryConfig);
|
|
37904
39965
|
}
|
|
37905
39966
|
/**
|
|
37906
39967
|
* Poll a cross-chain deposit until it reaches a terminal bridge state.
|
|
@@ -37927,10 +39988,30 @@ function formatRetryResult(operation, result) {
|
|
|
37927
39988
|
* console.log(`Bridge ended as ${result.outcome}`)
|
|
37928
39989
|
* ```
|
|
37929
39990
|
*/ async waitForCrossChainDeposit(params) {
|
|
37930
|
-
return waitForCrossChainDeposit$1(this.context, params);
|
|
39991
|
+
return withErrorTelemetry(async ()=>waitForCrossChainDeposit$1(this.context, params), EARN_EVENT_TYPES.WAIT_FOR_CROSS_CHAIN_DEPOSIT, this.telemetryConfig);
|
|
37931
39992
|
}
|
|
37932
39993
|
async deposit(params) {
|
|
37933
|
-
|
|
39994
|
+
const isCrossChain = isCrossChainDeposit(params);
|
|
39995
|
+
const context = {
|
|
39996
|
+
sourceChain: resolveChainName(params.from.chain),
|
|
39997
|
+
...isCrossChain && {
|
|
39998
|
+
destinationChain: resolveChainName(params.to.chain)
|
|
39999
|
+
}
|
|
40000
|
+
};
|
|
40001
|
+
const eventType = isCrossChain ? EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT : EARN_EVENT_TYPES.DEPOSIT;
|
|
40002
|
+
const result = await withErrorTelemetry(async ()=>deposit$3(this.context, params), eventType, this.telemetryConfig, context);
|
|
40003
|
+
if (result.kind === 'cross-chain') {
|
|
40004
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, this.analyticsTelemetryConfig, {
|
|
40005
|
+
sourceChain: resolveChainName(result.sourceChain),
|
|
40006
|
+
destinationChain: resolveChainName(result.destinationChain)
|
|
40007
|
+
});
|
|
40008
|
+
} else {
|
|
40009
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, this.analyticsTelemetryConfig, {
|
|
40010
|
+
...context,
|
|
40011
|
+
txHash: result.txHash
|
|
40012
|
+
});
|
|
40013
|
+
}
|
|
40014
|
+
return result;
|
|
37934
40015
|
}
|
|
37935
40016
|
/**
|
|
37936
40017
|
* Execute a withdrawal from a DeFi lending vault.
|
|
@@ -37956,7 +40037,15 @@ function formatRetryResult(operation, result) {
|
|
|
37956
40037
|
* console.log(`Withdrew ${result.amount} from ${result.vaultAddress}, tx: ${result.txHash}`)
|
|
37957
40038
|
* ```
|
|
37958
40039
|
*/ async withdraw(params) {
|
|
37959
|
-
|
|
40040
|
+
const context = {
|
|
40041
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
40042
|
+
};
|
|
40043
|
+
const result = await withErrorTelemetry(async ()=>withdraw$1(this.context, params), EARN_EVENT_TYPES.WITHDRAW, this.telemetryConfig, context);
|
|
40044
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, this.analyticsTelemetryConfig, {
|
|
40045
|
+
...context,
|
|
40046
|
+
txHash: result.txHash
|
|
40047
|
+
});
|
|
40048
|
+
return result;
|
|
37960
40049
|
}
|
|
37961
40050
|
/**
|
|
37962
40051
|
* Claim rewards from earn vaults.
|
|
@@ -37983,7 +40072,17 @@ function formatRetryResult(operation, result) {
|
|
|
37983
40072
|
*
|
|
37984
40073
|
* @internal
|
|
37985
40074
|
*/ async claimRewards(params) {
|
|
37986
|
-
|
|
40075
|
+
const context = {
|
|
40076
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
40077
|
+
};
|
|
40078
|
+
const result = await withErrorTelemetry(async ()=>claimRewards$1(this.context, params), EARN_EVENT_TYPES.CLAIM_REWARDS, this.telemetryConfig, context);
|
|
40079
|
+
if (result.status === 'claimed') {
|
|
40080
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, this.analyticsTelemetryConfig, {
|
|
40081
|
+
...context,
|
|
40082
|
+
txHash: result.txHash
|
|
40083
|
+
});
|
|
40084
|
+
}
|
|
40085
|
+
return result;
|
|
37987
40086
|
}
|
|
37988
40087
|
/**
|
|
37989
40088
|
* Get an informational quote for a deposit into a vault.
|
|
@@ -38005,7 +40104,9 @@ function formatRetryResult(operation, result) {
|
|
|
38005
40104
|
* console.log(`Expected shares: ${quote.expectedShares.amount}`)
|
|
38006
40105
|
* ```
|
|
38007
40106
|
*/ async getDepositQuote(params) {
|
|
38008
|
-
return getDepositQuote$1(this.context, params)
|
|
40107
|
+
return withErrorTelemetry(async ()=>getDepositQuote$1(this.context, params), EARN_EVENT_TYPES.GET_DEPOSIT_QUOTE, this.telemetryConfig, {
|
|
40108
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
40109
|
+
});
|
|
38009
40110
|
}
|
|
38010
40111
|
/**
|
|
38011
40112
|
* Get an informational quote for a withdrawal from a vault.
|
|
@@ -38027,7 +40128,9 @@ function formatRetryResult(operation, result) {
|
|
|
38027
40128
|
* console.log(`Shares to redeem: ${quote.sharesToRedeem.amount}`)
|
|
38028
40129
|
* ```
|
|
38029
40130
|
*/ async getWithdrawalQuote(params) {
|
|
38030
|
-
return getWithdrawalQuote$1(this.context, params)
|
|
40131
|
+
return withErrorTelemetry(async ()=>getWithdrawalQuote$1(this.context, params), EARN_EVENT_TYPES.GET_WITHDRAWAL_QUOTE, this.telemetryConfig, {
|
|
40132
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
40133
|
+
});
|
|
38031
40134
|
}
|
|
38032
40135
|
/**
|
|
38033
40136
|
* Get an informational quote for claiming rewards.
|
|
@@ -38049,7 +40152,9 @@ function formatRetryResult(operation, result) {
|
|
|
38049
40152
|
*
|
|
38050
40153
|
* @internal
|
|
38051
40154
|
*/ async getClaimRewardsQuote(params) {
|
|
38052
|
-
return getClaimRewardsQuote$1(this.context, params)
|
|
40155
|
+
return withErrorTelemetry(async ()=>getClaimRewardsQuote$1(this.context, params), EARN_EVENT_TYPES.GET_CLAIM_REWARDS_QUOTE, this.telemetryConfig, {
|
|
40156
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
40157
|
+
});
|
|
38053
40158
|
}
|
|
38054
40159
|
}
|
|
38055
40160
|
|
|
@@ -38138,7 +40243,14 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
|
|
|
38138
40243
|
* const earnKit = createEarnKit(context)
|
|
38139
40244
|
* ```
|
|
38140
40245
|
*/ const createEarnKit = (context)=>{
|
|
38141
|
-
const kit = new EarnKit(
|
|
40246
|
+
const kit = new EarnKit({
|
|
40247
|
+
...context.disableErrorReporting != null && {
|
|
40248
|
+
disableErrorReporting: context.disableErrorReporting
|
|
40249
|
+
},
|
|
40250
|
+
...context.disableAnalytics != null && {
|
|
40251
|
+
disableAnalytics: context.disableAnalytics
|
|
40252
|
+
}
|
|
40253
|
+
});
|
|
38142
40254
|
registerActionHandlers(kit, context.actions.earn, 'earn');
|
|
38143
40255
|
return kit;
|
|
38144
40256
|
};
|
|
@@ -39447,7 +41559,7 @@ async function deposit$2(context, params) {
|
|
|
39447
41559
|
}
|
|
39448
41560
|
|
|
39449
41561
|
var name = "@circle-fin/unified-balance-kit";
|
|
39450
|
-
var version = "1.
|
|
41562
|
+
var version = "1.4.0";
|
|
39451
41563
|
var pkg = {
|
|
39452
41564
|
name: name,
|
|
39453
41565
|
version: version};
|
|
@@ -40788,7 +42900,8 @@ function throwNetworkMismatch(expected, actual) {
|
|
|
40788
42900
|
};
|
|
40789
42901
|
}
|
|
40790
42902
|
/**
|
|
40791
|
-
* Group intents
|
|
42903
|
+
* Group intents for signing (Solana one-per-intent, EVM batched by adapter
|
|
42904
|
+
* with every source chain retained by Gateway domain).
|
|
40792
42905
|
*
|
|
40793
42906
|
* @param intents - Burn intents from estimate response.
|
|
40794
42907
|
* @param allocations - Normalized allocations used to map domain → adapter/chain.
|
|
@@ -42165,22 +44278,32 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
42165
44278
|
*
|
|
42166
44279
|
* Single-intent sets become one burnIntent + signature; multi-intent sets become burnIntentSet + signature.
|
|
42167
44280
|
*
|
|
44281
|
+
* Sets flagged `contractSigner` carry `contractSigner: true`, which tells
|
|
44282
|
+
* Gateway to validate the signature with ERC-1271 (an offchain
|
|
44283
|
+
* `isValidSignature` simulation) instead of `ecrecover`. The flag is
|
|
44284
|
+
* omitted for EOA signers so their payloads stay byte-identical.
|
|
44285
|
+
*
|
|
42168
44286
|
* @param signedSets - Signed intent sets (intents + signature per signer).
|
|
42169
44287
|
* @returns Array of transfer payloads for POST /v1/transfer.
|
|
42170
44288
|
*/ function buildTransferRequestBody(signedSets) {
|
|
42171
44289
|
return signedSets.map((set)=>{
|
|
42172
44290
|
const firstIntent = set.intents[0];
|
|
44291
|
+
const contractSigner = set.contractSigner === true ? {
|
|
44292
|
+
contractSigner: true
|
|
44293
|
+
} : {};
|
|
42173
44294
|
if (set.intents.length === 1 && firstIntent) {
|
|
42174
44295
|
return {
|
|
42175
44296
|
burnIntent: serializeBurnIntent(firstIntent),
|
|
42176
|
-
signature: set.signature
|
|
44297
|
+
signature: set.signature,
|
|
44298
|
+
...contractSigner
|
|
42177
44299
|
};
|
|
42178
44300
|
}
|
|
42179
44301
|
return {
|
|
42180
44302
|
burnIntentSet: {
|
|
42181
44303
|
intents: set.intents.map(serializeBurnIntent)
|
|
42182
44304
|
},
|
|
42183
|
-
signature: set.signature
|
|
44305
|
+
signature: set.signature,
|
|
44306
|
+
...contractSigner
|
|
42184
44307
|
};
|
|
42185
44308
|
});
|
|
42186
44309
|
}
|
|
@@ -42543,11 +44666,16 @@ const BPS_DIVISOR = 100_000n;
|
|
|
42543
44666
|
return required;
|
|
42544
44667
|
}
|
|
42545
44668
|
|
|
44669
|
+
function requireEvmChainsByDomain(group) {
|
|
44670
|
+
if (group.chainsByDomain !== undefined) return group.chainsByDomain;
|
|
44671
|
+
throw createValidationFailedError$1('adapterGroup.chainsByDomain', group.chainsByDomain, 'must be provided for an EVM adapter group');
|
|
44672
|
+
}
|
|
42546
44673
|
/**
|
|
42547
|
-
* Sign each adapter group: Solana one intent per signature, EVM
|
|
44674
|
+
* Sign each adapter group: Solana one intent per signature, and EVM either
|
|
44675
|
+
* batched for EOAs or split by source chain for ERC-1271 signers.
|
|
42548
44676
|
*
|
|
42549
44677
|
* @param adapterGroups - Groups from groupIntentsByAdapter.
|
|
42550
|
-
* @returns Promise of signed sets
|
|
44678
|
+
* @returns Promise of signed sets for buildTransferRequestBody.
|
|
42551
44679
|
*
|
|
42552
44680
|
* @example
|
|
42553
44681
|
* ```typescript
|
|
@@ -42560,9 +44688,10 @@ const BPS_DIVISOR = 100_000n;
|
|
|
42560
44688
|
if (group.chain.type === 'solana') {
|
|
42561
44689
|
return signSolanaIntentGroup(group);
|
|
42562
44690
|
}
|
|
42563
|
-
return
|
|
42564
|
-
|
|
42565
|
-
|
|
44691
|
+
return await signEvmIntentGroup({
|
|
44692
|
+
...group,
|
|
44693
|
+
chainsByDomain: requireEvmChainsByDomain(group)
|
|
44694
|
+
});
|
|
42566
44695
|
}));
|
|
42567
44696
|
return nested.flat();
|
|
42568
44697
|
}
|
|
@@ -43212,7 +45341,8 @@ async function runSpendNormalPath(params, destChain, useForwarder, dispatcher, s
|
|
|
43212
45341
|
signedSetCount: signedSets.length,
|
|
43213
45342
|
signatures: signedSets.map((s)=>({
|
|
43214
45343
|
intentCount: s.intents.length,
|
|
43215
|
-
signature: s.signature
|
|
45344
|
+
signature: s.signature,
|
|
45345
|
+
contractSigner: s.contractSigner === true
|
|
43216
45346
|
}))
|
|
43217
45347
|
}
|
|
43218
45348
|
});
|
|
@@ -45957,7 +48087,11 @@ const removeFundParamsSchema = z.object({
|
|
|
45957
48087
|
// Remove Fund Operations
|
|
45958
48088
|
// ---------------------------------------------------------------------------
|
|
45959
48089
|
/**
|
|
45960
|
-
* Kick off a delayed fund removal from an account.
|
|
48090
|
+
* Kick off a delayed recovery fund removal from an account.
|
|
48091
|
+
*
|
|
48092
|
+
* Use `initiateRemoveFund` only as a trustless fallback when the normal spend
|
|
48093
|
+
* flow is unavailable. For day-to-day movement out of a Unified Balance, use
|
|
48094
|
+
* `spend`.
|
|
45961
48095
|
*
|
|
45962
48096
|
* Validates `from` and `amount`, resolves the chain and token via
|
|
45963
48097
|
* {@link resolveRemoveFundParams}, selects the matching provider, then calls
|
|
@@ -45994,7 +48128,10 @@ const removeFundParamsSchema = z.object({
|
|
|
45994
48128
|
return provider.initiateRemoveFund(resolved);
|
|
45995
48129
|
}
|
|
45996
48130
|
/**
|
|
45997
|
-
* Complete a fund removal once the 7-day
|
|
48131
|
+
* Complete a recovery fund removal once the 7-day withdrawal delay has passed.
|
|
48132
|
+
*
|
|
48133
|
+
* Use `removeFund` only as a trustless fallback when the normal spend flow is
|
|
48134
|
+
* unavailable. For day-to-day movement out of a Unified Balance, use `spend`.
|
|
45998
48135
|
*
|
|
45999
48136
|
* Validates `from`, resolves the chain and token via
|
|
46000
48137
|
* {@link resolveRemoveFundParams}, selects the matching provider, then calls
|
|
@@ -46083,13 +48220,18 @@ const removeFundParamsSchema = z.object({
|
|
|
46083
48220
|
/** SDK name used in telemetry payloads. */ const SDK_NAME = resolveKitSdkName(pkg.name);
|
|
46084
48221
|
/**
|
|
46085
48222
|
* A high-level class-based interface for cross-chain USDC deposits,
|
|
46086
|
-
* spending, balance queries, delegation management, and
|
|
48223
|
+
* spending, balance queries, delegation management, and recovery fund removals.
|
|
46087
48224
|
*
|
|
46088
48225
|
* UnifiedBalanceKit provides a familiar class-based API for developers who
|
|
46089
48226
|
* prefer traditional object-oriented patterns. The class maintains an
|
|
46090
48227
|
* internal context and provides methods that delegate to the standalone
|
|
46091
48228
|
* operation functions exported by this package.
|
|
46092
48229
|
*
|
|
48230
|
+
* Use `spend` for normal movement out of a Unified Balance. `removeFund` is a
|
|
48231
|
+
* trustless recovery path for situations where the normal spend flow is
|
|
48232
|
+
* unavailable, and it requires a 7-day withdrawal delay before funds can be
|
|
48233
|
+
* removed.
|
|
48234
|
+
*
|
|
46093
48235
|
* @remarks
|
|
46094
48236
|
* For functional usage, import and use the operations directly:
|
|
46095
48237
|
* ```typescript
|
|
@@ -46310,7 +48452,11 @@ const removeFundParamsSchema = z.object({
|
|
|
46310
48452
|
});
|
|
46311
48453
|
}
|
|
46312
48454
|
/**
|
|
46313
|
-
* Kick off a delayed fund removal from an account.
|
|
48455
|
+
* Kick off a delayed recovery fund removal from an account.
|
|
48456
|
+
*
|
|
48457
|
+
* Use this only as a trustless fallback when the normal spend flow is
|
|
48458
|
+
* unavailable. For day-to-day movement out of a Unified Balance, use
|
|
48459
|
+
* `spend`.
|
|
46314
48460
|
*
|
|
46315
48461
|
* @param params - The account owner's adapter context, amount, and
|
|
46316
48462
|
* optional token type.
|
|
@@ -46324,7 +48470,12 @@ const removeFundParamsSchema = z.object({
|
|
|
46324
48470
|
});
|
|
46325
48471
|
}
|
|
46326
48472
|
/**
|
|
46327
|
-
* Complete a fund removal once the
|
|
48473
|
+
* Complete a recovery fund removal once the 7-day withdrawal delay has
|
|
48474
|
+
* passed.
|
|
48475
|
+
*
|
|
48476
|
+
* Use this only as a trustless fallback when the normal spend flow is
|
|
48477
|
+
* unavailable. For day-to-day movement out of a Unified Balance, use
|
|
48478
|
+
* `spend`.
|
|
46328
48479
|
*
|
|
46329
48480
|
* @param params - The account owner context matching the original
|
|
46330
48481
|
* fund removal initiation.
|
|
@@ -46451,6 +48602,11 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
46451
48602
|
* Internally holds a persistent {@link UnifiedBalanceKit} instance so that
|
|
46452
48603
|
* event dispatchers and custom fee policies are preserved across calls.
|
|
46453
48604
|
*
|
|
48605
|
+
* Use {@link AppKitUnifiedBalance.spend} for normal movement out of a Unified
|
|
48606
|
+
* Balance. {@link AppKitUnifiedBalance.removeFund} is a trustless recovery path
|
|
48607
|
+
* for situations where the normal spend flow is unavailable, and it requires a
|
|
48608
|
+
* 7-day withdrawal delay after {@link AppKitUnifiedBalance.initiateRemoveFund}.
|
|
48609
|
+
*
|
|
46454
48610
|
* @example
|
|
46455
48611
|
* ```typescript
|
|
46456
48612
|
* import { AppKit } from '@circle-fin/app-kit'
|
|
@@ -46662,7 +48818,12 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
46662
48818
|
return this.kit.removeDelegate(params);
|
|
46663
48819
|
}
|
|
46664
48820
|
/**
|
|
46665
|
-
*
|
|
48821
|
+
* Initiate a trustless recovery removal from an account.
|
|
48822
|
+
*
|
|
48823
|
+
* Use `spend` for normal movement out of a Unified Balance. `removeFund` is a
|
|
48824
|
+
* recovery path for situations where the normal spend flow is unavailable.
|
|
48825
|
+
* Calling this method starts the 7-day withdrawal delay before the removal can
|
|
48826
|
+
* be completed.
|
|
46666
48827
|
*
|
|
46667
48828
|
* @param params - The account owner's adapter context, amount, and token.
|
|
46668
48829
|
* @returns Promise resolving to the initiation details.
|
|
@@ -46681,11 +48842,16 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
46681
48842
|
return this.kit.initiateRemoveFund(params);
|
|
46682
48843
|
}
|
|
46683
48844
|
/**
|
|
46684
|
-
* Complete a
|
|
48845
|
+
* Complete a trustless recovery removal after the withdrawal delay.
|
|
48846
|
+
*
|
|
48847
|
+
* Use `spend` for normal movement out of a Unified Balance. `removeFund` is a
|
|
48848
|
+
* recovery path for situations where the normal spend flow is unavailable.
|
|
48849
|
+
* Both EVM and Solana removals require a 7-day withdrawal delay after
|
|
48850
|
+
* `initiateRemoveFund` before funds can be removed.
|
|
46685
48851
|
*
|
|
46686
48852
|
* @param params - The account owner context matching the original initiation.
|
|
46687
48853
|
* @returns Promise resolving to the fund removal details.
|
|
46688
|
-
* @throws {KitError} If the
|
|
48854
|
+
* @throws {KitError} If the withdrawal delay has not elapsed or the
|
|
46689
48855
|
* on-chain transaction fails.
|
|
46690
48856
|
*
|
|
46691
48857
|
* @example
|
|
@@ -46802,6 +48968,33 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
46802
48968
|
}
|
|
46803
48969
|
}
|
|
46804
48970
|
|
|
48971
|
+
const APP_KIT_CUSTOM_FEE_POLICY_KEYS = new Set([
|
|
48972
|
+
'bridge',
|
|
48973
|
+
'swap',
|
|
48974
|
+
'unifiedBalance'
|
|
48975
|
+
]);
|
|
48976
|
+
function assertAppKitCustomFeePolicy(policy) {
|
|
48977
|
+
if (policy === null || typeof policy !== 'object' || Array.isArray(policy)) {
|
|
48978
|
+
throw createValidationFailedError$1('policy', policy, 'AppKit custom fee policy must be an object');
|
|
48979
|
+
}
|
|
48980
|
+
for (const key of Object.keys(policy)){
|
|
48981
|
+
if (!APP_KIT_CUSTOM_FEE_POLICY_KEYS.has(key)) {
|
|
48982
|
+
throw createValidationFailedError$1(`policy.${key}`, key, 'AppKit custom fee policy only supports bridge, swap, and unifiedBalance');
|
|
48983
|
+
}
|
|
48984
|
+
}
|
|
48985
|
+
const candidate = policy;
|
|
48986
|
+
if (candidate.bridge !== undefined) {
|
|
48987
|
+
assertCustomFeePolicy$2(candidate.bridge);
|
|
48988
|
+
}
|
|
48989
|
+
if (candidate.swap !== undefined) {
|
|
48990
|
+
assertCustomFeePolicy$1(candidate.swap);
|
|
48991
|
+
}
|
|
48992
|
+
}
|
|
48993
|
+
function assertAppKitCustomFeePolicyScope(operation) {
|
|
48994
|
+
if (typeof operation !== 'string' || !APP_KIT_CUSTOM_FEE_POLICY_KEYS.has(operation)) {
|
|
48995
|
+
throw createValidationFailedError$1('operation', operation, 'AppKit custom fee policy operation must be bridge, swap, or unifiedBalance');
|
|
48996
|
+
}
|
|
48997
|
+
}
|
|
46805
48998
|
/**
|
|
46806
48999
|
* A high-level SDK for stablecoin operations, including bridging, swapping, and earn.
|
|
46807
49000
|
*
|
|
@@ -46924,18 +49117,28 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
46924
49117
|
* })
|
|
46925
49118
|
* ```
|
|
46926
49119
|
*/ constructor(config = {}){
|
|
46927
|
-
|
|
46928
|
-
|
|
49120
|
+
if (config.customFeePolicy !== undefined) {
|
|
49121
|
+
assertAppKitCustomFeePolicy(config.customFeePolicy);
|
|
49122
|
+
}
|
|
49123
|
+
const unifiedBalance = new AppKitUnifiedBalance({
|
|
49124
|
+
...config.unifiedBalance,
|
|
46929
49125
|
...config.disableErrorReporting != null && {
|
|
46930
49126
|
disableErrorReporting: config.disableErrorReporting
|
|
49127
|
+
},
|
|
49128
|
+
...config.disableAnalytics != null && {
|
|
49129
|
+
disableAnalytics: config.disableAnalytics
|
|
46931
49130
|
}
|
|
46932
49131
|
});
|
|
46933
|
-
|
|
46934
|
-
|
|
49132
|
+
if (config.customFeePolicy?.unifiedBalance != null) {
|
|
49133
|
+
unifiedBalance.setCustomFeePolicy(config.customFeePolicy.unifiedBalance);
|
|
49134
|
+
}
|
|
49135
|
+
this.context = createContext({
|
|
49136
|
+
...config,
|
|
46935
49137
|
...config.disableErrorReporting != null && {
|
|
46936
49138
|
disableErrorReporting: config.disableErrorReporting
|
|
46937
49139
|
}
|
|
46938
49140
|
});
|
|
49141
|
+
this.unifiedBalance = unifiedBalance;
|
|
46939
49142
|
this.earn = {
|
|
46940
49143
|
// A single implementation cannot satisfy the per-branch deposit
|
|
46941
49144
|
// overloads, so assert the overloaded interface shape; the
|
|
@@ -47379,6 +49582,77 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
47379
49582
|
*/ getSupportedChains(operationType) {
|
|
47380
49583
|
return getSupportedChains$2(this.context, operationType, this.unifiedBalance);
|
|
47381
49584
|
}
|
|
49585
|
+
/**
|
|
49586
|
+
* Set operation-scoped custom fee policies.
|
|
49587
|
+
*
|
|
49588
|
+
* Configure custom fees for only the operations that need them. Bridge and
|
|
49589
|
+
* swap policies are forwarded to the underlying kits when those operations
|
|
49590
|
+
* run. Unified balance policies are applied immediately to the namespaced
|
|
49591
|
+
* Unified Balance Kit.
|
|
49592
|
+
*
|
|
49593
|
+
* @param policy - Partial custom fee policy grouped by operation.
|
|
49594
|
+
* @returns Nothing.
|
|
49595
|
+
* @throws \{KitError\} If `policy` or a provided operation policy is invalid.
|
|
49596
|
+
*
|
|
49597
|
+
* @example
|
|
49598
|
+
* ```typescript
|
|
49599
|
+
* kit.setCustomFeePolicy({
|
|
49600
|
+
* bridge: {
|
|
49601
|
+
* computeFee: () => '1.00',
|
|
49602
|
+
* resolveFeeRecipientAddress: () => '0x1234567890123456789012345678901234567890',
|
|
49603
|
+
* },
|
|
49604
|
+
* })
|
|
49605
|
+
* ```
|
|
49606
|
+
*/ setCustomFeePolicy(policy) {
|
|
49607
|
+
assertAppKitCustomFeePolicy(policy);
|
|
49608
|
+
if (policy.unifiedBalance != null) {
|
|
49609
|
+
this.unifiedBalance.setCustomFeePolicy(policy.unifiedBalance);
|
|
49610
|
+
}
|
|
49611
|
+
this.context.customFeePolicy = {
|
|
49612
|
+
...this.context.customFeePolicy,
|
|
49613
|
+
...policy
|
|
49614
|
+
};
|
|
49615
|
+
}
|
|
49616
|
+
/**
|
|
49617
|
+
* Remove an AppKit-level custom fee policy for one operation.
|
|
49618
|
+
*
|
|
49619
|
+
* Bridge and swap policies are removed from AppKit's persistent context so
|
|
49620
|
+
* future operations fall back to legacy fee hooks. Unified balance policies
|
|
49621
|
+
* are also removed from the namespaced Unified Balance Kit.
|
|
49622
|
+
*
|
|
49623
|
+
* @param operation - Operation whose custom fee policy should be removed.
|
|
49624
|
+
* @returns Nothing.
|
|
49625
|
+
* @throws \{KitError\} If `operation` is invalid.
|
|
49626
|
+
*
|
|
49627
|
+
* @example
|
|
49628
|
+
* ```typescript
|
|
49629
|
+
* kit.removeCustomFeePolicy('bridge')
|
|
49630
|
+
* ```
|
|
49631
|
+
*/ removeCustomFeePolicy(operation) {
|
|
49632
|
+
assertAppKitCustomFeePolicyScope(operation);
|
|
49633
|
+
if (operation === 'unifiedBalance') {
|
|
49634
|
+
this.unifiedBalance.removeCustomFeePolicy();
|
|
49635
|
+
}
|
|
49636
|
+
if (this.context.customFeePolicy == null) {
|
|
49637
|
+
return;
|
|
49638
|
+
}
|
|
49639
|
+
const currentPolicy = this.context.customFeePolicy;
|
|
49640
|
+
const nextPolicy = {};
|
|
49641
|
+
if (operation !== 'bridge' && currentPolicy.bridge !== undefined) {
|
|
49642
|
+
nextPolicy.bridge = currentPolicy.bridge;
|
|
49643
|
+
}
|
|
49644
|
+
if (operation !== 'swap' && currentPolicy.swap !== undefined) {
|
|
49645
|
+
nextPolicy.swap = currentPolicy.swap;
|
|
49646
|
+
}
|
|
49647
|
+
if (operation !== 'unifiedBalance' && currentPolicy.unifiedBalance !== undefined) {
|
|
49648
|
+
nextPolicy.unifiedBalance = currentPolicy.unifiedBalance;
|
|
49649
|
+
}
|
|
49650
|
+
if (Object.keys(nextPolicy).length === 0) {
|
|
49651
|
+
delete this.context.customFeePolicy;
|
|
49652
|
+
return;
|
|
49653
|
+
}
|
|
49654
|
+
this.context.customFeePolicy = nextPolicy;
|
|
49655
|
+
}
|
|
47382
49656
|
on(actionOrWildCard, handler) {
|
|
47383
49657
|
const action = actionOrWildCard;
|
|
47384
49658
|
const typedHandler = handler;
|
|
@@ -47433,5 +49707,5 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
47433
49707
|
}
|
|
47434
49708
|
}
|
|
47435
49709
|
|
|
47436
|
-
export { AppKit, BalanceError, Blockchain, BridgeChain, EarnChain, EarnError, EarnKit, InputError, KitError, NetworkError, OnchainError, RateLimitError, RpcError, ServiceError, SwapChain, TOKEN_ALIASES, TransferSpeed, UnifiedBalanceChain, anyDepositParamsSchema, claimRewardsParamsSchema, createEarnKitContext, depositParamsSchema$1 as depositParamsSchema, claimRewards$1 as earnClaimRewards, deposit$3 as earnDeposit, exploreVaults$1 as earnExploreVaults, exploreVaultsIterator$1 as earnExploreVaultsIterator, getClaimRewardsQuote$1 as earnGetClaimRewardsQuote, getCrossChainDepositStatus$1 as earnGetCrossChainDepositStatus, getCrossChainDepositStatusOutcome as earnGetCrossChainDepositStatusOutcome, getDepositQuote$1 as earnGetDepositQuote, getPosition$1 as earnGetPosition, getSupportedChains$3 as earnGetSupportedChains, getVaults$1 as earnGetVaults, getWithdrawalQuote$1 as earnGetWithdrawalQuote, waitForCrossChainDeposit$1 as earnWaitForCrossChainDeposit, withdraw$1 as earnWithdraw, exploreVaultsIteratorParamsSchema, exploreVaultsParamsSchema, getClaimRewardsQuoteParamsSchema, getCrossChainDepositStatusParamsSchema, getDepositQuoteParamsSchema, getErrorCode, getErrorMessage, getPositionParamsSchema, getTokenDecimals, getVaultsParamsSchema, getWithdrawalQuoteParamsSchema, isBalanceError, isFatalError, isInputError, isKitError, isNetworkError, isOnchainError, isRateLimitError, isRetryableError$1 as isRetryableError, isRpcError, isServiceError, isTerminalCrossChainDepositStatus as isTerminalEarnCrossChainDepositStatus, isTokenAddress, isTokenAlias, isUserCancellationError, setExternalPrefix, validateToken, waitForCrossChainDepositParamsSchema, withdrawParamsSchema };
|
|
49710
|
+
export { AppKit, BalanceError, Blockchain, BridgeChain, EARN_EXECUTE_REVIEW_KIND, EarnChain, EarnError, EarnKit, InputError, KitError, NetworkError, OnchainError, RateLimitError, RpcError, ServiceError, SwapChain, TOKEN_ALIASES, TransferSpeed, UnifiedBalanceChain, anyDepositParamsSchema, claimRewardsParamsSchema, createEarnKitContext, depositParamsSchema$1 as depositParamsSchema, claimRewards$1 as earnClaimRewards, deposit$3 as earnDeposit, exploreVaults$1 as earnExploreVaults, exploreVaultsIterator$1 as earnExploreVaultsIterator, getClaimRewardsQuote$1 as earnGetClaimRewardsQuote, getCrossChainDepositStatus$1 as earnGetCrossChainDepositStatus, getCrossChainDepositStatusOutcome as earnGetCrossChainDepositStatusOutcome, getDepositQuote$1 as earnGetDepositQuote, getPosition$1 as earnGetPosition, getSupportedChains$3 as earnGetSupportedChains, getVaults$1 as earnGetVaults, getWithdrawalQuote$1 as earnGetWithdrawalQuote, waitForCrossChainDeposit$1 as earnWaitForCrossChainDeposit, withdraw$1 as earnWithdraw, exploreVaultsIteratorParamsSchema, exploreVaultsParamsSchema, getClaimRewardsQuoteParamsSchema, getCrossChainDepositStatusParamsSchema, getDepositQuoteParamsSchema, getErrorCode, getErrorMessage, getPositionParamsSchema, getTokenDecimals, getVaultsParamsSchema, getWithdrawalQuoteParamsSchema, isBalanceError, isEarnExecuteReview, isFatalError, isInputError, isKitError, isNetworkError, isOnchainError, isRateLimitError, isRetryableError$1 as isRetryableError, isRpcError, isServiceError, isTerminalCrossChainDepositStatus as isTerminalEarnCrossChainDepositStatus, isTokenAddress, isTokenAlias, isUserCancellationError, setExternalPrefix, validateToken, waitForCrossChainDepositParamsSchema, withdrawParamsSchema };
|
|
47437
49711
|
//# sourceMappingURL=index.mjs.map
|