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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solforge",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,10 @@
1
+ import { decodeBase58 } from "../server/lib/base58";
2
+
3
+ const s = process.argv[2] || "84eT";
4
+ const bytes = decodeBase58(s);
5
+ console.log(bytes);
6
+ console.log(
7
+ Array.from(bytes)
8
+ .map((b) => b.toString(16).padStart(2, "0"))
9
+ .join(" "),
10
+ );
@@ -1,242 +1,328 @@
1
1
  import {
2
- AddressLookupTableInstruction,
3
- ComputeBudgetInstruction,
4
- PublicKey,
5
- StakeInstruction,
6
- SystemInstruction,
7
- SystemProgram,
8
- TransactionInstruction,
9
- VoteInstruction,
2
+ AddressLookupTableInstruction,
3
+ ComputeBudgetInstruction,
4
+ PublicKey,
5
+ StakeInstruction,
6
+ SystemInstruction,
7
+ SystemProgram,
8
+ TransactionInstruction,
9
+ VoteInstruction,
10
10
  } from "@solana/web3.js";
11
+ import {
12
+ TOKEN_PROGRAM_ID,
13
+ ASSOCIATED_TOKEN_PROGRAM_ID,
14
+ TOKEN_2022_PROGRAM_ID,
15
+ } from "@solana/spl-token";
16
+ import { tryParseSplToken } from "./parsers/spl-token";
17
+ import { tryParseAta } from "./parsers/spl-associated-token-account";
11
18
  import { decodeBase58 as _decodeBase58 } from "./base58";
12
19
 
13
20
  export type ParsedInstruction =
14
- | { program: string; programId: string; parsed: { type: string; info: any } }
15
- | { programId: string; accounts: string[]; data: string };
21
+ | {
22
+ program: string;
23
+ programId: string;
24
+ parsed: { type: string; info: unknown };
25
+ }
26
+ | { programId: string; accounts: string[]; data: string };
16
27
 
17
28
  function makeIx(
18
- programId: string,
19
- accountKeys: string[],
20
- accounts: number[],
21
- dataBase58: string,
29
+ programId: string,
30
+ accountKeys: string[],
31
+ accounts: number[],
32
+ dataBase58: string,
22
33
  ): TransactionInstruction {
23
- const keys = accounts.map((i) => ({
24
- pubkey: new PublicKey(accountKeys[i] || SystemProgram.programId),
25
- isSigner: false,
26
- isWritable: false,
27
- }));
28
- const data = Buffer.from(_decodeBase58(dataBase58));
29
- return new TransactionInstruction({ programId: new PublicKey(programId), keys, data });
34
+ const keys = accounts.map((i) => ({
35
+ pubkey: new PublicKey(accountKeys[i] || SystemProgram.programId),
36
+ isSigner: false,
37
+ isWritable: false,
38
+ }));
39
+ const data = Buffer.from(_decodeBase58(dataBase58));
40
+ return new TransactionInstruction({
41
+ programId: new PublicKey(programId),
42
+ keys,
43
+ data,
44
+ });
30
45
  }
31
46
 
32
- function ok(program: string, programId: string, type: string, info: any): ParsedInstruction {
33
- return { program, programId, parsed: { type, info } };
47
+ function ok(
48
+ program: string,
49
+ programId: string,
50
+ type: string,
51
+ info: unknown,
52
+ ): ParsedInstruction {
53
+ return { program, programId, parsed: { type, info } };
34
54
  }
35
55
 
36
56
  export function parseInstruction(
37
- programId: string,
38
- accounts: number[],
39
- dataBase58: string,
40
- accountKeys: string[],
57
+ programId: string,
58
+ accounts: number[],
59
+ dataBase58: string,
60
+ accountKeys: string[],
61
+ tokenBalanceHints?: Array<{ mint: string; decimals: number }>,
41
62
  ): ParsedInstruction {
42
- try {
43
- const pid = new PublicKey(programId);
44
- const ix = makeIx(programId, accountKeys, accounts, dataBase58);
63
+ try {
64
+ const pid = new PublicKey(programId);
65
+ const ix = makeIx(programId, accountKeys, accounts, dataBase58);
45
66
 
46
- // System Program
47
- if (pid.equals(SystemProgram.programId)) {
48
- const t = SystemInstruction.decodeInstructionType(ix);
49
- switch (t) {
50
- case "Create": {
51
- const p = SystemInstruction.decodeCreateAccount(ix);
52
- return ok("system", programId, "createAccount", {
53
- fromPubkey: p.fromPubkey.toBase58(),
54
- newAccountPubkey: p.newAccountPubkey.toBase58(),
55
- lamports: Number(p.lamports),
56
- space: Number(p.space),
57
- programId: p.programId.toBase58(),
58
- });
59
- }
60
- case "Transfer": {
61
- const p = SystemInstruction.decodeTransfer(ix);
62
- return ok("system", programId, "transfer", {
63
- source: p.fromPubkey.toBase58(),
64
- destination: p.toPubkey.toBase58(),
65
- lamports: Number(p.lamports),
66
- });
67
- }
68
- case "TransferWithSeed": {
69
- const p = SystemInstruction.decodeTransferWithSeed(ix);
70
- return ok("system", programId, "transferWithSeed", {
71
- fromPubkey: p.fromPubkey.toBase58(),
72
- basePubkey: p.basePubkey.toBase58(),
73
- toPubkey: p.toPubkey.toBase58(),
74
- lamports: Number(p.lamports),
75
- seed: p.seed,
76
- programId: p.programId.toBase58(),
77
- });
78
- }
79
- case "Allocate": {
80
- const p = SystemInstruction.decodeAllocate(ix);
81
- return ok("system", programId, "allocate", {
82
- accountPubkey: p.accountPubkey.toBase58(),
83
- space: Number(p.space),
84
- });
85
- }
86
- case "AllocateWithSeed": {
87
- const p = SystemInstruction.decodeAllocateWithSeed(ix);
88
- return ok("system", programId, "allocateWithSeed", {
89
- accountPubkey: p.accountPubkey.toBase58(),
90
- basePubkey: p.basePubkey.toBase58(),
91
- seed: p.seed,
92
- space: Number(p.space),
93
- programId: p.programId.toBase58(),
94
- });
95
- }
96
- case "Assign": {
97
- const p = SystemInstruction.decodeAssign(ix);
98
- return ok("system", programId, "assign", {
99
- accountPubkey: p.accountPubkey.toBase58(),
100
- programId: p.programId.toBase58(),
101
- });
102
- }
103
- case "AssignWithSeed": {
104
- const p = SystemInstruction.decodeAssignWithSeed(ix);
105
- return ok("system", programId, "assignWithSeed", {
106
- accountPubkey: p.accountPubkey.toBase58(),
107
- basePubkey: p.basePubkey.toBase58(),
108
- seed: p.seed,
109
- programId: p.programId.toBase58(),
110
- });
111
- }
112
- case "InitializeNonceAccount":
113
- case "AdvanceNonceAccount":
114
- case "WithdrawNonceAccount":
115
- case "AuthorizeNonceAccount":
116
- case "CreateWithSeed":
117
- case "UpgradeNonceAccount": {
118
- // For brevity: rely on type only; details can be added if needed
119
- return ok("system", programId, t, {});
67
+ // SPL Token (legacy) and Token-2022
68
+ if (pid.equals(TOKEN_PROGRAM_ID) || pid.equals(TOKEN_2022_PROGRAM_ID)) {
69
+ const parsed = tryParseSplToken(
70
+ ix,
71
+ programId,
72
+ accountKeys,
73
+ dataBase58,
74
+ tokenBalanceHints,
75
+ );
76
+ if (parsed) return parsed;
120
77
  }
121
- default:
122
- break;
123
- }
124
- }
125
78
 
126
- // Compute Budget
127
- try {
128
- const t = ComputeBudgetInstruction.decodeInstructionType(ix);
129
- switch (t) {
130
- case "SetComputeUnitLimit": {
131
- const p = ComputeBudgetInstruction.decodeSetComputeUnitLimit(ix);
132
- return ok("computeBudget", programId, "setComputeUnitLimit", { units: Number(p.units) });
133
- }
134
- case "SetComputeUnitPrice": {
135
- const p = ComputeBudgetInstruction.decodeSetComputeUnitPrice(ix);
136
- return ok("computeBudget", programId, "setComputeUnitPrice", { microLamports: Number(p.microLamports) });
137
- }
138
- case "RequestHeapFrame": {
139
- const p = ComputeBudgetInstruction.decodeRequestHeapFrame(ix);
140
- return ok("computeBudget", programId, "requestHeapFrame", { bytes: Number(p.bytes) });
141
- }
142
- case "RequestUnits": {
143
- const p = ComputeBudgetInstruction.decodeRequestUnits(ix);
144
- return ok("computeBudget", programId, "requestUnits", { units: Number(p.units), additionalFee: Number(p.additionalFee) });
145
- }
146
- }
147
- } catch {}
79
+ // Associated Token Account
80
+ if (pid.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
81
+ const parsed = tryParseAta(ix, programId);
82
+ if (parsed) return parsed;
83
+ }
148
84
 
149
- // Stake
150
- try {
151
- const t = StakeInstruction.decodeInstructionType(ix);
152
- switch (t) {
153
- case "Initialize":
154
- return ok("stake", programId, "initialize", {});
155
- case "Delegate": {
156
- const p = StakeInstruction.decodeDelegate(ix);
157
- return ok("stake", programId, "delegate", {
158
- stakePubkey: p.stakePubkey.toBase58(),
159
- votePubkey: p.votePubkey.toBase58(),
160
- authorizedPubkey: p.authorizedPubkey.toBase58(),
161
- });
162
- }
163
- case "Authorize": {
164
- const p = StakeInstruction.decodeAuthorize(ix);
165
- return ok("stake", programId, "authorize", {
166
- stakePubkey: p.stakePubkey.toBase58(),
167
- authorizedPubkey: p.authorizedPubkey.toBase58(),
168
- newAuthorizedPubkey: p.newAuthorizedPubkey.toBase58(),
169
- stakeAuthorizationType: p.stakeAuthorizationType.index,
170
- });
171
- }
172
- case "AuthorizeWithSeed":
173
- return ok("stake", programId, "authorizeWithSeed", {});
174
- case "Split":
175
- return ok("stake", programId, "split", {});
176
- case "Withdraw":
177
- return ok("stake", programId, "withdraw", {});
178
- case "Deactivate":
179
- return ok("stake", programId, "deactivate", {});
180
- case "Merge":
181
- return ok("stake", programId, "merge", {});
182
- }
183
- } catch {}
85
+ // System Program
86
+ if (pid.equals(SystemProgram.programId)) {
87
+ try {
88
+ const t = SystemInstruction.decodeInstructionType(ix);
89
+ switch (t) {
90
+ case "Create": {
91
+ try {
92
+ const p = SystemInstruction.decodeCreateAccount(ix);
93
+ const from = p.fromPubkey.toBase58();
94
+ const newAcc = p.newAccountPubkey.toBase58();
95
+ const owner = p.programId.toBase58();
96
+ return ok("system", programId, "createAccount", {
97
+ // Explorer-compatible field names
98
+ source: from,
99
+ newAccount: newAcc,
100
+ owner,
101
+ lamports: Number(p.lamports),
102
+ space: Number(p.space),
103
+ // Keep legacy aliases too
104
+ fromPubkey: from,
105
+ newAccountPubkey: newAcc,
106
+ programId: owner,
107
+ });
108
+ } catch {
109
+ return ok("system", programId, "createAccount", {});
110
+ }
111
+ }
112
+ case "Transfer": {
113
+ try {
114
+ const p = SystemInstruction.decodeTransfer(ix);
115
+ return ok("system", programId, "transfer", {
116
+ source: p.fromPubkey.toBase58(),
117
+ destination: p.toPubkey.toBase58(),
118
+ lamports: Number(p.lamports),
119
+ });
120
+ } catch {
121
+ return ok("system", programId, "transfer", {});
122
+ }
123
+ }
124
+ case "TransferWithSeed": {
125
+ try {
126
+ const p = SystemInstruction.decodeTransferWithSeed(ix);
127
+ return ok("system", programId, "transferWithSeed", {
128
+ fromPubkey: p.fromPubkey.toBase58(),
129
+ basePubkey: p.basePubkey.toBase58(),
130
+ toPubkey: p.toPubkey.toBase58(),
131
+ lamports: Number(p.lamports),
132
+ seed: p.seed,
133
+ programId: p.programId.toBase58(),
134
+ });
135
+ } catch {
136
+ return ok("system", programId, "transferWithSeed", {});
137
+ }
138
+ }
139
+ case "Allocate": {
140
+ try {
141
+ const p = SystemInstruction.decodeAllocate(ix);
142
+ return ok("system", programId, "allocate", {
143
+ accountPubkey: p.accountPubkey.toBase58(),
144
+ space: Number(p.space),
145
+ });
146
+ } catch {
147
+ return ok("system", programId, "allocate", {});
148
+ }
149
+ }
150
+ case "AllocateWithSeed": {
151
+ try {
152
+ const p = SystemInstruction.decodeAllocateWithSeed(ix);
153
+ return ok("system", programId, "allocateWithSeed", {
154
+ accountPubkey: p.accountPubkey.toBase58(),
155
+ basePubkey: p.basePubkey.toBase58(),
156
+ seed: p.seed,
157
+ space: Number(p.space),
158
+ programId: p.programId.toBase58(),
159
+ });
160
+ } catch {
161
+ return ok("system", programId, "allocateWithSeed", {});
162
+ }
163
+ }
164
+ case "Assign": {
165
+ try {
166
+ const p = SystemInstruction.decodeAssign(ix);
167
+ return ok("system", programId, "assign", {
168
+ accountPubkey: p.accountPubkey.toBase58(),
169
+ programId: p.programId.toBase58(),
170
+ });
171
+ } catch {
172
+ return ok("system", programId, "assign", {});
173
+ }
174
+ }
175
+ case "AssignWithSeed": {
176
+ try {
177
+ const p = SystemInstruction.decodeAssignWithSeed(ix);
178
+ return ok("system", programId, "assignWithSeed", {
179
+ accountPubkey: p.accountPubkey.toBase58(),
180
+ basePubkey: p.basePubkey.toBase58(),
181
+ seed: p.seed,
182
+ programId: p.programId.toBase58(),
183
+ });
184
+ } catch {
185
+ return ok("system", programId, "assignWithSeed", {});
186
+ }
187
+ }
188
+ case "InitializeNonceAccount":
189
+ case "AdvanceNonceAccount":
190
+ case "WithdrawNonceAccount":
191
+ case "AuthorizeNonceAccount":
192
+ case "CreateWithSeed":
193
+ case "UpgradeNonceAccount": {
194
+ return ok("system", programId, t, {});
195
+ }
196
+ default:
197
+ return ok("system", programId, t, {});
198
+ }
199
+ } catch {
200
+ // If we cannot even decode the instruction type, fallthrough to raw
201
+ }
202
+ }
184
203
 
185
- // Vote
186
- try {
187
- const t = VoteInstruction.decodeInstructionType(ix);
188
- switch (t) {
189
- case "InitializeAccount":
190
- return ok("vote", programId, "initialize", {});
191
- case "Authorize":
192
- return ok("vote", programId, "authorize", {});
193
- case "AuthorizeWithSeed":
194
- return ok("vote", programId, "authorizeWithSeed", {});
195
- case "Withdraw":
196
- return ok("vote", programId, "withdraw", {});
197
- default:
198
- return ok("vote", programId, t, {});
199
- }
200
- } catch {}
204
+ // Compute Budget
205
+ try {
206
+ const t = ComputeBudgetInstruction.decodeInstructionType(ix);
207
+ switch (t) {
208
+ case "SetComputeUnitLimit": {
209
+ const p = ComputeBudgetInstruction.decodeSetComputeUnitLimit(ix);
210
+ return ok("computeBudget", programId, "setComputeUnitLimit", {
211
+ units: Number(p.units),
212
+ });
213
+ }
214
+ case "SetComputeUnitPrice": {
215
+ const p = ComputeBudgetInstruction.decodeSetComputeUnitPrice(ix);
216
+ return ok("computeBudget", programId, "setComputeUnitPrice", {
217
+ microLamports: Number(p.microLamports),
218
+ });
219
+ }
220
+ case "RequestHeapFrame": {
221
+ const p = ComputeBudgetInstruction.decodeRequestHeapFrame(ix);
222
+ return ok("computeBudget", programId, "requestHeapFrame", {
223
+ bytes: Number(p.bytes),
224
+ });
225
+ }
226
+ case "RequestUnits": {
227
+ const p = ComputeBudgetInstruction.decodeRequestUnits(ix);
228
+ return ok("computeBudget", programId, "requestUnits", {
229
+ units: Number(p.units),
230
+ additionalFee: Number(p.additionalFee),
231
+ });
232
+ }
233
+ }
234
+ } catch {}
201
235
 
202
- // Address Lookup Table
203
- try {
204
- const t = AddressLookupTableInstruction.decodeInstructionType(ix);
205
- switch (t) {
206
- case "CreateLookupTable":
207
- case "ExtendLookupTable":
208
- case "CloseLookupTable":
209
- case "FreezeLookupTable":
210
- case "DeactivateLookupTable":
211
- return ok("address-lookup-table", programId, t, {});
212
- }
213
- } catch {}
236
+ // Stake
237
+ try {
238
+ const t = StakeInstruction.decodeInstructionType(ix);
239
+ switch (t) {
240
+ case "Initialize":
241
+ return ok("stake", programId, "initialize", {});
242
+ case "Delegate": {
243
+ const p = StakeInstruction.decodeDelegate(ix);
244
+ return ok("stake", programId, "delegate", {
245
+ stakePubkey: p.stakePubkey.toBase58(),
246
+ votePubkey: p.votePubkey.toBase58(),
247
+ authorizedPubkey: p.authorizedPubkey.toBase58(),
248
+ });
249
+ }
250
+ case "Authorize": {
251
+ const p = StakeInstruction.decodeAuthorize(ix);
252
+ return ok("stake", programId, "authorize", {
253
+ stakePubkey: p.stakePubkey.toBase58(),
254
+ authorizedPubkey: p.authorizedPubkey.toBase58(),
255
+ newAuthorizedPubkey: p.newAuthorizedPubkey.toBase58(),
256
+ stakeAuthorizationType: p.stakeAuthorizationType.index,
257
+ });
258
+ }
259
+ case "AuthorizeWithSeed":
260
+ return ok("stake", programId, "authorizeWithSeed", {});
261
+ case "Split":
262
+ return ok("stake", programId, "split", {});
263
+ case "Withdraw":
264
+ return ok("stake", programId, "withdraw", {});
265
+ case "Deactivate":
266
+ return ok("stake", programId, "deactivate", {});
267
+ case "Merge":
268
+ return ok("stake", programId, "merge", {});
269
+ }
270
+ } catch {}
214
271
 
215
- // Memo program: parse utf8 memo if possible
216
- if (
217
- programId === "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" ||
218
- programId === "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"
219
- ) {
220
- try {
221
- const bytes = _decodeBase58(dataBase58);
222
- const memo = new TextDecoder().decode(bytes);
223
- return ok("spl-memo", programId, "memo", { memo });
224
- } catch {}
225
- return ok("spl-memo", programId, "memo", {});
226
- }
272
+ // Vote
273
+ try {
274
+ const t = VoteInstruction.decodeInstructionType(ix);
275
+ switch (t) {
276
+ case "InitializeAccount":
277
+ return ok("vote", programId, "initialize", {});
278
+ case "Authorize":
279
+ return ok("vote", programId, "authorize", {});
280
+ case "AuthorizeWithSeed":
281
+ return ok("vote", programId, "authorizeWithSeed", {});
282
+ case "Withdraw":
283
+ return ok("vote", programId, "withdraw", {});
284
+ default:
285
+ return ok("vote", programId, t, {});
286
+ }
287
+ } catch {}
227
288
 
228
- // Fallback: unknown program, return raw
229
- return {
230
- programId,
231
- accounts: accounts.map((i) => accountKeys[i] || ""),
232
- data: dataBase58,
233
- };
234
- } catch {
235
- return {
236
- programId,
237
- accounts: accounts.map((i) => accountKeys[i] || ""),
238
- data: dataBase58,
239
- };
240
- }
241
- }
289
+ // Address Lookup Table
290
+ try {
291
+ const t = AddressLookupTableInstruction.decodeInstructionType(ix);
292
+ switch (t) {
293
+ case "CreateLookupTable":
294
+ case "ExtendLookupTable":
295
+ case "CloseLookupTable":
296
+ case "FreezeLookupTable":
297
+ case "DeactivateLookupTable":
298
+ return ok("address-lookup-table", programId, t, {});
299
+ }
300
+ } catch {}
242
301
 
302
+ // Memo program: parse utf8 memo if possible
303
+ if (
304
+ programId === "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" ||
305
+ programId === "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"
306
+ ) {
307
+ try {
308
+ const bytes = _decodeBase58(dataBase58);
309
+ const memo = new TextDecoder().decode(bytes);
310
+ return ok("spl-memo", programId, "memo", { memo });
311
+ } catch {}
312
+ return ok("spl-memo", programId, "memo", {});
313
+ }
314
+
315
+ // Fallback: unknown program, return raw
316
+ return {
317
+ programId,
318
+ accounts: accounts.map((i) => accountKeys[i] || ""),
319
+ data: dataBase58,
320
+ };
321
+ } catch {
322
+ return {
323
+ programId,
324
+ accounts: accounts.map((i) => accountKeys[i] || ""),
325
+ data: dataBase58,
326
+ };
327
+ }
328
+ }
@@ -0,0 +1,50 @@
1
+ import type { TransactionInstruction } from "@solana/web3.js";
2
+ import { ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token";
3
+
4
+ // Keep shape compatible with instruction-parser
5
+ export type ParsedInstruction =
6
+ | {
7
+ program: string;
8
+ programId: string;
9
+ parsed: { type: string; info: unknown };
10
+ }
11
+ | { programId: string; accounts: string[]; data: string };
12
+
13
+ function ok(programId: string, type: string, info: unknown): ParsedInstruction {
14
+ return {
15
+ program: "spl-associated-token-account",
16
+ programId,
17
+ parsed: { type, info },
18
+ };
19
+ }
20
+
21
+ function asBase58(ix: TransactionInstruction, idx: number): string | undefined {
22
+ try {
23
+ return ix.keys[idx]?.pubkey?.toBase58();
24
+ } catch {
25
+ return undefined;
26
+ }
27
+ }
28
+
29
+ export function tryParseAta(
30
+ ix: TransactionInstruction,
31
+ programIdStr: string,
32
+ ): ParsedInstruction | null {
33
+ try {
34
+ if (!ix.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) return null;
35
+ // Both create (empty) and createIdempotent ([1]) map to type "create" in explorers
36
+ const type = "create";
37
+ // Expected keys: [payer, associatedToken, owner, mint, systemProgram, tokenProgram]
38
+ const info = {
39
+ source: asBase58(ix, 0),
40
+ account: asBase58(ix, 1),
41
+ wallet: asBase58(ix, 2),
42
+ mint: asBase58(ix, 3),
43
+ systemProgram: asBase58(ix, 4),
44
+ tokenProgram: asBase58(ix, 5),
45
+ };
46
+ return ok(programIdStr, type, info);
47
+ } catch {
48
+ return null;
49
+ }
50
+ }