@xgamesfun/solana 0.25.0 → 0.25.2
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 +81 -31
- package/bin/xsolana.mjs +19 -5
- package/examples/stakers-draw.json +1 -0
- package/js/client/index.js +1 -0
- package/js/client/kit.js +36 -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/http.mjs +136 -0
- package/js/mcp/server.mjs +34 -10
- package/js/mcp/tools.mjs +50 -2
- package/js/play/index.js +116 -2
- package/js/play/sponsor.js +302 -0
- package/js/results/index.js +2 -2
- package/package.json +35 -8
- package/release.json +1 -1
- package/skills/xsolana-economy/SKILL.md +32 -13
- package/skills/xsolana-economy/references/after-launch.md +2 -1
- package/skills/xsolana-economy/references/apps.md +28 -0
- 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/client/index.d.ts +1 -0
- package/types/client/kit.d.ts +49 -0
- package/types/economy/cost.d.mts +1 -1
- package/types/economy/explain.d.mts +3 -0
- package/types/economy/plan.d.mts +14 -14
- 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/http.d.mts +30 -0
- package/types/mcp/server.d.mts +321 -0
- package/types/mcp/tools.d.mts +71 -1
- package/types/play/index.d.ts +3 -1
- package/types/play/sponsor.d.ts +149 -0
- 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/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,
|
|
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
|
|
2
|
+
Copyright 2026 xgames.fun
|
|
3
3
|
|
|
4
|
-
This product includes software developed at
|
|
4
|
+
This product includes software developed at xgames.fun.
|
|
5
5
|
|
|
6
|
-
Parts of the JavaScript client and tools (js/) are adapted from the
|
|
7
|
-
MIT License, Copyright (c) 2026
|
|
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
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
# solana-kit
|
|
1
|
+
# solana-kit: Solana game and NFT economies without a smart contract
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Loot boxes and gacha, shops, lotteries and raffles, auctions, NFT staking farms, crafting, quests and capped rewards on
|
|
4
|
+
Solana, written as a JSON spec instead of a new Anchor program. They all run on **loot**, one shared, open Solana
|
|
5
|
+
program, and an AI coding agent (Claude Code, Codex, Cursor or any MCP client) can design, check, simulate and price
|
|
6
|
+
one from a sentence. This repository is the npm package `@xgamesfun/solana`:
|
|
5
7
|
|
|
6
8
|
- a JavaScript client with no Solana SDK (`@xgamesfun/solana/client`), a player module for apps (`/play`) and a results
|
|
7
9
|
client (`/results`);
|
|
8
10
|
- the economy spec and its checker, quick simulation, cost and plain-words explainer;
|
|
9
|
-
- the `xsolana` CLI and an MCP server with
|
|
11
|
+
- the `xsolana` CLI and an MCP server with eleven `xsolana_*` tools;
|
|
10
12
|
- the `xsolana-economy` skill, and one plugin that carries the tools and the skill to Claude Code and Codex;
|
|
11
13
|
- a local approval page where a project's owner approves going live in their own wallet.
|
|
12
14
|
|
|
@@ -35,7 +37,10 @@ An economy is a JSON spec, not a new smart contract. Every project shares the on
|
|
|
35
37
|
- **Players also trust the token and the collection.** A mint's issuer (a freeze authority, a pause, a transfer hook, a
|
|
36
38
|
permanent delegate) or a collection's controller can stop or move what loot holds. USDC has a freeze authority.
|
|
37
39
|
- **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
|
|
40
|
+
trusts, a results server or deciders. Checking that a player really did it is the project's job, on its own server:
|
|
41
|
+
a server that decides the result, replays it, or only bounds what the page reports leaves a cheater very different
|
|
42
|
+
amounts. A grant says which in `trust`, and `xsolana review` says what a cheater gets on every route
|
|
43
|
+
([trust and cheating](skills/xsolana-economy/references/trust-and-cheating.md)).
|
|
39
44
|
- **The hosted results server is optional.** `https://play.xgames.fun/xsolana` (`@xgamesfun/solana/results`'s
|
|
40
45
|
default) signs vouchers for games without a server of their own. It can only pay what a grant allows, and only for a
|
|
41
46
|
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 +107,24 @@ codex plugin add xsolana@xgamesfun
|
|
|
102
107
|
```
|
|
103
108
|
|
|
104
109
|
**Any agent with MCP** (Cursor, opencode, Cline…): run the server with
|
|
105
|
-
`npx -y --package=@xgamesfun/solana@0.25.
|
|
110
|
+
`npx -y --package=@xgamesfun/solana@0.25.2 xsolana mcp`. Or let `npx -y @xgamesfun/solana@0.25.2 connect` register it, with
|
|
106
111
|
the skill, for Claude Code and Codex.
|
|
107
112
|
|
|
113
|
+
**The skill alone, in any agent that reads skills** (Claude Code, Codex, Copilot, Gemini CLI, Cursor, Windsurf, Cline,
|
|
114
|
+
opencode…): `npx skills add XGamesFun/solana-kit` finds it and installs it for the agents you have. Without the MCP
|
|
115
|
+
server it runs the same tools through `npx`. Each GitHub release also carries `xsolana-economy.zip`, the skill as one
|
|
116
|
+
file for apps that take a skill as an upload (the Claude and ChatGPT desktop apps).
|
|
117
|
+
|
|
118
|
+
**Over HTTP**: `npx -y --package=@xgamesfun/solana@0.25.2 xsolana mcp --http` serves the tools that read nothing but
|
|
119
|
+
their arguments (guide, examples, check, simulate, cost, explain, review, status, client) at
|
|
120
|
+
`http://127.0.0.1:8787/mcp`, MCP's Streamable HTTP transport. `@xgamesfun/solana/mcp/http` exports the same handler
|
|
121
|
+
(`mcpHttp(request) → response`) for a hosted endpoint: it reads no files, and the agent hands the person the spec and the
|
|
122
|
+
`approve` command to go live.
|
|
123
|
+
|
|
108
124
|
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
125
|
monthly draw"_.
|
|
110
126
|
|
|
111
|
-
Going live is always yours: `npx -y @xgamesfun/solana@0.25.
|
|
127
|
+
Going live is always yours: `npx -y @xgamesfun/solana@0.25.2 approve` opens a page on your computer where you connect a
|
|
112
128
|
devnet wallet and approve each transaction. The agent never signs and never asks for a key.
|
|
113
129
|
|
|
114
130
|
## Use it from a terminal
|
|
@@ -116,12 +132,13 @@ devnet wallet and approve each transaction. The agent never signs and never asks
|
|
|
116
132
|
Every tool runs from the CLI too, on a spec file (`xsolana.json` by default):
|
|
117
133
|
|
|
118
134
|
```bash
|
|
119
|
-
npx -y @xgamesfun/solana@0.25.
|
|
120
|
-
npx -y @xgamesfun/solana@0.25.
|
|
121
|
-
npx -y @xgamesfun/solana@0.25.
|
|
122
|
-
npx -y @xgamesfun/solana@0.25.
|
|
123
|
-
npx -y @xgamesfun/solana@0.25.
|
|
124
|
-
npx -y @xgamesfun/solana@0.25.
|
|
135
|
+
npx -y @xgamesfun/solana@0.25.2 examples shop with loot boxes # the nearest worked examples
|
|
136
|
+
npx -y @xgamesfun/solana@0.25.2 check xsolana.json # refusals and warnings
|
|
137
|
+
npx -y @xgamesfun/solana@0.25.2 simulate xsolana.json # where the money goes, day by day
|
|
138
|
+
npx -y @xgamesfun/solana@0.25.2 cost xsolana.json # what going live and each action cost
|
|
139
|
+
npx -y @xgamesfun/solana@0.25.2 explain xsolana.json # the economy in plain words
|
|
140
|
+
npx -y @xgamesfun/solana@0.25.2 review xsolana.json # what a cheater gets on each route (exits 1 on a high finding)
|
|
141
|
+
npx -y @xgamesfun/solana@0.25.2 approve xsolana.json # go live, approved in your own wallet
|
|
125
142
|
```
|
|
126
143
|
|
|
127
144
|
`xsolana help` lists every command. Every command names the exact version, so `npx` runs that release and never whatever
|
|
@@ -138,30 +155,63 @@ import * as loot from '@xgamesfun/solana/client';
|
|
|
138
155
|
const ix = loot.openBoxIx({ player, project, box, nonce }); // one builder per instruction, no Solana SDK
|
|
139
156
|
```
|
|
140
157
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
|
149
|
-
|
|
|
158
|
+
Players with no SOL can still play when the project sponsors their fees: `solanaApp({ sponsor })` has a key on the
|
|
159
|
+
project's own server pay each step's fees and rent (never its price), and `@xgamesfun/solana/sponsor` is that server's
|
|
160
|
+
side: `sponsorSigner` dry-runs and checks every transaction before it signs, under the project's own per-player budget.
|
|
161
|
+
|
|
162
|
+
Building on `@solana/kit`? `loot.toKitInstruction(ix)` gives any builder's instruction in Kit's shape
|
|
163
|
+
(`{ programAddress, accounts, data }`), and `loot.fromKitInstruction` turns one back.
|
|
164
|
+
|
|
165
|
+
| Import | What |
|
|
166
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
167
|
+
| `@xgamesfun/solana` | the economy tools: `check`, `simulate`, `plan`, `cost`, `explainEconomy`, `review`, `model` |
|
|
168
|
+
| `@xgamesfun/solana/client` | every PDA, one builder per instruction, account and receipt decoders, grant vouchers |
|
|
169
|
+
| `@xgamesfun/solana/play` | `solanaApp`: what a player does with a live economy, from a browser app |
|
|
170
|
+
| `@xgamesfun/solana/results` | results and vouchers a game's server signs |
|
|
171
|
+
| `@xgamesfun/solana/chain/*` | browser-safe Solana primitives: base58, Borsh, transactions, PDAs, tokens, Core |
|
|
172
|
+
| `@xgamesfun/solana/idl.json` | the program's IDL, the authority for account and argument layouts |
|
|
173
|
+
| `@xgamesfun/solana/release.json` | the program build, IDL and client this release is pinned to |
|
|
150
174
|
|
|
151
175
|
The client, chain and play modules run in browsers and Node 22 or later, with no bundler required. The package
|
|
152
176
|
ships TypeScript declarations for every entry point.
|
|
153
177
|
|
|
178
|
+
## Questions people ask
|
|
179
|
+
|
|
180
|
+
**How do I make a loot box, gacha or pack opening on Solana without writing a smart contract?** Describe it as a box
|
|
181
|
+
in a spec (its price, its outcomes and their odds, what each one pays), run `xsolana check` and `xsolana simulate`, then
|
|
182
|
+
approve it in your wallet with `xsolana approve`. Rolls come from MagicBlock's VRF, and a buyer is refunded if the
|
|
183
|
+
oracle never answers. [`examples/`](examples) has whole worked economies.
|
|
184
|
+
|
|
185
|
+
**How do I add staking to an NFT collection, even one I didn't mint?** A staking farm takes any collection, with lock
|
|
186
|
+
tiers, boosts and fixed or shared reward rates. The project needs no authority over the NFTs: see
|
|
187
|
+
[`docs/nfts.md`](docs/nfts.md).
|
|
188
|
+
|
|
189
|
+
**Can I run a lottery, raffle or auction on Solana without deploying a program?** Yes: lotteries (including no-loss
|
|
190
|
+
ones), raffles with a Merkle allowlist, and auctions that refund the outbid at once are all routes in the same spec.
|
|
191
|
+
|
|
192
|
+
**Is there an MCP server or Claude Code plugin for building on Solana?** Yes: the `xsolana` plugin for Claude Code and
|
|
193
|
+
Codex, and `npx -y --package=@xgamesfun/solana@0.25.2 xsolana mcp` for any MCP client. It is listed in the MCP
|
|
194
|
+
Registry as `io.github.XGamesFun/solana-kit`.
|
|
195
|
+
|
|
196
|
+
**How is this different from writing my own Anchor program?** Nothing is written, deployed or upgraded per project:
|
|
197
|
+
every project's rules are checked by the one program, and its tests run every instruction against that program in
|
|
198
|
+
LiteSVM. The trade-off is that an economy can only be what the spec can say ([known limits](docs/program.md#known-limits)).
|
|
199
|
+
|
|
200
|
+
**Does it run on mainnet?** Not yet: devnet only until the program is independently audited.
|
|
201
|
+
|
|
154
202
|
## Documentation
|
|
155
203
|
|
|
156
|
-
|
|
|
157
|
-
|
|
|
158
|
-
| [`docs/
|
|
159
|
-
| [`docs/
|
|
160
|
-
| [`docs/
|
|
161
|
-
| [`
|
|
162
|
-
| [`
|
|
163
|
-
| [`
|
|
164
|
-
| [`
|
|
204
|
+
| | |
|
|
205
|
+
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
206
|
+
| [`docs/tutorial-loot-box.md`](docs/tutorial-loot-box.md) | a loot box from one sentence to going live, step by step |
|
|
207
|
+
| [`docs/program.md`](docs/program.md) | the program: who signs, accounts, every instruction, fees, limits, receipts, costs |
|
|
208
|
+
| [`docs/nfts.md`](docs/nfts.md) | NFTs and tokens, with and without the project's authority |
|
|
209
|
+
| [`docs/harnesses.md`](docs/harnesses.md) | how the plugin reaches Claude Code and Codex, and the release checklist |
|
|
210
|
+
| [`skills/xsolana-economy`](skills/xsolana-economy/SKILL.md) | the skill an agent follows |
|
|
211
|
+
| [`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 |
|
|
212
|
+
| [`examples/`](examples) | whole economies that check and go live in the tests; `ideas.json`, 112 real economies rated |
|
|
213
|
+
| [`evals/`](evals/README.md) | fresh-session evals of agents building on loot |
|
|
214
|
+
| [`CHANGELOG.md`](CHANGELOG.md), [`SECURITY.md`](SECURITY.md) | versions, the trust model, reporting a vulnerability |
|
|
165
215
|
|
|
166
216
|
## Layout
|
|
167
217
|
|
package/bin/xsolana.mjs
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// the same answer, for an agent (or a person) without MCP.
|
|
4
4
|
//
|
|
5
5
|
// xsolana mcp serve the tools over stdio (what a plugin's MCP config runs)
|
|
6
|
+
// xsolana mcp --http [--port n] [--host h] serve the hosted tools over HTTP at /mcp (127.0.0.1:8787 by default)
|
|
6
7
|
// xsolana guide [part] [--json] what an economy can be made of, and what loot cannot do
|
|
7
8
|
// 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)
|
|
9
|
+
// xsolana check|simulate|cost|explain|review|publish|status [file] [--json] one tool on a spec file (default xsolana.json)
|
|
9
10
|
// xsolana approve [file] [--port n] [--no-open] open the local page where the owner approves going live in their wallet
|
|
10
11
|
// xsolana connect [--agent claude|codex] [--scope user|project] [--local] [--remove] [--dry-run] register the MCP server and the skill
|
|
11
12
|
// xsolana tool --list | xsolana tool <name> '<json>' [--args-file f] [--json] any tool by name
|
|
@@ -25,31 +26,35 @@ const USAGE = `xsolana: the tools for loot, one shared Solana program for game a
|
|
|
25
26
|
xsolana simulate [file] the quick simulation
|
|
26
27
|
xsolana cost [file] what going live and each action cost
|
|
27
28
|
xsolana explain [file] the economy in plain words
|
|
29
|
+
xsolana review [file] what a cheater gets on each route, and what to fix before going live
|
|
28
30
|
xsolana publish [file] what going live does, and how to approve it
|
|
29
31
|
xsolana approve [file] approve going live in your own wallet (a local page; --port n, --no-open)
|
|
30
32
|
xsolana status [file] a live economy on chain
|
|
31
33
|
xsolana connect add the xsolana tools to Claude Code or Codex (--agent claude|codex, --scope user|project, --local, --remove, --dry-run)
|
|
32
34
|
xsolana mcp serve the tools over MCP (stdio)
|
|
35
|
+
xsolana mcp --http serve the hosted tools over HTTP at http://127.0.0.1:8787/mcp (--port n, --host h)
|
|
33
36
|
xsolana tool --list every tool; xsolana tool <name> '<json>' runs one (--args-file f)
|
|
34
37
|
xsolana version this package's xsolana version and program
|
|
35
38
|
|
|
36
|
-
A tool's command (guide, examples, check … status, tool) takes --json for its whole answer as JSON
|
|
39
|
+
A tool's command (guide, examples, check … status, tool) takes --json for its whole answer as JSON.
|
|
40
|
+
review exits 1 when it finds something high.`;
|
|
37
41
|
|
|
38
42
|
/** A failure the person caused (a bad flag, a missing file): said in one line, without a stack. */
|
|
39
43
|
class UsageError extends Error {}
|
|
40
44
|
|
|
41
45
|
/** The flags that take a value: `--name value` or `--name=value`. */
|
|
42
|
-
const VALUED = new Set(['--args-file', '--agent', '--scope', '--port']);
|
|
46
|
+
const VALUED = new Set(['--args-file', '--agent', '--scope', '--port', '--host']);
|
|
43
47
|
/** The flags each command takes; any other is refused. */
|
|
44
48
|
const TOOL_FLAGS = ['--json'];
|
|
45
49
|
const FLAGS = {
|
|
46
|
-
mcp: [],
|
|
50
|
+
mcp: ['--http', '--port', '--host'],
|
|
47
51
|
guide: TOOL_FLAGS,
|
|
48
52
|
examples: TOOL_FLAGS,
|
|
49
53
|
check: TOOL_FLAGS,
|
|
50
54
|
simulate: TOOL_FLAGS,
|
|
51
55
|
cost: TOOL_FLAGS,
|
|
52
56
|
explain: TOOL_FLAGS,
|
|
57
|
+
review: TOOL_FLAGS,
|
|
53
58
|
publish: TOOL_FLAGS,
|
|
54
59
|
status: TOOL_FLAGS,
|
|
55
60
|
approve: ['--port', '--no-open'],
|
|
@@ -105,8 +110,10 @@ async function main(argv) {
|
|
|
105
110
|
const { runTool } = await import('../js/mcp/server.mjs');
|
|
106
111
|
const r = await runTool(name, input, { cwd: process.cwd() });
|
|
107
112
|
const failed = Boolean(r.isError || r.ok === false);
|
|
113
|
+
// A review is a report whatever it finds: printed on stdout, its exit code saying whether a finding is high.
|
|
114
|
+
const report = name === 'xsolana_review' && !r.isError && !r.refusals;
|
|
108
115
|
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));
|
|
116
|
+
else (failed && !report ? console.error : console.log)(r.text ?? JSON.stringify(r, null, 2));
|
|
110
117
|
if (failed) process.exitCode = 1;
|
|
111
118
|
}
|
|
112
119
|
|
|
@@ -114,6 +121,12 @@ async function main(argv) {
|
|
|
114
121
|
|
|
115
122
|
switch (command) {
|
|
116
123
|
case 'mcp': {
|
|
124
|
+
if (flags.has('--http')) {
|
|
125
|
+
const { serveHttp } = await import('../js/mcp/http.mjs');
|
|
126
|
+
await serveHttp({ port: portOf(values.get('--port')) ?? 8787, host: values.get('--host') ?? '127.0.0.1' });
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (flags.has('--port') || flags.has('--host')) throw new UsageError('--port and --host go with --http.');
|
|
117
130
|
const { serveStdio } = await import('../js/mcp/server.mjs');
|
|
118
131
|
await serveStdio();
|
|
119
132
|
break;
|
|
@@ -128,6 +141,7 @@ async function main(argv) {
|
|
|
128
141
|
case 'simulate':
|
|
129
142
|
case 'cost':
|
|
130
143
|
case 'explain':
|
|
144
|
+
case 'review':
|
|
131
145
|
case 'publish':
|
|
132
146
|
case 'status':
|
|
133
147
|
await tool(`xsolana_${command}`, fileInput());
|
package/js/client/index.js
CHANGED
package/js/client/kit.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Instructions in @solana/kit's shape. The builders here return { programId, keys, data } (the shape of
|
|
2
|
+
// @solana/web3.js 1.x, which js/chain/tx.js compiles); an app built on @solana/kit takes { programAddress, accounts,
|
|
3
|
+
// data } instead, with each account's role as a number. These convert one to the other, with no dependency on Kit.
|
|
4
|
+
|
|
5
|
+
/** @solana/kit's AccountRole: bit 1 is a signer, bit 0 is writable. */
|
|
6
|
+
export const KIT_ACCOUNT_ROLE = Object.freeze({ READONLY: 0, WRITABLE: 1, READONLY_SIGNER: 2, WRITABLE_SIGNER: 3 });
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* One instruction from a builder here, as @solana/kit takes it: `appendTransactionMessageInstruction(toKitInstruction(ix), m)`.
|
|
10
|
+
* @param {{ programId: string, keys: { pubkey: string, isSigner: boolean, isWritable: boolean }[], data: Uint8Array }} ix
|
|
11
|
+
* @returns {{ programAddress: string, accounts: { address: string, role: number }[], data: Uint8Array }}
|
|
12
|
+
*/
|
|
13
|
+
export function toKitInstruction(ix) {
|
|
14
|
+
return {
|
|
15
|
+
programAddress: ix.programId,
|
|
16
|
+
accounts: ix.keys.map(k => ({ address: k.pubkey, role: (k.isSigner ? 2 : 0) | (k.isWritable ? 1 : 0) })),
|
|
17
|
+
data: ix.data,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A @solana/kit instruction in the shape this package's transactions take (js/chain/tx.js, js/play).
|
|
23
|
+
* @param {{ programAddress: string, accounts?: { address: string, role: number }[], data?: Uint8Array }} ix
|
|
24
|
+
* @returns {{ programId: string, keys: { pubkey: string, isSigner: boolean, isWritable: boolean }[], data: Uint8Array }}
|
|
25
|
+
*/
|
|
26
|
+
export function fromKitInstruction(ix) {
|
|
27
|
+
return {
|
|
28
|
+
programId: ix.programAddress,
|
|
29
|
+
keys: (ix.accounts ?? []).map(a => ({
|
|
30
|
+
pubkey: a.address,
|
|
31
|
+
isSigner: (a.role & 2) !== 0,
|
|
32
|
+
isWritable: (a.role & 1) !== 0,
|
|
33
|
+
})),
|
|
34
|
+
data: ix.data ?? new Uint8Array(),
|
|
35
|
+
};
|
|
36
|
+
}
|
package/js/client/random.js
CHANGED
|
@@ -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
|
|
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
|
|
package/js/economy/check.mjs
CHANGED
|
@@ -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)
|
package/js/economy/cost.mjs
CHANGED
|
@@ -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:
|
|
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
|
*/
|
package/js/economy/explain.mjs
CHANGED
|
@@ -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 ?? []) {
|
package/js/economy/guide.mjs
CHANGED
|
@@ -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
|
];
|
package/js/economy/live.mjs
CHANGED
|
@@ -137,7 +137,8 @@ export function changedParts(economy) {
|
|
|
137
137
|
const only = new Set();
|
|
138
138
|
const fixed = [];
|
|
139
139
|
const removed = [];
|
|
140
|
-
|
|
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] ?? []) {
|