@nodiusxyz/solana-agent-kit 0.0.0-stage → 0.1.1

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.
@@ -0,0 +1,112 @@
1
+ import { NodiusClient } from "@nodiusxyz/sdk";
2
+ import { Connection } from "@solana/web3.js";
3
+ import { endpoint, rawAmount } from "./config.js";
4
+ export interface Pricing {
5
+ ok?: boolean;
6
+ credits_per_usdc: number;
7
+ sol_usdc: { price: number; age_secs: number } | null;
8
+ methods: Record<string, any>;
9
+ }
10
+ export function finite(n: unknown): number {
11
+ if (typeof n !== "number" || !Number.isFinite(n) || n < 0)
12
+ throw new Error("Invalid live numeric field");
13
+ return n;
14
+ }
15
+ export function estimateBuy(p: Pricing, lamports: bigint): number {
16
+ const c = finite(p.credits_per_usdc),
17
+ q = p.sol_usdc,
18
+ m = p.methods?.executeSnipe;
19
+ if (!q || finite(q.age_secs) > 15 || finite(q.price) <= 0 || !m || c <= 0)
20
+ throw new Error("Fresh authoritative buy pricing unavailable");
21
+ const v =
22
+ finite(m.build_fee_credits) +
23
+ Math.ceil(
24
+ ((Number(lamports) / 1e9) * q.price * c * finite(m.flow_fee_bps)) / 10000,
25
+ );
26
+ if (!Number.isSafeInteger(v) || v <= 0 || !Number.isSafeInteger(c) || c <= 0)
27
+ throw new Error("Unsafe pricing estimate/rate");
28
+ return v;
29
+ }
30
+ export function sendFee(p: Pricing): number {
31
+ const m = p.methods?.sendAndConfirm;
32
+ const base = finite(m?.base_credits);
33
+ // landed_credits is the terminal TOTAL, not a surcharge on base_credits.
34
+ const fee = Math.max(base, finite(m?.landed_credits));
35
+ if (!Number.isSafeInteger(fee) || fee <= 0)
36
+ throw new Error("Invalid sendAndConfirm live fee");
37
+ return fee;
38
+ }
39
+ export class Transport {
40
+ readonly sdk: NodiusClient;
41
+ readonly connection: Connection;
42
+ readonly origin: string;
43
+ constructor(
44
+ origin: string,
45
+ private readonly apiKey?: string,
46
+ ) {
47
+ this.origin = endpoint(origin);
48
+ this.sdk = new NodiusClient(this.origin, {
49
+ apiKey,
50
+ autoPay: false,
51
+ maxRetries: 0,
52
+ timeout: 10000,
53
+ });
54
+ this.connection = new Connection(this.origin + "/rpc", {
55
+ commitment: "confirmed",
56
+ httpHeaders: apiKey ? { "X-Api-Key": apiKey } : {},
57
+ disableRetryOnRateLimit: true,
58
+ fetch: (url, init) =>
59
+ fetch(url, {
60
+ ...init,
61
+ signal: AbortSignal.timeout(10000),
62
+ redirect: "error",
63
+ cache: "no-store",
64
+ }),
65
+ });
66
+ }
67
+ async pricing(): Promise<Pricing> {
68
+ const r = await fetch(this.origin + "/pricing", {
69
+ signal: AbortSignal.timeout(10000),
70
+ cache: "no-store",
71
+ redirect: "error",
72
+ });
73
+ if (!r.ok) throw new Error("Pricing HTTP failure");
74
+ const p = (await r.json()) as Pricing;
75
+ if (p.ok !== true) throw new Error("Invalid pricing response");
76
+ finite(p.credits_per_usdc);
77
+ return p;
78
+ }
79
+ // The published SDK converts bigint to a JSON STRING, but rpc.rs requires as_u64().
80
+ // Emit validated digits as an unquoted numeric JSON literal, never through Number.
81
+ async sell(
82
+ mint: string,
83
+ amount: string,
84
+ slippage: number,
85
+ priority: number,
86
+ ): Promise<any> {
87
+ rawAmount(amount);
88
+ const params = JSON.stringify({
89
+ mint,
90
+ slippage_bps: slippage,
91
+ priority_fee_micro_lamports: priority,
92
+ });
93
+ const body = `{"jsonrpc":"2.0","id":1,"method":"executeSell","params":[${params.slice(0, -1)},"token_amount":${amount}}]}`;
94
+ const r = await fetch(this.origin + "/rpc", {
95
+ method: "POST",
96
+ headers: {
97
+ "Content-Type": "application/json",
98
+ ...(this.apiKey ? { "X-Api-Key": this.apiKey } : {}),
99
+ },
100
+ body,
101
+ signal: AbortSignal.timeout(10000),
102
+ redirect: "error",
103
+ });
104
+ if (!r.ok)
105
+ throw new Error("Sell build HTTP failure (charge may be uncertain)");
106
+ const v = (await r.json()) as any;
107
+ if (v.error)
108
+ throw new Error("Sell builder rejected; consult account ledger");
109
+ if (!v.result) throw new Error("Malformed sell build result");
110
+ return v.result;
111
+ }
112
+ }
@@ -0,0 +1,315 @@
1
+ import {
2
+ PublicKey,
3
+ VersionedTransaction,
4
+ TransactionMessage,
5
+ TransactionInstruction,
6
+ ComputeBudgetProgram,
7
+ SystemProgram,
8
+ } from "@solana/web3.js";
9
+
10
+ const PUMP = new PublicKey("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P");
11
+ const FEE = new PublicKey("pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ");
12
+ const ATA = new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
13
+ const TOKEN = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
14
+ const TOKEN22 = new PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");
15
+ const WSOL = new PublicKey("So11111111111111111111111111111111111111112");
16
+ const SYSTEM = SystemProgram.programId;
17
+ const pda = (program: PublicKey, seed: string, ...keys: PublicKey[]) =>
18
+ PublicKey.findProgramAddressSync(
19
+ [Buffer.from(seed), ...keys.map((k) => k.toBuffer())],
20
+ program,
21
+ )[0];
22
+ const ata = (owner: PublicKey, mint: PublicKey, token: PublicKey) =>
23
+ PublicKey.findProgramAddressSync(
24
+ [owner.toBuffer(), token.toBuffer(), mint.toBuffer()],
25
+ ATA,
26
+ )[0];
27
+ function requirePolicy(ok: boolean, reason: string): asserts ok {
28
+ if (!ok) throw new Error("V2 policy: " + reason);
29
+ }
30
+ export interface V2Policy {
31
+ side: "buy" | "sell";
32
+ mint: PublicKey;
33
+ maxSol: bigint;
34
+ amountRaw?: bigint;
35
+ maxPriority: bigint;
36
+ maxPriorityLamports?: bigint;
37
+ requestedPriority?: bigint;
38
+ reportedCost: bigint;
39
+ tokensOut?: bigint;
40
+ }
41
+ /** Offline instruction policy only. Never signs, mutates or fetches account state. */
42
+ export function verifyV2(
43
+ tx: VersionedTransaction,
44
+ wallet: PublicKey,
45
+ p: V2Policy,
46
+ ): {
47
+ tradeCost: bigint;
48
+ ataCount: number;
49
+ instructions: any[];
50
+ limitations: string;
51
+ } {
52
+ requirePolicy(p.side === "buy" || p.side === "sell", "unknown side");
53
+ requirePolicy(p.maxPriority >= 0n && p.maxSol >= 0n, "negative budget");
54
+ const m = tx.message;
55
+ requirePolicy(
56
+ m.version === 0 && m.addressTableLookups.length === 0,
57
+ "requires v0 without ALTs",
58
+ );
59
+ requirePolicy(
60
+ m.header.numRequiredSignatures === 1 &&
61
+ m.staticAccountKeys[0]?.equals(wallet) &&
62
+ m.isAccountWritable(0),
63
+ "payer/signer mismatch",
64
+ );
65
+ requirePolicy(
66
+ tx.signatures.length <= 1 &&
67
+ tx.signatures.every((s) => s.length === 64 && s.every((b) => b === 0)),
68
+ "must be unsigned",
69
+ );
70
+ // Account for the one 64-byte signing placeholder even when builder omits it.
71
+ requirePolicy(
72
+ 1 + 64 + m.serialize().length <= 1232,
73
+ "signed envelope exceeds packet size",
74
+ );
75
+ const instructions = TransactionMessage.decompile(m).instructions;
76
+ const buy = p.side === "buy";
77
+ requirePolicy(
78
+ buy
79
+ ? instructions.length === 8
80
+ : instructions.length === 3 || instructions.length === 4,
81
+ "unexpected instruction sequence",
82
+ );
83
+ let price = 0n,
84
+ limit = 0n;
85
+ for (let i = 0; i < 2; i++) {
86
+ const ix = instructions[i],
87
+ d = Buffer.from(ix.data);
88
+ requirePolicy(
89
+ ix.programId.equals(ComputeBudgetProgram.programId) &&
90
+ ix.keys.length === 0,
91
+ "compute program/accounts",
92
+ );
93
+ if (i === 0) {
94
+ requirePolicy(
95
+ d.length === 9 && d[0] === 3,
96
+ "canonical compute price required first",
97
+ );
98
+ price = d.readBigUInt64LE(1);
99
+ requirePolicy(price <= p.maxPriority, "CU price exceeds explicit cap");
100
+ } else {
101
+ requirePolicy(
102
+ (d.length === 5 || d.length === 9) && d[0] === 2,
103
+ "canonical compute limit required second",
104
+ );
105
+ requirePolicy(
106
+ d.length === 5 || d.readUInt32LE(5) === 0,
107
+ "legacy CU high bytes nonzero",
108
+ );
109
+ limit = BigInt(d.readUInt32LE(1));
110
+ requirePolicy(
111
+ limit >= 1n && limit <= 1400000n,
112
+ "CU limit outside runtime bounds",
113
+ );
114
+ }
115
+ }
116
+ if (p.requestedPriority !== undefined)
117
+ requirePolicy(
118
+ price === p.requestedPriority,
119
+ "priority price differs from explicit request",
120
+ );
121
+ if (p.maxPriorityLamports !== undefined)
122
+ requirePolicy(
123
+ p.maxPriorityLamports >= 0n &&
124
+ (price * limit + 999999n) / 1000000n <= p.maxPriorityLamports,
125
+ "total priority fee exceeds lamport cap",
126
+ );
127
+ const trade = instructions[buy ? 7 : 2],
128
+ d = Buffer.from(trade.data);
129
+ requirePolicy(
130
+ trade.programId.equals(PUMP) && trade.keys.length === (buy ? 27 : 26),
131
+ "one Pump trade layout required",
132
+ );
133
+ requirePolicy(
134
+ d.length === 24 &&
135
+ d
136
+ .subarray(0, 8)
137
+ .equals(
138
+ Buffer.from(buy ? "b817ee6167c5d33d" : "5df6823ce7e940b2", "hex"),
139
+ ),
140
+ "trade discriminator/payload",
141
+ );
142
+ const quantity = d.readBigUInt64LE(8),
143
+ bound = d.readBigUInt64LE(16);
144
+ requirePolicy(
145
+ quantity > 0n && bound > 0n && bound === p.reportedCost,
146
+ "zero or reported trade bound mismatch",
147
+ );
148
+ if (buy)
149
+ requirePolicy(
150
+ quantity === p.tokensOut && bound <= p.maxSol,
151
+ "buy tokensOut/trade cap mismatch",
152
+ );
153
+ else
154
+ requirePolicy(quantity === p.amountRaw, "sell exact raw quantity mismatch");
155
+ const k = trade.keys;
156
+ const baseToken = k[3].pubkey;
157
+ requirePolicy(
158
+ baseToken.equals(TOKEN) || baseToken.equals(TOKEN22),
159
+ "unsupported base token program",
160
+ );
161
+ const curve = pda(PUMP, "bonding-curve", p.mint),
162
+ userVolume = pda(PUMP, "user_volume_accumulator", wallet);
163
+ const baseAta = ata(wallet, p.mint, baseToken),
164
+ wsolAta = ata(wallet, WSOL, TOKEN);
165
+ const volumeIndex = buy ? 20 : 19,
166
+ feeIndex = buy ? 22 : 21;
167
+ const expected = new Map<number, PublicKey>([
168
+ [0, pda(PUMP, "global")],
169
+ [1, p.mint],
170
+ [2, WSOL],
171
+ [3, baseToken],
172
+ [4, TOKEN],
173
+ [5, ATA],
174
+ [7, ata(k[6].pubkey, WSOL, TOKEN)],
175
+ [9, ata(k[8].pubkey, WSOL, TOKEN)],
176
+ [10, curve],
177
+ [11, ata(curve, p.mint, baseToken)],
178
+ [12, ata(curve, WSOL, TOKEN)],
179
+ [13, wallet],
180
+ [14, baseAta],
181
+ [15, wsolAta],
182
+ [17, ata(k[16].pubkey, WSOL, TOKEN)],
183
+ [18, pda(FEE, "sharing-config", p.mint)],
184
+ [volumeIndex, userVolume],
185
+ [volumeIndex + 1, ata(userVolume, WSOL, TOKEN)],
186
+ // Source execute_snipe.rs: ["fee_config", pump_program_id] under pfee.
187
+ [feeIndex, pda(FEE, "fee_config", PUMP)],
188
+ [feeIndex + 1, FEE],
189
+ [feeIndex + 2, SYSTEM],
190
+ [feeIndex + 3, pda(PUMP, "__event_authority")],
191
+ [feeIndex + 4, PUMP],
192
+ ]);
193
+ if (buy) expected.set(19, pda(PUMP, "global_volume_accumulator"));
194
+ for (const [i, address] of expected)
195
+ requirePolicy(
196
+ k[i].pubkey.equals(address),
197
+ "trade account/PDA mismatch at " + i,
198
+ );
199
+ const readonly = new Set([
200
+ 0,
201
+ 1,
202
+ 2,
203
+ 3,
204
+ 4,
205
+ 5,
206
+ 18,
207
+ feeIndex,
208
+ feeIndex + 1,
209
+ feeIndex + 2,
210
+ feeIndex + 3,
211
+ feeIndex + 4,
212
+ ]);
213
+ k.forEach((key, i) =>
214
+ requirePolicy(
215
+ key.isSigner === (i === 13) && key.isWritable === !readonly.has(i),
216
+ "trade privilege mismatch at " + i,
217
+ ),
218
+ );
219
+ // Privileges are message-global: wallet aliases in ATA/close authority positions
220
+ // are also writable signers. Exact comparison rejects elevation of fixed programs.
221
+ function keys(
222
+ ix: TransactionInstruction,
223
+ addresses: PublicKey[],
224
+ writable: boolean[],
225
+ signer: boolean[],
226
+ ) {
227
+ requirePolicy(
228
+ ix.keys.length === addresses.length &&
229
+ ix.keys.every(
230
+ (key, i) =>
231
+ key.pubkey.equals(addresses[i]) &&
232
+ key.isWritable === writable[i] &&
233
+ key.isSigner === signer[i],
234
+ ),
235
+ "auxiliary accounts/privileges",
236
+ );
237
+ }
238
+ function create(
239
+ ix: TransactionInstruction,
240
+ mint: PublicKey,
241
+ token: PublicKey,
242
+ address: PublicKey,
243
+ ) {
244
+ requirePolicy(
245
+ ix.programId.equals(ATA) && ix.data.length === 1 && ix.data[0] === 1,
246
+ "idempotent ATA required",
247
+ );
248
+ keys(
249
+ ix,
250
+ [wallet, address, wallet, mint, SYSTEM, token],
251
+ [true, true, true, false, false, false],
252
+ [true, false, true, false, false, false],
253
+ );
254
+ }
255
+ function close(
256
+ ix: TransactionInstruction,
257
+ token: PublicKey,
258
+ address: PublicKey,
259
+ ) {
260
+ requirePolicy(
261
+ ix.programId.equals(token) && ix.data.length === 1 && ix.data[0] === 9,
262
+ "exact token close required",
263
+ );
264
+ keys(
265
+ ix,
266
+ [address, wallet, wallet],
267
+ [true, true, true],
268
+ [false, true, true],
269
+ );
270
+ }
271
+ if (buy) {
272
+ // This current server order was simulated successfully; closing after buy is
273
+ // NOT equivalent in that test. This order is not a funding-lock proof.
274
+ create(instructions[2], WSOL, TOKEN, wsolAta);
275
+ const transfer = instructions[3],
276
+ td = Buffer.from(transfer.data);
277
+ requirePolicy(
278
+ transfer.programId.equals(SYSTEM) &&
279
+ td.length === 12 &&
280
+ td.readUInt32LE(0) === 2,
281
+ "exact System transfer required",
282
+ );
283
+ keys(transfer, [wallet, wsolAta], [true, true], [true, false]);
284
+ requirePolicy(
285
+ td.readBigUInt64LE(4) <= bound,
286
+ "funding exceeds decoded buy bound",
287
+ );
288
+ const sync = instructions[4];
289
+ requirePolicy(
290
+ sync.programId.equals(TOKEN) &&
291
+ sync.data.length === 1 &&
292
+ sync.data[0] === 17,
293
+ "exact SyncNative required",
294
+ );
295
+ keys(sync, [wsolAta], [true], [false]);
296
+ close(instructions[5], TOKEN, wsolAta);
297
+ create(instructions[6], p.mint, baseToken, baseAta);
298
+ } else if (instructions.length === 4)
299
+ close(instructions[3], baseToken, baseAta);
300
+ return {
301
+ tradeCost: buy ? bound : 0n,
302
+ ataCount: buy ? 2 : 0,
303
+ instructions: instructions.map((ix) => ({
304
+ program: ix.programId.toBase58(),
305
+ accounts: ix.keys.map((k) => ({
306
+ address: k.pubkey.toBase58(),
307
+ writable: k.isWritable,
308
+ signer: k.isSigner,
309
+ })),
310
+ data_hex: Buffer.from(ix.data).toString("hex"),
311
+ })),
312
+ limitations:
313
+ "Partial offline policy, not a complete protocol safety proof. Dynamic fee recipients and creator vault are trusted builder/state inputs; only their associated WSOL derivations are checked. Account ownership/state, dynamic fees, rent, inventory/full-sell eligibility and program/CPI behavior are not proven. Buy maxSol is a trade cap, not an aggregate CPI-fee/rent/network-spend cap; the observed pre-buy refund does not lock funds. Caller must separately bound exact message fees/rent and require explicit --ack-incomplete-policy consent before signing.",
314
+ };
315
+ }