@circle-fin/app-kit 1.9.0 → 1.11.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 +60 -0
- package/bridge.cjs +691 -43
- package/bridge.d.cts +159 -11
- package/bridge.d.mts +159 -11
- package/bridge.d.ts +159 -11
- package/bridge.mjs +691 -43
- package/chains.cjs +19 -2
- package/chains.d.cts +1 -0
- package/chains.d.mts +1 -0
- package/chains.d.ts +1 -0
- package/chains.mjs +19 -2
- package/context.cjs +12 -0
- package/context.d.cts +164 -13
- package/context.d.mts +164 -13
- package/context.d.ts +164 -13
- package/context.mjs +12 -0
- package/earn.cjs +1559 -468
- package/earn.d.cts +533 -95
- package/earn.d.mts +533 -95
- package/earn.d.ts +533 -95
- package/earn.mjs +1559 -469
- package/estimateBridge.cjs +691 -43
- package/estimateBridge.d.cts +159 -11
- package/estimateBridge.d.mts +159 -11
- package/estimateBridge.d.ts +159 -11
- package/estimateBridge.mjs +691 -43
- package/estimateSwap.cjs +1065 -118
- package/estimateSwap.d.cts +159 -11
- package/estimateSwap.d.mts +159 -11
- package/estimateSwap.d.ts +159 -11
- package/estimateSwap.mjs +1065 -118
- package/index.cjs +2981 -718
- package/index.d.cts +1104 -133
- package/index.d.mts +1104 -133
- package/index.d.ts +1104 -133
- package/index.mjs +2981 -718
- package/package.json +7 -6
- package/swap.cjs +1065 -118
- package/swap.d.cts +159 -11
- package/swap.d.mts +159 -11
- package/swap.d.ts +159 -11
- package/swap.mjs +1065 -118
- package/unifiedBalance.cjs +817 -140
- package/unifiedBalance.d.cts +250 -9
- package/unifiedBalance.d.mts +250 -9
- package/unifiedBalance.d.ts +250 -9
- package/unifiedBalance.mjs +817 -140
package/earn.cjs
CHANGED
|
@@ -18,15 +18,29 @@
|
|
|
18
18
|
|
|
19
19
|
'use strict';
|
|
20
20
|
|
|
21
|
+
// Buffer polyfill setup - executes before any other code
|
|
22
|
+
// Ensures globalThis.Buffer is available for Solana libraries
|
|
23
|
+
const { Buffer } = require('buffer');
|
|
24
|
+
if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
|
|
25
|
+
globalThis.Buffer = Buffer;
|
|
26
|
+
}
|
|
27
|
+
if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
|
|
28
|
+
window.Buffer = Buffer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
21
32
|
var zod = require('zod');
|
|
22
33
|
require('pino');
|
|
34
|
+
require('@ethersproject/bytes');
|
|
35
|
+
require('@ethersproject/abi');
|
|
36
|
+
require('@ethersproject/address');
|
|
23
37
|
var web3_js = require('@solana/web3.js');
|
|
24
38
|
require('bn.js');
|
|
25
39
|
require('@coral-xyz/anchor');
|
|
26
40
|
require('bs58');
|
|
27
41
|
require('@noble/curves/ed25519');
|
|
28
|
-
var keccak256 = require('@ethersproject/keccak256');
|
|
29
42
|
var units = require('@ethersproject/units');
|
|
43
|
+
var keccak256 = require('@ethersproject/keccak256');
|
|
30
44
|
|
|
31
45
|
// Import global type declarations
|
|
32
46
|
/**
|
|
@@ -43,6 +57,51 @@ var units = require('@ethersproject/units');
|
|
|
43
57
|
* }
|
|
44
58
|
* ```
|
|
45
59
|
*/ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
|
|
60
|
+
/**
|
|
61
|
+
* Check whether the current runtime exposes a browser DOM.
|
|
62
|
+
*
|
|
63
|
+
* @remarks
|
|
64
|
+
* This intentionally does not treat every non-Node runtime as a browser.
|
|
65
|
+
* Server-side edge runtimes such as Cloudflare Workers, Deno, and Supabase
|
|
66
|
+
* Edge Functions do not expose Node globals but can safely use server
|
|
67
|
+
* credentials. A Node.js runtime remains server-side even when a test or SSR
|
|
68
|
+
* environment provides a DOM shim.
|
|
69
|
+
*
|
|
70
|
+
* @returns `true` when running in a browser window, `false` otherwise.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import { isBrowserEnvironment } from '@core/utils'
|
|
75
|
+
*
|
|
76
|
+
* if (isBrowserEnvironment()) {
|
|
77
|
+
* throw new Error('Server-only secrets must not be used in the browser')
|
|
78
|
+
* }
|
|
79
|
+
* ```
|
|
80
|
+
*/ const isBrowserEnvironment = ()=>{
|
|
81
|
+
const browserWindow = globalThis.window;
|
|
82
|
+
return !isNodeEnvironment() && browserWindow?.document !== undefined;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Return the SDK User-Agent request header only when running in Node.js.
|
|
86
|
+
*
|
|
87
|
+
* Browsers forbid manually setting `User-Agent`, and a custom fallback header
|
|
88
|
+
* can trigger CORS preflight. Non-Node server runtimes also omit this optional
|
|
89
|
+
* attribution header because they cannot set it reliably.
|
|
90
|
+
*
|
|
91
|
+
* @returns A User-Agent header in Node.js, or an empty object otherwise.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* import { getNodeUserAgentHeader } from '@core/utils'
|
|
96
|
+
*
|
|
97
|
+
* const headers = {
|
|
98
|
+
* 'Content-Type': 'application/json',
|
|
99
|
+
* ...getNodeUserAgentHeader(),
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
|
|
103
|
+
'User-Agent': getUserAgent()
|
|
104
|
+
} : {};
|
|
46
105
|
/**
|
|
47
106
|
* Detect the runtime environment and return a shortened identifier.
|
|
48
107
|
*
|
|
@@ -1761,14 +1820,14 @@ class KitError extends Error {
|
|
|
1761
1820
|
}
|
|
1762
1821
|
|
|
1763
1822
|
/**
|
|
1764
|
-
* Standardized error definitions for Earn
|
|
1823
|
+
* Standardized error definitions for Earn operations.
|
|
1765
1824
|
*
|
|
1766
1825
|
* These error codes provide fine-grained categorization of failures
|
|
1767
|
-
* from the
|
|
1826
|
+
* from the Earn service, enabling SDK consumers to distinguish
|
|
1768
1827
|
* between input errors (fix your request) and service errors (retry later).
|
|
1769
1828
|
*
|
|
1770
1829
|
* Error code ranges:
|
|
1771
|
-
* - 1100-
|
|
1830
|
+
* - 1100-1106: INPUT errors — invalid, unsupported, or stale request state
|
|
1772
1831
|
* - 8100-8105: SERVICE errors — retryable backend/provider failures
|
|
1773
1832
|
*
|
|
1774
1833
|
* @example
|
|
@@ -1821,6 +1880,14 @@ class KitError extends Error {
|
|
|
1821
1880
|
name: 'EARN_UNSUPPORTED_BRIDGE_ROUTE',
|
|
1822
1881
|
type: 'INPUT'
|
|
1823
1882
|
},
|
|
1883
|
+
/**
|
|
1884
|
+
* The bridge quote expired. This is an INPUT error because the prepared
|
|
1885
|
+
* request is stale and must be replaced instead of retried.
|
|
1886
|
+
*/ BRIDGE_QUOTE_EXPIRED: {
|
|
1887
|
+
code: 1106,
|
|
1888
|
+
name: 'EARN_BRIDGE_QUOTE_EXPIRED',
|
|
1889
|
+
type: 'INPUT'
|
|
1890
|
+
},
|
|
1824
1891
|
/** The proxy signing call failed — retryable. */ SIGNING_FAILED: {
|
|
1825
1892
|
code: 8100,
|
|
1826
1893
|
name: 'EARN_SIGNING_FAILED',
|
|
@@ -1880,6 +1947,9 @@ function getOptionalString(value) {
|
|
|
1880
1947
|
* internal-error, vault-refresh-busy, off-chain-paused, position-PnL-pending,
|
|
1881
1948
|
* bridge failures/status lookup failures
|
|
1882
1949
|
*
|
|
1950
|
+
* Quote expiry is INPUT/FATAL because callers must start a fresh bridge prepare
|
|
1951
|
+
* flow rather than retry the stale prepared bundle.
|
|
1952
|
+
*
|
|
1883
1953
|
* Unrecognized codes fall through to `parseApiError` for HTTP-status-based
|
|
1884
1954
|
* handling.
|
|
1885
1955
|
*
|
|
@@ -2113,6 +2183,13 @@ function getOptionalString(value) {
|
|
|
2113
2183
|
errorDef: EarnError.PROVIDER_ERROR,
|
|
2114
2184
|
recoverability: 'FATAL'
|
|
2115
2185
|
}
|
|
2186
|
+
],
|
|
2187
|
+
[
|
|
2188
|
+
380506,
|
|
2189
|
+
{
|
|
2190
|
+
errorDef: EarnError.BRIDGE_QUOTE_EXPIRED,
|
|
2191
|
+
recoverability: 'FATAL'
|
|
2192
|
+
}
|
|
2116
2193
|
]
|
|
2117
2194
|
]);
|
|
2118
2195
|
/**
|
|
@@ -2836,7 +2913,10 @@ var EarnChain;
|
|
|
2836
2913
|
contracts: {
|
|
2837
2914
|
v1: {
|
|
2838
2915
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
2839
|
-
minter: GATEWAY_MINTER_EVM_TESTNET
|
|
2916
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
2917
|
+
// DepositForHandler the GenericExecutor calls to run a fast cross-chain
|
|
2918
|
+
// deposit into the GatewayWallet above.
|
|
2919
|
+
depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
|
|
2840
2920
|
}
|
|
2841
2921
|
},
|
|
2842
2922
|
forwarderSupported: {
|
|
@@ -5904,7 +5984,10 @@ var Chains = {
|
|
|
5904
5984
|
minter: zod.z.string({
|
|
5905
5985
|
required_error: 'Gateway minter address is required. Please provide a valid contract address.',
|
|
5906
5986
|
invalid_type_error: 'Gateway minter address must be a string.'
|
|
5907
|
-
}).min(1, 'Gateway minter address cannot be empty.')
|
|
5987
|
+
}).min(1, 'Gateway minter address cannot be empty.'),
|
|
5988
|
+
depositForHandler: zod.z.string({
|
|
5989
|
+
invalid_type_error: 'Gateway depositForHandler address must be a string.'
|
|
5990
|
+
}).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
|
|
5908
5991
|
}).strict() // Reject any additional properties not defined in the schema
|
|
5909
5992
|
;
|
|
5910
5993
|
/**
|
|
@@ -6367,6 +6450,39 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
6367
6450
|
throw new Error(`Invalid chain identifier type: ${typeof chainIdentifier}. Expected ChainDefinition object, Blockchain enum, or string literal.`);
|
|
6368
6451
|
}
|
|
6369
6452
|
|
|
6453
|
+
/**
|
|
6454
|
+
* Resolve a chain identifier to a plain chain-name string.
|
|
6455
|
+
*
|
|
6456
|
+
* Accept a string literal (`'Ethereum'`), a `ChainDefinition`-like
|
|
6457
|
+
* object (`{ chain: 'Ethereum' }`), or `null`/`undefined` and return
|
|
6458
|
+
* the chain name as a string. Return `undefined` when the value
|
|
6459
|
+
* cannot be resolved.
|
|
6460
|
+
*
|
|
6461
|
+
* @remarks
|
|
6462
|
+
* Unlike `resolveChainIdentifier` (which returns a full `ChainDefinition`
|
|
6463
|
+
* and throws on invalid input), this helper is intentionally lenient and
|
|
6464
|
+
* never throws — it is safe to call in error-handling and telemetry paths.
|
|
6465
|
+
*
|
|
6466
|
+
* @param value - A string, chain-definition object, or nullish value.
|
|
6467
|
+
* @returns The chain name string, or `undefined`.
|
|
6468
|
+
*
|
|
6469
|
+
* @example
|
|
6470
|
+
* ```typescript
|
|
6471
|
+
* import { resolveChainName } from '@core/chains'
|
|
6472
|
+
*
|
|
6473
|
+
* resolveChainName('Ethereum') // 'Ethereum'
|
|
6474
|
+
* resolveChainName({ chain: 'Ethereum' }) // 'Ethereum'
|
|
6475
|
+
* resolveChainName(undefined) // undefined
|
|
6476
|
+
* ```
|
|
6477
|
+
*/ function resolveChainName(value) {
|
|
6478
|
+
if (value == null) return undefined;
|
|
6479
|
+
if (typeof value === 'string') return value;
|
|
6480
|
+
if (typeof value === 'object' && 'chain' in value && typeof value.chain === 'string') {
|
|
6481
|
+
return value.chain;
|
|
6482
|
+
}
|
|
6483
|
+
return undefined;
|
|
6484
|
+
}
|
|
6485
|
+
|
|
6370
6486
|
/**
|
|
6371
6487
|
* Extracts chain information including name, display name, and expected address format.
|
|
6372
6488
|
*
|
|
@@ -6751,13 +6867,12 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
6751
6867
|
headers: {
|
|
6752
6868
|
...DEFAULT_CONFIG$1.headers,
|
|
6753
6869
|
...config.headers ?? {},
|
|
6754
|
-
//
|
|
6755
|
-
//
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
}
|
|
6870
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
6871
|
+
// fallback header the SDK used instead trips CORS preflight against the
|
|
6872
|
+
// Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
|
|
6873
|
+
// blocking the request. So send the SDK user agent only in Node;
|
|
6874
|
+
// browsers omit it entirely.
|
|
6875
|
+
...getNodeUserAgentHeader()
|
|
6761
6876
|
}
|
|
6762
6877
|
};
|
|
6763
6878
|
let lastError;
|
|
@@ -8016,6 +8131,223 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8016
8131
|
return explorerUrl;
|
|
8017
8132
|
}
|
|
8018
8133
|
|
|
8134
|
+
/**
|
|
8135
|
+
* CCTP forwarding magic bytes prefix.
|
|
8136
|
+
*
|
|
8137
|
+
* The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
|
|
8138
|
+
* This prefix is right-padded to 24 bytes in the final hookData.
|
|
8139
|
+
*/ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
|
|
8140
|
+
|
|
8141
|
+
/**
|
|
8142
|
+
* Project an arbitrary payload onto the exact set of fields the telemetry
|
|
8143
|
+
* endpoint accepts.
|
|
8144
|
+
*
|
|
8145
|
+
* @remarks
|
|
8146
|
+
* Defense-in-depth before the last network hop: rather than
|
|
8147
|
+
* `JSON.stringify`-ing the caller's object verbatim, only the
|
|
8148
|
+
* allowlisted {@link ClientLogPayload} fields (and the allowlisted
|
|
8149
|
+
* sub-fields of `errorDetails` / `clientContext`) are copied across.
|
|
8150
|
+
* A regressing upstream mapper — or a plain-JS caller that bypasses the
|
|
8151
|
+
* type — therefore cannot exfiltrate stray properties (secrets, PII,
|
|
8152
|
+
* raw error stacks) through the analytics channel. Optional fields are
|
|
8153
|
+
* only included when present so the serialised shape matches the
|
|
8154
|
+
* server's strict schema.
|
|
8155
|
+
*
|
|
8156
|
+
* @internal
|
|
8157
|
+
*/ function toSafePayload(payload) {
|
|
8158
|
+
const clientContext = {
|
|
8159
|
+
platform: payload.clientContext.platform,
|
|
8160
|
+
os: payload.clientContext.os,
|
|
8161
|
+
runtimeName: payload.clientContext.runtimeName
|
|
8162
|
+
};
|
|
8163
|
+
const safe = {
|
|
8164
|
+
sdkName: payload.sdkName,
|
|
8165
|
+
sdkVersion: payload.sdkVersion,
|
|
8166
|
+
eventType: payload.eventType,
|
|
8167
|
+
timestamp: payload.timestamp,
|
|
8168
|
+
clientContext
|
|
8169
|
+
};
|
|
8170
|
+
if (payload.sourceChain !== undefined) safe['sourceChain'] = payload.sourceChain;
|
|
8171
|
+
if (payload.destinationChain !== undefined) safe['destinationChain'] = payload.destinationChain;
|
|
8172
|
+
if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
|
|
8173
|
+
if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
|
|
8174
|
+
if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
|
|
8175
|
+
if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
|
|
8176
|
+
if (payload.errorDetails !== undefined) {
|
|
8177
|
+
const errorDetails = {
|
|
8178
|
+
...payload.errorDetails.errorCode !== undefined && {
|
|
8179
|
+
errorCode: payload.errorDetails.errorCode
|
|
8180
|
+
},
|
|
8181
|
+
...payload.errorDetails.errorType !== undefined && {
|
|
8182
|
+
errorType: payload.errorDetails.errorType
|
|
8183
|
+
}
|
|
8184
|
+
};
|
|
8185
|
+
safe['errorDetails'] = errorDetails;
|
|
8186
|
+
}
|
|
8187
|
+
return safe;
|
|
8188
|
+
}
|
|
8189
|
+
/**
|
|
8190
|
+
* Default telemetry endpoint.
|
|
8191
|
+
*
|
|
8192
|
+
* Override via the `STABLECOIN_KITS_TELEMETRY_URL` environment variable
|
|
8193
|
+
* (e.g. for staging or local development).
|
|
8194
|
+
*
|
|
8195
|
+
* @internal
|
|
8196
|
+
*/ const DEFAULT_LOGS_URL = 'https://api.circle.com/v1/stablecoinKits/logs';
|
|
8197
|
+
/**
|
|
8198
|
+
* Resolve the telemetry endpoint URL.
|
|
8199
|
+
*
|
|
8200
|
+
* @internal
|
|
8201
|
+
*/ function getLogsUrl() {
|
|
8202
|
+
if (isNodeEnvironment() && typeof process.env['STABLECOIN_KITS_TELEMETRY_URL'] === 'string' && process.env['STABLECOIN_KITS_TELEMETRY_URL'].length > 0) {
|
|
8203
|
+
return process.env['STABLECOIN_KITS_TELEMETRY_URL'];
|
|
8204
|
+
}
|
|
8205
|
+
return DEFAULT_LOGS_URL;
|
|
8206
|
+
}
|
|
8207
|
+
/**
|
|
8208
|
+
* Send a telemetry event to the proxy service.
|
|
8209
|
+
*
|
|
8210
|
+
* @remarks
|
|
8211
|
+
* Fire-and-forget: the returned promise is intentionally not awaited
|
|
8212
|
+
* by the caller. A fetch failure (network error, non-2xx, timeout)
|
|
8213
|
+
* is silently swallowed so telemetry never blocks or fails user
|
|
8214
|
+
* operations.
|
|
8215
|
+
*
|
|
8216
|
+
* @param payload - The structured log payload matching the server schema.
|
|
8217
|
+
*
|
|
8218
|
+
* @example
|
|
8219
|
+
* ```typescript
|
|
8220
|
+
* import { emitAnalyticsLog } from '@core/utils'
|
|
8221
|
+
*
|
|
8222
|
+
* // Fire-and-forget — do not await
|
|
8223
|
+
* void emitAnalyticsLog(payload)
|
|
8224
|
+
* ```
|
|
8225
|
+
*/ async function emitAnalyticsLog(payload) {
|
|
8226
|
+
// Hand-rolled timeout via `AbortController` + `setTimeout` rather than
|
|
8227
|
+
// `AbortSignal.timeout(...)` so we can `clearTimeout` the handle in a
|
|
8228
|
+
// `finally`. `AbortSignal.timeout` registers a timer that stays on the
|
|
8229
|
+
// event loop until it fires even if the fetch already settled, which
|
|
8230
|
+
// manifests as spurious `TimeoutError` unhandled rejections during
|
|
8231
|
+
// process teardown (notably between e2e test fork lifecycles). See
|
|
8232
|
+
// nodejs/node#48298 for the underlying issue.
|
|
8233
|
+
const controller = new AbortController();
|
|
8234
|
+
const timeoutHandle = setTimeout(()=>{
|
|
8235
|
+
controller.abort(new DOMException('Telemetry request timed out', 'TimeoutError'));
|
|
8236
|
+
}, 5_000);
|
|
8237
|
+
// Don't let the timer keep the Node event loop alive in short-lived
|
|
8238
|
+
// CLIs / test processes; telemetry is best-effort and must never
|
|
8239
|
+
// block clean process exit. `unref` only exists on Node's `Timeout`
|
|
8240
|
+
// object, not on the `number` returned by the browser's `setTimeout`,
|
|
8241
|
+
// so we feature-detect rather than call unconditionally.
|
|
8242
|
+
if (typeof timeoutHandle.unref === 'function') {
|
|
8243
|
+
timeoutHandle.unref();
|
|
8244
|
+
}
|
|
8245
|
+
try {
|
|
8246
|
+
await fetch(getLogsUrl(), {
|
|
8247
|
+
method: 'POST',
|
|
8248
|
+
headers: {
|
|
8249
|
+
'Content-Type': 'application/json',
|
|
8250
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
8251
|
+
// fallback header the SDK used instead trips CORS preflight (it isn't
|
|
8252
|
+
// in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
|
|
8253
|
+
// it only in Node; browsers omit it entirely.
|
|
8254
|
+
...getNodeUserAgentHeader()
|
|
8255
|
+
},
|
|
8256
|
+
body: JSON.stringify(toSafePayload(payload)),
|
|
8257
|
+
signal: controller.signal
|
|
8258
|
+
});
|
|
8259
|
+
} catch {
|
|
8260
|
+
// Silently swallow — telemetry must never break user operations.
|
|
8261
|
+
} finally{
|
|
8262
|
+
clearTimeout(timeoutHandle);
|
|
8263
|
+
}
|
|
8264
|
+
}
|
|
8265
|
+
|
|
8266
|
+
/**
|
|
8267
|
+
* Build the `clientContext` object for telemetry payloads.
|
|
8268
|
+
*
|
|
8269
|
+
* @remarks
|
|
8270
|
+
* Use the exported `getRuntime()` and `isNodeEnvironment()` from
|
|
8271
|
+
* `@core/utils` to detect the runtime environment. The returned
|
|
8272
|
+
* string is parsed into the structured `ClientContext` fields
|
|
8273
|
+
* expected by the server schema.
|
|
8274
|
+
*
|
|
8275
|
+
* @returns A {@link ClientContext} with platform, OS, and runtime name
|
|
8276
|
+
* populated from the current environment.
|
|
8277
|
+
*
|
|
8278
|
+
* @example
|
|
8279
|
+
* ```typescript
|
|
8280
|
+
* import { buildClientContext } from '@core/utils'
|
|
8281
|
+
*
|
|
8282
|
+
* const ctx = buildClientContext()
|
|
8283
|
+
* // Node: { platform: 'node', os: 'darwin', runtimeName: null }
|
|
8284
|
+
* // Browser: { platform: 'browser', os: null, runtimeName: 'chrome' }
|
|
8285
|
+
* ```
|
|
8286
|
+
*/ function buildClientContext() {
|
|
8287
|
+
const runtime = getRuntime();
|
|
8288
|
+
if (runtime.startsWith('browser/')) {
|
|
8289
|
+
return {
|
|
8290
|
+
platform: 'browser',
|
|
8291
|
+
os: null,
|
|
8292
|
+
runtimeName: runtime.slice('browser/'.length).toLowerCase()
|
|
8293
|
+
};
|
|
8294
|
+
}
|
|
8295
|
+
if (runtime.startsWith('node/')) {
|
|
8296
|
+
return {
|
|
8297
|
+
platform: 'node',
|
|
8298
|
+
os: isNodeEnvironment() ? process.platform : null,
|
|
8299
|
+
runtimeName: null
|
|
8300
|
+
};
|
|
8301
|
+
}
|
|
8302
|
+
return {
|
|
8303
|
+
platform: 'node',
|
|
8304
|
+
os: null,
|
|
8305
|
+
runtimeName: null
|
|
8306
|
+
};
|
|
8307
|
+
}
|
|
8308
|
+
|
|
8309
|
+
/**
|
|
8310
|
+
* Extract structured error details from an unknown error value.
|
|
8311
|
+
*
|
|
8312
|
+
* @remarks
|
|
8313
|
+
* Handle three cases:
|
|
8314
|
+
* - `KitError` — extract `code` and `name`.
|
|
8315
|
+
* - `Error` — extract `name`.
|
|
8316
|
+
* - Anything else — return empty details.
|
|
8317
|
+
*
|
|
8318
|
+
* Only structured, bounded fields (`errorCode`, `errorType`) are
|
|
8319
|
+
* included. Free-text fields (`message`, `stack`) are intentionally
|
|
8320
|
+
* omitted to avoid leaking secrets or PII through vendor telemetry.
|
|
8321
|
+
*
|
|
8322
|
+
* @param error - The thrown value to extract details from.
|
|
8323
|
+
* @returns A {@link ErrorDetails} object suitable for telemetry payloads.
|
|
8324
|
+
*
|
|
8325
|
+
* @example
|
|
8326
|
+
* ```typescript
|
|
8327
|
+
* import { extractErrorDetails } from '@core/utils'
|
|
8328
|
+
*
|
|
8329
|
+
* try {
|
|
8330
|
+
* await riskyOperation()
|
|
8331
|
+
* } catch (error) {
|
|
8332
|
+
* const details = extractErrorDetails(error)
|
|
8333
|
+
* // { errorCode: '1001', errorType: 'INPUT_NETWORK_MISMATCH' }
|
|
8334
|
+
* }
|
|
8335
|
+
* ```
|
|
8336
|
+
*/ function extractErrorDetails(error) {
|
|
8337
|
+
if (error instanceof KitError) {
|
|
8338
|
+
return {
|
|
8339
|
+
errorCode: String(error.code),
|
|
8340
|
+
errorType: error.name
|
|
8341
|
+
};
|
|
8342
|
+
}
|
|
8343
|
+
if (error instanceof Error) {
|
|
8344
|
+
return {
|
|
8345
|
+
errorType: error.name
|
|
8346
|
+
};
|
|
8347
|
+
}
|
|
8348
|
+
return {};
|
|
8349
|
+
}
|
|
8350
|
+
|
|
8019
8351
|
/**
|
|
8020
8352
|
* Strip the `@circle-fin/` scope from a kit package name to produce the
|
|
8021
8353
|
* short SDK name used in telemetry payloads.
|
|
@@ -8034,8 +8366,154 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8034
8366
|
return pkgName.replace('@circle-fin/', '');
|
|
8035
8367
|
}
|
|
8036
8368
|
|
|
8369
|
+
/**
|
|
8370
|
+
* Soft signal for the case where building or emitting a telemetry payload
|
|
8371
|
+
* threw — for example, a buggy `TelemetryContextResolver`, a regression in
|
|
8372
|
+
* `extractErrorDetails`, or a synchronous failure inside `emitAnalyticsLog`
|
|
8373
|
+
* before it could swallow the error itself. Logged with a stable prefix so
|
|
8374
|
+
* consumers can grep for it. We deliberately do not re-throw: the caller's
|
|
8375
|
+
* original operation error must always win.
|
|
8376
|
+
*
|
|
8377
|
+
* @internal
|
|
8378
|
+
*/ function warnTelemetryDrop(eventType, cause) {
|
|
8379
|
+
try {
|
|
8380
|
+
// Pass `cause` as the second console.warn argument rather than
|
|
8381
|
+
// string-coercing it. `String(err)` (and `err.message` alone)
|
|
8382
|
+
// discards the stack trace, nested `cause`, and any custom Error
|
|
8383
|
+
// properties — exactly the context an on-call needs when a
|
|
8384
|
+
// resolver-closure regression triggers this path.
|
|
8385
|
+
console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
|
|
8386
|
+
} catch {
|
|
8387
|
+
// console.warn itself throwing is the user's environment; nothing more we
|
|
8388
|
+
// can do without risking the original operation error.
|
|
8389
|
+
}
|
|
8390
|
+
}
|
|
8391
|
+
/**
|
|
8392
|
+
* Build a telemetry payload from common fields.
|
|
8393
|
+
*
|
|
8394
|
+
* @internal
|
|
8395
|
+
*/ function buildPayload(config, eventType, errorDetails, context) {
|
|
8396
|
+
return {
|
|
8397
|
+
sdkName: config.sdkName,
|
|
8398
|
+
sdkVersion: config.sdkVersion,
|
|
8399
|
+
eventType,
|
|
8400
|
+
timestamp: new Date().toISOString(),
|
|
8401
|
+
...errorDetails !== undefined && {
|
|
8402
|
+
errorDetails
|
|
8403
|
+
},
|
|
8404
|
+
clientContext: buildClientContext(),
|
|
8405
|
+
...context?.sourceChain != null && {
|
|
8406
|
+
sourceChain: context.sourceChain
|
|
8407
|
+
},
|
|
8408
|
+
...context?.destinationChain != null && {
|
|
8409
|
+
destinationChain: context.destinationChain
|
|
8410
|
+
},
|
|
8411
|
+
...context?.tokenIn != null && {
|
|
8412
|
+
tokenIn: context.tokenIn
|
|
8413
|
+
},
|
|
8414
|
+
...context?.tokenOut != null && {
|
|
8415
|
+
tokenOut: context.tokenOut
|
|
8416
|
+
},
|
|
8417
|
+
...context?.txHash != null && {
|
|
8418
|
+
txHash: context.txHash
|
|
8419
|
+
},
|
|
8420
|
+
...context?.correlationId != null && {
|
|
8421
|
+
correlationId: context.correlationId
|
|
8422
|
+
}
|
|
8423
|
+
};
|
|
8424
|
+
}
|
|
8425
|
+
/**
|
|
8426
|
+
* Emit telemetry for a completed operation without affecting its caller.
|
|
8427
|
+
*
|
|
8428
|
+
* No-ops when `config.disabled` is `true`. Like {@link withErrorTelemetry},
|
|
8429
|
+
* failures while constructing or submitting the telemetry payload are reported
|
|
8430
|
+
* as a soft warning and never change a completed operation's result.
|
|
8431
|
+
*
|
|
8432
|
+
* @param eventType - The telemetry event type for the completed operation.
|
|
8433
|
+
* @param config - Per-kit SDK identity and disabled flag.
|
|
8434
|
+
* @param context - Optional chain, token, and transaction context.
|
|
8435
|
+
* @returns Nothing.
|
|
8436
|
+
* @throws Never — telemetry failures are reported as warnings.
|
|
8437
|
+
*
|
|
8438
|
+
* @example
|
|
8439
|
+
* ```typescript
|
|
8440
|
+
* import { emitSuccessTelemetry } from '@core/utils'
|
|
8441
|
+
*
|
|
8442
|
+
* emitSuccessTelemetry(
|
|
8443
|
+
* 'bridge_bridge',
|
|
8444
|
+
* { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
|
|
8445
|
+
* { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
|
|
8446
|
+
* )
|
|
8447
|
+
* ```
|
|
8448
|
+
*/ function emitSuccessTelemetry(eventType, config, context) {
|
|
8449
|
+
if (config.disabled) {
|
|
8450
|
+
return;
|
|
8451
|
+
}
|
|
8452
|
+
try {
|
|
8453
|
+
void emitAnalyticsLog(buildPayload(config, eventType, undefined, context));
|
|
8454
|
+
} catch (telemetryError) {
|
|
8455
|
+
warnTelemetryDrop(eventType, telemetryError);
|
|
8456
|
+
}
|
|
8457
|
+
}
|
|
8458
|
+
/**
|
|
8459
|
+
* Wrap an async operation with error telemetry.
|
|
8460
|
+
*
|
|
8461
|
+
* Execute `fn` and, if it throws, emit an error telemetry payload
|
|
8462
|
+
* before re-throwing. No-ops when `config.disabled` is `true`.
|
|
8463
|
+
*
|
|
8464
|
+
* `context` may be a static {@link TelemetryContext} or a
|
|
8465
|
+
* {@link TelemetryContextResolver}. The resolver is invoked in the
|
|
8466
|
+
* catch branch, so it can read state — most importantly `txHash` —
|
|
8467
|
+
* that the wrapped operation set after a successful broadcast. The
|
|
8468
|
+
* resolver must close over per-call locals only; passing instance
|
|
8469
|
+
* state would break isolation between concurrent invocations.
|
|
8470
|
+
*
|
|
8471
|
+
* @param fn - The async operation to execute.
|
|
8472
|
+
* @param eventType - The telemetry event type for this operation.
|
|
8473
|
+
* @param config - Per-kit SDK identity and disabled flag.
|
|
8474
|
+
* @param context - Optional context, static or lazily resolved.
|
|
8475
|
+
* @returns The result of the operation.
|
|
8476
|
+
* @throws Re-throws any error after emitting telemetry.
|
|
8477
|
+
*
|
|
8478
|
+
* @example
|
|
8479
|
+
* ```typescript
|
|
8480
|
+
* import { withErrorTelemetry } from '@core/utils'
|
|
8481
|
+
*
|
|
8482
|
+
* let txHash: string | undefined
|
|
8483
|
+
* const result = await withErrorTelemetry(
|
|
8484
|
+
* () => provider.swap(params, h => { txHash = h }),
|
|
8485
|
+
* 'swap_swap',
|
|
8486
|
+
* { sdkName: 'swap-kit', sdkVersion: '1.0.0', disabled: false },
|
|
8487
|
+
* () => ({
|
|
8488
|
+
* sourceChain: 'Ethereum',
|
|
8489
|
+
* tokenIn: 'USDC',
|
|
8490
|
+
* tokenOut: 'EURC',
|
|
8491
|
+
* ...(txHash != null && { txHash }),
|
|
8492
|
+
* }),
|
|
8493
|
+
* )
|
|
8494
|
+
* ```
|
|
8495
|
+
*/ async function withErrorTelemetry(fn, eventType, config, context) {
|
|
8496
|
+
try {
|
|
8497
|
+
return await fn();
|
|
8498
|
+
} catch (error) {
|
|
8499
|
+
if (!config.disabled) {
|
|
8500
|
+
try {
|
|
8501
|
+
const resolved = typeof context === 'function' ? context() : context;
|
|
8502
|
+
void emitAnalyticsLog(buildPayload(config, eventType, extractErrorDetails(error), resolved));
|
|
8503
|
+
} catch (telemetryError) {
|
|
8504
|
+
// Never let telemetry emission mask the original operation error.
|
|
8505
|
+
// But surface a soft signal so silent telemetry drops are
|
|
8506
|
+
// discoverable (e.g. a regression in a resolver closure or in
|
|
8507
|
+
// `extractErrorDetails`) instead of vanishing without any trace.
|
|
8508
|
+
warnTelemetryDrop(eventType, telemetryError);
|
|
8509
|
+
}
|
|
8510
|
+
}
|
|
8511
|
+
throw error;
|
|
8512
|
+
}
|
|
8513
|
+
}
|
|
8514
|
+
|
|
8037
8515
|
var name$3 = "@circle-fin/bridge-kit";
|
|
8038
|
-
var version$3 = "1.12.
|
|
8516
|
+
var version$3 = "1.12.2";
|
|
8039
8517
|
var pkg$3 = {
|
|
8040
8518
|
name: name$3,
|
|
8041
8519
|
version: version$3};
|
|
@@ -8860,6 +9338,17 @@ var TransferSpeed;
|
|
|
8860
9338
|
clock: zod.z.any().optional()
|
|
8861
9339
|
}).passthrough();
|
|
8862
9340
|
|
|
9341
|
+
/**
|
|
9342
|
+
* The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
|
|
9343
|
+
* hookData must start with.
|
|
9344
|
+
*
|
|
9345
|
+
* Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
|
|
9346
|
+
* so this module-level constant does not reference the Node `Buffer` global at
|
|
9347
|
+
* import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
|
|
9348
|
+
* bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
|
|
9349
|
+
* that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
|
|
9350
|
+
*/ Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
9351
|
+
|
|
8863
9352
|
/**
|
|
8864
9353
|
* The minimum finality threshold for CCTPv2 transfers.
|
|
8865
9354
|
*
|
|
@@ -8892,7 +9381,7 @@ var TransferSpeed;
|
|
|
8892
9381
|
registerKit(`${pkg$3.name}/${pkg$3.version}`);
|
|
8893
9382
|
|
|
8894
9383
|
var name$2 = "@circle-fin/swap-kit";
|
|
8895
|
-
var version$2 = "1.
|
|
9384
|
+
var version$2 = "1.5.0";
|
|
8896
9385
|
var pkg$2 = {
|
|
8897
9386
|
name: name$2,
|
|
8898
9387
|
version: version$2};
|
|
@@ -8957,7 +9446,10 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
8957
9446
|
}).min(1, 'kitKey must be a non-empty string').optional(),
|
|
8958
9447
|
provider: zod.z.string({
|
|
8959
9448
|
invalid_type_error: 'provider must be a string'
|
|
8960
|
-
}).min(1, 'provider must be a non-empty string').optional()
|
|
9449
|
+
}).min(1, 'provider must be a non-empty string').optional(),
|
|
9450
|
+
batchTransactions: zod.z.boolean({
|
|
9451
|
+
invalid_type_error: 'batchTransactions must be a boolean'
|
|
9452
|
+
}).optional()
|
|
8961
9453
|
});
|
|
8962
9454
|
/**
|
|
8963
9455
|
* Zod schema for adapter context.
|
|
@@ -9396,7 +9888,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
9396
9888
|
/**
|
|
9397
9889
|
* Circle Stablecoin Service API Key.
|
|
9398
9890
|
* Must be a valid API key format.
|
|
9399
|
-
*/ apiKey: apiKeySchema
|
|
9891
|
+
*/ apiKey: apiKeySchema.optional()
|
|
9400
9892
|
}).superRefine(requireCrossChainQuoteToAddress);
|
|
9401
9893
|
/**
|
|
9402
9894
|
* Zod schema for validating CreateSwapRequest parameters.
|
|
@@ -9454,7 +9946,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
9454
9946
|
/**
|
|
9455
9947
|
* Circle Stablecoin Service API Key.
|
|
9456
9948
|
* Must be a valid API key format.
|
|
9457
|
-
*/ apiKey: apiKeySchema
|
|
9949
|
+
*/ apiKey: apiKeySchema.optional()
|
|
9458
9950
|
});
|
|
9459
9951
|
/**
|
|
9460
9952
|
* Zod schema for validating GetSwapStatusResponse data.
|
|
@@ -9490,7 +9982,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
9490
9982
|
toChain: zod.z.string({
|
|
9491
9983
|
invalid_type_error: 'toChain must be a string'
|
|
9492
9984
|
}).min(1, 'toChain must be a non-empty string if provided').optional(),
|
|
9493
|
-
apiKey: apiKeySchema
|
|
9985
|
+
apiKey: apiKeySchema.optional()
|
|
9494
9986
|
});
|
|
9495
9987
|
/**
|
|
9496
9988
|
* Zod schema for validating CreateSwapResponse payloads.
|
|
@@ -9499,13 +9991,15 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
9499
9991
|
required_error: 'fee token is required',
|
|
9500
9992
|
invalid_type_error: 'fee token must be a string'
|
|
9501
9993
|
}).min(1, 'fee token must be a non-empty string'),
|
|
9502
|
-
amount: feeAmountSchema
|
|
9994
|
+
amount: feeAmountSchema,
|
|
9995
|
+
decimals: zod.z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
|
|
9996
|
+
symbol: zod.z.string({
|
|
9997
|
+
invalid_type_error: 'fee token symbol must be a string'
|
|
9998
|
+
}).min(1, 'fee token symbol must be a non-empty string').optional()
|
|
9503
9999
|
});
|
|
9504
10000
|
/**
|
|
9505
10001
|
* Developer fee item schema with basis field.
|
|
9506
|
-
*/ const createSwapDeveloperFeeItemSchema =
|
|
9507
|
-
token: zod.z.string().min(1, 'fee token must be a non-empty string'),
|
|
9508
|
-
amount: feeAmountSchema,
|
|
10002
|
+
*/ const createSwapDeveloperFeeItemSchema = createSwapFeeItemSchema.extend({
|
|
9509
10003
|
basis: zod.z.enum([
|
|
9510
10004
|
'inputAmount',
|
|
9511
10005
|
'estimatedAmount'
|
|
@@ -9597,7 +10091,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
9597
10091
|
addresses: zod.z.array(zod.z.string({
|
|
9598
10092
|
invalid_type_error: 'addresses entries must be strings'
|
|
9599
10093
|
}).min(1, 'addresses entries must be non-empty strings')).min(1, 'addresses must contain at least one entry when provided').max(MAX_RATE_ADDRESSES_PER_REQUEST, `addresses supports at most ${String(MAX_RATE_ADDRESSES_PER_REQUEST)} values per request`).optional(),
|
|
9600
|
-
apiKey: apiKeySchema
|
|
10094
|
+
apiKey: apiKeySchema.optional()
|
|
9601
10095
|
});
|
|
9602
10096
|
/**
|
|
9603
10097
|
* Zod schema for validating GetTokenRatesResponse payloads.
|
|
@@ -9628,6 +10122,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
9628
10122
|
required_error: 'estimatedAmount is required',
|
|
9629
10123
|
invalid_type_error: 'estimatedAmount must be a string'
|
|
9630
10124
|
}).min(1, 'estimatedAmount must be a non-empty string'),
|
|
10125
|
+
// Per-swap join key echoed back verbatim on success telemetry. Optional so a
|
|
10126
|
+
// not-yet-upgraded service (no field) still validates during rollout. A
|
|
10127
|
+
// malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
|
|
10128
|
+
// than throwing: this is a telemetry-only field (stripped from the developer
|
|
10129
|
+
// result, never used for control flow), so it must not be able to abort the
|
|
10130
|
+
// swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
|
|
10131
|
+
// never-throw contract of the rest of the telemetry stack. Implemented with
|
|
10132
|
+
// `preprocess` rather than Zod's `.catch()` because static analysis misreads
|
|
10133
|
+
// `.catch` on the schema chain as an unhandled Promise (S7785).
|
|
10134
|
+
correlationId: zod.z.preprocess((value)=>zod.z.string().uuid().safeParse(value).success ? value : undefined, zod.z.string().optional()),
|
|
9631
10135
|
config: createSwapRequestBaseSchema.shape.config.optional(),
|
|
9632
10136
|
fees: createSwapFeesSchema.optional(),
|
|
9633
10137
|
transaction: createSwapTransactionSchema
|
|
@@ -12186,7 +12690,7 @@ new Set(Object.values(Blockchain));
|
|
|
12186
12690
|
registerKit(`${pkg$2.name}/${pkg$2.version}`);
|
|
12187
12691
|
|
|
12188
12692
|
var name$1 = "@circle-fin/earn-kit";
|
|
12189
|
-
var version$1 = "1.
|
|
12693
|
+
var version$1 = "1.4.0";
|
|
12190
12694
|
var pkg$1 = {
|
|
12191
12695
|
name: name$1,
|
|
12192
12696
|
version: version$1};
|
|
@@ -12653,7 +13157,7 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
12653
13157
|
*
|
|
12654
13158
|
* @param params - Adapter, chain, token/delegate/wallet addresses, the required
|
|
12655
13159
|
* allowance for the signed payload, and a revert message for on-chain failure.
|
|
12656
|
-
* @returns The approval transaction
|
|
13160
|
+
* @returns The approval transaction result when an approval was submitted, or
|
|
12657
13161
|
* `undefined` when the existing allowance already covers `requiredAllowance`
|
|
12658
13162
|
* (or `requiredAllowance` is zero).
|
|
12659
13163
|
* @throws {@link KitError} If the `token.allowance` response is malformed.
|
|
@@ -12661,7 +13165,7 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
12661
13165
|
*
|
|
12662
13166
|
* @example
|
|
12663
13167
|
* ```typescript
|
|
12664
|
-
* const
|
|
13168
|
+
* const approval = await approveAllowanceIfNeeded({
|
|
12665
13169
|
* adapter,
|
|
12666
13170
|
* chain,
|
|
12667
13171
|
* tokenAddress: usdcAddress,
|
|
@@ -12723,7 +13227,15 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
12723
13227
|
maxAttempts: params.allowancePropagation?.maxAttempts ?? DEFAULT_PROPAGATION_ATTEMPTS,
|
|
12724
13228
|
delayMs: params.allowancePropagation?.delayMs ?? DEFAULT_PROPAGATION_DELAY_MS
|
|
12725
13229
|
});
|
|
12726
|
-
return
|
|
13230
|
+
return {
|
|
13231
|
+
txHash: approvalTxHash,
|
|
13232
|
+
...approvalReceipt.gasUsed !== undefined && {
|
|
13233
|
+
gasUsed: approvalReceipt.gasUsed
|
|
13234
|
+
},
|
|
13235
|
+
...approvalReceipt.effectiveGasPrice !== undefined && {
|
|
13236
|
+
effectiveGasPrice: approvalReceipt.effectiveGasPrice
|
|
13237
|
+
}
|
|
13238
|
+
};
|
|
12727
13239
|
}
|
|
12728
13240
|
|
|
12729
13241
|
/** @internal */ function isSameAddress(actual, expected) {
|
|
@@ -12871,7 +13383,13 @@ const GAS_SAFETY_MULTIPLIER_DENOMINATOR = 10n;
|
|
|
12871
13383
|
}
|
|
12872
13384
|
return {
|
|
12873
13385
|
txHash,
|
|
12874
|
-
explorerUrl
|
|
13386
|
+
explorerUrl,
|
|
13387
|
+
...receipt.gasUsed !== undefined && {
|
|
13388
|
+
gasUsed: receipt.gasUsed
|
|
13389
|
+
},
|
|
13390
|
+
...receipt.effectiveGasPrice !== undefined && {
|
|
13391
|
+
effectiveGasPrice: receipt.effectiveGasPrice
|
|
13392
|
+
}
|
|
12875
13393
|
};
|
|
12876
13394
|
}
|
|
12877
13395
|
|
|
@@ -13183,112 +13701,6 @@ const EARN_OPERATIONS = new Set([
|
|
|
13183
13701
|
return hasEarnServiceParamsShape(operation, candidate['params']);
|
|
13184
13702
|
}
|
|
13185
13703
|
|
|
13186
|
-
function buildGasFeeBase(name, chain) {
|
|
13187
|
-
return {
|
|
13188
|
-
name,
|
|
13189
|
-
token: chain.nativeCurrency.symbol,
|
|
13190
|
-
blockchain: chain.chain
|
|
13191
|
-
};
|
|
13192
|
-
}
|
|
13193
|
-
function buildGasFeeSuccess(name, chain, fees) {
|
|
13194
|
-
return {
|
|
13195
|
-
...buildGasFeeBase(name, chain),
|
|
13196
|
-
fees
|
|
13197
|
-
};
|
|
13198
|
-
}
|
|
13199
|
-
function buildGasFeeFailure(name, chain, error) {
|
|
13200
|
-
return {
|
|
13201
|
-
...buildGasFeeBase(name, chain),
|
|
13202
|
-
fees: null,
|
|
13203
|
-
error: getErrorMessage(error)
|
|
13204
|
-
};
|
|
13205
|
-
}
|
|
13206
|
-
async function estimatePreparedGasFee(name, chain, prepared) {
|
|
13207
|
-
try {
|
|
13208
|
-
const estimate = bufferEstimatedGas(await prepared.estimate());
|
|
13209
|
-
if (estimate.gas <= 0n) {
|
|
13210
|
-
throw createValidationFailedError('estimate.gas', estimate.gas.toString(), 'gas estimate must be greater than zero');
|
|
13211
|
-
}
|
|
13212
|
-
return buildGasFeeSuccess(name, chain, estimate);
|
|
13213
|
-
} catch (error) {
|
|
13214
|
-
return buildGasFeeFailure(name, chain, error);
|
|
13215
|
-
}
|
|
13216
|
-
}
|
|
13217
|
-
async function estimateApprovalGasFeeIfNeeded(params) {
|
|
13218
|
-
const { adapter, chain, address, tokenAddress, delegate, requiredAllowance } = params;
|
|
13219
|
-
if (requiredAllowance <= 0n) {
|
|
13220
|
-
return undefined;
|
|
13221
|
-
}
|
|
13222
|
-
try {
|
|
13223
|
-
const allowancePrepared = await adapter.prepareAction('token.allowance', {
|
|
13224
|
-
tokenAddress,
|
|
13225
|
-
delegate
|
|
13226
|
-
}, {
|
|
13227
|
-
chain,
|
|
13228
|
-
address
|
|
13229
|
-
});
|
|
13230
|
-
const allowanceRaw = await allowancePrepared.execute();
|
|
13231
|
-
const currentAllowance = parseAllowanceResponse(allowanceRaw);
|
|
13232
|
-
if (currentAllowance >= requiredAllowance) {
|
|
13233
|
-
return undefined;
|
|
13234
|
-
}
|
|
13235
|
-
// Reuse the execute path's approval builder so the estimate simulates the
|
|
13236
|
-
// exact approval (action, amount, and WARM_SLOT_RESIDUAL) that
|
|
13237
|
-
// approveAllowanceIfNeeded later submits.
|
|
13238
|
-
const approvalPrepared = await prepareApprovalAction({
|
|
13239
|
-
adapter,
|
|
13240
|
-
chain,
|
|
13241
|
-
address,
|
|
13242
|
-
tokenAddress,
|
|
13243
|
-
delegate,
|
|
13244
|
-
currentAllowance,
|
|
13245
|
-
requiredAllowance
|
|
13246
|
-
});
|
|
13247
|
-
return await estimatePreparedGasFee('Approve', chain, approvalPrepared);
|
|
13248
|
-
} catch (error) {
|
|
13249
|
-
return buildGasFeeFailure('Approve', chain, error);
|
|
13250
|
-
}
|
|
13251
|
-
}
|
|
13252
|
-
/**
|
|
13253
|
-
* Estimate gas fee entries for an earn quote without submitting transactions.
|
|
13254
|
-
*
|
|
13255
|
-
* Each entry is produced by simulating the prepared transaction against
|
|
13256
|
-
* current chain state. When an approval is required (allowance below the
|
|
13257
|
-
* signed payload's required amount), the subsequent action simulation runs
|
|
13258
|
-
* without that approval in place and is expected to revert — the action entry
|
|
13259
|
-
* then carries `fees: null` with the revert message while the approval entry
|
|
13260
|
-
* still estimates normally. Quote consumers must treat that as "estimate
|
|
13261
|
-
* pending approval", not a hard failure.
|
|
13262
|
-
*
|
|
13263
|
-
* @internal
|
|
13264
|
-
*/ async function estimateEarnQuoteGasFees(params) {
|
|
13265
|
-
const { adapter, chain, address, actionName, actionKey, actionParams, approval } = params;
|
|
13266
|
-
const gasFees = [];
|
|
13267
|
-
if (approval !== undefined) {
|
|
13268
|
-
const approvalEstimate = await estimateApprovalGasFeeIfNeeded({
|
|
13269
|
-
adapter,
|
|
13270
|
-
chain,
|
|
13271
|
-
address,
|
|
13272
|
-
tokenAddress: approval.token,
|
|
13273
|
-
delegate: approval.delegate,
|
|
13274
|
-
requiredAllowance: approval.requiredAllowance
|
|
13275
|
-
});
|
|
13276
|
-
if (approvalEstimate !== undefined) {
|
|
13277
|
-
gasFees.push(approvalEstimate);
|
|
13278
|
-
}
|
|
13279
|
-
}
|
|
13280
|
-
try {
|
|
13281
|
-
const actionPrepared = await adapter.prepareAction(actionKey, actionParams, {
|
|
13282
|
-
chain,
|
|
13283
|
-
address
|
|
13284
|
-
});
|
|
13285
|
-
gasFees.push(await estimatePreparedGasFee(actionName, chain, actionPrepared));
|
|
13286
|
-
} catch (error) {
|
|
13287
|
-
gasFees.push(buildGasFeeFailure(actionName, chain, error));
|
|
13288
|
-
}
|
|
13289
|
-
return gasFees;
|
|
13290
|
-
}
|
|
13291
|
-
|
|
13292
13704
|
// ---------------------------------------------------------------------------
|
|
13293
13705
|
// Shared primitives
|
|
13294
13706
|
// ---------------------------------------------------------------------------
|
|
@@ -13366,7 +13778,11 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
13366
13778
|
asset: zod.z.string(),
|
|
13367
13779
|
assetAddress: zod.z.string(),
|
|
13368
13780
|
lltv: zod.z.number(),
|
|
13369
|
-
supplyUsd: zod.z.number()
|
|
13781
|
+
supplyUsd: zod.z.number(),
|
|
13782
|
+
// Optional during the expand/contract window (a backend that predates the
|
|
13783
|
+
// field omits the key), mirroring the `.optional()` facets on the base
|
|
13784
|
+
// schema; `null` when the product exposes no per-market allocation (V2).
|
|
13785
|
+
allocationPct: zod.z.number().nullable().optional()
|
|
13370
13786
|
});
|
|
13371
13787
|
/**
|
|
13372
13788
|
* Zod schema for a Morpho vault warning in the API response.
|
|
@@ -13380,15 +13796,82 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
13380
13796
|
])
|
|
13381
13797
|
});
|
|
13382
13798
|
/**
|
|
13383
|
-
* Zod schema for
|
|
13799
|
+
* Zod schema for the manager (curator) facet in the API response.
|
|
13384
13800
|
*
|
|
13385
13801
|
* @internal
|
|
13386
|
-
*/ const
|
|
13387
|
-
vaultAddress: zod.z.string(),
|
|
13388
|
-
chain: zod.z.string(),
|
|
13802
|
+
*/ const managerSchema = zod.z.object({
|
|
13389
13803
|
name: zod.z.string(),
|
|
13390
|
-
|
|
13391
|
-
|
|
13804
|
+
address: zod.z.string().optional(),
|
|
13805
|
+
// Only 'curator' is emitted today (Morpho V1/V2). Additional manager roles
|
|
13806
|
+
// are added here as the providers that emit them land, rather than shipped
|
|
13807
|
+
// speculatively.
|
|
13808
|
+
type: zod.z.enum([
|
|
13809
|
+
'curator'
|
|
13810
|
+
])
|
|
13811
|
+
});
|
|
13812
|
+
/**
|
|
13813
|
+
* Zod schema for the APY profile facet in the API response.
|
|
13814
|
+
*
|
|
13815
|
+
* @internal
|
|
13816
|
+
*/ const apyProfileSchema = zod.z.object({
|
|
13817
|
+
current: zod.z.number(),
|
|
13818
|
+
native: zod.z.number().nullable(),
|
|
13819
|
+
d7: zod.z.number().nullable(),
|
|
13820
|
+
d30: zod.z.number().nullable(),
|
|
13821
|
+
d90: zod.z.number().nullable(),
|
|
13822
|
+
rewardShare: zod.z.number().nullable(),
|
|
13823
|
+
source: zod.z.string().optional(),
|
|
13824
|
+
asOf: zod.z.string().optional()
|
|
13825
|
+
});
|
|
13826
|
+
/**
|
|
13827
|
+
* Zod schema for the fee split facet in the API response.
|
|
13828
|
+
*
|
|
13829
|
+
* @internal
|
|
13830
|
+
*/ const feeInfoSchema = zod.z.object({
|
|
13831
|
+
performance: zod.z.number().nullable(),
|
|
13832
|
+
management: zod.z.number().nullable()
|
|
13833
|
+
});
|
|
13834
|
+
/**
|
|
13835
|
+
* Zod schema for the liquidity profile facet in the API response.
|
|
13836
|
+
*
|
|
13837
|
+
* `totalSupply` is the outstanding vault share tokens (ERC4626 totalSupply);
|
|
13838
|
+
* it is validated as a raw JSON amount, like `totalDeposits`/`available`.
|
|
13839
|
+
*
|
|
13840
|
+
* @internal
|
|
13841
|
+
*/ const liquidityProfileSchema = zod.z.object({
|
|
13842
|
+
totalDeposits: amountJsonSchema,
|
|
13843
|
+
available: amountJsonSchema,
|
|
13844
|
+
totalSupply: amountJsonSchema,
|
|
13845
|
+
status: zod.z.enum([
|
|
13846
|
+
'active',
|
|
13847
|
+
'low_liquidity'
|
|
13848
|
+
])
|
|
13849
|
+
});
|
|
13850
|
+
/**
|
|
13851
|
+
* Zod schema for the risk signals facet in the API response.
|
|
13852
|
+
*
|
|
13853
|
+
* @internal
|
|
13854
|
+
*/ const riskSignalsSchema = zod.z.object({
|
|
13855
|
+
circleSentinel: zod.z.boolean(),
|
|
13856
|
+
warnings: zod.z.array(vaultWarningSchema).optional(),
|
|
13857
|
+
earnKitWarnings: zod.z.array(zod.z.string()).optional()
|
|
13858
|
+
});
|
|
13859
|
+
/**
|
|
13860
|
+
* Zod schema for the universal earn-opportunity base in the API response.
|
|
13861
|
+
*
|
|
13862
|
+
* Retains every existing deprecated flat field (kept validated through the
|
|
13863
|
+
* expand/contract window so default-strip does not drop them) and adds the
|
|
13864
|
+
* new nested facets. The nested facets are `.optional()` during the
|
|
13865
|
+
* transition so the SDK still validates against a not-yet-fully-deployed
|
|
13866
|
+
* backend; they become required after Expand ships.
|
|
13867
|
+
*
|
|
13868
|
+
* @internal
|
|
13869
|
+
*/ const vaultInfoResponseSchema = zod.z.object({
|
|
13870
|
+
vaultAddress: zod.z.string(),
|
|
13871
|
+
chain: zod.z.string(),
|
|
13872
|
+
name: zod.z.string(),
|
|
13873
|
+
protocol: zod.z.string(),
|
|
13874
|
+
asset: zod.z.string(),
|
|
13392
13875
|
assetAddress: zod.z.string(),
|
|
13393
13876
|
currentApy: zod.z.number(),
|
|
13394
13877
|
nativeApy: zod.z.number(),
|
|
@@ -13405,6 +13888,96 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
13405
13888
|
warnings: zod.z.array(vaultWarningSchema).optional(),
|
|
13406
13889
|
earnKitWarnings: zod.z.array(zod.z.string()).optional()
|
|
13407
13890
|
});
|
|
13891
|
+
/**
|
|
13892
|
+
* Shared base schema: existing flat fields (kept) plus the new nested
|
|
13893
|
+
* facets and neutral identity. Facets are `.optional()` during the
|
|
13894
|
+
* transition; flip to required once the backend is confirmed emitting.
|
|
13895
|
+
*
|
|
13896
|
+
* @internal
|
|
13897
|
+
*/ const earnBaseSchema = vaultInfoResponseSchema.extend({
|
|
13898
|
+
address: zod.z.string().optional(),
|
|
13899
|
+
asOf: zod.z.string().optional(),
|
|
13900
|
+
manager: managerSchema.nullable().optional(),
|
|
13901
|
+
apyProfile: apyProfileSchema.optional(),
|
|
13902
|
+
fee: feeInfoSchema.optional(),
|
|
13903
|
+
liquidityProfile: liquidityProfileSchema.optional(),
|
|
13904
|
+
riskSignals: riskSignalsSchema.optional()
|
|
13905
|
+
});
|
|
13906
|
+
/**
|
|
13907
|
+
* Zod schema for the `vault` opportunity variant.
|
|
13908
|
+
*
|
|
13909
|
+
* @internal
|
|
13910
|
+
*/ const vaultOpportunitySchema = earnBaseSchema.extend({
|
|
13911
|
+
productType: zod.z.literal('vault'),
|
|
13912
|
+
collateral: zod.z.array(collateralSchema)
|
|
13913
|
+
});
|
|
13914
|
+
/**
|
|
13915
|
+
* Discriminated union over `productType`. Add union members here as new
|
|
13916
|
+
* product types (e.g. `lending_market`, `rwa_token`) land.
|
|
13917
|
+
*
|
|
13918
|
+
* @internal
|
|
13919
|
+
*/ const earnOpportunityVariants = [
|
|
13920
|
+
vaultOpportunitySchema
|
|
13921
|
+
];
|
|
13922
|
+
/** @internal */ const earnOpportunitySchema = zod.z.discriminatedUnion('productType', earnOpportunityVariants);
|
|
13923
|
+
/** Product types this SDK version knows how to parse. */ const knownProductTypes = new Set(earnOpportunityVariants.map((variant)=>variant.shape.productType.value));
|
|
13924
|
+
/**
|
|
13925
|
+
* Tolerant list parser for earn opportunities.
|
|
13926
|
+
*
|
|
13927
|
+
* `z.discriminatedUnion` throws on an unrecognized discriminant and
|
|
13928
|
+
* `z.array` fails the whole array if any element fails. Two migration-window
|
|
13929
|
+
* cases are smoothed over here so neither breaks an already-shipped SDK:
|
|
13930
|
+
*
|
|
13931
|
+
* - A backend that predates `productType` omits it entirely. `'vault'` was the
|
|
13932
|
+
* only opportunity type then, so default a missing discriminant to `'vault'`
|
|
13933
|
+
* rather than dropping every vault the backend returns.
|
|
13934
|
+
* - A future backend adds a *second* `productType` this SDK version does not
|
|
13935
|
+
* know. Drop those elements (a present-but-unrecognized discriminant) instead
|
|
13936
|
+
* of rejecting the whole list.
|
|
13937
|
+
*
|
|
13938
|
+
* Only the drop above is a *tolerant* case. Anything that is not a plain object
|
|
13939
|
+
* with a present-but-unknown string `productType` — `null`, `undefined`,
|
|
13940
|
+
* primitives, or an object whose `productType` is malformed — is passed through
|
|
13941
|
+
* untouched so `z.array(earnOpportunitySchema)` reports it as a normal
|
|
13942
|
+
* validation failure. It is deliberately not silently dropped (which would hide
|
|
13943
|
+
* malformed backend data) and never throws here (an unguarded property read on
|
|
13944
|
+
* a non-object would escape `safeParse` as a raw `TypeError` instead of a
|
|
13945
|
+
* `ZodError`).
|
|
13946
|
+
*
|
|
13947
|
+
* @internal
|
|
13948
|
+
*/ const earnOpportunityListSchema = zod.z.preprocess((raw)=>{
|
|
13949
|
+
if (!Array.isArray(raw)) {
|
|
13950
|
+
return raw;
|
|
13951
|
+
}
|
|
13952
|
+
// Array.isArray narrows `raw` to `any[]`; view it as `unknown[]` so the
|
|
13953
|
+
// map/filter chain stays type-safe and no `any` leaks into the return.
|
|
13954
|
+
const entries = raw;
|
|
13955
|
+
return entries.map((entry)=>{
|
|
13956
|
+
// Only touch plain objects; non-objects fall through to fail validation.
|
|
13957
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
13958
|
+
return entry;
|
|
13959
|
+
}
|
|
13960
|
+
const record = entry;
|
|
13961
|
+
// Older backend predating productType: default to the only type then.
|
|
13962
|
+
return record.productType === undefined ? {
|
|
13963
|
+
...record,
|
|
13964
|
+
productType: 'vault'
|
|
13965
|
+
} : record;
|
|
13966
|
+
}).filter((entry)=>{
|
|
13967
|
+
// Drop ONLY a present-but-unknown string discriminant (a future
|
|
13968
|
+
// productType this SDK version doesn't know). Everything else —
|
|
13969
|
+
// non-objects, a non-string productType — flows through to
|
|
13970
|
+
// z.array(earnOpportunitySchema) and fails/passes validation normally.
|
|
13971
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
13972
|
+
return true;
|
|
13973
|
+
}
|
|
13974
|
+
const productType = entry.productType;
|
|
13975
|
+
if (typeof productType !== 'string') {
|
|
13976
|
+
return true;
|
|
13977
|
+
}
|
|
13978
|
+
return knownProductTypes.has(productType);
|
|
13979
|
+
});
|
|
13980
|
+
}, zod.z.array(earnOpportunitySchema));
|
|
13408
13981
|
// ---------------------------------------------------------------------------
|
|
13409
13982
|
// Position response schema
|
|
13410
13983
|
// ---------------------------------------------------------------------------
|
|
@@ -13534,6 +14107,7 @@ const positionPnlSchema = zod.z.discriminatedUnion('status', [
|
|
|
13534
14107
|
*
|
|
13535
14108
|
* @internal
|
|
13536
14109
|
*/ const depositPayloadSchema = zod.z.object({
|
|
14110
|
+
execId: bridgeDepositExecIdSchema,
|
|
13537
14111
|
executionParams: depositExecutionParamsSchema,
|
|
13538
14112
|
signature: hexSignatureSchema
|
|
13539
14113
|
});
|
|
@@ -13625,6 +14199,21 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
13625
14199
|
amount: amountJsonSchema,
|
|
13626
14200
|
vaultAddress: hexAddressSchema
|
|
13627
14201
|
}).passthrough();
|
|
14202
|
+
/** @internal */ const bridgeQuoteExpirySchema = zod.z.discriminatedUnion('mode', [
|
|
14203
|
+
zod.z.object({
|
|
14204
|
+
mode: zod.z.literal('TIMESTAMP'),
|
|
14205
|
+
expiresAt: zod.z.string().datetime({
|
|
14206
|
+
offset: true
|
|
14207
|
+
})
|
|
14208
|
+
}),
|
|
14209
|
+
zod.z.object({
|
|
14210
|
+
mode: zod.z.literal('BLOCK_NUMBER'),
|
|
14211
|
+
expiresAtBlock: zod.z.number().int(),
|
|
14212
|
+
blockEstimatedAt: zod.z.string().datetime({
|
|
14213
|
+
offset: true
|
|
14214
|
+
}).optional()
|
|
14215
|
+
})
|
|
14216
|
+
]).optional().catch(undefined);
|
|
13628
14217
|
/**
|
|
13629
14218
|
* Zod schema for the bridge deposit prepare payload.
|
|
13630
14219
|
*
|
|
@@ -13636,6 +14225,10 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
13636
14225
|
execId: bridgeDepositExecIdSchema,
|
|
13637
14226
|
erc3009TypedData: bridgeDepositPreparedBundleSchema,
|
|
13638
14227
|
expiresAt: zod.z.string().datetime(),
|
|
14228
|
+
quoteIssuedAt: zod.z.string().datetime({
|
|
14229
|
+
offset: true
|
|
14230
|
+
}).optional().catch(undefined),
|
|
14231
|
+
quoteExpiry: bridgeQuoteExpirySchema,
|
|
13639
14232
|
review: bridgeDepositPrepareReviewSchema
|
|
13640
14233
|
});
|
|
13641
14234
|
/**
|
|
@@ -13701,6 +14294,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
13701
14294
|
*
|
|
13702
14295
|
* @internal
|
|
13703
14296
|
*/ const withdrawPayloadSchema = zod.z.object({
|
|
14297
|
+
execId: bridgeDepositExecIdSchema,
|
|
13704
14298
|
executionParams: withdrawExecutionParamsSchema,
|
|
13705
14299
|
signature: hexSignatureSchema
|
|
13706
14300
|
});
|
|
@@ -13714,6 +14308,27 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
13714
14308
|
data: withdrawPayloadSchema
|
|
13715
14309
|
});
|
|
13716
14310
|
// ---------------------------------------------------------------------------
|
|
14311
|
+
// Transaction report response schema
|
|
14312
|
+
// ---------------------------------------------------------------------------
|
|
14313
|
+
/**
|
|
14314
|
+
* Zod schema for the transaction report payload inside the API `data` envelope.
|
|
14315
|
+
*
|
|
14316
|
+
* The Earn Service returns an empty payload (`{"data":{}}`) on success, so the
|
|
14317
|
+
* schema accepts any object shape and does not require specific fields.
|
|
14318
|
+
*
|
|
14319
|
+
* @internal
|
|
14320
|
+
*/ const transactionReportPayloadSchema = zod.z.object({}).passthrough();
|
|
14321
|
+
/**
|
|
14322
|
+
* Zod schema for the `POST /v1/earnKit/transactions/report` API response.
|
|
14323
|
+
*
|
|
14324
|
+
* The Earn Service API wraps the transaction report payload in a `data`
|
|
14325
|
+
* envelope.
|
|
14326
|
+
*
|
|
14327
|
+
* @internal
|
|
14328
|
+
*/ const transactionReportResponseSchema = zod.z.object({
|
|
14329
|
+
data: transactionReportPayloadSchema
|
|
14330
|
+
});
|
|
14331
|
+
// ---------------------------------------------------------------------------
|
|
13717
14332
|
// Claim rewards response schema
|
|
13718
14333
|
// ---------------------------------------------------------------------------
|
|
13719
14334
|
/**
|
|
@@ -13774,6 +14389,30 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
13774
14389
|
token: zod.z.string(),
|
|
13775
14390
|
amount: amountJsonSchema
|
|
13776
14391
|
});
|
|
14392
|
+
/**
|
|
14393
|
+
* Zod schema for a native gas-fee entry in an EarnKit quote response.
|
|
14394
|
+
*
|
|
14395
|
+
* The Earn Service backend estimates gas server-side and returns one entry per
|
|
14396
|
+
* action (`Approve`, `Deposit`, `Withdraw`). A successful estimate carries
|
|
14397
|
+
* `fees` in the SDK `EstimatedGas` shape (`{ gas, gasPrice, fee }`), each a raw
|
|
14398
|
+
* integer string in the chain's native base units. When the backend cannot
|
|
14399
|
+
* estimate an action it returns `fees: null` with an `error` message instead.
|
|
14400
|
+
*
|
|
14401
|
+
* The schema deliberately validates almost nothing beyond the envelope: `name`
|
|
14402
|
+
* is optional and `fees` is entirely unvalidated (`unknown`). ALL validation
|
|
14403
|
+
* of `fees` — that it is an object at all, and that `gas`, `gasPrice`, and
|
|
14404
|
+
* `fee` are parseable integer strings — is deferred to {@link toQuoteGasFees},
|
|
14405
|
+
* which degrades a malformed entry to a `fees: null` soft failure. This is
|
|
14406
|
+
* intentional: gas is best-effort, so a single unparseable gas entry (a wrong
|
|
14407
|
+
* type such as `fees: 123` or `fees: 'bad'`, a missing field, or a non-numeric
|
|
14408
|
+
* `fee`) must never fail Zod validation and reject the entire quote.
|
|
14409
|
+
*
|
|
14410
|
+
* @internal
|
|
14411
|
+
*/ const quoteGasFeeSchema = zod.z.object({
|
|
14412
|
+
name: zod.z.string().optional(),
|
|
14413
|
+
fees: zod.z.unknown(),
|
|
14414
|
+
error: zod.z.string().optional()
|
|
14415
|
+
}).passthrough();
|
|
13777
14416
|
/**
|
|
13778
14417
|
* Zod schema for the inner deposit quote payload.
|
|
13779
14418
|
*
|
|
@@ -13789,7 +14428,8 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
13789
14428
|
expectedShares: amountJsonSchema,
|
|
13790
14429
|
sharePrice: zod.z.string(),
|
|
13791
14430
|
currentApy: zod.z.number(),
|
|
13792
|
-
fees: zod.z.array(feeSchema).optional()
|
|
14431
|
+
fees: zod.z.array(feeSchema).optional(),
|
|
14432
|
+
gasFees: zod.z.array(quoteGasFeeSchema).optional()
|
|
13793
14433
|
});
|
|
13794
14434
|
/**
|
|
13795
14435
|
* Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
|
|
@@ -13816,6 +14456,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
13816
14456
|
sharePrice: zod.z.string(),
|
|
13817
14457
|
maxWithdrawable: amountJsonSchema,
|
|
13818
14458
|
fees: zod.z.array(feeSchema),
|
|
14459
|
+
gasFees: zod.z.array(quoteGasFeeSchema).optional(),
|
|
13819
14460
|
warnings: zod.z.array(zod.z.string()).optional()
|
|
13820
14461
|
});
|
|
13821
14462
|
/**
|
|
@@ -13873,7 +14514,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
13873
14514
|
*
|
|
13874
14515
|
* @internal
|
|
13875
14516
|
*/ const getVaultsPayloadSchema = zod.z.object({
|
|
13876
|
-
vaults:
|
|
14517
|
+
vaults: earnOpportunityListSchema,
|
|
13877
14518
|
errors: zod.z.array(vaultErrorSchema)
|
|
13878
14519
|
});
|
|
13879
14520
|
/**
|
|
@@ -13903,7 +14544,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
13903
14544
|
*
|
|
13904
14545
|
* @internal
|
|
13905
14546
|
*/ const exploreVaultsPayloadSchema = zod.z.object({
|
|
13906
|
-
vaults:
|
|
14547
|
+
vaults: earnOpportunityListSchema,
|
|
13907
14548
|
pagination: explorePaginationSchema
|
|
13908
14549
|
});
|
|
13909
14550
|
/**
|
|
@@ -13996,6 +14637,16 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
13996
14637
|
*/ function isWithdrawResponse(value) {
|
|
13997
14638
|
return withdrawResponseSchema.safeParse(value).success;
|
|
13998
14639
|
}
|
|
14640
|
+
/**
|
|
14641
|
+
* Type guard for the transaction report API response.
|
|
14642
|
+
*
|
|
14643
|
+
* @param value - Unknown response value to validate
|
|
14644
|
+
* @returns True when the value matches the transaction report response shape
|
|
14645
|
+
*
|
|
14646
|
+
* @internal
|
|
14647
|
+
*/ function isTransactionReportResponse(value) {
|
|
14648
|
+
return transactionReportResponseSchema.safeParse(value).success;
|
|
14649
|
+
}
|
|
13999
14650
|
/**
|
|
14000
14651
|
* Type guard for the claim rewards API response.
|
|
14001
14652
|
*
|
|
@@ -14038,7 +14689,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
14038
14689
|
}
|
|
14039
14690
|
|
|
14040
14691
|
var name = "@circle-fin/provider-earn-service";
|
|
14041
|
-
var version = "1.
|
|
14692
|
+
var version = "1.3.1";
|
|
14042
14693
|
var pkg = {
|
|
14043
14694
|
name: name,
|
|
14044
14695
|
version: version};
|
|
@@ -14100,15 +14751,25 @@ var pkg = {
|
|
|
14100
14751
|
*
|
|
14101
14752
|
* @internal
|
|
14102
14753
|
*/ function buildConfig(serviceConfig) {
|
|
14754
|
+
// The kit key is a server-only secret. Reject it in the browser so it cannot
|
|
14755
|
+
// leak into a client bundle (no-op in Node.js). Keyless usage stays allowed.
|
|
14756
|
+
if (serviceConfig?.kitKey !== undefined && isBrowserEnvironment()) {
|
|
14757
|
+
throw createValidationFailedError('kitKey', '[redacted]', 'kitKey must not be provided in a browser environment — it is a server-only secret. Run EarnKit operations that use a kit key on your server, or omit kitKey to use the permissionless (keyless) client path');
|
|
14758
|
+
}
|
|
14103
14759
|
const baseUrl = serviceConfig?.baseUrl ?? EARN_SERVICE_BASE_URL;
|
|
14104
|
-
|
|
14760
|
+
// The API CORS policy does not allow this custom header. Keep the existing
|
|
14761
|
+
// per-request version attribution for Node callers, but omit it in browsers
|
|
14762
|
+
// so public EarnKit endpoints do not fail at CORS preflight.
|
|
14763
|
+
const sdkVersionHeader = isNodeEnvironment() ? {
|
|
14764
|
+
[SDK_VERSION_HEADER]: resolveSdkVersionHeader()
|
|
14765
|
+
} : {};
|
|
14105
14766
|
if (serviceConfig?.kitKey === undefined) {
|
|
14106
14767
|
return {
|
|
14107
14768
|
pollingConfig: {
|
|
14108
14769
|
...DEFAULT_CONFIG,
|
|
14109
14770
|
headers: {
|
|
14110
14771
|
...DEFAULT_CONFIG.headers,
|
|
14111
|
-
|
|
14772
|
+
...sdkVersionHeader
|
|
14112
14773
|
}
|
|
14113
14774
|
},
|
|
14114
14775
|
baseUrl
|
|
@@ -14126,7 +14787,7 @@ var pkg = {
|
|
|
14126
14787
|
...DEFAULT_CONFIG,
|
|
14127
14788
|
headers: {
|
|
14128
14789
|
...DEFAULT_CONFIG.headers,
|
|
14129
|
-
|
|
14790
|
+
...sdkVersionHeader,
|
|
14130
14791
|
Authorization: `Bearer ${serviceConfig.kitKey}`
|
|
14131
14792
|
}
|
|
14132
14793
|
},
|
|
@@ -14158,7 +14819,7 @@ var pkg = {
|
|
|
14158
14819
|
}
|
|
14159
14820
|
|
|
14160
14821
|
/**
|
|
14161
|
-
* Convert an API vault info object into the SDK {@link
|
|
14822
|
+
* Convert an API vault info object into the SDK {@link EarnOpportunity} shape.
|
|
14162
14823
|
*
|
|
14163
14824
|
* Map the API chain code back to the SDK chain identifier and hydrate the
|
|
14164
14825
|
* amount payloads into {@link Amount} instances.
|
|
@@ -14169,16 +14830,29 @@ var pkg = {
|
|
|
14169
14830
|
*
|
|
14170
14831
|
* @internal
|
|
14171
14832
|
*/ function toVaultInfo(data) {
|
|
14172
|
-
const { totalDeposits, liquidity, ...vault } = data;
|
|
14833
|
+
const { totalDeposits, liquidity, liquidityProfile, ...vault } = data;
|
|
14173
14834
|
const chain = toSdkChain(vault.chain);
|
|
14174
14835
|
if (chain === undefined) {
|
|
14175
14836
|
throw createInvalidChainError(vault.chain, 'Chain returned by the Earn Service is not supported by the SDK');
|
|
14176
14837
|
}
|
|
14838
|
+
// The nested facets are `.optional()` in the schema (a backend that predates
|
|
14839
|
+
// them omits them) and are typed optional on `EarnOpportunity` to match.
|
|
14840
|
+
// Convert the nested liquidity amounts when present and pass the remaining
|
|
14841
|
+
// facets straight through; each absent facet stays absent rather than being
|
|
14842
|
+
// asserted present by a cast.
|
|
14177
14843
|
return {
|
|
14178
14844
|
...vault,
|
|
14179
14845
|
chain,
|
|
14180
14846
|
totalDeposits: Amount.fromJSON(totalDeposits),
|
|
14181
|
-
liquidity: Amount.fromJSON(liquidity)
|
|
14847
|
+
liquidity: Amount.fromJSON(liquidity),
|
|
14848
|
+
...liquidityProfile !== undefined && {
|
|
14849
|
+
liquidityProfile: {
|
|
14850
|
+
...liquidityProfile,
|
|
14851
|
+
totalDeposits: Amount.fromJSON(liquidityProfile.totalDeposits),
|
|
14852
|
+
available: Amount.fromJSON(liquidityProfile.available),
|
|
14853
|
+
totalSupply: Amount.fromJSON(liquidityProfile.totalSupply)
|
|
14854
|
+
}
|
|
14855
|
+
}
|
|
14182
14856
|
};
|
|
14183
14857
|
}
|
|
14184
14858
|
|
|
@@ -14213,8 +14887,11 @@ function toVaultError(error) {
|
|
|
14213
14887
|
}
|
|
14214
14888
|
try {
|
|
14215
14889
|
const response = await pollApiGet(url.toString(), isGetVaultsResponse, pollingConfig);
|
|
14890
|
+
// `pollApiGet` validates via a boolean guard and returns the raw JSON — it
|
|
14891
|
+
// does not run the schema's preprocess. Parse explicitly so unknown
|
|
14892
|
+
// `productType` values are dropped before `toVaultInfo`.
|
|
14216
14893
|
return {
|
|
14217
|
-
vaults: response.data.vaults.map(toVaultInfo),
|
|
14894
|
+
vaults: earnOpportunityListSchema.parse(response.data.vaults).map(toVaultInfo),
|
|
14218
14895
|
errors: response.data.errors.map(toVaultError)
|
|
14219
14896
|
};
|
|
14220
14897
|
} catch (error) {
|
|
@@ -14263,8 +14940,11 @@ function toVaultError(error) {
|
|
|
14263
14940
|
}
|
|
14264
14941
|
try {
|
|
14265
14942
|
const response = await pollApiGet(url.toString(), isExploreVaultsResponse, pollingConfig);
|
|
14943
|
+
// `pollApiGet` validates via a boolean guard and returns the raw JSON — it
|
|
14944
|
+
// does not run the schema's preprocess. Parse explicitly so unknown
|
|
14945
|
+
// `productType` values are dropped before `toVaultInfo`.
|
|
14266
14946
|
return {
|
|
14267
|
-
vaults: response.data.vaults.map(toVaultInfo),
|
|
14947
|
+
vaults: earnOpportunityListSchema.parse(response.data.vaults).map(toVaultInfo),
|
|
14268
14948
|
pagination: response.data.pagination
|
|
14269
14949
|
};
|
|
14270
14950
|
} catch (error) {
|
|
@@ -14428,6 +15108,12 @@ function toPositionInfo(data) {
|
|
|
14428
15108
|
execId: response.data.execId,
|
|
14429
15109
|
preparedBundle,
|
|
14430
15110
|
expiresAt: response.data.expiresAt,
|
|
15111
|
+
...response.data.quoteIssuedAt !== undefined && {
|
|
15112
|
+
quoteIssuedAt: response.data.quoteIssuedAt
|
|
15113
|
+
},
|
|
15114
|
+
...response.data.quoteExpiry !== undefined && {
|
|
15115
|
+
quoteExpiry: response.data.quoteExpiry
|
|
15116
|
+
},
|
|
14431
15117
|
review: response.data.review
|
|
14432
15118
|
};
|
|
14433
15119
|
} catch (error) {
|
|
@@ -14769,7 +15455,110 @@ function toClaimedAmount(reward) {
|
|
|
14769
15455
|
}
|
|
14770
15456
|
}
|
|
14771
15457
|
|
|
14772
|
-
|
|
15458
|
+
/**
|
|
15459
|
+
* Map the Earn Service's server-side quote gas estimates into the SDK
|
|
15460
|
+
* {@link EarnGasFeeEstimate} shape.
|
|
15461
|
+
*
|
|
15462
|
+
* The Earn Service estimates gas for each action (`Approve`, `Deposit`,
|
|
15463
|
+
* `Withdraw`) and returns `{ name, fees: { gas, gasPrice, fee } }` with raw
|
|
15464
|
+
* integer strings.
|
|
15465
|
+
* The SDK type additionally carries `token` (the chain's native currency
|
|
15466
|
+
* symbol) and `blockchain`, which are filled in here from the chain
|
|
15467
|
+
* definition.
|
|
15468
|
+
*
|
|
15469
|
+
* Gas reporting is best-effort: a malformed entry (e.g. a non-integer string
|
|
15470
|
+
* that fails `BigInt` parsing) degrades to a `{ fees: null, error }` estimate
|
|
15471
|
+
* rather than throwing, so one bad entry never fails the whole quote.
|
|
15472
|
+
*
|
|
15473
|
+
* @param gasFees - Backend gas-fee entries from the quote response, if any.
|
|
15474
|
+
* @param chain - Chain definition, used for the native token symbol and
|
|
15475
|
+
* blockchain identifier.
|
|
15476
|
+
* @returns One {@link EarnGasFeeEstimate} per backend entry (empty when the
|
|
15477
|
+
* backend returned none).
|
|
15478
|
+
*
|
|
15479
|
+
* @example
|
|
15480
|
+
* ```typescript
|
|
15481
|
+
* toQuoteGasFees(
|
|
15482
|
+
* [{ name: 'Deposit', fees: { gas: '364142', gasPrice: '21000000000', fee: '7646982000000000' } }],
|
|
15483
|
+
* arcTestnet,
|
|
15484
|
+
* )
|
|
15485
|
+
* // [{ name: 'Deposit', token: 'USDC', blockchain: 'Arc_Testnet',
|
|
15486
|
+
* // fees: { gas: 364142n, gasPrice: 21000000000n, fee: '7646982000000000' } }]
|
|
15487
|
+
* ```
|
|
15488
|
+
*
|
|
15489
|
+
* @internal
|
|
15490
|
+
*/ function toQuoteGasFees(gasFees, chain) {
|
|
15491
|
+
if (gasFees === undefined) {
|
|
15492
|
+
return [];
|
|
15493
|
+
}
|
|
15494
|
+
return gasFees.map((entry)=>{
|
|
15495
|
+
const base = {
|
|
15496
|
+
// `name` is optional on the wire; label an unnamed entry rather than
|
|
15497
|
+
// emitting `name: undefined`.
|
|
15498
|
+
name: entry.name ?? 'Unknown',
|
|
15499
|
+
token: chain.nativeCurrency.symbol,
|
|
15500
|
+
blockchain: chain.chain
|
|
15501
|
+
};
|
|
15502
|
+
// The Earn Service itself reports a failed estimate as `fees: null` with
|
|
15503
|
+
// an error; propagate that soft failure verbatim.
|
|
15504
|
+
if (entry.fees === null || entry.fees === undefined) {
|
|
15505
|
+
return {
|
|
15506
|
+
...base,
|
|
15507
|
+
fees: null,
|
|
15508
|
+
error: entry.error ?? 'gas estimate unavailable'
|
|
15509
|
+
};
|
|
15510
|
+
}
|
|
15511
|
+
// `fees` is `unknown` at the schema layer, so ALL validation happens here:
|
|
15512
|
+
// that it is an object at all, and that `gas`, `gasPrice`, and `fee` are
|
|
15513
|
+
// each parseable integer strings (including `fee`, which the SDK contract
|
|
15514
|
+
// requires be a numeric base-unit string). Any failure — a wrong type
|
|
15515
|
+
// (`fees: 123`), a missing field, or a non-numeric value — degrades the
|
|
15516
|
+
// whole entry to a `fees: null` soft failure rather than surfacing a
|
|
15517
|
+
// malformed "successful" estimate or rejecting the quote.
|
|
15518
|
+
try {
|
|
15519
|
+
if (typeof entry.fees !== 'object') {
|
|
15520
|
+
throw new TypeError(`gas fees must be an object (got ${typeof entry.fees})`);
|
|
15521
|
+
}
|
|
15522
|
+
const { gas, gasPrice, fee } = entry.fees;
|
|
15523
|
+
return {
|
|
15524
|
+
...base,
|
|
15525
|
+
fees: {
|
|
15526
|
+
gas: toBigInt('gas', gas),
|
|
15527
|
+
gasPrice: toBigInt('gasPrice', gasPrice),
|
|
15528
|
+
fee: toBigInt('fee', fee).toString()
|
|
15529
|
+
}
|
|
15530
|
+
};
|
|
15531
|
+
} catch (error) {
|
|
15532
|
+
return {
|
|
15533
|
+
...base,
|
|
15534
|
+
fees: null,
|
|
15535
|
+
error: getErrorMessage(error)
|
|
15536
|
+
};
|
|
15537
|
+
}
|
|
15538
|
+
});
|
|
15539
|
+
}
|
|
15540
|
+
/**
|
|
15541
|
+
* Parse an unknown value into a `bigint`, rejecting anything that is not a
|
|
15542
|
+
* non-empty integer string. `BigInt` alone is too permissive for this path —
|
|
15543
|
+
* it accepts numbers, booleans, and empty strings — so guard the type first.
|
|
15544
|
+
*
|
|
15545
|
+
* @param field - Field name, used in the thrown error message.
|
|
15546
|
+
* @param value - Raw value from the backend gas entry.
|
|
15547
|
+
* @returns The parsed `bigint`.
|
|
15548
|
+
* @throws {TypeError} When `value` is not a non-empty integer string.
|
|
15549
|
+
*/ function toBigInt(field, value) {
|
|
15550
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
15551
|
+
throw new TypeError(`gas fee field "${field}" must be an integer string`);
|
|
15552
|
+
}
|
|
15553
|
+
try {
|
|
15554
|
+
// BigInt throws on non-integer strings (e.g. "1.5", "not-a-number").
|
|
15555
|
+
return BigInt(value);
|
|
15556
|
+
} catch {
|
|
15557
|
+
throw new Error(`gas fee field "${field}" is not a valid integer string: ${value}`);
|
|
15558
|
+
}
|
|
15559
|
+
}
|
|
15560
|
+
|
|
15561
|
+
function toDepositQuoteInfo(data, chain) {
|
|
14773
15562
|
const fees = (data.fees ?? []).map(({ token: feeTokenSymbol, ...fee })=>{
|
|
14774
15563
|
// Earn Service returns fee.token as a display symbol, for example "USDC".
|
|
14775
15564
|
return {
|
|
@@ -14798,7 +15587,10 @@ function toDepositQuoteInfo(data) {
|
|
|
14798
15587
|
sharePrice: data.sharePrice,
|
|
14799
15588
|
currentApy: data.currentApy,
|
|
14800
15589
|
fees,
|
|
14801
|
-
|
|
15590
|
+
// The Earn Service estimates gas server-side; the chain fills token/blockchain.
|
|
15591
|
+
// Cross-chain quotes resolve no local chain definition, so gasFees stays
|
|
15592
|
+
// empty there (unchanged behavior).
|
|
15593
|
+
gasFees: chain === undefined ? [] : toQuoteGasFees(data.gasFees, chain)
|
|
14802
15594
|
};
|
|
14803
15595
|
}
|
|
14804
15596
|
/**
|
|
@@ -14833,7 +15625,7 @@ function toDepositQuoteInfo(data) {
|
|
|
14833
15625
|
};
|
|
14834
15626
|
try {
|
|
14835
15627
|
const response = await pollApiPost(url.toString(), requestBody, isDepositQuoteResponse, pollingConfig);
|
|
14836
|
-
return toDepositQuoteInfo(response.data);
|
|
15628
|
+
return toDepositQuoteInfo(response.data, params.chainDefinition);
|
|
14837
15629
|
} catch (error) {
|
|
14838
15630
|
throw parseEarnApiError(error, {
|
|
14839
15631
|
operation: 'getDepositQuote'
|
|
@@ -14841,7 +15633,7 @@ function toDepositQuoteInfo(data) {
|
|
|
14841
15633
|
}
|
|
14842
15634
|
}
|
|
14843
15635
|
|
|
14844
|
-
function toWithdrawalQuoteInfo(data) {
|
|
15636
|
+
function toWithdrawalQuoteInfo(data, chain) {
|
|
14845
15637
|
return {
|
|
14846
15638
|
vaultAddress: data.vaultAddress,
|
|
14847
15639
|
vaultName: data.vaultName,
|
|
@@ -14869,7 +15661,7 @@ function toWithdrawalQuoteInfo(data) {
|
|
|
14869
15661
|
status
|
|
14870
15662
|
}
|
|
14871
15663
|
})),
|
|
14872
|
-
gasFees: [],
|
|
15664
|
+
gasFees: chain === undefined ? [] : toQuoteGasFees(data.gasFees, chain),
|
|
14873
15665
|
// Wire format uses `warnings`, but the SDK surface uses
|
|
14874
15666
|
// `earnKitWarnings` to match the precedent set by `VaultInfo` —
|
|
14875
15667
|
// `warnings` is reserved for the structured `VaultWarning` shape.
|
|
@@ -14901,7 +15693,7 @@ function toWithdrawalQuoteInfo(data) {
|
|
|
14901
15693
|
};
|
|
14902
15694
|
try {
|
|
14903
15695
|
const response = await pollApiPost(url.toString(), requestBody, isWithdrawalQuoteResponse, pollingConfig);
|
|
14904
|
-
return toWithdrawalQuoteInfo(response.data);
|
|
15696
|
+
return toWithdrawalQuoteInfo(response.data, params.chainDefinition);
|
|
14905
15697
|
} catch (error) {
|
|
14906
15698
|
throw parseEarnApiError(error, {
|
|
14907
15699
|
operation: 'getWithdrawalQuote'
|
|
@@ -14937,6 +15729,8 @@ function toWithdrawalQuoteInfo(data) {
|
|
|
14937
15729
|
amount: Amount.fromJSON(r.amount),
|
|
14938
15730
|
address: r.token
|
|
14939
15731
|
})),
|
|
15732
|
+
// The claimRewards/quote response does not carry a gas estimate (unlike
|
|
15733
|
+
// deposit/withdrawal quotes), so there is nothing to surface here.
|
|
14940
15734
|
gasFees: []
|
|
14941
15735
|
};
|
|
14942
15736
|
} catch (error) {
|
|
@@ -14946,6 +15740,83 @@ function toWithdrawalQuoteInfo(data) {
|
|
|
14946
15740
|
}
|
|
14947
15741
|
}
|
|
14948
15742
|
|
|
15743
|
+
/**
|
|
15744
|
+
* Build the native gas triple the backend expects for `gasUsed`.
|
|
15745
|
+
*
|
|
15746
|
+
* Returns `undefined` unless both receipt components are present, so the
|
|
15747
|
+
* caller can omit the field entirely — the Earn Service treats a missing
|
|
15748
|
+
* triple as "skip the gas cache write, still return 200".
|
|
15749
|
+
*
|
|
15750
|
+
* @param gasUsed - Receipt gas units used.
|
|
15751
|
+
* @param effectiveGasPrice - Receipt effective gas price.
|
|
15752
|
+
* @returns The `{ gas, gasPrice, fee }` triple, or `undefined` when either
|
|
15753
|
+
* component is missing.
|
|
15754
|
+
*
|
|
15755
|
+
* @example
|
|
15756
|
+
* ```typescript
|
|
15757
|
+
* buildReportedGasUsed(362454n, 29466364605n)
|
|
15758
|
+
* // { gas: '362454', gasPrice: '29466364605', fee: '10680201716540670' }
|
|
15759
|
+
* ```
|
|
15760
|
+
*
|
|
15761
|
+
* @internal
|
|
15762
|
+
*/ function buildReportedGasUsed(gasUsed, effectiveGasPrice) {
|
|
15763
|
+
if (gasUsed === undefined || effectiveGasPrice === undefined) {
|
|
15764
|
+
return undefined;
|
|
15765
|
+
}
|
|
15766
|
+
return {
|
|
15767
|
+
gas: gasUsed.toString(),
|
|
15768
|
+
gasPrice: effectiveGasPrice.toString(),
|
|
15769
|
+
fee: (gasUsed * effectiveGasPrice).toString()
|
|
15770
|
+
};
|
|
15771
|
+
}
|
|
15772
|
+
/**
|
|
15773
|
+
* Report the outcome of an SDK-submitted same-chain Earn transaction.
|
|
15774
|
+
*
|
|
15775
|
+
* @param params - Transaction report parameters.
|
|
15776
|
+
* @throws {@link KitError} When the API call fails.
|
|
15777
|
+
*
|
|
15778
|
+
* @internal
|
|
15779
|
+
*/ async function reportEarnTransaction(params) {
|
|
15780
|
+
const { pollingConfig, baseUrl } = buildConfig(params.config);
|
|
15781
|
+
const url = new URL(`${EARN_KIT_API_PREFIX}/transactions/report`, baseUrl);
|
|
15782
|
+
// The report endpoint is not idempotent: success reports refresh the gas
|
|
15783
|
+
// cache and failure reports increment counts. If the first request succeeds
|
|
15784
|
+
// server-side but the client times out or sees a transient 5xx, retrying
|
|
15785
|
+
// would duplicate the report (double-writing an outcome or inflating failure
|
|
15786
|
+
// counts). Reporting is best-effort (see the fire-and-forget caller), so
|
|
15787
|
+
// make exactly one attempt and never retry — a single dropped report is
|
|
15788
|
+
// preferable to a duplicated one. `maxRetries` here is the total attempt
|
|
15789
|
+
// count in pollApiWithValidation (loop runs `attempt <= maxRetries`), so 1
|
|
15790
|
+
// means one request with no retry; 0 would skip the request entirely.
|
|
15791
|
+
const reportConfig = {
|
|
15792
|
+
...pollingConfig,
|
|
15793
|
+
maxRetries: 1
|
|
15794
|
+
};
|
|
15795
|
+
const gasUsed = buildReportedGasUsed(params.gasUsed, params.effectiveGasPrice);
|
|
15796
|
+
const requestBody = {
|
|
15797
|
+
execId: params.execId,
|
|
15798
|
+
chain: params.chain,
|
|
15799
|
+
status: params.status,
|
|
15800
|
+
action: params.action,
|
|
15801
|
+
...params.txHash !== undefined && {
|
|
15802
|
+
txHash: params.txHash
|
|
15803
|
+
},
|
|
15804
|
+
...gasUsed !== undefined && {
|
|
15805
|
+
gasUsed
|
|
15806
|
+
},
|
|
15807
|
+
...params.errorCode !== undefined && {
|
|
15808
|
+
errorCode: params.errorCode
|
|
15809
|
+
}
|
|
15810
|
+
};
|
|
15811
|
+
try {
|
|
15812
|
+
await pollApiPost(url.toString(), requestBody, isTransactionReportResponse, reportConfig);
|
|
15813
|
+
} catch (error) {
|
|
15814
|
+
throw parseEarnApiError(error, {
|
|
15815
|
+
operation: 'transactionReport'
|
|
15816
|
+
});
|
|
15817
|
+
}
|
|
15818
|
+
}
|
|
15819
|
+
|
|
14949
15820
|
/**
|
|
14950
15821
|
* Sum the amounts across every token input to size the allowance approval.
|
|
14951
15822
|
*
|
|
@@ -15056,6 +15927,59 @@ function toWithdrawalQuoteInfo(data) {
|
|
|
15056
15927
|
// Intentionally built-ins-only: Earn bridge support is limited to SDK-known
|
|
15057
15928
|
// token contracts plus the explicit ERC-3009 domain allowlist below.
|
|
15058
15929
|
const TOKEN_REGISTRY = createTokenRegistry();
|
|
15930
|
+
function submitTransactionReport(reportContext, action, status, details) {
|
|
15931
|
+
void reportEarnTransaction({
|
|
15932
|
+
execId: reportContext.execId,
|
|
15933
|
+
chain: reportContext.chain,
|
|
15934
|
+
config: reportContext.config,
|
|
15935
|
+
action,
|
|
15936
|
+
status,
|
|
15937
|
+
...details
|
|
15938
|
+
}).catch(()=>undefined);
|
|
15939
|
+
}
|
|
15940
|
+
function reportTransactionSuccess(reportContext, action, result) {
|
|
15941
|
+
if (result === undefined) {
|
|
15942
|
+
return;
|
|
15943
|
+
}
|
|
15944
|
+
submitTransactionReport(reportContext, action, 'success', {
|
|
15945
|
+
txHash: result.txHash,
|
|
15946
|
+
gasUsed: result.gasUsed,
|
|
15947
|
+
effectiveGasPrice: result.effectiveGasPrice
|
|
15948
|
+
});
|
|
15949
|
+
}
|
|
15950
|
+
function reportTransactionFailure(reportContext, action, error) {
|
|
15951
|
+
submitTransactionReport(reportContext, action, 'failure', {
|
|
15952
|
+
txHash: transactionReportTxHash(error),
|
|
15953
|
+
errorCode: transactionReportErrorCode(error)
|
|
15954
|
+
});
|
|
15955
|
+
}
|
|
15956
|
+
function transactionReportErrorCode(error) {
|
|
15957
|
+
if (isKitError(error)) {
|
|
15958
|
+
return error.name;
|
|
15959
|
+
}
|
|
15960
|
+
const message = getErrorMessage(error);
|
|
15961
|
+
if (/user (rejected|denied)|rejected by user/i.test(message)) {
|
|
15962
|
+
return 'USER_REJECTED';
|
|
15963
|
+
}
|
|
15964
|
+
if (/insufficient funds/i.test(message)) {
|
|
15965
|
+
return 'INSUFFICIENT_FUNDS';
|
|
15966
|
+
}
|
|
15967
|
+
if (/timeout|timed out/i.test(message)) {
|
|
15968
|
+
return 'TIMEOUT';
|
|
15969
|
+
}
|
|
15970
|
+
return 'UNKNOWN_ERROR';
|
|
15971
|
+
}
|
|
15972
|
+
function transactionReportTxHash(error) {
|
|
15973
|
+
if (!isKitError(error)) {
|
|
15974
|
+
return undefined;
|
|
15975
|
+
}
|
|
15976
|
+
const trace = error.cause?.trace;
|
|
15977
|
+
if (typeof trace !== 'object' || trace === null) {
|
|
15978
|
+
return undefined;
|
|
15979
|
+
}
|
|
15980
|
+
const txHash = trace['txHash'];
|
|
15981
|
+
return typeof txHash === 'string' && txHash !== '' ? txHash : undefined;
|
|
15982
|
+
}
|
|
15059
15983
|
/**
|
|
15060
15984
|
* Build the typed error raised when a cross-chain wait is cancelled via its
|
|
15061
15985
|
* `AbortSignal`. Mirrors `@core/adapter-base`'s `createAbortError` (same
|
|
@@ -15377,7 +16301,7 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
15377
16301
|
const adapterContractAddress = requireAdapterContract(chain);
|
|
15378
16302
|
const { adapter } = params.from;
|
|
15379
16303
|
const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
15380
|
-
const { executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
|
|
16304
|
+
const { execId, executionParams, signature } = await this.runPhase(ctx, 'deposit', 'fetchParams', async ()=>fetchDeposit({
|
|
15381
16305
|
vaultAddress,
|
|
15382
16306
|
amount: params.amount,
|
|
15383
16307
|
address,
|
|
@@ -15385,32 +16309,55 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
15385
16309
|
config
|
|
15386
16310
|
}), ()=>undefined);
|
|
15387
16311
|
validateExecutionDeadline(executionParams);
|
|
16312
|
+
const transactionReportContext = {
|
|
16313
|
+
execId,
|
|
16314
|
+
chain: apiChain,
|
|
16315
|
+
config
|
|
16316
|
+
};
|
|
15388
16317
|
const approvalToken = resolveEarnApprovalToken(executionParams);
|
|
15389
16318
|
const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
|
|
15390
16319
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
15391
16320
|
if (!options.skipApprove && approvalToken !== undefined && requiredAllowance > 0n) {
|
|
15392
|
-
await this.runPhase(ctx, 'approve', 'approve', async ()=>
|
|
16321
|
+
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
16322
|
+
try {
|
|
16323
|
+
const approval = await approveAllowanceIfNeeded({
|
|
16324
|
+
adapter,
|
|
16325
|
+
chain,
|
|
16326
|
+
tokenAddress: approvalToken,
|
|
16327
|
+
delegate: adapterContractAddress,
|
|
16328
|
+
address,
|
|
16329
|
+
requiredAllowance,
|
|
16330
|
+
revertMessage: 'Earn deposit token approval reverted on-chain'
|
|
16331
|
+
});
|
|
16332
|
+
reportTransactionSuccess(transactionReportContext, 'Approve', approval);
|
|
16333
|
+
return approval;
|
|
16334
|
+
} catch (error) {
|
|
16335
|
+
reportTransactionFailure(transactionReportContext, 'Approve', error);
|
|
16336
|
+
throw error;
|
|
16337
|
+
}
|
|
16338
|
+
}, (approval)=>approval?.txHash);
|
|
16339
|
+
}
|
|
16340
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'deposit', 'execute', async ()=>{
|
|
16341
|
+
try {
|
|
16342
|
+
const result = await executeEarnAction({
|
|
15393
16343
|
adapter,
|
|
15394
16344
|
chain,
|
|
15395
|
-
tokenAddress: approvalToken,
|
|
15396
|
-
delegate: adapterContractAddress,
|
|
15397
16345
|
address,
|
|
15398
|
-
|
|
15399
|
-
|
|
15400
|
-
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15404
|
-
|
|
15405
|
-
|
|
15406
|
-
|
|
15407
|
-
|
|
15408
|
-
|
|
15409
|
-
|
|
15410
|
-
|
|
15411
|
-
|
|
15412
|
-
|
|
15413
|
-
}), ({ txHash })=>txHash);
|
|
16346
|
+
actionKey: 'earn.deposit',
|
|
16347
|
+
actionParams: {
|
|
16348
|
+
executeParams: executionParams,
|
|
16349
|
+
tokenInputs,
|
|
16350
|
+
signature
|
|
16351
|
+
},
|
|
16352
|
+
revertMessage: 'Earn deposit reverted on-chain'
|
|
16353
|
+
});
|
|
16354
|
+
reportTransactionSuccess(transactionReportContext, 'Deposit', result);
|
|
16355
|
+
return result;
|
|
16356
|
+
} catch (error) {
|
|
16357
|
+
reportTransactionFailure(transactionReportContext, 'Deposit', error);
|
|
16358
|
+
throw error;
|
|
16359
|
+
}
|
|
16360
|
+
}, ({ txHash })=>txHash);
|
|
15414
16361
|
return {
|
|
15415
16362
|
kind: 'same-chain',
|
|
15416
16363
|
txHash,
|
|
@@ -15490,7 +16437,13 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
15490
16437
|
amount: params.amount,
|
|
15491
16438
|
sourceChain: sourceChain.chain,
|
|
15492
16439
|
destinationChain: destinationChain.chain,
|
|
15493
|
-
expiresAt: prepared.expiresAt
|
|
16440
|
+
expiresAt: prepared.expiresAt,
|
|
16441
|
+
...prepared.quoteIssuedAt !== undefined && {
|
|
16442
|
+
quoteIssuedAt: prepared.quoteIssuedAt
|
|
16443
|
+
},
|
|
16444
|
+
...prepared.quoteExpiry !== undefined && {
|
|
16445
|
+
quoteExpiry: prepared.quoteExpiry
|
|
16446
|
+
}
|
|
15494
16447
|
};
|
|
15495
16448
|
}
|
|
15496
16449
|
/** {@inheritdoc} */ async withdraw(params) {
|
|
@@ -15511,7 +16464,7 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
15511
16464
|
const adapterContractAddress = requireAdapterContract(chain);
|
|
15512
16465
|
const vaultAddress = assertHexAddress('vaultAddress', params.vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
15513
16466
|
const { adapter } = params.from;
|
|
15514
|
-
const { executionParams, signature } = await this.runPhase(ctx, 'withdraw', 'fetchParams', async ()=>fetchWithdraw({
|
|
16467
|
+
const { execId, executionParams, signature } = await this.runPhase(ctx, 'withdraw', 'fetchParams', async ()=>fetchWithdraw({
|
|
15515
16468
|
vaultAddress,
|
|
15516
16469
|
amount: params.amount,
|
|
15517
16470
|
address,
|
|
@@ -15519,32 +16472,55 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
15519
16472
|
config
|
|
15520
16473
|
}), ()=>undefined);
|
|
15521
16474
|
validateExecutionDeadline(executionParams);
|
|
16475
|
+
const transactionReportContext = {
|
|
16476
|
+
execId,
|
|
16477
|
+
chain: apiChain,
|
|
16478
|
+
config
|
|
16479
|
+
};
|
|
15522
16480
|
const tokenInputs = buildEarnTokenInputs(executionParams, vaultAddress);
|
|
15523
16481
|
const approvalToken = tokenInputs[0]?.token;
|
|
15524
16482
|
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
15525
16483
|
if (!options.skipApprove && approvalToken !== undefined) {
|
|
15526
|
-
await this.runPhase(ctx, 'approve', 'approve', async ()=>
|
|
16484
|
+
await this.runPhase(ctx, 'approve', 'approve', async ()=>{
|
|
16485
|
+
try {
|
|
16486
|
+
const approval = await approveAllowanceIfNeeded({
|
|
16487
|
+
adapter,
|
|
16488
|
+
chain,
|
|
16489
|
+
tokenAddress: approvalToken,
|
|
16490
|
+
delegate: adapterContractAddress,
|
|
16491
|
+
address,
|
|
16492
|
+
requiredAllowance,
|
|
16493
|
+
revertMessage: 'Vault share token approval reverted on-chain'
|
|
16494
|
+
});
|
|
16495
|
+
reportTransactionSuccess(transactionReportContext, 'Approve', approval);
|
|
16496
|
+
return approval;
|
|
16497
|
+
} catch (error) {
|
|
16498
|
+
reportTransactionFailure(transactionReportContext, 'Approve', error);
|
|
16499
|
+
throw error;
|
|
16500
|
+
}
|
|
16501
|
+
}, (approval)=>approval?.txHash);
|
|
16502
|
+
}
|
|
16503
|
+
const { txHash, explorerUrl } = await this.runPhase(ctx, 'withdraw', 'execute', async ()=>{
|
|
16504
|
+
try {
|
|
16505
|
+
const result = await executeEarnAction({
|
|
15527
16506
|
adapter,
|
|
15528
16507
|
chain,
|
|
15529
|
-
tokenAddress: approvalToken,
|
|
15530
|
-
delegate: adapterContractAddress,
|
|
15531
16508
|
address,
|
|
15532
|
-
|
|
15533
|
-
|
|
15534
|
-
|
|
15535
|
-
|
|
15536
|
-
|
|
15537
|
-
|
|
15538
|
-
|
|
15539
|
-
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
}), ({ txHash })=>txHash);
|
|
16509
|
+
actionKey: 'earn.withdraw',
|
|
16510
|
+
actionParams: {
|
|
16511
|
+
executeParams: executionParams,
|
|
16512
|
+
tokenInputs,
|
|
16513
|
+
signature
|
|
16514
|
+
},
|
|
16515
|
+
revertMessage: 'Earn withdraw reverted on-chain'
|
|
16516
|
+
});
|
|
16517
|
+
reportTransactionSuccess(transactionReportContext, 'Withdraw', result);
|
|
16518
|
+
return result;
|
|
16519
|
+
} catch (error) {
|
|
16520
|
+
reportTransactionFailure(transactionReportContext, 'Withdraw', error);
|
|
16521
|
+
throw error;
|
|
16522
|
+
}
|
|
16523
|
+
}, ({ txHash })=>txHash);
|
|
15548
16524
|
return {
|
|
15549
16525
|
txHash,
|
|
15550
16526
|
explorerUrl,
|
|
@@ -15678,141 +16654,6 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
15678
16654
|
}
|
|
15679
16655
|
}
|
|
15680
16656
|
}
|
|
15681
|
-
gasEstimateFailure(name, chain, error) {
|
|
15682
|
-
return {
|
|
15683
|
-
name,
|
|
15684
|
-
token: chain.nativeCurrency.symbol,
|
|
15685
|
-
blockchain: chain.chain,
|
|
15686
|
-
fees: null,
|
|
15687
|
-
error: getErrorMessage(error)
|
|
15688
|
-
};
|
|
15689
|
-
}
|
|
15690
|
-
async estimateDepositQuoteGasFees(params) {
|
|
15691
|
-
const { adapter, chain, apiChain, address, vaultAddress, amount, config } = params;
|
|
15692
|
-
try {
|
|
15693
|
-
const adapterContractAddress = requireAdapterContract(chain);
|
|
15694
|
-
const normalizedVaultAddress = assertHexAddress('vaultAddress', vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
15695
|
-
const { executionParams, signature } = await fetchDeposit({
|
|
15696
|
-
vaultAddress: normalizedVaultAddress,
|
|
15697
|
-
amount,
|
|
15698
|
-
address,
|
|
15699
|
-
chain: apiChain,
|
|
15700
|
-
config
|
|
15701
|
-
});
|
|
15702
|
-
validateExecutionDeadline(executionParams);
|
|
15703
|
-
const approvalToken = resolveEarnApprovalToken(executionParams);
|
|
15704
|
-
const tokenInputs = approvalToken === undefined ? [] : buildEarnTokenInputs(executionParams, approvalToken);
|
|
15705
|
-
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
15706
|
-
return await estimateEarnQuoteGasFees({
|
|
15707
|
-
adapter,
|
|
15708
|
-
chain,
|
|
15709
|
-
address,
|
|
15710
|
-
actionName: 'Deposit',
|
|
15711
|
-
actionKey: 'earn.deposit',
|
|
15712
|
-
actionParams: {
|
|
15713
|
-
executeParams: executionParams,
|
|
15714
|
-
tokenInputs,
|
|
15715
|
-
signature
|
|
15716
|
-
},
|
|
15717
|
-
approval: approvalToken !== undefined && requiredAllowance > 0n ? {
|
|
15718
|
-
token: approvalToken,
|
|
15719
|
-
delegate: adapterContractAddress,
|
|
15720
|
-
requiredAllowance
|
|
15721
|
-
} : undefined
|
|
15722
|
-
});
|
|
15723
|
-
} catch (error) {
|
|
15724
|
-
return [
|
|
15725
|
-
this.gasEstimateFailure('Deposit', chain, error)
|
|
15726
|
-
];
|
|
15727
|
-
}
|
|
15728
|
-
}
|
|
15729
|
-
async estimateWithdrawalQuoteGasFees(params) {
|
|
15730
|
-
const { adapter, chain, apiChain, address, vaultAddress, amount, config } = params;
|
|
15731
|
-
try {
|
|
15732
|
-
const adapterContractAddress = requireAdapterContract(chain);
|
|
15733
|
-
const normalizedVaultAddress = assertHexAddress('vaultAddress', vaultAddress, 'Vault address must be a 0x-prefixed 20-byte hex address.');
|
|
15734
|
-
const { executionParams, signature } = await fetchWithdraw({
|
|
15735
|
-
vaultAddress: normalizedVaultAddress,
|
|
15736
|
-
amount,
|
|
15737
|
-
address,
|
|
15738
|
-
chain: apiChain,
|
|
15739
|
-
config
|
|
15740
|
-
});
|
|
15741
|
-
validateExecutionDeadline(executionParams);
|
|
15742
|
-
const tokenInputs = buildEarnTokenInputs(executionParams, normalizedVaultAddress);
|
|
15743
|
-
const approvalToken = tokenInputs[0]?.token;
|
|
15744
|
-
const requiredAllowance = sumTokenInputAmounts(tokenInputs);
|
|
15745
|
-
return await estimateEarnQuoteGasFees({
|
|
15746
|
-
adapter,
|
|
15747
|
-
chain,
|
|
15748
|
-
address,
|
|
15749
|
-
actionName: 'Withdraw',
|
|
15750
|
-
actionKey: 'earn.withdraw',
|
|
15751
|
-
actionParams: {
|
|
15752
|
-
executeParams: executionParams,
|
|
15753
|
-
tokenInputs,
|
|
15754
|
-
signature
|
|
15755
|
-
},
|
|
15756
|
-
approval: approvalToken !== undefined ? {
|
|
15757
|
-
token: approvalToken,
|
|
15758
|
-
delegate: adapterContractAddress,
|
|
15759
|
-
requiredAllowance
|
|
15760
|
-
} : undefined
|
|
15761
|
-
});
|
|
15762
|
-
} catch (error) {
|
|
15763
|
-
return [
|
|
15764
|
-
this.gasEstimateFailure('Withdraw', chain, error)
|
|
15765
|
-
];
|
|
15766
|
-
}
|
|
15767
|
-
}
|
|
15768
|
-
async estimateClaimRewardsQuoteGasFees(params) {
|
|
15769
|
-
const { adapter, chain, apiChain, address, vaultAddress, config } = params;
|
|
15770
|
-
try {
|
|
15771
|
-
requireAdapterContract(chain);
|
|
15772
|
-
const { rewards, executionParams, signature } = await fetchClaimRewards({
|
|
15773
|
-
address,
|
|
15774
|
-
chain: apiChain,
|
|
15775
|
-
vaultAddress,
|
|
15776
|
-
config
|
|
15777
|
-
});
|
|
15778
|
-
if (rewards.length === 0) {
|
|
15779
|
-
return [];
|
|
15780
|
-
}
|
|
15781
|
-
const missingExecutionParams = executionParams === undefined;
|
|
15782
|
-
const missingSignature = signature === undefined;
|
|
15783
|
-
if (missingExecutionParams || missingSignature) {
|
|
15784
|
-
throw new KitError({
|
|
15785
|
-
...EarnError.INTERNAL_ERROR,
|
|
15786
|
-
recoverability: 'RETRYABLE',
|
|
15787
|
-
message: 'Claim rewards response must include executionParams and signature when rewards are claimable',
|
|
15788
|
-
cause: {
|
|
15789
|
-
trace: {
|
|
15790
|
-
rewardsCount: rewards.length,
|
|
15791
|
-
missingExecutionParams,
|
|
15792
|
-
missingSignature
|
|
15793
|
-
}
|
|
15794
|
-
}
|
|
15795
|
-
});
|
|
15796
|
-
}
|
|
15797
|
-
validateExecutionDeadline(executionParams);
|
|
15798
|
-
return await estimateEarnQuoteGasFees({
|
|
15799
|
-
adapter,
|
|
15800
|
-
chain,
|
|
15801
|
-
address,
|
|
15802
|
-
actionName: 'Claim Rewards',
|
|
15803
|
-
actionKey: 'earn.claimRewards',
|
|
15804
|
-
actionParams: {
|
|
15805
|
-
executeParams: executionParams,
|
|
15806
|
-
tokenInputs: [],
|
|
15807
|
-
signature
|
|
15808
|
-
}
|
|
15809
|
-
});
|
|
15810
|
-
} catch (error) {
|
|
15811
|
-
return [
|
|
15812
|
-
this.gasEstimateFailure('Claim Rewards', chain, error)
|
|
15813
|
-
];
|
|
15814
|
-
}
|
|
15815
|
-
}
|
|
15816
16657
|
/** {@inheritdoc} */ async getDepositQuote(params) {
|
|
15817
16658
|
const config = this.resolveConfig(params.config);
|
|
15818
16659
|
if (hasQuoteDestinationChain(params)) {
|
|
@@ -15835,96 +16676,43 @@ function finishElapsedWait(lastStatus, lastError) {
|
|
|
15835
16676
|
throw createValidationFailedError('chain', destinationChain.chain, 'chain is only supported for cross-chain Earn deposit quotes; omit chain/address when quoting on the source chain');
|
|
15836
16677
|
}
|
|
15837
16678
|
const { address, chain, chainDefinition } = await resolveAdapterContext(params.from);
|
|
15838
|
-
//
|
|
15839
|
-
//
|
|
15840
|
-
//
|
|
15841
|
-
|
|
15842
|
-
|
|
15843
|
-
|
|
15844
|
-
|
|
15845
|
-
|
|
15846
|
-
|
|
15847
|
-
|
|
15848
|
-
|
|
15849
|
-
this.estimateDepositQuoteGasFees({
|
|
15850
|
-
adapter: params.from.adapter,
|
|
15851
|
-
chain: chainDefinition,
|
|
15852
|
-
apiChain: chain,
|
|
15853
|
-
address,
|
|
15854
|
-
vaultAddress: params.vaultAddress,
|
|
15855
|
-
amount: params.amount,
|
|
15856
|
-
config
|
|
15857
|
-
})
|
|
15858
|
-
]);
|
|
15859
|
-
return {
|
|
15860
|
-
...quote,
|
|
15861
|
-
gasFees
|
|
15862
|
-
};
|
|
16679
|
+
// Gas is estimated server-side by the Earn Service and returned on the quote, so the
|
|
16680
|
+
// SDK no longer simulates it locally. `chainDefinition` lets the fetch fill
|
|
16681
|
+
// the native token symbol / blockchain on each gas entry.
|
|
16682
|
+
return fetchDepositQuote({
|
|
16683
|
+
vaultAddress: params.vaultAddress,
|
|
16684
|
+
amount: params.amount,
|
|
16685
|
+
address,
|
|
16686
|
+
chain,
|
|
16687
|
+
config,
|
|
16688
|
+
chainDefinition
|
|
16689
|
+
});
|
|
15863
16690
|
}
|
|
15864
16691
|
/** {@inheritdoc} */ async getWithdrawalQuote(params) {
|
|
15865
16692
|
const config = this.resolveConfig(params.config);
|
|
15866
16693
|
const { address, chain, chainDefinition } = await resolveAdapterContext(params.from);
|
|
15867
|
-
//
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
config
|
|
15877
|
-
}),
|
|
15878
|
-
this.estimateWithdrawalQuoteGasFees({
|
|
15879
|
-
adapter: params.from.adapter,
|
|
15880
|
-
chain: chainDefinition,
|
|
15881
|
-
apiChain: chain,
|
|
15882
|
-
address,
|
|
15883
|
-
vaultAddress: params.vaultAddress,
|
|
15884
|
-
amount: params.amount,
|
|
15885
|
-
config
|
|
15886
|
-
})
|
|
15887
|
-
]);
|
|
15888
|
-
return {
|
|
15889
|
-
...quote,
|
|
15890
|
-
gasFees
|
|
15891
|
-
};
|
|
16694
|
+
// Gas is estimated server-side by the Earn Service and returned on the quote.
|
|
16695
|
+
return fetchWithdrawalQuote({
|
|
16696
|
+
vaultAddress: params.vaultAddress,
|
|
16697
|
+
amount: params.amount,
|
|
16698
|
+
address,
|
|
16699
|
+
chain,
|
|
16700
|
+
config,
|
|
16701
|
+
chainDefinition
|
|
16702
|
+
});
|
|
15892
16703
|
}
|
|
15893
16704
|
/** {@inheritdoc} */ async getClaimRewardsQuote(params) {
|
|
15894
16705
|
const config = this.resolveConfig(params.config);
|
|
15895
|
-
const { address, chain
|
|
15896
|
-
|
|
16706
|
+
const { address, chain } = await resolveAdapterContext(params.from);
|
|
16707
|
+
// The claimRewards/quote response carries no gas estimate (unlike
|
|
16708
|
+
// deposit/withdrawal quotes), and the SDK no longer estimates gas locally,
|
|
16709
|
+
// so gasFees is always empty for claim rewards.
|
|
16710
|
+
return fetchClaimRewardsQuote({
|
|
15897
16711
|
vaultAddress: params.vaultAddress,
|
|
15898
16712
|
address,
|
|
15899
16713
|
chain,
|
|
15900
16714
|
config
|
|
15901
16715
|
});
|
|
15902
|
-
// No claimable rewards means there is nothing to execute, so there is no
|
|
15903
|
-
// gas to estimate. Short-circuit on the already-fetched quote rather than
|
|
15904
|
-
// calling the (heavier) claim execution endpoint again — this also keeps
|
|
15905
|
-
// `gasFees` empty as documented, instead of risking a `{ fees: null }`
|
|
15906
|
-
// estimation-error entry when the adapter/RPC is unavailable. This
|
|
15907
|
-
// short-circuit is why the claim path stays sequential instead of using
|
|
15908
|
-
// the Promise.all pattern of the deposit/withdrawal quotes: estimating in
|
|
15909
|
-
// parallel would hit the signing endpoint even when nothing is claimable.
|
|
15910
|
-
if (quote.rewards.length === 0) {
|
|
15911
|
-
return {
|
|
15912
|
-
...quote,
|
|
15913
|
-
gasFees: []
|
|
15914
|
-
};
|
|
15915
|
-
}
|
|
15916
|
-
const gasFees = await this.estimateClaimRewardsQuoteGasFees({
|
|
15917
|
-
adapter: params.from.adapter,
|
|
15918
|
-
chain: chainDefinition,
|
|
15919
|
-
apiChain: chain,
|
|
15920
|
-
address,
|
|
15921
|
-
vaultAddress: params.vaultAddress,
|
|
15922
|
-
config
|
|
15923
|
-
});
|
|
15924
|
-
return {
|
|
15925
|
-
...quote,
|
|
15926
|
-
gasFees
|
|
15927
|
-
};
|
|
15928
16716
|
}
|
|
15929
16717
|
}
|
|
15930
16718
|
function hasDepositDestination(params) {
|
|
@@ -16103,6 +16891,12 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
|
|
|
16103
16891
|
if (config.providers !== undefined && !Array.isArray(config.providers)) {
|
|
16104
16892
|
throw createValidationFailedError('config.providers', config.providers, 'providers must be an array of earn providers when provided');
|
|
16105
16893
|
}
|
|
16894
|
+
if (config.disableAnalytics !== undefined && typeof config.disableAnalytics !== 'boolean') {
|
|
16895
|
+
throw createValidationFailedError('config.disableAnalytics', config.disableAnalytics, 'disableAnalytics must be a boolean when provided');
|
|
16896
|
+
}
|
|
16897
|
+
if (config.disableErrorReporting !== undefined && typeof config.disableErrorReporting !== 'boolean') {
|
|
16898
|
+
throw createValidationFailedError('config.disableErrorReporting', config.disableErrorReporting, 'disableErrorReporting must be a boolean when provided');
|
|
16899
|
+
}
|
|
16106
16900
|
const defaultProviders = getDefaultProviders();
|
|
16107
16901
|
const providers = [
|
|
16108
16902
|
...config.providers ?? [],
|
|
@@ -16114,6 +16908,31 @@ function createUnsupportedCrossChainDepositError(chain, reason) {
|
|
|
16114
16908
|
return context;
|
|
16115
16909
|
}
|
|
16116
16910
|
|
|
16911
|
+
/**
|
|
16912
|
+
* Register Earn Kit telemetry event type strings with the shared registry so
|
|
16913
|
+
* error telemetry helpers remain compile-time checked.
|
|
16914
|
+
*
|
|
16915
|
+
* @internal
|
|
16916
|
+
*/ /**
|
|
16917
|
+
* Telemetry event type identifiers for Earn Kit operations.
|
|
16918
|
+
*
|
|
16919
|
+
* @internal
|
|
16920
|
+
*/ const EARN_EVENT_TYPES = {
|
|
16921
|
+
GET_VAULTS: 'earn_get_vaults',
|
|
16922
|
+
EXPLORE_VAULTS: 'earn_explore_vaults',
|
|
16923
|
+
GET_POSITION: 'earn_get_position',
|
|
16924
|
+
GET_CROSS_CHAIN_DEPOSIT_STATUS: 'earn_get_cross_chain_deposit_status',
|
|
16925
|
+
WAIT_FOR_CROSS_CHAIN_DEPOSIT: 'earn_wait_for_cross_chain_deposit',
|
|
16926
|
+
DEPOSIT: 'earn_deposit',
|
|
16927
|
+
CROSS_CHAIN_DEPOSIT: 'earn_cross_chain_deposit',
|
|
16928
|
+
WITHDRAW: 'earn_withdraw',
|
|
16929
|
+
CLAIM_REWARDS: 'earn_claim_rewards',
|
|
16930
|
+
GET_DEPOSIT_QUOTE: 'earn_get_deposit_quote',
|
|
16931
|
+
GET_WITHDRAWAL_QUOTE: 'earn_get_withdrawal_quote',
|
|
16932
|
+
GET_CLAIM_REWARDS_QUOTE: 'earn_get_claim_rewards_quote',
|
|
16933
|
+
RETRY: 'earn_retry'
|
|
16934
|
+
};
|
|
16935
|
+
|
|
16117
16936
|
/**
|
|
16118
16937
|
* Format a provider amount object as a human-readable decimal string.
|
|
16119
16938
|
*
|
|
@@ -16162,11 +16981,27 @@ function formatPositionPnL(pnl) {
|
|
|
16162
16981
|
* @param vault - Provider vault info with raw amount objects
|
|
16163
16982
|
* @returns Vault info with total deposits and liquidity formatted as strings
|
|
16164
16983
|
*/ function formatVaultInfo(vault) {
|
|
16165
|
-
|
|
16984
|
+
// The flat `totalDeposits`/`liquidity` are deprecated aliases that are
|
|
16985
|
+
// intentionally dual-read through the migration window so existing
|
|
16986
|
+
// consumers keep receiving them until Contract.
|
|
16987
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
16988
|
+
const { totalDeposits, liquidity, liquidityProfile, ...rest } = vault;
|
|
16989
|
+
// `liquidityProfile` is `.optional()` in the response schema during the
|
|
16990
|
+
// expand/contract window (an old backend that predates the nested facets
|
|
16991
|
+
// omits it), so only format and re-attach it when present — matching the
|
|
16992
|
+
// provider-side `toVaultInfo` mapper.
|
|
16166
16993
|
return {
|
|
16167
16994
|
...rest,
|
|
16168
16995
|
totalDeposits: formatAmount(totalDeposits),
|
|
16169
|
-
liquidity: formatAmount(liquidity)
|
|
16996
|
+
liquidity: formatAmount(liquidity),
|
|
16997
|
+
...liquidityProfile !== undefined && {
|
|
16998
|
+
liquidityProfile: {
|
|
16999
|
+
...liquidityProfile,
|
|
17000
|
+
totalDeposits: formatAmount(liquidityProfile.totalDeposits),
|
|
17001
|
+
available: formatAmount(liquidityProfile.available),
|
|
17002
|
+
totalSupply: formatAmount(liquidityProfile.totalSupply)
|
|
17003
|
+
}
|
|
17004
|
+
}
|
|
16170
17005
|
};
|
|
16171
17006
|
}
|
|
16172
17007
|
/**
|
|
@@ -17613,6 +18448,14 @@ function hasCrossChainDestination(params) {
|
|
|
17613
18448
|
return formatClaimRewardsQuoteInfo(result);
|
|
17614
18449
|
}
|
|
17615
18450
|
|
|
18451
|
+
/** SDK name used in telemetry payloads. */ const SDK_NAME = resolveKitSdkName(pkg$1.name);
|
|
18452
|
+
/**
|
|
18453
|
+
* Determine whether deposit parameters target a destination chain.
|
|
18454
|
+
*
|
|
18455
|
+
* @internal
|
|
18456
|
+
*/ function isCrossChainDeposit(params) {
|
|
18457
|
+
return 'to' in params && params.to !== undefined;
|
|
18458
|
+
}
|
|
17616
18459
|
function formatRetryResult(operation, result) {
|
|
17617
18460
|
switch(operation){
|
|
17618
18461
|
case 'deposit':
|
|
@@ -17627,6 +18470,70 @@ function formatRetryResult(operation, result) {
|
|
|
17627
18470
|
}
|
|
17628
18471
|
}
|
|
17629
18472
|
}
|
|
18473
|
+
/**
|
|
18474
|
+
* Emit the success event corresponding to a completed retry.
|
|
18475
|
+
*
|
|
18476
|
+
* @internal
|
|
18477
|
+
*/ function emitRetrySuccessTelemetry(trace, result, config) {
|
|
18478
|
+
switch(trace.operation){
|
|
18479
|
+
case 'deposit':
|
|
18480
|
+
{
|
|
18481
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
18482
|
+
if ('to' in trace.params && trace.params.to !== undefined) {
|
|
18483
|
+
const destinationChain = resolveChainName(trace.params.to.chain);
|
|
18484
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, config, {
|
|
18485
|
+
...sourceChain != null && {
|
|
18486
|
+
sourceChain
|
|
18487
|
+
},
|
|
18488
|
+
...destinationChain != null && {
|
|
18489
|
+
destinationChain
|
|
18490
|
+
}
|
|
18491
|
+
});
|
|
18492
|
+
return;
|
|
18493
|
+
}
|
|
18494
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, config, {
|
|
18495
|
+
...sourceChain != null && {
|
|
18496
|
+
sourceChain
|
|
18497
|
+
},
|
|
18498
|
+
...'txHash' in result && {
|
|
18499
|
+
txHash: result.txHash
|
|
18500
|
+
}
|
|
18501
|
+
});
|
|
18502
|
+
return;
|
|
18503
|
+
}
|
|
18504
|
+
case 'withdraw':
|
|
18505
|
+
{
|
|
18506
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
18507
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, config, {
|
|
18508
|
+
...sourceChain != null && {
|
|
18509
|
+
sourceChain
|
|
18510
|
+
},
|
|
18511
|
+
...'txHash' in result && {
|
|
18512
|
+
txHash: result.txHash
|
|
18513
|
+
}
|
|
18514
|
+
});
|
|
18515
|
+
return;
|
|
18516
|
+
}
|
|
18517
|
+
case 'claimRewards':
|
|
18518
|
+
{
|
|
18519
|
+
if ('rewards' in result && result.status === 'claimed') {
|
|
18520
|
+
const sourceChain = resolveChainName(trace.params.from.chain);
|
|
18521
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, config, {
|
|
18522
|
+
...sourceChain != null && {
|
|
18523
|
+
sourceChain
|
|
18524
|
+
},
|
|
18525
|
+
txHash: result.txHash
|
|
18526
|
+
});
|
|
18527
|
+
}
|
|
18528
|
+
return;
|
|
18529
|
+
}
|
|
18530
|
+
default:
|
|
18531
|
+
{
|
|
18532
|
+
const exhaustive = trace;
|
|
18533
|
+
throw createValidationFailedError('error.cause.trace', exhaustive, 'EarnKit.retry() does not support this earn operation');
|
|
18534
|
+
}
|
|
18535
|
+
}
|
|
18536
|
+
}
|
|
17630
18537
|
/**
|
|
17631
18538
|
* A high-level class-based interface for DeFi lending vault operations.
|
|
17632
18539
|
*
|
|
@@ -17685,6 +18592,8 @@ function formatRetryResult(operation, result) {
|
|
|
17685
18592
|
* ```
|
|
17686
18593
|
*/ class EarnKit {
|
|
17687
18594
|
context;
|
|
18595
|
+
/** Per-kit identity and opt-out state for error telemetry. */ telemetryConfig;
|
|
18596
|
+
/** Per-kit identity and opt-out state for success telemetry. */ analyticsTelemetryConfig;
|
|
17688
18597
|
/**
|
|
17689
18598
|
* Event dispatcher for step-level events emitted during multi-phase earn
|
|
17690
18599
|
* operations. Prefer {@link EarnKit.on} / {@link EarnKit.off} over using
|
|
@@ -17709,6 +18618,16 @@ function formatRetryResult(operation, result) {
|
|
|
17709
18618
|
*/ constructor(config = {}){
|
|
17710
18619
|
this.context = createEarnKitContext(config);
|
|
17711
18620
|
this.actionDispatcher = new Actionable();
|
|
18621
|
+
this.telemetryConfig = {
|
|
18622
|
+
sdkName: SDK_NAME,
|
|
18623
|
+
sdkVersion: pkg$1.version,
|
|
18624
|
+
disabled: config.disableErrorReporting === true
|
|
18625
|
+
};
|
|
18626
|
+
this.analyticsTelemetryConfig = {
|
|
18627
|
+
sdkName: SDK_NAME,
|
|
18628
|
+
sdkVersion: pkg$1.version,
|
|
18629
|
+
disabled: config.disableAnalytics === true
|
|
18630
|
+
};
|
|
17712
18631
|
for (const provider of this.context.providers){
|
|
17713
18632
|
provider.registerDispatcher(this.actionDispatcher);
|
|
17714
18633
|
}
|
|
@@ -17769,29 +18688,36 @@ function formatRetryResult(operation, result) {
|
|
|
17769
18688
|
* }
|
|
17770
18689
|
* ```
|
|
17771
18690
|
*/ async retry(error) {
|
|
17772
|
-
|
|
17773
|
-
|
|
17774
|
-
|
|
17775
|
-
|
|
17776
|
-
|
|
17777
|
-
|
|
17778
|
-
|
|
17779
|
-
|
|
17780
|
-
|
|
17781
|
-
|
|
17782
|
-
|
|
17783
|
-
|
|
17784
|
-
|
|
17785
|
-
|
|
17786
|
-
|
|
17787
|
-
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
|
|
17791
|
-
|
|
18691
|
+
const result = await withErrorTelemetry(async ()=>{
|
|
18692
|
+
if (!isKitError(error)) {
|
|
18693
|
+
throw createValidationFailedError('error', error, 'EarnKit.retry() requires a KitError thrown by a previous earn operation');
|
|
18694
|
+
}
|
|
18695
|
+
if (!isRetryableError$1(error)) {
|
|
18696
|
+
throw createValidationFailedError('error.recoverability', error.recoverability, 'EarnKit.retry() requires a retryable or resumable error — check isRetryableError(error) first');
|
|
18697
|
+
}
|
|
18698
|
+
const trace = error.cause?.trace;
|
|
18699
|
+
if (!isEarnErrorTrace(trace)) {
|
|
18700
|
+
throw createValidationFailedError('error.cause.trace', trace, 'EarnKit.retry() requires a KitError carrying earn retry context (operation, steps, provider, params)');
|
|
18701
|
+
}
|
|
18702
|
+
const provider = this.context.providers.find((candidate)=>candidate.name === trace.provider);
|
|
18703
|
+
if (provider === undefined) {
|
|
18704
|
+
throw createValidationFailedError('error.cause.trace.provider', trace.provider, `No earn provider named "${trace.provider}" is registered with this kit`);
|
|
18705
|
+
}
|
|
18706
|
+
const result = await provider.retry(error);
|
|
18707
|
+
// `provider.retry` returns a flat result union with no compile-time link to
|
|
18708
|
+
// `trace.operation`, so narrow the operation here to select the matching
|
|
18709
|
+
// overload. The result cast in each branch is sound: the provider always
|
|
18710
|
+
// returns the result type corresponding to the resumed operation.
|
|
18711
|
+
if (trace.operation === 'claimRewards') {
|
|
18712
|
+
return formatRetryResult(trace.operation, result);
|
|
18713
|
+
}
|
|
17792
18714
|
return formatRetryResult(trace.operation, result);
|
|
18715
|
+
}, EARN_EVENT_TYPES.RETRY, this.telemetryConfig);
|
|
18716
|
+
const trace = isKitError(error) ? error.cause?.trace : undefined;
|
|
18717
|
+
if (isEarnErrorTrace(trace)) {
|
|
18718
|
+
emitRetrySuccessTelemetry(trace, result, this.analyticsTelemetryConfig);
|
|
17793
18719
|
}
|
|
17794
|
-
return
|
|
18720
|
+
return result;
|
|
17795
18721
|
}
|
|
17796
18722
|
/**
|
|
17797
18723
|
* Return the chains supported by configured earn providers.
|
|
@@ -17825,7 +18751,9 @@ function formatRetryResult(operation, result) {
|
|
|
17825
18751
|
* result.vaults.forEach(v => console.log(`${v.name}: ${(v.currentApy * 100).toFixed(2)}% APY`))
|
|
17826
18752
|
* ```
|
|
17827
18753
|
*/ async getVaults(params) {
|
|
17828
|
-
|
|
18754
|
+
const result = await withErrorTelemetry(async ()=>getVaults$1(this.context, params), EARN_EVENT_TYPES.GET_VAULTS, this.telemetryConfig);
|
|
18755
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.GET_VAULTS, this.analyticsTelemetryConfig, {});
|
|
18756
|
+
return result;
|
|
17829
18757
|
}
|
|
17830
18758
|
/**
|
|
17831
18759
|
* Discover vaults available on a chain.
|
|
@@ -17850,7 +18778,12 @@ function formatRetryResult(operation, result) {
|
|
|
17850
18778
|
* const guarded = result.vaults.filter(v => v.circleGuarded) // Circle-guarded vaults only
|
|
17851
18779
|
* ```
|
|
17852
18780
|
*/ async exploreVaults(params) {
|
|
17853
|
-
|
|
18781
|
+
const context = {
|
|
18782
|
+
sourceChain: resolveChainName(params.chain)
|
|
18783
|
+
};
|
|
18784
|
+
const result = await withErrorTelemetry(async ()=>exploreVaults$1(this.context, params), EARN_EVENT_TYPES.EXPLORE_VAULTS, this.telemetryConfig, context);
|
|
18785
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.EXPLORE_VAULTS, this.analyticsTelemetryConfig, context);
|
|
18786
|
+
return result;
|
|
17854
18787
|
}
|
|
17855
18788
|
/**
|
|
17856
18789
|
* Lazily iterate every vault available on a chain.
|
|
@@ -17897,7 +18830,9 @@ function formatRetryResult(operation, result) {
|
|
|
17897
18830
|
* }
|
|
17898
18831
|
* ```
|
|
17899
18832
|
*/ async getPosition(params) {
|
|
17900
|
-
return getPosition$1(this.context, params)
|
|
18833
|
+
return withErrorTelemetry(async ()=>getPosition$1(this.context, params), EARN_EVENT_TYPES.GET_POSITION, this.telemetryConfig, {
|
|
18834
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
18835
|
+
});
|
|
17901
18836
|
}
|
|
17902
18837
|
/**
|
|
17903
18838
|
* Fetch the current status of a cross-chain deposit by execution ID.
|
|
@@ -17922,7 +18857,7 @@ function formatRetryResult(operation, result) {
|
|
|
17922
18857
|
* console.log(`Bridge ${status.execId} is ${status.status}`)
|
|
17923
18858
|
* ```
|
|
17924
18859
|
*/ async getCrossChainDepositStatus(params) {
|
|
17925
|
-
return getCrossChainDepositStatus$1(this.context, params);
|
|
18860
|
+
return withErrorTelemetry(async ()=>getCrossChainDepositStatus$1(this.context, params), EARN_EVENT_TYPES.GET_CROSS_CHAIN_DEPOSIT_STATUS, this.telemetryConfig);
|
|
17926
18861
|
}
|
|
17927
18862
|
/**
|
|
17928
18863
|
* Poll a cross-chain deposit until it reaches a terminal bridge state.
|
|
@@ -17949,10 +18884,30 @@ function formatRetryResult(operation, result) {
|
|
|
17949
18884
|
* console.log(`Bridge ended as ${result.outcome}`)
|
|
17950
18885
|
* ```
|
|
17951
18886
|
*/ async waitForCrossChainDeposit(params) {
|
|
17952
|
-
return waitForCrossChainDeposit$1(this.context, params);
|
|
18887
|
+
return withErrorTelemetry(async ()=>waitForCrossChainDeposit$1(this.context, params), EARN_EVENT_TYPES.WAIT_FOR_CROSS_CHAIN_DEPOSIT, this.telemetryConfig);
|
|
17953
18888
|
}
|
|
17954
18889
|
async deposit(params) {
|
|
17955
|
-
|
|
18890
|
+
const isCrossChain = isCrossChainDeposit(params);
|
|
18891
|
+
const context = {
|
|
18892
|
+
sourceChain: resolveChainName(params.from.chain),
|
|
18893
|
+
...isCrossChain && {
|
|
18894
|
+
destinationChain: resolveChainName(params.to.chain)
|
|
18895
|
+
}
|
|
18896
|
+
};
|
|
18897
|
+
const eventType = isCrossChain ? EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT : EARN_EVENT_TYPES.DEPOSIT;
|
|
18898
|
+
const result = await withErrorTelemetry(async ()=>deposit$1(this.context, params), eventType, this.telemetryConfig, context);
|
|
18899
|
+
if (result.kind === 'cross-chain') {
|
|
18900
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CROSS_CHAIN_DEPOSIT, this.analyticsTelemetryConfig, {
|
|
18901
|
+
sourceChain: resolveChainName(result.sourceChain),
|
|
18902
|
+
destinationChain: resolveChainName(result.destinationChain)
|
|
18903
|
+
});
|
|
18904
|
+
} else {
|
|
18905
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.DEPOSIT, this.analyticsTelemetryConfig, {
|
|
18906
|
+
...context,
|
|
18907
|
+
txHash: result.txHash
|
|
18908
|
+
});
|
|
18909
|
+
}
|
|
18910
|
+
return result;
|
|
17956
18911
|
}
|
|
17957
18912
|
/**
|
|
17958
18913
|
* Execute a withdrawal from a DeFi lending vault.
|
|
@@ -17978,7 +18933,15 @@ function formatRetryResult(operation, result) {
|
|
|
17978
18933
|
* console.log(`Withdrew ${result.amount} from ${result.vaultAddress}, tx: ${result.txHash}`)
|
|
17979
18934
|
* ```
|
|
17980
18935
|
*/ async withdraw(params) {
|
|
17981
|
-
|
|
18936
|
+
const context = {
|
|
18937
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
18938
|
+
};
|
|
18939
|
+
const result = await withErrorTelemetry(async ()=>withdraw$1(this.context, params), EARN_EVENT_TYPES.WITHDRAW, this.telemetryConfig, context);
|
|
18940
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.WITHDRAW, this.analyticsTelemetryConfig, {
|
|
18941
|
+
...context,
|
|
18942
|
+
txHash: result.txHash
|
|
18943
|
+
});
|
|
18944
|
+
return result;
|
|
17982
18945
|
}
|
|
17983
18946
|
/**
|
|
17984
18947
|
* Claim rewards from earn vaults.
|
|
@@ -18005,7 +18968,17 @@ function formatRetryResult(operation, result) {
|
|
|
18005
18968
|
*
|
|
18006
18969
|
* @internal
|
|
18007
18970
|
*/ async claimRewards(params) {
|
|
18008
|
-
|
|
18971
|
+
const context = {
|
|
18972
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
18973
|
+
};
|
|
18974
|
+
const result = await withErrorTelemetry(async ()=>claimRewards$1(this.context, params), EARN_EVENT_TYPES.CLAIM_REWARDS, this.telemetryConfig, context);
|
|
18975
|
+
if (result.status === 'claimed') {
|
|
18976
|
+
emitSuccessTelemetry(EARN_EVENT_TYPES.CLAIM_REWARDS, this.analyticsTelemetryConfig, {
|
|
18977
|
+
...context,
|
|
18978
|
+
txHash: result.txHash
|
|
18979
|
+
});
|
|
18980
|
+
}
|
|
18981
|
+
return result;
|
|
18009
18982
|
}
|
|
18010
18983
|
/**
|
|
18011
18984
|
* Get an informational quote for a deposit into a vault.
|
|
@@ -18027,7 +19000,9 @@ function formatRetryResult(operation, result) {
|
|
|
18027
19000
|
* console.log(`Expected shares: ${quote.expectedShares.amount}`)
|
|
18028
19001
|
* ```
|
|
18029
19002
|
*/ async getDepositQuote(params) {
|
|
18030
|
-
return getDepositQuote$1(this.context, params)
|
|
19003
|
+
return withErrorTelemetry(async ()=>getDepositQuote$1(this.context, params), EARN_EVENT_TYPES.GET_DEPOSIT_QUOTE, this.telemetryConfig, {
|
|
19004
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
19005
|
+
});
|
|
18031
19006
|
}
|
|
18032
19007
|
/**
|
|
18033
19008
|
* Get an informational quote for a withdrawal from a vault.
|
|
@@ -18049,7 +19024,9 @@ function formatRetryResult(operation, result) {
|
|
|
18049
19024
|
* console.log(`Shares to redeem: ${quote.sharesToRedeem.amount}`)
|
|
18050
19025
|
* ```
|
|
18051
19026
|
*/ async getWithdrawalQuote(params) {
|
|
18052
|
-
return getWithdrawalQuote$1(this.context, params)
|
|
19027
|
+
return withErrorTelemetry(async ()=>getWithdrawalQuote$1(this.context, params), EARN_EVENT_TYPES.GET_WITHDRAWAL_QUOTE, this.telemetryConfig, {
|
|
19028
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
19029
|
+
});
|
|
18053
19030
|
}
|
|
18054
19031
|
/**
|
|
18055
19032
|
* Get an informational quote for claiming rewards.
|
|
@@ -18071,34 +19048,111 @@ function formatRetryResult(operation, result) {
|
|
|
18071
19048
|
*
|
|
18072
19049
|
* @internal
|
|
18073
19050
|
*/ async getClaimRewardsQuote(params) {
|
|
18074
|
-
return getClaimRewardsQuote$1(this.context, params)
|
|
19051
|
+
return withErrorTelemetry(async ()=>getClaimRewardsQuote$1(this.context, params), EARN_EVENT_TYPES.GET_CLAIM_REWARDS_QUOTE, this.telemetryConfig, {
|
|
19052
|
+
sourceChain: resolveChainName(params.from.chain)
|
|
19053
|
+
});
|
|
18075
19054
|
}
|
|
18076
19055
|
}
|
|
18077
19056
|
|
|
18078
19057
|
// Auto-register this kit for user agent tracking
|
|
18079
19058
|
registerKit(`${pkg$1.name}/${pkg$1.version}`);
|
|
18080
19059
|
|
|
19060
|
+
/**
|
|
19061
|
+
* Register event handlers from a context actions map to a kit instance.
|
|
19062
|
+
*
|
|
19063
|
+
* This utility function registers event handlers stored in a context actions map
|
|
19064
|
+
* with a kit instance that supports event handling via an `on` method. It handles
|
|
19065
|
+
* wildcard handlers ('*') and prefixed action handlers, stripping the prefix
|
|
19066
|
+
* before registration.
|
|
19067
|
+
*
|
|
19068
|
+
* The function is designed to be reusable across different operation types
|
|
19069
|
+
* (bridge, swap, stake, etc.) by accepting a configurable prefix parameter.
|
|
19070
|
+
*
|
|
19071
|
+
* @param kit - The kit instance to register handlers with (must have an `on` method)
|
|
19072
|
+
* @param handlers - Map of action names to arrays of handler functions
|
|
19073
|
+
* @param prefix - Optional prefix to strip from action names (e.g., 'bridge.')
|
|
19074
|
+
*
|
|
19075
|
+
* @example
|
|
19076
|
+
* ```typescript
|
|
19077
|
+
* import { registerActionHandlers } from '@circle-fin/app-kit/utils'
|
|
19078
|
+
* import { BridgeKit } from '@circle-fin/bridge-kit'
|
|
19079
|
+
*
|
|
19080
|
+
* const kit = new BridgeKit()
|
|
19081
|
+
* const handlers = {
|
|
19082
|
+
* '*': [(payload) => console.log('All actions:', payload)],
|
|
19083
|
+
* 'bridge.approve': [(payload) => console.log('Approved:', payload)],
|
|
19084
|
+
* 'bridge.burn': [(payload) => console.log('Burned:', payload)],
|
|
19085
|
+
* }
|
|
19086
|
+
*
|
|
19087
|
+
* registerActionHandlers(kit, handlers, 'bridge.')
|
|
19088
|
+
* ```
|
|
19089
|
+
*
|
|
19090
|
+
* @example
|
|
19091
|
+
* ```typescript
|
|
19092
|
+
* import { registerActionHandlers } from '@circle-fin/app-kit/utils'
|
|
19093
|
+
* import { SwapKit } from '@circle-fin/swap-kit'
|
|
19094
|
+
*
|
|
19095
|
+
* const kit = new SwapKit()
|
|
19096
|
+
* const handlers = {
|
|
19097
|
+
* 'swap.initiate': [(payload) => console.log('Swap initiated:', payload)],
|
|
19098
|
+
* }
|
|
19099
|
+
*
|
|
19100
|
+
* registerActionHandlers(kit, handlers, 'swap.')
|
|
19101
|
+
* ```
|
|
19102
|
+
*/ const registerActionHandlers = (kit, handlers, prefix = '')=>{
|
|
19103
|
+
for (const [action, handlerArray] of Object.entries(handlers)){
|
|
19104
|
+
// Register all handlers for this action
|
|
19105
|
+
for (const handler of handlerArray){
|
|
19106
|
+
if (action === '*') {
|
|
19107
|
+
// Wildcard handlers are registered as-is
|
|
19108
|
+
kit.on('*', handler);
|
|
19109
|
+
} else if (prefix && action.startsWith(prefix)) {
|
|
19110
|
+
// Remove prefix to get the actual kit action name
|
|
19111
|
+
const kitAction = action.split('.').at(1);
|
|
19112
|
+
if (kitAction) {
|
|
19113
|
+
kit.on(kitAction, handler);
|
|
19114
|
+
}
|
|
19115
|
+
} else if (!prefix) {
|
|
19116
|
+
// No prefix configured, register action as-is
|
|
19117
|
+
kit.on(action, handler);
|
|
19118
|
+
}
|
|
19119
|
+
// Actions that don't match the prefix are silently ignored
|
|
19120
|
+
}
|
|
19121
|
+
}
|
|
19122
|
+
};
|
|
19123
|
+
|
|
18081
19124
|
/**
|
|
18082
19125
|
* Create an EarnKit instance for AppKit earn operations.
|
|
18083
19126
|
*
|
|
18084
|
-
*
|
|
18085
|
-
*
|
|
18086
|
-
*
|
|
18087
|
-
* reserved until EarnKit fee support ships.
|
|
19127
|
+
* Attaches any earn event handlers previously registered on the AppKit
|
|
19128
|
+
* context (via `kit.on('earn.*', …)` or `kit.on('*', …)`) so step events
|
|
19129
|
+
* fire during the returned kit's earn operations.
|
|
18088
19130
|
*
|
|
18089
|
-
*
|
|
19131
|
+
* @remarks Developer fee hooks from the AppKit context are not applied.
|
|
19132
|
+
* EarnKit does not yet support custom fee policies.
|
|
18090
19133
|
*
|
|
18091
|
-
* @param context - AppKit context
|
|
18092
|
-
* @returns
|
|
19134
|
+
* @param context - AppKit context with earn event handlers and kit options
|
|
19135
|
+
* @returns An EarnKit instance ready for AppKit earn operations
|
|
18093
19136
|
*
|
|
18094
19137
|
* @example
|
|
18095
19138
|
* ```typescript
|
|
18096
19139
|
* const earnKit = createEarnKit(context)
|
|
18097
19140
|
* ```
|
|
18098
|
-
*/ const createEarnKit = ()=>
|
|
19141
|
+
*/ const createEarnKit = (context)=>{
|
|
19142
|
+
const kit = new EarnKit({
|
|
19143
|
+
...context.disableErrorReporting != null && {
|
|
19144
|
+
disableErrorReporting: context.disableErrorReporting
|
|
19145
|
+
},
|
|
19146
|
+
...context.disableAnalytics != null && {
|
|
19147
|
+
disableAnalytics: context.disableAnalytics
|
|
19148
|
+
}
|
|
19149
|
+
});
|
|
19150
|
+
registerActionHandlers(kit, context.actions.earn, 'earn');
|
|
19151
|
+
return kit;
|
|
19152
|
+
};
|
|
18099
19153
|
|
|
18100
19154
|
async function deposit(context, params) {
|
|
18101
|
-
return createEarnKit().deposit(params);
|
|
19155
|
+
return createEarnKit(context).deposit(params);
|
|
18102
19156
|
}
|
|
18103
19157
|
/**
|
|
18104
19158
|
* Execute an earn withdrawal operation.
|
|
@@ -18122,7 +19176,7 @@ async function deposit(context, params) {
|
|
|
18122
19176
|
* })
|
|
18123
19177
|
* ```
|
|
18124
19178
|
*/ async function withdraw(context, params) {
|
|
18125
|
-
return createEarnKit().withdraw(params);
|
|
19179
|
+
return createEarnKit(context).withdraw(params);
|
|
18126
19180
|
}
|
|
18127
19181
|
/**
|
|
18128
19182
|
* Claim earn rewards.
|
|
@@ -18145,7 +19199,7 @@ async function deposit(context, params) {
|
|
|
18145
19199
|
* })
|
|
18146
19200
|
* ```
|
|
18147
19201
|
*/ async function claimRewards(context, params) {
|
|
18148
|
-
return createEarnKit().claimRewards(params);
|
|
19202
|
+
return createEarnKit(context).claimRewards(params);
|
|
18149
19203
|
}
|
|
18150
19204
|
/**
|
|
18151
19205
|
* Fetch vault information.
|
|
@@ -18167,7 +19221,7 @@ async function deposit(context, params) {
|
|
|
18167
19221
|
* })
|
|
18168
19222
|
* ```
|
|
18169
19223
|
*/ async function getVaults(context, params) {
|
|
18170
|
-
return createEarnKit().getVaults(params);
|
|
19224
|
+
return createEarnKit(context).getVaults(params);
|
|
18171
19225
|
}
|
|
18172
19226
|
/**
|
|
18173
19227
|
* Discover vaults available on a chain.
|
|
@@ -18191,7 +19245,7 @@ async function deposit(context, params) {
|
|
|
18191
19245
|
* })
|
|
18192
19246
|
* ```
|
|
18193
19247
|
*/ async function exploreVaults(context, params) {
|
|
18194
|
-
return createEarnKit().exploreVaults(params);
|
|
19248
|
+
return createEarnKit(context).exploreVaults(params);
|
|
18195
19249
|
}
|
|
18196
19250
|
/**
|
|
18197
19251
|
* Lazily iterate every vault available on a chain.
|
|
@@ -18215,7 +19269,7 @@ async function deposit(context, params) {
|
|
|
18215
19269
|
* }
|
|
18216
19270
|
* ```
|
|
18217
19271
|
*/ function exploreVaultsIterator(context, params) {
|
|
18218
|
-
return createEarnKit().exploreVaultsIterator(params);
|
|
19272
|
+
return createEarnKit(context).exploreVaultsIterator(params);
|
|
18219
19273
|
}
|
|
18220
19274
|
/**
|
|
18221
19275
|
* Fetch a wallet position in a vault.
|
|
@@ -18238,7 +19292,7 @@ async function deposit(context, params) {
|
|
|
18238
19292
|
* })
|
|
18239
19293
|
* ```
|
|
18240
19294
|
*/ async function getPosition(context, params) {
|
|
18241
|
-
return createEarnKit().getPosition(params);
|
|
19295
|
+
return createEarnKit(context).getPosition(params);
|
|
18242
19296
|
}
|
|
18243
19297
|
/**
|
|
18244
19298
|
* Fetch the current status of a cross-chain Earn deposit.
|
|
@@ -18260,7 +19314,7 @@ async function deposit(context, params) {
|
|
|
18260
19314
|
* console.log(status.status)
|
|
18261
19315
|
* ```
|
|
18262
19316
|
*/ async function getCrossChainDepositStatus(context, params) {
|
|
18263
|
-
return createEarnKit().getCrossChainDepositStatus(params);
|
|
19317
|
+
return createEarnKit(context).getCrossChainDepositStatus(params);
|
|
18264
19318
|
}
|
|
18265
19319
|
/**
|
|
18266
19320
|
* Poll a cross-chain Earn deposit until it reaches a terminal bridge state.
|
|
@@ -18283,7 +19337,7 @@ async function deposit(context, params) {
|
|
|
18283
19337
|
* console.log(result.outcome)
|
|
18284
19338
|
* ```
|
|
18285
19339
|
*/ async function waitForCrossChainDeposit(context, params) {
|
|
18286
|
-
return createEarnKit().waitForCrossChainDeposit(params);
|
|
19340
|
+
return createEarnKit(context).waitForCrossChainDeposit(params);
|
|
18287
19341
|
}
|
|
18288
19342
|
/**
|
|
18289
19343
|
* Fetch a deposit quote.
|
|
@@ -18307,7 +19361,7 @@ async function deposit(context, params) {
|
|
|
18307
19361
|
* })
|
|
18308
19362
|
* ```
|
|
18309
19363
|
*/ async function getDepositQuote(context, params) {
|
|
18310
|
-
return createEarnKit().getDepositQuote(params);
|
|
19364
|
+
return createEarnKit(context).getDepositQuote(params);
|
|
18311
19365
|
}
|
|
18312
19366
|
/**
|
|
18313
19367
|
* Fetch a withdrawal quote.
|
|
@@ -18331,7 +19385,7 @@ async function deposit(context, params) {
|
|
|
18331
19385
|
* })
|
|
18332
19386
|
* ```
|
|
18333
19387
|
*/ async function getWithdrawalQuote(context, params) {
|
|
18334
|
-
return createEarnKit().getWithdrawalQuote(params);
|
|
19388
|
+
return createEarnKit(context).getWithdrawalQuote(params);
|
|
18335
19389
|
}
|
|
18336
19390
|
/**
|
|
18337
19391
|
* Fetch a claim rewards quote.
|
|
@@ -18354,7 +19408,43 @@ async function deposit(context, params) {
|
|
|
18354
19408
|
* })
|
|
18355
19409
|
* ```
|
|
18356
19410
|
*/ async function getClaimRewardsQuote(context, params) {
|
|
18357
|
-
return createEarnKit().getClaimRewardsQuote(params);
|
|
19411
|
+
return createEarnKit(context).getClaimRewardsQuote(params);
|
|
19412
|
+
}
|
|
19413
|
+
/**
|
|
19414
|
+
* Resume a multi-phase earn operation that previously failed.
|
|
19415
|
+
*
|
|
19416
|
+
* Pass the {@link KitError} caught from `deposit`, `withdraw`, or
|
|
19417
|
+
* `claimRewards`. Completed phases can be skipped when the error carries
|
|
19418
|
+
* earn retry context. Call `isRetryableError(error)` first.
|
|
19419
|
+
*
|
|
19420
|
+
* @remarks
|
|
19421
|
+
* Retry re-fetches execution params and may re-submit the execute
|
|
19422
|
+
* transaction. Treat this as best-effort recovery if a prior execute
|
|
19423
|
+
* broadcast may still be in flight.
|
|
19424
|
+
*
|
|
19425
|
+
* @param context - AppKit context
|
|
19426
|
+
* @param error - The error caught from a previous multi-phase earn operation
|
|
19427
|
+
* @returns Promise resolving to the result of the resumed operation
|
|
19428
|
+
* @throws If the error is not retryable or lacks earn retry context
|
|
19429
|
+
*
|
|
19430
|
+
* @example
|
|
19431
|
+
* ```typescript
|
|
19432
|
+
* import { isRetryableError } from '@circle-fin/app-kit'
|
|
19433
|
+
* import { createContext } from '@circle-fin/app-kit/context'
|
|
19434
|
+
* import { retry } from '@circle-fin/app-kit/earn'
|
|
19435
|
+
*
|
|
19436
|
+
* const context = createContext()
|
|
19437
|
+
*
|
|
19438
|
+
* try {
|
|
19439
|
+
* await deposit(context, params)
|
|
19440
|
+
* } catch (error) {
|
|
19441
|
+
* if (isRetryableError(error)) {
|
|
19442
|
+
* const result = await retry(context, error)
|
|
19443
|
+
* }
|
|
19444
|
+
* }
|
|
19445
|
+
* ```
|
|
19446
|
+
*/ async function retry(context, error) {
|
|
19447
|
+
return createEarnKit(context).retry(error);
|
|
18358
19448
|
}
|
|
18359
19449
|
|
|
18360
19450
|
exports.claimRewards = claimRewards;
|
|
@@ -18367,6 +19457,7 @@ exports.getDepositQuote = getDepositQuote;
|
|
|
18367
19457
|
exports.getPosition = getPosition;
|
|
18368
19458
|
exports.getVaults = getVaults;
|
|
18369
19459
|
exports.getWithdrawalQuote = getWithdrawalQuote;
|
|
19460
|
+
exports.retry = retry;
|
|
18370
19461
|
exports.waitForCrossChainDeposit = waitForCrossChainDeposit;
|
|
18371
19462
|
exports.withdraw = withdraw;
|
|
18372
19463
|
//# sourceMappingURL=earn.cjs.map
|