@xgamesfun/solana 0.20.1 → 0.25.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 +95 -52
- package/bin/xsolana.mjs +23 -8
- package/examples/blind-forge.json +2 -4
- package/examples/coin-flip-arena.json +4 -12
- package/examples/duel-arena.json +1 -4
- package/examples/elements-set.json +2 -9
- package/examples/expedition-guild.json +1 -8
- package/examples/fair-presale.json +1 -3
- package/examples/guild-missions.json +3 -10
- package/examples/halving-seasons.json +4 -9
- package/examples/hero-trials.json +15 -6
- package/examples/holder-den.json +38 -0
- package/examples/holders-drop.json +105 -0
- package/examples/ideas.json +788 -788
- package/examples/match-predictions.json +24 -0
- package/examples/merge-2048.json +5 -5
- package/examples/nft-raffle.json +1 -0
- package/examples/pack-league.json +108 -0
- package/examples/paper-hands-farm.json +4 -16
- package/examples/partner-vault.json +20 -7
- package/examples/sacrifice-raffle.json +2 -10
- package/examples/shelter-fair.json +1 -0
- package/examples/stakers-draw.json +4 -15
- package/examples/star-fleet.json +1 -8
- package/examples/starforce.json +6 -6
- package/examples/stride-sneakers.json +164 -0
- package/examples/trade-in-shop.json +2 -3
- package/js/approve/details.mjs +122 -52
- package/js/approve/ed25519.mjs +5 -1
- package/js/approve/live-check.mjs +83 -26
- package/js/approve/page.html +217 -78
- package/js/approve/page.js +43 -15
- package/js/approve/server.mjs +290 -73
- package/js/chain/base58.js +7 -1
- package/js/chain/borsh.js +24 -11
- package/js/chain/bytes.js +11 -8
- package/js/chain/core.js +35 -7
- package/js/chain/curve.js +1 -1
- package/js/chain/lookup.js +124 -36
- package/js/chain/mint.js +6 -2
- package/js/chain/pda.js +2 -2
- package/js/chain/programs.js +6 -6
- package/js/chain/rpc.js +44 -16
- package/js/chain/token.js +13 -2
- package/js/chain/token2022.js +55 -16
- package/js/chain/tx.js +32 -22
- package/js/client/accounts.js +653 -131
- package/js/client/compressed.js +4 -3
- package/js/client/grant.js +267 -83
- package/js/client/idl/loot.js +1 -1
- package/js/client/idl/loot.json +2406 -2176
- package/js/client/index.js +1 -0
- package/js/client/ix.js +3059 -664
- package/js/client/pda.js +39 -33
- package/js/client/policy.js +52 -24
- package/js/client/random.js +70 -0
- package/js/client/sizes.js +414 -16
- package/js/client/traits.js +14 -5
- package/js/connect/connect.mjs +59 -17
- package/js/economy/check.mjs +4153 -725
- package/js/economy/cost.mjs +403 -64
- package/js/economy/examples.mjs +176 -35
- package/js/economy/explain.mjs +466 -111
- package/js/economy/guide.mjs +296 -72
- package/js/economy/legal.mjs +12 -6
- package/js/economy/live.mjs +83 -19
- package/js/economy/model.mjs +462 -124
- package/js/economy/plan.mjs +1306 -219
- package/js/economy/schema.json +390 -484
- package/js/economy/secrets.mjs +17 -7
- package/js/economy/simulate.mjs +883 -223
- package/js/economy/txsize.mjs +15 -3
- package/js/economy/validate.mjs +61 -24
- package/js/mcp/server.mjs +42 -14
- package/js/mcp/tools.mjs +307 -88
- package/js/play/index.js +2864 -619
- package/js/play/keys.js +193 -0
- package/js/results/index.js +223 -30
- package/js/results/server.js +230 -0
- package/js/results/wire.js +64 -16
- package/package.json +6 -4
- package/release.json +5 -5
- package/skills/xsolana-economy/SKILL.md +39 -21
- package/skills/xsolana-economy/references/after-launch.md +58 -38
- package/skills/xsolana-economy/references/apps.md +97 -50
- package/skills/xsolana-economy/references/sentence-to-spec.md +148 -121
- package/skills/xsolana-economy/references/telling-the-person.md +61 -17
- package/types/chain/core.d.ts +4 -2
- package/types/chain/lookup.d.ts +5 -2
- package/types/chain/rpc.d.ts +3 -1
- package/types/client/accounts.d.ts +217 -56
- package/types/client/grant.d.ts +72 -27
- package/types/client/idl/loot.d.ts +602 -549
- package/types/client/index.d.ts +1 -0
- package/types/client/ix.d.ts +3884 -247
- package/types/client/pda.d.ts +22 -16
- package/types/client/policy.d.ts +39 -14
- package/types/client/random.d.ts +26 -0
- package/types/client/sizes.d.ts +93 -5
- package/types/economy/check.d.mts +5 -10
- package/types/economy/cost.d.mts +2 -1
- package/types/economy/guide.d.mts +5 -0
- package/types/economy/live.d.mts +16 -1
- package/types/economy/model.d.mts +151 -38
- package/types/economy/plan.d.mts +237 -194
- package/types/economy/simulate.d.mts +21 -13
- package/types/mcp/tools.d.mts +21 -13
- package/types/play/index.d.ts +453 -53
- package/types/play/keys.d.ts +51 -0
- package/types/results/index.d.ts +7 -6
- package/types/results/server.d.ts +126 -0
- package/types/results/wire.d.ts +1 -1
package/js/economy/txsize.mjs
CHANGED
|
@@ -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 =
|
|
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(
|
|
34
|
-
|
|
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. */
|
package/js/economy/validate.mjs
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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(
|
|
28
|
-
if (!results.some(
|
|
29
|
-
const wanted =
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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)
|
|
54
|
-
|
|
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)
|
|
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))
|
|
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))
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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/mcp/server.mjs
CHANGED
|
@@ -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(
|
|
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(
|
|
31
|
+
const tool = tools.find(t => t.name === name);
|
|
32
32
|
if (!tool) {
|
|
33
|
-
const near = tools.filter(
|
|
34
|
-
return {
|
|
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 =
|
|
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 =
|
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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 =
|
|
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',
|
|
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,
|
|
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(
|
|
140
|
+
console.error(`xsolana MCP server ${VERSION}: ${tools.length} tools (${tools.map(t => t.name).join(', ')})`);
|
|
113
141
|
}
|