solforge 0.2.6 → 0.2.8
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 +6 -0
- package/server/lib/instruction-parser.ts +264 -0
- package/server/lib/parsers/spl-associated-token-account.ts +44 -0
- package/server/lib/parsers/spl-token.ts +172 -0
- package/server/methods/account/request-airdrop.ts +107 -84
- package/server/methods/admin/mint-to.ts +11 -38
- package/server/methods/transaction/get-transaction.ts +328 -267
- package/server/methods/transaction/inner-instructions.test.ts +63 -0
- package/server/methods/transaction/send-transaction.ts +248 -57
- package/server/rpc-server.ts +104 -67
- package/server/types.ts +56 -24
- package/src/db/schema/index.ts +1 -0
- package/src/db/schema/transactions.ts +29 -22
- package/src/db/schema/tx-account-states.ts +21 -0
- package/src/db/tx-store.ts +103 -70
- package/src/migrations-bundled.ts +8 -2
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { test, expect } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
Keypair,
|
|
4
|
+
LAMPORTS_PER_SOL,
|
|
5
|
+
TransactionMessage,
|
|
6
|
+
VersionedTransaction,
|
|
7
|
+
PublicKey,
|
|
8
|
+
} from "@solana/web3.js";
|
|
9
|
+
import {
|
|
10
|
+
getAssociatedTokenAddress,
|
|
11
|
+
createAssociatedTokenAccountInstruction,
|
|
12
|
+
} from "@solana/spl-token";
|
|
13
|
+
import { LiteSVMRpcServer } from "../../rpc-server";
|
|
14
|
+
|
|
15
|
+
type RpcResp<T = any> = { jsonrpc: "2.0"; id: number; result?: T; error?: { code: number; message: string; data?: unknown } };
|
|
16
|
+
|
|
17
|
+
function jsonReq(method: string, params?: unknown) {
|
|
18
|
+
return { jsonrpc: "2.0", id: Math.floor(Math.random() * 1e9), method, params };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test("captures inner instructions for ATA create (CPI)", async () => {
|
|
22
|
+
const server = new LiteSVMRpcServer();
|
|
23
|
+
async function call<T = any>(method: string, params?: unknown): Promise<RpcResp<T>> {
|
|
24
|
+
return (await server.handleRequest(jsonReq(method, params))) as RpcResp<T>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Payer + airdrop
|
|
28
|
+
const payer = Keypair.generate();
|
|
29
|
+
const recip = Keypair.generate();
|
|
30
|
+
await call("requestAirdrop", [payer.publicKey.toBase58(), 1 * LAMPORTS_PER_SOL]);
|
|
31
|
+
|
|
32
|
+
// Create a test mint via admin helper
|
|
33
|
+
const mintResp = await call<{ mint: string }>("solforgeCreateMint", [null, 6, null]);
|
|
34
|
+
expect(mintResp.error).toBeUndefined();
|
|
35
|
+
const mint = new PublicKey(mintResp.result!.mint);
|
|
36
|
+
|
|
37
|
+
// Build 1 ATA instruction that triggers CPIs into system + token
|
|
38
|
+
const bh = await call<{ value: { blockhash: string } }>("getLatestBlockhash", []);
|
|
39
|
+
const ata = await getAssociatedTokenAddress(mint, recip.publicKey, false);
|
|
40
|
+
const ix = createAssociatedTokenAccountInstruction(payer.publicKey, ata, recip.publicKey, mint);
|
|
41
|
+
const msg = new TransactionMessage({ payerKey: payer.publicKey, recentBlockhash: bh.result!.value.blockhash, instructions: [ix] }).compileToLegacyMessage();
|
|
42
|
+
const tx = new VersionedTransaction(msg);
|
|
43
|
+
tx.sign([payer]);
|
|
44
|
+
|
|
45
|
+
const sigResp = await call<string>("sendTransaction", [Buffer.from(tx.serialize()).toString("base64")]);
|
|
46
|
+
expect(sigResp.error).toBeUndefined();
|
|
47
|
+
const sig = sigResp.result!;
|
|
48
|
+
|
|
49
|
+
const txResp = await call<any>("getTransaction", [sig, { encoding: "json" }]);
|
|
50
|
+
expect(txResp.error).toBeUndefined();
|
|
51
|
+
const tr = txResp.result!;
|
|
52
|
+
|
|
53
|
+
// At least one top-level instruction
|
|
54
|
+
expect(Array.isArray(tr.transaction.message.instructions)).toBe(true);
|
|
55
|
+
expect(tr.transaction.message.instructions.length).toBe(1);
|
|
56
|
+
|
|
57
|
+
// Check inner instructions captured or (worst case) logs exist
|
|
58
|
+
const ii = tr.meta.innerInstructions;
|
|
59
|
+
const logs = tr.meta.logMessages || [];
|
|
60
|
+
expect(Array.isArray(ii)).toBe(true);
|
|
61
|
+
// At minimum, either we have structured inner ixs, or logs were captured
|
|
62
|
+
expect(ii.length > 0 || logs.length > 0).toBe(true);
|
|
63
|
+
});
|
|
@@ -25,22 +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
|
-
|
|
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 {}
|
|
44
73
|
|
|
45
74
|
// Collect SPL token accounts from instructions for pre/post token balance snapshots
|
|
46
75
|
const msgAny = msg as unknown as {
|
|
@@ -57,14 +86,15 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
57
86
|
TOKEN_2022_PROGRAM_ID.toBase58(),
|
|
58
87
|
]);
|
|
59
88
|
const tokenAccountSet = new Set<string>();
|
|
89
|
+
// 1) Collect from compiled ixs (best-effort)
|
|
60
90
|
for (const ci of compiled) {
|
|
61
91
|
try {
|
|
62
|
-
const pid = staticKeys[ci.programIdIndex]?.toBase58();
|
|
92
|
+
const pid = staticKeys[(ci as any).programIdIndex]?.toBase58();
|
|
63
93
|
if (!pid || !tokenProgramIds.has(pid)) continue;
|
|
64
|
-
const accIdxs: number[] = Array.isArray(ci.accountKeyIndexes)
|
|
65
|
-
? ci.accountKeyIndexes
|
|
66
|
-
: Array.isArray(ci.accounts)
|
|
67
|
-
? ci.accounts
|
|
94
|
+
const accIdxs: number[] = Array.isArray((ci as any).accountKeyIndexes)
|
|
95
|
+
? (ci as any).accountKeyIndexes
|
|
96
|
+
: Array.isArray((ci as any).accounts)
|
|
97
|
+
? (ci as any).accounts
|
|
68
98
|
: [];
|
|
69
99
|
for (const ix of accIdxs) {
|
|
70
100
|
const addr = staticKeys[ix]?.toBase58();
|
|
@@ -72,6 +102,17 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
72
102
|
}
|
|
73
103
|
} catch {}
|
|
74
104
|
}
|
|
105
|
+
// 2) Also collect from all static keys that are SPL token accounts pre-send
|
|
106
|
+
for (const pk of staticKeys) {
|
|
107
|
+
try {
|
|
108
|
+
const acc = context.svm.getAccount(pk);
|
|
109
|
+
if (!acc) continue;
|
|
110
|
+
const ownerStr = new PublicKey(acc.owner).toBase58();
|
|
111
|
+
if (tokenProgramIds.has(ownerStr) && (acc.data?.length ?? 0) >= ACCOUNT_SIZE) {
|
|
112
|
+
tokenAccountSet.add(pk.toBase58());
|
|
113
|
+
}
|
|
114
|
+
} catch {}
|
|
115
|
+
}
|
|
75
116
|
// Pre token balances
|
|
76
117
|
const preTokenBalances: unknown[] = [];
|
|
77
118
|
const ataToInfo = new Map<
|
|
@@ -130,7 +171,7 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
130
171
|
} catch {}
|
|
131
172
|
}
|
|
132
173
|
|
|
133
|
-
|
|
174
|
+
const result = context.svm.sendTransaction(tx);
|
|
134
175
|
|
|
135
176
|
try {
|
|
136
177
|
const rawErr = (result as { err?: unknown }).err;
|
|
@@ -146,19 +187,47 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
146
187
|
}
|
|
147
188
|
} catch {}
|
|
148
189
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
190
|
+
const signature = tx.signatures[0]
|
|
191
|
+
? context.encodeBase58(tx.signatures[0])
|
|
192
|
+
: context.encodeBase58(new Uint8Array(64).fill(0));
|
|
193
|
+
context.notifySignature(signature);
|
|
194
|
+
// Snapshot post balances and capture logs for rich view
|
|
195
|
+
const postBalances = staticKeys.map((pk) => {
|
|
196
|
+
try {
|
|
197
|
+
return Number(context.svm.getBalance(pk));
|
|
198
|
+
} catch {
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
const postAccountStates = staticKeys.map((pk) => {
|
|
203
|
+
try {
|
|
204
|
+
const addr = pk.toBase58();
|
|
205
|
+
const acc = context.svm.getAccount(pk);
|
|
206
|
+
if (!acc) return { address: addr, post: null } as const;
|
|
207
|
+
return {
|
|
208
|
+
address: addr,
|
|
209
|
+
post: {
|
|
210
|
+
lamports: Number(acc.lamports || 0n),
|
|
211
|
+
ownerProgram: new PublicKey(acc.owner).toBase58(),
|
|
212
|
+
executable: !!acc.executable,
|
|
213
|
+
rentEpoch: Number(acc.rentEpoch || 0),
|
|
214
|
+
dataLen: acc.data?.length ?? 0,
|
|
215
|
+
dataBase64: undefined,
|
|
216
|
+
lastSlot: Number(context.slot),
|
|
217
|
+
},
|
|
218
|
+
} as const;
|
|
219
|
+
} catch {
|
|
220
|
+
return { address: pk.toBase58(), post: null } as const;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
try {
|
|
224
|
+
if (process.env.DEBUG_TX_CAPTURE === "1") {
|
|
225
|
+
console.debug(
|
|
226
|
+
`[tx-capture] post snapshots: keys=${staticKeys.length} captured=${postAccountStates.length}`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
} catch {}
|
|
230
|
+
// Post token balances (scan token accounts among static keys)
|
|
162
231
|
const postTokenBalances: unknown[] = [];
|
|
163
232
|
for (const addr of tokenAccountSet) {
|
|
164
233
|
try {
|
|
@@ -209,28 +278,150 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
209
278
|
}
|
|
210
279
|
} catch {}
|
|
211
280
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
281
|
+
let logs: string[] = [];
|
|
282
|
+
let innerInstructions: unknown[] = [];
|
|
283
|
+
let computeUnits: number | null = null;
|
|
284
|
+
let returnData: { programId: string; dataBase64: string } | null = null;
|
|
285
|
+
try {
|
|
286
|
+
const DBG = process.env.DEBUG_TX_CAPTURE === "1";
|
|
287
|
+
const r: any = result as any;
|
|
288
|
+
// Logs can be on TransactionMetadata or in meta() for failures
|
|
289
|
+
try {
|
|
290
|
+
if (typeof r?.logs === "function") logs = r.logs();
|
|
291
|
+
} catch {}
|
|
292
|
+
let metaObj: any | undefined;
|
|
293
|
+
// Success shape: methods on result
|
|
294
|
+
if (
|
|
295
|
+
typeof r?.innerInstructions === "function" ||
|
|
296
|
+
typeof r?.computeUnitsConsumed === "function" ||
|
|
297
|
+
typeof r?.returnData === "function"
|
|
298
|
+
) {
|
|
299
|
+
metaObj = r;
|
|
300
|
+
}
|
|
301
|
+
// Failed shape: meta() returns TransactionMetadata
|
|
302
|
+
if (!metaObj && typeof r?.meta === "function") {
|
|
303
|
+
try {
|
|
304
|
+
metaObj = r.meta();
|
|
305
|
+
if (!logs.length && typeof metaObj?.logs === "function") {
|
|
306
|
+
logs = metaObj.logs();
|
|
307
|
+
}
|
|
308
|
+
} catch (e) {
|
|
309
|
+
if (DBG)
|
|
310
|
+
console.debug("[tx-capture] meta() threw while extracting:", e);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// Extract richer metadata from whichever object exposes it
|
|
314
|
+
if (metaObj) {
|
|
315
|
+
try {
|
|
316
|
+
const inner = metaObj.innerInstructions?.();
|
|
317
|
+
if (Array.isArray(inner)) {
|
|
318
|
+
innerInstructions = inner.map((group: any, index: number) => {
|
|
319
|
+
const instructions = Array.isArray(group)
|
|
320
|
+
? group
|
|
321
|
+
.map((ii: any) => {
|
|
322
|
+
try {
|
|
323
|
+
const inst = ii.instruction?.();
|
|
324
|
+
const accIdxs: number[] = Array.from(
|
|
325
|
+
inst?.accounts?.() || [],
|
|
326
|
+
);
|
|
327
|
+
const dataBytes: Uint8Array =
|
|
328
|
+
inst?.data?.() || new Uint8Array();
|
|
329
|
+
return {
|
|
330
|
+
programIdIndex: Number(
|
|
331
|
+
inst?.programIdIndex?.() ?? 0,
|
|
332
|
+
),
|
|
333
|
+
accounts: accIdxs,
|
|
334
|
+
data: context.encodeBase58(dataBytes),
|
|
335
|
+
stackHeight: Number(ii.stackHeight?.() ?? 0),
|
|
336
|
+
};
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
.filter(Boolean)
|
|
342
|
+
: [];
|
|
343
|
+
return { index, instructions };
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
} catch (e) {
|
|
347
|
+
if (DBG)
|
|
348
|
+
console.debug(
|
|
349
|
+
"[tx-capture] innerInstructions extraction failed:",
|
|
350
|
+
e,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
const cu = metaObj.computeUnitsConsumed?.();
|
|
355
|
+
if (typeof cu === "bigint") computeUnits = Number(cu);
|
|
356
|
+
} catch (e) {
|
|
357
|
+
if (DBG)
|
|
358
|
+
console.debug(
|
|
359
|
+
"[tx-capture] computeUnitsConsumed extraction failed:",
|
|
360
|
+
e,
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
const rd = metaObj.returnData?.();
|
|
365
|
+
if (rd) {
|
|
366
|
+
const pid = new PublicKey(rd.programId()).toBase58();
|
|
367
|
+
const dataB64 = Buffer.from(rd.data()).toString("base64");
|
|
368
|
+
returnData = { programId: pid, dataBase64: dataB64 };
|
|
369
|
+
}
|
|
370
|
+
} catch (e) {
|
|
371
|
+
if (DBG)
|
|
372
|
+
console.debug(
|
|
373
|
+
"[tx-capture] returnData extraction failed:",
|
|
374
|
+
e,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
} else if (DBG) {
|
|
378
|
+
console.debug(
|
|
379
|
+
"[tx-capture] no metadata object found on result shape",
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
} catch {}
|
|
383
|
+
try {
|
|
384
|
+
if (process.env.DEBUG_TX_CAPTURE === "1") {
|
|
385
|
+
console.debug(
|
|
386
|
+
`[tx-capture] sendTransaction meta: logs=${logs.length} innerGroups=${Array.isArray(innerInstructions) ? innerInstructions.length : 0} computeUnits=${computeUnits} returnData=${returnData ? "yes" : "no"}`,
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
} catch {}
|
|
390
|
+
context.recordTransaction(signature, tx, {
|
|
391
|
+
logs,
|
|
392
|
+
fee: 5000,
|
|
393
|
+
blockTime: Math.floor(Date.now() / 1000),
|
|
394
|
+
preBalances,
|
|
395
|
+
postBalances,
|
|
396
|
+
preTokenBalances,
|
|
397
|
+
postTokenBalances,
|
|
398
|
+
innerInstructions,
|
|
399
|
+
computeUnits,
|
|
400
|
+
returnData,
|
|
401
|
+
accountStates: (() => {
|
|
402
|
+
try {
|
|
403
|
+
const byAddr = new Map<string, { pre?: any; post?: any }>();
|
|
404
|
+
for (const s of preAccountStates)
|
|
405
|
+
byAddr.set(s.address, { pre: s.pre || null });
|
|
406
|
+
for (const s of postAccountStates) {
|
|
407
|
+
const e = byAddr.get(s.address) || {};
|
|
408
|
+
e.post = s.post || null;
|
|
409
|
+
byAddr.set(s.address, e);
|
|
410
|
+
}
|
|
411
|
+
return Array.from(byAddr.entries()).map(([address, v]) => ({
|
|
412
|
+
address,
|
|
413
|
+
pre: v.pre || null,
|
|
414
|
+
post: v.post || null,
|
|
415
|
+
}));
|
|
416
|
+
} catch {
|
|
417
|
+
return [] as Array<{
|
|
418
|
+
address: string;
|
|
419
|
+
pre?: unknown;
|
|
420
|
+
post?: unknown;
|
|
421
|
+
}>;
|
|
422
|
+
}
|
|
423
|
+
})(),
|
|
424
|
+
});
|
|
234
425
|
|
|
235
426
|
return context.createSuccessResponse(id, signature);
|
|
236
427
|
} catch (error: unknown) {
|
package/server/rpc-server.ts
CHANGED
|
@@ -150,23 +150,36 @@ export class LiteSVMRpcServer {
|
|
|
150
150
|
} catch {}
|
|
151
151
|
},
|
|
152
152
|
listPrograms: () => Array.from(this.knownPrograms),
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
153
|
+
recordTransaction: (signature, tx, meta) => {
|
|
154
|
+
this.txRecords.set(signature, {
|
|
155
|
+
tx,
|
|
156
|
+
logs: meta?.logs || [],
|
|
157
|
+
err: meta?.err ?? null,
|
|
158
|
+
fee: meta?.fee ?? 5000,
|
|
159
|
+
slot: Number(this.slot),
|
|
160
|
+
blockTime: meta?.blockTime,
|
|
161
|
+
preBalances: meta?.preBalances,
|
|
162
|
+
postBalances: meta?.postBalances,
|
|
163
|
+
preTokenBalances: (
|
|
164
|
+
meta as { preTokenBalances?: unknown[] } | undefined
|
|
165
|
+
)?.preTokenBalances,
|
|
166
|
+
postTokenBalances: (
|
|
167
|
+
meta as { postTokenBalances?: unknown[] } | undefined
|
|
168
|
+
)?.postTokenBalances,
|
|
169
|
+
innerInstructions: meta?.innerInstructions || [],
|
|
170
|
+
computeUnits:
|
|
171
|
+
meta?.computeUnits == null
|
|
172
|
+
? null
|
|
173
|
+
: Number(meta.computeUnits),
|
|
174
|
+
returnData: meta?.returnData ?? null,
|
|
175
|
+
});
|
|
176
|
+
try {
|
|
177
|
+
if (process.env.DEBUG_TX_CAPTURE === "1") {
|
|
178
|
+
console.debug(
|
|
179
|
+
`[tx-capture] recordTransaction: sig=${signature} slot=${this.slot} logs=${meta?.logs?.length || 0} inner=${Array.isArray(meta?.innerInstructions) ? meta?.innerInstructions?.length : 0} cu=${meta?.computeUnits ?? null} returnData=${meta?.returnData ? "yes" : "no"}`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
} catch {}
|
|
170
183
|
|
|
171
184
|
// Persist to SQLite for durability and history queries
|
|
172
185
|
try {
|
|
@@ -227,58 +240,82 @@ export class LiteSVMRpcServer {
|
|
|
227
240
|
: "legacy"
|
|
228
241
|
: 0;
|
|
229
242
|
const rawBase64 = Buffer.from(tx.serialize()).toString("base64");
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
243
|
+
this.store
|
|
244
|
+
.insertTransactionBundle({
|
|
245
|
+
signature,
|
|
246
|
+
slot: Number(this.slot),
|
|
247
|
+
blockTime: meta?.blockTime,
|
|
248
|
+
version,
|
|
249
|
+
fee: Number(meta?.fee ?? 5000),
|
|
250
|
+
err: meta?.err ?? null,
|
|
251
|
+
rawBase64,
|
|
252
|
+
preBalances: Array.isArray(meta?.preBalances)
|
|
253
|
+
? (meta?.preBalances as number[])
|
|
254
|
+
: [],
|
|
255
|
+
postBalances: Array.isArray(meta?.postBalances)
|
|
256
|
+
? (meta?.postBalances as number[])
|
|
257
|
+
: [],
|
|
258
|
+
logs: Array.isArray(meta?.logs) ? (meta?.logs as string[]) : [],
|
|
259
|
+
preTokenBalances: (() => {
|
|
260
|
+
const arr = (
|
|
261
|
+
meta as { preTokenBalances?: unknown[] } | undefined
|
|
262
|
+
)?.preTokenBalances;
|
|
263
|
+
return Array.isArray(arr) ? arr : [];
|
|
264
|
+
})(),
|
|
265
|
+
postTokenBalances: (() => {
|
|
266
|
+
const arr = (
|
|
267
|
+
meta as { postTokenBalances?: unknown[] } | undefined
|
|
268
|
+
)?.postTokenBalances;
|
|
269
|
+
return Array.isArray(arr) ? arr : [];
|
|
270
|
+
})(),
|
|
271
|
+
innerInstructions: Array.isArray(meta?.innerInstructions)
|
|
272
|
+
? meta?.innerInstructions
|
|
273
|
+
: [],
|
|
274
|
+
computeUnits:
|
|
275
|
+
meta?.computeUnits == null
|
|
276
|
+
? null
|
|
277
|
+
: Number(meta.computeUnits),
|
|
278
|
+
returnData: meta?.returnData ?? null,
|
|
279
|
+
accounts,
|
|
280
|
+
accountStates: Array.isArray(meta?.accountStates)
|
|
281
|
+
? meta?.accountStates
|
|
282
|
+
: [],
|
|
283
|
+
})
|
|
284
|
+
.catch(() => {});
|
|
261
285
|
|
|
262
286
|
// Upsert account snapshots for static keys
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
287
|
+
const snapshots = keys
|
|
288
|
+
.map((addr) => {
|
|
289
|
+
try {
|
|
290
|
+
const acc = this.svm.getAccount(new PublicKey(addr));
|
|
291
|
+
if (!acc) return null;
|
|
292
|
+
const ownerStr = new PublicKey(acc.owner).toBase58();
|
|
293
|
+
let dataBase64: string | undefined;
|
|
294
|
+
// Store raw data for SPL Token accounts to reflect balance changes
|
|
295
|
+
try {
|
|
296
|
+
if (
|
|
297
|
+
ownerStr === "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" ||
|
|
298
|
+
ownerStr === "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
|
299
|
+
) {
|
|
300
|
+
if (acc.data && acc.data.length > 0) {
|
|
301
|
+
dataBase64 = Buffer.from(acc.data).toString("base64");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} catch {}
|
|
305
|
+
return {
|
|
306
|
+
address: addr,
|
|
307
|
+
lamports: Number(acc.lamports || 0n),
|
|
308
|
+
ownerProgram: ownerStr,
|
|
309
|
+
executable: !!acc.executable,
|
|
310
|
+
rentEpoch: Number(acc.rentEpoch || 0),
|
|
311
|
+
dataLen: acc.data?.length ?? 0,
|
|
312
|
+
dataBase64,
|
|
313
|
+
lastSlot: Number(this.slot),
|
|
314
|
+
};
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
})
|
|
282
319
|
.filter(Boolean) as import("../src/db/tx-store").AccountSnapshot[];
|
|
283
320
|
if (snapshots.length > 0)
|
|
284
321
|
this.store.upsertAccounts(snapshots).catch(() => {});
|