@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/swap.mjs
CHANGED
|
@@ -16,15 +16,27 @@
|
|
|
16
16
|
* limitations under the License.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
// Buffer polyfill setup - executes before any other code
|
|
20
|
+
// Ensures globalThis.Buffer is available for Solana libraries
|
|
21
|
+
import { Buffer } from 'buffer';
|
|
22
|
+
if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
|
|
23
|
+
globalThis.Buffer = Buffer;
|
|
24
|
+
}
|
|
25
|
+
if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
|
|
26
|
+
window.Buffer = Buffer;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
19
30
|
import { z } from 'zod';
|
|
20
31
|
import 'pino';
|
|
32
|
+
import { hexlify, hexZeroPad } from '@ethersproject/bytes';
|
|
33
|
+
import '@ethersproject/abi';
|
|
34
|
+
import { getAddress } from '@ethersproject/address';
|
|
21
35
|
import { PublicKey } from '@solana/web3.js';
|
|
22
36
|
import 'bn.js';
|
|
23
37
|
import '@coral-xyz/anchor';
|
|
24
38
|
import bs58 from 'bs58';
|
|
25
39
|
import '@noble/curves/ed25519';
|
|
26
|
-
import { hexlify, hexZeroPad } from '@ethersproject/bytes';
|
|
27
|
-
import { getAddress } from '@ethersproject/address';
|
|
28
40
|
import { formatUnits as formatUnits$1 } from '@ethersproject/units';
|
|
29
41
|
import { keccak256 } from '@ethersproject/keccak256';
|
|
30
42
|
|
|
@@ -43,6 +55,51 @@ import { keccak256 } from '@ethersproject/keccak256';
|
|
|
43
55
|
* }
|
|
44
56
|
* ```
|
|
45
57
|
*/ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
|
|
58
|
+
/**
|
|
59
|
+
* Check whether the current runtime exposes a browser DOM.
|
|
60
|
+
*
|
|
61
|
+
* @remarks
|
|
62
|
+
* This intentionally does not treat every non-Node runtime as a browser.
|
|
63
|
+
* Server-side edge runtimes such as Cloudflare Workers, Deno, and Supabase
|
|
64
|
+
* Edge Functions do not expose Node globals but can safely use server
|
|
65
|
+
* credentials. A Node.js runtime remains server-side even when a test or SSR
|
|
66
|
+
* environment provides a DOM shim.
|
|
67
|
+
*
|
|
68
|
+
* @returns `true` when running in a browser window, `false` otherwise.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* import { isBrowserEnvironment } from '@core/utils'
|
|
73
|
+
*
|
|
74
|
+
* if (isBrowserEnvironment()) {
|
|
75
|
+
* throw new Error('Server-only secrets must not be used in the browser')
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/ const isBrowserEnvironment = ()=>{
|
|
79
|
+
const browserWindow = globalThis.window;
|
|
80
|
+
return !isNodeEnvironment() && browserWindow?.document !== undefined;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Return the SDK User-Agent request header only when running in Node.js.
|
|
84
|
+
*
|
|
85
|
+
* Browsers forbid manually setting `User-Agent`, and a custom fallback header
|
|
86
|
+
* can trigger CORS preflight. Non-Node server runtimes also omit this optional
|
|
87
|
+
* attribution header because they cannot set it reliably.
|
|
88
|
+
*
|
|
89
|
+
* @returns A User-Agent header in Node.js, or an empty object otherwise.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* import { getNodeUserAgentHeader } from '@core/utils'
|
|
94
|
+
*
|
|
95
|
+
* const headers = {
|
|
96
|
+
* 'Content-Type': 'application/json',
|
|
97
|
+
* ...getNodeUserAgentHeader(),
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
|
|
101
|
+
'User-Agent': getUserAgent()
|
|
102
|
+
} : {};
|
|
46
103
|
/**
|
|
47
104
|
* Detect the runtime environment and return a shortened identifier.
|
|
48
105
|
*
|
|
@@ -3527,7 +3584,10 @@ var EarnChain;
|
|
|
3527
3584
|
contracts: {
|
|
3528
3585
|
v1: {
|
|
3529
3586
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
3530
|
-
minter: GATEWAY_MINTER_EVM_TESTNET
|
|
3587
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
3588
|
+
// DepositForHandler the GenericExecutor calls to run a fast cross-chain
|
|
3589
|
+
// deposit into the GatewayWallet above.
|
|
3590
|
+
depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
|
|
3531
3591
|
}
|
|
3532
3592
|
},
|
|
3533
3593
|
forwarderSupported: {
|
|
@@ -6595,7 +6655,10 @@ var Chains = /*#__PURE__*/Object.freeze({
|
|
|
6595
6655
|
minter: z.string({
|
|
6596
6656
|
required_error: 'Gateway minter address is required. Please provide a valid contract address.',
|
|
6597
6657
|
invalid_type_error: 'Gateway minter address must be a string.'
|
|
6598
|
-
}).min(1, 'Gateway minter address cannot be empty.')
|
|
6658
|
+
}).min(1, 'Gateway minter address cannot be empty.'),
|
|
6659
|
+
depositForHandler: z.string({
|
|
6660
|
+
invalid_type_error: 'Gateway depositForHandler address must be a string.'
|
|
6661
|
+
}).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
|
|
6599
6662
|
}).strict() // Reject any additional properties not defined in the schema
|
|
6600
6663
|
;
|
|
6601
6664
|
/**
|
|
@@ -8014,13 +8077,12 @@ const swapTokenEnumSchema = z.enum([
|
|
|
8014
8077
|
headers: {
|
|
8015
8078
|
...DEFAULT_CONFIG$1.headers,
|
|
8016
8079
|
...config.headers ?? {},
|
|
8017
|
-
//
|
|
8018
|
-
//
|
|
8019
|
-
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
}
|
|
8080
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
8081
|
+
// fallback header the SDK used instead trips CORS preflight against the
|
|
8082
|
+
// Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
|
|
8083
|
+
// blocking the request. So send the SDK user agent only in Node;
|
|
8084
|
+
// browsers omit it entirely.
|
|
8085
|
+
...getNodeUserAgentHeader()
|
|
8024
8086
|
}
|
|
8025
8087
|
};
|
|
8026
8088
|
let lastError;
|
|
@@ -9480,6 +9542,13 @@ const swapTokenEnumSchema = z.enum([
|
|
|
9480
9542
|
return explorerUrl;
|
|
9481
9543
|
}
|
|
9482
9544
|
|
|
9545
|
+
/**
|
|
9546
|
+
* CCTP forwarding magic bytes prefix.
|
|
9547
|
+
*
|
|
9548
|
+
* The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
|
|
9549
|
+
* This prefix is right-padded to 24 bytes in the final hookData.
|
|
9550
|
+
*/ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
|
|
9551
|
+
|
|
9483
9552
|
/**
|
|
9484
9553
|
* Project an arbitrary payload onto the exact set of fields the telemetry
|
|
9485
9554
|
* endpoint accepts.
|
|
@@ -9514,6 +9583,7 @@ const swapTokenEnumSchema = z.enum([
|
|
|
9514
9583
|
if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
|
|
9515
9584
|
if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
|
|
9516
9585
|
if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
|
|
9586
|
+
if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
|
|
9517
9587
|
if (payload.errorDetails !== undefined) {
|
|
9518
9588
|
const errorDetails = {
|
|
9519
9589
|
...payload.errorDetails.errorCode !== undefined && {
|
|
@@ -9584,18 +9654,15 @@ const swapTokenEnumSchema = z.enum([
|
|
|
9584
9654
|
timeoutHandle.unref();
|
|
9585
9655
|
}
|
|
9586
9656
|
try {
|
|
9587
|
-
const isNode = isNodeEnvironment();
|
|
9588
|
-
const userAgent = getUserAgent();
|
|
9589
9657
|
await fetch(getLogsUrl(), {
|
|
9590
9658
|
method: 'POST',
|
|
9591
9659
|
headers: {
|
|
9592
9660
|
'Content-Type': 'application/json',
|
|
9593
|
-
//
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
}
|
|
9661
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
9662
|
+
// fallback header the SDK used instead trips CORS preflight (it isn't
|
|
9663
|
+
// in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
|
|
9664
|
+
// it only in Node; browsers omit it entirely.
|
|
9665
|
+
...getNodeUserAgentHeader()
|
|
9599
9666
|
},
|
|
9600
9667
|
body: JSON.stringify(toSafePayload(payload)),
|
|
9601
9668
|
signal: controller.signal
|
|
@@ -9763,7 +9830,7 @@ const swapTokenEnumSchema = z.enum([
|
|
|
9763
9830
|
// discards the stack trace, nested `cause`, and any custom Error
|
|
9764
9831
|
// properties — exactly the context an on-call needs when a
|
|
9765
9832
|
// resolver-closure regression triggers this path.
|
|
9766
|
-
console.warn(`[stablecoin-kits telemetry] dropped
|
|
9833
|
+
console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
|
|
9767
9834
|
} catch {
|
|
9768
9835
|
// console.warn itself throwing is the user's environment; nothing more we
|
|
9769
9836
|
// can do without risking the original operation error.
|
|
@@ -9779,7 +9846,9 @@ const swapTokenEnumSchema = z.enum([
|
|
|
9779
9846
|
sdkVersion: config.sdkVersion,
|
|
9780
9847
|
eventType,
|
|
9781
9848
|
timestamp: new Date().toISOString(),
|
|
9782
|
-
errorDetails
|
|
9849
|
+
...errorDetails !== undefined && {
|
|
9850
|
+
errorDetails
|
|
9851
|
+
},
|
|
9783
9852
|
clientContext: buildClientContext(),
|
|
9784
9853
|
...context?.sourceChain != null && {
|
|
9785
9854
|
sourceChain: context.sourceChain
|
|
@@ -9795,9 +9864,45 @@ const swapTokenEnumSchema = z.enum([
|
|
|
9795
9864
|
},
|
|
9796
9865
|
...context?.txHash != null && {
|
|
9797
9866
|
txHash: context.txHash
|
|
9867
|
+
},
|
|
9868
|
+
...context?.correlationId != null && {
|
|
9869
|
+
correlationId: context.correlationId
|
|
9798
9870
|
}
|
|
9799
9871
|
};
|
|
9800
9872
|
}
|
|
9873
|
+
/**
|
|
9874
|
+
* Emit telemetry for a completed operation without affecting its caller.
|
|
9875
|
+
*
|
|
9876
|
+
* No-ops when `config.disabled` is `true`. Like {@link withErrorTelemetry},
|
|
9877
|
+
* failures while constructing or submitting the telemetry payload are reported
|
|
9878
|
+
* as a soft warning and never change a completed operation's result.
|
|
9879
|
+
*
|
|
9880
|
+
* @param eventType - The telemetry event type for the completed operation.
|
|
9881
|
+
* @param config - Per-kit SDK identity and disabled flag.
|
|
9882
|
+
* @param context - Optional chain, token, and transaction context.
|
|
9883
|
+
* @returns Nothing.
|
|
9884
|
+
* @throws Never — telemetry failures are reported as warnings.
|
|
9885
|
+
*
|
|
9886
|
+
* @example
|
|
9887
|
+
* ```typescript
|
|
9888
|
+
* import { emitSuccessTelemetry } from '@core/utils'
|
|
9889
|
+
*
|
|
9890
|
+
* emitSuccessTelemetry(
|
|
9891
|
+
* 'bridge_bridge',
|
|
9892
|
+
* { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
|
|
9893
|
+
* { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
|
|
9894
|
+
* )
|
|
9895
|
+
* ```
|
|
9896
|
+
*/ function emitSuccessTelemetry(eventType, config, context) {
|
|
9897
|
+
if (config.disabled) {
|
|
9898
|
+
return;
|
|
9899
|
+
}
|
|
9900
|
+
try {
|
|
9901
|
+
void emitAnalyticsLog(buildPayload(config, eventType, undefined, context));
|
|
9902
|
+
} catch (telemetryError) {
|
|
9903
|
+
warnTelemetryDrop(eventType, telemetryError);
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9801
9906
|
/**
|
|
9802
9907
|
* Wrap an async operation with error telemetry.
|
|
9803
9908
|
*
|
|
@@ -9856,7 +9961,7 @@ const swapTokenEnumSchema = z.enum([
|
|
|
9856
9961
|
}
|
|
9857
9962
|
|
|
9858
9963
|
var name$2 = "@circle-fin/bridge-kit";
|
|
9859
|
-
var version$2 = "1.12.
|
|
9964
|
+
var version$2 = "1.12.2";
|
|
9860
9965
|
var pkg$2 = {
|
|
9861
9966
|
name: name$2,
|
|
9862
9967
|
version: version$2};
|
|
@@ -10714,6 +10819,17 @@ var TransferSpeed;
|
|
|
10714
10819
|
clock: z.any().optional()
|
|
10715
10820
|
}).passthrough();
|
|
10716
10821
|
|
|
10822
|
+
/**
|
|
10823
|
+
* The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
|
|
10824
|
+
* hookData must start with.
|
|
10825
|
+
*
|
|
10826
|
+
* Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
|
|
10827
|
+
* so this module-level constant does not reference the Node `Buffer` global at
|
|
10828
|
+
* import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
|
|
10829
|
+
* bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
|
|
10830
|
+
* that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
|
|
10831
|
+
*/ Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
10832
|
+
|
|
10717
10833
|
/**
|
|
10718
10834
|
* The minimum finality threshold for CCTPv2 transfers.
|
|
10719
10835
|
*
|
|
@@ -10746,7 +10862,7 @@ var TransferSpeed;
|
|
|
10746
10862
|
registerKit(`${pkg$2.name}/${pkg$2.version}`);
|
|
10747
10863
|
|
|
10748
10864
|
var name$1 = "@circle-fin/swap-kit";
|
|
10749
|
-
var version$1 = "1.
|
|
10865
|
+
var version$1 = "1.5.0";
|
|
10750
10866
|
var pkg$1 = {
|
|
10751
10867
|
name: name$1,
|
|
10752
10868
|
version: version$1};
|
|
@@ -10811,7 +10927,10 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
10811
10927
|
}).min(1, 'kitKey must be a non-empty string').optional(),
|
|
10812
10928
|
provider: z.string({
|
|
10813
10929
|
invalid_type_error: 'provider must be a string'
|
|
10814
|
-
}).min(1, 'provider must be a non-empty string').optional()
|
|
10930
|
+
}).min(1, 'provider must be a non-empty string').optional(),
|
|
10931
|
+
batchTransactions: z.boolean({
|
|
10932
|
+
invalid_type_error: 'batchTransactions must be a boolean'
|
|
10933
|
+
}).optional()
|
|
10815
10934
|
});
|
|
10816
10935
|
/**
|
|
10817
10936
|
* Zod schema for adapter context.
|
|
@@ -11342,7 +11461,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11342
11461
|
/**
|
|
11343
11462
|
* Circle Stablecoin Service API Key.
|
|
11344
11463
|
* Must be a valid API key format.
|
|
11345
|
-
*/ apiKey: apiKeySchema
|
|
11464
|
+
*/ apiKey: apiKeySchema.optional()
|
|
11346
11465
|
}).superRefine(requireCrossChainQuoteToAddress);
|
|
11347
11466
|
/**
|
|
11348
11467
|
* Zod schema for validating CreateSwapRequest parameters.
|
|
@@ -11400,7 +11519,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11400
11519
|
/**
|
|
11401
11520
|
* Circle Stablecoin Service API Key.
|
|
11402
11521
|
* Must be a valid API key format.
|
|
11403
|
-
*/ apiKey: apiKeySchema
|
|
11522
|
+
*/ apiKey: apiKeySchema.optional()
|
|
11404
11523
|
});
|
|
11405
11524
|
/**
|
|
11406
11525
|
* Zod schema for validating GetSwapStatusResponse data.
|
|
@@ -11436,7 +11555,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11436
11555
|
toChain: z.string({
|
|
11437
11556
|
invalid_type_error: 'toChain must be a string'
|
|
11438
11557
|
}).min(1, 'toChain must be a non-empty string if provided').optional(),
|
|
11439
|
-
apiKey: apiKeySchema
|
|
11558
|
+
apiKey: apiKeySchema.optional()
|
|
11440
11559
|
});
|
|
11441
11560
|
/**
|
|
11442
11561
|
* Zod schema for validating CreateSwapResponse payloads.
|
|
@@ -11445,13 +11564,15 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11445
11564
|
required_error: 'fee token is required',
|
|
11446
11565
|
invalid_type_error: 'fee token must be a string'
|
|
11447
11566
|
}).min(1, 'fee token must be a non-empty string'),
|
|
11448
|
-
amount: feeAmountSchema
|
|
11567
|
+
amount: feeAmountSchema,
|
|
11568
|
+
decimals: z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
|
|
11569
|
+
symbol: z.string({
|
|
11570
|
+
invalid_type_error: 'fee token symbol must be a string'
|
|
11571
|
+
}).min(1, 'fee token symbol must be a non-empty string').optional()
|
|
11449
11572
|
});
|
|
11450
11573
|
/**
|
|
11451
11574
|
* Developer fee item schema with basis field.
|
|
11452
|
-
*/ const createSwapDeveloperFeeItemSchema =
|
|
11453
|
-
token: z.string().min(1, 'fee token must be a non-empty string'),
|
|
11454
|
-
amount: feeAmountSchema,
|
|
11575
|
+
*/ const createSwapDeveloperFeeItemSchema = createSwapFeeItemSchema.extend({
|
|
11455
11576
|
basis: z.enum([
|
|
11456
11577
|
'inputAmount',
|
|
11457
11578
|
'estimatedAmount'
|
|
@@ -11543,7 +11664,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11543
11664
|
addresses: z.array(z.string({
|
|
11544
11665
|
invalid_type_error: 'addresses entries must be strings'
|
|
11545
11666
|
}).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(),
|
|
11546
|
-
apiKey: apiKeySchema
|
|
11667
|
+
apiKey: apiKeySchema.optional()
|
|
11547
11668
|
});
|
|
11548
11669
|
/**
|
|
11549
11670
|
* Zod schema for validating GetTokenRatesResponse payloads.
|
|
@@ -11574,6 +11695,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11574
11695
|
required_error: 'estimatedAmount is required',
|
|
11575
11696
|
invalid_type_error: 'estimatedAmount must be a string'
|
|
11576
11697
|
}).min(1, 'estimatedAmount must be a non-empty string'),
|
|
11698
|
+
// Per-swap join key echoed back verbatim on success telemetry. Optional so a
|
|
11699
|
+
// not-yet-upgraded service (no field) still validates during rollout. A
|
|
11700
|
+
// malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
|
|
11701
|
+
// than throwing: this is a telemetry-only field (stripped from the developer
|
|
11702
|
+
// result, never used for control flow), so it must not be able to abort the
|
|
11703
|
+
// swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
|
|
11704
|
+
// never-throw contract of the rest of the telemetry stack. Implemented with
|
|
11705
|
+
// `preprocess` rather than Zod's `.catch()` because static analysis misreads
|
|
11706
|
+
// `.catch` on the schema chain as an unhandled Promise (S7785).
|
|
11707
|
+
correlationId: z.preprocess((value)=>z.string().uuid().safeParse(value).success ? value : undefined, z.string().optional()),
|
|
11577
11708
|
config: createSwapRequestBaseSchema.shape.config.optional(),
|
|
11578
11709
|
fees: createSwapFeesSchema.optional(),
|
|
11579
11710
|
transaction: createSwapTransactionSchema
|
|
@@ -11660,6 +11791,45 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11660
11791
|
* }
|
|
11661
11792
|
* ```
|
|
11662
11793
|
*/ const isGetTokenRatesResponse = (obj)=>getTokenRatesResponseSchema.safeParse(obj).success;
|
|
11794
|
+
/**
|
|
11795
|
+
* Assert that a Stablecoin Service kit key is not being supplied from a browser.
|
|
11796
|
+
*
|
|
11797
|
+
* The kit key (`KIT_KEY:<id>:<secret>`) is a server-only secret. Every
|
|
11798
|
+
* Stablecoin Service request that attaches an `Authorization: Bearer` header
|
|
11799
|
+
* funnels through this package, so calling this guard before that header is
|
|
11800
|
+
* built prevents the secret from being sent from — and thus bundled into — a
|
|
11801
|
+
* client application. In Node.js the check is a no-op, preserving the
|
|
11802
|
+
* legitimate "hold the kit key on the server, forward the prepared transaction
|
|
11803
|
+
* to the client" flow. When no kit key is supplied the permissionless (keyless)
|
|
11804
|
+
* client path remains fully allowed.
|
|
11805
|
+
*
|
|
11806
|
+
* @param apiKey - The inline kit key for the request, or `undefined` when none
|
|
11807
|
+
* was supplied (permissionless mode).
|
|
11808
|
+
* @returns Nothing.
|
|
11809
|
+
* @throws KitError with VALIDATION_FAILED when a kit key is supplied while
|
|
11810
|
+
* running in a browser environment. The secret value is never echoed.
|
|
11811
|
+
*
|
|
11812
|
+
* @example
|
|
11813
|
+
* ```typescript
|
|
11814
|
+
* import { assertBrowserSafeApiKey } from '@core/service-client'
|
|
11815
|
+
*
|
|
11816
|
+
* // Server (Node.js): no-op, request proceeds with the Authorization header.
|
|
11817
|
+
* assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
|
|
11818
|
+
*
|
|
11819
|
+
* // Browser: throws to stop the secret from leaking into the client bundle.
|
|
11820
|
+
* assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
|
|
11821
|
+
*
|
|
11822
|
+
* // Browser, permissionless: allowed.
|
|
11823
|
+
* assertBrowserSafeApiKey(undefined)
|
|
11824
|
+
* ```
|
|
11825
|
+
*/ const assertBrowserSafeApiKey = (apiKey)=>{
|
|
11826
|
+
if (apiKey === undefined) {
|
|
11827
|
+
return;
|
|
11828
|
+
}
|
|
11829
|
+
if (isBrowserEnvironment()) {
|
|
11830
|
+
throw createValidationFailedError$1('kitKey', '[redacted]', 'kitKey must not be provided in a browser environment — it is a server-only secret. Run kit operations that use a kit key on your server, or omit kitKey to use the permissionless (keyless) client path');
|
|
11831
|
+
}
|
|
11832
|
+
};
|
|
11663
11833
|
|
|
11664
11834
|
/**
|
|
11665
11835
|
* Create a cross-chain bridge and swap transaction through the Stablecoin Service.
|
|
@@ -11717,11 +11887,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11717
11887
|
const url = new URL('/v1/stablecoinKits/swap', STABLECOIN_SERVICE_BASE_URL).toString();
|
|
11718
11888
|
// Remove the API key from the request body
|
|
11719
11889
|
const { apiKey, ...requestBody } = validatedParams;
|
|
11890
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
11891
|
+
assertBrowserSafeApiKey(apiKey);
|
|
11720
11892
|
const effectiveConfig = {
|
|
11721
11893
|
...DEFAULT_CONFIG,
|
|
11722
11894
|
headers: {
|
|
11723
11895
|
...DEFAULT_CONFIG.headers,
|
|
11724
|
-
|
|
11896
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
11897
|
+
...apiKey !== undefined && {
|
|
11898
|
+
Authorization: `Bearer ${apiKey}`
|
|
11899
|
+
}
|
|
11725
11900
|
}
|
|
11726
11901
|
};
|
|
11727
11902
|
try {
|
|
@@ -11868,6 +12043,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11868
12043
|
}
|
|
11869
12044
|
// Use validated data
|
|
11870
12045
|
const validatedParams = result.data;
|
|
12046
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
12047
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
11871
12048
|
// Build the API URL
|
|
11872
12049
|
const url = buildQuoteUrl(validatedParams);
|
|
11873
12050
|
// Merge default config with Authorization header
|
|
@@ -11875,7 +12052,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11875
12052
|
...DEFAULT_CONFIG,
|
|
11876
12053
|
headers: {
|
|
11877
12054
|
...DEFAULT_CONFIG.headers,
|
|
11878
|
-
|
|
12055
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
12056
|
+
...validatedParams.apiKey !== undefined && {
|
|
12057
|
+
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
12058
|
+
}
|
|
11879
12059
|
}
|
|
11880
12060
|
};
|
|
11881
12061
|
return pollApiGet(url, isGetQuoteResponse, effectiveConfig);
|
|
@@ -11930,17 +12110,24 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11930
12110
|
const validatedParams = {
|
|
11931
12111
|
txHash: result.data.txHash,
|
|
11932
12112
|
chain: result.data.chain,
|
|
11933
|
-
|
|
12113
|
+
...result.data.apiKey !== undefined && {
|
|
12114
|
+
apiKey: result.data.apiKey
|
|
12115
|
+
},
|
|
11934
12116
|
...result.data.toChain !== undefined && {
|
|
11935
12117
|
toChain: result.data.toChain
|
|
11936
12118
|
}
|
|
11937
12119
|
};
|
|
12120
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
12121
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
11938
12122
|
const url = buildSwapStatusUrl(validatedParams);
|
|
11939
12123
|
const effectiveConfig = {
|
|
11940
12124
|
...DEFAULT_CONFIG,
|
|
11941
12125
|
headers: {
|
|
11942
12126
|
...DEFAULT_CONFIG.headers,
|
|
11943
|
-
|
|
12127
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
12128
|
+
...validatedParams.apiKey !== undefined && {
|
|
12129
|
+
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
12130
|
+
}
|
|
11944
12131
|
}
|
|
11945
12132
|
};
|
|
11946
12133
|
return pollApiGet(url, isGetSwapStatusResponse, effectiveConfig);
|
|
@@ -12029,17 +12216,24 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
12029
12216
|
}
|
|
12030
12217
|
const validatedParams = {
|
|
12031
12218
|
chain: result.data.chain,
|
|
12032
|
-
|
|
12219
|
+
...result.data.apiKey !== undefined && {
|
|
12220
|
+
apiKey: result.data.apiKey
|
|
12221
|
+
},
|
|
12033
12222
|
...result.data.addresses !== undefined && {
|
|
12034
12223
|
addresses: result.data.addresses
|
|
12035
12224
|
}
|
|
12036
12225
|
};
|
|
12226
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
12227
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
12037
12228
|
const url = buildTokenRatesUrl(validatedParams);
|
|
12038
12229
|
const effectiveConfig = {
|
|
12039
12230
|
...DEFAULT_CONFIG,
|
|
12040
12231
|
headers: {
|
|
12041
12232
|
...DEFAULT_CONFIG.headers,
|
|
12042
|
-
|
|
12233
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
12234
|
+
...validatedParams.apiKey !== undefined && {
|
|
12235
|
+
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
12236
|
+
}
|
|
12043
12237
|
}
|
|
12044
12238
|
};
|
|
12045
12239
|
return pollApiGet(url, isGetTokenRatesResponse, effectiveConfig);
|
|
@@ -12849,6 +13043,47 @@ const S_HEX_LENGTH = 32 * HEX_CHARS_PER_BYTE$1 // 32 bytes for 's'
|
|
|
12849
13043
|
*/ function hasSignTypedData(adapter) {
|
|
12850
13044
|
return typeof adapter === 'object' && adapter !== null && 'signTypedData' in adapter && typeof adapter.signTypedData === 'function';
|
|
12851
13045
|
}
|
|
13046
|
+
/**
|
|
13047
|
+
* Type guard to check if an adapter can actually produce an EIP-712
|
|
13048
|
+
* typed-data signature.
|
|
13049
|
+
*
|
|
13050
|
+
* @remarks
|
|
13051
|
+
* Strengthens {@link hasSignTypedData}: having a `signTypedData` method
|
|
13052
|
+
* does not guarantee it can succeed. Adapters whose signer is delegated
|
|
13053
|
+
* (e.g. through a signing strategy backed by a smart contract account)
|
|
13054
|
+
* expose the method but reject typed-data payloads at runtime. Such
|
|
13055
|
+
* adapters report their real capability through an optional
|
|
13056
|
+
* `supportsSignTypedData()` method, which this guard consults when
|
|
13057
|
+
* present. Adapters without the capability method are assumed able to
|
|
13058
|
+
* sign, preserving the previous duck-typing behavior.
|
|
13059
|
+
*
|
|
13060
|
+
* @param adapter - The adapter to check
|
|
13061
|
+
* @returns True if calling `signTypedData` can be expected to succeed
|
|
13062
|
+
*
|
|
13063
|
+
* @example
|
|
13064
|
+
* ```typescript
|
|
13065
|
+
* import { canSignTypedData } from '@core/adapter-evm'
|
|
13066
|
+
*
|
|
13067
|
+
* if (canSignTypedData(adapter)) {
|
|
13068
|
+
* const signature = await adapter.signTypedData(typedData, context)
|
|
13069
|
+
* } else {
|
|
13070
|
+
* // take an on-chain approval path instead of a permit signature
|
|
13071
|
+
* }
|
|
13072
|
+
* ```
|
|
13073
|
+
*/ function canSignTypedData(adapter) {
|
|
13074
|
+
if (!hasSignTypedData(adapter)) {
|
|
13075
|
+
return false;
|
|
13076
|
+
}
|
|
13077
|
+
if (typeof adapter.supportsSignTypedData === 'function') {
|
|
13078
|
+
// The value is `boolean` per the interface, but a plain-JS adapter may
|
|
13079
|
+
// return anything; treat it as untrusted and coerce to a strict
|
|
13080
|
+
// boolean. Comparing an `unknown` (not a `boolean`) also keeps the
|
|
13081
|
+
// lint autofix from stripping this as a redundant `=== true`.
|
|
13082
|
+
const supported = adapter.supportsSignTypedData();
|
|
13083
|
+
return supported === true;
|
|
13084
|
+
}
|
|
13085
|
+
return true;
|
|
13086
|
+
}
|
|
12852
13087
|
|
|
12853
13088
|
/**
|
|
12854
13089
|
* Build EIP-2612 typed data for permit signing.
|
|
@@ -13271,10 +13506,13 @@ enc.encode('used_transfer_spec_hash');
|
|
|
13271
13506
|
* at usage time rather than construction time.
|
|
13272
13507
|
*
|
|
13273
13508
|
* Validates:
|
|
13274
|
-
* - Kit key
|
|
13509
|
+
* - Kit key matches the required format (KIT_KEY:id:secret) when provided.
|
|
13510
|
+
* An absent or empty kit key is permitted (permissionless mode) — the swap
|
|
13511
|
+
* service now treats the key as optional.
|
|
13275
13512
|
*
|
|
13276
|
-
* @param kitKey - The inline kit key from the swap operation config
|
|
13277
|
-
* @throws KitError with VALIDATION_FAILED if kit key is
|
|
13513
|
+
* @param kitKey - The inline kit key from the swap operation config (optional)
|
|
13514
|
+
* @throws KitError with VALIDATION_FAILED if a kit key is provided but does not
|
|
13515
|
+
* match the KIT_KEY:<keyId>:<keySecret> format
|
|
13278
13516
|
*
|
|
13279
13517
|
* @example
|
|
13280
13518
|
* ```typescript
|
|
@@ -13286,9 +13524,11 @@ enc.encode('used_transfer_spec_hash');
|
|
|
13286
13524
|
* assertKitKey(kitKey)
|
|
13287
13525
|
* ```
|
|
13288
13526
|
*/ function assertKitKey(kitKey) {
|
|
13289
|
-
//
|
|
13527
|
+
// Permissionless mode: the swap service treats the kit key as optional, so an
|
|
13528
|
+
// absent (or empty) key is valid. Only validate the format when a key is
|
|
13529
|
+
// actually provided.
|
|
13290
13530
|
if (!kitKey) {
|
|
13291
|
-
|
|
13531
|
+
return;
|
|
13292
13532
|
}
|
|
13293
13533
|
const apiKeyResult = apiKeySchema.safeParse(kitKey);
|
|
13294
13534
|
if (!apiKeyResult.success) {
|
|
@@ -13585,8 +13825,8 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
13585
13825
|
validateResolvedAddress(resolvedTokenInAddress, chain);
|
|
13586
13826
|
validateResolvedAddress(resolvedTokenOutAddress, destinationChain);
|
|
13587
13827
|
validateResolvedAddress(to, destinationChain);
|
|
13588
|
-
const kitKey = config?.kitKey
|
|
13589
|
-
//
|
|
13828
|
+
const kitKey = config?.kitKey;
|
|
13829
|
+
// Validate the kit key format when one is provided (permissionless otherwise).
|
|
13590
13830
|
assertKitKey(kitKey);
|
|
13591
13831
|
// Validate custom fee configuration if present
|
|
13592
13832
|
const customFee = config?.customFee;
|
|
@@ -13631,7 +13871,10 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
13631
13871
|
}
|
|
13632
13872
|
}
|
|
13633
13873
|
},
|
|
13634
|
-
apiKey
|
|
13874
|
+
// Map kitKey → apiKey for the service client; omitted in permissionless mode.
|
|
13875
|
+
...kitKey ? {
|
|
13876
|
+
apiKey: kitKey
|
|
13877
|
+
} : {}
|
|
13635
13878
|
};
|
|
13636
13879
|
}
|
|
13637
13880
|
|
|
@@ -14285,6 +14528,37 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
14285
14528
|
}
|
|
14286
14529
|
}
|
|
14287
14530
|
|
|
14531
|
+
/**
|
|
14532
|
+
* Determine whether an adapter can produce an EIP-2612 permit signature.
|
|
14533
|
+
*
|
|
14534
|
+
* @remarks
|
|
14535
|
+
* A gasless permit needs two adapter capabilities: fetching the token's
|
|
14536
|
+
* EIP-2612 nonce and producing an EIP-712 typed-data signature. The
|
|
14537
|
+
* typed-data check uses {@link canSignTypedData} rather than a bare
|
|
14538
|
+
* `hasSignTypedData` guard so that an adapter routed through a signing
|
|
14539
|
+
* strategy that cannot produce typed-data signatures — one whose manifest
|
|
14540
|
+
* omits `evm-typed-data`, surfaced through an optional `supportsSignTypedData()`
|
|
14541
|
+
* — is correctly excluded. Such an adapter falls back to an on-chain approval
|
|
14542
|
+
* (batched into a single submission when it supports atomic execution) instead
|
|
14543
|
+
* of attempting a permit its strategy would reject.
|
|
14544
|
+
*
|
|
14545
|
+
* @param adapter - The source adapter to inspect.
|
|
14546
|
+
* @returns `true` when the adapter can both fetch a nonce and sign typed data.
|
|
14547
|
+
*
|
|
14548
|
+
* @example
|
|
14549
|
+
* ```typescript
|
|
14550
|
+
* import { adapterSupportsPermit } from './utils'
|
|
14551
|
+
*
|
|
14552
|
+
* if (adapterSupportsPermit(adapter)) {
|
|
14553
|
+
* // gasless permit path — fold the approval into the swap transaction
|
|
14554
|
+
* } else {
|
|
14555
|
+
* // on-chain approval path (batched when supportsAtomicBatch is true)
|
|
14556
|
+
* }
|
|
14557
|
+
* ```
|
|
14558
|
+
*/ function adapterSupportsPermit(adapter) {
|
|
14559
|
+
return hasEIP2612NonceFetching(adapter) && canSignTypedData(adapter);
|
|
14560
|
+
}
|
|
14561
|
+
|
|
14288
14562
|
/**
|
|
14289
14563
|
* Generate EIP-2612 permit signature for token approval.
|
|
14290
14564
|
*
|
|
@@ -14420,8 +14694,7 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
14420
14694
|
}
|
|
14421
14695
|
// Skip permit generation if the adapter lacks the required capabilities.
|
|
14422
14696
|
// handleEvmTokenApproval will have already sent an on-chain approval in this case.
|
|
14423
|
-
|
|
14424
|
-
if (!adapterSupportsPermit) {
|
|
14697
|
+
if (!adapterSupportsPermit(adapter)) {
|
|
14425
14698
|
return [
|
|
14426
14699
|
createFallbackTokenInput(tokenInAddress, inputAmount)
|
|
14427
14700
|
];
|
|
@@ -14980,6 +15253,65 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
|
|
|
14980
15253
|
return `Insufficient ${displaySymbol} balance for swap operation.\n\n` + `Wallet: ${walletAddress}\n` + `Current balance: ${currentDisplay}\n` + `Required: ${requiredDisplay}\n` + `Shortfall: ${shortfallDisplay}\n\n` + `This swap requires ${requiredSummary} to complete the transaction.\n\n` + `Action: Add at least ${actionAmount} to your wallet to complete this swap.`;
|
|
14981
15254
|
}
|
|
14982
15255
|
|
|
15256
|
+
/**
|
|
15257
|
+
* Determine which chain a fee token should be resolved and formatted against.
|
|
15258
|
+
*
|
|
15259
|
+
* @remarks
|
|
15260
|
+
* Fees returned by the service may be denominated in either the input token
|
|
15261
|
+
* (on the source chain) or the output token (on the destination chain). A
|
|
15262
|
+
* contract address only resolves on the chain it belongs to, so formatting a
|
|
15263
|
+
* destination-denominated fee against the source chain causes
|
|
15264
|
+
* {@link resolveTokenSymbol} to miss and the amount to be returned as raw base
|
|
15265
|
+
* units (e.g. a cross-chain swap charging a fee in the destination output
|
|
15266
|
+
* token — an EURC-on-Base address shows `'13202'` instead of `'0.013202'` when
|
|
15267
|
+
* resolved against the source chain). This is the fallback for fee items that
|
|
15268
|
+
* are not self-described with their own `decimals`/`chain`.
|
|
15269
|
+
*
|
|
15270
|
+
* Prefer the source chain (covers same-chain swaps and input-denominated
|
|
15271
|
+
* fees), then fall back to the destination chain when the token only resolves
|
|
15272
|
+
* there. When neither chain recognises the token, default to the source chain
|
|
15273
|
+
* so existing on-chain decimal lookups via the source adapter still apply.
|
|
15274
|
+
*
|
|
15275
|
+
* Symbol tokens (`'USDC'`, `'NATIVE'`) resolve on either chain, so the
|
|
15276
|
+
* source-first preference keeps them on the source chain. That is correct for
|
|
15277
|
+
* registry stablecoins, and for `'NATIVE'` only when both chains share native
|
|
15278
|
+
* decimals (EVM↔EVM, 18). It does NOT honor per-chain native decimals: a
|
|
15279
|
+
* `'NATIVE'`-denominated fee on a Solana↔EVM swap (9 vs 18) would be
|
|
15280
|
+
* mis-scaled. This is latent — providers emit the address form, and
|
|
15281
|
+
* self-describing fee items carry their own `decimals` and never reach this
|
|
15282
|
+
* helper — so the gap only opens for a future `'NATIVE'` fee that arrives
|
|
15283
|
+
* without `decimals` on a cross-native-decimal route.
|
|
15284
|
+
*
|
|
15285
|
+
* @param token - The fee token identifier — a symbol (`'USDC'`) or contract address.
|
|
15286
|
+
* @param sourceChain - The chain the swap originates from.
|
|
15287
|
+
* @param destinationChain - The chain the swap settles on (equals `sourceChain` for same-chain swaps).
|
|
15288
|
+
* @returns The chain definition the fee token should be resolved against.
|
|
15289
|
+
*
|
|
15290
|
+
* @example
|
|
15291
|
+
* ```typescript
|
|
15292
|
+
* import { resolveFeeChain } from './resolveFeeChain'
|
|
15293
|
+
* import { Ethereum, Base } from '@core/chains'
|
|
15294
|
+
*
|
|
15295
|
+
* // Cross-chain swap fee charged in the destination (output) token
|
|
15296
|
+
* resolveFeeChain('0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42', Ethereum, Base)
|
|
15297
|
+
* // => Base (EURC resolves on Base, not Ethereum)
|
|
15298
|
+
*
|
|
15299
|
+
* // Symbol or source-token fees stay on the source chain
|
|
15300
|
+
* resolveFeeChain('USDC', Ethereum, Base) // => Ethereum
|
|
15301
|
+
* ```
|
|
15302
|
+
*/ function resolveFeeChain(token, sourceChain, destinationChain) {
|
|
15303
|
+
if (sourceChain.chain === destinationChain.chain) {
|
|
15304
|
+
return sourceChain;
|
|
15305
|
+
}
|
|
15306
|
+
if (resolveTokenSymbol(token, sourceChain) !== null) {
|
|
15307
|
+
return sourceChain;
|
|
15308
|
+
}
|
|
15309
|
+
if (resolveTokenSymbol(token, destinationChain) !== null) {
|
|
15310
|
+
return destinationChain;
|
|
15311
|
+
}
|
|
15312
|
+
return sourceChain;
|
|
15313
|
+
}
|
|
15314
|
+
|
|
14983
15315
|
const TOKEN_REGISTRY$1 = createTokenRegistry();
|
|
14984
15316
|
/**
|
|
14985
15317
|
* Format a raw base-unit amount into a human-readable decimal string.
|
|
@@ -15070,6 +15402,186 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
|
|
|
15070
15402
|
}
|
|
15071
15403
|
}
|
|
15072
15404
|
|
|
15405
|
+
/**
|
|
15406
|
+
* Runtime guard for {@link BatchCapableSwapAdapter}.
|
|
15407
|
+
*
|
|
15408
|
+
* @param adapter - The adapter to inspect.
|
|
15409
|
+
* @returns `true` when the adapter exposes both batch methods.
|
|
15410
|
+
*
|
|
15411
|
+
* @example
|
|
15412
|
+
* ```typescript
|
|
15413
|
+
* if (isBatchCapableSwapAdapter(adapter)) {
|
|
15414
|
+
* // adapter.supportsAtomicBatch / adapter.batchExecute are available
|
|
15415
|
+
* }
|
|
15416
|
+
* ```
|
|
15417
|
+
*/ function isBatchCapableSwapAdapter(adapter) {
|
|
15418
|
+
return typeof adapter === 'object' && adapter !== null && typeof adapter.supportsAtomicBatch === 'function' && typeof adapter.batchExecute === 'function';
|
|
15419
|
+
}
|
|
15420
|
+
/**
|
|
15421
|
+
* Decide whether the EVM swap should take the batched approve-and-swap path.
|
|
15422
|
+
*
|
|
15423
|
+
* @remarks
|
|
15424
|
+
* Batching only helps when an on-chain approval would otherwise be required, so
|
|
15425
|
+
* it is skipped for native tokens (no approval) and for the gasless permit path
|
|
15426
|
+
* (already a single transaction). USDT is skipped because its reset-to-zero
|
|
15427
|
+
* allowance flow cannot be expressed as a fixed approve+swap pair. When those
|
|
15428
|
+
* gates pass, the adapter's actual atomic-batch capability is queried; any
|
|
15429
|
+
* failure resolves to `false` so the swap falls back to the sequential path.
|
|
15430
|
+
*
|
|
15431
|
+
* @param args - The decision inputs.
|
|
15432
|
+
* @param args.adapter - The source adapter.
|
|
15433
|
+
* @param args.chain - The source chain definition.
|
|
15434
|
+
* @param args.tokenInAddress - The resolved input-token address.
|
|
15435
|
+
* @param args.allowanceStrategy - Optional allowance strategy override.
|
|
15436
|
+
* @param args.batchTransactions - Optional explicit opt-out (`false` disables).
|
|
15437
|
+
* @returns `true` when the batched approve-and-swap path should be used.
|
|
15438
|
+
*
|
|
15439
|
+
* @example
|
|
15440
|
+
* ```typescript
|
|
15441
|
+
* const useBatched = await shouldUseBatchedSwap({
|
|
15442
|
+
* adapter,
|
|
15443
|
+
* chain,
|
|
15444
|
+
* tokenInAddress: '0xA0b8...',
|
|
15445
|
+
* allowanceStrategy: config?.allowanceStrategy,
|
|
15446
|
+
* batchTransactions: config?.batchTransactions,
|
|
15447
|
+
* })
|
|
15448
|
+
* ```
|
|
15449
|
+
*/ async function shouldUseBatchedSwap({ adapter, chain, tokenInAddress, allowanceStrategy, batchTransactions }) {
|
|
15450
|
+
// Explicit opt-out.
|
|
15451
|
+
if (batchTransactions === false) {
|
|
15452
|
+
return false;
|
|
15453
|
+
}
|
|
15454
|
+
// Batching is an EVM capability (EIP-5792 or a signing strategy).
|
|
15455
|
+
if (chain.type !== 'evm') {
|
|
15456
|
+
return false;
|
|
15457
|
+
}
|
|
15458
|
+
// Native tokens need no approval — the swap is already a single transaction.
|
|
15459
|
+
if (isNativeEvmAddress(tokenInAddress)) {
|
|
15460
|
+
return false;
|
|
15461
|
+
}
|
|
15462
|
+
// A gasless permit folds the approval into the swap transaction, so there is
|
|
15463
|
+
// nothing to batch. Mirrors the permit gate in handleEvmTokenApproval.
|
|
15464
|
+
const canUsePermit = allowanceStrategy !== 'approve' && supportsEIP2612(tokenInAddress, chain) && adapterSupportsPermit(adapter);
|
|
15465
|
+
if (canUsePermit) {
|
|
15466
|
+
return false;
|
|
15467
|
+
}
|
|
15468
|
+
// USDT's reset-to-zero allowance dance cannot be expressed as a fixed
|
|
15469
|
+
// approve+swap pair; leave it on the sequential path.
|
|
15470
|
+
const usdt = chain.usdtAddress?.toLowerCase();
|
|
15471
|
+
if (usdt !== undefined && tokenInAddress.toLowerCase() === usdt) {
|
|
15472
|
+
return false;
|
|
15473
|
+
}
|
|
15474
|
+
if (!isBatchCapableSwapAdapter(adapter)) {
|
|
15475
|
+
return false;
|
|
15476
|
+
}
|
|
15477
|
+
try {
|
|
15478
|
+
return await adapter.supportsAtomicBatch(chain);
|
|
15479
|
+
} catch {
|
|
15480
|
+
return false;
|
|
15481
|
+
}
|
|
15482
|
+
}
|
|
15483
|
+
/**
|
|
15484
|
+
* Execute the approval and swap as a single atomic batch.
|
|
15485
|
+
*
|
|
15486
|
+
* @remarks
|
|
15487
|
+
* Extracts the raw call data from both prepared requests, submits them as one
|
|
15488
|
+
* batch via `adapter.batchExecute`, and maps the swap receipt back to a
|
|
15489
|
+
* transaction hash. The `fromAddress` is threaded for adapters routed through a
|
|
15490
|
+
* signing strategy (which have no wallet account to read the sender from); the
|
|
15491
|
+
* wallet-client path ignores it.
|
|
15492
|
+
*
|
|
15493
|
+
* Following the batch contract, `batchExecute` never throws once the batch is
|
|
15494
|
+
* submitted — a missing or failed swap receipt is surfaced here as a thrown
|
|
15495
|
+
* {@link KitError} (FATAL) so the caller does not resubmit an already-broadcast
|
|
15496
|
+
* batch and double-swap.
|
|
15497
|
+
*
|
|
15498
|
+
* @param args - The execution inputs.
|
|
15499
|
+
* @param args.adapter - The batch-capable source adapter.
|
|
15500
|
+
* @param args.chain - The EVM chain to execute on.
|
|
15501
|
+
* @param args.approveRequest - The prepared ERC-20 approval request.
|
|
15502
|
+
* @param args.swapRequest - The prepared swap request (pre-approval / NONE permit).
|
|
15503
|
+
* @param args.fromAddress - The address authorizing the batch.
|
|
15504
|
+
* @returns The swap transaction hash and the executed approval + swap records.
|
|
15505
|
+
* @throws {@link KitError} when the prepared requests cannot yield call data.
|
|
15506
|
+
* @throws {@link KitError} when the batch does not confirm or the swap reverts.
|
|
15507
|
+
*
|
|
15508
|
+
* @example
|
|
15509
|
+
* ```typescript
|
|
15510
|
+
* const { swapTxHash, executedTransactions } = await executeBatchedApproveAndSwap({
|
|
15511
|
+
* adapter,
|
|
15512
|
+
* chain,
|
|
15513
|
+
* approveRequest,
|
|
15514
|
+
* swapRequest,
|
|
15515
|
+
* fromAddress: '0x742d...',
|
|
15516
|
+
* })
|
|
15517
|
+
* ```
|
|
15518
|
+
*/ async function executeBatchedApproveAndSwap({ adapter, chain, approveRequest, swapRequest, fromAddress }) {
|
|
15519
|
+
if (approveRequest.type !== 'evm' || swapRequest.type !== 'evm' || !approveRequest.getCallData || !swapRequest.getCallData) {
|
|
15520
|
+
throw new KitError({
|
|
15521
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
15522
|
+
recoverability: 'FATAL',
|
|
15523
|
+
message: 'Batched swap requires EVM prepared requests with getCallData() support.'
|
|
15524
|
+
});
|
|
15525
|
+
}
|
|
15526
|
+
const approveCallData = approveRequest.getCallData();
|
|
15527
|
+
const swapCallData = swapRequest.getCallData();
|
|
15528
|
+
const batchResult = await adapter.batchExecute([
|
|
15529
|
+
approveCallData,
|
|
15530
|
+
swapCallData
|
|
15531
|
+
], chain, {
|
|
15532
|
+
fromAddress
|
|
15533
|
+
});
|
|
15534
|
+
const swapReceipt = batchResult.receipts[1];
|
|
15535
|
+
// A missing swap receipt means the batch never confirmed (polling timed out
|
|
15536
|
+
// or the wallet returned fewer receipts than calls). Re-throw the underlying
|
|
15537
|
+
// error when present (already FATAL); otherwise surface a FATAL timeout so the
|
|
15538
|
+
// caller checks the batch status rather than resubmitting.
|
|
15539
|
+
if (swapReceipt === undefined || swapReceipt.txHash === '') {
|
|
15540
|
+
if (isKitError(batchResult.error)) {
|
|
15541
|
+
throw batchResult.error;
|
|
15542
|
+
}
|
|
15543
|
+
throw new KitError({
|
|
15544
|
+
...NetworkError.TIMEOUT,
|
|
15545
|
+
recoverability: 'FATAL',
|
|
15546
|
+
message: `Batched swap did not confirm on-chain (batchId: ${batchResult.batchId}). ` + 'The batch was already submitted — check its status before retrying.',
|
|
15547
|
+
// Preserve the underlying confirmation failure when it isn't a KitError —
|
|
15548
|
+
// the signing-strategy path returns a raw viem error (e.g. a dropped or
|
|
15549
|
+
// replaced tx) — so the root cause survives behind the generic timeout.
|
|
15550
|
+
cause: {
|
|
15551
|
+
trace: {
|
|
15552
|
+
batchId: batchResult.batchId,
|
|
15553
|
+
...batchResult.error != null && {
|
|
15554
|
+
error: batchResult.error
|
|
15555
|
+
}
|
|
15556
|
+
}
|
|
15557
|
+
}
|
|
15558
|
+
});
|
|
15559
|
+
}
|
|
15560
|
+
if (swapReceipt.status !== 'success') {
|
|
15561
|
+
throw createTransactionRevertedError(chain.name, 'Batched swap transaction reverted on-chain', undefined, swapReceipt.txHash, buildExplorerUrl(chain, swapReceipt.txHash));
|
|
15562
|
+
}
|
|
15563
|
+
const executedTransactions = [];
|
|
15564
|
+
const approveReceipt = batchResult.receipts[0];
|
|
15565
|
+
// An atomic batch is a single on-chain transaction, so the approve and swap
|
|
15566
|
+
// receipts share one hash. Only surface a distinct approval record when it is
|
|
15567
|
+
// genuinely a separate transaction; otherwise the lone swap record represents
|
|
15568
|
+
// the batch, avoiding a phantom duplicate tx in executedTransactions.
|
|
15569
|
+
if (approveReceipt !== undefined && approveReceipt.txHash !== '' && approveReceipt.txHash !== swapReceipt.txHash) {
|
|
15570
|
+
executedTransactions.push({
|
|
15571
|
+
type: 'approval',
|
|
15572
|
+
txHash: approveReceipt.txHash
|
|
15573
|
+
});
|
|
15574
|
+
}
|
|
15575
|
+
executedTransactions.push({
|
|
15576
|
+
type: 'swap',
|
|
15577
|
+
txHash: swapReceipt.txHash
|
|
15578
|
+
});
|
|
15579
|
+
return {
|
|
15580
|
+
swapTxHash: swapReceipt.txHash,
|
|
15581
|
+
executedTransactions
|
|
15582
|
+
};
|
|
15583
|
+
}
|
|
15584
|
+
|
|
15073
15585
|
/**
|
|
15074
15586
|
* Safety multiplier applied to locally estimated gas for EVM swap execution.
|
|
15075
15587
|
* Derived from refund cap (max 1/5 of total gas used) plus an extra 0.1 margin,
|
|
@@ -15196,7 +15708,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
15196
15708
|
const statusResult = await getSwapStatus$1({
|
|
15197
15709
|
txHash,
|
|
15198
15710
|
chain: chain.chain,
|
|
15199
|
-
apiKey
|
|
15711
|
+
...apiKey !== undefined && {
|
|
15712
|
+
apiKey
|
|
15713
|
+
}
|
|
15200
15714
|
});
|
|
15201
15715
|
if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
|
|
15202
15716
|
return {
|
|
@@ -15787,8 +16301,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
15787
16301
|
// Note: tokenInAddress from executionCtx is already resolved (handles NATIVE alias, ETH, etc.)
|
|
15788
16302
|
const isNativeToken = isNativeEvmAddress(executionCtx.tokenInAddress);
|
|
15789
16303
|
const tokenSupportsPermit = supportsEIP2612(executionCtx.tokenInAddress, chain);
|
|
15790
|
-
const
|
|
15791
|
-
const canUsePermitFlow = tokenSupportsPermit && adapterSupportsPermit && allowanceStrategy !== 'approve';
|
|
16304
|
+
const canUsePermitFlow = tokenSupportsPermit && adapterSupportsPermit(adapter) && allowanceStrategy !== 'approve';
|
|
15792
16305
|
const needsApproval = !isNativeToken && !canUsePermitFlow;
|
|
15793
16306
|
if (!needsApproval) {
|
|
15794
16307
|
return;
|
|
@@ -16067,34 +16580,55 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16067
16580
|
const serviceResponse = await createSwap(serviceParams);
|
|
16068
16581
|
// Track executed transactions
|
|
16069
16582
|
const executedTransactions = [];
|
|
16070
|
-
// Prepare swap action based on chain type
|
|
16071
|
-
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
}
|
|
16583
|
+
// Prepare swap action(s) based on chain type and batch capability.
|
|
16584
|
+
// Returns either a single prepared action (Solana / sequential EVM) or a
|
|
16585
|
+
// batched approve+swap plan (EVM atomic-batch path).
|
|
16586
|
+
const { preparedAction, batchedSwapPlan } = await this.prepareSwapRequests({
|
|
16587
|
+
adapter,
|
|
16588
|
+
chain,
|
|
16589
|
+
serviceResponse,
|
|
16590
|
+
resolvedContext,
|
|
16591
|
+
executionCtx,
|
|
16592
|
+
config,
|
|
16593
|
+
executedTransactions
|
|
16594
|
+
});
|
|
16083
16595
|
// Execute swap transaction via adapter
|
|
16084
16596
|
// For EVM chains, use gas limit from proxy service API
|
|
16085
16597
|
let txHash;
|
|
16086
16598
|
const evmGasLimit = 'gasLimit' in serviceResponse.transaction ? serviceResponse.transaction.gasLimit : undefined;
|
|
16087
16599
|
try {
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16600
|
+
if (batchedSwapPlan) {
|
|
16601
|
+
// Approve + swap submitted as one atomic batch. batchExecute confirms
|
|
16602
|
+
// the swap internally, so no separate waitForTransaction is needed.
|
|
16603
|
+
const batched = await executeBatchedApproveAndSwap({
|
|
16604
|
+
adapter: adapter,
|
|
16605
|
+
chain: chain,
|
|
16606
|
+
approveRequest: batchedSwapPlan.approveRequest,
|
|
16607
|
+
swapRequest: batchedSwapPlan.swapRequest,
|
|
16608
|
+
fromAddress: executionCtx.fromAddress
|
|
16609
|
+
});
|
|
16610
|
+
txHash = batched.swapTxHash;
|
|
16611
|
+
executedTransactions.push(...batched.executedTransactions);
|
|
16612
|
+
} else if (preparedAction) {
|
|
16613
|
+
txHash = await this.executeSwapTransaction(preparedAction, evmGasLimit);
|
|
16614
|
+
executedTransactions.push({
|
|
16615
|
+
type: 'swap',
|
|
16616
|
+
txHash
|
|
16617
|
+
});
|
|
16618
|
+
// Wait for transaction confirmation and verify success
|
|
16619
|
+
const txReceipt = await adapter.waitForTransaction(txHash, undefined, chain);
|
|
16620
|
+
if (txReceipt.status === 'reverted') {
|
|
16621
|
+
const explorerUrl = buildExplorerUrl(chain, txHash);
|
|
16622
|
+
throw createTransactionRevertedError(chain.name, 'Swap transaction reverted on-chain', undefined, txHash, explorerUrl);
|
|
16623
|
+
}
|
|
16624
|
+
} else {
|
|
16625
|
+
// Unreachable: the preparation step always yields either a batched plan
|
|
16626
|
+
// or a prepared action.
|
|
16627
|
+
throw new KitError({
|
|
16628
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
16629
|
+
recoverability: 'FATAL',
|
|
16630
|
+
message: 'No swap execution path was prepared.'
|
|
16631
|
+
});
|
|
16098
16632
|
}
|
|
16099
16633
|
} catch (err) {
|
|
16100
16634
|
handleSwapExecutionError(err, txHash, chain);
|
|
@@ -16113,8 +16647,15 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16113
16647
|
isCrossChainSwap,
|
|
16114
16648
|
txHash,
|
|
16115
16649
|
chain,
|
|
16116
|
-
|
|
16650
|
+
...serviceParams.apiKey !== undefined && {
|
|
16651
|
+
apiKey: serviceParams.apiKey
|
|
16652
|
+
}
|
|
16117
16653
|
});
|
|
16654
|
+
// Per-swap correlation id returned by the service as a top-level response
|
|
16655
|
+
// field for every chain (EVM + Solana). Attached to success telemetry so a
|
|
16656
|
+
// swap can be correlated across records; never used for control flow.
|
|
16657
|
+
// Undefined only against a not-yet-upgraded service that omits it.
|
|
16658
|
+
const correlationId = serviceResponse.correlationId;
|
|
16118
16659
|
// Build and return SwapResult
|
|
16119
16660
|
return {
|
|
16120
16661
|
tokenIn,
|
|
@@ -16124,6 +16665,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16124
16665
|
fromAddress: serviceParams.fromAddress,
|
|
16125
16666
|
toAddress: serviceParams.toAddress,
|
|
16126
16667
|
txHash,
|
|
16668
|
+
...correlationId !== undefined && {
|
|
16669
|
+
correlationId
|
|
16670
|
+
},
|
|
16127
16671
|
executedTransactions,
|
|
16128
16672
|
...config !== undefined && {
|
|
16129
16673
|
config
|
|
@@ -16138,6 +16682,79 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16138
16682
|
};
|
|
16139
16683
|
}
|
|
16140
16684
|
/**
|
|
16685
|
+
* Prepare the swap execution request(s) for the source wallet's chain.
|
|
16686
|
+
*
|
|
16687
|
+
* Produces either a single {@link PreparedChainRequest} (Solana, or the
|
|
16688
|
+
* sequential EVM approve-then-swap path) or a `batchedSwapPlan` (the EVM
|
|
16689
|
+
* atomic approve+swap path chosen when the adapter supports EIP-5792 atomic
|
|
16690
|
+
* batching). The caller executes whichever field is populated. Any on-chain
|
|
16691
|
+
* approval sent on the sequential path is appended to `executedTransactions`.
|
|
16692
|
+
*
|
|
16693
|
+
* @typeParam TFromAdapterCapabilities - Source-adapter capability set.
|
|
16694
|
+
* @param args - Inputs derived from the validated swap request.
|
|
16695
|
+
* @param args.adapter - Source-chain wallet adapter.
|
|
16696
|
+
* @param args.chain - Source chain definition.
|
|
16697
|
+
* @param args.serviceResponse - Validated createSwap response.
|
|
16698
|
+
* @param args.resolvedContext - Resolved operation context.
|
|
16699
|
+
* @param args.executionCtx - Minimal on-chain execution context.
|
|
16700
|
+
* @param args.config - Optional swap configuration (allowance/batch flags).
|
|
16701
|
+
* @param args.executedTransactions - Array appended with any sent approval.
|
|
16702
|
+
* @returns The prepared action or the batched approve+swap plan.
|
|
16703
|
+
* @throws KitError when the EVM atomic-batch path is selected but the chain
|
|
16704
|
+
* has no configured adapter contract.
|
|
16705
|
+
*/ async prepareSwapRequests(args) {
|
|
16706
|
+
const { adapter, chain, serviceResponse, resolvedContext, executionCtx, config, executedTransactions } = args;
|
|
16707
|
+
if (chain.type === 'solana') {
|
|
16708
|
+
// Solana: No approval needed, directly prepare swap action
|
|
16709
|
+
return {
|
|
16710
|
+
preparedAction: await prepareSolanaSwapAction(adapter, serviceResponse, resolvedContext)
|
|
16711
|
+
};
|
|
16712
|
+
}
|
|
16713
|
+
const useBatch = await shouldUseBatchedSwap({
|
|
16714
|
+
adapter,
|
|
16715
|
+
chain,
|
|
16716
|
+
tokenInAddress: executionCtx.tokenInAddress,
|
|
16717
|
+
allowanceStrategy: config?.allowanceStrategy,
|
|
16718
|
+
batchTransactions: config?.batchTransactions
|
|
16719
|
+
});
|
|
16720
|
+
if (useBatch) {
|
|
16721
|
+
// EVM chains: fuse the ERC-20 approval and the swap into a single atomic
|
|
16722
|
+
// batch (one signing challenge for smart-contract wallets). Force the
|
|
16723
|
+
// swap onto the pre-approval (PermitType.NONE) path since the approval
|
|
16724
|
+
// rides in the same batch.
|
|
16725
|
+
const adapterContractAddress = chain.kitContracts?.adapter;
|
|
16726
|
+
if (!adapterContractAddress) {
|
|
16727
|
+
throw new KitError({
|
|
16728
|
+
...InputError.VALIDATION_FAILED,
|
|
16729
|
+
recoverability: 'FATAL',
|
|
16730
|
+
message: `Adapter contract not configured for chain ${chain.name}. Swap operations require an adapter contract.`,
|
|
16731
|
+
cause: {
|
|
16732
|
+
trace: {
|
|
16733
|
+
chain: chain.name
|
|
16734
|
+
}
|
|
16735
|
+
}
|
|
16736
|
+
});
|
|
16737
|
+
}
|
|
16738
|
+
const [approveRequest, swapRequest] = await Promise.all([
|
|
16739
|
+
this.approve(adapter, executionCtx.amount, executionCtx.tokenInAddress, adapterContractAddress, resolvedContext),
|
|
16740
|
+
prepareEvmSwapAction(adapter, serviceResponse, resolvedContext, 'approve')
|
|
16741
|
+
]);
|
|
16742
|
+
return {
|
|
16743
|
+
batchedSwapPlan: {
|
|
16744
|
+
approveRequest,
|
|
16745
|
+
swapRequest
|
|
16746
|
+
}
|
|
16747
|
+
};
|
|
16748
|
+
}
|
|
16749
|
+
// EVM chains: Handle token approval if needed, then prepare the swap.
|
|
16750
|
+
// prepareEvmSwapAction handles EIP-2612 permit generation; the adapter
|
|
16751
|
+
// contract address is read from chain.kitContracts.adapter.
|
|
16752
|
+
await this.handleEvmTokenApproval(adapter, chain, executionCtx, resolvedContext, executedTransactions, config?.allowanceStrategy);
|
|
16753
|
+
return {
|
|
16754
|
+
preparedAction: await prepareEvmSwapAction(adapter, serviceResponse, resolvedContext, config?.allowanceStrategy)
|
|
16755
|
+
};
|
|
16756
|
+
}
|
|
16757
|
+
/**
|
|
16141
16758
|
* Executes a swap transaction with the appropriate gas limit for the chain type.
|
|
16142
16759
|
*
|
|
16143
16760
|
* For EVM chains, performs a local eth_estimateGas call, applies a 1.3x safety
|
|
@@ -16186,8 +16803,8 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16186
16803
|
*/ async buildFormattedFees(fees, chain, destinationChain, adapter, recipientAddress) {
|
|
16187
16804
|
if (!fees) return [];
|
|
16188
16805
|
const [providerFees, swapFees, developerFees] = await Promise.all([
|
|
16189
|
-
this.formatServiceFees(fees.provider, chain, 'provider', adapter),
|
|
16190
|
-
this.formatServiceFees(fees.swap, chain, 'swap', adapter),
|
|
16806
|
+
this.formatServiceFees(fees.provider, chain, destinationChain, 'provider', adapter),
|
|
16807
|
+
this.formatServiceFees(fees.swap, chain, destinationChain, 'swap', adapter),
|
|
16191
16808
|
recipientAddress ? this.formatDeveloperFees(fees.developer, chain, destinationChain, recipientAddress, adapter) : Promise.resolve([])
|
|
16192
16809
|
]);
|
|
16193
16810
|
return [
|
|
@@ -16197,6 +16814,45 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16197
16814
|
];
|
|
16198
16815
|
}
|
|
16199
16816
|
/**
|
|
16817
|
+
* Resolve a single fee item to its display token and human-readable amount.
|
|
16818
|
+
*
|
|
16819
|
+
* @remarks
|
|
16820
|
+
* Prefer the self-describing metadata the service attaches to each fee:
|
|
16821
|
+
* `decimals` (and `symbol`) come straight from the provider quote, so they
|
|
16822
|
+
* are authoritative even for a token absent from the SDK registry on both
|
|
16823
|
+
* chains. That is the case {@link resolveFeeChain} cannot recover — a
|
|
16824
|
+
* destination-denominated fee token resolves on neither the source registry
|
|
16825
|
+
* nor the source-bound adapter, leaving the amount as raw base units. When
|
|
16826
|
+
* the service omits `decimals` (optional during rollout), fall back to
|
|
16827
|
+
* inferring the fee token's chain and resolving via the registry/adapter.
|
|
16828
|
+
*
|
|
16829
|
+
* Like {@link formatTokenValue}, this never throws: fee display is cosmetic
|
|
16830
|
+
* and must not fail an estimate/swap. A malformed self-describing `decimals`
|
|
16831
|
+
* (e.g. a non-numeric `amount` or invalid decimal count that makes
|
|
16832
|
+
* {@link formatUnits} throw) falls through to chain-based resolution rather
|
|
16833
|
+
* than propagating out of {@link buildFormattedFees}.
|
|
16834
|
+
*
|
|
16835
|
+
* @param fee - The fee item from the service response.
|
|
16836
|
+
* @param chain - The source chain definition.
|
|
16837
|
+
* @param destinationChain - The destination chain definition.
|
|
16838
|
+
* @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
|
|
16839
|
+
* @returns Promise resolving to the formatted amount and display token.
|
|
16840
|
+
*/ async formatFeeValue(fee, chain, destinationChain, adapter) {
|
|
16841
|
+
if (fee.decimals != null) {
|
|
16842
|
+
try {
|
|
16843
|
+
return {
|
|
16844
|
+
amount: formatUnits(fee.amount, fee.decimals),
|
|
16845
|
+
token: fee.symbol ?? fee.token
|
|
16846
|
+
};
|
|
16847
|
+
} catch {
|
|
16848
|
+
// Malformed service metadata — fall through to chain-based resolution,
|
|
16849
|
+
// which never throws (worst case: raw passthrough).
|
|
16850
|
+
}
|
|
16851
|
+
}
|
|
16852
|
+
const feeChain = resolveFeeChain(fee.token, chain, destinationChain);
|
|
16853
|
+
return formatTokenValue(fee.amount, fee.token, feeChain, adapter);
|
|
16854
|
+
}
|
|
16855
|
+
/**
|
|
16200
16856
|
* Format service fee items into the SDK's ServiceSwapFee structure.
|
|
16201
16857
|
*
|
|
16202
16858
|
* @remarks
|
|
@@ -16207,14 +16863,15 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16207
16863
|
* - Raw passthrough only when both registry and adapter fail
|
|
16208
16864
|
*
|
|
16209
16865
|
* @param feeItems - Array of fee items from the service response.
|
|
16210
|
-
* @param chain - The chain definition for token resolution and formatting.
|
|
16866
|
+
* @param chain - The source chain definition for token resolution and formatting.
|
|
16867
|
+
* @param destinationChain - The destination chain, used to resolve fees denominated in the output token.
|
|
16211
16868
|
* @param type - The fee type to assign ('provider' or 'swap').
|
|
16212
16869
|
* @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
|
|
16213
16870
|
* @returns Promise resolving to formatted ServiceSwapFee array.
|
|
16214
|
-
*/ async formatServiceFees(feeItems, chain, type, adapter) {
|
|
16871
|
+
*/ async formatServiceFees(feeItems, chain, destinationChain, type, adapter) {
|
|
16215
16872
|
if (!feeItems) return [];
|
|
16216
16873
|
return Promise.all(feeItems.map(async (fee)=>{
|
|
16217
|
-
const formatted = await
|
|
16874
|
+
const formatted = await this.formatFeeValue(fee, chain, destinationChain, adapter);
|
|
16218
16875
|
return {
|
|
16219
16876
|
token: formatted.token,
|
|
16220
16877
|
amount: formatted.amount,
|
|
@@ -16226,16 +16883,15 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16226
16883
|
* Format developer fee items into the SDK's ServiceSwapFee structure.
|
|
16227
16884
|
*
|
|
16228
16885
|
* @param feeItems - Array of developer fee items from the service response.
|
|
16229
|
-
* @param chain - The chain definition for token resolution and formatting.
|
|
16886
|
+
* @param chain - The source chain definition for token resolution and formatting.
|
|
16887
|
+
* @param destinationChain - The destination chain, used to resolve fees denominated in the output token.
|
|
16230
16888
|
* @param recipientAddress - The developer's fee recipient address from config.
|
|
16231
16889
|
* @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
|
|
16232
16890
|
* @returns Promise resolving to formatted ServiceSwapFee array with developer entries.
|
|
16233
16891
|
*/ async formatDeveloperFees(feeItems, chain, destinationChain, recipientAddress, adapter) {
|
|
16234
16892
|
if (!feeItems) return [];
|
|
16235
|
-
const isCrossChainSwap = destinationChain.chain !== chain.chain;
|
|
16236
16893
|
return Promise.all(feeItems.map(async (fee)=>{
|
|
16237
|
-
const
|
|
16238
|
-
const formatted = await formatTokenValue(fee.amount, fee.token, feeChain, adapter);
|
|
16894
|
+
const formatted = await this.formatFeeValue(fee, chain, destinationChain, adapter);
|
|
16239
16895
|
return {
|
|
16240
16896
|
token: formatted.token,
|
|
16241
16897
|
amount: formatted.amount,
|
|
@@ -18630,22 +19286,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
18630
19286
|
try {
|
|
18631
19287
|
// Step 1: Build quote params directly (no need for buildServiceParams)
|
|
18632
19288
|
// Use chain.chain (Blockchain enum value like "World_Chain") not chain.name
|
|
19289
|
+
// The kit key is optional (permissionless mode); when absent the quote is
|
|
19290
|
+
// fetched without an Authorization header.
|
|
18633
19291
|
const kitKey = params.config?.kitKey;
|
|
18634
|
-
if (!kitKey) {
|
|
18635
|
-
throw new KitError({
|
|
18636
|
-
code: 1098,
|
|
18637
|
-
name: 'INPUT_VALIDATION_FAILED',
|
|
18638
|
-
type: 'INPUT',
|
|
18639
|
-
recoverability: 'FATAL',
|
|
18640
|
-
message: 'kitKey is required in config for callback-based fees',
|
|
18641
|
-
cause: {
|
|
18642
|
-
trace: {
|
|
18643
|
-
operation: 'handleOutputFeeCallback',
|
|
18644
|
-
params
|
|
18645
|
-
}
|
|
18646
|
-
}
|
|
18647
|
-
});
|
|
18648
|
-
}
|
|
18649
19292
|
// Resolve token aliases to addresses for the quote API
|
|
18650
19293
|
// The quote endpoint requires resolved addresses, not aliases like 'USDC'
|
|
18651
19294
|
const chain = params.from.chain;
|
|
@@ -18670,7 +19313,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
18670
19313
|
...params.config?.slippageBps !== undefined && {
|
|
18671
19314
|
slippageBps: params.config.slippageBps
|
|
18672
19315
|
},
|
|
18673
|
-
|
|
19316
|
+
...kitKey ? {
|
|
19317
|
+
apiKey: kitKey
|
|
19318
|
+
} : {}
|
|
18674
19319
|
};
|
|
18675
19320
|
// Step 2: Get quote from service
|
|
18676
19321
|
const quoteResponse = await getQuote(quoteParams);
|
|
@@ -18922,7 +19567,13 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
18922
19567
|
* amountIn: '50.00'
|
|
18923
19568
|
* })
|
|
18924
19569
|
* ```
|
|
18925
|
-
*/ async function swap$1(context, params, /**
|
|
19570
|
+
*/ async function swap$1(context, params, /**
|
|
19571
|
+
* @internal
|
|
19572
|
+
* Invoked after a successful broadcast with the on-chain `txHash` and the
|
|
19573
|
+
* service-issued `correlationId` (join key for success telemetry). The
|
|
19574
|
+
* service returns `correlationId` for every chain (EVM + Solana); it is
|
|
19575
|
+
* undefined only against a not-yet-upgraded service that omits the field.
|
|
19576
|
+
*/ onBroadcast) {
|
|
18926
19577
|
// Step 1: Validate parameters using schema
|
|
18927
19578
|
assertSwapParams(params, swapParamsSchema);
|
|
18928
19579
|
// Step 2: Resolve parameters (chain definitions, wallet addresses, token aliases preserved)
|
|
@@ -18942,13 +19593,16 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
18942
19593
|
// Step 5: Execute swap via provider
|
|
18943
19594
|
const swapParams = buildServiceSwapParams(resolvedParams, paramsWithFees);
|
|
18944
19595
|
const providerResult = await provider.swap(swapParams);
|
|
19596
|
+
// `correlationId` is an internal telemetry join key, not part of the public
|
|
19597
|
+
// SwapResult — strip it here so it never leaks into the formatted result.
|
|
19598
|
+
const { correlationId, ...providerResultPublic } = providerResult;
|
|
18945
19599
|
// A throwing `onBroadcast` must never strand the caller after a
|
|
18946
19600
|
// successful swap broadcast — the chain has moved. `safeInvokeCallback`
|
|
18947
19601
|
// swallows the error and surfaces a `console.warn` prefixed with
|
|
18948
19602
|
// `[stablecoin-kits swap-kit] callback threw and was swallowed:` so a
|
|
18949
19603
|
// kit-side closure bug stays debuggable rather than vanishing.
|
|
18950
19604
|
safeInvokeCallback('swap-kit', ()=>{
|
|
18951
|
-
onBroadcast?.(
|
|
19605
|
+
onBroadcast?.(providerResultPublic.txHash, correlationId);
|
|
18952
19606
|
});
|
|
18953
19607
|
const { tokenInDecimals, tokenOutDecimals } = await resolveTokenDecimals(context, resolvedParams);
|
|
18954
19608
|
// Step 6: Compose chain identity (owned by the kit, derived from the
|
|
@@ -18957,10 +19611,10 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
18957
19611
|
// resolves `progress` — the provider's snapshot, or a terminal `'DONE'` for
|
|
18958
19612
|
// a provider that omits it (a synchronous same-chain completion).
|
|
18959
19613
|
const composedResult = {
|
|
18960
|
-
...
|
|
19614
|
+
...providerResultPublic,
|
|
18961
19615
|
chainIn: resolvedParams.from.chain,
|
|
18962
19616
|
chainOut: resolvedParams.toChain ?? resolvedParams.from.chain,
|
|
18963
|
-
progress:
|
|
19617
|
+
progress: providerResultPublic.progress ?? {
|
|
18964
19618
|
status: 'DONE'
|
|
18965
19619
|
}
|
|
18966
19620
|
};
|
|
@@ -19112,7 +19766,9 @@ const sleep$1 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
19112
19766
|
...isCrossChain && {
|
|
19113
19767
|
toChain: chainOut
|
|
19114
19768
|
},
|
|
19115
|
-
|
|
19769
|
+
...params.kitKey ? {
|
|
19770
|
+
apiKey: params.kitKey
|
|
19771
|
+
} : {}
|
|
19116
19772
|
};
|
|
19117
19773
|
let raw = await getSwapStatus$1(request);
|
|
19118
19774
|
// When the service hasn't finished indexing a just-submitted swap it
|
|
@@ -19252,7 +19908,9 @@ const isResultShape = (params)=>'result' in params;
|
|
|
19252
19908
|
...chainOut !== undefined && {
|
|
19253
19909
|
chainOut
|
|
19254
19910
|
},
|
|
19255
|
-
|
|
19911
|
+
...params.kitKey ? {
|
|
19912
|
+
kitKey: params.kitKey
|
|
19913
|
+
} : {}
|
|
19256
19914
|
};
|
|
19257
19915
|
const deadline = Date.now() + timeoutMs;
|
|
19258
19916
|
let pollIndex = 0;
|
|
@@ -19413,7 +20071,9 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
19413
20071
|
const resolvedAddresses = params.tokens?.map((entry, index)=>resolveTokenEntry(entry, index, chain, chainDef, context));
|
|
19414
20072
|
return getTokenRates$1({
|
|
19415
20073
|
chain,
|
|
19416
|
-
|
|
20074
|
+
...params.kitKey ? {
|
|
20075
|
+
apiKey: params.kitKey
|
|
20076
|
+
} : {},
|
|
19417
20077
|
...resolvedAddresses !== undefined && {
|
|
19418
20078
|
addresses: resolvedAddresses
|
|
19419
20079
|
}
|
|
@@ -19807,7 +20467,13 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
19807
20467
|
*/ class SwapKit {
|
|
19808
20468
|
context;
|
|
19809
20469
|
/** Whether error telemetry is disabled. */ disableErrorReporting;
|
|
19810
|
-
/** Per-kit telemetry identity for
|
|
20470
|
+
/** Per-kit telemetry identity for error reporting. */ telemetryConfig;
|
|
20471
|
+
/**
|
|
20472
|
+
* Per-kit telemetry identity for success/analytics events. Gated by
|
|
20473
|
+
* `disableAnalytics` (independent of `disableErrorReporting`) so a developer
|
|
20474
|
+
* can opt out of volume analytics without also silencing error reports —
|
|
20475
|
+
* mirrors EarnKit.
|
|
20476
|
+
*/ analyticsTelemetryConfig;
|
|
19811
20477
|
/**
|
|
19812
20478
|
* Create a new SwapKit instance.
|
|
19813
20479
|
*
|
|
@@ -19861,6 +20527,11 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
19861
20527
|
sdkVersion: pkg$1.version,
|
|
19862
20528
|
disabled: this.disableErrorReporting
|
|
19863
20529
|
};
|
|
20530
|
+
this.analyticsTelemetryConfig = {
|
|
20531
|
+
sdkName: SDK_NAME,
|
|
20532
|
+
sdkVersion: pkg$1.version,
|
|
20533
|
+
disabled: config.disableAnalytics === true
|
|
20534
|
+
};
|
|
19864
20535
|
}
|
|
19865
20536
|
/**
|
|
19866
20537
|
* Estimate the output amount and fees for a swap operation.
|
|
@@ -19901,8 +20572,12 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
19901
20572
|
* console.log(`Fees:`, quote.fees)
|
|
19902
20573
|
* ```
|
|
19903
20574
|
*/ async estimate(params) {
|
|
20575
|
+
const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
|
|
19904
20576
|
return withErrorTelemetry(async ()=>estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
|
|
19905
20577
|
sourceChain: resolveChainName(params.from.chain),
|
|
20578
|
+
...destinationChain != null && {
|
|
20579
|
+
destinationChain
|
|
20580
|
+
},
|
|
19906
20581
|
tokenIn: params.tokenIn,
|
|
19907
20582
|
tokenOut: params.tokenOut
|
|
19908
20583
|
});
|
|
@@ -19961,16 +20636,54 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
19961
20636
|
* ```
|
|
19962
20637
|
*/ async swap(params) {
|
|
19963
20638
|
let txHash;
|
|
19964
|
-
|
|
19965
|
-
|
|
19966
|
-
|
|
20639
|
+
let correlationId;
|
|
20640
|
+
// Shared context builder so the error resolver and the success emit stay in
|
|
20641
|
+
// lockstep — a field added here reaches both call sites. Reads the per-call
|
|
20642
|
+
// locals lazily, so txHash/correlationId (set during the swap) are captured
|
|
20643
|
+
// whenever it is invoked.
|
|
20644
|
+
// Destination chain is the primary attribution dimension for cross-chain
|
|
20645
|
+
// swaps; resolved once from the (static) params. Omitted for same-chain
|
|
20646
|
+
// swaps that leave `to.chain` unset (destination == source).
|
|
20647
|
+
const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
|
|
20648
|
+
const buildTelemetryContext = ()=>({
|
|
19967
20649
|
sourceChain: resolveChainName(params.from.chain),
|
|
20650
|
+
...destinationChain != null && {
|
|
20651
|
+
destinationChain
|
|
20652
|
+
},
|
|
19968
20653
|
tokenIn: params.tokenIn,
|
|
19969
20654
|
tokenOut: params.tokenOut,
|
|
19970
20655
|
...txHash != null && {
|
|
19971
20656
|
txHash
|
|
20657
|
+
},
|
|
20658
|
+
...correlationId != null && {
|
|
20659
|
+
correlationId
|
|
19972
20660
|
}
|
|
19973
|
-
})
|
|
20661
|
+
});
|
|
20662
|
+
const result = await withErrorTelemetry(async ()=>swap$1(this.context, params, (h, cId)=>{
|
|
20663
|
+
txHash = h;
|
|
20664
|
+
correlationId = cId;
|
|
20665
|
+
}), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, buildTelemetryContext);
|
|
20666
|
+
// withErrorTelemetry only emits on failure. Record the successful swap here
|
|
20667
|
+
// so the backend can attribute swap volume to a developer: the client event
|
|
20668
|
+
// carries the (burn) txHash + correlationId, which joins to the
|
|
20669
|
+
// server-emitted event carrying entity_id. Best-effort; never throws.
|
|
20670
|
+
//
|
|
20671
|
+
// Emit whenever a broadcast happened, i.e. we have the source/burn txHash.
|
|
20672
|
+
// For a cross-chain swap that is the source-chain burn (progress is still
|
|
20673
|
+
// PENDING while the destination mint settles) — we intentionally attribute
|
|
20674
|
+
// at broadcast using the burn txHash rather than tracking the destination
|
|
20675
|
+
// leg, which keeps the capture simple and self-contained in swap().
|
|
20676
|
+
//
|
|
20677
|
+
// Guard against a terminal-failure result: the EVM provider throws on
|
|
20678
|
+
// revert today, but the kit is provider-agnostic, so a provider that
|
|
20679
|
+
// returns a FAILED/NOT_FOUND result without throwing must not be recorded
|
|
20680
|
+
// as a successful swap. Routed through analyticsTelemetryConfig so it is
|
|
20681
|
+
// gated by disableAnalytics, independent of error reporting.
|
|
20682
|
+
const status = result.progress?.status;
|
|
20683
|
+
if (txHash != null && status !== 'FAILED' && status !== 'NOT_FOUND') {
|
|
20684
|
+
emitSuccessTelemetry(SWAP_EVENT_TYPES.SWAP, this.analyticsTelemetryConfig, buildTelemetryContext());
|
|
20685
|
+
}
|
|
20686
|
+
return result;
|
|
19974
20687
|
}
|
|
19975
20688
|
/**
|
|
19976
20689
|
* Fetch the current status of a swap from the Stablecoin Service.
|
|
@@ -20349,6 +21062,9 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
|
|
|
20349
21062
|
const kit = new SwapKit({
|
|
20350
21063
|
...context.disableErrorReporting != null && {
|
|
20351
21064
|
disableErrorReporting: context.disableErrorReporting
|
|
21065
|
+
},
|
|
21066
|
+
...context.disableAnalytics != null && {
|
|
21067
|
+
disableAnalytics: context.disableAnalytics
|
|
20352
21068
|
}
|
|
20353
21069
|
});
|
|
20354
21070
|
if (hasBoth) {
|
|
@@ -20410,7 +21126,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
|
|
|
20410
21126
|
};
|
|
20411
21127
|
|
|
20412
21128
|
var name = "@circle-fin/earn-kit";
|
|
20413
|
-
var version = "1.
|
|
21129
|
+
var version = "1.4.0";
|
|
20414
21130
|
var pkg = {
|
|
20415
21131
|
name: name,
|
|
20416
21132
|
version: version};
|
|
@@ -20540,7 +21256,11 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
20540
21256
|
asset: z.string(),
|
|
20541
21257
|
assetAddress: z.string(),
|
|
20542
21258
|
lltv: z.number(),
|
|
20543
|
-
supplyUsd: z.number()
|
|
21259
|
+
supplyUsd: z.number(),
|
|
21260
|
+
// Optional during the expand/contract window (a backend that predates the
|
|
21261
|
+
// field omits the key), mirroring the `.optional()` facets on the base
|
|
21262
|
+
// schema; `null` when the product exposes no per-market allocation (V2).
|
|
21263
|
+
allocationPct: z.number().nullable().optional()
|
|
20544
21264
|
});
|
|
20545
21265
|
/**
|
|
20546
21266
|
* Zod schema for a Morpho vault warning in the API response.
|
|
@@ -20554,7 +21274,74 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
20554
21274
|
])
|
|
20555
21275
|
});
|
|
20556
21276
|
/**
|
|
20557
|
-
* Zod schema for
|
|
21277
|
+
* Zod schema for the manager (curator) facet in the API response.
|
|
21278
|
+
*
|
|
21279
|
+
* @internal
|
|
21280
|
+
*/ const managerSchema = z.object({
|
|
21281
|
+
name: z.string(),
|
|
21282
|
+
address: z.string().optional(),
|
|
21283
|
+
// Only 'curator' is emitted today (Morpho V1/V2). Additional manager roles
|
|
21284
|
+
// are added here as the providers that emit them land, rather than shipped
|
|
21285
|
+
// speculatively.
|
|
21286
|
+
type: z.enum([
|
|
21287
|
+
'curator'
|
|
21288
|
+
])
|
|
21289
|
+
});
|
|
21290
|
+
/**
|
|
21291
|
+
* Zod schema for the APY profile facet in the API response.
|
|
21292
|
+
*
|
|
21293
|
+
* @internal
|
|
21294
|
+
*/ const apyProfileSchema = z.object({
|
|
21295
|
+
current: z.number(),
|
|
21296
|
+
native: z.number().nullable(),
|
|
21297
|
+
d7: z.number().nullable(),
|
|
21298
|
+
d30: z.number().nullable(),
|
|
21299
|
+
d90: z.number().nullable(),
|
|
21300
|
+
rewardShare: z.number().nullable(),
|
|
21301
|
+
source: z.string().optional(),
|
|
21302
|
+
asOf: z.string().optional()
|
|
21303
|
+
});
|
|
21304
|
+
/**
|
|
21305
|
+
* Zod schema for the fee split facet in the API response.
|
|
21306
|
+
*
|
|
21307
|
+
* @internal
|
|
21308
|
+
*/ const feeInfoSchema = z.object({
|
|
21309
|
+
performance: z.number().nullable(),
|
|
21310
|
+
management: z.number().nullable()
|
|
21311
|
+
});
|
|
21312
|
+
/**
|
|
21313
|
+
* Zod schema for the liquidity profile facet in the API response.
|
|
21314
|
+
*
|
|
21315
|
+
* `totalSupply` is the outstanding vault share tokens (ERC4626 totalSupply);
|
|
21316
|
+
* it is validated as a raw JSON amount, like `totalDeposits`/`available`.
|
|
21317
|
+
*
|
|
21318
|
+
* @internal
|
|
21319
|
+
*/ const liquidityProfileSchema = z.object({
|
|
21320
|
+
totalDeposits: amountJsonSchema,
|
|
21321
|
+
available: amountJsonSchema,
|
|
21322
|
+
totalSupply: amountJsonSchema,
|
|
21323
|
+
status: z.enum([
|
|
21324
|
+
'active',
|
|
21325
|
+
'low_liquidity'
|
|
21326
|
+
])
|
|
21327
|
+
});
|
|
21328
|
+
/**
|
|
21329
|
+
* Zod schema for the risk signals facet in the API response.
|
|
21330
|
+
*
|
|
21331
|
+
* @internal
|
|
21332
|
+
*/ const riskSignalsSchema = z.object({
|
|
21333
|
+
circleSentinel: z.boolean(),
|
|
21334
|
+
warnings: z.array(vaultWarningSchema).optional(),
|
|
21335
|
+
earnKitWarnings: z.array(z.string()).optional()
|
|
21336
|
+
});
|
|
21337
|
+
/**
|
|
21338
|
+
* Zod schema for the universal earn-opportunity base in the API response.
|
|
21339
|
+
*
|
|
21340
|
+
* Retains every existing deprecated flat field (kept validated through the
|
|
21341
|
+
* expand/contract window so default-strip does not drop them) and adds the
|
|
21342
|
+
* new nested facets. The nested facets are `.optional()` during the
|
|
21343
|
+
* transition so the SDK still validates against a not-yet-fully-deployed
|
|
21344
|
+
* backend; they become required after Expand ships.
|
|
20558
21345
|
*
|
|
20559
21346
|
* @internal
|
|
20560
21347
|
*/ const vaultInfoResponseSchema = z.object({
|
|
@@ -20579,6 +21366,96 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
20579
21366
|
warnings: z.array(vaultWarningSchema).optional(),
|
|
20580
21367
|
earnKitWarnings: z.array(z.string()).optional()
|
|
20581
21368
|
});
|
|
21369
|
+
/**
|
|
21370
|
+
* Shared base schema: existing flat fields (kept) plus the new nested
|
|
21371
|
+
* facets and neutral identity. Facets are `.optional()` during the
|
|
21372
|
+
* transition; flip to required once the backend is confirmed emitting.
|
|
21373
|
+
*
|
|
21374
|
+
* @internal
|
|
21375
|
+
*/ const earnBaseSchema = vaultInfoResponseSchema.extend({
|
|
21376
|
+
address: z.string().optional(),
|
|
21377
|
+
asOf: z.string().optional(),
|
|
21378
|
+
manager: managerSchema.nullable().optional(),
|
|
21379
|
+
apyProfile: apyProfileSchema.optional(),
|
|
21380
|
+
fee: feeInfoSchema.optional(),
|
|
21381
|
+
liquidityProfile: liquidityProfileSchema.optional(),
|
|
21382
|
+
riskSignals: riskSignalsSchema.optional()
|
|
21383
|
+
});
|
|
21384
|
+
/**
|
|
21385
|
+
* Zod schema for the `vault` opportunity variant.
|
|
21386
|
+
*
|
|
21387
|
+
* @internal
|
|
21388
|
+
*/ const vaultOpportunitySchema = earnBaseSchema.extend({
|
|
21389
|
+
productType: z.literal('vault'),
|
|
21390
|
+
collateral: z.array(collateralSchema)
|
|
21391
|
+
});
|
|
21392
|
+
/**
|
|
21393
|
+
* Discriminated union over `productType`. Add union members here as new
|
|
21394
|
+
* product types (e.g. `lending_market`, `rwa_token`) land.
|
|
21395
|
+
*
|
|
21396
|
+
* @internal
|
|
21397
|
+
*/ const earnOpportunityVariants = [
|
|
21398
|
+
vaultOpportunitySchema
|
|
21399
|
+
];
|
|
21400
|
+
/** @internal */ const earnOpportunitySchema = z.discriminatedUnion('productType', earnOpportunityVariants);
|
|
21401
|
+
/** Product types this SDK version knows how to parse. */ const knownProductTypes = new Set(earnOpportunityVariants.map((variant)=>variant.shape.productType.value));
|
|
21402
|
+
/**
|
|
21403
|
+
* Tolerant list parser for earn opportunities.
|
|
21404
|
+
*
|
|
21405
|
+
* `z.discriminatedUnion` throws on an unrecognized discriminant and
|
|
21406
|
+
* `z.array` fails the whole array if any element fails. Two migration-window
|
|
21407
|
+
* cases are smoothed over here so neither breaks an already-shipped SDK:
|
|
21408
|
+
*
|
|
21409
|
+
* - A backend that predates `productType` omits it entirely. `'vault'` was the
|
|
21410
|
+
* only opportunity type then, so default a missing discriminant to `'vault'`
|
|
21411
|
+
* rather than dropping every vault the backend returns.
|
|
21412
|
+
* - A future backend adds a *second* `productType` this SDK version does not
|
|
21413
|
+
* know. Drop those elements (a present-but-unrecognized discriminant) instead
|
|
21414
|
+
* of rejecting the whole list.
|
|
21415
|
+
*
|
|
21416
|
+
* Only the drop above is a *tolerant* case. Anything that is not a plain object
|
|
21417
|
+
* with a present-but-unknown string `productType` — `null`, `undefined`,
|
|
21418
|
+
* primitives, or an object whose `productType` is malformed — is passed through
|
|
21419
|
+
* untouched so `z.array(earnOpportunitySchema)` reports it as a normal
|
|
21420
|
+
* validation failure. It is deliberately not silently dropped (which would hide
|
|
21421
|
+
* malformed backend data) and never throws here (an unguarded property read on
|
|
21422
|
+
* a non-object would escape `safeParse` as a raw `TypeError` instead of a
|
|
21423
|
+
* `ZodError`).
|
|
21424
|
+
*
|
|
21425
|
+
* @internal
|
|
21426
|
+
*/ const earnOpportunityListSchema = z.preprocess((raw)=>{
|
|
21427
|
+
if (!Array.isArray(raw)) {
|
|
21428
|
+
return raw;
|
|
21429
|
+
}
|
|
21430
|
+
// Array.isArray narrows `raw` to `any[]`; view it as `unknown[]` so the
|
|
21431
|
+
// map/filter chain stays type-safe and no `any` leaks into the return.
|
|
21432
|
+
const entries = raw;
|
|
21433
|
+
return entries.map((entry)=>{
|
|
21434
|
+
// Only touch plain objects; non-objects fall through to fail validation.
|
|
21435
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
21436
|
+
return entry;
|
|
21437
|
+
}
|
|
21438
|
+
const record = entry;
|
|
21439
|
+
// Older backend predating productType: default to the only type then.
|
|
21440
|
+
return record.productType === undefined ? {
|
|
21441
|
+
...record,
|
|
21442
|
+
productType: 'vault'
|
|
21443
|
+
} : record;
|
|
21444
|
+
}).filter((entry)=>{
|
|
21445
|
+
// Drop ONLY a present-but-unknown string discriminant (a future
|
|
21446
|
+
// productType this SDK version doesn't know). Everything else —
|
|
21447
|
+
// non-objects, a non-string productType — flows through to
|
|
21448
|
+
// z.array(earnOpportunitySchema) and fails/passes validation normally.
|
|
21449
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
21450
|
+
return true;
|
|
21451
|
+
}
|
|
21452
|
+
const productType = entry.productType;
|
|
21453
|
+
if (typeof productType !== 'string') {
|
|
21454
|
+
return true;
|
|
21455
|
+
}
|
|
21456
|
+
return knownProductTypes.has(productType);
|
|
21457
|
+
});
|
|
21458
|
+
}, z.array(earnOpportunitySchema));
|
|
20582
21459
|
// ---------------------------------------------------------------------------
|
|
20583
21460
|
// Position response schema
|
|
20584
21461
|
// ---------------------------------------------------------------------------
|
|
@@ -20708,6 +21585,7 @@ const positionPnlSchema = z.discriminatedUnion('status', [
|
|
|
20708
21585
|
*
|
|
20709
21586
|
* @internal
|
|
20710
21587
|
*/ const depositPayloadSchema = z.object({
|
|
21588
|
+
execId: bridgeDepositExecIdSchema,
|
|
20711
21589
|
executionParams: depositExecutionParamsSchema,
|
|
20712
21590
|
signature: hexSignatureSchema
|
|
20713
21591
|
});
|
|
@@ -20799,6 +21677,21 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
20799
21677
|
amount: amountJsonSchema,
|
|
20800
21678
|
vaultAddress: hexAddressSchema
|
|
20801
21679
|
}).passthrough();
|
|
21680
|
+
/** @internal */ const bridgeQuoteExpirySchema = z.discriminatedUnion('mode', [
|
|
21681
|
+
z.object({
|
|
21682
|
+
mode: z.literal('TIMESTAMP'),
|
|
21683
|
+
expiresAt: z.string().datetime({
|
|
21684
|
+
offset: true
|
|
21685
|
+
})
|
|
21686
|
+
}),
|
|
21687
|
+
z.object({
|
|
21688
|
+
mode: z.literal('BLOCK_NUMBER'),
|
|
21689
|
+
expiresAtBlock: z.number().int(),
|
|
21690
|
+
blockEstimatedAt: z.string().datetime({
|
|
21691
|
+
offset: true
|
|
21692
|
+
}).optional()
|
|
21693
|
+
})
|
|
21694
|
+
]).optional().catch(undefined);
|
|
20802
21695
|
/**
|
|
20803
21696
|
* Zod schema for the bridge deposit prepare payload.
|
|
20804
21697
|
*
|
|
@@ -20810,6 +21703,10 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
20810
21703
|
execId: bridgeDepositExecIdSchema,
|
|
20811
21704
|
erc3009TypedData: bridgeDepositPreparedBundleSchema,
|
|
20812
21705
|
expiresAt: z.string().datetime(),
|
|
21706
|
+
quoteIssuedAt: z.string().datetime({
|
|
21707
|
+
offset: true
|
|
21708
|
+
}).optional().catch(undefined),
|
|
21709
|
+
quoteExpiry: bridgeQuoteExpirySchema,
|
|
20813
21710
|
review: bridgeDepositPrepareReviewSchema
|
|
20814
21711
|
});
|
|
20815
21712
|
/**
|
|
@@ -20875,6 +21772,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
20875
21772
|
*
|
|
20876
21773
|
* @internal
|
|
20877
21774
|
*/ const withdrawPayloadSchema = z.object({
|
|
21775
|
+
execId: bridgeDepositExecIdSchema,
|
|
20878
21776
|
executionParams: withdrawExecutionParamsSchema,
|
|
20879
21777
|
signature: hexSignatureSchema
|
|
20880
21778
|
});
|
|
@@ -20888,6 +21786,27 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
20888
21786
|
data: withdrawPayloadSchema
|
|
20889
21787
|
});
|
|
20890
21788
|
// ---------------------------------------------------------------------------
|
|
21789
|
+
// Transaction report response schema
|
|
21790
|
+
// ---------------------------------------------------------------------------
|
|
21791
|
+
/**
|
|
21792
|
+
* Zod schema for the transaction report payload inside the API `data` envelope.
|
|
21793
|
+
*
|
|
21794
|
+
* The Earn Service returns an empty payload (`{"data":{}}`) on success, so the
|
|
21795
|
+
* schema accepts any object shape and does not require specific fields.
|
|
21796
|
+
*
|
|
21797
|
+
* @internal
|
|
21798
|
+
*/ const transactionReportPayloadSchema = z.object({}).passthrough();
|
|
21799
|
+
/**
|
|
21800
|
+
* Zod schema for the `POST /v1/earnKit/transactions/report` API response.
|
|
21801
|
+
*
|
|
21802
|
+
* The Earn Service API wraps the transaction report payload in a `data`
|
|
21803
|
+
* envelope.
|
|
21804
|
+
*
|
|
21805
|
+
* @internal
|
|
21806
|
+
*/ z.object({
|
|
21807
|
+
data: transactionReportPayloadSchema
|
|
21808
|
+
});
|
|
21809
|
+
// ---------------------------------------------------------------------------
|
|
20891
21810
|
// Claim rewards response schema
|
|
20892
21811
|
// ---------------------------------------------------------------------------
|
|
20893
21812
|
/**
|
|
@@ -20948,6 +21867,30 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
20948
21867
|
token: z.string(),
|
|
20949
21868
|
amount: amountJsonSchema
|
|
20950
21869
|
});
|
|
21870
|
+
/**
|
|
21871
|
+
* Zod schema for a native gas-fee entry in an EarnKit quote response.
|
|
21872
|
+
*
|
|
21873
|
+
* The Earn Service backend estimates gas server-side and returns one entry per
|
|
21874
|
+
* action (`Approve`, `Deposit`, `Withdraw`). A successful estimate carries
|
|
21875
|
+
* `fees` in the SDK `EstimatedGas` shape (`{ gas, gasPrice, fee }`), each a raw
|
|
21876
|
+
* integer string in the chain's native base units. When the backend cannot
|
|
21877
|
+
* estimate an action it returns `fees: null` with an `error` message instead.
|
|
21878
|
+
*
|
|
21879
|
+
* The schema deliberately validates almost nothing beyond the envelope: `name`
|
|
21880
|
+
* is optional and `fees` is entirely unvalidated (`unknown`). ALL validation
|
|
21881
|
+
* of `fees` — that it is an object at all, and that `gas`, `gasPrice`, and
|
|
21882
|
+
* `fee` are parseable integer strings — is deferred to {@link toQuoteGasFees},
|
|
21883
|
+
* which degrades a malformed entry to a `fees: null` soft failure. This is
|
|
21884
|
+
* intentional: gas is best-effort, so a single unparseable gas entry (a wrong
|
|
21885
|
+
* type such as `fees: 123` or `fees: 'bad'`, a missing field, or a non-numeric
|
|
21886
|
+
* `fee`) must never fail Zod validation and reject the entire quote.
|
|
21887
|
+
*
|
|
21888
|
+
* @internal
|
|
21889
|
+
*/ const quoteGasFeeSchema = z.object({
|
|
21890
|
+
name: z.string().optional(),
|
|
21891
|
+
fees: z.unknown(),
|
|
21892
|
+
error: z.string().optional()
|
|
21893
|
+
}).passthrough();
|
|
20951
21894
|
/**
|
|
20952
21895
|
* Zod schema for the inner deposit quote payload.
|
|
20953
21896
|
*
|
|
@@ -20963,7 +21906,8 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
20963
21906
|
expectedShares: amountJsonSchema,
|
|
20964
21907
|
sharePrice: z.string(),
|
|
20965
21908
|
currentApy: z.number(),
|
|
20966
|
-
fees: z.array(feeSchema).optional()
|
|
21909
|
+
fees: z.array(feeSchema).optional(),
|
|
21910
|
+
gasFees: z.array(quoteGasFeeSchema).optional()
|
|
20967
21911
|
});
|
|
20968
21912
|
/**
|
|
20969
21913
|
* Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
|
|
@@ -20990,6 +21934,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
20990
21934
|
sharePrice: z.string(),
|
|
20991
21935
|
maxWithdrawable: amountJsonSchema,
|
|
20992
21936
|
fees: z.array(feeSchema),
|
|
21937
|
+
gasFees: z.array(quoteGasFeeSchema).optional(),
|
|
20993
21938
|
warnings: z.array(z.string()).optional()
|
|
20994
21939
|
});
|
|
20995
21940
|
/**
|
|
@@ -21047,7 +21992,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
21047
21992
|
*
|
|
21048
21993
|
* @internal
|
|
21049
21994
|
*/ const getVaultsPayloadSchema = z.object({
|
|
21050
|
-
vaults:
|
|
21995
|
+
vaults: earnOpportunityListSchema,
|
|
21051
21996
|
errors: z.array(vaultErrorSchema)
|
|
21052
21997
|
});
|
|
21053
21998
|
/**
|
|
@@ -21077,7 +22022,7 @@ const bridgeDepositPrepareReviewSchema = z.object({
|
|
|
21077
22022
|
*
|
|
21078
22023
|
* @internal
|
|
21079
22024
|
*/ const exploreVaultsPayloadSchema = z.object({
|
|
21080
|
-
vaults:
|
|
22025
|
+
vaults: earnOpportunityListSchema,
|
|
21081
22026
|
pagination: explorePaginationSchema
|
|
21082
22027
|
});
|
|
21083
22028
|
/**
|
|
@@ -21645,6 +22590,8 @@ function hasCrossChainDepositQuoteShape(params) {
|
|
|
21645
22590
|
config: earnConfigSchema.optional()
|
|
21646
22591
|
});
|
|
21647
22592
|
|
|
22593
|
+
/** SDK name used in telemetry payloads. */ resolveKitSdkName(pkg.name);
|
|
22594
|
+
|
|
21648
22595
|
// Auto-register this kit for user agent tracking
|
|
21649
22596
|
registerKit(`${pkg.name}/${pkg.version}`);
|
|
21650
22597
|
|