@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.cjs
CHANGED
|
@@ -18,6 +18,17 @@
|
|
|
18
18
|
|
|
19
19
|
'use strict';
|
|
20
20
|
|
|
21
|
+
// Buffer polyfill setup - executes before any other code
|
|
22
|
+
// Ensures globalThis.Buffer is available for Solana libraries
|
|
23
|
+
const { Buffer } = require('buffer');
|
|
24
|
+
if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
|
|
25
|
+
globalThis.Buffer = Buffer;
|
|
26
|
+
}
|
|
27
|
+
if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
|
|
28
|
+
window.Buffer = Buffer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
21
32
|
var zod = require('zod');
|
|
22
33
|
var pino = require('pino');
|
|
23
34
|
var units = require('@ethersproject/units');
|
|
@@ -29,6 +40,7 @@ var web3_js = require('@solana/web3.js');
|
|
|
29
40
|
require('bn.js');
|
|
30
41
|
require('@coral-xyz/anchor');
|
|
31
42
|
require('@noble/curves/ed25519');
|
|
43
|
+
var viem = require('viem');
|
|
32
44
|
var keccak256 = require('@ethersproject/keccak256');
|
|
33
45
|
|
|
34
46
|
function _interopDefault (e) { return e && e.__esModule ? e.default : e; }
|
|
@@ -36,59 +48,66 @@ function _interopDefault (e) { return e && e.__esModule ? e.default : e; }
|
|
|
36
48
|
var pino__default = /*#__PURE__*/_interopDefault(pino);
|
|
37
49
|
var bs58__default = /*#__PURE__*/_interopDefault(bs58);
|
|
38
50
|
|
|
51
|
+
// Import global type declarations
|
|
39
52
|
/**
|
|
40
|
-
*
|
|
53
|
+
* Check whether the current runtime is Node.js.
|
|
41
54
|
*
|
|
42
|
-
*
|
|
43
|
-
* used for event handlers, and merges in any custom implementations provided
|
|
44
|
-
* via params.
|
|
55
|
+
* @returns `true` when running in Node.js, `false` otherwise.
|
|
45
56
|
*
|
|
46
|
-
* @
|
|
47
|
-
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* import { isNodeEnvironment } from '@core/utils'
|
|
60
|
+
*
|
|
61
|
+
* if (isNodeEnvironment()) {
|
|
62
|
+
* console.log('Running in Node.js')
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
|
|
66
|
+
/**
|
|
67
|
+
* Check whether the current runtime exposes a browser DOM.
|
|
68
|
+
*
|
|
69
|
+
* @remarks
|
|
70
|
+
* This intentionally does not treat every non-Node runtime as a browser.
|
|
71
|
+
* Server-side edge runtimes such as Cloudflare Workers, Deno, and Supabase
|
|
72
|
+
* Edge Functions do not expose Node globals but can safely use server
|
|
73
|
+
* credentials. A Node.js runtime remains server-side even when a test or SSR
|
|
74
|
+
* environment provides a DOM shim.
|
|
75
|
+
*
|
|
76
|
+
* @returns `true` when running in a browser window, `false` otherwise.
|
|
48
77
|
*
|
|
49
78
|
* @example
|
|
50
79
|
* ```typescript
|
|
51
|
-
*
|
|
52
|
-
* const defaultContext = createContext()
|
|
80
|
+
* import { isBrowserEnvironment } from '@core/utils'
|
|
53
81
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* if (type === 'bridge') {
|
|
58
|
-
* // Custom bridge fee logic
|
|
59
|
-
* return await calculateBridgeFee(params)
|
|
60
|
-
* }
|
|
61
|
-
* // Use default for other types
|
|
62
|
-
* return defaultFeeCalculation(type, params)
|
|
63
|
-
* }
|
|
64
|
-
* })
|
|
82
|
+
* if (isBrowserEnvironment()) {
|
|
83
|
+
* throw new Error('Server-only secrets must not be used in the browser')
|
|
84
|
+
* }
|
|
65
85
|
* ```
|
|
66
|
-
*/ const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
actions: {
|
|
70
|
-
bridge: {},
|
|
71
|
-
earn: {},
|
|
72
|
-
...params.actions
|
|
73
|
-
}
|
|
74
|
-
};
|
|
86
|
+
*/ const isBrowserEnvironment = ()=>{
|
|
87
|
+
const browserWindow = globalThis.window;
|
|
88
|
+
return !isNodeEnvironment() && browserWindow?.document !== undefined;
|
|
75
89
|
};
|
|
76
|
-
|
|
77
|
-
// Import global type declarations
|
|
78
90
|
/**
|
|
79
|
-
*
|
|
91
|
+
* Return the SDK User-Agent request header only when running in Node.js.
|
|
80
92
|
*
|
|
81
|
-
*
|
|
93
|
+
* Browsers forbid manually setting `User-Agent`, and a custom fallback header
|
|
94
|
+
* can trigger CORS preflight. Non-Node server runtimes also omit this optional
|
|
95
|
+
* attribution header because they cannot set it reliably.
|
|
96
|
+
*
|
|
97
|
+
* @returns A User-Agent header in Node.js, or an empty object otherwise.
|
|
82
98
|
*
|
|
83
99
|
* @example
|
|
84
100
|
* ```typescript
|
|
85
|
-
* import {
|
|
101
|
+
* import { getNodeUserAgentHeader } from '@core/utils'
|
|
86
102
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
103
|
+
* const headers = {
|
|
104
|
+
* 'Content-Type': 'application/json',
|
|
105
|
+
* ...getNodeUserAgentHeader(),
|
|
89
106
|
* }
|
|
90
107
|
* ```
|
|
91
|
-
*/ const
|
|
108
|
+
*/ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
|
|
109
|
+
'User-Agent': getUserAgent()
|
|
110
|
+
} : {};
|
|
92
111
|
/**
|
|
93
112
|
* Detect the runtime environment and return a shortened identifier.
|
|
94
113
|
*
|
|
@@ -4074,6 +4093,8 @@ function getOptionalString(value) {
|
|
|
4074
4093
|
Blockchain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
4075
4094
|
Blockchain["XDC"] = "XDC";
|
|
4076
4095
|
Blockchain["XDC_Apothem"] = "XDC_Apothem";
|
|
4096
|
+
Blockchain["X_Layer"] = "X_Layer";
|
|
4097
|
+
Blockchain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
4077
4098
|
Blockchain["ZKSync_Era"] = "ZKSync_Era";
|
|
4078
4099
|
Blockchain["ZKSync_Sepolia"] = "ZKSync_Sepolia";
|
|
4079
4100
|
})(exports.Blockchain || (exports.Blockchain = {}));
|
|
@@ -4127,6 +4148,7 @@ exports.BridgeChain = void 0;
|
|
|
4127
4148
|
BridgeChain["Unichain"] = "Unichain";
|
|
4128
4149
|
BridgeChain["World_Chain"] = "World_Chain";
|
|
4129
4150
|
BridgeChain["XDC"] = "XDC";
|
|
4151
|
+
BridgeChain["X_Layer"] = "X_Layer";
|
|
4130
4152
|
// Testnet chains with CCTPv2 support
|
|
4131
4153
|
BridgeChain["Arc_Testnet"] = "Arc_Testnet";
|
|
4132
4154
|
BridgeChain["Arbitrum_Sepolia"] = "Arbitrum_Sepolia";
|
|
@@ -4152,6 +4174,7 @@ exports.BridgeChain = void 0;
|
|
|
4152
4174
|
BridgeChain["Unichain_Sepolia"] = "Unichain_Sepolia";
|
|
4153
4175
|
BridgeChain["World_Chain_Sepolia"] = "World_Chain_Sepolia";
|
|
4154
4176
|
BridgeChain["XDC_Apothem"] = "XDC_Apothem";
|
|
4177
|
+
BridgeChain["X_Layer_Testnet"] = "X_Layer_Testnet";
|
|
4155
4178
|
})(exports.BridgeChain || (exports.BridgeChain = {}));
|
|
4156
4179
|
exports.UnifiedBalanceChain = void 0;
|
|
4157
4180
|
(function(UnifiedBalanceChain) {
|
|
@@ -6710,7 +6733,8 @@ exports.EarnChain = void 0;
|
|
|
6710
6733
|
isTestnet: true,
|
|
6711
6734
|
explorerUrl: 'https://amoy.polygonscan.com/tx/{hash}',
|
|
6712
6735
|
rpcEndpoints: [
|
|
6713
|
-
'https://
|
|
6736
|
+
'https://polygon-amoy-bor-rpc.publicnode.com',
|
|
6737
|
+
'https://polygon-amoy.drpc.org'
|
|
6714
6738
|
],
|
|
6715
6739
|
eurcAddress: null,
|
|
6716
6740
|
usdcAddress: '0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582',
|
|
@@ -7575,6 +7599,104 @@ exports.EarnChain = void 0;
|
|
|
7575
7599
|
}
|
|
7576
7600
|
});
|
|
7577
7601
|
|
|
7602
|
+
/**
|
|
7603
|
+
* X Layer Mainnet chain definition
|
|
7604
|
+
* @remarks
|
|
7605
|
+
* This represents the official production network for the X Layer blockchain.
|
|
7606
|
+
* X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
|
|
7607
|
+
* using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
|
|
7608
|
+
* OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
|
|
7609
|
+
*/ const XLayer = defineChain({
|
|
7610
|
+
type: 'evm',
|
|
7611
|
+
chain: exports.Blockchain.X_Layer,
|
|
7612
|
+
name: 'X Layer',
|
|
7613
|
+
title: 'X Layer Mainnet',
|
|
7614
|
+
nativeCurrency: {
|
|
7615
|
+
name: 'OKB',
|
|
7616
|
+
symbol: 'OKB',
|
|
7617
|
+
decimals: 18
|
|
7618
|
+
},
|
|
7619
|
+
chainId: 196,
|
|
7620
|
+
isTestnet: false,
|
|
7621
|
+
explorerUrl: 'https://www.oklink.com/xlayer/tx/{hash}',
|
|
7622
|
+
rpcEndpoints: [
|
|
7623
|
+
'https://xlayerrpc.okx.com'
|
|
7624
|
+
],
|
|
7625
|
+
eurcAddress: null,
|
|
7626
|
+
usdcAddress: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
7627
|
+
usdtAddress: null,
|
|
7628
|
+
cctp: {
|
|
7629
|
+
domain: 37,
|
|
7630
|
+
contracts: {
|
|
7631
|
+
v2: {
|
|
7632
|
+
type: 'split',
|
|
7633
|
+
tokenMessenger: '0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d',
|
|
7634
|
+
messageTransmitter: '0x81D40F21F12A8F0E3252Bccb954D722d4c464B64',
|
|
7635
|
+
confirmations: 65,
|
|
7636
|
+
fastConfirmations: 1
|
|
7637
|
+
}
|
|
7638
|
+
},
|
|
7639
|
+
forwarderSupported: {
|
|
7640
|
+
source: false,
|
|
7641
|
+
destination: false
|
|
7642
|
+
}
|
|
7643
|
+
},
|
|
7644
|
+
kitContracts: {
|
|
7645
|
+
bridge: BRIDGE_CONTRACT_EVM_MAINNET
|
|
7646
|
+
}
|
|
7647
|
+
});
|
|
7648
|
+
|
|
7649
|
+
/**
|
|
7650
|
+
* X Layer Testnet chain definition
|
|
7651
|
+
* @remarks
|
|
7652
|
+
* This represents the official test network for the X Layer blockchain.
|
|
7653
|
+
* X Layer is an EVM-compatible OP Stack Layer-2 blockchain built by OKX,
|
|
7654
|
+
* using OKB as its native gas token. (Migrated from Polygon zkEVM/CDK to the
|
|
7655
|
+
* OP Stack on 2025-10-27; older docs describing it as zkEVM are obsolete.)
|
|
7656
|
+
*/ const XLayerTestnet = defineChain({
|
|
7657
|
+
type: 'evm',
|
|
7658
|
+
chain: exports.Blockchain.X_Layer_Testnet,
|
|
7659
|
+
name: 'X Layer Testnet',
|
|
7660
|
+
title: 'X Layer Testnet',
|
|
7661
|
+
nativeCurrency: {
|
|
7662
|
+
name: 'OKB',
|
|
7663
|
+
symbol: 'OKB',
|
|
7664
|
+
decimals: 18
|
|
7665
|
+
},
|
|
7666
|
+
chainId: 1952,
|
|
7667
|
+
isTestnet: true,
|
|
7668
|
+
// Deliberately not oklink.com (used for mainnet): viem's bundled OKLink
|
|
7669
|
+
// testnet URL targets the deprecated pre-rebrand chain ID 195, not this
|
|
7670
|
+
// chain's ID (1952). Verified against the internal chain-expansion-scripts
|
|
7671
|
+
// config (`v2config.sandbox.yml`) — do not "normalize" this to match mainnet.
|
|
7672
|
+
explorerUrl: 'https://web3.okx.com/explorer/x-layer-testnet/tx/{hash}',
|
|
7673
|
+
rpcEndpoints: [
|
|
7674
|
+
'https://testrpc.xlayer.tech'
|
|
7675
|
+
],
|
|
7676
|
+
eurcAddress: null,
|
|
7677
|
+
usdcAddress: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
7678
|
+
usdtAddress: null,
|
|
7679
|
+
cctp: {
|
|
7680
|
+
domain: 37,
|
|
7681
|
+
contracts: {
|
|
7682
|
+
v2: {
|
|
7683
|
+
type: 'split',
|
|
7684
|
+
tokenMessenger: '0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA',
|
|
7685
|
+
messageTransmitter: '0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275',
|
|
7686
|
+
confirmations: 65,
|
|
7687
|
+
fastConfirmations: 1
|
|
7688
|
+
}
|
|
7689
|
+
},
|
|
7690
|
+
forwarderSupported: {
|
|
7691
|
+
source: false,
|
|
7692
|
+
destination: false
|
|
7693
|
+
}
|
|
7694
|
+
},
|
|
7695
|
+
kitContracts: {
|
|
7696
|
+
bridge: BRIDGE_CONTRACT_EVM_TESTNET
|
|
7697
|
+
}
|
|
7698
|
+
});
|
|
7699
|
+
|
|
7578
7700
|
/**
|
|
7579
7701
|
* ZKSync Era Mainnet chain definition
|
|
7580
7702
|
* @remarks
|
|
@@ -7694,6 +7816,8 @@ var Chains = {
|
|
|
7694
7816
|
WorldChainSepolia: WorldChainSepolia,
|
|
7695
7817
|
XDC: XDC,
|
|
7696
7818
|
XDCApothem: XDCApothem,
|
|
7819
|
+
XLayer: XLayer,
|
|
7820
|
+
XLayerTestnet: XLayerTestnet,
|
|
7697
7821
|
ZKSyncEra: ZKSyncEra,
|
|
7698
7822
|
ZKSyncEraSepolia: ZKSyncEraSepolia
|
|
7699
7823
|
};
|
|
@@ -9289,13 +9413,12 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9289
9413
|
headers: {
|
|
9290
9414
|
...DEFAULT_CONFIG$3.headers,
|
|
9291
9415
|
...config.headers ?? {},
|
|
9292
|
-
//
|
|
9293
|
-
//
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
}
|
|
9416
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
9417
|
+
// fallback header the SDK used instead trips CORS preflight against the
|
|
9418
|
+
// Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
|
|
9419
|
+
// blocking the request. So send the SDK user agent only in Node;
|
|
9420
|
+
// browsers omit it entirely.
|
|
9421
|
+
...getNodeUserAgentHeader()
|
|
9299
9422
|
}
|
|
9300
9423
|
};
|
|
9301
9424
|
let lastError;
|
|
@@ -10058,6 +10181,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
10058
10181
|
[exports.Blockchain.Unichain]: '0x078D782b760474a361dDA0AF3839290b0EF57AD6',
|
|
10059
10182
|
[exports.Blockchain.World_Chain]: '0x79A02482A880bCE3F13e09Da970dC34db4CD24d1',
|
|
10060
10183
|
[exports.Blockchain.XDC]: '0xfA2958CB79b0491CC627c1557F441eF849Ca8eb1',
|
|
10184
|
+
[exports.Blockchain.X_Layer]: '0xB6CEceAB302E2E4948951eE7843FC24E92933061',
|
|
10061
10185
|
[exports.Blockchain.ZKSync_Era]: '0x1d17CBcF0D6D143135aE902365D2E5e2A16538D4',
|
|
10062
10186
|
// =========================================================================
|
|
10063
10187
|
// Testnets (alphabetically sorted)
|
|
@@ -10092,6 +10216,7 @@ function parseOrThrow(value, schema, context) {
|
|
|
10092
10216
|
[exports.Blockchain.Unichain_Sepolia]: '0x31d0220469e10c4E71834a79b1f276d740d3768F',
|
|
10093
10217
|
[exports.Blockchain.World_Chain_Sepolia]: '0x66145f38cBAC35Ca6F1Dfb4914dF98F1614aeA88',
|
|
10094
10218
|
[exports.Blockchain.XDC_Apothem]: '0xb5AB69F7bBada22B28e79C8FFAECe55eF1c771D4',
|
|
10219
|
+
[exports.Blockchain.X_Layer_Testnet]: '0xDec90b78111Ba2fc6FC6d84d8B9ec159A2d4b9B3',
|
|
10095
10220
|
[exports.Blockchain.ZKSync_Sepolia]: '0xAe045DE5638162fa134807Cb558E15A3F5A7F853'
|
|
10096
10221
|
}
|
|
10097
10222
|
};
|
|
@@ -11041,6 +11166,52 @@ function parseOrThrow(value, schema, context) {
|
|
|
11041
11166
|
return explorerUrl;
|
|
11042
11167
|
}
|
|
11043
11168
|
|
|
11169
|
+
/**
|
|
11170
|
+
* Assert that a value has type `never` (exhaustive switch helper).
|
|
11171
|
+
*
|
|
11172
|
+
* @remarks
|
|
11173
|
+
* Use in the `default` branch of a switch over a discriminated union.
|
|
11174
|
+
* If all union members are handled, the default is unreachable and TypeScript
|
|
11175
|
+
* narrows the parameter to `never`. If a member is missed, the compiler errors.
|
|
11176
|
+
*
|
|
11177
|
+
* @param _x - The value (typed as `never` when switch is exhaustive).
|
|
11178
|
+
* @returns Never returns; always throws.
|
|
11179
|
+
* @throws Error when the switch is not exhaustive.
|
|
11180
|
+
*
|
|
11181
|
+
* @example
|
|
11182
|
+
* ```typescript
|
|
11183
|
+
* type Foo = { type: 'a'; x: number } | { type: 'b'; y: string }
|
|
11184
|
+
*
|
|
11185
|
+
* function handle(foo: Foo): string {
|
|
11186
|
+
* switch (foo.type) {
|
|
11187
|
+
* case 'a': return String(foo.x)
|
|
11188
|
+
* case 'b': return foo.y
|
|
11189
|
+
* default: return assertNever(foo)
|
|
11190
|
+
* }
|
|
11191
|
+
* }
|
|
11192
|
+
* ```
|
|
11193
|
+
*/ function assertNever$2(x) {
|
|
11194
|
+
// Plain `String(x)` collapses non-primitive union members (objects, arrays)
|
|
11195
|
+
// to `'[object Object]'`, which is useless when triaging which discriminant
|
|
11196
|
+
// was missed. Attempt `JSON.stringify` first so the thrown message preserves
|
|
11197
|
+
// the offending shape. Fall back to a minimal `typeof`-based label if
|
|
11198
|
+
// serialization fails (`BigInt` member, circular references, host objects).
|
|
11199
|
+
//
|
|
11200
|
+
// `x` is statically typed as `never` (the whole point of this helper), but
|
|
11201
|
+
// at runtime callers may still pass an unexpected value when the switch is
|
|
11202
|
+
// not actually exhaustive — that's exactly the bug we want to surface. Cast
|
|
11203
|
+
// through `unknown` so the runtime defence is not stripped by the compiler.
|
|
11204
|
+
const value = x;
|
|
11205
|
+
let stringified;
|
|
11206
|
+
try {
|
|
11207
|
+
const json = JSON.stringify(value);
|
|
11208
|
+
stringified = typeof json === 'string' ? json : `<${typeof value}>`;
|
|
11209
|
+
} catch {
|
|
11210
|
+
stringified = `<unstringifiable ${typeof value}>`;
|
|
11211
|
+
}
|
|
11212
|
+
throw new Error(`Unhandled switch case: ${stringified}`);
|
|
11213
|
+
}
|
|
11214
|
+
|
|
11044
11215
|
/**
|
|
11045
11216
|
* CCTP forwarding magic bytes prefix.
|
|
11046
11217
|
*
|
|
@@ -11226,6 +11397,7 @@ function resolveOptions(options) {
|
|
|
11226
11397
|
if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
|
|
11227
11398
|
if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
|
|
11228
11399
|
if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
|
|
11400
|
+
if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
|
|
11229
11401
|
if (payload.errorDetails !== undefined) {
|
|
11230
11402
|
const errorDetails = {
|
|
11231
11403
|
...payload.errorDetails.errorCode !== undefined && {
|
|
@@ -11296,18 +11468,15 @@ function resolveOptions(options) {
|
|
|
11296
11468
|
timeoutHandle.unref();
|
|
11297
11469
|
}
|
|
11298
11470
|
try {
|
|
11299
|
-
const isNode = isNodeEnvironment();
|
|
11300
|
-
const userAgent = getUserAgent();
|
|
11301
11471
|
await fetch(getLogsUrl(), {
|
|
11302
11472
|
method: 'POST',
|
|
11303
11473
|
headers: {
|
|
11304
11474
|
'Content-Type': 'application/json',
|
|
11305
|
-
//
|
|
11306
|
-
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
}
|
|
11475
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
11476
|
+
// fallback header the SDK used instead trips CORS preflight (it isn't
|
|
11477
|
+
// in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
|
|
11478
|
+
// it only in Node; browsers omit it entirely.
|
|
11479
|
+
...getNodeUserAgentHeader()
|
|
11311
11480
|
},
|
|
11312
11481
|
body: JSON.stringify(toSafePayload(payload)),
|
|
11313
11482
|
signal: controller.signal
|
|
@@ -11520,7 +11689,7 @@ function resolveOptions(options) {
|
|
|
11520
11689
|
// discards the stack trace, nested `cause`, and any custom Error
|
|
11521
11690
|
// properties — exactly the context an on-call needs when a
|
|
11522
11691
|
// resolver-closure regression triggers this path.
|
|
11523
|
-
console.warn(`[stablecoin-kits telemetry] dropped
|
|
11692
|
+
console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
|
|
11524
11693
|
} catch {
|
|
11525
11694
|
// console.warn itself throwing is the user's environment; nothing more we
|
|
11526
11695
|
// can do without risking the original operation error.
|
|
@@ -11536,7 +11705,9 @@ function resolveOptions(options) {
|
|
|
11536
11705
|
sdkVersion: config.sdkVersion,
|
|
11537
11706
|
eventType,
|
|
11538
11707
|
timestamp: new Date().toISOString(),
|
|
11539
|
-
errorDetails
|
|
11708
|
+
...errorDetails !== undefined && {
|
|
11709
|
+
errorDetails
|
|
11710
|
+
},
|
|
11540
11711
|
clientContext: buildClientContext(),
|
|
11541
11712
|
...context?.sourceChain != null && {
|
|
11542
11713
|
sourceChain: context.sourceChain
|
|
@@ -11552,9 +11723,45 @@ function resolveOptions(options) {
|
|
|
11552
11723
|
},
|
|
11553
11724
|
...context?.txHash != null && {
|
|
11554
11725
|
txHash: context.txHash
|
|
11726
|
+
},
|
|
11727
|
+
...context?.correlationId != null && {
|
|
11728
|
+
correlationId: context.correlationId
|
|
11555
11729
|
}
|
|
11556
11730
|
};
|
|
11557
11731
|
}
|
|
11732
|
+
/**
|
|
11733
|
+
* Emit telemetry for a completed operation without affecting its caller.
|
|
11734
|
+
*
|
|
11735
|
+
* No-ops when `config.disabled` is `true`. Like {@link withErrorTelemetry},
|
|
11736
|
+
* failures while constructing or submitting the telemetry payload are reported
|
|
11737
|
+
* as a soft warning and never change a completed operation's result.
|
|
11738
|
+
*
|
|
11739
|
+
* @param eventType - The telemetry event type for the completed operation.
|
|
11740
|
+
* @param config - Per-kit SDK identity and disabled flag.
|
|
11741
|
+
* @param context - Optional chain, token, and transaction context.
|
|
11742
|
+
* @returns Nothing.
|
|
11743
|
+
* @throws Never — telemetry failures are reported as warnings.
|
|
11744
|
+
*
|
|
11745
|
+
* @example
|
|
11746
|
+
* ```typescript
|
|
11747
|
+
* import { emitSuccessTelemetry } from '@core/utils'
|
|
11748
|
+
*
|
|
11749
|
+
* emitSuccessTelemetry(
|
|
11750
|
+
* 'bridge_bridge',
|
|
11751
|
+
* { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
|
|
11752
|
+
* { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
|
|
11753
|
+
* )
|
|
11754
|
+
* ```
|
|
11755
|
+
*/ function emitSuccessTelemetry(eventType, config, context) {
|
|
11756
|
+
if (config.disabled) {
|
|
11757
|
+
return;
|
|
11758
|
+
}
|
|
11759
|
+
try {
|
|
11760
|
+
void emitAnalyticsLog(buildPayload$1(config, eventType, undefined, context));
|
|
11761
|
+
} catch (telemetryError) {
|
|
11762
|
+
warnTelemetryDrop(eventType, telemetryError);
|
|
11763
|
+
}
|
|
11764
|
+
}
|
|
11558
11765
|
/**
|
|
11559
11766
|
* Wrap an async operation with error telemetry.
|
|
11560
11767
|
*
|
|
@@ -11665,7 +11872,7 @@ function resolveOptions(options) {
|
|
|
11665
11872
|
}
|
|
11666
11873
|
|
|
11667
11874
|
var name$4 = "@circle-fin/bridge-kit";
|
|
11668
|
-
var version$5 = "1.
|
|
11875
|
+
var version$5 = "1.13.0";
|
|
11669
11876
|
var pkg$5 = {
|
|
11670
11877
|
name: name$4,
|
|
11671
11878
|
version: version$5};
|
|
@@ -11702,13 +11909,21 @@ const assertCustomFeePolicySymbol$2 = Symbol('assertCustomFeePolicy');
|
|
|
11702
11909
|
computeFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional(),
|
|
11703
11910
|
calculateFee: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string()))).optional(),
|
|
11704
11911
|
resolveFeeRecipientAddress: zod.z.function().returns(zod.z.string().or(zod.z.promise(zod.z.string())))
|
|
11705
|
-
}).strict().
|
|
11912
|
+
}).strict().superRefine((data, ctx)=>{
|
|
11706
11913
|
const hasComputeFee = data.computeFee !== undefined;
|
|
11707
11914
|
const hasCalculateFee = data.calculateFee !== undefined;
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11915
|
+
if (hasComputeFee && hasCalculateFee) {
|
|
11916
|
+
ctx.addIssue({
|
|
11917
|
+
code: zod.z.ZodIssueCode.custom,
|
|
11918
|
+
message: 'Provide either computeFee or calculateFee, not both. Use computeFee (recommended) for human-readable amounts.'
|
|
11919
|
+
});
|
|
11920
|
+
}
|
|
11921
|
+
if (!hasComputeFee && !hasCalculateFee) {
|
|
11922
|
+
ctx.addIssue({
|
|
11923
|
+
code: zod.z.ZodIssueCode.custom,
|
|
11924
|
+
message: 'Provide either computeFee or calculateFee. Use computeFee (recommended) for human-readable amounts.'
|
|
11925
|
+
});
|
|
11926
|
+
}
|
|
11712
11927
|
});
|
|
11713
11928
|
/**
|
|
11714
11929
|
* Assert that the provided value conforms to {@link CustomFeePolicy}.
|
|
@@ -14301,7 +14516,13 @@ const FAST_TIER_FINALITY_THRESHOLD = 1000;
|
|
|
14301
14516
|
/**
|
|
14302
14517
|
* The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
|
|
14303
14518
|
* hookData must start with.
|
|
14304
|
-
|
|
14519
|
+
*
|
|
14520
|
+
* Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
|
|
14521
|
+
* so this module-level constant does not reference the Node `Buffer` global at
|
|
14522
|
+
* import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
|
|
14523
|
+
* bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
|
|
14524
|
+
* that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
|
|
14525
|
+
*/ const CCTP_FORWARD_MAGIC_HEX = Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
14305
14526
|
/**
|
|
14306
14527
|
* Determine whether a hookData blob begins with the `cctp-forward` envelope.
|
|
14307
14528
|
*
|
|
@@ -14502,14 +14723,32 @@ const CUSTOM_BURN_GAS_ESTIMATE_EVM = 201_525n // p99 and max are same here: 201_
|
|
|
14502
14723
|
;
|
|
14503
14724
|
const RECEIVE_MESSAGE_GAS_ESTIMATE_EVM = 237_401n // (99p: 163_963n + max: 310_839n) / 2 = 237_401n
|
|
14504
14725
|
;
|
|
14505
|
-
//
|
|
14506
|
-
//
|
|
14507
|
-
//
|
|
14508
|
-
|
|
14726
|
+
// Gas FLOORS, not ceilings — kept separate from the fee-estimate averages
|
|
14727
|
+
// above. `executePreparedChainRequest` submits
|
|
14728
|
+
// max(estimate * buffer, floor), so a chain whose real cost exceeds the floor
|
|
14729
|
+
// is covered by its own estimate, and a chain whose estimator under-reports
|
|
14730
|
+
// (Cronos: returns 30_600 where the EIP-7623 calldata floor is 45_000) is
|
|
14731
|
+
// covered by the floor.
|
|
14732
|
+
//
|
|
14733
|
+
// Two distinct chain surcharges drive these numbers, both measured live:
|
|
14734
|
+
// Sei — ~+51_500 per NEWLY CREATED storage slot (73_595 vs vanilla 22_100);
|
|
14735
|
+
// no flat per-tx surcharge (31_535, identical to Base).
|
|
14736
|
+
// Edge — ~+53_200 flat on EVERY tx (84_751 vs Base 31_535); storage priced
|
|
14737
|
+
// normally. Edge therefore fails warm as well as cold.
|
|
14738
|
+
// A floor must clear the worst COLD cost, since a slot that exists at estimate
|
|
14739
|
+
// time can be consumed before inclusion and cost a full step more on execution.
|
|
14740
|
+
// Each floor is therefore derived from the worst observed estimate *after* the
|
|
14741
|
+
// 1.25x buffer, plus headroom — sizing it below the buffered value would leave
|
|
14742
|
+
// the estimate governing and defeat the point of the floor.
|
|
14743
|
+
//
|
|
14744
|
+
// The `*_GAS_LIMIT_EVM` names are kept despite these being floors: they are
|
|
14745
|
+
// exported, so renaming to `*_GAS_FLOOR_EVM` would be a breaking change for
|
|
14746
|
+
// consumers. Read "LIMIT" here as "the limit we submit", never as a ceiling.
|
|
14747
|
+
const APPROVE_GAS_LIMIT_EVM = 150_000n // buffered worst cold 149_355 (Edge Testnet 119_484 x 1.25) + drift headroom
|
|
14509
14748
|
;
|
|
14510
|
-
const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM =
|
|
14749
|
+
const DEPOSIT_FOR_BURN_GAS_LIMIT_EVM = 500_000n // buffered worst 474_078 (Sei 379_263 x 1.25) + ~26k headroom
|
|
14511
14750
|
;
|
|
14512
|
-
const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839
|
|
14751
|
+
const RECEIVE_MESSAGE_GAS_LIMIT_EVM = 400_000n // observed max 310_839; clears Cronos' calldata floor ~10x
|
|
14513
14752
|
;
|
|
14514
14753
|
/**
|
|
14515
14754
|
* The minimum finality threshold for CCTPv2 transfers.
|
|
@@ -15974,6 +16213,63 @@ function hasPendingState(analysis, result) {
|
|
|
15974
16213
|
return waitForPendingTransaction(pendingStep, adapter, chain);
|
|
15975
16214
|
}
|
|
15976
16215
|
|
|
16216
|
+
/**
|
|
16217
|
+
* Multiplier applied to a successful gas estimate before it is submitted.
|
|
16218
|
+
*
|
|
16219
|
+
* Estimates are exact, not padded: Sei returns 109_739 for an approve that
|
|
16220
|
+
* consumes 107_717 (1.9% headroom). Chains that price storage in large steps
|
|
16221
|
+
* can exceed the estimate if state changes between estimation and inclusion,
|
|
16222
|
+
* so the estimate is padded before use.
|
|
16223
|
+
*
|
|
16224
|
+
* @remarks
|
|
16225
|
+
* This buffer alone does NOT cover Sei's ~51_500 per-new-slot step at approve
|
|
16226
|
+
* scale (25% of ~110_000 is only ~27_500). For approve, the FLOOR is what
|
|
16227
|
+
* covers a slot that exists at estimation time and is consumed before
|
|
16228
|
+
* inclusion — so do not lower `APPROVE_GAS_LIMIT_EVM` on the reasoning that
|
|
16229
|
+
* the estimate covers it. For burn the buffer does cover a step (25% of
|
|
16230
|
+
* ~300_000 exceeds 51_500).
|
|
16231
|
+
*/ const GAS_ESTIMATE_BUFFER_PERCENT = 125n;
|
|
16232
|
+
/**
|
|
16233
|
+
* Resolve the gas limit for an EVM request as `max(estimate * buffer, floor)`.
|
|
16234
|
+
*
|
|
16235
|
+
* Estimates first so chains whose real cost exceeds the floor are covered by
|
|
16236
|
+
* their own measurement, and falls back to the floor whenever estimation is
|
|
16237
|
+
* unavailable or under-reports. Estimation failure is never fatal here: before
|
|
16238
|
+
* floors existed these requests were submitted with a pinned limit and no
|
|
16239
|
+
* estimate at all, so degrading to the floor is never worse than the previous
|
|
16240
|
+
* behaviour.
|
|
16241
|
+
*
|
|
16242
|
+
* @param request - The prepared EVM request to size a gas limit for
|
|
16243
|
+
* @param gasFloor - The minimum gas limit to submit, in gas units
|
|
16244
|
+
* @returns The gas limit to submit, in gas units
|
|
16245
|
+
* @throws Never — estimation failures degrade to `gasFloor`
|
|
16246
|
+
*
|
|
16247
|
+
* @example
|
|
16248
|
+
* ```typescript
|
|
16249
|
+
* const gasLimit = await resolveGasLimit(request, 150_000)
|
|
16250
|
+
* ```
|
|
16251
|
+
*/ const resolveGasLimit = async (request, gasFloor)=>{
|
|
16252
|
+
try {
|
|
16253
|
+
// Deliberately called without a `fallback`: both the viem and ethers
|
|
16254
|
+
// adapters *return* the supplied fallback object when estimation reverts
|
|
16255
|
+
// rather than throwing, which would set the estimate to the floor and then
|
|
16256
|
+
// multiply it by the buffer below. Omitting it routes reverts through the
|
|
16257
|
+
// catch, so a failed estimate degrades to exactly the floor.
|
|
16258
|
+
const estimate = await request.estimate();
|
|
16259
|
+
// The arithmetic stays inside the try on purpose. `EstimatedGas.gas` is
|
|
16260
|
+
// typed `bigint`, but adapters are a public extension point and may be
|
|
16261
|
+
// implemented in plain JS, so a non-bigint `gas` would throw here
|
|
16262
|
+
// ("Cannot mix BigInt and other types"). Guarding it keeps the documented
|
|
16263
|
+
// contract — estimation never aborts a step, it degrades to the floor.
|
|
16264
|
+
const buffered = estimate.gas * GAS_ESTIMATE_BUFFER_PERCENT / 100n;
|
|
16265
|
+
// Convert before comparing: Math.max throws on BigInt operands, and gas
|
|
16266
|
+
// units are far below Number.MAX_SAFE_INTEGER so the narrowing is lossless.
|
|
16267
|
+
return Math.max(Number(buffered), gasFloor);
|
|
16268
|
+
} catch {
|
|
16269
|
+
// Estimation is best-effort; the floor is the known-safe value.
|
|
16270
|
+
return gasFloor;
|
|
16271
|
+
}
|
|
16272
|
+
};
|
|
15977
16273
|
/**
|
|
15978
16274
|
* Executes a prepared chain request and returns the result as a bridge step.
|
|
15979
16275
|
*
|
|
@@ -15987,8 +16283,8 @@ function hasPendingState(analysis, result) {
|
|
|
15987
16283
|
* - `adapter`: The adapter that will execute the transaction
|
|
15988
16284
|
* - `confirmations`: The number of confirmations to wait for (defaults to 1)
|
|
15989
16285
|
* - `timeout`: The timeout for the request in milliseconds
|
|
15990
|
-
* - `
|
|
15991
|
-
*
|
|
16286
|
+
* - `gasFloor`: Optional minimum gas limit (number); the request is submitted
|
|
16287
|
+
* with `max(estimate * 1.25, gasFloor)`. Ignored for non-EVM requests
|
|
15992
16288
|
* @returns The bridge step with the transaction details and explorer URL
|
|
15993
16289
|
* @throws If the transaction execution fails
|
|
15994
16290
|
*
|
|
@@ -16003,7 +16299,7 @@ function hasPendingState(analysis, result) {
|
|
|
16003
16299
|
* })
|
|
16004
16300
|
* console.log('Transaction hash:', step.txHash)
|
|
16005
16301
|
* ```
|
|
16006
|
-
*/ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout,
|
|
16302
|
+
*/ async function executePreparedChainRequest({ name, request, adapter, chain, confirmations = 1, timeout, gasFloor }) {
|
|
16007
16303
|
const step = {
|
|
16008
16304
|
name,
|
|
16009
16305
|
state: 'pending'
|
|
@@ -16016,8 +16312,8 @@ function hasPendingState(analysis, result) {
|
|
|
16016
16312
|
step.state = 'noop';
|
|
16017
16313
|
return step;
|
|
16018
16314
|
}
|
|
16019
|
-
const txHash = request.type === 'evm' &&
|
|
16020
|
-
gasLimit
|
|
16315
|
+
const txHash = request.type === 'evm' && gasFloor !== undefined ? await request.execute({
|
|
16316
|
+
gasLimit: await resolveGasLimit(request, gasFloor)
|
|
16021
16317
|
}) : await request.execute();
|
|
16022
16318
|
step.txHash = txHash;
|
|
16023
16319
|
const retryOptions = {
|
|
@@ -16091,7 +16387,7 @@ function hasPendingState(analysis, result) {
|
|
|
16091
16387
|
adapter: params.source.adapter,
|
|
16092
16388
|
chain: params.source.chain,
|
|
16093
16389
|
request: await provider.approve(params.source, approvalAmount),
|
|
16094
|
-
|
|
16390
|
+
gasFloor: Number(APPROVE_GAS_LIMIT_EVM)
|
|
16095
16391
|
});
|
|
16096
16392
|
}
|
|
16097
16393
|
|
|
@@ -16119,7 +16415,7 @@ function hasPendingState(analysis, result) {
|
|
|
16119
16415
|
adapter: params.source.adapter,
|
|
16120
16416
|
chain: params.source.chain,
|
|
16121
16417
|
request: await provider.burn(params),
|
|
16122
|
-
|
|
16418
|
+
gasFloor: Number(DEPOSIT_FOR_BURN_GAS_LIMIT_EVM)
|
|
16123
16419
|
});
|
|
16124
16420
|
}
|
|
16125
16421
|
|
|
@@ -16213,10 +16509,9 @@ function hasPendingState(analysis, result) {
|
|
|
16213
16509
|
request: mintRequest,
|
|
16214
16510
|
// Some chains (e.g. Cronos) enforce an EIP-7623 calldata gas floor that
|
|
16215
16511
|
// eth_estimateGas does not account for, returning a below-floor value
|
|
16216
|
-
// without reverting.
|
|
16217
|
-
//
|
|
16218
|
-
|
|
16219
|
-
gasLimit: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
|
|
16512
|
+
// without reverting. The floor covers those; chains that cost more than the
|
|
16513
|
+
// floor are covered by their own estimate.
|
|
16514
|
+
gasFloor: Number(RECEIVE_MESSAGE_GAS_LIMIT_EVM)
|
|
16220
16515
|
});
|
|
16221
16516
|
// Add forwarded: false for non-relayer mints
|
|
16222
16517
|
return {
|
|
@@ -16739,7 +17034,7 @@ const mockAttestationMessage = {
|
|
|
16739
17034
|
return step;
|
|
16740
17035
|
}
|
|
16741
17036
|
|
|
16742
|
-
var version$4 = "1.10.
|
|
17037
|
+
var version$4 = "1.10.2";
|
|
16743
17038
|
var pkg$4 = {
|
|
16744
17039
|
version: version$4};
|
|
16745
17040
|
|
|
@@ -18618,7 +18913,7 @@ function assertCCTPV2Config(config) {
|
|
|
18618
18913
|
]
|
|
18619
18914
|
];
|
|
18620
18915
|
|
|
18621
|
-
/** SDK name used in telemetry payloads. */ const SDK_NAME$
|
|
18916
|
+
/** SDK name used in telemetry payloads. */ const SDK_NAME$3 = resolveKitSdkName(pkg$5.name);
|
|
18622
18917
|
/**
|
|
18623
18918
|
* Pick the most-relevant `txHash` to attach to an error telemetry payload.
|
|
18624
18919
|
*
|
|
@@ -18760,7 +19055,7 @@ function assertCCTPV2Config(config) {
|
|
|
18760
19055
|
this.actionDispatcher = new Actionable();
|
|
18761
19056
|
this.disableErrorReporting = config.disableErrorReporting === true;
|
|
18762
19057
|
this.telemetryConfig = {
|
|
18763
|
-
sdkName: SDK_NAME$
|
|
19058
|
+
sdkName: SDK_NAME$3,
|
|
18764
19059
|
sdkVersion: pkg$5.version,
|
|
18765
19060
|
disabled: this.disableErrorReporting
|
|
18766
19061
|
};
|
|
@@ -19285,75 +19580,8 @@ function assertCCTPV2Config(config) {
|
|
|
19285
19580
|
// Auto-register this kit for user agent tracking
|
|
19286
19581
|
registerKit(`${pkg$5.name}/${pkg$5.version}`);
|
|
19287
19582
|
|
|
19288
|
-
/**
|
|
19289
|
-
* Create a BridgeKit instance with optional developer fee configuration.
|
|
19290
|
-
*
|
|
19291
|
-
* This utility creates a BridgeKit instance that optionally includes developer
|
|
19292
|
-
* fee configuration based on the provided AppKit context. If the context
|
|
19293
|
-
* provides both `getFee` and `getFeeRecipient` methods, they will be configured
|
|
19294
|
-
* as developer fees in the BridgeKit instance using the `setCustomFeePolicy` method.
|
|
19295
|
-
*
|
|
19296
|
-
* The fee integration transforms string-based fees from the context into the
|
|
19297
|
-
* format expected by BridgeKit, enabling seamless fee calculation across both kits.
|
|
19298
|
-
*
|
|
19299
|
-
* @param context - The AppKit context containing optional fee methods
|
|
19300
|
-
* @returns A configured BridgeKit instance with or without developer fees
|
|
19301
|
-
*
|
|
19302
|
-
* @example
|
|
19303
|
-
* ```typescript
|
|
19304
|
-
* import { createBridgeKit } from '@circle-fin/app-kit/utils'
|
|
19305
|
-
* import { createContext } from '@circle-fin/app-kit/context'
|
|
19306
|
-
*
|
|
19307
|
-
* // Create context with fee methods
|
|
19308
|
-
* const context = createContext({
|
|
19309
|
-
* getFee: async (type, params) => '1000000', // 1 USDC in micro-units
|
|
19310
|
-
* getFeeRecipient: async (type, info) => '0x742d35Cc4634C0532925a3b8D1d7'
|
|
19311
|
-
* })
|
|
19312
|
-
*
|
|
19313
|
-
* // Create BridgeKit with developer fees
|
|
19314
|
-
* const bridgeKit = createBridgeKit(context)
|
|
19315
|
-
* ```
|
|
19316
|
-
*
|
|
19317
|
-
* @example
|
|
19318
|
-
* ```typescript
|
|
19319
|
-
* import { createBridgeKit } from '@circle-fin/app-kit/utils'
|
|
19320
|
-
* import { createContext } from '@circle-fin/app-kit/context'
|
|
19321
|
-
*
|
|
19322
|
-
* // Create context without fee methods
|
|
19323
|
-
* const context = createContext()
|
|
19324
|
-
*
|
|
19325
|
-
* // Create standard BridgeKit instance
|
|
19326
|
-
* const bridgeKit = createBridgeKit(context)
|
|
19327
|
-
* ```
|
|
19328
|
-
*/ const createBridgeKit = (context)=>{
|
|
19329
|
-
const getFee = context.getFee?.bind(context);
|
|
19330
|
-
const getFeeRecipient = context.getFeeRecipient?.bind(context);
|
|
19331
|
-
const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
|
|
19332
|
-
const kit = new BridgeKit({
|
|
19333
|
-
...context.disableErrorReporting != null && {
|
|
19334
|
-
disableErrorReporting: context.disableErrorReporting
|
|
19335
|
-
},
|
|
19336
|
-
...context.headers != null && {
|
|
19337
|
-
headers: context.headers
|
|
19338
|
-
}
|
|
19339
|
-
});
|
|
19340
|
-
if (hasBoth) {
|
|
19341
|
-
kit.setCustomFeePolicy({
|
|
19342
|
-
calculateFee: async (params)=>{
|
|
19343
|
-
const feeStr = await getFee('bridge', params);
|
|
19344
|
-
return feeStr;
|
|
19345
|
-
},
|
|
19346
|
-
resolveFeeRecipientAddress: async (chain, params)=>await getFeeRecipient('bridge', {
|
|
19347
|
-
chain,
|
|
19348
|
-
params: params || {}
|
|
19349
|
-
})
|
|
19350
|
-
});
|
|
19351
|
-
}
|
|
19352
|
-
return kit;
|
|
19353
|
-
};
|
|
19354
|
-
|
|
19355
19583
|
var name$3 = "@circle-fin/swap-kit";
|
|
19356
|
-
var version$3 = "1.
|
|
19584
|
+
var version$3 = "1.5.1";
|
|
19357
19585
|
var pkg$3 = {
|
|
19358
19586
|
name: name$3,
|
|
19359
19587
|
version: version$3};
|
|
@@ -20186,6 +20414,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20186
20414
|
required_error: 'estimatedAmount is required',
|
|
20187
20415
|
invalid_type_error: 'estimatedAmount must be a string'
|
|
20188
20416
|
}).min(1, 'estimatedAmount must be a non-empty string'),
|
|
20417
|
+
// Per-swap join key echoed back verbatim on success telemetry. Optional so a
|
|
20418
|
+
// not-yet-upgraded service (no field) still validates during rollout. A
|
|
20419
|
+
// malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
|
|
20420
|
+
// than throwing: this is a telemetry-only field (stripped from the developer
|
|
20421
|
+
// result, never used for control flow), so it must not be able to abort the
|
|
20422
|
+
// swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
|
|
20423
|
+
// never-throw contract of the rest of the telemetry stack. Implemented with
|
|
20424
|
+
// `preprocess` rather than Zod's `.catch()` because static analysis misreads
|
|
20425
|
+
// `.catch` on the schema chain as an unhandled Promise (S7785).
|
|
20426
|
+
correlationId: zod.z.preprocess((value)=>zod.z.string().uuid().safeParse(value).success ? value : undefined, zod.z.string().optional()),
|
|
20189
20427
|
config: createSwapRequestBaseSchema.shape.config.optional(),
|
|
20190
20428
|
fees: createSwapFeesSchema.optional(),
|
|
20191
20429
|
transaction: createSwapTransactionSchema
|
|
@@ -20311,6 +20549,45 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20311
20549
|
// Validate without trimming - any whitespace will cause validation to fail
|
|
20312
20550
|
return apiKeyPattern.test(apiKey);
|
|
20313
20551
|
};
|
|
20552
|
+
/**
|
|
20553
|
+
* Assert that a Stablecoin Service kit key is not being supplied from a browser.
|
|
20554
|
+
*
|
|
20555
|
+
* The kit key (`KIT_KEY:<id>:<secret>`) is a server-only secret. Every
|
|
20556
|
+
* Stablecoin Service request that attaches an `Authorization: Bearer` header
|
|
20557
|
+
* funnels through this package, so calling this guard before that header is
|
|
20558
|
+
* built prevents the secret from being sent from — and thus bundled into — a
|
|
20559
|
+
* client application. In Node.js the check is a no-op, preserving the
|
|
20560
|
+
* legitimate "hold the kit key on the server, forward the prepared transaction
|
|
20561
|
+
* to the client" flow. When no kit key is supplied the permissionless (keyless)
|
|
20562
|
+
* client path remains fully allowed.
|
|
20563
|
+
*
|
|
20564
|
+
* @param apiKey - The inline kit key for the request, or `undefined` when none
|
|
20565
|
+
* was supplied (permissionless mode).
|
|
20566
|
+
* @returns Nothing.
|
|
20567
|
+
* @throws KitError with VALIDATION_FAILED when a kit key is supplied while
|
|
20568
|
+
* running in a browser environment. The secret value is never echoed.
|
|
20569
|
+
*
|
|
20570
|
+
* @example
|
|
20571
|
+
* ```typescript
|
|
20572
|
+
* import { assertBrowserSafeApiKey } from '@core/service-client'
|
|
20573
|
+
*
|
|
20574
|
+
* // Server (Node.js): no-op, request proceeds with the Authorization header.
|
|
20575
|
+
* assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
|
|
20576
|
+
*
|
|
20577
|
+
* // Browser: throws to stop the secret from leaking into the client bundle.
|
|
20578
|
+
* assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
|
|
20579
|
+
*
|
|
20580
|
+
* // Browser, permissionless: allowed.
|
|
20581
|
+
* assertBrowserSafeApiKey(undefined)
|
|
20582
|
+
* ```
|
|
20583
|
+
*/ const assertBrowserSafeApiKey = (apiKey)=>{
|
|
20584
|
+
if (apiKey === undefined) {
|
|
20585
|
+
return;
|
|
20586
|
+
}
|
|
20587
|
+
if (isBrowserEnvironment()) {
|
|
20588
|
+
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');
|
|
20589
|
+
}
|
|
20590
|
+
};
|
|
20314
20591
|
|
|
20315
20592
|
/**
|
|
20316
20593
|
* Create a cross-chain bridge and swap transaction through the Stablecoin Service.
|
|
@@ -20368,6 +20645,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20368
20645
|
const url = new URL('/v1/stablecoinKits/swap', STABLECOIN_SERVICE_BASE_URL).toString();
|
|
20369
20646
|
// Remove the API key from the request body
|
|
20370
20647
|
const { apiKey, ...requestBody } = validatedParams;
|
|
20648
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
20649
|
+
assertBrowserSafeApiKey(apiKey);
|
|
20371
20650
|
const effectiveConfig = {
|
|
20372
20651
|
...DEFAULT_CONFIG$1,
|
|
20373
20652
|
headers: {
|
|
@@ -20522,6 +20801,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20522
20801
|
}
|
|
20523
20802
|
// Use validated data
|
|
20524
20803
|
const validatedParams = result.data;
|
|
20804
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
20805
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
20525
20806
|
// Build the API URL
|
|
20526
20807
|
const url = buildQuoteUrl(validatedParams);
|
|
20527
20808
|
// Merge default config with Authorization header
|
|
@@ -20594,6 +20875,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20594
20875
|
toChain: result.data.toChain
|
|
20595
20876
|
}
|
|
20596
20877
|
};
|
|
20878
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
20879
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
20597
20880
|
const url = buildSwapStatusUrl(validatedParams);
|
|
20598
20881
|
const effectiveConfig = {
|
|
20599
20882
|
...DEFAULT_CONFIG$1,
|
|
@@ -20698,6 +20981,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20698
20981
|
addresses: result.data.addresses
|
|
20699
20982
|
}
|
|
20700
20983
|
};
|
|
20984
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
20985
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
20701
20986
|
const url = buildTokenRatesUrl(validatedParams);
|
|
20702
20987
|
const effectiveConfig = {
|
|
20703
20988
|
...DEFAULT_CONFIG$1,
|
|
@@ -20712,6 +20997,138 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
20712
20997
|
return pollApiGet(url, isGetTokenRatesResponse, effectiveConfig);
|
|
20713
20998
|
};
|
|
20714
20999
|
|
|
21000
|
+
/**
|
|
21001
|
+
* IAdapter contract ABI.
|
|
21002
|
+
*
|
|
21003
|
+
* Shared ABI for the on-chain Adapter contract used by multiple kits
|
|
21004
|
+
* (swap, earn) for executing signed instruction sets. The `execute()`
|
|
21005
|
+
* function accepts EIP-712 signed execution parameters, token inputs,
|
|
21006
|
+
* and a signature, then executes the corresponding on-chain
|
|
21007
|
+
* instructions.
|
|
21008
|
+
*/ const adapterContractAbi = [
|
|
21009
|
+
{
|
|
21010
|
+
type: 'function',
|
|
21011
|
+
name: 'execute',
|
|
21012
|
+
inputs: [
|
|
21013
|
+
{
|
|
21014
|
+
name: 'params',
|
|
21015
|
+
type: 'tuple',
|
|
21016
|
+
internalType: 'struct IAdapter.ExecutionParams',
|
|
21017
|
+
components: [
|
|
21018
|
+
{
|
|
21019
|
+
name: 'instructions',
|
|
21020
|
+
type: 'tuple[]',
|
|
21021
|
+
internalType: 'struct IAdapter.Instruction[]',
|
|
21022
|
+
components: [
|
|
21023
|
+
{
|
|
21024
|
+
name: 'target',
|
|
21025
|
+
type: 'address',
|
|
21026
|
+
internalType: 'address'
|
|
21027
|
+
},
|
|
21028
|
+
{
|
|
21029
|
+
name: 'data',
|
|
21030
|
+
type: 'bytes',
|
|
21031
|
+
internalType: 'bytes'
|
|
21032
|
+
},
|
|
21033
|
+
{
|
|
21034
|
+
name: 'value',
|
|
21035
|
+
type: 'uint256',
|
|
21036
|
+
internalType: 'uint256'
|
|
21037
|
+
},
|
|
21038
|
+
{
|
|
21039
|
+
name: 'tokenIn',
|
|
21040
|
+
type: 'address',
|
|
21041
|
+
internalType: 'address'
|
|
21042
|
+
},
|
|
21043
|
+
{
|
|
21044
|
+
name: 'amountToApprove',
|
|
21045
|
+
type: 'uint256',
|
|
21046
|
+
internalType: 'uint256'
|
|
21047
|
+
},
|
|
21048
|
+
{
|
|
21049
|
+
name: 'tokenOut',
|
|
21050
|
+
type: 'address',
|
|
21051
|
+
internalType: 'address'
|
|
21052
|
+
},
|
|
21053
|
+
{
|
|
21054
|
+
name: 'minTokenOut',
|
|
21055
|
+
type: 'uint256',
|
|
21056
|
+
internalType: 'uint256'
|
|
21057
|
+
}
|
|
21058
|
+
]
|
|
21059
|
+
},
|
|
21060
|
+
{
|
|
21061
|
+
name: 'tokens',
|
|
21062
|
+
type: 'tuple[]',
|
|
21063
|
+
internalType: 'struct IAdapter.TokenRecipient[]',
|
|
21064
|
+
components: [
|
|
21065
|
+
{
|
|
21066
|
+
name: 'token',
|
|
21067
|
+
type: 'address',
|
|
21068
|
+
internalType: 'address'
|
|
21069
|
+
},
|
|
21070
|
+
{
|
|
21071
|
+
name: 'beneficiary',
|
|
21072
|
+
type: 'address',
|
|
21073
|
+
internalType: 'address'
|
|
21074
|
+
}
|
|
21075
|
+
]
|
|
21076
|
+
},
|
|
21077
|
+
{
|
|
21078
|
+
name: 'execId',
|
|
21079
|
+
type: 'uint256',
|
|
21080
|
+
internalType: 'uint256'
|
|
21081
|
+
},
|
|
21082
|
+
{
|
|
21083
|
+
name: 'deadline',
|
|
21084
|
+
type: 'uint256',
|
|
21085
|
+
internalType: 'uint256'
|
|
21086
|
+
},
|
|
21087
|
+
{
|
|
21088
|
+
name: 'metadata',
|
|
21089
|
+
type: 'bytes',
|
|
21090
|
+
internalType: 'bytes'
|
|
21091
|
+
}
|
|
21092
|
+
]
|
|
21093
|
+
},
|
|
21094
|
+
{
|
|
21095
|
+
name: 'tokenInputs',
|
|
21096
|
+
type: 'tuple[]',
|
|
21097
|
+
internalType: 'struct IAdapter.TokenInput[]',
|
|
21098
|
+
components: [
|
|
21099
|
+
{
|
|
21100
|
+
name: 'permitType',
|
|
21101
|
+
type: 'uint8',
|
|
21102
|
+
internalType: 'enum IAdapter.PermitType'
|
|
21103
|
+
},
|
|
21104
|
+
{
|
|
21105
|
+
name: 'token',
|
|
21106
|
+
type: 'address',
|
|
21107
|
+
internalType: 'address'
|
|
21108
|
+
},
|
|
21109
|
+
{
|
|
21110
|
+
name: 'amount',
|
|
21111
|
+
type: 'uint256',
|
|
21112
|
+
internalType: 'uint256'
|
|
21113
|
+
},
|
|
21114
|
+
{
|
|
21115
|
+
name: 'permitCalldata',
|
|
21116
|
+
type: 'bytes',
|
|
21117
|
+
internalType: 'bytes'
|
|
21118
|
+
}
|
|
21119
|
+
]
|
|
21120
|
+
},
|
|
21121
|
+
{
|
|
21122
|
+
name: 'signature',
|
|
21123
|
+
type: 'bytes',
|
|
21124
|
+
internalType: 'bytes'
|
|
21125
|
+
}
|
|
21126
|
+
],
|
|
21127
|
+
outputs: [],
|
|
21128
|
+
stateMutability: 'payable'
|
|
21129
|
+
}
|
|
21130
|
+
];
|
|
21131
|
+
|
|
20715
21132
|
/**
|
|
20716
21133
|
* USDC ABI
|
|
20717
21134
|
*
|
|
@@ -21947,6 +22364,179 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
21947
22364
|
}
|
|
21948
22365
|
];
|
|
21949
22366
|
|
|
22367
|
+
/**
|
|
22368
|
+
* Minimal ERC-4626 tokenized-vault ABI.
|
|
22369
|
+
*
|
|
22370
|
+
* Covers only the mutating methods EarnKit bundles as inner instructions inside
|
|
22371
|
+
* an Adapter `execute()` call: `deposit`, `withdraw`, and `redeem`. It exists so
|
|
22372
|
+
* clients can decode the inner instruction calldata into a human-readable
|
|
22373
|
+
* summary of what a signer is authorizing (asset amount, receiver, owner)
|
|
22374
|
+
* rather than showing opaque bytes. The 4-byte selectors match the calldata the
|
|
22375
|
+
* earn service signs (`deposit(uint256,address)` = `0x6e553f65`,
|
|
22376
|
+
* `withdraw(uint256,address,address)` = `0xb460af94`,
|
|
22377
|
+
* `redeem(uint256,address,address)` = `0xba087652`).
|
|
22378
|
+
*/ const erc4626VaultAbi = [
|
|
22379
|
+
{
|
|
22380
|
+
type: 'function',
|
|
22381
|
+
name: 'deposit',
|
|
22382
|
+
stateMutability: 'nonpayable',
|
|
22383
|
+
inputs: [
|
|
22384
|
+
{
|
|
22385
|
+
name: 'assets',
|
|
22386
|
+
type: 'uint256',
|
|
22387
|
+
internalType: 'uint256'
|
|
22388
|
+
},
|
|
22389
|
+
{
|
|
22390
|
+
name: 'receiver',
|
|
22391
|
+
type: 'address',
|
|
22392
|
+
internalType: 'address'
|
|
22393
|
+
}
|
|
22394
|
+
],
|
|
22395
|
+
outputs: [
|
|
22396
|
+
{
|
|
22397
|
+
name: 'shares',
|
|
22398
|
+
type: 'uint256',
|
|
22399
|
+
internalType: 'uint256'
|
|
22400
|
+
}
|
|
22401
|
+
]
|
|
22402
|
+
},
|
|
22403
|
+
{
|
|
22404
|
+
type: 'function',
|
|
22405
|
+
name: 'withdraw',
|
|
22406
|
+
stateMutability: 'nonpayable',
|
|
22407
|
+
inputs: [
|
|
22408
|
+
{
|
|
22409
|
+
name: 'assets',
|
|
22410
|
+
type: 'uint256',
|
|
22411
|
+
internalType: 'uint256'
|
|
22412
|
+
},
|
|
22413
|
+
{
|
|
22414
|
+
name: 'receiver',
|
|
22415
|
+
type: 'address',
|
|
22416
|
+
internalType: 'address'
|
|
22417
|
+
},
|
|
22418
|
+
{
|
|
22419
|
+
name: 'owner',
|
|
22420
|
+
type: 'address',
|
|
22421
|
+
internalType: 'address'
|
|
22422
|
+
}
|
|
22423
|
+
],
|
|
22424
|
+
outputs: [
|
|
22425
|
+
{
|
|
22426
|
+
name: 'shares',
|
|
22427
|
+
type: 'uint256',
|
|
22428
|
+
internalType: 'uint256'
|
|
22429
|
+
}
|
|
22430
|
+
]
|
|
22431
|
+
},
|
|
22432
|
+
{
|
|
22433
|
+
type: 'function',
|
|
22434
|
+
name: 'redeem',
|
|
22435
|
+
stateMutability: 'nonpayable',
|
|
22436
|
+
inputs: [
|
|
22437
|
+
{
|
|
22438
|
+
name: 'shares',
|
|
22439
|
+
type: 'uint256',
|
|
22440
|
+
internalType: 'uint256'
|
|
22441
|
+
},
|
|
22442
|
+
{
|
|
22443
|
+
name: 'receiver',
|
|
22444
|
+
type: 'address',
|
|
22445
|
+
internalType: 'address'
|
|
22446
|
+
},
|
|
22447
|
+
{
|
|
22448
|
+
name: 'owner',
|
|
22449
|
+
type: 'address',
|
|
22450
|
+
internalType: 'address'
|
|
22451
|
+
}
|
|
22452
|
+
],
|
|
22453
|
+
outputs: [
|
|
22454
|
+
{
|
|
22455
|
+
name: 'assets',
|
|
22456
|
+
type: 'uint256',
|
|
22457
|
+
internalType: 'uint256'
|
|
22458
|
+
}
|
|
22459
|
+
]
|
|
22460
|
+
}
|
|
22461
|
+
];
|
|
22462
|
+
|
|
22463
|
+
/**
|
|
22464
|
+
* Minimal FeeTaker ABI.
|
|
22465
|
+
*
|
|
22466
|
+
* The earn service appends a `takeFeeERC20` instruction to withdraw bundles
|
|
22467
|
+
* when Circle charges a withdrawal fee. This ABI decodes that inner instruction
|
|
22468
|
+
* so the fee (token, beneficiary, amount) is visible in the signing summary
|
|
22469
|
+
* instead of appearing as opaque calldata alongside the redeem/withdraw call.
|
|
22470
|
+
*/ const feeTakerAbi = [
|
|
22471
|
+
{
|
|
22472
|
+
type: 'function',
|
|
22473
|
+
name: 'takeFeeERC20',
|
|
22474
|
+
stateMutability: 'nonpayable',
|
|
22475
|
+
inputs: [
|
|
22476
|
+
{
|
|
22477
|
+
name: 'token',
|
|
22478
|
+
type: 'address',
|
|
22479
|
+
internalType: 'address'
|
|
22480
|
+
},
|
|
22481
|
+
{
|
|
22482
|
+
name: 'beneficiary',
|
|
22483
|
+
type: 'address',
|
|
22484
|
+
internalType: 'address'
|
|
22485
|
+
},
|
|
22486
|
+
{
|
|
22487
|
+
name: 'fee',
|
|
22488
|
+
type: 'uint256',
|
|
22489
|
+
internalType: 'uint256'
|
|
22490
|
+
},
|
|
22491
|
+
{
|
|
22492
|
+
name: 'kitType',
|
|
22493
|
+
type: 'bytes8',
|
|
22494
|
+
internalType: 'bytes8'
|
|
22495
|
+
}
|
|
22496
|
+
],
|
|
22497
|
+
outputs: []
|
|
22498
|
+
}
|
|
22499
|
+
];
|
|
22500
|
+
|
|
22501
|
+
/**
|
|
22502
|
+
* Minimal Merkl Distributor ABI.
|
|
22503
|
+
*
|
|
22504
|
+
* EarnKit claim-rewards bundles a single `claim` instruction targeting the
|
|
22505
|
+
* Merkl Distributor, batching one entry per reward token. This ABI decodes that
|
|
22506
|
+
* inner instruction so the claimed tokens and amounts are visible in the signing
|
|
22507
|
+
* summary. `claim` uses dynamic array arguments, which is why a real ABI decoder
|
|
22508
|
+
* (rather than fixed-word slicing) is required for the reward instruction.
|
|
22509
|
+
*/ const merklDistributorAbi = [
|
|
22510
|
+
{
|
|
22511
|
+
type: 'function',
|
|
22512
|
+
name: 'claim',
|
|
22513
|
+
stateMutability: 'nonpayable',
|
|
22514
|
+
inputs: [
|
|
22515
|
+
{
|
|
22516
|
+
name: 'users',
|
|
22517
|
+
type: 'address[]',
|
|
22518
|
+
internalType: 'address[]'
|
|
22519
|
+
},
|
|
22520
|
+
{
|
|
22521
|
+
name: 'tokens',
|
|
22522
|
+
type: 'address[]',
|
|
22523
|
+
internalType: 'address[]'
|
|
22524
|
+
},
|
|
22525
|
+
{
|
|
22526
|
+
name: 'amounts',
|
|
22527
|
+
type: 'uint256[]',
|
|
22528
|
+
internalType: 'uint256[]'
|
|
22529
|
+
},
|
|
22530
|
+
{
|
|
22531
|
+
name: 'proofs',
|
|
22532
|
+
type: 'bytes32[][]',
|
|
22533
|
+
internalType: 'bytes32[][]'
|
|
22534
|
+
}
|
|
22535
|
+
],
|
|
22536
|
+
outputs: []
|
|
22537
|
+
}
|
|
22538
|
+
];
|
|
22539
|
+
|
|
21950
22540
|
/**
|
|
21951
22541
|
* Zod schema for validating EVM adapter capabilities.
|
|
21952
22542
|
*
|
|
@@ -23455,72 +24045,55 @@ function evmSigningData(burnIntent) {
|
|
|
23455
24045
|
* `0xef0100` followed by the 20-byte delegate address (23 bytes total).
|
|
23456
24046
|
* The underlying secp256k1 key still produces `ecrecover`-verifiable
|
|
23457
24047
|
* signatures, so for Gateway's purposes a 7702-delegated address is
|
|
23458
|
-
* an EOA, not
|
|
24048
|
+
* an EOA, not a contract signer.
|
|
23459
24049
|
*
|
|
23460
24050
|
* Spec: https://eips.ethereum.org/EIPS/eip-7702
|
|
23461
24051
|
*/ const EIP_7702_DELEGATION_PREFIX = '0xef0100';
|
|
23462
24052
|
/**
|
|
23463
|
-
*
|
|
23464
|
-
*
|
|
23465
|
-
* Gateway verifies burn-intent signatures with plain `ecrecover` (see
|
|
23466
|
-
* `evm-gateway-contracts/src/lib/EIP712Domain.sol`). Smart-contract
|
|
23467
|
-
* accounts (SCAs) produce signatures over wrapped hashes (ERC-1271 /
|
|
23468
|
-
* ERC-6492 / ERC-6900 replay-safe hashes) that Gateway cannot verify.
|
|
23469
|
-
* Additionally, the Circle Wallets backend rejects SCA typed-data signing
|
|
23470
|
-
* against Gateway's chainId-less domain with an opaque
|
|
23471
|
-
* `invalid integer value <nil>/<nil> for type uint256` error.
|
|
24053
|
+
* Determine whether `address` on `chain` signs as a contract (ERC-1271)
|
|
24054
|
+
* rather than as an EOA.
|
|
23472
24055
|
*
|
|
23473
|
-
*
|
|
23474
|
-
*
|
|
23475
|
-
* `
|
|
24056
|
+
* Gateway validates burn-intent signatures two ways: a static `ecrecover`
|
|
24057
|
+
* check for EOAs, and — for requests that carry `contractSigner: true` —
|
|
24058
|
+
* an offchain `isValidSignature` simulation against the signing contract
|
|
24059
|
+
* (ERC-1271). Gateway does not infer which one to use, so the caller must
|
|
24060
|
+
* declare it. This detects the contract case from on-chain bytecode.
|
|
23476
24061
|
*
|
|
23477
|
-
*
|
|
23478
|
-
*
|
|
23479
|
-
*
|
|
23480
|
-
*
|
|
23481
|
-
* docs for the exact API.
|
|
24062
|
+
* EIP-7702-delegated EOAs are treated as EOAs: they expose non-empty
|
|
24063
|
+
* bytecode (`0xef0100<delegate>`) but the underlying secp256k1 key still
|
|
24064
|
+
* produces `ecrecover`-verifiable signatures, so the cheaper EOA path
|
|
24065
|
+
* stays correct for them.
|
|
23482
24066
|
*
|
|
23483
|
-
* If bytecode cannot be read (RPC failure, etc.) the
|
|
23484
|
-
*
|
|
23485
|
-
*
|
|
24067
|
+
* If bytecode cannot be read (RPC failure, etc.) the address is reported
|
|
24068
|
+
* as an EOA and a warning is logged so the fallback is diagnosable. A
|
|
24069
|
+
* genuine contract signer misreported this way is rejected by Gateway with
|
|
24070
|
+
* an invalid-signature error rather than silently mis-attested.
|
|
23486
24071
|
*
|
|
23487
24072
|
* @param adapter - Anything exposing {@link EvmAdapterLike.readBytecode}.
|
|
23488
|
-
* @param address - Signer address to
|
|
24073
|
+
* @param address - Signer address to classify.
|
|
23489
24074
|
* @param chain - EVM chain where the signer lives.
|
|
23490
|
-
* @
|
|
24075
|
+
* @returns `true` when the signer is a contract account and the transfer
|
|
24076
|
+
* request must set `contractSigner: true`; `false` otherwise.
|
|
23491
24077
|
*
|
|
23492
24078
|
* @example
|
|
23493
24079
|
* ```typescript
|
|
23494
|
-
* import {
|
|
24080
|
+
* import { isContractSigner } from '@core/adapter-evm'
|
|
23495
24081
|
* import { Ethereum } from '@core/chains'
|
|
23496
24082
|
*
|
|
23497
|
-
* await
|
|
24083
|
+
* const useErc1271 = await isContractSigner(adapter, '0xabc...', Ethereum)
|
|
23498
24084
|
* ```
|
|
23499
|
-
*/ async function
|
|
24085
|
+
*/ async function isContractSigner(adapter, address, chain) {
|
|
23500
24086
|
let code;
|
|
23501
24087
|
try {
|
|
23502
24088
|
code = await adapter.readBytecode(address, chain);
|
|
23503
24089
|
} catch (err) {
|
|
23504
|
-
console.warn(`[gateway]
|
|
23505
|
-
return;
|
|
24090
|
+
console.warn(`[gateway] isContractSigner defaulting to EOA (readBytecode failed ` + `for ${address} on ${chain.name}): ` + (err instanceof Error ? err.message : String(err)));
|
|
24091
|
+
return false;
|
|
23506
24092
|
}
|
|
23507
24093
|
if (code === undefined || code === '0x' || code.toLowerCase().startsWith(EIP_7702_DELEGATION_PREFIX)) {
|
|
23508
|
-
return;
|
|
24094
|
+
return false;
|
|
23509
24095
|
}
|
|
23510
|
-
|
|
23511
|
-
...InputError.UNSUPPORTED_ACTION,
|
|
23512
|
-
recoverability: 'FATAL',
|
|
23513
|
-
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.`,
|
|
23514
|
-
cause: {
|
|
23515
|
-
trace: {
|
|
23516
|
-
operation: 'signEvmIntentGroup.assertSignerIsEoa',
|
|
23517
|
-
address,
|
|
23518
|
-
chain: chain.name,
|
|
23519
|
-
bytecodeBytes: (code.length - 2) / 2,
|
|
23520
|
-
bytecodePrefix: code.slice(0, 12)
|
|
23521
|
-
}
|
|
23522
|
-
}
|
|
23523
|
-
});
|
|
24096
|
+
return true;
|
|
23524
24097
|
}
|
|
23525
24098
|
|
|
23526
24099
|
/**
|
|
@@ -23547,78 +24120,177 @@ function evmSigningData(burnIntent) {
|
|
|
23547
24120
|
return typeof value === 'object' && value !== null && 'readBytecode' in value && typeof value.readBytecode === 'function';
|
|
23548
24121
|
}
|
|
23549
24122
|
|
|
24123
|
+
function resolveIntentChain(group, intent) {
|
|
24124
|
+
const sourceDomain = intent.spec.sourceDomain;
|
|
24125
|
+
const chain = group.chainsByDomain.get(sourceDomain);
|
|
24126
|
+
if (chain !== undefined) return chain;
|
|
24127
|
+
throw createValidationFailedError$1('intent.spec.sourceDomain', sourceDomain, `No source chain found for Gateway domain ${String(sourceDomain)}`);
|
|
24128
|
+
}
|
|
24129
|
+
function normalizeSignatureResult(result) {
|
|
24130
|
+
if (typeof result === 'string') {
|
|
24131
|
+
return {
|
|
24132
|
+
signature: result,
|
|
24133
|
+
contractSigner: false
|
|
24134
|
+
};
|
|
24135
|
+
}
|
|
24136
|
+
if (typeof result === 'object' && result !== null && 'signature' in result && typeof result.signature === 'string') {
|
|
24137
|
+
return {
|
|
24138
|
+
signature: result.signature,
|
|
24139
|
+
contractSigner: 'contractSigner' in result && result.contractSigner === true
|
|
24140
|
+
};
|
|
24141
|
+
}
|
|
24142
|
+
throw createValidationFailedError$1('signature', result, 'must be a signature string or an object containing a signature string');
|
|
24143
|
+
}
|
|
24144
|
+
function validateGroupIntents(intents) {
|
|
24145
|
+
evmSigningData(intents);
|
|
24146
|
+
}
|
|
24147
|
+
function collectChainsByDomain(group) {
|
|
24148
|
+
const chainsByDomain = new Map();
|
|
24149
|
+
for (const intent of group.intents){
|
|
24150
|
+
chainsByDomain.set(intent.spec.sourceDomain, resolveIntentChain(group, intent));
|
|
24151
|
+
}
|
|
24152
|
+
return chainsByDomain;
|
|
24153
|
+
}
|
|
24154
|
+
async function classifySignerTypes(group, chainsByDomain) {
|
|
24155
|
+
const { adapter, address } = group;
|
|
24156
|
+
// Duck-typed on readBytecode rather than `instanceof EvmAdapter` because
|
|
24157
|
+
// each consumer package bundles its own copy of the base class and the
|
|
24158
|
+
// `instanceof` identity check fails across package boundaries.
|
|
24159
|
+
// Empty strings are rejected to avoid calling eth_getCode('') on the RPC.
|
|
24160
|
+
const hasResolvedSigner = typeof address === 'string' && address.length > 0;
|
|
24161
|
+
const signerTypes = await Promise.all([
|
|
24162
|
+
...chainsByDomain
|
|
24163
|
+
].map(async ([sourceDomain, sourceChain])=>{
|
|
24164
|
+
const contractSigner = hasResolvedSigner && sourceChain.type === 'evm' && isEvmAdapterLike(adapter) ? await isContractSigner(adapter, address, sourceChain) : false;
|
|
24165
|
+
return [
|
|
24166
|
+
sourceDomain,
|
|
24167
|
+
contractSigner
|
|
24168
|
+
];
|
|
24169
|
+
}));
|
|
24170
|
+
return new Map(signerTypes);
|
|
24171
|
+
}
|
|
24172
|
+
function createSigningUnits(group, signerTypeByDomain) {
|
|
24173
|
+
const contractUnitsByDomain = new Map();
|
|
24174
|
+
let eoaUnit;
|
|
24175
|
+
for (const [index, intent] of group.intents.entries()){
|
|
24176
|
+
const sourceDomain = intent.spec.sourceDomain;
|
|
24177
|
+
const contractSigner = signerTypeByDomain.get(sourceDomain) ?? false;
|
|
24178
|
+
if (contractSigner) {
|
|
24179
|
+
const existingUnit = contractUnitsByDomain.get(sourceDomain);
|
|
24180
|
+
if (existingUnit === undefined) {
|
|
24181
|
+
contractUnitsByDomain.set(sourceDomain, {
|
|
24182
|
+
intents: [
|
|
24183
|
+
intent
|
|
24184
|
+
],
|
|
24185
|
+
chain: resolveIntentChain(group, intent),
|
|
24186
|
+
contractSigner: true,
|
|
24187
|
+
firstIntentIndex: index
|
|
24188
|
+
});
|
|
24189
|
+
} else {
|
|
24190
|
+
existingUnit.intents.push(intent);
|
|
24191
|
+
}
|
|
24192
|
+
} else {
|
|
24193
|
+
eoaUnit ??= {
|
|
24194
|
+
intents: [],
|
|
24195
|
+
chain: resolveIntentChain(group, intent),
|
|
24196
|
+
contractSigner: false,
|
|
24197
|
+
firstIntentIndex: index
|
|
24198
|
+
};
|
|
24199
|
+
eoaUnit.intents.push(intent);
|
|
24200
|
+
}
|
|
24201
|
+
}
|
|
24202
|
+
const signingUnits = [
|
|
24203
|
+
...contractUnitsByDomain.values()
|
|
24204
|
+
];
|
|
24205
|
+
if (eoaUnit !== undefined) signingUnits.push(eoaUnit);
|
|
24206
|
+
signingUnits.sort((a, b)=>a.firstIntentIndex - b.firstIntentIndex);
|
|
24207
|
+
return signingUnits;
|
|
24208
|
+
}
|
|
24209
|
+
async function signUnit(group, unit) {
|
|
24210
|
+
const { adapter, address } = group;
|
|
24211
|
+
const firstIntent = unit.intents[0];
|
|
24212
|
+
const typedData = unit.intents.length === 1 && firstIntent !== undefined ? evmSigningData(firstIntent) : evmSigningData(unit.intents);
|
|
24213
|
+
const operationContext = address === undefined ? {
|
|
24214
|
+
chain: unit.chain
|
|
24215
|
+
} : {
|
|
24216
|
+
chain: unit.chain,
|
|
24217
|
+
address
|
|
24218
|
+
};
|
|
24219
|
+
const signRequest = await adapter.prepareAction('gateway.v1.signBurnIntents', {
|
|
24220
|
+
typedData,
|
|
24221
|
+
chain: unit.chain
|
|
24222
|
+
}, operationContext);
|
|
24223
|
+
const result = normalizeSignatureResult(await signRequest.execute());
|
|
24224
|
+
return {
|
|
24225
|
+
intents: unit.intents,
|
|
24226
|
+
signature: result.signature,
|
|
24227
|
+
contractSigner: result.contractSigner || unit.contractSigner
|
|
24228
|
+
};
|
|
24229
|
+
}
|
|
24230
|
+
async function signUnits(group, signingUnits) {
|
|
24231
|
+
const signedSets = [];
|
|
24232
|
+
// Keep wallet prompts deterministic. Multiple adapter groups can still sign
|
|
24233
|
+
// in parallel, but one signer is asked for its chain-bound signatures in
|
|
24234
|
+
// source-intent order.
|
|
24235
|
+
for (const unit of signingUnits){
|
|
24236
|
+
signedSets.push(await signUnit(group, unit));
|
|
24237
|
+
}
|
|
24238
|
+
return signedSets;
|
|
24239
|
+
}
|
|
23550
24240
|
/**
|
|
23551
|
-
* Sign an EVM adapter group
|
|
23552
|
-
* EIP-712 ECDSA signature.
|
|
24241
|
+
* Sign an EVM adapter group.
|
|
23553
24242
|
*
|
|
23554
|
-
*
|
|
23555
|
-
*
|
|
24243
|
+
* EOA intents remain batched into one EIP-712 `BurnIntentSet`. ERC-1271
|
|
24244
|
+
* intents are grouped and signed per source chain because smart accounts
|
|
24245
|
+
* commonly include `chainId` in their replay-safe signature hash.
|
|
24246
|
+
* All returned entries can still be submitted together in one atomic Gateway
|
|
24247
|
+
* transfer request.
|
|
23556
24248
|
*
|
|
23557
|
-
* Before signing,
|
|
23558
|
-
*
|
|
23559
|
-
*
|
|
23560
|
-
*
|
|
23561
|
-
*
|
|
24249
|
+
* Before signing, classifies the signer as an EOA or a contract account.
|
|
24250
|
+
* Gateway validates EOA signatures with `ecrecover` and contract-account
|
|
24251
|
+
* signatures with ERC-1271, but it does not infer which one applies — the
|
|
24252
|
+
* transfer request has to declare it. The returned `contractSigner` flag
|
|
24253
|
+
* carries that decision through to `buildTransferRequestBody`.
|
|
23562
24254
|
*
|
|
23563
24255
|
* @param group - The adapter group containing the adapter, chain, and
|
|
23564
24256
|
* burn intents to sign.
|
|
23565
|
-
* @returns
|
|
24257
|
+
* @returns Signed entries with their intents, signatures, and Gateway signer
|
|
24258
|
+
* validation mode.
|
|
24259
|
+
* @throws KitError when an intent has no source-chain mapping or a signing
|
|
24260
|
+
* action returns an invalid signature shape.
|
|
23566
24261
|
*
|
|
23567
24262
|
* @example
|
|
23568
24263
|
* ```typescript
|
|
23569
24264
|
* import { signEvmIntentGroup } from '@core/adapter-evm'
|
|
23570
24265
|
*
|
|
23571
|
-
* const
|
|
24266
|
+
* const signedSets = await signEvmIntentGroup({
|
|
23572
24267
|
* adapter: evmAdapter,
|
|
23573
24268
|
* chain: ethereumChain,
|
|
23574
24269
|
* intents: [burnIntent1, burnIntent2],
|
|
24270
|
+
* chainsByDomain: new Map([
|
|
24271
|
+
* [0, ethereumChain],
|
|
24272
|
+
* [6, baseChain],
|
|
24273
|
+
* ]),
|
|
23575
24274
|
* address: '0x...',
|
|
23576
24275
|
* })
|
|
23577
|
-
* console.log(
|
|
24276
|
+
* console.log(signedSets)
|
|
23578
24277
|
* ```
|
|
23579
24278
|
*/ async function signEvmIntentGroup(group) {
|
|
23580
|
-
|
|
23581
|
-
|
|
23582
|
-
|
|
23583
|
-
|
|
23584
|
-
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
// Gateway verifies burn-intent signatures with plain ecrecover. An SCA
|
|
23588
|
-
// signer silently produces a signature over a wrapped hash that Gateway
|
|
23589
|
-
// cannot verify, and Circle Wallets' KMS rejects the typed data up front
|
|
23590
|
-
// with an opaque `<nil>/<nil>` error. Short-circuit with a clear message
|
|
23591
|
-
// when we can detect bytecode at the signer address. See DEVX-2774.
|
|
23592
|
-
//
|
|
23593
|
-
// Duck-typed on readBytecode rather than `instanceof EvmAdapter` because
|
|
23594
|
-
// each consumer package bundles its own copy of the base class and the
|
|
23595
|
-
// `instanceof` identity check fails across package boundaries.
|
|
23596
|
-
//
|
|
23597
|
-
// Empty string is defended against because assertSignerIsEoa would
|
|
23598
|
-
// otherwise call eth_getCode('') on the RPC.
|
|
23599
|
-
const hasResolvedSigner = typeof address === 'string' && address.length > 0;
|
|
23600
|
-
if (hasResolvedSigner && chain.type === 'evm' && isEvmAdapterLike(adapter)) {
|
|
23601
|
-
await assertSignerIsEoa(adapter, address, chain);
|
|
23602
|
-
}
|
|
23603
|
-
const firstIntent = groupIntents[0];
|
|
23604
|
-
const typedData = groupIntents.length === 1 && firstIntent ? evmSigningData(firstIntent) : evmSigningData(groupIntents);
|
|
23605
|
-
const signRequest = await adapter.prepareAction('gateway.v1.signBurnIntents', {
|
|
23606
|
-
typedData,
|
|
23607
|
-
chain
|
|
23608
|
-
}, operationContext);
|
|
23609
|
-
const sig = await signRequest.execute();
|
|
23610
|
-
return {
|
|
23611
|
-
intents: groupIntents,
|
|
23612
|
-
signature: sig
|
|
23613
|
-
};
|
|
24279
|
+
// Validate the collection before doing bytecode reads or asking a wallet
|
|
24280
|
+
// to sign. evmSigningData owns the canonical BurnIntent validation.
|
|
24281
|
+
validateGroupIntents(group.intents);
|
|
24282
|
+
const chainsByDomain = collectChainsByDomain(group);
|
|
24283
|
+
const signerTypeByDomain = await classifySignerTypes(group, chainsByDomain);
|
|
24284
|
+
const signingUnits = createSigningUnits(group, signerTypeByDomain);
|
|
24285
|
+
return await signUnits(group, signingUnits);
|
|
23614
24286
|
}
|
|
23615
24287
|
|
|
23616
24288
|
/**
|
|
23617
24289
|
* Add an EVM intent into the batched EVM group map.
|
|
23618
24290
|
*
|
|
23619
24291
|
* On EVM, all intents for the same adapter are batched into a single
|
|
23620
|
-
* group
|
|
23621
|
-
*
|
|
24292
|
+
* group. The signing step uses `chainsByDomain` to preserve EOA batching
|
|
24293
|
+
* while signing ERC-1271 intents separately on their source chains.
|
|
23622
24294
|
*
|
|
23623
24295
|
* @param intent - The burn intent to group.
|
|
23624
24296
|
* @param alloc - The allocation that resolved to this intent.
|
|
@@ -23635,6 +24307,7 @@ function evmSigningData(burnIntent) {
|
|
|
23635
24307
|
const existing = evmGroups.get(alloc.adapter);
|
|
23636
24308
|
if (existing) {
|
|
23637
24309
|
existing.intents.push(intent);
|
|
24310
|
+
existing.chainsByDomain.set(alloc.chain.gateway.domain, alloc.chain);
|
|
23638
24311
|
} else {
|
|
23639
24312
|
evmGroups.set(alloc.adapter, {
|
|
23640
24313
|
adapter: alloc.adapter,
|
|
@@ -23642,6 +24315,12 @@ function evmSigningData(burnIntent) {
|
|
|
23642
24315
|
intents: [
|
|
23643
24316
|
intent
|
|
23644
24317
|
],
|
|
24318
|
+
chainsByDomain: new Map([
|
|
24319
|
+
[
|
|
24320
|
+
alloc.chain.gateway.domain,
|
|
24321
|
+
alloc.chain
|
|
24322
|
+
]
|
|
24323
|
+
]),
|
|
23645
24324
|
address: alloc.sourceSigner
|
|
23646
24325
|
});
|
|
23647
24326
|
}
|
|
@@ -27469,6 +28148,11 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
27469
28148
|
apiKey: serviceParams.apiKey
|
|
27470
28149
|
}
|
|
27471
28150
|
});
|
|
28151
|
+
// Per-swap correlation id returned by the service as a top-level response
|
|
28152
|
+
// field for every chain (EVM + Solana). Attached to success telemetry so a
|
|
28153
|
+
// swap can be correlated across records; never used for control flow.
|
|
28154
|
+
// Undefined only against a not-yet-upgraded service that omits it.
|
|
28155
|
+
const correlationId = serviceResponse.correlationId;
|
|
27472
28156
|
// Build and return SwapResult
|
|
27473
28157
|
return {
|
|
27474
28158
|
tokenIn,
|
|
@@ -27478,6 +28162,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
27478
28162
|
fromAddress: serviceParams.fromAddress,
|
|
27479
28163
|
toAddress: serviceParams.toAddress,
|
|
27480
28164
|
txHash,
|
|
28165
|
+
...correlationId !== undefined && {
|
|
28166
|
+
correlationId
|
|
28167
|
+
},
|
|
27481
28168
|
executedTransactions,
|
|
27482
28169
|
...config !== undefined && {
|
|
27483
28170
|
config
|
|
@@ -30377,7 +31064,13 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
30377
31064
|
* amountIn: '50.00'
|
|
30378
31065
|
* })
|
|
30379
31066
|
* ```
|
|
30380
|
-
*/ async function swap$1(context, params, /**
|
|
31067
|
+
*/ async function swap$1(context, params, /**
|
|
31068
|
+
* @internal
|
|
31069
|
+
* Invoked after a successful broadcast with the on-chain `txHash` and the
|
|
31070
|
+
* service-issued `correlationId` (join key for success telemetry). The
|
|
31071
|
+
* service returns `correlationId` for every chain (EVM + Solana); it is
|
|
31072
|
+
* undefined only against a not-yet-upgraded service that omits the field.
|
|
31073
|
+
*/ onBroadcast) {
|
|
30381
31074
|
// Step 1: Validate parameters using schema
|
|
30382
31075
|
assertSwapParams(params, swapParamsSchema);
|
|
30383
31076
|
// Step 2: Resolve parameters (chain definitions, wallet addresses, token aliases preserved)
|
|
@@ -30397,13 +31090,16 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
30397
31090
|
// Step 5: Execute swap via provider
|
|
30398
31091
|
const swapParams = buildServiceSwapParams(resolvedParams, paramsWithFees);
|
|
30399
31092
|
const providerResult = await provider.swap(swapParams);
|
|
31093
|
+
// `correlationId` is an internal telemetry join key, not part of the public
|
|
31094
|
+
// SwapResult — strip it here so it never leaks into the formatted result.
|
|
31095
|
+
const { correlationId, ...providerResultPublic } = providerResult;
|
|
30400
31096
|
// A throwing `onBroadcast` must never strand the caller after a
|
|
30401
31097
|
// successful swap broadcast — the chain has moved. `safeInvokeCallback`
|
|
30402
31098
|
// swallows the error and surfaces a `console.warn` prefixed with
|
|
30403
31099
|
// `[stablecoin-kits swap-kit] callback threw and was swallowed:` so a
|
|
30404
31100
|
// kit-side closure bug stays debuggable rather than vanishing.
|
|
30405
31101
|
safeInvokeCallback('swap-kit', ()=>{
|
|
30406
|
-
onBroadcast?.(
|
|
31102
|
+
onBroadcast?.(providerResultPublic.txHash, correlationId);
|
|
30407
31103
|
});
|
|
30408
31104
|
const { tokenInDecimals, tokenOutDecimals } = await resolveTokenDecimals(context, resolvedParams);
|
|
30409
31105
|
// Step 6: Compose chain identity (owned by the kit, derived from the
|
|
@@ -30412,10 +31108,10 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
30412
31108
|
// resolves `progress` — the provider's snapshot, or a terminal `'DONE'` for
|
|
30413
31109
|
// a provider that omits it (a synchronous same-chain completion).
|
|
30414
31110
|
const composedResult = {
|
|
30415
|
-
...
|
|
31111
|
+
...providerResultPublic,
|
|
30416
31112
|
chainIn: resolvedParams.from.chain,
|
|
30417
31113
|
chainOut: resolvedParams.toChain ?? resolvedParams.from.chain,
|
|
30418
|
-
progress:
|
|
31114
|
+
progress: providerResultPublic.progress ?? {
|
|
30419
31115
|
status: 'DONE'
|
|
30420
31116
|
}
|
|
30421
31117
|
};
|
|
@@ -31196,7 +31892,7 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31196
31892
|
ESTIMATE: 'swap_estimate'
|
|
31197
31893
|
};
|
|
31198
31894
|
|
|
31199
|
-
/** SDK name used in telemetry payloads. */ const SDK_NAME$
|
|
31895
|
+
/** SDK name used in telemetry payloads. */ const SDK_NAME$2 = resolveKitSdkName(pkg$3.name);
|
|
31200
31896
|
/**
|
|
31201
31897
|
* A high-level class-based interface for same-chain and cross-chain token swap operations.
|
|
31202
31898
|
*
|
|
@@ -31268,7 +31964,13 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31268
31964
|
*/ class SwapKit {
|
|
31269
31965
|
context;
|
|
31270
31966
|
/** Whether error telemetry is disabled. */ disableErrorReporting;
|
|
31271
|
-
/** Per-kit telemetry identity for
|
|
31967
|
+
/** Per-kit telemetry identity for error reporting. */ telemetryConfig;
|
|
31968
|
+
/**
|
|
31969
|
+
* Per-kit telemetry identity for success/analytics events. Gated by
|
|
31970
|
+
* `disableAnalytics` (independent of `disableErrorReporting`) so a developer
|
|
31971
|
+
* can opt out of volume analytics without also silencing error reports —
|
|
31972
|
+
* mirrors EarnKit.
|
|
31973
|
+
*/ analyticsTelemetryConfig;
|
|
31272
31974
|
/**
|
|
31273
31975
|
* Create a new SwapKit instance.
|
|
31274
31976
|
*
|
|
@@ -31318,10 +32020,15 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31318
32020
|
this.context = createSwapKitContext(config);
|
|
31319
32021
|
this.disableErrorReporting = config.disableErrorReporting === true;
|
|
31320
32022
|
this.telemetryConfig = {
|
|
31321
|
-
sdkName: SDK_NAME$
|
|
32023
|
+
sdkName: SDK_NAME$2,
|
|
31322
32024
|
sdkVersion: pkg$3.version,
|
|
31323
32025
|
disabled: this.disableErrorReporting
|
|
31324
32026
|
};
|
|
32027
|
+
this.analyticsTelemetryConfig = {
|
|
32028
|
+
sdkName: SDK_NAME$2,
|
|
32029
|
+
sdkVersion: pkg$3.version,
|
|
32030
|
+
disabled: config.disableAnalytics === true
|
|
32031
|
+
};
|
|
31325
32032
|
}
|
|
31326
32033
|
/**
|
|
31327
32034
|
* Estimate the output amount and fees for a swap operation.
|
|
@@ -31362,8 +32069,12 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31362
32069
|
* console.log(`Fees:`, quote.fees)
|
|
31363
32070
|
* ```
|
|
31364
32071
|
*/ async estimate(params) {
|
|
32072
|
+
const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
|
|
31365
32073
|
return withErrorTelemetry(async ()=>estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
|
|
31366
32074
|
sourceChain: resolveChainName(params.from.chain),
|
|
32075
|
+
...destinationChain != null && {
|
|
32076
|
+
destinationChain
|
|
32077
|
+
},
|
|
31367
32078
|
tokenIn: params.tokenIn,
|
|
31368
32079
|
tokenOut: params.tokenOut
|
|
31369
32080
|
});
|
|
@@ -31422,16 +32133,54 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31422
32133
|
* ```
|
|
31423
32134
|
*/ async swap(params) {
|
|
31424
32135
|
let txHash;
|
|
31425
|
-
|
|
31426
|
-
|
|
31427
|
-
|
|
32136
|
+
let correlationId;
|
|
32137
|
+
// Shared context builder so the error resolver and the success emit stay in
|
|
32138
|
+
// lockstep — a field added here reaches both call sites. Reads the per-call
|
|
32139
|
+
// locals lazily, so txHash/correlationId (set during the swap) are captured
|
|
32140
|
+
// whenever it is invoked.
|
|
32141
|
+
// Destination chain is the primary attribution dimension for cross-chain
|
|
32142
|
+
// swaps; resolved once from the (static) params. Omitted for same-chain
|
|
32143
|
+
// swaps that leave `to.chain` unset (destination == source).
|
|
32144
|
+
const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
|
|
32145
|
+
const buildTelemetryContext = ()=>({
|
|
31428
32146
|
sourceChain: resolveChainName(params.from.chain),
|
|
32147
|
+
...destinationChain != null && {
|
|
32148
|
+
destinationChain
|
|
32149
|
+
},
|
|
31429
32150
|
tokenIn: params.tokenIn,
|
|
31430
32151
|
tokenOut: params.tokenOut,
|
|
31431
32152
|
...txHash != null && {
|
|
31432
32153
|
txHash
|
|
32154
|
+
},
|
|
32155
|
+
...correlationId != null && {
|
|
32156
|
+
correlationId
|
|
31433
32157
|
}
|
|
31434
|
-
})
|
|
32158
|
+
});
|
|
32159
|
+
const result = await withErrorTelemetry(async ()=>swap$1(this.context, params, (h, cId)=>{
|
|
32160
|
+
txHash = h;
|
|
32161
|
+
correlationId = cId;
|
|
32162
|
+
}), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, buildTelemetryContext);
|
|
32163
|
+
// withErrorTelemetry only emits on failure. Record the successful swap here
|
|
32164
|
+
// so the backend can attribute swap volume to a developer: the client event
|
|
32165
|
+
// carries the (burn) txHash + correlationId, which joins to the
|
|
32166
|
+
// server-emitted event carrying entity_id. Best-effort; never throws.
|
|
32167
|
+
//
|
|
32168
|
+
// Emit whenever a broadcast happened, i.e. we have the source/burn txHash.
|
|
32169
|
+
// For a cross-chain swap that is the source-chain burn (progress is still
|
|
32170
|
+
// PENDING while the destination mint settles) — we intentionally attribute
|
|
32171
|
+
// at broadcast using the burn txHash rather than tracking the destination
|
|
32172
|
+
// leg, which keeps the capture simple and self-contained in swap().
|
|
32173
|
+
//
|
|
32174
|
+
// Guard against a terminal-failure result: the EVM provider throws on
|
|
32175
|
+
// revert today, but the kit is provider-agnostic, so a provider that
|
|
32176
|
+
// returns a FAILED/NOT_FOUND result without throwing must not be recorded
|
|
32177
|
+
// as a successful swap. Routed through analyticsTelemetryConfig so it is
|
|
32178
|
+
// gated by disableAnalytics, independent of error reporting.
|
|
32179
|
+
const status = result.progress?.status;
|
|
32180
|
+
if (txHash != null && status !== 'FAILED' && status !== 'NOT_FOUND') {
|
|
32181
|
+
emitSuccessTelemetry(SWAP_EVENT_TYPES.SWAP, this.analyticsTelemetryConfig, buildTelemetryContext());
|
|
32182
|
+
}
|
|
32183
|
+
return result;
|
|
31435
32184
|
}
|
|
31436
32185
|
/**
|
|
31437
32186
|
* Fetch the current status of a swap from the Stablecoin Service.
|
|
@@ -31777,6 +32526,113 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
31777
32526
|
// Auto-register this kit for user agent tracking
|
|
31778
32527
|
registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
31779
32528
|
|
|
32529
|
+
/**
|
|
32530
|
+
* Creates a AppKit context.
|
|
32531
|
+
*
|
|
32532
|
+
* This function constructs a context object, initializes the actions registry
|
|
32533
|
+
* used for event handlers, and merges in any custom implementations provided
|
|
32534
|
+
* via params.
|
|
32535
|
+
*
|
|
32536
|
+
* @param params - Optional custom implementations to override defaults
|
|
32537
|
+
* @returns A AppKitContext
|
|
32538
|
+
*
|
|
32539
|
+
* @example
|
|
32540
|
+
* ```typescript
|
|
32541
|
+
* // Create context with all defaults
|
|
32542
|
+
* const defaultContext = createContext()
|
|
32543
|
+
*
|
|
32544
|
+
* // Create context with custom fee calculation
|
|
32545
|
+
* const customContext = createContext({
|
|
32546
|
+
* getFee: async (type, params) => {
|
|
32547
|
+
* if (type === 'bridge') {
|
|
32548
|
+
* // Custom bridge fee logic
|
|
32549
|
+
* return await calculateBridgeFee(params)
|
|
32550
|
+
* }
|
|
32551
|
+
* // Use default for other types
|
|
32552
|
+
* return defaultFeeCalculation(type, params)
|
|
32553
|
+
* }
|
|
32554
|
+
* })
|
|
32555
|
+
* ```
|
|
32556
|
+
*/ const createContext = (params = {})=>{
|
|
32557
|
+
return {
|
|
32558
|
+
...params,
|
|
32559
|
+
actions: {
|
|
32560
|
+
bridge: {},
|
|
32561
|
+
earn: {},
|
|
32562
|
+
...params.actions
|
|
32563
|
+
}
|
|
32564
|
+
};
|
|
32565
|
+
};
|
|
32566
|
+
|
|
32567
|
+
/**
|
|
32568
|
+
* Create a BridgeKit instance with optional developer fee configuration.
|
|
32569
|
+
*
|
|
32570
|
+
* This utility creates a BridgeKit instance that optionally includes developer
|
|
32571
|
+
* fee configuration based on the provided AppKit context. If the context
|
|
32572
|
+
* provides both `getFee` and `getFeeRecipient` methods, they will be configured
|
|
32573
|
+
* as developer fees in the BridgeKit instance using the `setCustomFeePolicy` method.
|
|
32574
|
+
*
|
|
32575
|
+
* The fee integration transforms string-based fees from the context into the
|
|
32576
|
+
* format expected by BridgeKit, enabling seamless fee calculation across both kits.
|
|
32577
|
+
*
|
|
32578
|
+
* @param context - The AppKit context containing optional fee methods
|
|
32579
|
+
* @returns A configured BridgeKit instance with or without developer fees
|
|
32580
|
+
*
|
|
32581
|
+
* @example
|
|
32582
|
+
* ```typescript
|
|
32583
|
+
* import { createBridgeKit } from '@circle-fin/app-kit/utils'
|
|
32584
|
+
* import { createContext } from '@circle-fin/app-kit/context'
|
|
32585
|
+
*
|
|
32586
|
+
* // Create context with fee methods
|
|
32587
|
+
* const context = createContext({
|
|
32588
|
+
* getFee: async (type, params) => '1000000', // 1 USDC in micro-units
|
|
32589
|
+
* getFeeRecipient: async (type, info) => '0x742d35Cc4634C0532925a3b8D1d7'
|
|
32590
|
+
* })
|
|
32591
|
+
*
|
|
32592
|
+
* // Create BridgeKit with developer fees
|
|
32593
|
+
* const bridgeKit = createBridgeKit(context)
|
|
32594
|
+
* ```
|
|
32595
|
+
*
|
|
32596
|
+
* @example
|
|
32597
|
+
* ```typescript
|
|
32598
|
+
* import { createBridgeKit } from '@circle-fin/app-kit/utils'
|
|
32599
|
+
* import { createContext } from '@circle-fin/app-kit/context'
|
|
32600
|
+
*
|
|
32601
|
+
* // Create context without fee methods
|
|
32602
|
+
* const context = createContext()
|
|
32603
|
+
*
|
|
32604
|
+
* // Create standard BridgeKit instance
|
|
32605
|
+
* const bridgeKit = createBridgeKit(context)
|
|
32606
|
+
* ```
|
|
32607
|
+
*/ const createBridgeKit = (context)=>{
|
|
32608
|
+
const getFee = context.getFee?.bind(context);
|
|
32609
|
+
const getFeeRecipient = context.getFeeRecipient?.bind(context);
|
|
32610
|
+
const hasBoth = typeof getFee === 'function' && typeof getFeeRecipient === 'function';
|
|
32611
|
+
const kit = new BridgeKit({
|
|
32612
|
+
...context.disableErrorReporting != null && {
|
|
32613
|
+
disableErrorReporting: context.disableErrorReporting
|
|
32614
|
+
},
|
|
32615
|
+
...context.headers != null && {
|
|
32616
|
+
headers: context.headers
|
|
32617
|
+
}
|
|
32618
|
+
});
|
|
32619
|
+
if (context.customFeePolicy?.bridge != null) {
|
|
32620
|
+
kit.setCustomFeePolicy(context.customFeePolicy.bridge);
|
|
32621
|
+
} else if (hasBoth) {
|
|
32622
|
+
kit.setCustomFeePolicy({
|
|
32623
|
+
calculateFee: async (params)=>{
|
|
32624
|
+
const feeStr = await getFee('bridge', params);
|
|
32625
|
+
return feeStr;
|
|
32626
|
+
},
|
|
32627
|
+
resolveFeeRecipientAddress: async (chain, params)=>await getFeeRecipient('bridge', {
|
|
32628
|
+
chain,
|
|
32629
|
+
params: params || {}
|
|
32630
|
+
})
|
|
32631
|
+
});
|
|
32632
|
+
}
|
|
32633
|
+
return kit;
|
|
32634
|
+
};
|
|
32635
|
+
|
|
31780
32636
|
/**
|
|
31781
32637
|
* Create a SwapKit instance with optional developer fee configuration.
|
|
31782
32638
|
*
|
|
@@ -31810,9 +32666,14 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
|
31810
32666
|
const kit = new SwapKit({
|
|
31811
32667
|
...context.disableErrorReporting != null && {
|
|
31812
32668
|
disableErrorReporting: context.disableErrorReporting
|
|
32669
|
+
},
|
|
32670
|
+
...context.disableAnalytics != null && {
|
|
32671
|
+
disableAnalytics: context.disableAnalytics
|
|
31813
32672
|
}
|
|
31814
32673
|
});
|
|
31815
|
-
if (
|
|
32674
|
+
if (context.customFeePolicy?.swap != null) {
|
|
32675
|
+
kit.setCustomFeePolicy(context.customFeePolicy.swap);
|
|
32676
|
+
} else if (hasBoth) {
|
|
31816
32677
|
kit.setCustomFeePolicy({
|
|
31817
32678
|
computeFee: async (params)=>{
|
|
31818
32679
|
// Adapt provider-level params (with tokenIn/tokenOut)
|
|
@@ -31871,7 +32732,7 @@ registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
|
31871
32732
|
};
|
|
31872
32733
|
|
|
31873
32734
|
var name$2 = "@circle-fin/earn-kit";
|
|
31874
|
-
var version$2 = "1.
|
|
32735
|
+
var version$2 = "1.5.0";
|
|
31875
32736
|
var pkg$2 = {
|
|
31876
32737
|
name: name$2,
|
|
31877
32738
|
version: version$2};
|
|
@@ -32514,6 +33375,683 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
32514
33375
|
return approvedToken;
|
|
32515
33376
|
}
|
|
32516
33377
|
|
|
33378
|
+
/**
|
|
33379
|
+
* Combined ABI of every inner instruction EarnKit can bundle inside an Adapter
|
|
33380
|
+
* `execute()` call. `decodeFunctionData` matches an instruction's calldata to
|
|
33381
|
+
* one of these functions by its 4-byte selector.
|
|
33382
|
+
*/ const earnInstructionAbi = [
|
|
33383
|
+
...erc4626VaultAbi,
|
|
33384
|
+
...feeTakerAbi,
|
|
33385
|
+
...merklDistributorAbi
|
|
33386
|
+
];
|
|
33387
|
+
/**
|
|
33388
|
+
* Extract and shallow-validate the `instructions` array from loosely-typed
|
|
33389
|
+
* signed execution params.
|
|
33390
|
+
*
|
|
33391
|
+
* The earn service schema validates `tokenIn`/`amountToApprove` and passes the
|
|
33392
|
+
* remaining instruction fields through untyped, so the params arrive as a plain
|
|
33393
|
+
* record; each accessed field is narrowed at runtime.
|
|
33394
|
+
*/ function requireInstructions(executionParams) {
|
|
33395
|
+
const instructions = executionParams['instructions'];
|
|
33396
|
+
if (!Array.isArray(instructions)) {
|
|
33397
|
+
throw decodeMismatchError('execution params are missing an instructions array', {
|
|
33398
|
+
instructions
|
|
33399
|
+
});
|
|
33400
|
+
}
|
|
33401
|
+
return instructions.map((instruction, index)=>{
|
|
33402
|
+
if (typeof instruction !== 'object' || instruction === null) {
|
|
33403
|
+
throw decodeMismatchError(`instructions[${index.toString()}] is not an object`, {
|
|
33404
|
+
index
|
|
33405
|
+
});
|
|
33406
|
+
}
|
|
33407
|
+
return instruction;
|
|
33408
|
+
});
|
|
33409
|
+
}
|
|
33410
|
+
/**
|
|
33411
|
+
* Build a fail-closed {@link KitError} for an earn decode or review failure.
|
|
33412
|
+
*
|
|
33413
|
+
* Marked non-recoverable: a mismatch between what would be shown and what would
|
|
33414
|
+
* be signed is never safe to retry, so the operation fails fast rather than
|
|
33415
|
+
* presenting misleading decoded data. `messagePrefix` names the failing stage
|
|
33416
|
+
* (calldata decode vs. review construction); callers bind it once and pass the
|
|
33417
|
+
* specific failure as `message`.
|
|
33418
|
+
*/ function failClosedEarnError(messagePrefix, message, trace) {
|
|
33419
|
+
return new KitError({
|
|
33420
|
+
...EarnError.INTERNAL_ERROR,
|
|
33421
|
+
recoverability: 'FATAL',
|
|
33422
|
+
message: `${messagePrefix}: ${message}`,
|
|
33423
|
+
cause: {
|
|
33424
|
+
trace
|
|
33425
|
+
}
|
|
33426
|
+
});
|
|
33427
|
+
}
|
|
33428
|
+
/**
|
|
33429
|
+
* Build a {@link KitError} for a decode or consistency failure.
|
|
33430
|
+
*
|
|
33431
|
+
* Thin wrapper over {@link failClosedEarnError} bound to the decode-stage
|
|
33432
|
+
* message prefix.
|
|
33433
|
+
*/ function decodeMismatchError(message, trace) {
|
|
33434
|
+
return failClosedEarnError('Unable to decode earn transaction', message, trace);
|
|
33435
|
+
}
|
|
33436
|
+
/**
|
|
33437
|
+
* Narrow an untyped value to a 0x-prefixed hex string, or fail fast.
|
|
33438
|
+
*/ function requireHex(value, path) {
|
|
33439
|
+
if (typeof value === 'string' && /^0x[0-9a-fA-F]*$/.test(value)) {
|
|
33440
|
+
return value;
|
|
33441
|
+
}
|
|
33442
|
+
throw decodeMismatchError(`${path} is not a hex string`, {
|
|
33443
|
+
path,
|
|
33444
|
+
value
|
|
33445
|
+
});
|
|
33446
|
+
}
|
|
33447
|
+
/**
|
|
33448
|
+
* Narrow an untyped value to a 20-byte EVM address, or fail fast.
|
|
33449
|
+
*/ function requireAddress(value, path) {
|
|
33450
|
+
if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value)) {
|
|
33451
|
+
return value;
|
|
33452
|
+
}
|
|
33453
|
+
throw decodeMismatchError(`${path} is not an address`, {
|
|
33454
|
+
path,
|
|
33455
|
+
value
|
|
33456
|
+
});
|
|
33457
|
+
}
|
|
33458
|
+
/**
|
|
33459
|
+
* Narrow an untyped `uint256`-like value (decimal string, bigint, or integer)
|
|
33460
|
+
* to a bigint, or fail fast.
|
|
33461
|
+
*/ function requireUint(value, path) {
|
|
33462
|
+
if (typeof value === 'bigint') {
|
|
33463
|
+
return value;
|
|
33464
|
+
}
|
|
33465
|
+
if (typeof value === 'string' && /^\d+$/.test(value)) {
|
|
33466
|
+
return BigInt(value);
|
|
33467
|
+
}
|
|
33468
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= 0) {
|
|
33469
|
+
return BigInt(value);
|
|
33470
|
+
}
|
|
33471
|
+
throw decodeMismatchError(`${path} is not a uint256 value`, {
|
|
33472
|
+
path,
|
|
33473
|
+
value
|
|
33474
|
+
});
|
|
33475
|
+
}
|
|
33476
|
+
/**
|
|
33477
|
+
* Decode inner instruction calldata against the earn instruction ABI, mapping a
|
|
33478
|
+
* viem decode failure (unknown selector, malformed args) to a fail-fast error.
|
|
33479
|
+
*/ function decodeEarnInstructionData(data, index) {
|
|
33480
|
+
try {
|
|
33481
|
+
return viem.decodeFunctionData({
|
|
33482
|
+
abi: earnInstructionAbi,
|
|
33483
|
+
data
|
|
33484
|
+
});
|
|
33485
|
+
} catch (error) {
|
|
33486
|
+
throw decodeMismatchError(`instructions[${index.toString()}] calldata is not a recognized earn instruction`, {
|
|
33487
|
+
index,
|
|
33488
|
+
selector: data.slice(0, 10),
|
|
33489
|
+
error: String(error)
|
|
33490
|
+
});
|
|
33491
|
+
}
|
|
33492
|
+
}
|
|
33493
|
+
/**
|
|
33494
|
+
* Decode Adapter `execute()` calldata, mapping a viem decode failure to a
|
|
33495
|
+
* fail-fast error.
|
|
33496
|
+
*/ function decodeExecuteCalldata(calldata) {
|
|
33497
|
+
try {
|
|
33498
|
+
return viem.decodeFunctionData({
|
|
33499
|
+
abi: adapterContractAbi,
|
|
33500
|
+
data: calldata
|
|
33501
|
+
});
|
|
33502
|
+
} catch (error) {
|
|
33503
|
+
throw decodeMismatchError('encoded calldata is not a valid Adapter execute() call', {
|
|
33504
|
+
error: String(error)
|
|
33505
|
+
});
|
|
33506
|
+
}
|
|
33507
|
+
}
|
|
33508
|
+
/**
|
|
33509
|
+
* Decode one inner instruction's calldata into a typed {@link
|
|
33510
|
+
* DecodedEarnInstruction}.
|
|
33511
|
+
*/ function decodeInstruction(instruction, index) {
|
|
33512
|
+
const target = requireAddress(instruction['target'], `instructions[${index.toString()}].target`);
|
|
33513
|
+
const data = requireHex(instruction['data'], `instructions[${index.toString()}].data`);
|
|
33514
|
+
const decoded = decodeEarnInstructionData(data, index);
|
|
33515
|
+
switch(decoded.functionName){
|
|
33516
|
+
case 'deposit':
|
|
33517
|
+
{
|
|
33518
|
+
const [assets, receiver] = decoded.args;
|
|
33519
|
+
return {
|
|
33520
|
+
method: 'deposit',
|
|
33521
|
+
vault: target,
|
|
33522
|
+
assets: assets.toString(),
|
|
33523
|
+
receiver
|
|
33524
|
+
};
|
|
33525
|
+
}
|
|
33526
|
+
case 'withdraw':
|
|
33527
|
+
{
|
|
33528
|
+
const [assets, receiver, owner] = decoded.args;
|
|
33529
|
+
return {
|
|
33530
|
+
method: 'withdraw',
|
|
33531
|
+
vault: target,
|
|
33532
|
+
assets: assets.toString(),
|
|
33533
|
+
receiver,
|
|
33534
|
+
owner
|
|
33535
|
+
};
|
|
33536
|
+
}
|
|
33537
|
+
case 'redeem':
|
|
33538
|
+
{
|
|
33539
|
+
const [shares, receiver, owner] = decoded.args;
|
|
33540
|
+
return {
|
|
33541
|
+
method: 'redeem',
|
|
33542
|
+
vault: target,
|
|
33543
|
+
shares: shares.toString(),
|
|
33544
|
+
receiver,
|
|
33545
|
+
owner
|
|
33546
|
+
};
|
|
33547
|
+
}
|
|
33548
|
+
case 'takeFeeERC20':
|
|
33549
|
+
{
|
|
33550
|
+
const [token, beneficiary, fee, kitType] = decoded.args;
|
|
33551
|
+
return {
|
|
33552
|
+
method: 'takeFeeERC20',
|
|
33553
|
+
feeTaker: target,
|
|
33554
|
+
token,
|
|
33555
|
+
beneficiary,
|
|
33556
|
+
fee: fee.toString(),
|
|
33557
|
+
kitType
|
|
33558
|
+
};
|
|
33559
|
+
}
|
|
33560
|
+
case 'claim':
|
|
33561
|
+
{
|
|
33562
|
+
const users = decoded.args[0];
|
|
33563
|
+
const tokens = decoded.args[1];
|
|
33564
|
+
const amounts = decoded.args[2];
|
|
33565
|
+
// Merkl claim(users, tokens, amounts, proofs) carries parallel arrays,
|
|
33566
|
+
// one entry per reward. Reject any length skew rather than padding with
|
|
33567
|
+
// zero amounts or dropping trailing entries, so the preview can never
|
|
33568
|
+
// misstate what is claimed or for whom.
|
|
33569
|
+
//
|
|
33570
|
+
// Note: Merkl `amounts` are the *cumulative lifetime* total claimable per
|
|
33571
|
+
// (user, token); the Distributor transfers only `amount - alreadyClaimed`.
|
|
33572
|
+
// This decode faithfully surfaces the signed cumulative value, which is
|
|
33573
|
+
// what `DecodedRewardClaim.amount` documents. See that type's doc.
|
|
33574
|
+
if (new Set([
|
|
33575
|
+
users.length,
|
|
33576
|
+
tokens.length,
|
|
33577
|
+
amounts.length
|
|
33578
|
+
]).size !== 1) {
|
|
33579
|
+
throw decodeMismatchError(`instructions[${index.toString()}] claim has mismatched recipient/token/amount lengths`, {
|
|
33580
|
+
index,
|
|
33581
|
+
users: users.length,
|
|
33582
|
+
tokens: tokens.length,
|
|
33583
|
+
amounts: amounts.length
|
|
33584
|
+
});
|
|
33585
|
+
}
|
|
33586
|
+
const rewards = tokens.map((token, rewardIndex)=>({
|
|
33587
|
+
recipient: requireAddress(users[rewardIndex], `instructions[${index.toString()}].claim.users[${rewardIndex.toString()}]`),
|
|
33588
|
+
address: token,
|
|
33589
|
+
amount: requireUint(amounts[rewardIndex], `instructions[${index.toString()}].claim.amounts[${rewardIndex.toString()}]`).toString()
|
|
33590
|
+
}));
|
|
33591
|
+
return {
|
|
33592
|
+
method: 'claim',
|
|
33593
|
+
distributor: target,
|
|
33594
|
+
rewards
|
|
33595
|
+
};
|
|
33596
|
+
}
|
|
33597
|
+
/* v8 ignore next 2 -- exhaustive switch; default is unreachable */ default:
|
|
33598
|
+
return assertNever$2(decoded);
|
|
33599
|
+
}
|
|
33600
|
+
}
|
|
33601
|
+
/**
|
|
33602
|
+
* Lift the primary values a wallet prompt cares about out of the decoded
|
|
33603
|
+
* instructions into a flat summary.
|
|
33604
|
+
*/ function buildSummary(instructions, envelope) {
|
|
33605
|
+
const summary = {};
|
|
33606
|
+
instructions.forEach((instruction, index)=>{
|
|
33607
|
+
switch(instruction.method){
|
|
33608
|
+
case 'deposit':
|
|
33609
|
+
case 'withdraw':
|
|
33610
|
+
case 'redeem':
|
|
33611
|
+
{
|
|
33612
|
+
// The summary lifts a single primary token movement to the top level.
|
|
33613
|
+
// An earn bundle carries exactly one deposit/withdraw/redeem today;
|
|
33614
|
+
// fail fast rather than silently overwriting an earlier one, which
|
|
33615
|
+
// would drop it from the wallet-facing preview.
|
|
33616
|
+
if (summary.token !== undefined) {
|
|
33617
|
+
throw decodeMismatchError('multiple deposit/withdraw/redeem instructions cannot be summarized into a single preview', {
|
|
33618
|
+
index
|
|
33619
|
+
});
|
|
33620
|
+
}
|
|
33621
|
+
// Pair the amount with the token it is actually denominated in so the
|
|
33622
|
+
// preview never folds two units into one entry:
|
|
33623
|
+
// - deposit: `assets` of the underlying asset pulled in (`tokenIn`)
|
|
33624
|
+
// - redeem: `shares` of the vault-share token burned (`tokenIn`)
|
|
33625
|
+
// - withdraw: `assets` of the underlying asset paid out (`tokenOut`).
|
|
33626
|
+
// `withdraw(assets)` counts the underlying received, not the shares
|
|
33627
|
+
// burned to produce it, so `tokenIn` (the share token) would misstate
|
|
33628
|
+
// the unit; the underlying is the instruction's `tokenOut`.
|
|
33629
|
+
const amount = instruction.method === 'redeem' ? instruction.shares : instruction.assets;
|
|
33630
|
+
const tokenField = instruction.method === 'withdraw' ? 'tokenOut' : 'tokenIn';
|
|
33631
|
+
summary.vault = instruction.vault;
|
|
33632
|
+
summary.receiver = instruction.receiver;
|
|
33633
|
+
summary.token = {
|
|
33634
|
+
address: requireAddress(envelope[index]?.[tokenField], `instructions[${index.toString()}].${tokenField}`),
|
|
33635
|
+
amount
|
|
33636
|
+
};
|
|
33637
|
+
break;
|
|
33638
|
+
}
|
|
33639
|
+
case 'takeFeeERC20':
|
|
33640
|
+
{
|
|
33641
|
+
// As with the vault case, a second fee would silently overwrite the
|
|
33642
|
+
// first and understate what is charged; fail fast instead.
|
|
33643
|
+
if (summary.fee !== undefined) {
|
|
33644
|
+
throw decodeMismatchError('multiple fee instructions cannot be summarized into a single preview', {
|
|
33645
|
+
index
|
|
33646
|
+
});
|
|
33647
|
+
}
|
|
33648
|
+
summary.fee = {
|
|
33649
|
+
address: instruction.token,
|
|
33650
|
+
amount: instruction.fee
|
|
33651
|
+
};
|
|
33652
|
+
break;
|
|
33653
|
+
}
|
|
33654
|
+
case 'claim':
|
|
33655
|
+
{
|
|
33656
|
+
// A second claim would silently drop the first from the preview
|
|
33657
|
+
// (rewards are already batched inside one Merkl claim); fail fast.
|
|
33658
|
+
if (summary.rewards !== undefined) {
|
|
33659
|
+
throw decodeMismatchError('multiple claim instructions cannot be summarized into a single preview', {
|
|
33660
|
+
index
|
|
33661
|
+
});
|
|
33662
|
+
}
|
|
33663
|
+
summary.rewards = instruction.rewards;
|
|
33664
|
+
break;
|
|
33665
|
+
}
|
|
33666
|
+
/* v8 ignore next 2 -- exhaustive switch; default is unreachable */ default:
|
|
33667
|
+
assertNever$2(instruction);
|
|
33668
|
+
}
|
|
33669
|
+
});
|
|
33670
|
+
return summary;
|
|
33671
|
+
}
|
|
33672
|
+
/**
|
|
33673
|
+
* Vault/claim instruction methods each declared action may decode to. The
|
|
33674
|
+
* mapping is many-to-one: a full withdrawal decodes to `redeem`, and any action
|
|
33675
|
+
* may carry an auxiliary `takeFeeERC20` alongside its primary instruction.
|
|
33676
|
+
*/ const ACTION_ALLOWED_METHODS = {
|
|
33677
|
+
deposit: new Set([
|
|
33678
|
+
'deposit'
|
|
33679
|
+
]),
|
|
33680
|
+
withdraw: new Set([
|
|
33681
|
+
'withdraw',
|
|
33682
|
+
'redeem'
|
|
33683
|
+
]),
|
|
33684
|
+
claimRewards: new Set([
|
|
33685
|
+
'claim'
|
|
33686
|
+
])
|
|
33687
|
+
};
|
|
33688
|
+
/**
|
|
33689
|
+
* Fail fast when the caller-declared `action` disagrees with the decoded
|
|
33690
|
+
* instructions, so the preview's headline can never mislabel what is signed
|
|
33691
|
+
* (e.g. a `deposit`-labeled call handed withdraw params). `takeFeeERC20` is an
|
|
33692
|
+
* auxiliary Circle-fee instruction and is allowed alongside any action.
|
|
33693
|
+
*/ function assertActionMatchesInstructions(action, instructions) {
|
|
33694
|
+
const allowed = ACTION_ALLOWED_METHODS[action];
|
|
33695
|
+
instructions.forEach((instruction, index)=>{
|
|
33696
|
+
if (instruction.method === 'takeFeeERC20') {
|
|
33697
|
+
return;
|
|
33698
|
+
}
|
|
33699
|
+
if (!allowed.has(instruction.method)) {
|
|
33700
|
+
throw decodeMismatchError(`decoded instruction method '${instruction.method}' does not match the '${action}' action`, {
|
|
33701
|
+
action,
|
|
33702
|
+
method: instruction.method,
|
|
33703
|
+
index
|
|
33704
|
+
});
|
|
33705
|
+
}
|
|
33706
|
+
});
|
|
33707
|
+
}
|
|
33708
|
+
/**
|
|
33709
|
+
* Decode a same-chain earn `execute()` bundle into a human-readable summary.
|
|
33710
|
+
*
|
|
33711
|
+
* Decodes every inner instruction in the service-signed `executionParams` — the
|
|
33712
|
+
* same object the SDK ABI-encodes into the transaction — so the returned decode
|
|
33713
|
+
* is a faithful, drift-free view of what the signer is authorizing: input token
|
|
33714
|
+
* and amount, target vault, receiver, any Circle fee, and claimed rewards. Fails
|
|
33715
|
+
* fast with a non-recoverable {@link KitError} if any instruction cannot be
|
|
33716
|
+
* decoded, rather than returning misleading data.
|
|
33717
|
+
*
|
|
33718
|
+
* @param input - Action, chain, adapter, and the signed execution params.
|
|
33719
|
+
* @returns The decoded transaction summary.
|
|
33720
|
+
* @throws {@link KitError} If an instruction's calldata cannot be decoded.
|
|
33721
|
+
*
|
|
33722
|
+
* @example
|
|
33723
|
+
* ```typescript
|
|
33724
|
+
* const decoded = decodeEarnExecute({
|
|
33725
|
+
* action: 'deposit',
|
|
33726
|
+
* chain: 'Arc_Testnet',
|
|
33727
|
+
* adapter: '0x7fb8c7260b63934d8da38af902f87ae6e284a845',
|
|
33728
|
+
* executionParams,
|
|
33729
|
+
* })
|
|
33730
|
+
* // decoded.summary -> { token: { address, amount }, vault, receiver }
|
|
33731
|
+
* ```
|
|
33732
|
+
*
|
|
33733
|
+
* @internal
|
|
33734
|
+
*/ function decodeEarnExecute(input) {
|
|
33735
|
+
const { action, chain, adapter, executionParams } = input;
|
|
33736
|
+
const envelope = requireInstructions(executionParams);
|
|
33737
|
+
const instructions = envelope.map((instruction, index)=>decodeInstruction(instruction, index));
|
|
33738
|
+
assertActionMatchesInstructions(action, instructions);
|
|
33739
|
+
return {
|
|
33740
|
+
action,
|
|
33741
|
+
chain,
|
|
33742
|
+
adapter,
|
|
33743
|
+
instructions,
|
|
33744
|
+
summary: buildSummary(instructions, envelope)
|
|
33745
|
+
};
|
|
33746
|
+
}
|
|
33747
|
+
/**
|
|
33748
|
+
* Assert that ABI-encoded Adapter `execute()` calldata encodes the same
|
|
33749
|
+
* instruction set as the service-signed execution params.
|
|
33750
|
+
*
|
|
33751
|
+
* Fail-fast preview check: the SDK encodes `execute(executeParams, ...)` locally,
|
|
33752
|
+
* so decoding those final bytes and comparing every field of each instruction
|
|
33753
|
+
* against the signed params proves the previewed instruction set matches what
|
|
33754
|
+
* will be signed. It compares `instructions[]` only — the outer `tokens`,
|
|
33755
|
+
* `execId`, `deadline`, and `metadata` are not re-compared here. The
|
|
33756
|
+
* authoritative integrity guarantee for the full signed struct is the on-chain
|
|
33757
|
+
* EIP-712 signature verification, which reverts if any signed field is altered.
|
|
33758
|
+
*
|
|
33759
|
+
* @param calldata - Encoded `execute()` calldata about to be signed.
|
|
33760
|
+
* @param executionParams - Service-signed execution params.
|
|
33761
|
+
* @throws {@link KitError} If the calldata is not an `execute()` call or any
|
|
33762
|
+
* instruction field differs from the signed params.
|
|
33763
|
+
*
|
|
33764
|
+
* @example
|
|
33765
|
+
* ```typescript
|
|
33766
|
+
* assertEarnCalldataMatchesExecuteParams(
|
|
33767
|
+
* prepared.getCallData().data,
|
|
33768
|
+
* executionParams,
|
|
33769
|
+
* )
|
|
33770
|
+
* ```
|
|
33771
|
+
*
|
|
33772
|
+
* @internal
|
|
33773
|
+
*/ function assertEarnCalldataMatchesExecuteParams(calldata, executionParams) {
|
|
33774
|
+
// adapterContractAbi declares only `execute`, so a successful decode is always
|
|
33775
|
+
// the execute() call; a non-execute selector throws inside
|
|
33776
|
+
// decodeExecuteCalldata above.
|
|
33777
|
+
const decoded = decodeExecuteCalldata(calldata);
|
|
33778
|
+
const encoded = decoded.args[0].instructions;
|
|
33779
|
+
const signed = requireInstructions(executionParams);
|
|
33780
|
+
// Compare each encoded instruction against its signed counterpart. Iterating
|
|
33781
|
+
// the encoded instructions and indexing the signed set keeps both mismatch
|
|
33782
|
+
// branches reachable: a signed set that is too short trips the guard below,
|
|
33783
|
+
// and one that is too long trips the post-loop check.
|
|
33784
|
+
encoded.forEach((instruction, index)=>{
|
|
33785
|
+
const signedInstruction = signed[index];
|
|
33786
|
+
if (signedInstruction === undefined) {
|
|
33787
|
+
throw decodeMismatchError(`signed params are missing instruction ${index.toString()}`, {
|
|
33788
|
+
index,
|
|
33789
|
+
encoded: encoded.length,
|
|
33790
|
+
signed: signed.length
|
|
33791
|
+
});
|
|
33792
|
+
}
|
|
33793
|
+
const path = `instructions[${index.toString()}]`;
|
|
33794
|
+
assertHexEqual(instruction.target, signedInstruction['target'], `${path}.target`);
|
|
33795
|
+
assertHexEqual(instruction.data, signedInstruction['data'], `${path}.data`);
|
|
33796
|
+
assertUintEqual(instruction.value, signedInstruction['value'], `${path}.value`);
|
|
33797
|
+
assertHexEqual(instruction.tokenIn, signedInstruction['tokenIn'], `${path}.tokenIn`);
|
|
33798
|
+
assertUintEqual(instruction.amountToApprove, signedInstruction['amountToApprove'], `${path}.amountToApprove`);
|
|
33799
|
+
assertHexEqual(instruction.tokenOut, signedInstruction['tokenOut'], `${path}.tokenOut`);
|
|
33800
|
+
assertUintEqual(instruction.minTokenOut, signedInstruction['minTokenOut'], `${path}.minTokenOut`);
|
|
33801
|
+
});
|
|
33802
|
+
if (signed.length > encoded.length) {
|
|
33803
|
+
throw decodeMismatchError('signed params contain more instructions than the encoded calldata', {
|
|
33804
|
+
encoded: encoded.length,
|
|
33805
|
+
signed: signed.length
|
|
33806
|
+
});
|
|
33807
|
+
}
|
|
33808
|
+
}
|
|
33809
|
+
/**
|
|
33810
|
+
* Assert two hex values are equal, case-insensitively (addresses and calldata).
|
|
33811
|
+
*/ function assertHexEqual(encoded, signed, path) {
|
|
33812
|
+
const signedHex = requireHex(signed, path);
|
|
33813
|
+
if (encoded.toLowerCase() !== signedHex.toLowerCase()) {
|
|
33814
|
+
throw decodeMismatchError(`${path} differs from signed params`, {
|
|
33815
|
+
path,
|
|
33816
|
+
encoded,
|
|
33817
|
+
signed: signedHex
|
|
33818
|
+
});
|
|
33819
|
+
}
|
|
33820
|
+
}
|
|
33821
|
+
/**
|
|
33822
|
+
* Assert an encoded bigint equals a signed `uint256`-like value.
|
|
33823
|
+
*/ function assertUintEqual(encoded, signed, path) {
|
|
33824
|
+
const signedUint = requireUint(signed, path);
|
|
33825
|
+
if (encoded !== signedUint) {
|
|
33826
|
+
throw decodeMismatchError(`${path} differs from signed params`, {
|
|
33827
|
+
path,
|
|
33828
|
+
encoded: encoded.toString(),
|
|
33829
|
+
signed: signedUint.toString()
|
|
33830
|
+
});
|
|
33831
|
+
}
|
|
33832
|
+
}
|
|
33833
|
+
|
|
33834
|
+
/**
|
|
33835
|
+
* Namespaced discriminator for the EarnKit authorization review.
|
|
33836
|
+
*
|
|
33837
|
+
* Applications match on this in an adapter `onBeforeAuthorize` hook to decide
|
|
33838
|
+
* whether the request carries EarnKit semantic data. Prefer the
|
|
33839
|
+
* {@link isEarnExecuteReview} type guard over comparing this string directly.
|
|
33840
|
+
*
|
|
33841
|
+
* @example
|
|
33842
|
+
* ```typescript
|
|
33843
|
+
* if (review?.kind === EARN_EXECUTE_REVIEW_KIND) { … }
|
|
33844
|
+
* ```
|
|
33845
|
+
*/ const EARN_EXECUTE_REVIEW_KIND = 'earn.execute';
|
|
33846
|
+
|
|
33847
|
+
/**
|
|
33848
|
+
* Build a fail-closed {@link KitError} for a review-construction failure.
|
|
33849
|
+
*
|
|
33850
|
+
* Marked non-recoverable: a review that cannot prove the calldata matches the
|
|
33851
|
+
* signed operation must abort authorization, never retry with misleading data.
|
|
33852
|
+
*/ function reviewError(message, trace) {
|
|
33853
|
+
return failClosedEarnError('Unable to build earn authorization review', message, trace);
|
|
33854
|
+
}
|
|
33855
|
+
/**
|
|
33856
|
+
* Narrow the canonical authorization payload to the single Adapter `execute()`
|
|
33857
|
+
* call a same-chain earn operation authorizes.
|
|
33858
|
+
*
|
|
33859
|
+
* Same-chain deposit, withdraw, and claim-rewards each authorize exactly one
|
|
33860
|
+
* `evm-calls` payload carrying one call. Anything else (typed data, a batch,
|
|
33861
|
+
* an empty call list) means this descriptor was attached to the wrong
|
|
33862
|
+
* authorization unit, so fail closed rather than decode misleading data.
|
|
33863
|
+
*/ function assertSingleEvmCallPayload(payload) {
|
|
33864
|
+
if (payload.type !== 'evm-calls') {
|
|
33865
|
+
throw reviewError(`expected an 'evm-calls' payload but received '${payload.type}'`, {
|
|
33866
|
+
type: payload.type
|
|
33867
|
+
});
|
|
33868
|
+
}
|
|
33869
|
+
const [call, ...rest] = payload.calls;
|
|
33870
|
+
if (call === undefined) {
|
|
33871
|
+
throw reviewError('the evm-calls payload contains no calls to review', {
|
|
33872
|
+
callCount: payload.calls.length
|
|
33873
|
+
});
|
|
33874
|
+
}
|
|
33875
|
+
if (rest.length > 0) {
|
|
33876
|
+
throw reviewError('a same-chain earn operation authorizes exactly one Adapter execute() call', {
|
|
33877
|
+
callCount: payload.calls.length
|
|
33878
|
+
});
|
|
33879
|
+
}
|
|
33880
|
+
return call;
|
|
33881
|
+
}
|
|
33882
|
+
/**
|
|
33883
|
+
* Select the final earn `execute()` call from an atomic Earn batch.
|
|
33884
|
+
*
|
|
33885
|
+
* Same-chain batched deposit/withdraw authorizes either `[execute]` when the
|
|
33886
|
+
* current allowance is sufficient, or `[approve, execute]` when a top-up is
|
|
33887
|
+
* required. Any other shape means the descriptor was attached to an
|
|
33888
|
+
* unexpected authorization unit, so fail closed.
|
|
33889
|
+
*/ function assertBatchedEarnExecuteCall(payload) {
|
|
33890
|
+
if (payload.type !== 'evm-calls') {
|
|
33891
|
+
throw reviewError(`expected an 'evm-calls' payload but received '${payload.type}'`, {
|
|
33892
|
+
type: payload.type
|
|
33893
|
+
});
|
|
33894
|
+
}
|
|
33895
|
+
if (payload.calls.length !== 1 && payload.calls.length !== 2) {
|
|
33896
|
+
throw reviewError('a batched earn operation authorizes [execute] or [approve, execute]', {
|
|
33897
|
+
callCount: payload.calls.length
|
|
33898
|
+
});
|
|
33899
|
+
}
|
|
33900
|
+
const executeCall = payload.calls.at(-1);
|
|
33901
|
+
if (executeCall === undefined) {
|
|
33902
|
+
throw reviewError('the earn batch contains no execute call to review', {
|
|
33903
|
+
callCount: payload.calls.length
|
|
33904
|
+
});
|
|
33905
|
+
}
|
|
33906
|
+
return executeCall;
|
|
33907
|
+
}
|
|
33908
|
+
/**
|
|
33909
|
+
* Map a canonical {@link EvmCall} to the {@link EarnEncodedTransaction} preview
|
|
33910
|
+
* shape, failing closed when the earn `execute()` call carries no calldata.
|
|
33911
|
+
*/ function toEarnEncodedTransaction(call) {
|
|
33912
|
+
if (call.data === undefined) {
|
|
33913
|
+
throw reviewError('the earn execute() call is missing calldata', {
|
|
33914
|
+
to: call.to
|
|
33915
|
+
});
|
|
33916
|
+
}
|
|
33917
|
+
return {
|
|
33918
|
+
to: call.to,
|
|
33919
|
+
data: call.data,
|
|
33920
|
+
...call.value !== undefined && {
|
|
33921
|
+
value: call.value
|
|
33922
|
+
}
|
|
33923
|
+
};
|
|
33924
|
+
}
|
|
33925
|
+
/**
|
|
33926
|
+
* Create an Earn authorization descriptor using the supplied canonical-payload
|
|
33927
|
+
* call selector.
|
|
33928
|
+
*
|
|
33929
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
33930
|
+
* @param selectCall - Fail-closed selector for the execute call under review.
|
|
33931
|
+
* @returns A lazy descriptor that decodes and verifies the selected call.
|
|
33932
|
+
*
|
|
33933
|
+
* @internal
|
|
33934
|
+
*/ function createEarnExecuteDescriptor(input, selectCall) {
|
|
33935
|
+
const { action, chain, executionParams } = input;
|
|
33936
|
+
const createReview = (payload)=>{
|
|
33937
|
+
const call = selectCall(payload);
|
|
33938
|
+
const encoded = toEarnEncodedTransaction(call);
|
|
33939
|
+
const decoded = decodeEarnExecute({
|
|
33940
|
+
action,
|
|
33941
|
+
chain,
|
|
33942
|
+
adapter: encoded.to,
|
|
33943
|
+
executionParams
|
|
33944
|
+
});
|
|
33945
|
+
// Prove the calldata about to be signed encodes the same instruction set as
|
|
33946
|
+
// the service-signed params. Throwing here aborts before the wallet prompt.
|
|
33947
|
+
assertEarnCalldataMatchesExecuteParams(encoded.data, executionParams);
|
|
33948
|
+
const review = {
|
|
33949
|
+
kind: EARN_EXECUTE_REVIEW_KIND,
|
|
33950
|
+
data: {
|
|
33951
|
+
encoded,
|
|
33952
|
+
decoded
|
|
33953
|
+
}
|
|
33954
|
+
};
|
|
33955
|
+
return review;
|
|
33956
|
+
};
|
|
33957
|
+
return {
|
|
33958
|
+
createReview
|
|
33959
|
+
};
|
|
33960
|
+
}
|
|
33961
|
+
/**
|
|
33962
|
+
* Build the lazy `earn.execute` authorization descriptor for a same-chain earn
|
|
33963
|
+
* action.
|
|
33964
|
+
*
|
|
33965
|
+
* The returned descriptor carries only a `createReview` factory — no intent
|
|
33966
|
+
* override, because the adapter's action system supplies the intent from the
|
|
33967
|
+
* action key. The factory is evaluated at most once, and only when the
|
|
33968
|
+
* application configured an adapter `onBeforeAuthorize` hook. When it runs it:
|
|
33969
|
+
*
|
|
33970
|
+
* 1. narrows the canonical payload to its single Adapter `execute()` call;
|
|
33971
|
+
* 2. lifts that call into an {@link EarnEncodedTransaction};
|
|
33972
|
+
* 3. decodes it into a `DecodedEarnTx`; and
|
|
33973
|
+
* 4. asserts the decoded calldata matches the service-signed params, throwing
|
|
33974
|
+
* (aborting authorization before the wallet or signer) on any mismatch.
|
|
33975
|
+
*
|
|
33976
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
33977
|
+
* @returns An authorization descriptor to pass as the fourth `prepareAction`
|
|
33978
|
+
* argument for the final earn action only (never the allowance approval).
|
|
33979
|
+
* @throws {@link KitError} From the review factory when the payload is not a
|
|
33980
|
+
* single earn `execute()` call or the calldata diverges from the signed
|
|
33981
|
+
* params. The throw surfaces through the adapter gate before authorization.
|
|
33982
|
+
*
|
|
33983
|
+
* @example
|
|
33984
|
+
* ```typescript
|
|
33985
|
+
* const descriptor = buildEarnExecuteDescriptor({
|
|
33986
|
+
* action: 'deposit',
|
|
33987
|
+
* chain: 'Arc_Testnet',
|
|
33988
|
+
* executionParams,
|
|
33989
|
+
* })
|
|
33990
|
+
* await adapter.prepareAction('earn.deposit', actionParams, ctx, {
|
|
33991
|
+
* authorization: descriptor,
|
|
33992
|
+
* })
|
|
33993
|
+
* ```
|
|
33994
|
+
*
|
|
33995
|
+
* @internal
|
|
33996
|
+
*/ function buildEarnExecuteDescriptor(input) {
|
|
33997
|
+
return createEarnExecuteDescriptor(input, assertSingleEvmCallPayload);
|
|
33998
|
+
}
|
|
33999
|
+
/**
|
|
34000
|
+
* Build a lazy `earn.execute` authorization descriptor for an atomic Earn
|
|
34001
|
+
* batch containing either `[execute]` or `[approve, execute]`.
|
|
34002
|
+
*
|
|
34003
|
+
* The review always decodes and verifies the final call against the
|
|
34004
|
+
* service-signed execution params. Unexpected payload types and call counts
|
|
34005
|
+
* fail closed before wallet authorization.
|
|
34006
|
+
*
|
|
34007
|
+
* @param input - The action, chain, and service-signed execution params.
|
|
34008
|
+
* @returns A descriptor suitable for `batchExecute` authorization options.
|
|
34009
|
+
* @throws {@link KitError} From the lazy review factory when the batch shape or
|
|
34010
|
+
* final execute calldata cannot be verified.
|
|
34011
|
+
*
|
|
34012
|
+
* @internal
|
|
34013
|
+
*/ function buildBatchedEarnExecuteDescriptor(input) {
|
|
34014
|
+
return createEarnExecuteDescriptor(input, assertBatchedEarnExecuteCall);
|
|
34015
|
+
}
|
|
34016
|
+
/**
|
|
34017
|
+
* Type guard: narrow an adapter authorization review to an EarnKit
|
|
34018
|
+
* `earn.execute` review.
|
|
34019
|
+
*
|
|
34020
|
+
* Use this inside an adapter `onBeforeAuthorize` hook to detect whether the
|
|
34021
|
+
* request carries EarnKit semantic data before reading it, instead of
|
|
34022
|
+
* comparing `review.kind` by hand. Robust against plain-JavaScript callers:
|
|
34023
|
+
* accepts `unknown` and checks the shape at runtime.
|
|
34024
|
+
*
|
|
34025
|
+
* @param review - The `review` field from an `AuthorizationRequest`, or any
|
|
34026
|
+
* value.
|
|
34027
|
+
* @returns `true` when `review` is an `earn.execute` review with the expected
|
|
34028
|
+
* `{ encoded, decoded }` data shape.
|
|
34029
|
+
*
|
|
34030
|
+
* @example
|
|
34031
|
+
* ```typescript
|
|
34032
|
+
* onBeforeAuthorize: async ({ review }) => {
|
|
34033
|
+
* if (isEarnExecuteReview(review)) {
|
|
34034
|
+
* await showEarnConfirmation(review.data.decoded.summary)
|
|
34035
|
+
* }
|
|
34036
|
+
* return 'approve'
|
|
34037
|
+
* }
|
|
34038
|
+
* ```
|
|
34039
|
+
*/ function isEarnExecuteReview(review) {
|
|
34040
|
+
if (typeof review !== 'object' || review === null) {
|
|
34041
|
+
return false;
|
|
34042
|
+
}
|
|
34043
|
+
const candidate = review;
|
|
34044
|
+
if (candidate.kind !== EARN_EXECUTE_REVIEW_KIND) {
|
|
34045
|
+
return false;
|
|
34046
|
+
}
|
|
34047
|
+
const data = candidate.data;
|
|
34048
|
+
if (typeof data !== 'object' || data === null) {
|
|
34049
|
+
return false;
|
|
34050
|
+
}
|
|
34051
|
+
const { encoded, decoded } = data;
|
|
34052
|
+
return typeof encoded === 'object' && encoded !== null && typeof decoded === 'object' && decoded !== null;
|
|
34053
|
+
}
|
|
34054
|
+
|
|
32517
34055
|
/**
|
|
32518
34056
|
* Prepare an earn adapter action, execute it, wait for confirmation, and
|
|
32519
34057
|
* throw a structured revert error if the receipt status is `'reverted'`.
|
|
@@ -32540,16 +34078,37 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
32540
34078
|
* address,
|
|
32541
34079
|
* actionKey: 'earn.deposit',
|
|
32542
34080
|
* actionParams: { executeParams, tokenInputs, signature },
|
|
34081
|
+
* action: 'deposit',
|
|
34082
|
+
* executionParams,
|
|
32543
34083
|
* revertMessage: 'Earn deposit reverted on-chain',
|
|
32544
34084
|
* })
|
|
32545
34085
|
* ```
|
|
32546
34086
|
*
|
|
32547
34087
|
* @internal
|
|
32548
34088
|
*/ async function executeEarnAction(params) {
|
|
32549
|
-
const { adapter, chain, address, actionKey, actionParams, revertMessage } = params;
|
|
34089
|
+
const { adapter, chain, address, actionKey, actionParams, action, executionParams, revertMessage } = params;
|
|
34090
|
+
// Attach the lazy `earn.execute` review to the final earn action only (never
|
|
34091
|
+
// the allowance approval, which runs on a separate path). The adapter's
|
|
34092
|
+
// action system supplies the intent from `actionKey`, so the descriptor
|
|
34093
|
+
// carries only the review factory. The factory is evaluated at most once,
|
|
34094
|
+
// and only when the application configured an `onBeforeAuthorize` hook.
|
|
34095
|
+
const authorization = buildEarnExecuteDescriptor({
|
|
34096
|
+
action,
|
|
34097
|
+
// The provider validates the chain is Earn-supported in
|
|
34098
|
+
// `resolveAdapterContext` before reaching execute, so the concrete chain
|
|
34099
|
+
// identifier is a valid `EarnChainIdentifier`. It is carried through to the
|
|
34100
|
+
// decoded preview's display `chain` field only.
|
|
34101
|
+
chain: chain.chain,
|
|
34102
|
+
executionParams
|
|
34103
|
+
});
|
|
34104
|
+
// The abstract `Adapter.prepareAction` is 3-arg; the fourth authorization
|
|
34105
|
+
// argument lives on the `withLegacyCompat` wrapper that produced the concrete
|
|
34106
|
+
// adapter passed here. Narrow the single seam that threads the descriptor.
|
|
32550
34107
|
const prepared = await adapter.prepareAction(actionKey, actionParams, {
|
|
32551
34108
|
chain,
|
|
32552
34109
|
address
|
|
34110
|
+
}, {
|
|
34111
|
+
authorization
|
|
32553
34112
|
});
|
|
32554
34113
|
const gasLimitOverride = await estimateBufferedGasLimit(prepared);
|
|
32555
34114
|
const txHash = prepared.type === 'evm' && gasLimitOverride !== undefined ? await prepared.execute({
|
|
@@ -32574,6 +34133,278 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
32574
34133
|
};
|
|
32575
34134
|
}
|
|
32576
34135
|
|
|
34136
|
+
/**
|
|
34137
|
+
* Decide whether a same-chain earn action should be submitted as a single
|
|
34138
|
+
* atomic batch.
|
|
34139
|
+
*
|
|
34140
|
+
* Returns `true` only when the consumer has not opted out
|
|
34141
|
+
* (`batchTransactions !== false`), the source chain is EVM, the adapter
|
|
34142
|
+
* structurally exposes the shared batch methods, and the wallet reports atomic
|
|
34143
|
+
* batch support. `address` is forwarded as `fromAddress` so developer-controlled
|
|
34144
|
+
* adapters can probe the specific wallet. Any thrown capability probe is
|
|
34145
|
+
* treated as "no support".
|
|
34146
|
+
*
|
|
34147
|
+
* @param params - Adapter, chain, address, and the resolved `batchTransactions` flag.
|
|
34148
|
+
* @returns `true` when batched execution should be attempted.
|
|
34149
|
+
*
|
|
34150
|
+
* @example
|
|
34151
|
+
* ```typescript
|
|
34152
|
+
* if (await shouldUseBatchedEarnAction({ adapter, chain, address, batchTransactions })) {
|
|
34153
|
+
* // take the batched approve + execute path
|
|
34154
|
+
* }
|
|
34155
|
+
* ```
|
|
34156
|
+
*
|
|
34157
|
+
* @internal
|
|
34158
|
+
*/ async function shouldUseBatchedEarnAction(params) {
|
|
34159
|
+
const { adapter, chain, address, batchTransactions } = params;
|
|
34160
|
+
if (batchTransactions === false) {
|
|
34161
|
+
return false;
|
|
34162
|
+
}
|
|
34163
|
+
if (chain.type !== 'evm') {
|
|
34164
|
+
return false;
|
|
34165
|
+
}
|
|
34166
|
+
const candidate = adapter;
|
|
34167
|
+
if (typeof candidate.supportsAtomicBatch !== 'function' || typeof candidate.batchExecute !== 'function') {
|
|
34168
|
+
return false;
|
|
34169
|
+
}
|
|
34170
|
+
try {
|
|
34171
|
+
return await candidate.supportsAtomicBatch(chain, {
|
|
34172
|
+
fromAddress: address
|
|
34173
|
+
});
|
|
34174
|
+
} catch {
|
|
34175
|
+
return false;
|
|
34176
|
+
}
|
|
34177
|
+
}
|
|
34178
|
+
async function buildSuccessfulBatchResult(adapter, chain, receipt, batchId, revertMessage) {
|
|
34179
|
+
const transaction = {
|
|
34180
|
+
txHash: receipt.txHash,
|
|
34181
|
+
explorerUrl: buildExplorerUrl(chain, receipt.txHash)
|
|
34182
|
+
};
|
|
34183
|
+
let confirmed;
|
|
34184
|
+
try {
|
|
34185
|
+
confirmed = await adapter.waitForTransaction(receipt.txHash, {
|
|
34186
|
+
confirmations: 1
|
|
34187
|
+
}, chain);
|
|
34188
|
+
} catch {
|
|
34189
|
+
// The batch adapter already confirmed success. Receipt enrichment is
|
|
34190
|
+
// telemetry-only, so an additional RPC failure must not turn an accepted
|
|
34191
|
+
// money-moving operation into a retryable business failure.
|
|
34192
|
+
return transaction;
|
|
34193
|
+
}
|
|
34194
|
+
if (confirmed.status === 'reverted') {
|
|
34195
|
+
throw createTransactionRevertedError(chain.name, revertMessage, {
|
|
34196
|
+
batchId
|
|
34197
|
+
}, receipt.txHash, transaction.explorerUrl);
|
|
34198
|
+
}
|
|
34199
|
+
return {
|
|
34200
|
+
...transaction,
|
|
34201
|
+
...confirmed.gasUsed !== undefined && {
|
|
34202
|
+
gasUsed: confirmed.gasUsed
|
|
34203
|
+
},
|
|
34204
|
+
...confirmed.effectiveGasPrice !== undefined && {
|
|
34205
|
+
effectiveGasPrice: confirmed.effectiveGasPrice
|
|
34206
|
+
}
|
|
34207
|
+
};
|
|
34208
|
+
}
|
|
34209
|
+
function throwBatchFailure(result, executeReceipt, chain, actionKey, revertMessage) {
|
|
34210
|
+
const cause = result.error;
|
|
34211
|
+
if (result.statusCode === 400) {
|
|
34212
|
+
throw new KitError({
|
|
34213
|
+
...RpcError.ENDPOINT_ERROR,
|
|
34214
|
+
recoverability: 'RETRYABLE',
|
|
34215
|
+
message: `Batched earn ${actionKey} failed off-chain before inclusion (batch ${result.batchId}).`,
|
|
34216
|
+
cause: {
|
|
34217
|
+
trace: {
|
|
34218
|
+
batchId: result.batchId,
|
|
34219
|
+
statusCode: result.statusCode,
|
|
34220
|
+
cause
|
|
34221
|
+
}
|
|
34222
|
+
}
|
|
34223
|
+
});
|
|
34224
|
+
}
|
|
34225
|
+
const causeTrace = cause instanceof KitError && typeof cause.cause?.trace === 'object' && cause.cause.trace !== null ? cause.cause.trace : undefined;
|
|
34226
|
+
if (cause instanceof KitError && causeTrace?.['kind'] === 'failed_offchain') {
|
|
34227
|
+
throw cause;
|
|
34228
|
+
}
|
|
34229
|
+
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 !== '';
|
|
34230
|
+
if (isConfirmedRevert) {
|
|
34231
|
+
throw createTransactionRevertedError(chain.name, revertMessage, {
|
|
34232
|
+
batchId: result.batchId,
|
|
34233
|
+
error: cause
|
|
34234
|
+
});
|
|
34235
|
+
}
|
|
34236
|
+
throw new KitError({
|
|
34237
|
+
...NetworkError.TIMEOUT,
|
|
34238
|
+
recoverability: 'FATAL',
|
|
34239
|
+
message: `Batched earn ${actionKey} was submitted (batch ${result.batchId}) but its outcome could not be confirmed; check the transaction status before retrying.`,
|
|
34240
|
+
cause: {
|
|
34241
|
+
trace: {
|
|
34242
|
+
batchId: result.batchId,
|
|
34243
|
+
cause
|
|
34244
|
+
}
|
|
34245
|
+
}
|
|
34246
|
+
});
|
|
34247
|
+
}
|
|
34248
|
+
/**
|
|
34249
|
+
* Execute the `approve` and `execute` steps of a same-chain earn action as a
|
|
34250
|
+
* single atomic batch.
|
|
34251
|
+
*
|
|
34252
|
+
* Prepare both `PreparedChainRequest` objects upfront, extract their raw call
|
|
34253
|
+
* data via `getCallData()`, then submit both through the adapter's shared
|
|
34254
|
+
* `batchExecute`. `address` is forwarded as `opts.fromAddress` so
|
|
34255
|
+
* developer-controlled adapters batch on behalf of the right wallet;
|
|
34256
|
+
* `idempotencyKey` is forwarded for adapters that deduplicate ambiguous
|
|
34257
|
+
* submissions (the Circle developer-controlled adapter reuses the Earn
|
|
34258
|
+
* execution id); other adapters may ignore either option. Reused by both the
|
|
34259
|
+
* deposit and withdraw flows via the `actionKey` parameter.
|
|
34260
|
+
*
|
|
34261
|
+
* @param params - Adapter, chain, action key, signed payload, and approval inputs.
|
|
34262
|
+
* @returns The confirmed execute transaction hash, explorer URL, and receipt
|
|
34263
|
+
* gas data when the adapter can retrieve it.
|
|
34264
|
+
* @throws {@link KitError} when the source chain is not EVM.
|
|
34265
|
+
* @throws {@link KitError} when calldata extraction (`getCallData`) is not
|
|
34266
|
+
* supported by the prepared requests.
|
|
34267
|
+
* @throws {@link KitError} when the batch reverts on-chain (a confirmed
|
|
34268
|
+
* terminal revert), carrying `batchId`.
|
|
34269
|
+
* @throws {@link KitError} RETRYABLE when EIP-5792 reports an off-chain
|
|
34270
|
+
* failure carrying `batchId` and status code `400`; no call was included.
|
|
34271
|
+
* @throws {@link KitError} FATAL `NetworkError.TIMEOUT` when the batch was
|
|
34272
|
+
* submitted but its outcome could not be confirmed (poll timeout or any
|
|
34273
|
+
* other non-revert post-submission failure); carries `batchId` so the caller
|
|
34274
|
+
* can check transaction status before retrying.
|
|
34275
|
+
* @remarks
|
|
34276
|
+
* Once the batch has been submitted this function does not fall back to the
|
|
34277
|
+
* sequential path — the batch is already on its way, so a fallback would risk
|
|
34278
|
+
* double-spend. Post-submission failures surface through the adapter's batch
|
|
34279
|
+
* result: a confirmed on-chain revert (Circle: a `TRANSACTION_REVERTED` cause;
|
|
34280
|
+
* Viem: status code `500`/`600`) is thrown as a revert error, status code `400`
|
|
34281
|
+
* is reported as a retryable off-chain failure, and any other unconfirmed
|
|
34282
|
+
* outcome is thrown as a FATAL timeout error carrying `batchId`.
|
|
34283
|
+
*
|
|
34284
|
+
* @example
|
|
34285
|
+
* ```typescript
|
|
34286
|
+
* const { txHash, explorerUrl } = await executeBatchedEarnAction({
|
|
34287
|
+
* adapter,
|
|
34288
|
+
* chain,
|
|
34289
|
+
* address,
|
|
34290
|
+
* actionKey: 'earn.deposit',
|
|
34291
|
+
* executeParams,
|
|
34292
|
+
* tokenInputs,
|
|
34293
|
+
* signature,
|
|
34294
|
+
* approvalToken: usdcAddress,
|
|
34295
|
+
* delegate: adapterContractAddress,
|
|
34296
|
+
* requiredAllowance: 1_000_000n,
|
|
34297
|
+
* idempotencyKey: '550e8400-e29b-41d4-a716-446655440000',
|
|
34298
|
+
* revertMessage: 'Earn deposit reverted on-chain',
|
|
34299
|
+
* })
|
|
34300
|
+
* ```
|
|
34301
|
+
*
|
|
34302
|
+
* @internal
|
|
34303
|
+
*/ async function executeBatchedEarnAction(params) {
|
|
34304
|
+
const { adapter, chain, address, actionKey, executeParams, tokenInputs, signature, approvalToken, delegate, requiredAllowance, idempotencyKey, revertMessage } = params;
|
|
34305
|
+
if (chain.type !== 'evm') {
|
|
34306
|
+
throw new KitError({
|
|
34307
|
+
...InputError.INVALID_CHAIN,
|
|
34308
|
+
recoverability: 'FATAL',
|
|
34309
|
+
message: 'Batched earn execution is only supported on EVM chains.'
|
|
34310
|
+
});
|
|
34311
|
+
}
|
|
34312
|
+
const evmChain = chain;
|
|
34313
|
+
const batchAdapter = adapter;
|
|
34314
|
+
// Read the current allowance so the approval tops up only the missing amount.
|
|
34315
|
+
// When the existing allowance already covers the payload, skip the approve
|
|
34316
|
+
// call and batch only the execute — this mirrors the sequential
|
|
34317
|
+
// approveAllowanceIfNeeded guard and avoids an increaseAllowance underflow
|
|
34318
|
+
// (requiredAllowance - currentAllowance would be negative, which reverts as
|
|
34319
|
+
// an out-of-range uint256).
|
|
34320
|
+
const allowancePrepared = await adapter.prepareAction('token.allowance', {
|
|
34321
|
+
tokenAddress: approvalToken,
|
|
34322
|
+
delegate
|
|
34323
|
+
}, {
|
|
34324
|
+
chain,
|
|
34325
|
+
address
|
|
34326
|
+
});
|
|
34327
|
+
const currentAllowance = parseAllowanceResponse(await allowancePrepared.execute());
|
|
34328
|
+
const approvalNeeded = currentAllowance < requiredAllowance;
|
|
34329
|
+
const executePrepared = await adapter.prepareAction(actionKey, {
|
|
34330
|
+
executeParams,
|
|
34331
|
+
tokenInputs,
|
|
34332
|
+
signature
|
|
34333
|
+
}, {
|
|
34334
|
+
chain,
|
|
34335
|
+
address
|
|
34336
|
+
});
|
|
34337
|
+
const approvePrepared = approvalNeeded ? await prepareApprovalAction({
|
|
34338
|
+
adapter,
|
|
34339
|
+
chain,
|
|
34340
|
+
address,
|
|
34341
|
+
tokenAddress: approvalToken,
|
|
34342
|
+
delegate,
|
|
34343
|
+
currentAllowance,
|
|
34344
|
+
requiredAllowance
|
|
34345
|
+
}) : undefined;
|
|
34346
|
+
if (executePrepared.type !== 'evm' || !executePrepared.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
|
+
if (approvePrepared !== undefined && (approvePrepared.type !== 'evm' || !approvePrepared.getCallData)) {
|
|
34354
|
+
throw new KitError({
|
|
34355
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
34356
|
+
recoverability: 'FATAL',
|
|
34357
|
+
message: 'Batched earn execution requires EVM prepared requests with getCallData() support.'
|
|
34358
|
+
});
|
|
34359
|
+
}
|
|
34360
|
+
const executeCallData = executePrepared.getCallData();
|
|
34361
|
+
// Prepend the approve call only when an allowance top-up is required.
|
|
34362
|
+
const calls = approvePrepared?.type === 'evm' && approvePrepared.getCallData ? [
|
|
34363
|
+
approvePrepared.getCallData(),
|
|
34364
|
+
executeCallData
|
|
34365
|
+
] : [
|
|
34366
|
+
executeCallData
|
|
34367
|
+
];
|
|
34368
|
+
const authorization = buildBatchedEarnExecuteDescriptor({
|
|
34369
|
+
action: actionKey === 'earn.deposit' ? 'deposit' : 'withdraw',
|
|
34370
|
+
chain: evmChain.chain,
|
|
34371
|
+
executionParams: executeParams
|
|
34372
|
+
});
|
|
34373
|
+
const result = await batchAdapter.batchExecute(calls, evmChain, {
|
|
34374
|
+
fromAddress: address,
|
|
34375
|
+
idempotencyKey,
|
|
34376
|
+
atomicRequired: true,
|
|
34377
|
+
authorization
|
|
34378
|
+
});
|
|
34379
|
+
// Success fans one confirmed hash across every receipt; the execute call is
|
|
34380
|
+
// the last one (approve, if present, precedes it). On failure a confirming
|
|
34381
|
+
// adapter returns no receipts, so a missing/non-success last receipt — or a
|
|
34382
|
+
// populated `result.error` — means the batch failed after submission (point
|
|
34383
|
+
// of no return). We never fall back, which would double-spend.
|
|
34384
|
+
const receiptCountMatches = result.receipts.length === calls.length;
|
|
34385
|
+
const executeReceipt = receiptCountMatches ? result.receipts[calls.length - 1] : undefined;
|
|
34386
|
+
const succeeded = receiptCountMatches && (result.statusCode === undefined || result.statusCode === 200) && result.error === undefined && executeReceipt?.status === 'success' && executeReceipt.txHash !== '';
|
|
34387
|
+
if (succeeded) {
|
|
34388
|
+
return buildSuccessfulBatchResult(adapter, evmChain, executeReceipt, result.batchId, revertMessage);
|
|
34389
|
+
}
|
|
34390
|
+
// Distinguish an off-chain rejection, a confirmed on-chain revert, and an
|
|
34391
|
+
// unknown outcome across both adapter conventions that share this contract:
|
|
34392
|
+
// - Circle SCA: no receipts + `error`; its trace kind identifies an
|
|
34393
|
+
// off-chain rejection, confirmed revert, or unconfirmed outcome.
|
|
34394
|
+
// - Viem EIP-5792: statusCode 500/600 explicitly confirms an on-chain
|
|
34395
|
+
// full/partial revert.
|
|
34396
|
+
// - Legacy/string-status wallets: a real-hash error receipt with no cause
|
|
34397
|
+
// is the best available confirmed-revert signal.
|
|
34398
|
+
// statusCode 400 is terminal but off-chain: the wallet confirms no call was
|
|
34399
|
+
// included, so it must not be labeled as a revert or unknown outcome.
|
|
34400
|
+
// Anything else — a poll timeout or any other post-submission failure with no
|
|
34401
|
+
// confirmed-revert signal — means the batch was submitted but its fate is
|
|
34402
|
+
// unconfirmed. Surface that as a FATAL (non-auto-retry) error carrying
|
|
34403
|
+
// `batchId` so the caller checks status before retrying, rather than
|
|
34404
|
+
// mislabeling it a revert.
|
|
34405
|
+
return throwBatchFailure(result, executeReceipt, evmChain, actionKey, revertMessage);
|
|
34406
|
+
}
|
|
34407
|
+
|
|
32577
34408
|
/**
|
|
32578
34409
|
* Validate that a service-signed execution payload has not expired before
|
|
32579
34410
|
* the SDK asks the wallet to broadcast a transaction.
|
|
@@ -33870,7 +35701,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
33870
35701
|
}
|
|
33871
35702
|
|
|
33872
35703
|
var name$1 = "@circle-fin/provider-earn-service";
|
|
33873
|
-
var version$1 = "1.
|
|
35704
|
+
var version$1 = "1.4.0";
|
|
33874
35705
|
var pkg$1 = {
|
|
33875
35706
|
name: name$1,
|
|
33876
35707
|
version: version$1};
|
|
@@ -33932,15 +35763,25 @@ var pkg$1 = {
|
|
|
33932
35763
|
*
|
|
33933
35764
|
* @internal
|
|
33934
35765
|
*/ function buildConfig(serviceConfig) {
|
|
35766
|
+
// The kit key is a server-only secret. Reject it in the browser so it cannot
|
|
35767
|
+
// leak into a client bundle (no-op in Node.js). Keyless usage stays allowed.
|
|
35768
|
+
if (serviceConfig?.kitKey !== undefined && isBrowserEnvironment()) {
|
|
35769
|
+
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');
|
|
35770
|
+
}
|
|
33935
35771
|
const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
|
|
33936
|
-
|
|
35772
|
+
// The API CORS policy does not allow this custom header. Keep the existing
|
|
35773
|
+
// per-request version attribution for Node callers, but omit it in browsers
|
|
35774
|
+
// so public EarnKit endpoints do not fail at CORS preflight.
|
|
35775
|
+
const sdkVersionHeader = isNodeEnvironment() ? {
|
|
35776
|
+
[SDK_VERSION_HEADER]: resolveSdkVersionHeader()
|
|
35777
|
+
} : {};
|
|
33937
35778
|
if (serviceConfig?.kitKey === undefined) {
|
|
33938
35779
|
return {
|
|
33939
35780
|
pollingConfig: {
|
|
33940
35781
|
...DEFAULT_CONFIG,
|
|
33941
35782
|
headers: {
|
|
33942
35783
|
...DEFAULT_CONFIG.headers,
|
|
33943
|
-
|
|
35784
|
+
...sdkVersionHeader
|
|
33944
35785
|
}
|
|
33945
35786
|
},
|
|
33946
35787
|
baseUrl
|
|
@@ -33958,7 +35799,7 @@ var pkg$1 = {
|
|
|
33958
35799
|
...DEFAULT_CONFIG,
|
|
33959
35800
|
headers: {
|
|
33960
35801
|
...DEFAULT_CONFIG.headers,
|
|
33961
|
-
|
|
35802
|
+
...sdkVersionHeader,
|
|
33962
35803
|
Authorization: `Bearer ${serviceConfig.kitKey}`
|
|
33963
35804
|
}
|
|
33964
35805
|
},
|
|
@@ -35498,6 +37339,46 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
35498
37339
|
const approvalToken = resolveEarnApprovalToken(executionParams);
|
|
35499
37340
|
const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
|
|
35500
37341
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
37342
|
+
const approvalNeeded = !options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n;
|
|
37343
|
+
// Batch-capable wallets bundle approve + deposit into one atomic
|
|
37344
|
+
// submission. Only attempt this when an approval is actually needed.
|
|
37345
|
+
if (approvalNeeded && approvalToken !== undefined && await shouldUseBatchedEarnAction({
|
|
37346
|
+
adapter,
|
|
37347
|
+
chain,
|
|
37348
|
+
address,
|
|
37349
|
+
batchTransactions: config?.batchTransactions
|
|
37350
|
+
})) {
|
|
37351
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>{
|
|
37352
|
+
try {
|
|
37353
|
+
const result = await executeBatchedEarnAction({
|
|
37354
|
+
adapter,
|
|
37355
|
+
chain,
|
|
37356
|
+
address,
|
|
37357
|
+
actionKey: 'earn.deposit',
|
|
37358
|
+
executeParams: executionParams,
|
|
37359
|
+
tokenInputs,
|
|
37360
|
+
signature,
|
|
37361
|
+
approvalToken,
|
|
37362
|
+
delegate: adapterContractAddress,
|
|
37363
|
+
requiredAllowance,
|
|
37364
|
+
idempotencyKey: execId,
|
|
37365
|
+
revertMessage: 'Earn deposit reverted on-chain'
|
|
37366
|
+
});
|
|
37367
|
+
reportTransactionSuccess(transactionReportContext, 'Deposit', result);
|
|
37368
|
+
return result;
|
|
37369
|
+
} catch (error) {
|
|
37370
|
+
reportTransactionFailure(transactionReportContext, 'Deposit', error);
|
|
37371
|
+
throw error;
|
|
37372
|
+
}
|
|
37373
|
+
}, ({ txHash })=>txHash);
|
|
37374
|
+
return {
|
|
37375
|
+
kind: 'same-chain',
|
|
37376
|
+
txHash,
|
|
37377
|
+
explorerUrl,
|
|
37378
|
+
vaultAddress,
|
|
37379
|
+
amount: params.amount
|
|
37380
|
+
};
|
|
37381
|
+
}
|
|
35501
37382
|
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
|
|
35502
37383
|
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
35503
37384
|
try {
|
|
@@ -35530,6 +37411,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
35530
37411
|
tokenInputs,
|
|
35531
37412
|
signature
|
|
35532
37413
|
},
|
|
37414
|
+
action: 'deposit',
|
|
37415
|
+
executionParams,
|
|
35533
37416
|
revertMessage: 'Earn deposit reverted on-chain'
|
|
35534
37417
|
});
|
|
35535
37418
|
reportTransactionSuccess(transactionReportContext, 'Deposit', result);
|
|
@@ -35661,6 +37544,44 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
35661
37544
|
const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
|
|
35662
37545
|
const approvalToken = tokenInputs[0]?.token;
|
|
35663
37546
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
37547
|
+
// Batch-capable wallets bundle approve + withdraw into one atomic
|
|
37548
|
+
// submission. Only attempt this when an approval is actually needed.
|
|
37549
|
+
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n && await shouldUseBatchedEarnAction({
|
|
37550
|
+
adapter,
|
|
37551
|
+
chain,
|
|
37552
|
+
address,
|
|
37553
|
+
batchTransactions: config?.batchTransactions
|
|
37554
|
+
})) {
|
|
37555
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'withdraw', 'execute', async ()=>{
|
|
37556
|
+
try {
|
|
37557
|
+
const result = await executeBatchedEarnAction({
|
|
37558
|
+
adapter,
|
|
37559
|
+
chain,
|
|
37560
|
+
address,
|
|
37561
|
+
actionKey: 'earn.withdraw',
|
|
37562
|
+
executeParams: executionParams,
|
|
37563
|
+
tokenInputs,
|
|
37564
|
+
signature,
|
|
37565
|
+
approvalToken,
|
|
37566
|
+
delegate: adapterContractAddress,
|
|
37567
|
+
requiredAllowance,
|
|
37568
|
+
idempotencyKey: execId,
|
|
37569
|
+
revertMessage: 'Earn withdraw reverted on-chain'
|
|
37570
|
+
});
|
|
37571
|
+
reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
|
|
37572
|
+
return result;
|
|
37573
|
+
} catch (error) {
|
|
37574
|
+
reportTransactionFailure(transactionReportContext, 'Withdraw', error);
|
|
37575
|
+
throw error;
|
|
37576
|
+
}
|
|
37577
|
+
}, ({ txHash })=>txHash);
|
|
37578
|
+
return {
|
|
37579
|
+
txHash,
|
|
37580
|
+
explorerUrl,
|
|
37581
|
+
vaultAddress,
|
|
37582
|
+
amount: params.amount
|
|
37583
|
+
};
|
|
37584
|
+
}
|
|
35664
37585
|
if (!options.skipApprove && approvalToken !== undefined) {
|
|
35665
37586
|
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
35666
37587
|
try {
|
|
@@ -35693,6 +37614,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
35693
37614
|
tokenInputs,
|
|
35694
37615
|
signature
|
|
35695
37616
|
},
|
|
37617
|
+
action: 'withdraw',
|
|
37618
|
+
executionParams,
|
|
35696
37619
|
revertMessage: 'Earn withdraw reverted on-chain'
|
|
35697
37620
|
});
|
|
35698
37621
|
reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
|
|
@@ -35772,6 +37695,8 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
35772
37695
|
tokenInputs: [],
|
|
35773
37696
|
signature
|
|
35774
37697
|
},
|
|
37698
|
+
action: 'claimRewards',
|
|
37699
|
+
executionParams,
|
|
35775
37700
|
revertMessage: 'Earn claim rewards reverted on-chain'
|
|
35776
37701
|
}), ({ txHash })=>txHash);
|
|
35777
37702
|
return {
|
|
@@ -36072,6 +37997,12 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
|
|
|
36072
37997
|
if (config.providers !== undefined && !Array.isArray(config.providers)) {
|
|
36073
37998
|
throw createValidationFailedError$1('config.providers', config.providers, 'providers must be an array of earn providers when provided');
|
|
36074
37999
|
}
|
|
38000
|
+
if (config.disableAnalytics !== undefined && typeof config.disableAnalytics !== 'boolean') {
|
|
38001
|
+
throw createValidationFailedError$1('config.disableAnalytics', config.disableAnalytics, 'disableAnalytics must be a boolean when provided');
|
|
38002
|
+
}
|
|
38003
|
+
if (config.disableErrorReporting !== undefined && typeof config.disableErrorReporting !== 'boolean') {
|
|
38004
|
+
throw createValidationFailedError$1('config.disableErrorReporting', config.disableErrorReporting, 'disableErrorReporting must be a boolean when provided');
|
|
38005
|
+
}
|
|
36075
38006
|
const defaultProviders = getDefaultProviders$1();
|
|
36076
38007
|
const providers = [
|
|
36077
38008
|
...config.providers ?? [],
|
|
@@ -36083,6 +38014,31 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
|
|
|
36083
38014
|
return context;
|
|
36084
38015
|
}
|
|
36085
38016
|
|
|
38017
|
+
/**
|
|
38018
|
+
* Register Earn Kit telemetry event type strings with the shared registry so
|
|
38019
|
+
* error telemetry helpers remain compile-time checked.
|
|
38020
|
+
*
|
|
38021
|
+
* @internal
|
|
38022
|
+
*/ /**
|
|
38023
|
+
* Telemetry event type identifiers for Earn Kit operations.
|
|
38024
|
+
*
|
|
38025
|
+
* @internal
|
|
38026
|
+
*/ const EARN_EVENT_TYPES = {
|
|
38027
|
+
GET_VAULTS: 'earn_get_vaults',
|
|
38028
|
+
EXPLORE_VAULTS: 'earn_explore_vaults',
|
|
38029
|
+
GET_POSITION: 'earn_get_position',
|
|
38030
|
+
GET_CROSS_CHAIN_DEPOSIT_STATUS: 'earn_get_cross_chain_deposit_status',
|
|
38031
|
+
WAIT_FOR_CROSS_CHAIN_DEPOSIT: 'earn_wait_for_cross_chain_deposit',
|
|
38032
|
+
DEPOSIT: 'earn_deposit',
|
|
38033
|
+
CROSS_CHAIN_DEPOSIT: 'earn_cross_chain_deposit',
|
|
38034
|
+
WITHDRAW: 'earn_withdraw',
|
|
38035
|
+
CLAIM_REWARDS: 'earn_claim_rewards',
|
|
38036
|
+
GET_DEPOSIT_QUOTE: 'earn_get_deposit_quote',
|
|
38037
|
+
GET_WITHDRAWAL_QUOTE: 'earn_get_withdrawal_quote',
|
|
38038
|
+
GET_CLAIM_REWARDS_QUOTE: 'earn_get_claim_rewards_quote',
|
|
38039
|
+
RETRY: 'earn_retry'
|
|
38040
|
+
};
|
|
38041
|
+
|
|
36086
38042
|
/**
|
|
36087
38043
|
* Format a provider amount object as a human-readable decimal string.
|
|
36088
38044
|
*
|
|
@@ -36424,11 +38380,16 @@ const sourceAdapterContextSchema = zod.z.object({
|
|
|
36424
38380
|
*
|
|
36425
38381
|
* Validate the optional Kit Key field using the standard `apiKeySchema`
|
|
36426
38382
|
* format (`KIT_KEY:<keyId>:<keySecret>`). When omitted, the SDK
|
|
36427
|
-
* operates in permissionless mode.
|
|
38383
|
+
* operates in permissionless mode. `baseUrl` overrides the Earn Service
|
|
38384
|
+
* endpoint (e.g. staging); `batchTransactions: false` opts out of atomic
|
|
38385
|
+
* batched execution. Both are forwarded to the provider, so this `.strict()`
|
|
38386
|
+
* schema must accept them or a valid config object is rejected.
|
|
36428
38387
|
*
|
|
36429
38388
|
* @internal
|
|
36430
38389
|
*/ const earnConfigSchema = zod.z.object({
|
|
36431
|
-
kitKey: apiKeySchema.optional()
|
|
38390
|
+
kitKey: apiKeySchema.optional(),
|
|
38391
|
+
baseUrl: zod.z.string().optional(),
|
|
38392
|
+
batchTransactions: zod.z.boolean().optional()
|
|
36432
38393
|
}).strict();
|
|
36433
38394
|
/**
|
|
36434
38395
|
* Canonical decimal form: a leading digit with no leading zeros (a single
|
|
@@ -37598,6 +39559,14 @@ function hasCrossChainDestination(params) {
|
|
|
37598
39559
|
return formatClaimRewardsQuoteInfo(result);
|
|
37599
39560
|
}
|
|
37600
39561
|
|
|
39562
|
+
/** SDK name used in telemetry payloads. */ const SDK_NAME$1 = resolveKitSdkName(pkg$2.name);
|
|
39563
|
+
/**
|
|
39564
|
+
* Determine whether deposit parameters target a destination chain.
|
|
39565
|
+
*
|
|
39566
|
+
* @internal
|
|
39567
|
+
*/ function isCrossChainDeposit(params) {
|
|
39568
|
+
return 'to' in params && params.to !== undefined;
|
|
39569
|
+
}
|
|
37601
39570
|
function formatRetryResult(operation, result) {
|
|
37602
39571
|
switch(operation){
|
|
37603
39572
|
case 'deposit':
|
|
@@ -37612,6 +39581,70 @@ function formatRetryResult(operation, result) {
|
|
|
37612
39581
|
}
|
|
37613
39582
|
}
|
|
37614
39583
|
}
|
|
39584
|
+
/**
|
|
39585
|
+
* Emit the success event corresponding to a completed retry.
|
|
39586
|
+
*
|
|
39587
|
+
* @internal
|
|
39588
|
+
*/ function emitRetrySuccessTelemetry(trace, result, config) {
|
|
39589
|
+
switch(trace.operation){
|
|
39590
|
+
case 'deposit':
|
|
39591
|
+
{
|
|
39592
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
39593
|
+
if ('to' in trace.params && trace.params.to !== undefined) {
|
|
39594
|
+
const destinationChain = resolveChainName(trace.params.to.chain);
|
|
39595
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, config, {
|
|
39596
|
+
...sourceChain != null && {
|
|
39597
|
+
sourceChain
|
|
39598
|
+
},
|
|
39599
|
+
...destinationChain != null && {
|
|
39600
|
+
destinationChain
|
|
39601
|
+
}
|
|
39602
|
+
});
|
|
39603
|
+
return;
|
|
39604
|
+
}
|
|
39605
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, config, {
|
|
39606
|
+
...sourceChain != null && {
|
|
39607
|
+
sourceChain
|
|
39608
|
+
},
|
|
39609
|
+
...'txHash' in result && {
|
|
39610
|
+
txHash: result.txHash
|
|
39611
|
+
}
|
|
39612
|
+
});
|
|
39613
|
+
return;
|
|
39614
|
+
}
|
|
39615
|
+
case 'withdraw':
|
|
39616
|
+
{
|
|
39617
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
39618
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, config, {
|
|
39619
|
+
...sourceChain != null && {
|
|
39620
|
+
sourceChain
|
|
39621
|
+
},
|
|
39622
|
+
...'txHash' in result && {
|
|
39623
|
+
txHash: result.txHash
|
|
39624
|
+
}
|
|
39625
|
+
});
|
|
39626
|
+
return;
|
|
39627
|
+
}
|
|
39628
|
+
case 'claimRewards':
|
|
39629
|
+
{
|
|
39630
|
+
if ('rewards' in result && result.status === 'claimed') {
|
|
39631
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
39632
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, config, {
|
|
39633
|
+
...sourceChain != null && {
|
|
39634
|
+
sourceChain
|
|
39635
|
+
},
|
|
39636
|
+
txHash: result.txHash
|
|
39637
|
+
});
|
|
39638
|
+
}
|
|
39639
|
+
return;
|
|
39640
|
+
}
|
|
39641
|
+
default:
|
|
39642
|
+
{
|
|
39643
|
+
const exhaustive = trace;
|
|
39644
|
+
throw createValidationFailedError$1('error.cause.trace', exhaustive, 'EarnKit.retry() does not support this earn operation');
|
|
39645
|
+
}
|
|
39646
|
+
}
|
|
39647
|
+
}
|
|
37615
39648
|
/**
|
|
37616
39649
|
* A high-level class-based interface for DeFi lending vault operations.
|
|
37617
39650
|
*
|
|
@@ -37670,6 +39703,8 @@ function formatRetryResult(operation, result) {
|
|
|
37670
39703
|
* ```
|
|
37671
39704
|
*/ class EarnKit {
|
|
37672
39705
|
context;
|
|
39706
|
+
/** Per-kit identity and opt-out state for error telemetry. */ telemetryConfig;
|
|
39707
|
+
/** Per-kit identity and opt-out state for success telemetry. */ analyticsTelemetryConfig;
|
|
37673
39708
|
/**
|
|
37674
39709
|
* Event dispatcher for step-level events emitted during multi-phase earn
|
|
37675
39710
|
* operations. Prefer {@link EarnKit.on} / {@link EarnKit.off} over using
|
|
@@ -37694,6 +39729,16 @@ function formatRetryResult(operation, result) {
|
|
|
37694
39729
|
*/ constructor(config = {}){
|
|
37695
39730
|
this.context = createEarnKitContext(config);
|
|
37696
39731
|
this.actionDispatcher = new Actionable();
|
|
39732
|
+
this.telemetryConfig = {
|
|
39733
|
+
sdkName: SDK_NAME$1,
|
|
39734
|
+
sdkVersion: pkg$2.version,
|
|
39735
|
+
disabled: config.disableErrorReporting === true
|
|
39736
|
+
};
|
|
39737
|
+
this.analyticsTelemetryConfig = {
|
|
39738
|
+
sdkName: SDK_NAME$1,
|
|
39739
|
+
sdkVersion: pkg$2.version,
|
|
39740
|
+
disabled: config.disableAnalytics === true
|
|
39741
|
+
};
|
|
37697
39742
|
for (const provider of this.context.providers){
|
|
37698
39743
|
provider.registerDispatcher(this.actionDispatcher);
|
|
37699
39744
|
}
|
|
@@ -37754,29 +39799,36 @@ function formatRetryResult(operation, result) {
|
|
|
37754
39799
|
* }
|
|
37755
39800
|
* ```
|
|
37756
39801
|
*/ async retry(error) {
|
|
37757
|
-
|
|
37758
|
-
|
|
37759
|
-
|
|
37760
|
-
|
|
37761
|
-
|
|
37762
|
-
|
|
37763
|
-
|
|
37764
|
-
|
|
37765
|
-
|
|
37766
|
-
|
|
37767
|
-
|
|
37768
|
-
|
|
37769
|
-
|
|
37770
|
-
|
|
37771
|
-
|
|
37772
|
-
|
|
37773
|
-
|
|
37774
|
-
|
|
37775
|
-
|
|
37776
|
-
|
|
39802
|
+
const result = await withErrorTelemetry(async ()=>{
|
|
39803
|
+
if (!isKitError(error)) {
|
|
39804
|
+
throw createValidationFailedError$1('error', error, 'EarnKit.retry() requires a KitError thrown by a previous earn operation');
|
|
39805
|
+
}
|
|
39806
|
+
if (!isRetryableError$1(error)) {
|
|
39807
|
+
throw createValidationFailedError$1('error.recoverability', error.recoverability, 'EarnKit.retry() requires a retryable or resumable error — check isRetryableError(error) first');
|
|
39808
|
+
}
|
|
39809
|
+
const trace = error.cause?.trace;
|
|
39810
|
+
if (!isEarnErrorTrace(trace)) {
|
|
39811
|
+
throw createValidationFailedError$1('error.cause.trace', trace, 'EarnKit.retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
|
|
39812
|
+
}
|
|
39813
|
+
const provider = this.context.providers.find((candidate)=>candidate.name === trace.provider);
|
|
39814
|
+
if (provider === undefined) {
|
|
39815
|
+
throw createValidationFailedError$1('error.cause.trace.provider', trace.provider, `No earn provider named "${trace.provider}" is registered with this kit`);
|
|
39816
|
+
}
|
|
39817
|
+
const result = await provider.retry(error);
|
|
39818
|
+
// `provider.retry` returns a flat result union with no compile-time link to
|
|
39819
|
+
// `trace.operation`, so narrow the operation here to select the matching
|
|
39820
|
+
// overload. The result cast in each branch is sound: the provider always
|
|
39821
|
+
// returns the result type corresponding to the resumed operation.
|
|
39822
|
+
if (trace.operation === 'claimRewards') {
|
|
39823
|
+
return formatRetryResult(trace.operation, result);
|
|
39824
|
+
}
|
|
37777
39825
|
return formatRetryResult(trace.operation, result);
|
|
39826
|
+
}, EARN_EVENT_TYPES.RETRY, this.telemetryConfig);
|
|
39827
|
+
const trace = isKitError(error) ? error.cause?.trace : undefined;
|
|
39828
|
+
if (isEarnErrorTrace(trace)) {
|
|
39829
|
+
emitRetrySuccessTelemetry(trace, result, this.analyticsTelemetryConfig);
|
|
37778
39830
|
}
|
|
37779
|
-
return
|
|
39831
|
+
return result;
|
|
37780
39832
|
}
|
|
37781
39833
|
/**
|
|
37782
39834
|
* Return the chains supported by configured earn providers.
|
|
@@ -37810,7 +39862,9 @@ function formatRetryResult(operation, result) {
|
|
|
37810
39862
|
* result.vaults.forEach(v => console.log(`${v.name}: ${(v.currentApy * 100).toFixed(2)}% APY`))
|
|
37811
39863
|
* ```
|
|
37812
39864
|
*/ async getVaults(params) {
|
|
37813
|
-
|
|
39865
|
+
const result = await withErrorTelemetry(async ()=>getVaults$1(this.context, params), EARN_EVENT_TYPES.GET_VAULTS, this.telemetryConfig);
|
|
39866
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.GET_VAULTS, this.analyticsTelemetryConfig, {});
|
|
39867
|
+
return result;
|
|
37814
39868
|
}
|
|
37815
39869
|
/**
|
|
37816
39870
|
* Discover vaults available on a chain.
|
|
@@ -37835,7 +39889,12 @@ function formatRetryResult(operation, result) {
|
|
|
37835
39889
|
* const guarded = result.vaults.filter(v => v.circleGuarded) // Circle-guarded vaults only
|
|
37836
39890
|
* ```
|
|
37837
39891
|
*/ async exploreVaults(params) {
|
|
37838
|
-
|
|
39892
|
+
const context = {
|
|
39893
|
+
sourceChain: resolveChainName(params.chain)
|
|
39894
|
+
};
|
|
39895
|
+
const result = await withErrorTelemetry(async ()=>exploreVaults$1(this.context, params), EARN_EVENT_TYPES.EXPLORE_VAULTS, this.telemetryConfig, context);
|
|
39896
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.EXPLORE_VAULTS, this.analyticsTelemetryConfig, context);
|
|
39897
|
+
return result;
|
|
37839
39898
|
}
|
|
37840
39899
|
/**
|
|
37841
39900
|
* Lazily iterate every vault available on a chain.
|
|
@@ -37882,7 +39941,9 @@ function formatRetryResult(operation, result) {
|
|
|
37882
39941
|
* }
|
|
37883
39942
|
* ```
|
|
37884
39943
|
*/ async getPosition(params) {
|
|
37885
|
-
return getPosition$1(this.context, params)
|
|
39944
|
+
return withErrorTelemetry(async ()=>getPosition$1(this.context, params), EARN_EVENT_TYPES.GET_POSITION, this.telemetryConfig, {
|
|
39945
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
39946
|
+
});
|
|
37886
39947
|
}
|
|
37887
39948
|
/**
|
|
37888
39949
|
* Fetch the current status of a cross-chain deposit by execution ID.
|
|
@@ -37907,7 +39968,7 @@ function formatRetryResult(operation, result) {
|
|
|
37907
39968
|
* console.log(`Bridge ${status.execId} is ${status.status}`)
|
|
37908
39969
|
* ```
|
|
37909
39970
|
*/ async getCrossChainDepositStatus(params) {
|
|
37910
|
-
return getCrossChainDepositStatus$1(this.context, params);
|
|
39971
|
+
return withErrorTelemetry(async ()=>getCrossChainDepositStatus$1(this.context, params), EARN_EVENT_TYPES.GET_CROSS_CHAIN_DEPOSIT_STATUS, this.telemetryConfig);
|
|
37911
39972
|
}
|
|
37912
39973
|
/**
|
|
37913
39974
|
* Poll a cross-chain deposit until it reaches a terminal bridge state.
|
|
@@ -37934,10 +39995,30 @@ function formatRetryResult(operation, result) {
|
|
|
37934
39995
|
* console.log(`Bridge ended as ${result.outcome}`)
|
|
37935
39996
|
* ```
|
|
37936
39997
|
*/ async waitForCrossChainDeposit(params) {
|
|
37937
|
-
return waitForCrossChainDeposit$1(this.context, params);
|
|
39998
|
+
return withErrorTelemetry(async ()=>waitForCrossChainDeposit$1(this.context, params), EARN_EVENT_TYPES.WAIT_FOR_CROSS_CHAIN_DEPOSIT, this.telemetryConfig);
|
|
37938
39999
|
}
|
|
37939
40000
|
async deposit(params) {
|
|
37940
|
-
|
|
40001
|
+
const isCrossChain = isCrossChainDeposit(params);
|
|
40002
|
+
const context = {
|
|
40003
|
+
sourceChain: resolveChainName(params.from.chain),
|
|
40004
|
+
...isCrossChain && {
|
|
40005
|
+
destinationChain: resolveChainName(params.to.chain)
|
|
40006
|
+
}
|
|
40007
|
+
};
|
|
40008
|
+
const eventType = isCrossChain ? EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT : EARN_EVENT_TYPES.DEPOSIT;
|
|
40009
|
+
const result = await withErrorTelemetry(async ()=>deposit$3(this.context, params), eventType, this.telemetryConfig, context);
|
|
40010
|
+
if (result.kind === 'cross-chain') {
|
|
40011
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, this.analyticsTelemetryConfig, {
|
|
40012
|
+
sourceChain: resolveChainName(result.sourceChain),
|
|
40013
|
+
destinationChain: resolveChainName(result.destinationChain)
|
|
40014
|
+
});
|
|
40015
|
+
} else {
|
|
40016
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, this.analyticsTelemetryConfig, {
|
|
40017
|
+
...context,
|
|
40018
|
+
txHash: result.txHash
|
|
40019
|
+
});
|
|
40020
|
+
}
|
|
40021
|
+
return result;
|
|
37941
40022
|
}
|
|
37942
40023
|
/**
|
|
37943
40024
|
* Execute a withdrawal from a DeFi lending vault.
|
|
@@ -37963,7 +40044,15 @@ function formatRetryResult(operation, result) {
|
|
|
37963
40044
|
* console.log(`Withdrew ${result.amount} from ${result.vaultAddress}, tx: ${result.txHash}`)
|
|
37964
40045
|
* ```
|
|
37965
40046
|
*/ async withdraw(params) {
|
|
37966
|
-
|
|
40047
|
+
const context = {
|
|
40048
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
40049
|
+
};
|
|
40050
|
+
const result = await withErrorTelemetry(async ()=>withdraw$1(this.context, params), EARN_EVENT_TYPES.WITHDRAW, this.telemetryConfig, context);
|
|
40051
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, this.analyticsTelemetryConfig, {
|
|
40052
|
+
...context,
|
|
40053
|
+
txHash: result.txHash
|
|
40054
|
+
});
|
|
40055
|
+
return result;
|
|
37967
40056
|
}
|
|
37968
40057
|
/**
|
|
37969
40058
|
* Claim rewards from earn vaults.
|
|
@@ -37990,7 +40079,17 @@ function formatRetryResult(operation, result) {
|
|
|
37990
40079
|
*
|
|
37991
40080
|
* @internal
|
|
37992
40081
|
*/ async claimRewards(params) {
|
|
37993
|
-
|
|
40082
|
+
const context = {
|
|
40083
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
40084
|
+
};
|
|
40085
|
+
const result = await withErrorTelemetry(async ()=>claimRewards$1(this.context, params), EARN_EVENT_TYPES.CLAIM_REWARDS, this.telemetryConfig, context);
|
|
40086
|
+
if (result.status === 'claimed') {
|
|
40087
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, this.analyticsTelemetryConfig, {
|
|
40088
|
+
...context,
|
|
40089
|
+
txHash: result.txHash
|
|
40090
|
+
});
|
|
40091
|
+
}
|
|
40092
|
+
return result;
|
|
37994
40093
|
}
|
|
37995
40094
|
/**
|
|
37996
40095
|
* Get an informational quote for a deposit into a vault.
|
|
@@ -38012,7 +40111,9 @@ function formatRetryResult(operation, result) {
|
|
|
38012
40111
|
* console.log(`Expected shares: ${quote.expectedShares.amount}`)
|
|
38013
40112
|
* ```
|
|
38014
40113
|
*/ async getDepositQuote(params) {
|
|
38015
|
-
return getDepositQuote$1(this.context, params)
|
|
40114
|
+
return withErrorTelemetry(async ()=>getDepositQuote$1(this.context, params), EARN_EVENT_TYPES.GET_DEPOSIT_QUOTE, this.telemetryConfig, {
|
|
40115
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
40116
|
+
});
|
|
38016
40117
|
}
|
|
38017
40118
|
/**
|
|
38018
40119
|
* Get an informational quote for a withdrawal from a vault.
|
|
@@ -38034,7 +40135,9 @@ function formatRetryResult(operation, result) {
|
|
|
38034
40135
|
* console.log(`Shares to redeem: ${quote.sharesToRedeem.amount}`)
|
|
38035
40136
|
* ```
|
|
38036
40137
|
*/ async getWithdrawalQuote(params) {
|
|
38037
|
-
return getWithdrawalQuote$1(this.context, params)
|
|
40138
|
+
return withErrorTelemetry(async ()=>getWithdrawalQuote$1(this.context, params), EARN_EVENT_TYPES.GET_WITHDRAWAL_QUOTE, this.telemetryConfig, {
|
|
40139
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
40140
|
+
});
|
|
38038
40141
|
}
|
|
38039
40142
|
/**
|
|
38040
40143
|
* Get an informational quote for claiming rewards.
|
|
@@ -38056,7 +40159,9 @@ function formatRetryResult(operation, result) {
|
|
|
38056
40159
|
*
|
|
38057
40160
|
* @internal
|
|
38058
40161
|
*/ async getClaimRewardsQuote(params) {
|
|
38059
|
-
return getClaimRewardsQuote$1(this.context, params)
|
|
40162
|
+
return withErrorTelemetry(async ()=>getClaimRewardsQuote$1(this.context, params), EARN_EVENT_TYPES.GET_CLAIM_REWARDS_QUOTE, this.telemetryConfig, {
|
|
40163
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
40164
|
+
});
|
|
38060
40165
|
}
|
|
38061
40166
|
}
|
|
38062
40167
|
|
|
@@ -38145,7 +40250,14 @@ registerKit(`${pkg$2.name}/${pkg$2.version}`);
|
|
|
38145
40250
|
* const earnKit = createEarnKit(context)
|
|
38146
40251
|
* ```
|
|
38147
40252
|
*/ const createEarnKit = (context)=>{
|
|
38148
|
-
const kit = new EarnKit(
|
|
40253
|
+
const kit = new EarnKit({
|
|
40254
|
+
...context.disableErrorReporting != null && {
|
|
40255
|
+
disableErrorReporting: context.disableErrorReporting
|
|
40256
|
+
},
|
|
40257
|
+
...context.disableAnalytics != null && {
|
|
40258
|
+
disableAnalytics: context.disableAnalytics
|
|
40259
|
+
}
|
|
40260
|
+
});
|
|
38149
40261
|
registerActionHandlers(kit, context.actions.earn, 'earn');
|
|
38150
40262
|
return kit;
|
|
38151
40263
|
};
|
|
@@ -39454,7 +41566,7 @@ async function deposit$2(context, params) {
|
|
|
39454
41566
|
}
|
|
39455
41567
|
|
|
39456
41568
|
var name = "@circle-fin/unified-balance-kit";
|
|
39457
|
-
var version = "1.
|
|
41569
|
+
var version = "1.4.0";
|
|
39458
41570
|
var pkg = {
|
|
39459
41571
|
name: name,
|
|
39460
41572
|
version: version};
|
|
@@ -40795,7 +42907,8 @@ function throwNetworkMismatch(expected, actual) {
|
|
|
40795
42907
|
};
|
|
40796
42908
|
}
|
|
40797
42909
|
/**
|
|
40798
|
-
* Group intents
|
|
42910
|
+
* Group intents for signing (Solana one-per-intent, EVM batched by adapter
|
|
42911
|
+
* with every source chain retained by Gateway domain).
|
|
40799
42912
|
*
|
|
40800
42913
|
* @param intents - Burn intents from estimate response.
|
|
40801
42914
|
* @param allocations - Normalized allocations used to map domain → adapter/chain.
|
|
@@ -42172,22 +44285,32 @@ const DEFAULT_GAS_FEE = parseUnits('0.1', USDC_DECIMALS);
|
|
|
42172
44285
|
*
|
|
42173
44286
|
* Single-intent sets become one burnIntent + signature; multi-intent sets become burnIntentSet + signature.
|
|
42174
44287
|
*
|
|
44288
|
+
* Sets flagged `contractSigner` carry `contractSigner: true`, which tells
|
|
44289
|
+
* Gateway to validate the signature with ERC-1271 (an offchain
|
|
44290
|
+
* `isValidSignature` simulation) instead of `ecrecover`. The flag is
|
|
44291
|
+
* omitted for EOA signers so their payloads stay byte-identical.
|
|
44292
|
+
*
|
|
42175
44293
|
* @param signedSets - Signed intent sets (intents + signature per signer).
|
|
42176
44294
|
* @returns Array of transfer payloads for POST /v1/transfer.
|
|
42177
44295
|
*/ function buildTransferRequestBody(signedSets) {
|
|
42178
44296
|
return signedSets.map((set)=>{
|
|
42179
44297
|
const firstIntent = set.intents[0];
|
|
44298
|
+
const contractSigner = set.contractSigner === true ? {
|
|
44299
|
+
contractSigner: true
|
|
44300
|
+
} : {};
|
|
42180
44301
|
if (set.intents.length === 1 && firstIntent) {
|
|
42181
44302
|
return {
|
|
42182
44303
|
burnIntent: serializeBurnIntent(firstIntent),
|
|
42183
|
-
signature: set.signature
|
|
44304
|
+
signature: set.signature,
|
|
44305
|
+
...contractSigner
|
|
42184
44306
|
};
|
|
42185
44307
|
}
|
|
42186
44308
|
return {
|
|
42187
44309
|
burnIntentSet: {
|
|
42188
44310
|
intents: set.intents.map(serializeBurnIntent)
|
|
42189
44311
|
},
|
|
42190
|
-
signature: set.signature
|
|
44312
|
+
signature: set.signature,
|
|
44313
|
+
...contractSigner
|
|
42191
44314
|
};
|
|
42192
44315
|
});
|
|
42193
44316
|
}
|
|
@@ -42550,11 +44673,16 @@ const BPS_DIVISOR = 100_000n;
|
|
|
42550
44673
|
return required;
|
|
42551
44674
|
}
|
|
42552
44675
|
|
|
44676
|
+
function requireEvmChainsByDomain(group) {
|
|
44677
|
+
if (group.chainsByDomain !== undefined) return group.chainsByDomain;
|
|
44678
|
+
throw createValidationFailedError$1('adapterGroup.chainsByDomain', group.chainsByDomain, 'must be provided for an EVM adapter group');
|
|
44679
|
+
}
|
|
42553
44680
|
/**
|
|
42554
|
-
* Sign each adapter group: Solana one intent per signature, EVM
|
|
44681
|
+
* Sign each adapter group: Solana one intent per signature, and EVM either
|
|
44682
|
+
* batched for EOAs or split by source chain for ERC-1271 signers.
|
|
42555
44683
|
*
|
|
42556
44684
|
* @param adapterGroups - Groups from groupIntentsByAdapter.
|
|
42557
|
-
* @returns Promise of signed sets
|
|
44685
|
+
* @returns Promise of signed sets for buildTransferRequestBody.
|
|
42558
44686
|
*
|
|
42559
44687
|
* @example
|
|
42560
44688
|
* ```typescript
|
|
@@ -42567,9 +44695,10 @@ const BPS_DIVISOR = 100_000n;
|
|
|
42567
44695
|
if (group.chain.type === 'solana') {
|
|
42568
44696
|
return signSolanaIntentGroup(group);
|
|
42569
44697
|
}
|
|
42570
|
-
return
|
|
42571
|
-
|
|
42572
|
-
|
|
44698
|
+
return await signEvmIntentGroup({
|
|
44699
|
+
...group,
|
|
44700
|
+
chainsByDomain: requireEvmChainsByDomain(group)
|
|
44701
|
+
});
|
|
42573
44702
|
}));
|
|
42574
44703
|
return nested.flat();
|
|
42575
44704
|
}
|
|
@@ -43219,7 +45348,8 @@ async function runSpendNormalPath(params, destChain, useForwarder, dispatcher, s
|
|
|
43219
45348
|
signedSetCount: signedSets.length,
|
|
43220
45349
|
signatures: signedSets.map((s)=>({
|
|
43221
45350
|
intentCount: s.intents.length,
|
|
43222
|
-
signature: s.signature
|
|
45351
|
+
signature: s.signature,
|
|
45352
|
+
contractSigner: s.contractSigner === true
|
|
43223
45353
|
}))
|
|
43224
45354
|
}
|
|
43225
45355
|
});
|
|
@@ -45964,7 +48094,11 @@ const removeFundParamsSchema = zod.z.object({
|
|
|
45964
48094
|
// Remove Fund Operations
|
|
45965
48095
|
// ---------------------------------------------------------------------------
|
|
45966
48096
|
/**
|
|
45967
|
-
* Kick off a delayed fund removal from an account.
|
|
48097
|
+
* Kick off a delayed recovery fund removal from an account.
|
|
48098
|
+
*
|
|
48099
|
+
* Use `initiateRemoveFund` only as a trustless fallback when the normal spend
|
|
48100
|
+
* flow is unavailable. For day-to-day movement out of a Unified Balance, use
|
|
48101
|
+
* `spend`.
|
|
45968
48102
|
*
|
|
45969
48103
|
* Validates `from` and `amount`, resolves the chain and token via
|
|
45970
48104
|
* {@link resolveRemoveFundParams}, selects the matching provider, then calls
|
|
@@ -46001,7 +48135,10 @@ const removeFundParamsSchema = zod.z.object({
|
|
|
46001
48135
|
return provider.initiateRemoveFund(resolved);
|
|
46002
48136
|
}
|
|
46003
48137
|
/**
|
|
46004
|
-
* Complete a fund removal once the 7-day
|
|
48138
|
+
* Complete a recovery fund removal once the 7-day withdrawal delay has passed.
|
|
48139
|
+
*
|
|
48140
|
+
* Use `removeFund` only as a trustless fallback when the normal spend flow is
|
|
48141
|
+
* unavailable. For day-to-day movement out of a Unified Balance, use `spend`.
|
|
46005
48142
|
*
|
|
46006
48143
|
* Validates `from`, resolves the chain and token via
|
|
46007
48144
|
* {@link resolveRemoveFundParams}, selects the matching provider, then calls
|
|
@@ -46090,13 +48227,18 @@ const removeFundParamsSchema = zod.z.object({
|
|
|
46090
48227
|
/** SDK name used in telemetry payloads. */ const SDK_NAME = resolveKitSdkName(pkg.name);
|
|
46091
48228
|
/**
|
|
46092
48229
|
* A high-level class-based interface for cross-chain USDC deposits,
|
|
46093
|
-
* spending, balance queries, delegation management, and
|
|
48230
|
+
* spending, balance queries, delegation management, and recovery fund removals.
|
|
46094
48231
|
*
|
|
46095
48232
|
* UnifiedBalanceKit provides a familiar class-based API for developers who
|
|
46096
48233
|
* prefer traditional object-oriented patterns. The class maintains an
|
|
46097
48234
|
* internal context and provides methods that delegate to the standalone
|
|
46098
48235
|
* operation functions exported by this package.
|
|
46099
48236
|
*
|
|
48237
|
+
* Use `spend` for normal movement out of a Unified Balance. `removeFund` is a
|
|
48238
|
+
* trustless recovery path for situations where the normal spend flow is
|
|
48239
|
+
* unavailable, and it requires a 7-day withdrawal delay before funds can be
|
|
48240
|
+
* removed.
|
|
48241
|
+
*
|
|
46100
48242
|
* @remarks
|
|
46101
48243
|
* For functional usage, import and use the operations directly:
|
|
46102
48244
|
* ```typescript
|
|
@@ -46317,7 +48459,11 @@ const removeFundParamsSchema = zod.z.object({
|
|
|
46317
48459
|
});
|
|
46318
48460
|
}
|
|
46319
48461
|
/**
|
|
46320
|
-
* Kick off a delayed fund removal from an account.
|
|
48462
|
+
* Kick off a delayed recovery fund removal from an account.
|
|
48463
|
+
*
|
|
48464
|
+
* Use this only as a trustless fallback when the normal spend flow is
|
|
48465
|
+
* unavailable. For day-to-day movement out of a Unified Balance, use
|
|
48466
|
+
* `spend`.
|
|
46321
48467
|
*
|
|
46322
48468
|
* @param params - The account owner's adapter context, amount, and
|
|
46323
48469
|
* optional token type.
|
|
@@ -46331,7 +48477,12 @@ const removeFundParamsSchema = zod.z.object({
|
|
|
46331
48477
|
});
|
|
46332
48478
|
}
|
|
46333
48479
|
/**
|
|
46334
|
-
* Complete a fund removal once the
|
|
48480
|
+
* Complete a recovery fund removal once the 7-day withdrawal delay has
|
|
48481
|
+
* passed.
|
|
48482
|
+
*
|
|
48483
|
+
* Use this only as a trustless fallback when the normal spend flow is
|
|
48484
|
+
* unavailable. For day-to-day movement out of a Unified Balance, use
|
|
48485
|
+
* `spend`.
|
|
46335
48486
|
*
|
|
46336
48487
|
* @param params - The account owner context matching the original
|
|
46337
48488
|
* fund removal initiation.
|
|
@@ -46458,6 +48609,11 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
46458
48609
|
* Internally holds a persistent {@link UnifiedBalanceKit} instance so that
|
|
46459
48610
|
* event dispatchers and custom fee policies are preserved across calls.
|
|
46460
48611
|
*
|
|
48612
|
+
* Use {@link AppKitUnifiedBalance.spend} for normal movement out of a Unified
|
|
48613
|
+
* Balance. {@link AppKitUnifiedBalance.removeFund} is a trustless recovery path
|
|
48614
|
+
* for situations where the normal spend flow is unavailable, and it requires a
|
|
48615
|
+
* 7-day withdrawal delay after {@link AppKitUnifiedBalance.initiateRemoveFund}.
|
|
48616
|
+
*
|
|
46461
48617
|
* @example
|
|
46462
48618
|
* ```typescript
|
|
46463
48619
|
* import { AppKit } from '@circle-fin/app-kit'
|
|
@@ -46669,7 +48825,12 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
46669
48825
|
return this.kit.removeDelegate(params);
|
|
46670
48826
|
}
|
|
46671
48827
|
/**
|
|
46672
|
-
*
|
|
48828
|
+
* Initiate a trustless recovery removal from an account.
|
|
48829
|
+
*
|
|
48830
|
+
* Use `spend` for normal movement out of a Unified Balance. `removeFund` is a
|
|
48831
|
+
* recovery path for situations where the normal spend flow is unavailable.
|
|
48832
|
+
* Calling this method starts the 7-day withdrawal delay before the removal can
|
|
48833
|
+
* be completed.
|
|
46673
48834
|
*
|
|
46674
48835
|
* @param params - The account owner's adapter context, amount, and token.
|
|
46675
48836
|
* @returns Promise resolving to the initiation details.
|
|
@@ -46688,11 +48849,16 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
46688
48849
|
return this.kit.initiateRemoveFund(params);
|
|
46689
48850
|
}
|
|
46690
48851
|
/**
|
|
46691
|
-
* Complete a
|
|
48852
|
+
* Complete a trustless recovery removal after the withdrawal delay.
|
|
48853
|
+
*
|
|
48854
|
+
* Use `spend` for normal movement out of a Unified Balance. `removeFund` is a
|
|
48855
|
+
* recovery path for situations where the normal spend flow is unavailable.
|
|
48856
|
+
* Both EVM and Solana removals require a 7-day withdrawal delay after
|
|
48857
|
+
* `initiateRemoveFund` before funds can be removed.
|
|
46692
48858
|
*
|
|
46693
48859
|
* @param params - The account owner context matching the original initiation.
|
|
46694
48860
|
* @returns Promise resolving to the fund removal details.
|
|
46695
|
-
* @throws {KitError} If the
|
|
48861
|
+
* @throws {KitError} If the withdrawal delay has not elapsed or the
|
|
46696
48862
|
* on-chain transaction fails.
|
|
46697
48863
|
*
|
|
46698
48864
|
* @example
|
|
@@ -46809,6 +48975,33 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
46809
48975
|
}
|
|
46810
48976
|
}
|
|
46811
48977
|
|
|
48978
|
+
const APP_KIT_CUSTOM_FEE_POLICY_KEYS = new Set([
|
|
48979
|
+
'bridge',
|
|
48980
|
+
'swap',
|
|
48981
|
+
'unifiedBalance'
|
|
48982
|
+
]);
|
|
48983
|
+
function assertAppKitCustomFeePolicy(policy) {
|
|
48984
|
+
if (policy === null || typeof policy !== 'object' || Array.isArray(policy)) {
|
|
48985
|
+
throw createValidationFailedError$1('policy', policy, 'AppKit custom fee policy must be an object');
|
|
48986
|
+
}
|
|
48987
|
+
for (const key of Object.keys(policy)){
|
|
48988
|
+
if (!APP_KIT_CUSTOM_FEE_POLICY_KEYS.has(key)) {
|
|
48989
|
+
throw createValidationFailedError$1(`policy.${key}`, key, 'AppKit custom fee policy only supports bridge, swap, and unifiedBalance');
|
|
48990
|
+
}
|
|
48991
|
+
}
|
|
48992
|
+
const candidate = policy;
|
|
48993
|
+
if (candidate.bridge !== undefined) {
|
|
48994
|
+
assertCustomFeePolicy$2(candidate.bridge);
|
|
48995
|
+
}
|
|
48996
|
+
if (candidate.swap !== undefined) {
|
|
48997
|
+
assertCustomFeePolicy$1(candidate.swap);
|
|
48998
|
+
}
|
|
48999
|
+
}
|
|
49000
|
+
function assertAppKitCustomFeePolicyScope(operation) {
|
|
49001
|
+
if (typeof operation !== 'string' || !APP_KIT_CUSTOM_FEE_POLICY_KEYS.has(operation)) {
|
|
49002
|
+
throw createValidationFailedError$1('operation', operation, 'AppKit custom fee policy operation must be bridge, swap, or unifiedBalance');
|
|
49003
|
+
}
|
|
49004
|
+
}
|
|
46812
49005
|
/**
|
|
46813
49006
|
* A high-level SDK for stablecoin operations, including bridging, swapping, and earn.
|
|
46814
49007
|
*
|
|
@@ -46931,18 +49124,28 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
46931
49124
|
* })
|
|
46932
49125
|
* ```
|
|
46933
49126
|
*/ constructor(config = {}){
|
|
46934
|
-
|
|
46935
|
-
|
|
49127
|
+
if (config.customFeePolicy !== undefined) {
|
|
49128
|
+
assertAppKitCustomFeePolicy(config.customFeePolicy);
|
|
49129
|
+
}
|
|
49130
|
+
const unifiedBalance = new AppKitUnifiedBalance({
|
|
49131
|
+
...config.unifiedBalance,
|
|
46936
49132
|
...config.disableErrorReporting != null && {
|
|
46937
49133
|
disableErrorReporting: config.disableErrorReporting
|
|
49134
|
+
},
|
|
49135
|
+
...config.disableAnalytics != null && {
|
|
49136
|
+
disableAnalytics: config.disableAnalytics
|
|
46938
49137
|
}
|
|
46939
49138
|
});
|
|
46940
|
-
|
|
46941
|
-
|
|
49139
|
+
if (config.customFeePolicy?.unifiedBalance != null) {
|
|
49140
|
+
unifiedBalance.setCustomFeePolicy(config.customFeePolicy.unifiedBalance);
|
|
49141
|
+
}
|
|
49142
|
+
this.context = createContext({
|
|
49143
|
+
...config,
|
|
46942
49144
|
...config.disableErrorReporting != null && {
|
|
46943
49145
|
disableErrorReporting: config.disableErrorReporting
|
|
46944
49146
|
}
|
|
46945
49147
|
});
|
|
49148
|
+
this.unifiedBalance = unifiedBalance;
|
|
46946
49149
|
this.earn = {
|
|
46947
49150
|
// A single implementation cannot satisfy the per-branch deposit
|
|
46948
49151
|
// overloads, so assert the overloaded interface shape; the
|
|
@@ -47386,6 +49589,77 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
47386
49589
|
*/ getSupportedChains(operationType) {
|
|
47387
49590
|
return getSupportedChains$2(this.context, operationType, this.unifiedBalance);
|
|
47388
49591
|
}
|
|
49592
|
+
/**
|
|
49593
|
+
* Set operation-scoped custom fee policies.
|
|
49594
|
+
*
|
|
49595
|
+
* Configure custom fees for only the operations that need them. Bridge and
|
|
49596
|
+
* swap policies are forwarded to the underlying kits when those operations
|
|
49597
|
+
* run. Unified balance policies are applied immediately to the namespaced
|
|
49598
|
+
* Unified Balance Kit.
|
|
49599
|
+
*
|
|
49600
|
+
* @param policy - Partial custom fee policy grouped by operation.
|
|
49601
|
+
* @returns Nothing.
|
|
49602
|
+
* @throws \{KitError\} If `policy` or a provided operation policy is invalid.
|
|
49603
|
+
*
|
|
49604
|
+
* @example
|
|
49605
|
+
* ```typescript
|
|
49606
|
+
* kit.setCustomFeePolicy({
|
|
49607
|
+
* bridge: {
|
|
49608
|
+
* computeFee: () => '1.00',
|
|
49609
|
+
* resolveFeeRecipientAddress: () => '0x1234567890123456789012345678901234567890',
|
|
49610
|
+
* },
|
|
49611
|
+
* })
|
|
49612
|
+
* ```
|
|
49613
|
+
*/ setCustomFeePolicy(policy) {
|
|
49614
|
+
assertAppKitCustomFeePolicy(policy);
|
|
49615
|
+
if (policy.unifiedBalance != null) {
|
|
49616
|
+
this.unifiedBalance.setCustomFeePolicy(policy.unifiedBalance);
|
|
49617
|
+
}
|
|
49618
|
+
this.context.customFeePolicy = {
|
|
49619
|
+
...this.context.customFeePolicy,
|
|
49620
|
+
...policy
|
|
49621
|
+
};
|
|
49622
|
+
}
|
|
49623
|
+
/**
|
|
49624
|
+
* Remove an AppKit-level custom fee policy for one operation.
|
|
49625
|
+
*
|
|
49626
|
+
* Bridge and swap policies are removed from AppKit's persistent context so
|
|
49627
|
+
* future operations fall back to legacy fee hooks. Unified balance policies
|
|
49628
|
+
* are also removed from the namespaced Unified Balance Kit.
|
|
49629
|
+
*
|
|
49630
|
+
* @param operation - Operation whose custom fee policy should be removed.
|
|
49631
|
+
* @returns Nothing.
|
|
49632
|
+
* @throws \{KitError\} If `operation` is invalid.
|
|
49633
|
+
*
|
|
49634
|
+
* @example
|
|
49635
|
+
* ```typescript
|
|
49636
|
+
* kit.removeCustomFeePolicy('bridge')
|
|
49637
|
+
* ```
|
|
49638
|
+
*/ removeCustomFeePolicy(operation) {
|
|
49639
|
+
assertAppKitCustomFeePolicyScope(operation);
|
|
49640
|
+
if (operation === 'unifiedBalance') {
|
|
49641
|
+
this.unifiedBalance.removeCustomFeePolicy();
|
|
49642
|
+
}
|
|
49643
|
+
if (this.context.customFeePolicy == null) {
|
|
49644
|
+
return;
|
|
49645
|
+
}
|
|
49646
|
+
const currentPolicy = this.context.customFeePolicy;
|
|
49647
|
+
const nextPolicy = {};
|
|
49648
|
+
if (operation !== 'bridge' && currentPolicy.bridge !== undefined) {
|
|
49649
|
+
nextPolicy.bridge = currentPolicy.bridge;
|
|
49650
|
+
}
|
|
49651
|
+
if (operation !== 'swap' && currentPolicy.swap !== undefined) {
|
|
49652
|
+
nextPolicy.swap = currentPolicy.swap;
|
|
49653
|
+
}
|
|
49654
|
+
if (operation !== 'unifiedBalance' && currentPolicy.unifiedBalance !== undefined) {
|
|
49655
|
+
nextPolicy.unifiedBalance = currentPolicy.unifiedBalance;
|
|
49656
|
+
}
|
|
49657
|
+
if (Object.keys(nextPolicy).length === 0) {
|
|
49658
|
+
delete this.context.customFeePolicy;
|
|
49659
|
+
return;
|
|
49660
|
+
}
|
|
49661
|
+
this.context.customFeePolicy = nextPolicy;
|
|
49662
|
+
}
|
|
47389
49663
|
on(actionOrWildCard, handler) {
|
|
47390
49664
|
const action = actionOrWildCard;
|
|
47391
49665
|
const typedHandler = handler;
|
|
@@ -47442,6 +49716,7 @@ registerKit(`${pkg.name}/${pkg.version}`);
|
|
|
47442
49716
|
|
|
47443
49717
|
exports.AppKit = AppKit;
|
|
47444
49718
|
exports.BalanceError = BalanceError;
|
|
49719
|
+
exports.EARN_EXECUTE_REVIEW_KIND = EARN_EXECUTE_REVIEW_KIND;
|
|
47445
49720
|
exports.EarnError = EarnError;
|
|
47446
49721
|
exports.EarnKit = EarnKit;
|
|
47447
49722
|
exports.InputError = InputError;
|
|
@@ -47482,6 +49757,7 @@ exports.getTokenDecimals = getTokenDecimals;
|
|
|
47482
49757
|
exports.getVaultsParamsSchema = getVaultsParamsSchema;
|
|
47483
49758
|
exports.getWithdrawalQuoteParamsSchema = getWithdrawalQuoteParamsSchema;
|
|
47484
49759
|
exports.isBalanceError = isBalanceError;
|
|
49760
|
+
exports.isEarnExecuteReview = isEarnExecuteReview;
|
|
47485
49761
|
exports.isFatalError = isFatalError;
|
|
47486
49762
|
exports.isInputError = isInputError;
|
|
47487
49763
|
exports.isKitError = isKitError;
|