@xgamesfun/solana 0.25.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 (38) hide show
  1. package/DISCLAIMER.md +1 -1
  2. package/NOTICE +4 -4
  3. package/README.md +33 -28
  4. package/bin/xsolana.mjs +9 -3
  5. package/examples/stakers-draw.json +1 -0
  6. package/js/client/random.js +1 -1
  7. package/js/economy/check.mjs +11 -0
  8. package/js/economy/cost.mjs +1 -1
  9. package/js/economy/explain.mjs +10 -3
  10. package/js/economy/guide.mjs +1 -1
  11. package/js/economy/live.mjs +2 -1
  12. package/js/economy/review.mjs +760 -0
  13. package/js/economy/schema.json +8 -4
  14. package/js/economy/trust.mjs +15 -0
  15. package/js/index.js +1 -0
  16. package/js/mcp/server.mjs +1 -1
  17. package/js/mcp/tools.mjs +26 -1
  18. package/js/results/index.js +2 -2
  19. package/package.json +1 -1
  20. package/release.json +1 -1
  21. package/skills/xsolana-economy/SKILL.md +29 -13
  22. package/skills/xsolana-economy/references/after-launch.md +2 -1
  23. package/skills/xsolana-economy/references/sentence-to-spec.md +1 -0
  24. package/skills/xsolana-economy/references/telling-the-person.md +6 -0
  25. package/skills/xsolana-economy/references/trust-and-cheating.md +126 -0
  26. package/types/economy/cost.d.mts +1 -1
  27. package/types/economy/explain.d.mts +3 -0
  28. package/types/economy/review.d.mts +45 -0
  29. package/types/economy/trust.d.mts +8 -0
  30. package/types/index.d.ts +1 -0
  31. package/types/mcp/tools.d.mts +62 -0
  32. package/types/play/index.d.ts +6 -6
  33. package/types/results/index.d.ts +1 -1
  34. package/examples/holder-den.json +0 -38
  35. package/examples/holders-drop.json +0 -105
  36. package/examples/match-predictions.json +0 -24
  37. package/examples/pack-league.json +0 -108
  38. package/examples/stride-sneakers.json +0 -164
package/DISCLAIMER.md CHANGED
@@ -35,7 +35,7 @@ Read this before you use this package, its tools or the loot program. Using them
35
35
 
36
36
  ## Limitation of liability
37
37
 
38
- To the fullest extent the law allows, games.fun, XGamesFun and the contributors to this package are not liable for any
38
+ To the fullest extent the law allows, xgames.fun, XGamesFun and the contributors to this package are not liable for any
39
39
  loss or damage of any kind, direct or indirect, including lost funds, tokens, NFTs, profits or data, arising from the
40
40
  use of, or inability to use, this package, the loot program, the results server or anything built with them, even if
41
41
  told such loss was possible.
package/NOTICE CHANGED
@@ -1,10 +1,10 @@
1
1
  loot
2
- Copyright 2026 games.fun
2
+ Copyright 2026 xgames.fun
3
3
 
4
- This product includes software developed at games.fun.
4
+ This product includes software developed at xgames.fun.
5
5
 
6
- Parts of the JavaScript client and tools (js/) are adapted from the games.fun kit,
7
- MIT License, Copyright (c) 2026 games.fun.
6
+ Parts of the JavaScript client and tools (js/) are adapted from the xgames.fun kit,
7
+ MIT License, Copyright (c) 2026 xgames.fun.
8
8
 
9
9
  The tests run upstream Solana programs (SPL Token, Token-2022, Associated Token, Memo,
10
10
  Metaplex Core, Token Metadata, Bubblegum, Account Compression, Noop, MagicBlock VRF) as
package/README.md CHANGED
@@ -6,7 +6,7 @@ package `@xgamesfun/solana`:
6
6
  - a JavaScript client with no Solana SDK (`@xgamesfun/solana/client`), a player module for apps (`/play`) and a results
7
7
  client (`/results`);
8
8
  - the economy spec and its checker, quick simulation, cost and plain-words explainer;
9
- - the `xsolana` CLI and an MCP server with ten `xsolana_*` tools;
9
+ - the `xsolana` CLI and an MCP server with eleven `xsolana_*` tools;
10
10
  - the `xsolana-economy` skill, and one plugin that carries the tools and the skill to Claude Code and Codex;
11
11
  - a local approval page where a project's owner approves going live in their own wallet.
12
12
 
@@ -35,7 +35,10 @@ An economy is a JSON spec, not a new smart contract. Every project shares the on
35
35
  - **Players also trust the token and the collection.** A mint's issuer (a freeze authority, a pause, a transfer hook, a
36
36
  permanent delegate) or a collection's controller can stop or move what loot holds. USDC has a freeze authority.
37
37
  - **The chain can't see a game.** Results a game reports (a win, a score, a clear) are vouched for by a key the project
38
- trusts, a results server or deciders. Checking that a player really did it is the project's job, on its own server.
38
+ trusts, a results server or deciders. Checking that a player really did it is the project's job, on its own server:
39
+ a server that decides the result, replays it, or only bounds what the page reports leaves a cheater very different
40
+ amounts. A grant says which in `trust`, and `xsolana review` says what a cheater gets on every route
41
+ ([trust and cheating](skills/xsolana-economy/references/trust-and-cheating.md)).
39
42
  - **The hosted results server is optional.** `https://play.xgames.fun/xsolana` (`@xgamesfun/solana/results`'s
40
43
  default) signs vouchers for games without a server of their own. It can only pay what a grant allows, and only for a
41
44
  grant whose owner named its key in `vouchers`. It sends nothing on chain, and it is offered as is: it can be down, and
@@ -102,13 +105,13 @@ codex plugin add xsolana@xgamesfun
102
105
  ```
103
106
 
104
107
  **Any agent with MCP** (Cursor, opencode, Cline…): run the server with
105
- `npx -y --package=@xgamesfun/solana@0.25.0 xsolana mcp`. Or let `npx -y @xgamesfun/solana@0.25.0 connect` register it, with
108
+ `npx -y --package=@xgamesfun/solana@0.25.1 xsolana mcp`. Or let `npx -y @xgamesfun/solana@0.25.1 connect` register it, with
106
109
  the skill, for Claude Code and Codex.
107
110
 
108
111
  Then ask for an economy, for example _"a fishing game where each cast costs 0.02 SOL, with a rare golden fish and a
109
112
  monthly draw"_.
110
113
 
111
- Going live is always yours: `npx -y @xgamesfun/solana@0.25.0 approve` opens a page on your computer where you connect a
114
+ Going live is always yours: `npx -y @xgamesfun/solana@0.25.1 approve` opens a page on your computer where you connect a
112
115
  devnet wallet and approve each transaction. The agent never signs and never asks for a key.
113
116
 
114
117
  ## Use it from a terminal
@@ -116,12 +119,13 @@ devnet wallet and approve each transaction. The agent never signs and never asks
116
119
  Every tool runs from the CLI too, on a spec file (`xsolana.json` by default):
117
120
 
118
121
  ```bash
119
- npx -y @xgamesfun/solana@0.25.0 examples shop with loot boxes # the nearest worked examples
120
- npx -y @xgamesfun/solana@0.25.0 check xsolana.json # refusals and warnings
121
- npx -y @xgamesfun/solana@0.25.0 simulate xsolana.json # where the money goes, day by day
122
- npx -y @xgamesfun/solana@0.25.0 cost xsolana.json # what going live and each action cost
123
- npx -y @xgamesfun/solana@0.25.0 explain xsolana.json # the economy in plain words
124
- npx -y @xgamesfun/solana@0.25.0 approve xsolana.json # go live, approved in your own wallet
122
+ npx -y @xgamesfun/solana@0.25.1 examples shop with loot boxes # the nearest worked examples
123
+ npx -y @xgamesfun/solana@0.25.1 check xsolana.json # refusals and warnings
124
+ npx -y @xgamesfun/solana@0.25.1 simulate xsolana.json # where the money goes, day by day
125
+ npx -y @xgamesfun/solana@0.25.1 cost xsolana.json # what going live and each action cost
126
+ npx -y @xgamesfun/solana@0.25.1 explain xsolana.json # the economy in plain words
127
+ npx -y @xgamesfun/solana@0.25.1 review xsolana.json # what a cheater gets on each route (exits 1 on a high finding)
128
+ npx -y @xgamesfun/solana@0.25.1 approve xsolana.json # go live, approved in your own wallet
125
129
  ```
126
130
 
127
131
  `xsolana help` lists every command. Every command names the exact version, so `npx` runs that release and never whatever
@@ -138,30 +142,31 @@ import * as loot from '@xgamesfun/solana/client';
138
142
  const ix = loot.openBoxIx({ player, project, box, nonce }); // one builder per instruction, no Solana SDK
139
143
  ```
140
144
 
141
- | Import | What |
142
- | -------------------------------- | ------------------------------------------------------------------------------------ |
143
- | `@xgamesfun/solana` | the economy tools: `check`, `simulate`, `plan`, `cost`, `explainEconomy`, `model` |
144
- | `@xgamesfun/solana/client` | every PDA, one builder per instruction, account and receipt decoders, grant vouchers |
145
- | `@xgamesfun/solana/play` | `solanaApp`: what a player does with a live economy, from a browser app |
146
- | `@xgamesfun/solana/results` | results and vouchers a game's server signs |
147
- | `@xgamesfun/solana/chain/*` | browser-safe Solana primitives: base58, Borsh, transactions, PDAs, tokens, Core |
148
- | `@xgamesfun/solana/idl.json` | the program's IDL, the authority for account and argument layouts |
149
- | `@xgamesfun/solana/release.json` | the program build, IDL and client this release is pinned to |
145
+ | Import | What |
146
+ | -------------------------------- | ------------------------------------------------------------------------------------------- |
147
+ | `@xgamesfun/solana` | the economy tools: `check`, `simulate`, `plan`, `cost`, `explainEconomy`, `review`, `model` |
148
+ | `@xgamesfun/solana/client` | every PDA, one builder per instruction, account and receipt decoders, grant vouchers |
149
+ | `@xgamesfun/solana/play` | `solanaApp`: what a player does with a live economy, from a browser app |
150
+ | `@xgamesfun/solana/results` | results and vouchers a game's server signs |
151
+ | `@xgamesfun/solana/chain/*` | browser-safe Solana primitives: base58, Borsh, transactions, PDAs, tokens, Core |
152
+ | `@xgamesfun/solana/idl.json` | the program's IDL, the authority for account and argument layouts |
153
+ | `@xgamesfun/solana/release.json` | the program build, IDL and client this release is pinned to |
150
154
 
151
155
  The client, chain and play modules run in browsers and Node 22 or later, with no bundler required. The package
152
156
  ships TypeScript declarations for every entry point.
153
157
 
154
158
  ## Documentation
155
159
 
156
- | | |
157
- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------- |
158
- | [`docs/program.md`](docs/program.md) | the program: who signs, accounts, every instruction, fees, limits, receipts, costs |
159
- | [`docs/nfts.md`](docs/nfts.md) | NFTs and tokens, with and without the project's authority |
160
- | [`docs/harnesses.md`](docs/harnesses.md) | how the plugin reaches Claude Code and Codex, and the release checklist |
161
- | [`skills/xsolana-economy`](skills/xsolana-economy/SKILL.md) | the skill an agent follows |
162
- | [`examples/`](examples) | whole economies that check and go live in the tests; `ideas.json`, 112 real economies rated |
163
- | [`evals/`](evals/README.md) | fresh-session evals of agents building on loot |
164
- | [`CHANGELOG.md`](CHANGELOG.md), [`SECURITY.md`](SECURITY.md) | versions, the trust model, reporting a vulnerability |
160
+ | | |
161
+ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
162
+ | [`docs/program.md`](docs/program.md) | the program: who signs, accounts, every instruction, fees, limits, receipts, costs |
163
+ | [`docs/nfts.md`](docs/nfts.md) | NFTs and tokens, with and without the project's authority |
164
+ | [`docs/harnesses.md`](docs/harnesses.md) | how the plugin reaches Claude Code and Codex, and the release checklist |
165
+ | [`skills/xsolana-economy`](skills/xsolana-economy/SKILL.md) | the skill an agent follows |
166
+ | [`trust-and-cheating.md`](skills/xsolana-economy/references/trust-and-cheating.md) | what a server decides, replays or bounds; who gets paid; an exploit checklist |
167
+ | [`examples/`](examples) | whole economies that check and go live in the tests; `ideas.json`, 112 real economies rated |
168
+ | [`evals/`](evals/README.md) | fresh-session evals of agents building on loot |
169
+ | [`CHANGELOG.md`](CHANGELOG.md), [`SECURITY.md`](SECURITY.md) | versions, the trust model, reporting a vulnerability |
165
170
 
166
171
  ## Layout
167
172
 
package/bin/xsolana.mjs CHANGED
@@ -5,7 +5,7 @@
5
5
  // xsolana mcp serve the tools over stdio (what a plugin's MCP config runs)
6
6
  // xsolana guide [part] [--json] what an economy can be made of, and what loot cannot do
7
7
  // xsolana examples [words…] [--json] the nearest worked examples and rated economies
8
- // xsolana check|simulate|cost|explain|publish|status [file] [--json] one tool on a spec file (default xsolana.json)
8
+ // xsolana check|simulate|cost|explain|review|publish|status [file] [--json] one tool on a spec file (default xsolana.json)
9
9
  // xsolana approve [file] [--port n] [--no-open] open the local page where the owner approves going live in their wallet
10
10
  // xsolana connect [--agent claude|codex] [--scope user|project] [--local] [--remove] [--dry-run] register the MCP server and the skill
11
11
  // xsolana tool --list | xsolana tool <name> '<json>' [--args-file f] [--json] any tool by name
@@ -25,6 +25,7 @@ const USAGE = `xsolana: the tools for loot, one shared Solana program for game a
25
25
  xsolana simulate [file] the quick simulation
26
26
  xsolana cost [file] what going live and each action cost
27
27
  xsolana explain [file] the economy in plain words
28
+ xsolana review [file] what a cheater gets on each route, and what to fix before going live
28
29
  xsolana publish [file] what going live does, and how to approve it
29
30
  xsolana approve [file] approve going live in your own wallet (a local page; --port n, --no-open)
30
31
  xsolana status [file] a live economy on chain
@@ -33,7 +34,8 @@ const USAGE = `xsolana: the tools for loot, one shared Solana program for game a
33
34
  xsolana tool --list every tool; xsolana tool <name> '<json>' runs one (--args-file f)
34
35
  xsolana version this package's xsolana version and program
35
36
 
36
- A tool's command (guide, examples, check … status, tool) takes --json for its whole answer as JSON.`;
37
+ A tool's command (guide, examples, check … status, tool) takes --json for its whole answer as JSON.
38
+ review exits 1 when it finds something high.`;
37
39
 
38
40
  /** A failure the person caused (a bad flag, a missing file): said in one line, without a stack. */
39
41
  class UsageError extends Error {}
@@ -50,6 +52,7 @@ const FLAGS = {
50
52
  simulate: TOOL_FLAGS,
51
53
  cost: TOOL_FLAGS,
52
54
  explain: TOOL_FLAGS,
55
+ review: TOOL_FLAGS,
53
56
  publish: TOOL_FLAGS,
54
57
  status: TOOL_FLAGS,
55
58
  approve: ['--port', '--no-open'],
@@ -105,8 +108,10 @@ async function main(argv) {
105
108
  const { runTool } = await import('../js/mcp/server.mjs');
106
109
  const r = await runTool(name, input, { cwd: process.cwd() });
107
110
  const failed = Boolean(r.isError || r.ok === false);
111
+ // A review is a report whatever it finds: printed on stdout, its exit code saying whether a finding is high.
112
+ const report = name === 'xsolana_review' && !r.isError && !r.refusals;
108
113
  if (flags.has('--json')) console.log(JSON.stringify(r, (_, v) => (typeof v === 'bigint' ? v.toString() : v), 2));
109
- else (failed ? console.error : console.log)(r.text ?? JSON.stringify(r, null, 2));
114
+ else (failed && !report ? console.error : console.log)(r.text ?? JSON.stringify(r, null, 2));
110
115
  if (failed) process.exitCode = 1;
111
116
  }
112
117
 
@@ -128,6 +133,7 @@ async function main(argv) {
128
133
  case 'simulate':
129
134
  case 'cost':
130
135
  case 'explain':
136
+ case 'review':
131
137
  case 'publish':
132
138
  case 'status':
133
139
  await tool(`xsolana_${command}`, fileInput());
@@ -84,6 +84,7 @@
84
84
  "grants": [
85
85
  {
86
86
  "key": "race",
87
+ "trust": "decides",
87
88
  "name": "Weekly race rewards",
88
89
  "prize": {
89
90
  "gives": "tokens",
@@ -1,7 +1,7 @@
1
1
  // The loot program's rolling module (`src/random`), mirrored: every random choice it makes is drawn from a stream of
2
2
  // the oracle's answer, by a path that names what is drawn, with one unbiased sampler. A page shows from an order's
3
3
  // answer what each draw gave (and a fresh unit's range traits) exactly as the program drew them. Held to the
4
- // program's own vectors (`js/test/fixtures/roll-vectors.json`, from gamesfun-loot's `tests/fixtures`).
4
+ // program's own vectors (`js/test/fixtures/roll-vectors.json`, from the program's own tests).
5
5
  import { concat, uintLE } from '../chain/bytes.js';
6
6
  import { sha256 } from '../chain/sha256.js';
7
7
 
@@ -4909,6 +4909,16 @@ export function check(economy, { network = 'devnet', now = Math.floor(Date.now()
4909
4909
  ? 'Make each claim cost a sybil something: a gate on holding a collectible the game sells or a locked stake, or vouchers from your server that count people ("perPerson", with an outside sign-in such as X). Keep "perDay" and "total" small either way.'
4910
4910
  : 'Give it a gate (a collectible each claimer must hold), or keep "total" small.',
4911
4911
  );
4912
+ // `trust` says what the server holding a voucher key checks: a grant with no voucher key or code has no such server.
4913
+ if (g.trust && !vouchers.length && !keys.length)
4914
+ refuse(
4915
+ 'E-GRANT-TRUST',
4916
+ `${where}.trust`,
4917
+ `${label(g)} says its server ${g.trust} the result ("trust"), but it is paid ${open ? 'to any wallet that asks (it is open)' : 'to the wallets on its list'}: no server signs for it, so nothing checks what a claimer did.`,
4918
+ open
4919
+ ? 'Remove "trust", or name the server’s public key in "vouchers" so its claims are signed.'
4920
+ : 'Remove "trust": the list is the owner’s own choice, written in the spec.',
4921
+ );
4912
4922
  for (const [i, v] of vouchers.entries())
4913
4923
  if (!isAddress(v))
4914
4924
  refuse(
@@ -5175,6 +5185,7 @@ export function check(economy, { network = 'devnet', now = Math.floor(Date.now()
5175
5185
  'W-VOUCHERS',
5176
5186
  'economy.grants',
5177
5187
  `A voucher key signs who earned a grant: whatever holds it (your server, a play host) decides who gets rewards, within the limits the program keeps. The program does not check that a player really did what the voucher says. The keys: ${voucherKeys.join(', ')}.`,
5188
+ 'Say on each such grant what its server checks before it signs ("trust": "decides", "replays" or "bounds"), run xsolana_review, and tell the person what a cheater gets.',
5178
5189
  );
5179
5190
 
5180
5191
  if (randomItems && !chance)
@@ -108,7 +108,7 @@ export function setupCost(plan, economy) {
108
108
  }
109
109
 
110
110
  /**
111
- * What each action costs whoever signs it while the economy runs: games.fun's fee, the network's, and for anything
111
+ * What each action costs whoever signs it while the economy runs: xgames.fun's fee, the network's, and for anything
112
112
  * that asks MagicBlock's oracle for a random number (a box's open, a lottery's draw, the authority's `roll`) the
113
113
  * oracle's own fee (`oracleSol`, model.mjs ORACLE_FEE).
114
114
  */
@@ -26,6 +26,7 @@ import {
26
26
  whenOutWords,
27
27
  } from './model.mjs';
28
28
  import { farmEnds, playSteps } from './plan.mjs';
29
+ import { TRUST_WORDS } from './trust.mjs';
29
30
  import { inAll, poolFate, round, simulate, unnamedExceptions, unnamedWords } from './simulate.mjs';
30
31
 
31
32
  const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
@@ -75,7 +76,7 @@ const paysWords = (l, treasury) =>
75
76
  ? `its places are paid from its pot (an account of its own, which only its winners are paid from): each place takes its percent of what the pot holds when the draw is asked, with the round’s ticket money, so whatever reaches the pot counts (the seed, a sponsor, shares of other sales, yield) and so does what earlier rounds’ places left; what this round’s places leave stays in the pot, and the next round always starts with it`
76
77
  : `its places are paid from each round’s own ticket money alone, which nothing sent from outside can change (no seed, no gift, no carry-over); what the places leave, and the part of places nobody won, goes ${toWords(destination(l.rest), treasury)}${l.rest === 'burn' ? ' when the round closes' : ''}`;
77
78
 
78
- const ruleWords = r => {
79
+ export const ruleWords = r => {
79
80
  if (!r) return '';
80
81
  if (r.wallets) return `they are one of the ${r.wallets.length} wallets it names`;
81
82
  if (r.items) return `any of ${r.items.map(k => `“${k}”`).join(', ')}`;
@@ -99,7 +100,7 @@ const stakeWords = (staked, m) => {
99
100
  );
100
101
  };
101
102
  /** A gate in a sentence: "Only players who show …", "Only the wallets it names …", "Only wallets with … staked …". */
102
- const gateWords = (gate, verb, m) =>
103
+ export const gateWords = (gate, verb, m) =>
103
104
  !gate
104
105
  ? ''
105
106
  : gate.wallets
@@ -464,8 +465,14 @@ export function explainEconomy(economy, { now = Math.floor(Date.now() / 1000) }
464
465
  const who = signers.length ? signers.join(' or ') : 'that asks (it is open to any wallet)';
465
466
  // A grant of a fixed amount of tokens, paid by voucher or list: each claim may name its own amount, up to the grant's.
466
467
  const named = signers.length > 0 && src.prize?.gives === 'tokens';
468
+ // What the voucher's server checks before it signs (`trust`): the spec's own word, never sent on chain.
469
+ const trust = src.trust
470
+ ? ` What its server checks: ${TRUST_WORDS[src.trust]}.`
471
+ : (src.vouchers ?? []).length
472
+ ? ' What its server checks before it signs is not said ("trust"): xsolana_review reads it as the weakest, a server that only bounds what the page reports.'
473
+ : '';
467
474
  actions.push(
468
- `Claim “${g.name}” (only the winning wallet claims, signing it; anyone may pay its fees): gives ${g.gives}, to a wallet ${who}${limits ? `; limits ${limits}` : '; no limits'}${src.gate ? (src.gate.wallets ? `; only the ${src.gate.wallets.length} wallets it names` : src.gate.list || src.gate.staked ? `; ${gateWords(src.gate, 'claim it', m).replace(/^Only/, 'only')}` : `; the wallet must hold ${ruleWords(src.gate)}${!signers.length ? ', and each one of those claims once too' : ''}`) : ''}${named ? `; a voucher or the list can name each claim’s own amount, up to that (a reward by rank or share)` : ''}. Each claim costs the grant’s funds about ${num(g.fundsPerClaimSol)} SOL of rent, and whoever sends it the ${fee} SOL player fee.`,
475
+ `Claim “${g.name}” (only the winning wallet claims, signing it; anyone may pay its fees): gives ${g.gives}, to a wallet ${who}${limits ? `; limits ${limits}` : '; no limits'}${src.gate ? (src.gate.wallets ? `; only the ${src.gate.wallets.length} wallets it names` : src.gate.list || src.gate.staked ? `; ${gateWords(src.gate, 'claim it', m).replace(/^Only/, 'only')}` : `; the wallet must hold ${ruleWords(src.gate)}${!signers.length ? ', and each one of those claims once too' : ''}`) : ''}${named ? `; a voucher or the list can name each claim’s own amount, up to that (a reward by rank or share)` : ''}. Each claim costs the grant’s funds about ${num(g.fundsPerClaimSol)} SOL of rent, and whoever sends it the ${fee} SOL player fee.${trust}`,
469
476
  );
470
477
  }
471
478
  for (const st of sim.stashes ?? []) {
@@ -139,7 +139,7 @@ export const CONVENTIONS = [
139
139
  export const SAFETY = [
140
140
  'A wallet is not a person: wallets are free, so a limit per wallet never limits a person. What bounds a reward is its totals ("perDay", "total": the most it can ever pay), and what makes farming cost something: a gate on holding a collectible the game sells or a locked stake, or vouchers from your server that count people ("perPerson": the voucher signer names a person hash, which needs an outside sign-in such as X; a wallet-only player has none).',
141
141
  'Open grants (no voucher, keys or list) pay any wallet: keep their prizes small or soulbound, gate them, and set "perDay". `xsolana_simulate` says what a crowd of wallets could take next to one.',
142
- 'The page can’t be trusted: a game runs in the player’s browser, so a result worth paying is decided or checked by your server, which signs the voucher. A server checks what a wallet holds with `collectiblesOf` / `holds`, pays only wallets a player proved (`proofRequest` / `verifyProof`), and makes promo codes with `makeCodes` (80 bits: a code’s key is public, so a short code can be guessed offline) and `codeAddresses` for the grant’s `keys` (all from `@xgamesfun/solana/results`).',
142
+ 'The page can’t be trusted: a game runs in the player’s browser, so a result worth paying is decided or checked by your server, which signs the voucher. A server checks what a wallet holds with `collectiblesOf` / `holds`, pays only wallets a player proved (`proofRequest` / `verifyProof`), and makes promo codes with `makeCodes` (80 bits: a code’s key is public, so a short code can be guessed offline) and `codeAddresses` for the grant’s `keys` (all from `@xgamesfun/solana/results`). How far a signed result can be trusted is what the server checks: it decides the result (runs the game or match; the page sends only inputs), replays it (re-runs the page’s inputs from randomness it keeps), or only bounds it (pace, order, caps: a script at human speed gets through). Say which on each voucher grant (`"trust"`), and run xsolana_review before going live: it says what a cheater gets on every route.',
143
143
  'Bots play well: even a verified result pays a bot what it pays a good player. Keep token rewards for results small and capped (a grant’s limits), give SOL prizes only by chance (the oracle) or by what your server decides, and don’t let a minted farm rate run without end into what its token buys (it compounds: give it an end, a term or an upkeep).',
144
144
  'A gate costs a sybil most when it asks for something the game sells: a rule takes any of several items (`{ "items": ["oak", "pine"] }`, up to 16), and can read the attributes only the program writes on every minted collectible, `item_id`, `rarity`, `serial` and `made` (`{ "attribute": "rarity", "atLeast": 2 }`: any of the game’s Rare or better).',
145
145
  ];
@@ -137,7 +137,8 @@ export function changedParts(economy) {
137
137
  const only = new Set();
138
138
  const fixed = [];
139
139
  const removed = [];
140
- const strip = ({ id, index, shopBox, seller, ...rest }) => JSON.stringify(rest);
140
+ // A grant's `trust` is the spec's own note about its server, never sent on chain: changing it publishes nothing.
141
+ const strip = ({ id, index, shopBox, seller, trust, ...rest }) => JSON.stringify(rest);
141
142
  for (const [list, kind] of Object.entries(PART_LISTS)) {
142
143
  const before = new Map((published[list] ?? []).map(p => [p.key, p]));
143
144
  for (const part of economy[list] ?? []) {