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.
@@ -25,51 +25,51 @@ export const sendTransaction: RpcMethodHandler = (id, params, context) => {
25
25
  : Array.isArray(msg.accountKeys)
26
26
  ? msg.accountKeys
27
27
  : [];
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 {}
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 pid = staticKeys[ci.programIdIndex]?.toBase58();
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 accIdxs: number[] = Array.isArray(ci.accountKeyIndexes)
94
- ? ci.accountKeyIndexes
95
- : Array.isArray(ci.accounts)
96
- ? ci.accounts
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
- const result = context.svm.sendTransaction(tx);
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
- 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 {}
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
- 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
- });
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) {