@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
@@ -13,15 +13,15 @@ const IDEAS = JSON.parse(readFileSync(new URL('ideas.json', DIR), 'utf8')).ideas
13
13
  /** Every worked example: { name, spec }. */
14
14
  export function examples() {
15
15
  return readdirSync(DIR)
16
- .filter((f) => f.endsWith('.json') && f !== 'ideas.json')
16
+ .filter(f => f.endsWith('.json') && f !== 'ideas.json')
17
17
  .sort()
18
- .map((f) => ({ name: f.replace(/\.json$/, ''), spec: JSON.parse(readFileSync(new URL(f, DIR), 'utf8')) }));
18
+ .map(f => ({ name: f.replace(/\.json$/, ''), spec: JSON.parse(readFileSync(new URL(f, DIR), 'utf8')) }));
19
19
  }
20
20
 
21
21
  /** One example's whole spec, by name. */
22
22
  export function example(name) {
23
23
  const n = String(name).replace(/\.json$/, '');
24
- const found = examples().find((e) => e.name === n);
24
+ const found = examples().find(e => e.name === n);
25
25
  if (!found) throw new Error(`No example called “${name}”. xsolana_examples lists them.`);
26
26
  return found;
27
27
  }
@@ -29,30 +29,161 @@ export function example(name) {
29
29
  export const ideas = () => IDEAS;
30
30
 
31
31
  const SYNONYMS = {
32
- gacha: ['box', 'boxes', 'pull'], loot: ['box', 'boxes'], mystery: ['box', 'boxes'], pack: ['box', 'boxes'], packs: ['box', 'boxes'], roll: ['box', 'boxes'], chest: ['box', 'boxes'],
33
- staking: ['farm', 'farms', 'stake'], stake: ['farm', 'farms'], yield: ['farm', 'farms'], earn: ['farm', 'farms'], boost: ['farm', 'farms', 'odds'],
34
- raffle: ['lottery', 'lotteries'], lottery: ['lottery', 'lotteries'], draw: ['lottery', 'lotteries'], jackpot: ['share', 'pool'],
35
- craft: ['recipe', 'recipes'], crafting: ['recipe', 'recipes'], merge: ['recipe', 'recipes'], upgrade: ['recipe', 'recipes'], breed: ['recipe', 'recipes', 'life'], breeding: ['recipe', 'recipes', 'life'],
36
- season: ['schedule', 'schedules'], seasons: ['schedule', 'schedules'], halving: ['schedule', 'schedules'], emission: ['schedule', 'schedules'], emissions: ['schedule', 'schedules'], vesting: ['schedule', 'schedules'],
37
- bet: ['contest', 'contests'], betting: ['contest', 'contests'], duel: ['contest', 'contests'], wager: ['contest', 'contests'], prediction: ['contest', 'contests'], fantasy: ['contest', 'contests'],
38
- shop: ['item', 'items'], store: ['item', 'items'], nft: ['item', 'items'], nfts: ['item', 'items'], collectible: ['item', 'items'], burn: ['burn'], deflation: ['burn'], deflationary: ['burn'],
39
- airdrop: ['grant', 'grants'], reward: ['grant', 'grants'], rewards: ['grant', 'grants'], quest: ['grant', 'grants'], quests: ['grant', 'grants'], giveaway: ['grant', 'grants'], allowlist: ['grant', 'grants'], whitelist: ['grant', 'grants'],
40
- gated: ['gate'], gate: ['gate'], 'no-loss': ['no-loss', 'lottery'], lossless: ['no-loss', 'lottery'], launchpad: ['staked', 'gate'], tiers: ['staked'], rank: ['grant', 'grants'], holders: ['gate', 'holder'], pfp: ['gate', 'collection'], 'bring-your-own': ['gate', 'collection'],
41
- auction: ['auction', 'auctions', 'bid'], auctions: ['auction', 'auctions', 'bid'], bid: ['auction', 'auctions'], bids: ['auction', 'auctions'], bidding: ['auction', 'auctions'], highest: ['auction', 'auctions'], outbid: ['auction', 'auctions'], english: ['auction', 'auctions'],
42
- dutch: ['fall', 'falling', 'drop'], descending: ['fall', 'falling'],
43
- referral: ['chosen', 'referred', 'referrer'], referrals: ['chosen', 'referred', 'referrer'], referrer: ['chosen', 'referred'], affiliate: ['chosen', 'referred'], affiliates: ['chosen', 'referred'], refer: ['chosen', 'referred'],
44
- rent: ['holder', 'chosen', 'rent'], landlord: ['holder', 'chosen', 'rent'], royalty: ['holder', 'share'], plot: ['chosen', 'holder', 'plot'],
45
- crowdfund: ['contest', 'goal', 'minpot'], crowdfunding: ['contest', 'goal', 'minpot'], pledge: ['contest', 'goal', 'minpot'], kickstarter: ['contest', 'goal', 'minpot'],
46
- leaderboard: ['decision', 'decisions'], tournament: ['contest', 'decision', 'decisions'], prize: ['pool', 'pools'], flip: ['coin', 'flip', 'box', 'boxes'],
32
+ gacha: ['box', 'boxes', 'pull'],
33
+ loot: ['box', 'boxes'],
34
+ mystery: ['box', 'boxes'],
35
+ pack: ['box', 'boxes'],
36
+ packs: ['box', 'boxes'],
37
+ roll: ['box', 'boxes'],
38
+ chest: ['box', 'boxes'],
39
+ staking: ['farm', 'farms', 'stake'],
40
+ stake: ['farm', 'farms'],
41
+ yield: ['farm', 'farms'],
42
+ earn: ['farm', 'farms'],
43
+ boost: ['farm', 'farms', 'odds'],
44
+ raffle: ['lottery', 'lotteries'],
45
+ lottery: ['lottery', 'lotteries'],
46
+ draw: ['lottery', 'lotteries'],
47
+ jackpot: ['lottery', 'lotteries', 'pot'],
48
+ craft: ['recipe', 'recipes'],
49
+ crafting: ['recipe', 'recipes'],
50
+ merge: ['recipe', 'recipes'],
51
+ upgrade: ['recipe', 'recipes'],
52
+ breed: ['recipe', 'recipes', 'life'],
53
+ breeding: ['recipe', 'recipes', 'life'],
54
+ season: ['schedule', 'schedules'],
55
+ seasons: ['schedule', 'schedules'],
56
+ halving: ['schedule', 'schedules'],
57
+ emission: ['schedule', 'schedules'],
58
+ emissions: ['schedule', 'schedules'],
59
+ vesting: ['schedule', 'schedules'],
60
+ bet: ['contest', 'contests'],
61
+ betting: ['contest', 'contests'],
62
+ duel: ['contest', 'contests'],
63
+ wager: ['contest', 'contests'],
64
+ prediction: ['contest', 'contests'],
65
+ fantasy: ['contest', 'contests'],
66
+ shop: ['item', 'items'],
67
+ store: ['item', 'items'],
68
+ nft: ['item', 'items'],
69
+ nfts: ['item', 'items'],
70
+ collectible: ['item', 'items'],
71
+ burn: ['burn'],
72
+ deflation: ['burn'],
73
+ deflationary: ['burn'],
74
+ airdrop: ['grant', 'grants'],
75
+ reward: ['grant', 'grants'],
76
+ rewards: ['grant', 'grants'],
77
+ quest: ['grant', 'grants'],
78
+ quests: ['grant', 'grants'],
79
+ giveaway: ['grant', 'grants'],
80
+ allowlist: ['grant', 'grants'],
81
+ whitelist: ['grant', 'grants'],
82
+ gated: ['gate'],
83
+ gate: ['gate'],
84
+ 'no-loss': ['no-loss', 'lottery'],
85
+ lossless: ['no-loss', 'lottery'],
86
+ launchpad: ['staked', 'gate'],
87
+ tiers: ['staked'],
88
+ rank: ['grant', 'grants'],
89
+ holders: ['gate', 'holder'],
90
+ pfp: ['gate', 'collection'],
91
+ 'bring-your-own': ['gate', 'collection'],
92
+ auction: ['auction', 'auctions', 'bid'],
93
+ auctions: ['auction', 'auctions', 'bid'],
94
+ bid: ['auction', 'auctions'],
95
+ bids: ['auction', 'auctions'],
96
+ bidding: ['auction', 'auctions'],
97
+ highest: ['auction', 'auctions'],
98
+ outbid: ['auction', 'auctions'],
99
+ english: ['auction', 'auctions'],
100
+ dutch: ['fall', 'falling', 'drop'],
101
+ descending: ['fall', 'falling'],
102
+ referral: ['chosen', 'referred', 'referrer'],
103
+ referrals: ['chosen', 'referred', 'referrer'],
104
+ referrer: ['chosen', 'referred'],
105
+ affiliate: ['chosen', 'referred'],
106
+ affiliates: ['chosen', 'referred'],
107
+ refer: ['chosen', 'referred'],
108
+ rent: ['holder', 'chosen', 'rent'],
109
+ landlord: ['holder', 'chosen', 'rent'],
110
+ royalty: ['holder', 'share'],
111
+ plot: ['chosen', 'holder', 'plot'],
112
+ crowdfund: ['contest', 'goal', 'minpot'],
113
+ crowdfunding: ['contest', 'goal', 'minpot'],
114
+ pledge: ['contest', 'goal', 'minpot'],
115
+ kickstarter: ['contest', 'goal', 'minpot'],
116
+ leaderboard: ['decision', 'decisions'],
117
+ tournament: ['contest', 'decision', 'decisions'],
118
+ prize: ['pool', 'pools'],
119
+ flip: ['coin', 'flip', 'box', 'boxes'],
47
120
  };
48
- const words = (s) => String(s ?? '').toLowerCase().match(/[a-z0-9][a-z0-9.+-]*/g) ?? [];
49
- const STOP = new Set(['a', 'an', 'the', 'and', 'or', 'of', 'to', 'in', 'for', 'with', 'my', 'our', 'that', 'is', 'it', 'on', 'every', 'each', 'make', 'me', 'game', 'i', 'want', 'like', 'build']);
121
+ const words = s =>
122
+ String(s ?? '')
123
+ .toLowerCase()
124
+ .match(/[a-z0-9][a-z0-9.+-]*/g) ?? [];
125
+ const STOP = new Set([
126
+ 'a',
127
+ 'an',
128
+ 'the',
129
+ 'and',
130
+ 'or',
131
+ 'of',
132
+ 'to',
133
+ 'in',
134
+ 'for',
135
+ 'with',
136
+ 'my',
137
+ 'our',
138
+ 'that',
139
+ 'is',
140
+ 'it',
141
+ 'on',
142
+ 'every',
143
+ 'each',
144
+ 'make',
145
+ 'me',
146
+ 'game',
147
+ 'i',
148
+ 'want',
149
+ 'like',
150
+ 'build',
151
+ ]);
50
152
 
51
153
  /** The parts a spec uses, and features it shows. */
52
154
  export function partsUsed(spec) {
53
- const out = ['items', 'pools', 'boxes', 'payments', 'recipes', 'schedules', 'lotteries', 'farms', 'contests', 'auctions', 'holds', 'grants', 'decisions'].filter((k) => Array.isArray(spec?.[k]) && spec[k].length);
155
+ const out = [
156
+ 'items',
157
+ 'pools',
158
+ 'boxes',
159
+ 'payments',
160
+ 'recipes',
161
+ 'schedules',
162
+ 'lotteries',
163
+ 'farms',
164
+ 'contests',
165
+ 'auctions',
166
+ 'holds',
167
+ 'grants',
168
+ 'decisions',
169
+ ].filter(k => Array.isArray(spec?.[k]) && spec[k].length);
54
170
  const all = JSON.stringify(spec ?? {}).replace(/\s/g, '');
55
- for (const [f, re] of [['rise', /"rise"/], ['fall', /"fall"/], ['life', /"life"/], ['holder', /holder:/], ['burn', /"burn"/], ['gate', /"gate"/], ['odds', /"odds"/], ['token-2022', /token-2022/], ['share', /"gives":"share"/], ['staked', /"staked":\{/], ['no-loss', /"noLoss":true/], ['chosen', /"to":"chosen/], ['minpot', /"minPot":/], ['shared-clock', /"clock":"shared"/]]) if (re.test(all)) out.push(f);
171
+ for (const [f, re] of [
172
+ ['rise', /"rise"/],
173
+ ['fall', /"fall"/],
174
+ ['life', /"life"/],
175
+ ['holder', /holder:/],
176
+ ['burn', /"burn"/],
177
+ ['gate', /"gate"/],
178
+ ['odds', /"odds"/],
179
+ ['token-2022', /token-2022/],
180
+ ['staked', /"staked":\{/],
181
+ ['no-loss', /"noLoss":true/],
182
+ ['chosen', /"to":"chosen/],
183
+ ['minpot', /"minPot":/],
184
+ ['shared-clock', /"clock":"shared"/],
185
+ ])
186
+ if (re.test(all)) out.push(f);
56
187
  return out;
57
188
  }
58
189
 
@@ -60,9 +191,9 @@ function score(text, query, parts = []) {
60
191
  const have = new Set(words(text));
61
192
  const uses = new Set(parts);
62
193
  let s = 0;
63
- for (const w of words(query).filter((x) => !STOP.has(x))) {
194
+ for (const w of words(query).filter(x => !STOP.has(x))) {
64
195
  if (have.has(w)) s += 2;
65
- else if ([...have].some((h) => h.length > 3 && w.length > 3 && (h.startsWith(w) || w.startsWith(h)))) s += 1;
196
+ else if ([...have].some(h => h.length > 3 && w.length > 3 && (h.startsWith(w) || w.startsWith(h)))) s += 1;
66
197
  for (const syn of SYNONYMS[w] ?? []) s += (have.has(syn) ? 1 : 0) + (uses.has(syn) ? 2 : 0);
67
198
  }
68
199
  return s;
@@ -72,9 +203,9 @@ const FIT_BONUS = { great: 0.5, some: 0.25, 'a-lot': 0, cannot: 0 };
72
203
 
73
204
  /** Each entry of `list` scored against `query` by its words, the closest first, keeping those that match well enough. */
74
205
  function closest(list, query, limit) {
75
- const scored = list.map((e) => ({ e, s: score(e.words.replace(/-/g, ' '), query) })).sort((a, b) => b.s - a.s);
206
+ const scored = list.map(e => ({ e, s: score(e.words.replace(/-/g, ' '), query) })).sort((a, b) => b.s - a.s);
76
207
  const best = scored[0]?.s ?? 0;
77
- return scored.filter((x) => x.s >= 2 && x.s * 2 >= best).slice(0, limit);
208
+ return scored.filter(x => x.s >= 2 && x.s * 2 >= best).slice(0, limit);
78
209
  }
79
210
 
80
211
  /**
@@ -84,7 +215,7 @@ function closest(list, query, limit) {
84
215
  */
85
216
  export function limits(query, { limit = 3, atLeast = 0 } = {}) {
86
217
  return closest(CANNOT, query, limit)
87
- .filter((x) => x.s >= atLeast)
218
+ .filter(x => x.s >= atLeast)
88
219
  .map(({ e }) => ({ what: e.what, why: e.why, nearest: e.nearest }));
89
220
  }
90
221
 
@@ -95,20 +226,30 @@ export function hows(query, { limit = 3 } = {}) {
95
226
 
96
227
  /** The examples, ideas, hows and limits closest to `query`. */
97
228
  export function search(query, { limit = 6, fit } = NO_OPTIONS) {
98
- const ex = examples().map(({ name, spec }) => ({ name, title: spec.name, prompt: spec.prompt, parts: partsUsed(spec) }));
229
+ const ex = examples().map(({ name, spec }) => ({
230
+ name,
231
+ title: spec.name,
232
+ prompt: spec.prompt,
233
+ parts: partsUsed(spec),
234
+ }));
99
235
  const exampleHits = ex
100
- .map((e) => ({ e, s: score(`${e.name} ${e.title} ${e.prompt}`, query, e.parts) }))
101
- .filter((x) => x.s > 0)
236
+ .map(e => ({ e, s: score(`${e.name} ${e.title} ${e.prompt}`, query, e.parts) }))
237
+ .filter(x => x.s > 0)
102
238
  .sort((a, b) => b.s - a.s)
103
239
  .slice(0, limit)
104
- .map((x) => x.e);
105
- const ideaHits = IDEAS.filter((i) => !fit || i.fit === fit)
106
- .map((i) => ({ i, s: score(`${i.name} ${i.section} ${i.how}`, query) + FIT_BONUS[i.fit] }))
107
- .filter((x) => x.s > 0.5)
240
+ .map(x => x.e);
241
+ const ideaHits = IDEAS.filter(i => !fit || i.fit === fit)
242
+ .map(i => ({ i, s: score(`${i.name} ${i.section} ${i.how}`, query) + FIT_BONUS[i.fit] }))
243
+ .filter(x => x.s > 0.5)
108
244
  .sort((a, b) => b.s - a.s)
109
245
  .slice(0, limit)
110
246
  .map(({ i }) => i);
111
247
  // A "can't" shows only when it matches at least as well as the best matching capability.
112
248
  const how = closest(HOW, query, 3);
113
- return { examples: exampleHits, ideas: ideaHits, hows: how.map(({ e }) => ({ what: e.what, how: e.how })), limits: limits(query, { atLeast: how[0]?.s ?? 0 }) };
249
+ return {
250
+ examples: exampleHits,
251
+ ideas: ideaHits,
252
+ hows: how.map(({ e }) => ({ what: e.what, how: e.how })),
253
+ limits: limits(query, { atLeast: how[0]?.s ?? 0 }),
254
+ };
114
255
  }