@xgamesfun/solana 0.28.2 → 0.28.3
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 +11 -11
- package/js/play/index.js +6 -1
- package/js/results/index.js +29 -8
- package/package.json +1 -1
- package/skills/xsolana-economy/SKILL.md +5 -5
- package/types/results/index.d.ts +8 -1
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ codex plugin add xsolana@xgamesfun
|
|
|
77
77
|
|
|
78
78
|
**claude.ai, ChatGPT or Claude Desktop:** add `https://xgames.fun/mcp` as a custom connector. Nothing to install.
|
|
79
79
|
|
|
80
|
-
**Cursor, Cline, opencode or any MCP client:** run `npx -y --package=@xgamesfun/solana@0.28.
|
|
80
|
+
**Cursor, Cline, opencode or any MCP client:** run `npx -y --package=@xgamesfun/solana@0.28.3 xsolana mcp`.
|
|
81
81
|
|
|
82
82
|
**Any agent that reads skills** (Copilot, Gemini CLI, Windsurf…): `npx skills add XGamesFun/solana-kit`.
|
|
83
83
|
|
|
@@ -88,26 +88,26 @@ are in [docs/harnesses.md](docs/harnesses.md).
|
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
90
|
# find a worked example close to your idea
|
|
91
|
-
npx -y @xgamesfun/solana@0.28.
|
|
91
|
+
npx -y @xgamesfun/solana@0.28.3 examples shop with loot boxes
|
|
92
92
|
|
|
93
93
|
# using a coin you already launched? read it from the chain into its "tokens" entry
|
|
94
|
-
npx -y @xgamesfun/solana@0.28.
|
|
94
|
+
npx -y @xgamesfun/solana@0.28.3 token <its mint address>
|
|
95
95
|
|
|
96
96
|
# does it work? where does the money go? what does it cost?
|
|
97
|
-
npx -y @xgamesfun/solana@0.28.
|
|
98
|
-
npx -y @xgamesfun/solana@0.28.
|
|
99
|
-
npx -y @xgamesfun/solana@0.28.
|
|
97
|
+
npx -y @xgamesfun/solana@0.28.3 check xsolana.json
|
|
98
|
+
npx -y @xgamesfun/solana@0.28.3 simulate xsolana.json
|
|
99
|
+
npx -y @xgamesfun/solana@0.28.3 cost xsolana.json
|
|
100
100
|
|
|
101
101
|
# the whole economy in plain words, and what a cheater could get
|
|
102
|
-
npx -y @xgamesfun/solana@0.28.
|
|
103
|
-
npx -y @xgamesfun/solana@0.28.
|
|
102
|
+
npx -y @xgamesfun/solana@0.28.3 explain xsolana.json
|
|
103
|
+
npx -y @xgamesfun/solana@0.28.3 review xsolana.json
|
|
104
104
|
|
|
105
105
|
# go live: a key file for the project (it prints the address to send SOL to, never the key)…
|
|
106
|
-
npx -y @xgamesfun/solana@0.28.
|
|
106
|
+
npx -y @xgamesfun/solana@0.28.3 keygen ~/.config/xsolana/my-game.json
|
|
107
107
|
# …then everything going live would send, and a code; nothing is sent
|
|
108
|
-
npx -y @xgamesfun/solana@0.28.
|
|
108
|
+
npx -y @xgamesfun/solana@0.28.3 approve xsolana.json --keypair ~/.config/xsolana/my-game.json
|
|
109
109
|
# …and with that code, exactly what it listed is sent
|
|
110
|
-
npx -y @xgamesfun/solana@0.28.
|
|
110
|
+
npx -y @xgamesfun/solana@0.28.3 approve xsolana.json --keypair ~/.config/xsolana/my-game.json --yes <code>
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
Each command names an exact version, so you always run the release you meant to. `xsolana help` lists them all.
|
package/js/play/index.js
CHANGED
|
@@ -4922,7 +4922,12 @@ export function solanaApp({
|
|
|
4922
4922
|
only(others, ['server', 'token', 'fetchImpl'], 'claimWins');
|
|
4923
4923
|
const base = server.replace(/\/$/, '');
|
|
4924
4924
|
const ask = async (path, init) => {
|
|
4925
|
-
|
|
4925
|
+
// (one server answers for both clusters: a request for mainnet says so, one for devnet names none, as before)
|
|
4926
|
+
const at = path.startsWith('http') ? path : `${base}${path}`;
|
|
4927
|
+
const on = loot.cluster();
|
|
4928
|
+
const url =
|
|
4929
|
+
on === 'devnet' || /[?&]cluster=/.test(at) ? at : `${at}${at.includes('?') ? '&' : '?'}cluster=${on}`;
|
|
4930
|
+
const r = await fetchImpl(url, init);
|
|
4926
4931
|
// The status first: a proxy's error page is no JSON, and says nothing a player can act on but its status.
|
|
4927
4932
|
const body = await r.json().catch(() => null);
|
|
4928
4933
|
if (!r.ok)
|
package/js/results/index.js
CHANGED
|
@@ -135,7 +135,7 @@ export async function report({
|
|
|
135
135
|
}) {
|
|
136
136
|
const result = { game, round, endedAt, winners };
|
|
137
137
|
const all = [await signResult(signer, result, cluster)];
|
|
138
|
-
const response = await fetchImpl(
|
|
138
|
+
const response = await fetchImpl(serverUrl(server, '/v1/results', cluster), {
|
|
139
139
|
method: 'POST',
|
|
140
140
|
headers: { 'content-type': 'application/json' },
|
|
141
141
|
body: JSON.stringify({ ...result, cert, signatures: all }),
|
|
@@ -165,9 +165,16 @@ async function answerOf(response) {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
/** A wallet's vouchers for a round it won: [{ grant, publicKey, signature, round, expires, … }]. */
|
|
168
|
-
export async function fetchVouchers({
|
|
168
|
+
export async function fetchVouchers({
|
|
169
|
+
server,
|
|
170
|
+
game,
|
|
171
|
+
round,
|
|
172
|
+
wallet,
|
|
173
|
+
cluster = onCluster(),
|
|
174
|
+
fetchImpl = globalThis.fetch,
|
|
175
|
+
}) {
|
|
169
176
|
const response = await fetchImpl(
|
|
170
|
-
|
|
177
|
+
serverUrl(server, `/v1/vouchers?game=${q(game)}&round=${q(round)}&wallet=${q(wallet)}`, cluster),
|
|
171
178
|
);
|
|
172
179
|
return (await answerOf(response)).vouchers;
|
|
173
180
|
}
|
|
@@ -226,9 +233,19 @@ export async function signVoucher({
|
|
|
226
233
|
};
|
|
227
234
|
}
|
|
228
235
|
|
|
236
|
+
/**
|
|
237
|
+
* A path on a results server, for the cluster the request is on. One server answers for both clusters, and a request
|
|
238
|
+
* that names none is devnet's: so nothing is added on devnet (as every request was before), and `cluster=mainnet` on
|
|
239
|
+
* mainnet.
|
|
240
|
+
*/
|
|
241
|
+
export function serverUrl(server, path, cluster = onCluster()) {
|
|
242
|
+
const url = `${String(server).replace(/\/$/, '')}${path}`;
|
|
243
|
+
return cluster === 'devnet' ? url : `${url}${url.includes('?') ? '&' : '?'}cluster=${encodeURIComponent(cluster)}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
229
246
|
/** Posts JSON to the server and answers its body, or throws its { code, why }. */
|
|
230
|
-
async function post(server, path, body, fetchImpl) {
|
|
231
|
-
const response = await fetchImpl(
|
|
247
|
+
async function post(server, path, body, fetchImpl, cluster = onCluster()) {
|
|
248
|
+
const response = await fetchImpl(serverUrl(server, path, cluster), {
|
|
232
249
|
method: 'POST',
|
|
233
250
|
headers: { 'content-type': 'application/json' },
|
|
234
251
|
body: JSON.stringify(body),
|
|
@@ -237,7 +254,8 @@ async function post(server, path, body, fetchImpl) {
|
|
|
237
254
|
}
|
|
238
255
|
|
|
239
256
|
/** Gets JSON from the server and answers its body, or throws its { code, why }. */
|
|
240
|
-
const fetchJson = async (server, path, fetchImpl
|
|
257
|
+
const fetchJson = async (server, path, fetchImpl, cluster = onCluster()) =>
|
|
258
|
+
answerOf(await fetchImpl(serverUrl(server, path, cluster)));
|
|
241
259
|
|
|
242
260
|
/**
|
|
243
261
|
* A draw, from the organizer's side and the players'. `signMessage(text) → signature bytes` is the organizer's wallet
|
|
@@ -278,13 +296,14 @@ export const draws = {
|
|
|
278
296
|
cluster = onCluster(),
|
|
279
297
|
fetchImpl = globalThis.fetch,
|
|
280
298
|
}) => {
|
|
281
|
-
if (token) return post(server, `/v1/draws/${seg(game)}/${seg(draw)}/entries`, { token }, fetchImpl);
|
|
299
|
+
if (token) return post(server, `/v1/draws/${seg(game)}/${seg(draw)}/entries`, { token }, fetchImpl, cluster);
|
|
282
300
|
const text = entryText({ cluster, game, draw });
|
|
283
301
|
return post(
|
|
284
302
|
server,
|
|
285
303
|
`/v1/draws/${seg(game)}/${seg(draw)}/entries`,
|
|
286
304
|
{ wallet, text, signature: encodeBase58(await signMessage(new TextEncoder().encode(text))) },
|
|
287
305
|
fetchImpl,
|
|
306
|
+
cluster,
|
|
288
307
|
);
|
|
289
308
|
},
|
|
290
309
|
/** Locks the players the server holds now to `beacon`, a drand round still to come. Checks their hash itself. */
|
|
@@ -300,7 +319,7 @@ export const draws = {
|
|
|
300
319
|
net = 'quicknet',
|
|
301
320
|
fetchImpl = globalThis.fetch,
|
|
302
321
|
}) => {
|
|
303
|
-
const { draw: view } = await fetchJson(server, `/v1/draws/${seg(game)}/${seg(draw)}`, fetchImpl);
|
|
322
|
+
const { draw: view } = await fetchJson(server, `/v1/draws/${seg(game)}/${seg(draw)}`, fetchImpl, cluster);
|
|
304
323
|
const text = lockText({
|
|
305
324
|
cluster,
|
|
306
325
|
game,
|
|
@@ -320,6 +339,7 @@ export const draws = {
|
|
|
320
339
|
...(cert ? { cert } : {}),
|
|
321
340
|
},
|
|
322
341
|
fetchImpl,
|
|
342
|
+
cluster,
|
|
323
343
|
);
|
|
324
344
|
},
|
|
325
345
|
/** The draw as anyone may check it; settled, with its result, once its beacon round is out. */
|
|
@@ -358,6 +378,7 @@ export const giveaways = {
|
|
|
358
378
|
`/v1/giveaways/${seg(game)}/${seg(giveaway)}/result`,
|
|
359
379
|
{ endedAt, winners: winners.map(({ wallet, place }) => ({ wallet, place })), signature },
|
|
360
380
|
fetchImpl,
|
|
381
|
+
cluster,
|
|
361
382
|
);
|
|
362
383
|
},
|
|
363
384
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xgamesfun/solana",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.3",
|
|
4
4
|
"mcpName": "io.github.XGamesFun/solana-kit",
|
|
5
5
|
"description": "Loot boxes, gacha, shops, lotteries, auctions, NFT staking, crafting and rewards on Solana without writing a smart contract: one shared program, its JS client, a CLI, and an MCP server and skill so Claude Code, Codex or any AI agent can design, check, simulate and launch an economy.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -11,9 +11,9 @@ signed by a key file on the owner's own machine, once they have read what will b
|
|
|
11
11
|
**devnet**, where nothing is real money, and on **mainnet**, where everything is: an economy is on devnet unless its
|
|
12
12
|
file says `"network": "mainnet"`.
|
|
13
13
|
|
|
14
|
-
The tools are the `xsolana_*` MCP tools. Without MCP, the same from a terminal: `npx -y @xgamesfun/solana@0.28.
|
|
15
|
-
`npx -y @xgamesfun/solana@0.28.
|
|
16
|
-
or `npx -y @xgamesfun/solana@0.28.
|
|
14
|
+
The tools are the `xsolana_*` MCP tools. Without MCP, the same from a terminal: `npx -y @xgamesfun/solana@0.28.3 <guide|examples|check|simulate|cost|explain|review|publish|status> [xsolana.json]`,
|
|
15
|
+
`npx -y @xgamesfun/solana@0.28.3 token <mint>` for a coin made elsewhere,
|
|
16
|
+
or `npx -y @xgamesfun/solana@0.28.3 tool <name> '<json>'` for any tool (always with the exact version).
|
|
17
17
|
|
|
18
18
|
## Rules (always)
|
|
19
19
|
|
|
@@ -141,7 +141,7 @@ or `npx -y @xgamesfun/solana@0.28.2 tool <name> '<json>'` for any tool (always w
|
|
|
141
141
|
9. **When they say "put it live"**: `xsolana_publish` lists every step and its cost and gives the commands, which you
|
|
142
142
|
run in the project folder exactly as given (they name the exact version; never drop it):
|
|
143
143
|
- **A new economy gets a key file**, unless the project has one:
|
|
144
|
-
`npx -y @xgamesfun/solana@0.28.
|
|
144
|
+
`npx -y @xgamesfun/solana@0.28.3 keygen ~/.config/xsolana/<name>.json`. Give the person the address it prints. A
|
|
145
145
|
Solana key file they already have and name themselves (`~/.config/solana/id.json`) works the same; never go
|
|
146
146
|
looking for one.
|
|
147
147
|
- **Say what the key file is** before anything is sent: the project's owner, and, unless the spec says otherwise,
|
|
@@ -156,7 +156,7 @@ or `npx -y @xgamesfun/solana@0.28.2 tool <name> '<json>'` for any tool (always w
|
|
|
156
156
|
devnet SOL for an economy on devnet, real SOL for one on mainnet), any seed in a coin that already exists, and every NFT a stash, a raffle or an auction names. A
|
|
157
157
|
collection they already have needs the key's address as its update authority. If they would rather not move
|
|
158
158
|
those, offer the browser wallet.
|
|
159
|
-
- **Show it**: `npx -y @xgamesfun/solana@0.28.
|
|
159
|
+
- **Show it**: `npx -y @xgamesfun/solana@0.28.3 approve xsolana.json --keypair <key file>` prints every step with
|
|
160
160
|
what it pays and trusts, the warnings, the notice and a code, and sends nothing. Give the person that printout as
|
|
161
161
|
it is, not a summary of it, and ask.
|
|
162
162
|
- **On their yes**, the same command with `--yes <code>`. What lands is written back into `xsolana.json`. If it
|
package/types/results/index.d.ts
CHANGED
|
@@ -40,7 +40,8 @@ export declare function report({ server, cert, signer, game, round, endedAt, win
|
|
|
40
40
|
winners: any;
|
|
41
41
|
}): Promise<any>;
|
|
42
42
|
/** A wallet's vouchers for a round it won: [{ grant, publicKey, signature, round, expires, … }]. */
|
|
43
|
-
export declare function fetchVouchers({ server, game, round, wallet, fetchImpl }: {
|
|
43
|
+
export declare function fetchVouchers({ server, game, round, wallet, cluster, fetchImpl, }: {
|
|
44
|
+
cluster?: "devnet" | "mainnet" | undefined;
|
|
44
45
|
fetchImpl?: typeof fetch | undefined;
|
|
45
46
|
game: any;
|
|
46
47
|
round: any;
|
|
@@ -80,6 +81,12 @@ export declare function signVoucher({ signer, game, grant, incarnation, wallet,
|
|
|
80
81
|
personHash: string;
|
|
81
82
|
amount: string;
|
|
82
83
|
}>;
|
|
84
|
+
/**
|
|
85
|
+
* A path on a results server, for the cluster the request is on. One server answers for both clusters, and a request
|
|
86
|
+
* that names none is devnet's: so nothing is added on devnet (as every request was before), and `cluster=mainnet` on
|
|
87
|
+
* mainnet.
|
|
88
|
+
*/
|
|
89
|
+
export declare function serverUrl(server: any, path: any, cluster?: "devnet" | "mainnet"): string;
|
|
83
90
|
/**
|
|
84
91
|
* A draw, from the organizer's side and the players'. `signMessage(text) → signature bytes` is the organizer's wallet
|
|
85
92
|
* (its signMessage) or a host key's sign; `by` its address; `cert` the owner's host certificate when `by` is a host key.
|