@nodiusxyz/solana-agent-kit 0.0.0-stage → 0.1.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/dist/index.js ADDED
@@ -0,0 +1,276 @@
1
+ import { z } from "zod";
2
+ import { run } from "./internal/app.js";
3
+ import { endpoint as validateEndpoint } from "./internal/config.js";
4
+ const integer = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
5
+ const decimal = z.string().regex(/^(0|[1-9]\d*)(\.\d{1,9})?$/);
6
+ const base = {
7
+ mint: z.string().min(32).max(44),
8
+ maxSol: decimal,
9
+ maxFeeCredits: integer,
10
+ slippageBps: integer.max(9999),
11
+ priorityFee: integer,
12
+ maxPriorityFee: integer,
13
+ maxPriorityLamports: integer.optional(),
14
+ execute: z.boolean().default(false),
15
+ buildOnly: z.boolean().default(false),
16
+ operationId: z
17
+ .string()
18
+ .regex(/^[A-Za-z0-9_-]{1,80}$/)
19
+ .optional(),
20
+ acceptUnquotedBuildCharge: z.boolean().default(false),
21
+ };
22
+ export const tradeSchema = z.discriminatedUnion("side", [
23
+ z.object({ ...base, side: z.literal("buy"), amountSol: decimal }).strict(),
24
+ z
25
+ .object({
26
+ ...base,
27
+ side: z.literal("sell"),
28
+ amountRaw: z.string().regex(/^[1-9]\d*$/),
29
+ })
30
+ .strict(),
31
+ ]);
32
+ const tradeActionSchema = z
33
+ .object({
34
+ ...base,
35
+ side: z.enum(["buy", "sell"]),
36
+ amountSol: decimal.optional(),
37
+ amountRaw: z
38
+ .string()
39
+ .regex(/^[1-9]\d*$/)
40
+ .optional(),
41
+ })
42
+ .strict();
43
+ export const monitorSchema = z
44
+ .object({
45
+ maxFeeCredits: integer,
46
+ seconds: integer.min(1).max(300).default(30),
47
+ maxEvents: integer.min(1).max(1000).default(100),
48
+ mint: z.string().min(32).max(44).optional(),
49
+ kinds: z
50
+ .array(z.enum(["newPair", "graduation", "walletActivity", "largeSwap"]))
51
+ .min(1)
52
+ .max(4)
53
+ .optional(),
54
+ })
55
+ .strict();
56
+ export const statusSchema = z
57
+ .object({ operationId: z.string().regex(/^[A-Za-z0-9_-]{1,80}$/) })
58
+ .strict();
59
+ export const sendSchema = z
60
+ .object({
61
+ signedTxBase64: z.string().min(1).max(1644),
62
+ maxFeeCredits: integer,
63
+ execute: z.boolean().default(false),
64
+ operationId: z
65
+ .string()
66
+ .regex(/^[A-Za-z0-9_-]{1,80}$/)
67
+ .optional(),
68
+ })
69
+ .strict();
70
+ export function createNodiusPlugin(options) {
71
+ const parsed = z
72
+ .object({
73
+ apiKey: z
74
+ .string()
75
+ .min(1)
76
+ .regex(/^[^\r\n]+$/)
77
+ .optional(),
78
+ endpoint: z.string().optional(),
79
+ journalDir: z.string().min(1),
80
+ allowExecution: z.boolean().default(false),
81
+ ackIncompletePolicy: z.boolean().default(false),
82
+ })
83
+ .strict()
84
+ .parse(options);
85
+ const o = Object.freeze({
86
+ ...parsed,
87
+ endpoint: validateEndpoint(parsed.endpoint ?? "https://rpc.nodius.xyz"),
88
+ });
89
+ let owner;
90
+ const recent = z
91
+ .object({ limit: integer.min(1).max(50).default(20) })
92
+ .strict();
93
+ function clean(value) {
94
+ const text = (s) => o.apiKey ? s.split(o.apiKey).join("[REDACTED]") : s;
95
+ if (typeof value === "string")
96
+ return text(value);
97
+ if (typeof value === "bigint")
98
+ return value.toString();
99
+ if (Array.isArray(value))
100
+ return value.map(clean);
101
+ if (value && typeof value === "object")
102
+ return Object.fromEntries(Object.entries(value)
103
+ .filter(([, v]) => v !== undefined)
104
+ .map(([k, v]) => [text(k), clean(v)]));
105
+ return value;
106
+ }
107
+ async function invoke(agent, input, kind) {
108
+ try {
109
+ return clean(await dispatch(agent, input, kind));
110
+ }
111
+ catch (e) {
112
+ throw new Error(clean(e instanceof Error ? e.message : String(e)));
113
+ }
114
+ }
115
+ async function dispatch(agent, input, kind) {
116
+ if (!owner || agent !== owner)
117
+ throw Error("Plugin agent mismatch");
118
+ let args = {
119
+ command: kind,
120
+ endpoint: o.endpoint ?? "https://rpc.nodius.xyz",
121
+ journal: o.journalDir,
122
+ };
123
+ if (kind === "recent") {
124
+ const v = recent.parse(input);
125
+ args.limit = String(v.limit);
126
+ }
127
+ else if (kind === "send") {
128
+ const v = sendSchema.parse(input);
129
+ if (v.execute && o.allowExecution !== true)
130
+ throw Error("Host execution is disabled");
131
+ if (v.execute && o.ackIncompletePolicy !== true)
132
+ throw Error("Host incomplete policy acknowledgement required");
133
+ args["signed-tx-base64"] = v.signedTxBase64;
134
+ args.wallet = owner.wallet.publicKey.toBase58();
135
+ args["max-fee-credits"] = String(v.maxFeeCredits);
136
+ args.execute = v.execute;
137
+ if (v.operationId)
138
+ args["operation-id"] = v.operationId;
139
+ }
140
+ else if (kind === "status") {
141
+ const v = statusSchema.parse(input);
142
+ args["operation-id"] = v.operationId;
143
+ }
144
+ else if (kind === "monitor") {
145
+ const v = monitorSchema.parse(input);
146
+ args["max-fee-credits"] = String(v.maxFeeCredits);
147
+ args.seconds = String(v.seconds);
148
+ args["max-events"] = String(v.maxEvents);
149
+ if (v.mint)
150
+ args.mint = v.mint;
151
+ if (v.kinds)
152
+ args.kinds = v.kinds.join(",");
153
+ }
154
+ else {
155
+ const v = tradeSchema.parse(input);
156
+ if (v.execute && o.allowExecution !== true)
157
+ throw Error("Host execution is disabled");
158
+ if (v.execute && !v.buildOnly && o.ackIncompletePolicy !== true)
159
+ throw Error("Host incomplete policy acknowledgement required");
160
+ args.command = v.side;
161
+ args.wallet = owner.wallet.publicKey.toBase58();
162
+ const map = {
163
+ amountSol: "amount-sol",
164
+ amountRaw: "amount-raw",
165
+ maxSol: "max-sol",
166
+ maxFeeCredits: "max-fee-credits",
167
+ slippageBps: "slippage-bps",
168
+ priorityFee: "priority-fee",
169
+ maxPriorityFee: "max-priority-fee",
170
+ maxPriorityLamports: "max-priority-lamports",
171
+ operationId: "operation-id",
172
+ buildOnly: "build-only",
173
+ acceptUnquotedBuildCharge: "accept-unquoted-build-charge",
174
+ };
175
+ for (const [k, value] of Object.entries(v)) {
176
+ if (k === "side")
177
+ continue;
178
+ args[map[k] ?? k] = typeof value === "boolean" ? value : String(value);
179
+ }
180
+ args["ack-incomplete-policy"] = o.ackIncompletePolicy === true;
181
+ }
182
+ const notifications = [];
183
+ let monitorPlan = {};
184
+ const result = await run(args, { NODIUS_API_KEY: o.apiKey }, (v) => {
185
+ if (kind === "monitor") {
186
+ if (v.state === "monitor-plan")
187
+ monitorPlan = v;
188
+ else
189
+ notifications.push(v);
190
+ }
191
+ }, {
192
+ publicKey: owner.wallet.publicKey,
193
+ signTransaction: (tx) => owner.wallet.signTransaction(tx),
194
+ });
195
+ return kind === "monitor"
196
+ ? { ...monitorPlan, ...result, notifications }
197
+ : result;
198
+ }
199
+ const plugin = {
200
+ name: "nodius",
201
+ methods: {
202
+ nodiusSend: (input) => invoke(owner, input, "send"),
203
+ nodiusStatus: (input) => invoke(owner, input, "status"),
204
+ nodiusMonitor: (input) => invoke(owner, input, "monitor"),
205
+ nodiusRecent: (input = {}) => invoke(owner, input, "recent"),
206
+ nodiusTrade: (input) => invoke(owner, input, "trade"),
207
+ },
208
+ actions: [
209
+ {
210
+ name: "nodiusSend",
211
+ similes: [],
212
+ description: "Submit independently reviewed, already signed own-wallet transaction. Defaults plan; no trade instruction proof. Requires both host permissions.",
213
+ examples: [],
214
+ schema: sendSchema,
215
+ handler: (a, i) => invoke(a, i, "send"),
216
+ },
217
+ {
218
+ name: "nodiusStatus",
219
+ similes: [],
220
+ description: "Reconcile local operation status; never rebuild or rebroadcast",
221
+ examples: [],
222
+ schema: statusSchema,
223
+ handler: (a, i) => invoke(a, i, "status"),
224
+ },
225
+ {
226
+ name: "nodiusMonitor",
227
+ similes: [],
228
+ description: "Paid bounded signal monitor (<=300s, <=1000 events); no autonomous trading",
229
+ examples: [],
230
+ schema: monitorSchema,
231
+ handler: (a, i) => invoke(a, i, "monitor"),
232
+ },
233
+ {
234
+ name: "nodiusRecent",
235
+ similes: [],
236
+ description: "Read recent Nodius signals; never trades",
237
+ examples: [],
238
+ schema: recent,
239
+ handler: (a, i) => invoke(a, i, "recent"),
240
+ },
241
+ {
242
+ name: "nodiusTrade",
243
+ similes: [],
244
+ description: "Buy/sell defaults to read-only plan. Host must separately authorize charged execution and partial policy.",
245
+ examples: [],
246
+ schema: tradeActionSchema,
247
+ handler: (a, i) => invoke(a, i, "trade"),
248
+ },
249
+ ],
250
+ initialize(agent) {
251
+ if (owner && owner !== agent)
252
+ throw Error("Plugin instance already bound to another agent");
253
+ owner = agent;
254
+ },
255
+ };
256
+ // Keep ZodObject identity for the published tool converters, but sanitize
257
+ // validation issues before the framework's pre-handler executor surfaces them.
258
+ for (const action of plugin.actions) {
259
+ const schema = action.schema.strict();
260
+ const sync = schema.safeParse.bind(schema), asyncParse = schema.safeParseAsync.bind(schema);
261
+ schema.safeParse = (data, params) => {
262
+ const r = sync(data, params);
263
+ return r.success
264
+ ? r
265
+ : { success: false, error: new z.ZodError(clean(r.error.issues)) };
266
+ };
267
+ schema.safeParseAsync = async (data, params) => {
268
+ const r = await asyncParse(data, params);
269
+ return r.success
270
+ ? r
271
+ : { success: false, error: new z.ZodError(clean(r.error.issues)) };
272
+ };
273
+ action.schema = schema;
274
+ }
275
+ return plugin;
276
+ }
@@ -0,0 +1,6 @@
1
+ import { PublicKey, VersionedTransaction } from "@solana/web3.js";
2
+ import { Args } from "./config.js";
3
+ export declare function run(a: Args, env?: NodeJS.ProcessEnv, emit?: (v: any) => void, bridge?: {
4
+ publicKey: PublicKey;
5
+ signTransaction(tx: VersionedTransaction): Promise<VersionedTransaction>;
6
+ }): Promise<any>;
@@ -0,0 +1,387 @@
1
+ import { PublicKey, VersionedTransaction } from "@solana/web3.js";
2
+ import { required, integer, rawAmount, solLamports } from "./config.js";
3
+ import { Transport, estimateBuy, finite, sendFee } from "./transport.js";
4
+ import { monitor } from "./monitor.js";
5
+ import { Journal, fingerprint, outcome } from "./journal.js";
6
+ import { decodeBase64, verifyUnsigned, checkSignature, inspectEnvelope, decodeBuilderUnsigned, } from "./safety.js";
7
+ function safeLamports(n) {
8
+ if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 0)
9
+ throw new Error("Unsafe response lamports");
10
+ return BigInt(n);
11
+ }
12
+ function requireAuth(env) {
13
+ if (!env.NODIUS_API_KEY)
14
+ throw new Error("Set NODIUS_API_KEY in environment (not CLI)");
15
+ }
16
+ async function identity(t, wallet) {
17
+ const account = await t.sdk.getBillingAccount();
18
+ if (account.pubkey !== wallet.toBase58())
19
+ throw new Error("API key billing identity does not match wallet");
20
+ return account;
21
+ }
22
+ async function submit(t, j, id, tx, emit) {
23
+ const signature = checkSignature(tx);
24
+ await j.reserveSignature(signature);
25
+ await j.event(id, {
26
+ state: "unknown",
27
+ phase: "submission-intent",
28
+ signature,
29
+ });
30
+ emit({ state: "unknown", phase: "submission-intent", signature });
31
+ let state = "unknown";
32
+ try {
33
+ // Exactly one SDK call. maxRetries:0 disables upstream retries, NOT the
34
+ // server's intentional bounded same-byte multi-leg resends.
35
+ const result = await t.sdk.call("sendAndConfirm", [
36
+ Buffer.from(tx.serialize()).toString("base64"),
37
+ {
38
+ encoding: "base64",
39
+ maxRetries: 0,
40
+ skipPreflight: false,
41
+ preflightCommitment: "confirmed",
42
+ },
43
+ ]);
44
+ if (result?.signature === signature) {
45
+ const chainError = result.chain_err ?? result.chain_error;
46
+ const landedError = Number.isSafeInteger(result.slot) &&
47
+ result.slot >= 0 &&
48
+ typeof result.landing_time_ms === "number" &&
49
+ Number.isFinite(result.landing_time_ms) &&
50
+ result.landing_time_ms >= 0 &&
51
+ typeof result.error === "string" &&
52
+ result.error.length > 0 &&
53
+ !/timed?\s*out|timeout|without confirmation/i.test(result.error);
54
+ const definitive = chainError != null ||
55
+ result.definitive_error === true ||
56
+ landedError ||
57
+ (typeof result.error === "string" &&
58
+ /^(Transaction failed on-chain:|Transaction failed:)/.test(result.error));
59
+ state = definitive
60
+ ? "failed"
61
+ : result.confirmed === true
62
+ ? "confirmed"
63
+ : "unknown";
64
+ }
65
+ }
66
+ catch {
67
+ /* HTTP/RPC/timeout errors are ambiguous; no rebroadcast or rebuild. */
68
+ }
69
+ await j.event(id, {
70
+ state,
71
+ phase: state === "unknown" ? "submission-uncertain" : "submission-result",
72
+ signature,
73
+ });
74
+ return {
75
+ state,
76
+ operation_id: id,
77
+ signature,
78
+ note: state === "unknown"
79
+ ? "Do not rebuild/rebroadcast. Read status and account ledger; no automatic retry."
80
+ : undefined,
81
+ };
82
+ }
83
+ export async function run(a, env = process.env, emit = () => { }, bridge) {
84
+ const origin = typeof a.endpoint === "string"
85
+ ? a.endpoint
86
+ : (env.NODIUS_ENDPOINT ?? "https://rpc.nodius.xyz");
87
+ if (a.command === "help")
88
+ return {
89
+ commands: [
90
+ "recent",
91
+ "monitor",
92
+ "inspect",
93
+ "migrate",
94
+ "buy",
95
+ "sell",
96
+ "send",
97
+ "status",
98
+ ],
99
+ note: "See README. Trading defaults to a read-only plan; monitoring is a paid stream.",
100
+ };
101
+ const t = new Transport(origin, env.NODIUS_API_KEY);
102
+ if (a.command === "monitor") {
103
+ requireAuth(env);
104
+ const filters = {};
105
+ if (a.mint)
106
+ filters.mint = new PublicKey(required(a, "mint")).toBase58();
107
+ if (a.kinds) {
108
+ const kinds = required(a, "kinds").split(",");
109
+ if (kinds.some((k) => !["newPair", "graduation", "walletActivity", "largeSwap"].includes(k)) ||
110
+ new Set(kinds).size !== kinds.length)
111
+ throw new Error("Unsupported/duplicate signal kinds");
112
+ filters.kinds = kinds;
113
+ }
114
+ return monitor(t.origin, env.NODIUS_API_KEY, {
115
+ seconds: integer(String(a.seconds ?? "30"), 300),
116
+ maxEvents: integer(String(a["max-events"] ?? "100"), 1000),
117
+ maxFeeCredits: a["max-fee-credits"] === undefined
118
+ ? undefined
119
+ : integer(required(a, "max-fee-credits")),
120
+ filters,
121
+ }, emit);
122
+ }
123
+ if (a.command === "recent") {
124
+ const limit = integer(String(a.limit ?? "20"), 50);
125
+ if (!limit)
126
+ throw new Error("Positive limit required");
127
+ return t.sdk.signalsRecent(limit);
128
+ }
129
+ if (a.command === "inspect" || a.command === "migrate") {
130
+ requireAuth(env);
131
+ const wallet = new PublicKey(required(a, "wallet"));
132
+ const [account, balance, onchain, version] = await Promise.all([
133
+ t.sdk.getBillingAccount(),
134
+ t.connection.getBalance(wallet),
135
+ t.connection.getAccountInfo(wallet),
136
+ t.connection.getVersion(),
137
+ ]);
138
+ return {
139
+ state: "read-only",
140
+ wallet: wallet.toBase58(),
141
+ billing_identity_matches: account.pubkey === wallet.toBase58(),
142
+ billing_account: account,
143
+ balance_lamports: balance,
144
+ onchain_account: onchain,
145
+ version,
146
+ migration: "new Connection(origin + /rpc, {httpHeaders: {X-Api-Key: process.env.NODIUS_API_KEY}, disableRetryOnRateLimit: true})",
147
+ };
148
+ }
149
+ const j = new Journal(typeof a.journal === "string"
150
+ ? a.journal
151
+ : (env.NODIUS_JOURNAL ?? ".nodius-journal"), env.NODIUS_API_KEY);
152
+ if (a.command === "status") {
153
+ requireAuth(env);
154
+ const record = await j.read(required(a, "operation-id"));
155
+ if (!record.signature)
156
+ return {
157
+ ...record,
158
+ state: record.state === "built" ? "built" : "unknown",
159
+ note: "No known signature: check account ledger. No rebuild/resume performed.",
160
+ };
161
+ let chain = null;
162
+ try {
163
+ const status = (await t.sdk.getSignatureStatuses([
164
+ record.signature,
165
+ ]));
166
+ chain = status?.value?.[0] ?? null;
167
+ }
168
+ catch {
169
+ /* Read failure does not authorize another send. */
170
+ }
171
+ const observed = outcome(chain);
172
+ const state = ["confirmed", "failed"].includes(record.state)
173
+ ? record.state
174
+ : observed;
175
+ await j.event(record.id, {
176
+ state,
177
+ phase: "status-reconciled",
178
+ signature: record.signature,
179
+ });
180
+ return { ...record, state, chain_status: chain };
181
+ }
182
+ if (a.command === "send") {
183
+ const bytes = decodeBase64(required(a, "signed-tx-base64"));
184
+ if (bytes.length > 1232)
185
+ throw new Error("Signed transaction exceeds packet limit");
186
+ const tx = VersionedTransaction.deserialize(bytes);
187
+ if (!Buffer.from(tx.serialize()).equals(bytes))
188
+ throw new Error("Noncanonical signed transaction");
189
+ const signature = checkSignature(tx);
190
+ const wallet = new PublicKey(required(a, "wallet"));
191
+ if (tx.message.header.numRequiredSignatures !== 1 ||
192
+ !tx.message.staticAccountKeys[0].equals(wallet))
193
+ throw new Error("Signed transaction payer/signer mismatch");
194
+ const maxFee = integer(required(a, "max-fee-credits"));
195
+ const p = await t.pricing(), fee = sendFee(p);
196
+ if (fee > maxFee)
197
+ throw new Error("Send fee exceeds cap");
198
+ if (!a.execute)
199
+ return {
200
+ state: "plan",
201
+ signature,
202
+ send_fee_credits: fee,
203
+ note: "Existing signed transaction must be independently reviewed; this command does not validate trade instructions.",
204
+ };
205
+ requireAuth(env);
206
+ await identity(t, wallet);
207
+ const id = required(a, "operation-id");
208
+ await j.begin(id, fingerprint({ origin, signature }), {
209
+ command: "send",
210
+ wallet: wallet.toBase58(),
211
+ });
212
+ return submit(t, j, id, tx, emit);
213
+ }
214
+ if (a.command !== "buy" && a.command !== "sell")
215
+ throw new Error("Unsupported command");
216
+ if (a["build-only"] && !a.execute)
217
+ throw new Error("--build-only requires --execute");
218
+ const side = a.command;
219
+ const mint = new PublicKey(required(a, "mint")), wallet = new PublicKey(required(a, "wallet"));
220
+ const maxSol = solLamports(required(a, "max-sol"));
221
+ if (maxSol <= 0n || maxSol > BigInt(Number.MAX_SAFE_INTEGER))
222
+ throw new Error("Max SOL must be positive and safely representable in lamports");
223
+ const amount = side === "buy"
224
+ ? solLamports(required(a, "amount-sol"))
225
+ : rawAmount(required(a, "amount-raw"));
226
+ if (amount <= 0n ||
227
+ (side === "buy" &&
228
+ (amount > maxSol || amount > BigInt(Number.MAX_SAFE_INTEGER))))
229
+ throw new Error("Amount outside SOL budget");
230
+ const maxFee = integer(required(a, "max-fee-credits")), slippage = integer(required(a, "slippage-bps"), 9999), priority = integer(required(a, "priority-fee")), priorityCap = integer(required(a, "max-priority-fee"));
231
+ if (priority > priorityCap)
232
+ throw new Error("Priority exceeds cap");
233
+ if (priority > 0 && a["max-priority-lamports"] === undefined)
234
+ throw new Error("Nonzero priority requires --max-priority-lamports");
235
+ const maxPriorityLamports = BigInt(integer(String(a["max-priority-lamports"] ?? "0")));
236
+ const p = await t.pricing();
237
+ const sendCredits = sendFee(p);
238
+ const estimate = side === "buy" ? estimateBuy(p, maxSol) : null;
239
+ const plan = {
240
+ state: "plan",
241
+ side,
242
+ wallet: wallet.toBase58(),
243
+ mint: mint.toBase58(),
244
+ amount_raw: side === "sell" ? amount.toString() : undefined,
245
+ amount_lamports: side === "buy" ? amount.toString() : undefined,
246
+ max_sol_lamports: maxSol.toString(),
247
+ estimated_build_fee_credits: estimate,
248
+ send_fee_credits: sendCredits,
249
+ live_pricing: p,
250
+ limits: {
251
+ max_fee_credits: maxFee,
252
+ slippage_bps: slippage,
253
+ priority_fee_micro_lamports: priority,
254
+ max_priority_fee_micro_lamports: priorityCap,
255
+ },
256
+ warning: "Builder is charged BEFORE returning. Buy estimate is not a server-side billing cap. Sell build is unquoted; post-build cap cannot undo its charge. Send failure does not automatically refund a successful builder.",
257
+ };
258
+ if (!a.execute)
259
+ return plan;
260
+ if (side === "sell" && !a["accept-unquoted-build-charge"])
261
+ throw new Error("Sell charge unquoted: explicit --accept-unquoted-build-charge required BEFORE builder");
262
+ if (estimate !== null &&
263
+ estimate + (a["build-only"] ? 0 : sendCredits) > maxFee)
264
+ throw new Error("Estimated builder/send credits exceed cap");
265
+ if (!a["build-only"] && !a["ack-incomplete-policy"])
266
+ throw new Error("Partial instruction policy: --ack-incomplete-policy required before signing");
267
+ requireAuth(env);
268
+ if (!a["build-only"] && (!bridge || !bridge.publicKey.equals(wallet)))
269
+ throw new Error("Framework wallet mismatch or missing bridge");
270
+ const account = await identity(t, wallet);
271
+ if (finite(account.balance) <
272
+ (estimate ?? 0) + (a["build-only"] ? 0 : sendCredits))
273
+ throw new Error("Insufficient billing balance");
274
+ const id = required(a, "operation-id");
275
+ const intent = {
276
+ origin,
277
+ side,
278
+ mint: mint.toBase58(),
279
+ wallet: wallet.toBase58(),
280
+ amount: amount.toString(),
281
+ slippage,
282
+ priority,
283
+ };
284
+ await j.begin(id, fingerprint({
285
+ origin,
286
+ side,
287
+ mint: mint.toBase58(),
288
+ wallet: wallet.toBase58(),
289
+ amount: amount.toString(),
290
+ }), intent);
291
+ await j.event(id, { state: "unknown", phase: "builder-intent" });
292
+ let result;
293
+ try {
294
+ result =
295
+ side === "buy"
296
+ ? await t.sdk.executeSnipe(mint.toBase58(), Number(amount) / 1e9, slippage, { priority_fee_micro_lamports: priority })
297
+ : await t.sell(mint.toBase58(), amount.toString(), slippage, priority);
298
+ }
299
+ catch {
300
+ await j.event(id, { state: "unknown", phase: "builder-uncertain" });
301
+ return {
302
+ state: "unknown",
303
+ operation_id: id,
304
+ note: "Builder response failed. Check ledger for debit/refund; never auto-rebuild. No transaction signed/submitted.",
305
+ };
306
+ }
307
+ await j.event(id, {
308
+ state: "built",
309
+ phase: "builder-returned",
310
+ build_result: {
311
+ mint: new PublicKey(result.mint).toBase58(),
312
+ fee_credits: finite(result.fee_credits),
313
+ ...(side === "buy"
314
+ ? {
315
+ tokens_out: safeLamports(result.tokens_out).toString(),
316
+ max_sol_cost_lamports: safeLamports(result.max_sol_cost_lamports).toString(),
317
+ }
318
+ : {
319
+ min_sol_output_lamports: safeLamports(result.min_sol_output_lamports).toString(),
320
+ }),
321
+ },
322
+ });
323
+ const charged = finite(result.fee_credits);
324
+ if (charged + (a["build-only"] ? 0 : sendCredits) > maxFee)
325
+ throw new Error("Already-charged builder plus send exceeds max credits; NOT signing/submitting (no automatic builder refund)");
326
+ if (result.mint !== mint.toBase58())
327
+ throw new Error("Builder mint mismatch");
328
+ const cost = safeLamports(side === "buy"
329
+ ? result.max_sol_cost_lamports
330
+ : result.min_sol_output_lamports);
331
+ if (side === "buy" && cost > maxSol)
332
+ throw new Error("Already-charged build exceeds returned SOL cap; NOT signing/submitting");
333
+ const tx = decodeBuilderUnsigned(result.unsigned_tx_base64);
334
+ if (a["build-only"]) {
335
+ const envelope = inspectEnvelope(tx, wallet);
336
+ emit({
337
+ state: "built",
338
+ operation_id: id,
339
+ fee_credits: charged,
340
+ policy: envelope,
341
+ });
342
+ return {
343
+ state: "built",
344
+ operation_id: id,
345
+ fee_credits: charged,
346
+ unsigned_tx_base64: result.unsigned_tx_base64,
347
+ policy: envelope,
348
+ note: "Charged unsigned construction only. No signing/submission; instruction semantics are not authorized. No automatic resume/rebuild.",
349
+ };
350
+ }
351
+ const inspected = verifyUnsigned(tx, wallet, {
352
+ side,
353
+ mint,
354
+ maxSol,
355
+ amountRaw: side === "sell" ? amount : undefined,
356
+ maxPriority: BigInt(priorityCap),
357
+ maxPriorityLamports,
358
+ requestedPriority: BigInt(priority),
359
+ tokensOut: side === "buy" ? safeLamports(result.tokens_out) : undefined,
360
+ reportedCost: cost,
361
+ });
362
+ emit({
363
+ state: "built",
364
+ operation_id: id,
365
+ fee_credits: charged,
366
+ policy: inspected,
367
+ });
368
+ // Network fees are queried against this exact message; rent is conservatively
369
+ // reserved for a new token ATA. Pump-side CPI rent remains outside this partial policy.
370
+ const fee = await t.connection.getFeeForMessage(tx.message);
371
+ if (fee.value === null)
372
+ throw new Error("Message fee unavailable/expired");
373
+ let overhead = safeLamports(fee.value);
374
+ if (inspected.ataCount)
375
+ overhead +=
376
+ BigInt(inspected.ataCount) *
377
+ safeLamports(await t.connection.getMinimumBalanceForRentExemption(165));
378
+ if (inspected.tradeCost + overhead > maxSol)
379
+ throw new Error("Trade + known network/ATA rent exceeds max SOL; NOT signing");
380
+ const originalMessage = Buffer.from(tx.message.serialize());
381
+ const signed = await bridge.signTransaction(tx);
382
+ if (!signed ||
383
+ !Buffer.from(signed.message.serialize()).equals(originalMessage))
384
+ throw new Error("Wallet mutated approved transaction message");
385
+ checkSignature(signed);
386
+ return submit(t, j, id, signed, emit);
387
+ }
@@ -0,0 +1,10 @@
1
+ export type Args = {
2
+ command: string;
3
+ [key: string]: string | boolean;
4
+ };
5
+ export declare function parseArgs(argv: string[]): Args;
6
+ export declare function required(a: Args, k: string): string;
7
+ export declare function integer(s: string, max?: number): number;
8
+ export declare function rawAmount(s: string): bigint;
9
+ export declare function solLamports(s: string): bigint;
10
+ export declare function endpoint(s: string): string;