@xgamesfun/solana 0.20.1 → 0.25.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 +95 -52
- package/bin/xsolana.mjs +23 -8
- package/examples/blind-forge.json +2 -4
- package/examples/coin-flip-arena.json +4 -12
- package/examples/duel-arena.json +1 -4
- package/examples/elements-set.json +2 -9
- package/examples/expedition-guild.json +1 -8
- package/examples/fair-presale.json +1 -3
- package/examples/guild-missions.json +3 -10
- package/examples/halving-seasons.json +4 -9
- package/examples/hero-trials.json +15 -6
- package/examples/holder-den.json +38 -0
- package/examples/holders-drop.json +105 -0
- package/examples/ideas.json +788 -788
- package/examples/match-predictions.json +24 -0
- package/examples/merge-2048.json +5 -5
- package/examples/nft-raffle.json +1 -0
- package/examples/pack-league.json +108 -0
- package/examples/paper-hands-farm.json +4 -16
- package/examples/partner-vault.json +20 -7
- package/examples/sacrifice-raffle.json +2 -10
- package/examples/shelter-fair.json +1 -0
- package/examples/stakers-draw.json +4 -15
- package/examples/star-fleet.json +1 -8
- package/examples/starforce.json +6 -6
- package/examples/stride-sneakers.json +164 -0
- package/examples/trade-in-shop.json +2 -3
- package/js/approve/details.mjs +122 -52
- package/js/approve/ed25519.mjs +5 -1
- package/js/approve/live-check.mjs +83 -26
- package/js/approve/page.html +217 -78
- package/js/approve/page.js +43 -15
- package/js/approve/server.mjs +290 -73
- package/js/chain/base58.js +7 -1
- package/js/chain/borsh.js +24 -11
- package/js/chain/bytes.js +11 -8
- package/js/chain/core.js +35 -7
- package/js/chain/curve.js +1 -1
- package/js/chain/lookup.js +124 -36
- package/js/chain/mint.js +6 -2
- package/js/chain/pda.js +2 -2
- package/js/chain/programs.js +6 -6
- package/js/chain/rpc.js +44 -16
- package/js/chain/token.js +13 -2
- package/js/chain/token2022.js +55 -16
- package/js/chain/tx.js +32 -22
- package/js/client/accounts.js +653 -131
- package/js/client/compressed.js +4 -3
- package/js/client/grant.js +267 -83
- package/js/client/idl/loot.js +1 -1
- package/js/client/idl/loot.json +2406 -2176
- package/js/client/index.js +1 -0
- package/js/client/ix.js +3059 -664
- package/js/client/pda.js +39 -33
- package/js/client/policy.js +52 -24
- package/js/client/random.js +70 -0
- package/js/client/sizes.js +414 -16
- package/js/client/traits.js +14 -5
- package/js/connect/connect.mjs +59 -17
- package/js/economy/check.mjs +4153 -725
- package/js/economy/cost.mjs +403 -64
- package/js/economy/examples.mjs +176 -35
- package/js/economy/explain.mjs +466 -111
- package/js/economy/guide.mjs +296 -72
- package/js/economy/legal.mjs +12 -6
- package/js/economy/live.mjs +83 -19
- package/js/economy/model.mjs +462 -124
- package/js/economy/plan.mjs +1306 -219
- package/js/economy/schema.json +390 -484
- package/js/economy/secrets.mjs +17 -7
- package/js/economy/simulate.mjs +883 -223
- package/js/economy/txsize.mjs +15 -3
- package/js/economy/validate.mjs +61 -24
- package/js/mcp/server.mjs +42 -14
- package/js/mcp/tools.mjs +307 -88
- package/js/play/index.js +2864 -619
- package/js/play/keys.js +193 -0
- package/js/results/index.js +223 -30
- package/js/results/server.js +230 -0
- package/js/results/wire.js +64 -16
- package/package.json +6 -4
- package/release.json +5 -5
- package/skills/xsolana-economy/SKILL.md +39 -21
- package/skills/xsolana-economy/references/after-launch.md +58 -38
- package/skills/xsolana-economy/references/apps.md +97 -50
- package/skills/xsolana-economy/references/sentence-to-spec.md +148 -121
- package/skills/xsolana-economy/references/telling-the-person.md +61 -17
- package/types/chain/core.d.ts +4 -2
- package/types/chain/lookup.d.ts +5 -2
- package/types/chain/rpc.d.ts +3 -1
- package/types/client/accounts.d.ts +217 -56
- package/types/client/grant.d.ts +72 -27
- package/types/client/idl/loot.d.ts +602 -549
- package/types/client/index.d.ts +1 -0
- package/types/client/ix.d.ts +3884 -247
- package/types/client/pda.d.ts +22 -16
- package/types/client/policy.d.ts +39 -14
- package/types/client/random.d.ts +26 -0
- package/types/client/sizes.d.ts +93 -5
- package/types/economy/check.d.mts +5 -10
- package/types/economy/cost.d.mts +2 -1
- package/types/economy/guide.d.mts +5 -0
- package/types/economy/live.d.mts +16 -1
- package/types/economy/model.d.mts +151 -38
- package/types/economy/plan.d.mts +237 -194
- package/types/economy/simulate.d.mts +21 -13
- package/types/mcp/tools.d.mts +21 -13
- package/types/play/index.d.ts +453 -53
- package/types/play/keys.d.ts +51 -0
- package/types/results/index.d.ts +7 -6
- package/types/results/server.d.ts +126 -0
- package/types/results/wire.d.ts +1 -1
|
@@ -6,34 +6,41 @@ winner nobody pays.
|
|
|
6
6
|
|
|
7
7
|
## Who sends each step
|
|
8
8
|
|
|
9
|
-
| Step
|
|
10
|
-
|
|
|
11
|
-
| A box's roll and settle, a shop buy
|
|
12
|
-
| A lottery draw,
|
|
13
|
-
| A schedule that is due (an emission, rent, a drip)
|
|
14
|
-
| Ending an auction
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
| A
|
|
21
|
-
| A prize
|
|
22
|
-
|
|
|
9
|
+
| Step | Who can send it | If nobody does |
|
|
10
|
+
| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| A box's roll and settle, a shop buy | the player, in their own purchase (`js/play` does both in one prompt) | the player, a key the player named for the order (`settler`) or the project's owner or authority settle a rolled order, a normal item too (the app's backend delivers it from the `OrderSettled` receipt of a finalized transaction whose `meta.err` is null, once per order: `loot.receipts(tx)` gives none for a transaction with an error, whose logs can still read as all-success; a truncated log stays truncated when read again, so it then goes by the order's account); a roll that failed or never came refunds (`app.refund`) |
|
|
12
|
+
| A lottery draw, then each part pulled | **anyone**: the page (`app.claims()` lists each available step), or a sweep. The draw's ask fixes the round's money (what its places take of the pot, or of the round's own ticket money for a lottery with `rest`; each pool's prize) and opens the next round at once; paid from a pot, it first moves in what the round before left that pot, so the next round always sees it, whoever pulls and whenever. A pull pays one part into its owner's account, opened in the same transaction when it is not there (the sender pays its rent): an entry's own (a refund, a ticket back), its places' part of the pot or their prizes (a raffled NFT, an item, tokens, a roll), or a part owed in common (a share, what the places leave: to the pot, or to `rest`; a burned `rest` stays for the close to burn) | selling stops at draw time. A round nobody draws waits, with no deadline. Once drawn, every part waits in the round for its own pull, in any order, holding up nobody else; the pull that leaves nothing owed closes the round, and the rent of its record goes back to whoever fronted it (the lottery's `roundRent`, or its first buyer). An NFT that can't move is made a hold for its winner. If the oracle never answers a draw (7 days after it was first asked for), the next ask cancels it, and every buyer's refund is pulled; the page pulls the sender's own refund in the cancel's own transaction. A due round's draw rides in front of the next ticket purchase. An empty round moves on with the next ticket. A one-time lottery ends after its round |
|
|
13
|
+
| A schedule that is due (an emission, rent, a drip) | the project's owner or authority, or who it pays (a wallet, the treasury): `app.runSchedule` signed by one of them, or their sweep (a sealed burn: anyone) | **a run missed is missed**: one sent late pays once, for the latest run due, and the runs before it lapse, so keep a key of the project's sending it on time. Only a sealed schedule's fixed amount pays every run due (a backlog of years takes a few calls) |
|
|
14
|
+
| Ending an auction | **anyone**: usually the winner, from the page | it waits: whenever it is ended, the lot goes to the highest bidder. A seller whose account can't take the money then doesn't hold up the winner: the lot goes, the auction stays open owing it (`app.auctions()`: `delivered`, `proceedsOwed`), and the next end, anyone's, pays and closes it |
|
|
15
|
+
| Catching a farm up (`walk_farm`), when it has passed more of its seats' end times than a move can walk | **anyone**, for free: `app.claims()` lists it, `app.walkFarm(key)` sends it in parts (`app.farmWalk(key)` says how many) | moves of that farm fail until it is walked; nothing is lost by the wait: what each seat earned by its end is exact |
|
|
16
|
+
| A contest's entries back when it is off, or a winner's part of a contest with no prize or picks | **anyone** (each entry's money goes only to its player); a winner's first claim with a prize or picks: the **winner or the project's keys**, and past 30 days the project's keys may pick for them | they wait; nothing is lost. Each winner's part is written at the decision (the pot then, and a split prize's vault), so the order of claims changes nothing; tokens sent to the pot after it go to nobody (burned at the close), and a split prize's part a winner gives up goes back to its pool, not to later winners |
|
|
17
|
+
| Opening a contest | the **authority** (the owner's wallet by default) | nothing happens |
|
|
18
|
+
| Naming its winner, calling it off | the **authority**, or **anyone** sending a verdict one of its `deciders` signed (a results server's key: `decideIx` with `verdict`, after the Ed25519 check of `verdictBytes`); a winner only once entries close, a call-off any time | after `resultDue` it is off by the clock and everyone claims back; a crowdfund short of its `minPot` when entries close is off at once |
|
|
19
|
+
| Moving tokens out of a pool, minting, a `decisions` payout, rewriting a collectible | the **authority** | nothing happens |
|
|
20
|
+
| A grant claim | the **winner** only, from the page: the winning wallet signs every claim, whatever proves it (a voucher, the list, an open grant); anyone may pay its fees, nobody claims for it | the prize waits for them |
|
|
21
|
+
| A prize given directly (a grant's, a lottery place's, a contest's, a mission's, a recipe's: an item, tokens, an NFT of a stash) | the winner's own claim (a grant's: the winner signs it), the place's payment, the winning entry's claim, the unstake or the craft: it is given in that transaction, the sender paying what it opens (a collectible's rent, the winner's token account) | a grant's claim or a craft of a prize it can't give is refused; a lottery's place, a contest's claim or a mission's end goes on without it (an NFT already given, an item sold out or switched off, a pool short pays what it holds) |
|
|
22
|
+
| A prize that is a roll of a box (a grant's, a lottery's place, a contest's winners, an auction's mystery lot, a mission's chest) | the winner's own claim (a grant's), the place's payment or the auction's end makes the winner's order (its rent goes back to the winner at settle); then the **winner or the project's keys** ask the oracle for the roll (`loot.askRollIx`, its fee the asker's) and the winner, a key the winner named, or the project's keys settle it (`loot.settleIx`), which hands over what it rolled, a stash's NFT included; `app.claims()` lists them | it waits, and is never refunded while it waits; asked and never answered, they ask again. What the box gives that has run out (an empty stash, a sold-out item) can't be won: the roll lands on its other outcomes, and a fixed prize its pool runs short of pays what the pool holds. With nothing at all in stock, only the winner asks, and that ends the roll with nothing (an auction's mystery lot: the winning bid refunded); a roll whose answer finds nothing at all waits again, for a restock |
|
|
23
|
+
| Putting more NFTs in a stash, or taking one out | the **owner** (or the authority), holding them: add them to the stash's `nfts` and publish again (only the new ones go in), or `loot.stashIx` with `out` to any wallet | a stash nobody refills just runs out |
|
|
23
24
|
|
|
24
25
|
"Anyone" means the page can offer a button for it, and any player can press it. Build those buttons with `js/play`:
|
|
25
|
-
`app.claims()` lists what this player can finish now (a draw, its
|
|
26
|
+
`app.claims()` lists what this player can finish now (a draw, its cancellation, a pull, a payout, a refund, an
|
|
26
27
|
auction to end), and `app.claim(c)` sends one. A **sweep** is a server that sends overdue public steps for the
|
|
27
28
|
projects it watches. One may or may not be running for the person's project, so never rely on it: the page's buttons
|
|
28
|
-
are what finish them. A sweep
|
|
29
|
-
|
|
30
|
-
A lottery
|
|
31
|
-
|
|
32
|
-
and
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
are what finish them. A sweep needs only its own fee payer for draws, refunds and returns; to settle or run a schedule, it needs a project key or a key the player named.
|
|
30
|
+
|
|
31
|
+
A round of a lottery is a record of its own, opened by its first purchase (the SOL the lottery holds for it,
|
|
32
|
+
`roundRent`, put in at setup by default and sized from the lottery's expected rounds and entries, pays its header, its
|
|
33
|
+
token accounts and each entry's bytes; with too little, its first buyer fronts the header and accounts, about 0.004 SOL
|
|
34
|
+
and up, and each buyer their entry; all back when the round closes, to whoever paid), and it holds its ticket money in
|
|
35
|
+
accounts of its own until each part is pulled: shares in common and burns run after the draw, and the pot holds only
|
|
36
|
+
what nobody is owed. While a round paid from a pot waits for the oracle, the next round's draw waits too: it needs that
|
|
37
|
+
round's answer to know what it left the pot. Each purchase adds an entry: funded by `roundRent`, the buyer pays only the fee and the price; a
|
|
38
|
+
buyer who pays its bytes themselves (about 0.0002 SOL) never gets them back (they go to the treasury when the round
|
|
39
|
+
closes). A share the
|
|
40
|
+
buyer chooses (a referrer) is paid as they buy. Cancelling (only for a draw the oracle never answered, or a round short of its least sale) owes every entry its
|
|
41
|
+
whole ticket payment back, with no split or burn; network and oracle fees stay spent. Prizes and outside donations
|
|
42
|
+
already in the pot stay there for the lottery; they are not ticket refunds. A round holds about 70,000 to 260,000
|
|
43
|
+
purchases, by how wide its entries are.
|
|
37
44
|
|
|
38
45
|
Every account a payment goes to is opened by the transaction that pays it, and whoever sends it pays the rent: the
|
|
39
46
|
treasury's or a payee's account at a settle, a winner's at a payout, an outbid bidder's at the next bid. Nothing waits
|
|
@@ -47,12 +54,14 @@ methods). What the project writes about one (a level, a use) goes in its Sheet (
|
|
|
47
54
|
the authority with `collectible` `Set`, by a farm's growth, or by anyone proving the traits the project vouched for
|
|
48
55
|
(`app.proveTraits`).
|
|
49
56
|
|
|
50
|
-
A new mission is a new farm: add it to the spec and publish again; `runs` ends it that long after it goes live. Near the end of a farm that shares
|
|
57
|
+
A new mission is a new farm: add it to the spec and publish again; `runs` ends it that long after it goes live. Near the end of a farm that shares what lands, fund it at least its release span (its longest lock, a day at least) before the end: what is not released by the end, and what a seat forfeits after it, stays in its rewards vault, where the authority may take back what is free (`loot.moveTokensIx` with `from: { Farm: [id] }`), never what the farm owes its seats. No seat needs a refresh at its end. If rewards cannot move, unstaking can explicitly forfeit them.
|
|
51
58
|
|
|
52
|
-
Changing a live box's roll (its outcomes, odds, `stockOdds`, `rollsOn`, `then`) fails
|
|
53
|
-
|
|
54
|
-
lottery's place,
|
|
55
|
-
|
|
59
|
+
Changing a live box's roll (its outcomes, odds, `stockOdds`, `whenOut`, `rollsOn`, `then`) fails a purchase still waiting for its
|
|
60
|
+
roll, and an auction's roll lot, each of which gets its payment back at once (`W-LIVE-ROLLS`). Any other roll waiting (a
|
|
61
|
+
grant's, a mission's chest, a lottery's place, a recipe's) holds no payment and rolls on the box as edited: nothing spent is
|
|
62
|
+
lost. A roll asked and left unanswered 7 days can be refunded when it holds a payment or an NFT (its refund names the
|
|
63
|
+
box, whose count of draws waiting it leaves: `app.refund` does). A box that stops selling when an outcome runs out (the
|
|
64
|
+
default) sells again once the project restocks it (more units, tokens into its pool, NFTs into its stash) or edits it. Its price, rise, fall, split, switch, window and gate change freely. A set's way out (the recipe that unbinds it)
|
|
56
65
|
should take no `pay` line (`W-UNBIND-PAY`): a recipe with one waits while the project is paused or the platform
|
|
57
66
|
suspends it, and the pieces stay locked in their farm meanwhile.
|
|
58
67
|
|
|
@@ -77,12 +86,21 @@ the winner claims themselves. The grant's limits (per wallet in all and per wind
|
|
|
77
86
|
total, between two dates) are the most anything can ever pay out, whatever happens to a server.
|
|
78
87
|
|
|
79
88
|
- **A list** (an airdrop, a known set of winners): the owner writes the wallets in the grant's `list`; no server.
|
|
80
|
-
- **Promo codes, claim links**: the
|
|
81
|
-
code
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
- **Promo codes, claim links**: make the codes with `makeCodes(n)` (`@xgamesfun/solana/results`: 80 bits each, never
|
|
90
|
+
fewer than 64, since each code's key is public in the spec and a short code can be found by trying them all
|
|
91
|
+
offline) and put `await codeAddresses(codes)` in the grant's `keys`, with `limits.perKey: 1`. Keep the codes out of
|
|
92
|
+
the repository. The player types a code; the page makes `codeVoucher({ code, game, grant, incarnation, wallet,
|
|
93
|
+
now })` for the player's own wallet and claims (`app.claimGrant(key, { voucher })`). A code pays once in all; a
|
|
94
|
+
signature someone copies pays only the wallet it names.
|
|
84
95
|
- **Show a badge, claim once**: an open grant (no `vouchers`, `keys` or `list`) with a `gate` and `limits.perWallet`:
|
|
85
96
|
any wallet that shows the collectible claims, and each collectible shown counts too. No server.
|
|
97
|
+
- **A wallet is not a person.** Wallets are free, so a per-wallet limit never limits a person, and a game runs in the
|
|
98
|
+
player's browser, so its own claims prove nothing. For a reward worth farming: the server decides or checks the
|
|
99
|
+
result; it pays only a wallet the player proved (`proofRequest` / `verifyProof` from `@xgamesfun/solana/results`: a
|
|
100
|
+
text the wallet signs, with the game, a nonce the server takes once, and an expiry); it counts people where it can
|
|
101
|
+
(`perPerson`, with a person hash the voucher names: that needs an outside sign-in such as X, and a wallet-only
|
|
102
|
+
player has none); it checks what a wallet holds on the chain, never the page's word (`collectiblesOf`, `holds`); and
|
|
103
|
+
the grant's `perDay` and `total` bound everything whatever happens to a key. Keep token rewards for results small.
|
|
86
104
|
- **Vouchers from a results server**: the grant names the server's public key in `vouchers`; the server checks the
|
|
87
105
|
game's result and signs a voucher (for a grant of tokens, with each winner's own `amount`, up to the grant's: rewards
|
|
88
106
|
by rank); the winner's page claims with it (`app.claimGrant(key, { voucher })`).
|
|
@@ -101,11 +119,13 @@ total, between two dates) are the most anything can ever pay out, whatever happe
|
|
|
101
119
|
- **A random draw nobody can rig** (a free draw for members): a results server's computed draws (drand), or a lottery.
|
|
102
120
|
|
|
103
121
|
Per wallet per day ("2 wins a day each") uses `limits.perWalletEach: { "count": 2, "every": "1d" }`;
|
|
104
|
-
`perDay` separately counts all wallets together. A
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
122
|
+
`perDay` separately counts all wallets together. A grant keeps every round it paid each wallet within one incarnation,
|
|
123
|
+
16 bytes a round (from the grant's funds), and each wallet's count in 28: one voucher may name several rounds, each
|
|
124
|
+
paid once, and the same voucher sent again later pays what a limit held back. Changing limits or voucher keys does not
|
|
125
|
+
clear its history. Closing and recreating a grant starts a new campaign with new records and invalidates old vouchers;
|
|
126
|
+
anyone may then close the old records (`loot.closeShardIx`), their rent to the owner. A grant for more than about
|
|
127
|
+
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
|
|
128
|
+
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
|
|
109
129
|
a result and include it in the voucher it signs.
|
|
110
130
|
|
|
111
131
|
## Before you act on what the person says happened
|
|
@@ -12,82 +12,117 @@ npm install @xgamesfun/solana
|
|
|
12
12
|
```js
|
|
13
13
|
import { solanaApp, walletStandardSigner } from '@xgamesfun/solana/play';
|
|
14
14
|
|
|
15
|
-
const spec = await (await fetch('./xsolana.json')).json();
|
|
15
|
+
const spec = await (await fetch('./xsolana.json')).json(); // the live spec, served with the page
|
|
16
16
|
|
|
17
17
|
// Connect a wallet (Phantom, Solflare, Backpack all register through the Wallet Standard).
|
|
18
18
|
const wallets = [];
|
|
19
|
-
const api = {
|
|
19
|
+
const api = {
|
|
20
|
+
register: (...w) => {
|
|
21
|
+
wallets.push(...w);
|
|
22
|
+
return () => {};
|
|
23
|
+
},
|
|
24
|
+
};
|
|
20
25
|
window.addEventListener('wallet-standard:register-wallet', ({ detail }) => detail(api));
|
|
21
26
|
window.dispatchEvent(Object.assign(new Event('wallet-standard:app-ready'), { detail: api }));
|
|
22
|
-
const wallet = wallets.find(
|
|
23
|
-
const {
|
|
27
|
+
const wallet = wallets.find(w => w.chains.some(c => c.startsWith('solana:')));
|
|
28
|
+
const {
|
|
29
|
+
accounts: [account],
|
|
30
|
+
} = await wallet.features['standard:connect'].connect();
|
|
24
31
|
|
|
25
32
|
const app = solanaApp({ spec, rpcUrl: 'https://api.devnet.solana.com', wallet: walletStandardSigner(wallet, account) });
|
|
26
33
|
|
|
27
|
-
const shop = await app.shop();
|
|
28
|
-
await app.buy('badge', { listing: shop.find(
|
|
29
|
-
const boxes = await app.boxes();
|
|
30
|
-
await app.openBox('chest', { listing: boxes.find(
|
|
31
|
-
await app.pay('entry');
|
|
34
|
+
const shop = await app.shop(); // [{ key, name, price, priceUnits, currency, active, left }]
|
|
35
|
+
await app.buy('badge', { listing: shop.find(l => l.key === 'badge') }); // the price shown is the most it pays
|
|
36
|
+
const boxes = await app.boxes(); // each box's price, outcome chances and whenOut (show it: stops selling, or draws among what is left)
|
|
37
|
+
await app.openBox('chest', { listing: boxes.find(l => l.key === 'chest') }); // pays, waits for the roll, settles
|
|
38
|
+
await app.pay('entry'); // a payment of the spec
|
|
32
39
|
const lotteries = await app.lotteries(); // [{ key, name, price, currency, status, round, sold, drawsAt, nftTicket, … }]
|
|
33
|
-
await app.buyTickets('weekly', 2, { listing: lotteries.find(
|
|
40
|
+
await app.buyTickets('weekly', 2, { listing: lotteries.find(l => l.key === 'weekly') }); // an NFT ticket: { nft: asset, listing }
|
|
34
41
|
await app.payNft(asset, { to, reference }); // pay with an NFT (burned, or given to `to`): admit the player by the receipt
|
|
35
|
-
await app.stake('farm', '10', 0);
|
|
36
|
-
await app.harvest('farm');
|
|
37
|
-
await app.staked('farm');
|
|
38
|
-
await app.refresh('guild', asset);
|
|
39
|
-
const recipes = await app.recipes();
|
|
40
|
-
await app.craft('merge', [assetA, assetB], 1, { listing: recipes.find(
|
|
41
|
-
await app.auctions();
|
|
42
|
-
await app.bid('comet', '0.6');
|
|
43
|
-
await app.endAuction('comet');
|
|
44
|
-
await app.unwrap();
|
|
45
|
-
await app.claimGrant('welcome');
|
|
46
|
-
await app.claimWins({ server, token });
|
|
47
|
-
|
|
42
|
+
await app.stake('farm', '10', 0); // tokens, at lock tier 0; app.stakeNft('farm', asset) for a collectible
|
|
43
|
+
await app.harvest('farm'); // rewards; app.unstake('farm', '10') once the lock has run
|
|
44
|
+
await app.staked('farm'); // the player's seats: [{ asset, amount, freeAt, boostPercent, waiting, rewards }]
|
|
45
|
+
await app.refresh('guild', asset); // a farm with growth: writes the NFT's points and level now (anyone may)
|
|
46
|
+
const recipes = await app.recipes(); // [{ key, name, active, version }]
|
|
47
|
+
await app.craft('merge', [assetA, assetB], 1, { listing: recipes.find(r => r.key === 'merge') }); // a changed recipe is refused
|
|
48
|
+
await app.auctions(); // each auction: high, nextBid, leading, secondsLeft, open, over, waiting (SOL)
|
|
49
|
+
await app.bid('comet', '0.6'); // the bid it tops goes back to its bidder in the same transaction
|
|
50
|
+
await app.endAuction('comet'); // once it is over, anyone: the lot to the winner { winner, asset?, rolled? }
|
|
51
|
+
await app.unwrap(); // SOL back from a bid that was topped, from wrapped SOL to plain SOL
|
|
52
|
+
await app.claimGrant('welcome'); // a wallet on the grant's list (or { voucher } from your server)
|
|
53
|
+
await app.claimWins({ server, token }); // every prize a results server vouched for or listed, claimed by the winner; [{ round, signature | error }]
|
|
54
|
+
// (`token`: the player's sign-in with that server, when it asks for one: a stash drop's NFT is given only at a signed-in claim)
|
|
48
55
|
const loan = await app.loanOffer(lender, 1); // a lender's offer: { amount, repay, lasts, risks }: show every risk's `says`
|
|
49
56
|
await app.borrow(lender, 1, asset, { acceptRisks: true }); // only once the player saw the risks (refused without it)
|
|
50
|
-
for (const c of await app.claims()) await app.claim(c);
|
|
51
|
-
|
|
52
|
-
await app.runSchedule('drip');
|
|
53
|
-
await app.
|
|
54
|
-
await app.
|
|
57
|
+
for (const c of await app.claims()) await app.claim(c); // what this player can finish: a roll to collect, a refund,
|
|
58
|
+
// a contest payout, a due draw, an auction to end
|
|
59
|
+
await app.runSchedule('drip'); // a schedule that is due (the project's keys or who it pays); null when not yet. A run missed is missed: only a sealed fixed amount pays every run due
|
|
60
|
+
await app.farmWalk('guild'); // { ends, needsWalk, parts }: end times the farm has passed; a move refuses (FarmNeedsWalk) past what it can walk
|
|
61
|
+
await app.walkFarm('guild'); // walks it in parts (anyone, free); claims() lists it when it is needed
|
|
62
|
+
await app.collectibles(); // what the player owns: [{ asset, item, name, uri, attributes }] (`item`: its key)
|
|
63
|
+
await app.balances(); // what they hold of each of the economy's tokens
|
|
55
64
|
```
|
|
56
65
|
|
|
57
66
|
- **Serving it**: a plain `index.html` with a `<script type="module">` can't import a bare `@xgamesfun/solana/play` by
|
|
58
67
|
itself. Either use a bundler (Vite: `npm create vite@latest`, then `npm install @xgamesfun/solana`), or put this import
|
|
59
68
|
map before the module script and serve the folder (`npx serve .`):
|
|
60
69
|
```html
|
|
61
|
-
<script type="importmap">
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
<script type="importmap">
|
|
71
|
+
{
|
|
72
|
+
"imports": {
|
|
73
|
+
"@xgamesfun/solana/play": "./node_modules/@xgamesfun/solana/js/play/index.js",
|
|
74
|
+
"@xgamesfun/solana/client": "./node_modules/@xgamesfun/solana/js/client/index.js"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
65
78
|
```
|
|
66
79
|
Tell the person the one command that starts it, and that it serves on devnet.
|
|
67
80
|
- **What a roll looks like**: a box of several outcomes is rolled by the oracle a few seconds after the open; `openBox`
|
|
68
81
|
waits for it. Show the player that it is rolling.
|
|
82
|
+
- **A page that closes before the settle**: the SOL its settle key holds is not lost in the same browser (on another device, with nothing stored, it can't be found yet). Build the wallet with
|
|
83
|
+
`walletStandardSigner(wallet, account)`: with a wallet that signs messages, the page asks once a session for a
|
|
84
|
+
signature over a fixed text ("loot: the keys that settle your orders…"; it moves nothing), and every settle key comes
|
|
85
|
+
from it, so a reload makes it again. With one that signs no messages, the key is kept in the page's storage,
|
|
86
|
+
encrypted. Call `app.recover()` when the page opens (after the wallet connects): it settles what rolled, sends back
|
|
87
|
+
what a key holds for an order that is gone, and leaves one still rolling for later. Say so to the player in a line
|
|
88
|
+
("Finishing an earlier purchase…").
|
|
89
|
+
- **Packs**: `app.openBox(key, { listing, count: 10 })` opens ten packs in one purchase: one wallet prompt, one oracle
|
|
90
|
+
answer, `maxPrice` a pack's. It returns `won` (every card) and `assets`; a pack too big for one transaction is
|
|
91
|
+
settled in parts, each its own transaction (`parts`: their signatures), sent by the throwaway key the purchase funds (`oneSignature`, the default),
|
|
92
|
+
so the player signs once. More packs than one oracle answer draws (or than keep an order's rent and fee within 0.01
|
|
93
|
+
SOL: `loot.opensAnOrder(box)`) are several orders in that one purchase (`orders`). A pack of fixed cards (one outcome, nothing random) is bought in one step: its cards come
|
|
94
|
+
in the purchase, no oracle and no settle (one too big for a transaction is opened instead).
|
|
95
|
+
- **A roll a route makes** (a craft, a bundle bought, a grant claimed, an auction won): the page asks for it in the
|
|
96
|
+
route's own transaction and settles it after with a throwaway key, so the player signs once. When the rolled box has
|
|
97
|
+
nothing in stock, nothing is sent and the call rejects with SoldOut: tell the player to try again later. A compressed card's id is not in `assets` (it is the tree's count when minted): read it
|
|
98
|
+
from `claims()` or the player's assets. Show each card as it lands.
|
|
69
99
|
- **What an auction looks like**: show the high bid, the least next bid (`nextBid`) and a countdown (`secondsLeft`;
|
|
70
100
|
a late bid moves the end, so read `auctions()` again after every bid). A player who was outbid has their bid back
|
|
71
101
|
already, nothing to claim: in SOL it waits in their wrapped-SOL account (`waiting`), so say "You were outbid:
|
|
72
102
|
0.5 SOL is back in your wallet" and offer `unwrap()` to turn it into plain SOL (any later purchase does it too).
|
|
73
103
|
When `over` is true, show an "End the auction" button to everyone (the winner usually presses it; it mints or
|
|
74
|
-
hands over the lot). There is no deadline: however late it is ended, the lot goes to the highest bidder.
|
|
104
|
+
hands over the lot). There is no deadline: however late it is ended, the lot goes to the highest bidder. An auction with
|
|
105
|
+
`delivered` has given its lot, and still owes its seller, whose account couldn't take the money (`proceedsOwed`): keep it
|
|
106
|
+
on the seller's page until an end (`app.endAuction`, anyone's) pays and closes it. Don't treat it as closed at its end.
|
|
75
107
|
- **NFTs the project has no authority over**: a gate or odds that name a collection find the player's Core NFTs of it
|
|
76
108
|
themselves; for a Token Metadata NFT pass `shown: { mint, owner }`, for a compressed one `shown: { compressed:
|
|
77
|
-
|
|
109
|
+
assetId, tree }` after a `loot.proveLeafIx` in the same transaction. `app.stakeNft`, `app.unstakeNft`, `app.refresh`,
|
|
78
110
|
`app.harvest`, `app.hold` and `app.endAuction` take any NFT: a Core asset, a Token Metadata mint (legacy or pNFT),
|
|
79
111
|
or a compressed NFT's asset id; the page finds its kind itself, and proves a compressed one first (its RPC must
|
|
80
112
|
offer DAS: `getAsset`, `getAssetProof`). In a farm that watches, `app.harvest` shows the NFT is still the player's,
|
|
81
113
|
and `app.stakeNft` of an NFT someone else staked and sold takes their seat over. What the project wrote about one is in
|
|
82
114
|
its Sheet (`loot.decodeSheet`).
|
|
83
115
|
- **Off-chain traits** (the spec's `traits`): the project builds the tree with `loot.traitsTree([{ nft, attributes
|
|
84
|
-
|
|
116
|
+
}])` from its NFTs' metadata and publishes each NFT's `attributes` and `proof` with the page; a player (or the page,
|
|
85
117
|
before a stake) calls `app.proveTraits(nft, attributes, proof)` once per NFT, and its boosts and gates read them.
|
|
86
118
|
- **Rolls on an NFT**: `app.openBox(boxKey, { on: asset })` for a box that rolls on one (an enchant). Leaving a
|
|
87
|
-
mission that rolls, `app.unstakeNft` returns `{ signature, roll }`; a recipe that rolls, `app.craft` returns
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
119
|
+
mission that rolls, `app.unstakeNft` returns `{ signature, roll, rolled }`; a recipe that rolls, `app.craft` returns
|
|
120
|
+
`roll` and `rolled`; a grant that gives a roll, `app.claimGrant` returns `{ signature, rolls }`; the winner ending an
|
|
121
|
+
auction of a roll, `app.endAuction` returns `rolled`. Each asks the oracle in the player's one transaction, and a
|
|
122
|
+
throwaway key that transaction funds settles it: one prompt. `rolled` (and each of `rolls`) is what the roll gave
|
|
123
|
+
(`won`, each prize it set aside, and `order.fate`: the numbers moved and where the NFT went). When the step and its
|
|
124
|
+
ask can't go together, the roll waits: `app.collect(roll)` asks for it, waits and settles it, and a page should offer
|
|
125
|
+
to collect any roll of the player's still waiting.
|
|
91
126
|
- **What a staking page shows**: `app.staked(farmKey)` lists the player's seats in a farm, each with what it is owed
|
|
92
127
|
now (`waiting`, in `rewards`), the program's own sum from a dry run of the seat (nothing signed or sent), and
|
|
93
128
|
when its lock is over (`freeAt`). Show that; don't work out rewards from the rate yourself (a sharing farm splits
|
|
@@ -95,24 +130,27 @@ await app.balances(); // what they hold of each of the econo
|
|
|
95
130
|
or a harvest.
|
|
96
131
|
- **Getting out of a farm**: after the farm lock ends, `app.unstakeNft(farmKey, asset)` releases the collectible;
|
|
97
132
|
`app.unstake(farmKey, amount)` returns tokens. If reward transfers are blocked, offer an explicit choice to give
|
|
98
|
-
up rewards with `{ forfeit: true }` as the last argument. Remove a token seat's booster before
|
|
99
|
-
its tokens.
|
|
133
|
+
up rewards with `{ forfeit: true }` as the last argument. Remove a token seat's booster before taking out all
|
|
134
|
+
its tokens. An early exit (a tier's `exitFee`) keeps the rewards the seat was credited; what is taken out leaves at
|
|
135
|
+
once (no unbonding wait). Forfeiting does not bypass the lock or the token's or NFT issuer's own controls: say which custody
|
|
100
136
|
the farm uses and never promise an unconditional exit (`telling-the-person.md`, NFT custody).
|
|
101
137
|
- **Farm token hooks and Core adapters**: `stake`, `unstake`, `stakeNft`, `unstakeNft`, and `harvest` accept
|
|
102
138
|
`extra` account metas or `transfers` keyed by mint in their options. Static Token-2022 hooks and required memos
|
|
103
139
|
resolve automatically. For dynamic hooks, provide every actual `{ source, destination, authority, amount }`
|
|
104
|
-
transfer:
|
|
105
|
-
|
|
106
|
-
pool balance. Without that plan the app fails before signing; refresh the plan if balances or fee terms change.
|
|
140
|
+
transfer: the farm's rewards vault to the player when the action pays (crediting moves no tokens); token principal
|
|
141
|
+
too when it moves. Do not guess amounts from the vault's balance. Without that plan the app fails before signing; refresh the plan if balances or fee terms change.
|
|
107
142
|
Explicit `extra` replaces automatic resolution and must include all hook, memo and Core adapter accounts.
|
|
108
143
|
For example, `app.unstakeNft('heroes', asset, { transfers: { [rewardsMint]: rewardTransfers } })`, or
|
|
109
144
|
`app.unstakeNft('heroes', asset, { extra: adapterAndTokenAccounts })`. Harvest takes options third:
|
|
110
145
|
`app.harvest('heroes', asset, { transfers: { [rewardsMint]: rewardTransfers } })`.
|
|
111
146
|
A forfeit exit skips reward-mint reads and reward ATA creation; principal transfers still need their own mint.
|
|
112
|
-
- **Lottery steps**: `claims()` lists a due draw,
|
|
113
|
-
for a draw the oracle never answered,
|
|
114
|
-
|
|
115
|
-
|
|
147
|
+
- **Lottery steps**: `claims()` lists a due draw (`reveal`), what this player is owed (`pull`), paying everyone else
|
|
148
|
+
and the shares owed in common (`payout`), and for a draw the oracle never answered, asking again or, after its
|
|
149
|
+
timeout, the cancel-and-refund (`cancel-draw`; `after-launch.md` says how the money moves). A draw that cancels the
|
|
150
|
+
round (that one, or a `reveal` of a round short of its least sale) pulls the sender's own refund in the same
|
|
151
|
+
transaction. A ticket bought when the round's draw is due sends the draw first, in the same prompt, and buys into
|
|
152
|
+
the next round (the buyer pays the oracle's 0.0008 SOL for it). Show each as a button
|
|
153
|
+
and read the list again after each one. The parts are pulled in any order: nobody waits on anyone else.
|
|
116
154
|
- **Gates**: a gated buy, ticket, stake, entry, bid or claim finds the player's collectible that passes the gate by
|
|
117
155
|
itself ("You need a Guild Pass to buy this" when there is none). Use `collectibles()` and its `item` key to show
|
|
118
156
|
what a player holds; never match NFTs by name. A list gate (`{ "list": [...] }`) takes the player's proof: the
|
|
@@ -120,8 +158,17 @@ await app.balances(); // what they hold of each of the econo
|
|
|
120
158
|
power of two as the plan's root does), or takes it from a server that keeps a long list. A stake gate
|
|
121
159
|
(`{ "staked": … }`) needs nothing from the page: js/play passes the player's seat and the farm, and says plainly when
|
|
122
160
|
the stake is too small or its lock is over (show the farm's locked tiers next to the gate).
|
|
123
|
-
- **A no-loss lottery** (`noLoss`):
|
|
124
|
-
|
|
161
|
+
- **A no-loss lottery** (`noLoss`): once the round is drawn every ticket is owed back to its buyer (a pull, which
|
|
162
|
+
anyone may send, so say "your tickets come back after the draw"), and the winners then collect the pot.
|
|
163
|
+
- **A lottery round** finishes in a few steps whatever its size: `app.claims()` lists the draw (`reveal`, whose ask
|
|
164
|
+
fixes the round's money and opens the next round at once), what this player is owed (`pull`: a refund, a place's
|
|
165
|
+
part of the pot, its prize; a raffled NFT; a roll asked for in the same transaction and settled in the same prompt)
|
|
166
|
+
and paying everyone else and the parts owed in common (`payout`); `app.claim(c)` sends one. The last pull closes
|
|
167
|
+
the round.
|
|
168
|
+
- **Repaying a loan** (the player's own, or a friend's): `app.loan(asset, due)` reads it; show its `repay` (base units
|
|
169
|
+
of its `mint`), then `app.repay(asset, due, { repay })` pays exactly that, in the payer's one prompt (a loan whose
|
|
170
|
+
debt changed since is refused); a throwaway key that transaction funds sends the NFT home to its borrower, a pNFT's
|
|
171
|
+
or a compressed NFT's too (one prompt). A lender sees an overdue unpaid loan in `claims()` (`release`, to them).
|
|
125
172
|
- **A grant that pays by rank**: a voucher with an `amount` (base units of the grant's token) pays that much, up to
|
|
126
173
|
the grant's; `app.claimGrant(key, { voucher })` passes it, and `{ proof, round, personHash, amount }` for a list.
|
|
127
174
|
- **The price the player saw is the most they pay**: `app.buy` and `app.openBox` need the listing `shop()` or
|