@xgamesfun/solana 0.24.0 → 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 (92) hide show
  1. package/README.md +94 -51
  2. package/bin/xsolana.mjs +23 -8
  3. package/examples/blind-forge.json +2 -4
  4. package/examples/coin-flip-arena.json +2 -5
  5. package/examples/duel-arena.json +1 -4
  6. package/examples/fair-presale.json +1 -3
  7. package/examples/hero-trials.json +14 -2
  8. package/examples/holder-den.json +38 -0
  9. package/examples/holders-drop.json +105 -0
  10. package/examples/ideas.json +788 -788
  11. package/examples/match-predictions.json +24 -0
  12. package/examples/nft-raffle.json +1 -0
  13. package/examples/pack-league.json +108 -0
  14. package/examples/partner-vault.json +19 -3
  15. package/examples/sacrifice-raffle.json +2 -10
  16. package/examples/shelter-fair.json +1 -0
  17. package/examples/stakers-draw.json +3 -7
  18. package/examples/stride-sneakers.json +164 -0
  19. package/examples/trade-in-shop.json +2 -3
  20. package/js/approve/details.mjs +117 -48
  21. package/js/approve/ed25519.mjs +5 -1
  22. package/js/approve/live-check.mjs +83 -26
  23. package/js/approve/page.html +217 -78
  24. package/js/approve/page.js +43 -15
  25. package/js/approve/server.mjs +276 -74
  26. package/js/chain/base58.js +7 -1
  27. package/js/chain/borsh.js +15 -11
  28. package/js/chain/bytes.js +11 -8
  29. package/js/chain/core.js +21 -5
  30. package/js/chain/curve.js +1 -1
  31. package/js/chain/lookup.js +118 -36
  32. package/js/chain/mint.js +6 -2
  33. package/js/chain/pda.js +2 -2
  34. package/js/chain/programs.js +6 -6
  35. package/js/chain/rpc.js +40 -14
  36. package/js/chain/token.js +13 -2
  37. package/js/chain/token2022.js +55 -16
  38. package/js/chain/tx.js +32 -22
  39. package/js/client/accounts.js +270 -80
  40. package/js/client/compressed.js +4 -3
  41. package/js/client/grant.js +165 -40
  42. package/js/client/idl/loot.js +1 -1
  43. package/js/client/idl/loot.json +104 -16
  44. package/js/client/ix.js +2121 -457
  45. package/js/client/pda.js +20 -18
  46. package/js/client/policy.js +12 -5
  47. package/js/client/random.js +1 -1
  48. package/js/client/sizes.js +220 -65
  49. package/js/client/traits.js +14 -5
  50. package/js/connect/connect.mjs +59 -17
  51. package/js/economy/check.mjs +3873 -690
  52. package/js/economy/cost.mjs +380 -56
  53. package/js/economy/examples.mjs +176 -35
  54. package/js/economy/explain.mjs +461 -110
  55. package/js/economy/guide.mjs +282 -59
  56. package/js/economy/legal.mjs +12 -6
  57. package/js/economy/live.mjs +60 -19
  58. package/js/economy/model.mjs +320 -97
  59. package/js/economy/plan.mjs +1022 -203
  60. package/js/economy/schema.json +127 -369
  61. package/js/economy/secrets.mjs +17 -7
  62. package/js/economy/simulate.mjs +771 -178
  63. package/js/economy/txsize.mjs +15 -3
  64. package/js/economy/validate.mjs +61 -24
  65. package/js/mcp/server.mjs +42 -14
  66. package/js/mcp/tools.mjs +293 -80
  67. package/js/play/index.js +1960 -461
  68. package/js/play/keys.js +35 -9
  69. package/js/results/index.js +223 -30
  70. package/js/results/server.js +230 -0
  71. package/js/results/wire.js +64 -16
  72. package/package.json +6 -4
  73. package/release.json +5 -5
  74. package/skills/xsolana-economy/SKILL.md +30 -16
  75. package/skills/xsolana-economy/references/after-launch.md +30 -20
  76. package/skills/xsolana-economy/references/apps.md +47 -36
  77. package/skills/xsolana-economy/references/sentence-to-spec.md +148 -129
  78. package/skills/xsolana-economy/references/telling-the-person.md +9 -6
  79. package/types/chain/core.d.ts +1 -1
  80. package/types/chain/lookup.d.ts +1 -1
  81. package/types/client/accounts.d.ts +24 -3
  82. package/types/client/grant.d.ts +2 -2
  83. package/types/client/idl/loot.d.ts +22 -0
  84. package/types/client/ix.d.ts +54 -45
  85. package/types/client/pda.d.ts +3 -1
  86. package/types/economy/guide.d.mts +5 -0
  87. package/types/economy/model.d.mts +48 -3
  88. package/types/economy/plan.d.mts +44 -2
  89. package/types/play/index.d.ts +8 -19
  90. package/types/results/index.d.ts +7 -6
  91. package/types/results/server.d.ts +126 -0
  92. package/types/results/wire.d.ts +1 -1
@@ -10,17 +10,68 @@ import { encodeBase58 } from '../chain/base58.js';
10
10
  import { encodeBorsh } from '../chain/borsh.js';
11
11
  import IDL from '../client/idl/loot.js';
12
12
  import { createCollectionInstruction } from '../chain/core.js';
13
- import { ASSOCIATED_TOKEN_PROGRAM, NATIVE_MINT, SYSTEM_PROGRAM, SYSVAR_INSTRUCTIONS, SYSVAR_SLOT_HASHES, TOKEN_2022_PROGRAM, TOKEN_PROGRAM } from '../chain/pda.js';
13
+ import {
14
+ ASSOCIATED_TOKEN_PROGRAM,
15
+ NATIVE_MINT,
16
+ SYSTEM_PROGRAM,
17
+ SYSVAR_INSTRUCTIONS,
18
+ SYSVAR_SLOT_HASHES,
19
+ TOKEN_2022_PROGRAM,
20
+ TOKEN_PROGRAM,
21
+ } from '../chain/pda.js';
14
22
  import { createTableIx, extendTableIx } from '../chain/lookup.js';
15
23
  import { MEMO_PROGRAM } from '../chain/token2022.js';
16
24
  import { createAssociatedTokenAccountIdempotent, syncNative, transferLamports } from '../chain/programs.js';
17
- import { createAccountIx, initializeMint2Ix, MINT_BYTES, mintToIx, setAuthorityIx, transferCheckedIx } from '../chain/token.js';
25
+ import {
26
+ createAccountIx,
27
+ initializeMint2Ix,
28
+ MINT_BYTES,
29
+ mintToIx,
30
+ setAuthorityIx,
31
+ transferCheckedIx,
32
+ } from '../chain/token.js';
18
33
  import { compileMessage, transactionSize } from '../chain/tx.js';
19
34
  import { sha256 } from '../chain/sha256.js';
20
35
  import { utf8 } from '../chain/bytes.js';
21
36
  import * as loot from '../client/index.js';
22
37
  import { check } from './check.mjs';
23
- import { ACCOUNT_OVERHEAD, boxBuys, rent, RENT_PER_BYTE, boxRolls, compressedTree, destination, effectOf, gateOf, listOf, lotterySplit, model, MOST_RENT_PER_BYTE, outcomeParts, perWalletOf, placeOf, lifeLines, prizeLine, prizePool, prizeRolls, prizeToken, rarityNumber, ruleOf, sharesOf, spendOf, stashNfts, toBy, toEnum, toSeconds, toTime, toUnits, weights, wholeCount, windowOf } from './model.mjs';
38
+ import {
39
+ ACCOUNT_OVERHEAD,
40
+ boxBuys,
41
+ rent,
42
+ RENT_PER_BYTE,
43
+ boxRolls,
44
+ compressedTree,
45
+ destination,
46
+ effectOf,
47
+ gateOf,
48
+ listOf,
49
+ lotterySplit,
50
+ model,
51
+ MOST_RENT_PER_BYTE,
52
+ paysFromOf,
53
+ outcomeParts,
54
+ perWalletOf,
55
+ placeOf,
56
+ lifeLines,
57
+ prizeLine,
58
+ prizePool,
59
+ prizeRolls,
60
+ prizeToken,
61
+ rarityNumber,
62
+ ruleOf,
63
+ sharesOf,
64
+ spendOf,
65
+ stashNfts,
66
+ toBy,
67
+ toEnum,
68
+ toSeconds,
69
+ toTime,
70
+ toUnits,
71
+ weights,
72
+ wholeCount,
73
+ windowOf,
74
+ } from './model.mjs';
24
75
  import { BUDGET } from './txsize.mjs';
25
76
 
26
77
  const owner = 'owner';
@@ -28,10 +79,20 @@ const owner = 'owner';
28
79
  export const PER_APPROVAL = 12;
29
80
 
30
81
  const amount = (value, t) => toUnits(value ?? 0, t?.decimals ?? 0);
31
- const shares = (split, m) => sharesOf(split).map((s) => ({ to: toEnum(s.to, m), bps: s.bps ?? 0 }));
32
- const rise = (r, t) => (r ? { step: amount(r.step ?? 0, t), growth_bps: Math.round((r.growthPercent ?? 0) * 100) } : null);
33
- const fall = (f, t, now) => (f ? { step: amount(f.step, t), every: toSeconds(f.every), floor: amount(f.floor ?? 0, t), since: BigInt(toTime(f.start, now)), shared: f.clock === 'shared' } : null);
34
- const tokenProgramOf = (t) => (t?.program === 'token-2022' ? TOKEN_2022_PROGRAM : TOKEN_PROGRAM);
82
+ const shares = (split, m) => sharesOf(split).map(s => ({ to: toEnum(s.to, m), bps: s.bps ?? 0 }));
83
+ const rise = (r, t) =>
84
+ r ? { step: amount(r.step ?? 0, t), growth_bps: Math.round((r.growthPercent ?? 0) * 100) } : null;
85
+ const fall = (f, t, now) =>
86
+ f
87
+ ? {
88
+ step: amount(f.step, t),
89
+ every: toSeconds(f.every),
90
+ floor: amount(f.floor ?? 0, t),
91
+ since: BigInt(toTime(f.start, now)),
92
+ shared: f.clock === 'shared',
93
+ }
94
+ : null;
95
+ const tokenProgramOf = t => (t?.program === 'token-2022' ? TOKEN_2022_PROGRAM : TOKEN_PROGRAM);
35
96
 
36
97
  // ---------------------------------------------------------------- spec parts → the program's args
37
98
 
@@ -48,9 +109,18 @@ export function itemArgs(it, m) {
48
109
  uri: it.uri ?? '',
49
110
  max_supply: it.supply ?? 0,
50
111
  active: it.active !== false,
51
- collection: kind === 'Normal' ? NOBODY : it.collection ?? '@collection',
112
+ collection: kind === 'Normal' ? NOBODY : (it.collection ?? '@collection'),
52
113
  // A fresh collectible's traits, each drawn per unit (a table's row, or a number in a range).
53
- traits: kind === 'Normal' ? [] : (it.drawn ?? []).map((t) => ({ key: t.attribute, from: t.table !== undefined ? { Table: [m?.table.get(t.table)?.id ?? 0] } : { Range: { min: BigInt(t.min ?? 0), max: BigInt(t.max ?? 0) } } })),
114
+ traits:
115
+ kind === 'Normal'
116
+ ? []
117
+ : (it.drawn ?? []).map(t => ({
118
+ key: t.attribute,
119
+ from:
120
+ t.table !== undefined
121
+ ? { Table: [m?.table.get(t.table)?.id ?? 0] }
122
+ : { Range: { min: BigInt(t.min ?? 0), max: BigInt(t.max ?? 0) } },
123
+ })),
54
124
  };
55
125
  }
56
126
 
@@ -62,7 +132,7 @@ export function itemArgs(it, m) {
62
132
  */
63
133
  export const boxOrderBytes = (b, m, opens = 1, { opened = false } = {}) => {
64
134
  const args = boxArgs(b, m);
65
- const items = opened ? Object.fromEntries(m.items.map((it) => [it.id, itemArgs(it, m)])) : undefined;
135
+ const items = opened ? Object.fromEntries(m.items.map(it => [it.id, itemArgs(it, m)])) : undefined;
66
136
  return loot.orderBytes(placeholders(args), (args.shares ?? []).length, opens, items);
67
137
  };
68
138
 
@@ -72,12 +142,16 @@ export const TABLE_BATCH_BYTES = 800;
72
142
 
73
143
  /** A spec table's rows as the program takes them ({ weight, left, label }), in batches that fit a transaction each. */
74
144
  export function tableBatches(t) {
75
- const rows = t.rows.map((r) => ({ weight: r.weight, left: r.stock ?? 4294967295, label: t.kind === 'numbers' ? '' : String(r.label ?? '') }));
145
+ const rows = t.rows.map(r => ({
146
+ weight: r.weight,
147
+ left: r.stock ?? 4294967295,
148
+ label: t.kind === 'numbers' ? '' : String(r.label ?? ''),
149
+ }));
76
150
  const batches = [[]];
77
151
  let bytes = 0;
78
152
  for (const r of rows) {
79
153
  const size = 12 + new TextEncoder().encode(r.label).length;
80
- if (bytes + size > TABLE_BATCH_BYTES && batches.at(-1).length) batches.push([]), (bytes = 0);
154
+ if (bytes + size > TABLE_BATCH_BYTES && batches.at(-1).length) (batches.push([]), (bytes = 0));
81
155
  batches.at(-1).push(r);
82
156
  bytes += size;
83
157
  }
@@ -90,8 +164,17 @@ export function partPrize(o, m) {
90
164
  if (o.gives === 'item' && o.compressed) return { Compressed: { item: m.item.get(o.item)?.id ?? 0, tree: '@tree' } };
91
165
  if (o.gives === 'item') return { Item: [m.item.get(o.item)?.id ?? 0] };
92
166
  if (o.gives === 'stash') return { Stash: [m.stash.get(o.stash)?.id ?? 0] };
93
- if (o.gives === 'pay') return { Tokens: { from: { Pool: [m.pool.get(o.pool)?.index ?? 0] }, amount: amount(o.amount, m.poolToken(o.pool)) } };
94
- if (o.gives === 'mint') return { Tokens: { from: { Mint: [`@mint:${o.currency ?? m.currencyKey}`] }, amount: amount(o.amount, m.tokens.get(o.currency ?? m.currencyKey)) } };
167
+ if (o.gives === 'pay')
168
+ return {
169
+ Tokens: { from: { Pool: [m.pool.get(o.pool)?.index ?? 0] }, amount: amount(o.amount, m.poolToken(o.pool)) },
170
+ };
171
+ if (o.gives === 'mint')
172
+ return {
173
+ Tokens: {
174
+ from: { Mint: [`@mint:${o.currency ?? m.currencyKey}`] },
175
+ amount: amount(o.amount, m.tokens.get(o.currency ?? m.currencyKey)),
176
+ },
177
+ };
95
178
  return prizeArgs(o, m);
96
179
  }
97
180
 
@@ -109,17 +192,30 @@ export function boxArgs(b, m) {
109
192
  shares: shares(b.split, m),
110
193
  // What one open draws: each slot so many draws among the outcomes it names (all, when none), no outcome twice
111
194
  // when distinct. None is one draw among every outcome.
112
- slots: (b.slots ?? []).map((s) => ({ count: s.count, only: s.from ?? [], distinct: Boolean(s.distinct), share: Math.round((s.share ?? 0) * 100) })),
195
+ slots: (b.slots ?? []).map(s => ({
196
+ count: s.count,
197
+ only: s.from ?? [],
198
+ distinct: Boolean(s.distinct),
199
+ share: Math.round((s.share ?? 0) * 100),
200
+ })),
113
201
  // An entry gives its prizes (a bundle of any number), and may burn the payment in place of the split.
114
- entries: (b.outcomes ?? []).map((o, i) => ({ prizes: outcomeParts(o).map((part) => partPrize(part, m)), weight: w[i] })),
202
+ entries: (b.outcomes ?? []).map((o, i) => ({
203
+ prizes: outcomeParts(o).map(part => partPrize(part, m)),
204
+ weight: w[i],
205
+ })),
115
206
  gate: gateOf(b.gate, m),
116
- odds: (b.odds ?? []).map((o) => ({ entry: o.outcome, boost: { rule: ruleOf(o.when, m), effect: effectOf(o.add) } })),
207
+ odds: (b.odds ?? []).map(o => ({ entry: o.outcome, boost: { rule: ruleOf(o.when, m), effect: effectOf(o.add) } })),
117
208
  window: windowOf(b, m.now),
118
209
  per_wallet: perWalletOf(b),
119
210
  // A box that rolls on an NFT: the rule it must pass, and what each outcome does to it.
120
211
  subject: b.rollsOn ? ruleOf(b.rollsOn, m) : null,
121
212
  fates: (b.outcomes ?? []).flatMap((o, i) => {
122
- const shifts = (o.shifts ?? []).map((x) => ({ key: x.attribute, by: toBy(x.by), most: toSeconds(x.upTo ?? 0), from_now: Boolean(x.fromNow) }));
213
+ const shifts = (o.shifts ?? []).map(x => ({
214
+ key: x.attribute,
215
+ by: toBy(x.by),
216
+ most: toSeconds(x.upTo ?? 0),
217
+ from_now: Boolean(x.fromNow),
218
+ }));
123
219
  // An outcome's own `then`, or the box's: an NFT taken as its price goes there whatever the roll gives.
124
220
  const then = thenOf(o.then ?? b.then, m);
125
221
  return shifts.length || then !== null ? [{ entry: i, shifts, then }] : [];
@@ -136,30 +232,50 @@ export const thenOf = (then, m) => {
136
232
  };
137
233
 
138
234
  export function recipeArgs(r, m) {
139
- const tok = (key) => m.tokens.get(key ?? m.currencyKey);
140
- const inputs = (r.in ?? []).map((l) => {
235
+ const tok = key => m.tokens.get(key ?? m.currencyKey);
236
+ const inputs = (r.in ?? []).map(l => {
141
237
  if (l.takes === 'burn') return { Burn: { item: m.item.get(l.item)?.id ?? 0, count: l.count ?? 1 } };
142
238
  if (l.takes === 'use') return { Use: { item: m.item.get(l.item)?.id ?? 0 } };
143
239
  // A staked one names the farm it sits in (craft.rs): the craft takes it only from its seat there.
144
- if (l.takes === 'staked') return { Staked: { item: m.item.get(l.item)?.id ?? 0, farm: m.farm.get(l.farm)?.id ?? 0 } };
240
+ if (l.takes === 'staked')
241
+ return { Staked: { item: m.item.get(l.item)?.id ?? 0, farm: m.farm.get(l.farm)?.id ?? 0 } };
145
242
  // A renew keeps the collectible as a use does; its life's raise counts its uses from 0 again (`lifeLines`).
146
243
  if (l.takes === 'renew') return { Use: { item: m.item.get(l.item)?.id ?? 0 } };
147
244
  if (l.takes === 'show') return { Show: { collection: l.collection ?? '@collection' } };
148
245
  // A trade-in: one NFT of `collection`, any kind, burned, given on, or put in a stash.
149
246
  if (l.takes === 'trade') return { Trade: { collection: l.collection ?? '@collection', then: spendOf(l.then, m) } };
150
- return { Pay: { mint: `@mint:${l.currency ?? m.currencyKey}`, amount: amount(l.amount, tok(l.currency)), to: toEnum(l.to, m) } };
247
+ return {
248
+ Pay: {
249
+ mint: `@mint:${l.currency ?? m.currencyKey}`,
250
+ amount: amount(l.amount, tok(l.currency)),
251
+ to: toEnum(l.to, m),
252
+ },
253
+ };
151
254
  });
152
255
  const life = lifeLines(r, m);
153
256
  // A line is a prize (prize.rs), or a roll on a collectible it keeps (`on`).
154
- const outputs = (r.out ?? []).map((l) => (l.gives === 'roll' && l.on !== undefined ? { RollOn: { box_id: m.box.get(l.box)?.id ?? 0, place: l.on } } : { Prize: [prizeArgs(l, m)] }));
257
+ const outputs = (r.out ?? []).map(l =>
258
+ l.gives === 'roll' && l.on !== undefined
259
+ ? { RollOn: { box_id: m.box.get(l.box)?.id ?? 0, place: l.on } }
260
+ : { Prize: [prizeArgs(l, m)] },
261
+ );
155
262
  return {
156
263
  active: r.active !== false,
157
264
  inputs,
158
265
  outputs,
159
266
  // The recipe's own needs and raises, then what the life of each collectible it takes adds (`lifeLines`).
160
- needs: [...(r.needs ?? []).map((n) => ({ place: n.place, key: n.key, test: { AtLeast: [n.atLeast ?? 0] } })), ...life.needs],
161
- keeps: (r.keeps ?? []).map((k) => ({ key: k.key, from: k.from, to: k.to })),
162
- raises: [...(r.raises ?? []).map((x) => ({ place: x.place, shift: { key: x.key, by: toBy(x.by), most: toSeconds(x.upTo ?? 0), from_now: Boolean(x.fromNow) } })), ...life.raises],
267
+ needs: [
268
+ ...(r.needs ?? []).map(n => ({ place: n.place, key: n.key, test: { AtLeast: [n.atLeast ?? 0] } })),
269
+ ...life.needs,
270
+ ],
271
+ keeps: (r.keeps ?? []).map(k => ({ key: k.key, from: k.from, to: k.to })),
272
+ raises: [
273
+ ...(r.raises ?? []).map(x => ({
274
+ place: x.place,
275
+ shift: { key: x.key, by: toBy(x.by), most: toSeconds(x.upTo ?? 0), from_now: Boolean(x.fromNow) },
276
+ })),
277
+ ...life.raises,
278
+ ],
163
279
  window: windowOf(r, m.now),
164
280
  per_wallet: perWalletOf(r),
165
281
  };
@@ -167,13 +283,18 @@ export function recipeArgs(r, m) {
167
283
 
168
284
  export function scheduleArgs(s, m) {
169
285
  const every = toSeconds(s.every);
170
- const poolOf = (key) => m.pool.get(key)?.index ?? 0;
286
+ const poolOf = key => m.pool.get(key)?.index ?? 0;
171
287
  let action;
172
288
  if (s.does === 'issue') {
173
289
  // Minted into a pool, or into a farm's rewards vault ("to": "farm:<key>"): an emission its stakers share.
174
290
  const farm = destination(s.to ?? '').kind === 'farm' ? m.farm.get(destination(s.to).key) : null;
175
291
  const t = m.tokens.get(s.currency ?? (farm ? farm.rewardsToken : m.pool.get(s.pool)?.currency));
176
- action = { from: { Mint: [`@mint:${t?.key}`] }, to: farm ? toEnum(s.to, m) : { Pool: [poolOf(s.pool)] }, amount: { Fixed: [amount(s.amount, t)] }, halve_every: s.halveEvery ?? 0 };
292
+ action = {
293
+ from: { Mint: [`@mint:${t?.key}`] },
294
+ to: farm ? toEnum(s.to, m) : { Pool: [poolOf(s.pool)] },
295
+ amount: { Fixed: [amount(s.amount, t)] },
296
+ halve_every: s.halveEvery ?? 0,
297
+ };
177
298
  } else if (s.sealed) {
178
299
  // A sealed schedule pays from its own vault, in `currency`.
179
300
  const t = m.tokens.get(s.currency ?? m.currencyKey);
@@ -186,7 +307,13 @@ export function scheduleArgs(s, m) {
186
307
  const how = s.percent !== undefined ? { Bps: [Math.round(s.percent * 100)] } : { Fixed: [amount(s.amount, t)] };
187
308
  action = { from: { Pool: [poolOf(from)] }, to, amount: how, halve_every: s.halveEvery ?? 0 };
188
309
  }
189
- return { active: s.active !== false, action, every: BigInt(every), next_at: BigInt(s.start ? toTime(s.start, m.now) : m.now + every), max_runs: s.maxRuns ?? 0 };
310
+ return {
311
+ active: s.active !== false,
312
+ action,
313
+ every: BigInt(every),
314
+ next_at: BigInt(s.start ? toTime(s.start, m.now) : m.now + every),
315
+ max_runs: s.maxRuns ?? 0,
316
+ };
190
317
  }
191
318
 
192
319
  /**
@@ -195,7 +322,8 @@ export function scheduleArgs(s, m) {
195
322
  */
196
323
  export function prizeArgs(p, m) {
197
324
  const line = prizeLine(p);
198
- if (line.gives === 'item' && line.compressed) return { Compressed: { item: m.item.get(line.item)?.id ?? 0, tree: '@tree' } };
325
+ if (line.gives === 'item' && line.compressed)
326
+ return { Compressed: { item: m.item.get(line.item)?.id ?? 0, tree: '@tree' } };
199
327
  if (line.gives === 'item') return { Item: [m.item.get(line.item)?.id ?? 0] };
200
328
  // An NFT a lottery's place holds in the lottery's own stash (a raffle), or one of a project stash.
201
329
  if (line.gives === 'nft' && line.held) return { Held: [line.nft] };
@@ -213,14 +341,20 @@ export function prizeArgs(p, m) {
213
341
  * its own amount (whole units of `t`, the grant's token) and round, so one wallet listed under several rounds claims
214
342
  * each once (monthly vesting, a season's airdrop).
215
343
  */
216
- export const listLeaves = (list, t) => list.map((e) => (typeof e === 'string' ? loot.listLeaf(e, null) : loot.listLeaf(e.wallet, null, loot.listRound(e.round ?? 0), e.amount !== undefined ? amount(e.amount, t) : 0n)));
344
+ export const listLeaves = (list, t) =>
345
+ list.map(e =>
346
+ typeof e === 'string'
347
+ ? loot.listLeaf(e, null)
348
+ : loot.listLeaf(e.wallet, null, loot.listRound(e.round ?? 0), e.amount !== undefined ? amount(e.amount, t) : 0n),
349
+ );
217
350
 
218
351
  /** A prize line's `Prize`, or null for none. */
219
352
  const prizeOrNull = (p, m) => (prizeLine(p) ? prizeArgs(p, m) : null);
220
353
 
221
354
  /**
222
- * A lottery's args: its own pot in `currency` (the project's by default), ticket money split by `split` (and
223
- * `cutPercent` to the treasury) with the rest into the pot. No `every` draws once, at `firstDraw`.
355
+ * A lottery's args: its money in `currency` (the project's by default), ticket money split by `split` (and
356
+ * `cutPercent` to the treasury) with the rest for the places, which are paid from its pot (`pot`) or from each round's
357
+ * own money, what they leave going to `rest` (`paysFromOf`). No `every` draws once, at `firstDraw`.
224
358
  */
225
359
  export function lotteryArgs(l, m) {
226
360
  const potKey = l.currency ?? m.currencyKey;
@@ -232,10 +366,14 @@ export function lotteryArgs(l, m) {
232
366
  terms: {
233
367
  price: l.ticketNft ? 0n : amount(l.price, ticket),
234
368
  mint: `@mint:${potKey}`,
369
+ // Where the places are paid from: a pot kept from round to round, or each round's own money.
370
+ pays_from: paysFromOf(l, m),
235
371
  shares: shares(lotterySplit(l), m),
236
372
  // A place takes a share of the pot, a prize for its winner, or both.
237
373
  // A run of `times` places alike: "places 2 to 201 each take 1%" is one.
238
- places: (l.places ?? []).map(placeOf).map((p) => ({ bps: Math.round(p.percent * 100), prize: prizeOrNull(p.prize, m), times: p.times })),
374
+ places: (l.places ?? [])
375
+ .map(placeOf)
376
+ .map(p => ({ bps: Math.round(p.percent * 100), prize: prizeOrNull(p.prize, m), times: p.times })),
239
377
  ticket_mint: l.ticketCurrency ? `@mint:${l.ticketCurrency}` : '11111111111111111111111111111111',
240
378
  draws_at: BigInt(l.firstDraw ? toTime(l.firstDraw, m.now) : m.now + every),
241
379
  // A ticket paid with one NFT that passes the rule, in place of tokens.
@@ -260,14 +398,27 @@ export function lotteryArgs(l, m) {
260
398
  export function roundSize(l, m) {
261
399
  const { terms } = placeholders(lotteryArgs(l, m));
262
400
  const places = terms.places.reduce((n, p) => n + Number(p.times ?? 1), 0);
263
- const tokens = terms.places.filter((p) => p.prize?.Tokens).length;
264
- const pools = terms.places.filter((p) => p.prize?.Tokens?.from.Pool).length;
265
- const own = terms.places.filter((p) => p.prize?.Held || p.prize === 'Raffled' || p.prize?.Raffled !== undefined).reduce((n, p) => n + Number(p.times ?? 1), 0);
401
+ const tokens = terms.places.filter(p => p.prize?.Tokens).length;
402
+ const pools = terms.places.filter(p => p.prize?.Tokens?.from.Pool).length;
403
+ const own = terms.places
404
+ .filter(p => p.prize?.Held || p.prize === 'Raffled' || p.prize?.Raffled !== undefined)
405
+ .reduce((n, p) => n + Number(p.times ?? 1), 0);
266
406
  const termsBytes = encodeBorsh(IDL, { defined: { name: 'LotteryTerms' } }, terms).length;
267
407
  // The tag; project, lottery, id, incarnation, round, bump, opener, lent, head, status, revision; the terms; fee, sold,
268
408
  // to_pot; common; owns, owed, the draw's slots and times, answer and first ticket, the pot's four numbers; paid; prizes (59
269
409
  // bytes each); accounts (64 each); nfts; assigned.
270
- const head = 1 + 130 + termsBytes + 17 + (4 + 8 * terms.shares.length) + 112 + (4 + Math.ceil((2 * places) / 8)) + (4 + 59 * tokens) + (4 + 64 * (2 + pools)) + (4 + 32 * own) + 4;
410
+ const head =
411
+ 1 +
412
+ 130 +
413
+ termsBytes +
414
+ 17 +
415
+ (4 + 8 * terms.shares.length) +
416
+ 112 +
417
+ (4 + Math.ceil((2 * places) / 8)) +
418
+ (4 + 59 * tokens) +
419
+ (4 + 64 * (2 + pools)) +
420
+ (4 + 32 * own) +
421
+ 4;
271
422
  const payment = l.ticketCurrency ?? l.currency ?? m.currencyKey;
272
423
  const accounts = (l.ticketNft ? 1 : payment === (l.currency ?? m.currencyKey) ? 1 : 2) + pools;
273
424
  return { head, accounts };
@@ -279,7 +430,8 @@ export function roundSize(l, m) {
279
430
  export function roundsExpected(l, m) {
280
431
  const every = toSeconds(l.every ?? 0);
281
432
  const perDay = Number(m.scenario?.perDay?.[`lottery:${l.key}`] ?? 0);
282
- const days = every > 0 ? every / 86_400 : l.firstDraw ? Math.max(1, (toTime(l.firstDraw, m.now) - m.now) / 86_400) : 1;
433
+ const days =
434
+ every > 0 ? every / 86_400 : l.firstDraw ? Math.max(1, (toTime(l.firstDraw, m.now) - m.now) / 86_400) : 1;
283
435
  const rounds = l.expect?.rounds ?? (every > 0 ? 2 : 1);
284
436
  const entries = l.expect?.entries ?? (perDay > 0 ? Math.ceil(perDay * days) : 100);
285
437
  return { rounds, entries };
@@ -297,7 +449,10 @@ export function roundRentOf(l, m) {
297
449
  if (l.roundRent !== undefined) return toUnits(l.roundRent, 9);
298
450
  const { head, accounts } = roundSize(l, m);
299
451
  const { rounds, entries } = roundsExpected(l, m);
300
- const width = loot.entryWidth(lotterySplit(l).some((s) => String(s.to).startsWith('chosen')), Boolean(l.ticketNft));
452
+ const width = loot.entryWidth(
453
+ lotterySplit(l).some(s => String(s.to).startsWith('chosen')),
454
+ Boolean(l.ticketNft),
455
+ );
301
456
  // The header and its entries' list head (6 bytes), each token account, each entry's bytes.
302
457
  const one = rent(head + 6) + BigInt(accounts) * rent(165) + BigInt(entries * width) * RENT_PER_BYTE;
303
458
  const all = BigInt(rounds) * one;
@@ -315,8 +470,16 @@ export function contestArgs(c, m, at = m.now) {
315
470
  // program refuses them at once): with no "entriesClose", an hour before the result is due (halfway for a contest
316
471
  // shorter than two hours); with no "resultDue", an hour after entries close.
317
472
  const deciders = (c.deciders ?? []).length > 0;
318
- const due = c.resultDue === undefined && c.entriesClose !== undefined && deciders ? toSeconds(c.entriesClose) + 3600 : toSeconds(c.resultDue ?? c.entriesClose ?? '1d');
319
- const closes = c.entriesClose !== undefined ? toSeconds(c.entriesClose) : deciders ? due - Math.min(3600, Math.floor(due / 2)) : due;
473
+ const due =
474
+ c.resultDue === undefined && c.entriesClose !== undefined && deciders
475
+ ? toSeconds(c.entriesClose) + 3600
476
+ : toSeconds(c.resultDue ?? c.entriesClose ?? '1d');
477
+ const closes =
478
+ c.entriesClose !== undefined
479
+ ? toSeconds(c.entriesClose)
480
+ : deciders
481
+ ? due - Math.min(3600, Math.floor(due / 2))
482
+ : due;
320
483
  return {
321
484
  sides: c.sides,
322
485
  closes_at: BigInt(at + closes),
@@ -325,7 +488,7 @@ export function contestArgs(c, m, at = m.now) {
325
488
  gate: gateOf(c.gate, m),
326
489
  deciders: c.deciders ?? [],
327
490
  // A contest of NFTs counts its pot in NFTs.
328
- min_pot: c.stakeNft ? wholeCount(c.minPot) ?? 0n : amount(c.minPot ?? 0, t),
491
+ min_pot: c.stakeNft ? (wholeCount(c.minPot) ?? 0n) : amount(c.minPot ?? 0, t),
329
492
  stake: amount(c.stake ?? 0, t),
330
493
  prize: prizeOrNull(c.prize, m),
331
494
  // A token prize set aside whole at the decision and split among the winners by what each put in.
@@ -338,11 +501,11 @@ export function contestArgs(c, m, at = m.now) {
338
501
  }
339
502
 
340
503
  export function farmArgs(f, m) {
341
- const tiers = (f.tiers ?? [{ lock: 0, multiplier: 1 }]).map((t) => ({
504
+ const tiers = (f.tiers ?? [{ lock: 0, multiplier: 1 }]).map(t => ({
342
505
  lock: toSeconds(t.lock),
343
506
  bps: Math.round((t.multiplier ?? 1) * 10_000),
344
507
  prize: prizeOrNull(t.prize, m),
345
- roll: t.roll ? m.box.get(t.roll)?.id ?? 0 : null,
508
+ roll: t.roll ? (m.box.get(t.roll)?.id ?? 0) : null,
346
509
  // A token seat may leave before this tier's lock by leaving this share of what it takes out.
347
510
  exit_bps: Math.round((t.exitFee ?? 0) * 100),
348
511
  // How long a seat of this tier earns and grows from when its lock started (0: no end): a term.
@@ -350,7 +513,10 @@ export function farmArgs(f, m) {
350
513
  }));
351
514
  // Every rule of its gate; with none, a farm of one item's collectibles takes that item.
352
515
  const named = listOf(f.gate);
353
- const gates = f.stakes !== 'collectibles' ? [] : (named.length ? named : f.item ? [{ item: f.item, attribute: 'item_id' }] : []).map((r) => ruleOf(r, m));
516
+ const gates =
517
+ f.stakes !== 'collectibles'
518
+ ? []
519
+ : (named.length ? named : f.item ? [{ item: f.item, attribute: 'item_id' }] : []).map(r => ruleOf(r, m));
354
520
  // A rate is per NFT_UNIT of weight: one collectible, or 1,000,000 base units of a staked token.
355
521
  const rewards = m.tokens.get(f.rewardsToken);
356
522
  const staked = f.stakes === 'collectibles' ? null : m.tokens.get(f.stakes);
@@ -360,11 +526,26 @@ export function farmArgs(f, m) {
360
526
  return {
361
527
  custody: { freeze: 'Freeze', watch: 'Watch' }[f.custody] ?? 'Escrow',
362
528
  tiers,
363
- boosts: (f.boosts ?? []).map((b) => ({ rule: ruleOf(b.when, m), effect: effectOf(b.add) })),
529
+ boosts: (f.boosts ?? []).map(b => ({ rule: ruleOf(b.when, m), effect: effectOf(b.add) })),
364
530
  max_boost_bps: Math.round((f.maxBoostPercent ?? 0) * 100),
365
531
  gates,
366
- rate: f.rate ? { per_day: staked && !shared ? (perDay * 1_000_000n) / 10n ** BigInt(staked.decimals ?? 0) : perDay, mints: Boolean(f.rate.minted), shared, halve_every: f.rate.halveEvery ? toSeconds(f.rate.halveEvery) : 0 } : null,
367
- growth: listOf(f.growth).map((g) => ({ key: g.attribute ?? 'xp', per_day: g.perDay, level: (g.levels ?? []).length ? g.level ?? 'level' : '', steps: g.levels ?? [], streak: Boolean(g.streak), profile: false, at_least: 0n })),
532
+ rate: f.rate
533
+ ? {
534
+ per_day: staked && !shared ? (perDay * 1_000_000n) / 10n ** BigInt(staked.decimals ?? 0) : perDay,
535
+ mints: Boolean(f.rate.minted),
536
+ shared,
537
+ halve_every: f.rate.halveEvery ? toSeconds(f.rate.halveEvery) : 0,
538
+ }
539
+ : null,
540
+ growth: listOf(f.growth).map(g => ({
541
+ key: g.attribute ?? 'xp',
542
+ per_day: g.perDay,
543
+ level: (g.levels ?? []).length ? (g.level ?? 'level') : '',
544
+ steps: g.levels ?? [],
545
+ streak: Boolean(g.streak),
546
+ profile: false,
547
+ at_least: 0n,
548
+ })),
368
549
  // A farm that runs for a time ends that long after it goes live.
369
550
  ends_at: f.runs ? BigInt(m.now + toSeconds(f.runs)) : 0n,
370
551
  // The times it keeps on what is staked: its lock, and an upkeep.
@@ -376,18 +557,28 @@ export function farmArgs(f, m) {
376
557
  }
377
558
 
378
559
  /** A fee's sink (`Sink`): "burn", "rewards" (the farm's own vault), or "pool:<key>", one of the project's pools. */
379
- export const sinkOf = (to, m) => (to === 'burn' ? 'Burn' : to === 'rewards' ? 'Rewards' : { Pool: [m.pool.get(String(to).replace(/^pool:/, ''))?.index ?? 0] });
560
+ export const sinkOf = (to, m) =>
561
+ to === 'burn'
562
+ ? 'Burn'
563
+ : to === 'rewards'
564
+ ? 'Rewards'
565
+ : { Pool: [m.pool.get(String(to).replace(/^pool:/, ''))?.index ?? 0] };
380
566
 
381
567
  /** Whether a spec's farm keeps a record of when its seats stop earning (`loot.farmKeepsEnds`): a tier's term or an upkeep,
382
568
  * and it pays something. Each different time waiting costs whoever writes it `loot.END_BYTES` of rent. */
383
- export const farmEnds = (f) => ((f.tiers ?? []).some((t) => t.earnsFor) || Boolean(f.upkeepAttribute)) && (!f.rate || Number(f.rate.perDay) > 0);
569
+ export const farmEnds = f =>
570
+ ((f.tiers ?? []).some(t => t.earnsFor) || Boolean(f.upkeepAttribute)) && (!f.rate || Number(f.rate.perDay) > 0);
384
571
 
385
572
  /** An auction's args: its lot, reserve, least raise, times and split. "start" is when it goes live unless it says. */
386
573
  export function auctionArgs(a, m) {
387
574
  const t = m.priceToken(a);
388
575
  const startsAt = toTime(a.start, m.now);
389
576
  return {
390
- lot: a.lot?.asset ? { Asset: [a.lot.asset] } : a.lot?.roll ? { Roll: [rollOf(a.lot.roll, m)] } : { Item: [m.item.get(a.lot?.item)?.id ?? 0] },
577
+ lot: a.lot?.asset
578
+ ? { Asset: [a.lot.asset] }
579
+ : a.lot?.roll
580
+ ? { Roll: [rollOf(a.lot.roll, m)] }
581
+ : { Item: [m.item.get(a.lot?.item)?.id ?? 0] },
391
582
  // With NFT bids, the reserve counts NFTs.
392
583
  reserve: a.bidNfts ? BigInt(a.reserve ?? 1) : amount(a.reserve ?? 0, t),
393
584
  min_raise_bps: Math.round((a.minRaise ?? 5) * 100),
@@ -403,7 +594,7 @@ export function auctionArgs(a, m) {
403
594
 
404
595
  const NOBODY = '11111111111111111111111111111111';
405
596
  /** The program id of the box a prize rolls (a box key), or null for none. */
406
- const rollOf = (box, m) => (box ? m.box.get(box)?.id ?? 0 : null);
597
+ const rollOf = (box, m) => (box ? (m.box.get(box)?.id ?? 0) : null);
407
598
 
408
599
  export function grantArgs(g, m) {
409
600
  const gives = prizeArgs(g.prize ?? g.gives, m);
@@ -414,8 +605,10 @@ export function grantArgs(g, m) {
414
605
  return {
415
606
  gives,
416
607
  vouchers,
417
- list_root: list.length ? loot.merkleRoot(listLeaves(list, prizeToken(prizeLine(g.prize ?? g.gives), m))) : new Uint8Array(32),
418
- key_root: keys.length ? loot.merkleRoot(keys.map((k) => loot.keyLeaf(k))) : new Uint8Array(32),
608
+ list_root: list.length
609
+ ? loot.merkleRoot(listLeaves(list, prizeToken(prizeLine(g.prize ?? g.gives), m)))
610
+ : new Uint8Array(32),
611
+ key_root: keys.length ? loot.merkleRoot(keys.map(k => loot.keyLeaf(k))) : new Uint8Array(32),
419
612
  // A grant that names no voucher key and no list is one any wallet may claim, within its limits.
420
613
  open: !(g.vouchers ?? []).length && !list.length && !keys.length,
421
614
  limits: {
@@ -437,7 +630,7 @@ export function grantArgs(g, m) {
437
630
 
438
631
  /**
439
632
  * Replaces "@mint:<key>" strings anywhere in `value` with the mint `ctx` names, "@ata:<wallet>:<key>" with that
440
- * wallet's token account for it, "@pot:<id>" with a lottery's pot, "@farm:<id>" with a farm's address and "@rewards:<id>"
633
+ * wallet's token account for it, "@pot:<lottery id>:<pot id>" with a lottery's pot, "@farm:<id>" with a farm's address and "@rewards:<id>"
441
634
  * with a farm's rewards vault.
442
635
  */
443
636
  function resolve(value, ctx) {
@@ -469,7 +662,8 @@ function resolve(value, ctx) {
469
662
  }
470
663
  if (typeof value === 'string' && value.startsWith('@pot:')) {
471
664
  if (!ctx.project) throw new Error('No project yet for a lottery’s pot.');
472
- return loot.pda.pot(loot.pda.lottery(ctx.project, Number(value.slice(5))));
665
+ const [, lottery, pot] = value.split(':');
666
+ return loot.pda.lotteryPot(loot.pda.lottery(ctx.project, Number(lottery)), Number(pot));
473
667
  }
474
668
  if (typeof value === 'string' && value.startsWith('@ata:')) {
475
669
  const [, wallet, key, program] = value.split(':');
@@ -477,7 +671,7 @@ function resolve(value, ctx) {
477
671
  if (!mint) throw new Error(`No mint yet for the token “${key}”.`);
478
672
  return loot.ata(wallet, mint, program === 'token-2022' ? TOKEN_2022_PROGRAM : TOKEN_PROGRAM);
479
673
  }
480
- if (Array.isArray(value)) return value.map((v) => resolve(v, ctx));
674
+ if (Array.isArray(value)) return value.map(v => resolve(v, ctx));
481
675
  if (value instanceof Uint8Array || typeof value !== 'object' || value === null) return value;
482
676
  return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolve(v, ctx)]));
483
677
  }
@@ -487,7 +681,7 @@ function resolve(value, ctx) {
487
681
  const encodedSize = loot.accountBytes;
488
682
  /** Bubblegum's config of a tree. */
489
683
  const TREE_CONFIG_BYTES = 96;
490
- const placeholderKey = (name) => encodeBase58(sha256(utf8(`loot-plan:${name}`)));
684
+ const placeholderKey = name => encodeBase58(sha256(utf8(`loot-plan:${name}`)));
491
685
 
492
686
  /**
493
687
  * Every step that sets the economy up, in order: the tokens the project mints, the collection, the project, the pools, items, boxes (the shop's too), recipes, schedules, lotteries, grants,
@@ -502,15 +696,15 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
502
696
  const m = model(economy, { now });
503
697
  const live = madeSoFar(given);
504
698
  const steps = [];
505
- const want = (id) => !only || only.has(id);
699
+ const want = id => !only || only.has(id);
506
700
  const landed = new Set(live?.landed ?? []);
507
- const add = (step) => want(step.part) && !landed.has(step.id) && steps.push({ creates: [], signers: [owner], ...step });
508
- const project = (ctx) => ctx.project;
701
+ const add = step => want(step.part) && !landed.has(step.id) && steps.push({ creates: [], signers: [owner], ...step });
702
+ const project = ctx => ctx.project;
509
703
  const fresh = !live?.project;
510
704
 
511
705
  // Tokens the project mints: a classic mint with the owner as its authority, handed to the project right after.
512
706
  for (const t of m.tokens.values()) {
513
- if (t.source !== 'game' || live?.mints?.[t.key] || t.mint && !t.mint.startsWith('@')) continue;
707
+ if (t.source !== 'game' || live?.mints?.[t.key] || (t.mint && !t.mint.startsWith('@'))) continue;
514
708
  const key = `new:mint/${t.key}`;
515
709
  if (t.fixedSupply) {
516
710
  const supply = amount(t.supply, t);
@@ -519,12 +713,26 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
519
713
  part: `token:${t.key}`,
520
714
  says: `Make the token ${t.symbol} (${t.decimals} decimals) with a fixed supply of ${t.supply}, all of it to your wallet, then turn its minting off for good`,
521
715
  signers: [owner, key],
522
- creates: [{ what: `the mint of ${t.symbol}`, bytes: MINT_BYTES }, { what: `your ${t.symbol} account`, bytes: 165 }],
523
- build: (ctx) => [
524
- createAccountIx({ from: ctx.owner, address: ctx.keys[key], lamports: ctx.rent(MINT_BYTES), space: MINT_BYTES, owner: TOKEN_PROGRAM }),
716
+ creates: [
717
+ { what: `the mint of ${t.symbol}`, bytes: MINT_BYTES },
718
+ { what: `your ${t.symbol} account`, bytes: 165 },
719
+ ],
720
+ build: ctx => [
721
+ createAccountIx({
722
+ from: ctx.owner,
723
+ address: ctx.keys[key],
724
+ lamports: ctx.rent(MINT_BYTES),
725
+ space: MINT_BYTES,
726
+ owner: TOKEN_PROGRAM,
727
+ }),
525
728
  initializeMint2Ix({ mint: ctx.keys[key], decimals: t.decimals, authority: ctx.owner }),
526
729
  createAssociatedTokenAccountIdempotent(ctx.owner, ctx.owner, ctx.keys[key]),
527
- mintToIx({ mint: ctx.keys[key], to: loot.ata(ctx.owner, ctx.keys[key]), authority: ctx.owner, amount: supply }),
730
+ mintToIx({
731
+ mint: ctx.keys[key],
732
+ to: loot.ata(ctx.owner, ctx.keys[key]),
733
+ authority: ctx.owner,
734
+ amount: supply,
735
+ }),
528
736
  setAuthorityIx({ account: ctx.keys[key], current: ctx.owner, newAuthority: null }),
529
737
  ],
530
738
  });
@@ -536,8 +744,14 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
536
744
  says: `Make the token ${t.symbol} (${t.decimals} decimals), then hand its minting to the project`,
537
745
  signers: [owner, key],
538
746
  creates: [{ what: `the mint of ${t.symbol}`, bytes: MINT_BYTES }],
539
- build: (ctx) => [
540
- createAccountIx({ from: ctx.owner, address: ctx.keys[key], lamports: ctx.rent(MINT_BYTES), space: MINT_BYTES, owner: TOKEN_PROGRAM }),
747
+ build: ctx => [
748
+ createAccountIx({
749
+ from: ctx.owner,
750
+ address: ctx.keys[key],
751
+ lamports: ctx.rent(MINT_BYTES),
752
+ space: MINT_BYTES,
753
+ owner: TOKEN_PROGRAM,
754
+ }),
541
755
  initializeMint2Ix({ mint: ctx.keys[key], decimals: t.decimals, authority: ctx.owner }),
542
756
  setAuthorityIx({ account: ctx.keys[key], current: ctx.owner, newAuthority: ctx.project }),
543
757
  ],
@@ -555,20 +769,47 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
555
769
  part: 'project',
556
770
  says: `Make the collection “${c.name}”, with ${c.royaltyPercent ?? 5}% resale royalty to you; the project may mint into it${tree ? ', as Core NFTs or compressed ones' : ''}`,
557
771
  signers: [owner, 'new:collection'],
558
- creates: [{ what: 'the collection', bytes: 1 + 32 + 4 + c.name.length + 4 + (c.uri ?? '').length + 140 + (tree ? 3 : 0), approx: true }],
559
- build: (ctx) => [createCollectionInstruction({ collection: ctx.keys['new:collection'], creator: ctx.owner, project: ctx.project, name: c.name, uri: c.uri ?? '', royaltyBps: Math.round((c.royaltyPercent ?? 5) * 100), compressed: Boolean(tree) })],
772
+ creates: [
773
+ {
774
+ what: 'the collection',
775
+ bytes: 1 + 32 + 4 + c.name.length + 4 + (c.uri ?? '').length + 140 + (tree ? 3 : 0),
776
+ approx: true,
777
+ },
778
+ ],
779
+ build: ctx => [
780
+ createCollectionInstruction({
781
+ collection: ctx.keys['new:collection'],
782
+ creator: ctx.owner,
783
+ project: ctx.project,
784
+ name: c.name,
785
+ uri: c.uri ?? '',
786
+ royaltyBps: Math.round((c.royaltyPercent ?? 5) * 100),
787
+ compressed: Boolean(tree),
788
+ }),
789
+ ],
560
790
  });
561
791
  // The treasury is a wallet: the remainder of every split goes to its account of that mint, which the program
562
792
  // opens when it is not there. The project's vaults, and every account a share pays, open the same way when first
563
793
  // paid, so the setup opens none of them.
564
- const collections = (ctx) => [...(ctx.collection ? [ctx.collection] : []), ...m.collections];
794
+ const collections = ctx => [...(ctx.collection ? [ctx.collection] : []), ...m.collections];
565
795
  const count = (c ? 1 : 0) + m.collections.length;
566
796
  add({
567
797
  id: 'project',
568
798
  part: 'project',
569
799
  says: `Open the project on the loot program${count ? ` with ${count === 1 ? 'its collection' : `its ${count} collections`}` : ', with no collection'}; ${m.authority === 'owner' ? 'your wallet' : m.authority} signs its decisions, and ${m.treasuryWallet ?? 'your wallet'} is its treasury`,
570
- creates: [{ what: 'the project', bytes: loot.projectBytes(count) }, { what: 'the project’s fee box', bytes: 0 }],
571
- build: (ctx) => [loot.createProjectIx({ owner: ctx.owner, id: m.projectNumber, collections: collections(ctx), treasury: ctx.treasury, authority: ctx.authority })],
800
+ creates: [
801
+ { what: 'the project', bytes: loot.projectBytes(count) },
802
+ { what: 'the project’s fee box', bytes: 0 },
803
+ ],
804
+ build: ctx => [
805
+ loot.createProjectIx({
806
+ owner: ctx.owner,
807
+ id: m.projectNumber,
808
+ collections: collections(ctx),
809
+ treasury: ctx.treasury,
810
+ authority: ctx.authority,
811
+ }),
812
+ ],
572
813
  });
573
814
  }
574
815
 
@@ -579,10 +820,25 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
579
820
  part: 'tree',
580
821
  says: `Open the project’s tree of compressed NFTs, for ${(2 ** tree.depth).toLocaleString('en-US')} of them in all`,
581
822
  signers: [owner, 'new:tree'],
582
- creates: [{ what: 'the tree of compressed NFTs', bytes: tree.bytes }, { what: 'the tree’s config (Bubblegum’s)', bytes: TREE_CONFIG_BYTES }],
583
- build: (ctx) => [
584
- createAccountIx({ from: ctx.owner, address: ctx.keys['new:tree'], lamports: ctx.rent(tree.bytes), space: tree.bytes, owner: loot.COMPRESSION_PROGRAM }),
585
- loot.openTreeIx({ signer: ctx.owner, project: project(ctx), tree: ctx.keys['new:tree'], maxDepth: tree.depth, maxBufferSize: tree.buffer }),
823
+ creates: [
824
+ { what: 'the tree of compressed NFTs', bytes: tree.bytes },
825
+ { what: 'the tree’s config (Bubblegum’s)', bytes: TREE_CONFIG_BYTES },
826
+ ],
827
+ build: ctx => [
828
+ createAccountIx({
829
+ from: ctx.owner,
830
+ address: ctx.keys['new:tree'],
831
+ lamports: ctx.rent(tree.bytes),
832
+ space: tree.bytes,
833
+ owner: loot.COMPRESSION_PROGRAM,
834
+ }),
835
+ loot.openTreeIx({
836
+ signer: ctx.owner,
837
+ project: project(ctx),
838
+ tree: ctx.keys['new:tree'],
839
+ maxDepth: tree.depth,
840
+ maxBufferSize: tree.buffer,
841
+ }),
586
842
  ],
587
843
  });
588
844
 
@@ -595,8 +851,19 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
595
851
  id: 'accounts',
596
852
  part: 'project',
597
853
  says: `Open ${accounts.length} token account${accounts.length === 1 ? '' : 's'} the economy pays into (the treasury’s, the project’s vaults, the wallets its splits name), so no player’s purchase opens them`,
598
- creates: accounts.map((x) => ({ what: `${x.who}’s account of ${m.tokens.get(x.token)?.symbol ?? x.token}`, bytes: 165 })),
599
- build: (ctx) => accounts.map((x) => createAssociatedTokenAccountIdempotent(ctx.owner, x.who === 'the project' ? ctx.project : x.who === 'the treasury' ? ctx.treasury : x.who, ctx.mints[x.token], programsOf(ctx)[ctx.mints[x.token]] ?? TOKEN_PROGRAM)),
854
+ creates: accounts.map(x => ({
855
+ what: `${x.who}’s account of ${m.tokens.get(x.token)?.symbol ?? x.token}`,
856
+ bytes: 165,
857
+ })),
858
+ build: ctx =>
859
+ accounts.map(x =>
860
+ createAssociatedTokenAccountIdempotent(
861
+ ctx.owner,
862
+ x.who === 'the project' ? ctx.project : x.who === 'the treasury' ? ctx.treasury : x.who,
863
+ ctx.mints[x.token],
864
+ programsOf(ctx)[ctx.mints[x.token]] ?? TOKEN_PROGRAM,
865
+ ),
866
+ ),
600
867
  });
601
868
 
602
869
  // The project's standing lookup table (the owner its authority): the addresses its transactions name most, so a
@@ -608,29 +875,55 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
608
875
  add({
609
876
  id: i ? `table:${i}` : 'table',
610
877
  part: 'project',
611
- says: i ? `Add ${group.length} more addresses to the project’s lookup table` : `Make the project’s lookup table of the ${named.length} addresses its transactions name most, so a large one takes one approval`,
612
- creates: i ? [{ what: `room for ${group.length} addresses in the lookup table`, bytes: 32 * group.length, grows: true }] : [{ what: 'the project’s lookup table', bytes: 56 + 32 * group.length }],
613
- build: (ctx) => {
878
+ says: i
879
+ ? `Add ${group.length} more addresses to the project’s lookup table`
880
+ : `Make the project’s lookup table of the ${named.length} addresses its transactions name most, so a large one takes one approval`,
881
+ creates: i
882
+ ? [{ what: `room for ${group.length} addresses in the lookup table`, bytes: 32 * group.length, grows: true }]
883
+ : [{ what: 'the project’s lookup table', bytes: 56 + 32 * group.length }],
884
+ build: ctx => {
614
885
  // With no recent slot known (a plan built offline), there is no table to make.
615
886
  if (ctx.slot == null) return [];
616
887
  const { address: table, instruction } = createTableIx({ authority: ctx.owner, slot: ctx.slot });
617
- return [...(i ? [] : [instruction]), extendTableIx({ table, authority: ctx.owner, addresses: group.map((at) => at(ctx)).filter(Boolean) })];
888
+ return [
889
+ ...(i ? [] : [instruction]),
890
+ extendTableIx({ table, authority: ctx.owner, addresses: group.map(at => at(ctx)).filter(Boolean) }),
891
+ ];
618
892
  },
619
- }));
893
+ }),
894
+ );
620
895
  }
621
896
 
622
897
  for (const p of m.pools) {
623
898
  const t = m.tokens.get(p.currency);
624
- add({ id: `pool:${p.key}`, part: `pool:${p.key}`, says: `Open the pool “${p.key}” for ${t?.symbol}`, creates: [{ what: `the pool “${p.key}”`, bytes: 165 }], build: (ctx) => [loot.openPoolIx({ owner: ctx.owner, project: project(ctx), index: p.index, mint: ctx.mints[p.currency] })] });
899
+ add({
900
+ id: `pool:${p.key}`,
901
+ part: `pool:${p.key}`,
902
+ says: `Open the pool “${p.key}” for ${t?.symbol}`,
903
+ creates: [{ what: `the pool “${p.key}”`, bytes: 165 }],
904
+ build: ctx => [
905
+ loot.openPoolIx({ owner: ctx.owner, project: project(ctx), index: p.index, mint: ctx.mints[p.currency] }),
906
+ ],
907
+ });
625
908
  }
626
909
  const record = (part, def, says, kind, what, options = () => ({})) => {
627
910
  const [variant] = Object.keys(def);
628
911
  const bytes = recordBytesOf(kind, def[variant]);
629
- // A lottery's first write opens its pot: a token account as well as the record (each round holds its own money).
630
- const pot = kind === 'Lottery' ? [{ what: `${what}’s pot`, bytes: 165 }] : def.Schedule?.args.action.from.Vest ? [{ what: `${what}’s sealed vault`, bytes: 165 }] : [];
912
+ // A lottery's write opens the pot it pays from: a token account as well as the record (each round holds its own
913
+ // money). One paid from each round's own money opens none.
914
+ const pot = def.Lottery?.args.terms.pays_from.Pot
915
+ ? [{ what: `${what}’s pot`, bytes: 165 }]
916
+ : def.Schedule?.args.action.from.Vest
917
+ ? [{ what: `${what}’s sealed vault`, bytes: 165 }]
918
+ : [];
631
919
  add({
632
- id: `${part}:${kind}`, part, says, creates: [{ what, bytes }, ...pot],
633
- build: (ctx) => [loot.upsertIx({ owner: ctx.owner, project: project(ctx), def: resolve(def, ctx), ...options(ctx) })],
920
+ id: `${part}:${kind}`,
921
+ part,
922
+ says,
923
+ creates: [{ what, bytes }, ...pot],
924
+ build: ctx => [
925
+ loot.upsertIx({ owner: ctx.owner, project: project(ctx), def: resolve(def, ctx), ...options(ctx) }),
926
+ ],
634
927
  });
635
928
  };
636
929
  // Each table, a batch of rows a transaction (`TABLE_BATCH_BYTES` of rows at most), sealed with its last batch.
@@ -643,43 +936,106 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
643
936
  id: `table:${t.key}:${i}`,
644
937
  part: `table:${t.key}`,
645
938
  says: `Write ${rows.length} row${rows.length === 1 ? '' : 's'} of the table “${t.key}”${seal ? ', and seal it: its weights never change from now on' : ''}`,
646
- creates: i === 0 ? [{ what: `the table “${t.key}”, ${t.rows.length} rows`, bytes: loot.tableRowAt(len, t.rows.length - 1) + len }] : [],
647
- build: (ctx) => [loot.upsertIx({ owner: ctx.owner, project: project(ctx), def: { Table: { id: t.id, args: { payload: t.kind === 'numbers' ? 'Number' : 'Label', rows, seal } } } })],
939
+ creates:
940
+ i === 0
941
+ ? [
942
+ {
943
+ what: `the table “${t.key}”, ${t.rows.length} rows`,
944
+ bytes: loot.tableRowAt(len, t.rows.length - 1) + len,
945
+ },
946
+ ]
947
+ : [],
948
+ build: ctx => [
949
+ loot.upsertIx({
950
+ owner: ctx.owner,
951
+ project: project(ctx),
952
+ def: { Table: { id: t.id, args: { payload: t.kind === 'numbers' ? 'Number' : 'Label', rows, seal } } },
953
+ }),
954
+ ],
648
955
  });
649
956
  });
650
957
  }
651
- for (const it of m.items) record(`item:${it.key}`, { Item: { id: it.id, args: itemArgs(it, m) } }, `${it.active === false ? 'Switch off' : 'Write'} the item “${it.name}”`, 'Item', `the item “${it.name}”`);
958
+ for (const it of m.items)
959
+ record(
960
+ `item:${it.key}`,
961
+ { Item: { id: it.id, args: itemArgs(it, m) } },
962
+ `${it.active === false ? 'Switch off' : 'Write'} the item “${it.name}”`,
963
+ 'Item',
964
+ `the item “${it.name}”`,
965
+ );
652
966
  // A box's line names its odds, so the owner sees what they approve (above all when a live box's odds change).
653
- const odds = (b) => {
967
+ const odds = b => {
654
968
  const ws = weights(b);
655
969
  const total = ws.reduce((n, w) => n + w, 0) || 1;
656
- const part = (o) => (o.gives === 'item' ? m.item.get(o.item)?.name ?? o.item : o.gives === 'stash' ? `an NFT of “${m.stash.get(o.stash)?.name ?? o.stash}”` : o.gives === 'pay' ? `${o.amount} ${m.poolToken(o.pool)?.symbol ?? ''}`.trim() : o.gives === 'share' ? `${o.percent}% of “${o.pool}”` : o.gives === 'mint' ? `${o.amount} ${m.tokens.get(o.currency ?? m.currencyKey)?.symbol ?? ''}`.trim() : o.gives === 'nft' ? `the NFT ${String(o.nft).slice(0, 4)}…` : o.gives === 'roll' ? `a roll of “${m.box.get(o.box)?.name ?? o.box}”` : '');
657
- const what = (o) => outcomeParts(o).map(part).join(' + ') || 'nothing';
970
+ const part = o =>
971
+ o.gives === 'item'
972
+ ? (m.item.get(o.item)?.name ?? o.item)
973
+ : o.gives === 'stash'
974
+ ? `an NFT of “${m.stash.get(o.stash)?.name ?? o.stash}”`
975
+ : o.gives === 'pay'
976
+ ? `${o.amount} ${m.poolToken(o.pool)?.symbol ?? ''}`.trim()
977
+ : o.gives === 'share'
978
+ ? `${o.percent}% of “${o.pool}”`
979
+ : o.gives === 'mint'
980
+ ? `${o.amount} ${m.tokens.get(o.currency ?? m.currencyKey)?.symbol ?? ''}`.trim()
981
+ : o.gives === 'nft'
982
+ ? `the NFT ${String(o.nft).slice(0, 4)}…`
983
+ : o.gives === 'roll'
984
+ ? `a roll of “${m.box.get(o.box)?.name ?? o.box}”`
985
+ : '';
986
+ const what = o => outcomeParts(o).map(part).join(' + ') || 'nothing';
658
987
  return (b.outcomes ?? []).map((o, i) => `${what(o)} ${Math.round((ws[i] / total) * 10_000) / 100}%`).join(', ');
659
988
  };
660
- const wasLive = new Set((live?.published?.boxes ?? []).map((b) => b.key));
989
+ const wasLive = new Set((live?.published?.boxes ?? []).map(b => b.key));
661
990
  // A box is written before any box that names it to roll (one may name itself): in that order.
662
991
  const boxOrder = [];
663
992
  const visit = (b, path = new Set()) => {
664
993
  if (boxOrder.includes(b) || path.has(b)) return;
665
994
  path.add(b);
666
995
  for (const id of loot.defRolls({ Box: { args: boxArgs(b, m) } })) {
667
- const named = m.allBoxes.find((x) => x.id === id);
996
+ const named = m.allBoxes.find(x => x.id === id);
668
997
  if (named && named !== b) visit(named, path);
669
998
  }
670
999
  boxOrder.push(b);
671
1000
  };
672
1001
  for (const b of m.allBoxes) visit(b);
673
1002
  for (const b of boxOrder) {
674
- const says = b.shopOf ? `Put “${b.name}” in the shop at ${b.price} ${m.priceToken(b)?.symbol}` : `${b.active === false ? 'Switch off' : wasLive.has(b.key) ? 'Change' : 'Put on sale'} the box “${b.name}” at ${b.price} ${m.priceToken(b)?.symbol}${b.active === false ? '' : `: ${odds(b)}`}`;
675
- record(b.shopOf ? `item:${b.shopOf}` : `box:${b.key}`, { Box: { id: b.id, args: boxArgs(b, m) } }, says, 'LootBox', b.shopOf ? `the shop listing of “${b.name}”` : `the box “${b.name}”`);
1003
+ const says = b.shopOf
1004
+ ? `Put “${b.name}” in the shop at ${b.price} ${m.priceToken(b)?.symbol}`
1005
+ : `${b.active === false ? 'Switch off' : wasLive.has(b.key) ? 'Change' : 'Put on sale'} the box “${b.name}” at ${b.price} ${m.priceToken(b)?.symbol}${b.active === false ? '' : `: ${odds(b)}`}`;
1006
+ record(
1007
+ b.shopOf ? `item:${b.shopOf}` : `box:${b.key}`,
1008
+ { Box: { id: b.id, args: boxArgs(b, m) } },
1009
+ says,
1010
+ 'LootBox',
1011
+ b.shopOf ? `the shop listing of “${b.name}”` : `the box “${b.name}”`,
1012
+ );
676
1013
  }
677
- for (const r of m.recipes) record(`recipe:${r.key}`, { Recipe: { id: r.id, args: recipeArgs(r, m) } }, `Write the recipe “${r.name}”`, 'Recipe', `the recipe “${r.name}”`);
1014
+ for (const r of m.recipes)
1015
+ record(
1016
+ `recipe:${r.key}`,
1017
+ { Recipe: { id: r.id, args: recipeArgs(r, m) } },
1018
+ `Write the recipe “${r.name}”`,
1019
+ 'Recipe',
1020
+ `the recipe “${r.name}”`,
1021
+ );
678
1022
  for (const s of m.schedules) {
679
- record(`schedule:${s.key}`, { Schedule: { id: s.id, args: scheduleArgs(s, m) } }, `${s.active === false ? 'Stop' : 'Start'} the ${s.sealed ? 'sealed ' : ''}schedule “${s.name ?? s.key}”`, 'Schedule', `the schedule “${s.name ?? s.key}”`);
1023
+ record(
1024
+ `schedule:${s.key}`,
1025
+ { Schedule: { id: s.id, args: scheduleArgs(s, m) } },
1026
+ `${s.active === false ? 'Stop' : 'Start'} the ${s.sealed ? 'sealed ' : ''}schedule “${s.name ?? s.key}”`,
1027
+ 'Schedule',
1028
+ `the schedule “${s.name ?? s.key}”`,
1029
+ );
680
1030
  // A sealed schedule's vault is funded once, at go-live: from the owner's wallet, minted, or from a pool.
681
1031
  const id = `fund:${s.key}`;
682
- if (!s.sealed || !Number(s.fund ?? 0) || (live?.landed ?? []).includes(id) || live?.published?.schedules?.some((x) => x.key === s.key)) continue;
1032
+ if (
1033
+ !s.sealed ||
1034
+ !Number(s.fund ?? 0) ||
1035
+ (live?.landed ?? []).includes(id) ||
1036
+ live?.published?.schedules?.some(x => x.key === s.key)
1037
+ )
1038
+ continue;
683
1039
  const t = m.tokens.get(s.currency ?? m.currencyKey);
684
1040
  const from = s.fundFrom ?? 'wallet';
685
1041
  add({
@@ -688,39 +1044,97 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
688
1044
  signers: from === 'wallet' || m.authority === 'owner' ? [owner] : ['authority'],
689
1045
  says: `Put ${s.fund} ${t?.symbol ?? ''} in the sealed schedule “${s.name ?? s.key}”${from === 'wallet' ? ' from your wallet' : from === 'mint' ? ', minted' : ` from “${from.slice(5)}”`}: nobody can take it back`,
690
1046
  creates: [],
691
- build: (ctx) => {
1047
+ build: ctx => {
692
1048
  const mint = ctx.mints[t.key];
693
1049
  const vest = loot.pda.vest(loot.pda.schedule(project(ctx), s.id));
694
1050
  const value = amount(s.fund, t);
695
1051
  if (from === 'wallet') {
696
1052
  const tokenProgram = programsOf(ctx)[mint] ?? TOKEN_PROGRAM;
697
1053
  // SOL is wrapped first, as a seed is: the owner's wallet holds it as plain SOL, the vault takes wrapped SOL.
698
- const wrap = t.wsol ? [createAssociatedTokenAccountIdempotent(ctx.owner, ctx.owner, NATIVE_MINT), transferLamports(ctx.owner, loot.ata(ctx.owner, NATIVE_MINT), value), syncNative(loot.ata(ctx.owner, NATIVE_MINT))] : [];
699
- return [...wrap, transferCheckedIx({ from: loot.ata(ctx.owner, mint, tokenProgram), mint, to: vest, owner: ctx.owner, amount: value, decimals: t.decimals ?? 0, tokenProgram })];
1054
+ const wrap = t.wsol
1055
+ ? [
1056
+ createAssociatedTokenAccountIdempotent(ctx.owner, ctx.owner, NATIVE_MINT),
1057
+ transferLamports(ctx.owner, loot.ata(ctx.owner, NATIVE_MINT), value),
1058
+ syncNative(loot.ata(ctx.owner, NATIVE_MINT)),
1059
+ ]
1060
+ : [];
1061
+ return [
1062
+ ...wrap,
1063
+ transferCheckedIx({
1064
+ from: loot.ata(ctx.owner, mint, tokenProgram),
1065
+ mint,
1066
+ to: vest,
1067
+ owner: ctx.owner,
1068
+ amount: value,
1069
+ decimals: t.decimals ?? 0,
1070
+ tokenProgram,
1071
+ }),
1072
+ ];
700
1073
  }
701
1074
  const source = from === 'mint' ? { Mint: [mint] } : { Pool: [m.pool.get(from.slice(5))?.index ?? 0] };
702
- return [loot.moveTokensIx({ authority: ctx.authority, project: project(ctx), mint, from: source, to: { Account: [vest] }, amount: { Fixed: [value] }, programs: programsOf(ctx) })];
1075
+ return [
1076
+ loot.moveTokensIx({
1077
+ authority: ctx.authority,
1078
+ project: project(ctx),
1079
+ mint,
1080
+ from: source,
1081
+ to: { Account: [vest] },
1082
+ amount: { Fixed: [value] },
1083
+ programs: programsOf(ctx),
1084
+ }),
1085
+ ];
703
1086
  },
704
1087
  });
705
1088
  }
706
1089
  for (const l of m.lotteries) {
707
1090
  // A raffle's NFTs go into the lottery's own stash first, several a call: the lottery is written naming NFTs already
708
1091
  // there, or drawing from as many as its places.
709
- const raffled = [...(l.places ?? []).map(placeOf).filter((p) => p.prize?.held).map((p) => ({ nft: p.prize.nft, collection: p.prize.collection })), ...(l.nfts ?? []).map((n) => (typeof n === 'string' ? { nft: n } : n))];
710
- const opened = live?.published?.lotteries?.some((x) => x.key === l.key);
711
- groupsOf(raffled.filter((n) => !(live?.landed ?? []).includes(`stash:lottery-${l.key}:${n.nft}`)), STASH_GROUP).forEach((group, i) => {
1092
+ const raffled = [
1093
+ ...(l.places ?? [])
1094
+ .map(placeOf)
1095
+ .filter(p => p.prize?.held)
1096
+ .map(p => ({ nft: p.prize.nft, collection: p.prize.collection })),
1097
+ ...(l.nfts ?? []).map(n => (typeof n === 'string' ? { nft: n } : n)),
1098
+ ];
1099
+ const opened = live?.published?.lotteries?.some(x => x.key === l.key);
1100
+ groupsOf(
1101
+ raffled.filter(n => !(live?.landed ?? []).includes(`stash:lottery-${l.key}:${n.nft}`)),
1102
+ STASH_GROUP,
1103
+ ).forEach((group, i) => {
712
1104
  if (opened) return;
713
1105
  add({
714
- id: `stash:lottery-${l.key}:${group.map((n) => n.nft).join(',')}`,
1106
+ id: `stash:lottery-${l.key}:${group.map(n => n.nft).join(',')}`,
715
1107
  part: `lottery:${l.key}`,
716
- says: `Put your NFT${group.length === 1 ? ` ${group[0].nft}` : `s ${group.map((n) => n.nft).join(', ')}`} in the lottery “${l.name}”: ${group.length === 1 ? 'it stays' : 'they stay'} there until ${group.length === 1 ? 'its winner takes it' : 'their winners take them'}, and nobody, you included, can take ${group.length === 1 ? 'it' : 'them'} out before`,
1108
+ says: `Put your NFT${group.length === 1 ? ` ${group[0].nft}` : `s ${group.map(n => n.nft).join(', ')}`} in the lottery “${l.name}”: ${group.length === 1 ? 'it stays' : 'they stay'} there until ${group.length === 1 ? 'its winner takes it' : 'their winners take them'}, and nobody, you included, can take ${group.length === 1 ? 'it' : 'them'} out before`,
717
1109
  creates: i === 0 ? [{ what: `the lottery “${l.name}”’s stash`, bytes: stashBytes(raffled.length) }] : [],
718
- build: (ctx) => [loot.stashIx({ signer: ctx.owner, project: project(ctx), lottery: l.id, asset: group[0].nft, collection: group[0].collection ?? ctx.collection, more: group.slice(1).map((n) => ({ asset: n.nft, collection: n.collection ?? ctx.collection })) })],
1110
+ build: ctx => [
1111
+ loot.stashIx({
1112
+ signer: ctx.owner,
1113
+ project: project(ctx),
1114
+ lottery: l.id,
1115
+ asset: group[0].nft,
1116
+ collection: group[0].collection ?? ctx.collection,
1117
+ more: group.slice(1).map(n => ({ asset: n.nft, collection: n.collection ?? ctx.collection })),
1118
+ }),
1119
+ ],
719
1120
  });
720
1121
  });
721
- record(`lottery:${l.key}`, { Lottery: { id: l.id, args: lotteryArgs(l, m) } }, `Start the ${l.raffle ? 'raffle' : 'lottery'} “${l.name}”`, 'Lottery', `the lottery “${l.name}”`);
1122
+ record(
1123
+ `lottery:${l.key}`,
1124
+ { Lottery: { id: l.id, args: lotteryArgs(l, m) } },
1125
+ `Start the ${l.raffle ? 'raffle' : 'lottery'} “${l.name}”`,
1126
+ 'Lottery',
1127
+ `the lottery “${l.name}”`,
1128
+ );
722
1129
  }
723
- for (const g of m.grants) record(`grant:${g.key}`, { Grant: { id: g.id, args: grantArgs(g, m) } }, `Write the grant “${g.name}”`, 'Grant', `the grant “${g.name}”`);
1130
+ for (const g of m.grants)
1131
+ record(
1132
+ `grant:${g.key}`,
1133
+ { Grant: { id: g.id, args: grantArgs(g, m) } },
1134
+ `Write the grant “${g.name}”`,
1135
+ 'Grant',
1136
+ `the grant “${g.name}”`,
1137
+ );
724
1138
  /** The boxes a farm's tiers roll, tier by tier (its roll, then a prize that is a roll). */
725
1139
  for (const f of m.farms) {
726
1140
  if (live?.farms?.includes(f.key)) continue;
@@ -729,13 +1143,27 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
729
1143
  id: `farm:${f.key}`,
730
1144
  part: `farm:${f.key}`,
731
1145
  says: `Open the farm “${f.name}”: stake ${f.stakes === 'collectibles' ? 'collectibles' : m.tokens.get(f.stakes)?.symbol}, earn ${rewards?.symbol}${f.rate?.minted ? ', minted' : ' from its own rewards vault'}`,
732
- creates: [{ what: `the farm “${f.name}”`, bytes: farmBytes(f, m) }, { what: 'its rewards vault', bytes: 165 }, ...(f.stakes === 'collectibles' ? [] : [{ what: 'its stake vault', bytes: 165 }]), ...(farmEnds(f) ? [{ what: 'its record of when seats stop earning', bytes: loot.ENDS_HEAD_BYTES }] : [])],
733
- build: (ctx) => [loot.openFarmIx({ owner: ctx.owner, project: project(ctx), id: f.id, args: resolve(farmArgs(f, m), ctx), rewardsMint: ctx.mints[rewards.key], stakedMint: f.stakes === 'collectibles' ? null : ctx.mints[f.stakes] })],
1146
+ creates: [
1147
+ { what: `the farm “${f.name}”`, bytes: farmBytes(f, m) },
1148
+ { what: 'its rewards vault', bytes: 165 },
1149
+ ...(f.stakes === 'collectibles' ? [] : [{ what: 'its stake vault', bytes: 165 }]),
1150
+ ...(farmEnds(f) ? [{ what: 'its record of when seats stop earning', bytes: loot.ENDS_HEAD_BYTES }] : []),
1151
+ ],
1152
+ build: ctx => [
1153
+ loot.openFarmIx({
1154
+ owner: ctx.owner,
1155
+ project: project(ctx),
1156
+ id: f.id,
1157
+ args: resolve(farmArgs(f, m), ctx),
1158
+ rewardsMint: ctx.mints[rewards.key],
1159
+ stakedMint: f.stakes === 'collectibles' ? null : ctx.mints[f.stakes],
1160
+ }),
1161
+ ],
734
1162
  });
735
1163
  }
736
1164
 
737
1165
  // Traits the project vouches for from NFTs' off-chain metadata: one Merkle root per collection, signed once.
738
- const vouched = new Map((live?.published?.traits ?? []).map((t) => [t.collection, t.root]));
1166
+ const vouched = new Map((live?.published?.traits ?? []).map(t => [t.collection, t.root]));
739
1167
  for (const t of m.traits) {
740
1168
  if (vouched.get(t.collection) === t.root) continue;
741
1169
  add({
@@ -743,7 +1171,14 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
743
1171
  part: `traits:${t.collection}`,
744
1172
  says: `Vouch for the traits of the NFTs of ${t.collection} (a root of their off-chain metadata)`,
745
1173
  creates: [{ what: `the traits of ${t.collection}`, bytes: 1 + 32 * 3 }],
746
- build: (ctx) => [loot.setTraitsIx({ signer: ctx.owner, project: project(ctx), key: t.collection, root: Uint8Array.from(t.root.match(/../g).map((h) => parseInt(h, 16))) })],
1174
+ build: ctx => [
1175
+ loot.setTraitsIx({
1176
+ signer: ctx.owner,
1177
+ project: project(ctx),
1178
+ key: t.collection,
1179
+ root: Uint8Array.from(t.root.match(/../g).map(h => parseInt(h, 16))),
1180
+ }),
1181
+ ],
747
1182
  });
748
1183
  }
749
1184
 
@@ -751,32 +1186,65 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
751
1186
  // needs a proof fetched when it is signed, so the checker leaves those to `loot.stashIx` after go-live.
752
1187
  // What is in a live stash already: what its last publish listed, and the steps of this one that landed.
753
1188
  const stashed = new Set([
754
- ...(live?.published?.stashes ?? []).flatMap((s) => stashNfts(s).map((n) => `${s.key}:${n.asset}`)),
1189
+ ...(live?.published?.stashes ?? []).flatMap(s => stashNfts(s).map(n => `${s.key}:${n.asset}`)),
755
1190
  // A step puts in a group of NFTs: `stash:<key>:<asset>,<asset>…`.
756
- ...(live?.landed ?? []).filter((id) => id.startsWith('stash:')).flatMap((id) => {
757
- const [key, assets = ''] = id.slice(6).split(/:(.*)/s);
758
- return assets.split(',').map((asset) => `${key}:${asset}`);
759
- }),
1191
+ ...(live?.landed ?? [])
1192
+ .filter(id => id.startsWith('stash:'))
1193
+ .flatMap(id => {
1194
+ const [key, assets = ''] = id.slice(6).split(/:(.*)/s);
1195
+ return assets.split(',').map(asset => `${key}:${asset}`);
1196
+ }),
760
1197
  ]);
761
1198
  for (const s of m.stashes) {
762
1199
  const nfts = stashNfts(s);
763
- const putting = nfts.filter((n) => n.kind !== 'compressed' && !stashed.has(`${s.key}:${n.asset}`));
1200
+ const putting = nfts.filter(n => n.kind !== 'compressed' && !stashed.has(`${s.key}:${n.asset}`));
764
1201
  // The first NFT this plan puts in names the stash's rent: all of it for a new stash, what it grows by for one live already.
765
- const fresh = !nfts.some((n) => stashed.has(`${s.key}:${n.asset}`));
1202
+ const fresh = !nfts.some(n => stashed.has(`${s.key}:${n.asset}`));
766
1203
  // Several NFTs a call, each with its weight: Core NFTs a group of `STASH_GROUP`, Token Metadata ones two (each takes
767
1204
  // more accounts).
768
- const kindOf = (n) => ({ asset: n.asset, weight: n.weight, collection: n.kind === 'core' ? n.collection : undefined, tokenMetadata: n.kind === 'token-metadata' ? { programmable: n.programmable, ruleSet: n.ruleSet ?? undefined } : undefined });
769
- const groups = [...groupsOf(putting.filter((n) => n.kind === 'core'), STASH_GROUP), ...groupsOf(putting.filter((n) => n.kind !== 'core'), 2)];
1205
+ const kindOf = n => ({
1206
+ asset: n.asset,
1207
+ weight: n.weight,
1208
+ collection: n.kind === 'core' ? n.collection : undefined,
1209
+ tokenMetadata:
1210
+ n.kind === 'token-metadata' ? { programmable: n.programmable, ruleSet: n.ruleSet ?? undefined } : undefined,
1211
+ });
1212
+ const groups = [
1213
+ ...groupsOf(
1214
+ putting.filter(n => n.kind === 'core'),
1215
+ STASH_GROUP,
1216
+ ),
1217
+ ...groupsOf(
1218
+ putting.filter(n => n.kind !== 'core'),
1219
+ 2,
1220
+ ),
1221
+ ];
770
1222
  for (const [i, group] of groups.entries()) {
771
1223
  add({
772
- id: `stash:${s.key}:${group.map((n) => n.asset).join(',')}`,
1224
+ id: `stash:${s.key}:${group.map(n => n.asset).join(',')}`,
773
1225
  part: `stash:${s.key}`,
774
- says: `Put your NFT${group.length === 1 ? ` ${group[0].asset}` : `s ${group.map((n) => n.asset).join(', ')}`} in the stash “${s.name}”: the project holds ${group.length === 1 ? 'it' : 'them'} until a prize or a roll gives ${group.length === 1 ? 'it' : 'them'} away`,
775
- creates: i > 0 ? [] : fresh ? [{ what: `the stash “${s.name}”, with ${putting.length} NFT${putting.length === 1 ? '' : 's'}`, bytes: stashBytes(putting.length) }] : [{ what: `room for ${putting.length} more NFT${putting.length === 1 ? '' : 's'} in “${s.name}”`, bytes: 36 * putting.length, grows: true }],
776
- build: (ctx) => {
777
- const [first, ...more] = group.map((n) => {
1226
+ says: `Put your NFT${group.length === 1 ? ` ${group[0].asset}` : `s ${group.map(n => n.asset).join(', ')}`} in the stash “${s.name}”: the project holds ${group.length === 1 ? 'it' : 'them'} until a prize or a roll gives ${group.length === 1 ? 'it' : 'them'} away`,
1227
+ creates:
1228
+ i > 0
1229
+ ? []
1230
+ : fresh
1231
+ ? [
1232
+ {
1233
+ what: `the stash “${s.name}”, with ${putting.length} NFT${putting.length === 1 ? '' : 's'}`,
1234
+ bytes: stashBytes(putting.length),
1235
+ },
1236
+ ]
1237
+ : [
1238
+ {
1239
+ what: `room for ${putting.length} more NFT${putting.length === 1 ? '' : 's'} in “${s.name}”`,
1240
+ bytes: 36 * putting.length,
1241
+ grows: true,
1242
+ },
1243
+ ],
1244
+ build: ctx => {
1245
+ const [first, ...more] = group.map(n => {
778
1246
  const k = kindOf(n);
779
- return { ...k, collection: n.kind === 'core' ? k.collection ?? ctx.collection : undefined };
1247
+ return { ...k, collection: n.kind === 'core' ? (k.collection ?? ctx.collection) : undefined };
780
1248
  });
781
1249
  return [loot.stashIx({ signer: ctx.owner, project: project(ctx), id: s.id, ...first, more })];
782
1250
  },
@@ -802,10 +1270,46 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
802
1270
  ? `Open the auction “${a.name}” of a roll of the box “${m.box.get(a.lot.roll)?.name ?? a.lot.roll}”, made for its winner at the end (${terms})`
803
1271
  : `Open the auction “${a.name}” of one new “${m.item.get(a.lot?.item)?.name ?? a.lot?.item}” (${terms})`,
804
1272
  // An auction of NFT bids is made with room for the most a bid holds (`MAX_BID_NFTS`), its seller's rent.
805
- creates: [{ what: `the auction “${a.name}” (back when it ends)`, bytes: encodedSize('Auction', { project: NOBODY, id: 0, seller: NOBODY, mint: NOBODY, lot: args.lot, ours: false, pin: 0, reserve: 0n, min_raise_bps: 0, starts_at: 0n, ends_at: 0n, extend: 0, shares: args.shares, gate: args.gate, nft_bids: placeholders(args).nft_bids, high_bidder: NOBODY, high: 0n, last_bid_at: 0n, proceeds_owed: 0n, held: [] }) + (args.nft_bids ? 32 * loot.MAX_BID_NFTS : 0) }],
806
- build: (ctx) => {
1273
+ creates: [
1274
+ {
1275
+ what: `the auction “${a.name}” (back when it ends)`,
1276
+ bytes:
1277
+ encodedSize('Auction', {
1278
+ project: NOBODY,
1279
+ id: 0,
1280
+ seller: NOBODY,
1281
+ mint: NOBODY,
1282
+ lot: args.lot,
1283
+ ours: false,
1284
+ pin: 0,
1285
+ reserve: 0n,
1286
+ min_raise_bps: 0,
1287
+ starts_at: 0n,
1288
+ ends_at: 0n,
1289
+ extend: 0,
1290
+ shares: args.shares,
1291
+ gate: args.gate,
1292
+ nft_bids: placeholders(args).nft_bids,
1293
+ high_bidder: NOBODY,
1294
+ high: 0n,
1295
+ last_bid_at: 0n,
1296
+ proceeds_owed: 0n,
1297
+ held: [],
1298
+ }) + (args.nft_bids ? 32 * loot.MAX_BID_NFTS : 0),
1299
+ },
1300
+ ],
1301
+ build: ctx => {
807
1302
  const seller = auctionSeller(a, ctx);
808
- return [loot.openAuctionIx({ seller, project: project(ctx), collection: a.lot?.collection ?? ctx.collection, id: a.id, mint: ctx.mints[t.key], args: resolve(args, ctx) })];
1303
+ return [
1304
+ loot.openAuctionIx({
1305
+ seller,
1306
+ project: project(ctx),
1307
+ collection: a.lot?.collection ?? ctx.collection,
1308
+ id: a.id,
1309
+ mint: ctx.mints[t.key],
1310
+ args: resolve(args, ctx),
1311
+ }),
1312
+ ];
809
1313
  },
810
1314
  });
811
1315
  }
@@ -822,34 +1326,107 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
822
1326
  part: `seed:${p.key}`,
823
1327
  signers: [signer],
824
1328
  says: `Mint ${p.seed} ${t.symbol} into “${p.key}”`,
825
- build: (ctx) => [loot.moveTokensIx({ authority: ctx.authority, project: project(ctx), mint: ctx.mints[t.key], from: { Mint: [ctx.mints[t.key]] }, to: { Pool: [p.index] }, amount: { Fixed: [units] }, reference: `seed ${p.key}` })],
1329
+ build: ctx => [
1330
+ loot.moveTokensIx({
1331
+ authority: ctx.authority,
1332
+ project: project(ctx),
1333
+ mint: ctx.mints[t.key],
1334
+ from: { Mint: [ctx.mints[t.key]] },
1335
+ to: { Pool: [p.index] },
1336
+ amount: { Fixed: [units] },
1337
+ reference: `seed ${p.key}`,
1338
+ }),
1339
+ ],
826
1340
  });
827
1341
  } else {
828
1342
  add({
829
1343
  id: `seed:${p.key}`,
830
1344
  part: `seed:${p.key}`,
831
1345
  says: `Put ${p.seed} ${t.symbol} into “${p.key}”`,
832
- build: (ctx) => [
833
- ...(t.wsol ? [createAssociatedTokenAccountIdempotent(ctx.owner, ctx.owner, NATIVE_MINT), transferLamports(ctx.owner, loot.ata(ctx.owner, NATIVE_MINT), units), syncNative(loot.ata(ctx.owner, NATIVE_MINT))] : []),
834
- loot.payIx({ player: ctx.owner, project: project(ctx), mint: ctx.mints[t.key], treasury: ctx.treasury, amount: units, shares: [{ to: { Pool: [p.index] }, bps: 10_000 }], reference: `seed ${p.key}`, programs: { [ctx.mints[t.key]]: tokenProgramOf(t) } }),
1346
+ build: ctx => [
1347
+ ...(t.wsol
1348
+ ? [
1349
+ createAssociatedTokenAccountIdempotent(ctx.owner, ctx.owner, NATIVE_MINT),
1350
+ transferLamports(ctx.owner, loot.ata(ctx.owner, NATIVE_MINT), units),
1351
+ syncNative(loot.ata(ctx.owner, NATIVE_MINT)),
1352
+ ]
1353
+ : []),
1354
+ loot.payIx({
1355
+ player: ctx.owner,
1356
+ project: project(ctx),
1357
+ mint: ctx.mints[t.key],
1358
+ treasury: ctx.treasury,
1359
+ amount: units,
1360
+ shares: [{ to: { Pool: [p.index] }, bps: 10_000 }],
1361
+ reference: `seed ${p.key}`,
1362
+ programs: { [ctx.mints[t.key]]: tokenProgramOf(t) },
1363
+ }),
835
1364
  ],
836
1365
  });
837
1366
  }
838
1367
  }
1368
+ /** A seed in a token the project mints (no fixed supply): the owner's wallet holds none of it, so the authority
1369
+ * mints it straight into its account, as a pool's seed is minted into the pool. */
1370
+ const mintedSeed = (id, part, t, units, says, account) => {
1371
+ const signer = m.authority === 'owner' ? owner : 'authority';
1372
+ add({
1373
+ id,
1374
+ part,
1375
+ signers: [signer],
1376
+ says,
1377
+ build: ctx => [
1378
+ loot.moveTokensIx({
1379
+ authority: ctx.authority,
1380
+ project: project(ctx),
1381
+ mint: ctx.mints[t.key],
1382
+ from: { Mint: [ctx.mints[t.key]] },
1383
+ to: { Account: [account(ctx)] },
1384
+ amount: { Fixed: [units] },
1385
+ reference: id.replace(/^seed:[a-z]+:/, 'seed '),
1386
+ }),
1387
+ ],
1388
+ });
1389
+ };
839
1390
  // A farm's seed: the owner puts it straight into the farm's rewards vault, once the farm is open.
840
1391
  for (const f of m.farms) {
841
1392
  if (!(Number(f.seed ?? 0) > 0) || live?.seeded?.includes(`farm:${f.key}`)) continue;
842
1393
  const t = m.tokens.get(f.rewardsToken);
843
1394
  const units = amount(f.seed, t);
1395
+ if (t.gameMints) {
1396
+ mintedSeed(
1397
+ `seed:farm:${f.key}`,
1398
+ `farm:${f.key}`,
1399
+ t,
1400
+ units,
1401
+ `Mint ${f.seed} ${t.symbol} into the rewards of “${f.name}”`,
1402
+ ctx => loot.pda.rewards(loot.pda.farm(project(ctx), f.id)),
1403
+ );
1404
+ continue;
1405
+ }
844
1406
  add({
845
1407
  id: `seed:farm:${f.key}`,
846
1408
  part: `farm:${f.key}`,
847
1409
  says: `Put ${f.seed} ${t.symbol} into the rewards of “${f.name}”`,
848
- build: (ctx) => {
1410
+ build: ctx => {
849
1411
  const vault = loot.pda.rewards(loot.pda.farm(project(ctx), f.id));
850
1412
  return [
851
- ...(t.wsol ? [createAssociatedTokenAccountIdempotent(ctx.owner, ctx.owner, NATIVE_MINT), transferLamports(ctx.owner, loot.ata(ctx.owner, NATIVE_MINT), units), syncNative(loot.ata(ctx.owner, NATIVE_MINT))] : []),
852
- loot.payIx({ player: ctx.owner, project: project(ctx), mint: ctx.mints[t.key], treasury: ctx.treasury, amount: units, shares: [{ to: { Account: [vault] }, bps: 10_000 }], reference: `seed ${f.key}`, programs: programsOf(ctx) }),
1413
+ ...(t.wsol
1414
+ ? [
1415
+ createAssociatedTokenAccountIdempotent(ctx.owner, ctx.owner, NATIVE_MINT),
1416
+ transferLamports(ctx.owner, loot.ata(ctx.owner, NATIVE_MINT), units),
1417
+ syncNative(loot.ata(ctx.owner, NATIVE_MINT)),
1418
+ ]
1419
+ : []),
1420
+ loot.payIx({
1421
+ player: ctx.owner,
1422
+ project: project(ctx),
1423
+ mint: ctx.mints[t.key],
1424
+ treasury: ctx.treasury,
1425
+ amount: units,
1426
+ shares: [{ to: { Account: [vault] }, bps: 10_000 }],
1427
+ reference: `seed ${f.key}`,
1428
+ programs: programsOf(ctx),
1429
+ }),
853
1430
  ];
854
1431
  },
855
1432
  });
@@ -859,15 +1436,41 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
859
1436
  if (!(Number(l.seed ?? 0) > 0) || live?.seeded?.includes(`lottery:${l.key}`)) continue;
860
1437
  const t = m.tokens.get(l.currency ?? m.currencyKey);
861
1438
  const units = amount(l.seed, t);
1439
+ if (t.gameMints) {
1440
+ mintedSeed(
1441
+ `seed:lottery:${l.key}`,
1442
+ `lottery:${l.key}`,
1443
+ t,
1444
+ units,
1445
+ `Mint ${l.seed} ${t.symbol} into the pot of “${l.name}”`,
1446
+ ctx => loot.pda.lotteryPot(loot.pda.lottery(project(ctx), l.id), l.pot),
1447
+ );
1448
+ continue;
1449
+ }
862
1450
  add({
863
1451
  id: `seed:lottery:${l.key}`,
864
1452
  part: `lottery:${l.key}`,
865
1453
  says: `Put ${l.seed} ${t.symbol} into the pot of “${l.name}”`,
866
- build: (ctx) => {
867
- const pot = loot.pda.pot(loot.pda.lottery(project(ctx), l.id));
1454
+ build: ctx => {
1455
+ const pot = loot.pda.lotteryPot(loot.pda.lottery(project(ctx), l.id), l.pot);
868
1456
  return [
869
- ...(t.wsol ? [createAssociatedTokenAccountIdempotent(ctx.owner, ctx.owner, NATIVE_MINT), transferLamports(ctx.owner, loot.ata(ctx.owner, NATIVE_MINT), units), syncNative(loot.ata(ctx.owner, NATIVE_MINT))] : []),
870
- loot.payIx({ player: ctx.owner, project: project(ctx), mint: ctx.mints[t.key], treasury: ctx.treasury, amount: units, shares: [{ to: { Account: [pot] }, bps: 10_000 }], reference: `seed ${l.key}`, programs: programsOf(ctx) }),
1457
+ ...(t.wsol
1458
+ ? [
1459
+ createAssociatedTokenAccountIdempotent(ctx.owner, ctx.owner, NATIVE_MINT),
1460
+ transferLamports(ctx.owner, loot.ata(ctx.owner, NATIVE_MINT), units),
1461
+ syncNative(loot.ata(ctx.owner, NATIVE_MINT)),
1462
+ ]
1463
+ : []),
1464
+ loot.payIx({
1465
+ player: ctx.owner,
1466
+ project: project(ctx),
1467
+ mint: ctx.mints[t.key],
1468
+ treasury: ctx.treasury,
1469
+ amount: units,
1470
+ shares: [{ to: { Account: [pot] }, bps: 10_000 }],
1471
+ reference: `seed ${l.key}`,
1472
+ programs: programsOf(ctx),
1473
+ }),
871
1474
  ];
872
1475
  },
873
1476
  });
@@ -878,17 +1481,34 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
878
1481
  // it holds unless the spec names a `roundRent` to add.
879
1482
  for (const l of m.lotteries) {
880
1483
  const lamports = roundRentOf(l, m);
881
- if (lamports <= 0n || (l.roundRent === undefined && (live?.published?.lotteries ?? []).some((x) => x.key === l.key))) continue;
1484
+ if (lamports <= 0n || (l.roundRent === undefined && (live?.published?.lotteries ?? []).some(x => x.key === l.key)))
1485
+ continue;
882
1486
  const sol = Number(lamports) / 1e9;
883
- add({ id: `rent:lottery:${l.key}`, part: `lottery:${l.key}`, says: `Put ${sol} SOL in “${l.name}” for its rounds’ rent and its tickets’ entries${l.roundRent === undefined ? ` (sized for ${roundsExpected(l, m).rounds} round${roundsExpected(l, m).rounds === 1 ? '' : 's'} of ${roundsExpected(l, m).entries} purchases at once)` : ''}, so no buyer pays program rent; each round gives it back when it closes`, build: (ctx) => [transferLamports(ctx.owner, loot.pda.lottery(project(ctx), l.id), lamports)] });
1487
+ add({
1488
+ id: `rent:lottery:${l.key}`,
1489
+ part: `lottery:${l.key}`,
1490
+ says: `Put ${sol} SOL in “${l.name}” for its rounds’ rent and its tickets’ entries${l.roundRent === undefined ? ` (sized for ${roundsExpected(l, m).rounds} round${roundsExpected(l, m).rounds === 1 ? '' : 's'} of ${roundsExpected(l, m).entries} purchases at once)` : ''}, so no buyer pays program rent; each round gives it back when it closes`,
1491
+ build: ctx => [transferLamports(ctx.owner, loot.pda.lottery(project(ctx), l.id), lamports)],
1492
+ });
884
1493
  }
885
1494
  for (const g of m.grants) {
886
1495
  if (!(Number(g.fund ?? 0) > 0) || live?.funded?.includes(g.key)) continue;
887
1496
  const lamports = toUnits(g.fund, 9);
888
- add({ id: `fund:${g.key}`, part: `fund:${g.key}`, says: `Put ${g.fund} SOL in the funds of the grant “${g.name}” (they pay each claim’s rent)`, build: (ctx) => [transferLamports(ctx.owner, loot.pda.grant(project(ctx), g.id), lamports)] });
1497
+ add({
1498
+ id: `fund:${g.key}`,
1499
+ part: `fund:${g.key}`,
1500
+ says: `Put ${g.fund} SOL in the funds of the grant “${g.name}” (they pay each claim’s rent)`,
1501
+ build: ctx => [transferLamports(ctx.owner, loot.pda.grant(project(ctx), g.id), lamports)],
1502
+ });
889
1503
  }
890
1504
  // The authority's steps go last: the owner approves theirs in one batch, the authority theirs in another.
891
- return { steps: [...steps.filter((x) => !x.signers.includes('authority')), ...steps.filter((x) => x.signers.includes('authority'))], model: m };
1505
+ return {
1506
+ steps: [
1507
+ ...steps.filter(x => !x.signers.includes('authority')),
1508
+ ...steps.filter(x => x.signers.includes('authority')),
1509
+ ],
1510
+ model: m,
1511
+ };
892
1512
  }
893
1513
 
894
1514
  /** The token accounts going live opens (`setupSteps`): [{ who ('the treasury', 'the project' or a wallet), token }], each
@@ -900,10 +1520,10 @@ export function goLiveAccounts(m) {
900
1520
  add('the treasury', token);
901
1521
  for (const s of sharesOf(part.split)) if (s.kind === 'wallet') add(s.key, token);
902
1522
  };
903
- for (const b of m.allBoxes) takes(b), add('the project', b.currency ?? m.currencyKey);
1523
+ for (const b of m.allBoxes) (takes(b), add('the project', b.currency ?? m.currencyKey));
904
1524
  for (const p of m.payments) takes(p);
905
1525
  for (const l of m.lotteries) if (!l.ticketNft) takes(l, l.ticketCurrency ?? l.currency ?? m.currencyKey);
906
- for (const a of m.auctions) if (!a.bidNfts) takes(a), add('the project', a.currency ?? m.currencyKey);
1526
+ for (const a of m.auctions) if (!a.bidNfts) (takes(a), add('the project', a.currency ?? m.currencyKey));
907
1527
  return [...out.values()];
908
1528
  }
909
1529
 
@@ -916,28 +1536,49 @@ const TABLE_GROUP = 24;
916
1536
  * the treasury's account of it, then each pool, item, box and stash. Each a function of the go-live context.
917
1537
  */
918
1538
  export function standingAddresses(m) {
919
- const fixed = [loot.LOOT_PROGRAM, loot.pda.platform(), loot.pda.identity(), loot.VRF_PROGRAM, loot.VRF_QUEUE, loot.MPL_CORE_PROGRAM, TOKEN_PROGRAM, TOKEN_2022_PROGRAM, ASSOCIATED_TOKEN_PROGRAM, SYSTEM_PROGRAM, SYSVAR_SLOT_HASHES, SYSVAR_INSTRUCTIONS, MEMO_PROGRAM].map((a) => () => a);
1539
+ const fixed = [
1540
+ loot.LOOT_PROGRAM,
1541
+ loot.pda.platform(),
1542
+ loot.pda.identity(),
1543
+ loot.VRF_PROGRAM,
1544
+ loot.VRF_QUEUE,
1545
+ loot.MPL_CORE_PROGRAM,
1546
+ TOKEN_PROGRAM,
1547
+ TOKEN_2022_PROGRAM,
1548
+ ASSOCIATED_TOKEN_PROGRAM,
1549
+ SYSTEM_PROGRAM,
1550
+ SYSVAR_SLOT_HASHES,
1551
+ SYSVAR_INSTRUCTIONS,
1552
+ MEMO_PROGRAM,
1553
+ ].map(a => () => a);
920
1554
  const programOf = (ctx, key) => programsOf(ctx)[ctx.mints[key]] ?? TOKEN_PROGRAM;
921
1555
  return [
922
1556
  ...fixed,
923
- (ctx) => ctx.project, (ctx) => loot.pda.fees(ctx.project), (ctx) => ctx.treasury,
924
- ...(m.collection ? [(ctx) => ctx.collection] : []),
925
- ...[...m.tokens.values()].flatMap((t) => [(ctx) => ctx.mints[t.key], (ctx) => ctx.mints[t.key] && loot.ata(ctx.project, ctx.mints[t.key], programOf(ctx, t.key)), (ctx) => ctx.mints[t.key] && loot.ata(ctx.treasury, ctx.mints[t.key], programOf(ctx, t.key))]),
926
- ...m.pools.map((p) => (ctx) => loot.pda.pool(ctx.project, p.index)),
927
- ...m.items.map((it) => (ctx) => loot.pda.item(ctx.project, it.id)),
928
- ...m.allBoxes.map((b) => (ctx) => loot.pda.box(ctx.project, b.id)),
929
- ...m.stashes.map((s) => (ctx) => loot.pda.stash(ctx.project, s.id)),
1557
+ ctx => ctx.project,
1558
+ ctx => loot.pda.fees(ctx.project),
1559
+ ctx => ctx.treasury,
1560
+ ...(m.collection ? [ctx => ctx.collection] : []),
1561
+ ...[...m.tokens.values()].flatMap(t => [
1562
+ ctx => ctx.mints[t.key],
1563
+ ctx => ctx.mints[t.key] && loot.ata(ctx.project, ctx.mints[t.key], programOf(ctx, t.key)),
1564
+ ctx => ctx.mints[t.key] && loot.ata(ctx.treasury, ctx.mints[t.key], programOf(ctx, t.key)),
1565
+ ]),
1566
+ ...m.pools.map(p => ctx => loot.pda.pool(ctx.project, p.index)),
1567
+ ...m.items.map(it => ctx => loot.pda.item(ctx.project, it.id)),
1568
+ ...m.allBoxes.map(b => ctx => loot.pda.box(ctx.project, b.id)),
1569
+ ...m.stashes.map(s => ctx => loot.pda.stash(ctx.project, s.id)),
930
1570
  ].slice(0, 256);
931
1571
  }
932
1572
 
933
1573
  /** How many Core NFTs one `stash` call puts in (`Piece::another`: three accounts each after the first). */
934
1574
  const STASH_GROUP = 6;
935
1575
  /** `list` in groups of `n`. */
936
- const groupsOf = (list, n) => Array.from({ length: Math.ceil(list.length / n) }, (_, i) => list.slice(i * n, i * n + n));
1576
+ const groupsOf = (list, n) =>
1577
+ Array.from({ length: Math.ceil(list.length / n) }, (_, i) => list.slice(i * n, i * n + n));
937
1578
 
938
1579
  /** A stash's bytes with `n` NFTs: a table of NFTs (`table.rs`): its 51-byte header, a total for each group of 4,096
939
1580
  * rows and each block of 64, and 36 bytes an NFT (its weight and its address). */
940
- export const stashBytes = (n) => (n ? loot.tableRowAt(36, n - 1) + 36 : 51);
1581
+ export const stashBytes = n => (n ? loot.tableRowAt(36, n - 1) + 36 : 51);
941
1582
 
942
1583
  /** Who puts a spec's auction up, and so is named in its address: the owner for a collectible it holds, the authority for a new unit of an item. */
943
1584
  export const auctionSeller = (a, ctx) => (a.lot?.asset ? ctx.owner : ctx.authority);
@@ -950,33 +1591,93 @@ export const recordBytesOf = (kind, body) => {
950
1591
  Recipe: () => ({ project: NOBODY, id: 0, version: 0, ...placeholders(args) }),
951
1592
  Schedule: () => ({ project: NOBODY, id: 0, ...placeholders(args), runs: 0 }),
952
1593
  // Its terms and its round number: each round keeps the rest in a record of its own.
953
- Lottery: () => ({ project: NOBODY, id: 0, incarnation: 0n, revision: 0, ...placeholders(loot.withDefaults({ Lottery: { args } }).Lottery.args), round: 0 }),
954
- Grant: () => ({ project: NOBODY, id: 0, incarnation: 0n, ...placeholders(args), counted: { count: 0, window: 0, in_window: 0 } }),
1594
+ Lottery: () => ({
1595
+ project: NOBODY,
1596
+ id: 0,
1597
+ incarnation: 0n,
1598
+ revision: 0,
1599
+ ...placeholders(loot.withDefaults({ Lottery: { args } }).Lottery.args),
1600
+ round: 0,
1601
+ pots: 0,
1602
+ owing: NOBODY,
1603
+ }),
1604
+ Grant: () => ({
1605
+ project: NOBODY,
1606
+ id: 0,
1607
+ incarnation: 0n,
1608
+ ...placeholders(args),
1609
+ counted: { count: 0, window: 0, in_window: 0 },
1610
+ }),
955
1611
  }[kind];
956
1612
  return encodedSize(kind, base());
957
1613
  };
958
1614
  /** A farm's bytes as `open_farm` writes it: its args and its numbers. */
959
- export const farmBytes = (f, m) => encodedSize('Farm', {
960
- project: NOBODY, id: 0, farmed: f.stakes === 'collectibles' ? 'Collectibles' : { Tokens: { mint: NOBODY } }, ...placeholders(farmArgs(f, m)), rewards_mint: NOBODY,
961
- rate_carry: 0n, opened_at: 0n, total_weight: 0n, reward_per_weight: 0n, reward_remainder: 0n, owed: 0n, clock: 0n, unreleased: 0n, release_ends: 0n, release_from: 0n, release_base: 0n,
962
- keeps_ends: farmEnds(f),
963
- });
1615
+ export const farmBytes = (f, m) =>
1616
+ encodedSize('Farm', {
1617
+ project: NOBODY,
1618
+ id: 0,
1619
+ farmed: f.stakes === 'collectibles' ? 'Collectibles' : { Tokens: { mint: NOBODY } },
1620
+ ...placeholders(farmArgs(f, m)),
1621
+ rewards_mint: NOBODY,
1622
+ rate_carry: 0n,
1623
+ opened_at: 0n,
1624
+ total_weight: 0n,
1625
+ reward_per_weight: 0n,
1626
+ reward_remainder: 0n,
1627
+ owed: 0n,
1628
+ clock: 0n,
1629
+ unreleased: 0n,
1630
+ release_ends: 0n,
1631
+ release_from: 0n,
1632
+ release_base: 0n,
1633
+ keeps_ends: farmEnds(f),
1634
+ });
964
1635
  /** Exact initial contest record size for its configured shares, gate, sides and deciders. */
965
- export const contestBytes = (contest, m) => encodedSize('Contest', { project: NOBODY, id: 0, incarnation: 0n, opener: NOBODY, mint: NOBODY, decided_at: 0n, ...placeholders(contestArgs(contest, m)), verdict: 'Open', winner: 0, owed: 0n, unclaimed: 0n, sides: Array(contest.sides).fill(0n), share: 0, extras: 0, picks: 0 });
1636
+ export const contestBytes = (contest, m) =>
1637
+ encodedSize('Contest', {
1638
+ project: NOBODY,
1639
+ id: 0,
1640
+ incarnation: 0n,
1641
+ opener: NOBODY,
1642
+ mint: NOBODY,
1643
+ decided_at: 0n,
1644
+ ...placeholders(contestArgs(contest, m)),
1645
+ verdict: 'Open',
1646
+ winner: 0,
1647
+ owed: 0n,
1648
+ unclaimed: 0n,
1649
+ pot_won: 0n,
1650
+ prize_won: 0n,
1651
+ sides: Array(contest.sides).fill(0n),
1652
+ share: 0,
1653
+ extras: 0,
1654
+ picks: 0,
1655
+ });
966
1656
  /** Args with "@mint:" names standing in as addresses, for sizing. */
967
- const placeholders = (args) => resolve(args, { project: placeholderKey('project'), collection: placeholderKey('new:collection'), tree: placeholderKey('new:tree'), treasury: placeholderKey('treasury'), mints: new Proxy({}, { get: (_, k) => placeholderKey(`mint/${String(k)}`) }) });
1657
+ const placeholders = args =>
1658
+ resolve(args, {
1659
+ project: placeholderKey('project'),
1660
+ collection: placeholderKey('new:collection'),
1661
+ tree: placeholderKey('new:tree'),
1662
+ treasury: placeholderKey('treasury'),
1663
+ mints: new Proxy({}, { get: (_, k) => placeholderKey(`mint/${String(k)}`) }),
1664
+ });
968
1665
  /** The token program of each Token-2022 mint a context names (the program's account lists name it). */
969
- const programsOf = (ctx) => ctx.tokenPrograms ?? {};
1666
+ const programsOf = ctx => ctx.tokenPrograms ?? {};
970
1667
 
971
1668
  // ---------------------------------------------------------------- contexts
972
1669
 
973
1670
  /** Stand-in addresses for every name a plan uses, so it can be built and sized without a wallet. */
974
- export function placeholderContext(m, { rent = (bytes) => (BigInt(bytes) + ACCOUNT_OVERHEAD) * MOST_RENT_PER_BYTE } = {}) {
1671
+ export function placeholderContext(
1672
+ m,
1673
+ { rent = bytes => (BigInt(bytes) + ACCOUNT_OVERHEAD) * MOST_RENT_PER_BYTE } = {},
1674
+ ) {
975
1675
  const ownerKey = placeholderKey('owner');
976
1676
  const collection = m.collection ? placeholderKey('new:collection') : null;
977
1677
  const tree = compressedTree(m) ? placeholderKey('new:tree') : null;
978
1678
  const mints = {};
979
- for (const t of m.tokens.values()) mints[t.key] = t.mint?.startsWith('@') ? placeholderKey(`new:mint/${t.key}`) : t.mint;
1679
+ for (const t of m.tokens.values())
1680
+ mints[t.key] = t.mint?.startsWith('@') ? placeholderKey(`new:mint/${t.key}`) : t.mint;
980
1681
  return {
981
1682
  owner: ownerKey,
982
1683
  authority: m.authority === 'owner' ? ownerKey : m.authority,
@@ -985,7 +1686,13 @@ export function placeholderContext(m, { rent = (bytes) => (BigInt(bytes) + ACCOU
985
1686
  project: loot.pda.project(ownerKey, m.projectNumber),
986
1687
  treasury: m.treasuryWallet ?? ownerKey,
987
1688
  mints,
988
- keys: { ...(collection ? { 'new:collection': collection } : {}), ...(tree ? { 'new:tree': tree } : {}), ...Object.fromEntries([...m.tokens.values()].map((t) => [`new:mint/${t.key}`, placeholderKey(`new:mint/${t.key}`)])) },
1689
+ keys: {
1690
+ ...(collection ? { 'new:collection': collection } : {}),
1691
+ ...(tree ? { 'new:tree': tree } : {}),
1692
+ ...Object.fromEntries(
1693
+ [...m.tokens.values()].map(t => [`new:mint/${t.key}`, placeholderKey(`new:mint/${t.key}`)]),
1694
+ ),
1695
+ },
989
1696
  rent,
990
1697
  slot: 0n,
991
1698
  };
@@ -1013,7 +1720,8 @@ export function madeSoFar(live) {
1013
1720
  }
1014
1721
  if (!made('project', 'collection')) delete out.collection;
1015
1722
  if (!made('tree', 'tree')) delete out.tree;
1016
- if (live.mints) out.mints = Object.fromEntries(Object.entries(live.mints).filter(([key]) => made(`token:${key}`, `token:${key}`)));
1723
+ if (live.mints)
1724
+ out.mints = Object.fromEntries(Object.entries(live.mints).filter(([key]) => made(`token:${key}`, `token:${key}`)));
1017
1725
  return out;
1018
1726
  }
1019
1727
 
@@ -1022,13 +1730,17 @@ export function setupContext(m, { owner: ownerKey, keys, live: given = {}, rent
1022
1730
  const live = madeSoFar(given) ?? {};
1023
1731
  // A setup goes on only in the owner's own project: never in one a file names for them.
1024
1732
  const own = loot.pda.project(ownerKey, m.projectNumber);
1025
- if (live.project && live.project !== own) throw new Error(`The live section names the project ${live.project}, which is not the project ${own} of the wallet ${ownerKey}.`);
1733
+ if (live.project && live.project !== own)
1734
+ throw new Error(
1735
+ `The live section names the project ${live.project}, which is not the project ${own} of the wallet ${ownerKey}.`,
1736
+ );
1026
1737
  const collection = live.collection ?? keys['new:collection'] ?? null;
1027
1738
  const tree = live.tree ?? keys['new:tree'] ?? null;
1028
1739
  const mints = {};
1029
1740
  // A mint the spec names by its address is that one, whatever the live section says; one the setup makes is the
1030
1741
  // live section's once it landed (checked against the chain by the approval page), else this setup's fresh key.
1031
- for (const t of m.tokens.values()) mints[t.key] = t.mint && !t.mint.startsWith('@') ? t.mint : live.mints?.[t.key] ?? keys[`new:mint/${t.key}`];
1742
+ for (const t of m.tokens.values())
1743
+ mints[t.key] = t.mint && !t.mint.startsWith('@') ? t.mint : (live.mints?.[t.key] ?? keys[`new:mint/${t.key}`]);
1032
1744
  return {
1033
1745
  owner: ownerKey,
1034
1746
  authority: m.authority === 'owner' ? ownerKey : m.authority,
@@ -1045,13 +1757,14 @@ export function setupContext(m, { owner: ownerKey, keys, live: given = {}, rent
1045
1757
  // ---------------------------------------------------------------- packing
1046
1758
 
1047
1759
  /** The bytes of a transaction of `instructions` the owner pays for: what `pack` holds against the budget. */
1048
- export const stepBytes = (instructions, ctx) => transactionSize(compileMessage({ payer: ctx.owner, blockhash: placeholderKey('blockhash'), instructions }));
1760
+ export const stepBytes = (instructions, ctx) =>
1761
+ transactionSize(compileMessage({ payer: ctx.owner, blockhash: placeholderKey('blockhash'), instructions }));
1049
1762
 
1050
1763
  /** Packs steps into transactions under the size budget, one signer set at a time; each step whole. */
1051
1764
  export function pack(steps, ctx) {
1052
1765
  const transactions = [];
1053
1766
  let current = null;
1054
- const sizeOf = (instructions) => stepBytes(instructions, ctx);
1767
+ const sizeOf = instructions => stepBytes(instructions, ctx);
1055
1768
  for (const step of steps) {
1056
1769
  const instructions = step.build(ctx);
1057
1770
  // A step with nothing to send (the lookup table of a plan built with no recent slot) is left out.
@@ -1071,7 +1784,14 @@ export function pack(steps, ctx) {
1071
1784
  const size = sizeOf(instructions);
1072
1785
  if (size > BUDGET) throw new Error(`The step “${step.says}” is ${size} bytes: too big for one transaction.`);
1073
1786
  // `built`: each step's own instructions, in the order of `steps`.
1074
- current = { signer: signerKind, steps: [step], built: [instructions], instructions, signers: [...step.signers], alone: Boolean(step.alone) };
1787
+ current = {
1788
+ signer: signerKind,
1789
+ steps: [step],
1790
+ built: [instructions],
1791
+ instructions,
1792
+ signers: [...step.signers],
1793
+ alone: Boolean(step.alone),
1794
+ };
1075
1795
  transactions.push(current);
1076
1796
  }
1077
1797
  for (const t of transactions) t.bytes = sizeOf(t.instructions);
@@ -1086,7 +1806,12 @@ export function plan(economy, { now = Math.floor(Date.now() / 1000), ctx = null,
1086
1806
  const checked = check(economy, { now });
1087
1807
  if (!checked.ok) return { ok: false, refusals: checked.refusals, warnings: checked.warnings };
1088
1808
  const { steps, model: m } = setupSteps(economy, { now, only, live });
1089
- if (ctx) ctx.tokenPrograms ??= Object.fromEntries([...m.tokens.values()].filter((t) => t.program === 'token-2022' && ctx.mints[t.key]).map((t) => [ctx.mints[t.key], TOKEN_2022_PROGRAM]));
1809
+ if (ctx)
1810
+ ctx.tokenPrograms ??= Object.fromEntries(
1811
+ [...m.tokens.values()]
1812
+ .filter(t => t.program === 'token-2022' && ctx.mints[t.key])
1813
+ .map(t => [ctx.mints[t.key], TOKEN_2022_PROGRAM]),
1814
+ );
1090
1815
  const context = ctx ?? placeholderContext(m);
1091
1816
  const transactions = pack(steps, context);
1092
1817
  return {
@@ -1095,8 +1820,10 @@ export function plan(economy, { now = Math.floor(Date.now() / 1000), ctx = null,
1095
1820
  steps,
1096
1821
  transactions,
1097
1822
  // A wallet approves up to PER_APPROVAL transactions at once: the owner's, then the authority's (its steps go last).
1098
- approvals: Math.max(1, Math.ceil(transactions.filter((t) => t.signer === 'owner').length / PER_APPROVAL)) + Math.ceil(transactions.filter((t) => t.signer === 'authority').length / PER_APPROVAL),
1099
- freshKeys: [...new Set(steps.flatMap((s) => s.signers.filter((x) => x.startsWith('new:'))))],
1823
+ approvals:
1824
+ Math.max(1, Math.ceil(transactions.filter(t => t.signer === 'owner').length / PER_APPROVAL)) +
1825
+ Math.ceil(transactions.filter(t => t.signer === 'authority').length / PER_APPROVAL),
1826
+ freshKeys: [...new Set(steps.flatMap(s => s.signers.filter(x => x.startsWith('new:'))))],
1100
1827
  };
1101
1828
  }
1102
1829
 
@@ -1108,7 +1835,14 @@ export function playSteps(economy) {
1108
1835
  // A shop item, and any box with one outcome and nothing random, sells in one instruction: price split, prize given.
1109
1836
  for (const b of m.shop) {
1110
1837
  const it = m.item.get(b.shopOf);
1111
- add(`item:${it.key}`, 'buy', 'player', it.kind === 'normal' ? `buy “${it.name}”: the project’s backend delivers it from the receipt` : `buy “${it.name}”: minted to them at once`);
1838
+ add(
1839
+ `item:${it.key}`,
1840
+ 'buy',
1841
+ 'player',
1842
+ it.kind === 'normal'
1843
+ ? `buy “${it.name}”: the project’s backend delivers it from the receipt`
1844
+ : `buy “${it.name}”: minted to them at once`,
1845
+ );
1112
1846
  }
1113
1847
  for (const b of m.boxes) {
1114
1848
  if (boxBuys(b, m)) {
@@ -1121,35 +1855,120 @@ export function playSteps(economy) {
1121
1855
  }
1122
1856
  for (const p of m.payments) add(`payment:${p.key}`, 'pay', 'player', `pay “${p.name}”`);
1123
1857
  add('payments', 'pay_nft', 'player', 'pay with an NFT (burned, or given to a wallet): the receipt is the record');
1124
- for (const s of m.stashes) add(`stash:${s.key}`, 'stash', 'the owner or the authority', `put an NFT it holds in “${s.name}”, or take one out to any wallet`);
1125
- for (const r of prizeRolls(m).filter((r) => !['mission', 'recipe'].includes(r.route))) {
1126
- add(r.by, 'ask_roll', 'the winner or the project’s keys, who pay the oracle', `ask for the roll of “${m.box.get(r.box)?.name ?? r.box}” a winner got (${r.route})`);
1858
+ for (const s of m.stashes)
1859
+ add(
1860
+ `stash:${s.key}`,
1861
+ 'stash',
1862
+ 'the owner or the authority',
1863
+ `put an NFT it holds in “${s.name}”, or take one out to any wallet`,
1864
+ );
1865
+ for (const r of prizeRolls(m).filter(r => !['mission', 'recipe'].includes(r.route))) {
1866
+ add(
1867
+ r.by,
1868
+ 'ask_roll',
1869
+ 'the winner or the project’s keys, who pay the oracle',
1870
+ `ask for the roll of “${m.box.get(r.box)?.name ?? r.box}” a winner got (${r.route})`,
1871
+ );
1127
1872
  add(r.by, 'settle', 'the winner, a key they named, or the project’s keys', 'carry out what it rolled');
1128
1873
  }
1129
1874
  for (const r of m.recipes) add(`recipe:${r.key}`, 'craft', 'player', `craft “${r.name}”`);
1130
- for (const s of m.schedules) add(`schedule:${s.key}`, 'run_schedule', 'the project’s keys, or who it pays', `run “${s.name ?? s.key}” when it is due`);
1131
- for (const l of m.lotteries) add(`lottery:${l.key}`, 'buy_tickets', 'player', 'buy tickets'), add(`lottery:${l.key}`, 'draw_lottery', 'anyone', 'ask for the draw when it is due'), add(`lottery:${l.key}`, 'pull', 'anyone', 'pay one part: a refund, a place’s part of the pot or its prize, a share owed in common');
1875
+ for (const s of m.schedules)
1876
+ add(
1877
+ `schedule:${s.key}`,
1878
+ 'run_schedule',
1879
+ 'the project’s keys, or who it pays',
1880
+ `run “${s.name ?? s.key}” when it is due`,
1881
+ );
1882
+ for (const l of m.lotteries)
1883
+ (add(`lottery:${l.key}`, 'buy_tickets', 'player', 'buy tickets'),
1884
+ add(`lottery:${l.key}`, 'draw_lottery', 'anyone', 'ask for the draw when it is due'),
1885
+ add(
1886
+ `lottery:${l.key}`,
1887
+ 'pull',
1888
+ 'anyone',
1889
+ 'pay one part: a refund, a place’s part of the pot or its prize, a share owed in common',
1890
+ ));
1132
1891
  for (const f of m.farms) {
1133
- if (f.stakes === 'collectibles') add(`farm:${f.key}`, 'stake_nft', 'player', 'stake a collectible'), add(`farm:${f.key}`, 'unstake_nft', 'player', 'take it out once its lock has run');
1134
- else add(`farm:${f.key}`, 'stake_tokens', 'player', 'stake'), add(`farm:${f.key}`, 'unstake_tokens', 'player', 'unstake or collect'), add(`farm:${f.key}`, 'stake_nft', 'player', 'put a collectible on as a booster');
1892
+ if (f.stakes === 'collectibles')
1893
+ (add(`farm:${f.key}`, 'stake_nft', 'player', 'stake a collectible'),
1894
+ add(`farm:${f.key}`, 'unstake_nft', 'player', 'take it out once its lock has run'));
1895
+ else
1896
+ (add(`farm:${f.key}`, 'stake_tokens', 'player', 'stake'),
1897
+ add(`farm:${f.key}`, 'unstake_tokens', 'player', 'unstake or collect'),
1898
+ add(`farm:${f.key}`, 'stake_nft', 'player', 'put a collectible on as a booster'));
1135
1899
  add(`farm:${f.key}`, 'harvest', 'player', 'collect rewards');
1136
1900
  add(`farm:${f.key}`, 'refresh_seat', 'anyone', 'read a seat’s boosts again');
1137
- if (farmEnds(f)) add(`farm:${f.key}`, 'walk_farm', 'anyone', 'catch the farm up, in parts, when it has passed more end times than a move can walk');
1901
+ if (farmEnds(f))
1902
+ add(
1903
+ `farm:${f.key}`,
1904
+ 'walk_farm',
1905
+ 'anyone',
1906
+ 'catch the farm up, in parts, when it has passed more end times than a move can walk',
1907
+ );
1138
1908
  }
1139
1909
  for (const c of m.contests) {
1140
1910
  add(`contest:${c.key}`, 'open_contest', 'authority', `open “${c.name}”`);
1141
1911
  add(`contest:${c.key}`, 'enter', 'player', 'back a side');
1142
- add(`contest:${c.key}`, 'decide', 'authority (or anyone sending a decider’s verdict)', 'name the side that won, or call it off before its result is due');
1143
- add(`contest:${c.key}`, 'claim', c.stakeNft || c.prize ? 'the winner or the project’s keys' : 'anyone', 'pay an entry');
1912
+ add(
1913
+ `contest:${c.key}`,
1914
+ 'decide',
1915
+ 'authority (or anyone sending a decider’s verdict)',
1916
+ 'name the side that won, or call it off before its result is due',
1917
+ );
1918
+ add(
1919
+ `contest:${c.key}`,
1920
+ 'claim',
1921
+ c.stakeNft || c.prize ? 'the winner or the project’s keys' : 'anyone',
1922
+ 'pay an entry',
1923
+ );
1144
1924
  }
1145
1925
  for (const a of m.auctions) {
1146
- add(`auction:${a.key}`, 'open_auction', a.lot?.asset ? 'the owner, alone (a collectible of theirs)' : 'the authority, alone (a new unit of an item)', `put the lot up (at go-live)`);
1926
+ add(
1927
+ `auction:${a.key}`,
1928
+ 'open_auction',
1929
+ a.lot?.asset ? 'the owner, alone (a collectible of theirs)' : 'the authority, alone (a new unit of an item)',
1930
+ `put the lot up (at go-live)`,
1931
+ );
1147
1932
  add(`auction:${a.key}`, 'bid', 'player', 'bid: the bid it tops goes back at once');
1148
- add(`auction:${a.key}`, 'end_auction', 'anyone once it is over; its seller, to call off one with no bid', 'sell to the high bidder, or send the lot home');
1933
+ add(
1934
+ `auction:${a.key}`,
1935
+ 'end_auction',
1936
+ 'anyone once it is over; its seller, to call off one with no bid',
1937
+ 'sell to the high bidder, or send the lot home',
1938
+ );
1149
1939
  }
1150
- for (const h of m.holds) add(`hold:${h.key}`, 'hold', 'player', `hand in a “${h.item}” until ${h.until ?? `${h.for ?? 'the time they choose'} from then`}`), add(`hold:${h.key}`, 'release', 'authority while it runs, anyone after', 'say where it goes, or send it home');
1151
- for (const g of m.grants) add(`grant:${g.key}`, 'claim_grant', 'the winning wallet, which signs it (with a voucher, a list proof, or an open grant); anyone may pay its fees', `claim “${g.name}”`);
1152
- for (const d of m.decisions) add(`decision:${d.key}`, { payout: 'move_tokens', burn: 'move_tokens', issue: 'move_tokens', make: 'collectible', unmake: 'collectible', set: 'collectible', award: 'release', decide: 'decide', roll: 'roll', open_contest: 'open_contest' }[d.does], 'authority', d.when);
1940
+ for (const h of m.holds)
1941
+ (add(
1942
+ `hold:${h.key}`,
1943
+ 'hold',
1944
+ 'player',
1945
+ `hand in a “${h.item}” until ${h.until ?? `${h.for ?? 'the time they choose'} from then`}`,
1946
+ ),
1947
+ add(`hold:${h.key}`, 'release', 'authority while it runs, anyone after', 'say where it goes, or send it home'));
1948
+ for (const g of m.grants)
1949
+ add(
1950
+ `grant:${g.key}`,
1951
+ 'claim_grant',
1952
+ 'the winning wallet, which signs it (with a voucher, a list proof, or an open grant); anyone may pay its fees',
1953
+ `claim “${g.name}”`,
1954
+ );
1955
+ for (const d of m.decisions)
1956
+ add(
1957
+ `decision:${d.key}`,
1958
+ {
1959
+ payout: 'move_tokens',
1960
+ burn: 'move_tokens',
1961
+ issue: 'move_tokens',
1962
+ make: 'collectible',
1963
+ unmake: 'collectible',
1964
+ set: 'collectible',
1965
+ award: 'release',
1966
+ decide: 'decide',
1967
+ roll: 'roll',
1968
+ open_contest: 'open_contest',
1969
+ }[d.does],
1970
+ 'authority',
1971
+ d.when,
1972
+ );
1153
1973
  return rows;
1154
1974
  }
1155
-