solforge 0.2.6 → 0.2.7
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/server/lib/instruction-parser.ts +242 -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 +230 -51
- package/server/rpc-server.ts +72 -48
- 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 {
|
|
@@ -130,7 +159,7 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
130
159
|
} catch {}
|
|
131
160
|
}
|
|
132
161
|
|
|
133
|
-
|
|
162
|
+
const result = context.svm.sendTransaction(tx);
|
|
134
163
|
|
|
135
164
|
try {
|
|
136
165
|
const rawErr = (result as { err?: unknown }).err;
|
|
@@ -146,18 +175,46 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
146
175
|
}
|
|
147
176
|
} catch {}
|
|
148
177
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
178
|
+
const signature = tx.signatures[0]
|
|
179
|
+
? context.encodeBase58(tx.signatures[0])
|
|
180
|
+
: context.encodeBase58(new Uint8Array(64).fill(0));
|
|
181
|
+
context.notifySignature(signature);
|
|
182
|
+
// Snapshot post balances and capture logs for rich view
|
|
183
|
+
const postBalances = staticKeys.map((pk) => {
|
|
184
|
+
try {
|
|
185
|
+
return Number(context.svm.getBalance(pk));
|
|
186
|
+
} catch {
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
const postAccountStates = staticKeys.map((pk) => {
|
|
191
|
+
try {
|
|
192
|
+
const addr = pk.toBase58();
|
|
193
|
+
const acc = context.svm.getAccount(pk);
|
|
194
|
+
if (!acc) return { address: addr, post: null } as const;
|
|
195
|
+
return {
|
|
196
|
+
address: addr,
|
|
197
|
+
post: {
|
|
198
|
+
lamports: Number(acc.lamports || 0n),
|
|
199
|
+
ownerProgram: new PublicKey(acc.owner).toBase58(),
|
|
200
|
+
executable: !!acc.executable,
|
|
201
|
+
rentEpoch: Number(acc.rentEpoch || 0),
|
|
202
|
+
dataLen: acc.data?.length ?? 0,
|
|
203
|
+
dataBase64: undefined,
|
|
204
|
+
lastSlot: Number(context.slot),
|
|
205
|
+
},
|
|
206
|
+
} as const;
|
|
207
|
+
} catch {
|
|
208
|
+
return { address: pk.toBase58(), post: null } as const;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
try {
|
|
212
|
+
if (process.env.DEBUG_TX_CAPTURE === "1") {
|
|
213
|
+
console.debug(
|
|
214
|
+
`[tx-capture] post snapshots: keys=${staticKeys.length} captured=${postAccountStates.length}`,
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
} catch {}
|
|
161
218
|
// Post token balances
|
|
162
219
|
const postTokenBalances: unknown[] = [];
|
|
163
220
|
for (const addr of tokenAccountSet) {
|
|
@@ -209,28 +266,150 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
209
266
|
}
|
|
210
267
|
} catch {}
|
|
211
268
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
269
|
+
let logs: string[] = [];
|
|
270
|
+
let innerInstructions: unknown[] = [];
|
|
271
|
+
let computeUnits: number | null = null;
|
|
272
|
+
let returnData: { programId: string; dataBase64: string } | null = null;
|
|
273
|
+
try {
|
|
274
|
+
const DBG = process.env.DEBUG_TX_CAPTURE === "1";
|
|
275
|
+
const r: any = result as any;
|
|
276
|
+
// Logs can be on TransactionMetadata or in meta() for failures
|
|
277
|
+
try {
|
|
278
|
+
if (typeof r?.logs === "function") logs = r.logs();
|
|
279
|
+
} catch {}
|
|
280
|
+
let metaObj: any | undefined;
|
|
281
|
+
// Success shape: methods on result
|
|
282
|
+
if (
|
|
283
|
+
typeof r?.innerInstructions === "function" ||
|
|
284
|
+
typeof r?.computeUnitsConsumed === "function" ||
|
|
285
|
+
typeof r?.returnData === "function"
|
|
286
|
+
) {
|
|
287
|
+
metaObj = r;
|
|
288
|
+
}
|
|
289
|
+
// Failed shape: meta() returns TransactionMetadata
|
|
290
|
+
if (!metaObj && typeof r?.meta === "function") {
|
|
291
|
+
try {
|
|
292
|
+
metaObj = r.meta();
|
|
293
|
+
if (!logs.length && typeof metaObj?.logs === "function") {
|
|
294
|
+
logs = metaObj.logs();
|
|
295
|
+
}
|
|
296
|
+
} catch (e) {
|
|
297
|
+
if (DBG)
|
|
298
|
+
console.debug("[tx-capture] meta() threw while extracting:", e);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Extract richer metadata from whichever object exposes it
|
|
302
|
+
if (metaObj) {
|
|
303
|
+
try {
|
|
304
|
+
const inner = metaObj.innerInstructions?.();
|
|
305
|
+
if (Array.isArray(inner)) {
|
|
306
|
+
innerInstructions = inner.map((group: any, index: number) => {
|
|
307
|
+
const instructions = Array.isArray(group)
|
|
308
|
+
? group
|
|
309
|
+
.map((ii: any) => {
|
|
310
|
+
try {
|
|
311
|
+
const inst = ii.instruction?.();
|
|
312
|
+
const accIdxs: number[] = Array.from(
|
|
313
|
+
inst?.accounts?.() || [],
|
|
314
|
+
);
|
|
315
|
+
const dataBytes: Uint8Array =
|
|
316
|
+
inst?.data?.() || new Uint8Array();
|
|
317
|
+
return {
|
|
318
|
+
programIdIndex: Number(
|
|
319
|
+
inst?.programIdIndex?.() ?? 0,
|
|
320
|
+
),
|
|
321
|
+
accounts: accIdxs,
|
|
322
|
+
data: context.encodeBase58(dataBytes),
|
|
323
|
+
stackHeight: Number(ii.stackHeight?.() ?? 0),
|
|
324
|
+
};
|
|
325
|
+
} catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
})
|
|
329
|
+
.filter(Boolean)
|
|
330
|
+
: [];
|
|
331
|
+
return { index, instructions };
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
} catch (e) {
|
|
335
|
+
if (DBG)
|
|
336
|
+
console.debug(
|
|
337
|
+
"[tx-capture] innerInstructions extraction failed:",
|
|
338
|
+
e,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
try {
|
|
342
|
+
const cu = metaObj.computeUnitsConsumed?.();
|
|
343
|
+
if (typeof cu === "bigint") computeUnits = Number(cu);
|
|
344
|
+
} catch (e) {
|
|
345
|
+
if (DBG)
|
|
346
|
+
console.debug(
|
|
347
|
+
"[tx-capture] computeUnitsConsumed extraction failed:",
|
|
348
|
+
e,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const rd = metaObj.returnData?.();
|
|
353
|
+
if (rd) {
|
|
354
|
+
const pid = new PublicKey(rd.programId()).toBase58();
|
|
355
|
+
const dataB64 = Buffer.from(rd.data()).toString("base64");
|
|
356
|
+
returnData = { programId: pid, dataBase64: dataB64 };
|
|
357
|
+
}
|
|
358
|
+
} catch (e) {
|
|
359
|
+
if (DBG)
|
|
360
|
+
console.debug(
|
|
361
|
+
"[tx-capture] returnData extraction failed:",
|
|
362
|
+
e,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
} else if (DBG) {
|
|
366
|
+
console.debug(
|
|
367
|
+
"[tx-capture] no metadata object found on result shape",
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
} catch {}
|
|
371
|
+
try {
|
|
372
|
+
if (process.env.DEBUG_TX_CAPTURE === "1") {
|
|
373
|
+
console.debug(
|
|
374
|
+
`[tx-capture] sendTransaction meta: logs=${logs.length} innerGroups=${Array.isArray(innerInstructions) ? innerInstructions.length : 0} computeUnits=${computeUnits} returnData=${returnData ? "yes" : "no"}`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
} catch {}
|
|
378
|
+
context.recordTransaction(signature, tx, {
|
|
379
|
+
logs,
|
|
380
|
+
fee: 5000,
|
|
381
|
+
blockTime: Math.floor(Date.now() / 1000),
|
|
382
|
+
preBalances,
|
|
383
|
+
postBalances,
|
|
384
|
+
preTokenBalances,
|
|
385
|
+
postTokenBalances,
|
|
386
|
+
innerInstructions,
|
|
387
|
+
computeUnits,
|
|
388
|
+
returnData,
|
|
389
|
+
accountStates: (() => {
|
|
390
|
+
try {
|
|
391
|
+
const byAddr = new Map<string, { pre?: any; post?: any }>();
|
|
392
|
+
for (const s of preAccountStates)
|
|
393
|
+
byAddr.set(s.address, { pre: s.pre || null });
|
|
394
|
+
for (const s of postAccountStates) {
|
|
395
|
+
const e = byAddr.get(s.address) || {};
|
|
396
|
+
e.post = s.post || null;
|
|
397
|
+
byAddr.set(s.address, e);
|
|
398
|
+
}
|
|
399
|
+
return Array.from(byAddr.entries()).map(([address, v]) => ({
|
|
400
|
+
address,
|
|
401
|
+
pre: v.pre || null,
|
|
402
|
+
post: v.post || null,
|
|
403
|
+
}));
|
|
404
|
+
} catch {
|
|
405
|
+
return [] as Array<{
|
|
406
|
+
address: string;
|
|
407
|
+
pre?: unknown;
|
|
408
|
+
post?: unknown;
|
|
409
|
+
}>;
|
|
410
|
+
}
|
|
411
|
+
})(),
|
|
412
|
+
});
|
|
234
413
|
|
|
235
414
|
return context.createSuccessResponse(id, signature);
|
|
236
415
|
} 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,37 +240,48 @@ 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
287
|
const snapshots = keys
|
package/server/types.ts
CHANGED
|
@@ -44,30 +44,62 @@ export interface RpcMethodContext {
|
|
|
44
44
|
listMints?: () => string[];
|
|
45
45
|
registerProgram?: (program: PublicKey | string) => void;
|
|
46
46
|
listPrograms?: () => string[];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
47
|
+
recordTransaction: (
|
|
48
|
+
signature: string,
|
|
49
|
+
tx: VersionedTransaction,
|
|
50
|
+
meta?: {
|
|
51
|
+
logs?: string[];
|
|
52
|
+
err?: unknown;
|
|
53
|
+
fee?: number;
|
|
54
|
+
blockTime?: number;
|
|
55
|
+
preBalances?: number[];
|
|
56
|
+
postBalances?: number[];
|
|
57
|
+
preTokenBalances?: unknown[];
|
|
58
|
+
postTokenBalances?: unknown[];
|
|
59
|
+
innerInstructions?: unknown[];
|
|
60
|
+
computeUnits?: number | bigint | null;
|
|
61
|
+
returnData?: { programId: string; dataBase64: string } | null;
|
|
62
|
+
// Optional rich per-account snapshots captured around execution
|
|
63
|
+
accountStates?: Array<{
|
|
64
|
+
address: string;
|
|
65
|
+
pre?: {
|
|
66
|
+
lamports?: number;
|
|
67
|
+
ownerProgram?: string;
|
|
68
|
+
executable?: boolean;
|
|
69
|
+
rentEpoch?: number;
|
|
70
|
+
dataLen?: number;
|
|
71
|
+
dataBase64?: string | null;
|
|
72
|
+
lastSlot?: number;
|
|
73
|
+
} | null;
|
|
74
|
+
post?: {
|
|
75
|
+
lamports?: number;
|
|
76
|
+
ownerProgram?: string;
|
|
77
|
+
executable?: boolean;
|
|
78
|
+
rentEpoch?: number;
|
|
79
|
+
dataLen?: number;
|
|
80
|
+
dataBase64?: string | null;
|
|
81
|
+
lastSlot?: number;
|
|
82
|
+
} | null;
|
|
83
|
+
}>;
|
|
84
|
+
},
|
|
85
|
+
) => void;
|
|
86
|
+
getRecordedTransaction: (signature: string) =>
|
|
87
|
+
| {
|
|
88
|
+
tx: VersionedTransaction;
|
|
89
|
+
logs: string[];
|
|
90
|
+
err: unknown;
|
|
91
|
+
fee: number;
|
|
92
|
+
slot: number;
|
|
93
|
+
blockTime?: number;
|
|
94
|
+
preBalances?: number[];
|
|
95
|
+
postBalances?: number[];
|
|
96
|
+
preTokenBalances?: unknown[];
|
|
97
|
+
postTokenBalances?: unknown[];
|
|
98
|
+
innerInstructions?: unknown[];
|
|
99
|
+
computeUnits?: number | null;
|
|
100
|
+
returnData?: { programId: string; dataBase64: string } | null;
|
|
101
|
+
}
|
|
102
|
+
| undefined;
|
|
71
103
|
}
|
|
72
104
|
|
|
73
105
|
export type RpcMethodHandler = (
|
package/src/db/schema/index.ts
CHANGED
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
2
2
|
|
|
3
3
|
export const transactions = sqliteTable(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
4
|
+
"transactions",
|
|
5
|
+
{
|
|
6
|
+
signature: text("signature").primaryKey(),
|
|
7
|
+
slot: integer("slot").notNull(),
|
|
8
|
+
blockTime: integer("block_time"),
|
|
9
|
+
version: text("version").notNull(), // 0 | "legacy"
|
|
10
|
+
errJson: text("err_json"),
|
|
11
|
+
fee: integer("fee").notNull(),
|
|
12
|
+
rawBase64: text("raw_base64").notNull(),
|
|
13
|
+
preBalancesJson: text("pre_balances_json").notNull(),
|
|
14
|
+
postBalancesJson: text("post_balances_json").notNull(),
|
|
15
|
+
logsJson: text("logs_json").notNull(),
|
|
16
|
+
preTokenBalancesJson: text("pre_token_balances_json")
|
|
17
|
+
.default("[]")
|
|
18
|
+
.notNull(),
|
|
19
|
+
postTokenBalancesJson: text("post_token_balances_json")
|
|
20
|
+
.default("[]")
|
|
21
|
+
.notNull(),
|
|
22
|
+
// Additional rich metadata captured after execution
|
|
23
|
+
innerInstructionsJson: text("inner_instructions_json")
|
|
24
|
+
.default("[]")
|
|
25
|
+
.notNull(),
|
|
26
|
+
computeUnits: integer("compute_units"),
|
|
27
|
+
returnDataProgramId: text("return_data_program_id"),
|
|
28
|
+
returnDataBase64: text("return_data_base64"),
|
|
29
|
+
},
|
|
30
|
+
(t) => ({
|
|
31
|
+
slotIdx: index("idx_transactions_slot").on(t.slot),
|
|
32
|
+
}),
|
|
26
33
|
);
|
|
27
34
|
|
|
28
35
|
export type TransactionRow = typeof transactions.$inferSelect;
|