@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.
- package/DISCLAIMER.md +1 -1
- package/NOTICE +4 -4
- package/README.md +33 -28
- package/bin/xsolana.mjs +9 -3
- package/examples/stakers-draw.json +1 -0
- package/js/client/random.js +1 -1
- package/js/economy/check.mjs +11 -0
- package/js/economy/cost.mjs +1 -1
- package/js/economy/explain.mjs +10 -3
- package/js/economy/guide.mjs +1 -1
- package/js/economy/live.mjs +2 -1
- package/js/economy/review.mjs +760 -0
- package/js/economy/schema.json +8 -4
- package/js/economy/trust.mjs +15 -0
- package/js/index.js +1 -0
- package/js/mcp/server.mjs +1 -1
- package/js/mcp/tools.mjs +26 -1
- package/js/results/index.js +2 -2
- package/package.json +1 -1
- package/release.json +1 -1
- package/skills/xsolana-economy/SKILL.md +29 -13
- package/skills/xsolana-economy/references/after-launch.md +2 -1
- package/skills/xsolana-economy/references/sentence-to-spec.md +1 -0
- package/skills/xsolana-economy/references/telling-the-person.md +6 -0
- package/skills/xsolana-economy/references/trust-and-cheating.md +126 -0
- package/types/economy/cost.d.mts +1 -1
- package/types/economy/explain.d.mts +3 -0
- package/types/economy/review.d.mts +45 -0
- package/types/economy/trust.d.mts +8 -0
- package/types/index.d.ts +1 -0
- package/types/mcp/tools.d.mts +62 -0
- package/types/play/index.d.ts +6 -6
- package/types/results/index.d.ts +1 -1
- package/examples/holder-den.json +0 -38
- package/examples/holders-drop.json +0 -105
- package/examples/match-predictions.json +0 -24
- package/examples/pack-league.json +0 -108
- package/examples/stride-sneakers.json +0 -164
package/js/economy/schema.json
CHANGED
|
@@ -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
|
|
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 (
|
|
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 },
|
package/js/results/index.js
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
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.
|
|
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": "
|
|
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.
|
|
13
|
-
or `npx -y @xgamesfun/solana@0.25.
|
|
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.
|
|
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
|
|
57
|
-
rewards for results small, and have a server
|
|
58
|
-
|
|
59
|
-
`
|
|
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. **
|
|
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.
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
|
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.
|
package/types/economy/cost.d.mts
CHANGED
|
@@ -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:
|
|
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';
|
package/types/mcp/tools.d.mts
CHANGED
|
@@ -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: {
|