@strkfarm/sdk 2.0.0-dev.26 → 2.0.0-dev.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +190 -36
- package/dist/cli.mjs +188 -34
- package/dist/index.browser.global.js +79130 -49354
- package/dist/index.browser.mjs +18039 -11431
- package/dist/index.d.ts +2869 -898
- package/dist/index.js +19036 -12207
- package/dist/index.mjs +18942 -12158
- package/package.json +1 -1
- package/src/data/avnu.abi.json +840 -0
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/dataTypes/_bignumber.ts +13 -4
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +76 -41
- package/src/index.browser.ts +2 -1
- package/src/interfaces/common.tsx +167 -2
- package/src/modules/ExtendedWrapperSDk/types.ts +26 -4
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +110 -67
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +4 -4
- package/src/modules/ekubo-pricer.ts +79 -0
- package/src/modules/ekubo-quoter.ts +46 -30
- package/src/modules/erc20.ts +17 -0
- package/src/modules/harvests.ts +43 -29
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-from-api.ts +156 -15
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +40 -4
- package/src/modules/pricerBase.ts +2 -1
- package/src/node/deployer.ts +36 -1
- package/src/node/pricer-redis.ts +2 -1
- package/src/strategies/base-strategy.ts +78 -10
- package/src/strategies/ekubo-cl-vault.tsx +906 -347
- package/src/strategies/factory.ts +159 -0
- package/src/strategies/index.ts +6 -1
- package/src/strategies/registry.ts +239 -0
- package/src/strategies/sensei.ts +335 -7
- package/src/strategies/svk-strategy.ts +97 -27
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +2 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +177 -268
- package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
- package/src/strategies/universal-adapters/common-adapter.ts +206 -203
- package/src/strategies/universal-adapters/extended-adapter.ts +155 -336
- package/src/strategies/universal-adapters/index.ts +9 -8
- package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
- package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +200 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +110 -75
- package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +476 -0
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +762 -844
- package/src/strategies/universal-adapters/vesu-position-common.ts +251 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
- package/src/strategies/universal-lst-muliplier-strategy.tsx +396 -204
- package/src/strategies/universal-strategy.tsx +1426 -1178
- package/src/strategies/vesu-extended-strategy/services/executionService.ts +2251 -0
- package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +2941 -0
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +12 -1
- package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +52 -0
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +1 -0
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +3 -1
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +158 -124
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +377 -1781
- package/src/strategies/vesu-rebalance.tsx +255 -152
- package/src/utils/health-factor-math.ts +4 -1
- package/src/utils/index.ts +2 -1
- package/src/utils/logger.browser.ts +22 -4
- package/src/utils/logger.node.ts +259 -24
- package/src/utils/starknet-call-parser.ts +1036 -0
- package/src/utils/strategy-utils.ts +61 -0
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
|
@@ -0,0 +1,1036 @@
|
|
|
1
|
+
import { hash, uint256 } from "starknet";
|
|
2
|
+
import type { Call } from "starknet";
|
|
3
|
+
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
4
|
+
import type { TokenInfo } from "@/interfaces";
|
|
5
|
+
import type { ManageCall } from "@/strategies/universal-adapters/baseAdapter";
|
|
6
|
+
import { logger } from "./logger";
|
|
7
|
+
|
|
8
|
+
type DecoderEntry = {
|
|
9
|
+
label: string;
|
|
10
|
+
decode: (
|
|
11
|
+
calldata: string[],
|
|
12
|
+
options: StarknetCallParserOptions,
|
|
13
|
+
) => unknown;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type ParsedStarknetCall = {
|
|
17
|
+
target: string;
|
|
18
|
+
method: string;
|
|
19
|
+
selector?: string;
|
|
20
|
+
sanitizer?: string;
|
|
21
|
+
decodedArgs: unknown;
|
|
22
|
+
rawCalldata: string[];
|
|
23
|
+
parserUsed: string;
|
|
24
|
+
parseError?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type StarknetCallParserOptions = {
|
|
28
|
+
tokenSymbols?: Record<string, string>;
|
|
29
|
+
tokenDecimals?: Record<string, number>;
|
|
30
|
+
poolNames?: Record<string, string>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Integration guide for new methods/protocols:
|
|
35
|
+
* 1) Add method name to METHOD_BY_SELECTOR if it comes from ManageCall selector.
|
|
36
|
+
* 2) Add a decoder in METHOD_DECODERS with a focused decode* method.
|
|
37
|
+
* 3) Keep decoder output concise and operator-friendly.
|
|
38
|
+
* 4) Prefer existing primitives (ContractAddr, uint256, Web3Number where needed).
|
|
39
|
+
* 5) Add/extend tests in test/starknet-call-parser.test.ts for parse + log behavior.
|
|
40
|
+
*/
|
|
41
|
+
export class StarknetCallParser {
|
|
42
|
+
private static readonly METHOD_DECODERS: Record<string, DecoderEntry[]> = {
|
|
43
|
+
modify_lever: [
|
|
44
|
+
{
|
|
45
|
+
label: "vesu-multiple",
|
|
46
|
+
decode: (calldata, options) =>
|
|
47
|
+
StarknetCallParser.decodeModifyLever(calldata, options),
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
modify_delegation: [
|
|
51
|
+
{
|
|
52
|
+
label: "vesu-pool-v2",
|
|
53
|
+
decode: (calldata) =>
|
|
54
|
+
StarknetCallParser.decodeModifyDelegation(calldata, true),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: "vesu-singleton",
|
|
58
|
+
decode: (calldata) =>
|
|
59
|
+
StarknetCallParser.decodeModifyDelegation(calldata, false),
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
modify_position: [
|
|
63
|
+
{
|
|
64
|
+
label: "vesu-modify-position",
|
|
65
|
+
decode: (calldata, options) =>
|
|
66
|
+
StarknetCallParser.decodeModifyPosition(calldata, options),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
approve: [
|
|
70
|
+
{
|
|
71
|
+
label: "erc20",
|
|
72
|
+
decode: (calldata) => StarknetCallParser.decodeApprove(calldata),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
flash_loan: [
|
|
76
|
+
{
|
|
77
|
+
label: "vault-manager",
|
|
78
|
+
decode: (calldata) => StarknetCallParser.decodeFlashLoan(calldata),
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
bring_liquidity: [
|
|
82
|
+
{
|
|
83
|
+
label: "universal-vault",
|
|
84
|
+
decode: (calldata) => StarknetCallParser.decodeBringLiquidity(calldata),
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
manage_vault_with_merkle_verification: [
|
|
88
|
+
{
|
|
89
|
+
label: "vault-manager",
|
|
90
|
+
decode: (calldata, options) =>
|
|
91
|
+
StarknetCallParser.decodeManageVaultWithMerkleVerification(
|
|
92
|
+
calldata,
|
|
93
|
+
options,
|
|
94
|
+
),
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
multi_route_swap: [
|
|
98
|
+
{
|
|
99
|
+
label: "avnu",
|
|
100
|
+
decode: (calldata, options) =>
|
|
101
|
+
StarknetCallParser.decodeMultiRouteSwap(calldata, options),
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
private static readonly METHOD_BY_SELECTOR: Map<string, string> = new Map(
|
|
107
|
+
[
|
|
108
|
+
"modify_lever",
|
|
109
|
+
"modify_delegation",
|
|
110
|
+
"modify_position",
|
|
111
|
+
"approve",
|
|
112
|
+
"flash_loan",
|
|
113
|
+
"bring_liquidity",
|
|
114
|
+
"manage_vault_with_merkle_verification",
|
|
115
|
+
"multi_route_swap",
|
|
116
|
+
].map((method) => [
|
|
117
|
+
StarknetCallParser.normalizeHex(hash.getSelectorFromName(method)),
|
|
118
|
+
method,
|
|
119
|
+
]),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
static parseManageCall(
|
|
123
|
+
manageCall: ManageCall,
|
|
124
|
+
options: StarknetCallParserOptions = {},
|
|
125
|
+
): ParsedStarknetCall {
|
|
126
|
+
const selector = StarknetCallParser.normalizeHex(manageCall.call.selector);
|
|
127
|
+
const method =
|
|
128
|
+
StarknetCallParser.METHOD_BY_SELECTOR.get(selector) ??
|
|
129
|
+
`unknown_selector_${selector}`;
|
|
130
|
+
const target = StarknetCallParser.addressToString(
|
|
131
|
+
manageCall.call.contractAddress,
|
|
132
|
+
);
|
|
133
|
+
const sanitizer = StarknetCallParser.addressToString(manageCall.sanitizer);
|
|
134
|
+
|
|
135
|
+
return StarknetCallParser.parseRaw({
|
|
136
|
+
method,
|
|
137
|
+
target,
|
|
138
|
+
selector,
|
|
139
|
+
sanitizer,
|
|
140
|
+
calldata: manageCall.call.calldata,
|
|
141
|
+
options,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
static parseManageCalls(
|
|
146
|
+
manageCalls: ManageCall[],
|
|
147
|
+
options: StarknetCallParserOptions = {},
|
|
148
|
+
): ParsedStarknetCall[] {
|
|
149
|
+
return manageCalls.map((call) =>
|
|
150
|
+
StarknetCallParser.parseManageCall(call, options),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
static parseCall(
|
|
155
|
+
call: Call,
|
|
156
|
+
options: StarknetCallParserOptions = {},
|
|
157
|
+
): ParsedStarknetCall {
|
|
158
|
+
return StarknetCallParser.parseRaw({
|
|
159
|
+
method: call.entrypoint,
|
|
160
|
+
target: StarknetCallParser.addressToString(call.contractAddress),
|
|
161
|
+
calldata: call.calldata,
|
|
162
|
+
options,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
static parseCalls(
|
|
167
|
+
calls: Call[],
|
|
168
|
+
options: StarknetCallParserOptions = {},
|
|
169
|
+
): ParsedStarknetCall[] {
|
|
170
|
+
return calls.map((call) => StarknetCallParser.parseCall(call, options));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
static logManageCalls(
|
|
174
|
+
context: string,
|
|
175
|
+
manageCalls: ManageCall[],
|
|
176
|
+
options: StarknetCallParserOptions = {},
|
|
177
|
+
): void {
|
|
178
|
+
logger.debug(
|
|
179
|
+
StarknetCallParser.formatManageCallsSummary(context, manageCalls, options),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
static logCalls(
|
|
184
|
+
context: string,
|
|
185
|
+
calls: Call[],
|
|
186
|
+
options: StarknetCallParserOptions = {},
|
|
187
|
+
): void {
|
|
188
|
+
logger.debug(StarknetCallParser.formatCallsSummary(context, calls, options));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static logCallsSummary(
|
|
192
|
+
context: string,
|
|
193
|
+
calls: Call[],
|
|
194
|
+
options: StarknetCallParserOptions = {},
|
|
195
|
+
sink: (message: string) => void = logger.debug,
|
|
196
|
+
): void {
|
|
197
|
+
sink(StarknetCallParser.formatCallsSummary(context, calls, options));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
static logManageCallsSummary(
|
|
201
|
+
context: string,
|
|
202
|
+
manageCalls: ManageCall[],
|
|
203
|
+
options: StarknetCallParserOptions = {},
|
|
204
|
+
sink: (message: string) => void = logger.debug,
|
|
205
|
+
): void {
|
|
206
|
+
sink(
|
|
207
|
+
StarknetCallParser.formatManageCallsSummary(context, manageCalls, options),
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static formatCallsSummary(
|
|
212
|
+
context: string,
|
|
213
|
+
calls: Call[],
|
|
214
|
+
options: StarknetCallParserOptions = {},
|
|
215
|
+
): string {
|
|
216
|
+
const parsed = StarknetCallParser.parseCalls(calls, options);
|
|
217
|
+
const summaries = parsed.map((call, index) =>
|
|
218
|
+
StarknetCallParser.toCallSummary(call, index),
|
|
219
|
+
);
|
|
220
|
+
return `${context}: ${summaries.join(" | ")}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
static formatManageCallsSummary(
|
|
224
|
+
context: string,
|
|
225
|
+
manageCalls: ManageCall[],
|
|
226
|
+
options: StarknetCallParserOptions = {},
|
|
227
|
+
): string {
|
|
228
|
+
const parsed = StarknetCallParser.parseManageCalls(manageCalls, options);
|
|
229
|
+
const summaries = parsed.map((call, index) =>
|
|
230
|
+
StarknetCallParser.toCallSummary(call, index),
|
|
231
|
+
);
|
|
232
|
+
return `${context}: ${summaries.join(" | ")}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static buildTokenSymbolLookup(tokens: TokenInfo[]): Record<string, string> {
|
|
236
|
+
return tokens.reduce<Record<string, string>>((acc, token) => {
|
|
237
|
+
const addr = StarknetCallParser.toAddressHex(token.address.address);
|
|
238
|
+
acc[addr] = token.symbol;
|
|
239
|
+
return acc;
|
|
240
|
+
}, {});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
static buildPoolNameLookup(
|
|
244
|
+
pools: { poolId: string | bigint; name: string }[],
|
|
245
|
+
): Record<string, string> {
|
|
246
|
+
return pools.reduce<Record<string, string>>((acc, pool) => {
|
|
247
|
+
acc[StarknetCallParser.toPoolIdKey(pool.poolId)] = pool.name;
|
|
248
|
+
return acc;
|
|
249
|
+
}, {});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static buildTokenDecimalsLookup(tokens: TokenInfo[]): Record<string, number> {
|
|
253
|
+
return tokens.reduce<Record<string, number>>((acc, token) => {
|
|
254
|
+
const addr = StarknetCallParser.toAddressHex(token.address.address);
|
|
255
|
+
acc[addr] = token.decimals;
|
|
256
|
+
return acc;
|
|
257
|
+
}, {});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private static parseRaw(params: {
|
|
261
|
+
method: string;
|
|
262
|
+
target: string;
|
|
263
|
+
calldata: unknown;
|
|
264
|
+
selector?: string;
|
|
265
|
+
sanitizer?: string;
|
|
266
|
+
options: StarknetCallParserOptions;
|
|
267
|
+
}): ParsedStarknetCall {
|
|
268
|
+
const rawCalldata = StarknetCallParser.toStringCalldata(params.calldata);
|
|
269
|
+
const decoders = StarknetCallParser.METHOD_DECODERS[params.method] ?? [];
|
|
270
|
+
|
|
271
|
+
if (decoders.length === 0) {
|
|
272
|
+
return {
|
|
273
|
+
target: params.target,
|
|
274
|
+
method: params.method,
|
|
275
|
+
selector: params.selector,
|
|
276
|
+
sanitizer: params.sanitizer,
|
|
277
|
+
decodedArgs: {},
|
|
278
|
+
rawCalldata,
|
|
279
|
+
parserUsed: "none",
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let parseError: string | undefined;
|
|
284
|
+
for (const decoder of decoders) {
|
|
285
|
+
try {
|
|
286
|
+
const decodedArgs = StarknetCallParser.enrichDecodedArgs(
|
|
287
|
+
decoder.decode(rawCalldata, params.options),
|
|
288
|
+
params.options,
|
|
289
|
+
);
|
|
290
|
+
return {
|
|
291
|
+
target: params.target,
|
|
292
|
+
method: params.method,
|
|
293
|
+
selector: params.selector,
|
|
294
|
+
sanitizer: params.sanitizer,
|
|
295
|
+
decodedArgs,
|
|
296
|
+
rawCalldata,
|
|
297
|
+
parserUsed: `abi:${decoder.label}`,
|
|
298
|
+
};
|
|
299
|
+
} catch (err) {
|
|
300
|
+
parseError = err instanceof Error ? err.message : String(err);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
target: params.target,
|
|
306
|
+
method: params.method,
|
|
307
|
+
selector: params.selector,
|
|
308
|
+
sanitizer: params.sanitizer,
|
|
309
|
+
decodedArgs: {},
|
|
310
|
+
rawCalldata,
|
|
311
|
+
parserUsed: "abi-fallback",
|
|
312
|
+
parseError,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private static enrichDecodedArgs(
|
|
317
|
+
decodedArgs: unknown,
|
|
318
|
+
options: StarknetCallParserOptions,
|
|
319
|
+
): unknown {
|
|
320
|
+
if (!decodedArgs || typeof decodedArgs !== "object") return decodedArgs;
|
|
321
|
+
const args = decodedArgs as Record<string, unknown>;
|
|
322
|
+
const poolId = args.poolId;
|
|
323
|
+
if (poolId === undefined) return decodedArgs;
|
|
324
|
+
const key = StarknetCallParser.toPoolIdKey(poolId as string | bigint);
|
|
325
|
+
const poolName = options.poolNames?.[key];
|
|
326
|
+
if (!poolName) return decodedArgs;
|
|
327
|
+
return { ...args, poolName };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private static toStringCalldata(
|
|
331
|
+
calldata: unknown,
|
|
332
|
+
): string[] {
|
|
333
|
+
if (!calldata) return [];
|
|
334
|
+
if (!Array.isArray(calldata)) {
|
|
335
|
+
return [String(calldata)];
|
|
336
|
+
}
|
|
337
|
+
return Array.from(calldata).map((value) =>
|
|
338
|
+
typeof value === "bigint" ? value.toString() : String(value),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private static toBigInt(value: unknown): bigint | null {
|
|
343
|
+
try {
|
|
344
|
+
if (typeof value === "bigint") return value;
|
|
345
|
+
if (typeof value === "number") return BigInt(value.toString());
|
|
346
|
+
if (typeof value === "string") return BigInt(value);
|
|
347
|
+
return null;
|
|
348
|
+
} catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private static decodeApprove(calldata: string[]) {
|
|
354
|
+
if (calldata.length < 3) {
|
|
355
|
+
throw new Error(`approve calldata too short: ${calldata.length}`);
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
spender: StarknetCallParser.toAddressHex(calldata[0]),
|
|
359
|
+
amount: StarknetCallParser.u256FromParts(calldata[1], calldata[2]),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private static decodeBringLiquidity(calldata: string[]) {
|
|
364
|
+
if (calldata.length < 2) {
|
|
365
|
+
throw new Error(`bring_liquidity calldata too short: ${calldata.length}`);
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
amount: StarknetCallParser.u256FromParts(calldata[0], calldata[1]),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private static decodeFlashLoan(calldata: string[]) {
|
|
373
|
+
if (calldata.length < 6) {
|
|
374
|
+
throw new Error(`flash_loan calldata too short: ${calldata.length}`);
|
|
375
|
+
}
|
|
376
|
+
const dataLen = Number(StarknetCallParser.toBigInt(calldata[5]) ?? 0n);
|
|
377
|
+
return {
|
|
378
|
+
receiver: StarknetCallParser.toAddressHex(calldata[0]),
|
|
379
|
+
asset: StarknetCallParser.toAddressHex(calldata[1]),
|
|
380
|
+
amount: StarknetCallParser.u256FromParts(calldata[2], calldata[3]),
|
|
381
|
+
isLegacy: calldata[4] === "1",
|
|
382
|
+
dataLen,
|
|
383
|
+
data: calldata.slice(6, 6 + dataLen),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private static decodeModifyDelegation(calldata: string[], isV2: boolean) {
|
|
388
|
+
if (isV2) {
|
|
389
|
+
if (calldata.length !== 2) {
|
|
390
|
+
throw new Error(`modify_delegation v2 calldata length mismatch: ${calldata.length}`);
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
version: "v2",
|
|
394
|
+
delegatee: StarknetCallParser.toAddressHex(calldata[0]),
|
|
395
|
+
delegation: calldata[1] === "1",
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (calldata.length !== 3) {
|
|
400
|
+
throw new Error(`modify_delegation v1 calldata length mismatch: ${calldata.length}`);
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
version: "v1",
|
|
404
|
+
poolId: StarknetCallParser.toPoolIdKey(calldata[0]),
|
|
405
|
+
delegatee: StarknetCallParser.toAddressHex(calldata[1]),
|
|
406
|
+
delegation: calldata[2] === "1",
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private static parseVesuAmount(
|
|
411
|
+
calldata: string[],
|
|
412
|
+
cursor: { value: number },
|
|
413
|
+
tokenAddress: string,
|
|
414
|
+
options: StarknetCallParserOptions,
|
|
415
|
+
) {
|
|
416
|
+
const amountTypeTag = calldata[cursor.value++] ?? "0";
|
|
417
|
+
const denominationTag = calldata[cursor.value++] ?? "0";
|
|
418
|
+
const abs = StarknetCallParser.u256FromParts(
|
|
419
|
+
calldata[cursor.value++] ?? "0",
|
|
420
|
+
calldata[cursor.value++] ?? "0",
|
|
421
|
+
);
|
|
422
|
+
const isNegative = calldata[cursor.value++] === "1";
|
|
423
|
+
const absHuman = StarknetCallParser.toHumanAmount(abs, tokenAddress, options);
|
|
424
|
+
const signedAmount = isNegative ? `-${abs}` : abs;
|
|
425
|
+
const signedAmountHuman = isNegative ? `-${absHuman}` : absHuman;
|
|
426
|
+
return {
|
|
427
|
+
amountTypeTag,
|
|
428
|
+
denominationTag,
|
|
429
|
+
abs,
|
|
430
|
+
absHuman,
|
|
431
|
+
isNegative,
|
|
432
|
+
signedAmount,
|
|
433
|
+
signedAmountHuman,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private static decodeModifyPosition(
|
|
438
|
+
calldata: string[],
|
|
439
|
+
options: StarknetCallParserOptions,
|
|
440
|
+
) {
|
|
441
|
+
const tryParseV1 = () => {
|
|
442
|
+
if (calldata.length < 15) {
|
|
443
|
+
throw new Error(`modify_position v1 calldata too short: ${calldata.length}`);
|
|
444
|
+
}
|
|
445
|
+
const cursor = { value: 0 };
|
|
446
|
+
const poolId = StarknetCallParser.toPoolIdKey(calldata[cursor.value++]);
|
|
447
|
+
const collateralAssetAddress = StarknetCallParser.toAddressHex(calldata[cursor.value++]);
|
|
448
|
+
const debtAssetAddress = StarknetCallParser.toAddressHex(calldata[cursor.value++]);
|
|
449
|
+
const user = StarknetCallParser.toAddressHex(calldata[cursor.value++]);
|
|
450
|
+
const collateral = StarknetCallParser.parseVesuAmount(
|
|
451
|
+
calldata,
|
|
452
|
+
cursor,
|
|
453
|
+
collateralAssetAddress,
|
|
454
|
+
options,
|
|
455
|
+
);
|
|
456
|
+
const debt = StarknetCallParser.parseVesuAmount(
|
|
457
|
+
calldata,
|
|
458
|
+
cursor,
|
|
459
|
+
debtAssetAddress,
|
|
460
|
+
options,
|
|
461
|
+
);
|
|
462
|
+
const dataLen = Number(StarknetCallParser.toBigInt(calldata[cursor.value++]) ?? 0n);
|
|
463
|
+
const data = calldata.slice(cursor.value, cursor.value + dataLen);
|
|
464
|
+
if (data.length !== dataLen) {
|
|
465
|
+
throw new Error(`modify_position v1 data length mismatch: expected ${dataLen}, got ${data.length}`);
|
|
466
|
+
}
|
|
467
|
+
const collateralAsset = StarknetCallParser.toAssetLabel(collateralAssetAddress, options);
|
|
468
|
+
const debtAsset = StarknetCallParser.toAssetLabel(debtAssetAddress, options);
|
|
469
|
+
return {
|
|
470
|
+
version: "v1",
|
|
471
|
+
poolId,
|
|
472
|
+
collateralAsset,
|
|
473
|
+
collateralAssetAddress,
|
|
474
|
+
debtAsset,
|
|
475
|
+
debtAssetAddress,
|
|
476
|
+
user,
|
|
477
|
+
collateral,
|
|
478
|
+
debt,
|
|
479
|
+
dataLen,
|
|
480
|
+
data,
|
|
481
|
+
summary: `Modify position v1: collateral ${collateral.signedAmountHuman} ${collateralAsset}, debt ${debt.signedAmountHuman} ${debtAsset}`,
|
|
482
|
+
};
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const tryParseV2 = () => {
|
|
486
|
+
if (calldata.length < 13) {
|
|
487
|
+
throw new Error(`modify_position v2 calldata too short: ${calldata.length}`);
|
|
488
|
+
}
|
|
489
|
+
const cursor = { value: 0 };
|
|
490
|
+
const collateralAssetAddress = StarknetCallParser.toAddressHex(calldata[cursor.value++]);
|
|
491
|
+
const debtAssetAddress = StarknetCallParser.toAddressHex(calldata[cursor.value++]);
|
|
492
|
+
const user = StarknetCallParser.toAddressHex(calldata[cursor.value++]);
|
|
493
|
+
const collateral = StarknetCallParser.parseVesuAmount(
|
|
494
|
+
calldata,
|
|
495
|
+
cursor,
|
|
496
|
+
collateralAssetAddress,
|
|
497
|
+
options,
|
|
498
|
+
);
|
|
499
|
+
const debt = StarknetCallParser.parseVesuAmount(
|
|
500
|
+
calldata,
|
|
501
|
+
cursor,
|
|
502
|
+
debtAssetAddress,
|
|
503
|
+
options,
|
|
504
|
+
);
|
|
505
|
+
const collateralAsset = StarknetCallParser.toAssetLabel(collateralAssetAddress, options);
|
|
506
|
+
const debtAsset = StarknetCallParser.toAssetLabel(debtAssetAddress, options);
|
|
507
|
+
return {
|
|
508
|
+
version: "v2",
|
|
509
|
+
collateralAsset,
|
|
510
|
+
collateralAssetAddress,
|
|
511
|
+
debtAsset,
|
|
512
|
+
debtAssetAddress,
|
|
513
|
+
user,
|
|
514
|
+
collateral,
|
|
515
|
+
debt,
|
|
516
|
+
summary: `Modify position v2: collateral ${collateral.signedAmountHuman} ${collateralAsset}, debt ${debt.signedAmountHuman} ${debtAsset}`,
|
|
517
|
+
};
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
return tryParseV1();
|
|
522
|
+
} catch {
|
|
523
|
+
return tryParseV2();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private static decodeModifyLever(
|
|
528
|
+
calldata: string[],
|
|
529
|
+
options: StarknetCallParserOptions,
|
|
530
|
+
) {
|
|
531
|
+
if (calldata.length < 6) {
|
|
532
|
+
throw new Error(`modify_lever calldata too short: ${calldata.length}`);
|
|
533
|
+
}
|
|
534
|
+
const actionTag = calldata[0] ?? "0";
|
|
535
|
+
const action = actionTag === "0" ? "IncreaseLever" : "DecreaseLever";
|
|
536
|
+
const cursor = { value: 1 };
|
|
537
|
+
|
|
538
|
+
const poolId = StarknetCallParser.toPoolIdKey(calldata[cursor.value++]);
|
|
539
|
+
const collateralAssetAddress = StarknetCallParser.toAddressHex(
|
|
540
|
+
calldata[cursor.value++],
|
|
541
|
+
);
|
|
542
|
+
const debtAssetAddress = StarknetCallParser.toAddressHex(calldata[cursor.value++]);
|
|
543
|
+
const collateralAsset = StarknetCallParser.toAssetLabel(
|
|
544
|
+
collateralAssetAddress,
|
|
545
|
+
options,
|
|
546
|
+
);
|
|
547
|
+
const debtAsset = StarknetCallParser.toAssetLabel(debtAssetAddress, options);
|
|
548
|
+
const user = StarknetCallParser.toAddressHex(calldata[cursor.value++]);
|
|
549
|
+
|
|
550
|
+
if (calldata.length <= 6) {
|
|
551
|
+
const amountOrSubMargin = calldata[5] ?? "0";
|
|
552
|
+
return {
|
|
553
|
+
action,
|
|
554
|
+
poolId,
|
|
555
|
+
collateralAsset,
|
|
556
|
+
collateralAssetAddress,
|
|
557
|
+
debtAsset,
|
|
558
|
+
debtAssetAddress,
|
|
559
|
+
user,
|
|
560
|
+
amountOrSubMargin,
|
|
561
|
+
summary:
|
|
562
|
+
action === "IncreaseLever"
|
|
563
|
+
? `Modify lever increase: margin ${StarknetCallParser.toHumanAmount(amountOrSubMargin, collateralAssetAddress, options)} ${collateralAsset}`
|
|
564
|
+
: `Modify lever decrease: sub margin ${StarknetCallParser.toHumanAmount(amountOrSubMargin, collateralAssetAddress, options)} ${collateralAsset}`,
|
|
565
|
+
calldataLen: calldata.length,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (action === "IncreaseLever") {
|
|
570
|
+
const addMargin = calldata[cursor.value++];
|
|
571
|
+
const marginSwap = StarknetCallParser.parseSwapArray(calldata, cursor);
|
|
572
|
+
const marginSwapLimitAmount = calldata[cursor.value++] ?? "0";
|
|
573
|
+
const leverSwap = StarknetCallParser.parseSwapArray(calldata, cursor);
|
|
574
|
+
const leverSwapLimitAmount = calldata[cursor.value++] ?? "0";
|
|
575
|
+
const marginSwapInputAmount = marginSwap.swapTokenAmountAbs;
|
|
576
|
+
const inferredDebtAmount = leverSwap.swapTokenAmountAbs;
|
|
577
|
+
const marginInputAssetAddress = marginSwap.firstTokenAddress ?? "";
|
|
578
|
+
const marginInputAsset = marginInputAssetAddress
|
|
579
|
+
? StarknetCallParser.toAssetLabel(marginInputAssetAddress, options)
|
|
580
|
+
: "margin_token";
|
|
581
|
+
const marginSwapInputAmountHuman = StarknetCallParser.toHumanAmount(
|
|
582
|
+
marginSwapInputAmount,
|
|
583
|
+
marginInputAssetAddress || collateralAssetAddress,
|
|
584
|
+
options,
|
|
585
|
+
);
|
|
586
|
+
const inferredDebtAmountHuman = StarknetCallParser.toHumanAmount(
|
|
587
|
+
inferredDebtAmount,
|
|
588
|
+
debtAssetAddress,
|
|
589
|
+
options,
|
|
590
|
+
);
|
|
591
|
+
const addMarginHuman = StarknetCallParser.toHumanAmount(
|
|
592
|
+
addMargin,
|
|
593
|
+
collateralAssetAddress,
|
|
594
|
+
options,
|
|
595
|
+
);
|
|
596
|
+
const marginSwapLimitAmountHuman = StarknetCallParser.toHumanAmount(
|
|
597
|
+
marginSwapLimitAmount,
|
|
598
|
+
marginInputAssetAddress || collateralAssetAddress,
|
|
599
|
+
options,
|
|
600
|
+
);
|
|
601
|
+
const leverSwapLimitAmountHuman = StarknetCallParser.toHumanAmount(
|
|
602
|
+
leverSwapLimitAmount,
|
|
603
|
+
collateralAssetAddress,
|
|
604
|
+
options,
|
|
605
|
+
);
|
|
606
|
+
const marginMode =
|
|
607
|
+
marginSwap.count > 0 || addMargin === "0" ? "margin_swap" : "direct_margin";
|
|
608
|
+
|
|
609
|
+
return {
|
|
610
|
+
action,
|
|
611
|
+
poolId,
|
|
612
|
+
collateralAsset,
|
|
613
|
+
collateralAssetAddress,
|
|
614
|
+
debtAsset,
|
|
615
|
+
debtAssetAddress,
|
|
616
|
+
user,
|
|
617
|
+
addMargin,
|
|
618
|
+
marginMode,
|
|
619
|
+
marginSwapCount: marginSwap.count,
|
|
620
|
+
marginSwapLimitAmount,
|
|
621
|
+
marginSwapLimitAmountHuman,
|
|
622
|
+
marginSwapInputAmount,
|
|
623
|
+
marginSwapInputAmountHuman,
|
|
624
|
+
leverSwapCount: leverSwap.count,
|
|
625
|
+
inferredDebtAmount,
|
|
626
|
+
inferredDebtAmountHuman,
|
|
627
|
+
leverSwapLimitAmount,
|
|
628
|
+
leverSwapLimitAmountHuman,
|
|
629
|
+
summary:
|
|
630
|
+
marginMode === "margin_swap"
|
|
631
|
+
? `Modify lever increase: margin swap exact-output to collateral (first swap input ${marginSwapInputAmountHuman} ${marginInputAsset}, max input ${marginSwapLimitAmountHuman} ${marginInputAsset}); lever swap exact-input debt->collateral, debt ${inferredDebtAmountHuman} ${debtAsset} -> at least ${leverSwapLimitAmountHuman} ${collateralAsset}`
|
|
632
|
+
: `Modify lever increase: direct margin ${addMarginHuman} ${collateralAsset}; lever swap exact-input debt->collateral, debt ${inferredDebtAmountHuman} ${debtAsset} -> at least ${leverSwapLimitAmountHuman} ${collateralAsset}`,
|
|
633
|
+
calldataLen: calldata.length,
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const subMargin = calldata[cursor.value++];
|
|
638
|
+
const recipient = StarknetCallParser.toAddressHex(calldata[cursor.value++]);
|
|
639
|
+
const leverSwap = StarknetCallParser.parseSwapArray(calldata, cursor);
|
|
640
|
+
const leverSwapLimitAmount = calldata[cursor.value++] ?? "0";
|
|
641
|
+
const leverSwapInputAmount = leverSwap.swapTokenAmountAbs;
|
|
642
|
+
|
|
643
|
+
const leverSwapWeightsLen = Number(
|
|
644
|
+
StarknetCallParser.toBigInt(calldata[cursor.value++]) ?? 0n,
|
|
645
|
+
);
|
|
646
|
+
cursor.value += leverSwapWeightsLen;
|
|
647
|
+
|
|
648
|
+
const withdrawSwap = StarknetCallParser.parseSwapArray(calldata, cursor);
|
|
649
|
+
const withdrawSwapLimitAmount = calldata[cursor.value++] ?? "0";
|
|
650
|
+
const withdrawSwapInputAmount = withdrawSwap.swapTokenAmountAbs;
|
|
651
|
+
|
|
652
|
+
const withdrawSwapWeightsLen = Number(
|
|
653
|
+
StarknetCallParser.toBigInt(calldata[cursor.value++]) ?? 0n,
|
|
654
|
+
);
|
|
655
|
+
cursor.value += withdrawSwapWeightsLen;
|
|
656
|
+
|
|
657
|
+
const closePosition = calldata[cursor.value] === "1";
|
|
658
|
+
const subMarginHuman = StarknetCallParser.toHumanAmount(
|
|
659
|
+
subMargin,
|
|
660
|
+
collateralAssetAddress,
|
|
661
|
+
options,
|
|
662
|
+
);
|
|
663
|
+
const leverLimitTokenAddress = closePosition
|
|
664
|
+
? collateralAssetAddress
|
|
665
|
+
: debtAssetAddress;
|
|
666
|
+
const leverSwapLimitAmountHuman = StarknetCallParser.toHumanAmount(
|
|
667
|
+
leverSwapLimitAmount,
|
|
668
|
+
leverLimitTokenAddress,
|
|
669
|
+
options,
|
|
670
|
+
);
|
|
671
|
+
const leverSwapInputAmountHuman = StarknetCallParser.toHumanAmount(
|
|
672
|
+
leverSwapInputAmount,
|
|
673
|
+
collateralAssetAddress,
|
|
674
|
+
options,
|
|
675
|
+
);
|
|
676
|
+
const withdrawTokenAddress = withdrawSwap.inferredOutputTokenAddress ?? "";
|
|
677
|
+
const withdrawTokenLabel = withdrawTokenAddress
|
|
678
|
+
? StarknetCallParser.toAssetLabel(withdrawTokenAddress, options)
|
|
679
|
+
: "withdrawToken";
|
|
680
|
+
|
|
681
|
+
const withdrawSwapLimitAmountHuman = StarknetCallParser.toHumanAmount(
|
|
682
|
+
withdrawSwapLimitAmount,
|
|
683
|
+
withdrawTokenAddress,
|
|
684
|
+
options,
|
|
685
|
+
);
|
|
686
|
+
const withdrawSwapInputAmountHuman = StarknetCallParser.toHumanAmount(
|
|
687
|
+
withdrawSwapInputAmount,
|
|
688
|
+
collateralAssetAddress,
|
|
689
|
+
options,
|
|
690
|
+
);
|
|
691
|
+
return {
|
|
692
|
+
action,
|
|
693
|
+
poolId,
|
|
694
|
+
collateralAsset,
|
|
695
|
+
collateralAssetAddress,
|
|
696
|
+
debtAsset,
|
|
697
|
+
debtAssetAddress,
|
|
698
|
+
user,
|
|
699
|
+
subMargin,
|
|
700
|
+
recipient,
|
|
701
|
+
leverSwapCount: leverSwap.count,
|
|
702
|
+
leverSwapInputAmount,
|
|
703
|
+
leverSwapInputAmountHuman,
|
|
704
|
+
leverSwapLimitAmount,
|
|
705
|
+
leverSwapLimitAmountHuman,
|
|
706
|
+
withdrawSwapCount: withdrawSwap.count,
|
|
707
|
+
withdrawSwapInputAmount,
|
|
708
|
+
withdrawSwapInputAmountHuman,
|
|
709
|
+
withdrawSwapLimitAmount,
|
|
710
|
+
withdrawSwapLimitAmountHuman,
|
|
711
|
+
closePosition,
|
|
712
|
+
summary: closePosition
|
|
713
|
+
? `Modify lever decrease: close position, repay all outstanding ${debtAsset} debt using max ${leverSwapLimitAmountHuman} ${collateralAsset} (first swap input ${leverSwapInputAmountHuman} ${collateralAsset}), remaining collateral is freed${withdrawSwap.count > 0 ? ` (withdraw swap active: input ${withdrawSwapInputAmountHuman} ${collateralAsset}, min ${withdrawSwapLimitAmountHuman} ${withdrawTokenLabel})` : ""}`
|
|
714
|
+
: `Modify lever decrease: repay at least ${leverSwapLimitAmountHuman} ${debtAsset} debt via collateral swap (first swap input ${leverSwapInputAmountHuman} ${collateralAsset}), free ${subMarginHuman} ${collateralAsset} collateral${withdrawSwap.count > 0 ? ` (withdraw swap active: input ${withdrawSwapInputAmountHuman} ${collateralAsset}, min ${withdrawSwapLimitAmountHuman} ${withdrawTokenLabel})` : ""}`,
|
|
715
|
+
calldataLen: calldata.length,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
private static decodeMultiRouteSwap(
|
|
720
|
+
calldata: string[],
|
|
721
|
+
options: StarknetCallParserOptions,
|
|
722
|
+
) {
|
|
723
|
+
if (calldata.length < 8) {
|
|
724
|
+
throw new Error(`multi_route_swap calldata too short: ${calldata.length}`);
|
|
725
|
+
}
|
|
726
|
+
const fromAddress = StarknetCallParser.toAddressHex(calldata[0]);
|
|
727
|
+
const toAddress = StarknetCallParser.toAddressHex(calldata[3]);
|
|
728
|
+
const fromAmount = StarknetCallParser.u256FromParts(calldata[1], calldata[2]);
|
|
729
|
+
const minTokenOutput = StarknetCallParser.u256FromParts(
|
|
730
|
+
calldata[6],
|
|
731
|
+
calldata[7],
|
|
732
|
+
);
|
|
733
|
+
const fromAmountHuman = StarknetCallParser.toHumanAmountIfDecimals(
|
|
734
|
+
fromAmount,
|
|
735
|
+
fromAddress,
|
|
736
|
+
options,
|
|
737
|
+
);
|
|
738
|
+
const minTokenOutputHuman = StarknetCallParser.toHumanAmountIfDecimals(
|
|
739
|
+
minTokenOutput,
|
|
740
|
+
toAddress,
|
|
741
|
+
options,
|
|
742
|
+
);
|
|
743
|
+
|
|
744
|
+
return {
|
|
745
|
+
fromTokenSymbol: options.tokenSymbols?.[fromAddress] ?? fromAddress,
|
|
746
|
+
toTokenSymbol: options.tokenSymbols?.[toAddress] ?? toAddress,
|
|
747
|
+
fromAmount,
|
|
748
|
+
...(fromAmountHuman !== undefined ? { fromAmountHuman } : {}),
|
|
749
|
+
minTokenOutput,
|
|
750
|
+
...(minTokenOutputHuman !== undefined ? { minTokenOutputHuman } : {}),
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private static decodeManageVaultWithMerkleVerification(
|
|
755
|
+
calldata: string[],
|
|
756
|
+
options: StarknetCallParserOptions,
|
|
757
|
+
) {
|
|
758
|
+
let cursor = 0;
|
|
759
|
+
|
|
760
|
+
const readLen = (): number => {
|
|
761
|
+
const length = Number(StarknetCallParser.toBigInt(calldata[cursor]) ?? 0n);
|
|
762
|
+
cursor += 1;
|
|
763
|
+
return length;
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
const readFlatSpan = (): string[] => {
|
|
767
|
+
const length = readLen();
|
|
768
|
+
const values = calldata.slice(cursor, cursor + length);
|
|
769
|
+
cursor += length;
|
|
770
|
+
return values;
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
const readNestedSpan = (): string[][] => {
|
|
774
|
+
const outerLen = readLen();
|
|
775
|
+
const nested: string[][] = [];
|
|
776
|
+
for (let i = 0; i < outerLen; i += 1) {
|
|
777
|
+
nested.push(readFlatSpan());
|
|
778
|
+
}
|
|
779
|
+
return nested;
|
|
780
|
+
};
|
|
781
|
+
|
|
782
|
+
const proofs = readNestedSpan();
|
|
783
|
+
const decoderAndSanitizers = readFlatSpan();
|
|
784
|
+
const targets = readFlatSpan();
|
|
785
|
+
const selectors = readFlatSpan();
|
|
786
|
+
const calldatas = readNestedSpan();
|
|
787
|
+
|
|
788
|
+
const internalCalls = selectors.map((selector, index) => {
|
|
789
|
+
const selectorHex = StarknetCallParser.toSelectorHex(selector);
|
|
790
|
+
const method =
|
|
791
|
+
StarknetCallParser.METHOD_BY_SELECTOR.get(selectorHex) ??
|
|
792
|
+
`unknown_selector_${selectorHex}`;
|
|
793
|
+
|
|
794
|
+
const decoded = StarknetCallParser.parseRaw({
|
|
795
|
+
method,
|
|
796
|
+
target: StarknetCallParser.toAddressHex(targets[index] ?? "0x0"),
|
|
797
|
+
selector: selectorHex,
|
|
798
|
+
sanitizer: StarknetCallParser.toAddressHex(
|
|
799
|
+
decoderAndSanitizers[index] ?? "0x0",
|
|
800
|
+
),
|
|
801
|
+
calldata: calldatas[index] ?? [],
|
|
802
|
+
options,
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
return {
|
|
806
|
+
index,
|
|
807
|
+
...decoded,
|
|
808
|
+
};
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
return {
|
|
812
|
+
proofsCount: proofs.length,
|
|
813
|
+
internalCallsCount: internalCalls.length,
|
|
814
|
+
internalCalls,
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
private static toCallSummary(
|
|
819
|
+
parsed: ParsedStarknetCall,
|
|
820
|
+
index: number,
|
|
821
|
+
): string {
|
|
822
|
+
const prefix = `#${index} ${parsed.method}@${parsed.target}`;
|
|
823
|
+
const args = parsed.decodedArgs as any;
|
|
824
|
+
|
|
825
|
+
if (parsed.method === "manage_vault_with_merkle_verification") {
|
|
826
|
+
const internalCalls = Array.isArray(args?.internalCalls)
|
|
827
|
+
? args.internalCalls
|
|
828
|
+
: [];
|
|
829
|
+
const internalSummary = internalCalls
|
|
830
|
+
.map(
|
|
831
|
+
(call: any, i: number) =>
|
|
832
|
+
`${i}:${call.method}(${StarknetCallParser.pickKeyFields(call.decodedArgs)})`,
|
|
833
|
+
)
|
|
834
|
+
.join(", ");
|
|
835
|
+
return `${prefix} internalCalls=${args?.internalCallsCount ?? 0} [${internalSummary}]`;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return `${prefix} ${StarknetCallParser.pickKeyFields(args)}`;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
private static pickKeyFields(decodedArgs: any): string {
|
|
842
|
+
if (!decodedArgs || typeof decodedArgs !== "object") return "";
|
|
843
|
+
if (typeof decodedArgs.summary === "string" && decodedArgs.summary.length > 0) {
|
|
844
|
+
return decodedArgs.summary;
|
|
845
|
+
}
|
|
846
|
+
const keys = [
|
|
847
|
+
"fromTokenSymbol",
|
|
848
|
+
"toTokenSymbol",
|
|
849
|
+
"fromAmount",
|
|
850
|
+
"fromAmountHuman",
|
|
851
|
+
"minTokenOutput",
|
|
852
|
+
"minTokenOutputHuman",
|
|
853
|
+
"spender",
|
|
854
|
+
"amount",
|
|
855
|
+
"inferredDebtAmountHuman",
|
|
856
|
+
"leverSwapLimitAmountHuman",
|
|
857
|
+
"withdrawSwapLimitAmountHuman",
|
|
858
|
+
"version",
|
|
859
|
+
"delegatee",
|
|
860
|
+
"delegation",
|
|
861
|
+
"action",
|
|
862
|
+
"poolId",
|
|
863
|
+
"calldataLen",
|
|
864
|
+
];
|
|
865
|
+
|
|
866
|
+
const parts: string[] = [];
|
|
867
|
+
if (decodedArgs.poolId !== undefined) {
|
|
868
|
+
const poolName = decodedArgs.poolName ?? decodedArgs.poolLabel;
|
|
869
|
+
parts.push(
|
|
870
|
+
poolName ? `poolId=${decodedArgs.poolId}(${poolName})` : `poolId=${decodedArgs.poolId}`,
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
for (const key of keys) {
|
|
874
|
+
if (key === "poolId") continue;
|
|
875
|
+
if (decodedArgs[key] !== undefined) {
|
|
876
|
+
parts.push(`${key}=${decodedArgs[key]}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (parts.length === 0) {
|
|
880
|
+
return `fields=${Object.keys(decodedArgs).length}`;
|
|
881
|
+
}
|
|
882
|
+
return parts.join(", ");
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
private static u256FromParts(low: string, high: string): string {
|
|
886
|
+
try {
|
|
887
|
+
return uint256
|
|
888
|
+
.uint256ToBN({ low, high } as unknown as { low: string; high: string })
|
|
889
|
+
.toString();
|
|
890
|
+
} catch {
|
|
891
|
+
return "0";
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
private static toSelectorHex(selector: string): string {
|
|
896
|
+
try {
|
|
897
|
+
if (selector.startsWith("0x") || selector.startsWith("0X")) {
|
|
898
|
+
return selector.toLowerCase();
|
|
899
|
+
}
|
|
900
|
+
return `0x${BigInt(selector).toString(16)}`.toLowerCase();
|
|
901
|
+
} catch {
|
|
902
|
+
return selector.toLowerCase();
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
private static parseSwapArray(
|
|
907
|
+
calldata: string[],
|
|
908
|
+
cursor: { value: number },
|
|
909
|
+
): {
|
|
910
|
+
count: number;
|
|
911
|
+
swapTokenAmountAbs: string;
|
|
912
|
+
firstTokenAddress?: string;
|
|
913
|
+
inferredOutputTokenAddress?: string;
|
|
914
|
+
} {
|
|
915
|
+
const count = Number(
|
|
916
|
+
StarknetCallParser.toBigInt(calldata[cursor.value++]) ?? 0n,
|
|
917
|
+
);
|
|
918
|
+
let swapTokenAmountAbs = "0";
|
|
919
|
+
let firstTokenAddress: string | undefined;
|
|
920
|
+
let inferredOutputTokenAddress: string | undefined;
|
|
921
|
+
|
|
922
|
+
for (let i = 0; i < count; i += 1) {
|
|
923
|
+
const routeLen = Number(
|
|
924
|
+
StarknetCallParser.toBigInt(calldata[cursor.value++]) ?? 0n,
|
|
925
|
+
);
|
|
926
|
+
const routePairs: Array<{ token0: string; token1: string }> = [];
|
|
927
|
+
// route node = pool_key(5) + sqrt_ratio_limit(u256=2) + skip_ahead(1)
|
|
928
|
+
for (let j = 0; j < routeLen; j++) {
|
|
929
|
+
const token0 = StarknetCallParser.toAddressHex(
|
|
930
|
+
calldata[cursor.value++] ?? "0x0",
|
|
931
|
+
);
|
|
932
|
+
const token1 = StarknetCallParser.toAddressHex(
|
|
933
|
+
calldata[cursor.value++] ?? "0x0",
|
|
934
|
+
);
|
|
935
|
+
cursor.value += 6;
|
|
936
|
+
routePairs.push({ token0, token1 });
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// token_amount: token + i129(mag + sign)
|
|
940
|
+
const tokenAddress = StarknetCallParser.toAddressHex(
|
|
941
|
+
calldata[cursor.value++] ?? "0x0",
|
|
942
|
+
);
|
|
943
|
+
let currentTokenForRoute: string | undefined = tokenAddress;
|
|
944
|
+
for (const pair of routePairs) {
|
|
945
|
+
if (currentTokenForRoute === pair.token0) {
|
|
946
|
+
currentTokenForRoute = pair.token1;
|
|
947
|
+
} else if (currentTokenForRoute === pair.token1) {
|
|
948
|
+
currentTokenForRoute = pair.token0;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
const mag = StarknetCallParser.toBigInt(calldata[cursor.value++]) ?? 0n;
|
|
952
|
+
const sign = calldata[cursor.value++] === "1" ? -1n : 1n;
|
|
953
|
+
if (i === 0) {
|
|
954
|
+
firstTokenAddress = tokenAddress;
|
|
955
|
+
inferredOutputTokenAddress = currentTokenForRoute;
|
|
956
|
+
swapTokenAmountAbs = (mag * sign < 0n ? -mag * sign : mag * sign).toString();
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
return {
|
|
961
|
+
count,
|
|
962
|
+
swapTokenAmountAbs,
|
|
963
|
+
firstTokenAddress,
|
|
964
|
+
inferredOutputTokenAddress,
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
private static toPoolIdKey(poolId: string | bigint): string {
|
|
969
|
+
try {
|
|
970
|
+
const asBigInt = typeof poolId === "bigint" ? poolId : BigInt(poolId);
|
|
971
|
+
return `0x${asBigInt.toString(16)}`.toLowerCase();
|
|
972
|
+
} catch {
|
|
973
|
+
return String(poolId).toLowerCase();
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
private static toAssetLabel(
|
|
978
|
+
address: string,
|
|
979
|
+
options: StarknetCallParserOptions,
|
|
980
|
+
): string {
|
|
981
|
+
return options.tokenSymbols?.[address] ?? address;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
private static toHumanAmount(
|
|
985
|
+
rawWei: string,
|
|
986
|
+
tokenAddress: string,
|
|
987
|
+
options: StarknetCallParserOptions,
|
|
988
|
+
): string {
|
|
989
|
+
const decimals = options.tokenDecimals?.[tokenAddress];
|
|
990
|
+
if (decimals === undefined) return rawWei;
|
|
991
|
+
try {
|
|
992
|
+
return Web3Number.fromWei(rawWei, decimals).toNumber().toString();
|
|
993
|
+
} catch {
|
|
994
|
+
return rawWei;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
private static toHumanAmountIfDecimals(
|
|
999
|
+
rawWei: string,
|
|
1000
|
+
tokenAddress: string,
|
|
1001
|
+
options: StarknetCallParserOptions,
|
|
1002
|
+
): string | undefined {
|
|
1003
|
+
const decimals = options.tokenDecimals?.[tokenAddress];
|
|
1004
|
+
if (decimals === undefined) return undefined;
|
|
1005
|
+
try {
|
|
1006
|
+
return Web3Number.fromWei(rawWei, decimals).toNumber().toString();
|
|
1007
|
+
} catch {
|
|
1008
|
+
return undefined;
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
private static normalizeHex(value: string): string {
|
|
1013
|
+
return value.toLowerCase();
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
private static toAddressHex(value: string | bigint): string {
|
|
1017
|
+
try {
|
|
1018
|
+
const input = typeof value === "bigint" ? `0x${value.toString(16)}` : value;
|
|
1019
|
+
return `0x${ContractAddr.from(input).toBigInt().toString(16)}`.toLowerCase();
|
|
1020
|
+
} catch {
|
|
1021
|
+
if (typeof value === "bigint") {
|
|
1022
|
+
return `0x${value.toString(16)}`.toLowerCase();
|
|
1023
|
+
}
|
|
1024
|
+
return String(value).toLowerCase();
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
private static addressToString(value: unknown): string {
|
|
1029
|
+
if (typeof value === "string") return value;
|
|
1030
|
+
if (typeof value === "bigint") return `0x${value.toString(16)}`;
|
|
1031
|
+
if (value && typeof value === "object" && "address" in (value as any)) {
|
|
1032
|
+
return String((value as any).address);
|
|
1033
|
+
}
|
|
1034
|
+
return String(value ?? "");
|
|
1035
|
+
}
|
|
1036
|
+
}
|