@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
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://raw.githubusercontent.com/XGamesFun/solana-kit/main/js/economy/schema.json",
4
4
  "title": "A loot economy (spec version 2)",
5
- "description": "One project's economy on the shared loot program: its currencies, collectibles, pools and the mechanics that move them. Kept as xsolana.json in a project (or as the `economy` section of a games.fun game). Amounts are in whole units of their token, percents are percents, durations are like \"7d\".",
5
+ "description": "One project's economy on the shared loot program: its currencies, collectibles, pools and the mechanics that move them. Kept as xsolana.json in a project (or as the `economy` section of an xgames.fun game). Amounts are in whole units of their token, percents are percents, durations are like \"7d\".",
6
6
  "type": "object",
7
7
  "required": ["version"],
8
8
  "additionalProperties": false,
@@ -363,7 +363,7 @@
363
363
  "type": "object",
364
364
  "required": ["key", "source"],
365
365
  "additionalProperties": false,
366
- "description": "A currency. `sol`: SOL (players pay in wrapped SOL). `existing`: any mint by address, launched anywhere, classic or Token-2022, pump.fun included (games.fun takes no share of its fees). `project` (`game` is its older spelling): a token made when the economy goes live: the project mints it as its rules say (points, tickets, shards), or, with `supply`, a fixed supply minted once to the owner (a community token you keep).",
366
+ "description": "A currency. `sol`: SOL (players pay in wrapped SOL). `existing`: any mint by address, launched anywhere, classic or Token-2022, pump.fun included (xgames.fun takes no share of its fees). `project` (`game` is its older spelling): a token made when the economy goes live: the project mints it as its rules say (points, tickets, shards), or, with `supply`, a fixed supply minted once to the owner (a community token you keep).",
367
367
  "properties": {
368
368
  "key": {
369
369
  "$ref": "#/$defs/key"
@@ -397,8 +397,8 @@
397
397
  "$ref": "#/$defs/address"
398
398
  },
399
399
  "venue": {
400
- "enum": ["games.fun", "pump.fun", "meteora", "other"],
401
- "description": "Where the token trades, recorded for the app and not read or enforced by the tools: loot doesn't launch or trade tokens. A token launched elsewhere is `existing`, by its mint."
400
+ "enum": ["xgames.fun", "games.fun", "pump.fun", "meteora", "other"],
401
+ "description": "Where the token trades, recorded for the app and not read or enforced by the tools: loot doesn't launch or trade tokens. A token launched elsewhere is `existing`, by its mint. `games.fun` is the older spelling of `xgames.fun`."
402
402
  },
403
403
  "uri": {
404
404
  "$ref": "#/$defs/link"
@@ -2462,6 +2462,10 @@
2462
2462
  "type": "integer",
2463
2463
  "minimum": 1,
2464
2464
  "description": "About how many rounds each wallet will be paid over its life. Default 1."
2465
+ },
2466
+ "trust": {
2467
+ "enum": ["decides", "replays", "bounds"],
2468
+ "description": "What the server holding a voucher key checks before it signs, said honestly: \"decides\" (it runs the game or match itself; the page sends only inputs), \"replays\" (it runs the page's inputs again, from randomness it keeps, and reads the result), \"bounds\" (the page runs the game and reports; the server checks only pace, order and daily caps, so a script at human speed gets through). Never sent on chain and changes nothing that goes live: xsolana_review and xsolana_explain read it. Only on a grant with `vouchers` or `keys`."
2465
2469
  }
2466
2470
  }
2467
2471
  },
@@ -0,0 +1,15 @@
1
+ // How far a result a voucher's server signs can be trusted: the levels a grant declares in `trust`, and each in
2
+ // words. The spec's own note about its server: never sent on chain. Pure.
3
+
4
+ /** The trust levels a voucher grant declares, strongest first. */
5
+ export const TRUST_LEVELS = Object.freeze(['decides', 'replays', 'bounds']);
6
+
7
+ /** Each trust level in words: what the server does, and what a cheater still gets. */
8
+ export const TRUST_WORDS = Object.freeze({
9
+ decides:
10
+ 'the server decides the result itself (it runs the game or the match; the page sends only inputs): nothing the page says can forge one, though a bot that plays well still earns what a good player does',
11
+ replays:
12
+ 'the server runs the page’s inputs again and reads the result itself: a forged result fails, though a bot that plays well still earns, and one that knows the run’s randomness up front can search for the best run offline',
13
+ bounds:
14
+ 'the page runs the game and reports the result, and the server checks only its pace, order and daily caps: a script that waits as long as a person would gets every reward, up to the limits',
15
+ });
package/js/index.js CHANGED
@@ -4,6 +4,7 @@ export { simulate, explain } from './economy/simulate.mjs';
4
4
  export { plan, setupSteps, playSteps } from './economy/plan.mjs';
5
5
  export { cost, explainCost } from './economy/cost.mjs';
6
6
  export { explainEconomy } from './economy/explain.mjs';
7
+ export { review, describeReview, SEVERITIES, TRUST_LEVELS, TRUST_WORDS } from './economy/review.mjs';
7
8
  export { model } from './economy/model.mjs';
8
9
  export { prepare, recordLanded, changedParts } from './economy/live.mjs';
9
10
  export { guideText, catalog, CANNOT } from './economy/guide.mjs';
package/js/mcp/server.mjs CHANGED
@@ -10,7 +10,7 @@ import { PROGRAM_INFO, tools, VERSION } from './tools.mjs';
10
10
 
11
11
  export const INSTRUCTIONS = [
12
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).',
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_review before going live (fix its high findings, say honestly what each voucher server checks, and tell the person what a cheater gets); 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
14
  `Program ${PROGRAM_INFO.program} on ${PROGRAM_INFO.cluster} (loot ${VERSION}). Devnet only until audited.`,
15
15
  ].join(' ');
16
16
 
package/js/mcp/tools.mjs CHANGED
@@ -15,6 +15,7 @@ import { cost, explainCost } from '../economy/cost.mjs';
15
15
  import { changedParts } from '../economy/live.mjs';
16
16
  import { example, examples, ideas, search, partsUsed } from '../economy/examples.mjs';
17
17
  import { explainEconomy } from '../economy/explain.mjs';
18
+ import { describeReview, review } from '../economy/review.mjs';
18
19
  import { CANNOT, catalog, guideText, PARTS } from '../economy/guide.mjs';
19
20
  import { model, rent as rentOf } from '../economy/model.mjs';
20
21
  import { plan } from '../economy/plan.mjs';
@@ -339,7 +340,7 @@ export const tools = [
339
340
  const verdict = check(economy, { now: nowOf(ctx) });
340
341
  const head = verdict.refusals.length || verdict.warnings.length ? describe(verdict) : 'No problems found.';
341
342
  const tail = verdict.ok
342
- ? `\nReady (${plural(verdict.warnings.length, 'warning')}). Next: xsolana_simulate and xsolana_explain, and tell the person what it means in plain words.`
343
+ ? `\nReady (${plural(verdict.warnings.length, 'warning')}). Next: xsolana_simulate and xsolana_explain, and tell the person what it means in plain words; before going live, xsolana_review.`
343
344
  : `\n${plural(verdict.refusals.length, 'refusal')}: fix each as its fix says, then check again.`;
344
345
  return {
345
346
  ok: verdict.ok,
@@ -420,6 +421,30 @@ export const tools = [
420
421
  return { ...r, from, text: r.text + footer };
421
422
  },
422
423
  },
424
+ {
425
+ name: 'xsolana_review',
426
+ annotations: READ,
427
+ description:
428
+ 'Review an economy the way a cheater reads it, before it goes live (not a validity check: xsolana_check is that). For every route that pays or gives value it says who can trigger it, what the program enforces, what it trusts off the chain, and the most one cheater gets; then findings with stable ids and a severity; then the trade-off to show the person: for each grant paid on vouchers, whether its server decides, replays or only bounds the result (its "trust"), what a cheater gets at that level, and what the next level costs to build. Fix every high finding, declare "trust" honestly on every voucher grant, and tell the person in plain words which level each signed route uses and what a cheater gets, so they choose the trade-off. Give the spec inline (`spec`), a file, or an example. Answers { ok (false when a finding is high, or the spec does not check: then `refusals`), routes: [{ kind ("grant", "farm", "item", "box", "payment", "recipe", "lottery", "contest", "auction", "schedule", "hold", "decision"), key, name, trigger, enforced: [words], trusts: [words], trust ("decides" | "replays" | "bounds" | null), ceiling (words), feeds: ["farm:<key>", …: what its prize is staked in, crafted into or buys] }], findings: [{ id (R-…), severity ("high" | "medium" | "low" | "info"), route ("grant:<key>", "farm:<key>", … or "economy"), message, fix }], tradeoffs (markdown) }.',
429
+ inputSchema: { type: 'object', properties: SOURCE, additionalProperties: false },
430
+ async handler(input, ctx) {
431
+ const { economy, from } = await load(input, ctx);
432
+ const r = review(economy, { now: nowOf(ctx) });
433
+ if (r.refusals) return refused({ refusals: r.refusals, warnings: [] }, from);
434
+ const highs = r.findings.filter(f => f.severity === 'high').length;
435
+ const tail = highs
436
+ ? `\n${plural(highs, 'high finding')}: fix ${highs === 1 ? 'it' : 'them'} (or have the person accept ${highs === 1 ? 'it' : 'them'} in plain words) before going live, then review again.`
437
+ : '\nNo high finding. Tell the person, in plain words, which level each signed route uses and what a cheater gets (the trade-offs above).';
438
+ return {
439
+ ok: r.ok,
440
+ from,
441
+ routes: r.routes,
442
+ findings: r.findings,
443
+ tradeoffs: r.tradeoffs,
444
+ text: `${describeReview(r, economy.name)}${tail}${footer}`,
445
+ };
446
+ },
447
+ },
423
448
  {
424
449
  name: 'xsolana_save',
425
450
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
@@ -1,5 +1,5 @@
1
1
  // `@xgamesfun/solana/results`: what a game uses with a results server, in a host server or in a player's game page.
2
- // Browser-safe: Web Crypto only. `server` is RESULTS_SERVER (the public one, on the games.fun play host), or another
2
+ // Browser-safe: Web Crypto only. `server` is RESULTS_SERVER (the public one, on the xgames.fun play host), or another
3
3
  // one's URL. The server confirms who won (from signatures rooted on the chain) and signs grant vouchers; winners claim
4
4
  // them with js/play (`claimWins`, `claimGrant`). The program pays a wallet each round once, ever, whatever is signed.
5
5
  //
@@ -40,7 +40,7 @@ export {
40
40
  rosterHash,
41
41
  } from './wire.js';
42
42
 
43
- /** games.fun's results server, on its play host (devnet until the program's audit). */
43
+ /** xgames.fun's results server, on its play host (devnet until the program's audit). */
44
44
  export const RESULTS_SERVER = 'https://play.xgames.fun/xsolana';
45
45
 
46
46
  const subtle = () => globalThis.crypto.subtle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xgamesfun/solana",
3
- "version": "0.25.0",
3
+ "version": "0.25.1",
4
4
  "mcpName": "io.github.XGamesFun/solana-kit",
5
5
  "description": "One shared Solana program for game and community economies, its JS client, and the tools that let an AI agent design, check, simulate and launch an economy on it.",
6
6
  "license": "Apache-2.0",
package/release.json CHANGED
@@ -4,5 +4,5 @@
4
4
  "sbfBytes": 1741840,
5
5
  "sbfSha256": "ea19d15d03211f7bd0b25179d51f75544342225127fcb631e687eae6750c9ba7",
6
6
  "idlSha256": "41e621990c17b8645f59bb2a170271645ad370d1c1bf9f1dbdd04f817b82d4cf",
7
- "clientSha256": "11270da0bd539b1cb79e8c61527e4b0686f2d03a2e2b617c823dcabf97f05f6e"
7
+ "clientSha256": "78b426dc003d261537934856302fe9767db149fd37b814af2569b95cc1333624"
8
8
  }
@@ -9,8 +9,8 @@ An economy is one JSON spec (`xsolana.json` in the project) on **loot**, a Solan
9
9
  writes or deploys a contract: the spec is checked, simulated and priced by the `xsolana_*` tools, and the project's
10
10
  owner puts it live by approving in their own wallet. It runs on **devnet** until the program is audited.
11
11
 
12
- The tools are the `xsolana_*` MCP tools. Without MCP, the same from a terminal: `npx -y @xgamesfun/solana@0.25.0 <guide|examples|check|simulate|cost|explain|publish|status> [xsolana.json]`,
13
- or `npx -y @xgamesfun/solana@0.25.0 tool <name> '<json>'` for any tool (always with the exact version).
12
+ The tools are the `xsolana_*` MCP tools. Without MCP, the same from a terminal: `npx -y @xgamesfun/solana@0.25.1 <guide|examples|check|simulate|cost|explain|review|publish|status> [xsolana.json]`,
13
+ or `npx -y @xgamesfun/solana@0.25.1 tool <name> '<json>'` for any tool (always with the exact version).
14
14
 
15
15
  ## Rules (always)
16
16
 
@@ -41,7 +41,9 @@ or `npx -y @xgamesfun/solana@0.25.0 tool <name> '<json>'` for any tool (always w
41
41
  anything a limit, break the idea into its pieces (what players pay, what they get, what is random, what the chain
42
42
  can't see, what is held and for how long, who is paid) and find the part for each (`references/sentence-to-spec.md`).
43
43
  What the chain can't see (a match, a score, something done offline) is vouched for by a signer: a results server's voucher, a
44
- decider's key, a list. Build the combination and say which part does which piece. When the tools' words and the
44
+ decider's key, a list. The signer is not the end of the story: the program checks its signature and limits, never
45
+ what happened, so a signed result is worth what that server checks (it decides the result, replays it, or only
46
+ bounds what the page reports: rule 12). Build the combination and say which part does which piece. When the tools' words and the
45
47
  checker disagree (a field the schema takes that a sentence somewhere calls impossible), the checker and
46
48
  `xsolana_guide`'s parts decide: try it in a spec and run `xsolana_check`.
47
49
  A piece is a limit only when no part or combination does it. Then:
@@ -53,10 +55,12 @@ or `npx -y @xgamesfun/solana@0.25.0 tool <name> '<json>'` for any tool (always w
53
55
  table.
54
56
 
55
57
  10. **Design for many wallets and bots.** Wallets are free and bots play well: bound every reward with totals
56
- (`perDay`, `total`), gate open rewards on holding something or count people by vouchers (`perPerson`), keep token
57
- rewards for results small, and have a server check what the page claims (`@xgamesfun/solana/results`:
58
- `collectiblesOf`, `verifyProof`, `makeCodes`). The checker warns about the rest (`W-GRANT-TOTAL`, `W-GRANT-OPEN`,
59
- `W-FARM-MINT`); xsolana_guide's "Designing against farming" says why.
58
+ (`perDay`, `total`), gate open rewards on holding something or count people by vouchers (`perPerson`, with the
59
+ server naming an X account, never a wallet), keep token rewards for results small, and have a server decide or
60
+ replay a result worth paying rather than take the page's word (`@xgamesfun/solana/results`: `collectiblesOf`,
61
+ `verifyProof`, `makeCodes`). Keep entry fees and prize pools for results the server decides and for chance, and
62
+ give a minted farm an end. The checker warns about the rest (`W-GRANT-TOTAL`, `W-GRANT-OPEN`, `W-FARM-MINT`);
63
+ `references/trust-and-cheating.md` says why.
60
64
 
61
65
  11. **People finish what follows; build the button, not a server.** Every step after a player's ask (settle a roll,
62
66
  draw a lottery, pull its parts (each place, a refund, a share), refund a draw the oracle never answered, claim a
@@ -66,6 +70,14 @@ or `npx -y @xgamesfun/solana@0.25.0 tool <name> '<json>'` for any tool (always w
66
70
  nobody claims for a player. loot needs no server to finish anything. Never make a key in a page or a server for
67
71
  the owner to fund: whoever signs pays.
68
72
 
73
+ 12. **Review before going live, and let the person choose the trade-off.** Run `xsolana_review` before the owner
74
+ approves. Fix every high finding, or have the person accept it in plain words. Declare `"trust"` honestly on every
75
+ voucher grant: `"decides"` (the server runs the game or match; the page sends only inputs), `"replays"` (it re-runs
76
+ the page's inputs from randomness it keeps) or `"bounds"` (it takes the page's word within a pace and caps). Then
77
+ tell the person, for each signed route, which level it uses, what a cheater gets (the review's ceiling, with what
78
+ the prize goes on to earn), and what the stronger level would cost to build. They choose; you don't choose for
79
+ them by leaving it out. `references/trust-and-cheating.md` has the levels, the costs and the checklist.
80
+
69
81
  ## The loop
70
82
 
71
83
  1. **Read what an economy can be made of**: `xsolana_guide` (every part and field, who acts and what it costs, what
@@ -96,14 +108,17 @@ or `npx -y @xgamesfun/solana@0.25.0 tool <name> '<json>'` for any tool (always w
96
108
  (every value of a fresh item's `{attribute}`), a table's rent, and the collectible rent a pack's settle pays.
97
109
  6. **Iterate on what they say.** "Make legendaries 0.5%", "a bigger seed": change, check, simulate, explain again.
98
110
  7. **Save it**: `xsolana_save {"spec": …}` writes `xsolana.json`, only if it checks. A live file keeps its program numbers.
99
- 8. **When they say "put it live"**: `xsolana_publish` lists every step and its cost and gives the command. Tell them to
111
+ 8. **Review it before they approve**: `xsolana_review` (rule 12). Fix the high findings, declare `trust` on every
112
+ voucher grant, and tell the person which level each signed route uses, what a cheater gets and what the stronger
113
+ level would cost. Run it again after every change that touches a route that pays.
114
+ 9. **When they say "put it live"**: `xsolana_publish` lists every step and its cost and gives the command. Tell them to
100
115
  run it themselves in the project folder, exactly as `xsolana_publish` gives it (it names the exact version:
101
- `npx -y @xgamesfun/solana@0.25.0 approve xsolana.json`; never drop the version). It opens a page on their computer where
116
+ `npx -y @xgamesfun/solana@0.25.1 approve xsolana.json`; never drop the version). It opens a page on their computer where
102
117
  they connect a devnet wallet and approve; what lands is written back into `xsolana.json`. You publish nothing.
103
- 9. **After it is live**: `xsolana_status` shows each part on chain; read it before acting on what the person says
104
- happened. A later change is the same loop: save, publish, and the approval sends only what changed. What only the
105
- owner signs (open or decide a contest, pay out) goes on an owner page they approve in their own wallet:
106
- `references/after-launch.md`.
118
+ 10. **After it is live**: `xsolana_status` shows each part on chain; read it before acting on what the person says
119
+ happened. A later change is the same loop: save, publish, and the approval sends only what changed. What only the
120
+ owner signs (open or decide a contest, pay out) goes on an owner page they approve in their own wallet:
121
+ `references/after-launch.md`.
107
122
 
108
123
  ## Building the app around it
109
124
 
@@ -121,3 +136,4 @@ For staking an NFT of another collection, show what custody means and get explic
121
136
  - Telling the person what they get, and what stays the project's job: `references/telling-the-person.md`
122
137
  - Building the app for a live economy: `references/apps.md`
123
138
  - After it goes live (who sends each step, owner pages, results from a game, a results server): `references/after-launch.md`
139
+ - What a server decides, replays or bounds, who gets paid, and the exploit checklist: `references/trust-and-cheating.md`
@@ -101,7 +101,8 @@ now })` for the player's own wallet and claims (`app.claimGrant(key, { voucher }
101
101
  (`perPerson`, with a person hash the voucher names: that needs an outside sign-in such as X, and a wallet-only
102
102
  player has none); it checks what a wallet holds on the chain, never the page's word (`collectiblesOf`, `holds`); and
103
103
  the grant's `perDay` and `total` bound everything whatever happens to a key. Keep token rewards for results small.
104
- - **Vouchers from a results server**: the grant names the server's public key in `vouchers`; the server checks the
104
+ - **Vouchers from a results server**: the grant names the server's public key in `vouchers`, and says in `trust`
105
+ what the server checks (`"decides"`, `"replays"` or `"bounds"`: `trust-and-cheating.md`); the server checks the
105
106
  game's result and signs a voucher (for a grant of tokens, with each winner's own `amount`, up to the grant's: rewards
106
107
  by rank); the winner's page claims with it (`app.claimGrant(key, { voucher })`).
107
108
  - The server can be the person's own game server (`signVoucher` from `@xgamesfun/solana/results`). It holds only that
@@ -40,6 +40,7 @@ maps to a part of the spec (`xsolana_guide` has every field).
40
40
  | "the expedition brings back a chest", "missions drop random loot" | a farm tier's `prize`: `{ "gives": "roll", "box": "<box>" }`, rolled when the mission is completed (its lock stayed, its upkeep never ran dry, not forfeited) while the NFT goes home; a known reward (tokens, an item, an NFT) is given as it leaves |
41
41
  | "only pass holders can buy it", "members only" | a `gate` of the item alone: `{ "item": "pass" }` |
42
42
  | "only holders can open it", "level 5 and up" | a `gate`: `{ "item": "pass", "attribute": "level", "atLeast": 5 }` |
43
+ | "holders of any of our plots", "any of the three species", "gold or diamond tier" | a rule of several: `{ "items": ["oak", "pine", "birch"] }` (any of these items, in the game's own collection) or `{ "attribute": "tier", "oneOf": ["gold", "diamond"] }`, up to 16 each; it works wherever a rule does (a gate, a farm's gate or boost, odds, `rollsOn`, a recipe's `show`) |
43
44
  | "only these players", "just the two of them", "the tournament's entrants" | a `gate` of wallets: `{ "wallets": ["<address>", …] }` (a long list is a `list` gate instead, below) |
44
45
  | "stake to enter", "staking tiers for a token sale", "only people who lock 100 of our token can buy" | a farm of a token that nothing funds when staking pays nothing (no `seed`, no share or schedule into `"farm:<key>"`; `W-FARM-DRY` says so), and a `gate` `{ "staked": { "farm": "<farm of a token>", "atLeast": 100 } }`: the wallet's stake in that farm, counted only while its tier's lock runs and from the second after it is made (one stake can't move to another wallet and count again; a tier with no lock never passes, `W-STAKED`). The app passes the seat and the farm |
45
46
  | "allowlist", "whitelist of 5,000 wallets", "a snapshot of our holders", "early supporters only" | a `gate` `{ "list": ["<address>", …] }` of any size: the program keeps only its root, the page passes each player's proof (`W-LIST`); a new list is a new root, published again |
@@ -40,6 +40,12 @@ and the spec differ (a limit, an amount, a key not set yet), change one of them
40
40
  For a collectible they already hold: "It stays with the project until someone wins it or the auction ends."
41
41
  8. **Anything else the checker warned about**, in a sentence each (a mint that can freeze accounts, a free box bots
42
42
  can drain, a grant with no limits or no funds).
43
+ 9. **What a cheater gets**, from `xsolana_review`, before the owner approves: for each route paid on a server's
44
+ voucher, which level it uses (the server decides, replays or only bounds the result), what one cheater gets there
45
+ (its ceiling, and what the prize goes on to earn), and what the stronger level would cost to build: "A script that
46
+ waits as long as a player would gets each reward NFT once per wallet, and each then earns tokens every day with no
47
+ end; having the server replay each run would stop forged runs, and needs the game to be deterministic." Then each
48
+ high finding and its fix. The person chooses the trade-off (`trust-and-cheating.md`).
43
49
 
44
50
  ## What stays the project's job
45
51
 
@@ -0,0 +1,126 @@
1
+ # Trust and cheating
2
+
3
+ The program enforces its own rules: a cheater can't take more than a route's limits, move a voucher to another
4
+ wallet, harvest someone else's seat or mint into the project's collection. What a cheater can still take is what the
5
+ economy's design and its servers leave open: a result the server never really checked, a person counted as many
6
+ wallets, a reward that keeps earning for good. This page is how to read an economy for that, and how to tell the
7
+ person what they are choosing. `xsolana_review` does the reading for a spec; this page says why.
8
+
9
+ ## How far a result can be trusted
10
+
11
+ A result is worth paying only as far as the server that signs the voucher can tell it happened. Strongest first:
12
+
13
+ - **The server decides it.** A turn game played on the server, or a room whose server runs the match: the page sends
14
+ inputs, never results. The result is what the server saw.
15
+ - **The server replays it.** A deterministic game (a fixed time step, seeded randomness): the page sends its inputs,
16
+ and the server runs them again and reads the result itself. Mind CPU limits: replay short runs, or a long one in
17
+ pieces as it is played. Never hand the page the whole run's randomness up front: with the seed and the rules, a
18
+ script searches for the best run offline and sends only that. Reveal randomness as the run goes, or keep the seed on
19
+ the server; one open run per player, and count abandoned runs toward the day's.
20
+ - **The server bounds it.** Any game the browser simulates: the server starts each run (an id and its own clock),
21
+ takes each checkpoint in order, refuses what is faster than the game allows, caps what one player earns a day, and
22
+ can ban. A script that plays at human speed still gets through: this limits what a cheater earns, it does not stop
23
+ one.
24
+
25
+ | Level | What it costs to build | CPU and latency | What a cheater gets | Fits |
26
+ | ------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
27
+ | decides | the most: the game's rules run on the server (or in a room that runs the match) | every move waits on the server | what a bot that plays well earns; nothing forged | turn games, card and board games, matches in a room, anything with hidden information |
28
+ | replays | a deterministic game (fixed step, seeded randomness the server keeps) and a replayer | a replay per run, after the fact; short runs or in pieces | what a bot that plays well earns; with the seed up front, the best run found | puzzles, runners, arcade games with short runs |
29
+ | bounds | the least: a run id, the server's clock, checkpoints in order, daily caps, a ban list | almost none | every reward a script claims at human speed, up to the limits | anything the browser simulates; prizes that are cosmetic, small or capped |
30
+
31
+ Even a result the server decided is worth what a good bot can earn. Pay a bounded or replayed result only what you would
32
+ accept losing to a patient script (small amounts, capped a day), and keep entry fees and prize pools for results the
33
+ server decided and for chance (the oracle). Tell players which one your game is.
34
+
35
+ **Say it in the spec.** Every grant paid on vouchers says what its server checks: `"trust": "decides"`, `"replays"` or
36
+ `"bounds"`. It is the spec's own note (it never goes on chain, and changing it publishes nothing), and it must be
37
+ honest: a server that takes the page's word within a pace is `bounds`, whatever else it does. `xsolana_review` reads a
38
+ voucher grant that says nothing as `bounds`, the weakest.
39
+
40
+ ## Who gets paid: wallets are not people
41
+
42
+ - **A wallet is free.** Any limit per wallet (`perWallet`, a gate's one claim per collectible shown) limits wallets,
43
+ never a person. What bounds a reward is its totals: `perDay` and `total` are the most it can ever pay, whatever
44
+ happens to a key or how many wallets come.
45
+ - **Count people through vouchers.** `perPerson` counts the person the voucher names (a person hash), which only the
46
+ server chooses: the player can't pick or change it, and an open grant has none. So it is exactly as good as the
47
+ server's choice: a real outside identity (an X account) counts people; a wallet, or a voucher with no person, counts
48
+ nobody. For rewards worth farming, pay only players signed in with X.
49
+ - **Make farming cost something.** Gate on holding a collectible the game sells or a locked stake (`gate`), pay only
50
+ wallets a player proved (`proofRequest` / `verifyProof`), and keep prizes for results small.
51
+ - **Bots play well.** Even a verified result pays a bot what it pays a good player.
52
+
53
+ ## What the program guarantees
54
+
55
+ These were attacked on devnet and refused by the program; there is nothing to build for them:
56
+
57
+ - **A voucher is bound to its claim.** It signs the program, the grant, the grant's incarnation, the wallet, the person,
58
+ an expiry and its rounds. It can't be moved to another wallet, or replayed on another grant or a re-made one. A key
59
+ not among the grant's voucher keys is refused, and so is a claim with no voucher on a grant that isn't open.
60
+ - **The winning wallet signs its own claim.** Anyone may pay its fees; nobody claims for it.
61
+ - **A round pays a wallet once** per grant incarnation, whoever signs it again and whenever; `perWallet`,
62
+ `perPerson`, `perKey`, `perDay` and `total` are kept by the program.
63
+ - **A farm's gate can't be forged.** It checks the project's collection and the item number the program writes at mint
64
+ and never lets change: an NFT of another collection, one in no collection, an attribute written by hand, or a mint
65
+ into the project's collection by anyone but the project are refused. Boosts read the staked NFT's own item number.
66
+ - **One seat per NFT, ever.** In escrow, an NFT can't leave before its lock or be staked twice. Watched, an NFT sold
67
+ while staked earns its old staker nothing more, and its new holder takes the seat over. Nobody harvests or unstakes
68
+ someone else's seat.
69
+ - **The project's keys.** A token the project mints has the project as its only minter and no freeze authority. Only
70
+ the authority mints or moves pool funds; only the owner changes mechanics or closes records. Money held for a
71
+ record (a farm's stakes and rewards, a pot, an order, a bid) leaves only by that record's rules.
72
+ - **Chance is the oracle's.** Nobody, the project included, steers a roll or a draw.
73
+
74
+ ## What is still yours to get right: the checklist
75
+
76
+ Run `xsolana_review` and go through what it reports; these are the points behind its findings.
77
+
78
+ 1. **The signer decides who is paid.** The program checks a voucher's signature, wallet and limits, never what
79
+ happened in the game. Declare `trust` honestly on every voucher grant, and make the server's checks match it.
80
+ 2. **Person counting.** `perPerson` holds only when the server names a real person (an X account) in every voucher,
81
+ never a wallet. Without it, each free wallet claims again: set `perDay` and `total` as if every claim were a new
82
+ wallet.
83
+ 3. **Sybil wallets.** Open grants (no voucher, code or list) pay any wallet that asks: gate them on something a sybil
84
+ must pay for, keep their prizes small or soulbound, and set `perDay`.
85
+ 4. **Voucher key custody.** The key's secret lives only on the server: never in a page, a repository, a spec or a
86
+ chat. Anyone holding it pays anyone, up to the grant's limits, which is why `perDay` and `total` matter. To rotate
87
+ it, put the new public key in the grant's `vouchers` and publish. Use a different key on each cluster: a voucher's
88
+ bytes don't name the cluster.
89
+ 5. **Re-made grants pay again.** Closing and making a grant again starts a new incarnation: a new campaign whose
90
+ limits start from zero, and whose list pays every listed round again. Give a re-made grant fresh rounds.
91
+ 6. **Emission without an end.** A farm whose rate is minted, with no `runs`, no term on every tier (`earnsFor`), no
92
+ upkeep and no halving, mints for as long as anything is staked. Every way to get what it takes is then worth that
93
+ stream: a cheatable claim of a staked NFT compounds into tokens for good. Give it an end, or pay its rate from its
94
+ vault.
95
+ 7. **Prizes worth farming.** A tradeable prize (tokens, an NFT that can be sold, a roll) on a route a script can reach
96
+ is what a cheater farms. Keep such prizes for results the server decides, or small and capped.
97
+ 8. **Entry fees and prize pools.** Players' money (a payment, a box's or a lottery's share into a pool) paid out on a
98
+ bounded or replayed result goes to whoever scripts it. Keep those for decided results and chance.
99
+ 9. **NFT farm custody.** Escrow moves the NFT to the project while staked; freeze locks it in the wallet; watch leaves
100
+ it free, so it can be sold while staked and its buyer takes the seat. The issuer's own controls still apply to
101
+ NFTs of other collections.
102
+ 10. **The gate.** A farm or a route gated on the project's items (`items`, `item_id`, `rarity`) is as strong as the
103
+ ways those items are had. A gate on another collection's attributes trusts whoever can write them.
104
+ 11. **The authority and the treasury.** The owner's wallet alone changes every price, rate, grant, limit and voucher
105
+ key; the authority mints, moves pool funds, decides contests and releases holds, with no caps. Keep them in a
106
+ hardware wallet or a multisig; a server that signs gets an authority key of its own, never the owner's.
107
+ 12. **The page is untrusted.** A page builds the player's transactions, and a cheater builds their own: the program's
108
+ checks are what count, never the page's. Anything the page decides (a score, a reward amount, who won) is only as
109
+ good as the server that checks it.
110
+ 13. **Ordering.** A lottery's tickets are not weighted by time: one buyer can take most of a round right before the
111
+ draw. Anyone, the seller included, can bid an auction up. A box that keeps selling when an outcome runs out
112
+ (`drawLeft`) lets someone watching the stock empty a common outcome to make a rare likelier.
113
+ 14. **Contests.** Players' stakes go to the side the authority or a decider key names: keep contests for results the
114
+ server decides, or that anyone can check outside the game.
115
+
116
+ ## Telling the person
117
+
118
+ After `xsolana_review`, before the owner approves, say in plain words:
119
+
120
+ - for each signed route, which level its server uses and what a cheater gets ("A script that waits long enough gets
121
+ a Legendary per wallet, up to 100 a day, and each earns 50 tokens a day for good");
122
+ - what the next level would cost to build ("replaying runs needs the game to be deterministic and a server that re-runs
123
+ each run");
124
+ - every high finding, and its fix, or that they accept it.
125
+
126
+ They choose the trade-off, not you.
@@ -11,7 +11,7 @@ export declare function setupCost(plan: any, economy: any): {
11
11
  notes: string[];
12
12
  };
13
13
  /**
14
- * What each action costs whoever signs it while the economy runs: games.fun's fee, the network's, and for anything
14
+ * What each action costs whoever signs it while the economy runs: xgames.fun's fee, the network's, and for anything
15
15
  * that asks MagicBlock's oracle for a random number (a box's open, a lottery's draw, the authority's `roll`) the
16
16
  * oracle's own fee (`oracleSol`, model.mjs ORACLE_FEE).
17
17
  */
@@ -1,3 +1,6 @@
1
+ export declare const ruleWords: (r: any) => string;
2
+ /** A gate in a sentence: "Only players who show …", "Only the wallets it names …", "Only wallets with … staked …". */
3
+ export declare const gateWords: (gate: any, verb: any, m: any) => string;
1
4
  export declare function explainEconomy(economy: any, { now }?: {
2
5
  now?: number | undefined;
3
6
  }): {
@@ -0,0 +1,45 @@
1
+ import { TRUST_LEVELS, TRUST_WORDS } from './trust.mjs';
2
+ /** Severities, most serious first. */
3
+ export declare const SEVERITIES: readonly string[];
4
+ export { TRUST_LEVELS, TRUST_WORDS };
5
+ /**
6
+ * The review of an economy: { ok, routes, findings, tradeoffs }. `ok` is false when there is a high finding, or when
7
+ * the spec does not check (then `refusals` says why, and the rest is empty).
8
+ */
9
+ export declare function review(economy: any, { now }?: {
10
+ now?: number | undefined;
11
+ }): {
12
+ ok: boolean;
13
+ refusals: any[];
14
+ routes: never[];
15
+ findings: never[];
16
+ tradeoffs: string;
17
+ } | {
18
+ refusals?: undefined;
19
+ ok: boolean;
20
+ routes: ({
21
+ kind: string;
22
+ key: any;
23
+ name: any;
24
+ trigger: string;
25
+ enforced: any[];
26
+ trusts: any[];
27
+ trust: any;
28
+ ceiling: string;
29
+ feeds: any[];
30
+ } | {
31
+ kind: string;
32
+ key: any;
33
+ name: any;
34
+ trigger: string;
35
+ enforced: any[];
36
+ trusts: any;
37
+ trust: null;
38
+ ceiling: string;
39
+ feeds: any[];
40
+ })[];
41
+ findings: any[];
42
+ tradeoffs: string;
43
+ };
44
+ /** A review in words, for the terminal and the tool's text. */
45
+ export declare function describeReview(r: any, name?: string): string;
@@ -0,0 +1,8 @@
1
+ /** The trust levels a voucher grant declares, strongest first. */
2
+ export declare const TRUST_LEVELS: readonly string[];
3
+ /** Each trust level in words: what the server does, and what a cheater still gets. */
4
+ export declare const TRUST_WORDS: Readonly<{
5
+ decides: "the server decides the result itself (it runs the game or the match; the page sends only inputs): nothing the page says can forge one, though a bot that plays well still earns what a good player does";
6
+ replays: "the server runs the page’s inputs again and reads the result itself: a forged result fails, though a bot that plays well still earns, and one that knows the run’s randomness up front can search for the best run offline";
7
+ bounds: "the page runs the game and reports the result, and the server checks only its pace, order and daily caps: a script that waits as long as a person would gets every reward, up to the limits";
8
+ }>;
package/types/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { simulate, explain } from './economy/simulate.mjs';
3
3
  export { plan, setupSteps, playSteps } from './economy/plan.mjs';
4
4
  export { cost, explainCost } from './economy/cost.mjs';
5
5
  export { explainEconomy } from './economy/explain.mjs';
6
+ export { review, describeReview, SEVERITIES, TRUST_LEVELS, TRUST_WORDS } from './economy/review.mjs';
6
7
  export { model } from './economy/model.mjs';
7
8
  export { prepare, recordLanded, changedParts } from './economy/live.mjs';
8
9
  export { guideText, catalog, CANNOT } from './economy/guide.mjs';
@@ -451,6 +451,68 @@ export declare const tools: ({
451
451
  from: any;
452
452
  text: string;
453
453
  }>;
454
+ } | {
455
+ name: string;
456
+ annotations: {
457
+ readOnlyHint: boolean;
458
+ destructiveHint: boolean;
459
+ idempotentHint: boolean;
460
+ openWorldHint: boolean;
461
+ };
462
+ description: string;
463
+ inputSchema: {
464
+ type: string;
465
+ properties: {
466
+ spec: {
467
+ type: string;
468
+ description: string;
469
+ };
470
+ file: {
471
+ type: string;
472
+ description: string;
473
+ };
474
+ example: {
475
+ type: string;
476
+ description: string;
477
+ };
478
+ };
479
+ additionalProperties: boolean;
480
+ required?: undefined;
481
+ };
482
+ handler(input: any, ctx: any): Promise<{
483
+ ok: boolean;
484
+ from: any;
485
+ refusals: any;
486
+ warnings: any;
487
+ text: string;
488
+ } | {
489
+ ok: boolean;
490
+ from: any;
491
+ routes: ({
492
+ kind: string;
493
+ key: any;
494
+ name: any;
495
+ trigger: string;
496
+ enforced: any[];
497
+ trusts: any[];
498
+ trust: any;
499
+ ceiling: string;
500
+ feeds: any[];
501
+ } | {
502
+ kind: string;
503
+ key: any;
504
+ name: any;
505
+ trigger: string;
506
+ enforced: any[];
507
+ trusts: any;
508
+ trust: null;
509
+ ceiling: string;
510
+ feeds: any[];
511
+ })[];
512
+ findings: any[];
513
+ tradeoffs: string;
514
+ text: string;
515
+ }>;
454
516
  } | {
455
517
  name: string;
456
518
  annotations: {