@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.cjs
CHANGED
|
@@ -18,15 +18,27 @@
|
|
|
18
18
|
|
|
19
19
|
'use strict';
|
|
20
20
|
|
|
21
|
+
// Buffer polyfill setup - executes before any other code
|
|
22
|
+
// Ensures globalThis.Buffer is available for Solana libraries
|
|
23
|
+
const { Buffer } = require('buffer');
|
|
24
|
+
if (typeof globalThis !== 'undefined' && typeof globalThis.Buffer === 'undefined') {
|
|
25
|
+
globalThis.Buffer = Buffer;
|
|
26
|
+
}
|
|
27
|
+
if (typeof window !== 'undefined' && typeof window.Buffer === 'undefined') {
|
|
28
|
+
window.Buffer = Buffer;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
21
32
|
var zod = require('zod');
|
|
22
33
|
require('pino');
|
|
34
|
+
var bytes = require('@ethersproject/bytes');
|
|
35
|
+
require('@ethersproject/abi');
|
|
36
|
+
var address = require('@ethersproject/address');
|
|
23
37
|
var web3_js = require('@solana/web3.js');
|
|
24
38
|
require('bn.js');
|
|
25
39
|
require('@coral-xyz/anchor');
|
|
26
40
|
var bs58 = require('bs58');
|
|
27
41
|
require('@noble/curves/ed25519');
|
|
28
|
-
var bytes = require('@ethersproject/bytes');
|
|
29
|
-
var address = require('@ethersproject/address');
|
|
30
42
|
var units = require('@ethersproject/units');
|
|
31
43
|
var keccak256 = require('@ethersproject/keccak256');
|
|
32
44
|
|
|
@@ -49,6 +61,51 @@ var bs58__default = /*#__PURE__*/_interopDefault(bs58);
|
|
|
49
61
|
* }
|
|
50
62
|
* ```
|
|
51
63
|
*/ const isNodeEnvironment = ()=>typeof process !== 'undefined' && typeof process.versions === 'object' && typeof process.versions.node === 'string';
|
|
64
|
+
/**
|
|
65
|
+
* Check whether the current runtime exposes a browser DOM.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* This intentionally does not treat every non-Node runtime as a browser.
|
|
69
|
+
* Server-side edge runtimes such as Cloudflare Workers, Deno, and Supabase
|
|
70
|
+
* Edge Functions do not expose Node globals but can safely use server
|
|
71
|
+
* credentials. A Node.js runtime remains server-side even when a test or SSR
|
|
72
|
+
* environment provides a DOM shim.
|
|
73
|
+
*
|
|
74
|
+
* @returns `true` when running in a browser window, `false` otherwise.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* import { isBrowserEnvironment } from '@core/utils'
|
|
79
|
+
*
|
|
80
|
+
* if (isBrowserEnvironment()) {
|
|
81
|
+
* throw new Error('Server-only secrets must not be used in the browser')
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/ const isBrowserEnvironment = ()=>{
|
|
85
|
+
const browserWindow = globalThis.window;
|
|
86
|
+
return !isNodeEnvironment() && browserWindow?.document !== undefined;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Return the SDK User-Agent request header only when running in Node.js.
|
|
90
|
+
*
|
|
91
|
+
* Browsers forbid manually setting `User-Agent`, and a custom fallback header
|
|
92
|
+
* can trigger CORS preflight. Non-Node server runtimes also omit this optional
|
|
93
|
+
* attribution header because they cannot set it reliably.
|
|
94
|
+
*
|
|
95
|
+
* @returns A User-Agent header in Node.js, or an empty object otherwise.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* import { getNodeUserAgentHeader } from '@core/utils'
|
|
100
|
+
*
|
|
101
|
+
* const headers = {
|
|
102
|
+
* 'Content-Type': 'application/json',
|
|
103
|
+
* ...getNodeUserAgentHeader(),
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/ const getNodeUserAgentHeader = ()=>isNodeEnvironment() ? {
|
|
107
|
+
'User-Agent': getUserAgent()
|
|
108
|
+
} : {};
|
|
52
109
|
/**
|
|
53
110
|
* Detect the runtime environment and return a shortened identifier.
|
|
54
111
|
*
|
|
@@ -3533,7 +3590,10 @@ var EarnChain;
|
|
|
3533
3590
|
contracts: {
|
|
3534
3591
|
v1: {
|
|
3535
3592
|
wallet: GATEWAY_WALLET_EVM_TESTNET,
|
|
3536
|
-
minter: GATEWAY_MINTER_EVM_TESTNET
|
|
3593
|
+
minter: GATEWAY_MINTER_EVM_TESTNET,
|
|
3594
|
+
// DepositForHandler the GenericExecutor calls to run a fast cross-chain
|
|
3595
|
+
// deposit into the GatewayWallet above.
|
|
3596
|
+
depositForHandler: '0xD05E7D2E7d30b92c5F17d7d0fC575fce231F1A48'
|
|
3537
3597
|
}
|
|
3538
3598
|
},
|
|
3539
3599
|
forwarderSupported: {
|
|
@@ -6601,7 +6661,10 @@ var Chains = {
|
|
|
6601
6661
|
minter: zod.z.string({
|
|
6602
6662
|
required_error: 'Gateway minter address is required. Please provide a valid contract address.',
|
|
6603
6663
|
invalid_type_error: 'Gateway minter address must be a string.'
|
|
6604
|
-
}).min(1, 'Gateway minter address cannot be empty.')
|
|
6664
|
+
}).min(1, 'Gateway minter address cannot be empty.'),
|
|
6665
|
+
depositForHandler: zod.z.string({
|
|
6666
|
+
invalid_type_error: 'Gateway depositForHandler address must be a string.'
|
|
6667
|
+
}).min(1, 'Gateway depositForHandler address cannot be empty.').optional()
|
|
6605
6668
|
}).strict() // Reject any additional properties not defined in the schema
|
|
6606
6669
|
;
|
|
6607
6670
|
/**
|
|
@@ -8020,13 +8083,12 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
8020
8083
|
headers: {
|
|
8021
8084
|
...DEFAULT_CONFIG$1.headers,
|
|
8022
8085
|
...config.headers ?? {},
|
|
8023
|
-
//
|
|
8024
|
-
//
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
}
|
|
8086
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
8087
|
+
// fallback header the SDK used instead trips CORS preflight against the
|
|
8088
|
+
// Circle APIs (it isn't in their `Access-Control-Allow-Headers`),
|
|
8089
|
+
// blocking the request. So send the SDK user agent only in Node;
|
|
8090
|
+
// browsers omit it entirely.
|
|
8091
|
+
...getNodeUserAgentHeader()
|
|
8030
8092
|
}
|
|
8031
8093
|
};
|
|
8032
8094
|
let lastError;
|
|
@@ -9486,6 +9548,13 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9486
9548
|
return explorerUrl;
|
|
9487
9549
|
}
|
|
9488
9550
|
|
|
9551
|
+
/**
|
|
9552
|
+
* CCTP forwarding magic bytes prefix.
|
|
9553
|
+
*
|
|
9554
|
+
* The ASCII string "cctp-forward" (12 bytes) that identifies a forwarding request.
|
|
9555
|
+
* This prefix is right-padded to 24 bytes in the final hookData.
|
|
9556
|
+
*/ const CCTP_FORWARD_MAGIC_PREFIX = 'cctp-forward';
|
|
9557
|
+
|
|
9489
9558
|
/**
|
|
9490
9559
|
* Project an arbitrary payload onto the exact set of fields the telemetry
|
|
9491
9560
|
* endpoint accepts.
|
|
@@ -9520,6 +9589,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9520
9589
|
if (payload.tokenIn !== undefined) safe['tokenIn'] = payload.tokenIn;
|
|
9521
9590
|
if (payload.tokenOut !== undefined) safe['tokenOut'] = payload.tokenOut;
|
|
9522
9591
|
if (payload.txHash !== undefined) safe['txHash'] = payload.txHash;
|
|
9592
|
+
if (payload.correlationId !== undefined) safe['correlationId'] = payload.correlationId;
|
|
9523
9593
|
if (payload.errorDetails !== undefined) {
|
|
9524
9594
|
const errorDetails = {
|
|
9525
9595
|
...payload.errorDetails.errorCode !== undefined && {
|
|
@@ -9590,18 +9660,15 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9590
9660
|
timeoutHandle.unref();
|
|
9591
9661
|
}
|
|
9592
9662
|
try {
|
|
9593
|
-
const isNode = isNodeEnvironment();
|
|
9594
|
-
const userAgent = getUserAgent();
|
|
9595
9663
|
await fetch(getLogsUrl(), {
|
|
9596
9664
|
method: 'POST',
|
|
9597
9665
|
headers: {
|
|
9598
9666
|
'Content-Type': 'application/json',
|
|
9599
|
-
//
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
}
|
|
9667
|
+
// Browsers forbid setting a user-agent request header, and the custom
|
|
9668
|
+
// fallback header the SDK used instead trips CORS preflight (it isn't
|
|
9669
|
+
// in the telemetry endpoint's `Access-Control-Allow-Headers`), so send
|
|
9670
|
+
// it only in Node; browsers omit it entirely.
|
|
9671
|
+
...getNodeUserAgentHeader()
|
|
9605
9672
|
},
|
|
9606
9673
|
body: JSON.stringify(toSafePayload(payload)),
|
|
9607
9674
|
signal: controller.signal
|
|
@@ -9769,7 +9836,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9769
9836
|
// discards the stack trace, nested `cause`, and any custom Error
|
|
9770
9837
|
// properties — exactly the context an on-call needs when a
|
|
9771
9838
|
// resolver-closure regression triggers this path.
|
|
9772
|
-
console.warn(`[stablecoin-kits telemetry] dropped
|
|
9839
|
+
console.warn(`[stablecoin-kits telemetry] dropped event '${eventType}':`, cause);
|
|
9773
9840
|
} catch {
|
|
9774
9841
|
// console.warn itself throwing is the user's environment; nothing more we
|
|
9775
9842
|
// can do without risking the original operation error.
|
|
@@ -9785,7 +9852,9 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9785
9852
|
sdkVersion: config.sdkVersion,
|
|
9786
9853
|
eventType,
|
|
9787
9854
|
timestamp: new Date().toISOString(),
|
|
9788
|
-
errorDetails
|
|
9855
|
+
...errorDetails !== undefined && {
|
|
9856
|
+
errorDetails
|
|
9857
|
+
},
|
|
9789
9858
|
clientContext: buildClientContext(),
|
|
9790
9859
|
...context?.sourceChain != null && {
|
|
9791
9860
|
sourceChain: context.sourceChain
|
|
@@ -9801,9 +9870,45 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9801
9870
|
},
|
|
9802
9871
|
...context?.txHash != null && {
|
|
9803
9872
|
txHash: context.txHash
|
|
9873
|
+
},
|
|
9874
|
+
...context?.correlationId != null && {
|
|
9875
|
+
correlationId: context.correlationId
|
|
9804
9876
|
}
|
|
9805
9877
|
};
|
|
9806
9878
|
}
|
|
9879
|
+
/**
|
|
9880
|
+
* Emit telemetry for a completed operation without affecting its caller.
|
|
9881
|
+
*
|
|
9882
|
+
* No-ops when `config.disabled` is `true`. Like {@link withErrorTelemetry},
|
|
9883
|
+
* failures while constructing or submitting the telemetry payload are reported
|
|
9884
|
+
* as a soft warning and never change a completed operation's result.
|
|
9885
|
+
*
|
|
9886
|
+
* @param eventType - The telemetry event type for the completed operation.
|
|
9887
|
+
* @param config - Per-kit SDK identity and disabled flag.
|
|
9888
|
+
* @param context - Optional chain, token, and transaction context.
|
|
9889
|
+
* @returns Nothing.
|
|
9890
|
+
* @throws Never — telemetry failures are reported as warnings.
|
|
9891
|
+
*
|
|
9892
|
+
* @example
|
|
9893
|
+
* ```typescript
|
|
9894
|
+
* import { emitSuccessTelemetry } from '@core/utils'
|
|
9895
|
+
*
|
|
9896
|
+
* emitSuccessTelemetry(
|
|
9897
|
+
* 'bridge_bridge',
|
|
9898
|
+
* { sdkName: 'bridge-kit', sdkVersion: '1.0.0', disabled: false },
|
|
9899
|
+
* { sourceChain: 'Ethereum', destinationChain: 'Base', tokenIn: 'USDC' },
|
|
9900
|
+
* )
|
|
9901
|
+
* ```
|
|
9902
|
+
*/ function emitSuccessTelemetry(eventType, config, context) {
|
|
9903
|
+
if (config.disabled) {
|
|
9904
|
+
return;
|
|
9905
|
+
}
|
|
9906
|
+
try {
|
|
9907
|
+
void emitAnalyticsLog(buildPayload(config, eventType, undefined, context));
|
|
9908
|
+
} catch (telemetryError) {
|
|
9909
|
+
warnTelemetryDrop(eventType, telemetryError);
|
|
9910
|
+
}
|
|
9911
|
+
}
|
|
9807
9912
|
/**
|
|
9808
9913
|
* Wrap an async operation with error telemetry.
|
|
9809
9914
|
*
|
|
@@ -9862,7 +9967,7 @@ const swapTokenEnumSchema = zod.z.enum([
|
|
|
9862
9967
|
}
|
|
9863
9968
|
|
|
9864
9969
|
var name$2 = "@circle-fin/bridge-kit";
|
|
9865
|
-
var version$2 = "1.12.
|
|
9970
|
+
var version$2 = "1.12.2";
|
|
9866
9971
|
var pkg$2 = {
|
|
9867
9972
|
name: name$2,
|
|
9868
9973
|
version: version$2};
|
|
@@ -10720,6 +10825,17 @@ var TransferSpeed;
|
|
|
10720
10825
|
clock: zod.z.any().optional()
|
|
10721
10826
|
}).passthrough();
|
|
10722
10827
|
|
|
10828
|
+
/**
|
|
10829
|
+
* The ASCII "cctp-forward" magic, hex-encoded (no `0x`), that a forward-friendly
|
|
10830
|
+
* hookData must start with.
|
|
10831
|
+
*
|
|
10832
|
+
* Encoded with `TextEncoder` (a browser-safe Web API) rather than `Buffer.from`
|
|
10833
|
+
* so this module-level constant does not reference the Node `Buffer` global at
|
|
10834
|
+
* import time. App Kit inlines this provider into its bundle without a Buffer polyfill, and a
|
|
10835
|
+
* bare `Buffer` here crashes browser bundles (e.g. Vite) on load — even for apps
|
|
10836
|
+
* that never touch the prepaid FORWARD path. Mirrors `buildForwardingHookData`.
|
|
10837
|
+
*/ Array.from(new TextEncoder().encode(CCTP_FORWARD_MAGIC_PREFIX)).map((byte)=>byte.toString(16).padStart(2, '0')).join('');
|
|
10838
|
+
|
|
10723
10839
|
/**
|
|
10724
10840
|
* The minimum finality threshold for CCTPv2 transfers.
|
|
10725
10841
|
*
|
|
@@ -10752,7 +10868,7 @@ var TransferSpeed;
|
|
|
10752
10868
|
registerKit(`${pkg$2.name}/${pkg$2.version}`);
|
|
10753
10869
|
|
|
10754
10870
|
var name$1 = "@circle-fin/swap-kit";
|
|
10755
|
-
var version$1 = "1.
|
|
10871
|
+
var version$1 = "1.5.0";
|
|
10756
10872
|
var pkg$1 = {
|
|
10757
10873
|
name: name$1,
|
|
10758
10874
|
version: version$1};
|
|
@@ -10817,7 +10933,10 @@ const optionalChainIdentifierField = chainIdentifierField.optional();
|
|
|
10817
10933
|
}).min(1, 'kitKey must be a non-empty string').optional(),
|
|
10818
10934
|
provider: zod.z.string({
|
|
10819
10935
|
invalid_type_error: 'provider must be a string'
|
|
10820
|
-
}).min(1, 'provider must be a non-empty string').optional()
|
|
10936
|
+
}).min(1, 'provider must be a non-empty string').optional(),
|
|
10937
|
+
batchTransactions: zod.z.boolean({
|
|
10938
|
+
invalid_type_error: 'batchTransactions must be a boolean'
|
|
10939
|
+
}).optional()
|
|
10821
10940
|
});
|
|
10822
10941
|
/**
|
|
10823
10942
|
* Zod schema for adapter context.
|
|
@@ -11348,7 +11467,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11348
11467
|
/**
|
|
11349
11468
|
* Circle Stablecoin Service API Key.
|
|
11350
11469
|
* Must be a valid API key format.
|
|
11351
|
-
*/ apiKey: apiKeySchema
|
|
11470
|
+
*/ apiKey: apiKeySchema.optional()
|
|
11352
11471
|
}).superRefine(requireCrossChainQuoteToAddress);
|
|
11353
11472
|
/**
|
|
11354
11473
|
* Zod schema for validating CreateSwapRequest parameters.
|
|
@@ -11406,7 +11525,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11406
11525
|
/**
|
|
11407
11526
|
* Circle Stablecoin Service API Key.
|
|
11408
11527
|
* Must be a valid API key format.
|
|
11409
|
-
*/ apiKey: apiKeySchema
|
|
11528
|
+
*/ apiKey: apiKeySchema.optional()
|
|
11410
11529
|
});
|
|
11411
11530
|
/**
|
|
11412
11531
|
* Zod schema for validating GetSwapStatusResponse data.
|
|
@@ -11442,7 +11561,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11442
11561
|
toChain: zod.z.string({
|
|
11443
11562
|
invalid_type_error: 'toChain must be a string'
|
|
11444
11563
|
}).min(1, 'toChain must be a non-empty string if provided').optional(),
|
|
11445
|
-
apiKey: apiKeySchema
|
|
11564
|
+
apiKey: apiKeySchema.optional()
|
|
11446
11565
|
});
|
|
11447
11566
|
/**
|
|
11448
11567
|
* Zod schema for validating CreateSwapResponse payloads.
|
|
@@ -11451,13 +11570,15 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11451
11570
|
required_error: 'fee token is required',
|
|
11452
11571
|
invalid_type_error: 'fee token must be a string'
|
|
11453
11572
|
}).min(1, 'fee token must be a non-empty string'),
|
|
11454
|
-
amount: feeAmountSchema
|
|
11573
|
+
amount: feeAmountSchema,
|
|
11574
|
+
decimals: zod.z.number().int('fee token decimals must be an integer').nonnegative('fee token decimals must be a non-negative integer').optional(),
|
|
11575
|
+
symbol: zod.z.string({
|
|
11576
|
+
invalid_type_error: 'fee token symbol must be a string'
|
|
11577
|
+
}).min(1, 'fee token symbol must be a non-empty string').optional()
|
|
11455
11578
|
});
|
|
11456
11579
|
/**
|
|
11457
11580
|
* Developer fee item schema with basis field.
|
|
11458
|
-
*/ const createSwapDeveloperFeeItemSchema =
|
|
11459
|
-
token: zod.z.string().min(1, 'fee token must be a non-empty string'),
|
|
11460
|
-
amount: feeAmountSchema,
|
|
11581
|
+
*/ const createSwapDeveloperFeeItemSchema = createSwapFeeItemSchema.extend({
|
|
11461
11582
|
basis: zod.z.enum([
|
|
11462
11583
|
'inputAmount',
|
|
11463
11584
|
'estimatedAmount'
|
|
@@ -11549,7 +11670,7 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11549
11670
|
addresses: zod.z.array(zod.z.string({
|
|
11550
11671
|
invalid_type_error: 'addresses entries must be strings'
|
|
11551
11672
|
}).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(),
|
|
11552
|
-
apiKey: apiKeySchema
|
|
11673
|
+
apiKey: apiKeySchema.optional()
|
|
11553
11674
|
});
|
|
11554
11675
|
/**
|
|
11555
11676
|
* Zod schema for validating GetTokenRatesResponse payloads.
|
|
@@ -11580,6 +11701,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11580
11701
|
required_error: 'estimatedAmount is required',
|
|
11581
11702
|
invalid_type_error: 'estimatedAmount must be a string'
|
|
11582
11703
|
}).min(1, 'estimatedAmount must be a non-empty string'),
|
|
11704
|
+
// Per-swap join key echoed back verbatim on success telemetry. Optional so a
|
|
11705
|
+
// not-yet-upgraded service (no field) still validates during rollout. A
|
|
11706
|
+
// malformed/non-UUID value is coerced to `undefined` (no telemetry id) rather
|
|
11707
|
+
// than throwing: this is a telemetry-only field (stripped from the developer
|
|
11708
|
+
// result, never used for control flow), so it must not be able to abort the
|
|
11709
|
+
// swap via `parseCreateSwapResponse().parse()`. Mirrors the best-effort,
|
|
11710
|
+
// never-throw contract of the rest of the telemetry stack. Implemented with
|
|
11711
|
+
// `preprocess` rather than Zod's `.catch()` because static analysis misreads
|
|
11712
|
+
// `.catch` on the schema chain as an unhandled Promise (S7785).
|
|
11713
|
+
correlationId: zod.z.preprocess((value)=>zod.z.string().uuid().safeParse(value).success ? value : undefined, zod.z.string().optional()),
|
|
11583
11714
|
config: createSwapRequestBaseSchema.shape.config.optional(),
|
|
11584
11715
|
fees: createSwapFeesSchema.optional(),
|
|
11585
11716
|
transaction: createSwapTransactionSchema
|
|
@@ -11666,6 +11797,45 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11666
11797
|
* }
|
|
11667
11798
|
* ```
|
|
11668
11799
|
*/ const isGetTokenRatesResponse = (obj)=>getTokenRatesResponseSchema.safeParse(obj).success;
|
|
11800
|
+
/**
|
|
11801
|
+
* Assert that a Stablecoin Service kit key is not being supplied from a browser.
|
|
11802
|
+
*
|
|
11803
|
+
* The kit key (`KIT_KEY:<id>:<secret>`) is a server-only secret. Every
|
|
11804
|
+
* Stablecoin Service request that attaches an `Authorization: Bearer` header
|
|
11805
|
+
* funnels through this package, so calling this guard before that header is
|
|
11806
|
+
* built prevents the secret from being sent from — and thus bundled into — a
|
|
11807
|
+
* client application. In Node.js the check is a no-op, preserving the
|
|
11808
|
+
* legitimate "hold the kit key on the server, forward the prepared transaction
|
|
11809
|
+
* to the client" flow. When no kit key is supplied the permissionless (keyless)
|
|
11810
|
+
* client path remains fully allowed.
|
|
11811
|
+
*
|
|
11812
|
+
* @param apiKey - The inline kit key for the request, or `undefined` when none
|
|
11813
|
+
* was supplied (permissionless mode).
|
|
11814
|
+
* @returns Nothing.
|
|
11815
|
+
* @throws KitError with VALIDATION_FAILED when a kit key is supplied while
|
|
11816
|
+
* running in a browser environment. The secret value is never echoed.
|
|
11817
|
+
*
|
|
11818
|
+
* @example
|
|
11819
|
+
* ```typescript
|
|
11820
|
+
* import { assertBrowserSafeApiKey } from '@core/service-client'
|
|
11821
|
+
*
|
|
11822
|
+
* // Server (Node.js): no-op, request proceeds with the Authorization header.
|
|
11823
|
+
* assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
|
|
11824
|
+
*
|
|
11825
|
+
* // Browser: throws to stop the secret from leaking into the client bundle.
|
|
11826
|
+
* assertBrowserSafeApiKey('KIT_KEY:my-id:my-secret')
|
|
11827
|
+
*
|
|
11828
|
+
* // Browser, permissionless: allowed.
|
|
11829
|
+
* assertBrowserSafeApiKey(undefined)
|
|
11830
|
+
* ```
|
|
11831
|
+
*/ const assertBrowserSafeApiKey = (apiKey)=>{
|
|
11832
|
+
if (apiKey === undefined) {
|
|
11833
|
+
return;
|
|
11834
|
+
}
|
|
11835
|
+
if (isBrowserEnvironment()) {
|
|
11836
|
+
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');
|
|
11837
|
+
}
|
|
11838
|
+
};
|
|
11669
11839
|
|
|
11670
11840
|
/**
|
|
11671
11841
|
* Create a cross-chain bridge and swap transaction through the Stablecoin Service.
|
|
@@ -11723,11 +11893,16 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11723
11893
|
const url = new URL('/v1/stablecoinKits/swap', STABLECOIN_SERVICE_BASE_URL).toString();
|
|
11724
11894
|
// Remove the API key from the request body
|
|
11725
11895
|
const { apiKey, ...requestBody } = validatedParams;
|
|
11896
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
11897
|
+
assertBrowserSafeApiKey(apiKey);
|
|
11726
11898
|
const effectiveConfig = {
|
|
11727
11899
|
...DEFAULT_CONFIG,
|
|
11728
11900
|
headers: {
|
|
11729
11901
|
...DEFAULT_CONFIG.headers,
|
|
11730
|
-
|
|
11902
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
11903
|
+
...apiKey !== undefined && {
|
|
11904
|
+
Authorization: `Bearer ${apiKey}`
|
|
11905
|
+
}
|
|
11731
11906
|
}
|
|
11732
11907
|
};
|
|
11733
11908
|
try {
|
|
@@ -11874,6 +12049,8 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11874
12049
|
}
|
|
11875
12050
|
// Use validated data
|
|
11876
12051
|
const validatedParams = result.data;
|
|
12052
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
12053
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
11877
12054
|
// Build the API URL
|
|
11878
12055
|
const url = buildQuoteUrl(validatedParams);
|
|
11879
12056
|
// Merge default config with Authorization header
|
|
@@ -11881,7 +12058,10 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11881
12058
|
...DEFAULT_CONFIG,
|
|
11882
12059
|
headers: {
|
|
11883
12060
|
...DEFAULT_CONFIG.headers,
|
|
11884
|
-
|
|
12061
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
12062
|
+
...validatedParams.apiKey !== undefined && {
|
|
12063
|
+
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
12064
|
+
}
|
|
11885
12065
|
}
|
|
11886
12066
|
};
|
|
11887
12067
|
return pollApiGet(url, isGetQuoteResponse, effectiveConfig);
|
|
@@ -11936,17 +12116,24 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
11936
12116
|
const validatedParams = {
|
|
11937
12117
|
txHash: result.data.txHash,
|
|
11938
12118
|
chain: result.data.chain,
|
|
11939
|
-
|
|
12119
|
+
...result.data.apiKey !== undefined && {
|
|
12120
|
+
apiKey: result.data.apiKey
|
|
12121
|
+
},
|
|
11940
12122
|
...result.data.toChain !== undefined && {
|
|
11941
12123
|
toChain: result.data.toChain
|
|
11942
12124
|
}
|
|
11943
12125
|
};
|
|
12126
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
12127
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
11944
12128
|
const url = buildSwapStatusUrl(validatedParams);
|
|
11945
12129
|
const effectiveConfig = {
|
|
11946
12130
|
...DEFAULT_CONFIG,
|
|
11947
12131
|
headers: {
|
|
11948
12132
|
...DEFAULT_CONFIG.headers,
|
|
11949
|
-
|
|
12133
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
12134
|
+
...validatedParams.apiKey !== undefined && {
|
|
12135
|
+
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
12136
|
+
}
|
|
11950
12137
|
}
|
|
11951
12138
|
};
|
|
11952
12139
|
return pollApiGet(url, isGetSwapStatusResponse, effectiveConfig);
|
|
@@ -12035,17 +12222,24 @@ getQuoteRequestBaseSchema.superRefine(requireCrossChainQuoteToAddress);
|
|
|
12035
12222
|
}
|
|
12036
12223
|
const validatedParams = {
|
|
12037
12224
|
chain: result.data.chain,
|
|
12038
|
-
|
|
12225
|
+
...result.data.apiKey !== undefined && {
|
|
12226
|
+
apiKey: result.data.apiKey
|
|
12227
|
+
},
|
|
12039
12228
|
...result.data.addresses !== undefined && {
|
|
12040
12229
|
addresses: result.data.addresses
|
|
12041
12230
|
}
|
|
12042
12231
|
};
|
|
12232
|
+
// Never let a server-only kit key leave a browser (no-op in Node.js).
|
|
12233
|
+
assertBrowserSafeApiKey(validatedParams.apiKey);
|
|
12043
12234
|
const url = buildTokenRatesUrl(validatedParams);
|
|
12044
12235
|
const effectiveConfig = {
|
|
12045
12236
|
...DEFAULT_CONFIG,
|
|
12046
12237
|
headers: {
|
|
12047
12238
|
...DEFAULT_CONFIG.headers,
|
|
12048
|
-
|
|
12239
|
+
// Permissionless mode: no Authorization header when the kit key is absent.
|
|
12240
|
+
...validatedParams.apiKey !== undefined && {
|
|
12241
|
+
Authorization: `Bearer ${validatedParams.apiKey}`
|
|
12242
|
+
}
|
|
12049
12243
|
}
|
|
12050
12244
|
};
|
|
12051
12245
|
return pollApiGet(url, isGetTokenRatesResponse, effectiveConfig);
|
|
@@ -12855,6 +13049,47 @@ const S_HEX_LENGTH = 32 * HEX_CHARS_PER_BYTE$1 // 32 bytes for 's'
|
|
|
12855
13049
|
*/ function hasSignTypedData(adapter) {
|
|
12856
13050
|
return typeof adapter === 'object' && adapter !== null && 'signTypedData' in adapter && typeof adapter.signTypedData === 'function';
|
|
12857
13051
|
}
|
|
13052
|
+
/**
|
|
13053
|
+
* Type guard to check if an adapter can actually produce an EIP-712
|
|
13054
|
+
* typed-data signature.
|
|
13055
|
+
*
|
|
13056
|
+
* @remarks
|
|
13057
|
+
* Strengthens {@link hasSignTypedData}: having a `signTypedData` method
|
|
13058
|
+
* does not guarantee it can succeed. Adapters whose signer is delegated
|
|
13059
|
+
* (e.g. through a signing strategy backed by a smart contract account)
|
|
13060
|
+
* expose the method but reject typed-data payloads at runtime. Such
|
|
13061
|
+
* adapters report their real capability through an optional
|
|
13062
|
+
* `supportsSignTypedData()` method, which this guard consults when
|
|
13063
|
+
* present. Adapters without the capability method are assumed able to
|
|
13064
|
+
* sign, preserving the previous duck-typing behavior.
|
|
13065
|
+
*
|
|
13066
|
+
* @param adapter - The adapter to check
|
|
13067
|
+
* @returns True if calling `signTypedData` can be expected to succeed
|
|
13068
|
+
*
|
|
13069
|
+
* @example
|
|
13070
|
+
* ```typescript
|
|
13071
|
+
* import { canSignTypedData } from '@core/adapter-evm'
|
|
13072
|
+
*
|
|
13073
|
+
* if (canSignTypedData(adapter)) {
|
|
13074
|
+
* const signature = await adapter.signTypedData(typedData, context)
|
|
13075
|
+
* } else {
|
|
13076
|
+
* // take an on-chain approval path instead of a permit signature
|
|
13077
|
+
* }
|
|
13078
|
+
* ```
|
|
13079
|
+
*/ function canSignTypedData(adapter) {
|
|
13080
|
+
if (!hasSignTypedData(adapter)) {
|
|
13081
|
+
return false;
|
|
13082
|
+
}
|
|
13083
|
+
if (typeof adapter.supportsSignTypedData === 'function') {
|
|
13084
|
+
// The value is `boolean` per the interface, but a plain-JS adapter may
|
|
13085
|
+
// return anything; treat it as untrusted and coerce to a strict
|
|
13086
|
+
// boolean. Comparing an `unknown` (not a `boolean`) also keeps the
|
|
13087
|
+
// lint autofix from stripping this as a redundant `=== true`.
|
|
13088
|
+
const supported = adapter.supportsSignTypedData();
|
|
13089
|
+
return supported === true;
|
|
13090
|
+
}
|
|
13091
|
+
return true;
|
|
13092
|
+
}
|
|
12858
13093
|
|
|
12859
13094
|
/**
|
|
12860
13095
|
* Build EIP-2612 typed data for permit signing.
|
|
@@ -13277,10 +13512,13 @@ enc.encode('used_transfer_spec_hash');
|
|
|
13277
13512
|
* at usage time rather than construction time.
|
|
13278
13513
|
*
|
|
13279
13514
|
* Validates:
|
|
13280
|
-
* - Kit key
|
|
13515
|
+
* - Kit key matches the required format (KIT_KEY:id:secret) when provided.
|
|
13516
|
+
* An absent or empty kit key is permitted (permissionless mode) — the swap
|
|
13517
|
+
* service now treats the key as optional.
|
|
13281
13518
|
*
|
|
13282
|
-
* @param kitKey - The inline kit key from the swap operation config
|
|
13283
|
-
* @throws KitError with VALIDATION_FAILED if kit key is
|
|
13519
|
+
* @param kitKey - The inline kit key from the swap operation config (optional)
|
|
13520
|
+
* @throws KitError with VALIDATION_FAILED if a kit key is provided but does not
|
|
13521
|
+
* match the KIT_KEY:<keyId>:<keySecret> format
|
|
13284
13522
|
*
|
|
13285
13523
|
* @example
|
|
13286
13524
|
* ```typescript
|
|
@@ -13292,9 +13530,11 @@ enc.encode('used_transfer_spec_hash');
|
|
|
13292
13530
|
* assertKitKey(kitKey)
|
|
13293
13531
|
* ```
|
|
13294
13532
|
*/ function assertKitKey(kitKey) {
|
|
13295
|
-
//
|
|
13533
|
+
// Permissionless mode: the swap service treats the kit key as optional, so an
|
|
13534
|
+
// absent (or empty) key is valid. Only validate the format when a key is
|
|
13535
|
+
// actually provided.
|
|
13296
13536
|
if (!kitKey) {
|
|
13297
|
-
|
|
13537
|
+
return;
|
|
13298
13538
|
}
|
|
13299
13539
|
const apiKeyResult = apiKeySchema.safeParse(kitKey);
|
|
13300
13540
|
if (!apiKeyResult.success) {
|
|
@@ -13591,8 +13831,8 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
13591
13831
|
validateResolvedAddress(resolvedTokenInAddress, chain);
|
|
13592
13832
|
validateResolvedAddress(resolvedTokenOutAddress, destinationChain);
|
|
13593
13833
|
validateResolvedAddress(to, destinationChain);
|
|
13594
|
-
const kitKey = config?.kitKey
|
|
13595
|
-
//
|
|
13834
|
+
const kitKey = config?.kitKey;
|
|
13835
|
+
// Validate the kit key format when one is provided (permissionless otherwise).
|
|
13596
13836
|
assertKitKey(kitKey);
|
|
13597
13837
|
// Validate custom fee configuration if present
|
|
13598
13838
|
const customFee = config?.customFee;
|
|
@@ -13637,7 +13877,10 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
13637
13877
|
}
|
|
13638
13878
|
}
|
|
13639
13879
|
},
|
|
13640
|
-
apiKey
|
|
13880
|
+
// Map kitKey → apiKey for the service client; omitted in permissionless mode.
|
|
13881
|
+
...kitKey ? {
|
|
13882
|
+
apiKey: kitKey
|
|
13883
|
+
} : {}
|
|
13641
13884
|
};
|
|
13642
13885
|
}
|
|
13643
13886
|
|
|
@@ -14291,6 +14534,37 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
14291
14534
|
}
|
|
14292
14535
|
}
|
|
14293
14536
|
|
|
14537
|
+
/**
|
|
14538
|
+
* Determine whether an adapter can produce an EIP-2612 permit signature.
|
|
14539
|
+
*
|
|
14540
|
+
* @remarks
|
|
14541
|
+
* A gasless permit needs two adapter capabilities: fetching the token's
|
|
14542
|
+
* EIP-2612 nonce and producing an EIP-712 typed-data signature. The
|
|
14543
|
+
* typed-data check uses {@link canSignTypedData} rather than a bare
|
|
14544
|
+
* `hasSignTypedData` guard so that an adapter routed through a signing
|
|
14545
|
+
* strategy that cannot produce typed-data signatures — one whose manifest
|
|
14546
|
+
* omits `evm-typed-data`, surfaced through an optional `supportsSignTypedData()`
|
|
14547
|
+
* — is correctly excluded. Such an adapter falls back to an on-chain approval
|
|
14548
|
+
* (batched into a single submission when it supports atomic execution) instead
|
|
14549
|
+
* of attempting a permit its strategy would reject.
|
|
14550
|
+
*
|
|
14551
|
+
* @param adapter - The source adapter to inspect.
|
|
14552
|
+
* @returns `true` when the adapter can both fetch a nonce and sign typed data.
|
|
14553
|
+
*
|
|
14554
|
+
* @example
|
|
14555
|
+
* ```typescript
|
|
14556
|
+
* import { adapterSupportsPermit } from './utils'
|
|
14557
|
+
*
|
|
14558
|
+
* if (adapterSupportsPermit(adapter)) {
|
|
14559
|
+
* // gasless permit path — fold the approval into the swap transaction
|
|
14560
|
+
* } else {
|
|
14561
|
+
* // on-chain approval path (batched when supportsAtomicBatch is true)
|
|
14562
|
+
* }
|
|
14563
|
+
* ```
|
|
14564
|
+
*/ function adapterSupportsPermit(adapter) {
|
|
14565
|
+
return hasEIP2612NonceFetching(adapter) && canSignTypedData(adapter);
|
|
14566
|
+
}
|
|
14567
|
+
|
|
14294
14568
|
/**
|
|
14295
14569
|
* Generate EIP-2612 permit signature for token approval.
|
|
14296
14570
|
*
|
|
@@ -14426,8 +14700,7 @@ function resolveFeePayoutChain(tokenIn, tokenOut, sourceChain, destinationChain)
|
|
|
14426
14700
|
}
|
|
14427
14701
|
// Skip permit generation if the adapter lacks the required capabilities.
|
|
14428
14702
|
// handleEvmTokenApproval will have already sent an on-chain approval in this case.
|
|
14429
|
-
|
|
14430
|
-
if (!adapterSupportsPermit) {
|
|
14703
|
+
if (!adapterSupportsPermit(adapter)) {
|
|
14431
14704
|
return [
|
|
14432
14705
|
createFallbackTokenInput(tokenInAddress, inputAmount)
|
|
14433
14706
|
];
|
|
@@ -14986,6 +15259,65 @@ const TOKEN_REGISTRY$2 = createTokenRegistry();
|
|
|
14986
15259
|
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.`;
|
|
14987
15260
|
}
|
|
14988
15261
|
|
|
15262
|
+
/**
|
|
15263
|
+
* Determine which chain a fee token should be resolved and formatted against.
|
|
15264
|
+
*
|
|
15265
|
+
* @remarks
|
|
15266
|
+
* Fees returned by the service may be denominated in either the input token
|
|
15267
|
+
* (on the source chain) or the output token (on the destination chain). A
|
|
15268
|
+
* contract address only resolves on the chain it belongs to, so formatting a
|
|
15269
|
+
* destination-denominated fee against the source chain causes
|
|
15270
|
+
* {@link resolveTokenSymbol} to miss and the amount to be returned as raw base
|
|
15271
|
+
* units (e.g. a cross-chain swap charging a fee in the destination output
|
|
15272
|
+
* token — an EURC-on-Base address shows `'13202'` instead of `'0.013202'` when
|
|
15273
|
+
* resolved against the source chain). This is the fallback for fee items that
|
|
15274
|
+
* are not self-described with their own `decimals`/`chain`.
|
|
15275
|
+
*
|
|
15276
|
+
* Prefer the source chain (covers same-chain swaps and input-denominated
|
|
15277
|
+
* fees), then fall back to the destination chain when the token only resolves
|
|
15278
|
+
* there. When neither chain recognises the token, default to the source chain
|
|
15279
|
+
* so existing on-chain decimal lookups via the source adapter still apply.
|
|
15280
|
+
*
|
|
15281
|
+
* Symbol tokens (`'USDC'`, `'NATIVE'`) resolve on either chain, so the
|
|
15282
|
+
* source-first preference keeps them on the source chain. That is correct for
|
|
15283
|
+
* registry stablecoins, and for `'NATIVE'` only when both chains share native
|
|
15284
|
+
* decimals (EVM↔EVM, 18). It does NOT honor per-chain native decimals: a
|
|
15285
|
+
* `'NATIVE'`-denominated fee on a Solana↔EVM swap (9 vs 18) would be
|
|
15286
|
+
* mis-scaled. This is latent — providers emit the address form, and
|
|
15287
|
+
* self-describing fee items carry their own `decimals` and never reach this
|
|
15288
|
+
* helper — so the gap only opens for a future `'NATIVE'` fee that arrives
|
|
15289
|
+
* without `decimals` on a cross-native-decimal route.
|
|
15290
|
+
*
|
|
15291
|
+
* @param token - The fee token identifier — a symbol (`'USDC'`) or contract address.
|
|
15292
|
+
* @param sourceChain - The chain the swap originates from.
|
|
15293
|
+
* @param destinationChain - The chain the swap settles on (equals `sourceChain` for same-chain swaps).
|
|
15294
|
+
* @returns The chain definition the fee token should be resolved against.
|
|
15295
|
+
*
|
|
15296
|
+
* @example
|
|
15297
|
+
* ```typescript
|
|
15298
|
+
* import { resolveFeeChain } from './resolveFeeChain'
|
|
15299
|
+
* import { Ethereum, Base } from '@core/chains'
|
|
15300
|
+
*
|
|
15301
|
+
* // Cross-chain swap fee charged in the destination (output) token
|
|
15302
|
+
* resolveFeeChain('0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42', Ethereum, Base)
|
|
15303
|
+
* // => Base (EURC resolves on Base, not Ethereum)
|
|
15304
|
+
*
|
|
15305
|
+
* // Symbol or source-token fees stay on the source chain
|
|
15306
|
+
* resolveFeeChain('USDC', Ethereum, Base) // => Ethereum
|
|
15307
|
+
* ```
|
|
15308
|
+
*/ function resolveFeeChain(token, sourceChain, destinationChain) {
|
|
15309
|
+
if (sourceChain.chain === destinationChain.chain) {
|
|
15310
|
+
return sourceChain;
|
|
15311
|
+
}
|
|
15312
|
+
if (resolveTokenSymbol(token, sourceChain) !== null) {
|
|
15313
|
+
return sourceChain;
|
|
15314
|
+
}
|
|
15315
|
+
if (resolveTokenSymbol(token, destinationChain) !== null) {
|
|
15316
|
+
return destinationChain;
|
|
15317
|
+
}
|
|
15318
|
+
return sourceChain;
|
|
15319
|
+
}
|
|
15320
|
+
|
|
14989
15321
|
const TOKEN_REGISTRY$1 = createTokenRegistry();
|
|
14990
15322
|
/**
|
|
14991
15323
|
* Format a raw base-unit amount into a human-readable decimal string.
|
|
@@ -15076,6 +15408,186 @@ const TOKEN_REGISTRY$1 = createTokenRegistry();
|
|
|
15076
15408
|
}
|
|
15077
15409
|
}
|
|
15078
15410
|
|
|
15411
|
+
/**
|
|
15412
|
+
* Runtime guard for {@link BatchCapableSwapAdapter}.
|
|
15413
|
+
*
|
|
15414
|
+
* @param adapter - The adapter to inspect.
|
|
15415
|
+
* @returns `true` when the adapter exposes both batch methods.
|
|
15416
|
+
*
|
|
15417
|
+
* @example
|
|
15418
|
+
* ```typescript
|
|
15419
|
+
* if (isBatchCapableSwapAdapter(adapter)) {
|
|
15420
|
+
* // adapter.supportsAtomicBatch / adapter.batchExecute are available
|
|
15421
|
+
* }
|
|
15422
|
+
* ```
|
|
15423
|
+
*/ function isBatchCapableSwapAdapter(adapter) {
|
|
15424
|
+
return typeof adapter === 'object' && adapter !== null && typeof adapter.supportsAtomicBatch === 'function' && typeof adapter.batchExecute === 'function';
|
|
15425
|
+
}
|
|
15426
|
+
/**
|
|
15427
|
+
* Decide whether the EVM swap should take the batched approve-and-swap path.
|
|
15428
|
+
*
|
|
15429
|
+
* @remarks
|
|
15430
|
+
* Batching only helps when an on-chain approval would otherwise be required, so
|
|
15431
|
+
* it is skipped for native tokens (no approval) and for the gasless permit path
|
|
15432
|
+
* (already a single transaction). USDT is skipped because its reset-to-zero
|
|
15433
|
+
* allowance flow cannot be expressed as a fixed approve+swap pair. When those
|
|
15434
|
+
* gates pass, the adapter's actual atomic-batch capability is queried; any
|
|
15435
|
+
* failure resolves to `false` so the swap falls back to the sequential path.
|
|
15436
|
+
*
|
|
15437
|
+
* @param args - The decision inputs.
|
|
15438
|
+
* @param args.adapter - The source adapter.
|
|
15439
|
+
* @param args.chain - The source chain definition.
|
|
15440
|
+
* @param args.tokenInAddress - The resolved input-token address.
|
|
15441
|
+
* @param args.allowanceStrategy - Optional allowance strategy override.
|
|
15442
|
+
* @param args.batchTransactions - Optional explicit opt-out (`false` disables).
|
|
15443
|
+
* @returns `true` when the batched approve-and-swap path should be used.
|
|
15444
|
+
*
|
|
15445
|
+
* @example
|
|
15446
|
+
* ```typescript
|
|
15447
|
+
* const useBatched = await shouldUseBatchedSwap({
|
|
15448
|
+
* adapter,
|
|
15449
|
+
* chain,
|
|
15450
|
+
* tokenInAddress: '0xA0b8...',
|
|
15451
|
+
* allowanceStrategy: config?.allowanceStrategy,
|
|
15452
|
+
* batchTransactions: config?.batchTransactions,
|
|
15453
|
+
* })
|
|
15454
|
+
* ```
|
|
15455
|
+
*/ async function shouldUseBatchedSwap({ adapter, chain, tokenInAddress, allowanceStrategy, batchTransactions }) {
|
|
15456
|
+
// Explicit opt-out.
|
|
15457
|
+
if (batchTransactions === false) {
|
|
15458
|
+
return false;
|
|
15459
|
+
}
|
|
15460
|
+
// Batching is an EVM capability (EIP-5792 or a signing strategy).
|
|
15461
|
+
if (chain.type !== 'evm') {
|
|
15462
|
+
return false;
|
|
15463
|
+
}
|
|
15464
|
+
// Native tokens need no approval — the swap is already a single transaction.
|
|
15465
|
+
if (isNativeEvmAddress(tokenInAddress)) {
|
|
15466
|
+
return false;
|
|
15467
|
+
}
|
|
15468
|
+
// A gasless permit folds the approval into the swap transaction, so there is
|
|
15469
|
+
// nothing to batch. Mirrors the permit gate in handleEvmTokenApproval.
|
|
15470
|
+
const canUsePermit = allowanceStrategy !== 'approve' && supportsEIP2612(tokenInAddress, chain) && adapterSupportsPermit(adapter);
|
|
15471
|
+
if (canUsePermit) {
|
|
15472
|
+
return false;
|
|
15473
|
+
}
|
|
15474
|
+
// USDT's reset-to-zero allowance dance cannot be expressed as a fixed
|
|
15475
|
+
// approve+swap pair; leave it on the sequential path.
|
|
15476
|
+
const usdt = chain.usdtAddress?.toLowerCase();
|
|
15477
|
+
if (usdt !== undefined && tokenInAddress.toLowerCase() === usdt) {
|
|
15478
|
+
return false;
|
|
15479
|
+
}
|
|
15480
|
+
if (!isBatchCapableSwapAdapter(adapter)) {
|
|
15481
|
+
return false;
|
|
15482
|
+
}
|
|
15483
|
+
try {
|
|
15484
|
+
return await adapter.supportsAtomicBatch(chain);
|
|
15485
|
+
} catch {
|
|
15486
|
+
return false;
|
|
15487
|
+
}
|
|
15488
|
+
}
|
|
15489
|
+
/**
|
|
15490
|
+
* Execute the approval and swap as a single atomic batch.
|
|
15491
|
+
*
|
|
15492
|
+
* @remarks
|
|
15493
|
+
* Extracts the raw call data from both prepared requests, submits them as one
|
|
15494
|
+
* batch via `adapter.batchExecute`, and maps the swap receipt back to a
|
|
15495
|
+
* transaction hash. The `fromAddress` is threaded for adapters routed through a
|
|
15496
|
+
* signing strategy (which have no wallet account to read the sender from); the
|
|
15497
|
+
* wallet-client path ignores it.
|
|
15498
|
+
*
|
|
15499
|
+
* Following the batch contract, `batchExecute` never throws once the batch is
|
|
15500
|
+
* submitted — a missing or failed swap receipt is surfaced here as a thrown
|
|
15501
|
+
* {@link KitError} (FATAL) so the caller does not resubmit an already-broadcast
|
|
15502
|
+
* batch and double-swap.
|
|
15503
|
+
*
|
|
15504
|
+
* @param args - The execution inputs.
|
|
15505
|
+
* @param args.adapter - The batch-capable source adapter.
|
|
15506
|
+
* @param args.chain - The EVM chain to execute on.
|
|
15507
|
+
* @param args.approveRequest - The prepared ERC-20 approval request.
|
|
15508
|
+
* @param args.swapRequest - The prepared swap request (pre-approval / NONE permit).
|
|
15509
|
+
* @param args.fromAddress - The address authorizing the batch.
|
|
15510
|
+
* @returns The swap transaction hash and the executed approval + swap records.
|
|
15511
|
+
* @throws {@link KitError} when the prepared requests cannot yield call data.
|
|
15512
|
+
* @throws {@link KitError} when the batch does not confirm or the swap reverts.
|
|
15513
|
+
*
|
|
15514
|
+
* @example
|
|
15515
|
+
* ```typescript
|
|
15516
|
+
* const { swapTxHash, executedTransactions } = await executeBatchedApproveAndSwap({
|
|
15517
|
+
* adapter,
|
|
15518
|
+
* chain,
|
|
15519
|
+
* approveRequest,
|
|
15520
|
+
* swapRequest,
|
|
15521
|
+
* fromAddress: '0x742d...',
|
|
15522
|
+
* })
|
|
15523
|
+
* ```
|
|
15524
|
+
*/ async function executeBatchedApproveAndSwap({ adapter, chain, approveRequest, swapRequest, fromAddress }) {
|
|
15525
|
+
if (approveRequest.type !== 'evm' || swapRequest.type !== 'evm' || !approveRequest.getCallData || !swapRequest.getCallData) {
|
|
15526
|
+
throw new KitError({
|
|
15527
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
15528
|
+
recoverability: 'FATAL',
|
|
15529
|
+
message: 'Batched swap requires EVM prepared requests with getCallData() support.'
|
|
15530
|
+
});
|
|
15531
|
+
}
|
|
15532
|
+
const approveCallData = approveRequest.getCallData();
|
|
15533
|
+
const swapCallData = swapRequest.getCallData();
|
|
15534
|
+
const batchResult = await adapter.batchExecute([
|
|
15535
|
+
approveCallData,
|
|
15536
|
+
swapCallData
|
|
15537
|
+
], chain, {
|
|
15538
|
+
fromAddress
|
|
15539
|
+
});
|
|
15540
|
+
const swapReceipt = batchResult.receipts[1];
|
|
15541
|
+
// A missing swap receipt means the batch never confirmed (polling timed out
|
|
15542
|
+
// or the wallet returned fewer receipts than calls). Re-throw the underlying
|
|
15543
|
+
// error when present (already FATAL); otherwise surface a FATAL timeout so the
|
|
15544
|
+
// caller checks the batch status rather than resubmitting.
|
|
15545
|
+
if (swapReceipt === undefined || swapReceipt.txHash === '') {
|
|
15546
|
+
if (isKitError(batchResult.error)) {
|
|
15547
|
+
throw batchResult.error;
|
|
15548
|
+
}
|
|
15549
|
+
throw new KitError({
|
|
15550
|
+
...NetworkError.TIMEOUT,
|
|
15551
|
+
recoverability: 'FATAL',
|
|
15552
|
+
message: `Batched swap did not confirm on-chain (batchId: ${batchResult.batchId}). ` + 'The batch was already submitted — check its status before retrying.',
|
|
15553
|
+
// Preserve the underlying confirmation failure when it isn't a KitError —
|
|
15554
|
+
// the signing-strategy path returns a raw viem error (e.g. a dropped or
|
|
15555
|
+
// replaced tx) — so the root cause survives behind the generic timeout.
|
|
15556
|
+
cause: {
|
|
15557
|
+
trace: {
|
|
15558
|
+
batchId: batchResult.batchId,
|
|
15559
|
+
...batchResult.error != null && {
|
|
15560
|
+
error: batchResult.error
|
|
15561
|
+
}
|
|
15562
|
+
}
|
|
15563
|
+
}
|
|
15564
|
+
});
|
|
15565
|
+
}
|
|
15566
|
+
if (swapReceipt.status !== 'success') {
|
|
15567
|
+
throw createTransactionRevertedError(chain.name, 'Batched swap transaction reverted on-chain', undefined, swapReceipt.txHash, buildExplorerUrl(chain, swapReceipt.txHash));
|
|
15568
|
+
}
|
|
15569
|
+
const executedTransactions = [];
|
|
15570
|
+
const approveReceipt = batchResult.receipts[0];
|
|
15571
|
+
// An atomic batch is a single on-chain transaction, so the approve and swap
|
|
15572
|
+
// receipts share one hash. Only surface a distinct approval record when it is
|
|
15573
|
+
// genuinely a separate transaction; otherwise the lone swap record represents
|
|
15574
|
+
// the batch, avoiding a phantom duplicate tx in executedTransactions.
|
|
15575
|
+
if (approveReceipt !== undefined && approveReceipt.txHash !== '' && approveReceipt.txHash !== swapReceipt.txHash) {
|
|
15576
|
+
executedTransactions.push({
|
|
15577
|
+
type: 'approval',
|
|
15578
|
+
txHash: approveReceipt.txHash
|
|
15579
|
+
});
|
|
15580
|
+
}
|
|
15581
|
+
executedTransactions.push({
|
|
15582
|
+
type: 'swap',
|
|
15583
|
+
txHash: swapReceipt.txHash
|
|
15584
|
+
});
|
|
15585
|
+
return {
|
|
15586
|
+
swapTxHash: swapReceipt.txHash,
|
|
15587
|
+
executedTransactions
|
|
15588
|
+
};
|
|
15589
|
+
}
|
|
15590
|
+
|
|
15079
15591
|
/**
|
|
15080
15592
|
* Safety multiplier applied to locally estimated gas for EVM swap execution.
|
|
15081
15593
|
* Derived from refund cap (max 1/5 of total gas used) plus an extra 0.1 margin,
|
|
@@ -15202,7 +15714,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
15202
15714
|
const statusResult = await getSwapStatus$1({
|
|
15203
15715
|
txHash,
|
|
15204
15716
|
chain: chain.chain,
|
|
15205
|
-
apiKey
|
|
15717
|
+
...apiKey !== undefined && {
|
|
15718
|
+
apiKey
|
|
15719
|
+
}
|
|
15206
15720
|
});
|
|
15207
15721
|
if (statusResult.status === 'DONE' && statusResult.amountOut !== undefined) {
|
|
15208
15722
|
return {
|
|
@@ -15793,8 +16307,7 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
15793
16307
|
// Note: tokenInAddress from executionCtx is already resolved (handles NATIVE alias, ETH, etc.)
|
|
15794
16308
|
const isNativeToken = isNativeEvmAddress(executionCtx.tokenInAddress);
|
|
15795
16309
|
const tokenSupportsPermit = supportsEIP2612(executionCtx.tokenInAddress, chain);
|
|
15796
|
-
const
|
|
15797
|
-
const canUsePermitFlow = tokenSupportsPermit && adapterSupportsPermit && allowanceStrategy !== 'approve';
|
|
16310
|
+
const canUsePermitFlow = tokenSupportsPermit && adapterSupportsPermit(adapter) && allowanceStrategy !== 'approve';
|
|
15798
16311
|
const needsApproval = !isNativeToken && !canUsePermitFlow;
|
|
15799
16312
|
if (!needsApproval) {
|
|
15800
16313
|
return;
|
|
@@ -16073,34 +16586,55 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16073
16586
|
const serviceResponse = await createSwap(serviceParams);
|
|
16074
16587
|
// Track executed transactions
|
|
16075
16588
|
const executedTransactions = [];
|
|
16076
|
-
// Prepare swap action based on chain type
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
}
|
|
16589
|
+
// Prepare swap action(s) based on chain type and batch capability.
|
|
16590
|
+
// Returns either a single prepared action (Solana / sequential EVM) or a
|
|
16591
|
+
// batched approve+swap plan (EVM atomic-batch path).
|
|
16592
|
+
const { preparedAction, batchedSwapPlan } = await this.prepareSwapRequests({
|
|
16593
|
+
adapter,
|
|
16594
|
+
chain,
|
|
16595
|
+
serviceResponse,
|
|
16596
|
+
resolvedContext,
|
|
16597
|
+
executionCtx,
|
|
16598
|
+
config,
|
|
16599
|
+
executedTransactions
|
|
16600
|
+
});
|
|
16089
16601
|
// Execute swap transaction via adapter
|
|
16090
16602
|
// For EVM chains, use gas limit from proxy service API
|
|
16091
16603
|
let txHash;
|
|
16092
16604
|
const evmGasLimit = 'gasLimit' in serviceResponse.transaction ? serviceResponse.transaction.gasLimit : undefined;
|
|
16093
16605
|
try {
|
|
16094
|
-
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
|
|
16103
|
-
|
|
16606
|
+
if (batchedSwapPlan) {
|
|
16607
|
+
// Approve + swap submitted as one atomic batch. batchExecute confirms
|
|
16608
|
+
// the swap internally, so no separate waitForTransaction is needed.
|
|
16609
|
+
const batched = await executeBatchedApproveAndSwap({
|
|
16610
|
+
adapter: adapter,
|
|
16611
|
+
chain: chain,
|
|
16612
|
+
approveRequest: batchedSwapPlan.approveRequest,
|
|
16613
|
+
swapRequest: batchedSwapPlan.swapRequest,
|
|
16614
|
+
fromAddress: executionCtx.fromAddress
|
|
16615
|
+
});
|
|
16616
|
+
txHash = batched.swapTxHash;
|
|
16617
|
+
executedTransactions.push(...batched.executedTransactions);
|
|
16618
|
+
} else if (preparedAction) {
|
|
16619
|
+
txHash = await this.executeSwapTransaction(preparedAction, evmGasLimit);
|
|
16620
|
+
executedTransactions.push({
|
|
16621
|
+
type: 'swap',
|
|
16622
|
+
txHash
|
|
16623
|
+
});
|
|
16624
|
+
// Wait for transaction confirmation and verify success
|
|
16625
|
+
const txReceipt = await adapter.waitForTransaction(txHash, undefined, chain);
|
|
16626
|
+
if (txReceipt.status === 'reverted') {
|
|
16627
|
+
const explorerUrl = buildExplorerUrl(chain, txHash);
|
|
16628
|
+
throw createTransactionRevertedError(chain.name, 'Swap transaction reverted on-chain', undefined, txHash, explorerUrl);
|
|
16629
|
+
}
|
|
16630
|
+
} else {
|
|
16631
|
+
// Unreachable: the preparation step always yields either a batched plan
|
|
16632
|
+
// or a prepared action.
|
|
16633
|
+
throw new KitError({
|
|
16634
|
+
...InputError.UNSUPPORTED_ACTION,
|
|
16635
|
+
recoverability: 'FATAL',
|
|
16636
|
+
message: 'No swap execution path was prepared.'
|
|
16637
|
+
});
|
|
16104
16638
|
}
|
|
16105
16639
|
} catch (err) {
|
|
16106
16640
|
handleSwapExecutionError(err, txHash, chain);
|
|
@@ -16119,8 +16653,15 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16119
16653
|
isCrossChainSwap,
|
|
16120
16654
|
txHash,
|
|
16121
16655
|
chain,
|
|
16122
|
-
|
|
16656
|
+
...serviceParams.apiKey !== undefined && {
|
|
16657
|
+
apiKey: serviceParams.apiKey
|
|
16658
|
+
}
|
|
16123
16659
|
});
|
|
16660
|
+
// Per-swap correlation id returned by the service as a top-level response
|
|
16661
|
+
// field for every chain (EVM + Solana). Attached to success telemetry so a
|
|
16662
|
+
// swap can be correlated across records; never used for control flow.
|
|
16663
|
+
// Undefined only against a not-yet-upgraded service that omits it.
|
|
16664
|
+
const correlationId = serviceResponse.correlationId;
|
|
16124
16665
|
// Build and return SwapResult
|
|
16125
16666
|
return {
|
|
16126
16667
|
tokenIn,
|
|
@@ -16130,6 +16671,9 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16130
16671
|
fromAddress: serviceParams.fromAddress,
|
|
16131
16672
|
toAddress: serviceParams.toAddress,
|
|
16132
16673
|
txHash,
|
|
16674
|
+
...correlationId !== undefined && {
|
|
16675
|
+
correlationId
|
|
16676
|
+
},
|
|
16133
16677
|
executedTransactions,
|
|
16134
16678
|
...config !== undefined && {
|
|
16135
16679
|
config
|
|
@@ -16144,6 +16688,79 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16144
16688
|
};
|
|
16145
16689
|
}
|
|
16146
16690
|
/**
|
|
16691
|
+
* Prepare the swap execution request(s) for the source wallet's chain.
|
|
16692
|
+
*
|
|
16693
|
+
* Produces either a single {@link PreparedChainRequest} (Solana, or the
|
|
16694
|
+
* sequential EVM approve-then-swap path) or a `batchedSwapPlan` (the EVM
|
|
16695
|
+
* atomic approve+swap path chosen when the adapter supports EIP-5792 atomic
|
|
16696
|
+
* batching). The caller executes whichever field is populated. Any on-chain
|
|
16697
|
+
* approval sent on the sequential path is appended to `executedTransactions`.
|
|
16698
|
+
*
|
|
16699
|
+
* @typeParam TFromAdapterCapabilities - Source-adapter capability set.
|
|
16700
|
+
* @param args - Inputs derived from the validated swap request.
|
|
16701
|
+
* @param args.adapter - Source-chain wallet adapter.
|
|
16702
|
+
* @param args.chain - Source chain definition.
|
|
16703
|
+
* @param args.serviceResponse - Validated createSwap response.
|
|
16704
|
+
* @param args.resolvedContext - Resolved operation context.
|
|
16705
|
+
* @param args.executionCtx - Minimal on-chain execution context.
|
|
16706
|
+
* @param args.config - Optional swap configuration (allowance/batch flags).
|
|
16707
|
+
* @param args.executedTransactions - Array appended with any sent approval.
|
|
16708
|
+
* @returns The prepared action or the batched approve+swap plan.
|
|
16709
|
+
* @throws KitError when the EVM atomic-batch path is selected but the chain
|
|
16710
|
+
* has no configured adapter contract.
|
|
16711
|
+
*/ async prepareSwapRequests(args) {
|
|
16712
|
+
const { adapter, chain, serviceResponse, resolvedContext, executionCtx, config, executedTransactions } = args;
|
|
16713
|
+
if (chain.type === 'solana') {
|
|
16714
|
+
// Solana: No approval needed, directly prepare swap action
|
|
16715
|
+
return {
|
|
16716
|
+
preparedAction: await prepareSolanaSwapAction(adapter, serviceResponse, resolvedContext)
|
|
16717
|
+
};
|
|
16718
|
+
}
|
|
16719
|
+
const useBatch = await shouldUseBatchedSwap({
|
|
16720
|
+
adapter,
|
|
16721
|
+
chain,
|
|
16722
|
+
tokenInAddress: executionCtx.tokenInAddress,
|
|
16723
|
+
allowanceStrategy: config?.allowanceStrategy,
|
|
16724
|
+
batchTransactions: config?.batchTransactions
|
|
16725
|
+
});
|
|
16726
|
+
if (useBatch) {
|
|
16727
|
+
// EVM chains: fuse the ERC-20 approval and the swap into a single atomic
|
|
16728
|
+
// batch (one signing challenge for smart-contract wallets). Force the
|
|
16729
|
+
// swap onto the pre-approval (PermitType.NONE) path since the approval
|
|
16730
|
+
// rides in the same batch.
|
|
16731
|
+
const adapterContractAddress = chain.kitContracts?.adapter;
|
|
16732
|
+
if (!adapterContractAddress) {
|
|
16733
|
+
throw new KitError({
|
|
16734
|
+
...InputError.VALIDATION_FAILED,
|
|
16735
|
+
recoverability: 'FATAL',
|
|
16736
|
+
message: `Adapter contract not configured for chain ${chain.name}. Swap operations require an adapter contract.`,
|
|
16737
|
+
cause: {
|
|
16738
|
+
trace: {
|
|
16739
|
+
chain: chain.name
|
|
16740
|
+
}
|
|
16741
|
+
}
|
|
16742
|
+
});
|
|
16743
|
+
}
|
|
16744
|
+
const [approveRequest, swapRequest] = await Promise.all([
|
|
16745
|
+
this.approve(adapter, executionCtx.amount, executionCtx.tokenInAddress, adapterContractAddress, resolvedContext),
|
|
16746
|
+
prepareEvmSwapAction(adapter, serviceResponse, resolvedContext, 'approve')
|
|
16747
|
+
]);
|
|
16748
|
+
return {
|
|
16749
|
+
batchedSwapPlan: {
|
|
16750
|
+
approveRequest,
|
|
16751
|
+
swapRequest
|
|
16752
|
+
}
|
|
16753
|
+
};
|
|
16754
|
+
}
|
|
16755
|
+
// EVM chains: Handle token approval if needed, then prepare the swap.
|
|
16756
|
+
// prepareEvmSwapAction handles EIP-2612 permit generation; the adapter
|
|
16757
|
+
// contract address is read from chain.kitContracts.adapter.
|
|
16758
|
+
await this.handleEvmTokenApproval(adapter, chain, executionCtx, resolvedContext, executedTransactions, config?.allowanceStrategy);
|
|
16759
|
+
return {
|
|
16760
|
+
preparedAction: await prepareEvmSwapAction(adapter, serviceResponse, resolvedContext, config?.allowanceStrategy)
|
|
16761
|
+
};
|
|
16762
|
+
}
|
|
16763
|
+
/**
|
|
16147
16764
|
* Executes a swap transaction with the appropriate gas limit for the chain type.
|
|
16148
16765
|
*
|
|
16149
16766
|
* For EVM chains, performs a local eth_estimateGas call, applies a 1.3x safety
|
|
@@ -16192,8 +16809,8 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16192
16809
|
*/ async buildFormattedFees(fees, chain, destinationChain, adapter, recipientAddress) {
|
|
16193
16810
|
if (!fees) return [];
|
|
16194
16811
|
const [providerFees, swapFees, developerFees] = await Promise.all([
|
|
16195
|
-
this.formatServiceFees(fees.provider, chain, 'provider', adapter),
|
|
16196
|
-
this.formatServiceFees(fees.swap, chain, 'swap', adapter),
|
|
16812
|
+
this.formatServiceFees(fees.provider, chain, destinationChain, 'provider', adapter),
|
|
16813
|
+
this.formatServiceFees(fees.swap, chain, destinationChain, 'swap', adapter),
|
|
16197
16814
|
recipientAddress ? this.formatDeveloperFees(fees.developer, chain, destinationChain, recipientAddress, adapter) : Promise.resolve([])
|
|
16198
16815
|
]);
|
|
16199
16816
|
return [
|
|
@@ -16203,6 +16820,45 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16203
16820
|
];
|
|
16204
16821
|
}
|
|
16205
16822
|
/**
|
|
16823
|
+
* Resolve a single fee item to its display token and human-readable amount.
|
|
16824
|
+
*
|
|
16825
|
+
* @remarks
|
|
16826
|
+
* Prefer the self-describing metadata the service attaches to each fee:
|
|
16827
|
+
* `decimals` (and `symbol`) come straight from the provider quote, so they
|
|
16828
|
+
* are authoritative even for a token absent from the SDK registry on both
|
|
16829
|
+
* chains. That is the case {@link resolveFeeChain} cannot recover — a
|
|
16830
|
+
* destination-denominated fee token resolves on neither the source registry
|
|
16831
|
+
* nor the source-bound adapter, leaving the amount as raw base units. When
|
|
16832
|
+
* the service omits `decimals` (optional during rollout), fall back to
|
|
16833
|
+
* inferring the fee token's chain and resolving via the registry/adapter.
|
|
16834
|
+
*
|
|
16835
|
+
* Like {@link formatTokenValue}, this never throws: fee display is cosmetic
|
|
16836
|
+
* and must not fail an estimate/swap. A malformed self-describing `decimals`
|
|
16837
|
+
* (e.g. a non-numeric `amount` or invalid decimal count that makes
|
|
16838
|
+
* {@link formatUnits} throw) falls through to chain-based resolution rather
|
|
16839
|
+
* than propagating out of {@link buildFormattedFees}.
|
|
16840
|
+
*
|
|
16841
|
+
* @param fee - The fee item from the service response.
|
|
16842
|
+
* @param chain - The source chain definition.
|
|
16843
|
+
* @param destinationChain - The destination chain definition.
|
|
16844
|
+
* @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
|
|
16845
|
+
* @returns Promise resolving to the formatted amount and display token.
|
|
16846
|
+
*/ async formatFeeValue(fee, chain, destinationChain, adapter) {
|
|
16847
|
+
if (fee.decimals != null) {
|
|
16848
|
+
try {
|
|
16849
|
+
return {
|
|
16850
|
+
amount: formatUnits(fee.amount, fee.decimals),
|
|
16851
|
+
token: fee.symbol ?? fee.token
|
|
16852
|
+
};
|
|
16853
|
+
} catch {
|
|
16854
|
+
// Malformed service metadata — fall through to chain-based resolution,
|
|
16855
|
+
// which never throws (worst case: raw passthrough).
|
|
16856
|
+
}
|
|
16857
|
+
}
|
|
16858
|
+
const feeChain = resolveFeeChain(fee.token, chain, destinationChain);
|
|
16859
|
+
return formatTokenValue(fee.amount, fee.token, feeChain, adapter);
|
|
16860
|
+
}
|
|
16861
|
+
/**
|
|
16206
16862
|
* Format service fee items into the SDK's ServiceSwapFee structure.
|
|
16207
16863
|
*
|
|
16208
16864
|
* @remarks
|
|
@@ -16213,14 +16869,15 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16213
16869
|
* - Raw passthrough only when both registry and adapter fail
|
|
16214
16870
|
*
|
|
16215
16871
|
* @param feeItems - Array of fee items from the service response.
|
|
16216
|
-
* @param chain - The chain definition for token resolution and formatting.
|
|
16872
|
+
* @param chain - The source chain definition for token resolution and formatting.
|
|
16873
|
+
* @param destinationChain - The destination chain, used to resolve fees denominated in the output token.
|
|
16217
16874
|
* @param type - The fee type to assign ('provider' or 'swap').
|
|
16218
16875
|
* @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
|
|
16219
16876
|
* @returns Promise resolving to formatted ServiceSwapFee array.
|
|
16220
|
-
*/ async formatServiceFees(feeItems, chain, type, adapter) {
|
|
16877
|
+
*/ async formatServiceFees(feeItems, chain, destinationChain, type, adapter) {
|
|
16221
16878
|
if (!feeItems) return [];
|
|
16222
16879
|
return Promise.all(feeItems.map(async (fee)=>{
|
|
16223
|
-
const formatted = await
|
|
16880
|
+
const formatted = await this.formatFeeValue(fee, chain, destinationChain, adapter);
|
|
16224
16881
|
return {
|
|
16225
16882
|
token: formatted.token,
|
|
16226
16883
|
amount: formatted.amount,
|
|
@@ -16232,16 +16889,15 @@ async function fetchSameChainStatusSnapshot({ isCrossChainSwap, txHash, chain, a
|
|
|
16232
16889
|
* Format developer fee items into the SDK's ServiceSwapFee structure.
|
|
16233
16890
|
*
|
|
16234
16891
|
* @param feeItems - Array of developer fee items from the service response.
|
|
16235
|
-
* @param chain - The chain definition for token resolution and formatting.
|
|
16892
|
+
* @param chain - The source chain definition for token resolution and formatting.
|
|
16893
|
+
* @param destinationChain - The destination chain, used to resolve fees denominated in the output token.
|
|
16236
16894
|
* @param recipientAddress - The developer's fee recipient address from config.
|
|
16237
16895
|
* @param adapter - The adapter for on-chain decimals lookup of unregistered tokens.
|
|
16238
16896
|
* @returns Promise resolving to formatted ServiceSwapFee array with developer entries.
|
|
16239
16897
|
*/ async formatDeveloperFees(feeItems, chain, destinationChain, recipientAddress, adapter) {
|
|
16240
16898
|
if (!feeItems) return [];
|
|
16241
|
-
const isCrossChainSwap = destinationChain.chain !== chain.chain;
|
|
16242
16899
|
return Promise.all(feeItems.map(async (fee)=>{
|
|
16243
|
-
const
|
|
16244
|
-
const formatted = await formatTokenValue(fee.amount, fee.token, feeChain, adapter);
|
|
16900
|
+
const formatted = await this.formatFeeValue(fee, chain, destinationChain, adapter);
|
|
16245
16901
|
return {
|
|
16246
16902
|
token: formatted.token,
|
|
16247
16903
|
amount: formatted.amount,
|
|
@@ -18636,22 +19292,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
18636
19292
|
try {
|
|
18637
19293
|
// Step 1: Build quote params directly (no need for buildServiceParams)
|
|
18638
19294
|
// Use chain.chain (Blockchain enum value like "World_Chain") not chain.name
|
|
19295
|
+
// The kit key is optional (permissionless mode); when absent the quote is
|
|
19296
|
+
// fetched without an Authorization header.
|
|
18639
19297
|
const kitKey = params.config?.kitKey;
|
|
18640
|
-
if (!kitKey) {
|
|
18641
|
-
throw new KitError({
|
|
18642
|
-
code: 1098,
|
|
18643
|
-
name: 'INPUT_VALIDATION_FAILED',
|
|
18644
|
-
type: 'INPUT',
|
|
18645
|
-
recoverability: 'FATAL',
|
|
18646
|
-
message: 'kitKey is required in config for callback-based fees',
|
|
18647
|
-
cause: {
|
|
18648
|
-
trace: {
|
|
18649
|
-
operation: 'handleOutputFeeCallback',
|
|
18650
|
-
params
|
|
18651
|
-
}
|
|
18652
|
-
}
|
|
18653
|
-
});
|
|
18654
|
-
}
|
|
18655
19298
|
// Resolve token aliases to addresses for the quote API
|
|
18656
19299
|
// The quote endpoint requires resolved addresses, not aliases like 'USDC'
|
|
18657
19300
|
const chain = params.from.chain;
|
|
@@ -18676,7 +19319,9 @@ const transformAmount = (value, direction, decimals)=>formatUnits(value, decimal
|
|
|
18676
19319
|
...params.config?.slippageBps !== undefined && {
|
|
18677
19320
|
slippageBps: params.config.slippageBps
|
|
18678
19321
|
},
|
|
18679
|
-
|
|
19322
|
+
...kitKey ? {
|
|
19323
|
+
apiKey: kitKey
|
|
19324
|
+
} : {}
|
|
18680
19325
|
};
|
|
18681
19326
|
// Step 2: Get quote from service
|
|
18682
19327
|
const quoteResponse = await getQuote(quoteParams);
|
|
@@ -18928,7 +19573,13 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
18928
19573
|
* amountIn: '50.00'
|
|
18929
19574
|
* })
|
|
18930
19575
|
* ```
|
|
18931
|
-
*/ async function swap$1(context, params, /**
|
|
19576
|
+
*/ async function swap$1(context, params, /**
|
|
19577
|
+
* @internal
|
|
19578
|
+
* Invoked after a successful broadcast with the on-chain `txHash` and the
|
|
19579
|
+
* service-issued `correlationId` (join key for success telemetry). The
|
|
19580
|
+
* service returns `correlationId` for every chain (EVM + Solana); it is
|
|
19581
|
+
* undefined only against a not-yet-upgraded service that omits the field.
|
|
19582
|
+
*/ onBroadcast) {
|
|
18932
19583
|
// Step 1: Validate parameters using schema
|
|
18933
19584
|
assertSwapParams(params, swapParamsSchema);
|
|
18934
19585
|
// Step 2: Resolve parameters (chain definitions, wallet addresses, token aliases preserved)
|
|
@@ -18948,13 +19599,16 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
18948
19599
|
// Step 5: Execute swap via provider
|
|
18949
19600
|
const swapParams = buildServiceSwapParams(resolvedParams, paramsWithFees);
|
|
18950
19601
|
const providerResult = await provider.swap(swapParams);
|
|
19602
|
+
// `correlationId` is an internal telemetry join key, not part of the public
|
|
19603
|
+
// SwapResult — strip it here so it never leaks into the formatted result.
|
|
19604
|
+
const { correlationId, ...providerResultPublic } = providerResult;
|
|
18951
19605
|
// A throwing `onBroadcast` must never strand the caller after a
|
|
18952
19606
|
// successful swap broadcast — the chain has moved. `safeInvokeCallback`
|
|
18953
19607
|
// swallows the error and surfaces a `console.warn` prefixed with
|
|
18954
19608
|
// `[stablecoin-kits swap-kit] callback threw and was swallowed:` so a
|
|
18955
19609
|
// kit-side closure bug stays debuggable rather than vanishing.
|
|
18956
19610
|
safeInvokeCallback('swap-kit', ()=>{
|
|
18957
|
-
onBroadcast?.(
|
|
19611
|
+
onBroadcast?.(providerResultPublic.txHash, correlationId);
|
|
18958
19612
|
});
|
|
18959
19613
|
const { tokenInDecimals, tokenOutDecimals } = await resolveTokenDecimals(context, resolvedParams);
|
|
18960
19614
|
// Step 6: Compose chain identity (owned by the kit, derived from the
|
|
@@ -18963,10 +19617,10 @@ async function resolveTokenDecimals(context, resolvedParams) {
|
|
|
18963
19617
|
// resolves `progress` — the provider's snapshot, or a terminal `'DONE'` for
|
|
18964
19618
|
// a provider that omits it (a synchronous same-chain completion).
|
|
18965
19619
|
const composedResult = {
|
|
18966
|
-
...
|
|
19620
|
+
...providerResultPublic,
|
|
18967
19621
|
chainIn: resolvedParams.from.chain,
|
|
18968
19622
|
chainOut: resolvedParams.toChain ?? resolvedParams.from.chain,
|
|
18969
|
-
progress:
|
|
19623
|
+
progress: providerResultPublic.progress ?? {
|
|
18970
19624
|
status: 'DONE'
|
|
18971
19625
|
}
|
|
18972
19626
|
};
|
|
@@ -19118,7 +19772,9 @@ const sleep$1 = async (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
|
19118
19772
|
...isCrossChain && {
|
|
19119
19773
|
toChain: chainOut
|
|
19120
19774
|
},
|
|
19121
|
-
|
|
19775
|
+
...params.kitKey ? {
|
|
19776
|
+
apiKey: params.kitKey
|
|
19777
|
+
} : {}
|
|
19122
19778
|
};
|
|
19123
19779
|
let raw = await getSwapStatus$1(request);
|
|
19124
19780
|
// When the service hasn't finished indexing a just-submitted swap it
|
|
@@ -19258,7 +19914,9 @@ const isResultShape = (params)=>'result' in params;
|
|
|
19258
19914
|
...chainOut !== undefined && {
|
|
19259
19915
|
chainOut
|
|
19260
19916
|
},
|
|
19261
|
-
|
|
19917
|
+
...params.kitKey ? {
|
|
19918
|
+
kitKey: params.kitKey
|
|
19919
|
+
} : {}
|
|
19262
19920
|
};
|
|
19263
19921
|
const deadline = Date.now() + timeoutMs;
|
|
19264
19922
|
let pollIndex = 0;
|
|
@@ -19419,7 +20077,9 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
19419
20077
|
const resolvedAddresses = params.tokens?.map((entry, index)=>resolveTokenEntry(entry, index, chain, chainDef, context));
|
|
19420
20078
|
return getTokenRates$1({
|
|
19421
20079
|
chain,
|
|
19422
|
-
|
|
20080
|
+
...params.kitKey ? {
|
|
20081
|
+
apiKey: params.kitKey
|
|
20082
|
+
} : {},
|
|
19423
20083
|
...resolvedAddresses !== undefined && {
|
|
19424
20084
|
addresses: resolvedAddresses
|
|
19425
20085
|
}
|
|
@@ -19813,7 +20473,13 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
19813
20473
|
*/ class SwapKit {
|
|
19814
20474
|
context;
|
|
19815
20475
|
/** Whether error telemetry is disabled. */ disableErrorReporting;
|
|
19816
|
-
/** Per-kit telemetry identity for
|
|
20476
|
+
/** Per-kit telemetry identity for error reporting. */ telemetryConfig;
|
|
20477
|
+
/**
|
|
20478
|
+
* Per-kit telemetry identity for success/analytics events. Gated by
|
|
20479
|
+
* `disableAnalytics` (independent of `disableErrorReporting`) so a developer
|
|
20480
|
+
* can opt out of volume analytics without also silencing error reports —
|
|
20481
|
+
* mirrors EarnKit.
|
|
20482
|
+
*/ analyticsTelemetryConfig;
|
|
19817
20483
|
/**
|
|
19818
20484
|
* Create a new SwapKit instance.
|
|
19819
20485
|
*
|
|
@@ -19867,6 +20533,11 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
19867
20533
|
sdkVersion: pkg$1.version,
|
|
19868
20534
|
disabled: this.disableErrorReporting
|
|
19869
20535
|
};
|
|
20536
|
+
this.analyticsTelemetryConfig = {
|
|
20537
|
+
sdkName: SDK_NAME,
|
|
20538
|
+
sdkVersion: pkg$1.version,
|
|
20539
|
+
disabled: config.disableAnalytics === true
|
|
20540
|
+
};
|
|
19870
20541
|
}
|
|
19871
20542
|
/**
|
|
19872
20543
|
* Estimate the output amount and fees for a swap operation.
|
|
@@ -19907,8 +20578,12 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
19907
20578
|
* console.log(`Fees:`, quote.fees)
|
|
19908
20579
|
* ```
|
|
19909
20580
|
*/ async estimate(params) {
|
|
20581
|
+
const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
|
|
19910
20582
|
return withErrorTelemetry(async ()=>estimate(this.context, params), SWAP_EVENT_TYPES.ESTIMATE, this.telemetryConfig, {
|
|
19911
20583
|
sourceChain: resolveChainName(params.from.chain),
|
|
20584
|
+
...destinationChain != null && {
|
|
20585
|
+
destinationChain
|
|
20586
|
+
},
|
|
19912
20587
|
tokenIn: params.tokenIn,
|
|
19913
20588
|
tokenOut: params.tokenOut
|
|
19914
20589
|
});
|
|
@@ -19967,16 +20642,54 @@ function resolveTokenEntry(entry, index, chain, chainDef, context) {
|
|
|
19967
20642
|
* ```
|
|
19968
20643
|
*/ async swap(params) {
|
|
19969
20644
|
let txHash;
|
|
19970
|
-
|
|
19971
|
-
|
|
19972
|
-
|
|
20645
|
+
let correlationId;
|
|
20646
|
+
// Shared context builder so the error resolver and the success emit stay in
|
|
20647
|
+
// lockstep — a field added here reaches both call sites. Reads the per-call
|
|
20648
|
+
// locals lazily, so txHash/correlationId (set during the swap) are captured
|
|
20649
|
+
// whenever it is invoked.
|
|
20650
|
+
// Destination chain is the primary attribution dimension for cross-chain
|
|
20651
|
+
// swaps; resolved once from the (static) params. Omitted for same-chain
|
|
20652
|
+
// swaps that leave `to.chain` unset (destination == source).
|
|
20653
|
+
const destinationChain = params.to?.chain ? resolveChainName(params.to.chain) : undefined;
|
|
20654
|
+
const buildTelemetryContext = ()=>({
|
|
19973
20655
|
sourceChain: resolveChainName(params.from.chain),
|
|
20656
|
+
...destinationChain != null && {
|
|
20657
|
+
destinationChain
|
|
20658
|
+
},
|
|
19974
20659
|
tokenIn: params.tokenIn,
|
|
19975
20660
|
tokenOut: params.tokenOut,
|
|
19976
20661
|
...txHash != null && {
|
|
19977
20662
|
txHash
|
|
20663
|
+
},
|
|
20664
|
+
...correlationId != null && {
|
|
20665
|
+
correlationId
|
|
19978
20666
|
}
|
|
19979
|
-
})
|
|
20667
|
+
});
|
|
20668
|
+
const result = await withErrorTelemetry(async ()=>swap$1(this.context, params, (h, cId)=>{
|
|
20669
|
+
txHash = h;
|
|
20670
|
+
correlationId = cId;
|
|
20671
|
+
}), SWAP_EVENT_TYPES.SWAP, this.telemetryConfig, buildTelemetryContext);
|
|
20672
|
+
// withErrorTelemetry only emits on failure. Record the successful swap here
|
|
20673
|
+
// so the backend can attribute swap volume to a developer: the client event
|
|
20674
|
+
// carries the (burn) txHash + correlationId, which joins to the
|
|
20675
|
+
// server-emitted event carrying entity_id. Best-effort; never throws.
|
|
20676
|
+
//
|
|
20677
|
+
// Emit whenever a broadcast happened, i.e. we have the source/burn txHash.
|
|
20678
|
+
// For a cross-chain swap that is the source-chain burn (progress is still
|
|
20679
|
+
// PENDING while the destination mint settles) — we intentionally attribute
|
|
20680
|
+
// at broadcast using the burn txHash rather than tracking the destination
|
|
20681
|
+
// leg, which keeps the capture simple and self-contained in swap().
|
|
20682
|
+
//
|
|
20683
|
+
// Guard against a terminal-failure result: the EVM provider throws on
|
|
20684
|
+
// revert today, but the kit is provider-agnostic, so a provider that
|
|
20685
|
+
// returns a FAILED/NOT_FOUND result without throwing must not be recorded
|
|
20686
|
+
// as a successful swap. Routed through analyticsTelemetryConfig so it is
|
|
20687
|
+
// gated by disableAnalytics, independent of error reporting.
|
|
20688
|
+
const status = result.progress?.status;
|
|
20689
|
+
if (txHash != null && status !== 'FAILED' && status !== 'NOT_FOUND') {
|
|
20690
|
+
emitSuccessTelemetry(SWAP_EVENT_TYPES.SWAP, this.analyticsTelemetryConfig, buildTelemetryContext());
|
|
20691
|
+
}
|
|
20692
|
+
return result;
|
|
19980
20693
|
}
|
|
19981
20694
|
/**
|
|
19982
20695
|
* Fetch the current status of a swap from the Stablecoin Service.
|
|
@@ -20355,6 +21068,9 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
|
|
|
20355
21068
|
const kit = new SwapKit({
|
|
20356
21069
|
...context.disableErrorReporting != null && {
|
|
20357
21070
|
disableErrorReporting: context.disableErrorReporting
|
|
21071
|
+
},
|
|
21072
|
+
...context.disableAnalytics != null && {
|
|
21073
|
+
disableAnalytics: context.disableAnalytics
|
|
20358
21074
|
}
|
|
20359
21075
|
});
|
|
20360
21076
|
if (hasBoth) {
|
|
@@ -20416,7 +21132,7 @@ registerKit(`${pkg$1.name}/${pkg$1.version}`);
|
|
|
20416
21132
|
};
|
|
20417
21133
|
|
|
20418
21134
|
var name = "@circle-fin/earn-kit";
|
|
20419
|
-
var version = "1.
|
|
21135
|
+
var version = "1.4.0";
|
|
20420
21136
|
var pkg = {
|
|
20421
21137
|
name: name,
|
|
20422
21138
|
version: version};
|
|
@@ -20546,7 +21262,11 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
20546
21262
|
asset: zod.z.string(),
|
|
20547
21263
|
assetAddress: zod.z.string(),
|
|
20548
21264
|
lltv: zod.z.number(),
|
|
20549
|
-
supplyUsd: zod.z.number()
|
|
21265
|
+
supplyUsd: zod.z.number(),
|
|
21266
|
+
// Optional during the expand/contract window (a backend that predates the
|
|
21267
|
+
// field omits the key), mirroring the `.optional()` facets on the base
|
|
21268
|
+
// schema; `null` when the product exposes no per-market allocation (V2).
|
|
21269
|
+
allocationPct: zod.z.number().nullable().optional()
|
|
20550
21270
|
});
|
|
20551
21271
|
/**
|
|
20552
21272
|
* Zod schema for a Morpho vault warning in the API response.
|
|
@@ -20560,7 +21280,74 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
20560
21280
|
])
|
|
20561
21281
|
});
|
|
20562
21282
|
/**
|
|
20563
|
-
* Zod schema for
|
|
21283
|
+
* Zod schema for the manager (curator) facet in the API response.
|
|
21284
|
+
*
|
|
21285
|
+
* @internal
|
|
21286
|
+
*/ const managerSchema = zod.z.object({
|
|
21287
|
+
name: zod.z.string(),
|
|
21288
|
+
address: zod.z.string().optional(),
|
|
21289
|
+
// Only 'curator' is emitted today (Morpho V1/V2). Additional manager roles
|
|
21290
|
+
// are added here as the providers that emit them land, rather than shipped
|
|
21291
|
+
// speculatively.
|
|
21292
|
+
type: zod.z.enum([
|
|
21293
|
+
'curator'
|
|
21294
|
+
])
|
|
21295
|
+
});
|
|
21296
|
+
/**
|
|
21297
|
+
* Zod schema for the APY profile facet in the API response.
|
|
21298
|
+
*
|
|
21299
|
+
* @internal
|
|
21300
|
+
*/ const apyProfileSchema = zod.z.object({
|
|
21301
|
+
current: zod.z.number(),
|
|
21302
|
+
native: zod.z.number().nullable(),
|
|
21303
|
+
d7: zod.z.number().nullable(),
|
|
21304
|
+
d30: zod.z.number().nullable(),
|
|
21305
|
+
d90: zod.z.number().nullable(),
|
|
21306
|
+
rewardShare: zod.z.number().nullable(),
|
|
21307
|
+
source: zod.z.string().optional(),
|
|
21308
|
+
asOf: zod.z.string().optional()
|
|
21309
|
+
});
|
|
21310
|
+
/**
|
|
21311
|
+
* Zod schema for the fee split facet in the API response.
|
|
21312
|
+
*
|
|
21313
|
+
* @internal
|
|
21314
|
+
*/ const feeInfoSchema = zod.z.object({
|
|
21315
|
+
performance: zod.z.number().nullable(),
|
|
21316
|
+
management: zod.z.number().nullable()
|
|
21317
|
+
});
|
|
21318
|
+
/**
|
|
21319
|
+
* Zod schema for the liquidity profile facet in the API response.
|
|
21320
|
+
*
|
|
21321
|
+
* `totalSupply` is the outstanding vault share tokens (ERC4626 totalSupply);
|
|
21322
|
+
* it is validated as a raw JSON amount, like `totalDeposits`/`available`.
|
|
21323
|
+
*
|
|
21324
|
+
* @internal
|
|
21325
|
+
*/ const liquidityProfileSchema = zod.z.object({
|
|
21326
|
+
totalDeposits: amountJsonSchema,
|
|
21327
|
+
available: amountJsonSchema,
|
|
21328
|
+
totalSupply: amountJsonSchema,
|
|
21329
|
+
status: zod.z.enum([
|
|
21330
|
+
'active',
|
|
21331
|
+
'low_liquidity'
|
|
21332
|
+
])
|
|
21333
|
+
});
|
|
21334
|
+
/**
|
|
21335
|
+
* Zod schema for the risk signals facet in the API response.
|
|
21336
|
+
*
|
|
21337
|
+
* @internal
|
|
21338
|
+
*/ const riskSignalsSchema = zod.z.object({
|
|
21339
|
+
circleSentinel: zod.z.boolean(),
|
|
21340
|
+
warnings: zod.z.array(vaultWarningSchema).optional(),
|
|
21341
|
+
earnKitWarnings: zod.z.array(zod.z.string()).optional()
|
|
21342
|
+
});
|
|
21343
|
+
/**
|
|
21344
|
+
* Zod schema for the universal earn-opportunity base in the API response.
|
|
21345
|
+
*
|
|
21346
|
+
* Retains every existing deprecated flat field (kept validated through the
|
|
21347
|
+
* expand/contract window so default-strip does not drop them) and adds the
|
|
21348
|
+
* new nested facets. The nested facets are `.optional()` during the
|
|
21349
|
+
* transition so the SDK still validates against a not-yet-fully-deployed
|
|
21350
|
+
* backend; they become required after Expand ships.
|
|
20564
21351
|
*
|
|
20565
21352
|
* @internal
|
|
20566
21353
|
*/ const vaultInfoResponseSchema = zod.z.object({
|
|
@@ -20585,6 +21372,96 @@ const bridgeFeeTokenSchema = hexAddressSchema;
|
|
|
20585
21372
|
warnings: zod.z.array(vaultWarningSchema).optional(),
|
|
20586
21373
|
earnKitWarnings: zod.z.array(zod.z.string()).optional()
|
|
20587
21374
|
});
|
|
21375
|
+
/**
|
|
21376
|
+
* Shared base schema: existing flat fields (kept) plus the new nested
|
|
21377
|
+
* facets and neutral identity. Facets are `.optional()` during the
|
|
21378
|
+
* transition; flip to required once the backend is confirmed emitting.
|
|
21379
|
+
*
|
|
21380
|
+
* @internal
|
|
21381
|
+
*/ const earnBaseSchema = vaultInfoResponseSchema.extend({
|
|
21382
|
+
address: zod.z.string().optional(),
|
|
21383
|
+
asOf: zod.z.string().optional(),
|
|
21384
|
+
manager: managerSchema.nullable().optional(),
|
|
21385
|
+
apyProfile: apyProfileSchema.optional(),
|
|
21386
|
+
fee: feeInfoSchema.optional(),
|
|
21387
|
+
liquidityProfile: liquidityProfileSchema.optional(),
|
|
21388
|
+
riskSignals: riskSignalsSchema.optional()
|
|
21389
|
+
});
|
|
21390
|
+
/**
|
|
21391
|
+
* Zod schema for the `vault` opportunity variant.
|
|
21392
|
+
*
|
|
21393
|
+
* @internal
|
|
21394
|
+
*/ const vaultOpportunitySchema = earnBaseSchema.extend({
|
|
21395
|
+
productType: zod.z.literal('vault'),
|
|
21396
|
+
collateral: zod.z.array(collateralSchema)
|
|
21397
|
+
});
|
|
21398
|
+
/**
|
|
21399
|
+
* Discriminated union over `productType`. Add union members here as new
|
|
21400
|
+
* product types (e.g. `lending_market`, `rwa_token`) land.
|
|
21401
|
+
*
|
|
21402
|
+
* @internal
|
|
21403
|
+
*/ const earnOpportunityVariants = [
|
|
21404
|
+
vaultOpportunitySchema
|
|
21405
|
+
];
|
|
21406
|
+
/** @internal */ const earnOpportunitySchema = zod.z.discriminatedUnion('productType', earnOpportunityVariants);
|
|
21407
|
+
/** Product types this SDK version knows how to parse. */ const knownProductTypes = new Set(earnOpportunityVariants.map((variant)=>variant.shape.productType.value));
|
|
21408
|
+
/**
|
|
21409
|
+
* Tolerant list parser for earn opportunities.
|
|
21410
|
+
*
|
|
21411
|
+
* `z.discriminatedUnion` throws on an unrecognized discriminant and
|
|
21412
|
+
* `z.array` fails the whole array if any element fails. Two migration-window
|
|
21413
|
+
* cases are smoothed over here so neither breaks an already-shipped SDK:
|
|
21414
|
+
*
|
|
21415
|
+
* - A backend that predates `productType` omits it entirely. `'vault'` was the
|
|
21416
|
+
* only opportunity type then, so default a missing discriminant to `'vault'`
|
|
21417
|
+
* rather than dropping every vault the backend returns.
|
|
21418
|
+
* - A future backend adds a *second* `productType` this SDK version does not
|
|
21419
|
+
* know. Drop those elements (a present-but-unrecognized discriminant) instead
|
|
21420
|
+
* of rejecting the whole list.
|
|
21421
|
+
*
|
|
21422
|
+
* Only the drop above is a *tolerant* case. Anything that is not a plain object
|
|
21423
|
+
* with a present-but-unknown string `productType` — `null`, `undefined`,
|
|
21424
|
+
* primitives, or an object whose `productType` is malformed — is passed through
|
|
21425
|
+
* untouched so `z.array(earnOpportunitySchema)` reports it as a normal
|
|
21426
|
+
* validation failure. It is deliberately not silently dropped (which would hide
|
|
21427
|
+
* malformed backend data) and never throws here (an unguarded property read on
|
|
21428
|
+
* a non-object would escape `safeParse` as a raw `TypeError` instead of a
|
|
21429
|
+
* `ZodError`).
|
|
21430
|
+
*
|
|
21431
|
+
* @internal
|
|
21432
|
+
*/ const earnOpportunityListSchema = zod.z.preprocess((raw)=>{
|
|
21433
|
+
if (!Array.isArray(raw)) {
|
|
21434
|
+
return raw;
|
|
21435
|
+
}
|
|
21436
|
+
// Array.isArray narrows `raw` to `any[]`; view it as `unknown[]` so the
|
|
21437
|
+
// map/filter chain stays type-safe and no `any` leaks into the return.
|
|
21438
|
+
const entries = raw;
|
|
21439
|
+
return entries.map((entry)=>{
|
|
21440
|
+
// Only touch plain objects; non-objects fall through to fail validation.
|
|
21441
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
21442
|
+
return entry;
|
|
21443
|
+
}
|
|
21444
|
+
const record = entry;
|
|
21445
|
+
// Older backend predating productType: default to the only type then.
|
|
21446
|
+
return record.productType === undefined ? {
|
|
21447
|
+
...record,
|
|
21448
|
+
productType: 'vault'
|
|
21449
|
+
} : record;
|
|
21450
|
+
}).filter((entry)=>{
|
|
21451
|
+
// Drop ONLY a present-but-unknown string discriminant (a future
|
|
21452
|
+
// productType this SDK version doesn't know). Everything else —
|
|
21453
|
+
// non-objects, a non-string productType — flows through to
|
|
21454
|
+
// z.array(earnOpportunitySchema) and fails/passes validation normally.
|
|
21455
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
21456
|
+
return true;
|
|
21457
|
+
}
|
|
21458
|
+
const productType = entry.productType;
|
|
21459
|
+
if (typeof productType !== 'string') {
|
|
21460
|
+
return true;
|
|
21461
|
+
}
|
|
21462
|
+
return knownProductTypes.has(productType);
|
|
21463
|
+
});
|
|
21464
|
+
}, zod.z.array(earnOpportunitySchema));
|
|
20588
21465
|
// ---------------------------------------------------------------------------
|
|
20589
21466
|
// Position response schema
|
|
20590
21467
|
// ---------------------------------------------------------------------------
|
|
@@ -20714,6 +21591,7 @@ const positionPnlSchema = zod.z.discriminatedUnion('status', [
|
|
|
20714
21591
|
*
|
|
20715
21592
|
* @internal
|
|
20716
21593
|
*/ const depositPayloadSchema = zod.z.object({
|
|
21594
|
+
execId: bridgeDepositExecIdSchema,
|
|
20717
21595
|
executionParams: depositExecutionParamsSchema,
|
|
20718
21596
|
signature: hexSignatureSchema
|
|
20719
21597
|
});
|
|
@@ -20805,6 +21683,21 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
20805
21683
|
amount: amountJsonSchema,
|
|
20806
21684
|
vaultAddress: hexAddressSchema
|
|
20807
21685
|
}).passthrough();
|
|
21686
|
+
/** @internal */ const bridgeQuoteExpirySchema = zod.z.discriminatedUnion('mode', [
|
|
21687
|
+
zod.z.object({
|
|
21688
|
+
mode: zod.z.literal('TIMESTAMP'),
|
|
21689
|
+
expiresAt: zod.z.string().datetime({
|
|
21690
|
+
offset: true
|
|
21691
|
+
})
|
|
21692
|
+
}),
|
|
21693
|
+
zod.z.object({
|
|
21694
|
+
mode: zod.z.literal('BLOCK_NUMBER'),
|
|
21695
|
+
expiresAtBlock: zod.z.number().int(),
|
|
21696
|
+
blockEstimatedAt: zod.z.string().datetime({
|
|
21697
|
+
offset: true
|
|
21698
|
+
}).optional()
|
|
21699
|
+
})
|
|
21700
|
+
]).optional().catch(undefined);
|
|
20808
21701
|
/**
|
|
20809
21702
|
* Zod schema for the bridge deposit prepare payload.
|
|
20810
21703
|
*
|
|
@@ -20816,6 +21709,10 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
20816
21709
|
execId: bridgeDepositExecIdSchema,
|
|
20817
21710
|
erc3009TypedData: bridgeDepositPreparedBundleSchema,
|
|
20818
21711
|
expiresAt: zod.z.string().datetime(),
|
|
21712
|
+
quoteIssuedAt: zod.z.string().datetime({
|
|
21713
|
+
offset: true
|
|
21714
|
+
}).optional().catch(undefined),
|
|
21715
|
+
quoteExpiry: bridgeQuoteExpirySchema,
|
|
20819
21716
|
review: bridgeDepositPrepareReviewSchema
|
|
20820
21717
|
});
|
|
20821
21718
|
/**
|
|
@@ -20881,6 +21778,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
20881
21778
|
*
|
|
20882
21779
|
* @internal
|
|
20883
21780
|
*/ const withdrawPayloadSchema = zod.z.object({
|
|
21781
|
+
execId: bridgeDepositExecIdSchema,
|
|
20884
21782
|
executionParams: withdrawExecutionParamsSchema,
|
|
20885
21783
|
signature: hexSignatureSchema
|
|
20886
21784
|
});
|
|
@@ -20894,6 +21792,27 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
20894
21792
|
data: withdrawPayloadSchema
|
|
20895
21793
|
});
|
|
20896
21794
|
// ---------------------------------------------------------------------------
|
|
21795
|
+
// Transaction report response schema
|
|
21796
|
+
// ---------------------------------------------------------------------------
|
|
21797
|
+
/**
|
|
21798
|
+
* Zod schema for the transaction report payload inside the API `data` envelope.
|
|
21799
|
+
*
|
|
21800
|
+
* The Earn Service returns an empty payload (`{"data":{}}`) on success, so the
|
|
21801
|
+
* schema accepts any object shape and does not require specific fields.
|
|
21802
|
+
*
|
|
21803
|
+
* @internal
|
|
21804
|
+
*/ const transactionReportPayloadSchema = zod.z.object({}).passthrough();
|
|
21805
|
+
/**
|
|
21806
|
+
* Zod schema for the `POST /v1/earnKit/transactions/report` API response.
|
|
21807
|
+
*
|
|
21808
|
+
* The Earn Service API wraps the transaction report payload in a `data`
|
|
21809
|
+
* envelope.
|
|
21810
|
+
*
|
|
21811
|
+
* @internal
|
|
21812
|
+
*/ zod.z.object({
|
|
21813
|
+
data: transactionReportPayloadSchema
|
|
21814
|
+
});
|
|
21815
|
+
// ---------------------------------------------------------------------------
|
|
20897
21816
|
// Claim rewards response schema
|
|
20898
21817
|
// ---------------------------------------------------------------------------
|
|
20899
21818
|
/**
|
|
@@ -20954,6 +21873,30 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
20954
21873
|
token: zod.z.string(),
|
|
20955
21874
|
amount: amountJsonSchema
|
|
20956
21875
|
});
|
|
21876
|
+
/**
|
|
21877
|
+
* Zod schema for a native gas-fee entry in an EarnKit quote response.
|
|
21878
|
+
*
|
|
21879
|
+
* The Earn Service backend estimates gas server-side and returns one entry per
|
|
21880
|
+
* action (`Approve`, `Deposit`, `Withdraw`). A successful estimate carries
|
|
21881
|
+
* `fees` in the SDK `EstimatedGas` shape (`{ gas, gasPrice, fee }`), each a raw
|
|
21882
|
+
* integer string in the chain's native base units. When the backend cannot
|
|
21883
|
+
* estimate an action it returns `fees: null` with an `error` message instead.
|
|
21884
|
+
*
|
|
21885
|
+
* The schema deliberately validates almost nothing beyond the envelope: `name`
|
|
21886
|
+
* is optional and `fees` is entirely unvalidated (`unknown`). ALL validation
|
|
21887
|
+
* of `fees` — that it is an object at all, and that `gas`, `gasPrice`, and
|
|
21888
|
+
* `fee` are parseable integer strings — is deferred to {@link toQuoteGasFees},
|
|
21889
|
+
* which degrades a malformed entry to a `fees: null` soft failure. This is
|
|
21890
|
+
* intentional: gas is best-effort, so a single unparseable gas entry (a wrong
|
|
21891
|
+
* type such as `fees: 123` or `fees: 'bad'`, a missing field, or a non-numeric
|
|
21892
|
+
* `fee`) must never fail Zod validation and reject the entire quote.
|
|
21893
|
+
*
|
|
21894
|
+
* @internal
|
|
21895
|
+
*/ const quoteGasFeeSchema = zod.z.object({
|
|
21896
|
+
name: zod.z.string().optional(),
|
|
21897
|
+
fees: zod.z.unknown(),
|
|
21898
|
+
error: zod.z.string().optional()
|
|
21899
|
+
}).passthrough();
|
|
20957
21900
|
/**
|
|
20958
21901
|
* Zod schema for the inner deposit quote payload.
|
|
20959
21902
|
*
|
|
@@ -20969,7 +21912,8 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
20969
21912
|
expectedShares: amountJsonSchema,
|
|
20970
21913
|
sharePrice: zod.z.string(),
|
|
20971
21914
|
currentApy: zod.z.number(),
|
|
20972
|
-
fees: zod.z.array(feeSchema).optional()
|
|
21915
|
+
fees: zod.z.array(feeSchema).optional(),
|
|
21916
|
+
gasFees: zod.z.array(quoteGasFeeSchema).optional()
|
|
20973
21917
|
});
|
|
20974
21918
|
/**
|
|
20975
21919
|
* Zod schema for the `POST /v1/earnKit/deposit/quote` API response.
|
|
@@ -20996,6 +21940,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
20996
21940
|
sharePrice: zod.z.string(),
|
|
20997
21941
|
maxWithdrawable: amountJsonSchema,
|
|
20998
21942
|
fees: zod.z.array(feeSchema),
|
|
21943
|
+
gasFees: zod.z.array(quoteGasFeeSchema).optional(),
|
|
20999
21944
|
warnings: zod.z.array(zod.z.string()).optional()
|
|
21000
21945
|
});
|
|
21001
21946
|
/**
|
|
@@ -21053,7 +21998,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
21053
21998
|
*
|
|
21054
21999
|
* @internal
|
|
21055
22000
|
*/ const getVaultsPayloadSchema = zod.z.object({
|
|
21056
|
-
vaults:
|
|
22001
|
+
vaults: earnOpportunityListSchema,
|
|
21057
22002
|
errors: zod.z.array(vaultErrorSchema)
|
|
21058
22003
|
});
|
|
21059
22004
|
/**
|
|
@@ -21083,7 +22028,7 @@ const bridgeDepositPrepareReviewSchema = zod.z.object({
|
|
|
21083
22028
|
*
|
|
21084
22029
|
* @internal
|
|
21085
22030
|
*/ const exploreVaultsPayloadSchema = zod.z.object({
|
|
21086
|
-
vaults:
|
|
22031
|
+
vaults: earnOpportunityListSchema,
|
|
21087
22032
|
pagination: explorePaginationSchema
|
|
21088
22033
|
});
|
|
21089
22034
|
/**
|
|
@@ -21651,6 +22596,8 @@ function hasCrossChainDepositQuoteShape(params) {
|
|
|
21651
22596
|
config: earnConfigSchema.optional()
|
|
21652
22597
|
});
|
|
21653
22598
|
|
|
22599
|
+
/** SDK name used in telemetry payloads. */ resolveKitSdkName(pkg.name);
|
|
22600
|
+
|
|
21654
22601
|
// Auto-register this kit for user agent tracking
|
|
21655
22602
|
registerKit(`${pkg.name}/${pkg.version}`);
|
|
21656
22603
|
|