@xgamesfun/solana 0.24.0 → 0.25.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.
Files changed (99) hide show
  1. package/DISCLAIMER.md +1 -1
  2. package/NOTICE +4 -4
  3. package/README.md +100 -52
  4. package/bin/xsolana.mjs +32 -11
  5. package/examples/blind-forge.json +2 -4
  6. package/examples/coin-flip-arena.json +2 -5
  7. package/examples/duel-arena.json +1 -4
  8. package/examples/fair-presale.json +1 -3
  9. package/examples/hero-trials.json +14 -2
  10. package/examples/ideas.json +788 -788
  11. package/examples/nft-raffle.json +1 -0
  12. package/examples/partner-vault.json +19 -3
  13. package/examples/sacrifice-raffle.json +2 -10
  14. package/examples/shelter-fair.json +1 -0
  15. package/examples/stakers-draw.json +4 -7
  16. package/examples/trade-in-shop.json +2 -3
  17. package/js/approve/details.mjs +117 -48
  18. package/js/approve/ed25519.mjs +5 -1
  19. package/js/approve/live-check.mjs +83 -26
  20. package/js/approve/page.html +217 -78
  21. package/js/approve/page.js +43 -15
  22. package/js/approve/server.mjs +276 -74
  23. package/js/chain/base58.js +7 -1
  24. package/js/chain/borsh.js +15 -11
  25. package/js/chain/bytes.js +11 -8
  26. package/js/chain/core.js +21 -5
  27. package/js/chain/curve.js +1 -1
  28. package/js/chain/lookup.js +118 -36
  29. package/js/chain/mint.js +6 -2
  30. package/js/chain/pda.js +2 -2
  31. package/js/chain/programs.js +6 -6
  32. package/js/chain/rpc.js +40 -14
  33. package/js/chain/token.js +13 -2
  34. package/js/chain/token2022.js +55 -16
  35. package/js/chain/tx.js +32 -22
  36. package/js/client/accounts.js +270 -80
  37. package/js/client/compressed.js +4 -3
  38. package/js/client/grant.js +165 -40
  39. package/js/client/idl/loot.js +1 -1
  40. package/js/client/idl/loot.json +104 -16
  41. package/js/client/ix.js +2121 -457
  42. package/js/client/pda.js +20 -18
  43. package/js/client/policy.js +12 -5
  44. package/js/client/random.js +2 -2
  45. package/js/client/sizes.js +220 -65
  46. package/js/client/traits.js +14 -5
  47. package/js/connect/connect.mjs +59 -17
  48. package/js/economy/check.mjs +3884 -690
  49. package/js/economy/cost.mjs +381 -57
  50. package/js/economy/examples.mjs +176 -35
  51. package/js/economy/explain.mjs +468 -110
  52. package/js/economy/guide.mjs +282 -59
  53. package/js/economy/legal.mjs +12 -6
  54. package/js/economy/live.mjs +62 -20
  55. package/js/economy/model.mjs +320 -97
  56. package/js/economy/plan.mjs +1022 -203
  57. package/js/economy/review.mjs +760 -0
  58. package/js/economy/schema.json +134 -372
  59. package/js/economy/secrets.mjs +17 -7
  60. package/js/economy/simulate.mjs +771 -178
  61. package/js/economy/trust.mjs +15 -0
  62. package/js/economy/txsize.mjs +15 -3
  63. package/js/economy/validate.mjs +61 -24
  64. package/js/index.js +1 -0
  65. package/js/mcp/server.mjs +43 -15
  66. package/js/mcp/tools.mjs +318 -80
  67. package/js/play/index.js +1960 -461
  68. package/js/play/keys.js +35 -9
  69. package/js/results/index.js +225 -32
  70. package/js/results/server.js +230 -0
  71. package/js/results/wire.js +64 -16
  72. package/package.json +6 -4
  73. package/release.json +5 -5
  74. package/skills/xsolana-economy/SKILL.md +52 -22
  75. package/skills/xsolana-economy/references/after-launch.md +32 -21
  76. package/skills/xsolana-economy/references/apps.md +47 -36
  77. package/skills/xsolana-economy/references/sentence-to-spec.md +149 -129
  78. package/skills/xsolana-economy/references/telling-the-person.md +15 -6
  79. package/skills/xsolana-economy/references/trust-and-cheating.md +126 -0
  80. package/types/chain/core.d.ts +1 -1
  81. package/types/chain/lookup.d.ts +1 -1
  82. package/types/client/accounts.d.ts +24 -3
  83. package/types/client/grant.d.ts +2 -2
  84. package/types/client/idl/loot.d.ts +22 -0
  85. package/types/client/ix.d.ts +54 -45
  86. package/types/client/pda.d.ts +3 -1
  87. package/types/economy/cost.d.mts +1 -1
  88. package/types/economy/explain.d.mts +3 -0
  89. package/types/economy/guide.d.mts +5 -0
  90. package/types/economy/model.d.mts +48 -3
  91. package/types/economy/plan.d.mts +44 -2
  92. package/types/economy/review.d.mts +45 -0
  93. package/types/economy/trust.d.mts +8 -0
  94. package/types/index.d.ts +1 -0
  95. package/types/mcp/tools.d.mts +62 -0
  96. package/types/play/index.d.ts +14 -25
  97. package/types/results/index.d.ts +8 -7
  98. package/types/results/server.d.ts +126 -0
  99. package/types/results/wire.d.ts +1 -1
@@ -0,0 +1,15 @@
1
+ // How far a result a voucher's server signs can be trusted: the levels a grant declares in `trust`, and each in
2
+ // words. The spec's own note about its server: never sent on chain. Pure.
3
+
4
+ /** The trust levels a voucher grant declares, strongest first. */
5
+ export const TRUST_LEVELS = Object.freeze(['decides', 'replays', 'bounds']);
6
+
7
+ /** Each trust level in words: what the server does, and what a cheater still gets. */
8
+ export const TRUST_WORDS = Object.freeze({
9
+ decides:
10
+ 'the server decides the result itself (it runs the game or the match; the page sends only inputs): nothing the page says can forge one, though a bot that plays well still earns what a good player does',
11
+ replays:
12
+ 'the server runs the page’s inputs again and reads the result itself: a forged result fails, though a bot that plays well still earns, and one that knows the run’s randomness up front can search for the best run offline',
13
+ bounds:
14
+ 'the page runs the game and reports the result, and the server checks only its pace, order and daily caps: a script that waits as long as a person would gets every reward, up to the limits',
15
+ });
@@ -18,7 +18,7 @@ export const MAX_TRANSACTION_BYTES = 1232;
18
18
  export const WALLET_ROOM = 64;
19
19
  export const BUDGET = MAX_TRANSACTION_BYTES - WALLET_ROOM;
20
20
 
21
- export const compact = (n) => (n < 0x80 ? 1 : n < 0x4000 ? 2 : 3);
21
+ export const compact = n => (n < 0x80 ? 1 : n < 0x4000 ? 2 : 3);
22
22
 
23
23
  /**
24
24
  * instructions: [{ program: name, accounts: [names], data: bytes }]; signers: [names] (the fee payer first).
@@ -30,8 +30,20 @@ export function transactionSize({ instructions, signers }) {
30
30
  for (const a of ix.accounts) keys.add(a);
31
31
  keys.add(`program:${ix.program}`);
32
32
  }
33
- const body = instructions.reduce((n, ix) => n + 1 + compact(ix.accounts.length) + ix.accounts.length + compact(ix.data) + ix.data, 0);
34
- return compact(signers.length) + 64 * signers.length + 3 + compact(keys.size) + 32 * keys.size + 32 + compact(instructions.length) + body;
33
+ const body = instructions.reduce(
34
+ (n, ix) => n + 1 + compact(ix.accounts.length) + ix.accounts.length + compact(ix.data) + ix.data,
35
+ 0,
36
+ );
37
+ return (
38
+ compact(signers.length) +
39
+ 64 * signers.length +
40
+ 3 +
41
+ compact(keys.size) +
42
+ 32 * keys.size +
43
+ 32 +
44
+ compact(instructions.length) +
45
+ body
46
+ );
35
47
  }
36
48
 
37
49
  /** How many accounts the transaction locks: each account once, its programs and signers included. */
@@ -5,41 +5,67 @@ import { readFileSync } from 'node:fs';
5
5
 
6
6
  export const SCHEMA = JSON.parse(readFileSync(new URL('./schema.json', import.meta.url), 'utf8'));
7
7
 
8
- const typeOf = (v) => (v === null ? 'null' : Array.isArray(v) ? 'array' : Number.isInteger(v) ? 'integer' : typeof v);
8
+ const typeOf = v => (v === null ? 'null' : Array.isArray(v) ? 'array' : Number.isInteger(v) ? 'integer' : typeof v);
9
9
  const isType = (v, t) => t === typeOf(v) || (t === 'number' && typeof v === 'number' && Number.isFinite(v));
10
- const show = (v) => JSON.stringify(v)?.slice(0, 60);
10
+ const show = v => JSON.stringify(v)?.slice(0, 60);
11
11
  /** "a" or "an", as the word after it takes: "an object", "an array", "a string". */
12
- const article = (word) => (/^[aeiou]/.test(word) ? `an ${word}` : `a ${word}`);
12
+ const article = word => (/^[aeiou]/.test(word) ? `an ${word}` : `a ${word}`);
13
13
  /** A value as text with its objects' keys in order, so two equal values read the same (for uniqueItems). */
14
- const canonical = (v) => JSON.stringify(v, (_, x) => (x && typeof x === 'object' && !Array.isArray(x) ? Object.fromEntries(Object.entries(x).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) : x));
14
+ const canonical = v =>
15
+ JSON.stringify(v, (_, x) =>
16
+ x && typeof x === 'object' && !Array.isArray(x)
17
+ ? Object.fromEntries(Object.entries(x).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
18
+ : x,
19
+ );
15
20
 
16
21
  function resolve(root, ref) {
17
22
  if (!ref.startsWith('#/')) throw new Error(`Only local $ref is supported: ${ref}`);
18
- return ref.slice(2).split('/').reduce((node, key) => node?.[key], root);
23
+ return ref
24
+ .slice(2)
25
+ .split('/')
26
+ .reduce((node, key) => node?.[key], root);
19
27
  }
20
28
 
21
29
  /** Every way `value` breaks `schema`, as { path, message }. An empty list means it is valid. */
22
30
  export function validate(value, schema = SCHEMA, root = schema, path = 'economy') {
23
31
  const errors = [];
24
- const add = (message) => errors.push({ path, message });
32
+ const add = message => errors.push({ path, message });
25
33
  if (schema.$ref) return validate(value, resolve(root, schema.$ref), root, path);
26
34
  if (schema.anyOf) {
27
- const results = schema.anyOf.map((s) => validate(value, s, root, path));
28
- if (!results.some((r) => r.length === 0)) {
29
- const wanted = schema.description ?? results.flat().map((e) => e.message).join('; or ');
35
+ const results = schema.anyOf.map(s => validate(value, s, root, path));
36
+ if (!results.some(r => r.length === 0)) {
37
+ const wanted =
38
+ schema.description ??
39
+ results
40
+ .flat()
41
+ .map(e => e.message)
42
+ .join('; or ');
30
43
  add(`${show(value)} is not allowed here (${wanted})`);
31
44
  }
32
45
  return errors;
33
46
  }
34
- // Exactly one of the shapes: none is not allowed, and two at once would be read two ways.
47
+ // Exactly one of the shapes: none is not allowed, and two at once would be read two ways. Shapes that only name a
48
+ // field each (`required`) are one choice among fields of an object, whose other keywords still apply.
35
49
  if (schema.oneOf) {
36
- const results = schema.oneOf.map((s) => validate(value, s, root, path));
37
- const fits = results.filter((r) => r.length === 0).length;
38
- if (fits !== 1) {
39
- const wanted = schema.description ?? results.flat().map((e) => e.message).join('; or ');
40
- add(`${show(value)} is not allowed here (${fits ? 'it fits more than one of its forms: ' : ''}${wanted})`);
50
+ const fields = schema.oneOf.every(s => Object.keys(s).length === 1 && s.required?.length === 1)
51
+ ? schema.oneOf.map(s => s.required[0])
52
+ : null;
53
+ const results = schema.oneOf.map(s => validate(value, s, root, path));
54
+ const fits = results.filter(r => r.length === 0).length;
55
+ if (fields && typeOf(value) === 'object') {
56
+ if (fits !== 1) add(`needs exactly one of ${fields.map(f => `"${f}"`).join(' or ')}${fits ? ', not both' : ''}`);
57
+ } else {
58
+ if (fits !== 1) {
59
+ const wanted =
60
+ schema.description ??
61
+ results
62
+ .flat()
63
+ .map(e => e.message)
64
+ .join('; or ');
65
+ add(`${show(value)} is not allowed here (${fits ? 'it fits more than one of its forms: ' : ''}${wanted})`);
66
+ }
67
+ return errors;
41
68
  }
42
- return errors;
43
69
  }
44
70
  if (schema.not && validate(value, schema.not, root, path).length === 0) add(`${show(value)} is not allowed here`);
45
71
  if ('const' in schema && value !== schema.const) add(`must be ${show(schema.const)}`);
@@ -50,13 +76,16 @@ export function validate(value, schema = SCHEMA, root = schema, path = 'economy'
50
76
  }
51
77
  if (typeof value === 'string') {
52
78
  if (schema.pattern && !new RegExp(schema.pattern).test(value)) add(`${show(value)} is not in the expected form`);
53
- if (schema.minLength !== undefined && value.length < schema.minLength) add(`must have at least ${schema.minLength} characters`);
54
- if (schema.maxLength !== undefined && value.length > schema.maxLength) add(`must have at most ${schema.maxLength} characters`);
79
+ if (schema.minLength !== undefined && value.length < schema.minLength)
80
+ add(`must have at least ${schema.minLength} characters`);
81
+ if (schema.maxLength !== undefined && value.length > schema.maxLength)
82
+ add(`must have at most ${schema.maxLength} characters`);
55
83
  }
56
84
  if (typeof value === 'number') {
57
85
  if (schema.minimum !== undefined && value < schema.minimum) add(`must be at least ${schema.minimum}`);
58
86
  if (schema.maximum !== undefined && value > schema.maximum) add(`must be at most ${schema.maximum}`);
59
- if (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum) add(`must be more than ${schema.exclusiveMinimum}`);
87
+ if (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum)
88
+ add(`must be more than ${schema.exclusiveMinimum}`);
60
89
  }
61
90
  if (Array.isArray(value)) {
62
91
  if (schema.minItems !== undefined && value.length < schema.minItems) add(`needs at least ${schema.minItems}`);
@@ -65,7 +94,11 @@ export function validate(value, schema = SCHEMA, root = schema, path = 'economy'
65
94
  const seen = new Map();
66
95
  value.forEach((v, i) => {
67
96
  const text = canonical(v);
68
- if (seen.has(text)) errors.push({ path: `${path}[${i}]`, message: `is the same as ${path}[${seen.get(text)}]: each is listed once` });
97
+ if (seen.has(text))
98
+ errors.push({
99
+ path: `${path}[${i}]`,
100
+ message: `is the same as ${path}[${seen.get(text)}]: each is listed once`,
101
+ });
69
102
  else seen.set(text, i);
70
103
  });
71
104
  }
@@ -75,10 +108,14 @@ export function validate(value, schema = SCHEMA, root = schema, path = 'economy'
75
108
  for (const key of schema.required ?? []) if (!(key in value)) add(`needs "${key}"`);
76
109
  for (const [key, v] of Object.entries(value)) {
77
110
  const where = `${path}.${key}`;
78
- if (schema.propertyNames?.pattern && !new RegExp(schema.propertyNames.pattern).test(key)) errors.push({ path: where, message: 'is not a name this place takes' });
79
- if (schema.properties && key in schema.properties) errors.push(...validate(v, schema.properties[key], root, where));
80
- else if (schema.additionalProperties === false) errors.push({ path: where, message: 'is not a field this place takes' });
81
- else if (typeOf(schema.additionalProperties) === 'object') errors.push(...validate(v, schema.additionalProperties, root, where));
111
+ if (schema.propertyNames?.pattern && !new RegExp(schema.propertyNames.pattern).test(key))
112
+ errors.push({ path: where, message: 'is not a name this place takes' });
113
+ if (schema.properties && key in schema.properties)
114
+ errors.push(...validate(v, schema.properties[key], root, where));
115
+ else if (schema.additionalProperties === false)
116
+ errors.push({ path: where, message: 'is not a field this place takes' });
117
+ else if (typeOf(schema.additionalProperties) === 'object')
118
+ errors.push(...validate(v, schema.additionalProperties, root, where));
82
119
  }
83
120
  }
84
121
  return errors;
package/js/index.js CHANGED
@@ -4,6 +4,7 @@ export { simulate, explain } from './economy/simulate.mjs';
4
4
  export { plan, setupSteps, playSteps } from './economy/plan.mjs';
5
5
  export { cost, explainCost } from './economy/cost.mjs';
6
6
  export { explainEconomy } from './economy/explain.mjs';
7
+ export { review, describeReview, SEVERITIES, TRUST_LEVELS, TRUST_WORDS } from './economy/review.mjs';
7
8
  export { model } from './economy/model.mjs';
8
9
  export { prepare, recordLanded, changedParts } from './economy/live.mjs';
9
10
  export { guideText, catalog, CANNOT } from './economy/guide.mjs';
package/js/mcp/server.mjs CHANGED
@@ -10,7 +10,7 @@ import { PROGRAM_INFO, tools, VERSION } from './tools.mjs';
10
10
 
11
11
  export const INSTRUCTIONS = [
12
12
  'loot is one shared Solana program for game and community economies (shops, loot boxes, lotteries, auctions, staking farms, crafting, contests, schedules, perks for holders of any NFT, capped rewards). An economy is a JSON spec, not a new smart contract: never write Anchor or Rust for what loot covers.',
13
- 'Start with xsolana_guide, then xsolana_examples with the person’s own words; write the spec; xsolana_check after every change; xsolana_simulate and xsolana_explain before telling them what it does; xsolana_save; xsolana_publish when they say to put it live (they approve in their own wallet: you never sign or ask for a key).',
13
+ 'Start with xsolana_guide, then xsolana_examples with the person’s own words; write the spec; xsolana_check after every change; xsolana_simulate and xsolana_explain before telling them what it does; xsolana_review before going live (fix its high findings, say honestly what each voucher server checks, and tell the person what a cheater gets); xsolana_save; xsolana_publish when they say to put it live (they approve in their own wallet: you never sign or ask for a key).',
14
14
  `Program ${PROGRAM_INFO.program} on ${PROGRAM_INFO.cluster} (loot ${VERSION}). Devnet only until audited.`,
15
15
  ].join(' ');
16
16
 
@@ -23,15 +23,18 @@ export const PROTOCOLS = ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05'
23
23
  /** Every way `input` does not fit a tool's input schema, in words; [] when it fits. */
24
24
  export function inputProblems(schema, input) {
25
25
  if (!input || typeof input !== 'object' || Array.isArray(input)) return ['arguments must be an object'];
26
- return validate(input, schema, schema, 'arguments').map((e) => `${e.path} ${e.message}`);
26
+ return validate(input, schema, schema, 'arguments').map(e => `${e.path} ${e.message}`);
27
27
  }
28
28
 
29
29
  /** Runs one tool by name: the same checks and answer on the server and in the terminal. */
30
30
  export async function runTool(name, input = {}, ctx = {}) {
31
- const tool = tools.find((t) => t.name === name);
31
+ const tool = tools.find(t => t.name === name);
32
32
  if (!tool) {
33
- const near = tools.filter((t) => t.name.includes(String(name).replace(/^xsolana_/, ''))).map((t) => t.name);
34
- return { isError: true, text: `No tool ${name}.${near.length ? ` Did you mean ${near.join(', ')}?` : ''} Tools: ${tools.map((t) => t.name).join(', ')}.` };
33
+ const near = tools.filter(t => t.name.includes(String(name).replace(/^xsolana_/, ''))).map(t => t.name);
34
+ return {
35
+ isError: true,
36
+ text: `No tool ${name}.${near.length ? ` Did you mean ${near.join(', ')}?` : ''} Tools: ${tools.map(t => t.name).join(', ')}.`,
37
+ };
35
38
  }
36
39
  const bad = inputProblems(tool.inputSchema, input);
37
40
  if (bad.length) return { isError: true, text: bad.join('; ') };
@@ -42,27 +45,48 @@ export async function runTool(name, input = {}, ctx = {}) {
42
45
  }
43
46
  }
44
47
 
45
- const jsonable = (value) => JSON.parse(JSON.stringify(value, (_, v) => (typeof v === 'bigint' ? v.toString() : v instanceof Uint8Array ? Buffer.from(v).toString('hex') : v)));
48
+ const jsonable = value =>
49
+ JSON.parse(
50
+ JSON.stringify(value, (_, v) =>
51
+ typeof v === 'bigint' ? v.toString() : v instanceof Uint8Array ? Buffer.from(v).toString('hex') : v,
52
+ ),
53
+ );
46
54
 
47
55
  /** The answer to one JSON-RPC request, or null for a notification. */
48
56
  export async function handle(message, ctx = {}) {
49
57
  const { id, method, params } = message ?? {};
50
- const reply = (result) => (id === undefined ? null : { jsonrpc: '2.0', id, result });
58
+ const reply = result => (id === undefined ? null : { jsonrpc: '2.0', id, result });
51
59
  const fail = (code, text) => (id === undefined ? null : { jsonrpc: '2.0', id, error: { code, message: text } });
52
60
  switch (method) {
53
61
  case 'initialize': {
54
62
  const asked = params?.protocolVersion;
55
- return reply({ protocolVersion: PROTOCOLS.includes(asked) ? asked : PROTOCOLS[0], capabilities: { tools: {} }, serverInfo: { name: 'xsolana', version: VERSION }, instructions: INSTRUCTIONS });
63
+ return reply({
64
+ protocolVersion: PROTOCOLS.includes(asked) ? asked : PROTOCOLS[0],
65
+ capabilities: { tools: {} },
66
+ serverInfo: { name: 'xsolana', version: VERSION },
67
+ instructions: INSTRUCTIONS,
68
+ });
56
69
  }
57
70
  case 'ping':
58
71
  return reply({});
59
72
  case 'tools/list':
60
- return reply({ tools: tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema, annotations: t.annotations })) });
73
+ return reply({
74
+ tools: tools.map(t => ({
75
+ name: t.name,
76
+ description: t.description,
77
+ inputSchema: t.inputSchema,
78
+ annotations: t.annotations,
79
+ })),
80
+ });
61
81
  case 'tools/call': {
62
82
  const result = await runTool(params?.name, params?.arguments ?? {}, { ...ctx, cwd: ctx.cwd ?? process.cwd() });
63
83
  // A tool that failed says so (isError), and keeps whatever it found (a save's refusals, say) as structuredContent.
64
84
  const { text, isError, ...data } = jsonable(result);
65
- return reply({ ...(isError ? { isError: true } : {}), content: [{ type: 'text', text }], structuredContent: { text, ...data } });
85
+ return reply({
86
+ ...(isError ? { isError: true } : {}),
87
+ content: [{ type: 'text', text }],
88
+ structuredContent: { text, ...data },
89
+ });
66
90
  }
67
91
  default:
68
92
  if (method?.startsWith('notifications/')) return null;
@@ -74,15 +98,19 @@ export async function handle(message, ctx = {}) {
74
98
  * Serves the tools over stdio until stdin closes. Once it closes, every request already read is answered before
75
99
  * `exit` runs, so a client that sends its last request and closes still gets the reply.
76
100
  */
77
- export async function serveStdio({ input = process.stdin, output = process.stdout, exit = () => process.exit(0) } = {}) {
101
+ export async function serveStdio({
102
+ input = process.stdin,
103
+ output = process.stdout,
104
+ exit = () => process.exit(0),
105
+ } = {}) {
78
106
  console.log = (...a) => console.error(...a);
79
107
  console.info = (...a) => console.error(...a);
80
108
  let buffer = '';
81
- const write = (m) => m && output.write(`${JSON.stringify(m)}\n`);
109
+ const write = m => m && output.write(`${JSON.stringify(m)}\n`);
82
110
  // The requests read and not yet answered.
83
111
  const pending = new Set();
84
112
  input.setEncoding('utf8');
85
- input.on('data', (chunk) => {
113
+ input.on('data', chunk => {
86
114
  buffer += chunk;
87
115
  let at;
88
116
  while ((at = buffer.indexOf('\n')) >= 0) {
@@ -98,7 +126,7 @@ export async function serveStdio({ input = process.stdin, output = process.stdou
98
126
  }
99
127
  for (const m of Array.isArray(message) ? message : [message]) {
100
128
  const done = handle(m)
101
- .then(write, (e) => write({ jsonrpc: '2.0', id: m?.id ?? null, error: { code: -32603, message: e.message } }))
129
+ .then(write, e => write({ jsonrpc: '2.0', id: m?.id ?? null, error: { code: -32603, message: e.message } }))
102
130
  .finally(() => pending.delete(done));
103
131
  pending.add(done);
104
132
  }
@@ -109,5 +137,5 @@ export async function serveStdio({ input = process.stdin, output = process.stdou
109
137
  // (the last reply is written out before the process goes)
110
138
  output.write('', () => exit());
111
139
  });
112
- console.error(`xsolana MCP server ${VERSION}: ${tools.length} tools (${tools.map((t) => t.name).join(', ')})`);
140
+ console.error(`xsolana MCP server ${VERSION}: ${tools.length} tools (${tools.map(t => t.name).join(', ')})`);
113
141
  }