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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "solforge",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,6 @@
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(Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join(" "));
@@ -0,0 +1,264 @@
1
+ import {
2
+ AddressLookupTableInstruction,
3
+ ComputeBudgetInstruction,
4
+ PublicKey,
5
+ StakeInstruction,
6
+ SystemInstruction,
7
+ SystemProgram,
8
+ TransactionInstruction,
9
+ VoteInstruction,
10
+ } from "@solana/web3.js";
11
+ import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from "@solana/spl-token";
12
+ import { tryParseSplToken } from "./parsers/spl-token";
13
+ import { tryParseAta } from "./parsers/spl-associated-token-account";
14
+ import { decodeBase58 as _decodeBase58 } from "./base58";
15
+
16
+ export type ParsedInstruction =
17
+ | { program: string; programId: string; parsed: { type: string; info: any } }
18
+ | { programId: string; accounts: string[]; data: string };
19
+
20
+ function makeIx(
21
+ programId: string,
22
+ accountKeys: string[],
23
+ accounts: number[],
24
+ dataBase58: string,
25
+ ): TransactionInstruction {
26
+ const keys = accounts.map((i) => ({
27
+ pubkey: new PublicKey(accountKeys[i] || SystemProgram.programId),
28
+ isSigner: false,
29
+ isWritable: false,
30
+ }));
31
+ const data = Buffer.from(_decodeBase58(dataBase58));
32
+ return new TransactionInstruction({ programId: new PublicKey(programId), keys, data });
33
+ }
34
+
35
+ function ok(program: string, programId: string, type: string, info: any): ParsedInstruction {
36
+ return { program, programId, parsed: { type, info } };
37
+ }
38
+
39
+ export function parseInstruction(
40
+ programId: string,
41
+ accounts: number[],
42
+ dataBase58: string,
43
+ accountKeys: string[],
44
+ ): ParsedInstruction {
45
+ try {
46
+ const pid = new PublicKey(programId);
47
+ const ix = makeIx(programId, accountKeys, accounts, dataBase58);
48
+
49
+ // SPL Token
50
+ if (pid.equals(TOKEN_PROGRAM_ID)) {
51
+ const parsed = tryParseSplToken(ix, programId, accountKeys, dataBase58);
52
+ if (parsed) return parsed;
53
+ }
54
+
55
+ // Associated Token Account
56
+ if (pid.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) {
57
+ const parsed = tryParseAta(ix, programId);
58
+ if (parsed) return parsed;
59
+ }
60
+
61
+ // System Program
62
+ if (pid.equals(SystemProgram.programId)) {
63
+ const t = SystemInstruction.decodeInstructionType(ix);
64
+ switch (t) {
65
+ case "Create": {
66
+ const p = SystemInstruction.decodeCreateAccount(ix);
67
+ const from = p.fromPubkey.toBase58();
68
+ const newAcc = p.newAccountPubkey.toBase58();
69
+ const owner = p.programId.toBase58();
70
+ return ok("system", programId, "createAccount", {
71
+ // Explorer-compatible field names
72
+ source: from,
73
+ newAccount: newAcc,
74
+ owner,
75
+ lamports: Number(p.lamports),
76
+ space: Number(p.space),
77
+ // Keep legacy aliases too
78
+ fromPubkey: from,
79
+ newAccountPubkey: newAcc,
80
+ programId: owner,
81
+ });
82
+ }
83
+ case "Transfer": {
84
+ const p = SystemInstruction.decodeTransfer(ix);
85
+ return ok("system", programId, "transfer", {
86
+ source: p.fromPubkey.toBase58(),
87
+ destination: p.toPubkey.toBase58(),
88
+ lamports: Number(p.lamports),
89
+ });
90
+ }
91
+ case "TransferWithSeed": {
92
+ const p = SystemInstruction.decodeTransferWithSeed(ix);
93
+ return ok("system", programId, "transferWithSeed", {
94
+ fromPubkey: p.fromPubkey.toBase58(),
95
+ basePubkey: p.basePubkey.toBase58(),
96
+ toPubkey: p.toPubkey.toBase58(),
97
+ lamports: Number(p.lamports),
98
+ seed: p.seed,
99
+ programId: p.programId.toBase58(),
100
+ });
101
+ }
102
+ case "Allocate": {
103
+ const p = SystemInstruction.decodeAllocate(ix);
104
+ return ok("system", programId, "allocate", {
105
+ accountPubkey: p.accountPubkey.toBase58(),
106
+ space: Number(p.space),
107
+ });
108
+ }
109
+ case "AllocateWithSeed": {
110
+ const p = SystemInstruction.decodeAllocateWithSeed(ix);
111
+ return ok("system", programId, "allocateWithSeed", {
112
+ accountPubkey: p.accountPubkey.toBase58(),
113
+ basePubkey: p.basePubkey.toBase58(),
114
+ seed: p.seed,
115
+ space: Number(p.space),
116
+ programId: p.programId.toBase58(),
117
+ });
118
+ }
119
+ case "Assign": {
120
+ const p = SystemInstruction.decodeAssign(ix);
121
+ return ok("system", programId, "assign", {
122
+ accountPubkey: p.accountPubkey.toBase58(),
123
+ programId: p.programId.toBase58(),
124
+ });
125
+ }
126
+ case "AssignWithSeed": {
127
+ const p = SystemInstruction.decodeAssignWithSeed(ix);
128
+ return ok("system", programId, "assignWithSeed", {
129
+ accountPubkey: p.accountPubkey.toBase58(),
130
+ basePubkey: p.basePubkey.toBase58(),
131
+ seed: p.seed,
132
+ programId: p.programId.toBase58(),
133
+ });
134
+ }
135
+ case "InitializeNonceAccount":
136
+ case "AdvanceNonceAccount":
137
+ case "WithdrawNonceAccount":
138
+ case "AuthorizeNonceAccount":
139
+ case "CreateWithSeed":
140
+ case "UpgradeNonceAccount": {
141
+ // For brevity: rely on type only; details can be added if needed
142
+ return ok("system", programId, t, {});
143
+ }
144
+ default:
145
+ break;
146
+ }
147
+ }
148
+
149
+ // Compute Budget
150
+ try {
151
+ const t = ComputeBudgetInstruction.decodeInstructionType(ix);
152
+ switch (t) {
153
+ case "SetComputeUnitLimit": {
154
+ const p = ComputeBudgetInstruction.decodeSetComputeUnitLimit(ix);
155
+ return ok("computeBudget", programId, "setComputeUnitLimit", { units: Number(p.units) });
156
+ }
157
+ case "SetComputeUnitPrice": {
158
+ const p = ComputeBudgetInstruction.decodeSetComputeUnitPrice(ix);
159
+ return ok("computeBudget", programId, "setComputeUnitPrice", { microLamports: Number(p.microLamports) });
160
+ }
161
+ case "RequestHeapFrame": {
162
+ const p = ComputeBudgetInstruction.decodeRequestHeapFrame(ix);
163
+ return ok("computeBudget", programId, "requestHeapFrame", { bytes: Number(p.bytes) });
164
+ }
165
+ case "RequestUnits": {
166
+ const p = ComputeBudgetInstruction.decodeRequestUnits(ix);
167
+ return ok("computeBudget", programId, "requestUnits", { units: Number(p.units), additionalFee: Number(p.additionalFee) });
168
+ }
169
+ }
170
+ } catch {}
171
+
172
+ // Stake
173
+ try {
174
+ const t = StakeInstruction.decodeInstructionType(ix);
175
+ switch (t) {
176
+ case "Initialize":
177
+ return ok("stake", programId, "initialize", {});
178
+ case "Delegate": {
179
+ const p = StakeInstruction.decodeDelegate(ix);
180
+ return ok("stake", programId, "delegate", {
181
+ stakePubkey: p.stakePubkey.toBase58(),
182
+ votePubkey: p.votePubkey.toBase58(),
183
+ authorizedPubkey: p.authorizedPubkey.toBase58(),
184
+ });
185
+ }
186
+ case "Authorize": {
187
+ const p = StakeInstruction.decodeAuthorize(ix);
188
+ return ok("stake", programId, "authorize", {
189
+ stakePubkey: p.stakePubkey.toBase58(),
190
+ authorizedPubkey: p.authorizedPubkey.toBase58(),
191
+ newAuthorizedPubkey: p.newAuthorizedPubkey.toBase58(),
192
+ stakeAuthorizationType: p.stakeAuthorizationType.index,
193
+ });
194
+ }
195
+ case "AuthorizeWithSeed":
196
+ return ok("stake", programId, "authorizeWithSeed", {});
197
+ case "Split":
198
+ return ok("stake", programId, "split", {});
199
+ case "Withdraw":
200
+ return ok("stake", programId, "withdraw", {});
201
+ case "Deactivate":
202
+ return ok("stake", programId, "deactivate", {});
203
+ case "Merge":
204
+ return ok("stake", programId, "merge", {});
205
+ }
206
+ } catch {}
207
+
208
+ // Vote
209
+ try {
210
+ const t = VoteInstruction.decodeInstructionType(ix);
211
+ switch (t) {
212
+ case "InitializeAccount":
213
+ return ok("vote", programId, "initialize", {});
214
+ case "Authorize":
215
+ return ok("vote", programId, "authorize", {});
216
+ case "AuthorizeWithSeed":
217
+ return ok("vote", programId, "authorizeWithSeed", {});
218
+ case "Withdraw":
219
+ return ok("vote", programId, "withdraw", {});
220
+ default:
221
+ return ok("vote", programId, t, {});
222
+ }
223
+ } catch {}
224
+
225
+ // Address Lookup Table
226
+ try {
227
+ const t = AddressLookupTableInstruction.decodeInstructionType(ix);
228
+ switch (t) {
229
+ case "CreateLookupTable":
230
+ case "ExtendLookupTable":
231
+ case "CloseLookupTable":
232
+ case "FreezeLookupTable":
233
+ case "DeactivateLookupTable":
234
+ return ok("address-lookup-table", programId, t, {});
235
+ }
236
+ } catch {}
237
+
238
+ // Memo program: parse utf8 memo if possible
239
+ if (
240
+ programId === "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr" ||
241
+ programId === "Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"
242
+ ) {
243
+ try {
244
+ const bytes = _decodeBase58(dataBase58);
245
+ const memo = new TextDecoder().decode(bytes);
246
+ return ok("spl-memo", programId, "memo", { memo });
247
+ } catch {}
248
+ return ok("spl-memo", programId, "memo", {});
249
+ }
250
+
251
+ // Fallback: unknown program, return raw
252
+ return {
253
+ programId,
254
+ accounts: accounts.map((i) => accountKeys[i] || ""),
255
+ data: dataBase58,
256
+ };
257
+ } catch {
258
+ return {
259
+ programId,
260
+ accounts: accounts.map((i) => accountKeys[i] || ""),
261
+ data: dataBase58,
262
+ };
263
+ }
264
+ }
@@ -0,0 +1,44 @@
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
+ | { program: string; programId: string; parsed: { type: string; info: any } }
7
+ | { programId: string; accounts: string[]; data: string };
8
+
9
+ function ok(programId: string, type: string, info: any): ParsedInstruction {
10
+ return { program: "spl-associated-token-account", programId, parsed: { type, info } };
11
+ }
12
+
13
+ function asBase58(ix: TransactionInstruction, idx: number): string | undefined {
14
+ try {
15
+ return ix.keys[idx]?.pubkey?.toBase58();
16
+ } catch {
17
+ return undefined;
18
+ }
19
+ }
20
+
21
+ export function tryParseAta(
22
+ ix: TransactionInstruction,
23
+ programIdStr: string,
24
+ ): ParsedInstruction | null {
25
+ try {
26
+ if (!ix.programId.equals(ASSOCIATED_TOKEN_PROGRAM_ID)) return null;
27
+ const dataLen = ix.data?.length ?? 0;
28
+ // Both create (empty) and createIdempotent ([1]) map to type "create" in explorers
29
+ const type = "create";
30
+ // Expected keys: [payer, associatedToken, owner, mint, systemProgram, tokenProgram]
31
+ const info = {
32
+ source: asBase58(ix, 0),
33
+ account: asBase58(ix, 1),
34
+ wallet: asBase58(ix, 2),
35
+ mint: asBase58(ix, 3),
36
+ systemProgram: asBase58(ix, 4),
37
+ tokenProgram: asBase58(ix, 5),
38
+ };
39
+ return ok(programIdStr, type, info);
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
@@ -0,0 +1,172 @@
1
+ import type { PublicKey, TransactionInstruction } from "@solana/web3.js";
2
+ import {
3
+ TOKEN_PROGRAM_ID,
4
+ decodeInstruction as splDecodeInstruction,
5
+ decodeMintToCheckedInstruction,
6
+ decodeTransferInstruction,
7
+ decodeTransferCheckedInstruction,
8
+ decodeInitializeAccount3Instruction,
9
+ decodeInitializeImmutableOwnerInstruction,
10
+ } from "@solana/spl-token";
11
+ import { u8 } from "@solana/buffer-layout";
12
+
13
+ // Keep shape compatible with instruction-parser
14
+ export type ParsedInstruction =
15
+ | { program: string; programId: string; parsed: { type: string; info: any } }
16
+ | { programId: string; accounts: string[]; data: string };
17
+
18
+ function ok(programId: string, type: string, info: any): ParsedInstruction {
19
+ return { program: "spl-token", programId, parsed: { type, info } };
20
+ }
21
+
22
+ function asBase58(pk: PublicKey | undefined): string | undefined {
23
+ try {
24
+ return pk ? pk.toBase58() : undefined;
25
+ } catch {
26
+ return undefined;
27
+ }
28
+ }
29
+
30
+ export function tryParseSplToken(
31
+ ix: TransactionInstruction,
32
+ programIdStr: string,
33
+ accountKeys: string[],
34
+ dataBase58: string,
35
+ ): ParsedInstruction | null {
36
+ try {
37
+ if (!ix.programId.equals(TOKEN_PROGRAM_ID)) return null;
38
+
39
+ // Probe with generic decoder (ignore result), then try specific decoders
40
+ try {
41
+ splDecodeInstruction(ix);
42
+ } catch {}
43
+
44
+ // MintToChecked
45
+ try {
46
+ const m = decodeMintToCheckedInstruction(ix);
47
+ const amount = m.data.amount;
48
+ const decimals = m.data.decimals;
49
+ const amtStr = typeof amount === "bigint" ? amount.toString() : String(amount);
50
+ const base = BigInt(10) ** BigInt(decimals);
51
+ const whole = BigInt(amtStr) / base;
52
+ const frac = BigInt(amtStr) % base;
53
+ const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
54
+ const uiStr = fracStr.length ? `${whole}.${fracStr}` : `${whole}`;
55
+ return ok(programIdStr, "mintToChecked", {
56
+ account: asBase58(m.keys.destination.pubkey),
57
+ mint: asBase58(m.keys.mint.pubkey),
58
+ mintAuthority: asBase58(m.keys.authority.pubkey),
59
+ tokenAmount: {
60
+ amount: amtStr,
61
+ decimals,
62
+ uiAmount: Number(uiStr),
63
+ uiAmountString: uiStr,
64
+ },
65
+ });
66
+ } catch {}
67
+
68
+ // Transfer / TransferChecked
69
+ try {
70
+ const t = decodeTransferInstruction(ix);
71
+ const amt = t.data.amount;
72
+ return ok(programIdStr, "transfer", {
73
+ amount: (typeof amt === "bigint" ? amt.toString() : String(amt)),
74
+ source: asBase58(t.keys.source.pubkey),
75
+ destination: asBase58(t.keys.destination.pubkey),
76
+ authority: asBase58(t.keys.owner.pubkey),
77
+ });
78
+ } catch {}
79
+
80
+ try {
81
+ const t = decodeTransferCheckedInstruction(ix);
82
+ const amt = t.data.amount;
83
+ const decimals = t.data.decimals;
84
+ return ok(programIdStr, "transferChecked", {
85
+ tokenAmount: {
86
+ amount: (typeof amt === "bigint" ? amt.toString() : String(amt)),
87
+ decimals,
88
+ },
89
+ source: asBase58(t.keys.source.pubkey),
90
+ destination: asBase58(t.keys.destination.pubkey),
91
+ authority: asBase58(t.keys.owner.pubkey),
92
+ mint: asBase58(t.keys.mint.pubkey),
93
+ });
94
+ } catch {}
95
+
96
+ // InitializeAccount3
97
+ try {
98
+ const a = decodeInitializeAccount3Instruction(ix);
99
+ return ok(programIdStr, "initializeAccount3", {
100
+ account: asBase58(a.keys.account.pubkey),
101
+ mint: asBase58(a.keys.mint.pubkey),
102
+ owner: asBase58(a.data.owner),
103
+ });
104
+ } catch {}
105
+
106
+ // InitializeImmutableOwner
107
+ try {
108
+ const im = decodeInitializeImmutableOwnerInstruction(ix, TOKEN_PROGRAM_ID);
109
+ return ok(programIdStr, "initializeImmutableOwner", {
110
+ account: asBase58(im.keys.account.pubkey),
111
+ });
112
+ } catch {}
113
+
114
+ // GetAccountDataSize: decode extension types (u16 little-endian sequence)
115
+ try {
116
+ const bytes = Buffer.from(bs58decode(dataBase58));
117
+ const t = u8().decode(bytes);
118
+ // 21 corresponds to TokenInstruction.GetAccountDataSize
119
+ if (t === 21) {
120
+ const mint = asBase58(ix.keys[0]?.pubkey);
121
+ const extCodes: number[] = [];
122
+ for (let i = 1; i + 1 < bytes.length; i += 2) {
123
+ const code = bytes[i] | (bytes[i + 1] << 8);
124
+ extCodes.push(code);
125
+ }
126
+ const extMap: Record<number, string> = {
127
+ 7: "immutableOwner",
128
+ 8: "memoTransfer",
129
+ 9: "nonTransferable",
130
+ 12: "permanentDelegate",
131
+ 14: "transferHook",
132
+ 15: "transferHookAccount",
133
+ 18: "metadataPointer",
134
+ 19: "tokenMetadata",
135
+ 20: "groupPointer",
136
+ 21: "tokenGroup",
137
+ 22: "groupMemberPointer",
138
+ 23: "tokenGroupMember",
139
+ 25: "scaledUiAmountConfig",
140
+ 26: "pausableConfig",
141
+ 27: "pausableAccount",
142
+ };
143
+ const extensionTypes = extCodes.map((c) => extMap[c] || String(c));
144
+ return ok(programIdStr, "getAccountDataSize", { mint, extensionTypes });
145
+ }
146
+ } catch {}
147
+
148
+ // Unknown SPL token instruction
149
+ return null;
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+
155
+ // Local base58 decode to avoid importing from sibling file
156
+ const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
157
+ const BASE = BigInt(ALPHABET.length);
158
+ function bs58decode(str: string): Uint8Array {
159
+ let num = 0n;
160
+ for (const char of str) {
161
+ const index = ALPHABET.indexOf(char);
162
+ if (index === -1) throw new Error("Invalid base58 character");
163
+ num = num * BASE + BigInt(index);
164
+ }
165
+ const bytes: number[] = [];
166
+ while (num > 0n) {
167
+ bytes.unshift(Number(num % 256n));
168
+ num = num / 256n;
169
+ }
170
+ for (let i = 0; i < str.length && str[i] === "1"; i++) bytes.unshift(0);
171
+ return new Uint8Array(bytes.length > 0 ? bytes : [0]);
172
+ }