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 +1 -1
- package/scripts/decode-b58.ts +6 -0
- package/server/lib/instruction-parser.ts +264 -0
- package/server/lib/parsers/spl-associated-token-account.ts +44 -0
- package/server/lib/parsers/spl-token.ts +172 -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 +248 -57
- package/server/rpc-server.ts +104 -67
- 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
package/package.json
CHANGED
|
@@ -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
|
+
}
|