@xgamesfun/solana 0.27.1 → 0.27.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.
Files changed (37) hide show
  1. package/README.md +34 -17
  2. package/bin/xsolana.mjs +72 -7
  3. package/js/approve/details.mjs +1 -1
  4. package/js/approve/keyfile.mjs +242 -0
  5. package/js/approve/live-check.mjs +5 -2
  6. package/js/approve/server.mjs +94 -13
  7. package/js/approve/with-key.mjs +298 -0
  8. package/js/chain/lookup.js +11 -0
  9. package/js/chain/programs.js +49 -1
  10. package/js/chain/rpc.js +53 -6
  11. package/js/economy/check.mjs +2 -2
  12. package/js/economy/cost.mjs +20 -4
  13. package/js/economy/explain.mjs +3 -3
  14. package/js/economy/guide.mjs +2 -2
  15. package/js/economy/live.mjs +1 -1
  16. package/js/economy/plan.mjs +34 -6
  17. package/js/economy/review.mjs +1 -1
  18. package/js/economy/schema.json +1 -1
  19. package/js/economy/txsize.mjs +3 -2
  20. package/js/mcp/server.mjs +3 -3
  21. package/js/mcp/tools.mjs +50 -13
  22. package/js/play/index.js +21 -7
  23. package/package.json +5 -1
  24. package/release.json +1 -1
  25. package/skills/xsolana-economy/SKILL.md +58 -19
  26. package/skills/xsolana-economy/references/after-launch.md +43 -12
  27. package/skills/xsolana-economy/references/apps.md +1 -1
  28. package/skills/xsolana-economy/references/sentence-to-spec.md +3 -3
  29. package/skills/xsolana-economy/references/telling-the-person.md +18 -11
  30. package/skills/xsolana-economy/references/trust-and-cheating.md +4 -2
  31. package/types/approve/keyfile.d.mts +36 -0
  32. package/types/chain/lookup.d.ts +12 -0
  33. package/types/chain/programs.d.ts +22 -0
  34. package/types/chain/rpc.d.ts +15 -1
  35. package/types/economy/plan.d.mts +21 -4
  36. package/types/mcp/server.d.mts +4 -3
  37. package/types/mcp/tools.d.mts +14 -5
@@ -1,26 +1,38 @@
1
1
  ---
2
2
  name: xsolana-economy
3
- description: Build a web3, token or NFT economy on Solana without writing a smart contract, on loot, one shared program every project uses. Covers shops, loot boxes and gacha, packs, random NFT and token prizes from a stash of NFTs the project holds, coin flips and prize pools, lotteries and raffles, auctions where the highest bid wins and the outbid get their money back, staking farms with lock tiers and NFT boosts, crafting and merging, seasons and emissions, prediction pools and contests, quests, airdrops and capped rewards, holder-gated drops, and perks or staking for any NFT, whether the project minted it or not. Turns a person's idea into a JSON spec, then checks, simulates and prices it, explains it in plain words, and hands the person the command that puts it live in their own wallet. Use whenever someone asks for anything onchain that players or members pay, earn, stake, open, craft or win, for a game, a community, a creator or a brand, and before writing any Anchor, Rust or Solidity for it.
3
+ description: Build a web3, token or NFT economy on Solana without writing a smart contract, on loot, one shared program every project uses. Covers shops, loot boxes and gacha, packs, random NFT and token prizes from a stash of NFTs the project holds, coin flips and prize pools, lotteries and raffles, auctions where the highest bid wins and the outbid get their money back, staking farms with lock tiers and NFT boosts, crafting and merging, seasons and emissions, prediction pools and contests, quests, airdrops and capped rewards, holder-gated drops, and perks or staking for any NFT, whether the project minted it or not. Turns a person's idea into a JSON spec, then checks, simulates and prices it, explains it in plain words, and puts it live with a key file on their own machine, after their yes. Use whenever someone asks for anything onchain that players or members pay, earn, stake, open, craft or win, for a game, a community, a creator or a brand, and before writing any Anchor, Rust or Solidity for it.
4
4
  ---
5
5
 
6
6
  # Build an economy on loot
7
7
 
8
8
  An economy is one JSON spec (`xsolana.json` in the project) on **loot**, a Solana program every project shares. Nobody
9
- writes or deploys a contract: the spec is checked, simulated and priced by the `xsolana_*` tools, and the project's
10
- owner puts it live by approving in their own wallet. It runs on **devnet** until the program is audited.
9
+ writes or deploys a contract: the spec is checked, simulated and priced by the `xsolana_*` tools, and it goes live
10
+ signed by a key file on the owner's own machine, once they have read what will be sent and said yes. It runs on
11
+ **devnet** until the program is audited.
11
12
 
12
- The tools are the `xsolana_*` MCP tools. Without MCP, the same from a terminal: `npx -y @xgamesfun/solana@0.27.1 <guide|examples|check|simulate|cost|explain|review|publish|status> [xsolana.json]`,
13
- `npx -y @xgamesfun/solana@0.27.1 token <mint>` for a coin made elsewhere,
14
- or `npx -y @xgamesfun/solana@0.27.1 tool <name> '<json>'` for any tool (always with the exact version).
13
+ The tools are the `xsolana_*` MCP tools. Without MCP, the same from a terminal: `npx -y @xgamesfun/solana@0.27.2 <guide|examples|check|simulate|cost|explain|review|publish|status> [xsolana.json]`,
14
+ `npx -y @xgamesfun/solana@0.27.2 token <mint>` for a coin made elsewhere,
15
+ or `npx -y @xgamesfun/solana@0.27.2 tool <name> '<json>'` for any tool (always with the exact version).
15
16
 
16
17
  ## Rules (always)
17
18
 
18
19
  1. **Never write, build or deploy a program.** No Anchor, Rust, Solidity or custom program, and no deploy: not by you,
19
20
  not by a command or page you give the person, not even "just on devnet". Unaudited code holding people's money is
20
21
  the risk loot exists to remove. If the idea needs something loot can't do, follow rule 9.
21
- 2. **You never sign, and you never ask for or handle a key.** No private key, seed phrase or keypair file: not in
22
- chat, not in a spec, not in a file. Going live is the person running `xsolana approve` and approving in their own
23
- wallet. If they paste a key, tell them to treat it as exposed and move its funds; write it nowhere.
22
+ 2. **Going live is signed by a key file on the person's own machine, after their yes; you never ask for, read or
23
+ print a key, and you open no signing page.** `xsolana keygen <file>` makes the project's key file (outside any
24
+ repository, unless git ignores it there; it prints the address, never the key). It is read only through the tools:
25
+ `xsolana approve <spec> --keypair <file>`, which prints everything it would send and sends nothing until it is run
26
+ again with the code it printed, which you do only after the person has read that printout and said yes; and, for
27
+ what only the authority signs later, `readKeyFile` in a script (`references/after-launch.md`). A key file is the
28
+ safer way to set up: it is a new key that has never held anything else, so the wallet that holds what the person
29
+ owns is never connected or asked to sign; it signs each transaction exactly as it was prepared and shown; and
30
+ nothing opens in their browser. What it costs: the key is a plain file, so anything on the machine that can read
31
+ it can sign as the project, and by default what the economy earns lands at its address. The page for a browser
32
+ wallet (`xsolana approve <spec> --browser`) is only for a person who asks for it. The key file is the project's
33
+ owner: whoever can read it controls the project. Never open it, copy it or print it, and never put it in a chat, a
34
+ spec, a repository or a page. Never ask for a seed phrase or the key of a wallet the person already has; if they
35
+ paste one, tell them to treat it as exposed and move its funds, and write it nowhere.
24
36
  3. **Devnet only.** Nothing here is real money yet; say so.
25
37
  4. **The checker decides.** Never save or publish a spec with refusals. Fix each refusal the way its `fix` says.
26
38
  5. **Numbers come from the tools.** What a box gives back, how long a pool lasts, what going live costs: quote
@@ -32,9 +44,10 @@ or `npx -y @xgamesfun/solana@0.27.1 tool <name> '<json>'` for any tool (always w
32
44
  say it in one plain sentence. Don't repeat it in later answers, or add
33
45
  legal notes to pages and apps you build, unless the person asks or a flag of a new kind appears. When they ask about
34
46
  the law, answer what you know and leave the rest to their lawyer: you are not one. Never call anything an
35
- investment, a return or passive income, and never promise it is safe. The approval page shows the flags and that
36
- the program is not independently audited and comes as is, and asks the owner to tick that they read them, so going
37
- live needs nothing more from you. For an auction, say once how bidding works (`W-AUCTION-SHILL`): anyone, the
47
+ investment, a return or passive income, and never promise it is safe. What `xsolana approve` prints before
48
+ anything is sent shows the flags and that the program is not independently audited and comes as is: the person
49
+ reads it before their yes (on the browser page, they tick that they read it), so going live needs nothing more
50
+ from you. For an auction, say once how bidding works (`W-AUCTION-SHILL`): anyone, the
38
51
  seller included, can bid to push the price up.
39
52
  7. **Take addresses from the tools, never from memory**: the program, its accounts, the IDL. Every answer names them.
40
53
  8. **Public data only in a spec**: names, prices, percents, public addresses. Leave out `id`, `index`, `shopBox`,
@@ -121,14 +134,40 @@ or `npx -y @xgamesfun/solana@0.27.1 tool <name> '<json>'` for any tool (always w
121
134
  8. **Review it before they approve**: `xsolana_review` (rule 12). Fix the high findings, declare `trust` on every
122
135
  voucher grant, and tell the person which level each signed route uses, what a cheater gets and what the stronger
123
136
  level would cost. Run it again after every change that touches a route that pays.
124
- 9. **When they say "put it live"**: `xsolana_publish` lists every step and its cost and gives the command. Tell them to
125
- run it themselves in the project folder, exactly as `xsolana_publish` gives it (it names the exact version:
126
- `npx -y @xgamesfun/solana@0.27.1 approve xsolana.json`; never drop the version). It opens a page on their computer where
127
- they connect a devnet wallet and approve; what lands is written back into `xsolana.json`. You publish nothing.
137
+ 9. **When they say "put it live"**: `xsolana_publish` lists every step and its cost and gives the commands, which you
138
+ run in the project folder exactly as given (they name the exact version; never drop it):
139
+ - **A new economy gets a key file**, unless the project has one:
140
+ `npx -y @xgamesfun/solana@0.27.2 keygen ~/.config/xsolana/<name>.json`. Give the person the address it prints. A
141
+ Solana key file they already have and name themselves (`~/.config/solana/id.json`) works the same; never go
142
+ looking for one.
143
+ - **Say what the key file is** before anything is sent: the project's owner, and, unless the spec says otherwise,
144
+ its treasury (what sales leave lands at its address) and its authority (it signs what only the game decides).
145
+ Which key goes live is for good: every later change, and everything only the authority signs, is signed by this
146
+ file, and these tools can't hand a project to another owner. If they will want to sign in a browser wallet
147
+ later, go live in one now. If the economy takes money in, ask for the public address of a wallet they hold and
148
+ set `"treasury": "wallet:<address>"` first: it can't be changed through the spec once live. Tell them to back
149
+ the file up themselves (a USB stick, a password manager): if it is lost, nobody can change the economy, decide
150
+ a contest or move what its address holds, and nothing restores it. You never open or copy it.
151
+ - **Everything the setup takes from the owner must be at the key's address first**: the devnet SOL
152
+ (`xsolana_cost`), any seed in a coin that already exists, and every NFT a stash, a raffle or an auction names. A
153
+ collection they already have needs the key's address as its update authority. If they would rather not move
154
+ those, offer the browser wallet.
155
+ - **Show it**: `npx -y @xgamesfun/solana@0.27.2 approve xsolana.json --keypair <key file>` prints every step with
156
+ what it pays and trusts, the warnings, the notice and a code, and sends nothing. Give the person that printout as
157
+ it is, not a summary of it, and ask.
158
+ - **On their yes**, the same command with `--yes <code>`. What lands is written back into `xsolana.json`. If it
159
+ stops part way, run it again without `--yes`: it prints what is left and a new code. What is left is part of what
160
+ they said yes to, so go on with that code; if it shows a step or a warning they have not seen, ask again.
161
+ - **A change to an economy that is live** is signed by the key that owns it, and by no other: the key file it went
162
+ live with, or the browser wallet it went live in. Never make a new key for it, and never remove the file's live
163
+ section because a key was refused: ask the person how it went live.
164
+ - **A browser wallet** (`… approve xsolana.json --browser`) only when they ask for one, or when a browser wallet
165
+ owns the project: give them the command to run in their own terminal. It serves a page on their computer and
166
+ keeps running until the page says it is live. Never run it because it seems easier.
128
167
  10. **After it is live**: `xsolana_status` shows each part on chain; read it before acting on what the person says
129
- happened. A later change is the same loop: save, publish, and the approval sends only what changed. What only the
130
- owner signs (open or decide a contest, pay out) goes on an owner page they approve in their own wallet:
131
- `references/after-launch.md`.
168
+ happened. A later change is the same loop: save, publish, and `approve` shows and sends only what changed, signed
169
+ by the same key. What only the authority signs while it runs (open or decide a contest, pay out) is signed where
170
+ its key is, a key file on their machine or a browser wallet on an owner page: `references/after-launch.md`.
132
171
 
133
172
  ## When they ask for a token
134
173
 
@@ -68,16 +68,46 @@ suspends it, and the pieces stay locked in their farm meanwhile.
68
68
  Leaving a farm needs only the player's wallet, never a server's signature; what can still keep an NFT from coming
69
69
  back is the NFT issuer's own controls (`telling-the-person.md`, NFT custody).
70
70
 
71
- ## The owner's own steps: an owner page
72
-
73
- What only the authority can sign (open or decide a contest, pay out, move a pool) needs a page the owner opens and
74
- approves in **their own wallet**, like the approve page. Build it with `@xgamesfun/solana/client` builders
75
- (`openContestIx`, `decideIx`, `moveTokensIx`) and the wallet's `signTransaction`, served locally or on the same site
76
- behind nothing more than the owner's wallet: the program checks the signature, so a page holds no secret.
77
-
78
- - Show what each button does in words before the wallet asks ("Name the Owls the winner: the pot of 0.42 SOL is
79
- shared by 7 backers; 5% goes to you").
80
- - Never put an owner's or authority's key in a page, a file or a server, and never ask the person to export one.
71
+ ## The authority's own steps
72
+
73
+ What only the authority can sign (open or decide a contest, pay out, move a pool) is signed where the authority's key
74
+ is. Unless the spec's `keys.authority` names another address, the authority is the owner: for a project set up with a
75
+ key file, that file. Build each step with `@xgamesfun/solana/client` builders (`openContestIx`, `decideIx`,
76
+ `moveTokensIx`).
77
+
78
+ - **A key file** (the usual case): a short script on the person's own machine, signed by `readKeyFile(file)` from
79
+ `@xgamesfun/solana/keyfile`. It gives the key's address, `sign(message)` and `signTransaction(wire)`, never the
80
+ key's bytes, and it refuses a file others can read, or one inside a repository that git does not ignore. Never read
81
+ the file any other way, and never copy it into the script, a page or a server. The script shows first and sends
82
+ only when run again after the person's yes:
83
+
84
+ ```js
85
+ import { readKeyFile } from '@xgamesfun/solana/keyfile';
86
+ import * as loot from '@xgamesfun/solana/client';
87
+ import { rpcClient } from '@xgamesfun/solana/chain/rpc';
88
+ import { compileMessage, wireTransaction } from '@xgamesfun/solana/chain/tx';
89
+
90
+ const key = await readKeyFile('~/.config/xsolana/<name>.json'); // an address and a way to sign, never the bytes
91
+ const rpc = rpcClient('https://api.devnet.solana.com');
92
+ const ix = loot.decideIx({/* the contest and its result: the builder's own arguments */});
93
+ console.log('Name the Owls the winner: the pot of 0.42 SOL is shared by 7 backers; 5% goes to you.');
94
+ if (process.argv[2] !== '--yes') process.exit(0); // shows first; run again with --yes after the person's yes
95
+ const { blockhash, lastValidBlockHeight } = await rpc.latestBlockhash();
96
+ const message = compileMessage({ payer: key.address, blockhash, instructions: [ix] });
97
+ const signature = await rpc.send(await key.signTransaction(wireTransaction(message)));
98
+ await rpc.confirm(signature, { lastValidBlockHeight });
99
+ ```
100
+
101
+ For a step the player module has (`app.runSchedule`, `app.callOffSale`), `solanaApp({ spec, rpcUrl, wallet: key })`
102
+ sends it: the key is a wallet to it.
103
+
104
+ - **A browser wallet**, when the person asks to sign there: a page the owner opens and approves in their own wallet,
105
+ with the wallet's `signTransaction`, served locally or on the same site behind nothing more than that wallet (the
106
+ program checks the signature, so a page holds no secret). It works only when the authority's address is that
107
+ wallet's: name it as `keys.authority` before going live (it can't be changed through the spec after). Show what
108
+ each button does in words before the wallet asks.
109
+ - Never put an owner's or authority's key in a page or on a server, and never ask the person to export or paste one.
110
+ A server that signs by itself gets an authority key of its own (`keys.authority`), never the owner's.
81
111
 
82
112
  ## Results from a game: who won, and paying them
83
113
 
@@ -109,7 +139,8 @@ now })` for the player's own wallet and claims (`app.claimGrant(key, { voucher }
109
139
  voucher key, never the owner's. Set the grant's `total` and `perWallet`: they bound what a stolen voucher key can
110
140
  pay.
111
141
  - Or a **results server** the person runs or names. It checks results signed by the game's host key, which the
112
- owner certifies once by signing a text in their wallet; the game server and pages talk to it with
142
+ owner certifies once by signing a text with the owner's key (in their wallet, when a browser wallet owns the
143
+ project); the game server and pages talk to it with
113
144
  `@xgamesfun/solana/results` (`report`, `giveaways`, `draws`), and winners claim what it vouched for with
114
145
  `app.claimWins({ server, token })` (`token`: the player's sign-in with that server, when it asks for one; a drop
115
146
  of NFTs from a stash gives one only at a signed-in claim, first come first served). Its voucher key is per
@@ -126,7 +157,7 @@ paid once, and the same voucher sent again later pays what a limit held back. Ch
126
157
  clear its history. Closing and recreating a grant starts a new campaign with new records and invalidates old vouchers;
127
158
  anyone may then close the old records (`loot.closeShardIx`), their rent to the owner. A grant for more than about
128
159
  100,000 wallet-rounds names `wallets` (and `rounds`) so its records are kept in enough lists. A list written in xsolana.json pays each wallet once per grant incarnation: editing it lets newly added wallets
129
- claim, never a wallet already paid (for repeated payouts, list the wallet again under a new `round`, or use vouchers). A list leaf names no grant or incarnation, so a grant closed and made again with the same list pays every listed round again: give a re-made grant a fresh list (new rounds); the approval page warns when a change does that. A results server must read the current grant incarnation when accepting
160
+ claim, never a wallet already paid (for repeated payouts, list the wallet again under a new `round`, or use vouchers). A list leaf names no grant or incarnation, so a grant closed and made again with the same list pays every listed round again: give a re-made grant a fresh list (new rounds); `approve` warns when a change does that. A results server must read the current grant incarnation when accepting
130
161
  a result and include it in the voucher it signs.
131
162
 
132
163
  ## Before you act on what the person says happened
@@ -246,6 +246,6 @@ assetId, tree }` after a `loot.proveLeafIx` in the same transaction. `app.stakeN
246
246
  day of their players costs the sponsor (`xsolana_cost`: the player fee, the oracle's fee and rent per step).
247
247
 
248
248
  - **Never in a page**: a private key, a keyed RPC link, or anything the authority signs (payouts, minting, rewriting
249
- a collectible). Those are the owner's or the authority's to sign in their own wallet, or on a server they run.
249
+ a collectible). Those are signed where the owner's or the authority's key is: a script with the project's key file, the authority's own browser wallet, or a server they run (`after-launch.md`).
250
250
  - **Lower level**: `@xgamesfun/solana/client` has one builder per instruction and the account decoders, for what the
251
251
  player module doesn't cover.
@@ -98,7 +98,7 @@ maps to a part of the spec (`xsolana_guide` has every field).
98
98
  | "pay the team 100 a week", "a landlord's rent" | a schedule `does: "pay"` from a pool `to` "wallet:<address>" or "holder:<asset>" (the project's keys, or a wallet it pays, run it when due: the page signed by one of them, or their sweep) |
99
99
  | "an entry fee", "pay per attempt" | `payments` with `amount` and a `split`. A payment has no gate, no opens/closes, no `"chosen:<item>"` share and no rising price: when the entry needs one, sell it as a box with one outcome that gives nothing (or an item), which has them all. Its split may name a `"chosen"` wallet (a referrer): `app.pay(key, { chosenRecipient })` |
100
100
  | "tips", "donations", "pay what you want", "any amount above 0.001" | a payment with `"any": true`: `amount` is the least; the page asks the amount and calls `app.pay(key, { amount })` |
101
- | "all proceeds to the school", "our DAO's vault gets the money", "the cut goes to the charity" | `"treasury": "wallet:<their address>"`: what every split leaves, and a lottery's cut, go there. Left out, the treasury is the owner's own wallet. The owner can change it later |
101
+ | "all proceeds to the school", "our DAO's vault gets the money", "the cut goes to the charity" | `"treasury": "wallet:<their address>"`: what every split leaves, and a lottery's cut, go there. Left out, the treasury is the owner's address (the project's key file). It can't be changed through the spec once live: ask before going live |
102
102
  | "several signers must approve spending", "a multisig" | loot isn't a multisig: the money goes to their multisig's vault (for example Squads): `"treasury": "wallet:<vault>"`, or a `wallet:` share; spending from it is approved in the multisig |
103
103
  | "60% to me, 40% to my co-designer", "10% to our charity", "split with my partners" | split shares `"to": "wallet:<their wallet address>"` (ask for each address). Going live opens each wallet's account for the currency. Use `account:` only for a token account that already exists |
104
104
  | "our partner gets 4%" (one fixed wallet) | a split share `"to": "wallet:<their address>"` |
@@ -193,8 +193,8 @@ in 50 hooks the Golden Carp, worth 0.5 SOL. 5% of every cast goes to a monthly p
193
193
  Perch 3 casts in 10; at 500 casts a day the treasury gets about 4 SOL a day. The pond takes in 5.5 SOL a day and
194
194
  pays out 5, but luck swings it about 4 SOL a week: on a 2 SOL seed a bad week stops the box selling, and the
195
195
  simulation suggests about 12.4. The monthly pot reaches about 21 SOL, because casts feed it as well as tickets.
196
- - `xsolana_cost` → about 2.01 SOL to go live, in one wallet approval: 2 SOL is the seed, the rest rent and fees.
196
+ - `xsolana_cost` → about 2.01 SOL to go live: 2 SOL is the seed, the rest rent and fees.
197
197
  - `xsolana_explain` → the words for the person (`telling-the-person.md`): pass on the 40% treasury share you chose, the
198
198
  thin seed and the flags.
199
199
  - The person: "start the pond with 12.5 SOL". Change the seed, check, simulate (the swing note is gone), explain:
200
- going live is now about 12.51 SOL. `xsolana_save`, and when they say so, `xsolana_publish` and the approve command.
200
+ going live is now about 12.51 SOL. `xsolana_save`, and when they say so, `xsolana_publish` and its key-file commands (SKILL.md, step 9).
@@ -16,8 +16,13 @@ and the spec differ (a limit, an amount, a key not set yet), change one of them
16
16
  4 SOL a week: start it with about 12.5 SOL rather than 2, or a bad week stops casts until it refills." A pool that
17
17
  runs dry is the first thing to say. A prize bigger than its pool is left out of the roll until the pool fills, so
18
18
  the odds players are shown don't hold until then (`W-PRIZE-SEED`).
19
- 4. **What going live costs.** "About 12.51 SOL on devnet, in one wallet approval: 12.5 SOL is the pond's seed, the
20
- rest is rent and fees."
19
+ 4. **What going live costs, and who signs it.** "About 12.51 SOL on devnet: 12.5 SOL is the pond's seed, the rest is
20
+ rent and fees. A key file on your computer signs it, once you have read what it sends and said yes." Then what the
21
+ key file is, once: "That key owns the project, so whoever can read its file controls it, and it is the key for
22
+ good: every later change is signed by it. Keep the file where it is and never paste it anywhere. Copy it yourself
23
+ to a USB stick or a password manager (I won't open or copy it): if it is lost, nobody can change the game's
24
+ economy or move what its address holds. Unless we name another wallet as the treasury, what the game earns for you
25
+ lands at the key's address, and moving it from there takes a script signed by the key file."
21
26
  5. **Who does what while it runs** (`after-launch.md` has the table): what anyone can send (a draw, an
22
27
  auction's end, claims), what only they can (a contest's result, a payout, a held item, a collectible's new level, each run of a schedule that isn't sealed: without them or a server holding a project key, an emission stops)
23
28
  and how often, and what happens if nobody does. If a payout rewards a result the game reports (a clear, a win,
@@ -30,9 +35,10 @@ and the spec differ (a limit, an amount, a key not set yet), change one of them
30
35
  "People pay for a share of a new token; selling a token to raise money is what securities law looks at first."
31
36
  Say each once, the
32
37
  first time the checker raises it; then leave them, saying them again only when the person asks or a flag of a new
33
- kind appears, and keep them out of pages and apps unless asked. The approval page shows them, with the program's
34
- status (not independently audited, as is, at their own risk, theirs to make legal where they offer it), and keeps
35
- Approve off until the owner ticks that they read it.
38
+ kind appears, and keep them out of pages and apps unless asked. What `xsolana approve` prints before anything is
39
+ sent shows them, with the program's status (not independently audited, as is, at their own risk, theirs to make
40
+ legal where they offer it): the person reads it before their yes. The page for a browser wallet keeps Approve off
41
+ until the owner ticks that they read it.
36
42
  7. **For an auction**, say how it ends, in their words: "The highest bid when the 48 hours are up wins. Each bid must
37
43
  be at least 0.5 SOL and 5% over the last; a bid in the last 10 minutes gives everyone 10 more. Whoever is outbid
38
44
  has their SOL back at once, in the same transaction. With no bid, the lot goes back (an item's unit is never
@@ -122,10 +128,10 @@ Don'ts:
122
128
  - **A token the setup makes is named on chain as it goes live**: its `name`, its `symbol` and its `uri` (a link to a
123
129
  JSON file the project hosts, `{ "name", "symbol", "image": "https://…" }`), which is what wallets and explorers show.
124
130
  Ask for the name, the ticker and a picture before going live: with no `uri` it shows by name and ticker with no
125
- picture. Only the owner's wallet can change the three later (Token Metadata's rule, not loot's): change them in the
126
- spec and approve again, and the record is written again for the price of a transaction (approving reads the record
127
- first: one its owner handed to another wallet, made unchangeable or gave a royalty or creators outside these tools
128
- is left as it is, and the page says so at each approve until the spec gives the old name back). The record costs about 0.014 SOL (`xsolana_cost` says it:
131
+ picture. Only the project's owner (its key) can change the three later (Token Metadata's rule, not loot's): change
132
+ them in the spec and approve again, and the record is written again for the price of a transaction (approving reads
133
+ the record first: one its owner handed to another wallet, made unchangeable or gave a royalty or creators outside
134
+ these tools is left as it is, and `approve` says so each time until the spec gives the old name back). The record costs about 0.014 SOL (`xsolana_cost` says it:
129
135
  rent, and 0.01 SOL Token Metadata keeps). A token that went live before the tools wrote one has none and
130
136
  can't be given one: only a mint's own authority can name it, and its minting was handed on or turned off.
131
137
  - **NFTs the project has no authority over** (any collection but its own): they count as they are. Say that
@@ -168,5 +174,6 @@ Don'ts:
168
174
  prize also opens the winner's account from the funds:
169
175
  `xsolana_simulate` gives the funds a claim needs. When the funds run out, claims still land: whoever pays for the claim
170
176
  (the winner, who always signs it, or anyone paying its fees for them) pays the rest. Only the winner can claim.
171
- - **Never walk the person through creating, exporting or pasting a private key**, for anything (a keeper, a server,
172
- a bot). Public steps need no key of theirs (`after-launch.md`).
177
+ - **Never walk the person through exporting or pasting a private key or a seed phrase**, for anything (a keeper, a
178
+ server, a bot). The project's key file is made by `xsolana keygen` and read only by the tools; public steps need no
179
+ key of theirs (`after-launch.md`).
@@ -102,8 +102,10 @@ Run `xsolana_review` and go through what it reports; these are the points behind
102
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
103
  ways those items are had. A gate on another collection's attributes trusts whoever can write them.
104
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.
105
+ key; the authority mints, moves pool funds, decides contests and releases holds, with no caps. On devnet the
106
+ owner's key is the key file the setup was signed with: readable by its user alone, outside any repository, never
107
+ pasted anywhere. With real money (mainnet is not open) those keys belong in a hardware wallet or a multisig; a
108
+ server that signs gets an authority key of its own, never the owner's.
107
109
  12. **The page is untrusted.** A page builds the player's transactions, and a cheater builds their own: the program's
108
110
  checks are what count, never the page's. Anything the page decides (a score, a reward amount, who won) is only as
109
111
  good as the server that checks it.
@@ -0,0 +1,36 @@
1
+ /** A refusal about a key file: said in one line, and it never quotes what the file holds. */
2
+ export declare class KeyFileError extends Error {
3
+ }
4
+ /** `file` as a whole path: a leading `~` is the home folder, whether or not a shell expanded it. */
5
+ export declare function keyFilePath(file: any): any;
6
+ /**
7
+ * Makes a new key and writes it to `file`, which must not exist: the folder is made if need be (for its user alone),
8
+ * the file is readable and writable by its user alone, and a path inside a git repository is refused. Returns
9
+ * { file, address }: the whole path and the key's address, never the key.
10
+ * @param {string} file
11
+ * @param {{ run?: Function }} [options] `run`: how git is run (for tests).
12
+ */
13
+ export declare function makeKeyFile(file: string, { run }?: {
14
+ run?: Function;
15
+ }): Promise<{
16
+ file: any;
17
+ address: string;
18
+ }>;
19
+ /**
20
+ * Reads the key in `file` to sign with: { file, address, sign(message), signTransaction(wire), drop() }, which is
21
+ * also what `@xgamesfun/solana/play` takes as a wallet. Refused: a file other users of the machine can read, one
22
+ * inside a git repository, and anything that is not a whole key. No refusal quotes the file.
23
+ * @param {string} file
24
+ * @param {{ run?: Function, platform?: string }} [options] `run`: how git is run; `platform`: the system's name (for tests).
25
+ */
26
+ export declare function readKeyFile(file: string, { run, platform }?: {
27
+ run?: Function;
28
+ platform?: string;
29
+ }): Promise<{
30
+ file: any;
31
+ address: string;
32
+ sign: (message: any) => Uint8Array<any>;
33
+ /** A wire transaction with this key's signature in its own slot, as a wallet's signTransaction gives it back. */
34
+ signTransaction(wire: any): Promise<Uint8Array<any>>;
35
+ drop: () => void;
36
+ }>;
@@ -113,6 +113,18 @@ export declare function compileFitting({ payer, blockhash, instructions, tables
113
113
  payer: any;
114
114
  tables?: never[] | undefined;
115
115
  }): Uint8Array<any> | null;
116
+ /**
117
+ * The message for a transaction that has two forms, `led` (the instructions with something put first for a wallet's
118
+ * sake: a compute budget) and `plain` (without it), keeping the kind of transaction `plain` alone would be: legacy
119
+ * when `plain` fits one (led when that fits too), else v0 with `tables` (led when that fits). null when neither fits.
120
+ */
121
+ export declare function compileKeepingKind({ payer, blockhash, led, plain, tables }: {
122
+ blockhash: any;
123
+ led: any;
124
+ payer: any;
125
+ plain: any;
126
+ tables?: never[] | undefined;
127
+ }): Uint8Array<any> | null;
116
128
  /**
117
129
  * The plan for sending `instructions` from `payer` as v0 with a table of their accounts made at `slot`, `first` the
118
130
  * addresses it holds first:
@@ -29,6 +29,28 @@ export declare const computeUnitPrice: (microLamports: any) => {
29
29
  keys: never[];
30
30
  data: Uint8Array<any>;
31
31
  };
32
+ /** Whether `ix` sets the transaction's compute unit limit. */
33
+ export declare const isComputeUnitLimit: (ix: any) => boolean;
34
+ /** Whether `ix` sets the transaction's compute unit price (its priority fee). */
35
+ export declare const isComputeUnitPrice: (ix: any) => boolean;
36
+ /**
37
+ * `instructions` led by the transaction's Compute Budget instructions: its unit limit, its unit price, then any other
38
+ * (a heap frame). A limit or a price it already carries is kept as it is; the one it lacks is added (`units`,
39
+ * `microLamports` a unit). The rest keep their order.
40
+ *
41
+ * A wallet that adds its own priority fee (Phantom does) adds it only to a transaction that sets neither, so one
42
+ * built this way comes back from the wallet with the message it was given.
43
+ */
44
+ export declare function withComputeBudget(instructions: any, { units, microLamports }: {
45
+ microLamports: any;
46
+ units: any;
47
+ }): any[];
48
+ /**
49
+ * The priority fee of a transaction of `instructions`, in lamports: its unit limit times its unit price (in
50
+ * micro-lamports a unit), rounded up to a whole lamport as the network does. 0n when it sets no price. With no limit
51
+ * set, the limit is taken at its most: 200,000 units for each other instruction, 1,400,000 at most.
52
+ */
53
+ export declare function priorityFee(instructions: any): bigint;
32
54
  /** Create `owner`'s token account for `mint` unless it already exists; `payer` pays the rent. */
33
55
  export declare function createAssociatedTokenAccountIdempotent(payer: any, owner: any, mint: any, tokenProgram?: string): {
34
56
  programId: string;
@@ -2,7 +2,15 @@
2
2
  export declare class RpcError extends Error {
3
3
  code: any;
4
4
  logs: any;
5
- constructor(message: any, { code, logs }?: Readonly<Record<string, any>>);
5
+ err: any;
6
+ maybeTaken: boolean;
7
+ /**
8
+ * `code`: the node's own error code, when a node answered. `logs`: a failed dry run's program logs. `err`: the
9
+ * node's own name for why a transaction was refused ("BlockhashNotFound", "AlreadyProcessed", or the program's
10
+ * error as the chain writes it). `maybeTaken`: a send whose earlier try got no answer, so a node may have taken the
11
+ * transaction although the last try was refused.
12
+ */
13
+ constructor(message: any, { code, logs, err, maybeTaken }?: Readonly<Record<string, any>>);
6
14
  }
7
15
  declare const sleep: (ms: any) => Promise<any>;
8
16
  /** The first block of each public Solana cluster: it names the cluster whatever URL reaches it. */
@@ -22,6 +30,12 @@ export declare function checkCluster(rpc: any): Promise<any>;
22
30
  * built without its errors' text logs the error's name as its message: then the name alone (the client's IDL has
23
31
  * the sentence: `loot.lootError`). */
24
32
  export declare function programError(err: any, logs: any): any;
33
+ /**
34
+ * Whether a send was refused because the cluster has this very transaction already (it was sent before): the node's
35
+ * own word for it (`err`), or, from a node that gives only a sentence, its sentence. Never a program's log line: a
36
+ * dry run that failed inside a program is that program's refusal, whatever its words.
37
+ */
38
+ export declare const alreadyProcessed: (error: any) => boolean;
25
39
  /** A client for one RPC URL. `fetchImpl` and `sleepImpl` are for tests. */
26
40
  export declare function rpcClient(url: any, { fetchImpl, sleepImpl }?: {
27
41
  fetchImpl?: typeof fetch | undefined;
@@ -1040,12 +1040,29 @@ export declare function setupContext(m: any, { owner: ownerKey, keys, live: give
1040
1040
  };
1041
1041
  /** The bytes of a transaction of `instructions` the owner pays for: what `pack` holds against the budget. */
1042
1042
  export declare const stepBytes: (instructions: any, ctx: any) => number;
1043
- /** The bytes of a transaction of `step` alone, as `pack` sends it (its compute limit included). */
1043
+ /** The bytes of a transaction of `step` alone, as `pack` holds it against the budget (a box's compute limit included). */
1044
1044
  export declare const stepAloneBytes: (step: any, ctx: any) => number;
1045
1045
  /**
1046
- * Packs steps into transactions under the size budget, one signer set at a time; each step whole. A transaction that
1047
- * carries a step needing more compute than an instruction has by default (`compute`: a box's write) asks for all a
1048
- * transaction can have, first.
1046
+ * The compute unit price every setup transaction sets, in micro-lamports a unit: 1, the least that is not 0 (a wallet
1047
+ * may read 0 as no price set). With the unit limit below it comes to 1.4 lamports, charged as 2 lamports a transaction
1048
+ * on top of the 5,000 a signature.
1049
+ */
1050
+ export declare const SETUP_UNIT_PRICE = 1n;
1051
+ /**
1052
+ * A setup transaction's instructions as the owner's wallet is handed them: led by a compute unit limit and a compute
1053
+ * unit price, each once. A wallet that adds its own priority fee (Phantom does) adds it only to a transaction that
1054
+ * sets neither, and the approval page sends a transaction only as it prepared it, so every setup transaction sets both
1055
+ * itself. The limit is all a transaction can have (1,400,000 units): nothing here measures what a step takes, a box's
1056
+ * write needs it, and no step may fail for want of compute. A limit or a price the instructions already carry is
1057
+ * kept.
1058
+ */
1059
+ export declare const withSetupBudget: (instructions: any) => any[];
1060
+ /**
1061
+ * Packs steps into transactions under the size budget, one signer set at a time; each step whole. Every transaction
1062
+ * leads with its compute budget (`withSetupBudget`): `BUDGET` keeps 64 bytes of a transaction free, and the two
1063
+ * instructions take 52 of them (12 where a box's write, `compute`, had its limit counted with the steps already). So
1064
+ * the steps pack as they did before the plan set a price, and `bytes`, the size of the transaction as it is sent, is
1065
+ * held to what one transaction carries.
1049
1066
  */
1050
1067
  export declare function pack(steps: any, ctx: any): {
1051
1068
  signer: string;
@@ -1,7 +1,7 @@
1
1
  export declare const INSTRUCTIONS: string;
2
2
  /**
3
3
  * What a hosted server (js/mcp/http.mjs) tells the agent: the same work, with the spec passed whole in every call,
4
- * and going live on the person's own computer, since a hosted server can neither write their files nor open a page there.
4
+ * and going live on the person's own computer, since a hosted server can neither write their files nor sign there.
5
5
  */
6
6
  export declare const REMOTE_INSTRUCTIONS: string;
7
7
  /**
@@ -260,16 +260,17 @@ export declare function runTool(name: any, input?: {}, ctx?: {}): Promise<{
260
260
  warnings: any[];
261
261
  text: string;
262
262
  } | {
263
- setupSol?: undefined;
264
263
  ok: boolean;
265
264
  transactions: number;
266
265
  approvals: number;
267
266
  steps: never[];
268
267
  text: string;
269
- command?: undefined;
270
268
  } | {
271
269
  ok: boolean;
272
270
  command: string;
271
+ keygen?: string | undefined;
272
+ keyFile: string;
273
+ browserCommand: string;
273
274
  transactions: number;
274
275
  approvals: number | undefined;
275
276
  setupSol: any;
@@ -9,11 +9,19 @@ export declare const PACKAGE_VERSION: any;
9
9
  */
10
10
  export declare function plainName(rel: any, paths?: any): string | null;
11
11
  /**
12
- * The command an owner runs to approve going live in their own wallet, pinned to this package's exact version, with
13
- * the spec's file (relative to the project folder, by `paths`' rules: the system's own unless given) as one plain
14
- * word. A name that is not plain throws.
12
+ * The command that puts an economy live, pinned to this package's exact version, with the spec's file (relative to the
13
+ * project folder, by `paths`' rules: the system's own unless given) as one plain word. A name that is not plain
14
+ * throws. How it is signed goes after it: `--keypair <key file>` (the usual way), or `--browser` when the person
15
+ * asks for a browser wallet.
15
16
  */
16
17
  export declare function approveCommand(file?: string, paths?: any): string;
18
+ /**
19
+ * Where a project's key file is kept unless the person names another place: outside any repository, under the
20
+ * economy's own name (`~` is the home folder, in any shell: the commands read it themselves).
21
+ */
22
+ export declare function keyFileFor(name: any): string;
23
+ /** The command that makes a project's key file (it prints the key's address, never the key). */
24
+ export declare const keygenCommand: (keyFile: any) => string;
17
25
  export declare const CLUSTER = "devnet";
18
26
  export declare const DEVNET_RPC = "https://api.devnet.solana.com";
19
27
  /** What every answer carries, so an agent never has to remember the program's address. */
@@ -626,16 +634,17 @@ export declare const tools: ({
626
634
  warnings: any;
627
635
  text: string;
628
636
  } | {
629
- setupSol?: undefined;
630
637
  ok: boolean;
631
638
  transactions: number;
632
639
  approvals: number;
633
640
  steps: never[];
634
641
  text: string;
635
- command?: undefined;
636
642
  } | {
637
643
  ok: boolean;
638
644
  command: string;
645
+ keygen?: string | undefined;
646
+ keyFile: string;
647
+ browserCommand: string;
639
648
  transactions: number;
640
649
  approvals: number | undefined;
641
650
  setupSol: any;