solforge 0.2.8 → 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 +5 -1
- package/server/lib/instruction-parser.ts +302 -238
- package/server/lib/parsers/spl-associated-token-account.ts +36 -30
- package/server/lib/parsers/spl-token.ts +285 -142
- 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 +269 -236
- package/server/rpc-server.ts +101 -104
- 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 {
|
|
@@ -89,13 +89,21 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
89
89
|
// 1) Collect from compiled ixs (best-effort)
|
|
90
90
|
for (const ci of compiled) {
|
|
91
91
|
try {
|
|
92
|
-
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;
|
|
93
98
|
if (!pid || !tokenProgramIds.has(pid)) continue;
|
|
94
|
-
const
|
|
95
|
-
? (
|
|
96
|
-
: Array.isArray(
|
|
97
|
-
? (
|
|
99
|
+
const accSrc = Array.isArray(rec.accountKeyIndexes)
|
|
100
|
+
? (rec.accountKeyIndexes as unknown[])
|
|
101
|
+
: Array.isArray(rec.accounts)
|
|
102
|
+
? (rec.accounts as unknown[])
|
|
98
103
|
: [];
|
|
104
|
+
const accIdxs: number[] = accSrc.filter(
|
|
105
|
+
(v): v is number => typeof v === "number",
|
|
106
|
+
);
|
|
99
107
|
for (const ix of accIdxs) {
|
|
100
108
|
const addr = staticKeys[ix]?.toBase58();
|
|
101
109
|
if (addr) tokenAccountSet.add(addr);
|
|
@@ -108,7 +116,10 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
108
116
|
const acc = context.svm.getAccount(pk);
|
|
109
117
|
if (!acc) continue;
|
|
110
118
|
const ownerStr = new PublicKey(acc.owner).toBase58();
|
|
111
|
-
if (
|
|
119
|
+
if (
|
|
120
|
+
tokenProgramIds.has(ownerStr) &&
|
|
121
|
+
(acc.data?.length ?? 0) >= ACCOUNT_SIZE
|
|
122
|
+
) {
|
|
112
123
|
tokenAccountSet.add(pk.toBase58());
|
|
113
124
|
}
|
|
114
125
|
} catch {}
|
|
@@ -171,7 +182,7 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
171
182
|
} catch {}
|
|
172
183
|
}
|
|
173
184
|
|
|
174
|
-
|
|
185
|
+
const result = context.svm.sendTransaction(tx);
|
|
175
186
|
|
|
176
187
|
try {
|
|
177
188
|
const rawErr = (result as { err?: unknown }).err;
|
|
@@ -187,46 +198,46 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
187
198
|
}
|
|
188
199
|
} catch {}
|
|
189
200
|
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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 {}
|
|
230
241
|
// Post token balances (scan token accounts among static keys)
|
|
231
242
|
const postTokenBalances: unknown[] = [];
|
|
232
243
|
for (const addr of tokenAccountSet) {
|
|
@@ -278,150 +289,172 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
|
|
|
278
289
|
}
|
|
279
290
|
} catch {}
|
|
280
291
|
}
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
+
});
|
|
425
458
|
|
|
426
459
|
return context.createSuccessResponse(id, signature);
|
|
427
460
|
} catch (error: unknown) {
|