@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.
Files changed (112) hide show
  1. package/README.md +95 -52
  2. package/bin/xsolana.mjs +23 -8
  3. package/examples/blind-forge.json +2 -4
  4. package/examples/coin-flip-arena.json +4 -12
  5. package/examples/duel-arena.json +1 -4
  6. package/examples/elements-set.json +2 -9
  7. package/examples/expedition-guild.json +1 -8
  8. package/examples/fair-presale.json +1 -3
  9. package/examples/guild-missions.json +3 -10
  10. package/examples/halving-seasons.json +4 -9
  11. package/examples/hero-trials.json +15 -6
  12. package/examples/holder-den.json +38 -0
  13. package/examples/holders-drop.json +105 -0
  14. package/examples/ideas.json +788 -788
  15. package/examples/match-predictions.json +24 -0
  16. package/examples/merge-2048.json +5 -5
  17. package/examples/nft-raffle.json +1 -0
  18. package/examples/pack-league.json +108 -0
  19. package/examples/paper-hands-farm.json +4 -16
  20. package/examples/partner-vault.json +20 -7
  21. package/examples/sacrifice-raffle.json +2 -10
  22. package/examples/shelter-fair.json +1 -0
  23. package/examples/stakers-draw.json +4 -15
  24. package/examples/star-fleet.json +1 -8
  25. package/examples/starforce.json +6 -6
  26. package/examples/stride-sneakers.json +164 -0
  27. package/examples/trade-in-shop.json +2 -3
  28. package/js/approve/details.mjs +122 -52
  29. package/js/approve/ed25519.mjs +5 -1
  30. package/js/approve/live-check.mjs +83 -26
  31. package/js/approve/page.html +217 -78
  32. package/js/approve/page.js +43 -15
  33. package/js/approve/server.mjs +290 -73
  34. package/js/chain/base58.js +7 -1
  35. package/js/chain/borsh.js +24 -11
  36. package/js/chain/bytes.js +11 -8
  37. package/js/chain/core.js +35 -7
  38. package/js/chain/curve.js +1 -1
  39. package/js/chain/lookup.js +124 -36
  40. package/js/chain/mint.js +6 -2
  41. package/js/chain/pda.js +2 -2
  42. package/js/chain/programs.js +6 -6
  43. package/js/chain/rpc.js +44 -16
  44. package/js/chain/token.js +13 -2
  45. package/js/chain/token2022.js +55 -16
  46. package/js/chain/tx.js +32 -22
  47. package/js/client/accounts.js +653 -131
  48. package/js/client/compressed.js +4 -3
  49. package/js/client/grant.js +267 -83
  50. package/js/client/idl/loot.js +1 -1
  51. package/js/client/idl/loot.json +2406 -2176
  52. package/js/client/index.js +1 -0
  53. package/js/client/ix.js +3059 -664
  54. package/js/client/pda.js +39 -33
  55. package/js/client/policy.js +52 -24
  56. package/js/client/random.js +70 -0
  57. package/js/client/sizes.js +414 -16
  58. package/js/client/traits.js +14 -5
  59. package/js/connect/connect.mjs +59 -17
  60. package/js/economy/check.mjs +4153 -725
  61. package/js/economy/cost.mjs +403 -64
  62. package/js/economy/examples.mjs +176 -35
  63. package/js/economy/explain.mjs +466 -111
  64. package/js/economy/guide.mjs +296 -72
  65. package/js/economy/legal.mjs +12 -6
  66. package/js/economy/live.mjs +83 -19
  67. package/js/economy/model.mjs +462 -124
  68. package/js/economy/plan.mjs +1306 -219
  69. package/js/economy/schema.json +390 -484
  70. package/js/economy/secrets.mjs +17 -7
  71. package/js/economy/simulate.mjs +883 -223
  72. package/js/economy/txsize.mjs +15 -3
  73. package/js/economy/validate.mjs +61 -24
  74. package/js/mcp/server.mjs +42 -14
  75. package/js/mcp/tools.mjs +307 -88
  76. package/js/play/index.js +2864 -619
  77. package/js/play/keys.js +193 -0
  78. package/js/results/index.js +223 -30
  79. package/js/results/server.js +230 -0
  80. package/js/results/wire.js +64 -16
  81. package/package.json +6 -4
  82. package/release.json +5 -5
  83. package/skills/xsolana-economy/SKILL.md +39 -21
  84. package/skills/xsolana-economy/references/after-launch.md +58 -38
  85. package/skills/xsolana-economy/references/apps.md +97 -50
  86. package/skills/xsolana-economy/references/sentence-to-spec.md +148 -121
  87. package/skills/xsolana-economy/references/telling-the-person.md +61 -17
  88. package/types/chain/core.d.ts +4 -2
  89. package/types/chain/lookup.d.ts +5 -2
  90. package/types/chain/rpc.d.ts +3 -1
  91. package/types/client/accounts.d.ts +217 -56
  92. package/types/client/grant.d.ts +72 -27
  93. package/types/client/idl/loot.d.ts +602 -549
  94. package/types/client/index.d.ts +1 -0
  95. package/types/client/ix.d.ts +3884 -247
  96. package/types/client/pda.d.ts +22 -16
  97. package/types/client/policy.d.ts +39 -14
  98. package/types/client/random.d.ts +26 -0
  99. package/types/client/sizes.d.ts +93 -5
  100. package/types/economy/check.d.mts +5 -10
  101. package/types/economy/cost.d.mts +2 -1
  102. package/types/economy/guide.d.mts +5 -0
  103. package/types/economy/live.d.mts +16 -1
  104. package/types/economy/model.d.mts +151 -38
  105. package/types/economy/plan.d.mts +237 -194
  106. package/types/economy/simulate.d.mts +21 -13
  107. package/types/mcp/tools.d.mts +21 -13
  108. package/types/play/index.d.ts +453 -53
  109. package/types/play/keys.d.ts +51 -0
  110. package/types/results/index.d.ts +7 -6
  111. package/types/results/server.d.ts +126 -0
  112. package/types/results/wire.d.ts +1 -1
@@ -4,12 +4,18 @@
4
4
 
5
5
  /** Each legal flag's sentence, for the person who approves, by the checker's warning code. */
6
6
  export const LEGAL = Object.freeze({
7
- 'W-CHANCE-PRIZE': 'Players pay for a chance at money (a box or a lottery that pays a prize, a bet on an outcome). That is gambling in many places: before real money, get a lawyer, and gate it by age and region.',
8
- 'W-CHANCE-ITEMS': 'Players pay for random items they can resell (a loot box). Some places treat that as gambling and many require the odds shown: show them on the page, and get a lawyer before real money.',
9
- 'W-AUCTION-SHILL': 'Anyone, the seller included, can bid to push an auction’s price up (shill bidding): a reserve is the honest way to set a floor.',
10
- 'W-REVENUE-SHARE': 'Stakers are paid from what players spend, which regulators look at when they decide whether a token is a security: before real money, get a lawyer, or pay rewards from emissions instead.',
7
+ 'W-CHANCE-PRIZE':
8
+ 'Players pay for a chance at money (a box or a lottery that pays a prize, a bet on an outcome). That is gambling in many places: before real money, get a lawyer, and gate it by age and region.',
9
+ 'W-CHANCE-ITEMS':
10
+ 'Players pay for random items they can resell (a loot box). Some places treat that as gambling and many require the odds shown: show them on the page, and get a lawyer before real money.',
11
+ 'W-AUCTION-SHILL':
12
+ 'Anyone, the seller included, can bid to push an auction’s price up (shill bidding): a reserve is the honest way to set a floor.',
13
+ 'W-REVENUE-SHARE':
14
+ 'Stakers are paid from what players spend, which regulators look at when they decide whether a token is a security: before real money, get a lawyer, or pay rewards from emissions instead.',
11
15
  });
12
16
 
13
17
  /** The legal flags a checker verdict raises, each once, in the order the checker raised them: [{ code, says }]. */
14
- export const legalFlags = (verdict) =>
15
- [...new Set((verdict?.warnings ?? []).map((w) => w.code))].filter((code) => Object.hasOwn(LEGAL, code)).map((code) => ({ code, says: LEGAL[code] }));
18
+ export const legalFlags = verdict =>
19
+ [...new Set((verdict?.warnings ?? []).map(w => w.code))]
20
+ .filter(code => Object.hasOwn(LEGAL, code))
21
+ .map(code => ({ code, says: LEGAL[code] }));
@@ -3,48 +3,96 @@
3
3
  import { encodeBase58 } from '../chain/base58.js';
4
4
  import { randomBytes } from '../chain/bytes.js';
5
5
  import { LOOT_PROGRAM } from '../client/pda.js';
6
- import { compressedTree, model } from './model.mjs';
6
+ import { compressedTree, listOf, model } from './model.mjs';
7
7
  import { auctionSeller, plan, setupContext } from './plan.mjs';
8
8
 
9
9
  /** A key for each fresh account the plan creates. Their secret halves are made by whoever signs (the page). */
10
10
  export function freshAddresses(names, random = randomBytes) {
11
- return Object.fromEntries(names.map((n) => [n, encodeBase58(random(32))]));
11
+ return Object.fromEntries(names.map(n => [n, encodeBase58(random(32))]));
12
12
  }
13
13
 
14
14
  /**
15
15
  * The transactions that put `economy` live for `owner`, with `keys` the addresses of the fresh accounts (each signs
16
16
  * the transaction that creates it). `live`: what is already live (a change publishes only what differs).
17
17
  */
18
- export function prepare(economy, { owner, keys, live = null, now = Math.floor(Date.now() / 1000), rent, only = null }) {
18
+ export function prepare(
19
+ economy,
20
+ { owner, keys, live = null, now = Math.floor(Date.now() / 1000), rent, only = null, slot = null },
21
+ ) {
19
22
  const m = model(economy, { now });
20
- const ctx = setupContext(m, { owner, keys, live: live ?? {}, rent });
23
+ // `slot`: a recent slot (in the slot hashes now), which the project's standing lookup table is made at.
24
+ const ctx = { ...setupContext(m, { owner, keys, live: live ?? {}, rent }), slot };
21
25
  const p = plan(economy, { now, ctx, live, only });
22
26
  if (!p.ok) return p;
27
+ // Going live always makes the project's standing lookup table (the plan's "table" step). Without a recent slot it
28
+ // can't be made: refused when a part's widest transaction needs it (`wideParts`), so no player is left with a
29
+ // transaction only a table carries in one approval.
30
+ const wide = p.steps.some(s => s.id === 'table') && slot == null ? wideParts(m, p.warnings) : [];
31
+ if (wide.length)
32
+ return {
33
+ ok: false,
34
+ warnings: p.warnings,
35
+ refusals: [
36
+ {
37
+ code: 'E-NO-TABLE',
38
+ where: wide[0].where,
39
+ message: `Going live makes the project’s standing lookup table, and no recent slot was read to make it at: ${wide.map(x => x.why).join('; ')}. Without the table each of those is a wallet’s own table first (more approvals), or can’t be sent at all by a wallet that sends legacy transactions only.`,
40
+ fix: 'Try again once the cluster answers (the approval page reads a recent slot from its slot hashes).',
41
+ },
42
+ ],
43
+ };
23
44
  return { ...p, ctx };
24
45
  }
25
46
 
47
+ /**
48
+ * The parts of an economy whose widest transaction passes a legacy transaction, so a player sends it in one approval
49
+ * only with the project's standing lookup table: a box's open or settle and a craft the checker sizes past one
50
+ * (W-TX-V0, W-SETTLE-V0), an auction whose bids are NFTs (each moves with its own accounts), and a farm of another
51
+ * collection's NFTs (a pNFT's stake and exit carry its token record and rule set). [{ where, why }]
52
+ */
53
+ export function wideParts(m, warnings = []) {
54
+ const out = warnings
55
+ .filter(w => w.code === 'W-TX-V0' || w.code === 'W-SETTLE-V0')
56
+ .map(w => ({
57
+ where: w.where,
58
+ why: `${w.where} (${w.code === 'W-TX-V0' ? 'its open, buy or craft' : 'its settle'} passes a legacy transaction)`,
59
+ }));
60
+ for (const a of m.auctions)
61
+ if (a.bidNfts) out.push({ where: `economy.auctions.${a.key}`, why: `the auction “${a.name}” (its bids are NFTs)` });
62
+ for (const f of m.farms)
63
+ if (f.stakes === 'collectibles' && listOf(f.gate).some(g => g?.collection))
64
+ out.push({
65
+ where: `economy.farms.${f.key}`,
66
+ why: `the farm “${f.name}” (it stakes another collection’s NFTs, a pNFT’s stake and exit pass a legacy transaction)`,
67
+ });
68
+ return out;
69
+ }
70
+
26
71
  /**
27
72
  * The spec once its setup landed: every part keeps the program number it was given, and `live` names the game, its
28
73
  * collection, the treasury, the mints made and the program, with a copy of what is now on chain.
29
74
  */
30
75
  export function recordLanded(economy, { ctx, signatures = [], at = new Date().toISOString(), now }) {
31
76
  const m = model(economy, { now });
32
- const ids = (list, field = 'id') => list.map((x) => x[field]);
33
- const withIds = (key, list, field = 'id') => (economy[key] ?? []).map((part, i) => ({ ...part, [field]: ids(list, field)[i] }));
77
+ const ids = (list, field = 'id') => list.map(x => x[field]);
78
+ const withIds = (key, list, field = 'id') =>
79
+ (economy[key] ?? []).map((part, i) => ({ ...part, [field]: ids(list, field)[i] }));
34
80
  const out = { ...economy };
35
81
  out.items = (economy.items ?? []).map((it, i) => {
36
- const shop = m.shop.find((b) => b.shopOf === it.key);
82
+ const shop = m.shop.find(b => b.shopOf === it.key);
37
83
  return { ...it, id: m.items[i].id, ...(shop ? { shopBox: shop.id } : {}) };
38
84
  });
39
85
  out.pools = withIds('pools', m.pools, 'index');
40
86
  out.boxes = withIds('boxes', m.boxes);
41
- for (const k of ['stashes', 'recipes', 'schedules', 'lotteries', 'farms', 'grants', 'auctions']) if (economy[k]) out[k] = withIds(k, m[k]);
42
- // A farm that pays nothing keeps the number of the empty pool made for it, so a pool added later never takes it.
43
- if (economy.farms) out.farms = out.farms.map((f, i) => (m.farms[i].unpaid ? { ...f, rewardsIndex: m.pool.get(m.farms[i].rewards).index } : f));
87
+ for (const k of ['stashes', 'tables', 'recipes', 'schedules', 'lotteries', 'farms', 'grants', 'auctions'])
88
+ if (economy[k]) out[k] = withIds(k, m[k]);
44
89
  // An auction's address names its seller: the page finds it by both.
45
- if (economy.auctions) out.auctions = out.auctions.map((a) => ({ ...a, seller: a.seller ?? auctionSeller(a, ctx) }));
90
+ if (economy.auctions) out.auctions = out.auctions.map(a => ({ ...a, seller: a.seller ?? auctionSeller(a, ctx) }));
46
91
  for (const k of ['items', 'pools', 'boxes']) if (!economy[k]) delete out[k];
47
- const mints = { ...(economy.live?.mints ?? {}), ...Object.fromEntries([...m.tokens.values()].filter((t) => t.source === 'game').map((t) => [t.key, ctx.mints[t.key]])) };
92
+ const mints = {
93
+ ...(economy.live?.mints ?? {}),
94
+ ...Object.fromEntries([...m.tokens.values()].filter(t => t.source === 'game').map(t => [t.key, ctx.mints[t.key]])),
95
+ };
48
96
  const { live: _, previous: __, ...published } = out;
49
97
  out.live = {
50
98
  program: LOOT_PROGRAM,
@@ -52,17 +100,29 @@ export function recordLanded(economy, { ctx, signatures = [], at = new Date().to
52
100
  // A project made with no collection (`"collection": false`) has none to write: the schema takes an address only.
53
101
  ...(ctx.collection ? { collection: ctx.collection } : {}),
54
102
  ...(ctx.tree ? { tree: ctx.tree } : {}),
103
+ ...(ctx.tables?.length ? { tables: ctx.tables } : {}),
55
104
  treasury: ctx.treasury,
56
105
  authority: ctx.authority,
57
106
  ...(Object.keys(mints).length ? { mints } : {}),
58
107
  at,
59
108
  published,
60
- transactions: [...(economy.live?.transactions ?? []), ...signatures.map((signature) => ({ signature }))].slice(-60),
109
+ transactions: [...(economy.live?.transactions ?? []), ...signatures.map(signature => ({ signature }))].slice(-60),
61
110
  };
62
111
  return out;
63
112
  }
64
113
 
65
- const PART_LISTS = { items: 'item', pools: 'pool', stashes: 'stash', boxes: 'box', recipes: 'recipe', schedules: 'schedule', lotteries: 'lottery', farms: 'farm', grants: 'grant', auctions: 'auction' };
114
+ const PART_LISTS = {
115
+ items: 'item',
116
+ pools: 'pool',
117
+ stashes: 'stash',
118
+ boxes: 'box',
119
+ recipes: 'recipe',
120
+ schedules: 'schedule',
121
+ lotteries: 'lottery',
122
+ farms: 'farm',
123
+ grants: 'grant',
124
+ auctions: 'auction',
125
+ };
66
126
  /** Parts that never change once live: a farm and an auction are never edited, a pool's token is fixed. */
67
127
  const FIXED = new Set(['farm', 'pool', 'auction']);
68
128
 
@@ -77,9 +137,9 @@ export function changedParts(economy) {
77
137
  const only = new Set();
78
138
  const fixed = [];
79
139
  const removed = [];
80
- const strip = ({ id, index, shopBox, seller, rewardsIndex, ...rest }) => JSON.stringify(rest);
140
+ const strip = ({ id, index, shopBox, seller, ...rest }) => JSON.stringify(rest);
81
141
  for (const [list, kind] of Object.entries(PART_LISTS)) {
82
- const before = new Map((published[list] ?? []).map((p) => [p.key, p]));
142
+ const before = new Map((published[list] ?? []).map(p => [p.key, p]));
83
143
  for (const part of economy[list] ?? []) {
84
144
  const was = before.get(part.key);
85
145
  if (!was) {
@@ -94,14 +154,18 @@ export function changedParts(economy) {
94
154
  }
95
155
  for (const key of before.keys()) removed.push(`${kind}:${key}`);
96
156
  }
97
- for (const t of economy.tokens ?? []) if (!(published.tokens ?? []).some((x) => x.key === t.key)) only.add(`token:${t.key}`);
157
+ for (const t of economy.tokens ?? [])
158
+ if (!(published.tokens ?? []).some(x => x.key === t.key)) only.add(`token:${t.key}`);
98
159
  // The first compressed prize opens the project's tree.
99
160
  if (!economy.live.tree && compressedTree(model(economy))) only.add('tree');
100
161
  // A traits root set again replaces the last.
101
- for (const t of economy.traits ?? []) if (!(published.traits ?? []).some((x) => x.collection === t.collection && x.root === t.root)) only.add(`traits:${t.collection}`);
162
+ for (const t of economy.traits ?? [])
163
+ if (!(published.traits ?? []).some(x => x.collection === t.collection && x.root === t.root))
164
+ only.add(`traits:${t.collection}`);
102
165
  const settings = [];
103
166
  if ((published.keys?.authority ?? 'owner') !== (economy.keys?.authority ?? 'owner')) settings.push('its authority');
104
167
  if ((published.treasury ?? null) !== (economy.treasury ?? null)) settings.push('its treasury');
105
- if (JSON.stringify(published.collections ?? []) !== JSON.stringify(economy.collections ?? [])) settings.push('the collections it takes');
168
+ if (JSON.stringify(published.collections ?? []) !== JSON.stringify(economy.collections ?? []))
169
+ settings.push('the collections it takes');
106
170
  return { only, fixed, removed, settings };
107
171
  }