@xgamesfun/solana 0.25.3 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -228
- package/js/approve/details.mjs +52 -2
- package/js/approve/live-check.mjs +0 -15
- package/js/approve/server.mjs +5 -4
- package/js/chain/token2022.js +3 -2
- package/js/client/accounts.js +67 -25
- package/js/client/grant.js +7 -2
- package/js/client/idl/loot.js +1 -1
- package/js/client/idl/loot.json +714 -182
- package/js/client/ix.js +617 -113
- package/js/client/sizes.js +35 -3
- package/js/economy/check.mjs +655 -242
- package/js/economy/cost.mjs +2 -4
- package/js/economy/explain.mjs +16 -2
- package/js/economy/guide.mjs +12 -12
- package/js/economy/model.mjs +133 -14
- package/js/economy/plan.mjs +36 -11
- package/js/economy/review.mjs +50 -8
- package/js/economy/schema.json +73 -29
- package/js/economy/simulate.mjs +116 -13
- package/js/mcp/tools.mjs +2 -2
- package/js/play/index.js +847 -296
- package/package.json +1 -1
- package/release.json +5 -5
- package/skills/xsolana-economy/SKILL.md +12 -4
- package/skills/xsolana-economy/references/after-launch.md +1 -1
- package/skills/xsolana-economy/references/apps.md +11 -2
- package/skills/xsolana-economy/references/sentence-to-spec.md +3 -3
- package/skills/xsolana-economy/references/telling-the-person.md +21 -4
- package/skills/xsolana-economy/references/trust-and-cheating.md +15 -5
- package/types/chain/token2022.d.ts +3 -2
- package/types/client/accounts.d.ts +34 -11
- package/types/client/idl/loot.d.ts +25 -4
- package/types/client/ix.d.ts +217 -45
- package/types/economy/check.d.mts +5 -0
- package/types/economy/model.d.mts +75 -12
- package/types/economy/plan.d.mts +22 -1
- package/types/economy/simulate.d.mts +12 -1
- package/types/mcp/server.d.mts +12 -1
- package/types/mcp/tools.d.mts +12 -1
- package/types/play/index.d.ts +210 -144
package/README.md
CHANGED
|
@@ -1,96 +1,52 @@
|
|
|
1
|
-
# solana-kit
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
>
|
|
19
|
-
>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
named), lottery draws, auction ends, refunds (anyone), contest results (the authority or its deciders) and schedule
|
|
51
|
-
runs (the project's keys). A run nobody sends on time is missed, except a sealed schedule's fixed amount. Apps built
|
|
52
|
-
with `@xgamesfun/solana/play` send the steps a player needs. Don't count on a hosted keeper.
|
|
53
|
-
- **The rules are strict.** A prize is a fixed amount: a jackpot that grows is a lottery's pot. A lottery says where its
|
|
54
|
-
places are paid from: a pot that carries over (gifts and what earlier rounds left count), or each round's own ticket
|
|
55
|
-
money, which nothing from outside can change. A prize never shifts to a later round. A draw uses the odds
|
|
56
|
-
as written. When an outcome runs out, a box either stops selling (`stopSales`, the default) or keeps selling and
|
|
57
|
-
draws among what's left (`drawLeft`, which lets a player empty a common outcome to make a rare one likelier).
|
|
58
|
-
- **Chance comes from an oracle.** MagicBlock's VRF answers every random roll and draw. If it stops answering, rolls wait. An
|
|
59
|
-
order still unanswered after 7 days can be refunded. Once a roll has waited 10 minutes, a buyer who sees a late
|
|
60
|
-
answer coming can take a refund instead.
|
|
61
|
-
- **Fees and rent.** The platform sets two flat fees: by default 0.002 SOL a player action and 0.00001 SOL a project
|
|
62
|
-
action. Each is capped at 0.01 SOL, and a raise waits 7 days. Accounts cost rent: going live, and each settle that
|
|
63
|
-
opens or mints something. `xsolana cost` gives the totals. The platform can suspend purchases, but it never stops
|
|
64
|
-
payouts, refunds or unstakes (the one exception is a recipe that charges).
|
|
65
|
-
- **Some things aren't built,** and some limits are kept by design. Read
|
|
66
|
-
[Known limits](docs/program.md#known-limits) before promising players something unusual.
|
|
67
|
-
- **The law is yours to check.** The checker's legal flags (chance at money, resellable random items, revenue
|
|
68
|
-
sharing) are reminders, not advice. A spec with no flag isn't therefore legal.
|
|
69
|
-
|
|
70
|
-
## What an economy can be made of
|
|
71
|
-
|
|
72
|
-
- payments and splits, in tokens or with an NFT
|
|
73
|
-
- shops (a purchase in one transaction) and loot boxes
|
|
74
|
-
- pools and timed schedules, sealed or not
|
|
75
|
-
- lotteries and raffles, including no-loss lotteries
|
|
76
|
-
- prizes of any kind on every route, random or not: items, tokens, and NFTs the project holds in a stash
|
|
77
|
-
- an NFT as the price on every route that takes money
|
|
78
|
-
- staking farms with lock tiers, boosts, early exits, fixed or shared rates and terms
|
|
79
|
-
- crafting recipes
|
|
80
|
-
- holds and contests, with prizes by rank or by stake
|
|
81
|
-
- auctions: the highest bid wins, the outbid refunded at once
|
|
82
|
-
- listings for sale or rent, and bids and loans between players
|
|
83
|
-
- perks, staking and holds for any NFT, whether the project has authority over it or not
|
|
84
|
-
- allowlists of any size (a Merkle list) on every gated route
|
|
85
|
-
- capped rewards and grants that a server vouches for
|
|
86
|
-
- rules on what a collectible says
|
|
87
|
-
|
|
88
|
-
Every move leaves a public receipt. [`docs/program.md`](docs/program.md) describes how the program behaves.
|
|
89
|
-
|
|
90
|
-
## Use it from an AI coding agent
|
|
91
|
-
|
|
92
|
-
The MCP server and the skill let Claude Code, Codex or any MCP agent turn a one-line idea into a checked, simulated
|
|
93
|
-
economy and hand you the command that puts it live in your own wallet.
|
|
1
|
+
# solana-kit
|
|
2
|
+
|
|
3
|
+
**Build loot boxes, shops, lotteries, auctions, NFT staking and rewards on Solana without writing a smart contract.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@xgamesfun/solana)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](#good-to-know)
|
|
8
|
+
[](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.XGamesFun/solana-kit)
|
|
9
|
+
|
|
10
|
+
You describe your game's or community's economy in a small JSON file: what players buy, open, stake, win or craft.
|
|
11
|
+
The tools check it, show you where the money goes, tell you what it costs, and then you put it live by approving it in
|
|
12
|
+
your own wallet. It runs on **loot**, one Solana program that every project shares, so there is no Anchor or Rust to
|
|
13
|
+
write, deploy or audit yourself.
|
|
14
|
+
|
|
15
|
+
Most people never touch the JSON: they ask an AI agent (Claude Code, Codex, Cursor, ChatGPT or Claude) for _"a fishing
|
|
16
|
+
game where each cast costs 0.02 SOL, with a rare golden fish and a monthly draw"_ and the agent does the rest.
|
|
17
|
+
|
|
18
|
+
> **Devnet only for now.** The program is not yet independently audited, so nothing here is for real money. The
|
|
19
|
+
> tools are provided as is: read [DISCLAIMER.md](DISCLAIMER.md) before you use them.
|
|
20
|
+
|
|
21
|
+
## What you can build
|
|
22
|
+
|
|
23
|
+
| Build | For example |
|
|
24
|
+
| ----------------------------- | -------------------------------------------------------------------------------- |
|
|
25
|
+
| **Shops** | A potion for 0.01 SOL, a skin paid in your own token, an item that costs an NFT |
|
|
26
|
+
| **Loot boxes, gacha, packs** | A crate with a 1% legendary, a five-card pack with a guaranteed rare, gacha pity |
|
|
27
|
+
| **Lotteries and raffles** | A weekly draw, a no-loss lottery, a raffle for an NFT with an allowlist |
|
|
28
|
+
| **Auctions** | Highest bid wins, and anyone outbid gets their money back at once |
|
|
29
|
+
| **NFT staking** | Lock tiers and boosts, for your collection or any collection you didn't mint |
|
|
30
|
+
| **Crafting** | Burn three common gems to forge a rare one |
|
|
31
|
+
| **Contests and predictions** | Pick a side, prizes by rank or by stake |
|
|
32
|
+
| **Rewards, quests, airdrops** | Capped rewards your game's server vouches for, daily chests, holder-only drops |
|
|
33
|
+
| **Trading between players** | Listings for sale or rent, offers, and loans against an NFT |
|
|
34
|
+
|
|
35
|
+
Every move leaves a public receipt on chain. Random rolls come from MagicBlock's verifiable randomness.
|
|
36
|
+
|
|
37
|
+
## How it works
|
|
38
|
+
|
|
39
|
+
1. **Describe it.** In a sentence to your agent, or as a spec file (`xsolana.json`).
|
|
40
|
+
2. **Check it.** `check` refuses anything the program would refuse; `simulate` shows where the money goes, day by
|
|
41
|
+
day; `cost` prices going live and each player action; `review` shows what a cheater could get.
|
|
42
|
+
3. **Approve it.** A page opens on your own computer, lists every transaction, and you approve them in your wallet.
|
|
43
|
+
Nothing here ever signs for you or asks for a key.
|
|
44
|
+
|
|
45
|
+
New here? The [loot box tutorial](docs/tutorial-loot-box.md) walks through all three with real output.
|
|
46
|
+
|
|
47
|
+
## Get started
|
|
48
|
+
|
|
49
|
+
### With an AI agent
|
|
94
50
|
|
|
95
51
|
**Claude Code**
|
|
96
52
|
|
|
@@ -106,165 +62,126 @@ codex plugin marketplace add XGamesFun/solana-kit
|
|
|
106
62
|
codex plugin add xsolana@xgamesfun
|
|
107
63
|
```
|
|
108
64
|
|
|
109
|
-
**
|
|
110
|
-
by URL). It is the hosted server (below, "Over HTTP"): nothing to install, and going live stays your own approval on your
|
|
111
|
-
computer. More at [xgames.fun/solana](https://xgames.fun/solana).
|
|
65
|
+
**claude.ai, ChatGPT or Claude Desktop:** add `https://xgames.fun/mcp` as a custom connector. Nothing to install.
|
|
112
66
|
|
|
113
|
-
**
|
|
114
|
-
`npx -y --package=@xgamesfun/solana@0.25.3 xsolana mcp`. Or let `npx -y @xgamesfun/solana@0.25.3 connect` register it, with
|
|
115
|
-
the skill, for Claude Code and Codex.
|
|
67
|
+
**Cursor, Cline, opencode or any MCP client:** run `npx -y --package=@xgamesfun/solana@0.26.0 xsolana mcp`.
|
|
116
68
|
|
|
117
|
-
**
|
|
118
|
-
opencode…): `npx skills add XGamesFun/solana-kit` finds it and installs it for the agents you have. Without the MCP
|
|
119
|
-
server it runs the same tools through `npx`. Each GitHub release also carries `xsolana-economy.zip`, the skill as one
|
|
120
|
-
file for apps that take a skill as an upload (the Claude and ChatGPT desktop apps).
|
|
69
|
+
**Any agent that reads skills** (Copilot, Gemini CLI, Windsurf…): `npx skills add XGamesFun/solana-kit`.
|
|
121
70
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
`http://127.0.0.1:8787/mcp`, MCP's Streamable HTTP transport. `@xgamesfun/solana/mcp/http` exports the same handler
|
|
125
|
-
(`mcpHttp(request) → response`) for a hosted endpoint: it reads no files, and the agent hands the person the spec and the
|
|
126
|
-
`approve` command to go live.
|
|
71
|
+
Then ask for the economy you want. More ways in, such as the skill as a zip for desktop apps or the server over HTTP,
|
|
72
|
+
are in [docs/harnesses.md](docs/harnesses.md).
|
|
127
73
|
|
|
128
|
-
|
|
129
|
-
monthly draw"_.
|
|
74
|
+
### From a terminal
|
|
130
75
|
|
|
131
|
-
|
|
132
|
-
|
|
76
|
+
```bash
|
|
77
|
+
# find a worked example close to your idea
|
|
78
|
+
npx -y @xgamesfun/solana@0.26.0 examples shop with loot boxes
|
|
133
79
|
|
|
134
|
-
|
|
80
|
+
# does it work? where does the money go? what does it cost?
|
|
81
|
+
npx -y @xgamesfun/solana@0.26.0 check xsolana.json
|
|
82
|
+
npx -y @xgamesfun/solana@0.26.0 simulate xsolana.json
|
|
83
|
+
npx -y @xgamesfun/solana@0.26.0 cost xsolana.json
|
|
135
84
|
|
|
136
|
-
|
|
85
|
+
# the whole economy in plain words, and what a cheater could get
|
|
86
|
+
npx -y @xgamesfun/solana@0.26.0 explain xsolana.json
|
|
87
|
+
npx -y @xgamesfun/solana@0.26.0 review xsolana.json
|
|
137
88
|
|
|
138
|
-
|
|
139
|
-
npx -y @xgamesfun/solana@0.
|
|
140
|
-
npx -y @xgamesfun/solana@0.25.3 check xsolana.json # refusals and warnings
|
|
141
|
-
npx -y @xgamesfun/solana@0.25.3 simulate xsolana.json # where the money goes, day by day
|
|
142
|
-
npx -y @xgamesfun/solana@0.25.3 cost xsolana.json # what going live and each action cost
|
|
143
|
-
npx -y @xgamesfun/solana@0.25.3 explain xsolana.json # the economy in plain words
|
|
144
|
-
npx -y @xgamesfun/solana@0.25.3 review xsolana.json # what a cheater gets on each route (exits 1 on a high finding)
|
|
145
|
-
npx -y @xgamesfun/solana@0.25.3 approve xsolana.json # go live, approved in your own wallet
|
|
89
|
+
# go live, approved in your own wallet
|
|
90
|
+
npx -y @xgamesfun/solana@0.26.0 approve xsolana.json
|
|
146
91
|
```
|
|
147
92
|
|
|
148
|
-
|
|
149
|
-
is newest under the name.
|
|
93
|
+
Each command names an exact version, so you always run the release you meant to. `xsolana help` lists them all.
|
|
150
94
|
|
|
151
|
-
|
|
95
|
+
### In your app
|
|
152
96
|
|
|
153
97
|
```bash
|
|
154
98
|
npm install @xgamesfun/solana
|
|
155
99
|
```
|
|
156
100
|
|
|
157
101
|
```js
|
|
158
|
-
import
|
|
159
|
-
const ix = loot.openBoxIx({ player, project, box, nonce }); // one builder per instruction, no Solana SDK
|
|
160
|
-
```
|
|
102
|
+
import { solanaApp, walletStandardSigner } from '@xgamesfun/solana/play';
|
|
161
103
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
side: `sponsorSigner` dry-runs and checks every transaction before it signs, under the project's own per-player budget.
|
|
165
|
-
|
|
166
|
-
Building on `@solana/kit`? `loot.toKitInstruction(ix)` gives any builder's instruction in Kit's shape
|
|
167
|
-
(`{ programAddress, accounts, data }`), and `loot.fromKitInstruction` turns one back.
|
|
168
|
-
|
|
169
|
-
| Import | What |
|
|
170
|
-
| -------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
171
|
-
| `@xgamesfun/solana` | the economy tools: `check`, `simulate`, `plan`, `cost`, `explainEconomy`, `review`, `model` |
|
|
172
|
-
| `@xgamesfun/solana/client` | every PDA, one builder per instruction, account and receipt decoders, grant vouchers |
|
|
173
|
-
| `@xgamesfun/solana/play` | `solanaApp`: what a player does with a live economy, from a browser app |
|
|
174
|
-
| `@xgamesfun/solana/results` | results and vouchers a game's server signs |
|
|
175
|
-
| `@xgamesfun/solana/chain/*` | browser-safe Solana primitives: base58, Borsh, transactions, PDAs, tokens, Core |
|
|
176
|
-
| `@xgamesfun/solana/idl.json` | the program's IDL, the authority for account and argument layouts |
|
|
177
|
-
| `@xgamesfun/solana/release.json` | the program build, IDL and client this release is pinned to |
|
|
178
|
-
|
|
179
|
-
The client, chain and play modules run in browsers and Node 22 or later, with no bundler required. The package
|
|
180
|
-
ships TypeScript declarations for every entry point.
|
|
181
|
-
|
|
182
|
-
## Questions people ask
|
|
183
|
-
|
|
184
|
-
**How do I make a loot box, gacha or pack opening on Solana without writing a smart contract?** Describe it as a box
|
|
185
|
-
in a spec (its price, its outcomes and their odds, what each one pays), run `xsolana check` and `xsolana simulate`, then
|
|
186
|
-
approve it in your wallet with `xsolana approve`. Rolls come from MagicBlock's VRF, and a buyer is refunded if the
|
|
187
|
-
oracle never answers. [`examples/`](examples) has whole worked economies.
|
|
188
|
-
|
|
189
|
-
**How do I add staking to an NFT collection, even one I didn't mint?** A staking farm takes any collection, with lock
|
|
190
|
-
tiers, boosts and fixed or shared reward rates. The project needs no authority over the NFTs: see
|
|
191
|
-
[`docs/nfts.md`](docs/nfts.md).
|
|
192
|
-
|
|
193
|
-
**Can I run a lottery, raffle or auction on Solana without deploying a program?** Yes: lotteries (including no-loss
|
|
194
|
-
ones), raffles with a Merkle allowlist, and auctions that refund the outbid at once are all routes in the same spec.
|
|
195
|
-
|
|
196
|
-
**Is there an MCP server or Claude Code plugin for building on Solana?** Yes: the `xsolana` plugin for Claude Code and
|
|
197
|
-
Codex, the hosted server at `https://xgames.fun/mcp` for chat apps, and `npx -y --package=@xgamesfun/solana@0.25.3 xsolana mcp` for any MCP client. It is listed in the MCP
|
|
198
|
-
Registry as `io.github.XGamesFun/solana-kit`.
|
|
199
|
-
|
|
200
|
-
**How is this different from writing my own Anchor program?** Nothing is written, deployed or upgraded per project:
|
|
201
|
-
every project's rules are checked by the one program, and its tests run every instruction against that program in
|
|
202
|
-
LiteSVM. The trade-off is that an economy can only be what the spec can say ([known limits](docs/program.md#known-limits)).
|
|
203
|
-
|
|
204
|
-
**Does it run on mainnet?** Not yet: devnet only until the program is independently audited.
|
|
205
|
-
|
|
206
|
-
## Documentation
|
|
207
|
-
|
|
208
|
-
| | |
|
|
209
|
-
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
210
|
-
| [`docs/tutorial-loot-box.md`](docs/tutorial-loot-box.md) | a loot box from one sentence to going live, step by step |
|
|
211
|
-
| [`docs/program.md`](docs/program.md) | the program: who signs, accounts, every instruction, fees, limits, receipts, costs |
|
|
212
|
-
| [`docs/nfts.md`](docs/nfts.md) | NFTs and tokens, with and without the project's authority |
|
|
213
|
-
| [`docs/harnesses.md`](docs/harnesses.md) | how the plugin reaches Claude Code and Codex, and the release checklist |
|
|
214
|
-
| [`skills/xsolana-economy`](skills/xsolana-economy/SKILL.md) | the skill an agent follows |
|
|
215
|
-
| [`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 |
|
|
216
|
-
| [`examples/`](examples) | whole economies that check and go live in the tests; `ideas.json`, 112 real economies rated |
|
|
217
|
-
| [`evals/`](evals/README.md) | fresh-session evals of agents building on loot |
|
|
218
|
-
| [`CHANGELOG.md`](CHANGELOG.md), [`SECURITY.md`](SECURITY.md) | versions, the trust model, reporting a vulnerability |
|
|
219
|
-
|
|
220
|
-
## Layout
|
|
221
|
-
|
|
222
|
-
| Path | What |
|
|
223
|
-
| --------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
224
|
-
| `js/chain/` | browser-safe Solana primitives, no SDK |
|
|
225
|
-
| `js/client/` | the client; `idl/` is the program's IDL |
|
|
226
|
-
| `js/economy/` | the economy spec (version 2, `schema.json`), checker, simulation, setup plan, cost, explainer, guide |
|
|
227
|
-
| `js/play/`, `js/results/` | a player's side of a live economy; results a server vouches for |
|
|
228
|
-
| `js/approve/`, `js/mcp/`, `js/connect/` | the approval page, the MCP server and its tools, `xsolana connect` |
|
|
229
|
-
| `js/sim/`, `js/test/` | LiteSVM running the real program; the tests and their pinned fixtures |
|
|
230
|
-
| `bin/xsolana.mjs` | the CLI |
|
|
231
|
-
| `skills/`, `plugin/` | the skill (copied to `plugin/skills`, `.claude/skills`, `.agents/skills`) and the plugin |
|
|
232
|
-
| `scripts/` | fixtures, skill sync, IDL sync, release manifest, a devnet end-to-end run |
|
|
233
|
-
| `release.json` | the program binary, IDL and client hashes this version is pinned to |
|
|
234
|
-
|
|
235
|
-
## Development
|
|
236
|
-
|
|
237
|
-
Node 22 or later.
|
|
104
|
+
// spec: your live xsolana.json; wallet: the player's own (Phantom, Solflare, Backpack…)
|
|
105
|
+
const app = solanaApp({ spec, wallet: walletStandardSigner(wallet, account) });
|
|
238
106
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
npm test # every instruction the client builds, and every example's whole setup, against the real program in LiteSVM
|
|
243
|
-
npm run lint # ESLint and Prettier (npm run format fixes the formatting)
|
|
244
|
-
npm run check # fixtures, skill copies and release.json in step, lint, then the tests
|
|
107
|
+
// the player sees the price, then approves the purchase in their wallet
|
|
108
|
+
const shop = await app.shop();
|
|
109
|
+
await app.buy('potion', { listing: shop.find(item => item.key === 'potion') });
|
|
245
110
|
```
|
|
246
111
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
|
252
|
-
|
|
|
253
|
-
| `
|
|
254
|
-
|
|
|
255
|
-
| `
|
|
256
|
-
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
112
|
+
No Solana SDK needed: it runs in the browser and in Node 22 or later, with TypeScript types included. The player
|
|
113
|
+
module covers buying, opening boxes, staking, crafting, tickets, bids and claims;
|
|
114
|
+
[apps.md](skills/xsolana-economy/references/apps.md) shows a whole page.
|
|
115
|
+
|
|
116
|
+
| Import | What it gives you |
|
|
117
|
+
| -------------------------------- | ------------------------------------------------------------------------------------- |
|
|
118
|
+
| `@xgamesfun/solana/play` | everything a player does with a live economy, from a web page |
|
|
119
|
+
| `@xgamesfun/solana/client` | one builder per instruction, every address, account and receipt decoders |
|
|
120
|
+
| `@xgamesfun/solana/sponsor` | your server pays players' fees (never their price), so players with no SOL can play |
|
|
121
|
+
| `@xgamesfun/solana/results` | results and vouchers your game's server signs |
|
|
122
|
+
| `@xgamesfun/solana` | the tools themselves: `check`, `simulate`, `cost`, `review`, `explainEconomy`, `plan` |
|
|
123
|
+
| `@xgamesfun/solana/mcp/http` | the MCP server as a web handler, to host it yourself |
|
|
124
|
+
| `@xgamesfun/solana/chain/*` | small browser-safe Solana building blocks: base58, Borsh, transactions, tokens, Core |
|
|
125
|
+
| `@xgamesfun/solana/idl.json` | the program's IDL |
|
|
126
|
+
| `@xgamesfun/solana/release.json` | the exact program build this release is for |
|
|
127
|
+
|
|
128
|
+
Using `@solana/kit`? `loot.toKitInstruction(ix)` turns any builder's instruction into Kit's shape.
|
|
129
|
+
|
|
130
|
+
## Good to know
|
|
131
|
+
|
|
132
|
+
- **Devnet only, not yet audited.** Use a devnet wallet. Mainnet waits for an independent audit.
|
|
133
|
+
- **Upgrades can mean setting up again.** No program version so far has carried accounts over. Pin the package version.
|
|
134
|
+
- **Players trust the project.** Your keys can mint and move your project's pools. The program protects players from
|
|
135
|
+
each other and from mistakes, not from the project they chose.
|
|
136
|
+
- **The chain can't see your game.** Wins and scores are vouched for by your server; checking that a player really did
|
|
137
|
+
it is your job. `review` shows what a cheater gets on each route.
|
|
138
|
+
- **Nothing runs by itself.** Draws, settles and schedule runs are sent by someone, usually your app.
|
|
139
|
+
- **Fees:** 0.002 SOL per player action and 0.00001 SOL per project action, plus rent. `cost` gives the totals.
|
|
140
|
+
|
|
141
|
+
The full list, with the details behind each, is in [Before you use it](docs/before-you-use.md). Some things aren't
|
|
142
|
+
built yet: read the [known limits](docs/program.md#known-limits) before promising players something unusual.
|
|
143
|
+
|
|
144
|
+
## Questions
|
|
145
|
+
|
|
146
|
+
**How do I make a loot box or gacha on Solana without writing a smart contract?** Describe it as a box (its price,
|
|
147
|
+
its outcomes and their odds), run `check` and `simulate`, then `approve` it in your wallet. If the randomness oracle
|
|
148
|
+
never answers, the buyer can get a refund. See the [tutorial](docs/tutorial-loot-box.md) and the
|
|
149
|
+
[examples](examples).
|
|
150
|
+
|
|
151
|
+
**Can I add staking to an NFT collection I didn't mint?** Yes. A staking farm takes any collection, with lock tiers,
|
|
152
|
+
boosts and reward rates, and needs no authority over the NFTs. See [NFTs and tokens](docs/nfts.md).
|
|
153
|
+
|
|
154
|
+
**Can I run a lottery, raffle or auction without deploying a program?** Yes: lotteries (no-loss ones too), raffles
|
|
155
|
+
with allowlists, and auctions that refund the outbid at once are all part of the same spec.
|
|
156
|
+
|
|
157
|
+
**Is there an MCP server or Claude Code plugin for this?** Yes: the `xsolana` plugin for Claude Code and Codex, the
|
|
158
|
+
hosted server at `https://xgames.fun/mcp`, and the local server for any MCP client. It's in the MCP Registry as
|
|
159
|
+
`io.github.XGamesFun/solana-kit`.
|
|
160
|
+
|
|
161
|
+
**How is this different from writing my own Anchor program?** Nothing is written or deployed per project: one shared
|
|
162
|
+
program enforces every project's rules, and it is tested instruction by instruction. The trade-off is that you can
|
|
163
|
+
build what the spec can describe, which is a lot, but not everything.
|
|
164
|
+
|
|
165
|
+
**Can players with no SOL play?** Yes, if you sponsor their fees from your own server
|
|
166
|
+
(`@xgamesfun/solana/sponsor`). They still pay any price themselves.
|
|
167
|
+
|
|
168
|
+
## Learn more
|
|
169
|
+
|
|
170
|
+
- [Loot box tutorial](docs/tutorial-loot-box.md): one sentence to going live, step by step
|
|
171
|
+
- [Examples](examples): whole economies that are checked and put live in the tests
|
|
172
|
+
- [How the program works](docs/program.md): who signs, every instruction, fees, limits, receipts
|
|
173
|
+
- [NFTs and tokens](docs/nfts.md): with and without the project's authority
|
|
174
|
+
- [Trust and cheating](skills/xsolana-economy/references/trust-and-cheating.md): what your server must check
|
|
175
|
+
- [The agent skill](skills/xsolana-economy/SKILL.md): what an AI agent follows to build an economy
|
|
176
|
+
- [Agents and harnesses](docs/harnesses.md): every way to reach the tools, and how releases are made
|
|
177
|
+
- [Changelog](CHANGELOG.md) · [Security](SECURITY.md) · [xgames.fun/solana](https://xgames.fun/solana)
|
|
261
178
|
|
|
262
179
|
## Contributing
|
|
263
180
|
|
|
264
|
-
Issues and pull requests are welcome
|
|
265
|
-
|
|
181
|
+
Issues and pull requests are welcome. [CONTRIBUTING.md](CONTRIBUTING.md) has the setup (`npm ci`, `npm run fixtures`,
|
|
182
|
+
`npm run check`), the repository's layout and how to send a change. Report a vulnerability privately, as
|
|
183
|
+
[SECURITY.md](SECURITY.md) describes.
|
|
266
184
|
|
|
267
185
|
## License
|
|
268
186
|
|
|
269
|
-
[Apache-2.0](LICENSE). See [NOTICE](NOTICE) and [DISCLAIMER.md](DISCLAIMER.md)
|
|
270
|
-
own risk.
|
|
187
|
+
[Apache-2.0](LICENSE). See [NOTICE](NOTICE) and [DISCLAIMER.md](DISCLAIMER.md).
|
package/js/approve/details.mjs
CHANGED
|
@@ -6,10 +6,31 @@ import { decodeBorsh } from '../chain/borsh.js';
|
|
|
6
6
|
import { equalBytes, toHex } from '../chain/bytes.js';
|
|
7
7
|
import { accountKind, decode, IDL, instructionOf, keyLeaf, merkleRoot } from '../client/index.js';
|
|
8
8
|
import { LOOT_PROGRAM, pda } from '../client/pda.js';
|
|
9
|
+
import { dateWords, FAR_SECONDS } from '../economy/check.mjs';
|
|
9
10
|
import { fromUnits } from '../economy/model.mjs';
|
|
10
11
|
import { grantArgs } from '../economy/plan.mjs';
|
|
11
12
|
|
|
12
13
|
const NOBODY = '11111111111111111111111111111111';
|
|
14
|
+
/** What places of tokens from a short pool get, by their `when_short`. */
|
|
15
|
+
const SHORT = {
|
|
16
|
+
InOrder: 'whole prizes, first place first, while the pool covers one',
|
|
17
|
+
InProportion: 'every place the same share of its prize',
|
|
18
|
+
Nothing: 'no place gets anything, and the pool keeps it',
|
|
19
|
+
};
|
|
20
|
+
/** What a far time is, by the field that holds it. */
|
|
21
|
+
const FAR = {
|
|
22
|
+
draws_at: 'The first draw',
|
|
23
|
+
ends_at: 'The auction ends',
|
|
24
|
+
result_by: 'The result is due',
|
|
25
|
+
next_at: 'The first run',
|
|
26
|
+
};
|
|
27
|
+
/** A number of seconds in words: "1 day", "36 hours", "90 seconds". */
|
|
28
|
+
const duration = v => {
|
|
29
|
+
const n = Number(v);
|
|
30
|
+
if (n % 86_400 === 0) return `${n / 86_400} day${n === 86_400 ? '' : 's'}`;
|
|
31
|
+
if (n % 3_600 === 0) return `${n / 3_600} hours`;
|
|
32
|
+
return `${n} seconds`;
|
|
33
|
+
};
|
|
13
34
|
const ZERO = new Uint8Array(32);
|
|
14
35
|
|
|
15
36
|
/** An instruction's accounts by name, nested groups flattened, in the order the program takes them. */
|
|
@@ -32,11 +53,12 @@ function readLootInstruction(ix) {
|
|
|
32
53
|
* connected; `treasury`: the treasury's address when it is known (else "your wallet" stands for it, as the setup
|
|
33
54
|
* makes it).
|
|
34
55
|
*/
|
|
35
|
-
export function namesOf(m, { owner = null, treasury = null } = {}) {
|
|
56
|
+
export function namesOf(m, { owner = null, treasury = null, now = null } = {}) {
|
|
36
57
|
return {
|
|
37
58
|
m,
|
|
38
59
|
owner,
|
|
39
60
|
treasury,
|
|
61
|
+
now,
|
|
40
62
|
pools: new Map(m.pools.map(p => [p.index, p.key])),
|
|
41
63
|
stashes: new Map(m.stashes.map(s => [s.id, s.name ?? s.key])),
|
|
42
64
|
grants: new Map(m.grants.map(g => [g.id, g])),
|
|
@@ -151,6 +173,8 @@ function readDetails(reads, names) {
|
|
|
151
173
|
: treasury()
|
|
152
174
|
: treasury();
|
|
153
175
|
const to = v => (v === 'Burn' ? 'burned' : `to ${where(v)}`);
|
|
176
|
+
// A time more than three years after now: the program takes it, and the owner sees its date before signing.
|
|
177
|
+
const far = v => Number(v) - (names.now ?? Math.floor(Date.now() / 1000)) > FAR_SECONDS;
|
|
154
178
|
const isTo = v =>
|
|
155
179
|
v === 'Burn' ||
|
|
156
180
|
v === 'Treasury' ||
|
|
@@ -176,7 +200,33 @@ function readDetails(reads, names) {
|
|
|
176
200
|
? `Places paid from: its pot ${v.Pot[0]}, kept from round to round (what anyone sends it, yield and what earlier rounds left all count)`
|
|
177
201
|
: `Places paid from: each round’s own ticket money alone; what they leave is ${v.Round.rest === 'Burn' ? 'burned' : `sent to ${where(v.Round.rest)}`}`,
|
|
178
202
|
);
|
|
179
|
-
} else if (k === '
|
|
203
|
+
} else if (k === 'when_out' && v === 'GiveNothing')
|
|
204
|
+
lines.push(
|
|
205
|
+
'When an outcome is out: a draw on it gives nothing, and a paid draw is not refunded (the box keeps selling)',
|
|
206
|
+
);
|
|
207
|
+
else if (k === 'closes_before' && BigInt(v ?? 0) > 0n)
|
|
208
|
+
lines.push(`Sales close ${duration(v)} before each draw`);
|
|
209
|
+
else if (k === 'time_weighted' && v === true)
|
|
210
|
+
lines.push(
|
|
211
|
+
'Tickets weighed by time: each purchase counts its tickets times the seconds left until sales close, so a ticket bought early has a better chance',
|
|
212
|
+
);
|
|
213
|
+
else if (k === 'places' && Array.isArray(v)) {
|
|
214
|
+
// What places of tokens from a pool get when it is short, read once when the draw is asked.
|
|
215
|
+
const short = [...new Set(v.filter(p => p?.prize?.Tokens?.from?.Pool).map(p => p.when_short))];
|
|
216
|
+
if (short.length)
|
|
217
|
+
lines.push(
|
|
218
|
+
`If a pool its places are paid from holds less than they win: ${short.map(x => SHORT[x] ?? x).join('; ')}`,
|
|
219
|
+
);
|
|
220
|
+
walk(v);
|
|
221
|
+
} else if (['draws_at', 'ends_at', 'result_by', 'next_at'].includes(k) && far(v))
|
|
222
|
+
lines.push(
|
|
223
|
+
`${FAR[k]} on ${dateWords(Number(v))}, ${Math.floor((Number(v) - (names.now ?? Math.floor(Date.now() / 1000))) / 31_536_000)} years from now: what waits on it is locked until then. Check the unit before approving`,
|
|
224
|
+
);
|
|
225
|
+
else if (k === 'extend' && read.name === 'open_auction' && Number(v) > FAR_SECONDS)
|
|
226
|
+
lines.push(
|
|
227
|
+
`A late bid moves the end by ${duration(v)}: one bid holds the lot and itself that long. Check the unit before approving`,
|
|
228
|
+
);
|
|
229
|
+
else if (k === 'to' && isTo(v))
|
|
180
230
|
lines.push(v === 'Burn' ? 'Burns the tokens it takes' : `Sends tokens to ${where(v)}`);
|
|
181
231
|
else if (k === 'Give' && Array.isArray(v) && typeof v[0] === 'string')
|
|
182
232
|
lines.push(`Gives the NFT to ${address(v[0])}`);
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { encodeBase58 } from '../chain/base58.js';
|
|
7
7
|
import { parseCoreCollection, MPL_CORE_PROGRAM } from '../chain/core.js';
|
|
8
8
|
import { parseMint } from '../chain/mint.js';
|
|
9
|
-
import { mayStartFrozen } from '../chain/token2022.js';
|
|
10
9
|
import { accountKind, decode } from '../client/accounts.js';
|
|
11
10
|
import { BUBBLEGUM_PROGRAM, LOOT_PROGRAM, pda } from '../client/pda.js';
|
|
12
11
|
import { SYSTEM_PROGRAM } from '../chain/pda.js';
|
|
@@ -128,16 +127,6 @@ export class MintMismatch extends Error {}
|
|
|
128
127
|
*/
|
|
129
128
|
export async function checkMints({ m, chain }) {
|
|
130
129
|
const named = [...m.tokens.values()].filter(t => t.mint && !t.mint.startsWith('@'));
|
|
131
|
-
// The routes that read a mint's default account state once, as they open, and refuse one that can start frozen.
|
|
132
|
-
const once = new Map();
|
|
133
|
-
const reads = (key, what) => key && !once.has(key) && once.set(key, what);
|
|
134
|
-
for (const l of m.lotteries)
|
|
135
|
-
(reads(l.currency ?? m.currencyKey, `the lottery ${l.name ?? l.key}`),
|
|
136
|
-
reads(l.ticketCurrency, `the lottery ${l.name ?? l.key}`));
|
|
137
|
-
for (const c of m.contests) reads(c.currency ?? m.currencyKey, `the contest ${c.name ?? c.key}`);
|
|
138
|
-
for (const a of m.auctions) reads(a.currency ?? m.currencyKey, `the auction ${a.name ?? a.key}`);
|
|
139
|
-
for (const s of m.schedules.filter(x => x.sealed))
|
|
140
|
-
reads(s.currency ?? m.currencyKey, `the sealed schedule ${s.name ?? s.key}`);
|
|
141
130
|
for (const t of named) {
|
|
142
131
|
const what = `the token ${t.symbol ?? t.key} (“${t.key}”)`;
|
|
143
132
|
if (t.wsol) {
|
|
@@ -162,10 +151,6 @@ export async function checkMints({ m, chain }) {
|
|
|
162
151
|
throw new MintMismatch(
|
|
163
152
|
`The spec says ${what} is a ${t.program} token; its mint ${t.mint} is ${mint.program}. Write "program": "${mint.program}".`,
|
|
164
153
|
);
|
|
165
|
-
if (once.has(t.key) && mayStartFrozen(account))
|
|
166
|
-
throw new MintMismatch(
|
|
167
|
-
`The mint ${t.mint} of ${what} has a default account state that starts new accounts frozen, or that its freeze authority can switch to frozen, so ${once.get(t.key)} would be refused as it opens (the program refuses such a mint wherever a refund or a payout goes into an account opened for it). Use another token there.`,
|
|
168
|
-
);
|
|
169
154
|
}
|
|
170
155
|
await checkTreasury({ m, chain });
|
|
171
156
|
}
|
package/js/approve/server.mjs
CHANGED
|
@@ -221,6 +221,7 @@ export async function createApproval({
|
|
|
221
221
|
*/
|
|
222
222
|
const names = () =>
|
|
223
223
|
namesOf(model(spec, { now: at }), {
|
|
224
|
+
now: at,
|
|
224
225
|
treasury: madeSoFar(live())?.treasury ?? model(spec, { now: at }).treasuryWallet ?? null,
|
|
225
226
|
});
|
|
226
227
|
/**
|
|
@@ -232,7 +233,7 @@ export async function createApproval({
|
|
|
232
233
|
async function readLive(p) {
|
|
233
234
|
if (!spec.live?.published || typeof chain.account !== 'function') return;
|
|
234
235
|
// The names a published list or key list goes by: the published spec's, each held against the root on chain.
|
|
235
|
-
const n = namesOf(model(spec.live.published, { now: at }), { treasury: p.ctx.treasury });
|
|
236
|
+
const n = namesOf(model(spec.live.published, { now: at }), { now: at, treasury: p.ctx.treasury });
|
|
236
237
|
const was = new Map();
|
|
237
238
|
for (const t of p.transactions) {
|
|
238
239
|
for (const [i, step] of t.steps.entries()) {
|
|
@@ -381,14 +382,14 @@ export async function createApproval({
|
|
|
381
382
|
trusted = await trust(wallet);
|
|
382
383
|
} catch (e) {
|
|
383
384
|
if (!(e instanceof ApproveError)) throw e;
|
|
384
|
-
const v = view(p0, namesOf(model(spec, { now: at }), { owner: wallet }), { refused: e.message });
|
|
385
|
+
const v = view(p0, namesOf(model(spec, { now: at }), { now: at, owner: wallet }), { refused: e.message });
|
|
385
386
|
return { ...v, remaining: p0.ok ? p0.transactions.length : 0, done: false };
|
|
386
387
|
}
|
|
387
388
|
if (!p0.ok || !p0.transactions.length)
|
|
388
389
|
return { ...view(p0, names(), { project: trusted.project }), remaining: 0, done: p0.ok };
|
|
389
390
|
const p = await build(trusted.owner);
|
|
390
391
|
await readLive(p);
|
|
391
|
-
const v = view(p, namesOf(model(spec, { now: at }), { owner: wallet, treasury: p.ctx.treasury }), {
|
|
392
|
+
const v = view(p, namesOf(model(spec, { now: at }), { now: at, owner: wallet, treasury: p.ctx.treasury }), {
|
|
392
393
|
project: p.ctx.project,
|
|
393
394
|
});
|
|
394
395
|
return { ...v, remaining: p.transactions.length, done: false };
|
|
@@ -435,7 +436,7 @@ export async function createApproval({
|
|
|
435
436
|
return wireTransaction(message, sigs);
|
|
436
437
|
});
|
|
437
438
|
// What each transaction pays to whom, read back from the very instructions the wallet signs.
|
|
438
|
-
const n = namesOf(model(spec, { now: at }), { owner: wallet, treasury: p.ctx.treasury });
|
|
439
|
+
const n = namesOf(model(spec, { now: at }), { now: at, owner: wallet, treasury: p.ctx.treasury });
|
|
439
440
|
// Every line names the project it goes to first: always the wallet's own, or the one it is the authority of.
|
|
440
441
|
const details = batch.map(t => [
|
|
441
442
|
`Project: ${p.ctx.project}${p.ctx.owner === wallet ? ' (your own)' : ` (owned by ${p.ctx.owner})`}`,
|
package/js/chain/token2022.js
CHANGED
|
@@ -60,8 +60,9 @@ export function hookProgramOf(mintData) {
|
|
|
60
60
|
/**
|
|
61
61
|
* Whether a mint's new accounts start frozen, or can be made to (as the program judges it): a Token-2022
|
|
62
62
|
* mint carrying `DefaultAccountState` set to frozen, or set to anything while it has a freeze authority, which can switch
|
|
63
|
-
* it to frozen whenever it likes.
|
|
64
|
-
*
|
|
63
|
+
* it to frozen whenever it likes. The program takes such a mint everywhere: it is the project's choice. A refund or a
|
|
64
|
+
* payout opened into an account that starts frozen does not land until its issuer thaws it, so the tools warn the
|
|
65
|
+
* project, and a bidder or a borrower before they sign.
|
|
65
66
|
*/
|
|
66
67
|
export function mayStartFrozen({ owner, data }) {
|
|
67
68
|
if (
|