@continuumdao/ctm-mpc-defi 0.2.47 → 0.2.49

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.
Files changed (68) hide show
  1. package/dist/agent/catalog.cjs +1181 -13
  2. package/dist/agent/catalog.cjs.map +1 -1
  3. package/dist/agent/catalog.d.ts +1532 -7
  4. package/dist/agent/catalog.js +1142 -14
  5. package/dist/agent/catalog.js.map +1 -1
  6. package/dist/agent/skills/continuum-dao/SKILL.md +2 -0
  7. package/dist/agent/skills/derive/SKILL.md +112 -0
  8. package/dist/agent/skills/hyperliquid/SKILL.md +22 -2
  9. package/dist/chains/evm/index.d.ts +118 -6
  10. package/dist/chains/near/index.d.ts +7 -1
  11. package/dist/chains/solana/index.d.ts +7 -1
  12. package/dist/core/index.cjs +2 -1
  13. package/dist/core/index.cjs.map +1 -1
  14. package/dist/core/index.d.ts +138 -7
  15. package/dist/core/index.js +2 -1
  16. package/dist/core/index.js.map +1 -1
  17. package/dist/index.cjs +2 -1
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.ts +848 -16
  20. package/dist/index.js +2 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/protocols/evm/aave-v4/index.d.ts +600 -6
  23. package/dist/protocols/evm/aerodrome/index.cjs.map +1 -1
  24. package/dist/protocols/evm/aerodrome/index.d.ts +125 -5
  25. package/dist/protocols/evm/aerodrome/index.js.map +1 -1
  26. package/dist/protocols/evm/arcus/index.d.ts +66 -2
  27. package/dist/protocols/evm/circle-cctp/index.d.ts +71 -2
  28. package/dist/protocols/evm/compound-v3/index.d.ts +52 -1
  29. package/dist/protocols/evm/continuum-dao/index.cjs +475 -1
  30. package/dist/protocols/evm/continuum-dao/index.cjs.map +1 -1
  31. package/dist/protocols/evm/continuum-dao/index.d.ts +230 -2
  32. package/dist/protocols/evm/continuum-dao/index.js +463 -3
  33. package/dist/protocols/evm/continuum-dao/index.js.map +1 -1
  34. package/dist/protocols/evm/curve-dao/index.d.ts +66 -2
  35. package/dist/protocols/evm/derive/index.cjs +2655 -0
  36. package/dist/protocols/evm/derive/index.cjs.map +1 -0
  37. package/dist/protocols/evm/derive/index.d.ts +859 -0
  38. package/dist/protocols/evm/derive/index.js +2536 -0
  39. package/dist/protocols/evm/derive/index.js.map +1 -0
  40. package/dist/protocols/evm/ethena/index.d.ts +52 -1
  41. package/dist/protocols/evm/euler-v2/index.d.ts +115 -16
  42. package/dist/protocols/evm/gmx/index.d.ts +66 -2
  43. package/dist/protocols/evm/hyperliquid/index.cjs +545 -267
  44. package/dist/protocols/evm/hyperliquid/index.cjs.map +1 -1
  45. package/dist/protocols/evm/hyperliquid/index.d.ts +152 -7
  46. package/dist/protocols/evm/hyperliquid/index.js +535 -269
  47. package/dist/protocols/evm/hyperliquid/index.js.map +1 -1
  48. package/dist/protocols/evm/lido/index.d.ts +52 -1
  49. package/dist/protocols/evm/maple/index.d.ts +52 -1
  50. package/dist/protocols/evm/morpho/index.d.ts +115 -4
  51. package/dist/protocols/evm/pendle/index.cjs +2 -1
  52. package/dist/protocols/evm/pendle/index.cjs.map +1 -1
  53. package/dist/protocols/evm/pendle/index.d.ts +125 -5
  54. package/dist/protocols/evm/pendle/index.js +2 -1
  55. package/dist/protocols/evm/pendle/index.js.map +1 -1
  56. package/dist/protocols/evm/permit2/index.d.ts +14 -1
  57. package/dist/protocols/evm/sky/index.d.ts +52 -1
  58. package/dist/protocols/evm/uniswap-v4/index.d.ts +78 -4
  59. package/dist/protocols/evm/venice/index.d.ts +52 -1
  60. package/package.json +7 -2
  61. package/dist/buildBatch-tsEUVn_a.d.ts +0 -45
  62. package/dist/eip712Multisign-6oNIlObj.d.ts +0 -39
  63. package/dist/envelope-Cm-llShH.d.ts +0 -37
  64. package/dist/merklDistributor-CdqtZC9N.d.ts +0 -65
  65. package/dist/multisign-Dr0j9fHm.d.ts +0 -489
  66. package/dist/registry-BvsXh0-r.d.ts +0 -8
  67. package/dist/types-RJpoU4Pc.d.ts +0 -34
  68. package/dist/types-lIfv59N1.d.ts +0 -63
@@ -0,0 +1,2536 @@
1
+ import { parseAbi, getAddress, parseUnits, encodeAbiParameters, keccak256, toHex, hashTypedData, hashMessage, stringToHex, encodeFunctionData, defineChain, createPublicClient, parseGwei, serializeTransaction, http } from 'viem';
2
+ import { getClientIdFromKeyGenResult, fetchChainFeeParams, gasLimitFromEstimateAndChainConfig, gweiToDecimalString, proposalTxParamsToFeeSnapshot, alignEip1559FeesWithLatestBase } from '@continuumdao/continuum-node-sdk';
3
+
4
+ // src/core/registry.ts
5
+ var modules = [];
6
+ function registerProtocolModule(mod) {
7
+ const existing = modules.findIndex((m) => m.id === mod.id);
8
+ if (existing >= 0) {
9
+ modules[existing] = mod;
10
+ } else {
11
+ modules.push(mod);
12
+ }
13
+ }
14
+
15
+ // src/protocols/evm/derive/constants.ts
16
+ var DERIVE_PROTOCOL_ID = "derive";
17
+ var DERIVE_MATCHING_VERIFYING_CONTRACT = "0xeB8d770ec18DB98Db922E9D83260A585b9F0DeAD";
18
+ var DERIVE_ACTION_TYPEHASH = "0x4d7a9f27c403ff9c0f19bce61d76d82f9aa29f8d6d4b0c5474607d9770d1af17";
19
+ var DERIVE_TRADE_MODULE = "0xB8D20c2B7a1Ad2EE33Bc50eF10876eD3035b5e7b";
20
+ var DERIVE_TRANSFER_MODULE = "0x01259207A40925b794C8ac320456F7F6c8FE2636";
21
+ var DERIVE_DOMAIN_SEPARATOR_MAINNET = "0xda616dfabb88681b08e1592820a41d55ddc62d68de110e327ae99d734506fe19";
22
+ var DERIVE_DOMAIN_SEPARATOR_TESTNET = "0x24d674cd5f2b9d564691c51e9d88f649b99246a2244dd74ce27b96578d773e85";
23
+ var DERIVE_MAINNET_API = "https://api.derive.xyz/v3";
24
+ var DERIVE_TESTNET_API = "https://testnet.api.derive.xyz/v3";
25
+ var DERIVE_MAINNET_API_V2 = "https://api.lyra.finance";
26
+ var DERIVE_TESTNET_API_V2 = "https://api-demo.lyra.finance";
27
+ var DERIVE_ACTION_MANAGER_TESTNET = "0xd3625eCf97E5554C62A48Ac1c9284C9dCeFceB68";
28
+ var DERIVE_EIP712_NAME = "Matching";
29
+ var DERIVE_EIP712_VERSION = "1.0";
30
+ var DERIVE_DEFAULT_SIGNATURE_EXPIRY_SEC = 300;
31
+ var DERIVE_MIN_ORDER_EXPIRY_SEC = 10;
32
+ var DERIVE_OHLCV_INTERVALS = ["1m", "5m", "15m", "30m", "1h", "4h", "1d", "1w"];
33
+ var DERIVE_OHLCV_PERIOD_SEC = {
34
+ "1m": 60,
35
+ "5m": 300,
36
+ "15m": 900,
37
+ "30m": 1800,
38
+ "1h": 3600,
39
+ "4h": 14400,
40
+ "1d": 86400,
41
+ "1w": 604800
42
+ };
43
+ var DERIVE_DEFAULT_OHLCV_INTERVAL = "15m";
44
+ var DERIVE_DEFAULT_OHLCV_BARS = 96;
45
+ var DERIVE_DEFAULT_TICKER_ATM_STRIKES = 9;
46
+ var DERIVE_DEFAULT_LOOKBACK_DAYS = 2;
47
+ var DERIVE_MAX_LOOKBACK_DAYS = 90;
48
+ var DERIVE_MAX_OHLCV_BARS = 2e3;
49
+ var DERIVE_UNFUNDED_NOTES = "No Derive subaccount on this KeyGen. Fund from a logo row: Ethereum L1 USDC/WETH/WBTC (ctm_derive_build_deposit_new_subaccount_multisign or register_deposit_address + transfer), Socket USDC/WETH/WBTC on Optimism/Base/Arbitrum (ctm_derive_build_socket_deposit_multisign), or HyperEVM HYPE (ctm_derive_build_hype_vault_deposit_multisign). After credit, EIP-712 orders use Derive collateral.";
50
+ var DERIVE_UNFUNDED_NEXT = [
51
+ "ctm_derive_fetch_risk_universes",
52
+ "ctm_derive_build_deposit_new_subaccount_multisign",
53
+ "ctm_derive_register_deposit_address",
54
+ "ctm_derive_build_socket_deposit_multisign",
55
+ "ctm_derive_build_hype_vault_deposit_multisign"
56
+ ];
57
+ var DERIVE_MISSING_SUBACCOUNT_ERROR = "subaccountId is required and must come from ctm_derive_fetch_subaccounts. If that list is empty, fund from a Derive logo row (Ethereum L1, Socket on OP/Base/Arb, or HyperEVM HYPE). Do not use subaccount_id 0 for orders.";
58
+ var DERIVE_SETTLEMENT_CHAIN_IDS = [1, 11155111];
59
+ function deriveNetworkFromChainId(chainId) {
60
+ return chainId === 11155111 ? "testnet" : "mainnet";
61
+ }
62
+ function deriveSettlementChainId(network) {
63
+ return network === "testnet" ? 11155111 : 1;
64
+ }
65
+ function deriveApiBaseUrl(network = "mainnet") {
66
+ return network === "testnet" ? DERIVE_TESTNET_API : DERIVE_MAINNET_API;
67
+ }
68
+ function deriveApiFallbackBaseUrl(network = "mainnet") {
69
+ return network === "testnet" ? DERIVE_TESTNET_API_V2 : DERIVE_MAINNET_API_V2;
70
+ }
71
+ function deriveApiBaseUrlForChainId(chainId) {
72
+ return deriveApiBaseUrl(deriveNetworkFromChainId(chainId));
73
+ }
74
+ function deriveActionManagerAddress(chainId, override) {
75
+ const explicit = (override ?? process.env.DERIVE_ACTION_MANAGER ?? "").trim();
76
+ if (explicit) return getAddress(explicit);
77
+ if (chainId === 11155111) return DERIVE_ACTION_MANAGER_TESTNET;
78
+ throw new Error(
79
+ "Derive mainnet ACTION_MANAGER is not published yet. Pass actionManager or set DERIVE_ACTION_MANAGER. Testnet (11155111) uses the documented Sepolia address."
80
+ );
81
+ }
82
+ function isDeriveSettlementChainId(chainId) {
83
+ return DERIVE_SETTLEMENT_CHAIN_IDS.includes(chainId);
84
+ }
85
+ var DERIVE_ASSET_ROW_CHAIN_IDS = [1, 10, 8453, 42161, 999, 11155111];
86
+ function isDeriveAssetRowChainId(chainId) {
87
+ return DERIVE_ASSET_ROW_CHAIN_IDS.includes(chainId);
88
+ }
89
+ function isDeriveChainSupported(chainId) {
90
+ return isDeriveAssetRowChainId(chainId);
91
+ }
92
+ var NATIVE_ZERO = "0x0000000000000000000000000000000000000000";
93
+ var USDC = {
94
+ 1: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
95
+ 10: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
96
+ 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
97
+ 42161: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
98
+ 11155111: "0x73Efab09362052D26FB93A730Be4F8a5EdC833af"
99
+ };
100
+ var WETH = {
101
+ 1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
102
+ 10: "0x4200000000000000000000000000000000000006",
103
+ 8453: "0x4200000000000000000000000000000000000006",
104
+ 42161: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"
105
+ };
106
+ var WBTC = {
107
+ 1: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
108
+ 10: "0x68f180fcCe6836688e9084f035309E29Bf0A2095",
109
+ 42161: "0x2f2a2543B76A4166549B7eCDcE5542a773Aa44fB"
110
+ };
111
+ var WHYPE = "0x5555555555555555555555555555555555555555";
112
+ function deriveMappedTokensForChain(chainId) {
113
+ const tokens = [];
114
+ const add = (address, symbol, decimals, roles) => {
115
+ if (!address) return;
116
+ tokens.push({ address: getAddress(address), symbol, decimals, roles });
117
+ };
118
+ if ([1, 10, 8453, 42161].includes(chainId)) {
119
+ add(NATIVE_ZERO, "ETH", 18, ["native", "underlying"]);
120
+ add(WETH[chainId], "WETH", 18, ["native_wrapped", "underlying"]);
121
+ }
122
+ if (chainId === 999) {
123
+ add(NATIVE_ZERO, "HYPE", 18, ["native", "collateral"]);
124
+ add(WHYPE, "WHYPE", 18, ["native_wrapped", "collateral"]);
125
+ }
126
+ add(USDC[chainId], "USDC", 6, ["collateral", "quote"]);
127
+ add(WBTC[chainId], "WBTC", 8, ["underlying", "collateral"]);
128
+ return tokens;
129
+ }
130
+ function deriveNativeWrappedForChain(chainId) {
131
+ if (chainId === 999) return WHYPE;
132
+ return WETH[chainId];
133
+ }
134
+ function deriveAdvisorNotes() {
135
+ return "Fund from Ethereum USDC/WETH/WBTC (L1 ACTION_MANAGER or deposit address), Socket USDC/WETH/WBTC on Optimism/Base/Arbitrum, or HyperEVM HYPE (vault). Derive Chain 957 is not a Continuum Assets chain. After credit, EIP-712 options use Derive collateral.";
136
+ }
137
+ function deriveKeyForNodeAssetRow(args) {
138
+ const raw = (args.contractAddress ?? "").trim();
139
+ if (!raw || raw.toLowerCase() === "native") {
140
+ const w = (args.nativeWrapped ?? "").trim();
141
+ if (!w) return NATIVE_ZERO;
142
+ try {
143
+ return getAddress(w).toLowerCase();
144
+ } catch {
145
+ return null;
146
+ }
147
+ }
148
+ try {
149
+ const a = getAddress(raw);
150
+ if (a.toLowerCase() === NATIVE_ZERO && args.nativeWrapped) {
151
+ return getAddress(args.nativeWrapped.trim()).toLowerCase();
152
+ }
153
+ return a.toLowerCase();
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
158
+
159
+ // src/protocols/evm/derive/api.ts
160
+ function methodName(method) {
161
+ return method.replace(/^\//, "");
162
+ }
163
+ function isDerivePublicMethod(method) {
164
+ return methodName(method).startsWith("public/");
165
+ }
166
+ function isEmptyDerivePublicResult(method, result) {
167
+ const m = methodName(method);
168
+ if (result == null) return true;
169
+ if (m === "public/get_all_currencies" && Array.isArray(result) && result.length === 0) return true;
170
+ if (m === "public/get_tickers" && result && typeof result === "object") {
171
+ const tickers = result.tickers;
172
+ if (Array.isArray(tickers) && tickers.length === 0) return true;
173
+ if (tickers && typeof tickers === "object" && !Array.isArray(tickers) && Object.keys(tickers).length === 0) {
174
+ return true;
175
+ }
176
+ }
177
+ return false;
178
+ }
179
+ function isDeriveRecoverablePublicError(err) {
180
+ const name = err instanceof Error ? err.name : "";
181
+ const msg = err instanceof Error ? err.message : String(err);
182
+ return name === "TimeoutError" || name === "AbortError" || /Internal error|HTTP 404|Not Found|returned empty|empty catalog|Ticker not found|aborted due to timeout|The operation was aborted/i.test(
183
+ msg
184
+ );
185
+ }
186
+ function unwrapResult(json, method) {
187
+ if (json && typeof json === "object") {
188
+ const rec = json;
189
+ if (rec.error) {
190
+ const err = rec.error;
191
+ const detail = err.data != null && String(err.data).trim() ? ` (${String(err.data).slice(0, 160)})` : "";
192
+ throw new Error(`Derive ${method} failed: ${err.message ?? JSON.stringify(rec.error)}${detail}`);
193
+ }
194
+ if ("result" in rec) return rec.result;
195
+ }
196
+ return json;
197
+ }
198
+ async function deriveRpcPost(args) {
199
+ const method = methodName(args.method);
200
+ const headers = { "Content-Type": "application/json" };
201
+ if (args.auth) {
202
+ headers["X-DeriveWallet"] = args.auth.wallet;
203
+ headers["X-DeriveTimestamp"] = args.auth.timestamp;
204
+ headers["X-DeriveSignature"] = args.auth.signature;
205
+ }
206
+ const res = await fetch(`${args.base}/${method}`, {
207
+ method: "POST",
208
+ headers,
209
+ body: JSON.stringify(args.params ?? {}),
210
+ cache: "no-store",
211
+ signal: AbortSignal.timeout(15e3)
212
+ });
213
+ const text = await res.text().catch(() => "");
214
+ if (!res.ok || !text.trim() || text.trim() === "Not Found") {
215
+ throw new Error(`Derive ${method} HTTP ${res.status}: ${text.slice(0, 240) || "empty body"}`);
216
+ }
217
+ let json;
218
+ try {
219
+ json = JSON.parse(text);
220
+ } catch {
221
+ throw new Error(`Derive ${method} returned non-JSON: ${text.slice(0, 160)}`);
222
+ }
223
+ const result = unwrapResult(json, method);
224
+ if (isEmptyDerivePublicResult(method, result)) {
225
+ throw new Error(`Derive ${method} returned empty catalog`);
226
+ }
227
+ return result;
228
+ }
229
+ async function deriveRpc(args) {
230
+ const network = args.network ?? "mainnet";
231
+ const method = methodName(args.method);
232
+ const primary = deriveApiBaseUrl(network);
233
+ try {
234
+ return await deriveRpcPost({
235
+ base: primary,
236
+ method,
237
+ params: args.params,
238
+ auth: args.auth
239
+ });
240
+ } catch (err) {
241
+ const fallback = deriveApiFallbackBaseUrl(network);
242
+ if (!isDerivePublicMethod(method) || fallback === primary || !isDeriveRecoverablePublicError(err)) {
243
+ throw err;
244
+ }
245
+ return await deriveRpcPost({
246
+ base: fallback,
247
+ method,
248
+ params: args.params,
249
+ auth: args.auth
250
+ });
251
+ }
252
+ }
253
+ function requireDeriveAuth(auth, toolHint) {
254
+ if (!auth?.wallet?.trim() || !auth?.timestamp?.trim() || !auth?.signature?.trim()) {
255
+ throw new Error(
256
+ `${toolHint} needs EIP-191 login headers (X-DeriveWallet / X-DeriveTimestamp / X-DeriveSignature). The node caches one personal_sign of a millisecond timestamp for the KeyGen executor.`
257
+ );
258
+ }
259
+ return auth;
260
+ }
261
+
262
+ // src/protocols/evm/derive/assets.ts
263
+ async function deriveFetchInstrument(args) {
264
+ const name = args.instrumentName.trim().toUpperCase();
265
+ if (!name) throw new Error("instrumentName is required.");
266
+ const result = await deriveRpc({
267
+ method: "public/get_instrument",
268
+ params: { instrument_name: name },
269
+ network: args.network
270
+ });
271
+ if (!result?.instrument_name && !result?.base_asset_address) {
272
+ throw new Error(`Derive instrument not found: ${name}`);
273
+ }
274
+ return { ...result, instrument_name: result.instrument_name ?? name };
275
+ }
276
+ async function deriveFetchCurrenciesRaw(args) {
277
+ const result = await deriveRpc({
278
+ method: "public/get_all_currencies",
279
+ network: args?.network
280
+ });
281
+ if (Array.isArray(result)) return result;
282
+ if (result && typeof result === "object" && Array.isArray(result.currencies)) {
283
+ return result.currencies;
284
+ }
285
+ return [];
286
+ }
287
+ function asInstrumentList(result) {
288
+ if (Array.isArray(result)) return result;
289
+ if (result && typeof result === "object" && Array.isArray(result.instruments)) {
290
+ return result.instruments;
291
+ }
292
+ return [];
293
+ }
294
+ async function deriveFetchOptionInstrumentsOneShot(args) {
295
+ const result = await deriveRpc({
296
+ method: "public/get_instruments",
297
+ params: {
298
+ instrument_type: "option",
299
+ currency: args.currency,
300
+ expired: args.expired
301
+ },
302
+ network: args.network
303
+ });
304
+ return asInstrumentList(result);
305
+ }
306
+ async function deriveFetchOptionInstrumentPage(args) {
307
+ const result = await deriveRpc({
308
+ method: "public/get_all_instruments",
309
+ params: {
310
+ instrument_type: "option",
311
+ currency: args.currency,
312
+ expired: args.expired,
313
+ page: args.page,
314
+ page_size: args.pageSize
315
+ },
316
+ network: args.network
317
+ });
318
+ return asInstrumentList(result);
319
+ }
320
+ async function deriveFetchOptionInstruments(args) {
321
+ const currency = args.currency.trim().toUpperCase();
322
+ if (!currency) throw new Error("currency is required (e.g. ETH).");
323
+ const expired = args.expired ?? false;
324
+ const pageSize = args.pageSize ?? 200;
325
+ if (args.page == null) {
326
+ try {
327
+ const oneShot = await deriveFetchOptionInstrumentsOneShot({ currency, expired, network: args.network });
328
+ if (oneShot.length) return { instruments: oneShot };
329
+ } catch {
330
+ }
331
+ }
332
+ if (args.page != null) {
333
+ return {
334
+ instruments: await deriveFetchOptionInstrumentPage({
335
+ currency,
336
+ expired,
337
+ page: args.page,
338
+ pageSize,
339
+ network: args.network
340
+ })
341
+ };
342
+ }
343
+ const instruments = [];
344
+ for (let page = 1; page <= 8; page++) {
345
+ const chunk = await deriveFetchOptionInstrumentPage({
346
+ currency,
347
+ expired,
348
+ page,
349
+ pageSize,
350
+ network: args.network
351
+ });
352
+ instruments.push(...chunk);
353
+ if (chunk.length < pageSize) break;
354
+ }
355
+ return { instruments };
356
+ }
357
+ function deriveSearchInstruments(instruments, query, limit = 25) {
358
+ const q = query.trim().toUpperCase();
359
+ if (!q) return instruments.slice(0, limit);
360
+ return instruments.filter((i) => {
361
+ const name = String(i.instrument_name ?? "").toUpperCase();
362
+ return name.includes(q) || name.replace(/-/g, "").includes(q.replace(/-/g, ""));
363
+ }).slice(0, limit);
364
+ }
365
+
366
+ // src/protocols/evm/derive/reads.ts
367
+ function networkFromArgs(chainId, network) {
368
+ if (network) return network;
369
+ if (chainId != null) return deriveNetworkFromChainId(chainId);
370
+ return "mainnet";
371
+ }
372
+ function assertInterval(interval) {
373
+ const v = interval ?? DERIVE_DEFAULT_OHLCV_INTERVAL;
374
+ if (!DERIVE_OHLCV_INTERVALS.includes(v)) {
375
+ throw new Error(`Invalid Derive OHLCV interval: ${interval}. Valid: ${DERIVE_OHLCV_INTERVALS.join(", ")}`);
376
+ }
377
+ return v;
378
+ }
379
+ function resolveDeriveOhlcvWindow(args) {
380
+ const periodSec = DERIVE_OHLCV_PERIOD_SEC[args.interval];
381
+ const endTimeMs = args.endTimeMs ?? Date.now();
382
+ let startTimeMs = args.startTimeMs;
383
+ if (startTimeMs == null) {
384
+ if (args.lookbackDays != null || args.lookbackHours != null) {
385
+ const days = args.lookbackDays ?? args.lookbackHours / 24;
386
+ if (days > DERIVE_MAX_LOOKBACK_DAYS) {
387
+ throw new Error(`lookbackDays max is ${DERIVE_MAX_LOOKBACK_DAYS}.`);
388
+ }
389
+ startTimeMs = endTimeMs - Math.round(days * 864e5);
390
+ } else {
391
+ startTimeMs = endTimeMs - DERIVE_DEFAULT_OHLCV_BARS * periodSec * 1e3;
392
+ }
393
+ }
394
+ if (startTimeMs >= endTimeMs) {
395
+ throw new Error("Invalid OHLCV time range: startTimeMs must be before endTimeMs.");
396
+ }
397
+ const expectedBars = Math.ceil((endTimeMs - startTimeMs) / (periodSec * 1e3));
398
+ if (expectedBars > DERIVE_MAX_OHLCV_BARS) {
399
+ throw new Error(
400
+ `OHLCV range needs ~${expectedBars} bars at ${args.interval} (max ${DERIVE_MAX_OHLCV_BARS}). Use a coarser interval or shorter lookback.`
401
+ );
402
+ }
403
+ return { startTimeMs, endTimeMs, expectedBars, periodSec };
404
+ }
405
+ function mapDeriveTradingviewCandles(raw) {
406
+ const rows = Array.isArray(raw) ? raw : raw && typeof raw === "object" && Array.isArray(raw.candles) ? raw.candles : [];
407
+ return rows.map((row) => {
408
+ const r = row;
409
+ const tsSec = Number(r.timestamp_bucket ?? r.timestamp ?? 0);
410
+ return {
411
+ timestampMs: Number.isFinite(tsSec) ? tsSec * 1e3 : 0,
412
+ open: String(r.open_price ?? r.open ?? "0"),
413
+ high: String(r.high_price ?? r.high ?? "0"),
414
+ low: String(r.low_price ?? r.low ?? "0"),
415
+ close: String(r.close_price ?? r.close ?? "0"),
416
+ volume: String(r.volume_usd ?? r.volume_contracts ?? r.volume ?? "0")
417
+ };
418
+ }).filter((c) => c.timestampMs > 0).sort((a, b) => a.timestampMs - b.timestampMs);
419
+ }
420
+ var INSTRUMENT_NAME_RE = /^([A-Z0-9]+)-(\d{8})-(\d+(?:\.\d+)?)-([CP])$/;
421
+ function deriveParseInstrumentName(name) {
422
+ const m = name.trim().toUpperCase().match(INSTRUMENT_NAME_RE);
423
+ if (!m) return null;
424
+ return { currency: m[1], expiryYmd: m[2], strike: m[3], optionType: m[4] };
425
+ }
426
+ function deriveExpiryUnixToYmd(expirySec) {
427
+ const d = new Date(expirySec * 1e3);
428
+ const y = d.getUTCFullYear();
429
+ const mo = String(d.getUTCMonth() + 1).padStart(2, "0");
430
+ const day = String(d.getUTCDate()).padStart(2, "0");
431
+ return `${y}${mo}${day}`;
432
+ }
433
+ function tickerScalar(row, keys) {
434
+ for (const key of keys) {
435
+ const v = row[key];
436
+ if (v != null && String(v).trim() !== "") return String(v);
437
+ }
438
+ return null;
439
+ }
440
+ function mapDeriveTickerRow(raw) {
441
+ if (!raw || typeof raw !== "object") return null;
442
+ const row = raw;
443
+ const inner = row.instrument_ticker && typeof row.instrument_ticker === "object" ? row.instrument_ticker : row;
444
+ const instrumentName = tickerScalar(inner, ["instrument_name", "instrumentName"])?.toUpperCase();
445
+ if (!instrumentName) return null;
446
+ const parsed = deriveParseInstrumentName(instrumentName);
447
+ const details = inner.option_details && typeof inner.option_details === "object" ? inner.option_details : {};
448
+ const expiryRaw = details.expiry;
449
+ const expiry = expiryRaw != null && Number.isFinite(Number(expiryRaw)) ? Number(expiryRaw) : null;
450
+ const optionTypeRaw = String(details.option_type ?? parsed?.optionType ?? "").trim().toUpperCase();
451
+ const optionType = optionTypeRaw === "C" || optionTypeRaw === "P" ? optionTypeRaw : parsed?.optionType ?? null;
452
+ const strike = tickerScalar(details, ["strike"]) ?? parsed?.strike ?? null;
453
+ const greeks = inner.option_pricing && typeof inner.option_pricing === "object" ? inner.option_pricing : null;
454
+ return {
455
+ instrumentName,
456
+ bidPx: tickerScalar(inner, ["b", "best_bid_price", "bid"]),
457
+ askPx: tickerScalar(inner, ["a", "best_ask_price", "ask"]),
458
+ markPx: tickerScalar(inner, ["M", "mark_price", "mark"]) ?? tickerScalar(greeks ?? {}, ["m", "mark_price"]),
459
+ indexPx: tickerScalar(inner, ["I", "index_price", "index"]),
460
+ strike,
461
+ expiry,
462
+ expiryYmd: parsed?.expiryYmd ?? (expiry != null ? deriveExpiryUnixToYmd(expiry) : null),
463
+ optionType,
464
+ greeks
465
+ };
466
+ }
467
+ function collectDeriveTickerRows(raw) {
468
+ if (Array.isArray(raw)) return raw;
469
+ if (!raw || typeof raw !== "object") return [];
470
+ const rec = raw;
471
+ if (Array.isArray(rec.tickers)) return rec.tickers;
472
+ if (rec.tickers && typeof rec.tickers === "object") {
473
+ return Object.entries(rec.tickers).map(([name, row]) => {
474
+ if (row && typeof row === "object" && !Array.isArray(row)) {
475
+ const inner = row;
476
+ if (!inner.instrument_name && !inner.instrumentName) {
477
+ return { ...inner, instrument_name: name };
478
+ }
479
+ }
480
+ return row;
481
+ });
482
+ }
483
+ if (rec.result != null) return collectDeriveTickerRows(rec.result);
484
+ return [];
485
+ }
486
+ function normalizeDeriveExpiryYmd(raw) {
487
+ if (typeof raw === "number" && Number.isFinite(raw)) return String(Math.trunc(raw));
488
+ return String(raw ?? "").trim().replace(/-/g, "");
489
+ }
490
+ function deriveAtmTickerWindow(tickers, maxStrikes = DERIVE_DEFAULT_TICKER_ATM_STRIKES) {
491
+ const strikes = [
492
+ ...new Set(
493
+ tickers.map((t) => Number(t.strike)).filter((n) => Number.isFinite(n) && n > 0)
494
+ )
495
+ ].sort((a, b) => a - b);
496
+ if (strikes.length <= maxStrikes) return tickers;
497
+ const indexPx = Number(tickers.find((t) => t.indexPx)?.indexPx);
498
+ let center = Math.floor(strikes.length / 2);
499
+ if (Number.isFinite(indexPx) && indexPx > 0) {
500
+ let best = 0;
501
+ let bestDist = Infinity;
502
+ strikes.forEach((strike, i) => {
503
+ const dist = Math.abs(strike - indexPx);
504
+ if (dist < bestDist) {
505
+ bestDist = dist;
506
+ best = i;
507
+ }
508
+ });
509
+ center = best;
510
+ }
511
+ const half = Math.floor(maxStrikes / 2);
512
+ let start = Math.max(0, center - half);
513
+ if (start + maxStrikes > strikes.length) start = Math.max(0, strikes.length - maxStrikes);
514
+ const keep = new Set(strikes.slice(start, start + maxStrikes));
515
+ return tickers.filter((t) => keep.has(Number(t.strike)));
516
+ }
517
+ async function deriveFetchTickersSummary(args) {
518
+ const network = networkFromArgs(args.chainId, args.network);
519
+ const currency = args.currency.trim().toUpperCase();
520
+ const expiryDate = normalizeDeriveExpiryYmd(args.expiryDate);
521
+ if (!currency) throw new Error("currency is required.");
522
+ if (!/^\d{8}$/.test(expiryDate)) {
523
+ throw new Error("expiryDate must be YYYYMMDD (e.g. 20260925 or 2026-09-25).");
524
+ }
525
+ const raw = await deriveRpc({
526
+ method: "public/get_tickers",
527
+ params: { instrument_type: "option", currency, expiry_date: expiryDate },
528
+ network
529
+ });
530
+ let tickers = collectDeriveTickerRows(raw).map(mapDeriveTickerRow).filter((row) => row != null);
531
+ if (args.optionType) tickers = tickers.filter((row) => row.optionType === args.optionType);
532
+ tickers.sort((a, b) => Number(a.strike ?? 0) - Number(b.strike ?? 0) || a.instrumentName.localeCompare(b.instrumentName));
533
+ const listedCount = tickers.length;
534
+ const ladder = args.ladder ?? "atm";
535
+ if (ladder === "atm") tickers = deriveAtmTickerWindow(tickers);
536
+ const indexPx = tickers.find((row) => row.indexPx)?.indexPx ?? null;
537
+ return {
538
+ currency,
539
+ expiryDate,
540
+ optionType: args.optionType ?? null,
541
+ ladder,
542
+ indexPx,
543
+ listedCount,
544
+ tickerCount: tickers.length,
545
+ callCount: tickers.filter((row) => row.optionType === "C").length,
546
+ putCount: tickers.filter((row) => row.optionType === "P").length,
547
+ tickers,
548
+ notes: ladder === "atm" ? `ATM strike/premium ladder (~${DERIVE_DEFAULT_TICKER_ATM_STRIKES} strikes around index). Pass ladder: "all" for every strike on this expiry. Then snapshot or order_quote one instrumentName.` : "Full strike/premium ladder for this expiry. Then snapshot or order_quote one instrumentName.",
549
+ network
550
+ };
551
+ }
552
+ function mapDeriveOrderBookLevels(raw) {
553
+ if (!Array.isArray(raw)) return [];
554
+ return raw.map((row) => {
555
+ if (Array.isArray(row) && row.length >= 2) {
556
+ return { price: String(row[0]), amount: String(row[1]) };
557
+ }
558
+ if (row && typeof row === "object") {
559
+ const r = row;
560
+ const price = r.price ?? r[0];
561
+ const amount = r.amount ?? r.size ?? r[1];
562
+ if (price != null && amount != null) return { price: String(price), amount: String(amount) };
563
+ }
564
+ return null;
565
+ }).filter((row) => row != null).slice(0, 8);
566
+ }
567
+ async function deriveFetchOrderBookSummary(args) {
568
+ const network = networkFromArgs(args.chainId, args.network);
569
+ const instrumentName = args.instrumentName.trim().toUpperCase();
570
+ if (!instrumentName) throw new Error("instrumentName is required.");
571
+ const raw = await deriveRpc({
572
+ method: "public/get_order_book",
573
+ params: { instrument_name: instrumentName, depth: args.depth ?? 10 },
574
+ network
575
+ });
576
+ return {
577
+ instrumentName,
578
+ bids: mapDeriveOrderBookLevels(raw.bids),
579
+ asks: mapDeriveOrderBookLevels(raw.asks),
580
+ network
581
+ };
582
+ }
583
+ async function deriveFetchCurrenciesSummary(args) {
584
+ const network = networkFromArgs(args.chainId, args.network);
585
+ const currencies = await deriveFetchCurrenciesRaw({ network });
586
+ return {
587
+ currencies: currencies.map((c) => ({
588
+ currency: String(c.currency ?? c.name ?? "")
589
+ })),
590
+ network
591
+ };
592
+ }
593
+ function summarizeDeriveOptionChain(args) {
594
+ const currency = args.currency.trim().toUpperCase();
595
+ const byExpiry = /* @__PURE__ */ new Map();
596
+ let callCount = 0;
597
+ let putCount = 0;
598
+ for (const row of args.instruments) {
599
+ if (row.isActive === false) continue;
600
+ const parsed = deriveParseInstrumentName(row.instrumentName);
601
+ const optionTypeRaw = String(row.optionType ?? parsed?.optionType ?? "").trim().toUpperCase();
602
+ const optionType = optionTypeRaw === "C" || optionTypeRaw === "P" ? optionTypeRaw : null;
603
+ const expiryYmd = parsed?.expiryYmd ?? (row.expiry != null && Number.isFinite(row.expiry) ? deriveExpiryUnixToYmd(row.expiry) : "");
604
+ if (!expiryYmd) continue;
605
+ const strikeNum = Number(row.strike ?? parsed?.strike ?? NaN);
606
+ const bucket = byExpiry.get(expiryYmd) ?? { expiryUnix: row.expiry ?? null, calls: 0, puts: 0, strikes: [] };
607
+ if (bucket.expiryUnix == null && row.expiry != null) bucket.expiryUnix = row.expiry;
608
+ if (optionType === "C") {
609
+ bucket.calls += 1;
610
+ callCount += 1;
611
+ } else if (optionType === "P") {
612
+ bucket.puts += 1;
613
+ putCount += 1;
614
+ }
615
+ if (Number.isFinite(strikeNum)) bucket.strikes.push(strikeNum);
616
+ byExpiry.set(expiryYmd, bucket);
617
+ }
618
+ const expiries = [...byExpiry.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([expiryYmd, bucket]) => {
619
+ const sorted = [...bucket.strikes].sort((a, b) => a - b);
620
+ return {
621
+ expiryYmd,
622
+ expiryUnix: bucket.expiryUnix,
623
+ callCount: bucket.calls,
624
+ putCount: bucket.puts,
625
+ minStrike: sorted[0] != null ? String(sorted[0]) : null,
626
+ maxStrike: sorted[sorted.length - 1] != null ? String(sorted[sorted.length - 1]) : null
627
+ };
628
+ });
629
+ return {
630
+ currency,
631
+ instrumentCount: callCount + putCount,
632
+ callCount,
633
+ putCount,
634
+ expiries
635
+ };
636
+ }
637
+ async function deriveFetchOptionChainSummary(args) {
638
+ const network = networkFromArgs(args.chainId, args.network);
639
+ const { instruments } = await deriveFetchOptionInstruments({
640
+ currency: args.currency,
641
+ expired: args.expired,
642
+ network
643
+ });
644
+ return {
645
+ currency: args.currency.trim().toUpperCase(),
646
+ instruments: instruments.map((i) => {
647
+ const parsed = deriveParseInstrumentName(i.instrument_name);
648
+ const optionTypeRaw = String(i.option_details?.option_type ?? parsed?.optionType ?? "").trim().toUpperCase();
649
+ return {
650
+ instrumentName: i.instrument_name,
651
+ instrumentType: i.instrument_type ?? "option",
652
+ isActive: i.is_active ?? true,
653
+ strike: i.option_details?.strike ?? parsed?.strike ?? null,
654
+ expiry: i.option_details?.expiry ?? null,
655
+ optionType: optionTypeRaw === "C" || optionTypeRaw === "P" ? optionTypeRaw : parsed?.optionType ?? null
656
+ };
657
+ }),
658
+ network
659
+ };
660
+ }
661
+ async function deriveFetchOptionChainAgentSummary(args) {
662
+ const full = await deriveFetchOptionChainSummary(args);
663
+ return {
664
+ ...summarizeDeriveOptionChain(full),
665
+ notes: 'Complete expiry list (counts + min/max strike only \u2014 not a missing-weekly warning). Next: ctm_derive_fetch_tickers {currency, expiryDate} for an ATM premium ladder. expiryDate accepts 20260925 or 2026-09-25. Pass ladder: "all" only if the operator wants every strike.',
666
+ network: full.network
667
+ };
668
+ }
669
+ async function deriveSearchInstrumentsSummary(args) {
670
+ const network = networkFromArgs(args.chainId, args.network);
671
+ const { instruments } = await deriveFetchOptionInstruments({ currency: args.currency, network });
672
+ const hits = deriveSearchInstruments(instruments, args.query, args.limit ?? 25);
673
+ return {
674
+ instruments: hits.map((i) => ({ instrumentName: i.instrument_name })),
675
+ network
676
+ };
677
+ }
678
+ async function deriveFetchMarketSnapshotSummary(args) {
679
+ const network = networkFromArgs(args.chainId, args.network);
680
+ const name = args.instrumentName.trim().toUpperCase();
681
+ const ticker = await deriveRpc({
682
+ method: "public/get_ticker",
683
+ params: { instrument_name: name },
684
+ network
685
+ });
686
+ const mark = ticker.M != null ? String(ticker.M) : null;
687
+ const index = ticker.I != null ? String(ticker.I) : null;
688
+ const bid = ticker.b != null ? String(ticker.b) : null;
689
+ const ask = ticker.a != null ? String(ticker.a) : null;
690
+ const midUsd = mark ?? (bid && ask ? String((Number(bid) + Number(ask)) / 2) : bid ?? ask);
691
+ if (!midUsd) throw new Error(`No live ticker for ${name}.`);
692
+ return {
693
+ snapshot: {
694
+ instrumentName: name,
695
+ livePrice: {
696
+ markPx: mark,
697
+ indexPx: index,
698
+ bidPx: bid,
699
+ askPx: ask,
700
+ midUsd,
701
+ source: mark ? "mark" : "bidAsk"
702
+ },
703
+ greeks: ticker.option_pricing ?? null,
704
+ fetchedAtMs: Date.now()
705
+ },
706
+ network
707
+ };
708
+ }
709
+ async function deriveFetchOhlcvSummary(args) {
710
+ const network = networkFromArgs(args.chainId, args.network);
711
+ const interval = assertInterval(args.interval);
712
+ const { startTimeMs, endTimeMs, expectedBars, periodSec } = resolveDeriveOhlcvWindow({
713
+ interval,
714
+ lookbackDays: args.lookbackDays,
715
+ lookbackHours: args.lookbackHours,
716
+ startTimeMs: args.startTimeMs,
717
+ endTimeMs: args.endTimeMs
718
+ });
719
+ const kind = args.marketKind ?? (args.currency && !args.instrumentName ? "index" : "option");
720
+ let candles;
721
+ let market;
722
+ if (kind === "index") {
723
+ const currency = (args.currency ?? args.instrumentName ?? "").trim().toUpperCase().replace(/-.*$/, "");
724
+ if (!currency) throw new Error("currency is required for index OHLCV.");
725
+ market = currency;
726
+ const raw = await deriveRpc({
727
+ method: "public/get_index_chart_data",
728
+ params: {
729
+ currency,
730
+ start_timestamp: Math.floor(startTimeMs / 1e3),
731
+ end_timestamp: Math.floor(endTimeMs / 1e3),
732
+ period: periodSec
733
+ },
734
+ network
735
+ });
736
+ candles = mapDeriveTradingviewCandles(raw);
737
+ } else {
738
+ const instrumentName = (args.instrumentName ?? "").trim().toUpperCase();
739
+ if (!instrumentName) throw new Error("instrumentName is required for option OHLCV.");
740
+ market = instrumentName;
741
+ const raw = await deriveRpc({
742
+ method: "public/get_tradingview_chart_data",
743
+ params: {
744
+ instrument_name: instrumentName,
745
+ start_timestamp: Math.floor(startTimeMs / 1e3),
746
+ end_timestamp: Math.floor(endTimeMs / 1e3),
747
+ period: periodSec
748
+ },
749
+ network
750
+ });
751
+ candles = mapDeriveTradingviewCandles(raw);
752
+ }
753
+ if (candles.length > expectedBars) {
754
+ candles = candles.slice(-expectedBars);
755
+ }
756
+ if (candles.length === 0) {
757
+ throw new Error(`No OHLCV data for ${market} between ${startTimeMs} and ${endTimeMs} at ${interval}.`);
758
+ }
759
+ return {
760
+ ohlcv: {
761
+ market,
762
+ coin: market,
763
+ interval,
764
+ marketKind: kind,
765
+ startTimeMs,
766
+ endTimeMs,
767
+ expectedBars,
768
+ candleCount: candles.length,
769
+ latestCandle: candles[candles.length - 1] ?? null,
770
+ candles,
771
+ fetchedAtMs: Date.now(),
772
+ dataSource: "derive"
773
+ },
774
+ dataSource: "derive",
775
+ network
776
+ };
777
+ }
778
+ async function deriveOrderQuoteSummary(args) {
779
+ const network = networkFromArgs(args.chainId, args.network);
780
+ const instrumentName = args.instrumentName.trim().toUpperCase();
781
+ const quote = await deriveRpc({
782
+ method: "public/order_quote",
783
+ params: {
784
+ instrument_name: instrumentName,
785
+ direction: args.direction,
786
+ amount: args.amount,
787
+ limit_price: args.limitPrice,
788
+ ...args.subaccountId && args.subaccountId > 0 ? { subaccount_id: args.subaccountId } : {}
789
+ },
790
+ network
791
+ });
792
+ return { quote, instrumentName, network };
793
+ }
794
+ function synthesizeDeriveRiskUniversesFromCurrencies(currencies) {
795
+ const byKey = /* @__PURE__ */ new Map();
796
+ for (const c of currencies) {
797
+ const name = String(c.currency ?? "").trim().toUpperCase();
798
+ if (!name) continue;
799
+ const addrs = c.protocol_asset_addresses ?? {};
800
+ const types = (c.instrument_types ?? []).map((t) => String(t));
801
+ const collateral = {
802
+ name,
803
+ address: addrs.option ?? addrs.spot ?? null,
804
+ erc20: { underlying_erc20: addrs.underlying_erc20 ?? null }
805
+ };
806
+ for (const mgr of c.managers ?? []) {
807
+ const address = String(mgr.address ?? "").trim();
808
+ if (!address) continue;
809
+ const key = `${address.toLowerCase()}:${mgr.margin_type ?? ""}:${mgr.currency ?? ""}`;
810
+ const existing = byKey.get(key);
811
+ if (existing) {
812
+ if (!existing.instrument_types.includes("option") && types.includes("option")) {
813
+ existing.instrument_types = [.../* @__PURE__ */ new Set([...existing.instrument_types, ...types])];
814
+ }
815
+ if (!existing.collaterals.some((row) => row.name === name)) existing.collaterals.push(collateral);
816
+ continue;
817
+ }
818
+ byKey.set(key, {
819
+ manager_id: typeof mgr.manager_id === "number" ? mgr.manager_id : null,
820
+ address,
821
+ margin_type: mgr.margin_type ?? null,
822
+ currency: mgr.currency ?? null,
823
+ instrument_types: types,
824
+ collaterals: [collateral]
825
+ });
826
+ }
827
+ }
828
+ return [
829
+ {
830
+ risk_universe_id: null,
831
+ source: "public/get_all_currencies",
832
+ managers: [...byKey.values()]
833
+ }
834
+ ];
835
+ }
836
+ async function deriveFetchRiskUniversesSummary(args) {
837
+ const network = networkFromArgs(args.chainId, args.network);
838
+ try {
839
+ const result = await deriveRpc({ method: "public/get_risk_universes", network });
840
+ const universes = Array.isArray(result) ? result : result?.universes ?? [];
841
+ if (universes.length) {
842
+ return {
843
+ universes,
844
+ notes: "Pick a manager that lists ETH-OPTION (or the underlying) and accepts your collateral (usually USDC). L1 ACTION_MANAGER uses manager_id + collaterals[].address + underlying_erc20. Socket / HYPE vault deposits do not need the protocol-asset address.",
845
+ network
846
+ };
847
+ }
848
+ } catch {
849
+ }
850
+ const currencies = await deriveFetchCurrenciesRaw({ network });
851
+ return {
852
+ universes: synthesizeDeriveRiskUniversesFromCurrencies(currencies),
853
+ notes: "v3 public/get_risk_universes is failing on mainnet; managers below are reconstructed from public/get_all_currencies (address + margin_type + protocol assets). L1 ACTION_MANAGER still needs a numeric manager_id \u2014 use Socket / HYPE vault, or register_deposit_address, until v3 recovers. Socket / HYPE vault deposits do not need the protocol-asset address.",
854
+ network
855
+ };
856
+ }
857
+ async function deriveFetchPendingDepositsSummary(args) {
858
+ const network = networkFromArgs(args.chainId, args.network);
859
+ const wallet = (args.wallet ?? args.executorAddress ?? "").trim();
860
+ if (!wallet) throw new Error("wallet or executorAddress is required.");
861
+ const result = await deriveRpc({
862
+ method: "public/get_pending_deposits",
863
+ params: { wallet },
864
+ network
865
+ });
866
+ const pending = Array.isArray(result) ? result : result?.deposits ?? [];
867
+ return {
868
+ pending,
869
+ credited: Array.isArray(pending) && pending.length === 0,
870
+ notes: pending.length === 0 ? "No pending L1 deposits. If you just sent depositToNewSubaccount, wait ~2 minutes and retry, then call ctm_derive_fetch_subaccounts." : "Deposit is queued on L1 and not yet credited. Wait and poll again \u2014 do not place orders until fetch_subaccounts returns ids.",
871
+ network
872
+ };
873
+ }
874
+ function networkFromArgs2(chainId, network) {
875
+ if (network) return network;
876
+ if (chainId != null) return deriveNetworkFromChainId(chainId);
877
+ return "mainnet";
878
+ }
879
+ function parseSubaccountIds(result) {
880
+ if (Array.isArray(result)) {
881
+ return result.map((x) => Number(x)).filter((n) => Number.isFinite(n) && n > 0);
882
+ }
883
+ if (result && typeof result === "object") {
884
+ const rec = result;
885
+ const ids = rec.subaccount_ids ?? rec.subaccounts ?? rec.ids;
886
+ if (Array.isArray(ids)) {
887
+ return ids.map((x) => typeof x === "object" && x ? Number(x.subaccount_id ?? x) : Number(x)).filter((n) => Number.isFinite(n) && n > 0);
888
+ }
889
+ }
890
+ return [];
891
+ }
892
+ function deriveEmptySubaccountsPayload(wallet) {
893
+ return {
894
+ wallet,
895
+ subaccounts: [],
896
+ funded: false,
897
+ notes: DERIVE_UNFUNDED_NOTES,
898
+ next: [...DERIVE_UNFUNDED_NEXT]
899
+ };
900
+ }
901
+ function resolveWallet(args) {
902
+ const raw = (args.wallet ?? args.executorAddress ?? "").trim();
903
+ if (!raw) throw new Error("wallet or executorAddress is required.");
904
+ return raw;
905
+ }
906
+ async function deriveFetchSubaccountsSummary(args) {
907
+ const network = networkFromArgs2(args.chainId, args.network);
908
+ const wallet = getAddress(resolveWallet(args));
909
+ const auth = requireDeriveAuth(args.auth, "ctm_derive_fetch_subaccounts");
910
+ const result = await deriveRpc({
911
+ method: "private/get_subaccounts",
912
+ params: { wallet },
913
+ network,
914
+ auth
915
+ });
916
+ const ids = parseSubaccountIds(result);
917
+ if (ids.length === 0) {
918
+ return { ...deriveEmptySubaccountsPayload(wallet), network };
919
+ }
920
+ return {
921
+ wallet,
922
+ subaccounts: ids,
923
+ funded: true,
924
+ notes: "Same KeyGen owns these subaccounts on every chain. Place/cancel/replace need subaccountId from this list. First deposit also creates a fallback (manager 0) \u2014 two ids is normal.",
925
+ next: ["ctm_derive_fetch_portfolio", "ctm_derive_build_place_order_multisign"],
926
+ network
927
+ };
928
+ }
929
+ async function deriveFetchPortfolioSummary(args) {
930
+ const network = networkFromArgs2(args.chainId, args.network);
931
+ if (!args.subaccountId || args.subaccountId <= 0) throw new Error(DERIVE_MISSING_SUBACCOUNT_ERROR);
932
+ const auth = requireDeriveAuth(args.auth, "ctm_derive_fetch_portfolio");
933
+ const portfolio = await deriveRpc({
934
+ method: "private/get_subaccount",
935
+ params: { subaccount_id: args.subaccountId },
936
+ network,
937
+ auth
938
+ });
939
+ return { portfolio, subaccountId: args.subaccountId, wallet: getAddress(resolveWallet(args)), network };
940
+ }
941
+ async function deriveFetchPositionsSummary(args) {
942
+ const network = networkFromArgs2(args.chainId, args.network);
943
+ if (!args.subaccountId || args.subaccountId <= 0) throw new Error(DERIVE_MISSING_SUBACCOUNT_ERROR);
944
+ const auth = requireDeriveAuth(args.auth, "ctm_derive_fetch_positions");
945
+ const result = await deriveRpc({
946
+ method: "private/get_positions",
947
+ params: { subaccount_id: args.subaccountId },
948
+ network,
949
+ auth
950
+ });
951
+ const positions = Array.isArray(result) ? result : result.positions ?? [];
952
+ return { positions, subaccountId: args.subaccountId, wallet: getAddress(resolveWallet(args)), network };
953
+ }
954
+ async function deriveFetchOpenOrdersSummary(args) {
955
+ const network = networkFromArgs2(args.chainId, args.network);
956
+ if (!args.subaccountId || args.subaccountId <= 0) throw new Error(DERIVE_MISSING_SUBACCOUNT_ERROR);
957
+ const auth = requireDeriveAuth(args.auth, "ctm_derive_fetch_open_orders");
958
+ const result = await deriveRpc({
959
+ method: "private/get_open_orders",
960
+ params: { subaccount_id: args.subaccountId },
961
+ network,
962
+ auth
963
+ });
964
+ const orders = Array.isArray(result) ? result : result.orders ?? [];
965
+ return { orders, subaccountId: args.subaccountId, wallet: getAddress(resolveWallet(args)), network };
966
+ }
967
+ var DERIVE_ACTION_TYPES = {
968
+ Action: [
969
+ { name: "subaccountId", type: "uint256" },
970
+ { name: "nonce", type: "uint256" },
971
+ { name: "module", type: "address" },
972
+ { name: "data", type: "bytes" },
973
+ { name: "expiry", type: "uint256" },
974
+ { name: "owner", type: "address" },
975
+ { name: "signer", type: "address" }
976
+ ]
977
+ };
978
+ function deriveE18(value, field) {
979
+ const text = value.trim();
980
+ if (!/^-?\d+(\.\d+)?$/.test(text)) {
981
+ throw new Error(`${field} must be a decimal string (no scientific notation).`);
982
+ }
983
+ const scaled = parseUnits(text, 18);
984
+ if (scaled % 1000000n !== 0n) {
985
+ throw new Error(`${field} has more than 12 decimal places \u2014 Derive rejects sub-1e12 precision.`);
986
+ }
987
+ return scaled;
988
+ }
989
+ function deriveRandomNonce(nowMs = Date.now()) {
990
+ const nanos = BigInt(nowMs) * 1000000n + BigInt(Math.floor(Math.random() * 1e6));
991
+ return nanos.toString();
992
+ }
993
+ function deriveExpirySec(secondsFromNow = DERIVE_DEFAULT_SIGNATURE_EXPIRY_SEC, nowMs = Date.now()) {
994
+ if (secondsFromNow < DERIVE_MIN_ORDER_EXPIRY_SEC) {
995
+ throw new Error(`Order signature expiry must be at least ${DERIVE_MIN_ORDER_EXPIRY_SEC} seconds.`);
996
+ }
997
+ return Math.floor(nowMs / 1e3) + secondsFromNow;
998
+ }
999
+ function deriveMatchingDomain(network) {
1000
+ return {
1001
+ name: DERIVE_EIP712_NAME,
1002
+ version: DERIVE_EIP712_VERSION,
1003
+ chainId: BigInt(deriveSettlementChainId(network)),
1004
+ verifyingContract: DERIVE_MATCHING_VERIFYING_CONTRACT
1005
+ };
1006
+ }
1007
+ function deriveDomainSeparator(network) {
1008
+ return network === "testnet" ? DERIVE_DOMAIN_SEPARATOR_TESTNET : DERIVE_DOMAIN_SEPARATOR_MAINNET;
1009
+ }
1010
+ function encodeDeriveTradeData(args) {
1011
+ return encodeAbiParameters(
1012
+ [
1013
+ { type: "address" },
1014
+ { type: "uint256" },
1015
+ { type: "int256" },
1016
+ { type: "int256" },
1017
+ { type: "uint256" },
1018
+ { type: "uint256" },
1019
+ { type: "bool" }
1020
+ ],
1021
+ [
1022
+ getAddress(args.assetAddress),
1023
+ BigInt(args.subId),
1024
+ deriveE18(args.limitPrice, "limitPrice"),
1025
+ deriveE18(args.amount, "amount"),
1026
+ deriveE18(args.maxFee, "maxFee"),
1027
+ BigInt(args.recipientSubaccountId),
1028
+ args.isBid
1029
+ ]
1030
+ );
1031
+ }
1032
+ function encodeDeriveTransferData(args) {
1033
+ const amount = deriveE18(args.amount, "amount");
1034
+ if (amount <= 0n) throw new Error("transfer amount must be strictly positive");
1035
+ return encodeAbiParameters(
1036
+ [
1037
+ { type: "uint256" },
1038
+ { type: "uint256" },
1039
+ { type: "address" },
1040
+ { type: "uint256" },
1041
+ { type: "uint256" },
1042
+ { type: "uint256" }
1043
+ ],
1044
+ [
1045
+ BigInt(args.toSubaccountId),
1046
+ BigInt(args.newSubaccountManager),
1047
+ getAddress(args.asset),
1048
+ BigInt(args.subId),
1049
+ amount,
1050
+ deriveE18(args.maxFeeUsd, "maxFeeUsd")
1051
+ ]
1052
+ );
1053
+ }
1054
+ function deriveActionTypehash() {
1055
+ return keccak256(toHex("Action(uint256 subaccountId,uint256 nonce,address module,bytes data,uint256 expiry,address owner,address signer)"));
1056
+ }
1057
+ function buildDeriveActionTypedData(args) {
1058
+ const owner = getAddress(args.owner);
1059
+ const signer = getAddress(args.signer);
1060
+ return {
1061
+ domain: deriveMatchingDomain(args.network),
1062
+ types: DERIVE_ACTION_TYPES,
1063
+ primaryType: "Action",
1064
+ message: {
1065
+ subaccountId: BigInt(args.subaccountId),
1066
+ nonce: BigInt(args.nonce),
1067
+ module: getAddress(args.module),
1068
+ data: args.data,
1069
+ expiry: BigInt(args.expirySec),
1070
+ owner,
1071
+ signer
1072
+ }
1073
+ };
1074
+ }
1075
+ function hashDeriveAction(args) {
1076
+ const typed = buildDeriveActionTypedData(args);
1077
+ return hashTypedData({
1078
+ domain: typed.domain,
1079
+ types: typed.types,
1080
+ primaryType: "Action",
1081
+ message: typed.message
1082
+ });
1083
+ }
1084
+ function assertPositiveSubaccountId(subaccountId) {
1085
+ if (subaccountId == null) {
1086
+ throw new Error("subaccountId is required.");
1087
+ }
1088
+ const id = BigInt(subaccountId);
1089
+ if (id <= 0n) {
1090
+ throw new Error("subaccountId must be a positive Derive subaccount id (not 0).");
1091
+ }
1092
+ return id;
1093
+ }
1094
+ function deriveLoginTimestamp(nowMs = Date.now()) {
1095
+ return String(nowMs);
1096
+ }
1097
+ function deriveLoginMessage(timestampMs) {
1098
+ const ts = timestampMs.trim();
1099
+ if (!/^\d+$/.test(ts)) throw new Error("Derive login timestamp must be a millisecond UTC decimal string.");
1100
+ return ts;
1101
+ }
1102
+ function deriveLoginDigest(timestampMs) {
1103
+ return hashMessage(deriveLoginMessage(timestampMs));
1104
+ }
1105
+ function deriveAuthHeaders(auth) {
1106
+ return {
1107
+ "X-DeriveWallet": getAddress(auth.wallet),
1108
+ "X-DeriveTimestamp": auth.timestamp.trim(),
1109
+ "X-DeriveSignature": auth.signature.trim()
1110
+ };
1111
+ }
1112
+
1113
+ // src/core/purpose.ts
1114
+ function mergePurposeText(purposeText, purposeSuffix) {
1115
+ const t = (purposeText ?? "").trim();
1116
+ const suffix = (purposeSuffix ?? "").trim();
1117
+ if (!suffix) return t;
1118
+ return t ? `${t}
1119
+
1120
+ ${suffix}` : suffix;
1121
+ }
1122
+
1123
+ // src/core/envelope.ts
1124
+ function finalizeMultisign(input) {
1125
+ const { keyGen, destinationChainID, legs } = input;
1126
+ if (legs.length === 0) {
1127
+ throw new Error("finalizeMultisign requires at least one leg");
1128
+ }
1129
+ const ph = (keyGen.pubkeyhex ?? "").trim();
1130
+ if (!ph) throw new Error("keyGen pubKey (pubkeyhex) is required");
1131
+ const keyList = keyGen.keylist ?? [];
1132
+ const clientId = getClientIdFromKeyGenResult(keyGen);
1133
+ const first = legs[0];
1134
+ const messageHashes = legs.map((l) => l.msgHash);
1135
+ const messageRawBatch = legs.map((l) => l.msgRaw);
1136
+ const batchMeta = legs.map((l) => ({
1137
+ destinationAddress: l.destinationAddress,
1138
+ signatureText: l.signatureText,
1139
+ ...l.audit
1140
+ }));
1141
+ const proposalTxParams = legs.map((l) => l.proposalTxParams).filter((p) => p != null && typeof p === "object");
1142
+ const extraPayload = {
1143
+ batchMeta,
1144
+ ...input.extraJSON ?? {}
1145
+ };
1146
+ const extraJSON = JSON.stringify(extraPayload, (_, v) => typeof v === "bigint" ? v.toString() : v);
1147
+ const bodyForSign = {
1148
+ keyList,
1149
+ pubKey: ph,
1150
+ msgHash: messageHashes[0],
1151
+ msgRaw: first.msgRaw,
1152
+ destinationChainID,
1153
+ destinationAddress: input.destinationAddress ?? first.destinationAddress,
1154
+ extraJSON,
1155
+ signatureText: first.signatureText,
1156
+ purpose: mergePurposeText(input.purposeText, input.purposeSuffix),
1157
+ ...first.feeSnapshot
1158
+ };
1159
+ if (legs.length > 1) {
1160
+ bodyForSign.messageHashes = messageHashes;
1161
+ bodyForSign.messageRawBatch = messageRawBatch;
1162
+ }
1163
+ if (proposalTxParams.length > 0) {
1164
+ bodyForSign.proposalTxParams = proposalTxParams;
1165
+ }
1166
+ const valueWei = first.valueWei;
1167
+ if (valueWei != null && valueWei > 0n) {
1168
+ bodyForSign.value = valueWei.toString();
1169
+ }
1170
+ if (clientId) bodyForSign.clientId = clientId;
1171
+ if (input.expiryDate != null && input.expiryDate > 0) {
1172
+ bodyForSign.expiryDate = Math.floor(input.expiryDate);
1173
+ }
1174
+ return { bodyForSign, messageToSign: JSON.stringify(bodyForSign) };
1175
+ }
1176
+
1177
+ // src/core/eip712Multisign.ts
1178
+ var EIP712_SIGN_REQUEST_KIND = "eip712";
1179
+ function msgHashNo0x(digest) {
1180
+ return digest.startsWith("0x") ? digest.slice(2) : digest;
1181
+ }
1182
+ function eip712SignatureText(domain, primaryType) {
1183
+ return JSON.stringify({
1184
+ kind: "EIP-712",
1185
+ ...typeof domain.name === "string" ? { name: domain.name } : {},
1186
+ primaryType
1187
+ });
1188
+ }
1189
+ function jsonStringifyForAudit(value) {
1190
+ return JSON.stringify(value, (_, v) => typeof v === "bigint" ? v.toString() : v);
1191
+ }
1192
+ function eip712LegEnvelope(typedData, delivery, digest) {
1193
+ return {
1194
+ version: 1,
1195
+ digest,
1196
+ domain: typedData.domain,
1197
+ types: typedData.types,
1198
+ primaryType: typedData.primaryType,
1199
+ message: typedData.message,
1200
+ delivery
1201
+ };
1202
+ }
1203
+ function eip712MsgRawHex(envelope) {
1204
+ return stringToHex(jsonStringifyForAudit(envelope));
1205
+ }
1206
+ function buildEip712Multisign(args) {
1207
+ if (args.legs.length < 1) {
1208
+ throw new Error("buildEip712Multisign requires at least one leg");
1209
+ }
1210
+ const eip712 = [];
1211
+ const legs = args.legs.map((leg) => {
1212
+ const digest = hashTypedData({
1213
+ domain: leg.typedData.domain,
1214
+ types: leg.typedData.types,
1215
+ primaryType: leg.typedData.primaryType,
1216
+ message: leg.typedData.message
1217
+ });
1218
+ const envelope = eip712LegEnvelope(leg.typedData, leg.delivery, digest);
1219
+ eip712.push(envelope);
1220
+ return {
1221
+ msgHash: msgHashNo0x(digest),
1222
+ msgRaw: eip712MsgRawHex(envelope),
1223
+ destinationAddress: args.destinationAddress,
1224
+ signatureText: eip712SignatureText(leg.typedData.domain, leg.typedData.primaryType),
1225
+ audit: {
1226
+ kind: "EIP712",
1227
+ primaryType: leg.typedData.primaryType,
1228
+ deliveryKind: leg.delivery.kind,
1229
+ ...leg.audit ?? {}
1230
+ },
1231
+ feeSnapshot: {}
1232
+ };
1233
+ });
1234
+ return finalizeMultisign({
1235
+ keyGen: args.keyGen,
1236
+ purposeText: args.purposeText,
1237
+ purposeSuffix: args.purposeSuffix,
1238
+ destinationChainID: args.destinationChainID,
1239
+ destinationAddress: args.destinationAddress,
1240
+ extraJSON: {
1241
+ signRequestKind: EIP712_SIGN_REQUEST_KIND,
1242
+ eip712
1243
+ },
1244
+ expiryDate: args.expiryDate,
1245
+ legs
1246
+ });
1247
+ }
1248
+
1249
+ // src/protocols/evm/derive/exchangeMultisign.ts
1250
+ var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
1251
+ function networkFromChain(chainId) {
1252
+ return deriveNetworkFromChainId(chainId);
1253
+ }
1254
+ function requireSubaccount(subaccountId) {
1255
+ try {
1256
+ return assertPositiveSubaccountId(subaccountId);
1257
+ } catch {
1258
+ throw new Error(DERIVE_MISSING_SUBACCOUNT_ERROR);
1259
+ }
1260
+ }
1261
+ async function loadTradeData(args) {
1262
+ const instrument = await deriveFetchInstrument({ instrumentName: args.instrumentName, network: args.network });
1263
+ const assetAddress = String(instrument.base_asset_address ?? "").trim();
1264
+ const subId = instrument.base_asset_sub_id ?? instrument.sub_id;
1265
+ if (!assetAddress || subId == null) {
1266
+ throw new Error(`Instrument ${args.instrumentName} is missing base_asset_address / sub_id.`);
1267
+ }
1268
+ const data = encodeDeriveTradeData({
1269
+ assetAddress,
1270
+ subId,
1271
+ limitPrice: args.limitPrice,
1272
+ amount: args.amount,
1273
+ maxFee: args.maxFee,
1274
+ recipientSubaccountId: args.subaccountId,
1275
+ isBid: args.direction === "buy"
1276
+ });
1277
+ return { instrument, assetAddress, subId: String(subId), data };
1278
+ }
1279
+ function deriveExchangeDelivery(args) {
1280
+ const ticket = args.ticketKind ?? "limit";
1281
+ const triggerPrice = (args.triggerPrice ?? "").trim();
1282
+ const durationSec = args.algoDurationSec;
1283
+ const slices = args.algoNumSlices;
1284
+ if (ticket === "stop_limit") {
1285
+ if (!triggerPrice || !(Number(triggerPrice) > 0)) {
1286
+ throw new Error("Stop limit requires a positive trigger price.");
1287
+ }
1288
+ }
1289
+ if (ticket === "twap") {
1290
+ if (durationSec == null || !Number.isFinite(durationSec) || durationSec < 60) {
1291
+ throw new Error("TWAP requires algoDurationSec of at least 60.");
1292
+ }
1293
+ if (slices == null || !Number.isInteger(slices) || slices < 2) {
1294
+ throw new Error("TWAP requires algoNumSlices of at least 2.");
1295
+ }
1296
+ }
1297
+ return {
1298
+ kind: "derive_exchange",
1299
+ method: args.method,
1300
+ chainId: args.chainId,
1301
+ settlementChainId: deriveSettlementChainId(args.network),
1302
+ network: args.network,
1303
+ params: {
1304
+ subaccount_id: Number(args.subaccountId),
1305
+ nonce: args.nonce,
1306
+ signer: args.signer,
1307
+ signature_expiry_sec: args.signatureExpirySec,
1308
+ instrument_name: args.instrumentName,
1309
+ direction: args.direction,
1310
+ amount: args.amount,
1311
+ limit_price: args.limitPrice,
1312
+ max_fee: args.maxFee,
1313
+ order_type: args.orderType,
1314
+ time_in_force: args.timeInForce,
1315
+ reduce_only: args.reduceOnly,
1316
+ ...args.label ? { label: args.label } : {},
1317
+ ...args.orderIdToCancel ? { order_id_to_cancel: args.orderIdToCancel } : {},
1318
+ ...args.nonceToCancel ? { nonce_to_cancel: args.nonceToCancel } : {},
1319
+ ...ticket === "stop_limit" ? {
1320
+ trigger_price: triggerPrice,
1321
+ trigger_type: args.triggerType ?? "stoploss",
1322
+ trigger_price_type: args.triggerPriceType ?? "mark"
1323
+ } : {},
1324
+ ...ticket === "twap" ? {
1325
+ algo_type: "twap",
1326
+ algo_duration_sec: durationSec,
1327
+ algo_num_slices: slices
1328
+ } : {}
1329
+ }
1330
+ };
1331
+ }
1332
+ async function buildDerivePlaceOrderMultisign(args) {
1333
+ const subaccountId = requireSubaccount(args.subaccountId);
1334
+ const network = networkFromChain(args.chainId);
1335
+ const owner = getAddress(args.executorAddress);
1336
+ const nonce = args.nonce ?? deriveRandomNonce();
1337
+ const expirySec = deriveExpirySec(args.signatureExpirySec);
1338
+ const { data, instrument } = await loadTradeData({
1339
+ instrumentName: args.instrumentName,
1340
+ direction: args.direction,
1341
+ amount: args.amount,
1342
+ limitPrice: args.limitPrice,
1343
+ maxFee: args.maxFee,
1344
+ subaccountId,
1345
+ network
1346
+ });
1347
+ const typedData = buildDeriveActionTypedData({
1348
+ network,
1349
+ subaccountId,
1350
+ nonce,
1351
+ module: DERIVE_TRADE_MODULE,
1352
+ data,
1353
+ expirySec,
1354
+ owner,
1355
+ signer: owner
1356
+ });
1357
+ return buildEip712Multisign({
1358
+ keyGen: args.keyGen,
1359
+ purposeText: args.purposeText,
1360
+ destinationChainID: String(args.chainId),
1361
+ destinationAddress: ZERO_ADDRESS,
1362
+ expiryDate: args.expiryDate,
1363
+ legs: [
1364
+ {
1365
+ typedData,
1366
+ delivery: deriveExchangeDelivery({
1367
+ method: "private/order",
1368
+ chainId: args.chainId,
1369
+ network,
1370
+ subaccountId: subaccountId.toString(),
1371
+ nonce,
1372
+ signer: owner,
1373
+ signatureExpirySec: expirySec,
1374
+ instrumentName: instrument.instrument_name,
1375
+ direction: args.direction,
1376
+ amount: args.amount,
1377
+ limitPrice: args.limitPrice,
1378
+ maxFee: args.maxFee,
1379
+ orderType: args.orderType ?? "limit",
1380
+ timeInForce: args.timeInForce ?? "gtc",
1381
+ reduceOnly: args.reduceOnly === true,
1382
+ label: args.label,
1383
+ ticketKind: args.ticketKind,
1384
+ triggerPrice: args.triggerPrice,
1385
+ triggerType: args.triggerType,
1386
+ triggerPriceType: args.triggerPriceType,
1387
+ algoDurationSec: args.algoDurationSec,
1388
+ algoNumSlices: args.algoNumSlices
1389
+ }),
1390
+ audit: {
1391
+ protocol: DERIVE_PROTOCOL_ID,
1392
+ action: "placeOrder",
1393
+ instrumentName: instrument.instrument_name,
1394
+ subaccountId: subaccountId.toString()
1395
+ }
1396
+ }
1397
+ ]
1398
+ });
1399
+ }
1400
+ async function buildDeriveReplaceOrderMultisign(args) {
1401
+ const hasOrder = Boolean(args.orderIdToCancel?.trim());
1402
+ const hasNonce = Boolean(args.nonceToCancel?.trim());
1403
+ if (hasOrder === hasNonce) {
1404
+ throw new Error("Replace requires exactly one of orderIdToCancel or nonceToCancel.");
1405
+ }
1406
+ const subaccountId = requireSubaccount(args.subaccountId);
1407
+ const network = networkFromChain(args.chainId);
1408
+ const owner = getAddress(args.executorAddress);
1409
+ const nonce = args.nonce ?? deriveRandomNonce();
1410
+ const expirySec = deriveExpirySec(args.signatureExpirySec);
1411
+ const { data, instrument } = await loadTradeData({
1412
+ instrumentName: args.instrumentName,
1413
+ direction: args.direction,
1414
+ amount: args.amount,
1415
+ limitPrice: args.limitPrice,
1416
+ maxFee: args.maxFee,
1417
+ subaccountId,
1418
+ network
1419
+ });
1420
+ const typedData = buildDeriveActionTypedData({
1421
+ network,
1422
+ subaccountId,
1423
+ nonce,
1424
+ module: DERIVE_TRADE_MODULE,
1425
+ data,
1426
+ expirySec,
1427
+ owner,
1428
+ signer: owner
1429
+ });
1430
+ return buildEip712Multisign({
1431
+ keyGen: args.keyGen,
1432
+ purposeText: args.purposeText,
1433
+ destinationChainID: String(args.chainId),
1434
+ destinationAddress: ZERO_ADDRESS,
1435
+ expiryDate: args.expiryDate,
1436
+ legs: [
1437
+ {
1438
+ typedData,
1439
+ delivery: deriveExchangeDelivery({
1440
+ method: "private/replace",
1441
+ chainId: args.chainId,
1442
+ network,
1443
+ subaccountId: subaccountId.toString(),
1444
+ nonce,
1445
+ signer: owner,
1446
+ signatureExpirySec: expirySec,
1447
+ instrumentName: instrument.instrument_name,
1448
+ direction: args.direction,
1449
+ amount: args.amount,
1450
+ limitPrice: args.limitPrice,
1451
+ maxFee: args.maxFee,
1452
+ orderType: args.orderType ?? "limit",
1453
+ timeInForce: args.timeInForce ?? "gtc",
1454
+ reduceOnly: args.reduceOnly === true,
1455
+ label: args.label,
1456
+ ticketKind: args.ticketKind,
1457
+ triggerPrice: args.triggerPrice,
1458
+ triggerType: args.triggerType,
1459
+ triggerPriceType: args.triggerPriceType,
1460
+ algoDurationSec: args.algoDurationSec,
1461
+ algoNumSlices: args.algoNumSlices,
1462
+ orderIdToCancel: args.orderIdToCancel,
1463
+ nonceToCancel: args.nonceToCancel
1464
+ }),
1465
+ audit: {
1466
+ protocol: DERIVE_PROTOCOL_ID,
1467
+ action: "replaceOrder",
1468
+ instrumentName: instrument.instrument_name,
1469
+ subaccountId: subaccountId.toString()
1470
+ }
1471
+ }
1472
+ ]
1473
+ });
1474
+ }
1475
+ function buildDeriveCancelMultisign(args) {
1476
+ const subaccountId = requireSubaccount(args.subaccountId);
1477
+ const instrumentName = args.instrumentName.trim().toUpperCase();
1478
+ const orderId = args.orderId.trim();
1479
+ if (!instrumentName) throw new Error("instrumentName is required.");
1480
+ if (!orderId) throw new Error("orderId is required.");
1481
+ const wallet = getAddress(args.executorAddress);
1482
+ const timestamp = deriveLoginTimestamp();
1483
+ const digest = deriveLoginDigest(timestamp);
1484
+ const msgHash = digest.startsWith("0x") ? digest.slice(2) : digest;
1485
+ const network = networkFromChain(args.chainId);
1486
+ return finalizeMultisign({
1487
+ keyGen: args.keyGen,
1488
+ purposeText: args.purposeText,
1489
+ destinationChainID: String(args.chainId),
1490
+ destinationAddress: wallet,
1491
+ expiryDate: args.expiryDate,
1492
+ extraJSON: {
1493
+ signRequestKind: "personal_sign",
1494
+ delivery: {
1495
+ kind: "derive_exchange",
1496
+ method: "private/cancel",
1497
+ chainId: args.chainId,
1498
+ network,
1499
+ settlementChainId: deriveSettlementChainId(network),
1500
+ login: { wallet, timestamp },
1501
+ params: {
1502
+ instrument_name: instrumentName,
1503
+ order_id: orderId,
1504
+ subaccount_id: Number(subaccountId)
1505
+ }
1506
+ }
1507
+ },
1508
+ legs: [
1509
+ {
1510
+ msgHash,
1511
+ msgRaw: stringToHex(timestamp),
1512
+ destinationAddress: wallet,
1513
+ signatureText: "Derive private/cancel EIP-191 login",
1514
+ audit: {
1515
+ kind: "PERSONAL_SIGN",
1516
+ protocol: DERIVE_PROTOCOL_ID,
1517
+ action: "cancel",
1518
+ instrumentName,
1519
+ orderId,
1520
+ subaccountId: subaccountId.toString()
1521
+ },
1522
+ feeSnapshot: {}
1523
+ }
1524
+ ]
1525
+ });
1526
+ }
1527
+ function defiEvmTransport(rpcUrl, opts) {
1528
+ const url = rpcUrl.trim();
1529
+ return http(url, opts);
1530
+ }
1531
+ function routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimit) {
1532
+ if (chainGasLimit != null && Number.isFinite(chainGasLimit) && chainGasLimit > 0) {
1533
+ return gasLimitFromEstimateAndChainConfig(estimatedGas, chainGasLimit);
1534
+ }
1535
+ return (estimatedGas * 12n + 9n) / 10n;
1536
+ }
1537
+
1538
+ // src/chains/evm/buildBatch.ts
1539
+ async function buildEvmMultisignBatch(args) {
1540
+ const { context, steps } = args;
1541
+ const {
1542
+ chainId,
1543
+ rpcUrl,
1544
+ executorAddress,
1545
+ chainDetail,
1546
+ useCustomGas,
1547
+ customGasChainDetails,
1548
+ keyGen,
1549
+ purposeText
1550
+ } = context;
1551
+ if (steps.length === 0) throw new Error("buildEvmMultisignBatch requires at least one step");
1552
+ const ch = defineChain({
1553
+ id: chainId,
1554
+ name: "Destination",
1555
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
1556
+ rpcUrls: { default: { http: [rpcUrl] } }
1557
+ });
1558
+ const publicClient = createPublicClient({ chain: ch, transport: defiEvmTransport(rpcUrl) });
1559
+ const feeParams = await fetchChainFeeParams(rpcUrl, chainId);
1560
+ const legacy = Boolean(chainDetail?.legacy) || !feeParams.isEip1559;
1561
+ const latestBaseFeeWei = !legacy ? (await publicClient.getBlock({ blockTag: "latest" })).baseFeePerGas ?? 0n : 0n;
1562
+ const gasLimitConfig = useCustomGas && chainDetail?.gasLimit != null ? Number(chainDetail.gasLimit) : void 0;
1563
+ const chainGasLimitRouter = chainDetail?.gasLimit != null && Number.isFinite(Number(chainDetail.gasLimit)) && Number(chainDetail.gasLimit) > 0 ? Number(chainDetail.gasLimit) : void 0;
1564
+ const gasFeeMultiplier = useCustomGas && chainDetail?.gasMultiplier != null ? Number(chainDetail.gasMultiplier) : void 0;
1565
+ const executor = getAddress(executorAddress);
1566
+ const baseNonce = await publicClient.getTransactionCount({ address: executor, blockTag: "pending" });
1567
+ const legs = [];
1568
+ for (let i = 0; i < steps.length; i++) {
1569
+ const step = steps[i];
1570
+ const currentNonce = baseNonce + i;
1571
+ let estimatedGas;
1572
+ if (args.estimateGasForStep) {
1573
+ estimatedGas = await args.estimateGasForStep({ step, index: i, publicClient, executor });
1574
+ } else {
1575
+ try {
1576
+ estimatedGas = await publicClient.estimateGas({
1577
+ to: step.to,
1578
+ data: step.data,
1579
+ value: step.value,
1580
+ account: executor
1581
+ });
1582
+ } catch {
1583
+ estimatedGas = step.fallbackGas ?? 100000n;
1584
+ }
1585
+ }
1586
+ let gasLimitI;
1587
+ if (args.resolveGasLimit) {
1588
+ gasLimitI = await args.resolveGasLimit({ step, index: i, estimatedGas, publicClient });
1589
+ } else if (step.routerSwap) {
1590
+ gasLimitI = routerSwapGasLimitFromEstimate(estimatedGas, chainGasLimitRouter);
1591
+ } else {
1592
+ gasLimitI = useCustomGas ? gasLimitFromEstimateAndChainConfig(estimatedGas, gasLimitConfig) : estimatedGas;
1593
+ }
1594
+ let proposalTxParams;
1595
+ let feeSnapshot;
1596
+ let serialized;
1597
+ if (legacy) {
1598
+ let gasPriceWei = await publicClient.getGasPrice();
1599
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1600
+ gasPriceWei = gasPriceWei * BigInt(100 + gasFeeMultiplier) / 100n;
1601
+ }
1602
+ if (useCustomGas && chainDetail?.gasPrice != null && chainDetail.gasPrice > 0) {
1603
+ const configured = parseGwei(gweiToDecimalString(Number(chainDetail.gasPrice)));
1604
+ if (configured > gasPriceWei) gasPriceWei = configured;
1605
+ }
1606
+ serialized = serializeTransaction({
1607
+ type: "legacy",
1608
+ to: step.to,
1609
+ data: step.data,
1610
+ value: step.value,
1611
+ gas: gasLimitI,
1612
+ gasPrice: gasPriceWei,
1613
+ nonce: currentNonce,
1614
+ chainId
1615
+ });
1616
+ proposalTxParams = {
1617
+ nonce: currentNonce,
1618
+ gasLimit: gasLimitI.toString(),
1619
+ txType: "legacy",
1620
+ gasPrice: gasPriceWei.toString()
1621
+ };
1622
+ feeSnapshot = proposalTxParamsToFeeSnapshot(proposalTxParams);
1623
+ } else {
1624
+ const fetchedBase = feeParams.baseFeeGwei ?? 0;
1625
+ const fetchedPriority = feeParams.priorityFeeGwei ?? 0;
1626
+ const configuredBase = useCustomGas && chainDetail?.baseFee != null ? Number(chainDetail.baseFee) : 0;
1627
+ const configuredPriority = useCustomGas && chainDetail?.priorityFee != null ? Number(chainDetail.priorityFee) : 0;
1628
+ const effectiveBaseFeeGwei = Math.max(fetchedBase, configuredBase);
1629
+ const effectivePriorityFeeGwei = Math.max(fetchedPriority, configuredPriority);
1630
+ const baseFeeMultiplierPct = useCustomGas && chainDetail?.baseFeeMultiplier != null ? Math.max(100, Number(chainDetail.baseFeeMultiplier)) : 100;
1631
+ const baseComponentGwei = effectiveBaseFeeGwei * baseFeeMultiplierPct / 100;
1632
+ const maxFeePerGasGwei = baseComponentGwei + effectivePriorityFeeGwei;
1633
+ let maxPriorityFeePerGas = effectivePriorityFeeGwei > 0 ? parseGwei(gweiToDecimalString(effectivePriorityFeeGwei)) : parseGwei("1");
1634
+ let maxFeePerGas = parseGwei(gweiToDecimalString(maxFeePerGasGwei));
1635
+ if (useCustomGas && gasFeeMultiplier != null && gasFeeMultiplier > 0) {
1636
+ maxPriorityFeePerGas = maxPriorityFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
1637
+ maxFeePerGas = maxFeePerGas * BigInt(100 + gasFeeMultiplier) / 100n;
1638
+ }
1639
+ ({ maxFeePerGas, maxPriorityFeePerGas } = alignEip1559FeesWithLatestBase(
1640
+ maxFeePerGas,
1641
+ maxPriorityFeePerGas,
1642
+ latestBaseFeeWei
1643
+ ));
1644
+ serialized = serializeTransaction({
1645
+ type: "eip1559",
1646
+ to: step.to,
1647
+ data: step.data,
1648
+ value: step.value,
1649
+ gas: gasLimitI,
1650
+ maxFeePerGas,
1651
+ maxPriorityFeePerGas,
1652
+ nonce: currentNonce,
1653
+ chainId
1654
+ });
1655
+ proposalTxParams = {
1656
+ nonce: currentNonce,
1657
+ gasLimit: gasLimitI.toString(),
1658
+ txType: "eip1559",
1659
+ maxFeePerGas: maxFeePerGas.toString(),
1660
+ maxPriorityFeePerGas: maxPriorityFeePerGas.toString()
1661
+ };
1662
+ feeSnapshot = i === 0 ? proposalTxParamsToFeeSnapshot(proposalTxParams) : {};
1663
+ }
1664
+ const h = keccak256(serialized);
1665
+ const msgHash = h.startsWith("0x") ? h.slice(2) : h;
1666
+ const batchMetaExtra = args.buildBatchMeta({ step, index: i, gasLimit: gasLimitI });
1667
+ legs.push({
1668
+ msgHash,
1669
+ msgRaw: i === 0 && args.firstMsgRawNo0x != null ? args.firstMsgRawNo0x : serialized,
1670
+ destinationAddress: step.to,
1671
+ signatureText: typeof batchMetaExtra.signatureText === "string" ? batchMetaExtra.signatureText : JSON.stringify(batchMetaExtra.signatureText ?? {}),
1672
+ audit: batchMetaExtra,
1673
+ feeSnapshot: i === 0 ? feeSnapshot : {},
1674
+ proposalTxParams,
1675
+ valueWei: i === 0 ? step.value : void 0
1676
+ });
1677
+ if (i === 0 && args.firstMsgRawNo0x != null) {
1678
+ legs[0].msgRaw = args.firstMsgRawNo0x;
1679
+ }
1680
+ }
1681
+ const extraJSON = {};
1682
+ if (useCustomGas && customGasChainDetails && Object.keys(customGasChainDetails).length > 0) {
1683
+ extraJSON.customGasChainDetails = customGasChainDetails;
1684
+ }
1685
+ const result = finalizeMultisign({
1686
+ keyGen,
1687
+ purposeText,
1688
+ purposeSuffix: args.purposeSuffix,
1689
+ destinationChainID: String(chainId),
1690
+ destinationAddress: args.destinationAddress ?? steps[0].to,
1691
+ legs,
1692
+ extraJSON: Object.keys(extraJSON).length > 0 ? extraJSON : void 0,
1693
+ expiryDate: context.expiryDate
1694
+ });
1695
+ const pv = args.payableValueWei;
1696
+ if (pv != null && pv > 0n) {
1697
+ result.bodyForSign.value = pv.toString();
1698
+ }
1699
+ return result;
1700
+ }
1701
+ var erc20AllowanceAbi = parseAbi([
1702
+ "function allowance(address owner, address spender) view returns (uint256)",
1703
+ "function decimals() view returns (uint8)"
1704
+ ]);
1705
+ var erc20ApproveAbi = parseAbi(["function approve(address spender, uint256 amount) returns (bool)"]);
1706
+ function publicClientForRpc(chainId, rpcUrl) {
1707
+ const ch = defineChain({
1708
+ id: chainId,
1709
+ name: `chain-${chainId}`,
1710
+ nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
1711
+ rpcUrls: { default: { http: [rpcUrl.trim()] } }
1712
+ });
1713
+ return createPublicClient({ chain: ch, transport: defiEvmTransport(rpcUrl.trim()) });
1714
+ }
1715
+ async function resolveErc20Decimals(args) {
1716
+ const token = getAddress(args.token);
1717
+ const onChain = await args.publicClient.readContract({
1718
+ address: token,
1719
+ abi: erc20AllowanceAbi,
1720
+ functionName: "decimals"
1721
+ });
1722
+ const n = typeof onChain === "bigint" ? Number(onChain) : Number(onChain);
1723
+ if (!Number.isFinite(n) || n < 0 || n > 36) {
1724
+ throw new Error(`Invalid on-chain decimals for ${token}.`);
1725
+ }
1726
+ for (const hint of [args.registryDecimals, args.apiDecimals]) {
1727
+ }
1728
+ return n;
1729
+ }
1730
+ async function buildConditionalErc20ApproveSteps(args) {
1731
+ if (args.amountWei <= 0n) return [];
1732
+ const token = getAddress(args.token);
1733
+ const spender = getAddress(args.spender);
1734
+ const owner = getAddress(args.owner);
1735
+ const fallback = args.fallbackGas ?? 100000n;
1736
+ const allowance = await args.publicClient.readContract({
1737
+ address: token,
1738
+ abi: erc20AllowanceAbi,
1739
+ functionName: "allowance",
1740
+ args: [owner, spender]
1741
+ });
1742
+ if (allowance >= args.amountWei) return [];
1743
+ const steps = [];
1744
+ if (allowance > 0n) {
1745
+ steps.push({
1746
+ to: token,
1747
+ data: encodeFunctionData({ abi: erc20ApproveAbi, functionName: "approve", args: [spender, 0n] }),
1748
+ value: 0n,
1749
+ fallbackGas: fallback
1750
+ });
1751
+ }
1752
+ steps.push({
1753
+ to: token,
1754
+ data: encodeFunctionData({
1755
+ abi: erc20ApproveAbi,
1756
+ functionName: "approve",
1757
+ args: [spender, args.amountWei]
1758
+ }),
1759
+ value: 0n,
1760
+ fallbackGas: fallback
1761
+ });
1762
+ return steps;
1763
+ }
1764
+
1765
+ // src/protocols/evm/derive/depositMultisign.ts
1766
+ var DEPOSIT_APPROVE_FALLBACK = 100000n;
1767
+ var DEPOSIT_CALL_FALLBACK = 450000n;
1768
+ var actionManagerAbi = parseAbi([
1769
+ "function depositToNewSubaccount(address protocolAsset, uint256 amount, uint256 managerId, address owner)"
1770
+ ]);
1771
+ async function buildDeriveDepositNewSubaccountMultisign(args) {
1772
+ if (!isDeriveSettlementChainId(args.chainId)) {
1773
+ throw new Error(
1774
+ "ACTION_MANAGER.depositToNewSubaccount is Ethereum L1 only (chainId 1 / 11155111). Use ctm_derive_build_socket_deposit_multisign on OP/Base/Arb, or ctm_derive_build_hype_vault_deposit_multisign on HyperEVM."
1775
+ );
1776
+ }
1777
+ const executor = getAddress(args.executorAddress);
1778
+ const protocolAsset = getAddress(args.protocolAsset);
1779
+ const underlying = getAddress(args.underlyingErc20);
1780
+ const managerId = BigInt(args.managerId);
1781
+ if (managerId <= 0n) throw new Error("managerId must be a positive risk-universe manager id.");
1782
+ const actionManager = deriveActionManagerAddress(args.chainId, args.actionManager);
1783
+ const publicClient = publicClientForRpc(args.chainId, args.rpcUrl);
1784
+ const decimals = await resolveErc20Decimals({ publicClient, token: underlying });
1785
+ const amountWei = parseUnits(args.amountHuman.trim(), decimals);
1786
+ if (amountWei <= 0n) throw new Error("amountHuman must be a positive deposit amount.");
1787
+ const approveSteps = await buildConditionalErc20ApproveSteps({
1788
+ publicClient,
1789
+ token: underlying,
1790
+ spender: actionManager,
1791
+ owner: executor,
1792
+ amountWei,
1793
+ fallbackGas: DEPOSIT_APPROVE_FALLBACK
1794
+ });
1795
+ const depositData = encodeFunctionData({
1796
+ abi: actionManagerAbi,
1797
+ functionName: "depositToNewSubaccount",
1798
+ args: [protocolAsset, amountWei, managerId, executor]
1799
+ });
1800
+ const steps = [
1801
+ ...approveSteps.map((s) => ({
1802
+ to: s.to,
1803
+ data: s.data,
1804
+ value: s.value,
1805
+ fallbackGas: s.fallbackGas ?? DEPOSIT_APPROVE_FALLBACK
1806
+ })),
1807
+ { to: actionManager, data: depositData, value: 0n, fallbackGas: DEPOSIT_CALL_FALLBACK }
1808
+ ];
1809
+ const firstDataNo0x = steps[0].data.startsWith("0x") ? steps[0].data.slice(2) : steps[0].data;
1810
+ const gasLimitConfig = args.useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
1811
+ const n = steps.length;
1812
+ const purposeSuffix = n === 1 ? "Derive: depositToNewSubaccount (allowance already set). First credit creates trading + fallback subaccounts." : `Derive: ${n}-tx batch \u2014 approve L1 collateral to ACTION_MANAGER, then depositToNewSubaccount.`;
1813
+ return buildEvmMultisignBatch({
1814
+ context: {
1815
+ chainCategory: "evm",
1816
+ keyGen: args.keyGen,
1817
+ purposeText: args.purposeText,
1818
+ chainId: args.chainId,
1819
+ rpcUrl: args.rpcUrl.trim(),
1820
+ executorAddress: executor,
1821
+ chainDetail: args.chainDetail,
1822
+ useCustomGas: args.useCustomGas,
1823
+ customGasChainDetails: args.customGasChainDetails,
1824
+ expiryDate: args.expiryDate
1825
+ },
1826
+ steps,
1827
+ purposeSuffix,
1828
+ firstMsgRawNo0x: firstDataNo0x,
1829
+ destinationAddress: steps[0].to,
1830
+ resolveGasLimit: ({ index, estimatedGas }) => {
1831
+ if (index === steps.length - 1 && index > 0) {
1832
+ const gas = DEPOSIT_CALL_FALLBACK;
1833
+ return args.useCustomGas ? gasLimitFromEstimateAndChainConfig(gas, gasLimitConfig) : gas;
1834
+ }
1835
+ return estimatedGas;
1836
+ },
1837
+ buildBatchMeta: ({ index }) => {
1838
+ const isDeposit = index === steps.length - 1;
1839
+ return {
1840
+ signatureText: JSON.stringify({
1841
+ kind: "Derive",
1842
+ protocol: DERIVE_PROTOCOL_ID,
1843
+ action: isDeposit ? "depositToNewSubaccount" : "approve",
1844
+ chainId: args.chainId,
1845
+ managerId: args.managerId
1846
+ })
1847
+ };
1848
+ }
1849
+ });
1850
+ }
1851
+ var TRANSFER_FALLBACK = 80000n;
1852
+ var erc20TransferAbi = parseAbi(["function transfer(address to, uint256 amount) returns (bool)"]);
1853
+ async function deriveRegisterDepositAddress(args) {
1854
+ const network = args.network ?? deriveNetworkFromChainId(args.chainId ?? 1);
1855
+ const wallet = getAddress(args.wallet);
1856
+ const subaccountId = args.subaccountId ?? 0;
1857
+ const managerId = args.managerId;
1858
+ if (!subaccountId && !managerId) {
1859
+ throw new Error("register_deposit_address needs managerId (new subaccount) or subaccountId (existing).");
1860
+ }
1861
+ const result = await deriveRpc({
1862
+ method: "public/register_deposit_address",
1863
+ params: {
1864
+ wallet,
1865
+ subaccount_id: subaccountId,
1866
+ ...managerId ? { manager_id: managerId } : {}
1867
+ },
1868
+ network
1869
+ });
1870
+ const depositAddress = (result?.deposit_address ?? "").trim();
1871
+ if (!depositAddress) throw new Error("Derive did not return a deposit_address.");
1872
+ return {
1873
+ depositAddress: getAddress(depositAddress),
1874
+ wallet,
1875
+ managerId: result.manager_id ?? managerId ?? null,
1876
+ subaccountId: result.subaccount_id ?? (subaccountId || null),
1877
+ network,
1878
+ notes: "Send the registered token on Ethereum L1 (chainId 1 / 11155111) only. The keeper sweeps into ACTION_MANAGER. Do not send from Base/Arb to this address."
1879
+ };
1880
+ }
1881
+ async function buildDeriveDepositAddressTransferMultisign(args) {
1882
+ if (!isDeriveSettlementChainId(args.chainId)) {
1883
+ throw new Error("Standard deposit-address transfers are Ethereum L1 only (chainId 1 / 11155111). Use Socket on Arb/OP/Base.");
1884
+ }
1885
+ const executor = getAddress(args.executorAddress);
1886
+ const token = getAddress(args.underlyingErc20);
1887
+ const to = getAddress(args.depositAddress);
1888
+ const publicClient = publicClientForRpc(args.chainId, args.rpcUrl);
1889
+ const decimals = await resolveErc20Decimals({ publicClient, token });
1890
+ const amountWei = parseUnits(args.amountHuman.trim(), decimals);
1891
+ if (amountWei <= 0n) throw new Error("amountHuman must be a positive deposit amount.");
1892
+ const data = encodeFunctionData({
1893
+ abi: erc20TransferAbi,
1894
+ functionName: "transfer",
1895
+ args: [to, amountWei]
1896
+ });
1897
+ return buildEvmMultisignBatch({
1898
+ context: {
1899
+ chainCategory: "evm",
1900
+ keyGen: args.keyGen,
1901
+ purposeText: args.purposeText,
1902
+ chainId: args.chainId,
1903
+ rpcUrl: args.rpcUrl.trim(),
1904
+ executorAddress: executor,
1905
+ chainDetail: args.chainDetail,
1906
+ useCustomGas: args.useCustomGas,
1907
+ customGasChainDetails: args.customGasChainDetails,
1908
+ expiryDate: args.expiryDate
1909
+ },
1910
+ steps: [{ to: token, data, value: 0n, fallbackGas: TRANSFER_FALLBACK }],
1911
+ purposeSuffix: "Derive: ERC-20 transfer to registered L1 deposit address (standard / keeper sweep).",
1912
+ firstMsgRawNo0x: data.startsWith("0x") ? data.slice(2) : data,
1913
+ destinationAddress: token,
1914
+ buildBatchMeta: () => ({
1915
+ signatureText: JSON.stringify({
1916
+ kind: "Derive",
1917
+ protocol: DERIVE_PROTOCOL_ID,
1918
+ action: "depositAddressTransfer",
1919
+ chainId: args.chainId,
1920
+ depositAddress: to
1921
+ })
1922
+ })
1923
+ });
1924
+ }
1925
+ var DERIVE_SOCKET_APP_CHAIN_ID = 957;
1926
+ var DERIVE_SOCKET_CHAIN_IDS = [1, 10, 8453, 42161];
1927
+ var DERIVE_HYPE_CHAIN_ID = 999;
1928
+ var DERIVE_WHYPE = "0x5555555555555555555555555555555555555555";
1929
+ var DERIVE_HYPE_VAULT_DEFAULT = "0xb792F94Ed56eD0bA163e5Ba139e2Bd0029A81d0a";
1930
+ var DERIVE_SOCKET_MSG_GAS_LIMIT = 500000n;
1931
+ var DERIVE_SOCKET_FEE_FALLBACK_WEI = 2000000000000000n;
1932
+ var SOCKET_ROUTES = [
1933
+ {
1934
+ chainId: 1,
1935
+ symbol: "USDC",
1936
+ token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
1937
+ vault: "0x6D303CEE7959f814042D31E0624fB88Ec6fbcC1d",
1938
+ connector: "0x200AF8FCdD5246D70B369A98143Ac8930A077B7A",
1939
+ decimals: 6
1940
+ },
1941
+ {
1942
+ chainId: 1,
1943
+ symbol: "WETH",
1944
+ token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
1945
+ vault: "0xD4efe33C66B8CdE33B8896a2126E41e5dB571b7e",
1946
+ connector: "0xCf814e58f1649F94d37E51f730D6bF72409fA09c",
1947
+ decimals: 18
1948
+ },
1949
+ {
1950
+ chainId: 1,
1951
+ symbol: "WBTC",
1952
+ token: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
1953
+ vault: "0x3Eec7c855aF33280F1eD38b93059F5aa5862E3ab",
1954
+ connector: "0xA621Bc5A9d13D39eb098865B723CEee71BB5C181",
1955
+ decimals: 8
1956
+ },
1957
+ {
1958
+ chainId: 10,
1959
+ symbol: "USDC",
1960
+ token: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
1961
+ vault: "0xDEf0bfBdf7530C75AB3C73f8d2F64d9eaA7aA98e",
1962
+ connector: "0xfd76d8b79C2E2c86EA0814e92D5cA0e4E8096c13",
1963
+ decimals: 6
1964
+ },
1965
+ {
1966
+ chainId: 10,
1967
+ symbol: "WETH",
1968
+ token: "0x4200000000000000000000000000000000000006",
1969
+ vault: "0xdD4c717a69763176d8B7A687728e228597eAB86d",
1970
+ connector: "0x27f4B23944e2bb59b1E276aff22FD2Be45658F64",
1971
+ decimals: 18
1972
+ },
1973
+ {
1974
+ chainId: 10,
1975
+ symbol: "WBTC",
1976
+ token: "0x68f180fcCe6836688e9084f035309E29Bf0A2095",
1977
+ vault: "0xE5967877065f111a556850d8f05b8DaD88edCEc9",
1978
+ connector: "0x906A44Daa8bdA5599a384264e3811b9BeA1109b4",
1979
+ decimals: 8
1980
+ },
1981
+ {
1982
+ chainId: 8453,
1983
+ symbol: "USDC",
1984
+ token: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
1985
+ vault: "0x4e798659b9846F1da7B6D6B5d09d581270aB6FEC",
1986
+ connector: "0x57B03E14d409ADC7fAb6CFc44b5886CAD2D5f02b",
1987
+ decimals: 6
1988
+ },
1989
+ {
1990
+ chainId: 8453,
1991
+ symbol: "WETH",
1992
+ token: "0x4200000000000000000000000000000000000006",
1993
+ vault: "0xBd282333710B9C7e33E8a37d027885A7C079Ae23",
1994
+ connector: "0xbE9DBda519e15a1c0d238cEa0b3daD47a484A6Ff",
1995
+ decimals: 18
1996
+ },
1997
+ {
1998
+ chainId: 42161,
1999
+ symbol: "USDC",
2000
+ token: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
2001
+ vault: "0x5e027ad442e031424b5a2C0ad6f656662Be32882",
2002
+ connector: "0x17Fc4c7ea8267044b6D0ACC17a6C049Bed6F8B21",
2003
+ decimals: 6
2004
+ },
2005
+ {
2006
+ chainId: 42161,
2007
+ symbol: "WETH",
2008
+ token: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
2009
+ vault: "0x8e9f58E6c206CB9C98aBb9F235E0f02D65dFc922",
2010
+ connector: "0x3809150509df79D96334c4EB6Ba1c386827C3c67",
2011
+ decimals: 18
2012
+ },
2013
+ {
2014
+ chainId: 42161,
2015
+ symbol: "WBTC",
2016
+ token: "0x2f2a2543B76A4166549B7eCDcE5542a773Aa44fB",
2017
+ vault: "0x3D20c6A2b719129af175E0ff7B1875DEb360896f",
2018
+ connector: "0x42c846313c37845B9d67BB5c1F4F48E528234Afc",
2019
+ decimals: 8
2020
+ }
2021
+ ];
2022
+ function norm(address) {
2023
+ return getAddress(address).toLowerCase();
2024
+ }
2025
+ function isDeriveSocketChainId(chainId) {
2026
+ return DERIVE_SOCKET_CHAIN_IDS.includes(chainId);
2027
+ }
2028
+ function deriveHypeVaultAddress(override) {
2029
+ const explicit = (override ?? process.env.DERIVE_HYPE_VAULT ?? "").trim();
2030
+ return getAddress(explicit || DERIVE_HYPE_VAULT_DEFAULT);
2031
+ }
2032
+ function deriveSocketRouteForToken(chainId, token) {
2033
+ let key;
2034
+ try {
2035
+ key = norm(token);
2036
+ } catch {
2037
+ return null;
2038
+ }
2039
+ const hit = SOCKET_ROUTES.find((r) => r.chainId === chainId && norm(r.token) === key);
2040
+ return hit ?? null;
2041
+ }
2042
+ function deriveSocketWethRoute(chainId) {
2043
+ return SOCKET_ROUTES.find((r) => r.chainId === chainId && r.symbol === "WETH") ?? null;
2044
+ }
2045
+ function deriveFundRouteForAssetRow(args) {
2046
+ const raw = (args.contractAddress ?? "").trim();
2047
+ const isNative = !raw || raw.toLowerCase() === "native" || raw.toLowerCase() === NATIVE_ZERO;
2048
+ if (args.chainId === DERIVE_HYPE_CHAIN_ID) {
2049
+ if (isNative || raw && norm(raw) === norm(DERIVE_WHYPE)) {
2050
+ return {
2051
+ kind: "hype_vault",
2052
+ chainId: DERIVE_HYPE_CHAIN_ID,
2053
+ symbol: "HYPE",
2054
+ token: isNative ? NATIVE_ZERO : getAddress(DERIVE_WHYPE),
2055
+ decimals: 18,
2056
+ native: isNative
2057
+ };
2058
+ }
2059
+ return null;
2060
+ }
2061
+ if (args.chainId === 11155111) {
2062
+ if (isNative) return null;
2063
+ try {
2064
+ return {
2065
+ kind: "l1_direct",
2066
+ chainId: 11155111,
2067
+ symbol: "USDC",
2068
+ token: getAddress(raw),
2069
+ decimals: 6,
2070
+ native: false
2071
+ };
2072
+ } catch {
2073
+ return null;
2074
+ }
2075
+ }
2076
+ if (args.chainId === 1) {
2077
+ if (isNative) {
2078
+ const weth = deriveSocketWethRoute(1);
2079
+ if (!weth) return null;
2080
+ return { kind: "l1_direct", chainId: 1, symbol: "ETH", token: NATIVE_ZERO, decimals: 18, native: true, socket: weth };
2081
+ }
2082
+ const socket = deriveSocketRouteForToken(1, raw);
2083
+ if (!socket) return null;
2084
+ return {
2085
+ kind: "l1_direct",
2086
+ chainId: 1,
2087
+ symbol: socket.symbol,
2088
+ token: getAddress(socket.token),
2089
+ decimals: socket.decimals,
2090
+ native: false,
2091
+ socket
2092
+ };
2093
+ }
2094
+ if (isDeriveSocketChainId(args.chainId)) {
2095
+ if (isNative) {
2096
+ const weth = deriveSocketWethRoute(args.chainId);
2097
+ if (!weth) return null;
2098
+ return {
2099
+ kind: "socket",
2100
+ chainId: args.chainId,
2101
+ symbol: "ETH",
2102
+ token: NATIVE_ZERO,
2103
+ decimals: 18,
2104
+ native: true,
2105
+ socket: weth
2106
+ };
2107
+ }
2108
+ const socket = deriveSocketRouteForToken(args.chainId, raw);
2109
+ if (!socket) return null;
2110
+ return {
2111
+ kind: "socket",
2112
+ chainId: args.chainId,
2113
+ symbol: socket.symbol,
2114
+ token: getAddress(socket.token),
2115
+ decimals: socket.decimals,
2116
+ native: false,
2117
+ socket
2118
+ };
2119
+ }
2120
+ return null;
2121
+ }
2122
+ function deriveFundableTokensForChain(chainId) {
2123
+ if (chainId === DERIVE_HYPE_CHAIN_ID) {
2124
+ return [
2125
+ { address: NATIVE_ZERO, symbol: "HYPE", decimals: 18, roles: ["native", "collateral"] },
2126
+ { address: getAddress(DERIVE_WHYPE), symbol: "WHYPE", decimals: 18, roles: ["native_wrapped", "collateral"] }
2127
+ ];
2128
+ }
2129
+ if (chainId === 11155111) {
2130
+ return [{ address: getAddress("0x73Efab09362052D26FB93A730Be4F8a5EdC833af"), symbol: "USDC", decimals: 6, roles: ["collateral"] }];
2131
+ }
2132
+ const rows = SOCKET_ROUTES.filter((r) => r.chainId === chainId).map((r) => ({
2133
+ address: getAddress(r.token),
2134
+ symbol: r.symbol,
2135
+ decimals: r.decimals,
2136
+ roles: r.symbol === "USDC" ? ["collateral", "quote"] : ["collateral", "underlying"]
2137
+ }));
2138
+ if (deriveSocketWethRoute(chainId)) {
2139
+ rows.unshift({ address: NATIVE_ZERO, symbol: "ETH", decimals: 18, roles: ["native", "underlying"] });
2140
+ }
2141
+ return rows;
2142
+ }
2143
+ var APPROVE_FALLBACK = 100000n;
2144
+ var WRAP_FALLBACK = 80000n;
2145
+ var BRIDGE_FALLBACK = 350000n;
2146
+ var wethDepositAbi = parseAbi(["function deposit() payable"]);
2147
+ var socketVaultLegacyAbi = parseAbi([
2148
+ "function getMinFees(address connector, uint256 minGasLimit) view returns (uint256)",
2149
+ "function depositToAppChain(address receiver, uint256 amount, uint256 msgGasLimit, address connector) payable"
2150
+ ]);
2151
+ async function readSocketFee(args) {
2152
+ try {
2153
+ const fee = await args.publicClient.readContract({
2154
+ address: args.vault,
2155
+ abi: socketVaultLegacyAbi,
2156
+ functionName: "getMinFees",
2157
+ args: [args.connector, DERIVE_SOCKET_MSG_GAS_LIMIT]
2158
+ });
2159
+ if (typeof fee === "bigint" && fee > 0n) return fee;
2160
+ } catch {
2161
+ }
2162
+ return DERIVE_SOCKET_FEE_FALLBACK_WEI;
2163
+ }
2164
+ async function buildDeriveSocketDepositMultisign(args) {
2165
+ const executor = getAddress(args.executorAddress);
2166
+ const receiver = getAddress(args.receiver ?? args.executorAddress);
2167
+ const rawToken = (args.token ?? "").trim();
2168
+ const isNative = !rawToken || rawToken.toLowerCase() === "native" || rawToken.toLowerCase() === NATIVE_ZERO;
2169
+ const route = isNative ? deriveSocketWethRoute(args.chainId) : deriveSocketRouteForToken(args.chainId, rawToken);
2170
+ if (!route) {
2171
+ throw new Error(
2172
+ `No Derive Socket route for chain ${args.chainId} token ${rawToken || "native"}. Fundable rows are USDC / WETH / WBTC on Ethereum, Optimism, Base, Arbitrum.`
2173
+ );
2174
+ }
2175
+ const vault = getAddress(route.vault);
2176
+ const connector = getAddress(route.connector);
2177
+ const publicClient = publicClientForRpc(args.chainId, args.rpcUrl);
2178
+ const erc20 = getAddress(route.token);
2179
+ const decimals = isNative ? 18 : await resolveErc20Decimals({ publicClient, token: erc20 });
2180
+ const amountWei = parseUnits(args.amountHuman.trim(), decimals);
2181
+ if (amountWei <= 0n) throw new Error("amountHuman must be a positive deposit amount.");
2182
+ const feeWei = await readSocketFee({ publicClient, vault, connector });
2183
+ const vaultCalldata = encodeFunctionData({
2184
+ abi: socketVaultLegacyAbi,
2185
+ functionName: "depositToAppChain",
2186
+ args: [receiver, amountWei, DERIVE_SOCKET_MSG_GAS_LIMIT, connector]
2187
+ });
2188
+ const steps = [];
2189
+ if (isNative) {
2190
+ steps.push({
2191
+ to: erc20,
2192
+ data: encodeFunctionData({ abi: wethDepositAbi, functionName: "deposit" }),
2193
+ value: amountWei,
2194
+ fallbackGas: WRAP_FALLBACK
2195
+ });
2196
+ }
2197
+ const approveSteps = await buildConditionalErc20ApproveSteps({
2198
+ publicClient,
2199
+ token: erc20,
2200
+ spender: vault,
2201
+ owner: executor,
2202
+ amountWei,
2203
+ fallbackGas: APPROVE_FALLBACK
2204
+ });
2205
+ steps.push(
2206
+ ...approveSteps.map((s) => ({
2207
+ to: s.to,
2208
+ data: s.data,
2209
+ value: s.value,
2210
+ fallbackGas: s.fallbackGas ?? APPROVE_FALLBACK
2211
+ }))
2212
+ );
2213
+ steps.push({ to: vault, data: vaultCalldata, value: feeWei, fallbackGas: BRIDGE_FALLBACK });
2214
+ const firstDataNo0x = steps[0].data.startsWith("0x") ? steps[0].data.slice(2) : steps[0].data;
2215
+ const gasLimitConfig = args.useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
2216
+ return buildEvmMultisignBatch({
2217
+ context: {
2218
+ chainCategory: "evm",
2219
+ keyGen: args.keyGen,
2220
+ purposeText: args.purposeText,
2221
+ chainId: args.chainId,
2222
+ rpcUrl: args.rpcUrl.trim(),
2223
+ executorAddress: executor,
2224
+ chainDetail: args.chainDetail,
2225
+ useCustomGas: args.useCustomGas,
2226
+ customGasChainDetails: args.customGasChainDetails,
2227
+ expiryDate: args.expiryDate
2228
+ },
2229
+ steps,
2230
+ purposeSuffix: `Derive Socket deposit ${route.symbol} \u2192 Derive Chain (FAST). Credit in ~2\u20135 min; then fetch_subaccounts.`,
2231
+ firstMsgRawNo0x: firstDataNo0x,
2232
+ destinationAddress: steps[0].to,
2233
+ payableValueWei: steps.reduce((sum, s) => sum + s.value, 0n),
2234
+ resolveGasLimit: ({ index, estimatedGas }) => {
2235
+ if (index === steps.length - 1) {
2236
+ const gas = BRIDGE_FALLBACK;
2237
+ return args.useCustomGas ? gasLimitFromEstimateAndChainConfig(gas, gasLimitConfig) : gas;
2238
+ }
2239
+ return estimatedGas;
2240
+ },
2241
+ buildBatchMeta: ({ index }) => ({
2242
+ signatureText: JSON.stringify({
2243
+ kind: "Derive",
2244
+ protocol: DERIVE_PROTOCOL_ID,
2245
+ action: index === steps.length - 1 ? "socketDeposit" : isNative && index === 0 ? "wrap" : "approve",
2246
+ chainId: args.chainId,
2247
+ symbol: route.symbol,
2248
+ vault
2249
+ })
2250
+ })
2251
+ });
2252
+ }
2253
+ var WRAP_FALLBACK2 = 80000n;
2254
+ var APPROVE_FALLBACK2 = 100000n;
2255
+ var DEPOSIT_FALLBACK = 250000n;
2256
+ var whypeDepositAbi = parseAbi(["function deposit() payable"]);
2257
+ var vaultDepositAbi = parseAbi(["function deposit(uint256 amount)"]);
2258
+ async function buildDeriveHypeVaultDepositMultisign(args) {
2259
+ if (args.chainId !== DERIVE_HYPE_CHAIN_ID) {
2260
+ throw new Error("Derive HYPE vault deposits are HyperEVM only (chainId 999).");
2261
+ }
2262
+ const executor = getAddress(args.executorAddress);
2263
+ const vault = deriveHypeVaultAddress(args.vault);
2264
+ const raw = (args.token ?? NATIVE_ZERO).trim();
2265
+ const isNative = !raw || raw.toLowerCase() === "native" || raw.toLowerCase() === NATIVE_ZERO;
2266
+ const amountWei = parseUnits(args.amountHuman.trim(), 18);
2267
+ if (amountWei <= 0n) throw new Error("amountHuman must be a positive HYPE amount.");
2268
+ const publicClient = publicClientForRpc(args.chainId, args.rpcUrl);
2269
+ const steps = [];
2270
+ if (isNative) {
2271
+ steps.push({
2272
+ to: getAddress(DERIVE_WHYPE),
2273
+ data: encodeFunctionData({ abi: whypeDepositAbi, functionName: "deposit" }),
2274
+ value: amountWei,
2275
+ fallbackGas: WRAP_FALLBACK2
2276
+ });
2277
+ }
2278
+ const approveSteps = await buildConditionalErc20ApproveSteps({
2279
+ publicClient,
2280
+ token: getAddress(DERIVE_WHYPE),
2281
+ spender: vault,
2282
+ owner: executor,
2283
+ amountWei,
2284
+ fallbackGas: APPROVE_FALLBACK2
2285
+ });
2286
+ steps.push(
2287
+ ...approveSteps.map((s) => ({
2288
+ to: s.to,
2289
+ data: s.data,
2290
+ value: s.value,
2291
+ fallbackGas: s.fallbackGas ?? APPROVE_FALLBACK2
2292
+ }))
2293
+ );
2294
+ steps.push({
2295
+ to: vault,
2296
+ data: encodeFunctionData({ abi: vaultDepositAbi, functionName: "deposit", args: [amountWei] }),
2297
+ value: 0n,
2298
+ fallbackGas: DEPOSIT_FALLBACK
2299
+ });
2300
+ const firstDataNo0x = steps[0].data.startsWith("0x") ? steps[0].data.slice(2) : steps[0].data;
2301
+ const gasLimitConfig = args.useCustomGas && args.chainDetail?.gasLimit != null ? Number(args.chainDetail.gasLimit) : void 0;
2302
+ return buildEvmMultisignBatch({
2303
+ context: {
2304
+ chainCategory: "evm",
2305
+ keyGen: args.keyGen,
2306
+ purposeText: args.purposeText,
2307
+ chainId: args.chainId,
2308
+ rpcUrl: args.rpcUrl.trim(),
2309
+ executorAddress: executor,
2310
+ chainDetail: args.chainDetail,
2311
+ useCustomGas: args.useCustomGas,
2312
+ customGasChainDetails: args.customGasChainDetails,
2313
+ expiryDate: args.expiryDate
2314
+ },
2315
+ steps,
2316
+ purposeSuffix: "Derive: wrap HYPE \u2192 WHYPE (if native), approve vault, deposit. First credit creates a Standard Margin subaccount.",
2317
+ firstMsgRawNo0x: firstDataNo0x,
2318
+ destinationAddress: steps[0].to,
2319
+ payableValueWei: steps.reduce((sum, s) => sum + s.value, 0n),
2320
+ resolveGasLimit: ({ index, estimatedGas }) => {
2321
+ if (index === steps.length - 1) {
2322
+ const gas = DEPOSIT_FALLBACK;
2323
+ return args.useCustomGas ? gasLimitFromEstimateAndChainConfig(gas, gasLimitConfig) : gas;
2324
+ }
2325
+ return estimatedGas;
2326
+ },
2327
+ buildBatchMeta: ({ index }) => ({
2328
+ signatureText: JSON.stringify({
2329
+ kind: "Derive",
2330
+ protocol: DERIVE_PROTOCOL_ID,
2331
+ action: index === steps.length - 1 ? "hypeVaultDeposit" : isNative && index === 0 ? "wrap" : "approve",
2332
+ chainId: args.chainId,
2333
+ vault
2334
+ })
2335
+ })
2336
+ });
2337
+ }
2338
+ var ZERO_ADDRESS2 = "0x0000000000000000000000000000000000000000";
2339
+ async function buildDeriveTransferSpotMultisign(args) {
2340
+ let fromId;
2341
+ try {
2342
+ fromId = assertPositiveSubaccountId(args.fromSubaccountId);
2343
+ } catch {
2344
+ throw new Error(DERIVE_MISSING_SUBACCOUNT_ERROR);
2345
+ }
2346
+ const toId = BigInt(args.toSubaccountId ?? 0);
2347
+ const newManager = BigInt(args.newSubaccountManager);
2348
+ if (toId === 0n && newManager <= 0n) {
2349
+ throw new Error("newSubaccountManager is required when toSubaccountId is 0 (create an extra subaccount).");
2350
+ }
2351
+ const owner = getAddress(args.executorAddress);
2352
+ const network = deriveNetworkFromChainId(args.chainId);
2353
+ const nonce = args.nonce ?? deriveRandomNonce();
2354
+ const expirySec = deriveExpirySec(args.signatureExpirySec);
2355
+ const data = encodeDeriveTransferData({
2356
+ toSubaccountId: toId,
2357
+ newSubaccountManager: newManager,
2358
+ asset: args.asset,
2359
+ subId: args.subId ?? 0,
2360
+ amount: args.amount,
2361
+ maxFeeUsd: args.maxFeeUsd ?? "0"
2362
+ });
2363
+ const typedData = buildDeriveActionTypedData({
2364
+ network,
2365
+ subaccountId: fromId,
2366
+ nonce,
2367
+ module: DERIVE_TRANSFER_MODULE,
2368
+ data,
2369
+ expirySec,
2370
+ owner,
2371
+ signer: owner
2372
+ });
2373
+ return buildEip712Multisign({
2374
+ keyGen: args.keyGen,
2375
+ purposeText: args.purposeText,
2376
+ destinationChainID: String(args.chainId),
2377
+ destinationAddress: ZERO_ADDRESS2,
2378
+ expiryDate: args.expiryDate,
2379
+ legs: [
2380
+ {
2381
+ typedData,
2382
+ delivery: {
2383
+ kind: "derive_exchange",
2384
+ method: "private/transfer_spot",
2385
+ chainId: args.chainId,
2386
+ network,
2387
+ settlementChainId: deriveSettlementChainId(network),
2388
+ params: {
2389
+ subaccount_id: Number(fromId),
2390
+ nonce,
2391
+ signer: owner,
2392
+ signature_expiry_sec: expirySec,
2393
+ asset: getAddress(args.asset),
2394
+ amount: args.amount,
2395
+ to_subaccount_id: Number(toId),
2396
+ new_subaccount_manager: Number(newManager)
2397
+ }
2398
+ },
2399
+ audit: {
2400
+ protocol: DERIVE_PROTOCOL_ID,
2401
+ action: "transferSpot",
2402
+ fromSubaccountId: fromId.toString(),
2403
+ toSubaccountId: toId.toString(),
2404
+ newSubaccountManager: newManager.toString()
2405
+ }
2406
+ }
2407
+ ]
2408
+ });
2409
+ }
2410
+
2411
+ // src/protocols/evm/derive/index.ts
2412
+ var deriveProtocolModule = {
2413
+ id: DERIVE_PROTOCOL_ID,
2414
+ chainCategory: "evm",
2415
+ isChainSupported(ctx) {
2416
+ if (ctx.chainCategory !== "evm") return false;
2417
+ const chainId = typeof ctx.chainId === "number" ? ctx.chainId : Number(ctx.chainId);
2418
+ return Number.isFinite(chainId) && isDeriveChainSupported(chainId);
2419
+ },
2420
+ isTokenSupported(token) {
2421
+ if (token.category !== "evm") return false;
2422
+ return token.kind === "erc20" || token.kind === "native";
2423
+ },
2424
+ actions: [
2425
+ {
2426
+ id: "derive.depositNewSubaccount",
2427
+ protocolId: DERIVE_PROTOCOL_ID,
2428
+ chainCategory: "evm",
2429
+ description: "Ethereum L1 approve + ACTION_MANAGER.depositToNewSubaccount. First credit creates the trading + fallback subaccounts. chainId 1 / 11155111. Arb/OP/Base use derive.socketDeposit; HyperEVM HYPE uses derive.hypeVaultDeposit.",
2430
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
2431
+ params: {
2432
+ protocolAsset: { type: "string", required: true, description: "Risk-universe collateral protocol asset" },
2433
+ underlyingErc20: { type: "string", required: true, description: "L1 ERC-20 to approve (usually USDC)" },
2434
+ amountHuman: { type: "string", required: true, description: "Deposit amount in underlying decimals" },
2435
+ managerId: { type: "number", required: true, description: "Risk-universe manager_id" }
2436
+ }
2437
+ },
2438
+ {
2439
+ id: "derive.depositAddressTransfer",
2440
+ protocolId: DERIVE_PROTOCOL_ID,
2441
+ chainCategory: "evm",
2442
+ description: "Ethereum L1 ERC-20 transfer to a registered Derive deposit address (standard / keeper sweep). Call register_deposit_address first.",
2443
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
2444
+ params: {
2445
+ underlyingErc20: { type: "string", required: true, description: "L1 ERC-20 to send (usually USDC)" },
2446
+ amountHuman: { type: "string", required: true, description: "Transfer amount in token units" },
2447
+ depositAddress: { type: "string", required: true, description: "From register_deposit_address" }
2448
+ }
2449
+ },
2450
+ {
2451
+ id: "derive.socketDeposit",
2452
+ protocolId: DERIVE_PROTOCOL_ID,
2453
+ chainCategory: "evm",
2454
+ description: "Socket FAST deposit of USDC/WETH/WBTC on Ethereum, Optimism, Base, or Arbitrum into Derive Chain. Native ETH wraps to WETH first. useCustomGas.",
2455
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
2456
+ params: {
2457
+ token: { type: "string", required: true, description: "Row ERC-20 or native 0x0 (wraps to WETH)" },
2458
+ amountHuman: { type: "string", required: true, description: "Deposit amount in token units" }
2459
+ }
2460
+ },
2461
+ {
2462
+ id: "derive.hypeVaultDeposit",
2463
+ protocolId: DERIVE_PROTOCOL_ID,
2464
+ chainCategory: "evm",
2465
+ description: "HyperEVM (999): wrap native HYPE to WHYPE if needed, approve Derive vault, deposit.",
2466
+ commonParams: ["keyGen", "purposeText", "useCustomGas"],
2467
+ params: {
2468
+ amountHuman: { type: "string", required: true, description: "HYPE amount" },
2469
+ token: { type: "string", required: false, description: "Native 0x0 or WHYPE" }
2470
+ }
2471
+ },
2472
+ {
2473
+ id: "derive.placeOrder",
2474
+ protocolId: DERIVE_PROTOCOL_ID,
2475
+ chainCategory: "evm",
2476
+ description: "Place a Derive options limit order via EIP-712 Action (delivery derive_exchange). Does not spend the current-chain token.",
2477
+ commonParams: ["keyGen", "purposeText"],
2478
+ params: {
2479
+ instrumentName: { type: "string", required: true, description: "e.g. ETH-20260626-3000-C" },
2480
+ direction: { type: "string", required: true, description: "buy or sell" },
2481
+ amount: { type: "string", required: true, description: "Contract size" },
2482
+ limitPrice: { type: "string", required: true, description: "Limit price" },
2483
+ maxFee: { type: "string", required: true, description: "Max fee (e18-scaled, \u226412 decimals)" },
2484
+ subaccountId: { type: "number", required: true, description: "From fetch_subaccounts \u2014 never 0" }
2485
+ }
2486
+ },
2487
+ {
2488
+ id: "derive.replaceOrder",
2489
+ protocolId: DERIVE_PROTOCOL_ID,
2490
+ chainCategory: "evm",
2491
+ description: "Replace a Derive open order (EIP-712 Action + orderIdToCancel or nonceToCancel).",
2492
+ commonParams: ["keyGen", "purposeText"],
2493
+ params: {
2494
+ instrumentName: { type: "string", required: true, description: "Instrument of the replacement order" },
2495
+ direction: { type: "string", required: true, description: "buy or sell" },
2496
+ amount: { type: "string", required: true, description: "Contract size" },
2497
+ limitPrice: { type: "string", required: true, description: "Limit price" },
2498
+ maxFee: { type: "string", required: true, description: "Max fee" },
2499
+ subaccountId: { type: "number", required: true, description: "From fetch_subaccounts" },
2500
+ orderIdToCancel: { type: "string", required: false, description: "Existing order id (xor nonceToCancel)" },
2501
+ nonceToCancel: { type: "string", required: false, description: "Existing order nonce (xor orderIdToCancel)" }
2502
+ }
2503
+ },
2504
+ {
2505
+ id: "derive.cancel",
2506
+ protocolId: DERIVE_PROTOCOL_ID,
2507
+ chainCategory: "evm",
2508
+ description: "Cancel a Derive open order. v3 cancel is session-auth; this signs EIP-191 login for private/cancel delivery.",
2509
+ commonParams: ["keyGen", "purposeText"],
2510
+ params: {
2511
+ instrumentName: { type: "string", required: true, description: "Instrument of the open order" },
2512
+ orderId: { type: "string", required: true, description: "order_id from fetch_open_orders" },
2513
+ subaccountId: { type: "number", required: true, description: "From fetch_subaccounts" }
2514
+ }
2515
+ },
2516
+ {
2517
+ id: "derive.transferSpot",
2518
+ protocolId: DERIVE_PROTOCOL_ID,
2519
+ chainCategory: "evm",
2520
+ description: "EIP-712 transfer_spot. Use toSubaccountId 0 + newSubaccountManager to create an extra subaccount after the first funded one.",
2521
+ commonParams: ["keyGen", "purposeText"],
2522
+ params: {
2523
+ fromSubaccountId: { type: "number", required: true, description: "Funded source subaccount" },
2524
+ toSubaccountId: { type: "number", required: true, description: "0 to create an extra subaccount" },
2525
+ newSubaccountManager: { type: "number", required: true, description: "Required when toSubaccountId is 0" },
2526
+ asset: { type: "string", required: true, description: "Protocol asset address" },
2527
+ amount: { type: "string", required: true, description: "Transfer amount" }
2528
+ }
2529
+ }
2530
+ ]
2531
+ };
2532
+ registerProtocolModule(deriveProtocolModule);
2533
+
2534
+ export { DERIVE_ACTION_MANAGER_TESTNET, DERIVE_ACTION_TYPEHASH, DERIVE_ACTION_TYPES, DERIVE_ASSET_ROW_CHAIN_IDS, DERIVE_DEFAULT_LOOKBACK_DAYS, DERIVE_DEFAULT_OHLCV_BARS, DERIVE_DEFAULT_OHLCV_INTERVAL, DERIVE_DEFAULT_SIGNATURE_EXPIRY_SEC, DERIVE_DEFAULT_TICKER_ATM_STRIKES, DERIVE_DOMAIN_SEPARATOR_MAINNET, DERIVE_DOMAIN_SEPARATOR_TESTNET, DERIVE_EIP712_NAME, DERIVE_EIP712_VERSION, DERIVE_HYPE_CHAIN_ID, DERIVE_HYPE_VAULT_DEFAULT, DERIVE_MAINNET_API, DERIVE_MAINNET_API_V2, DERIVE_MATCHING_VERIFYING_CONTRACT, DERIVE_MAX_LOOKBACK_DAYS, DERIVE_MAX_OHLCV_BARS, DERIVE_MIN_ORDER_EXPIRY_SEC, DERIVE_MISSING_SUBACCOUNT_ERROR, DERIVE_OHLCV_INTERVALS, DERIVE_OHLCV_PERIOD_SEC, DERIVE_PROTOCOL_ID, DERIVE_SETTLEMENT_CHAIN_IDS, DERIVE_SOCKET_APP_CHAIN_ID, DERIVE_SOCKET_CHAIN_IDS, DERIVE_SOCKET_FEE_FALLBACK_WEI, DERIVE_SOCKET_MSG_GAS_LIMIT, DERIVE_TESTNET_API, DERIVE_TESTNET_API_V2, DERIVE_TRADE_MODULE, DERIVE_TRANSFER_MODULE, DERIVE_UNFUNDED_NEXT, DERIVE_UNFUNDED_NOTES, DERIVE_WHYPE, NATIVE_ZERO, assertPositiveSubaccountId, buildDeriveActionTypedData, buildDeriveCancelMultisign, buildDeriveDepositAddressTransferMultisign, buildDeriveDepositNewSubaccountMultisign, buildDeriveHypeVaultDepositMultisign, buildDerivePlaceOrderMultisign, buildDeriveReplaceOrderMultisign, buildDeriveSocketDepositMultisign, buildDeriveTransferSpotMultisign, collectDeriveTickerRows, deriveActionManagerAddress, deriveActionTypehash, deriveAdvisorNotes, deriveApiBaseUrl, deriveApiBaseUrlForChainId, deriveApiFallbackBaseUrl, deriveAtmTickerWindow, deriveAuthHeaders, deriveDomainSeparator, deriveE18, deriveEmptySubaccountsPayload, deriveExpirySec, deriveExpiryUnixToYmd, deriveFetchCurrenciesRaw, deriveFetchCurrenciesSummary, deriveFetchInstrument, deriveFetchMarketSnapshotSummary, deriveFetchOhlcvSummary, deriveFetchOpenOrdersSummary, deriveFetchOptionChainAgentSummary, deriveFetchOptionChainSummary, deriveFetchOptionInstruments, deriveFetchOrderBookSummary, deriveFetchPendingDepositsSummary, deriveFetchPortfolioSummary, deriveFetchPositionsSummary, deriveFetchRiskUniversesSummary, deriveFetchSubaccountsSummary, deriveFetchTickersSummary, deriveFundRouteForAssetRow, deriveFundableTokensForChain, deriveHypeVaultAddress, deriveKeyForNodeAssetRow, deriveLoginDigest, deriveLoginMessage, deriveLoginTimestamp, deriveMappedTokensForChain, deriveMatchingDomain, deriveNativeWrappedForChain, deriveNetworkFromChainId, deriveOrderQuoteSummary, deriveParseInstrumentName, deriveProtocolModule, deriveRandomNonce, deriveRegisterDepositAddress, deriveRpc, deriveSearchInstruments, deriveSearchInstrumentsSummary, deriveSettlementChainId, deriveSocketRouteForToken, deriveSocketWethRoute, encodeDeriveTradeData, encodeDeriveTransferData, hashDeriveAction, isDeriveAssetRowChainId, isDeriveChainSupported, isDerivePublicMethod, isDeriveRecoverablePublicError, isDeriveSettlementChainId, isDeriveSocketChainId, isEmptyDerivePublicResult, mapDeriveOrderBookLevels, mapDeriveTickerRow, mapDeriveTradingviewCandles, normalizeDeriveExpiryYmd, requireDeriveAuth, resolveDeriveOhlcvWindow, summarizeDeriveOptionChain, synthesizeDeriveRiskUniversesFromCurrencies };
2535
+ //# sourceMappingURL=index.js.map
2536
+ //# sourceMappingURL=index.js.map