@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 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.2 xsolana mcp`.
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.2 examples shop with loot boxes
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.2 token <its mint address>
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.2 check xsolana.json
98
- npx -y @xgamesfun/solana@0.28.2 simulate xsolana.json
99
- npx -y @xgamesfun/solana@0.28.2 cost xsolana.json
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.2 explain xsolana.json
103
- npx -y @xgamesfun/solana@0.28.2 review xsolana.json
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.2 keygen ~/.config/xsolana/my-game.json
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.2 approve xsolana.json --keypair ~/.config/xsolana/my-game.json
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.2 approve xsolana.json --keypair ~/.config/xsolana/my-game.json --yes <code>
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
- const r = await fetchImpl(path.startsWith('http') ? path : `${base}${path}`, init);
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)
@@ -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(`${server.replace(/\/$/, '')}/v1/results`, {
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({ server, game, round, wallet, fetchImpl = globalThis.fetch }) {
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
- `${server.replace(/\/$/, '')}/v1/vouchers?game=${q(game)}&round=${q(round)}&wallet=${q(wallet)}`,
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(`${server.replace(/\/$/, '')}${path}`, {
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) => answerOf(await fetchImpl(`${server.replace(/\/$/, '')}${path}`));
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.2",
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.2 <guide|examples|check|simulate|cost|explain|review|publish|status> [xsolana.json]`,
15
- `npx -y @xgamesfun/solana@0.28.2 token <mint>` for a coin made elsewhere,
16
- or `npx -y @xgamesfun/solana@0.28.2 tool <name> '<json>'` for any tool (always with the exact version).
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.2 keygen ~/.config/xsolana/<name>.json`. Give the person the address it prints. A
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.2 approve xsolana.json --keypair <key file>` prints every step with
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
@@ -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.