solforge 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/decode-b58.ts +6 -0
- package/server/lib/instruction-parser.ts +264 -0
- package/server/lib/parsers/spl-associated-token-account.ts +44 -0
- package/server/lib/parsers/spl-token.ts +172 -0
- package/server/methods/account/request-airdrop.ts +107 -84
- package/server/methods/admin/mint-to.ts +11 -38
- package/server/methods/transaction/get-transaction.ts +328 -267
- package/server/methods/transaction/inner-instructions.test.ts +63 -0
- package/server/methods/transaction/send-transaction.ts +248 -57
- package/server/rpc-server.ts +104 -67
- package/server/types.ts +56 -24
- package/src/db/schema/index.ts +1 -0
- package/src/db/schema/transactions.ts +29 -22
- package/src/db/schema/tx-account-states.ts +21 -0
- package/src/db/tx-store.ts +103 -70
- package/src/migrations-bundled.ts +8 -2
package/server/types.ts
CHANGED
|
@@ -44,30 +44,62 @@ export interface RpcMethodContext {
|
|
|
44
44
|
listMints?: () => string[];
|
|
45
45
|
registerProgram?: (program: PublicKey | string) => void;
|
|
46
46
|
listPrograms?: () => string[];
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
47
|
+
recordTransaction: (
|
|
48
|
+
signature: string,
|
|
49
|
+
tx: VersionedTransaction,
|
|
50
|
+
meta?: {
|
|
51
|
+
logs?: string[];
|
|
52
|
+
err?: unknown;
|
|
53
|
+
fee?: number;
|
|
54
|
+
blockTime?: number;
|
|
55
|
+
preBalances?: number[];
|
|
56
|
+
postBalances?: number[];
|
|
57
|
+
preTokenBalances?: unknown[];
|
|
58
|
+
postTokenBalances?: unknown[];
|
|
59
|
+
innerInstructions?: unknown[];
|
|
60
|
+
computeUnits?: number | bigint | null;
|
|
61
|
+
returnData?: { programId: string; dataBase64: string } | null;
|
|
62
|
+
// Optional rich per-account snapshots captured around execution
|
|
63
|
+
accountStates?: Array<{
|
|
64
|
+
address: string;
|
|
65
|
+
pre?: {
|
|
66
|
+
lamports?: number;
|
|
67
|
+
ownerProgram?: string;
|
|
68
|
+
executable?: boolean;
|
|
69
|
+
rentEpoch?: number;
|
|
70
|
+
dataLen?: number;
|
|
71
|
+
dataBase64?: string | null;
|
|
72
|
+
lastSlot?: number;
|
|
73
|
+
} | null;
|
|
74
|
+
post?: {
|
|
75
|
+
lamports?: number;
|
|
76
|
+
ownerProgram?: string;
|
|
77
|
+
executable?: boolean;
|
|
78
|
+
rentEpoch?: number;
|
|
79
|
+
dataLen?: number;
|
|
80
|
+
dataBase64?: string | null;
|
|
81
|
+
lastSlot?: number;
|
|
82
|
+
} | null;
|
|
83
|
+
}>;
|
|
84
|
+
},
|
|
85
|
+
) => void;
|
|
86
|
+
getRecordedTransaction: (signature: string) =>
|
|
87
|
+
| {
|
|
88
|
+
tx: VersionedTransaction;
|
|
89
|
+
logs: string[];
|
|
90
|
+
err: unknown;
|
|
91
|
+
fee: number;
|
|
92
|
+
slot: number;
|
|
93
|
+
blockTime?: number;
|
|
94
|
+
preBalances?: number[];
|
|
95
|
+
postBalances?: number[];
|
|
96
|
+
preTokenBalances?: unknown[];
|
|
97
|
+
postTokenBalances?: unknown[];
|
|
98
|
+
innerInstructions?: unknown[];
|
|
99
|
+
computeUnits?: number | null;
|
|
100
|
+
returnData?: { programId: string; dataBase64: string } | null;
|
|
101
|
+
}
|
|
102
|
+
| undefined;
|
|
71
103
|
}
|
|
72
104
|
|
|
73
105
|
export type RpcMethodHandler = (
|
package/src/db/schema/index.ts
CHANGED
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
2
2
|
|
|
3
3
|
export const transactions = sqliteTable(
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
4
|
+
"transactions",
|
|
5
|
+
{
|
|
6
|
+
signature: text("signature").primaryKey(),
|
|
7
|
+
slot: integer("slot").notNull(),
|
|
8
|
+
blockTime: integer("block_time"),
|
|
9
|
+
version: text("version").notNull(), // 0 | "legacy"
|
|
10
|
+
errJson: text("err_json"),
|
|
11
|
+
fee: integer("fee").notNull(),
|
|
12
|
+
rawBase64: text("raw_base64").notNull(),
|
|
13
|
+
preBalancesJson: text("pre_balances_json").notNull(),
|
|
14
|
+
postBalancesJson: text("post_balances_json").notNull(),
|
|
15
|
+
logsJson: text("logs_json").notNull(),
|
|
16
|
+
preTokenBalancesJson: text("pre_token_balances_json")
|
|
17
|
+
.default("[]")
|
|
18
|
+
.notNull(),
|
|
19
|
+
postTokenBalancesJson: text("post_token_balances_json")
|
|
20
|
+
.default("[]")
|
|
21
|
+
.notNull(),
|
|
22
|
+
// Additional rich metadata captured after execution
|
|
23
|
+
innerInstructionsJson: text("inner_instructions_json")
|
|
24
|
+
.default("[]")
|
|
25
|
+
.notNull(),
|
|
26
|
+
computeUnits: integer("compute_units"),
|
|
27
|
+
returnDataProgramId: text("return_data_program_id"),
|
|
28
|
+
returnDataBase64: text("return_data_base64"),
|
|
29
|
+
},
|
|
30
|
+
(t) => ({
|
|
31
|
+
slotIdx: index("idx_transactions_slot").on(t.slot),
|
|
32
|
+
}),
|
|
26
33
|
);
|
|
27
34
|
|
|
28
35
|
export type TransactionRow = typeof transactions.$inferSelect;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { index, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
2
|
+
|
|
3
|
+
export const txAccountStates = sqliteTable(
|
|
4
|
+
"tx_account_states",
|
|
5
|
+
{
|
|
6
|
+
signature: text("signature").notNull(),
|
|
7
|
+
address: text("address").notNull(),
|
|
8
|
+
// JSON blobs capturing minimal account snapshot
|
|
9
|
+
// { lamports, ownerProgram, executable, rentEpoch, dataLen, dataBase64? }
|
|
10
|
+
preJson: text("pre_json"),
|
|
11
|
+
postJson: text("post_json"),
|
|
12
|
+
},
|
|
13
|
+
(t) => ({
|
|
14
|
+
pk: primaryKey({ columns: [t.signature, t.address], name: "pk_tx_account_states" }),
|
|
15
|
+
addrIdx: index("idx_tx_account_states_address").on(t.address),
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export type TxAccountStateRow = typeof txAccountStates.$inferSelect;
|
|
20
|
+
export type NewTxAccountStateRow = typeof txAccountStates.$inferInsert;
|
|
21
|
+
|
package/src/db/tx-store.ts
CHANGED
|
@@ -4,27 +4,36 @@ import { accounts } from "./schema/accounts";
|
|
|
4
4
|
import { addressSignatures } from "./schema/address-signatures";
|
|
5
5
|
import { transactions } from "./schema/transactions";
|
|
6
6
|
import { txAccounts } from "./schema/tx-accounts";
|
|
7
|
+
import { txAccountStates } from "./schema/tx-account-states";
|
|
7
8
|
|
|
8
9
|
export type InsertTxBundle = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
10
|
+
signature: string;
|
|
11
|
+
slot: number;
|
|
12
|
+
blockTime?: number;
|
|
13
|
+
version: 0 | "legacy";
|
|
14
|
+
fee: number;
|
|
15
|
+
err: unknown | null;
|
|
16
|
+
rawBase64: string;
|
|
17
|
+
preBalances: number[];
|
|
18
|
+
postBalances: number[];
|
|
19
|
+
logs: string[];
|
|
20
|
+
innerInstructions?: unknown[];
|
|
21
|
+
computeUnits?: number | bigint | null;
|
|
22
|
+
returnData?: { programId: string; dataBase64: string } | null;
|
|
23
|
+
accounts: Array<{
|
|
24
|
+
address: string;
|
|
25
|
+
index: number;
|
|
26
|
+
signer: boolean;
|
|
27
|
+
writable: boolean;
|
|
28
|
+
programIdIndex?: number;
|
|
29
|
+
}>;
|
|
30
|
+
preTokenBalances?: unknown[];
|
|
31
|
+
postTokenBalances?: unknown[];
|
|
32
|
+
accountStates?: Array<{
|
|
33
|
+
address: string;
|
|
34
|
+
pre?: Partial<AccountSnapshot> | null;
|
|
35
|
+
post?: Partial<AccountSnapshot> | null;
|
|
36
|
+
}>;
|
|
28
37
|
};
|
|
29
38
|
|
|
30
39
|
export type AccountSnapshot = {
|
|
@@ -39,57 +48,81 @@ export type AccountSnapshot = {
|
|
|
39
48
|
};
|
|
40
49
|
|
|
41
50
|
export class TxStore {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
51
|
+
async insertTransactionBundle(bundle: InsertTxBundle): Promise<void> {
|
|
52
|
+
const errJson = bundle.err ? JSON.stringify(bundle.err) : null;
|
|
53
|
+
await db.transaction(async (tx) => {
|
|
54
|
+
await tx
|
|
55
|
+
.insert(transactions)
|
|
56
|
+
.values({
|
|
57
|
+
signature: bundle.signature,
|
|
58
|
+
slot: bundle.slot,
|
|
59
|
+
blockTime: bundle.blockTime ?? null,
|
|
60
|
+
version: String(bundle.version),
|
|
61
|
+
errJson,
|
|
62
|
+
fee: bundle.fee,
|
|
63
|
+
rawBase64: bundle.rawBase64,
|
|
64
|
+
preBalancesJson: JSON.stringify(bundle.preBalances ?? []),
|
|
65
|
+
postBalancesJson: JSON.stringify(bundle.postBalances ?? []),
|
|
66
|
+
logsJson: JSON.stringify(bundle.logs ?? []),
|
|
67
|
+
preTokenBalancesJson: JSON.stringify(bundle.preTokenBalances ?? []),
|
|
68
|
+
postTokenBalancesJson: JSON.stringify(bundle.postTokenBalances ?? []),
|
|
69
|
+
innerInstructionsJson: JSON.stringify(bundle.innerInstructions ?? []),
|
|
70
|
+
computeUnits:
|
|
71
|
+
bundle.computeUnits == null
|
|
72
|
+
? null
|
|
73
|
+
: Number(bundle.computeUnits),
|
|
74
|
+
returnDataProgramId: bundle.returnData?.programId ?? null,
|
|
75
|
+
returnDataBase64: bundle.returnData?.dataBase64 ?? null,
|
|
76
|
+
})
|
|
77
|
+
.onConflictDoNothing();
|
|
78
|
+
|
|
79
|
+
if (Array.isArray(bundle.accounts) && bundle.accounts.length > 0) {
|
|
80
|
+
await tx
|
|
81
|
+
.insert(txAccounts)
|
|
82
|
+
.values(
|
|
83
|
+
bundle.accounts.map((a) => ({
|
|
84
|
+
signature: bundle.signature,
|
|
85
|
+
accountIndex: a.index,
|
|
86
|
+
address: a.address,
|
|
87
|
+
signer: a.signer ? 1 : 0,
|
|
88
|
+
writable: a.writable ? 1 : 0,
|
|
89
|
+
programIdIndex: a.programIdIndex ?? null,
|
|
90
|
+
})),
|
|
91
|
+
)
|
|
92
|
+
.onConflictDoNothing();
|
|
93
|
+
|
|
94
|
+
await tx
|
|
95
|
+
.insert(addressSignatures)
|
|
96
|
+
.values(
|
|
97
|
+
bundle.accounts.map((a) => ({
|
|
98
|
+
address: a.address,
|
|
99
|
+
signature: bundle.signature,
|
|
100
|
+
slot: bundle.slot,
|
|
101
|
+
err: errJson ? 1 : 0,
|
|
102
|
+
blockTime: bundle.blockTime ?? null,
|
|
103
|
+
})),
|
|
104
|
+
)
|
|
105
|
+
.onConflictDoNothing();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
Array.isArray(bundle.accountStates) &&
|
|
110
|
+
bundle.accountStates.length > 0
|
|
111
|
+
) {
|
|
112
|
+
await tx
|
|
113
|
+
.insert(txAccountStates)
|
|
114
|
+
.values(
|
|
115
|
+
bundle.accountStates.map((s) => ({
|
|
116
|
+
signature: bundle.signature,
|
|
117
|
+
address: s.address,
|
|
118
|
+
preJson: s.pre ? JSON.stringify(s.pre) : null,
|
|
119
|
+
postJson: s.post ? JSON.stringify(s.post) : null,
|
|
120
|
+
})),
|
|
121
|
+
)
|
|
122
|
+
.onConflictDoNothing();
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
93
126
|
|
|
94
127
|
async upsertAccounts(snapshots: AccountSnapshot[]): Promise<void> {
|
|
95
128
|
if (!Array.isArray(snapshots) || snapshots.length === 0) return;
|
|
@@ -10,8 +10,14 @@ import mig0000 from "../drizzle/0000_friendly_millenium_guard.sql" with {
|
|
|
10
10
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
11
11
|
// @ts-expect-error - Bun import attributes
|
|
12
12
|
import mig0001 from "../drizzle/0001_stale_sentinels.sql" with { type: "file" };
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
14
|
+
// @ts-expect-error - Bun import attributes
|
|
15
|
+
import mig0002 from "../drizzle/0002_graceful_caretaker.sql" with {
|
|
16
|
+
type: "file",
|
|
17
|
+
};
|
|
13
18
|
|
|
14
19
|
export const bundledMigrations: Array<{ name: string; path: string }> = [
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
{ name: "0000_friendly_millenium_guard.sql", path: mig0000 },
|
|
21
|
+
{ name: "0001_stale_sentinels.sql", path: mig0001 },
|
|
22
|
+
{ name: "0002_graceful_caretaker.sql", path: mig0002 },
|
|
17
23
|
];
|