@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/src/index.ts ADDED
@@ -0,0 +1,296 @@
1
+ import type { SolanaAgentKit, Plugin } from "solana-agent-kit";
2
+ import { z } from "zod";
3
+ import { run } from "./internal/app.js";
4
+ import { endpoint as validateEndpoint, type Args } from "./internal/config.js";
5
+ export interface NodiusOptions {
6
+ apiKey?: string;
7
+ endpoint?: string;
8
+ journalDir: string;
9
+ allowExecution?: boolean;
10
+ ackIncompletePolicy?: boolean;
11
+ }
12
+ const integer = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
13
+ const decimal = z.string().regex(/^(0|[1-9]\d*)(\.\d{1,9})?$/);
14
+ const base = {
15
+ mint: z.string().min(32).max(44),
16
+ maxSol: decimal,
17
+ maxFeeCredits: integer,
18
+ slippageBps: integer.max(9999),
19
+ priorityFee: integer,
20
+ maxPriorityFee: integer,
21
+ maxPriorityLamports: integer.optional(),
22
+ execute: z.boolean().default(false),
23
+ buildOnly: z.boolean().default(false),
24
+ operationId: z
25
+ .string()
26
+ .regex(/^[A-Za-z0-9_-]{1,80}$/)
27
+ .optional(),
28
+ acceptUnquotedBuildCharge: z.boolean().default(false),
29
+ };
30
+ export const tradeSchema = z.discriminatedUnion("side", [
31
+ z.object({ ...base, side: z.literal("buy"), amountSol: decimal }).strict(),
32
+ z
33
+ .object({
34
+ ...base,
35
+ side: z.literal("sell"),
36
+ amountRaw: z.string().regex(/^[1-9]\d*$/),
37
+ })
38
+ .strict(),
39
+ ]);
40
+ const tradeActionSchema = z
41
+ .object({
42
+ ...base,
43
+ side: z.enum(["buy", "sell"]),
44
+ amountSol: decimal.optional(),
45
+ amountRaw: z
46
+ .string()
47
+ .regex(/^[1-9]\d*$/)
48
+ .optional(),
49
+ })
50
+ .strict();
51
+ export const monitorSchema = z
52
+ .object({
53
+ maxFeeCredits: integer,
54
+ seconds: integer.min(1).max(300).default(30),
55
+ maxEvents: integer.min(1).max(1000).default(100),
56
+ mint: z.string().min(32).max(44).optional(),
57
+ kinds: z
58
+ .array(z.enum(["newPair", "graduation", "walletActivity", "largeSwap"]))
59
+ .min(1)
60
+ .max(4)
61
+ .optional(),
62
+ })
63
+ .strict();
64
+ export const statusSchema = z
65
+ .object({ operationId: z.string().regex(/^[A-Za-z0-9_-]{1,80}$/) })
66
+ .strict();
67
+ export const sendSchema = z
68
+ .object({
69
+ signedTxBase64: z.string().min(1).max(1644),
70
+ maxFeeCredits: integer,
71
+ execute: z.boolean().default(false),
72
+ operationId: z
73
+ .string()
74
+ .regex(/^[A-Za-z0-9_-]{1,80}$/)
75
+ .optional(),
76
+ })
77
+ .strict();
78
+ export type SendInput = z.input<typeof sendSchema>;
79
+ export type StatusInput = z.input<typeof statusSchema>;
80
+ export type MonitorInput = z.input<typeof monitorSchema>;
81
+ export type TradeInput = z.input<typeof tradeSchema>;
82
+ export type NodiusResult = Record<string, unknown>;
83
+ export function createNodiusPlugin(options: NodiusOptions) {
84
+ const parsed = z
85
+ .object({
86
+ apiKey: z
87
+ .string()
88
+ .min(1)
89
+ .regex(/^[^\r\n]+$/)
90
+ .optional(),
91
+ endpoint: z.string().optional(),
92
+ journalDir: z.string().min(1),
93
+ allowExecution: z.boolean().default(false),
94
+ ackIncompletePolicy: z.boolean().default(false),
95
+ })
96
+ .strict()
97
+ .parse(options);
98
+ const o = Object.freeze({
99
+ ...parsed,
100
+ endpoint: validateEndpoint(parsed.endpoint ?? "https://rpc.nodius.xyz"),
101
+ });
102
+ let owner: SolanaAgentKit | undefined;
103
+ const recent = z
104
+ .object({ limit: integer.min(1).max(50).default(20) })
105
+ .strict();
106
+ function clean(value: unknown): any {
107
+ const text = (s: string) =>
108
+ o.apiKey ? s.split(o.apiKey).join("[REDACTED]") : s;
109
+ if (typeof value === "string") return text(value);
110
+ if (typeof value === "bigint") return value.toString();
111
+ if (Array.isArray(value)) return value.map(clean);
112
+ if (value && typeof value === "object")
113
+ return Object.fromEntries(
114
+ Object.entries(value)
115
+ .filter(([, v]) => v !== undefined)
116
+ .map(([k, v]) => [text(k), clean(v)]),
117
+ );
118
+ return value;
119
+ }
120
+ async function invoke(
121
+ agent: SolanaAgentKit,
122
+ input: unknown,
123
+ kind: "recent" | "trade" | "monitor" | "status" | "send",
124
+ ): Promise<NodiusResult> {
125
+ try {
126
+ return clean(await dispatch(agent, input, kind));
127
+ } catch (e) {
128
+ throw new Error(clean(e instanceof Error ? e.message : String(e)));
129
+ }
130
+ }
131
+ async function dispatch(
132
+ agent: SolanaAgentKit,
133
+ input: unknown,
134
+ kind: "recent" | "trade" | "monitor" | "status" | "send",
135
+ ): Promise<NodiusResult> {
136
+ if (!owner || agent !== owner) throw Error("Plugin agent mismatch");
137
+ let args: Args = {
138
+ command: kind,
139
+ endpoint: o.endpoint ?? "https://rpc.nodius.xyz",
140
+ journal: o.journalDir,
141
+ };
142
+ if (kind === "recent") {
143
+ const v = recent.parse(input);
144
+ args.limit = String(v.limit);
145
+ } else if (kind === "send") {
146
+ const v = sendSchema.parse(input);
147
+ if (v.execute && o.allowExecution !== true)
148
+ throw Error("Host execution is disabled");
149
+ if (v.execute && o.ackIncompletePolicy !== true)
150
+ throw Error("Host incomplete policy acknowledgement required");
151
+ args["signed-tx-base64"] = v.signedTxBase64;
152
+ args.wallet = owner.wallet.publicKey.toBase58();
153
+ args["max-fee-credits"] = String(v.maxFeeCredits);
154
+ args.execute = v.execute;
155
+ if (v.operationId) args["operation-id"] = v.operationId;
156
+ } else if (kind === "status") {
157
+ const v = statusSchema.parse(input);
158
+ args["operation-id"] = v.operationId;
159
+ } else if (kind === "monitor") {
160
+ const v = monitorSchema.parse(input);
161
+ args["max-fee-credits"] = String(v.maxFeeCredits);
162
+ args.seconds = String(v.seconds);
163
+ args["max-events"] = String(v.maxEvents);
164
+ if (v.mint) args.mint = v.mint;
165
+ if (v.kinds) args.kinds = v.kinds.join(",");
166
+ } else {
167
+ const v = tradeSchema.parse(input);
168
+ if (v.execute && o.allowExecution !== true)
169
+ throw Error("Host execution is disabled");
170
+ if (v.execute && !v.buildOnly && o.ackIncompletePolicy !== true)
171
+ throw Error("Host incomplete policy acknowledgement required");
172
+ args.command = v.side;
173
+ args.wallet = owner.wallet.publicKey.toBase58();
174
+ const map: Record<string, string> = {
175
+ amountSol: "amount-sol",
176
+ amountRaw: "amount-raw",
177
+ maxSol: "max-sol",
178
+ maxFeeCredits: "max-fee-credits",
179
+ slippageBps: "slippage-bps",
180
+ priorityFee: "priority-fee",
181
+ maxPriorityFee: "max-priority-fee",
182
+ maxPriorityLamports: "max-priority-lamports",
183
+ operationId: "operation-id",
184
+ buildOnly: "build-only",
185
+ acceptUnquotedBuildCharge: "accept-unquoted-build-charge",
186
+ };
187
+ for (const [k, value] of Object.entries(v)) {
188
+ if (k === "side") continue;
189
+ args[map[k] ?? k] = typeof value === "boolean" ? value : String(value);
190
+ }
191
+ args["ack-incomplete-policy"] = o.ackIncompletePolicy === true;
192
+ }
193
+ const notifications: unknown[] = [];
194
+ let monitorPlan: Record<string, unknown> = {};
195
+ const result = await run(
196
+ args,
197
+ { NODIUS_API_KEY: o.apiKey },
198
+ (v) => {
199
+ if (kind === "monitor") {
200
+ if (v.state === "monitor-plan") monitorPlan = v;
201
+ else notifications.push(v);
202
+ }
203
+ },
204
+ {
205
+ publicKey: owner.wallet.publicKey,
206
+ signTransaction: (tx) => owner!.wallet.signTransaction(tx),
207
+ },
208
+ );
209
+ return kind === "monitor"
210
+ ? { ...monitorPlan, ...result, notifications }
211
+ : result;
212
+ }
213
+ const plugin = {
214
+ name: "nodius",
215
+ methods: {
216
+ nodiusSend: (input: SendInput) => invoke(owner!, input, "send"),
217
+ nodiusStatus: (input: StatusInput) => invoke(owner!, input, "status"),
218
+ nodiusMonitor: (input: MonitorInput) => invoke(owner!, input, "monitor"),
219
+ nodiusRecent: (input: { limit?: number } = {}) =>
220
+ invoke(owner!, input, "recent"),
221
+ nodiusTrade: (input: TradeInput) => invoke(owner!, input, "trade"),
222
+ },
223
+ actions: [
224
+ {
225
+ name: "nodiusSend",
226
+ similes: [],
227
+ description:
228
+ "Submit independently reviewed, already signed own-wallet transaction. Defaults plan; no trade instruction proof. Requires both host permissions.",
229
+ examples: [],
230
+ schema: sendSchema,
231
+ handler: (a: SolanaAgentKit, i: unknown) => invoke(a, i, "send"),
232
+ },
233
+ {
234
+ name: "nodiusStatus",
235
+ similes: [],
236
+ description:
237
+ "Reconcile local operation status; never rebuild or rebroadcast",
238
+ examples: [],
239
+ schema: statusSchema,
240
+ handler: (a: SolanaAgentKit, i: unknown) => invoke(a, i, "status"),
241
+ },
242
+ {
243
+ name: "nodiusMonitor",
244
+ similes: [],
245
+ description:
246
+ "Paid bounded signal monitor (<=300s, <=1000 events); no autonomous trading",
247
+ examples: [],
248
+ schema: monitorSchema,
249
+ handler: (a: SolanaAgentKit, i: unknown) => invoke(a, i, "monitor"),
250
+ },
251
+ {
252
+ name: "nodiusRecent",
253
+ similes: [],
254
+ description: "Read recent Nodius signals; never trades",
255
+ examples: [],
256
+ schema: recent,
257
+ handler: (a: SolanaAgentKit, i: unknown) => invoke(a, i, "recent"),
258
+ },
259
+ {
260
+ name: "nodiusTrade",
261
+ similes: [],
262
+ description:
263
+ "Buy/sell defaults to read-only plan. Host must separately authorize charged execution and partial policy.",
264
+ examples: [],
265
+ schema: tradeActionSchema,
266
+ handler: (a: SolanaAgentKit, i: unknown) => invoke(a, i, "trade"),
267
+ },
268
+ ],
269
+ initialize(agent: SolanaAgentKit) {
270
+ if (owner && owner !== agent)
271
+ throw Error("Plugin instance already bound to another agent");
272
+ owner = agent;
273
+ },
274
+ } satisfies Plugin;
275
+ // Keep ZodObject identity for the published tool converters, but sanitize
276
+ // validation issues before the framework's pre-handler executor surfaces them.
277
+ for (const action of plugin.actions) {
278
+ const schema: z.AnyZodObject = action.schema.strict();
279
+ const sync = schema.safeParse.bind(schema),
280
+ asyncParse = schema.safeParseAsync.bind(schema);
281
+ schema.safeParse = (data, params) => {
282
+ const r = sync(data, params);
283
+ return r.success
284
+ ? r
285
+ : { success: false, error: new z.ZodError(clean(r.error.issues)) };
286
+ };
287
+ schema.safeParseAsync = async (data, params) => {
288
+ const r = await asyncParse(data, params);
289
+ return r.success
290
+ ? r
291
+ : { success: false, error: new z.ZodError(clean(r.error.issues)) };
292
+ };
293
+ action.schema = schema as typeof action.schema;
294
+ }
295
+ return plugin;
296
+ }