@xgamesfun/solana 0.0.0-stage → 0.20.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 (134) hide show
  1. package/DISCLAIMER.md +41 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +13 -0
  4. package/README.md +172 -2
  5. package/bin/xsolana.mjs +189 -0
  6. package/examples/artwork-auction.json +51 -0
  7. package/examples/blind-forge.json +128 -0
  8. package/examples/coin-flip-arena.json +108 -0
  9. package/examples/duel-arena.json +137 -0
  10. package/examples/elements-set.json +281 -0
  11. package/examples/event-badges.json +67 -0
  12. package/examples/expedition-guild.json +161 -0
  13. package/examples/fair-presale.json +48 -0
  14. package/examples/falling-drop.json +90 -0
  15. package/examples/guild-missions.json +144 -0
  16. package/examples/halving-seasons.json +112 -0
  17. package/examples/hero-trials.json +96 -0
  18. package/examples/ideas.json +789 -0
  19. package/examples/level-ladder.json +65 -0
  20. package/examples/merge-2048.json +128 -0
  21. package/examples/nft-raffle.json +33 -0
  22. package/examples/paper-hands-farm.json +56 -0
  23. package/examples/partner-vault.json +115 -0
  24. package/examples/sacrifice-raffle.json +118 -0
  25. package/examples/scorched-treasury.json +84 -0
  26. package/examples/shelter-fair.json +164 -0
  27. package/examples/spark-pity.json +153 -0
  28. package/examples/stakers-draw.json +125 -0
  29. package/examples/star-fleet.json +157 -0
  30. package/examples/starforce.json +116 -0
  31. package/examples/stud-farm.json +134 -0
  32. package/examples/team-vesting.json +70 -0
  33. package/examples/trade-in-shop.json +168 -0
  34. package/js/approve/details.mjs +262 -0
  35. package/js/approve/ed25519.mjs +36 -0
  36. package/js/approve/live-check.mjs +144 -0
  37. package/js/approve/page.html +86 -0
  38. package/js/approve/page.js +203 -0
  39. package/js/approve/server.mjs +711 -0
  40. package/js/chain/base58.js +73 -0
  41. package/js/chain/borsh.js +261 -0
  42. package/js/chain/bytes.js +141 -0
  43. package/js/chain/core.js +169 -0
  44. package/js/chain/curve.js +52 -0
  45. package/js/chain/lookup.js +273 -0
  46. package/js/chain/mint.js +96 -0
  47. package/js/chain/pda.js +66 -0
  48. package/js/chain/programs.js +102 -0
  49. package/js/chain/rpc.js +217 -0
  50. package/js/chain/sha256.js +68 -0
  51. package/js/chain/token.js +85 -0
  52. package/js/chain/token2022.js +247 -0
  53. package/js/chain/tx.js +224 -0
  54. package/js/client/accounts.js +347 -0
  55. package/js/client/compressed.js +46 -0
  56. package/js/client/grant.js +240 -0
  57. package/js/client/idl/loot.js +2 -0
  58. package/js/client/idl/loot.json +17064 -0
  59. package/js/client/index.js +9 -0
  60. package/js/client/ix.js +2156 -0
  61. package/js/client/pda.js +110 -0
  62. package/js/client/policy.js +48 -0
  63. package/js/client/sizes.js +54 -0
  64. package/js/client/traits.js +44 -0
  65. package/js/connect/connect.mjs +182 -0
  66. package/js/economy/check.mjs +1991 -0
  67. package/js/economy/cost.mjs +170 -0
  68. package/js/economy/examples.mjs +114 -0
  69. package/js/economy/explain.mjs +235 -0
  70. package/js/economy/guide.mjs +231 -0
  71. package/js/economy/legal.mjs +15 -0
  72. package/js/economy/live.mjs +107 -0
  73. package/js/economy/model.mjs +583 -0
  74. package/js/economy/plan.mjs +887 -0
  75. package/js/economy/schema.json +2923 -0
  76. package/js/economy/secrets.mjs +60 -0
  77. package/js/economy/simulate.mjs +952 -0
  78. package/js/economy/txsize.mjs +45 -0
  79. package/js/economy/validate.mjs +85 -0
  80. package/js/index.js +10 -0
  81. package/js/mcp/server.mjs +113 -0
  82. package/js/mcp/tools.mjs +479 -0
  83. package/js/play/index.js +2109 -0
  84. package/js/results/index.js +184 -0
  85. package/js/results/wire.js +90 -0
  86. package/package.json +100 -4
  87. package/release.json +8 -0
  88. package/skills/xsolana-economy/SKILL.md +105 -0
  89. package/skills/xsolana-economy/references/after-launch.md +114 -0
  90. package/skills/xsolana-economy/references/apps.md +157 -0
  91. package/skills/xsolana-economy/references/sentence-to-spec.md +172 -0
  92. package/skills/xsolana-economy/references/telling-the-person.md +93 -0
  93. package/types/chain/base58.d.ts +10 -0
  94. package/types/chain/borsh.d.ts +23 -0
  95. package/types/chain/bytes.d.ts +45 -0
  96. package/types/chain/core.d.ts +50 -0
  97. package/types/chain/curve.d.ts +1 -0
  98. package/types/chain/lookup.d.ts +191 -0
  99. package/types/chain/mint.d.ts +25 -0
  100. package/types/chain/pda.d.ts +25 -0
  101. package/types/chain/programs.d.ts +65 -0
  102. package/types/chain/rpc.d.ts +66 -0
  103. package/types/chain/sha256.d.ts +1 -0
  104. package/types/chain/token.d.ts +141 -0
  105. package/types/chain/token2022.d.ts +53 -0
  106. package/types/chain/tx.d.ts +60 -0
  107. package/types/client/accounts.d.ts +178 -0
  108. package/types/client/compressed.d.ts +29 -0
  109. package/types/client/grant.d.ts +125 -0
  110. package/types/client/idl/loot.d.ts +4467 -0
  111. package/types/client/index.d.ts +8 -0
  112. package/types/client/ix.d.ts +1687 -0
  113. package/types/client/pda.d.ts +91 -0
  114. package/types/client/policy.d.ts +23 -0
  115. package/types/client/sizes.d.ts +13 -0
  116. package/types/client/traits.d.ts +12 -0
  117. package/types/economy/check.d.mts +105 -0
  118. package/types/economy/cost.d.mts +68 -0
  119. package/types/economy/examples.d.mts +27 -0
  120. package/types/economy/explain.d.mts +19 -0
  121. package/types/economy/guide.d.mts +140 -0
  122. package/types/economy/legal.d.mts +12 -0
  123. package/types/economy/live.d.mts +109 -0
  124. package/types/economy/model.d.mts +363 -0
  125. package/types/economy/plan.d.mts +1013 -0
  126. package/types/economy/secrets.d.mts +4 -0
  127. package/types/economy/simulate.d.mts +156 -0
  128. package/types/economy/txsize.d.mts +17 -0
  129. package/types/economy/validate.d.mts +3 -0
  130. package/types/index.d.ts +9 -0
  131. package/types/mcp/tools.d.mts +627 -0
  132. package/types/play/index.d.ts +469 -0
  133. package/types/results/index.d.ts +155 -0
  134. package/types/results/wire.d.ts +122 -0
@@ -0,0 +1,45 @@
1
+ // How many bytes a Solana transaction takes, from what is in it, without building it. Pure.
2
+ //
3
+ // A legacy transaction is: the signatures (a count, then 64 bytes each), a 3-byte header, the account keys (a
4
+ // count, then 32 bytes each, every account once), a 32-byte blockhash, and the instructions (a count, then for
5
+ // each: its program's index, a count and one byte per account, a length and the data). Counts are compact-u16
6
+ // (1 byte below 128, 2 below 16,384).
7
+ //
8
+ // Why the estimate never comes out smaller than the real transaction:
9
+ // - accounts are counted once per distinct name, and two names never stand for one address less often than the
10
+ // real transaction would share them (two names for one address only makes the real one smaller);
11
+ // - instruction data is sized byte for byte from the program's IDL (idl.mjs), and other programs' data from their
12
+ // documented layouts at its longest;
13
+ // - no address lookup tables are assumed.
14
+ // A wallet may add its own compute-budget instructions (a price and a limit: about 52 bytes), so the plan keeps
15
+ // WALLET_ROOM free below the network's 1,232-byte limit.
16
+
17
+ export const MAX_TRANSACTION_BYTES = 1232;
18
+ export const WALLET_ROOM = 64;
19
+ export const BUDGET = MAX_TRANSACTION_BYTES - WALLET_ROOM;
20
+
21
+ export const compact = (n) => (n < 0x80 ? 1 : n < 0x4000 ? 2 : 3);
22
+
23
+ /**
24
+ * instructions: [{ program: name, accounts: [names], data: bytes }]; signers: [names] (the fee payer first).
25
+ * Returns the estimated wire size in bytes.
26
+ */
27
+ export function transactionSize({ instructions, signers }) {
28
+ const keys = new Set(signers);
29
+ for (const ix of instructions) {
30
+ for (const a of ix.accounts) keys.add(a);
31
+ keys.add(`program:${ix.program}`);
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;
35
+ }
36
+
37
+ /** How many accounts the transaction locks: each account once, its programs and signers included. */
38
+ export function transactionLocks({ instructions, signers }) {
39
+ const keys = new Set(signers);
40
+ for (const ix of instructions) {
41
+ for (const a of ix.accounts) keys.add(a);
42
+ keys.add(`program:${ix.program}`);
43
+ }
44
+ return keys.size;
45
+ }
@@ -0,0 +1,85 @@
1
+ // A small JSON Schema validator for the keywords schema.json uses, so the kit needs no schema library: type, const,
2
+ // enum, pattern, minimum, maximum, exclusiveMinimum, minLength, maxLength, minItems, maxItems, uniqueItems, items,
3
+ // required, properties, additionalProperties, propertyNames, anyOf, oneOf, not and local $ref. Pure.
4
+ import { readFileSync } from 'node:fs';
5
+
6
+ export const SCHEMA = JSON.parse(readFileSync(new URL('./schema.json', import.meta.url), 'utf8'));
7
+
8
+ const typeOf = (v) => (v === null ? 'null' : Array.isArray(v) ? 'array' : Number.isInteger(v) ? 'integer' : typeof v);
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);
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}`);
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));
15
+
16
+ function resolve(root, ref) {
17
+ if (!ref.startsWith('#/')) throw new Error(`Only local $ref is supported: ${ref}`);
18
+ return ref.slice(2).split('/').reduce((node, key) => node?.[key], root);
19
+ }
20
+
21
+ /** Every way `value` breaks `schema`, as { path, message }. An empty list means it is valid. */
22
+ export function validate(value, schema = SCHEMA, root = schema, path = 'economy') {
23
+ const errors = [];
24
+ const add = (message) => errors.push({ path, message });
25
+ if (schema.$ref) return validate(value, resolve(root, schema.$ref), root, path);
26
+ 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 ');
30
+ add(`${show(value)} is not allowed here (${wanted})`);
31
+ }
32
+ return errors;
33
+ }
34
+ // Exactly one of the shapes: none is not allowed, and two at once would be read two ways.
35
+ 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})`);
41
+ }
42
+ return errors;
43
+ }
44
+ if (schema.not && validate(value, schema.not, root, path).length === 0) add(`${show(value)} is not allowed here`);
45
+ if ('const' in schema && value !== schema.const) add(`must be ${show(schema.const)}`);
46
+ if (schema.enum && !schema.enum.includes(value)) add(`must be one of ${schema.enum.map(show).join(', ')}`);
47
+ if (schema.type && !isType(value, schema.type)) {
48
+ add(`must be ${schema.type === 'integer' ? 'a whole number' : article(schema.type)}`);
49
+ return errors;
50
+ }
51
+ if (typeof value === 'string') {
52
+ 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`);
55
+ }
56
+ if (typeof value === 'number') {
57
+ if (schema.minimum !== undefined && value < schema.minimum) add(`must be at least ${schema.minimum}`);
58
+ 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}`);
60
+ }
61
+ if (Array.isArray(value)) {
62
+ if (schema.minItems !== undefined && value.length < schema.minItems) add(`needs at least ${schema.minItems}`);
63
+ if (schema.maxItems !== undefined && value.length > schema.maxItems) add(`can have at most ${schema.maxItems}`);
64
+ if (schema.uniqueItems) {
65
+ const seen = new Map();
66
+ value.forEach((v, i) => {
67
+ 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` });
69
+ else seen.set(text, i);
70
+ });
71
+ }
72
+ if (schema.items) value.forEach((v, i) => errors.push(...validate(v, schema.items, root, `${path}[${i}]`)));
73
+ }
74
+ if (typeOf(value) === 'object') {
75
+ for (const key of schema.required ?? []) if (!(key in value)) add(`needs "${key}"`);
76
+ for (const [key, v] of Object.entries(value)) {
77
+ 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));
82
+ }
83
+ }
84
+ return errors;
85
+ }
package/js/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // @xgamesfun/solana: the economy tools (Node). The client is '@xgamesfun/solana/client' (browser-safe).
2
+ export { check, describe } from './economy/check.mjs';
3
+ export { simulate, explain } from './economy/simulate.mjs';
4
+ export { plan, setupSteps, playSteps } from './economy/plan.mjs';
5
+ export { cost, explainCost } from './economy/cost.mjs';
6
+ export { explainEconomy } from './economy/explain.mjs';
7
+ export { model } from './economy/model.mjs';
8
+ export { prepare, recordLanded, changedParts } from './economy/live.mjs';
9
+ export { guideText, catalog, CANNOT } from './economy/guide.mjs';
10
+ export { tools, PROGRAM_INFO, VERSION } from './mcp/tools.mjs';
@@ -0,0 +1,113 @@
1
+ // The xsolana MCP server (stdio): the tools in tools.mjs for any agent that speaks MCP (Claude Code, Codex, Cursor,
2
+ // opencode, Cline). It has no dependencies, so `npx -y --package=@xgamesfun/solana@<version> xsolana mcp` starts in the
3
+ // time an agent allows for it on a first install: MCP over stdio is newline-delimited JSON-RPC 2.0, and arguments are checked against each tool's
4
+ // input schema with the economy's own validator (js/economy/validate.mjs), here and on the terminal path
5
+ // (`xsolana tool`), with the same words. Answers go out as text and as structuredContent.
6
+ //
7
+ // stdout is the protocol: everything else goes to stderr.
8
+ import { validate } from '../economy/validate.mjs';
9
+ import { PROGRAM_INFO, tools, VERSION } from './tools.mjs';
10
+
11
+ export const INSTRUCTIONS = [
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).',
14
+ `Program ${PROGRAM_INFO.program} on ${PROGRAM_INFO.cluster} (loot ${VERSION}). Devnet only until audited.`,
15
+ ].join(' ');
16
+
17
+ /**
18
+ * The protocol versions this server speaks, newest first. A client that asks for one of them gets it; any other ask
19
+ * gets the newest, and the client decides whether it can go on (the spec's version negotiation).
20
+ */
21
+ export const PROTOCOLS = ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05'];
22
+
23
+ /** Every way `input` does not fit a tool's input schema, in words; [] when it fits. */
24
+ export function inputProblems(schema, input) {
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}`);
27
+ }
28
+
29
+ /** Runs one tool by name: the same checks and answer on the server and in the terminal. */
30
+ export async function runTool(name, input = {}, ctx = {}) {
31
+ const tool = tools.find((t) => t.name === name);
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(', ')}.` };
35
+ }
36
+ const bad = inputProblems(tool.inputSchema, input);
37
+ if (bad.length) return { isError: true, text: bad.join('; ') };
38
+ try {
39
+ return await tool.handler(input, ctx);
40
+ } catch (e) {
41
+ return { isError: true, text: e.message };
42
+ }
43
+ }
44
+
45
+ const jsonable = (value) => JSON.parse(JSON.stringify(value, (_, v) => (typeof v === 'bigint' ? v.toString() : v instanceof Uint8Array ? Buffer.from(v).toString('hex') : v)));
46
+
47
+ /** The answer to one JSON-RPC request, or null for a notification. */
48
+ export async function handle(message, ctx = {}) {
49
+ const { id, method, params } = message ?? {};
50
+ const reply = (result) => (id === undefined ? null : { jsonrpc: '2.0', id, result });
51
+ const fail = (code, text) => (id === undefined ? null : { jsonrpc: '2.0', id, error: { code, message: text } });
52
+ switch (method) {
53
+ case 'initialize': {
54
+ const asked = params?.protocolVersion;
55
+ return reply({ protocolVersion: PROTOCOLS.includes(asked) ? asked : PROTOCOLS[0], capabilities: { tools: {} }, serverInfo: { name: 'xsolana', version: VERSION }, instructions: INSTRUCTIONS });
56
+ }
57
+ case 'ping':
58
+ return reply({});
59
+ case 'tools/list':
60
+ return reply({ tools: tools.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema, annotations: t.annotations })) });
61
+ case 'tools/call': {
62
+ const result = await runTool(params?.name, params?.arguments ?? {}, { ...ctx, cwd: ctx.cwd ?? process.cwd() });
63
+ // A tool that failed says so (isError), and keeps whatever it found (a save's refusals, say) as structuredContent.
64
+ const { text, isError, ...data } = jsonable(result);
65
+ return reply({ ...(isError ? { isError: true } : {}), content: [{ type: 'text', text }], structuredContent: { text, ...data } });
66
+ }
67
+ default:
68
+ if (method?.startsWith('notifications/')) return null;
69
+ return fail(-32601, `No method ${method}.`);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Serves the tools over stdio until stdin closes. Once it closes, every request already read is answered before
75
+ * `exit` runs, so a client that sends its last request and closes still gets the reply.
76
+ */
77
+ export async function serveStdio({ input = process.stdin, output = process.stdout, exit = () => process.exit(0) } = {}) {
78
+ console.log = (...a) => console.error(...a);
79
+ console.info = (...a) => console.error(...a);
80
+ let buffer = '';
81
+ const write = (m) => m && output.write(`${JSON.stringify(m)}\n`);
82
+ // The requests read and not yet answered.
83
+ const pending = new Set();
84
+ input.setEncoding('utf8');
85
+ input.on('data', (chunk) => {
86
+ buffer += chunk;
87
+ let at;
88
+ while ((at = buffer.indexOf('\n')) >= 0) {
89
+ const line = buffer.slice(0, at).trim();
90
+ buffer = buffer.slice(at + 1);
91
+ if (!line) continue;
92
+ let message;
93
+ try {
94
+ message = JSON.parse(line);
95
+ } catch {
96
+ write({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Not JSON.' } });
97
+ continue;
98
+ }
99
+ for (const m of Array.isArray(message) ? message : [message]) {
100
+ const done = handle(m)
101
+ .then(write, (e) => write({ jsonrpc: '2.0', id: m?.id ?? null, error: { code: -32603, message: e.message } }))
102
+ .finally(() => pending.delete(done));
103
+ pending.add(done);
104
+ }
105
+ }
106
+ });
107
+ input.on('end', async () => {
108
+ while (pending.size) await Promise.allSettled([...pending]);
109
+ // (the last reply is written out before the process goes)
110
+ output.write('', () => exit());
111
+ });
112
+ console.error(`xsolana MCP server ${VERSION}: ${tools.length} tools (${tools.map((t) => t.name).join(', ')})`);
113
+ }