solforge 0.1.6 → 0.2.0
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/.agi/agi.sqlite +0 -0
- package/.claude/settings.local.json +9 -0
- package/.github/workflows/release-binaries.yml +133 -0
- package/.tmp/.787ebcdbf7b8fde8-00000000.hm +0 -0
- package/.tmp/.bffe6efebdf8aedc-00000000.hm +0 -0
- package/AGENTS.md +271 -0
- package/CLAUDE.md +106 -0
- package/PROJECT_STRUCTURE.md +124 -0
- package/README.md +367 -393
- package/SOLANA_KIT_GUIDE.md +251 -0
- package/SOLFORGE.md +119 -0
- package/biome.json +34 -0
- package/bun.lock +743 -0
- package/docs/bun-single-file-executable.md +585 -0
- package/docs/cli-plan.md +154 -0
- package/docs/data-indexing-plan.md +214 -0
- package/docs/gui-roadmap.md +202 -0
- package/drizzle/0000_friendly_millenium_guard.sql +53 -0
- package/drizzle/0001_stale_sentinels.sql +2 -0
- package/drizzle/meta/0000_snapshot.json +329 -0
- package/drizzle/meta/0001_snapshot.json +345 -0
- package/drizzle/meta/_journal.json +20 -0
- package/drizzle.config.ts +12 -0
- package/index.ts +21 -0
- package/mint.sh +47 -0
- package/package.json +45 -69
- package/postcss.config.js +6 -0
- package/rpc-server.ts.backup +519 -0
- package/server/index.ts +5 -0
- package/server/lib/base58.ts +33 -0
- package/server/lib/faucet.ts +110 -0
- package/server/lib/spl-token.ts +57 -0
- package/server/methods/TEMPLATE.md +117 -0
- package/server/methods/account/get-account-info.ts +90 -0
- package/server/methods/account/get-balance.ts +27 -0
- package/server/methods/account/get-multiple-accounts.ts +83 -0
- package/server/methods/account/get-parsed-account-info.ts +21 -0
- package/server/methods/account/index.ts +12 -0
- package/server/methods/account/parsers/index.ts +52 -0
- package/server/methods/account/parsers/loader-upgradeable.ts +66 -0
- package/server/methods/account/parsers/spl-token.ts +237 -0
- package/server/methods/account/parsers/system.ts +4 -0
- package/server/methods/account/request-airdrop.ts +219 -0
- package/server/methods/admin/adopt-mint-authority.ts +94 -0
- package/server/methods/admin/clone-program-accounts.ts +55 -0
- package/server/methods/admin/clone-program.ts +152 -0
- package/server/methods/admin/clone-token-accounts.ts +117 -0
- package/server/methods/admin/clone-token-mint.ts +82 -0
- package/server/methods/admin/create-mint.ts +114 -0
- package/server/methods/admin/create-token-account.ts +137 -0
- package/server/methods/admin/helpers.ts +70 -0
- package/server/methods/admin/index.ts +10 -0
- package/server/methods/admin/list-mints.ts +21 -0
- package/server/methods/admin/load-program.ts +52 -0
- package/server/methods/admin/mint-to.ts +278 -0
- package/server/methods/block/get-block-height.ts +5 -0
- package/server/methods/block/get-block.ts +35 -0
- package/server/methods/block/get-blocks-with-limit.ts +23 -0
- package/server/methods/block/get-latest-blockhash.ts +12 -0
- package/server/methods/block/get-slot.ts +5 -0
- package/server/methods/block/index.ts +6 -0
- package/server/methods/block/is-blockhash-valid.ts +23 -0
- package/server/methods/epoch/get-cluster-nodes.ts +17 -0
- package/server/methods/epoch/get-epoch-info.ts +16 -0
- package/server/methods/epoch/get-epoch-schedule.ts +15 -0
- package/server/methods/epoch/get-highest-snapshot-slot.ts +9 -0
- package/server/methods/epoch/get-leader-schedule.ts +8 -0
- package/server/methods/epoch/get-max-retransmit-slot.ts +9 -0
- package/server/methods/epoch/get-max-shred-insert-slot.ts +9 -0
- package/server/methods/epoch/get-slot-leader.ts +6 -0
- package/server/methods/epoch/get-slot-leaders.ts +9 -0
- package/server/methods/epoch/get-stake-activation.ts +9 -0
- package/server/methods/epoch/get-stake-minimum-delegation.ts +9 -0
- package/server/methods/epoch/get-vote-accounts.ts +19 -0
- package/server/methods/epoch/index.ts +13 -0
- package/server/methods/epoch/minimum-ledger-slot.ts +5 -0
- package/server/methods/fee/get-fee-calculator-for-blockhash.ts +12 -0
- package/server/methods/fee/get-fee-for-message.ts +8 -0
- package/server/methods/fee/get-fee-rate-governor.ts +16 -0
- package/server/methods/fee/get-fees.ts +14 -0
- package/server/methods/fee/get-recent-prioritization-fees.ts +22 -0
- package/server/methods/fee/index.ts +5 -0
- package/server/methods/get-address-lookup-table.ts +31 -0
- package/server/methods/index.ts +265 -0
- package/server/methods/performance/get-recent-performance-samples.ts +25 -0
- package/server/methods/performance/get-transaction-count.ts +5 -0
- package/server/methods/performance/index.ts +2 -0
- package/server/methods/program/get-block-commitment.ts +9 -0
- package/server/methods/program/get-block-production.ts +14 -0
- package/server/methods/program/get-block-time.ts +21 -0
- package/server/methods/program/get-blocks.ts +11 -0
- package/server/methods/program/get-first-available-block.ts +9 -0
- package/server/methods/program/get-genesis-hash.ts +6 -0
- package/server/methods/program/get-identity.ts +6 -0
- package/server/methods/program/get-inflation-governor.ts +15 -0
- package/server/methods/program/get-inflation-rate.ts +10 -0
- package/server/methods/program/get-inflation-reward.ts +12 -0
- package/server/methods/program/get-largest-accounts.ts +8 -0
- package/server/methods/program/get-parsed-program-accounts.ts +12 -0
- package/server/methods/program/get-parsed-token-accounts-by-delegate.ts +12 -0
- package/server/methods/program/get-parsed-token-accounts-by-owner.ts +12 -0
- package/server/methods/program/get-program-accounts.ts +221 -0
- package/server/methods/program/get-supply.ts +13 -0
- package/server/methods/program/get-token-account-balance.ts +64 -0
- package/server/methods/program/get-token-accounts-by-delegate.ts +81 -0
- package/server/methods/program/get-token-accounts-by-owner.ts +390 -0
- package/server/methods/program/get-token-largest-accounts.ts +80 -0
- package/server/methods/program/get-token-supply.ts +38 -0
- package/server/methods/program/index.ts +21 -0
- package/server/methods/solforge/index.ts +155 -0
- package/server/methods/system/get-health.ts +5 -0
- package/server/methods/system/get-minimum-balance-for-rent-exemption.ts +13 -0
- package/server/methods/system/get-version.ts +9 -0
- package/server/methods/system/index.ts +3 -0
- package/server/methods/transaction/get-confirmed-transaction.ts +11 -0
- package/server/methods/transaction/get-parsed-transaction.ts +21 -0
- package/server/methods/transaction/get-signature-statuses.ts +72 -0
- package/server/methods/transaction/get-signatures-for-address.ts +45 -0
- package/server/methods/transaction/get-transaction.ts +428 -0
- package/server/methods/transaction/index.ts +7 -0
- package/server/methods/transaction/send-transaction.ts +232 -0
- package/server/methods/transaction/simulate-transaction.ts +56 -0
- package/server/rpc-server.ts +474 -0
- package/server/types.ts +74 -0
- package/server/ws-server.ts +171 -0
- package/sf.config.json +38 -0
- package/src/cli/bootstrap.ts +67 -0
- package/src/cli/commands/airdrop.ts +37 -0
- package/src/cli/commands/config.ts +39 -0
- package/src/cli/commands/mint.ts +187 -0
- package/src/cli/commands/program-clone.ts +124 -0
- package/src/cli/commands/program-load.ts +64 -0
- package/src/cli/commands/rpc-start.ts +46 -0
- package/src/cli/commands/token-adopt-authority.ts +37 -0
- package/src/cli/commands/token-clone.ts +113 -0
- package/src/cli/commands/token-create.ts +81 -0
- package/src/cli/main.ts +130 -0
- package/src/cli/run-solforge.ts +98 -0
- package/src/cli/setup-utils.ts +54 -0
- package/src/cli/setup-wizard.ts +256 -0
- package/src/cli/utils/args.ts +15 -0
- package/src/config/index.ts +130 -0
- package/src/db/index.ts +83 -0
- package/src/db/schema/accounts.ts +23 -0
- package/src/db/schema/address-signatures.ts +31 -0
- package/src/db/schema/index.ts +5 -0
- package/src/db/schema/meta-kv.ts +9 -0
- package/src/db/schema/transactions.ts +29 -0
- package/src/db/schema/tx-accounts.ts +33 -0
- package/src/db/tx-store.ts +229 -0
- package/src/gui/public/app.css +1 -0
- package/src/gui/public/index.html +19 -0
- package/src/gui/server.ts +297 -0
- package/src/gui/src/api.ts +127 -0
- package/src/gui/src/app.tsx +390 -0
- package/src/gui/src/components/airdrop-mint-form.tsx +216 -0
- package/src/gui/src/components/clone-program-modal.tsx +183 -0
- package/src/gui/src/components/clone-token-modal.tsx +211 -0
- package/src/gui/src/components/modal.tsx +127 -0
- package/src/gui/src/components/programs-panel.tsx +112 -0
- package/src/gui/src/components/status-panel.tsx +122 -0
- package/src/gui/src/components/tokens-panel.tsx +116 -0
- package/src/gui/src/hooks/use-interval.ts +17 -0
- package/src/gui/src/index.css +529 -0
- package/src/gui/src/main.tsx +17 -0
- package/src/migrations-bundled.ts +17 -0
- package/src/rpc/start.ts +44 -0
- package/tailwind.config.js +27 -0
- package/test-client.ts +120 -0
- package/tmp/inspect-html.ts +4 -0
- package/tmp/response-test.ts +5 -0
- package/tmp/test-html.ts +5 -0
- package/tmp/test-server.ts +13 -0
- package/tsconfig.json +24 -23
- package/LICENSE +0 -21
- package/scripts/postinstall.cjs +0 -103
- package/src/api-server-entry.ts +0 -109
- package/src/commands/add-program.ts +0 -337
- package/src/commands/init.ts +0 -122
- package/src/commands/list.ts +0 -136
- package/src/commands/mint.ts +0 -336
- package/src/commands/start.ts +0 -878
- package/src/commands/status.ts +0 -99
- package/src/commands/stop.ts +0 -406
- package/src/config/manager.ts +0 -157
- package/src/index.ts +0 -188
- package/src/services/api-server.ts +0 -532
- package/src/services/port-manager.ts +0 -177
- package/src/services/process-registry.ts +0 -154
- package/src/services/program-cloner.ts +0 -317
- package/src/services/token-cloner.ts +0 -809
- package/src/services/validator.ts +0 -295
- package/src/types/config.ts +0 -110
- package/src/utils/shell.ts +0 -110
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import type { RpcMethodHandler } from "../../types";
|
|
3
|
+
|
|
4
|
+
// Load a program ELF into LiteSVM for a given programId
|
|
5
|
+
export const solforgeLoadProgram: RpcMethodHandler = async (
|
|
6
|
+
id,
|
|
7
|
+
params,
|
|
8
|
+
context,
|
|
9
|
+
) => {
|
|
10
|
+
try {
|
|
11
|
+
const [programIdStr, elfBase64] = params as [string, string];
|
|
12
|
+
if (!programIdStr || !elfBase64)
|
|
13
|
+
return context.createErrorResponse(
|
|
14
|
+
id,
|
|
15
|
+
-32602,
|
|
16
|
+
"Invalid params: programId, base64 required",
|
|
17
|
+
);
|
|
18
|
+
const pid = new PublicKey(programIdStr);
|
|
19
|
+
const bytes = Uint8Array.from(Buffer.from(elfBase64, "base64"));
|
|
20
|
+
try {
|
|
21
|
+
context.svm.addProgram(pid, bytes);
|
|
22
|
+
} catch {}
|
|
23
|
+
// Mirror program account metadata as executable; owner = upgradeable loader for realism
|
|
24
|
+
const LOADER_UPGRADEABLE = new PublicKey(
|
|
25
|
+
"BPFLoaderUpgradeab1e11111111111111111111111",
|
|
26
|
+
);
|
|
27
|
+
context.svm.setAccount(pid, {
|
|
28
|
+
lamports: 1_000_000_000,
|
|
29
|
+
data: new Uint8Array(bytes.length), // minimal stub data for the program account itself
|
|
30
|
+
owner: LOADER_UPGRADEABLE,
|
|
31
|
+
executable: true,
|
|
32
|
+
rentEpoch: 0,
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
context.registerProgram?.(pid);
|
|
36
|
+
} catch {}
|
|
37
|
+
return context.createSuccessResponse(id, {
|
|
38
|
+
ok: true,
|
|
39
|
+
programId: programIdStr,
|
|
40
|
+
size: bytes.length,
|
|
41
|
+
});
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return context.createErrorResponse(
|
|
44
|
+
id,
|
|
45
|
+
-32603,
|
|
46
|
+
"Load program failed",
|
|
47
|
+
(e as Error)?.message || String(e),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type { RpcMethodHandler } from "../../types";
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACCOUNT_SIZE,
|
|
3
|
+
AccountLayout,
|
|
4
|
+
createAssociatedTokenAccountInstruction,
|
|
5
|
+
createMintToCheckedInstruction,
|
|
6
|
+
getAssociatedTokenAddressSync,
|
|
7
|
+
MINT_SIZE,
|
|
8
|
+
MintLayout,
|
|
9
|
+
TOKEN_2022_PROGRAM_ID,
|
|
10
|
+
TOKEN_PROGRAM_ID,
|
|
11
|
+
} from "@solana/spl-token";
|
|
12
|
+
import type { TransactionInstruction } from "@solana/web3.js";
|
|
13
|
+
import {
|
|
14
|
+
PublicKey,
|
|
15
|
+
TransactionMessage,
|
|
16
|
+
VersionedTransaction,
|
|
17
|
+
} from "@solana/web3.js";
|
|
18
|
+
import type { RpcMethodHandler } from "../../types";
|
|
19
|
+
|
|
20
|
+
// Mint via a real SPL Token transaction signed by faucet (must be mint authority)
|
|
21
|
+
export const solforgeMintTo: RpcMethodHandler = async (id, params, context) => {
|
|
22
|
+
try {
|
|
23
|
+
const [mintStr, ownerStr, rawAmount] = params as [
|
|
24
|
+
string,
|
|
25
|
+
string,
|
|
26
|
+
number | string | bigint,
|
|
27
|
+
];
|
|
28
|
+
if (!mintStr || !ownerStr || rawAmount == null)
|
|
29
|
+
return context.createErrorResponse(
|
|
30
|
+
id,
|
|
31
|
+
-32602,
|
|
32
|
+
"Invalid params: mint, owner, amount required",
|
|
33
|
+
);
|
|
34
|
+
const mint = new PublicKey(mintStr);
|
|
35
|
+
const owner = new PublicKey(ownerStr);
|
|
36
|
+
const faucet = context.getFaucet();
|
|
37
|
+
|
|
38
|
+
// Read mint to get decimals and authority
|
|
39
|
+
const mintAcc = context.svm.getAccount(mint);
|
|
40
|
+
if (!mintAcc)
|
|
41
|
+
return context.createErrorResponse(
|
|
42
|
+
id,
|
|
43
|
+
-32004,
|
|
44
|
+
"Mint not found in LiteSVM",
|
|
45
|
+
);
|
|
46
|
+
const mintInfo = MintLayout.decode(
|
|
47
|
+
Buffer.from(mintAcc.data).slice(0, MINT_SIZE),
|
|
48
|
+
);
|
|
49
|
+
const decimals = mintInfo.decimals;
|
|
50
|
+
const hasAuth = mintInfo.mintAuthorityOption === 1;
|
|
51
|
+
const authPk = hasAuth ? new PublicKey(mintInfo.mintAuthority) : null;
|
|
52
|
+
if (!hasAuth || !authPk || !authPk.equals(faucet.publicKey)) {
|
|
53
|
+
return context.createErrorResponse(
|
|
54
|
+
id,
|
|
55
|
+
-32000,
|
|
56
|
+
"Mint has no faucet authority; cannot mint real tokens",
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const ixs: TransactionInstruction[] = [];
|
|
61
|
+
// Detect which token program the mint belongs to (SPL v1 vs Token-2022)
|
|
62
|
+
const mintOwnerStr = (() => {
|
|
63
|
+
try {
|
|
64
|
+
return (mintAcc.owner as PublicKey).toBase58();
|
|
65
|
+
} catch {
|
|
66
|
+
return String(mintAcc.owner);
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
69
|
+
const tokenProgramId =
|
|
70
|
+
mintOwnerStr === TOKEN_2022_PROGRAM_ID.toBase58()
|
|
71
|
+
? TOKEN_2022_PROGRAM_ID
|
|
72
|
+
: TOKEN_PROGRAM_ID;
|
|
73
|
+
|
|
74
|
+
// Derive ATA using the correct token program
|
|
75
|
+
const ata = getAssociatedTokenAddressSync(
|
|
76
|
+
mint,
|
|
77
|
+
owner,
|
|
78
|
+
true,
|
|
79
|
+
tokenProgramId,
|
|
80
|
+
);
|
|
81
|
+
const ataAcc = context.svm.getAccount(ata);
|
|
82
|
+
|
|
83
|
+
// Ensure ATA exists under the correct token program
|
|
84
|
+
if (!ataAcc || (ataAcc.data?.length ?? 0) < ACCOUNT_SIZE) {
|
|
85
|
+
ixs.push(
|
|
86
|
+
createAssociatedTokenAccountInstruction(
|
|
87
|
+
faucet.publicKey,
|
|
88
|
+
ata,
|
|
89
|
+
owner,
|
|
90
|
+
mint,
|
|
91
|
+
tokenProgramId,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const amount =
|
|
97
|
+
typeof rawAmount === "bigint" ? rawAmount : BigInt(rawAmount);
|
|
98
|
+
ixs.push(
|
|
99
|
+
createMintToCheckedInstruction(
|
|
100
|
+
mint,
|
|
101
|
+
ata,
|
|
102
|
+
faucet.publicKey,
|
|
103
|
+
amount,
|
|
104
|
+
decimals,
|
|
105
|
+
[],
|
|
106
|
+
tokenProgramId,
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Build a VersionedTransaction (legacy message) to ensure consistent encoding/decoding downstream
|
|
111
|
+
let rb = context.svm.latestBlockhash();
|
|
112
|
+
if (!rb || rb.length === 0) {
|
|
113
|
+
const bh = new Uint8Array(32);
|
|
114
|
+
crypto.getRandomValues(bh);
|
|
115
|
+
rb = context.encodeBase58(bh);
|
|
116
|
+
}
|
|
117
|
+
const msg = new TransactionMessage({
|
|
118
|
+
payerKey: faucet.publicKey,
|
|
119
|
+
recentBlockhash: rb,
|
|
120
|
+
instructions: ixs,
|
|
121
|
+
});
|
|
122
|
+
const legacy = msg.compileToLegacyMessage();
|
|
123
|
+
const vtx = new VersionedTransaction(legacy);
|
|
124
|
+
vtx.sign([faucet]);
|
|
125
|
+
|
|
126
|
+
// Capture preBalances for primary accounts referenced and token pre amount
|
|
127
|
+
const trackedKeys = [faucet.publicKey, ata, mint, owner];
|
|
128
|
+
const preBalances = trackedKeys.map((pk) => {
|
|
129
|
+
try {
|
|
130
|
+
return Number(context.svm.getBalance(pk) || 0n);
|
|
131
|
+
} catch {
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
// Token mint decimals and pre amount
|
|
136
|
+
let decsForMint = 0;
|
|
137
|
+
let preTokenAmt: bigint = 0n;
|
|
138
|
+
try {
|
|
139
|
+
const mintAcc0 = context.svm.getAccount(mint);
|
|
140
|
+
const mintInfo0 = mintAcc0
|
|
141
|
+
? MintLayout.decode(Buffer.from(mintAcc0.data).slice(0, MINT_SIZE))
|
|
142
|
+
: undefined;
|
|
143
|
+
decsForMint = Number(mintInfo0?.decimals ?? decimals ?? 0);
|
|
144
|
+
const ataAcc0 = context.svm.getAccount(ata);
|
|
145
|
+
if (ataAcc0 && (ataAcc0.data?.length ?? 0) >= ACCOUNT_SIZE) {
|
|
146
|
+
const dec0 = AccountLayout.decode(Buffer.from(ataAcc0.data));
|
|
147
|
+
preTokenAmt = BigInt(dec0.amount.toString());
|
|
148
|
+
}
|
|
149
|
+
} catch {}
|
|
150
|
+
|
|
151
|
+
// Send transaction via svm
|
|
152
|
+
const _res = context.svm.sendTransaction(vtx);
|
|
153
|
+
// Compute signature (base58) from the signed transaction
|
|
154
|
+
let signatureStr = "";
|
|
155
|
+
try {
|
|
156
|
+
const sigBytes = vtx.signatures?.[0];
|
|
157
|
+
if (sigBytes)
|
|
158
|
+
signatureStr = context.encodeBase58(new Uint8Array(sigBytes));
|
|
159
|
+
} catch {}
|
|
160
|
+
if (!signatureStr) signatureStr = `mint:${ata.toBase58()}:${Date.now()}`;
|
|
161
|
+
|
|
162
|
+
// Token balance deltas (pre/post) for ATA
|
|
163
|
+
type UiTokenAmount = {
|
|
164
|
+
amount: string;
|
|
165
|
+
decimals: number;
|
|
166
|
+
uiAmount: number;
|
|
167
|
+
uiAmountString: string;
|
|
168
|
+
};
|
|
169
|
+
type TokenBalance = {
|
|
170
|
+
accountIndex: number;
|
|
171
|
+
mint: string;
|
|
172
|
+
owner: string;
|
|
173
|
+
uiTokenAmount: UiTokenAmount;
|
|
174
|
+
};
|
|
175
|
+
let preTokenBalances: TokenBalance[] = [];
|
|
176
|
+
let postTokenBalances: TokenBalance[] = [];
|
|
177
|
+
try {
|
|
178
|
+
const decs = decsForMint;
|
|
179
|
+
const ui = (n: bigint) => ({
|
|
180
|
+
amount: n.toString(),
|
|
181
|
+
decimals: decs,
|
|
182
|
+
uiAmount: Number(n) / 10 ** decs,
|
|
183
|
+
uiAmountString: (Number(n) / 10 ** decs).toString(),
|
|
184
|
+
});
|
|
185
|
+
const preAmt = preTokenAmt;
|
|
186
|
+
const ataPostAcc = context.svm.getAccount(ata); // after send
|
|
187
|
+
const postAmt =
|
|
188
|
+
ataPostAcc && (ataPostAcc.data?.length ?? 0) >= ACCOUNT_SIZE
|
|
189
|
+
? BigInt(
|
|
190
|
+
AccountLayout.decode(
|
|
191
|
+
Buffer.from(ataPostAcc.data),
|
|
192
|
+
).amount.toString(),
|
|
193
|
+
)
|
|
194
|
+
: preAmt;
|
|
195
|
+
const msgAny = vtx.message as unknown as {
|
|
196
|
+
staticAccountKeys?: Array<string | PublicKey>;
|
|
197
|
+
accountKeys?: Array<string | PublicKey>;
|
|
198
|
+
};
|
|
199
|
+
const rawKeys: Array<string | PublicKey> = Array.isArray(
|
|
200
|
+
msgAny.staticAccountKeys,
|
|
201
|
+
)
|
|
202
|
+
? msgAny.staticAccountKeys
|
|
203
|
+
: Array.isArray(msgAny.accountKeys)
|
|
204
|
+
? msgAny.accountKeys
|
|
205
|
+
: [];
|
|
206
|
+
const keys = rawKeys.map((k: string | PublicKey) => {
|
|
207
|
+
try {
|
|
208
|
+
return typeof k === "string" ? k : new PublicKey(k).toBase58();
|
|
209
|
+
} catch {
|
|
210
|
+
return String(k);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const ataIndex = keys.indexOf(ata.toBase58());
|
|
214
|
+
preTokenBalances = [
|
|
215
|
+
{
|
|
216
|
+
accountIndex: ataIndex >= 0 ? ataIndex : 0,
|
|
217
|
+
mint: mint.toBase58(),
|
|
218
|
+
owner: owner.toBase58(),
|
|
219
|
+
uiTokenAmount: ui(preAmt),
|
|
220
|
+
},
|
|
221
|
+
];
|
|
222
|
+
postTokenBalances = [
|
|
223
|
+
{
|
|
224
|
+
accountIndex: ataIndex >= 0 ? ataIndex : 0,
|
|
225
|
+
mint: mint.toBase58(),
|
|
226
|
+
owner: owner.toBase58(),
|
|
227
|
+
uiTokenAmount: ui(postAmt),
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
} catch {}
|
|
231
|
+
|
|
232
|
+
// Insert into DB for explorer via context.recordTransaction for richer details
|
|
233
|
+
try {
|
|
234
|
+
const rawBase64 = Buffer.from(vtx.serialize()).toString("base64");
|
|
235
|
+
const postBalances = trackedKeys.map((pk) => {
|
|
236
|
+
try {
|
|
237
|
+
return Number(context.svm.getBalance(pk) || 0n);
|
|
238
|
+
} catch {
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
const logs: string[] = ["spl-token mintToChecked"];
|
|
243
|
+
try {
|
|
244
|
+
(vtx as unknown as { serialize: () => Uint8Array }).serialize = () =>
|
|
245
|
+
Buffer.from(rawBase64, "base64");
|
|
246
|
+
} catch {}
|
|
247
|
+
context.recordTransaction(signatureStr, vtx, {
|
|
248
|
+
logs,
|
|
249
|
+
fee: 0,
|
|
250
|
+
blockTime: Math.floor(Date.now() / 1000),
|
|
251
|
+
preBalances,
|
|
252
|
+
postBalances,
|
|
253
|
+
preTokenBalances,
|
|
254
|
+
postTokenBalances,
|
|
255
|
+
});
|
|
256
|
+
} catch {}
|
|
257
|
+
try {
|
|
258
|
+
context.notifySignature(signatureStr);
|
|
259
|
+
} catch {}
|
|
260
|
+
|
|
261
|
+
return context.createSuccessResponse(id, {
|
|
262
|
+
ok: true,
|
|
263
|
+
signature: signatureStr,
|
|
264
|
+
mint: mintStr,
|
|
265
|
+
owner: ownerStr,
|
|
266
|
+
amount: amount.toString(),
|
|
267
|
+
});
|
|
268
|
+
} catch (e) {
|
|
269
|
+
return context.createErrorResponse(
|
|
270
|
+
id,
|
|
271
|
+
-32603,
|
|
272
|
+
"MintTo failed",
|
|
273
|
+
(e as Error)?.message || String(e),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export type { RpcMethodHandler } from "../../types";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getBlock: RpcMethodHandler = (id, params, context) => {
|
|
4
|
+
try {
|
|
5
|
+
const [slotRaw] = params || [];
|
|
6
|
+
const slot = Number(slotRaw);
|
|
7
|
+
if (!Number.isFinite(slot) || slot < 0) throw new Error("Invalid slot");
|
|
8
|
+
const parentSlot = Math.max(0, slot - 1);
|
|
9
|
+
const blockhash = context.svm.latestBlockhash();
|
|
10
|
+
const previousBlockhash = context.svm.latestBlockhash();
|
|
11
|
+
const SLOT_TIME_MS = 400;
|
|
12
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
13
|
+
const slotDiff = Number(context.slot) - slot;
|
|
14
|
+
const blockTime =
|
|
15
|
+
slot > Number(context.slot)
|
|
16
|
+
? null
|
|
17
|
+
: currentTime -
|
|
18
|
+
Math.floor((Math.max(0, slotDiff) * SLOT_TIME_MS) / 1000);
|
|
19
|
+
return context.createSuccessResponse(id, {
|
|
20
|
+
blockhash,
|
|
21
|
+
previousBlockhash,
|
|
22
|
+
parentSlot,
|
|
23
|
+
transactions: [],
|
|
24
|
+
rewards: [],
|
|
25
|
+
blockTime,
|
|
26
|
+
});
|
|
27
|
+
} catch (error: any) {
|
|
28
|
+
return context.createErrorResponse(
|
|
29
|
+
id,
|
|
30
|
+
-32602,
|
|
31
|
+
"Invalid params",
|
|
32
|
+
error.message,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getBlocksWithLimit: RpcMethodHandler = (id, params, context) => {
|
|
4
|
+
try {
|
|
5
|
+
const [startSlotRaw, limitRaw] = params || [];
|
|
6
|
+
const start = Number(startSlotRaw);
|
|
7
|
+
const limit = Number(limitRaw);
|
|
8
|
+
if (!Number.isFinite(start) || start < 0)
|
|
9
|
+
throw new Error("Invalid start slot");
|
|
10
|
+
if (!Number.isFinite(limit) || limit <= 0) throw new Error("Invalid limit");
|
|
11
|
+
const end = Math.min(Number(context.slot), start + limit - 1);
|
|
12
|
+
const blocks: number[] = [];
|
|
13
|
+
for (let s = start; s <= end; s++) blocks.push(s);
|
|
14
|
+
return context.createSuccessResponse(id, blocks);
|
|
15
|
+
} catch (error: any) {
|
|
16
|
+
return context.createErrorResponse(
|
|
17
|
+
id,
|
|
18
|
+
-32602,
|
|
19
|
+
"Invalid params",
|
|
20
|
+
error.message,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getLatestBlockhash: RpcMethodHandler = (id, _params, context) => {
|
|
4
|
+
const blockhash = context.svm.latestBlockhash();
|
|
5
|
+
return context.createSuccessResponse(id, {
|
|
6
|
+
context: { slot: Number(context.slot) },
|
|
7
|
+
value: {
|
|
8
|
+
blockhash,
|
|
9
|
+
lastValidBlockHeight: Number(context.blockHeight + 150n),
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { getBlock } from "./get-block";
|
|
2
|
+
export { getBlockHeight } from "./get-block-height";
|
|
3
|
+
export { getBlocksWithLimit } from "./get-blocks-with-limit";
|
|
4
|
+
export { getLatestBlockhash } from "./get-latest-blockhash";
|
|
5
|
+
export { getSlot } from "./get-slot";
|
|
6
|
+
export { isBlockhashValid } from "./is-blockhash-valid";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const isBlockhashValid: RpcMethodHandler = (id, params, context) => {
|
|
4
|
+
try {
|
|
5
|
+
const [blockhash] = params || [];
|
|
6
|
+
const current = context.svm.latestBlockhash();
|
|
7
|
+
const isValid =
|
|
8
|
+
typeof blockhash === "string" &&
|
|
9
|
+
blockhash.length > 0 &&
|
|
10
|
+
blockhash === current;
|
|
11
|
+
return context.createSuccessResponse(id, {
|
|
12
|
+
context: { slot: Number(context.slot) },
|
|
13
|
+
value: isValid,
|
|
14
|
+
});
|
|
15
|
+
} catch (error: any) {
|
|
16
|
+
return context.createErrorResponse(
|
|
17
|
+
id,
|
|
18
|
+
-32602,
|
|
19
|
+
"Invalid params",
|
|
20
|
+
error.message,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getClusterNodes: RpcMethodHandler = (id, _params, _context) => {
|
|
4
|
+
return {
|
|
5
|
+
jsonrpc: "2.0",
|
|
6
|
+
id,
|
|
7
|
+
result: [
|
|
8
|
+
{
|
|
9
|
+
pubkey: "11111111111111111111111111111111",
|
|
10
|
+
gossip: "127.0.0.1:8001",
|
|
11
|
+
tpu: "127.0.0.1:8003",
|
|
12
|
+
rpc: "127.0.0.1:8899",
|
|
13
|
+
version: "1.17.0",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getEpochInfo: RpcMethodHandler = (id, _params, context) => {
|
|
4
|
+
const slotsPerEpoch = 432000;
|
|
5
|
+
const currentSlot = Number(context.slot);
|
|
6
|
+
const epoch = Math.floor(currentSlot / slotsPerEpoch);
|
|
7
|
+
const slotIndex = currentSlot % slotsPerEpoch;
|
|
8
|
+
return context.createSuccessResponse(id, {
|
|
9
|
+
absoluteSlot: currentSlot,
|
|
10
|
+
blockHeight: currentSlot,
|
|
11
|
+
epoch,
|
|
12
|
+
slotIndex,
|
|
13
|
+
slotsInEpoch: slotsPerEpoch,
|
|
14
|
+
transactionCount: currentSlot * 100,
|
|
15
|
+
});
|
|
16
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getEpochSchedule: RpcMethodHandler = (id, _params, _context) => {
|
|
4
|
+
return {
|
|
5
|
+
jsonrpc: "2.0",
|
|
6
|
+
id,
|
|
7
|
+
result: {
|
|
8
|
+
slotsPerEpoch: 432000,
|
|
9
|
+
leaderScheduleSlotOffset: 432000,
|
|
10
|
+
warmup: false,
|
|
11
|
+
firstNormalEpoch: 0,
|
|
12
|
+
firstNormalSlot: 0,
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getLeaderSchedule: RpcMethodHandler = (id, _params, _context) => {
|
|
4
|
+
const identity = "11111111111111111111111111111111";
|
|
5
|
+
const schedule: Record<string, number[]> = {};
|
|
6
|
+
schedule[identity] = Array.from({ length: 100 }, (_, i) => i);
|
|
7
|
+
return { jsonrpc: "2.0", id, result: schedule };
|
|
8
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getSlotLeaders: RpcMethodHandler = (id, params, _context) => {
|
|
4
|
+
const [_startSlot, limit] = params || [];
|
|
5
|
+
const LEADER_PUBKEY = "11111111111111111111111111111111";
|
|
6
|
+
const count = Math.min(Number(limit || 100), 5000);
|
|
7
|
+
const leaders = Array(count).fill(LEADER_PUBKEY);
|
|
8
|
+
return { jsonrpc: "2.0", id, result: leaders };
|
|
9
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getVoteAccounts: RpcMethodHandler = (id, _params, context) => {
|
|
4
|
+
return context.createSuccessResponse(id, {
|
|
5
|
+
current: [
|
|
6
|
+
{
|
|
7
|
+
votePubkey: "11111111111111111111111111111111",
|
|
8
|
+
nodePubkey: "11111111111111111111111111111111",
|
|
9
|
+
activatedStake: 1000000000,
|
|
10
|
+
epochVoteAccount: true,
|
|
11
|
+
commission: 0,
|
|
12
|
+
lastVote: Number(context.slot),
|
|
13
|
+
epochCredits: [[0, 1000, 0]],
|
|
14
|
+
rootSlot: Number(context.slot) - 1,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
delinquent: [],
|
|
18
|
+
});
|
|
19
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { getClusterNodes } from "./get-cluster-nodes";
|
|
2
|
+
export { getEpochInfo } from "./get-epoch-info";
|
|
3
|
+
export { getEpochSchedule } from "./get-epoch-schedule";
|
|
4
|
+
export { getHighestSnapshotSlot } from "./get-highest-snapshot-slot";
|
|
5
|
+
export { getLeaderSchedule } from "./get-leader-schedule";
|
|
6
|
+
export { getMaxRetransmitSlot } from "./get-max-retransmit-slot";
|
|
7
|
+
export { getMaxShredInsertSlot } from "./get-max-shred-insert-slot";
|
|
8
|
+
export { getSlotLeader } from "./get-slot-leader";
|
|
9
|
+
export { getSlotLeaders } from "./get-slot-leaders";
|
|
10
|
+
export { getStakeActivation } from "./get-stake-activation";
|
|
11
|
+
export { getStakeMinimumDelegation } from "./get-stake-minimum-delegation";
|
|
12
|
+
export { getVoteAccounts } from "./get-vote-accounts";
|
|
13
|
+
export { minimumLedgerSlot } from "./minimum-ledger-slot";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getFeeCalculatorForBlockhash: RpcMethodHandler = (
|
|
4
|
+
id,
|
|
5
|
+
_params,
|
|
6
|
+
context,
|
|
7
|
+
) => {
|
|
8
|
+
return context.createSuccessResponse(id, {
|
|
9
|
+
context: { slot: Number(context.slot) },
|
|
10
|
+
value: { feeCalculator: { lamportsPerSignature: 5000 } },
|
|
11
|
+
});
|
|
12
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getFeeForMessage: RpcMethodHandler = (id, _params, context) => {
|
|
4
|
+
return context.createSuccessResponse(id, {
|
|
5
|
+
context: { slot: Number(context.slot), apiVersion: "1.17.9" },
|
|
6
|
+
value: 5000,
|
|
7
|
+
});
|
|
8
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getFeeRateGovernor: RpcMethodHandler = (id, _params, context) => {
|
|
4
|
+
return context.createSuccessResponse(id, {
|
|
5
|
+
context: { slot: Number(context.slot) },
|
|
6
|
+
value: {
|
|
7
|
+
feeRateGovernor: {
|
|
8
|
+
burnPercent: 50,
|
|
9
|
+
maxLamportsPerSignature: 100000,
|
|
10
|
+
minLamportsPerSignature: 5000,
|
|
11
|
+
targetLamportsPerSignature: 10000,
|
|
12
|
+
targetSignaturesPerSlot: 20000,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
|
|
3
|
+
export const getFees: RpcMethodHandler = (id, _params, context) => {
|
|
4
|
+
const blockhash = context.svm.latestBlockhash();
|
|
5
|
+
return context.createSuccessResponse(id, {
|
|
6
|
+
context: { slot: Number(context.slot) },
|
|
7
|
+
value: {
|
|
8
|
+
blockhash,
|
|
9
|
+
feeCalculator: { lamportsPerSignature: 5000 },
|
|
10
|
+
lastValidSlot: Number(context.slot) + 150,
|
|
11
|
+
lastValidBlockHeight: Number(context.slot) + 150,
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
};
|