@xgamesfun/solana 0.0.0-stage → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/DISCLAIMER.md +41 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +13 -0
  4. package/README.md +172 -2
  5. package/bin/xsolana.mjs +189 -0
  6. package/examples/artwork-auction.json +51 -0
  7. package/examples/blind-forge.json +128 -0
  8. package/examples/coin-flip-arena.json +108 -0
  9. package/examples/duel-arena.json +137 -0
  10. package/examples/elements-set.json +281 -0
  11. package/examples/event-badges.json +67 -0
  12. package/examples/expedition-guild.json +161 -0
  13. package/examples/fair-presale.json +48 -0
  14. package/examples/falling-drop.json +90 -0
  15. package/examples/guild-missions.json +144 -0
  16. package/examples/halving-seasons.json +112 -0
  17. package/examples/hero-trials.json +96 -0
  18. package/examples/ideas.json +789 -0
  19. package/examples/level-ladder.json +65 -0
  20. package/examples/merge-2048.json +128 -0
  21. package/examples/nft-raffle.json +33 -0
  22. package/examples/paper-hands-farm.json +56 -0
  23. package/examples/partner-vault.json +115 -0
  24. package/examples/sacrifice-raffle.json +118 -0
  25. package/examples/scorched-treasury.json +84 -0
  26. package/examples/shelter-fair.json +164 -0
  27. package/examples/spark-pity.json +153 -0
  28. package/examples/stakers-draw.json +125 -0
  29. package/examples/star-fleet.json +157 -0
  30. package/examples/starforce.json +116 -0
  31. package/examples/stud-farm.json +134 -0
  32. package/examples/team-vesting.json +70 -0
  33. package/examples/trade-in-shop.json +168 -0
  34. package/js/approve/details.mjs +262 -0
  35. package/js/approve/ed25519.mjs +36 -0
  36. package/js/approve/live-check.mjs +144 -0
  37. package/js/approve/page.html +86 -0
  38. package/js/approve/page.js +203 -0
  39. package/js/approve/server.mjs +711 -0
  40. package/js/chain/base58.js +73 -0
  41. package/js/chain/borsh.js +261 -0
  42. package/js/chain/bytes.js +141 -0
  43. package/js/chain/core.js +169 -0
  44. package/js/chain/curve.js +52 -0
  45. package/js/chain/lookup.js +273 -0
  46. package/js/chain/mint.js +96 -0
  47. package/js/chain/pda.js +66 -0
  48. package/js/chain/programs.js +102 -0
  49. package/js/chain/rpc.js +217 -0
  50. package/js/chain/sha256.js +68 -0
  51. package/js/chain/token.js +85 -0
  52. package/js/chain/token2022.js +247 -0
  53. package/js/chain/tx.js +224 -0
  54. package/js/client/accounts.js +347 -0
  55. package/js/client/compressed.js +46 -0
  56. package/js/client/grant.js +240 -0
  57. package/js/client/idl/loot.js +2 -0
  58. package/js/client/idl/loot.json +17064 -0
  59. package/js/client/index.js +9 -0
  60. package/js/client/ix.js +2156 -0
  61. package/js/client/pda.js +110 -0
  62. package/js/client/policy.js +48 -0
  63. package/js/client/sizes.js +54 -0
  64. package/js/client/traits.js +44 -0
  65. package/js/connect/connect.mjs +182 -0
  66. package/js/economy/check.mjs +1991 -0
  67. package/js/economy/cost.mjs +170 -0
  68. package/js/economy/examples.mjs +114 -0
  69. package/js/economy/explain.mjs +235 -0
  70. package/js/economy/guide.mjs +231 -0
  71. package/js/economy/legal.mjs +15 -0
  72. package/js/economy/live.mjs +107 -0
  73. package/js/economy/model.mjs +583 -0
  74. package/js/economy/plan.mjs +887 -0
  75. package/js/economy/schema.json +2923 -0
  76. package/js/economy/secrets.mjs +60 -0
  77. package/js/economy/simulate.mjs +952 -0
  78. package/js/economy/txsize.mjs +45 -0
  79. package/js/economy/validate.mjs +85 -0
  80. package/js/index.js +10 -0
  81. package/js/mcp/server.mjs +113 -0
  82. package/js/mcp/tools.mjs +479 -0
  83. package/js/play/index.js +2109 -0
  84. package/js/results/index.js +184 -0
  85. package/js/results/wire.js +90 -0
  86. package/package.json +100 -4
  87. package/release.json +8 -0
  88. package/skills/xsolana-economy/SKILL.md +105 -0
  89. package/skills/xsolana-economy/references/after-launch.md +114 -0
  90. package/skills/xsolana-economy/references/apps.md +157 -0
  91. package/skills/xsolana-economy/references/sentence-to-spec.md +172 -0
  92. package/skills/xsolana-economy/references/telling-the-person.md +93 -0
  93. package/types/chain/base58.d.ts +10 -0
  94. package/types/chain/borsh.d.ts +23 -0
  95. package/types/chain/bytes.d.ts +45 -0
  96. package/types/chain/core.d.ts +50 -0
  97. package/types/chain/curve.d.ts +1 -0
  98. package/types/chain/lookup.d.ts +191 -0
  99. package/types/chain/mint.d.ts +25 -0
  100. package/types/chain/pda.d.ts +25 -0
  101. package/types/chain/programs.d.ts +65 -0
  102. package/types/chain/rpc.d.ts +66 -0
  103. package/types/chain/sha256.d.ts +1 -0
  104. package/types/chain/token.d.ts +141 -0
  105. package/types/chain/token2022.d.ts +53 -0
  106. package/types/chain/tx.d.ts +60 -0
  107. package/types/client/accounts.d.ts +178 -0
  108. package/types/client/compressed.d.ts +29 -0
  109. package/types/client/grant.d.ts +125 -0
  110. package/types/client/idl/loot.d.ts +4467 -0
  111. package/types/client/index.d.ts +8 -0
  112. package/types/client/ix.d.ts +1687 -0
  113. package/types/client/pda.d.ts +91 -0
  114. package/types/client/policy.d.ts +23 -0
  115. package/types/client/sizes.d.ts +13 -0
  116. package/types/client/traits.d.ts +12 -0
  117. package/types/economy/check.d.mts +105 -0
  118. package/types/economy/cost.d.mts +68 -0
  119. package/types/economy/examples.d.mts +27 -0
  120. package/types/economy/explain.d.mts +19 -0
  121. package/types/economy/guide.d.mts +140 -0
  122. package/types/economy/legal.d.mts +12 -0
  123. package/types/economy/live.d.mts +109 -0
  124. package/types/economy/model.d.mts +363 -0
  125. package/types/economy/plan.d.mts +1013 -0
  126. package/types/economy/secrets.d.mts +4 -0
  127. package/types/economy/simulate.d.mts +156 -0
  128. package/types/economy/txsize.d.mts +17 -0
  129. package/types/economy/validate.d.mts +3 -0
  130. package/types/index.d.ts +9 -0
  131. package/types/mcp/tools.d.mts +627 -0
  132. package/types/play/index.d.ts +469 -0
  133. package/types/results/index.d.ts +155 -0
  134. package/types/results/wire.d.ts +122 -0
@@ -0,0 +1,240 @@
1
+ // Grants: the bytes every voucher signer signs and `claim_grant` rebuilds, the approved list's Merkle tree, the
2
+ // Ed25519 precompile instruction that carries a voucher's signature, and the claim itself. Browser-safe.
3
+ //
4
+ // voucherBytes "loot-grant-v4" | program (32) | grant (32) | incarnation (u64 LE) | wallet (32) | person_hash (32) | round (16) | expires (i64 LE) | amount (u64 LE)
5
+ // listLeaf sha256("loot-list-v3" | wallet (32) | person_hash (32) | round (16) | amount (u64 LE)): a wallet's win of one round
6
+ //
7
+ // `amount`: the tokens this claim gives, up to the grant's fixed amount (a rank's reward, a share); 0 for the grant's
8
+ // prize as it is.
9
+ // keyLeaf sha256("loot-key-v1" | voucher key (32)): a key under a grant's key root (a promo code's)
10
+ // merkleRoot a node = sha256(min(a, b) | max(a, b)) (byte-wise); an odd node at a level goes up unchanged
11
+ // merkleProof the siblings from the leaf up (a promoted level adds none)
12
+ //
13
+ // A voucher's `person_hash` names one person across wallets (who decides that is the signer: a play host that saw
14
+ // a linked account, the project's own server). 32 zero bytes when there is none. Fixed vectors of these bytes are in
15
+ // the tests (js/test/client.test.mjs).
16
+ import { addressBytes } from '../chain/base58.js';
17
+ import { compareBytes, concat, fromHex, intLE, readUintLE, toBigInt, uintLE, utf8 } from '../chain/bytes.js';
18
+ import { sha256 } from '../chain/sha256.js';
19
+ import { ASSOCIATED_TOKEN_PROGRAM, gateAccounts, lootIx, prizeMetas, prizeNonce } from './ix.js';
20
+ import { LOOT_PROGRAM, pda } from './pda.js';
21
+ import IDL from './idl/loot.js';
22
+
23
+ export const VOUCHER_DOMAIN = 'loot-grant-v4';
24
+ export const LIST_DOMAIN = 'loot-list-v3';
25
+ export const KEY_DOMAIN = 'loot-key-v1';
26
+ /** 13 + 32 × 4 + 8 + 16 + 8. */
27
+ export const VOUCHER_BYTES = 181;
28
+ export const ZERO_HASH = new Uint8Array(32);
29
+ export const ED25519_PROGRAM = 'Ed25519SigVerify111111111111111111111111111';
30
+ /** The seconds of the day a grant's daily limit counts claims in (the IDL's `DAY_SECONDS`). */
31
+ const DAY_SECONDS = Number(IDL.constants.find((c) => c.name === 'DAY_SECONDS').value);
32
+ /** The day the program counts `per_day` in. */
33
+ export const dayOf = (unixSeconds) => Math.floor(Number(unixSeconds) / DAY_SECONDS);
34
+
35
+ /** 32 bytes from a base58 address, 32 bytes or 64 hex characters. */
36
+ function bytes32(value, what) {
37
+ if (value instanceof Uint8Array) {
38
+ if (value.length !== 32) throw new RangeError(`${what} must be 32 bytes, not ${value.length}`);
39
+ return value;
40
+ }
41
+ if (typeof value === 'string' && /^[0-9a-f]{64}$/i.test(value)) return fromHex(value);
42
+ if (typeof value === 'string') return addressBytes(value);
43
+ throw new TypeError(`${what} must be an address or 32 bytes`);
44
+ }
45
+
46
+ const personBytes = (value) => (value === null || value === undefined || value === '' ? ZERO_HASH : bytes32(value, 'person_hash'));
47
+
48
+ function roundBytes(value) {
49
+ const out = value instanceof Uint8Array ? value : typeof value === 'string' && /^[0-9a-f]{32}$/i.test(value) ? fromHex(value) : null;
50
+ if (!out || out.length !== 16) throw new RangeError('round must be 16 bytes (32 hex characters)');
51
+ return out;
52
+ }
53
+
54
+ /** An i64 little-endian (two's complement), as the program reads `expires`. */
55
+ export const i64LE = (value) => intLE(value, 8);
56
+
57
+ /**
58
+ * The 181 bytes a voucher key signs and `claim_grant` rebuilds from its own accounts and arguments. `round` names the
59
+ * win (the project round, draw or giveaway it was won in): the program pays a wallet each round once per grant incarnation.
60
+ */
61
+ export function voucherBytes({ program = LOOT_PROGRAM, grant, incarnation, wallet, personHash, round, expires, amount = 0n }) {
62
+ const out = concat(utf8(VOUCHER_DOMAIN), bytes32(program, 'program'), bytes32(grant, 'grant'), uintLE(incarnation, 8), bytes32(wallet, 'wallet'), personBytes(personHash), roundBytes(round), i64LE(expires), uintLE(amount, 8));
63
+ if (out.length !== VOUCHER_BYTES) throw new Error(`a voucher is ${VOUCHER_BYTES} bytes, not ${out.length}`);
64
+ return out;
65
+ }
66
+
67
+ /** The fields back from voucher bytes (addresses as bytes). */
68
+ export function readVoucher(bytes) {
69
+ if (!(bytes instanceof Uint8Array) || bytes.length !== VOUCHER_BYTES) throw new RangeError(`a voucher is ${VOUCHER_BYTES} bytes`);
70
+ const n = VOUCHER_DOMAIN.length;
71
+ if (new TextDecoder().decode(bytes.subarray(0, n)) !== VOUCHER_DOMAIN) throw new Error(`not a ${VOUCHER_DOMAIN} voucher`);
72
+ const at = (i, len) => bytes.slice(i, i + len);
73
+ let expires = 0n;
74
+ for (let i = 7; i >= 0; i--) expires = (expires << 8n) | BigInt(bytes[n + 152 + i]);
75
+ if (expires >= 1n << 63n) expires -= 1n << 64n;
76
+ return { program: at(n, 32), grant: at(n + 32, 32), incarnation: readUintLE(bytes, n + 64, 8), wallet: at(n + 72, 32), personHash: at(n + 104, 32), round: at(n + 136, 16), expires, amount: readUintLE(bytes, n + 160, 8) };
77
+ }
78
+
79
+ /** A voucher key's leaf under a grant's `key_root`. */
80
+ export const keyLeaf = (key) => sha256(concat(utf8(KEY_DOMAIN), bytes32(key, 'key')));
81
+
82
+ /** A list entry's round as the leaf names it: a number (a spec's `round`) in the first 8 of 16 bytes; 0 is zeros. */
83
+ export const listRound = (n = 0) => {
84
+ const out = new Uint8Array(16);
85
+ out.set(uintLE(n, 8));
86
+ return out;
87
+ };
88
+
89
+ /**
90
+ * A leaf of an approved list: `wallet` won `round` (16 bytes or 32 hex characters; zeros when not given). A round is
91
+ * paid once, so a list can name the winners of many rounds, and a wallet's earlier wins stay claimable when a new
92
+ * list adds later ones.
93
+ */
94
+ export const listLeaf = (wallet, personHash, round, amount = 0n) => sha256(concat(utf8(LIST_DOMAIN), bytes32(wallet, 'wallet'), personBytes(personHash), roundBytes(round ?? new Uint8Array(16)), uintLE(amount, 8)));
95
+
96
+ /** The prefix of a wallet's leaf in a list gate (`gateListLeaf`). */
97
+ export const GATE_LIST_DOMAIN = 'loot-list-v1';
98
+ /** A wallet's leaf in a list gate (`Gate::List`): sha256("loot-list-v1" | wallet). Its tree is a list's
99
+ * (`merkleRoot`, `merkleProof`), and its proof's nodes go where an NFT shown would (`gateAccounts`'s `listProof`). */
100
+ export const gateListLeaf = (wallet) => sha256(concat(utf8(GATE_LIST_DOMAIN), bytes32(wallet, 'wallet')));
101
+
102
+ /** The parent of two nodes: sha256 of the smaller then the larger. */
103
+ export const parentOf = (a, b) => (compareBytes(a, b) <= 0 ? sha256(concat(a, b)) : sha256(concat(b, a)));
104
+
105
+ function levelsOf(leaves) {
106
+ if (!leaves.length) throw new RangeError('a list needs at least one wallet');
107
+ const levels = [leaves.map((l) => bytes32(l, 'leaf'))];
108
+ while (levels.at(-1).length > 1) {
109
+ const level = levels.at(-1);
110
+ const next = [];
111
+ for (let i = 0; i < level.length; i += 2) next.push(i + 1 < level.length ? parentOf(level[i], level[i + 1]) : level[i]);
112
+ levels.push(next);
113
+ }
114
+ return levels;
115
+ }
116
+
117
+ /** The root of the leaves, in the order given. Whoever builds the list keeps that order. */
118
+ export const merkleRoot = (leaves) => levelsOf(leaves).at(-1)[0];
119
+
120
+ /** The siblings of leaf `index`, from the bottom up. */
121
+ export function merkleProof(leaves, index) {
122
+ const levels = levelsOf(leaves);
123
+ if (!Number.isInteger(index) || index < 0 || index >= levels[0].length) throw new RangeError('no leaf at that index');
124
+ const proof = [];
125
+ let i = index;
126
+ for (const level of levels.slice(0, -1)) {
127
+ const sibling = i % 2 ? i - 1 : i + 1;
128
+ if (sibling < level.length) proof.push(level[sibling]);
129
+ i = Math.floor(i / 2);
130
+ }
131
+ return proof;
132
+ }
133
+
134
+ /**
135
+ * A list gate's tree (`Gate::List`) for `wallets`, in the order given: its `root`, its `depth`, and `proofOf(wallet)`.
136
+ * The gate reads exactly `depth` nodes, so the leaves are filled up to a power of two with a leaf no wallet has (all
137
+ * zeros): every wallet's proof is then `depth` nodes long.
138
+ */
139
+ export function gateList(wallets) {
140
+ if (!wallets.length) throw new RangeError('a list needs at least one wallet');
141
+ const leaves = wallets.map((w) => gateListLeaf(w));
142
+ let size = 1;
143
+ while (size < leaves.length) size *= 2;
144
+ // Filled with a leaf no wallet has (all zeros): a wallet's leaf is a hash, so nobody can prove a filler.
145
+ while (leaves.length < size) leaves.push(new Uint8Array(32));
146
+ return {
147
+ root: merkleRoot(leaves),
148
+ depth: Math.log2(size),
149
+ proofOf: (wallet) => {
150
+ const at = wallets.indexOf(wallet);
151
+ if (at < 0) throw new RangeError(`${wallet} is not on the list`);
152
+ return merkleProof(leaves, at);
153
+ },
154
+ };
155
+ }
156
+
157
+ /** Whether `proof` leads from `leaf` to `root`. */
158
+ export function verifyProof(leaf, proof, root) {
159
+ let node = bytes32(leaf, 'leaf');
160
+ for (const sibling of proof) node = parentOf(node, bytes32(sibling, 'proof'));
161
+ return compareBytes(node, bytes32(root, 'root')) === 0;
162
+ }
163
+
164
+ /**
165
+ * The Ed25519 precompile instruction for one signature, everything in its own data: count 1, padding, seven u16
166
+ * offsets with instruction index 0xffff (this instruction), then the key at 16, the signature at 48, the message at
167
+ * 112. `claim_grant` reads it through the instructions sysvar, in the instruction right before itself.
168
+ */
169
+ export function ed25519Ix({ publicKey, signature, message }) {
170
+ const key = bytes32(publicKey, 'publicKey');
171
+ if (!(signature instanceof Uint8Array) || signature.length !== 64) throw new RangeError('signature must be 64 bytes');
172
+ const keyAt = 16;
173
+ const signatureAt = keyAt + 32;
174
+ const messageAt = signatureAt + 64;
175
+ const u16 = (n) => uintLE(n, 2);
176
+ const THIS = 0xffff;
177
+ const data = concat(Uint8Array.of(1, 0), u16(signatureAt), u16(THIS), u16(keyAt), u16(THIS), u16(messageAt), u16(message.length), u16(THIS), key, signature, message);
178
+ return { programId: ED25519_PROGRAM, keys: [], data };
179
+ }
180
+
181
+ /**
182
+ * The claim of grant `id` for `wallet`, sent and signed by `signer` (anyone: the play host, the owner's server, the
183
+ * player). The grant's funds pay its rent through the signer; what they can't cover, the signer pays. The signer pays
184
+ * the player fee with its own SOL. `grant` is the decoded Grant; `count` the wallet's receipt count before this claim (0 for a
185
+ * first claim); `poolMint` the mint of a Payout's pool, read from the pool.
186
+ * A voucher claim goes right after `ed25519Ix` in the same transaction, `voucherKey` the key that signed it (its
187
+ * counter is passed when the grant limits keys; a key under the key root carries its `proof`); a list claim carries
188
+ * its `proof`; an open grant (no keys, no key root, no list) needs neither. What passes its gate (`shown`, or
189
+ * `listProof` for a list gate) comes first. Its prize (`grant.gives`, `prizeMetas`)
190
+ * follows: `poolMint` for tokens from a pool, `item` (the decoded Item) for a collectible or a compressed NFT of one, `nft` for an NFT of a stash;
191
+ * a roll's order at `nonce` (`prizeNonce(grant, round, count)` by default), then the rolled box's prize mints
192
+ * (`rollMints`: `boxes` maps the box's id to its decoded LootBox, null for one never written; `rollPools` each pool its
193
+ * prizes name to its mint, null for one never opened). The grant's funds pay what it opens.
194
+ */
195
+ export function claimGrantIx({ project, id, grant, wallet, signer, count = 0, round, expires, personHash, amount = 0n, proof = [], poolMint, item, nft, boxes = {}, rollPools = {}, shown, listProof, programs, voucherKey, nonce, extra = [] }) {
196
+ if (!signer) throw new Error('A claim needs its signer: whoever sends it');
197
+ // Every claim is its winning wallet's own: the wallet signs it, whatever proves it (a voucher, a list, an open
198
+ // grant), so nobody claims for a player (WalletMustSign). `signer` pays the fees and what the claim opens: the
199
+ // wallet itself, or anyone who pays for it.
200
+ const address = pda.grant(project, id);
201
+ const person = personBytes(personHash);
202
+ const hasPerson = person.some((b) => b !== 0);
203
+ // A person a voucher names is counted when the grant limits persons; an open grant gated by an NFT
204
+ // counts the NFT shown, at a badge's address (a grant never does both; both go in `person_receipt`).
205
+ const badge = typeof shown === 'string' ? shown : shown?.asset ?? shown?.mint ?? shown?.compressed;
206
+ const counted = hasPerson && Number(grant.limits?.per_person ?? 0) > 0
207
+ ? pda.person(address, grant.incarnation, person)
208
+ : grant.open && grant.gate?.Holds && badge ? pda.badge(address, grant.incarnation, badge) : null;
209
+ const accounts = {
210
+ project,
211
+ grant: address,
212
+ signer,
213
+ wallet,
214
+ wallet_receipt: pda.grantReceipt(address, grant.incarnation, wallet),
215
+ person_receipt: counted,
216
+ code_receipt: voucherKey && Number(grant.limits?.per_key ?? 0) ? pda.code(address, grant.incarnation, voucherKey) : null,
217
+ token_program: undefined,
218
+ token_2022_program: undefined,
219
+ associated_token_program: ASSOCIATED_TOKEN_PROGRAM,
220
+ };
221
+ const roundAt = roundBytes(round ?? new Uint8Array(16));
222
+ nonce = toBigInt(nonce ?? prizeNonce(address, roundAt, toBigInt(count)));
223
+ // The prize's accounts, after an NFT the wallet shows: `poolMint` a pool's mint, `item` the decoded Item of a
224
+ // collectible (its next serial), `nft` an NFT's kind as `piece` takes it.
225
+ const gives = grant.gives;
226
+ const pool = gives.Tokens?.from.Pool?.[0];
227
+ const itemId = gives.Item?.[0] ?? gives.Compressed?.item;
228
+ if (pool !== undefined && !poolMint) throw new Error('A payout claim needs its pool’s mint (poolMint), read from the pool');
229
+ const prize = prizeMetas({
230
+ project, prize: gives, winner: wallet, nonce, programs,
231
+ poolMints: pool !== undefined ? { [pool]: poolMint } : {}, items: itemId !== undefined ? { [itemId]: item } : {}, nft,
232
+ strict: true, boxes, rollPools,
233
+ });
234
+ return lootIx(
235
+ 'claim_grant',
236
+ { round: roundAt, expires: toBigInt(expires ?? 0), person_hash: person, amount: toBigInt(amount), proof: proof.map((p) => bytes32(p, 'proof')), nonce },
237
+ accounts,
238
+ { remaining: [...gateAccounts(project, { gate: grant.gate, wallet, shown, listProof }), ...prize, ...extra], signer: ['wallet'] },
239
+ );
240
+ }