paralith-mcp 0.1.0 → 0.3.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/README.md +10 -2
- package/dist/chains.json +79 -42
- package/dist/client.mjs +8 -5
- package/dist/graph.json +2 -2
- package/dist/http.mjs +233 -22
- package/dist/server.mjs +644 -122
- package/dist/view.html +117 -0
- package/package.json +2 -1
package/dist/server.mjs
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res, err) => function __init() {
|
|
5
|
+
if (err) throw err[0];
|
|
6
|
+
try {
|
|
7
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
+
} catch (e) {
|
|
9
|
+
throw err = [e], e;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
10
16
|
|
|
11
17
|
// ../../backend/native/ans104.mjs
|
|
12
18
|
import crypto from "node:crypto";
|
|
13
|
-
var enc = new TextEncoder();
|
|
14
|
-
var sha384 = (b) => crypto.createHash("sha384").update(b).digest();
|
|
15
|
-
var sha256 = (b) => crypto.createHash("sha256").update(b).digest();
|
|
16
|
-
var b64url = (b) => Buffer.from(b).toString("base64url");
|
|
17
|
-
var b64urlDec = (s) => Buffer.from(s, "base64url");
|
|
18
19
|
function deepHash(value) {
|
|
19
20
|
if (Array.isArray(value)) {
|
|
20
21
|
const tag2 = enc.encode(`list${value.length}`);
|
|
@@ -102,7 +103,6 @@ async function signDataItem(jwk, item) {
|
|
|
102
103
|
data
|
|
103
104
|
]);
|
|
104
105
|
}
|
|
105
|
-
var jwkToAddress = (jwk) => b64url(sha256(b64urlDec(jwk.n)));
|
|
106
106
|
function walletShim(jwk) {
|
|
107
107
|
const address = jwkToAddress(jwk);
|
|
108
108
|
return {
|
|
@@ -122,6 +122,18 @@ function walletShim(jwk) {
|
|
|
122
122
|
}
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
|
+
var enc, sha384, sha256, b64url, b64urlDec, jwkToAddress;
|
|
126
|
+
var init_ans104 = __esm({
|
|
127
|
+
"../../backend/native/ans104.mjs"() {
|
|
128
|
+
"use strict";
|
|
129
|
+
enc = new TextEncoder();
|
|
130
|
+
sha384 = (b) => crypto.createHash("sha384").update(b).digest();
|
|
131
|
+
sha256 = (b) => crypto.createHash("sha256").update(b).digest();
|
|
132
|
+
b64url = (b) => Buffer.from(b).toString("base64url");
|
|
133
|
+
b64urlDec = (s) => Buffer.from(s, "base64url");
|
|
134
|
+
jwkToAddress = (jwk) => b64url(sha256(b64urlDec(jwk.n)));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
125
137
|
|
|
126
138
|
// src/keys.mjs
|
|
127
139
|
import crypto2 from "node:crypto";
|
|
@@ -134,7 +146,6 @@ import { mnemonicToAccount } from "viem/accounts";
|
|
|
134
146
|
import { Keypair } from "@solana/web3.js";
|
|
135
147
|
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
|
|
136
148
|
import { getKeyFromMnemonic } from "arweave-mnemonic-keys";
|
|
137
|
-
var CACHE_DIR = process.env.PARALITH_MCP_HOME || path.join(os.homedir(), ".paralith", "mcp");
|
|
138
149
|
function readPhrase(env = process.env) {
|
|
139
150
|
const raw = env.PARALITH_MNEMONIC ?? (env.PARALITH_MNEMONIC_FILE ? fs.readFileSync(env.PARALITH_MNEMONIC_FILE, "utf8") : null);
|
|
140
151
|
if (!raw) {
|
|
@@ -161,60 +172,260 @@ function slip10(seed, derivation) {
|
|
|
161
172
|
async function aoJwk(phrase, env) {
|
|
162
173
|
if (env.PARALITH_AO_JWK) return JSON.parse(fs.readFileSync(env.PARALITH_AO_JWK, "utf8"));
|
|
163
174
|
const id = crypto2.createHash("sha256").update(phrase).digest("hex").slice(0, 24);
|
|
164
|
-
const file = path.join(
|
|
175
|
+
const file = path.join(HOME, `ao-${id}.json`);
|
|
165
176
|
if (fs.existsSync(file)) return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
166
177
|
const jwk = await getKeyFromMnemonic(phrase);
|
|
167
|
-
fs.mkdirSync(
|
|
178
|
+
fs.mkdirSync(HOME, { recursive: true, mode: 448 });
|
|
168
179
|
fs.writeFileSync(file, JSON.stringify(jwk), { mode: 384 });
|
|
169
180
|
return jwk;
|
|
170
181
|
}
|
|
171
|
-
|
|
182
|
+
function chainWallets(phrase) {
|
|
172
183
|
const evm = mnemonicToAccount(phrase, { path: "m/44'/60'/0'/0/0" });
|
|
173
184
|
const solana = Keypair.fromSeed(slip10(mnemonicToSeedSync(phrase), "m/44'/501'/0'/0'"));
|
|
174
185
|
const sui = Ed25519Keypair.deriveKeypair(phrase, "m/44'/784'/0'/0'/0'");
|
|
175
|
-
const jwk = await aoJwk(phrase, env);
|
|
176
186
|
return {
|
|
177
|
-
ao: { jwk, address: jwkToAddress(jwk) },
|
|
178
187
|
base: { account: evm, address: evm.address },
|
|
179
188
|
solana: { keypair: solana, address: solana.publicKey.toBase58() },
|
|
180
189
|
sui: { keypair: sui, address: sui.getPublicKey().toSuiAddress() }
|
|
181
190
|
};
|
|
182
191
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
192
|
+
async function deriveWallets(phrase, env = process.env) {
|
|
193
|
+
const jwk = await aoJwk(phrase, env);
|
|
194
|
+
return { ao: { jwk, address: jwkToAddress(jwk) }, ...chainWallets(phrase) };
|
|
195
|
+
}
|
|
196
|
+
var HOME, publicAddresses;
|
|
197
|
+
var init_keys = __esm({
|
|
198
|
+
"src/keys.mjs"() {
|
|
199
|
+
"use strict";
|
|
200
|
+
init_ans104();
|
|
201
|
+
HOME = process.env.PARALITH_MCP_HOME || path.join(os.homedir(), ".paralith", "mcp");
|
|
202
|
+
publicAddresses = (w) => ({
|
|
203
|
+
ao: w.ao.address,
|
|
204
|
+
base: w.base.address,
|
|
205
|
+
solana: w.solana.address,
|
|
206
|
+
sui: w.sui.address
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// src/agents.mjs
|
|
212
|
+
import fs2 from "node:fs";
|
|
213
|
+
import path2 from "node:path";
|
|
214
|
+
function readAgents() {
|
|
215
|
+
try {
|
|
216
|
+
return JSON.parse(fs2.readFileSync(FILE, "utf8"));
|
|
217
|
+
} catch {
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function saveAgent(name, entry) {
|
|
222
|
+
const all = readAgents();
|
|
223
|
+
all[name] = { ...all[name], ...entry, addresses: { ...all[name]?.addresses, ...entry.addresses } };
|
|
224
|
+
fs2.mkdirSync(HOME, { recursive: true, mode: 448 });
|
|
225
|
+
fs2.writeFileSync(FILE, `${JSON.stringify(all, null, 2)}
|
|
226
|
+
`);
|
|
227
|
+
return all[name];
|
|
228
|
+
}
|
|
229
|
+
var FILE;
|
|
230
|
+
var init_agents = __esm({
|
|
231
|
+
"src/agents.mjs"() {
|
|
232
|
+
"use strict";
|
|
233
|
+
init_keys();
|
|
234
|
+
FILE = path2.join(HOME, "agents.json");
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// src/setup.mjs
|
|
239
|
+
var setup_exports = {};
|
|
240
|
+
__export(setup_exports, {
|
|
241
|
+
setup: () => setup,
|
|
242
|
+
upsertToml: () => upsertToml
|
|
243
|
+
});
|
|
244
|
+
import fs3 from "node:fs";
|
|
245
|
+
import os2 from "node:os";
|
|
246
|
+
import path3 from "node:path";
|
|
247
|
+
import { spawnSync } from "node:child_process";
|
|
248
|
+
import { generateMnemonic } from "@scure/bip39";
|
|
249
|
+
import { wordlist as wordlist2 } from "@scure/bip39/wordlists/english.js";
|
|
250
|
+
function run(cli, args) {
|
|
251
|
+
const r = WIN ? spawnSync([cli, ...args].map((a) => /[\s"&|<>^]/.test(a) ? `"${a}"` : a).join(" "), { shell: true, encoding: "utf8" }) : spawnSync(cli, args, { encoding: "utf8" });
|
|
252
|
+
return { ok: r.status === 0, out: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
|
|
253
|
+
}
|
|
254
|
+
function desktopConfig() {
|
|
255
|
+
const dir = WIN ? path3.join(process.env.APPDATA ?? path3.join(os2.homedir(), "AppData", "Roaming"), "Claude") : process.platform === "darwin" ? path3.join(os2.homedir(), "Library", "Application Support", "Claude") : path3.join(os2.homedir(), ".config", "Claude");
|
|
256
|
+
return path3.join(dir, "claude_desktop_config.json");
|
|
257
|
+
}
|
|
258
|
+
function upsertToml(text, env) {
|
|
259
|
+
const kept = [];
|
|
260
|
+
let skipping = false;
|
|
261
|
+
for (const line of text.split(/\r?\n/)) {
|
|
262
|
+
const header = /^\s*\[/.test(line);
|
|
263
|
+
if (header) skipping = /^\s*\[\s*mcp_servers\.(paralith|"paralith")\s*[\].]/.test(line);
|
|
264
|
+
if (!skipping) kept.push(line);
|
|
265
|
+
}
|
|
266
|
+
const s = JSON.stringify;
|
|
267
|
+
const block = [
|
|
268
|
+
"[mcp_servers.paralith]",
|
|
269
|
+
`command = ${s(LAUNCH.command)}`,
|
|
270
|
+
`args = [${LAUNCH.args.map(s).join(", ")}]`,
|
|
271
|
+
"startup_timeout_sec = 120",
|
|
272
|
+
// first run downloads the package
|
|
273
|
+
"tool_timeout_sec = 1200",
|
|
274
|
+
// a bridge deposit waits for the relayer
|
|
275
|
+
"",
|
|
276
|
+
"[mcp_servers.paralith.env]",
|
|
277
|
+
...Object.entries(env).map(([k, v]) => `${k} = ${s(v)}`)
|
|
278
|
+
];
|
|
279
|
+
const body = kept.join("\n").replace(/\s+$/, "");
|
|
280
|
+
return `${body ? `${body}
|
|
281
|
+
|
|
282
|
+
` : ""}${block.join("\n")}
|
|
283
|
+
`;
|
|
284
|
+
}
|
|
285
|
+
function writeToml(file, env) {
|
|
286
|
+
fs3.mkdirSync(path3.dirname(file), { recursive: true });
|
|
287
|
+
fs3.writeFileSync(file, upsertToml(fs3.existsSync(file) ? fs3.readFileSync(file, "utf8") : "", env));
|
|
288
|
+
}
|
|
289
|
+
function phraseFile(agent) {
|
|
290
|
+
const file = path3.join(HOME, "agents", `${agent}.txt`);
|
|
291
|
+
if (!fs3.existsSync(file)) {
|
|
292
|
+
fs3.mkdirSync(path3.dirname(file), { recursive: true, mode: 448 });
|
|
293
|
+
fs3.writeFileSync(file, `${generateMnemonic(wordlist2, 256)}
|
|
294
|
+
`, { mode: 384 });
|
|
295
|
+
}
|
|
296
|
+
return file;
|
|
297
|
+
}
|
|
298
|
+
async function setup(argv) {
|
|
299
|
+
const only = argv.find((a) => a.startsWith("--only"))?.split("=")[1]?.split(",") ?? (argv.includes("--only") ? argv[argv.indexOf("--only") + 1]?.split(",") : null);
|
|
300
|
+
const unknown = (only ?? []).filter((c) => !CLIENTS[c]);
|
|
301
|
+
if (unknown.length) throw new Error(`Unknown app: ${unknown.join(", ")}. Choose from ${Object.keys(CLIENTS).join(", ")}.`);
|
|
302
|
+
const done = [];
|
|
303
|
+
for (const [id, client] of Object.entries(CLIENTS)) {
|
|
304
|
+
if (only ? !only.includes(id) : !client.found()) continue;
|
|
305
|
+
const file = phraseFile(client.wallet);
|
|
306
|
+
const chain = chainWallets(readPhrase({ PARALITH_MNEMONIC_FILE: file }));
|
|
307
|
+
saveAgent(client.wallet, {
|
|
308
|
+
phraseFile: slash(file),
|
|
309
|
+
addresses: { base: chain.base.address, solana: chain.solana.address, sui: chain.sui.address }
|
|
310
|
+
});
|
|
311
|
+
try {
|
|
312
|
+
client.connect({ PARALITH_MNEMONIC_FILE: slash(file), PARALITH_AGENT: client.wallet });
|
|
313
|
+
done.push(client.label);
|
|
314
|
+
console.log(`\u2713 ${client.label} connected (wallet: ${client.wallet})`);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.log(`\u2717 ${client.label}: ${error.message.split("\n")[0]}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (!done.length) {
|
|
320
|
+
console.log("No agent app found. Install Claude Code, Claude Desktop, Codex or Grok, or name one with --only.");
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const agents = readAgents();
|
|
324
|
+
console.log("\nFund these to bridge (gas only; playing and trading need nothing):");
|
|
325
|
+
for (const name of new Set(Object.values(CLIENTS).filter((c) => done.includes(c.label)).map((c) => c.wallet))) {
|
|
326
|
+
const a = agents[name].addresses;
|
|
327
|
+
console.log(`
|
|
328
|
+
${name}
|
|
329
|
+
Base (ETH) ${a.base}
|
|
330
|
+
Solana (SOL) ${a.solana}
|
|
331
|
+
Sui (SUI) ${a.sui}`);
|
|
332
|
+
}
|
|
333
|
+
console.log(`
|
|
334
|
+
Phrases: ${slash(path3.join(HOME, "agents"))} (back them up; anything funded is spendable by the agent).`);
|
|
335
|
+
console.log(`Restart ${done.join(", ")}, then say: "Play Rune Realm and show me." Ask any of them "how are my agents doing?"`);
|
|
336
|
+
}
|
|
337
|
+
var WIN, NPX, LAUNCH, slash, installed, CLIENTS;
|
|
338
|
+
var init_setup = __esm({
|
|
339
|
+
"src/setup.mjs"() {
|
|
340
|
+
"use strict";
|
|
341
|
+
init_keys();
|
|
342
|
+
init_agents();
|
|
343
|
+
WIN = process.platform === "win32";
|
|
344
|
+
NPX = ["npx", "-y", "paralith-mcp@latest"];
|
|
345
|
+
LAUNCH = WIN ? { command: "cmd", args: ["/c", ...NPX] } : { command: NPX[0], args: NPX.slice(1) };
|
|
346
|
+
slash = (p) => p.replace(/\\/g, "/");
|
|
347
|
+
installed = (cli) => run(cli, ["--version"]).ok;
|
|
348
|
+
CLIENTS = {
|
|
349
|
+
claude: {
|
|
350
|
+
label: "Claude Code",
|
|
351
|
+
wallet: "claude",
|
|
352
|
+
found: () => installed("claude"),
|
|
353
|
+
connect(env) {
|
|
354
|
+
run("claude", ["mcp", "remove", "paralith", "--scope", "user"]);
|
|
355
|
+
const r = run("claude", [
|
|
356
|
+
"mcp",
|
|
357
|
+
"add",
|
|
358
|
+
"paralith",
|
|
359
|
+
"--scope",
|
|
360
|
+
"user",
|
|
361
|
+
...Object.entries(env).flatMap(([k, v]) => ["-e", `${k}=${v}`]),
|
|
362
|
+
"--",
|
|
363
|
+
LAUNCH.command,
|
|
364
|
+
...LAUNCH.args
|
|
365
|
+
]);
|
|
366
|
+
if (!r.ok) throw new Error(r.out);
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
"claude-desktop": {
|
|
370
|
+
label: "Claude Desktop",
|
|
371
|
+
wallet: "claude",
|
|
372
|
+
found: () => fs3.existsSync(path3.dirname(desktopConfig())),
|
|
373
|
+
connect(env) {
|
|
374
|
+
const file = desktopConfig();
|
|
375
|
+
const config = fs3.existsSync(file) ? JSON.parse(fs3.readFileSync(file, "utf8")) : {};
|
|
376
|
+
config.mcpServers = { ...config.mcpServers, paralith: { ...LAUNCH, env } };
|
|
377
|
+
fs3.writeFileSync(file, `${JSON.stringify(config, null, 2)}
|
|
378
|
+
`);
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
codex: {
|
|
382
|
+
label: "Codex",
|
|
383
|
+
wallet: "codex",
|
|
384
|
+
found: () => installed("codex") || fs3.existsSync(path3.join(os2.homedir(), ".codex")),
|
|
385
|
+
connect: (env) => writeToml(path3.join(os2.homedir(), ".codex", "config.toml"), env)
|
|
386
|
+
},
|
|
387
|
+
grok: {
|
|
388
|
+
label: "Grok",
|
|
389
|
+
wallet: "grok",
|
|
390
|
+
found: () => installed("grok") || fs3.existsSync(path3.join(os2.homedir(), ".grok")),
|
|
391
|
+
connect: (env) => writeToml(path3.join(os2.homedir(), ".grok", "config.toml"), env)
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
188
395
|
});
|
|
189
396
|
|
|
190
397
|
// src/runtime.mjs
|
|
191
|
-
var
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
398
|
+
var runtime_exports = {};
|
|
399
|
+
__export(runtime_exports, {
|
|
400
|
+
ID: () => ID,
|
|
401
|
+
NODE: () => NODE,
|
|
402
|
+
PROCESS_ALIASES: () => PROCESS_ALIASES,
|
|
403
|
+
READ_ONLY: () => READ_ONLY,
|
|
404
|
+
chains: () => chains,
|
|
405
|
+
formatAtoms: () => formatAtoms,
|
|
406
|
+
graph: () => graph,
|
|
407
|
+
parseAmount: () => parseAmount,
|
|
408
|
+
processId: () => processId,
|
|
409
|
+
readAtoms: () => readAtoms,
|
|
410
|
+
readKey: () => readKey,
|
|
411
|
+
runtime: () => runtime,
|
|
412
|
+
sleep: () => sleep,
|
|
413
|
+
until: () => until
|
|
414
|
+
});
|
|
415
|
+
import fs4 from "node:fs";
|
|
416
|
+
import path4 from "node:path";
|
|
417
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
206
418
|
function processId(nameOrId) {
|
|
207
419
|
if (ID.test(nameOrId)) return nameOrId;
|
|
208
420
|
const id = PROCESS_ALIASES[nameOrId];
|
|
209
421
|
if (!id) throw new Error(`Unknown process "${nameOrId}". Use a 43-character id or one of: ${Object.keys(PROCESS_ALIASES).join(", ")}`);
|
|
210
422
|
return id;
|
|
211
423
|
}
|
|
212
|
-
var READ_ONLY = !process.env.PARALITH_MNEMONIC && !process.env.PARALITH_MNEMONIC_FILE;
|
|
213
|
-
var loaded = null;
|
|
214
424
|
function runtime() {
|
|
215
425
|
loaded ??= (async () => {
|
|
216
426
|
const wallets = READ_ONLY ? null : await deriveWallets(readPhrase());
|
|
217
|
-
|
|
427
|
+
if (wallets && process.env.PARALITH_AGENT) saveAgent(process.env.PARALITH_AGENT, { addresses: publicAddresses(wallets) });
|
|
428
|
+
const api = await import(pathToFileURL(path4.join(DIST, "client.mjs")).href);
|
|
218
429
|
if (wallets) api.swarmWalletSlot.wallet = walletShim(wallets.ao.jwk);
|
|
219
430
|
return { api, wallets };
|
|
220
431
|
})();
|
|
@@ -223,7 +434,6 @@ function runtime() {
|
|
|
223
434
|
});
|
|
224
435
|
return loaded;
|
|
225
436
|
}
|
|
226
|
-
var HTML = /^\s*(?:<!doctype\s+html|<html|<head|<body)/i;
|
|
227
437
|
async function readKey(process2, key, node = NODE) {
|
|
228
438
|
const res = await fetch(
|
|
229
439
|
`${node}/${processId(process2)}~process@1.0/now/${key}`,
|
|
@@ -263,7 +473,6 @@ function parseAmount(text, decimals = 6) {
|
|
|
263
473
|
if (atoms <= 0n) throw new Error("Amount must be positive");
|
|
264
474
|
return atoms;
|
|
265
475
|
}
|
|
266
|
-
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
267
476
|
async function until(what, read, done, timeoutMs = 6e5, everyMs = 3e3) {
|
|
268
477
|
const deadline = Date.now() + timeoutMs;
|
|
269
478
|
for (; ; ) {
|
|
@@ -273,19 +482,43 @@ async function until(what, read, done, timeoutMs = 6e5, everyMs = 3e3) {
|
|
|
273
482
|
await sleep(everyMs);
|
|
274
483
|
}
|
|
275
484
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
485
|
+
var HERE, DIST, graph, NODE, chains, PROCESS_ALIASES, ID, READ_ONLY, loaded, HTML, sleep;
|
|
486
|
+
var init_runtime = __esm({
|
|
487
|
+
"src/runtime.mjs"() {
|
|
488
|
+
"use strict";
|
|
489
|
+
init_ans104();
|
|
490
|
+
init_keys();
|
|
491
|
+
init_agents();
|
|
492
|
+
HERE = path4.dirname(fileURLToPath(import.meta.url));
|
|
493
|
+
DIST = fs4.existsSync(path4.join(HERE, "graph.json")) ? HERE : path4.join(HERE, "..", "dist");
|
|
494
|
+
graph = JSON.parse(fs4.readFileSync(path4.join(DIST, "graph.json"), "utf8"));
|
|
495
|
+
NODE = graph.node.replace(/\/$/, "");
|
|
496
|
+
chains = JSON.parse(fs4.readFileSync(path4.join(DIST, "chains.json"), "utf8"));
|
|
497
|
+
PROCESS_ALIASES = {
|
|
498
|
+
game: graph.processes.game,
|
|
499
|
+
rune: graph.processes.rune,
|
|
500
|
+
pusd: graph.processes.pusd,
|
|
501
|
+
...graph.processes.dao ? { dao: graph.processes.dao } : {},
|
|
502
|
+
"game-venue": graph.processes.internalVenue,
|
|
503
|
+
"token-venue": graph.processes.externalVenue,
|
|
504
|
+
...Object.fromEntries(Object.entries(graph.processes.elements ?? {}).map(([k, v]) => [`${k}-token`, v]))
|
|
505
|
+
};
|
|
506
|
+
ID = /^[A-Za-z0-9_-]{43}$/;
|
|
507
|
+
READ_ONLY = !process.env.PARALITH_MNEMONIC && !process.env.PARALITH_MNEMONIC_FILE;
|
|
508
|
+
loaded = null;
|
|
509
|
+
HTML = /^\s*(?:<!doctype\s+html|<html|<head|<body)/i;
|
|
510
|
+
sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
511
|
+
}
|
|
512
|
+
});
|
|
280
513
|
|
|
281
514
|
// src/tool.mjs
|
|
282
515
|
import { z } from "zod";
|
|
283
|
-
function tool(server, name, description, inputSchema,
|
|
516
|
+
function tool(server, name, description, inputSchema, run2, annotations = {}, view = null) {
|
|
284
517
|
if (READ_ONLY && annotations !== READ) return;
|
|
285
518
|
const schema = view ? { ...inputSchema, full: z.boolean().optional().describe("Return the raw record instead of the compact view.") } : inputSchema;
|
|
286
519
|
server.registerTool(name, { description, inputSchema: schema, annotations }, async (args) => {
|
|
287
520
|
try {
|
|
288
|
-
const raw = await
|
|
521
|
+
const raw = await run2(args ?? {});
|
|
289
522
|
const value = view && !args?.full ? await view(raw) : raw;
|
|
290
523
|
const text = typeof value === "string" ? value : JSON.stringify(value, bigints, 2);
|
|
291
524
|
return { content: [{ type: "text", text: text ?? "null" }] };
|
|
@@ -294,7 +527,6 @@ function tool(server, name, description, inputSchema, run, annotations = {}, vie
|
|
|
294
527
|
}
|
|
295
528
|
});
|
|
296
529
|
}
|
|
297
|
-
var bigints = (_k, v) => typeof v === "bigint" ? v.toString() : v;
|
|
298
530
|
function explain(error) {
|
|
299
531
|
const name = error?.name ?? "";
|
|
300
532
|
const message = error?.message ?? String(error);
|
|
@@ -305,16 +537,19 @@ The message WAS accepted (slot ${error?.slot ?? "?"}); its result could not be c
|
|
|
305
537
|
}
|
|
306
538
|
return message;
|
|
307
539
|
}
|
|
308
|
-
var READ
|
|
309
|
-
var
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
540
|
+
var bigints, READ, OWN, WRITE;
|
|
541
|
+
var init_tool = __esm({
|
|
542
|
+
"src/tool.mjs"() {
|
|
543
|
+
"use strict";
|
|
544
|
+
init_runtime();
|
|
545
|
+
bigints = (_k, v) => typeof v === "bigint" ? v.toString() : v;
|
|
546
|
+
READ = { readOnlyHint: true, openWorldHint: true };
|
|
547
|
+
OWN = { readOnlyHint: true, openWorldHint: true };
|
|
548
|
+
WRITE = { readOnlyHint: false, destructiveHint: false, openWorldHint: true };
|
|
549
|
+
}
|
|
550
|
+
});
|
|
314
551
|
|
|
315
552
|
// src/views.mjs
|
|
316
|
-
var iso = (ms) => ms ? new Date(Number(ms)).toISOString() : void 0;
|
|
317
|
-
var uses = (moves) => Object.fromEntries(Object.entries(moves ?? {}).map(([n, m]) => [n, m?.count ?? m]));
|
|
318
553
|
function companion(m) {
|
|
319
554
|
if (!m) return void 0;
|
|
320
555
|
return {
|
|
@@ -332,7 +567,6 @@ function companion(m) {
|
|
|
332
567
|
...m.status?.until_time > Date.now() ? { busyUntil: iso(m.status.until_time) } : {}
|
|
333
568
|
};
|
|
334
569
|
}
|
|
335
|
-
var EXTRAS = ["rewards", "lootResult", "dailyClaimed", "economyResult", "withdrawal", "arenaLast", "lastHunt", "listing", "error"];
|
|
336
570
|
function player(p) {
|
|
337
571
|
if (!p) return p;
|
|
338
572
|
const companions = { ...p.monsters ?? {}, ...p.collection ?? {} };
|
|
@@ -401,19 +635,27 @@ function battle(b, address) {
|
|
|
401
635
|
recentTurns: turns
|
|
402
636
|
};
|
|
403
637
|
}
|
|
404
|
-
function hunt(
|
|
405
|
-
if (!
|
|
406
|
-
const { battle: fight, encounter, ...rest } =
|
|
638
|
+
function hunt(run2) {
|
|
639
|
+
if (!run2 || typeof run2 !== "object") return run2;
|
|
640
|
+
const { battle: fight, encounter, ...rest } = run2;
|
|
407
641
|
return {
|
|
408
642
|
...rest,
|
|
409
643
|
...encounter ? { encounter: companion(encounter) } : {},
|
|
410
|
-
...fight ? { battle: battle(fight,
|
|
644
|
+
...fight ? { battle: battle(fight, run2.playerId) } : {}
|
|
411
645
|
};
|
|
412
646
|
}
|
|
647
|
+
var iso, uses, EXTRAS;
|
|
648
|
+
var init_views = __esm({
|
|
649
|
+
"src/views.mjs"() {
|
|
650
|
+
"use strict";
|
|
651
|
+
iso = (ms) => ms ? new Date(Number(ms)).toISOString() : void 0;
|
|
652
|
+
uses = (moves) => Object.fromEntries(Object.entries(moves ?? {}).map(([n, m]) => [n, m?.count ?? m]));
|
|
653
|
+
EXTRAS = ["rewards", "lootResult", "dailyClaimed", "economyResult", "withdrawal", "arenaLast", "lastHunt", "listing", "error"];
|
|
654
|
+
}
|
|
655
|
+
});
|
|
413
656
|
|
|
414
657
|
// src/game.mjs
|
|
415
|
-
|
|
416
|
-
var BERRIES = ["air_berry", "fire_berry", "rock_berry", "water_berry"];
|
|
658
|
+
import { z as z2 } from "zod";
|
|
417
659
|
async function me() {
|
|
418
660
|
const { api, wallets } = await runtime();
|
|
419
661
|
return { api, address: wallets?.ao.address };
|
|
@@ -768,7 +1010,6 @@ function registerGame(server) {
|
|
|
768
1010
|
WRITE
|
|
769
1011
|
);
|
|
770
1012
|
}
|
|
771
|
-
var asPlayer = (r) => r?.address && r?.inventory ? player(r) : r;
|
|
772
1013
|
async function battleView(b) {
|
|
773
1014
|
return b?.id ? battle(b, (await me()).address) : b;
|
|
774
1015
|
}
|
|
@@ -784,16 +1025,26 @@ async function attackView(p) {
|
|
|
784
1025
|
...arenaLast ? { arenaLast } : {}
|
|
785
1026
|
};
|
|
786
1027
|
}
|
|
787
|
-
var huntView = (r) => r?.runId ? hunt(r) : asPlayer(r);
|
|
788
1028
|
function need(value, name) {
|
|
789
1029
|
if (value === void 0 || value === null || value === "") throw new Error(`${name} is required for this action`);
|
|
790
1030
|
return value;
|
|
791
1031
|
}
|
|
1032
|
+
var FACTIONS, BERRIES, asPlayer, huntView;
|
|
1033
|
+
var init_game = __esm({
|
|
1034
|
+
"src/game.mjs"() {
|
|
1035
|
+
"use strict";
|
|
1036
|
+
init_runtime();
|
|
1037
|
+
init_tool();
|
|
1038
|
+
init_views();
|
|
1039
|
+
FACTIONS = ["Sky Nomads", "Aqua Guardians", "Inferno Blades", "Stone Titans"];
|
|
1040
|
+
BERRIES = ["air_berry", "fire_berry", "rock_berry", "water_berry"];
|
|
1041
|
+
asPlayer = (r) => r?.address && r?.inventory ? player(r) : r;
|
|
1042
|
+
huntView = (r) => r?.runId ? hunt(r) : asPlayer(r);
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
792
1045
|
|
|
793
1046
|
// src/exchange.mjs
|
|
794
1047
|
import { z as z3 } from "zod";
|
|
795
|
-
var VENUE = z3.enum(["game", "token"]).describe("game: items vs Gold. token: element tokens vs TEST-RUNE.");
|
|
796
|
-
var venueProcess = (venue) => venue === "game" ? graph.processes.internalVenue : graph.processes.externalVenue;
|
|
797
1048
|
async function terms(venue) {
|
|
798
1049
|
const { api } = await runtime();
|
|
799
1050
|
const process2 = venueProcess(venue);
|
|
@@ -816,7 +1067,6 @@ function toWire(t, m, price, quantity) {
|
|
|
816
1067
|
if (perLot <= 0n) throw new Error("Price is below one quote atom per lot.");
|
|
817
1068
|
return { lots: (baseAtoms / lot).toString(), perLot: perLot.toString() };
|
|
818
1069
|
}
|
|
819
|
-
var human = (t, free) => Object.fromEntries(Object.entries(free ?? {}).map(([asset, atoms]) => [asset, formatAtoms(atoms, t.den(asset))]));
|
|
820
1070
|
function registerExchange(server) {
|
|
821
1071
|
tool(
|
|
822
1072
|
server,
|
|
@@ -971,6 +1221,17 @@ function registerExchange(server) {
|
|
|
971
1221
|
WRITE
|
|
972
1222
|
);
|
|
973
1223
|
}
|
|
1224
|
+
var VENUE, venueProcess, human;
|
|
1225
|
+
var init_exchange = __esm({
|
|
1226
|
+
"src/exchange.mjs"() {
|
|
1227
|
+
"use strict";
|
|
1228
|
+
init_runtime();
|
|
1229
|
+
init_tool();
|
|
1230
|
+
VENUE = z3.enum(["game", "token"]).describe("game: items vs Gold. token: element tokens vs TEST-RUNE.");
|
|
1231
|
+
venueProcess = (venue) => venue === "game" ? graph.processes.internalVenue : graph.processes.externalVenue;
|
|
1232
|
+
human = (t, free) => Object.fromEntries(Object.entries(free ?? {}).map(([asset, atoms]) => [asset, formatAtoms(atoms, t.den(asset))]));
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
974
1235
|
|
|
975
1236
|
// src/bridge.mjs
|
|
976
1237
|
import crypto3 from "node:crypto";
|
|
@@ -987,11 +1248,6 @@ import {
|
|
|
987
1248
|
} from "@solana/web3.js";
|
|
988
1249
|
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
989
1250
|
import { Transaction as SuiTransaction, coinWithBalance } from "@mysten/sui/transactions";
|
|
990
|
-
var DAILY_FAUCET = 100000000n;
|
|
991
|
-
var DAY_MS = 864e5;
|
|
992
|
-
var CHAIN = z4.enum(["base", "solana", "sui"]);
|
|
993
|
-
var PUSD = graph.processes.pusd;
|
|
994
|
-
var faucetToken = (cfg) => (cfg.tokens ?? []).find((t) => t.faucet) ?? cfg.tokens?.[0];
|
|
995
1251
|
function baseChain(w) {
|
|
996
1252
|
const cfg = chains.base;
|
|
997
1253
|
const token = faucetToken(cfg);
|
|
@@ -1152,11 +1408,23 @@ function suiChain(w) {
|
|
|
1152
1408
|
}
|
|
1153
1409
|
};
|
|
1154
1410
|
}
|
|
1411
|
+
async function gasOf(name, address) {
|
|
1412
|
+
if (name === "base") {
|
|
1413
|
+
const rpc = process.env.PARALITH_BASE_RPC || chains.base.rpc;
|
|
1414
|
+
const pub = createPublicClient({ chain: { ...base, id: chains.base.chainId }, transport: http(rpc) });
|
|
1415
|
+
return `${formatEther(await pub.getBalance({ address }))} ETH`;
|
|
1416
|
+
}
|
|
1417
|
+
if (name === "solana") {
|
|
1418
|
+
const conn = new Connection(process.env.PARALITH_SOLANA_RPC || chains.solana.rpc, "confirmed");
|
|
1419
|
+
return `${await conn.getBalance(new PublicKey(address)) / 1e9} SOL`;
|
|
1420
|
+
}
|
|
1421
|
+
const client = new SuiGrpcClient({ network: chains.sui.network, baseUrl: process.env.PARALITH_SUI_RPC || chains.sui.rpc });
|
|
1422
|
+
return `${Number((await client.getBalance({ owner: address })).balance.balance) / 1e9} SUI`;
|
|
1423
|
+
}
|
|
1155
1424
|
async function chainFor(name) {
|
|
1156
1425
|
const { wallets } = await runtime();
|
|
1157
1426
|
return { base: baseChain, solana: solanaChain, sui: suiChain }[name](wallets);
|
|
1158
1427
|
}
|
|
1159
|
-
var pusdBalance = (address) => readAtoms(PUSD, `balance-${address}`);
|
|
1160
1428
|
function registerBridge(server) {
|
|
1161
1429
|
tool(
|
|
1162
1430
|
server,
|
|
@@ -1280,28 +1548,32 @@ function registerBridge(server) {
|
|
|
1280
1548
|
READ
|
|
1281
1549
|
);
|
|
1282
1550
|
}
|
|
1551
|
+
var DAILY_FAUCET, DAY_MS, CHAIN, PUSD, faucetToken, pusdBalance;
|
|
1552
|
+
var init_bridge = __esm({
|
|
1553
|
+
"src/bridge.mjs"() {
|
|
1554
|
+
"use strict";
|
|
1555
|
+
init_runtime();
|
|
1556
|
+
init_tool();
|
|
1557
|
+
DAILY_FAUCET = 100000000n;
|
|
1558
|
+
DAY_MS = 864e5;
|
|
1559
|
+
CHAIN = z4.enum(["base", "solana", "sui"]);
|
|
1560
|
+
PUSD = graph.processes.pusd;
|
|
1561
|
+
faucetToken = (cfg) => (cfg.tokens ?? []).find((t) => t.faucet) ?? cfg.tokens?.[0];
|
|
1562
|
+
pusdBalance = (address) => readAtoms(PUSD, `balance-${address}`);
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1283
1565
|
|
|
1284
1566
|
// src/dao.mjs
|
|
1285
1567
|
import { z as z5 } from "zod";
|
|
1286
|
-
var RUNE = graph.processes.rune;
|
|
1287
|
-
var DEN = 6;
|
|
1288
1568
|
function daoProcess() {
|
|
1289
1569
|
const id = process.env.PARALITH_DAO || graph.processes.dao;
|
|
1290
1570
|
if (!ID.test(id ?? "")) throw new Error("The DAO is not deployed yet. Read the design at https://docs.paralithmarkets.com/dao/governance");
|
|
1291
1571
|
return id;
|
|
1292
1572
|
}
|
|
1293
|
-
var tags = (record) => Object.entries(record).filter(([, v]) => v !== void 0 && v !== "").map(([name, value]) => ({ name, value: String(value) }));
|
|
1294
1573
|
async function stakeOf(address) {
|
|
1295
1574
|
const row = await readKey(daoProcess(), `stake-${address}`);
|
|
1296
1575
|
return row ?? { active: "0", cooling: "0", releaseAt: 0, lockedUntil: 0, votes: {} };
|
|
1297
1576
|
}
|
|
1298
|
-
var runeView = (row) => row && {
|
|
1299
|
-
...row,
|
|
1300
|
-
active: formatAtoms(row.active ?? 0, DEN),
|
|
1301
|
-
cooling: formatAtoms(row.cooling ?? 0, DEN),
|
|
1302
|
-
...row.releaseAt ? { releaseAt: new Date(row.releaseAt).toISOString() } : {},
|
|
1303
|
-
...row.lockedUntil ? { lockedUntil: new Date(row.lockedUntil).toISOString() } : {}
|
|
1304
|
-
};
|
|
1305
1577
|
async function send(record, options = {}) {
|
|
1306
1578
|
const { api } = await runtime();
|
|
1307
1579
|
const reply = await api.rawSend(tags(record), { process: daoProcess(), node: NODE, ...options });
|
|
@@ -1419,11 +1691,11 @@ function registerDao(server) {
|
|
|
1419
1691
|
exec_process: z5.string().regex(ID).optional().describe("Process the passed proposal sends its one message to."),
|
|
1420
1692
|
exec_tags: z5.record(z5.string(), z5.string()).optional().describe('Tags of that message, e.g. {"Action": "Admin.SetRules", ...}. At most 16; required with exec_process.')
|
|
1421
1693
|
},
|
|
1422
|
-
async ({ title, body, link, exec_process, exec_tags }) => send({
|
|
1694
|
+
async ({ title, body, link: link2, exec_process, exec_tags }) => send({
|
|
1423
1695
|
Action: "Propose",
|
|
1424
1696
|
Title: title,
|
|
1425
1697
|
Body: body,
|
|
1426
|
-
Link:
|
|
1698
|
+
Link: link2,
|
|
1427
1699
|
"Exec-Process": exec_process,
|
|
1428
1700
|
"Exec-Tags": exec_tags && JSON.stringify(exec_tags)
|
|
1429
1701
|
}),
|
|
@@ -1446,13 +1718,27 @@ function registerDao(server) {
|
|
|
1446
1718
|
WRITE
|
|
1447
1719
|
);
|
|
1448
1720
|
}
|
|
1721
|
+
var RUNE, DEN, tags, runeView;
|
|
1722
|
+
var init_dao = __esm({
|
|
1723
|
+
"src/dao.mjs"() {
|
|
1724
|
+
"use strict";
|
|
1725
|
+
init_runtime();
|
|
1726
|
+
init_tool();
|
|
1727
|
+
RUNE = graph.processes.rune;
|
|
1728
|
+
DEN = 6;
|
|
1729
|
+
tags = (record) => Object.entries(record).filter(([, v]) => v !== void 0 && v !== "").map(([name, value]) => ({ name, value: String(value) }));
|
|
1730
|
+
runeView = (row) => row && {
|
|
1731
|
+
...row,
|
|
1732
|
+
active: formatAtoms(row.active ?? 0, DEN),
|
|
1733
|
+
cooling: formatAtoms(row.cooling ?? 0, DEN),
|
|
1734
|
+
...row.releaseAt ? { releaseAt: new Date(row.releaseAt).toISOString() } : {},
|
|
1735
|
+
...row.lockedUntil ? { lockedUntil: new Date(row.lockedUntil).toISOString() } : {}
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
});
|
|
1449
1739
|
|
|
1450
1740
|
// src/vaults.mjs
|
|
1451
1741
|
import { z as z6 } from "zod";
|
|
1452
|
-
var VENUE2 = z6.enum(["game", "token"]).describe("game: items vs Gold. token: element tokens vs TEST-RUNE.");
|
|
1453
|
-
var STRATEGY = z6.enum(["amm", "active", "grid"]).describe("amm: passive, steady, close to holding. active: concentrated, best sideways, loses most in trends. grid: fixed steps, best range-bound, loses in trends.");
|
|
1454
|
-
var venueProcess2 = (venue) => venue === "game" ? graph.processes.internalVenue : graph.processes.externalVenue;
|
|
1455
|
-
var DAY_MS2 = 864e5;
|
|
1456
1742
|
async function terms2(venue) {
|
|
1457
1743
|
const { api } = await runtime();
|
|
1458
1744
|
const process2 = venueProcess2(venue);
|
|
@@ -1465,8 +1751,6 @@ function marketFor2(t, item) {
|
|
|
1465
1751
|
if (!entry) throw new Error(`No market for "${item}". Markets: ${Object.keys(t.markets).join(", ")}`);
|
|
1466
1752
|
return { id: entry[0], ...entry[1] };
|
|
1467
1753
|
}
|
|
1468
|
-
var pct = (part, whole) => BigInt(whole || 0) > 0n ? `${(Number(BigInt(part || 0) * 10000n / BigInt(whole)) / 100).toFixed(2)}%` : "0%";
|
|
1469
|
-
var iso2 = (ms) => ms && Number(ms) > 0 ? new Date(Number(ms)).toISOString() : void 0;
|
|
1470
1754
|
function vaultView(t, m, v) {
|
|
1471
1755
|
const b = (atoms) => formatAtoms(atoms ?? 0, t.den(m.base));
|
|
1472
1756
|
const q = (atoms) => formatAtoms(atoms ?? 0, t.den(m.quote));
|
|
@@ -1624,30 +1908,219 @@ function registerVaults(server) {
|
|
|
1624
1908
|
WRITE
|
|
1625
1909
|
);
|
|
1626
1910
|
}
|
|
1911
|
+
var VENUE2, STRATEGY, venueProcess2, DAY_MS2, pct, iso2;
|
|
1912
|
+
var init_vaults = __esm({
|
|
1913
|
+
"src/vaults.mjs"() {
|
|
1914
|
+
"use strict";
|
|
1915
|
+
init_runtime();
|
|
1916
|
+
init_tool();
|
|
1917
|
+
VENUE2 = z6.enum(["game", "token"]).describe("game: items vs Gold. token: element tokens vs TEST-RUNE.");
|
|
1918
|
+
STRATEGY = z6.enum(["amm", "active", "grid"]).describe("amm: passive, steady, close to holding. active: concentrated, best sideways, loses most in trends. grid: fixed steps, best range-bound, loses in trends.");
|
|
1919
|
+
venueProcess2 = (venue) => venue === "game" ? graph.processes.internalVenue : graph.processes.externalVenue;
|
|
1920
|
+
DAY_MS2 = 864e5;
|
|
1921
|
+
pct = (part, whole) => BigInt(whole || 0) > 0n ? `${(Number(BigInt(part || 0) * 10000n / BigInt(whole)) / 100).toFixed(2)}%` : "0%";
|
|
1922
|
+
iso2 = (ms) => ms && Number(ms) > 0 ? new Date(Number(ms)).toISOString() : void 0;
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1627
1925
|
|
|
1628
|
-
// src/
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1926
|
+
// src/show.mjs
|
|
1927
|
+
import fs5 from "node:fs";
|
|
1928
|
+
import path5 from "node:path";
|
|
1929
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1930
|
+
import { z as z7 } from "zod";
|
|
1931
|
+
import { registerAppResource, registerAppTool, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
|
|
1932
|
+
function viewHtml() {
|
|
1933
|
+
for (const file of [path5.join(HERE2, "view.html"), path5.join(HERE2, "..", "dist", "view.html")]) {
|
|
1934
|
+
if (fs5.existsSync(file)) return fs5.readFileSync(file, "utf8");
|
|
1935
|
+
}
|
|
1936
|
+
throw new Error("view.html is missing: run npm run build");
|
|
1937
|
+
}
|
|
1938
|
+
function need2(args, key) {
|
|
1939
|
+
if (!args[key]) throw new Error(`${key} is required to show this.`);
|
|
1940
|
+
return args[key];
|
|
1941
|
+
}
|
|
1942
|
+
async function ownAddress() {
|
|
1943
|
+
if (READ_ONLY) return void 0;
|
|
1944
|
+
const { wallets } = await runtime();
|
|
1945
|
+
return wallets?.ao.address;
|
|
1946
|
+
}
|
|
1947
|
+
function registerShow(server) {
|
|
1948
|
+
const csp = { frameDomains: [GAME, TRADE] };
|
|
1949
|
+
registerAppResource(
|
|
1950
|
+
server,
|
|
1951
|
+
"Paralith view",
|
|
1952
|
+
URI,
|
|
1953
|
+
{ description: "Live game and exchange visuals." },
|
|
1954
|
+
async () => ({ contents: [{ uri: URI, mimeType: RESOURCE_MIME_TYPE, text: viewHtml(), _meta: { ui: { csp } } }] })
|
|
1955
|
+
);
|
|
1956
|
+
registerAppTool(server, "show", {
|
|
1957
|
+
description: "Show something with the game's and exchange's own visuals, live: battle (follows a player's arena fights, animated), companion (card), hunt, market (order book and chart), vault, position, bridge (transfers). Returns a link; clients that support MCP Apps also display it inline. Address defaults to yours.",
|
|
1958
|
+
inputSchema: {
|
|
1959
|
+
what: z7.enum(Object.keys(VIEWS)),
|
|
1960
|
+
address: z7.string().optional().describe("AO address; omit for yours."),
|
|
1961
|
+
monster_id: z7.string().optional().describe("companion: which one; omit for the active companion."),
|
|
1962
|
+
battle_id: z7.string().optional().describe("battle: a specific battle, with worker."),
|
|
1963
|
+
worker: z7.string().optional().describe("battle: the battle worker process id."),
|
|
1964
|
+
venue: z7.enum(["game", "token"]).optional().describe("market/vault/position. Default token."),
|
|
1965
|
+
market: z7.string().optional().describe("market/vault: the market, as exchange_markets lists it."),
|
|
1966
|
+
vault: z7.enum(["amm", "active", "grid"]).optional(),
|
|
1967
|
+
chain: z7.enum(["base", "solana", "sui"]).optional().describe("bridge: with tx, one transfer."),
|
|
1968
|
+
tx: z7.string().optional()
|
|
1969
|
+
},
|
|
1970
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
1971
|
+
_meta: { ui: { resourceUri: URI } }
|
|
1972
|
+
}, async (args) => {
|
|
1973
|
+
try {
|
|
1974
|
+
const view = VIEWS[args.what]({ ...args, address: args.address ?? await ownAddress() });
|
|
1975
|
+
return { structuredContent: { what: args.what, view }, content: [{ type: "text", text: `${args.what}: ${view}` }] };
|
|
1976
|
+
} catch (error) {
|
|
1977
|
+
return { isError: true, content: [{ type: "text", text: error.message }] };
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
var GAME, TRADE, URI, HERE2, link, VIEWS;
|
|
1982
|
+
var init_show = __esm({
|
|
1983
|
+
"src/show.mjs"() {
|
|
1984
|
+
"use strict";
|
|
1985
|
+
init_runtime();
|
|
1986
|
+
GAME = (process.env.PARALITH_GAME_URL || "https://game.paralithmarkets.com").replace(/\/$/, "");
|
|
1987
|
+
TRADE = (process.env.PARALITH_TRADE_URL || "https://trade.paralithmarkets.com").replace(/\/$/, "");
|
|
1988
|
+
URI = "ui://paralith/view";
|
|
1989
|
+
HERE2 = path5.dirname(fileURLToPath2(import.meta.url));
|
|
1990
|
+
link = (base2, page, params) => {
|
|
1991
|
+
const query = new URLSearchParams(Object.entries(params).filter(([, v]) => v !== void 0 && v !== ""));
|
|
1992
|
+
return `${base2}/embed/${page}${query.size ? `?${query}` : ""}`;
|
|
1993
|
+
};
|
|
1994
|
+
VIEWS = {
|
|
1995
|
+
battle: (a) => a.battle_id && a.worker ? link(GAME, "battle", { w: a.worker, b: a.battle_id, a: a.address }) : link(GAME, "battle", { a: need2(a, "address") }),
|
|
1996
|
+
companion: (a) => link(GAME, "companion", { a: need2(a, "address"), m: a.monster_id }),
|
|
1997
|
+
hunt: (a) => link(GAME, "hunt", { a: need2(a, "address") }),
|
|
1998
|
+
market: (a) => link(TRADE, "market", { venue: a.venue ?? "token", m: need2(a, "market") }),
|
|
1999
|
+
vault: (a) => link(TRADE, "vault", { venue: a.venue ?? "token", m: need2(a, "market"), v: a.vault ?? "amm" }),
|
|
2000
|
+
position: (a) => link(TRADE, "position", { venue: a.venue ?? "token", a: need2(a, "address") }),
|
|
2001
|
+
bridge: (a) => link(TRADE, "bridge", { a: a.address, chain: a.chain, tx: a.tx })
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
});
|
|
1632
2005
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
2006
|
+
// src/progress.mjs
|
|
2007
|
+
import { z as z8 } from "zod";
|
|
2008
|
+
async function targets(who) {
|
|
2009
|
+
const { wallets } = await runtime();
|
|
2010
|
+
const agents = readAgents();
|
|
2011
|
+
const self = wallets && { name: process.env.PARALITH_AGENT ?? "you", addresses: publicAddresses(wallets) };
|
|
2012
|
+
if (!who || who === "me") {
|
|
2013
|
+
if (!self) throw new Error("This server has no wallet; pass who: an AO address.");
|
|
2014
|
+
return [self];
|
|
2015
|
+
}
|
|
2016
|
+
if (who === "all") {
|
|
2017
|
+
const list = Object.entries(agents).map(([name, a]) => ({ name, addresses: a.addresses }));
|
|
2018
|
+
if (self && !list.some((t) => t.name === self.name)) list.unshift(self);
|
|
2019
|
+
if (!list.length) throw new Error("No agents are set up here. Run: npx -y paralith-mcp@latest setup");
|
|
2020
|
+
return list;
|
|
2021
|
+
}
|
|
2022
|
+
if (agents[who]) return [{ name: who, addresses: agents[who].addresses }];
|
|
2023
|
+
if (ID.test(who)) return [{ name: who, addresses: { ao: who } }];
|
|
2024
|
+
throw new Error(`"${who}" is not an agent here (${Object.keys(agents).join(", ") || "none"}) or an AO address.`);
|
|
2025
|
+
}
|
|
2026
|
+
async function report({ name, addresses: a }) {
|
|
2027
|
+
const { api } = await runtime();
|
|
2028
|
+
const out = { agent: name, addresses: a };
|
|
2029
|
+
const todo = [];
|
|
2030
|
+
const gas = Object.fromEntries(await Promise.all(["base", "solana", "sui"].filter((c) => a[c]).map(async (c) => [c, await gasOf(c, a[c]).catch(() => "unreadable")])));
|
|
2031
|
+
if (Object.keys(gas).length) out.gas = gas;
|
|
2032
|
+
if (!a.ao) {
|
|
2033
|
+
out.note = "AO key not derived yet: it appears once this agent's app has started the server.";
|
|
2034
|
+
return out;
|
|
2035
|
+
}
|
|
2036
|
+
const [p, tokens, venues, redemptions] = await Promise.all([
|
|
2037
|
+
api.readPlayer(a.ao).catch(() => null),
|
|
2038
|
+
Promise.all(Object.entries({ rune: graph.processes.rune, pusd: graph.processes.pusd, ...graph.processes.elements }).map(async ([t, id]) => [t, await readAtoms(id, `balance-${a.ao}`).catch(() => 0n)])),
|
|
2039
|
+
Promise.all(Object.entries(VENUES).map(async ([v, pid]) => [
|
|
2040
|
+
v,
|
|
2041
|
+
await api.readVenuePosition(pid, a.ao).catch(() => null),
|
|
2042
|
+
await api.readVenueVaultPositions(pid, a.ao).catch(() => null)
|
|
2043
|
+
])),
|
|
2044
|
+
readKey("pusd", "withdrawals").catch(() => null)
|
|
2045
|
+
]);
|
|
2046
|
+
if (!p) {
|
|
2047
|
+
out.game = { joined: false };
|
|
2048
|
+
todo.push("Join the game (game_join).");
|
|
2049
|
+
} else {
|
|
2050
|
+
const raw = p.monsters?.[p.activeId] ?? p.collection?.[p.activeId];
|
|
2051
|
+
const c = companion(raw);
|
|
2052
|
+
out.game = {
|
|
2053
|
+
faction: p.faction,
|
|
2054
|
+
gold: p.gold,
|
|
2055
|
+
rune: p.inventory?.rune ?? 0,
|
|
2056
|
+
companion: c && { name: c.name, level: c.level, exp: c.exp, energy: c.energy, happiness: c.happiness },
|
|
2057
|
+
arena: { wins: p.wins ?? 0, losses: p.losses ?? 0, rating: p.rating, sessionBattlesLeft: p.battlesRemaining ?? 0 },
|
|
2058
|
+
...p.battleFleet ? { inBattle: p.battleFleet.battleId } : {},
|
|
2059
|
+
...p.hunt ? { onHunt: p.hunt.runId } : {}
|
|
2060
|
+
};
|
|
2061
|
+
if (!p.dailyReadyAt || Number(p.dailyReadyAt) <= Date.now()) todo.push("Daily crate is ready (game_daily).");
|
|
2062
|
+
if (count(p.lootboxes)) todo.push(`${count(p.lootboxes)} loot box(es) to open (game_lootbox).`);
|
|
2063
|
+
if (raw && Number(raw.exp ?? 0) >= Number(raw.nextLevelExp ?? Infinity)) todo.push("Companion can level up (game_level_up).");
|
|
2064
|
+
if (c && (c.energy < 25 || c.happiness < 25)) todo.push("Companion is below the arena's 25 energy/happiness: feed or play.");
|
|
2065
|
+
}
|
|
2066
|
+
const held = Object.fromEntries(tokens.filter(([, v]) => v > 0n).map(([t, v]) => [t, formatAtoms(v)]));
|
|
2067
|
+
if (Object.keys(held).length) out.tokens = held;
|
|
2068
|
+
const trading = {};
|
|
2069
|
+
for (const [v, pos, vaults] of venues) {
|
|
2070
|
+
const row = {};
|
|
2071
|
+
if (pos?.orders?.length) row.openOrders = pos.orders.length;
|
|
2072
|
+
if (pos?.fills?.length) row.recentFills = pos.fills.length;
|
|
2073
|
+
const shares = Object.entries(vaults ?? {}).flatMap(([m, r]) => Object.entries(r).filter(([, x]) => BigInt(x?.shares ?? 0) > 0n).map(([s]) => `${m} ${s}`));
|
|
2074
|
+
if (shares.length) row.vaults = shares;
|
|
2075
|
+
if (Object.keys(row).length) trading[v] = row;
|
|
2076
|
+
}
|
|
2077
|
+
if (Object.keys(trading).length) out.trading = trading;
|
|
2078
|
+
const pending = (redemptions ?? []).filter((w) => w.account === a.ao && w.status !== "released");
|
|
2079
|
+
if (pending.length) out.bridge = { pendingRedemptions: pending.length };
|
|
2080
|
+
if (Object.keys(gas).length && Object.values(gas).every((g) => /^0 /.test(g))) {
|
|
2081
|
+
todo.push("No chain gas yet: fund an address above to bridge.");
|
|
2082
|
+
}
|
|
2083
|
+
if (todo.length) out.todo = todo;
|
|
2084
|
+
return out;
|
|
2085
|
+
}
|
|
2086
|
+
function registerProgress(server) {
|
|
2087
|
+
tool(
|
|
2088
|
+
server,
|
|
2089
|
+
"progress",
|
|
2090
|
+
'How an agent is doing, in one free read: game (companion, Gold, Rune, arena record, live battle or hunt), tokens, open orders and vault shares on both venues, pending bridge redemptions, chain gas, and what needs doing next. who: omit for yourself, "all" for every agent set up on this machine, an agent name (claude, codex, grok) or an AO address.',
|
|
2091
|
+
{ who: z8.string().optional() },
|
|
2092
|
+
async ({ who }) => {
|
|
2093
|
+
const list = await Promise.all((await targets(who)).map(report));
|
|
2094
|
+
return list.length === 1 ? list[0] : list;
|
|
2095
|
+
},
|
|
2096
|
+
READ
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
var VENUES, count;
|
|
2100
|
+
var init_progress = __esm({
|
|
2101
|
+
"src/progress.mjs"() {
|
|
2102
|
+
"use strict";
|
|
2103
|
+
init_runtime();
|
|
2104
|
+
init_tool();
|
|
2105
|
+
init_agents();
|
|
2106
|
+
init_keys();
|
|
2107
|
+
init_bridge();
|
|
2108
|
+
init_views();
|
|
2109
|
+
VENUES = { game: graph.processes.internalVenue, token: graph.processes.externalVenue };
|
|
2110
|
+
count = (x) => Array.isArray(x) ? x.length : Object.values(x ?? {}).reduce((n, v) => n + Number(v || 0), 0);
|
|
2111
|
+
}
|
|
2112
|
+
});
|
|
1643
2113
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
2114
|
+
// src/build.mjs
|
|
2115
|
+
var build_exports = {};
|
|
2116
|
+
__export(build_exports, {
|
|
2117
|
+
buildServer: () => buildServer
|
|
2118
|
+
});
|
|
2119
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2120
|
+
import { z as z9 } from "zod";
|
|
1648
2121
|
function buildServer() {
|
|
1649
2122
|
const server = new McpServer(
|
|
1650
|
-
{ name: "paralith", version: "0.
|
|
2123
|
+
{ name: "paralith", version: "0.3.0" },
|
|
1651
2124
|
{ instructions: READ_ONLY ? `${READ_ONLY_NOTE}
|
|
1652
2125
|
|
|
1653
2126
|
${INSTRUCTIONS}` : INSTRUCTIONS }
|
|
@@ -1678,7 +2151,7 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
|
|
|
1678
2151
|
server,
|
|
1679
2152
|
"read_state",
|
|
1680
2153
|
`Read any published key of any process, free: /<process>~process@1.0/now/<key>. process is an id or an alias (${Object.keys(PROCESS_ALIASES).join(", ")}). Returns null when absent.`,
|
|
1681
|
-
{ process:
|
|
2154
|
+
{ process: z9.string(), key: z9.string() },
|
|
1682
2155
|
async ({ process: process2, key }) => readKey(process2, key),
|
|
1683
2156
|
READ
|
|
1684
2157
|
);
|
|
@@ -1687,10 +2160,10 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
|
|
|
1687
2160
|
"send_message",
|
|
1688
2161
|
"Escape hatch: sign and send a raw message to any process and return its reply. Prefer the specific tools; use this only for an action they do not cover. Tag values must be strings; never use a tag named Target.",
|
|
1689
2162
|
{
|
|
1690
|
-
process:
|
|
1691
|
-
tags:
|
|
1692
|
-
data:
|
|
1693
|
-
push_outbox:
|
|
2163
|
+
process: z9.string(),
|
|
2164
|
+
tags: z9.record(z9.string(), z9.string()).describe('e.g. {"Action": "Pass.Info"}'),
|
|
2165
|
+
data: z9.string().optional(),
|
|
2166
|
+
push_outbox: z9.boolean().optional().describe("Deliver the messages this one emits to other processes (e.g. a Transfer to a process).")
|
|
1694
2167
|
},
|
|
1695
2168
|
async ({ process: process2, tags: tags2, data, push_outbox }) => {
|
|
1696
2169
|
if (Object.keys(tags2).some((k) => k.toLowerCase() === "target")) throw new Error("Target is not a usable tag name.");
|
|
@@ -1719,9 +2192,58 @@ ${INSTRUCTIONS}` : INSTRUCTIONS }
|
|
|
1719
2192
|
registerBridge(server);
|
|
1720
2193
|
registerDao(server);
|
|
1721
2194
|
registerVaults(server);
|
|
2195
|
+
registerShow(server);
|
|
2196
|
+
registerProgress(server);
|
|
1722
2197
|
return server;
|
|
1723
2198
|
}
|
|
2199
|
+
var DOCS, INSTRUCTIONS, READ_ONLY_NOTE;
|
|
2200
|
+
var init_build = __esm({
|
|
2201
|
+
"src/build.mjs"() {
|
|
2202
|
+
"use strict";
|
|
2203
|
+
init_runtime();
|
|
2204
|
+
init_keys();
|
|
2205
|
+
init_tool();
|
|
2206
|
+
init_game();
|
|
2207
|
+
init_exchange();
|
|
2208
|
+
init_bridge();
|
|
2209
|
+
init_dao();
|
|
2210
|
+
init_vaults();
|
|
2211
|
+
init_show();
|
|
2212
|
+
init_progress();
|
|
2213
|
+
DOCS = process.env.PARALITH_DOCS_URL || "https://docs.paralithmarkets.com";
|
|
2214
|
+
INSTRUCTIONS = `Paralith on AO (HyperBEAM): Rune Realm (a companion game), two order books, and the PUSD bridge.
|
|
2215
|
+
Everything is TEST-prefixed while unreleased. Reads are free; every write is a signed message (a few seconds round trip, longer under load).
|
|
2216
|
+
|
|
2217
|
+
Start: wallet -> game_player. No account? game_join (pick a faction).
|
|
2218
|
+
Core loop: game_daily once a day; game_companion feed/play/quest/claim to keep energy and happiness up and earn exp and Gold;
|
|
2219
|
+
game_level_up when exp allows; game_learn_move when an offer is pending.
|
|
2220
|
+
Arena: arena_enter (25 energy + 25 happiness, 4 fights), arena_fight mode=bot, then arena_attack each round
|
|
2221
|
+
(read arena_battle for moves and uses left; Rally and Mend are free once each), arena_end leave when done.
|
|
2222
|
+
Hunt: hunt_begin -> hunt_act search -> attack until defeated -> capture (1-3 Rune + a Scroll) or decline -> end.
|
|
2223
|
+
Markets: exchange_markets / exchange_book / exchange_order on venue "game" (items vs Gold) or "token" (elements vs TEST-RUNE).
|
|
2224
|
+
Vaults (pooled liquidity, one amm/active/grid per market): vault_list, vault_position, vault_deposit, vault_shares (withdraw/lock/burn), vault_fund, vault_crank.
|
|
2225
|
+
Rune: rune_move withdraw (game -> token) / deposit (token -> game). Bridge: bridge_balances, bridge_faucet, bridge_deposit, bridge_redeem.
|
|
2226
|
+
DAO (stake TEST-RUNE; a passed proposal may send one attached message): dao_info, dao_proposals, dao_stake stake/unstake/withdraw, dao_propose, dao_vote.
|
|
2227
|
+
|
|
2228
|
+
To report how you (or every agent set up on this machine) are doing, call progress (who: "all" for every agent); it also lists what needs doing next.
|
|
2229
|
+
|
|
2230
|
+
To let the user SEE something (an arena fight as it happens, a companion, a hunt, a market's book and chart, a vault, a position, bridge transfers), call show: it returns a live link and displays inline where the client supports it.
|
|
2231
|
+
|
|
2232
|
+
Rules: if a tool says a write WAS accepted but unconfirmed, never resend it; re-read state instead.
|
|
2233
|
+
A refusal ({"error": ...}) costs nothing. Use game_catalog for exact costs and move stats; docs at ${DOCS}/llms-full.txt.`;
|
|
2234
|
+
READ_ONLY_NOTE = `This endpoint is READ-ONLY: it explains the protocol and reads live state for any address.
|
|
2235
|
+
To play, trade or bridge, run the local server with a wallet phrase: ${DOCS}/ai/mcp`;
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
1724
2238
|
|
|
1725
2239
|
// src/server.mjs
|
|
1726
|
-
|
|
1727
|
-
|
|
2240
|
+
if (process.argv[2] === "setup") {
|
|
2241
|
+
const { setup: setup2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
2242
|
+
await setup2(process.argv.slice(3));
|
|
2243
|
+
} else {
|
|
2244
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
2245
|
+
const { runtime: runtime2 } = await Promise.resolve().then(() => (init_runtime(), runtime_exports));
|
|
2246
|
+
const { buildServer: buildServer2 } = await Promise.resolve().then(() => (init_build(), build_exports));
|
|
2247
|
+
await buildServer2().connect(new StdioServerTransport());
|
|
2248
|
+
runtime2().catch((error) => console.error(`paralith-mcp: ${error.message}`));
|
|
2249
|
+
}
|