solforge 0.1.7 → 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 -288
- package/src/commands/start.ts +0 -877
- 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 -485
- 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
- package/src/utils/token-loader.ts +0 -115
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
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`
|
|
@@ -0,0 +1,90 @@
|
|
|
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: any) {
|
|
83
|
+
return context.createErrorResponse(
|
|
84
|
+
id,
|
|
85
|
+
-32602,
|
|
86
|
+
"Invalid params",
|
|
87
|
+
error.message,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
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: any) {
|
|
20
|
+
return context.createErrorResponse(
|
|
21
|
+
id,
|
|
22
|
+
-32602,
|
|
23
|
+
"Invalid params",
|
|
24
|
+
error.message,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import type { RpcMethodHandler } from "../../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Implements the getMultipleAccounts RPC method
|
|
6
|
+
* @see https://docs.solana.com/api/http#getmultipleaccounts
|
|
7
|
+
*/
|
|
8
|
+
export const getMultipleAccounts: RpcMethodHandler = async (
|
|
9
|
+
id,
|
|
10
|
+
params,
|
|
11
|
+
context,
|
|
12
|
+
) => {
|
|
13
|
+
const [pubkeys, config] = params;
|
|
14
|
+
const encoding = config?.encoding || "base64";
|
|
15
|
+
|
|
16
|
+
const accounts = pubkeys.map((pubkeyStr: string) => {
|
|
17
|
+
try {
|
|
18
|
+
const pubkey = new PublicKey(pubkeyStr);
|
|
19
|
+
const account = context.svm.getAccount(pubkey);
|
|
20
|
+
|
|
21
|
+
if (!account) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const owner = new PublicKey(account.owner).toBase58();
|
|
26
|
+
if (encoding === "jsonParsed") {
|
|
27
|
+
const program =
|
|
28
|
+
owner === "11111111111111111111111111111111" ? "system" : "unknown";
|
|
29
|
+
const space = account.data?.length ?? 0;
|
|
30
|
+
return {
|
|
31
|
+
lamports: Number(account.lamports),
|
|
32
|
+
owner,
|
|
33
|
+
executable: account.executable,
|
|
34
|
+
rentEpoch: Number(account.rentEpoch || 0),
|
|
35
|
+
data: {
|
|
36
|
+
program,
|
|
37
|
+
parsed: program === "system" ? { type: "account", info: {} } : null,
|
|
38
|
+
space,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
lamports: Number(account.lamports),
|
|
45
|
+
owner,
|
|
46
|
+
data: [Buffer.from(account.data).toString("base64"), "base64"] as const,
|
|
47
|
+
executable: account.executable,
|
|
48
|
+
rentEpoch: Number(account.rentEpoch || 0),
|
|
49
|
+
};
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Opportunistic index update
|
|
56
|
+
try {
|
|
57
|
+
const snaps: any[] = [];
|
|
58
|
+
for (const pubkeyStr of pubkeys) {
|
|
59
|
+
try {
|
|
60
|
+
const pubkey = new PublicKey(pubkeyStr);
|
|
61
|
+
const acc = context.svm.getAccount(pubkey);
|
|
62
|
+
if (!acc) continue;
|
|
63
|
+
const owner = new PublicKey(acc.owner).toBase58();
|
|
64
|
+
snaps.push({
|
|
65
|
+
address: pubkey.toBase58(),
|
|
66
|
+
lamports: Number(acc.lamports || 0n),
|
|
67
|
+
ownerProgram: owner,
|
|
68
|
+
executable: !!acc.executable,
|
|
69
|
+
rentEpoch: Number(acc.rentEpoch || 0),
|
|
70
|
+
dataLen: acc.data?.length ?? 0,
|
|
71
|
+
dataBase64: undefined,
|
|
72
|
+
lastSlot: Number(context.slot),
|
|
73
|
+
});
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
76
|
+
if (snaps.length) await context.store?.upsertAccounts(snaps);
|
|
77
|
+
} catch {}
|
|
78
|
+
|
|
79
|
+
return context.createSuccessResponse(id, {
|
|
80
|
+
context: { slot: Number(context.slot) },
|
|
81
|
+
value: accounts,
|
|
82
|
+
});
|
|
83
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RpcMethodHandler } from "../../types";
|
|
2
|
+
import { getAccountInfo } from "./get-account-info";
|
|
3
|
+
|
|
4
|
+
export const getParsedAccountInfo: RpcMethodHandler = async (
|
|
5
|
+
id,
|
|
6
|
+
params,
|
|
7
|
+
context,
|
|
8
|
+
) => {
|
|
9
|
+
const [pubkey, config] = params || [];
|
|
10
|
+
const cfg = { ...(config || {}), encoding: "jsonParsed" };
|
|
11
|
+
try {
|
|
12
|
+
return await getAccountInfo(id, [pubkey, cfg], context);
|
|
13
|
+
} catch (error: any) {
|
|
14
|
+
return context.createErrorResponse(
|
|
15
|
+
id,
|
|
16
|
+
-32603,
|
|
17
|
+
"Internal error",
|
|
18
|
+
error.message,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account-related RPC methods
|
|
3
|
+
*
|
|
4
|
+
* This module contains all account-related RPC methods.
|
|
5
|
+
* Each method is in its own file to maintain modularity.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { getAccountInfo } from "./get-account-info";
|
|
9
|
+
export { getBalance } from "./get-balance";
|
|
10
|
+
export { getMultipleAccounts } from "./get-multiple-accounts";
|
|
11
|
+
export { getParsedAccountInfo } from "./get-parsed-account-info";
|
|
12
|
+
export { requestAirdrop } from "./request-airdrop";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import type { RpcMethodContext } from "../../../server/types";
|
|
3
|
+
import { parseUpgradeableLoader } from "./loader-upgradeable";
|
|
4
|
+
import { parseSplTokenAccountOrMint } from "./spl-token";
|
|
5
|
+
import { parseSystemAccount } from "./system";
|
|
6
|
+
|
|
7
|
+
export type ParsedAccountData = {
|
|
8
|
+
program: string;
|
|
9
|
+
parsed: any; // match Solana RPC jsonParsed payloads
|
|
10
|
+
space: number;
|
|
11
|
+
} | null;
|
|
12
|
+
|
|
13
|
+
export function parseAccountJson(
|
|
14
|
+
pubkey: PublicKey,
|
|
15
|
+
account: {
|
|
16
|
+
owner: PublicKey | string;
|
|
17
|
+
data: Uint8Array | Buffer | number[];
|
|
18
|
+
lamports: bigint | number;
|
|
19
|
+
executable?: boolean;
|
|
20
|
+
rentEpoch?: bigint | number;
|
|
21
|
+
},
|
|
22
|
+
context: RpcMethodContext,
|
|
23
|
+
): ParsedAccountData {
|
|
24
|
+
const ownerStr =
|
|
25
|
+
typeof account.owner === "string"
|
|
26
|
+
? account.owner
|
|
27
|
+
: new PublicKey(account.owner).toBase58();
|
|
28
|
+
const ownerPk =
|
|
29
|
+
typeof account.owner === "string"
|
|
30
|
+
? new PublicKey(account.owner)
|
|
31
|
+
: (account.owner as PublicKey);
|
|
32
|
+
const dataBytes =
|
|
33
|
+
account.data instanceof Uint8Array
|
|
34
|
+
? account.data
|
|
35
|
+
: Buffer.from(account.data as any);
|
|
36
|
+
const space = dataBytes.length;
|
|
37
|
+
|
|
38
|
+
// 1) System program
|
|
39
|
+
const sys = parseSystemAccount(ownerStr, space);
|
|
40
|
+
if (sys) return sys;
|
|
41
|
+
|
|
42
|
+
// 2) SPL Token (v1) & Token-2022
|
|
43
|
+
const token = parseSplTokenAccountOrMint(pubkey, ownerPk, dataBytes, context);
|
|
44
|
+
if (token) return token;
|
|
45
|
+
|
|
46
|
+
// 3) BPF Upgradeable Loader
|
|
47
|
+
const loader = parseUpgradeableLoader(ownerStr, dataBytes, context);
|
|
48
|
+
if (loader) return loader;
|
|
49
|
+
|
|
50
|
+
// 4) Unknown
|
|
51
|
+
return { program: "unknown", parsed: null, space };
|
|
52
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import type { RpcMethodContext } from "../../../server/types";
|
|
3
|
+
|
|
4
|
+
const LOADER_UPGRADEABLE = "BPFLoaderUpgradeab1e11111111111111111111111";
|
|
5
|
+
|
|
6
|
+
export function parseUpgradeableLoader(
|
|
7
|
+
owner: string,
|
|
8
|
+
data: Uint8Array,
|
|
9
|
+
context: RpcMethodContext,
|
|
10
|
+
) {
|
|
11
|
+
if (owner !== LOADER_UPGRADEABLE) return null;
|
|
12
|
+
const bytes = data;
|
|
13
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
14
|
+
const space = bytes.length;
|
|
15
|
+
let parsed: any = null;
|
|
16
|
+
try {
|
|
17
|
+
if (bytes.length >= 4) {
|
|
18
|
+
const tag = dv.getUint32(0, true);
|
|
19
|
+
if (tag === 2 && bytes.length >= 36) {
|
|
20
|
+
// Program: [u32 tag][32 programDataPubkey]
|
|
21
|
+
const pd = new PublicKey(bytes.slice(4, 36)).toBase58();
|
|
22
|
+
parsed = { type: "program", info: { programData: pd } };
|
|
23
|
+
} else if (tag === 3 && bytes.length >= 4 + 8 + 1) {
|
|
24
|
+
// ProgramData: [u32 tag][u64 slot][Option<Pubkey> upgradeAuthority]
|
|
25
|
+
const slot = Number(dv.getBigUint64(4, true));
|
|
26
|
+
let upgradeAuthority: string | null = null;
|
|
27
|
+
const opt = dv.getUint8(12);
|
|
28
|
+
let hdr = 13; // after u8 option
|
|
29
|
+
if (opt === 1 && bytes.length >= 13 + 32) {
|
|
30
|
+
upgradeAuthority = new PublicKey(bytes.slice(13, 45)).toBase58();
|
|
31
|
+
hdr = 45;
|
|
32
|
+
} else if (opt === 0) {
|
|
33
|
+
hdr = 13;
|
|
34
|
+
} else if (bytes.length >= 12 + 4) {
|
|
35
|
+
// Fallback u32 option at offset 12
|
|
36
|
+
const opt32 = dv.getUint32(12, true);
|
|
37
|
+
hdr = 16;
|
|
38
|
+
if (opt32 === 1 && bytes.length >= 16 + 32) {
|
|
39
|
+
upgradeAuthority = new PublicKey(bytes.slice(16, 48)).toBase58();
|
|
40
|
+
hdr = 48;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const programBytes = bytes.slice(hdr);
|
|
44
|
+
parsed = {
|
|
45
|
+
type: "programData",
|
|
46
|
+
info: {
|
|
47
|
+
slot: slot === 0 ? Number(context.slot) : slot,
|
|
48
|
+
upgradeAuthority,
|
|
49
|
+
authority: upgradeAuthority,
|
|
50
|
+
data: [Buffer.from(programBytes).toString("base64"), "base64"],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
} else if (tag === 1) {
|
|
54
|
+
// Buffer: [u32 tag][Option<Pubkey> authority]
|
|
55
|
+
let authority: string | null = null;
|
|
56
|
+
if (bytes.length >= 5) {
|
|
57
|
+
const hasAuth = dv.getUint8(4);
|
|
58
|
+
if (hasAuth === 1 && bytes.length >= 5 + 32)
|
|
59
|
+
authority = new PublicKey(bytes.slice(5, 37)).toBase58();
|
|
60
|
+
}
|
|
61
|
+
parsed = { type: "buffer", info: { authority } };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch {}
|
|
65
|
+
return { program: "bpf-upgradeable-loader", parsed, space };
|
|
66
|
+
}
|