solforge 0.2.7 → 0.2.9
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/package.json +1 -1
- package/scripts/decode-b58.ts +10 -0
- package/server/lib/instruction-parser.ts +305 -219
- package/server/lib/parsers/spl-associated-token-account.ts +50 -0
- package/server/lib/parsers/spl-token.ts +315 -0
- package/server/methods/account/request-airdrop.ts +121 -105
- package/server/methods/admin/mint-to.ts +29 -14
- package/server/methods/transaction/get-transaction.ts +390 -326
- package/server/methods/transaction/inner-instructions.test.ts +91 -50
- package/server/methods/transaction/send-transaction.ts +281 -236
- package/server/rpc-server.ts +84 -74
- package/server/types.ts +56 -56
- package/src/db/schema/transactions.ts +29 -29
- package/src/db/schema/tx-account-states.ts +16 -14
- package/src/db/tx-store.ts +97 -99
- package/src/migrations-bundled.ts +4 -4
|
@@ -25,51 +25,51 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
25
25
|
: Array.isArray(msg.accountKeys)
|
|
26
26
|
? msg.accountKeys
|
|
27
27
|
: [];
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
28
|
+
const staticKeys = rawKeys
|
|
29
|
+
.map((k) => {
|
|
30
|
+
try {
|
|
31
|
+
return typeof k === "string" ? new PublicKey(k) : (k as PublicKey);
|
|
32
|
+
} catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
.filter(Boolean) as PublicKey[];
|
|
37
|
+
// Pre snapshots and balances
|
|
38
|
+
const preBalances = staticKeys.map((pk) => {
|
|
39
|
+
try {
|
|
40
|
+
return Number(context.svm.getBalance(pk));
|
|
41
|
+
} catch {
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
const preAccountStates = staticKeys.map((pk) => {
|
|
46
|
+
try {
|
|
47
|
+
const addr = pk.toBase58();
|
|
48
|
+
const acc = context.svm.getAccount(pk);
|
|
49
|
+
if (!acc) return { address: addr, pre: null } as const;
|
|
50
|
+
return {
|
|
51
|
+
address: addr,
|
|
52
|
+
pre: {
|
|
53
|
+
lamports: Number(acc.lamports || 0n),
|
|
54
|
+
ownerProgram: new PublicKey(acc.owner).toBase58(),
|
|
55
|
+
executable: !!acc.executable,
|
|
56
|
+
rentEpoch: Number(acc.rentEpoch || 0),
|
|
57
|
+
dataLen: acc.data?.length ?? 0,
|
|
58
|
+
dataBase64: undefined,
|
|
59
|
+
lastSlot: Number(context.slot),
|
|
60
|
+
},
|
|
61
|
+
} as const;
|
|
62
|
+
} catch {
|
|
63
|
+
return { address: pk.toBase58(), pre: null } as const;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
try {
|
|
67
|
+
if (process.env.DEBUG_TX_CAPTURE === "1") {
|
|
68
|
+
console.debug(
|
|
69
|
+
`[tx-capture] pre snapshots: keys=${staticKeys.length} captured=${preAccountStates.length}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
} catch {}
|
|
73
73
|
|
|
74
74
|
// Collect SPL token accounts from instructions for pre/post token balance snapshots
|
|
75
75
|
const msgAny = msg as unknown as {
|
|
@@ -86,21 +86,44 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
86
86
|
TOKEN_2022_PROGRAM_ID.toBase58(),
|
|
87
87
|
]);
|
|
88
88
|
const tokenAccountSet = new Set<string>();
|
|
89
|
+
// 1) Collect from compiled ixs (best-effort)
|
|
89
90
|
for (const ci of compiled) {
|
|
90
91
|
try {
|
|
91
|
-
const
|
|
92
|
+
const rec = ci as Record<string, unknown>;
|
|
93
|
+
const pidIdx =
|
|
94
|
+
typeof rec.programIdIndex === "number"
|
|
95
|
+
? rec.programIdIndex
|
|
96
|
+
: undefined;
|
|
97
|
+
const pid = pidIdx != null ? staticKeys[pidIdx]?.toBase58() : undefined;
|
|
92
98
|
if (!pid || !tokenProgramIds.has(pid)) continue;
|
|
93
|
-
const
|
|
94
|
-
?
|
|
95
|
-
: Array.isArray(
|
|
96
|
-
?
|
|
99
|
+
const accSrc = Array.isArray(rec.accountKeyIndexes)
|
|
100
|
+
? (rec.accountKeyIndexes as unknown[])
|
|
101
|
+
: Array.isArray(rec.accounts)
|
|
102
|
+
? (rec.accounts as unknown[])
|
|
97
103
|
: [];
|
|
104
|
+
const accIdxs: number[] = accSrc.filter(
|
|
105
|
+
(v): v is number => typeof v === "number",
|
|
106
|
+
);
|
|
98
107
|
for (const ix of accIdxs) {
|
|
99
108
|
const addr = staticKeys[ix]?.toBase58();
|
|
100
109
|
if (addr) tokenAccountSet.add(addr);
|
|
101
110
|
}
|
|
102
111
|
} catch {}
|
|
103
112
|
}
|
|
113
|
+
// 2) Also collect from all static keys that are SPL token accounts pre-send
|
|
114
|
+
for (const pk of staticKeys) {
|
|
115
|
+
try {
|
|
116
|
+
const acc = context.svm.getAccount(pk);
|
|
117
|
+
if (!acc) continue;
|
|
118
|
+
const ownerStr = new PublicKey(acc.owner).toBase58();
|
|
119
|
+
if (
|
|
120
|
+
tokenProgramIds.has(ownerStr) &&
|
|
121
|
+
(acc.data?.length ?? 0) >= ACCOUNT_SIZE
|
|
122
|
+
) {
|
|
123
|
+
tokenAccountSet.add(pk.toBase58());
|
|
124
|
+
}
|
|
125
|
+
} catch {}
|
|
126
|
+
}
|
|
104
127
|
// Pre token balances
|
|
105
128
|
const preTokenBalances: unknown[] = [];
|
|
106
129
|
const ataToInfo = new Map<
|
|
@@ -159,7 +182,7 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
159
182
|
} catch {}
|
|
160
183
|
}
|
|
161
184
|
|
|
162
|
-
|
|
185
|
+
const result = context.svm.sendTransaction(tx);
|
|
163
186
|
|
|
164
187
|
try {
|
|
165
188
|
const rawErr = (result as { err?: unknown }).err;
|
|
@@ -175,47 +198,47 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
175
198
|
}
|
|
176
199
|
} catch {}
|
|
177
200
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// Post token balances
|
|
201
|
+
const signature = tx.signatures[0]
|
|
202
|
+
? context.encodeBase58(tx.signatures[0])
|
|
203
|
+
: context.encodeBase58(new Uint8Array(64).fill(0));
|
|
204
|
+
context.notifySignature(signature);
|
|
205
|
+
// Snapshot post balances and capture logs for rich view
|
|
206
|
+
const postBalances = staticKeys.map((pk) => {
|
|
207
|
+
try {
|
|
208
|
+
return Number(context.svm.getBalance(pk));
|
|
209
|
+
} catch {
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const postAccountStates = staticKeys.map((pk) => {
|
|
214
|
+
try {
|
|
215
|
+
const addr = pk.toBase58();
|
|
216
|
+
const acc = context.svm.getAccount(pk);
|
|
217
|
+
if (!acc) return { address: addr, post: null } as const;
|
|
218
|
+
return {
|
|
219
|
+
address: addr,
|
|
220
|
+
post: {
|
|
221
|
+
lamports: Number(acc.lamports || 0n),
|
|
222
|
+
ownerProgram: new PublicKey(acc.owner).toBase58(),
|
|
223
|
+
executable: !!acc.executable,
|
|
224
|
+
rentEpoch: Number(acc.rentEpoch || 0),
|
|
225
|
+
dataLen: acc.data?.length ?? 0,
|
|
226
|
+
dataBase64: undefined,
|
|
227
|
+
lastSlot: Number(context.slot),
|
|
228
|
+
},
|
|
229
|
+
} as const;
|
|
230
|
+
} catch {
|
|
231
|
+
return { address: pk.toBase58(), post: null } as const;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
try {
|
|
235
|
+
if (process.env.DEBUG_TX_CAPTURE === "1") {
|
|
236
|
+
console.debug(
|
|
237
|
+
`[tx-capture] post snapshots: keys=${staticKeys.length} captured=${postAccountStates.length}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
} catch {}
|
|
241
|
+
// Post token balances (scan token accounts among static keys)
|
|
219
242
|
const postTokenBalances: unknown[] = [];
|
|
220
243
|
for (const addr of tokenAccountSet) {
|
|
221
244
|
try {
|
|
@@ -266,150 +289,172 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
266
289
|
}
|
|
267
290
|
} catch {}
|
|
268
291
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
292
|
+
let logs: string[] = [];
|
|
293
|
+
let innerInstructions: unknown[] = [];
|
|
294
|
+
let computeUnits: number | null = null;
|
|
295
|
+
let returnData: { programId: string; dataBase64: string } | null = null;
|
|
296
|
+
try {
|
|
297
|
+
const DBG = process.env.DEBUG_TX_CAPTURE === "1";
|
|
298
|
+
// biome-ignore lint/suspicious/noExplicitAny: Result shape depends on litesvm version; guarded access
|
|
299
|
+
const r: any = result as any;
|
|
300
|
+
// Logs can be on TransactionMetadata or in meta() for failures
|
|
301
|
+
try {
|
|
302
|
+
if (typeof r?.logs === "function") logs = r.logs();
|
|
303
|
+
} catch {}
|
|
304
|
+
let metaObj: unknown | undefined;
|
|
305
|
+
// Success shape: methods on result
|
|
306
|
+
if (
|
|
307
|
+
typeof r?.innerInstructions === "function" ||
|
|
308
|
+
typeof r?.computeUnitsConsumed === "function" ||
|
|
309
|
+
typeof r?.returnData === "function"
|
|
310
|
+
) {
|
|
311
|
+
metaObj = r;
|
|
312
|
+
}
|
|
313
|
+
// Failed shape: meta() returns TransactionMetadata
|
|
314
|
+
if (!metaObj && typeof r?.meta === "function") {
|
|
315
|
+
try {
|
|
316
|
+
metaObj = r.meta();
|
|
317
|
+
if (!logs.length && typeof metaObj?.logs === "function") {
|
|
318
|
+
logs = metaObj.logs();
|
|
319
|
+
}
|
|
320
|
+
} catch (e) {
|
|
321
|
+
if (DBG)
|
|
322
|
+
console.debug("[tx-capture] meta() threw while extracting:", e);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Extract richer metadata from whichever object exposes it
|
|
326
|
+
if (metaObj) {
|
|
327
|
+
try {
|
|
328
|
+
const inner = (
|
|
329
|
+
metaObj as { innerInstructions?: () => unknown } | undefined
|
|
330
|
+
)?.innerInstructions?.();
|
|
331
|
+
if (Array.isArray(inner)) {
|
|
332
|
+
innerInstructions = inner.map((group, index: number) => {
|
|
333
|
+
const instructions = Array.isArray(group)
|
|
334
|
+
? (group as unknown[])
|
|
335
|
+
.map((ii) => {
|
|
336
|
+
try {
|
|
337
|
+
const inst = (
|
|
338
|
+
ii as { instruction?: () => unknown }
|
|
339
|
+
).instruction?.();
|
|
340
|
+
const accSrc = (
|
|
341
|
+
inst as { accounts?: () => unknown } | undefined
|
|
342
|
+
)?.accounts?.();
|
|
343
|
+
const accIdxs: number[] = Array.isArray(accSrc)
|
|
344
|
+
? (accSrc as unknown[]).filter(
|
|
345
|
+
(v): v is number => typeof v === "number",
|
|
346
|
+
)
|
|
347
|
+
: [];
|
|
348
|
+
const dataVal = (
|
|
349
|
+
inst as { data?: () => unknown } | undefined
|
|
350
|
+
)?.data?.();
|
|
351
|
+
const dataBytes: Uint8Array =
|
|
352
|
+
dataVal instanceof Uint8Array
|
|
353
|
+
? dataVal
|
|
354
|
+
: new Uint8Array();
|
|
355
|
+
return {
|
|
356
|
+
programIdIndex: Number(
|
|
357
|
+
(
|
|
358
|
+
inst as
|
|
359
|
+
| { programIdIndex?: () => unknown }
|
|
360
|
+
| undefined
|
|
361
|
+
)?.programIdIndex?.() ?? 0,
|
|
362
|
+
),
|
|
363
|
+
accounts: accIdxs,
|
|
364
|
+
data: context.encodeBase58(dataBytes),
|
|
365
|
+
stackHeight: Number(
|
|
366
|
+
(
|
|
367
|
+
ii as { stackHeight?: () => unknown }
|
|
368
|
+
).stackHeight?.() ?? 0,
|
|
369
|
+
),
|
|
370
|
+
};
|
|
371
|
+
} catch {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
.filter(Boolean)
|
|
376
|
+
: [];
|
|
377
|
+
return { index, instructions };
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
} catch (e) {
|
|
381
|
+
if (DBG)
|
|
382
|
+
console.debug(
|
|
383
|
+
"[tx-capture] innerInstructions extraction failed:",
|
|
384
|
+
e,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
const cu = (
|
|
389
|
+
metaObj as { computeUnitsConsumed?: () => unknown } | undefined
|
|
390
|
+
)?.computeUnitsConsumed?.();
|
|
391
|
+
if (typeof cu === "bigint") computeUnits = Number(cu);
|
|
392
|
+
} catch (e) {
|
|
393
|
+
if (DBG)
|
|
394
|
+
console.debug(
|
|
395
|
+
"[tx-capture] computeUnitsConsumed extraction failed:",
|
|
396
|
+
e,
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
const rd = (
|
|
401
|
+
metaObj as { returnData?: () => unknown } | undefined
|
|
402
|
+
)?.returnData?.();
|
|
403
|
+
if (rd) {
|
|
404
|
+
const pid = new PublicKey(rd.programId()).toBase58();
|
|
405
|
+
const dataB64 = Buffer.from(rd.data()).toString("base64");
|
|
406
|
+
returnData = { programId: pid, dataBase64: dataB64 };
|
|
407
|
+
}
|
|
408
|
+
} catch (e) {
|
|
409
|
+
if (DBG)
|
|
410
|
+
console.debug("[tx-capture] returnData extraction failed:", e);
|
|
411
|
+
}
|
|
412
|
+
} else if (DBG) {
|
|
413
|
+
console.debug("[tx-capture] no metadata object found on result shape");
|
|
414
|
+
}
|
|
415
|
+
} catch {}
|
|
416
|
+
try {
|
|
417
|
+
if (process.env.DEBUG_TX_CAPTURE === "1") {
|
|
418
|
+
console.debug(
|
|
419
|
+
`[tx-capture] sendTransaction meta: logs=${logs.length} innerGroups=${Array.isArray(innerInstructions) ? innerInstructions.length : 0} computeUnits=${computeUnits} returnData=${returnData ? "yes" : "no"}`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
} catch {}
|
|
423
|
+
context.recordTransaction(signature, tx, {
|
|
424
|
+
logs,
|
|
425
|
+
fee: 5000,
|
|
426
|
+
blockTime: Math.floor(Date.now() / 1000),
|
|
427
|
+
preBalances,
|
|
428
|
+
postBalances,
|
|
429
|
+
preTokenBalances,
|
|
430
|
+
postTokenBalances,
|
|
431
|
+
innerInstructions,
|
|
432
|
+
computeUnits,
|
|
433
|
+
returnData,
|
|
434
|
+
accountStates: (() => {
|
|
435
|
+
try {
|
|
436
|
+
const byAddr = new Map<string, { pre?: unknown; post?: unknown }>();
|
|
437
|
+
for (const s of preAccountStates)
|
|
438
|
+
byAddr.set(s.address, { pre: s.pre || null });
|
|
439
|
+
for (const s of postAccountStates) {
|
|
440
|
+
const e = byAddr.get(s.address) || {};
|
|
441
|
+
e.post = s.post || null;
|
|
442
|
+
byAddr.set(s.address, e);
|
|
443
|
+
}
|
|
444
|
+
return Array.from(byAddr.entries()).map(([address, v]) => ({
|
|
445
|
+
address,
|
|
446
|
+
pre: v.pre || null,
|
|
447
|
+
post: v.post || null,
|
|
448
|
+
}));
|
|
449
|
+
} catch {
|
|
450
|
+
return [] as Array<{
|
|
451
|
+
address: string;
|
|
452
|
+
pre?: unknown;
|
|
453
|
+
post?: unknown;
|
|
454
|
+
}>;
|
|
455
|
+
}
|
|
456
|
+
})(),
|
|
457
|
+
});
|
|
413
458
|
|
|
414
459
|
return context.createSuccessResponse(id, signature);
|
|
415
460
|
} catch (error: unknown) {
|