solforge 0.2.12 → 0.2.14
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 -5
- package/start.cjs +19 -23
- package/docs/API.md +0 -379
- package/docs/CONFIGURATION.md +0 -407
- package/docs/bun-single-file-executable.md +0 -585
- package/docs/cli-plan.md +0 -154
- package/docs/data-indexing-plan.md +0 -214
- package/docs/gui-roadmap.md +0 -202
- package/scripts/decode-b58.ts +0 -10
- package/scripts/install.sh +0 -112
- package/server/index.ts +0 -5
- package/server/lib/base58.ts +0 -33
- package/server/lib/faucet.ts +0 -110
- package/server/lib/instruction-parser.ts +0 -328
- package/server/lib/parsers/spl-associated-token-account.ts +0 -50
- package/server/lib/parsers/spl-token.ts +0 -340
- package/server/lib/spl-token.ts +0 -57
- package/server/methods/TEMPLATE.md +0 -117
- package/server/methods/account/get-account-info.ts +0 -86
- package/server/methods/account/get-balance.ts +0 -23
- package/server/methods/account/get-multiple-accounts.ts +0 -84
- package/server/methods/account/get-parsed-account-info.ts +0 -17
- package/server/methods/account/index.ts +0 -12
- package/server/methods/account/parsers/index.ts +0 -52
- package/server/methods/account/parsers/loader-upgradeable.ts +0 -79
- package/server/methods/account/parsers/spl-token.ts +0 -256
- package/server/methods/account/parsers/system.ts +0 -4
- package/server/methods/account/request-airdrop.ts +0 -271
- package/server/methods/admin/adopt-mint-authority.ts +0 -94
- package/server/methods/admin/clone-program-accounts.ts +0 -55
- package/server/methods/admin/clone-program.ts +0 -152
- package/server/methods/admin/clone-token-accounts.ts +0 -117
- package/server/methods/admin/clone-token-mint.ts +0 -82
- package/server/methods/admin/create-mint.ts +0 -114
- package/server/methods/admin/create-token-account.ts +0 -137
- package/server/methods/admin/helpers.ts +0 -70
- package/server/methods/admin/index.ts +0 -10
- package/server/methods/admin/list-mints.ts +0 -21
- package/server/methods/admin/load-program.ts +0 -52
- package/server/methods/admin/mint-to.ts +0 -266
- package/server/methods/block/get-block-height.ts +0 -5
- package/server/methods/block/get-block.ts +0 -31
- package/server/methods/block/get-blocks-with-limit.ts +0 -19
- package/server/methods/block/get-latest-blockhash.ts +0 -12
- package/server/methods/block/get-slot.ts +0 -5
- package/server/methods/block/index.ts +0 -6
- package/server/methods/block/is-blockhash-valid.ts +0 -19
- package/server/methods/epoch/get-cluster-nodes.ts +0 -17
- package/server/methods/epoch/get-epoch-info.ts +0 -16
- package/server/methods/epoch/get-epoch-schedule.ts +0 -15
- package/server/methods/epoch/get-highest-snapshot-slot.ts +0 -9
- package/server/methods/epoch/get-leader-schedule.ts +0 -8
- package/server/methods/epoch/get-max-retransmit-slot.ts +0 -9
- package/server/methods/epoch/get-max-shred-insert-slot.ts +0 -9
- package/server/methods/epoch/get-slot-leader.ts +0 -6
- package/server/methods/epoch/get-slot-leaders.ts +0 -9
- package/server/methods/epoch/get-stake-activation.ts +0 -9
- package/server/methods/epoch/get-stake-minimum-delegation.ts +0 -9
- package/server/methods/epoch/get-vote-accounts.ts +0 -19
- package/server/methods/epoch/index.ts +0 -13
- package/server/methods/epoch/minimum-ledger-slot.ts +0 -5
- package/server/methods/fee/get-fee-calculator-for-blockhash.ts +0 -12
- package/server/methods/fee/get-fee-for-message.ts +0 -8
- package/server/methods/fee/get-fee-rate-governor.ts +0 -16
- package/server/methods/fee/get-fees.ts +0 -14
- package/server/methods/fee/get-recent-prioritization-fees.ts +0 -22
- package/server/methods/fee/index.ts +0 -5
- package/server/methods/get-address-lookup-table.ts +0 -27
- package/server/methods/index.ts +0 -265
- package/server/methods/performance/get-recent-performance-samples.ts +0 -25
- package/server/methods/performance/get-transaction-count.ts +0 -5
- package/server/methods/performance/index.ts +0 -2
- package/server/methods/program/get-block-commitment.ts +0 -9
- package/server/methods/program/get-block-production.ts +0 -14
- package/server/methods/program/get-block-time.ts +0 -21
- package/server/methods/program/get-blocks.ts +0 -11
- package/server/methods/program/get-first-available-block.ts +0 -9
- package/server/methods/program/get-genesis-hash.ts +0 -6
- package/server/methods/program/get-identity.ts +0 -6
- package/server/methods/program/get-inflation-governor.ts +0 -15
- package/server/methods/program/get-inflation-rate.ts +0 -10
- package/server/methods/program/get-inflation-reward.ts +0 -12
- package/server/methods/program/get-largest-accounts.ts +0 -8
- package/server/methods/program/get-parsed-program-accounts.ts +0 -12
- package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +0 -12
- package/server/methods/program/get-parsed-token-accounts-by-owner.ts +0 -12
- package/server/methods/program/get-program-accounts.ts +0 -221
- package/server/methods/program/get-supply.ts +0 -13
- package/server/methods/program/get-token-account-balance.ts +0 -60
- package/server/methods/program/get-token-accounts-by-delegate.ts +0 -82
- package/server/methods/program/get-token-accounts-by-owner.ts +0 -416
- package/server/methods/program/get-token-largest-accounts.ts +0 -81
- package/server/methods/program/get-token-supply.ts +0 -39
- package/server/methods/program/index.ts +0 -21
- package/server/methods/solforge/index.ts +0 -158
- package/server/methods/system/get-health.ts +0 -5
- package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +0 -13
- package/server/methods/system/get-version.ts +0 -9
- package/server/methods/system/index.ts +0 -3
- package/server/methods/transaction/get-confirmed-transaction.ts +0 -11
- package/server/methods/transaction/get-parsed-transaction.ts +0 -17
- package/server/methods/transaction/get-signature-statuses.ts +0 -79
- package/server/methods/transaction/get-signatures-for-address.ts +0 -41
- package/server/methods/transaction/get-transaction.ts +0 -639
- package/server/methods/transaction/index.ts +0 -7
- package/server/methods/transaction/inner-instructions.test.ts +0 -104
- package/server/methods/transaction/send-transaction.ts +0 -469
- package/server/methods/transaction/simulate-transaction.ts +0 -57
- package/server/rpc-server.ts +0 -521
- package/server/types.ts +0 -109
- package/server/ws-server.ts +0 -178
- package/src/api-server-entry.ts +0 -109
- package/src/cli/bootstrap.ts +0 -67
- package/src/cli/commands/airdrop.ts +0 -37
- package/src/cli/commands/config.ts +0 -39
- package/src/cli/commands/mint.ts +0 -187
- package/src/cli/commands/program-clone.ts +0 -122
- package/src/cli/commands/program-load.ts +0 -64
- package/src/cli/commands/rpc-start.ts +0 -49
- package/src/cli/commands/token-adopt-authority.ts +0 -37
- package/src/cli/commands/token-clone.ts +0 -112
- package/src/cli/commands/token-create.ts +0 -81
- package/src/cli/main.ts +0 -158
- package/src/cli/run-solforge.ts +0 -112
- package/src/cli/setup-utils.ts +0 -54
- package/src/cli/setup-wizard.ts +0 -258
- package/src/cli/utils/args.ts +0 -15
- package/src/commands/add-program.ts +0 -333
- package/src/commands/init.ts +0 -122
- package/src/commands/list.ts +0 -136
- package/src/commands/mint.ts +0 -287
- package/src/commands/start.ts +0 -881
- package/src/commands/status.ts +0 -99
- package/src/commands/stop.ts +0 -405
- package/src/config/index.ts +0 -146
- package/src/config/manager.ts +0 -157
- package/src/db/index.ts +0 -83
- package/src/db/schema/accounts.ts +0 -23
- package/src/db/schema/address-signatures.ts +0 -31
- package/src/db/schema/index.ts +0 -6
- package/src/db/schema/meta-kv.ts +0 -9
- package/src/db/schema/transactions.ts +0 -36
- package/src/db/schema/tx-account-states.ts +0 -23
- package/src/db/schema/tx-accounts.ts +0 -33
- package/src/db/tx-store.ts +0 -264
- package/src/gui/public/app.css +0 -1556
- package/src/gui/public/build/main.css +0 -1569
- package/src/gui/public/build/main.js +0 -303
- package/src/gui/public/build/main.js.txt +0 -231
- package/src/gui/public/index.html +0 -19
- package/src/gui/server.ts +0 -296
- package/src/gui/src/api.ts +0 -127
- package/src/gui/src/app.tsx +0 -441
- package/src/gui/src/components/airdrop-mint-form.tsx +0 -246
- package/src/gui/src/components/clone-program-modal.tsx +0 -202
- package/src/gui/src/components/clone-token-modal.tsx +0 -230
- package/src/gui/src/components/modal.tsx +0 -134
- package/src/gui/src/components/programs-panel.tsx +0 -124
- package/src/gui/src/components/status-panel.tsx +0 -136
- package/src/gui/src/components/tokens-panel.tsx +0 -122
- package/src/gui/src/hooks/use-interval.ts +0 -17
- package/src/gui/src/index.css +0 -557
- package/src/gui/src/main.tsx +0 -17
- package/src/index.ts +0 -216
- package/src/migrations-bundled.ts +0 -23
- package/src/rpc/start.ts +0 -44
- package/src/services/api-server.ts +0 -504
- package/src/services/port-manager.ts +0 -174
- package/src/services/process-registry.ts +0 -153
- package/src/services/program-cloner.ts +0 -317
- package/src/services/token-cloner.ts +0 -811
- package/src/services/validator.ts +0 -293
- package/src/types/config.ts +0 -110
- package/src/utils/shell.ts +0 -110
- package/src/utils/token-loader.ts +0 -115
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
import type { PublicKey, TransactionInstruction } from "@solana/web3.js";
|
|
2
|
-
import {
|
|
3
|
-
decodeMintToCheckedInstruction,
|
|
4
|
-
decodeTransferInstruction,
|
|
5
|
-
decodeTransferCheckedInstruction,
|
|
6
|
-
decodeInitializeAccount3Instruction,
|
|
7
|
-
decodeInitializeImmutableOwnerInstruction,
|
|
8
|
-
decodeTransferCheckedInstructionUnchecked,
|
|
9
|
-
decodeTransferInstructionUnchecked,
|
|
10
|
-
decodeInitializeAccount3InstructionUnchecked,
|
|
11
|
-
decodeInitializeImmutableOwnerInstructionUnchecked,
|
|
12
|
-
} from "@solana/spl-token";
|
|
13
|
-
import { u8 } from "@solana/buffer-layout";
|
|
14
|
-
import { PublicKey as PK } from "@solana/web3.js";
|
|
15
|
-
import {
|
|
16
|
-
TOKEN_PROGRAM_ID as TOKEN_PROGRAM_V1,
|
|
17
|
-
TOKEN_2022_PROGRAM_ID as TOKEN_PROGRAM_2022,
|
|
18
|
-
getAssociatedTokenAddressSync,
|
|
19
|
-
} from "@solana/spl-token";
|
|
20
|
-
|
|
21
|
-
// Keep shape compatible with instruction-parser
|
|
22
|
-
export type ParsedInstruction =
|
|
23
|
-
| {
|
|
24
|
-
program: string;
|
|
25
|
-
programId: string;
|
|
26
|
-
parsed: { type: string; info: unknown };
|
|
27
|
-
}
|
|
28
|
-
| { programId: string; accounts: string[]; data: string };
|
|
29
|
-
|
|
30
|
-
function ok(programId: string, type: string, info: unknown): ParsedInstruction {
|
|
31
|
-
// Use a single label for both SPL v1 and Token-2022 for compatibility with UIs
|
|
32
|
-
return { program: "spl-token", programId, parsed: { type, info } };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function asBase58(pk: PublicKey | undefined): string | undefined {
|
|
36
|
-
try {
|
|
37
|
-
return pk ? pk.toBase58() : undefined;
|
|
38
|
-
} catch {
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function tryParseSplToken(
|
|
44
|
-
ix: TransactionInstruction,
|
|
45
|
-
programIdStr: string,
|
|
46
|
-
_accountKeys: string[],
|
|
47
|
-
dataBase58: string,
|
|
48
|
-
tokenBalanceHints?: Array<{ mint: string; decimals: number }>,
|
|
49
|
-
): ParsedInstruction | null {
|
|
50
|
-
try {
|
|
51
|
-
// Accept both SPL Token and Token-2022 program ids
|
|
52
|
-
// We pass the actual program id to decoders for strict match
|
|
53
|
-
const programPk = new PK(programIdStr);
|
|
54
|
-
// Don't early-return on program id; allow both programs
|
|
55
|
-
|
|
56
|
-
// MintToChecked
|
|
57
|
-
try {
|
|
58
|
-
const m = decodeMintToCheckedInstruction(ix, programPk);
|
|
59
|
-
const amount = m.data.amount;
|
|
60
|
-
const decimals = m.data.decimals;
|
|
61
|
-
const amtStr =
|
|
62
|
-
typeof amount === "bigint" ? amount.toString() : String(amount);
|
|
63
|
-
const base = BigInt(10) ** BigInt(decimals);
|
|
64
|
-
const whole = BigInt(amtStr) / base;
|
|
65
|
-
const frac = BigInt(amtStr) % base;
|
|
66
|
-
const fracStr = frac
|
|
67
|
-
.toString()
|
|
68
|
-
.padStart(decimals, "0")
|
|
69
|
-
.replace(/0+$/, "");
|
|
70
|
-
const uiStr = fracStr.length ? `${whole}.${fracStr}` : `${whole}`;
|
|
71
|
-
return ok(programIdStr, "mintToChecked", {
|
|
72
|
-
account: asBase58(m.keys.destination.pubkey),
|
|
73
|
-
mint: asBase58(m.keys.mint.pubkey),
|
|
74
|
-
mintAuthority: asBase58(m.keys.authority.pubkey),
|
|
75
|
-
tokenAmount: {
|
|
76
|
-
amount: amtStr,
|
|
77
|
-
decimals,
|
|
78
|
-
uiAmount: Number(uiStr),
|
|
79
|
-
uiAmountString: uiStr,
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
} catch {}
|
|
83
|
-
|
|
84
|
-
// Transfer / TransferChecked (strict)
|
|
85
|
-
try {
|
|
86
|
-
const t = decodeTransferInstruction(ix, programPk);
|
|
87
|
-
const amt = t.data.amount;
|
|
88
|
-
return ok(programIdStr, "transfer", {
|
|
89
|
-
amount: typeof amt === "bigint" ? amt.toString() : String(amt),
|
|
90
|
-
source: asBase58(t.keys.source.pubkey),
|
|
91
|
-
destination: asBase58(t.keys.destination.pubkey),
|
|
92
|
-
authority: asBase58(t.keys.owner.pubkey),
|
|
93
|
-
});
|
|
94
|
-
} catch {}
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
const t = decodeTransferCheckedInstruction(ix, programPk);
|
|
98
|
-
const amt = t.data.amount;
|
|
99
|
-
const decimals = t.data.decimals;
|
|
100
|
-
return ok(programIdStr, "transferChecked", {
|
|
101
|
-
tokenAmount: {
|
|
102
|
-
amount: typeof amt === "bigint" ? amt.toString() : String(amt),
|
|
103
|
-
decimals,
|
|
104
|
-
},
|
|
105
|
-
source: asBase58(t.keys.source.pubkey),
|
|
106
|
-
destination: asBase58(t.keys.destination.pubkey),
|
|
107
|
-
authority: asBase58(t.keys.owner.pubkey),
|
|
108
|
-
mint: asBase58(t.keys.mint.pubkey),
|
|
109
|
-
});
|
|
110
|
-
} catch {}
|
|
111
|
-
|
|
112
|
-
// InitializeAccount3 (strict)
|
|
113
|
-
try {
|
|
114
|
-
const a = decodeInitializeAccount3Instruction(ix, programPk);
|
|
115
|
-
return ok(programIdStr, "initializeAccount3", {
|
|
116
|
-
account: asBase58(a.keys.account.pubkey),
|
|
117
|
-
mint: asBase58(a.keys.mint.pubkey),
|
|
118
|
-
owner: asBase58(a.data.owner),
|
|
119
|
-
});
|
|
120
|
-
} catch {}
|
|
121
|
-
|
|
122
|
-
// InitializeImmutableOwner
|
|
123
|
-
try {
|
|
124
|
-
const im = decodeInitializeImmutableOwnerInstruction(ix, programPk);
|
|
125
|
-
return ok(programIdStr, "initializeImmutableOwner", {
|
|
126
|
-
account: asBase58(im.keys.account.pubkey),
|
|
127
|
-
});
|
|
128
|
-
} catch {}
|
|
129
|
-
|
|
130
|
-
// GetAccountDataSize: decode extension types (u16 little-endian sequence)
|
|
131
|
-
try {
|
|
132
|
-
const bytes = Buffer.from(bs58decode(dataBase58));
|
|
133
|
-
const t = u8().decode(bytes);
|
|
134
|
-
// 21 corresponds to TokenInstruction.GetAccountDataSize
|
|
135
|
-
if (t === 21) {
|
|
136
|
-
const mint = asBase58(ix.keys[0]?.pubkey);
|
|
137
|
-
const extCodes: number[] = [];
|
|
138
|
-
for (let i = 1; i + 1 < bytes.length; i += 2) {
|
|
139
|
-
const code = bytes[i] | (bytes[i + 1] << 8);
|
|
140
|
-
extCodes.push(code);
|
|
141
|
-
}
|
|
142
|
-
const extMap: Record<number, string> = {
|
|
143
|
-
7: "immutableOwner",
|
|
144
|
-
8: "memoTransfer",
|
|
145
|
-
9: "nonTransferable",
|
|
146
|
-
12: "permanentDelegate",
|
|
147
|
-
14: "transferHook",
|
|
148
|
-
15: "transferHookAccount",
|
|
149
|
-
18: "metadataPointer",
|
|
150
|
-
19: "tokenMetadata",
|
|
151
|
-
20: "groupPointer",
|
|
152
|
-
21: "tokenGroup",
|
|
153
|
-
22: "groupMemberPointer",
|
|
154
|
-
23: "tokenGroupMember",
|
|
155
|
-
25: "scaledUiAmountConfig",
|
|
156
|
-
26: "pausableConfig",
|
|
157
|
-
27: "pausableAccount",
|
|
158
|
-
};
|
|
159
|
-
const extensionTypes = extCodes.map((c) => extMap[c] || String(c));
|
|
160
|
-
return ok(programIdStr, "getAccountDataSize", { mint, extensionTypes });
|
|
161
|
-
}
|
|
162
|
-
} catch {}
|
|
163
|
-
|
|
164
|
-
// Unchecked fallbacks: decode data fields even if keys/validation missing
|
|
165
|
-
try {
|
|
166
|
-
const raw = bs58decode(dataBase58);
|
|
167
|
-
const op = raw[0];
|
|
168
|
-
// Transfer
|
|
169
|
-
if (op === 3) {
|
|
170
|
-
const t = decodeTransferInstructionUnchecked(ix);
|
|
171
|
-
const amt = t.data.amount;
|
|
172
|
-
return ok(programIdStr, "transfer", {
|
|
173
|
-
amount: typeof amt === "bigint" ? amt.toString() : String(amt),
|
|
174
|
-
source: asBase58(t.keys.source?.pubkey),
|
|
175
|
-
destination: asBase58(t.keys.destination?.pubkey),
|
|
176
|
-
authority: asBase58(t.keys.owner?.pubkey),
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
// TransferChecked
|
|
180
|
-
if (op === 12) {
|
|
181
|
-
const t = decodeTransferCheckedInstructionUnchecked(ix);
|
|
182
|
-
const amt = t.data.amount;
|
|
183
|
-
const decimals = t.data.decimals;
|
|
184
|
-
const hintMint = (() => {
|
|
185
|
-
try {
|
|
186
|
-
const dec = Number(decimals);
|
|
187
|
-
const candidates = (tokenBalanceHints || []).filter(
|
|
188
|
-
(h) => Number(h.decimals) === dec,
|
|
189
|
-
);
|
|
190
|
-
if (candidates.length === 1) return candidates[0].mint;
|
|
191
|
-
// Prefer non-zero decimals over 0 (filters out native 4uQe mint in many cases)
|
|
192
|
-
const nonZero = candidates.filter((c) => c.decimals > 0);
|
|
193
|
-
if (nonZero.length === 1) return nonZero[0].mint;
|
|
194
|
-
// Fall back to first candidate if multiple
|
|
195
|
-
return candidates[0]?.mint;
|
|
196
|
-
} catch {
|
|
197
|
-
return undefined;
|
|
198
|
-
}
|
|
199
|
-
})();
|
|
200
|
-
return ok(programIdStr, "transferChecked", {
|
|
201
|
-
tokenAmount: {
|
|
202
|
-
amount: typeof amt === "bigint" ? amt.toString() : String(amt),
|
|
203
|
-
decimals,
|
|
204
|
-
},
|
|
205
|
-
source: asBase58(t.keys.source?.pubkey),
|
|
206
|
-
destination: asBase58(t.keys.destination?.pubkey),
|
|
207
|
-
authority: asBase58(t.keys.owner?.pubkey),
|
|
208
|
-
mint: asBase58(t.keys.mint?.pubkey) || hintMint,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
// InitializeAccount3
|
|
212
|
-
if (op === 18) {
|
|
213
|
-
const a = decodeInitializeAccount3InstructionUnchecked(ix);
|
|
214
|
-
const hintMint = (() => {
|
|
215
|
-
try {
|
|
216
|
-
// Prefer single non-zero-decimals mint in this tx
|
|
217
|
-
const nonZero = (tokenBalanceHints || []).filter(
|
|
218
|
-
(h) => h.decimals > 0,
|
|
219
|
-
);
|
|
220
|
-
if (nonZero.length === 1) return nonZero[0].mint;
|
|
221
|
-
// Fall back to first available mint
|
|
222
|
-
return (tokenBalanceHints || [])[0]?.mint;
|
|
223
|
-
} catch {
|
|
224
|
-
return undefined;
|
|
225
|
-
}
|
|
226
|
-
})();
|
|
227
|
-
const ownerStr = asBase58(a.data.owner);
|
|
228
|
-
const mintStr = asBase58(a.keys.mint?.pubkey) || hintMint;
|
|
229
|
-
let accountStr = asBase58(a.keys.account?.pubkey);
|
|
230
|
-
try {
|
|
231
|
-
if (!accountStr && ownerStr && mintStr) {
|
|
232
|
-
const ownerPk = new PK(ownerStr);
|
|
233
|
-
const mintPk = new PK(mintStr);
|
|
234
|
-
const programId =
|
|
235
|
-
programIdStr === TOKEN_PROGRAM_2022.toBase58()
|
|
236
|
-
? TOKEN_PROGRAM_2022
|
|
237
|
-
: TOKEN_PROGRAM_V1;
|
|
238
|
-
const ata = getAssociatedTokenAddressSync(
|
|
239
|
-
mintPk,
|
|
240
|
-
ownerPk,
|
|
241
|
-
true,
|
|
242
|
-
programId,
|
|
243
|
-
);
|
|
244
|
-
accountStr = ata.toBase58();
|
|
245
|
-
}
|
|
246
|
-
} catch {}
|
|
247
|
-
return ok(programIdStr, "initializeAccount3", {
|
|
248
|
-
account: accountStr,
|
|
249
|
-
mint: mintStr,
|
|
250
|
-
owner: ownerStr,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
// InitializeImmutableOwner
|
|
254
|
-
if (op === 22) {
|
|
255
|
-
const im = decodeInitializeImmutableOwnerInstructionUnchecked(ix);
|
|
256
|
-
return ok(programIdStr, "initializeImmutableOwner", {
|
|
257
|
-
account: asBase58(im.keys.account?.pubkey),
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
} catch {}
|
|
261
|
-
|
|
262
|
-
// Fallback: classify by TokenInstruction opcode (first byte) when nothing else matched
|
|
263
|
-
try {
|
|
264
|
-
const raw = bs58decode(dataBase58);
|
|
265
|
-
if (raw.length > 0) {
|
|
266
|
-
const op = raw[0];
|
|
267
|
-
const map: Record<number, string> = {
|
|
268
|
-
0: "initializeMint",
|
|
269
|
-
1: "initializeAccount",
|
|
270
|
-
2: "initializeMultisig",
|
|
271
|
-
3: "transfer",
|
|
272
|
-
4: "approve",
|
|
273
|
-
5: "revoke",
|
|
274
|
-
6: "setAuthority",
|
|
275
|
-
7: "mintTo",
|
|
276
|
-
8: "burn",
|
|
277
|
-
9: "closeAccount",
|
|
278
|
-
10: "freezeAccount",
|
|
279
|
-
11: "thawAccount",
|
|
280
|
-
12: "transferChecked",
|
|
281
|
-
13: "approveChecked",
|
|
282
|
-
14: "mintToChecked",
|
|
283
|
-
15: "burnChecked",
|
|
284
|
-
16: "initializeAccount2",
|
|
285
|
-
17: "syncNative",
|
|
286
|
-
18: "initializeAccount3",
|
|
287
|
-
19: "initializeMultisig2",
|
|
288
|
-
20: "initializeMint2",
|
|
289
|
-
21: "getAccountDataSize",
|
|
290
|
-
22: "initializeImmutableOwner",
|
|
291
|
-
23: "amountToUiAmount",
|
|
292
|
-
24: "uiAmountToAmount",
|
|
293
|
-
25: "initializeMintCloseAuthority",
|
|
294
|
-
26: "transferFeeExtension",
|
|
295
|
-
27: "confidentialTransferExtension",
|
|
296
|
-
28: "defaultAccountStateExtension",
|
|
297
|
-
29: "reallocate",
|
|
298
|
-
30: "memoTransferExtension",
|
|
299
|
-
31: "createNativeMint",
|
|
300
|
-
32: "initializeNonTransferableMint",
|
|
301
|
-
33: "interestBearingMintExtension",
|
|
302
|
-
34: "cpiGuardExtension",
|
|
303
|
-
35: "initializePermanentDelegate",
|
|
304
|
-
36: "transferHookExtension",
|
|
305
|
-
39: "metadataPointerExtension",
|
|
306
|
-
40: "groupPointerExtension",
|
|
307
|
-
41: "groupMemberPointerExtension",
|
|
308
|
-
43: "scaledUiAmountExtension",
|
|
309
|
-
44: "pausableExtension",
|
|
310
|
-
};
|
|
311
|
-
const type = map[op];
|
|
312
|
-
if (type) return ok(programIdStr, type, {});
|
|
313
|
-
}
|
|
314
|
-
} catch {}
|
|
315
|
-
|
|
316
|
-
// Unknown SPL token instruction (unrecognized opcode)
|
|
317
|
-
return null;
|
|
318
|
-
} catch {
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Local base58 decode to avoid importing from sibling file
|
|
324
|
-
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
325
|
-
const BASE = BigInt(ALPHABET.length);
|
|
326
|
-
function bs58decode(str: string): Uint8Array {
|
|
327
|
-
let num = 0n;
|
|
328
|
-
for (const char of str) {
|
|
329
|
-
const index = ALPHABET.indexOf(char);
|
|
330
|
-
if (index === -1) throw new Error("Invalid base58 character");
|
|
331
|
-
num = num * BASE + BigInt(index);
|
|
332
|
-
}
|
|
333
|
-
const bytes: number[] = [];
|
|
334
|
-
while (num > 0n) {
|
|
335
|
-
bytes.unshift(Number(num % 256n));
|
|
336
|
-
num = num / 256n;
|
|
337
|
-
}
|
|
338
|
-
for (let i = 0; i < str.length && str[i] === "1"; i++) bytes.unshift(0);
|
|
339
|
-
return new Uint8Array(bytes.length > 0 ? bytes : [0]);
|
|
340
|
-
}
|
package/server/lib/spl-token.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { PublicKey } from "@solana/web3.js";
|
|
2
|
-
|
|
3
|
-
export const TOKEN_PROGRAM_ID = new PublicKey(
|
|
4
|
-
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
|
5
|
-
);
|
|
6
|
-
|
|
7
|
-
// Minimal SPL Token v1 layouts
|
|
8
|
-
export type DecodedMint = {
|
|
9
|
-
supply: bigint;
|
|
10
|
-
decimals: number;
|
|
11
|
-
isInitialized: boolean;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type DecodedTokenAccount = {
|
|
15
|
-
mint: string;
|
|
16
|
-
owner: string;
|
|
17
|
-
amount: bigint;
|
|
18
|
-
state: number;
|
|
19
|
-
delegatedAmount: bigint;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export function decodeMint(data: Uint8Array): DecodedMint | null {
|
|
23
|
-
if (!data || data.length < 82) return null;
|
|
24
|
-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
25
|
-
// supply at offset 36? Common layout: 4 (opt) + 32 (auth) + 8 (supply) + 1 (dec) + 1 (init)
|
|
26
|
-
const supply = view.getBigUint64(36, true);
|
|
27
|
-
const decimals = view.getUint8(44);
|
|
28
|
-
const isInitialized = view.getUint8(45) !== 0;
|
|
29
|
-
return { supply, decimals, isInitialized };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function decodeTokenAccount(
|
|
33
|
-
data: Uint8Array,
|
|
34
|
-
): DecodedTokenAccount | null {
|
|
35
|
-
if (!data || data.length < 165) return null;
|
|
36
|
-
const mint = new PublicKey(data.slice(0, 32)).toBase58();
|
|
37
|
-
const owner = new PublicKey(data.slice(32, 64)).toBase58();
|
|
38
|
-
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
39
|
-
const amount = view.getBigUint64(64, true);
|
|
40
|
-
const state = data[108];
|
|
41
|
-
const delegatedAmount = view.getBigUint64(124, true);
|
|
42
|
-
return { mint, owner, amount, state, delegatedAmount };
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function formatUiAmount(amount: bigint, decimals: number) {
|
|
46
|
-
const base = BigInt(10) ** BigInt(decimals);
|
|
47
|
-
const whole = amount / base;
|
|
48
|
-
const frac = amount % base;
|
|
49
|
-
const fracStr = frac.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
50
|
-
const ui = fracStr.length > 0 ? `${whole}.${fracStr}` : `${whole}`;
|
|
51
|
-
return {
|
|
52
|
-
amount: amount.toString(),
|
|
53
|
-
decimals,
|
|
54
|
-
uiAmount: Number(ui),
|
|
55
|
-
uiAmountString: ui,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# RPC Method Template
|
|
2
|
-
|
|
3
|
-
Use this template when adding new RPC methods to SolForge.
|
|
4
|
-
|
|
5
|
-
## File Template
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
import { PublicKey } from "@solana/web3.js"; // Only if needed
|
|
9
|
-
import type { RpcMethodHandler } from "../types"; // Adjust path based on location
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Implements the methodName RPC method
|
|
13
|
-
* @see https://docs.solana.com/api/http#methodname
|
|
14
|
-
*/
|
|
15
|
-
export const methodName: RpcMethodHandler = (id, params, context) => {
|
|
16
|
-
// 1. Parse parameters
|
|
17
|
-
const [param1, param2, config] = params;
|
|
18
|
-
|
|
19
|
-
// 2. Validate parameters (if needed)
|
|
20
|
-
if (!param1) {
|
|
21
|
-
return context.createErrorResponse(
|
|
22
|
-
id,
|
|
23
|
-
-32602,
|
|
24
|
-
"Missing required parameter: param1"
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
// 3. Execute the RPC logic
|
|
30
|
-
const result = context.svm.someOperation();
|
|
31
|
-
|
|
32
|
-
// 4. Format and return the response
|
|
33
|
-
return context.createSuccessResponse(id, {
|
|
34
|
-
context: {
|
|
35
|
-
slot: Number(context.slot),
|
|
36
|
-
apiVersion: "1.18.0" // If needed
|
|
37
|
-
},
|
|
38
|
-
value: result
|
|
39
|
-
});
|
|
40
|
-
} catch (error: any) {
|
|
41
|
-
// 5. Handle errors appropriately
|
|
42
|
-
return context.createErrorResponse(
|
|
43
|
-
id,
|
|
44
|
-
-32603, // Use appropriate error code
|
|
45
|
-
"Operation failed",
|
|
46
|
-
error.message
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Common Patterns
|
|
53
|
-
|
|
54
|
-
### Methods that modify state
|
|
55
|
-
```typescript
|
|
56
|
-
// After successful operation, increment slot/blockHeight
|
|
57
|
-
if (success) {
|
|
58
|
-
// This is handled in the main server now
|
|
59
|
-
// Just return success response
|
|
60
|
-
return context.createSuccessResponse(id, signature);
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Methods with optional config
|
|
65
|
-
```typescript
|
|
66
|
-
const [address, config] = params;
|
|
67
|
-
const encoding = config?.encoding || "base64";
|
|
68
|
-
const commitment = config?.commitment || "confirmed";
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Batch operations
|
|
72
|
-
```typescript
|
|
73
|
-
const results = addresses.map(addr => {
|
|
74
|
-
try {
|
|
75
|
-
// Process each item
|
|
76
|
-
return processItem(addr);
|
|
77
|
-
} catch {
|
|
78
|
-
// Return null for failed items
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## Error Codes Reference
|
|
85
|
-
|
|
86
|
-
- `-32700`: Parse error (JSON parsing failed)
|
|
87
|
-
- `-32600`: Invalid request (not a valid JSON-RPC request)
|
|
88
|
-
- `-32601`: Method not found
|
|
89
|
-
- `-32602`: Invalid params
|
|
90
|
-
- `-32603`: Internal error
|
|
91
|
-
- `-32000`: Generic server error
|
|
92
|
-
- `-32001`: Resource not found
|
|
93
|
-
- `-32002`: Resource unavailable
|
|
94
|
-
- `-32003`: Transaction rejected
|
|
95
|
-
- `-32004`: Method not supported
|
|
96
|
-
- `-32005`: Limit exceeded
|
|
97
|
-
|
|
98
|
-
## Checklist
|
|
99
|
-
|
|
100
|
-
Before committing a new RPC method:
|
|
101
|
-
|
|
102
|
-
- [ ] Method follows naming convention (camelCase)
|
|
103
|
-
- [ ] File uses kebab-case naming
|
|
104
|
-
- [ ] Proper TypeScript types used
|
|
105
|
-
- [ ] Error handling implemented
|
|
106
|
-
- [ ] Success case tested
|
|
107
|
-
- [ ] Error cases tested
|
|
108
|
-
- [ ] Added to methods/index.ts
|
|
109
|
-
- [ ] Documentation updated in README.md
|
|
110
|
-
- [ ] Follows SolForge patterns
|
|
111
|
-
|
|
112
|
-
## Examples in Codebase
|
|
113
|
-
|
|
114
|
-
- Simple query: See `get-balance.ts`
|
|
115
|
-
- Complex response: See `get-account-info.ts`
|
|
116
|
-
- Transaction handling: See `send-transaction.ts`
|
|
117
|
-
- Batch operation: See `get-multiple-accounts.ts`
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { PublicKey } from "@solana/web3.js";
|
|
2
|
-
import type { RpcMethodHandler } from "../../types";
|
|
3
|
-
import { parseAccountJson } from "./parsers";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Implements the getAccountInfo RPC method
|
|
7
|
-
* @see https://docs.solana.com/api/http#getaccountinfo
|
|
8
|
-
*/
|
|
9
|
-
export const getAccountInfo: RpcMethodHandler = async (id, params, context) => {
|
|
10
|
-
const [pubkeyStr, config] = params;
|
|
11
|
-
const encoding = config?.encoding || "base64";
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const pubkey = new PublicKey(pubkeyStr);
|
|
15
|
-
const account = context.svm.getAccount(pubkey);
|
|
16
|
-
|
|
17
|
-
if (!account) {
|
|
18
|
-
return context.createSuccessResponse(id, {
|
|
19
|
-
context: { slot: Number(context.slot) },
|
|
20
|
-
value: null,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const owner = new PublicKey(account.owner).toBase58();
|
|
25
|
-
// Opportunistic index update
|
|
26
|
-
try {
|
|
27
|
-
await context.store?.upsertAccounts([
|
|
28
|
-
{
|
|
29
|
-
address: pubkey.toBase58(),
|
|
30
|
-
lamports: Number(account.lamports || 0n),
|
|
31
|
-
ownerProgram: owner,
|
|
32
|
-
executable: !!account.executable,
|
|
33
|
-
rentEpoch: Number(account.rentEpoch || 0),
|
|
34
|
-
dataLen: account.data?.length ?? 0,
|
|
35
|
-
dataBase64: undefined,
|
|
36
|
-
lastSlot: Number(context.slot),
|
|
37
|
-
},
|
|
38
|
-
]);
|
|
39
|
-
} catch {}
|
|
40
|
-
|
|
41
|
-
if (encoding === "jsonParsed") {
|
|
42
|
-
const parsed = parseAccountJson(
|
|
43
|
-
pubkey,
|
|
44
|
-
{
|
|
45
|
-
owner: new PublicKey(account.owner),
|
|
46
|
-
data: account.data ? new Uint8Array(account.data) : new Uint8Array(),
|
|
47
|
-
lamports: account.lamports,
|
|
48
|
-
executable: account.executable,
|
|
49
|
-
rentEpoch: account.rentEpoch,
|
|
50
|
-
},
|
|
51
|
-
context,
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
return context.createSuccessResponse(id, {
|
|
55
|
-
context: { slot: Number(context.slot) },
|
|
56
|
-
value: {
|
|
57
|
-
lamports: Number(account.lamports),
|
|
58
|
-
owner,
|
|
59
|
-
executable: account.executable,
|
|
60
|
-
rentEpoch: Number(account.rentEpoch || 0),
|
|
61
|
-
data: parsed || {
|
|
62
|
-
program: "unknown",
|
|
63
|
-
parsed: null,
|
|
64
|
-
space: account.data?.length ?? 0,
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const accountInfo = {
|
|
71
|
-
lamports: Number(account.lamports),
|
|
72
|
-
owner,
|
|
73
|
-
data: [Buffer.from(account.data).toString("base64"), "base64"] as const,
|
|
74
|
-
executable: account.executable,
|
|
75
|
-
rentEpoch: Number(account.rentEpoch || 0),
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
return context.createSuccessResponse(id, {
|
|
79
|
-
context: { slot: Number(context.slot) },
|
|
80
|
-
value: accountInfo,
|
|
81
|
-
});
|
|
82
|
-
} catch (error: unknown) {
|
|
83
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
84
|
-
return context.createErrorResponse(id, -32602, "Invalid params", message);
|
|
85
|
-
}
|
|
86
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { PublicKey } from "@solana/web3.js";
|
|
2
|
-
import type { RpcMethodHandler } from "../../types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Implements the getBalance RPC method
|
|
6
|
-
* @see https://docs.solana.com/api/http#getbalance
|
|
7
|
-
*/
|
|
8
|
-
export const getBalance: RpcMethodHandler = (id, params, context) => {
|
|
9
|
-
const [pubkeyStr] = params;
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
const pubkey = new PublicKey(pubkeyStr);
|
|
13
|
-
const balance = context.svm.getBalance(pubkey);
|
|
14
|
-
|
|
15
|
-
return context.createSuccessResponse(id, {
|
|
16
|
-
context: { slot: Number(context.slot) },
|
|
17
|
-
value: Number(balance || 0n),
|
|
18
|
-
});
|
|
19
|
-
} catch (error: unknown) {
|
|
20
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
21
|
-
return context.createErrorResponse(id, -32602, "Invalid params", message);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { PublicKey } from "@solana/web3.js";
|
|
2
|
-
import type { RpcMethodHandler } from "../../types";
|
|
3
|
-
import type { AccountSnapshot } from "../../../src/db/tx-store";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Implements the getMultipleAccounts RPC method
|
|
7
|
-
* @see https://docs.solana.com/api/http#getmultipleaccounts
|
|
8
|
-
*/
|
|
9
|
-
export const getMultipleAccounts: RpcMethodHandler = async (
|
|
10
|
-
id,
|
|
11
|
-
params,
|
|
12
|
-
context,
|
|
13
|
-
) => {
|
|
14
|
-
const [pubkeys, config] = params;
|
|
15
|
-
const encoding = config?.encoding || "base64";
|
|
16
|
-
|
|
17
|
-
const accounts = pubkeys.map((pubkeyStr: string) => {
|
|
18
|
-
try {
|
|
19
|
-
const pubkey = new PublicKey(pubkeyStr);
|
|
20
|
-
const account = context.svm.getAccount(pubkey);
|
|
21
|
-
|
|
22
|
-
if (!account) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const owner = new PublicKey(account.owner).toBase58();
|
|
27
|
-
if (encoding === "jsonParsed") {
|
|
28
|
-
const program =
|
|
29
|
-
owner === "11111111111111111111111111111111" ? "system" : "unknown";
|
|
30
|
-
const space = account.data?.length ?? 0;
|
|
31
|
-
return {
|
|
32
|
-
lamports: Number(account.lamports),
|
|
33
|
-
owner,
|
|
34
|
-
executable: account.executable,
|
|
35
|
-
rentEpoch: Number(account.rentEpoch || 0),
|
|
36
|
-
data: {
|
|
37
|
-
program,
|
|
38
|
-
parsed: program === "system" ? { type: "account", info: {} } : null,
|
|
39
|
-
space,
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
lamports: Number(account.lamports),
|
|
46
|
-
owner,
|
|
47
|
-
data: [Buffer.from(account.data).toString("base64"), "base64"] as const,
|
|
48
|
-
executable: account.executable,
|
|
49
|
-
rentEpoch: Number(account.rentEpoch || 0),
|
|
50
|
-
};
|
|
51
|
-
} catch {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Opportunistic index update
|
|
57
|
-
try {
|
|
58
|
-
const snaps: AccountSnapshot[] = [];
|
|
59
|
-
for (const pubkeyStr of pubkeys) {
|
|
60
|
-
try {
|
|
61
|
-
const pubkey = new PublicKey(pubkeyStr);
|
|
62
|
-
const acc = context.svm.getAccount(pubkey);
|
|
63
|
-
if (!acc) continue;
|
|
64
|
-
const owner = new PublicKey(acc.owner).toBase58();
|
|
65
|
-
snaps.push({
|
|
66
|
-
address: pubkey.toBase58(),
|
|
67
|
-
lamports: Number(acc.lamports || 0n),
|
|
68
|
-
ownerProgram: owner,
|
|
69
|
-
executable: !!acc.executable,
|
|
70
|
-
rentEpoch: Number(acc.rentEpoch || 0),
|
|
71
|
-
dataLen: acc.data?.length ?? 0,
|
|
72
|
-
dataBase64: undefined,
|
|
73
|
-
lastSlot: Number(context.slot),
|
|
74
|
-
});
|
|
75
|
-
} catch {}
|
|
76
|
-
}
|
|
77
|
-
if (snaps.length) await context.store?.upsertAccounts(snaps);
|
|
78
|
-
} catch {}
|
|
79
|
-
|
|
80
|
-
return context.createSuccessResponse(id, {
|
|
81
|
-
context: { slot: Number(context.slot) },
|
|
82
|
-
value: accounts,
|
|
83
|
-
});
|
|
84
|
-
};
|