@xgamesfun/solana 0.24.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/DISCLAIMER.md +1 -1
  2. package/NOTICE +4 -4
  3. package/README.md +100 -52
  4. package/bin/xsolana.mjs +32 -11
  5. package/examples/blind-forge.json +2 -4
  6. package/examples/coin-flip-arena.json +2 -5
  7. package/examples/duel-arena.json +1 -4
  8. package/examples/fair-presale.json +1 -3
  9. package/examples/hero-trials.json +14 -2
  10. package/examples/ideas.json +788 -788
  11. package/examples/nft-raffle.json +1 -0
  12. package/examples/partner-vault.json +19 -3
  13. package/examples/sacrifice-raffle.json +2 -10
  14. package/examples/shelter-fair.json +1 -0
  15. package/examples/stakers-draw.json +4 -7
  16. package/examples/trade-in-shop.json +2 -3
  17. package/js/approve/details.mjs +117 -48
  18. package/js/approve/ed25519.mjs +5 -1
  19. package/js/approve/live-check.mjs +83 -26
  20. package/js/approve/page.html +217 -78
  21. package/js/approve/page.js +43 -15
  22. package/js/approve/server.mjs +276 -74
  23. package/js/chain/base58.js +7 -1
  24. package/js/chain/borsh.js +15 -11
  25. package/js/chain/bytes.js +11 -8
  26. package/js/chain/core.js +21 -5
  27. package/js/chain/curve.js +1 -1
  28. package/js/chain/lookup.js +118 -36
  29. package/js/chain/mint.js +6 -2
  30. package/js/chain/pda.js +2 -2
  31. package/js/chain/programs.js +6 -6
  32. package/js/chain/rpc.js +40 -14
  33. package/js/chain/token.js +13 -2
  34. package/js/chain/token2022.js +55 -16
  35. package/js/chain/tx.js +32 -22
  36. package/js/client/accounts.js +270 -80
  37. package/js/client/compressed.js +4 -3
  38. package/js/client/grant.js +165 -40
  39. package/js/client/idl/loot.js +1 -1
  40. package/js/client/idl/loot.json +104 -16
  41. package/js/client/ix.js +2121 -457
  42. package/js/client/pda.js +20 -18
  43. package/js/client/policy.js +12 -5
  44. package/js/client/random.js +2 -2
  45. package/js/client/sizes.js +220 -65
  46. package/js/client/traits.js +14 -5
  47. package/js/connect/connect.mjs +59 -17
  48. package/js/economy/check.mjs +3884 -690
  49. package/js/economy/cost.mjs +381 -57
  50. package/js/economy/examples.mjs +176 -35
  51. package/js/economy/explain.mjs +468 -110
  52. package/js/economy/guide.mjs +282 -59
  53. package/js/economy/legal.mjs +12 -6
  54. package/js/economy/live.mjs +62 -20
  55. package/js/economy/model.mjs +320 -97
  56. package/js/economy/plan.mjs +1022 -203
  57. package/js/economy/review.mjs +760 -0
  58. package/js/economy/schema.json +134 -372
  59. package/js/economy/secrets.mjs +17 -7
  60. package/js/economy/simulate.mjs +771 -178
  61. package/js/economy/trust.mjs +15 -0
  62. package/js/economy/txsize.mjs +15 -3
  63. package/js/economy/validate.mjs +61 -24
  64. package/js/index.js +1 -0
  65. package/js/mcp/server.mjs +43 -15
  66. package/js/mcp/tools.mjs +318 -80
  67. package/js/play/index.js +1960 -461
  68. package/js/play/keys.js +35 -9
  69. package/js/results/index.js +225 -32
  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 +52 -22
  75. package/skills/xsolana-economy/references/after-launch.md +32 -21
  76. package/skills/xsolana-economy/references/apps.md +47 -36
  77. package/skills/xsolana-economy/references/sentence-to-spec.md +149 -129
  78. package/skills/xsolana-economy/references/telling-the-person.md +15 -6
  79. package/skills/xsolana-economy/references/trust-and-cheating.md +126 -0
  80. package/types/chain/core.d.ts +1 -1
  81. package/types/chain/lookup.d.ts +1 -1
  82. package/types/client/accounts.d.ts +24 -3
  83. package/types/client/grant.d.ts +2 -2
  84. package/types/client/idl/loot.d.ts +22 -0
  85. package/types/client/ix.d.ts +54 -45
  86. package/types/client/pda.d.ts +3 -1
  87. package/types/economy/cost.d.mts +1 -1
  88. package/types/economy/explain.d.mts +3 -0
  89. package/types/economy/guide.d.mts +5 -0
  90. package/types/economy/model.d.mts +48 -3
  91. package/types/economy/plan.d.mts +44 -2
  92. package/types/economy/review.d.mts +45 -0
  93. package/types/economy/trust.d.mts +8 -0
  94. package/types/index.d.ts +1 -0
  95. package/types/mcp/tools.d.mts +62 -0
  96. package/types/play/index.d.ts +14 -25
  97. package/types/results/index.d.ts +8 -7
  98. package/types/results/server.d.ts +126 -0
  99. package/types/results/wire.d.ts +1 -1
package/js/client/ix.js CHANGED
@@ -18,16 +18,61 @@
18
18
  import { addressBytes, encodeBase58, NO_ADDRESS } from '../chain/base58.js';
19
19
  import { encodeAccount, encodeInstruction } from '../chain/borsh.js';
20
20
  import { compareBytes, concat, intLE, readUintLE, toBigInt, uintLE, utf8 } from '../chain/bytes.js';
21
- import { addressWithSeed, ASSOCIATED_TOKEN_PROGRAM, findProgramAddress, METAPLEX_METADATA_PROGRAM, SYSTEM_PROGRAM, SYSVAR_INSTRUCTIONS, SYSVAR_SLOT_HASHES, TOKEN_2022_PROGRAM, TOKEN_PROGRAM } from '../chain/pda.js';
22
- import { allocateWithSeedIx, createAccountWithSeedIx, initializeAccount3Ix, MINT_BYTES, TOKEN_ACCOUNT_BYTES } from '../chain/token.js';
21
+ import {
22
+ addressWithSeed,
23
+ ASSOCIATED_TOKEN_PROGRAM,
24
+ findProgramAddress,
25
+ METAPLEX_METADATA_PROGRAM,
26
+ SYSTEM_PROGRAM,
27
+ SYSVAR_INSTRUCTIONS,
28
+ SYSVAR_SLOT_HASHES,
29
+ TOKEN_2022_PROGRAM,
30
+ TOKEN_PROGRAM,
31
+ } from '../chain/pda.js';
32
+ import {
33
+ allocateWithSeedIx,
34
+ createAccountWithSeedIx,
35
+ initializeAccount3Ix,
36
+ MINT_BYTES,
37
+ TOKEN_ACCOUNT_BYTES,
38
+ } from '../chain/token.js';
23
39
  import { createAssociatedTokenAccountIdempotent, meta, transferLamports } from '../chain/programs.js';
24
40
  import { sha256 } from '../chain/sha256.js';
25
41
  import { MEMO_PROGRAM } from '../chain/token2022.js';
26
42
  import IDL from './idl/loot.js';
27
- import { chosenAtPurchase, CLAIMED, decode, ENDED, idleRound, nftSlot, ownStash, paymentMint, placesPulled, prizePlacesOwed, roundFinished, roundWinners, shareOf, tokenRun } from './accounts.js';
43
+ import {
44
+ chosenAtPurchase,
45
+ CLAIMED,
46
+ decode,
47
+ ENDED,
48
+ idleRound,
49
+ leftoverTo,
50
+ lotteryPots,
51
+ MAX_POTS,
52
+ nftSlot,
53
+ ownStash,
54
+ paymentMint,
55
+ placesPulled,
56
+ potIdOf,
57
+ prizePlacesOwed,
58
+ roundFinished,
59
+ roundWinners,
60
+ shareOf,
61
+ tokenRun,
62
+ } from './accounts.js';
28
63
  import { stream, TAG, weighted } from './random.js';
29
64
  import { tableBudget } from './sizes.js';
30
- import { ata, BUBBLEGUM_PROGRAM, COMPRESSION_PROGRAM, LOOT_PROGRAM, MPL_CORE_PROGRAM, NOOP_PROGRAM, pda, vault, VRF_QUEUE } from './pda.js';
65
+ import {
66
+ ata,
67
+ BUBBLEGUM_PROGRAM,
68
+ COMPRESSION_PROGRAM,
69
+ LOOT_PROGRAM,
70
+ MPL_CORE_PROGRAM,
71
+ NOOP_PROGRAM,
72
+ pda,
73
+ vault,
74
+ VRF_QUEUE,
75
+ } from './pda.js';
31
76
 
32
77
  // The options of a call made without any: read-only, and typed so the generated declarations take any option.
33
78
  const NO_OPTIONS = Object.freeze(/** @type {Record<string, any>} */ ({}));
@@ -40,7 +85,7 @@ export const TOKEN_METADATA_PROGRAM = METAPLEX_METADATA_PROGRAM;
40
85
  const ZERO_REFERENCE = new Uint8Array(32);
41
86
  /** The default address: names nobody, and nobody signs as it. */
42
87
  const NOBODY_KEY = '11111111111111111111111111111111';
43
- const hashToAddress = (hash) => encodeBase58(hash);
88
+ const hashToAddress = hash => encodeBase58(hash);
44
89
 
45
90
  /** 32 bytes a project names a call by (a match id, a payout's reason). Zeros when not given. */
46
91
  export function reference(value) {
@@ -53,8 +98,8 @@ export function reference(value) {
53
98
 
54
99
  // ---------------------------------------------------------------- any instruction
55
100
 
56
- const instructionDef = (name) => {
57
- const def = IDL.instructions.find((i) => i.name === name);
101
+ const instructionDef = name => {
102
+ const def = IDL.instructions.find(i => i.name === name);
58
103
  if (!def) throw new Error(`Unknown loot instruction ${name}`);
59
104
  return def;
60
105
  };
@@ -64,7 +109,8 @@ const instructionDef = (name) => {
64
109
  const BY_TAG = [...IDL.instructions].sort((a, b) => b.discriminator.length - a.discriminator.length);
65
110
 
66
111
  /** The IDL entry of the loot instruction whose data is `data` (its tag first), or null. */
67
- export const instructionOf = (data) => BY_TAG.find((i) => i.discriminator.length <= data.length && i.discriminator.every((b, j) => data[j] === b)) ?? null;
112
+ export const instructionOf = data =>
113
+ BY_TAG.find(i => i.discriminator.length <= data.length && i.discriminator.every((b, j) => data[j] === b)) ?? null;
68
114
 
69
115
  /**
70
116
  * An instruction's tail (`tokens::tail`): the accounts every NFT move hands Core (what a check its collection carries
@@ -74,19 +120,19 @@ export const instructionOf = (data) => BY_TAG.find((i) => i.discriminator.length
74
120
  */
75
121
  export function tailOf(extra = []) {
76
122
  if (!extra.length) return [];
77
- return [meta(MEMO_PROGRAM, false, false), ...extra.filter((m) => m.pubkey !== MEMO_PROGRAM)];
123
+ return [meta(MEMO_PROGRAM, false, false), ...extra.filter(m => m.pubkey !== MEMO_PROGRAM)];
78
124
  }
79
125
 
80
126
  /** The accounts of an instruction in order, each with its group (`checkout`, `fee`, `side`, `piece`) or ''. */
81
127
  function leaves(list, group = '') {
82
- return list.flatMap((a) => (a.accounts ? leaves(a.accounts, a.name) : [{ ...a, group }]));
128
+ return list.flatMap(a => (a.accounts ? leaves(a.accounts, a.name) : [{ ...a, group }]));
83
129
  }
84
130
 
85
131
  /** Accounts that are the same wherever an instruction names them. The program checks each by address, which the
86
132
  * IDL doesn't carry. */
87
133
  const DEFAULTS = {
88
134
  platform: () => pda.platform(),
89
- fees: (known) => known.project && pda.fees(known.project),
135
+ fees: known => known.project && pda.fees(known.project),
90
136
  program_identity: () => pda.identity(),
91
137
  vrf_program_identity: () => pda.oracleIdentity(),
92
138
  oracle_queue: () => VRF_QUEUE,
@@ -108,7 +154,7 @@ const SEED_SIZES = { u8: 1, u16: 2, u32: 4, u64: 8 };
108
154
  function seedBytes(seed, known, args, def) {
109
155
  if (seed.kind === 'const') return Uint8Array.from(seed.value);
110
156
  if (seed.kind === 'arg') {
111
- const arg = def.args.find((a) => a.name === seed.path);
157
+ const arg = def.args.find(a => a.name === seed.path);
112
158
  if (!arg || !(seed.path in args)) return null;
113
159
  if (arg.type === 'pubkey') return addressBytes(args[seed.path]);
114
160
  if (SEED_SIZES[arg.type]) return uintLE(args[seed.path], SEED_SIZES[arg.type]);
@@ -118,7 +164,7 @@ function seedBytes(seed, known, args, def) {
118
164
  // "side.farm" names the account `farm` of the group `side`; a path into an account's data ("order.requested_slot")
119
165
  // can't be resolved here.
120
166
  const parts = seed.path.split('.');
121
- const groups = new Set(def.accounts.filter((a) => a.accounts).map((a) => a.name));
167
+ const groups = new Set(def.accounts.filter(a => a.accounts).map(a => a.name));
122
168
  const name = parts.length === 1 ? parts[0] : groups.has(parts[0]) && parts.length === 2 ? parts[1] : null;
123
169
  return name && known[name] ? addressBytes(known[name]) : null;
124
170
  }
@@ -130,7 +176,12 @@ function seedBytes(seed, known, args, def) {
130
176
  * Options: `remaining` (metas after the instruction's own), `writable` and `signer` (leaf names whose flag is set
131
177
  * on, beyond the IDL's), `readonly` (leaf names whose writable flag is set off).
132
178
  */
133
- export function lootIx(name, args = {}, accounts = {}, { remaining = [], writable = [], signer = [], readonly = [] } = {}) {
179
+ export function lootIx(
180
+ name,
181
+ args = {},
182
+ accounts = {},
183
+ { remaining = [], writable = [], signer = [], readonly = [] } = {},
184
+ ) {
134
185
  const def = instructionDef(name);
135
186
  const list = leaves(def.accounts);
136
187
  const known = {};
@@ -150,7 +201,7 @@ export function lootIx(name, args = {}, accounts = {}, { remaining = [], writabl
150
201
  continue;
151
202
  }
152
203
  if (a.pda && !a.pda.program) {
153
- const seeds = a.pda.seeds.map((s) => seedBytes(s, known, args, def));
204
+ const seeds = a.pda.seeds.map(s => seedBytes(s, known, args, def));
154
205
  if (seeds.every(Boolean)) {
155
206
  known[a.name] = findProgramAddress(seeds, LOOT_PROGRAM)[0];
156
207
  progress = true;
@@ -159,7 +210,7 @@ export function lootIx(name, args = {}, accounts = {}, { remaining = [], writabl
159
210
  }
160
211
  if (!progress) break;
161
212
  }
162
- const keys = list.map((a) => {
213
+ const keys = list.map(a => {
163
214
  const key = known[a.name];
164
215
  if (key === null || (key === undefined && a.optional)) {
165
216
  if (!a.optional) throw new Error(`${name}: ${a.name} is not optional`);
@@ -177,7 +228,10 @@ export function lootIx(name, args = {}, accounts = {}, { remaining = [], writabl
177
228
  const tokenProgramOf = (programs, mint) => programs?.[mint] ?? TOKEN_PROGRAM;
178
229
 
179
230
  /** A wallet, then its associated token account of `mint`: where money owed to it goes. */
180
- const walletAccounts = (wallet, mint, programs) => [meta(wallet, false, false), meta(ata(wallet, mint, tokenProgramOf(programs, mint)), false, true)];
231
+ const walletAccounts = (wallet, mint, programs) => [
232
+ meta(wallet, false, false),
233
+ meta(ata(wallet, mint, tokenProgramOf(programs, mint)), false, true),
234
+ ];
181
235
 
182
236
  /**
183
237
  * The accounts one destination needs, in the order the program reads them: a pool, a named token account, a wallet
@@ -190,7 +244,7 @@ const walletAccounts = (wallet, mint, programs) => [meta(wallet, false, false),
190
244
  export function destinationAccounts(project, to, mint, { holders = {}, programs, treasury } = NO_OPTIONS) {
191
245
  if (to === 'Burn') return [];
192
246
  if (to === 'Treasury') {
193
- if (!treasury) throw new Error('A treasury share needs the project\'s treasury wallet (pass `treasury`).');
247
+ if (!treasury) throw new Error("A treasury share needs the project's treasury wallet (pass `treasury`).");
194
248
  return walletAccounts(treasury, mint, programs);
195
249
  }
196
250
  if (to.Pool) return [meta(pda.pool(project, to.Pool[0]), false, true)];
@@ -199,7 +253,12 @@ export function destinationAccounts(project, to, mint, { holders = {}, programs,
199
253
  if (to.Holder) {
200
254
  const asset = to.Holder[0];
201
255
  const holder = holders[asset];
202
- return [meta(asset, false, false), ...(holder ? walletAccounts(holder, mint, programs) : [meta(NO_ADDRESS, false, false), meta(NO_ADDRESS, false, false)])];
256
+ return [
257
+ meta(asset, false, false),
258
+ ...(holder
259
+ ? walletAccounts(holder, mint, programs)
260
+ : [meta(NO_ADDRESS, false, false), meta(NO_ADDRESS, false, false)]),
261
+ ];
203
262
  }
204
263
  if (to.Chosen) return [];
205
264
  throw new Error(`Unknown destination ${JSON.stringify(to)}`);
@@ -211,7 +270,10 @@ export function destinationAccounts(project, to, mint, { holders = {}, programs,
211
270
  export const MAX_SHARES = 8;
212
271
  /** `shares`, refused here when a split kept to be paid later holds more than `MAX_SHARES`. */
213
272
  const stored = (shares = []) => {
214
- if (shares.length > MAX_SHARES) throw new Error(`A split kept to be paid later holds at most ${MAX_SHARES} shares; this one has ${shares.length} (BadShares).`);
273
+ if (shares.length > MAX_SHARES)
274
+ throw new Error(
275
+ `A split kept to be paid later holds at most ${MAX_SHARES} shares; this one has ${shares.length} (BadShares).`,
276
+ );
215
277
  return shares;
216
278
  };
217
279
 
@@ -222,13 +284,18 @@ export const MAX_ITEM_TRAITS = 64;
222
284
  export const MAX_TRAIT_KEY = 64;
223
285
  /** An item's `traits`, refused here past the program's caps (`BadItem` when the owner signs). */
224
286
  const traitsInCaps = (traits = []) => {
225
- if (traits.length > MAX_ITEM_TRAITS) throw new Error(`An item draws at most ${MAX_ITEM_TRAITS} traits; this one draws ${traits.length} (BadItem).`);
226
- const long = traits.find((t) => utf8(String(t.key)).length > MAX_TRAIT_KEY);
227
- if (long) throw new Error(`A trait's name is at most ${MAX_TRAIT_KEY} bytes; “${String(long.key).slice(0, 24)}…” is ${utf8(String(long.key)).length} (BadItem).`);
287
+ if (traits.length > MAX_ITEM_TRAITS)
288
+ throw new Error(`An item draws at most ${MAX_ITEM_TRAITS} traits; this one draws ${traits.length} (BadItem).`);
289
+ const long = traits.find(t => utf8(String(t.key)).length > MAX_TRAIT_KEY);
290
+ if (long)
291
+ throw new Error(
292
+ `A trait's name is at most ${MAX_TRAIT_KEY} bytes; “${String(long.key).slice(0, 24)}…” is ${utf8(String(long.key)).length} (BadItem).`,
293
+ );
228
294
  return traits;
229
295
  };
230
296
 
231
- export const shareAccounts = (project, shares, mint, options) => shares.flatMap((s) => destinationAccounts(project, s.to, mint, options));
297
+ export const shareAccounts = (project, shares, mint, options) =>
298
+ shares.flatMap(s => destinationAccounts(project, s.to, mint, options));
232
299
 
233
300
  /**
234
301
  * A split with the buyer's choice written in, as the program does at `open_box` and `buy_tickets`: a wallet, the
@@ -236,15 +303,18 @@ export const shareAccounts = (project, shares, mint, options) => shares.flatMap(
236
303
  * contest's decision names its wallet the same way (`decideIx`).
237
304
  */
238
305
  export const chosenShares = (shares, choice) =>
239
- shares.map((s) => {
306
+ shares.map(s => {
240
307
  if (!s.to.Chosen) return s;
241
- if (!choice && s.to.Chosen.item != null) throw new Error('This share pays the holder of a collectible the buyer names: name one (`chosenRecipient`, `chosen`).');
308
+ if (!choice && s.to.Chosen.item != null)
309
+ throw new Error(
310
+ 'This share pays the holder of a collectible the buyer names: name one (`chosenRecipient`, `chosen`).',
311
+ );
242
312
  if (!choice) return { ...s, to: 'Treasury' };
243
313
  return { ...s, to: s.to.Chosen.item == null ? { Wallet: [choice] } : { Holder: [choice] } };
244
314
  });
245
315
 
246
316
  /** Whether a split burns, so the mint is written: it has a burn share. */
247
- export const sharesBurn = (shares) => shares.some((s) => s.to === 'Burn');
317
+ export const sharesBurn = shares => shares.some(s => s.to === 'Burn');
248
318
 
249
319
  /**
250
320
  * The account each prize of a box's entries comes with at the roll, in entry order and each entry's prizes in order:
@@ -255,20 +325,25 @@ export const sharesBurn = (shares) => shares.some((s) => s.to === 'Burn');
255
325
  */
256
326
  export function entryAccounts(project, entries, items = {}) {
257
327
  let budget = Math.max(0, tableBudget(entries));
258
- return entries.flatMap(({ prizes }) => prizes.flatMap((prize) => {
259
- // An item, compressed or not; a fresh one's tables after it, in the order of its traits (`items` maps an item's id
260
- // to its decoded Item: one left out is taken as not fresh).
261
- const id = prize.Item?.[0] ?? prize.Compressed?.item;
262
- if (id !== undefined) {
263
- const tables = traitTables(items[id]);
264
- const fits = tables.length <= budget;
265
- if (fits) budget -= tables.length;
266
- return [meta(pda.item(project, id), false, true), ...(fits ? tables : []).map((t) => meta(pda.table(project, t), false, true))];
267
- }
268
- if (prize.Tokens?.from.Pool) return [meta(pda.pool(project, prize.Tokens.from.Pool[0]), false, true)];
269
- const stash = prize.Nft?.stash ?? prize.Stash?.[0];
270
- return stash === undefined ? [] : [meta(pda.stash(project, stash), false, true)];
271
- }));
328
+ return entries.flatMap(({ prizes }) =>
329
+ prizes.flatMap(prize => {
330
+ // An item, compressed or not; a fresh one's tables after it, in the order of its traits (`items` maps an item's id
331
+ // to its decoded Item: one left out is taken as not fresh).
332
+ const id = prize.Item?.[0] ?? prize.Compressed?.item;
333
+ if (id !== undefined) {
334
+ const tables = traitTables(items[id]);
335
+ const fits = tables.length <= budget;
336
+ if (fits) budget -= tables.length;
337
+ return [
338
+ meta(pda.item(project, id), false, true),
339
+ ...(fits ? tables : []).map(t => meta(pda.table(project, t), false, true)),
340
+ ];
341
+ }
342
+ if (prize.Tokens?.from.Pool) return [meta(pda.pool(project, prize.Tokens.from.Pool[0]), false, true)];
343
+ const stash = prize.Nft?.stash ?? prize.Stash?.[0];
344
+ return stash === undefined ? [] : [meta(pda.stash(project, stash), false, true)];
345
+ }),
346
+ );
272
347
  }
273
348
 
274
349
  /**
@@ -277,18 +352,23 @@ export function entryAccounts(project, entries, items = {}) {
277
352
  * pool not opened yet has none and is left out).
278
353
  */
279
354
  export function prizeAccounts(project, entries, poolMints = {}, programs) {
280
- return prizeMints(entries, poolMints).map(({ mint, minted }) => [meta(mint, false, minted), meta(vault(project, mint, tokenProgramOf(programs, mint)), false, true)]).flat();
355
+ return prizeMints(entries, poolMints)
356
+ .map(({ mint, minted }) => [
357
+ meta(mint, false, minted),
358
+ meta(vault(project, mint, tokenProgramOf(programs, mint)), false, true),
359
+ ])
360
+ .flat();
281
361
  }
282
362
 
283
363
  /** The mints a box's tokens are in, in the order their prizes first name them: { mint, minted }. */
284
364
  function prizeMints(entries, poolMints = {}) {
285
365
  const mints = [];
286
- for (const prize of entries.flatMap((e) => e.prizes)) {
366
+ for (const prize of entries.flatMap(e => e.prizes)) {
287
367
  const from = prize.Tokens?.from;
288
368
  if (!from) continue;
289
369
  const mint = from.Mint ? from.Mint[0] : poolMints[from.Pool[0]];
290
370
  if (!mint) continue;
291
- const seen = mints.find((m) => m.mint === mint);
371
+ const seen = mints.find(m => m.mint === mint);
292
372
  if (seen) seen.minted ||= Boolean(from.Mint);
293
373
  else mints.push({ mint, minted: Boolean(from.Mint) });
294
374
  }
@@ -297,19 +377,20 @@ function prizeMints(entries, poolMints = {}) {
297
377
 
298
378
  /** Each pool a box's prizes name, mapped to its mint: `poolMints`'s, or the box's own currency when it isn't given. */
299
379
  function boxPoolMints(box, poolMints) {
300
- const pools = box.entries.flatMap((e) => e.prizes.flatMap((p) => (p.Tokens?.from.Pool ? [p.Tokens.from.Pool[0]] : [])));
301
- return Object.fromEntries(pools.map((pool) => [pool, poolMints && pool in poolMints ? poolMints[pool] : box.currency]));
380
+ const pools = box.entries.flatMap(e => e.prizes.flatMap(p => (p.Tokens?.from.Pool ? [p.Tokens.from.Pool[0]] : [])));
381
+ return Object.fromEntries(pools.map(pool => [pool, poolMints && pool in poolMints ? poolMints[pool] : box.currency]));
302
382
  }
303
383
 
304
384
  /** Whether a box leaves its outcome to the oracle: more than one entry, or an entry gives a stash's NFT at random. */
305
- export const boxRolls = (entries) => entries.length > 1 || entries.some((e) => e.prizes.some((p) => p.Stash));
385
+ export const boxRolls = entries => entries.length > 1 || entries.some(e => e.prizes.some(p => p.Stash));
306
386
 
307
387
  /** The tables a fresh item's traits draw from, in the order of its traits (none for an item whose units are alike). */
308
- export const traitTables = (item) => (item?.traits ?? []).flatMap((t) => (t.from?.Table ? [t.from.Table[0]] : []));
388
+ export const traitTables = item => (item?.traits ?? []).flatMap(t => (t.from?.Table ? [t.from.Table[0]] : []));
309
389
 
310
390
  /** Whether a box gives a fresh item (one with traits): its units' traits are drawn by the oracle (`items` maps an
311
391
  * item's id to its decoded Item, as `entryAccounts`). */
312
- export const boxFresh = (entries, items = {}) => entries.some((e) => e.prizes.some((p) => (items[p.Item?.[0] ?? p.Compressed?.item]?.traits ?? []).length > 0));
392
+ export const boxFresh = (entries, items = {}) =>
393
+ entries.some(e => e.prizes.some(p => (items[p.Item?.[0] ?? p.Compressed?.item]?.traits ?? []).length > 0));
313
394
 
314
395
  /**
315
396
  * What one prize a settle hands over may open or mint, at most: a collectible's Core asset with the program's plugins
@@ -327,10 +408,16 @@ export const SETTLE_RENT_UNIT = 6_000_000n;
327
408
  * each. A throwaway key that settles is funded with it, and gives back what it doesn't spend.
328
409
  */
329
410
  export function settleRent(box, opens = 1, items = {}) {
330
- const rents = (p) => Boolean(p.Item || p.Nft || p.Stash || (p.Compressed && (items[p.Compressed.item]?.traits ?? []).length));
331
- const most = Math.max(0, ...box.entries.map((e) => e.prizes.filter(rents).length));
332
- const sources = new Set(box.entries.flatMap((e) => e.prizes.flatMap((p) => (p.Tokens ? [JSON.stringify(p.Tokens.from)] : []))));
333
- return SETTLE_RENT_UNIT * (BigInt(most * boxDraws(box) * Math.max(1, opens)) + BigInt(sources.size) + (box.subject ? 1n : 0n));
411
+ const rents = p =>
412
+ Boolean(p.Item || p.Nft || p.Stash || (p.Compressed && (items[p.Compressed.item]?.traits ?? []).length));
413
+ const most = Math.max(0, ...box.entries.map(e => e.prizes.filter(rents).length));
414
+ const sources = new Set(
415
+ box.entries.flatMap(e => e.prizes.flatMap(p => (p.Tokens ? [JSON.stringify(p.Tokens.from)] : []))),
416
+ );
417
+ return (
418
+ SETTLE_RENT_UNIT *
419
+ (BigInt(most * boxDraws(box) * Math.max(1, opens)) + BigInt(sources.size) + (box.subject ? 1n : 0n))
420
+ );
334
421
  }
335
422
 
336
423
  /**
@@ -349,11 +436,11 @@ export function rollNonce(order, requestedSlot, k) {
349
436
  * taken until it settles or is refunded. `openBoxIx` makes a box's roll on one there; a mission's roll (`unstakeNftIx`)
350
437
  * and a recipe's (`craftIx`) take it as their `nonce`.
351
438
  */
352
- export const nonceOn = (asset) => readUintLE(addressBytes(asset), 0, 8);
439
+ export const nonceOn = asset => readUintLE(addressBytes(asset), 0, 8);
353
440
 
354
441
  /** Whether a box's roll holds the NFT it rolls on, or moves its numbers: an entry burns it, gives it
355
442
  * away or moves its numbers. Its order is at the NFT's nonce (`nonceOn`). */
356
- export const boxHolds = (box) => (box.fates ?? []).some((f) => f.then != null || (f.shifts ?? []).length > 0);
443
+ export const boxHolds = box => (box.fates ?? []).some(f => f.then != null || (f.shifts ?? []).length > 0);
357
444
 
358
445
  /**
359
446
  * The nonce of the order a roll a sender names is made in: a craft's roll on a collectible kept (`RollOn`) and a
@@ -366,7 +453,7 @@ export const namedRollNonce = (box, asset, named) => {
366
453
  };
367
454
 
368
455
  /** Whether a limit for each wallet ({ total, per_window, window }) limits anything. */
369
- const limits = (perWallet) => Boolean(perWallet && (Number(perWallet.total) || Number(perWallet.per_window)));
456
+ const limits = perWallet => Boolean(perWallet && (Number(perWallet.total) || Number(perWallet.per_window)));
370
457
 
371
458
  /**
372
459
  * The accounts of an NFT the player shows, for a gate or for odds: right after the instruction's other accounts.
@@ -379,8 +466,16 @@ export function shownAccounts(project, shown) {
379
466
  if (!shown) return [];
380
467
  if (typeof shown === 'string') return [meta(shown, false, false)];
381
468
  if (shown.asset) return [meta(shown.asset, false, false), meta(pda.sheet(project, shown.asset), false, false)];
382
- if (shown.compressed) return [shown.compressed, pda.leaf(shown.compressed), shown.tree, pda.sheet(project, shown.compressed)].map((key) => meta(key, false, false));
383
- return [shown.mint, tokenMetadata.metadata(shown.mint), ata(shown.owner, shown.mint, TOKEN_PROGRAM), pda.sheet(project, shown.mint)].map((key) => meta(key, false, false));
469
+ if (shown.compressed)
470
+ return [shown.compressed, pda.leaf(shown.compressed), shown.tree, pda.sheet(project, shown.compressed)].map(key =>
471
+ meta(key, false, false),
472
+ );
473
+ return [
474
+ shown.mint,
475
+ tokenMetadata.metadata(shown.mint),
476
+ ata(shown.owner, shown.mint, TOKEN_PROGRAM),
477
+ pda.sheet(project, shown.mint),
478
+ ].map(key => meta(key, false, false));
384
479
  }
385
480
 
386
481
  /**
@@ -389,9 +484,11 @@ export function shownAccounts(project, shown) {
389
484
  * one, or for a box's odds.
390
485
  */
391
486
  export const gateAccounts = (project, { gate, wallet, shown, listProof = [] } = NO_OPTIONS) => [
392
- ...listProof.map((node) => meta(typeof node === 'string' ? node : encodeBase58(node), false, false)),
487
+ ...listProof.map(node => meta(typeof node === 'string' ? node : encodeBase58(node), false, false)),
393
488
  // A stake gate reads the wallet's token seat in its farm, then the farm (its tiers' locks), where an NFT shown would be.
394
- ...(gate?.Staked ? [meta(pda.seat(gate.Staked.farm, wallet), false, false), meta(gate.Staked.farm, false, false)] : []),
489
+ ...(gate?.Staked
490
+ ? [meta(pda.seat(gate.Staked.farm, wallet), false, false), meta(gate.Staked.farm, false, false)]
491
+ : []),
395
492
  ...shownAccounts(project, shown),
396
493
  ];
397
494
 
@@ -401,7 +498,12 @@ export const gateAccounts = (project, { gate, wallet, shown, listProof = [] } =
401
498
  * is then at `prizeRollNonce` of it (plus the place where the route says so), which no roll on an NFT is at.
402
499
  */
403
500
  export const prizeNonce = (...parts) => {
404
- const hash = sha256(concat(utf8('loot:prize'), ...parts.map((p) => (typeof p === 'string' ? addressBytes(p) : p instanceof Uint8Array ? p : uintLE(p, 8)))));
501
+ const hash = sha256(
502
+ concat(
503
+ utf8('loot:prize'),
504
+ ...parts.map(p => (typeof p === 'string' ? addressBytes(p) : p instanceof Uint8Array ? p : uintLE(p, 8))),
505
+ ),
506
+ );
405
507
  return readUintLE(hash, 0, 8);
406
508
  };
407
509
 
@@ -413,18 +515,18 @@ export const prizeNonce = (...parts) => {
413
515
  * won is made at settle at `rollNonce(order, slot, k)` as it is (hashed under "roll", so no named nonce reaches it). A roll on an NFT (`nonceOn`, a recipe's `RollOn`, a mission's
414
516
  * `roll`) and a box's own order are not. Not `prizeNonce`, which makes the `n` a route names from its parts.
415
517
  */
416
- export const prizeRollNonce = (n) => {
518
+ export const prizeRollNonce = n => {
417
519
  // The program adds the place or `k` with `wrapping_add`: the same here.
418
520
  const hash = sha256(concat(utf8('prize'), uintLE(BigInt.asUintN(64, toBigInt(n)), 8)));
419
521
  return readUintLE(hash, 0, 8);
420
522
  };
421
523
 
422
524
  /** The NFT accounts every route takes as a `Piece`, in the program's order, with the writable flags the IDL has. */
423
- const PIECE = leaves(instructionDef('hold').accounts).filter((a) => a.group === 'piece');
525
+ const PIECE = leaves(instructionDef('hold').accounts).filter(a => a.group === 'piece');
424
526
 
425
527
  /** A `piece`'s accounts as metas, in the program's order: what a prize of an NFT names after its stash. */
426
528
  function pieceMetas(p) {
427
- return PIECE.map((a) => {
529
+ return PIECE.map(a => {
428
530
  const key = p.accounts[a.name];
429
531
  if (key == null) return meta(LOOT_PROGRAM, false, false);
430
532
  return meta(key, false, a.writable === true || p.writable.includes(a.name));
@@ -444,25 +546,53 @@ function pieceMetas(p) {
444
546
  * collectibles of the same item already made earlier in the instruction); `nft` the NFT's kind, as `piece` takes it
445
547
  * ({ collection, tokenMetadata, compressed }).
446
548
  */
447
- export function prizeMetas({ project, collection, prize, winner, nonce = 0n, poolMints = {}, items = {}, nft = {}, made = 0, projectMint, programs, lottery }) {
549
+ export function prizeMetas({
550
+ project,
551
+ collection,
552
+ prize,
553
+ winner,
554
+ nonce = 0n,
555
+ poolMints = {},
556
+ items = {},
557
+ nft = {},
558
+ made = 0,
559
+ projectMint,
560
+ programs,
561
+ lottery,
562
+ }) {
448
563
  if (prize.Tokens) {
449
564
  const { from } = prize.Tokens;
450
565
  // A pool never opened (its mint given as null) gives nothing: any mint stands in for its place, the project's.
451
566
  const pool = from.Pool?.[0];
452
567
  const mint = from.Mint ? from.Mint[0] : (poolMints[pool] ?? (pool in poolMints ? projectMint : undefined));
453
- if (!mint) throw new Error(`a prize from pool ${pool} needs that pool's mint (poolMints; null for a pool never opened, with projectMint)`);
568
+ if (!mint)
569
+ throw new Error(
570
+ `a prize from pool ${pool} needs that pool's mint (poolMints; null for a pool never opened, with projectMint)`,
571
+ );
454
572
  const pooled = from.Pool ? [meta(pda.pool(project, pool), false, true)] : [];
455
- return [meta(mint, false, Boolean(from.Mint)), ...pooled, meta(ata(winner, mint, tokenProgramOf(programs, mint)), false, true)];
573
+ return [
574
+ meta(mint, false, Boolean(from.Mint)),
575
+ ...pooled,
576
+ meta(ata(winner, mint, tokenProgramOf(programs, mint)), false, true),
577
+ ];
456
578
  }
457
579
  if (prize.Item) {
458
580
  const id = prize.Item[0];
459
581
  const item = items[id];
460
- if (item === undefined) throw new Error(`a prize of item ${id} needs the item (items: its decoded Item, or null for none written), for its kind and next serial`);
582
+ if (item === undefined)
583
+ throw new Error(
584
+ `a prize of item ${id} needs the item (items: its decoded Item, or null for none written), for its kind and next serial`,
585
+ );
461
586
  const address = pda.item(project, id);
462
587
  // A normal item, or one never written, is its address alone; a collectible is minted into its own collection.
463
588
  if (!item || item.kind !== 'Collectible') return [meta(address, false, true)];
464
589
  const serial = Number(item.issued) + 1 + made;
465
- return [meta(address, false, true), meta(item.collection ?? collection, false, true), meta(pda.itemAsset(address, serial), false, true), meta(MPL_CORE_PROGRAM, false, false)];
590
+ return [
591
+ meta(address, false, true),
592
+ meta(item.collection ?? collection, false, true),
593
+ meta(pda.itemAsset(address, serial), false, true),
594
+ meta(MPL_CORE_PROGRAM, false, false),
595
+ ];
466
596
  }
467
597
  if (prize.Nft) {
468
598
  const { stash, nft: asset } = prize.Nft;
@@ -471,23 +601,34 @@ export function prizeMetas({ project, collection, prize, winner, nonce = 0n, poo
471
601
  }
472
602
  if (prize.Held) {
473
603
  // An NFT a lottery's place promises, from that lottery's own stash.
474
- if (!lottery) throw new Error('A held NFT is a lottery\'s prize: name the lottery (`lottery`, its address).');
604
+ if (!lottery) throw new Error("A held NFT is a lottery's prize: name the lottery (`lottery`, its address).");
475
605
  const p = piece({ project, asset: prize.Held[0], wallet: winner, ...nft, ...ONLY_MOVES });
476
606
  return [meta(pda.lotteryStash(lottery), false, true), ...pieceMetas(p), ...p.proof];
477
607
  }
478
- if (prize.Stash) throw new Error('A stash\'s NFT at random is a box entry\'s prize only: elsewhere, a roll of a box that gives it.');
608
+ if (prize.Stash)
609
+ throw new Error("A stash's NFT at random is a box entry's prize only: elsewhere, a roll of a box that gives it.");
479
610
  if (prize.Compressed) {
480
611
  // A compressed NFT of the item, minted into the project's tree, in the item's collection.
481
612
  const { item: id, tree } = prize.Compressed;
482
613
  const item = items[id];
483
- if (!item) throw new Error(`a compressed prize of item ${id} needs the item (items: its decoded Item), for its collection`);
614
+ if (!item)
615
+ throw new Error(`a compressed prize of item ${id} needs the item (items: its decoded Item), for its collection`);
484
616
  return [
485
- meta(pda.item(project, id), false, true), meta(pda.treeConfig(tree), false, true), meta(tree, false, true), meta(item.collection, false, true),
486
- meta(BUBBLEGUM_PROGRAM, false, false), meta(COMPRESSION_PROGRAM, false, false), meta(NOOP_PROGRAM, false, false), meta(MPL_CORE_PROGRAM, false, false),
617
+ meta(pda.item(project, id), false, true),
618
+ meta(pda.treeConfig(tree), false, true),
619
+ meta(tree, false, true),
620
+ meta(item.collection, false, true),
621
+ meta(BUBBLEGUM_PROGRAM, false, false),
622
+ meta(COMPRESSION_PROGRAM, false, false),
623
+ meta(NOOP_PROGRAM, false, false),
624
+ meta(MPL_CORE_PROGRAM, false, false),
487
625
  meta(BUBBLEGUM_CORE_SIGNER, false, false),
488
626
  ];
489
627
  }
490
- return [meta(pda.box(project, prize.Roll[0]), false, false), meta(pda.order(project, winner, prizeRollNonce(nonce)), false, true)];
628
+ return [
629
+ meta(pda.box(project, prize.Roll[0]), false, false),
630
+ meta(pda.order(project, winner, prizeRollNonce(nonce)), false, true),
631
+ ];
491
632
  }
492
633
 
493
634
  /**
@@ -499,12 +640,22 @@ export function prizeMetas({ project, collection, prize, winner, nonce = 0n, poo
499
640
  */
500
641
  export function rollAccounts(project, box, rolls = {}) {
501
642
  if (!box) return [];
502
- const { items = {}, poolMints = {}, programs } = /** @type {{ items?: any, poolMints?: any, programs?: any }} */ (rolls);
503
- for (const p of box.entries.flatMap((e) => e.prizes)) {
643
+ const {
644
+ items = {},
645
+ poolMints = {},
646
+ programs,
647
+ } = /** @type {{ items?: any, poolMints?: any, programs?: any }} */ (rolls);
648
+ for (const p of box.entries.flatMap(e => e.prizes)) {
504
649
  const id = p.Item?.[0] ?? p.Compressed?.item;
505
- if (id !== undefined && items[id] === undefined) throw new Error(`a roll of box ${box.box_id ?? ''} needs item ${id} (rolls.items: its decoded Item, or null for none written), for its tables`);
650
+ if (id !== undefined && items[id] === undefined)
651
+ throw new Error(
652
+ `a roll of box ${box.box_id ?? ''} needs item ${id} (rolls.items: its decoded Item, or null for none written), for its tables`,
653
+ );
506
654
  const pool = p.Tokens?.from.Pool?.[0];
507
- if (pool !== undefined && !(pool in poolMints)) throw new Error(`a roll of box ${box.box_id ?? ''} needs the mint of pool ${pool} (rolls.poolMints; null for a pool never opened)`);
655
+ if (pool !== undefined && !(pool in poolMints))
656
+ throw new Error(
657
+ `a roll of box ${box.box_id ?? ''} needs the mint of pool ${pool} (rolls.poolMints; null for a pool never opened)`,
658
+ );
508
659
  }
509
660
  return [...entryAccounts(project, box.entries, items), ...prizeAccounts(project, box.entries, poolMints, programs)];
510
661
  }
@@ -514,7 +665,7 @@ export function rollAccounts(project, box, rolls = {}) {
514
665
  * order they first come, and nothing after it. What a roll can give is read when it is asked.
515
666
  */
516
667
  export function boxesAccounts(project, ids) {
517
- return [...new Set(ids)].map((id) => meta(pda.box(project, id), false, false));
668
+ return [...new Set(ids)].map(id => meta(pda.box(project, id), false, false));
518
669
  }
519
670
 
520
671
  /** The boxes a record names to roll later (`Def::rolls`), in order: a box's entries' rolls, a recipe's rolls and
@@ -522,12 +673,17 @@ export function boxesAccounts(project, ids) {
522
673
  export function defRolls(def) {
523
674
  const [kind] = Object.keys(def ?? {});
524
675
  const args = def?.[kind]?.args ?? {};
525
- const prizes = kind === 'Box' ? (args.entries ?? []).flatMap((e) => e.prizes ?? [])
526
- : kind === 'Recipe' ? (args.outputs ?? []).map((o) => o.Prize?.[0] ?? (o.RollOn ? { Roll: [o.RollOn.box_id] } : null))
527
- : kind === 'Grant' ? [args.gives]
528
- : kind === 'Lottery' ? (args.terms?.places ?? args.places ?? []).map((p) => (typeof p === 'object' ? p.prize : null))
529
- : [];
530
- return prizes.flatMap((p) => (p?.Roll ? [p.Roll[0]] : []));
676
+ const prizes =
677
+ kind === 'Box'
678
+ ? (args.entries ?? []).flatMap(e => e.prizes ?? [])
679
+ : kind === 'Recipe'
680
+ ? (args.outputs ?? []).map(o => o.Prize?.[0] ?? (o.RollOn ? { Roll: [o.RollOn.box_id] } : null))
681
+ : kind === 'Grant'
682
+ ? [args.gives]
683
+ : kind === 'Lottery'
684
+ ? (args.terms?.places ?? args.places ?? []).map(p => (typeof p === 'object' ? p.prize : null))
685
+ : [];
686
+ return prizes.flatMap(p => (p?.Roll ? [p.Roll[0]] : []));
531
687
  }
532
688
 
533
689
  /** Bubblegum's signer for its calls to Core, `["mpl_core_cpi_signer"]`. */
@@ -539,9 +695,19 @@ export const BUBBLEGUM_CORE_SIGNER = 'CbNY3JiXdXNE9tPNEk1aRZVEkWdj2v7kfJLNQwZZgp
539
695
  * canopy)` bytes, made empty first in the same transaction (a System `createAccount` owned by mpl-account-compression, the tree's keypair signing).
540
696
  */
541
697
  export const openTreeIx = ({ signer, project, tree, maxDepth, maxBufferSize }) =>
542
- lootIx('open_tree', { max_depth: maxDepth, max_buffer_size: maxBufferSize }, {
543
- signer, project, tree, tree_config: pda.treeConfig(tree), bubblegum_program: BUBBLEGUM_PROGRAM, compression_program: COMPRESSION_PROGRAM, log_wrapper: NOOP_PROGRAM,
544
- });
698
+ lootIx(
699
+ 'open_tree',
700
+ { max_depth: maxDepth, max_buffer_size: maxBufferSize },
701
+ {
702
+ signer,
703
+ project,
704
+ tree,
705
+ tree_config: pda.treeConfig(tree),
706
+ bubblegum_program: BUBBLEGUM_PROGRAM,
707
+ compression_program: COMPRESSION_PROGRAM,
708
+ log_wrapper: NOOP_PROGRAM,
709
+ },
710
+ );
545
711
 
546
712
  /** The bytes an mpl-account-compression tree of this shape takes (its header, changelog, rightmost path and canopy). */
547
713
  export function treeSize(maxDepth, maxBufferSize, canopy) {
@@ -556,20 +722,26 @@ export const ticketOf = (requester, slot) => {
556
722
  return hashToAddress(hash);
557
723
  };
558
724
 
559
-
560
725
  // ---------------------------------------------------------------- the platform
561
726
 
562
727
  /** Signed by the program's upgrade authority, once. `fees`: { player, project } lamports. */
563
728
  export const initPlatformIx = ({ admin, fees }) => lootIx('init_platform', { fees }, { admin });
564
729
 
565
730
  /** `nextAdmin` signs too when it is another key. */
566
- export const setPlatformIx = ({ admin, fees, nextAdmin = admin }) => lootIx('set_platform', { fees }, { admin, next_admin: nextAdmin });
731
+ export const setPlatformIx = ({ admin, fees, nextAdmin = admin }) =>
732
+ lootIx('set_platform', { fees }, { admin, next_admin: nextAdmin });
567
733
 
568
734
  /** The platform collects the fees waiting in each project's fee box, into `to`. */
569
735
  export const collectFeesIx = ({ admin, to, projects }) =>
570
- lootIx('collect_fees', {}, { admin, to }, { remaining: projects.map((project) => meta(pda.fees(project), false, true)) });
736
+ lootIx(
737
+ 'collect_fees',
738
+ {},
739
+ { admin, to },
740
+ { remaining: projects.map(project => meta(pda.fees(project), false, true)) },
741
+ );
571
742
 
572
- export const setSuspendedIx = ({ admin, project, suspended }) => lootIx('set_suspended', { suspended }, { admin, project });
743
+ export const setSuspendedIx = ({ admin, project, suspended }) =>
744
+ lootIx('set_suspended', { suspended }, { admin, project });
573
745
 
574
746
  // ---------------------------------------------------------------- a project's settings
575
747
 
@@ -583,7 +755,12 @@ export const setSuspendedIx = ({ admin, project, suspended }) => lootIx('set_sus
583
755
  * for that it can tell (an account of loot or of a token program, a program: `BadKey`).
584
756
  */
585
757
  export const createProjectIx = ({ owner, id = 0, authority, treasury, collections = [] }) =>
586
- lootIx('create_project', { id: toBigInt(id), authority, treasury, collections }, { owner, authority, treasury, project: pda.project(owner, id) }, { remaining: collections.map((c) => meta(c, false, false)) });
758
+ lootIx(
759
+ 'create_project',
760
+ { id: toBigInt(id), authority, treasury, collections },
761
+ { owner, authority, treasury, project: pda.project(owner, id) },
762
+ { remaining: collections.map(c => meta(c, false, false)) },
763
+ );
587
764
 
588
765
  /**
589
766
  * `update`: { authority?, treasury?, paused?, collections? }. `collections` replaces the project's list; every one in
@@ -592,10 +769,24 @@ export const createProjectIx = ({ owner, id = 0, authority, treasury, collection
592
769
  * authority or treasury comes with the account at its key (`new_authority`, `new_treasury`), which the program checks
593
770
  * as `createProjectIx`'s.
594
771
  */
595
- export const updateProjectIx = ({ owner, project, authority = null, paused = null, treasury = null, collections = null, newOwner = null }) =>
596
- lootIx('update_project', { update: { authority, treasury, paused, collections } }, { owner, project, new_owner: newOwner, new_authority: authority, new_treasury: treasury }, { remaining: (collections ?? []).map((c) => meta(c, false, false)) });
772
+ export const updateProjectIx = ({
773
+ owner,
774
+ project,
775
+ authority = null,
776
+ paused = null,
777
+ treasury = null,
778
+ collections = null,
779
+ newOwner = null,
780
+ }) =>
781
+ lootIx(
782
+ 'update_project',
783
+ { update: { authority, treasury, paused, collections } },
784
+ { owner, project, new_owner: newOwner, new_authority: authority, new_treasury: treasury },
785
+ { remaining: (collections ?? []).map(c => meta(c, false, false)) },
786
+ );
597
787
 
598
- export const openPoolIx = ({ owner, project, index, mint }) => lootIx('open_pool', { index }, { owner, project, mint, pool: pda.pool(project, index) });
788
+ export const openPoolIx = ({ owner, project, index, mint }) =>
789
+ lootIx('open_pool', { index }, { owner, project, mint, pool: pda.pool(project, index) });
599
790
 
600
791
  /** Where a record of the owner's lives: its kind's seed, the project and its id. */
601
792
  export function recordAddress(project, def) {
@@ -640,16 +831,21 @@ export function withDefaults(def) {
640
831
  stored(args.shares);
641
832
  if (!args.currency) throw new Error('A box names the mint its price is in (`currency`).');
642
833
  // What one open draws (`LootBox::slots`): none is one draw among every entry.
643
- args.slots = (args.slots ?? []).map((s) => ({ only: [], distinct: false, share: 0, ...s }));
834
+ args.slots = (args.slots ?? []).map(s => ({ only: [], distinct: false, share: 0, ...s }));
644
835
  // An entry: its prizes (`prizeMetas`) and its weight. A box burns its price with a share to `Burn`.
645
- if (!Array.isArray(args.entries)) throw new Error('A box needs its entries (`entries`): what its roll can give, each with its weight.');
646
- args.entries = args.entries.map((e) => ({ prizes: [], ...e }));
836
+ if (!Array.isArray(args.entries))
837
+ throw new Error('A box needs its entries (`entries`): what its roll can give, each with its weight.');
838
+ args.entries = args.entries.map(e => ({ prizes: [], ...e }));
647
839
  args.subject ??= null;
648
840
  // What it does once an entry can't be given (`WhenOut`): stop selling while any entry couldn't cover the draws of
649
841
  // a purchase and those waiting for their answer (the default), or draw among what is left ('DrawLeft').
650
842
  args.when_out ??= 'StopSales';
651
843
  // A fate's `then`: null sends the NFT back to its player; 'Burn', { Give: [wallet] } or { Stash: [id] }.
652
- args.fates = (args.fates ?? []).map((f) => ({ ...f, then: f.then ?? null, shifts: (f.shifts ?? []).map((s) => ({ from_now: false, ...s })) }));
844
+ args.fates = (args.fates ?? []).map(f => ({
845
+ ...f,
846
+ then: f.then ?? null,
847
+ shifts: (f.shifts ?? []).map(s => ({ from_now: false, ...s })),
848
+ }));
653
849
  }
654
850
  if (kind === 'Box' || kind === 'Recipe') {
655
851
  args.window ??= NO_WINDOW;
@@ -657,16 +853,34 @@ export function withDefaults(def) {
657
853
  }
658
854
  if (kind === 'Recipe') {
659
855
  // A raise moves a number on a kept collectible (`place`) as a box's fate does (`Shift`); a need tests one (`Test`).
660
- args.raises = (args.raises ?? []).map(({ place, shift, ...flat }) => ({ place, shift: { from_now: false, most: 0, ...(shift ?? flat) } }));
661
- args.needs = (args.needs ?? []).map(({ place, key, test, at_least: least }) => ({ place, key, test: test ?? { AtLeast: [least ?? 0] } }));
856
+ args.raises = (args.raises ?? []).map(({ place, shift, ...flat }) => ({
857
+ place,
858
+ shift: { from_now: false, most: 0, ...(shift ?? flat) },
859
+ }));
860
+ args.needs = (args.needs ?? []).map(({ place, key, test, at_least: least }) => ({
861
+ place,
862
+ key,
863
+ test: test ?? { AtLeast: [least ?? 0] },
864
+ }));
662
865
  }
663
866
  if (kind === 'Box' && args.fall) args.fall = { shared: false, ...args.fall };
664
867
  if (kind === 'Lottery') {
665
868
  // Its terms (`LotteryTerms`, what a round copies at its first purchase), given in `terms` or beside `every`.
666
869
  const { every, gate, per_wallet: perWallet, terms: given, ...flat } = args;
667
870
  const terms = { nft_ticket: null, returns: false, min_sold: 0, ticket_mint: NO_ADDRESS, ...flat, ...given };
871
+ // Where its places are paid from is the project's to name, never a default: `{ Pot: [id] }`, a pot kept from round
872
+ // to round, or `{ Round: { rest } }`, each round's own ticket money with what its places leave going to `rest`.
873
+ const pays = terms.pays_from;
874
+ if (!pays || !(pays.Pot || pays.Round))
875
+ throw new Error(
876
+ 'A lottery names where its places are paid from (`pays_from`): { Pot: [id] } or { Round: { rest } }.',
877
+ );
878
+ if (pays.Pot && !(Number(pays.Pot[0]) >= 0 && Number(pays.Pot[0]) < MAX_POTS))
879
+ throw new Error(`A lottery's pot id is below ${MAX_POTS}.`);
668
880
  // A run of places alike is { bps, prize, times }; a number is one place's share of the pot that gives nothing else.
669
- terms.places = (terms.places ?? []).map((p) => (typeof p === 'object' ? { prize: null, times: 1, ...p } : { bps: p, prize: null, times: 1 }));
881
+ terms.places = (terms.places ?? []).map(p =>
882
+ typeof p === 'object' ? { prize: null, times: 1, ...p } : { bps: p, prize: null, times: 1 },
883
+ );
670
884
  terms.shares = stored(terms.shares ?? []);
671
885
  return { [kind]: { ...body, args: { terms, every: every ?? 0n, gate: gate ?? null, per_wallet: perWallet ?? 0 } } };
672
886
  }
@@ -680,7 +894,8 @@ export function withDefaults(def) {
680
894
 
681
895
  /**
682
896
  * The owner writes one record (`def`: { Item: { id, args } }, { Box: … }, …), at its own address. A lottery names
683
- * its pot's mint, its pot (which its first write opens), its ticket mint, both token programs and the round it waits on
897
+ * its mint, the pot its terms pay from (which a write opens when it isn't open; pot 0's address for a lottery whose
898
+ * rounds pay from their own money, where any account will do), its ticket mint, both token programs and the round it waits on
684
899
  * (`lotteryState`: the lottery as it is, null for a new one; `idleRound`: its current round's record, or round 0's once
685
900
  * it drew once and ended: a lottery made again, edited once ended or made to repeat waits while that round runs or
686
901
  * still owes NFTs of its own stash), then its own stash when a place holds or draws an NFT of it. A box, a recipe, a grant or a lottery that
@@ -699,19 +914,25 @@ export function upsertIx({ owner, project, def: given, lotteryState = null }) {
699
914
  const vest = kind === 'Schedule' ? args.action?.from?.Vest?.[0] : null;
700
915
  if (vest) {
701
916
  // It pays a wallet, the treasury or a burn: no account it pays is named.
702
- remaining = [meta(vest, false, false), meta(pda.vest(recordAddress(project, def)), false, true), meta(TOKEN_PROGRAM, false, false), meta(TOKEN_2022_PROGRAM, false, false)];
917
+ remaining = [
918
+ meta(vest, false, false),
919
+ meta(pda.vest(recordAddress(project, def)), false, true),
920
+ meta(TOKEN_PROGRAM, false, false),
921
+ meta(TOKEN_2022_PROGRAM, false, false),
922
+ ];
703
923
  }
704
924
  if (kind === 'Lottery') {
705
925
  remaining = [
706
926
  meta(args.terms.mint, false, false),
707
- meta(pda.pot(recordAddress(project, def)), false, true),
927
+ meta(pda.lotteryPot(recordAddress(project, def), potIdOf(args.terms) ?? 0), false, true),
708
928
  meta(paymentMint(args.terms), false, false),
709
929
  meta(TOKEN_PROGRAM, false, false),
710
930
  meta(TOKEN_2022_PROGRAM, false, false),
711
931
  meta(lotteryState ? idleRound(recordAddress(project, def), lotteryState) : SYSTEM_PROGRAM, false, false),
712
932
  ];
713
933
  // A place that holds or draws an NFT of its own stash (`Prize::Held`, `Prize::Raffled`): that stash, after them.
714
- if (args.terms.places.some((p) => p.prize?.Held || p.prize === 'Raffled')) remaining.push(meta(pda.lotteryStash(recordAddress(project, def)), false, false));
934
+ if (args.terms.places.some(p => p.prize?.Held || p.prize === 'Raffled'))
935
+ remaining.push(meta(pda.lotteryStash(recordAddress(project, def)), false, false));
715
936
  }
716
937
  // Each box it names to roll later (a box's, a recipe's, a grant's, a lottery's places'), once.
717
938
  remaining.push(...boxesAccounts(project, defRolls(def)));
@@ -720,23 +941,33 @@ export function upsertIx({ owner, project, def: given, lotteryState = null }) {
720
941
 
721
942
  /**
722
943
  * The owner closes the record at `target`, and its rent comes back: a grant's with the funds it held. A lottery
723
- * (`lottery`: its decoded record) sends what its pot holds to `to`, a token account of the pot's mint (the owner's own
724
- * by default), and closes the pot, once its current round has no record (one that drew once and ended: once its round 0
725
- * gave out every NFT of its own stash, `idleRound`).
944
+ * (`lottery`: its decoded record) sends what every pot it opened holds (`Lottery::pots`) to `to`, a token account of its
945
+ * mint (the owner's own by default), and closes each, once its current round has no record (one that drew once and
946
+ * ended: once its round 0 gave out every NFT of its own stash, `idleRound`), and once the round its `owing` names has
947
+ * moved in what it owes its pot (pull it first, `Part::Common`). One that never opened a pot names only those rounds.
726
948
  */
727
949
  export function closeRecordIx({ owner, project, target, lottery, schedule, programs, to, extra = [] }) {
728
950
  // A sealed schedule never closes (CannotClose): its address, and its vault's, stay bound to its terms.
729
- if (schedule?.action?.from?.Vest) throw new Error('A sealed schedule never closes: its vault stays bound to its terms.');
951
+ if (schedule?.action?.from?.Vest)
952
+ throw new Error('A sealed schedule never closes: its vault stays bound to its terms.');
730
953
  if (!lottery) return lootIx('upsert', { def: null }, { owner, project, target });
731
954
  const { mint } = lottery.terms;
732
955
  const tokenProgram = tokenProgramOf(programs, mint);
956
+ const pots = lotteryPots(lottery);
957
+ const owing = lottery.owing && lottery.owing !== NO_ADDRESS ? [meta(lottery.owing, false, false)] : [];
733
958
  const remaining = [
734
959
  meta(idleRound(target, lottery), false, false),
735
- meta(pda.pot(target), false, true),
736
- meta(mint, false, true),
737
- meta(to ?? ata(owner, mint, tokenProgram), false, true),
738
- meta(TOKEN_PROGRAM, false, false),
739
- meta(TOKEN_2022_PROGRAM, false, false),
960
+ // The round that may still owe a pot: the close waits until it has moved its leftover in (RoundRunning).
961
+ ...owing,
962
+ ...(pots.length
963
+ ? [
964
+ meta(mint, false, true),
965
+ meta(to ?? ata(owner, mint, tokenProgram), false, true),
966
+ meta(TOKEN_PROGRAM, false, false),
967
+ meta(TOKEN_2022_PROGRAM, false, false),
968
+ ...pots.map(id => meta(pda.lotteryPot(target, id), false, true)),
969
+ ]
970
+ : []),
740
971
  ...tailOf(extra),
741
972
  ];
742
973
  return lootIx('upsert', { def: null }, { owner, project, target }, { remaining });
@@ -748,13 +979,35 @@ export function closeRecordIx({ owner, project, target, lottery, schedule, progr
748
979
  * `player` (or a sponsor, or the project's own keys funding a pool) pays `amount` of `mint`, split at once by `shares`.
749
980
  * The rest goes to the project's `treasury` wallet. The player opens any account this pays into that is not there.
750
981
  */
751
- export function payIx({ player, project, mint, treasury, amount, shares = [], reference: ref, playerToken, holders, programs, extra = [] }) {
982
+ export function payIx({
983
+ player,
984
+ project,
985
+ mint,
986
+ treasury,
987
+ amount,
988
+ shares = [],
989
+ reference: ref,
990
+ playerToken,
991
+ holders,
992
+ programs,
993
+ extra = [],
994
+ }) {
752
995
  const tokenProgram = tokenProgramOf(programs, mint);
753
996
  return lootIx(
754
997
  'pay',
755
998
  { amount, shares, reference: reference(ref) },
756
- { player, project, mint, treasury, treasury_token: ata(treasury, mint, tokenProgram), player_token: playerToken ?? ata(player, mint, tokenProgram) },
757
- { remaining: [...shareAccounts(project, shares, mint, { holders, programs, treasury }), ...tailOf(extra)], writable: sharesBurn(shares) ? ['mint'] : [] },
999
+ {
1000
+ player,
1001
+ project,
1002
+ mint,
1003
+ treasury,
1004
+ treasury_token: ata(treasury, mint, tokenProgram),
1005
+ player_token: playerToken ?? ata(player, mint, tokenProgram),
1006
+ },
1007
+ {
1008
+ remaining: [...shareAccounts(project, shares, mint, { holders, programs, treasury }), ...tailOf(extra)],
1009
+ writable: sharesBurn(shares) ? ['mint'] : [],
1010
+ },
758
1011
  );
759
1012
  }
760
1013
 
@@ -769,7 +1022,26 @@ export function payIx({ player, project, mint, treasury, amount, shares = [], re
769
1022
  * The player opens the project's vault for the price's mint, and for each prize's, when it is not there. A box that
770
1023
  * limits each wallet counts the purchase in the player's counts (`pda.counts`, its rent theirs).
771
1024
  */
772
- export function openBoxIx({ player, project, box, boxAddress, nonce, maxPrice, count = 1, items = {}, shown, listProof, subject, chosenRecipient = null, chosen = null, settler = NOBODY_KEY, poolMints, playerToken, programs, extra = [] }) {
1025
+ export function openBoxIx({
1026
+ player,
1027
+ project,
1028
+ box,
1029
+ boxAddress,
1030
+ nonce,
1031
+ maxPrice,
1032
+ count = 1,
1033
+ items = {},
1034
+ shown,
1035
+ listProof,
1036
+ subject,
1037
+ chosenRecipient = null,
1038
+ chosen = null,
1039
+ settler = NOBODY_KEY,
1040
+ poolMints,
1041
+ playerToken,
1042
+ programs,
1043
+ extra = [],
1044
+ }) {
773
1045
  const mint = box.currency;
774
1046
  poolMints = boxPoolMints(box, poolMints);
775
1047
  const tokenProgram = tokenProgramOf(programs, mint);
@@ -782,14 +1054,26 @@ export function openBoxIx({ player, project, box, boxAddress, nonce, maxPrice, c
782
1054
  // It is held until the roll settles when an entry burns it, gives it away or moves its numbers, and the roll is at its
783
1055
  // nonce.
784
1056
  const holds = boxHolds(box);
785
- const on = box.subject ? piece({ project, wallet: player, ...subject, moves: holds, locks: false, writes: false }) : standIn(address);
1057
+ const on = box.subject
1058
+ ? piece({ project, wallet: player, ...subject, moves: holds, locks: false, writes: false })
1059
+ : standIn(address);
786
1060
  if (box.subject && holds) nonce = nonceOn(on.accounts.asset);
787
1061
  // A list's proof, then the NFT shown for the gate and the odds (for a box that rolls on an NFT, a compressed one's
788
1062
  // proof instead: that NFT is the piece).
789
- const gate = [...gateAccounts(project, { gate: box.gate, wallet: player, listProof }), ...(box.subject ? on.proof : shownAccounts(project, shown))];
1063
+ const gate = [
1064
+ ...gateAccounts(project, { gate: box.gate, wallet: player, listProof }),
1065
+ ...(box.subject ? on.proof : shownAccounts(project, shown)),
1066
+ ];
790
1067
  return lootIx(
791
1068
  'open_box',
792
- { nonce, max_price: maxPrice ?? box.price * BigInt(count), chosen_recipient: chosenRecipient, version: box.version, count, settler },
1069
+ {
1070
+ nonce,
1071
+ max_price: maxPrice ?? box.price * BigInt(count),
1072
+ chosen_recipient: chosenRecipient,
1073
+ version: box.version,
1074
+ count,
1075
+ settler,
1076
+ },
793
1077
  {
794
1078
  loot_box: address,
795
1079
  player,
@@ -801,7 +1085,15 @@ export function openBoxIx({ player, project, box, boxAddress, nonce, maxPrice, c
801
1085
  counts: limits(box.per_wallet) ? pda.counts(project, player) : null,
802
1086
  ...on.accounts,
803
1087
  },
804
- { remaining: [...entryAccounts(project, box.entries, items), ...prizeAccounts(project, box.entries, poolMints, programs), ...gate, ...tailOf(extra)], writable: on.writable },
1088
+ {
1089
+ remaining: [
1090
+ ...entryAccounts(project, box.entries, items),
1091
+ ...prizeAccounts(project, box.entries, poolMints, programs),
1092
+ ...gate,
1093
+ ...tailOf(extra),
1094
+ ],
1095
+ writable: on.writable,
1096
+ },
805
1097
  );
806
1098
  }
807
1099
 
@@ -816,7 +1108,27 @@ export function openBoxIx({ player, project, box, boxAddress, nonce, maxPrice, c
816
1108
  * treasury wallet; `holders` maps a collectible a share pays to its holder; each roll the entry gives is made at the
817
1109
  * next nonce from `nonce` (`buyRolls`). `shown`/`listProof` pass the box's gate.
818
1110
  */
819
- export function buyIx({ player, project, box, boxAddress, maxPrice, count = 1, treasury, chosenRecipient = null, chosen = null, nonce = 0n, shown, listProof, items = {}, poolMints = {}, nfts = {}, holders, playerToken, programs, extra = [] }) {
1111
+ export function buyIx({
1112
+ player,
1113
+ project,
1114
+ box,
1115
+ boxAddress,
1116
+ maxPrice,
1117
+ count = 1,
1118
+ treasury,
1119
+ chosenRecipient = null,
1120
+ chosen = null,
1121
+ nonce = 0n,
1122
+ shown,
1123
+ listProof,
1124
+ items = {},
1125
+ poolMints = {},
1126
+ nfts = {},
1127
+ holders,
1128
+ playerToken,
1129
+ programs,
1130
+ extra = [],
1131
+ }) {
820
1132
  const mint = box.currency;
821
1133
  const tokenProgram = tokenProgramOf(programs, mint);
822
1134
  const address = boxAddress ?? pda.box(project, box.box_id);
@@ -824,21 +1136,48 @@ export function buyIx({ player, project, box, boxAddress, maxPrice, count = 1, t
824
1136
  const made = {};
825
1137
  const prizes = buyGives(box, count).flatMap(({ prize, k }) => {
826
1138
  const item = prize.Item?.[0];
827
- const lines = prizeMetas({ project, prize, winner: player, nonce: toBigInt(nonce) + BigInt(k), poolMints, items, nft: nfts[prize.Nft?.nft], made: made[item] ?? 0, projectMint: mint, programs });
1139
+ const lines = prizeMetas({
1140
+ project,
1141
+ prize,
1142
+ winner: player,
1143
+ nonce: toBigInt(nonce) + BigInt(k),
1144
+ poolMints,
1145
+ items,
1146
+ nft: nfts[prize.Nft?.nft],
1147
+ made: made[item] ?? 0,
1148
+ projectMint: mint,
1149
+ programs,
1150
+ });
828
1151
  if (item !== undefined) made[item] = (made[item] ?? 0) + 1;
829
1152
  return lines;
830
1153
  });
831
1154
  return lootIx(
832
1155
  'buy',
833
- { max_price: maxPrice ?? box.price * BigInt(count), chosen_recipient: chosenRecipient, version: box.version, nonce: toBigInt(nonce), count },
834
1156
  {
835
- player, project, loot_box: address, mint,
1157
+ max_price: maxPrice ?? box.price * BigInt(count),
1158
+ chosen_recipient: chosenRecipient,
1159
+ version: box.version,
1160
+ nonce: toBigInt(nonce),
1161
+ count,
1162
+ },
1163
+ {
1164
+ player,
1165
+ project,
1166
+ loot_box: address,
1167
+ mint,
836
1168
  player_token: playerToken ?? ata(player, mint, tokenProgram),
837
- treasury, treasury_token: ata(treasury, mint, tokenProgram),
838
- chosen, counts: limits(box.per_wallet) ? pda.counts(project, player) : null,
1169
+ treasury,
1170
+ treasury_token: ata(treasury, mint, tokenProgram),
1171
+ chosen,
1172
+ counts: limits(box.per_wallet) ? pda.counts(project, player) : null,
839
1173
  },
840
1174
  {
841
- remaining: [...gateAccounts(project, { gate: box.gate, wallet: player, shown, listProof }), ...shareAccounts(project, shares, mint, { holders, programs, treasury }), ...prizes, ...tailOf(extra)],
1175
+ remaining: [
1176
+ ...gateAccounts(project, { gate: box.gate, wallet: player, shown, listProof }),
1177
+ ...shareAccounts(project, shares, mint, { holders, programs, treasury }),
1178
+ ...prizes,
1179
+ ...tailOf(extra),
1180
+ ],
842
1181
  writable: sharesBurn(box.shares) ? ['mint'] : [],
843
1182
  },
844
1183
  );
@@ -867,7 +1206,18 @@ export function buyGives(box, count = 1) {
867
1206
  * for a prize in a mint with a transfer hook, the hook's accounts (`extra`). The buyer's odds boosts were fixed at
868
1207
  * purchase.
869
1208
  */
870
- export function callbackRollIx({ order, orderState, box, boxAddress, randomness, project, poolMints, items = {}, programs, extra = [] }) {
1209
+ export function callbackRollIx({
1210
+ order,
1211
+ orderState,
1212
+ box,
1213
+ boxAddress,
1214
+ randomness,
1215
+ project,
1216
+ poolMints,
1217
+ items = {},
1218
+ programs,
1219
+ extra = [],
1220
+ }) {
871
1221
  const projectAddress = project ?? orderState.project;
872
1222
  const keys = [
873
1223
  meta(pda.oracleIdentity(), true, false),
@@ -878,9 +1228,19 @@ export function callbackRollIx({ order, orderState, box, boxAddress, randomness,
878
1228
  ...entryAccounts(orderState.project, box.entries, items),
879
1229
  ];
880
1230
  const prizes = prizeAccounts(orderState.project, box.entries, boxPoolMints(box, poolMints), programs);
881
- if (prizes.length) keys.push(meta(TOKEN_PROGRAM, false, false), meta(TOKEN_2022_PROGRAM, false, false), meta(projectAddress, false, false), ...prizes);
1231
+ if (prizes.length)
1232
+ keys.push(
1233
+ meta(TOKEN_PROGRAM, false, false),
1234
+ meta(TOKEN_2022_PROGRAM, false, false),
1235
+ meta(projectAddress, false, false),
1236
+ ...prizes,
1237
+ );
882
1238
  keys.push(...tailOf(extra));
883
- return { programId: LOOT_PROGRAM, keys, data: encodeInstruction(IDL, 'callback_roll', { randomness: Uint8Array.from(randomness) }) };
1239
+ return {
1240
+ programId: LOOT_PROGRAM,
1241
+ keys,
1242
+ data: encodeInstruction(IDL, 'callback_roll', { randomness: Uint8Array.from(randomness) }),
1243
+ };
884
1244
  }
885
1245
 
886
1246
  /**
@@ -892,26 +1252,46 @@ export function callbackRollIx({ order, orderState, box, boxAddress, randomness,
892
1252
  * Compressed the item, the tree's config, the tree, the collection, Bubblegum's programs; for a fresh one, then its
893
1253
  * tables and the Sheet of the NFT it mints
894
1254
  */
895
- export function wonAccounts({ order, items = {}, nfts = {}, prizeTo, putBack = [], programs, from = 0, upto = order.won.length, tree, treeMinted = 0n, unminted = [] }) {
1255
+ export function wonAccounts({
1256
+ order,
1257
+ items = {},
1258
+ nfts = {},
1259
+ prizeTo,
1260
+ putBack = [],
1261
+ programs,
1262
+ from = 0,
1263
+ upto = order.won.length,
1264
+ tree,
1265
+ treeMinted = 0n,
1266
+ unminted = [],
1267
+ }) {
896
1268
  const project = order.project;
897
1269
  // A compressed prize is minted into `tree` (any of the project's with room), the next NFT it mints: after
898
1270
  // `treeMinted`, and the compressed prizes before it in this part that were minted. One of an item whose collection
899
1271
  // lacks Bubblegum's plugin (`unminted`: those collections) gives nothing and mints nothing: the next takes its place.
900
1272
  let minted = BigInt(treeMinted);
901
1273
  const skips = new Set(unminted);
902
- return order.won.slice(from, upto).flatMap((won) => {
1274
+ return order.won.slice(from, upto).flatMap(won => {
903
1275
  if (won.Compressed) {
904
1276
  const { item: id, drawn } = won.Compressed;
905
1277
  const address = pda.item(project, id);
906
1278
  const item = items[id];
907
1279
  if (!item) throw new Error(`settling item ${id} needs the item (items), for its collection and its traits`);
908
- const tables = drawn != null ? traitTables(item).map((t) => meta(pda.table(project, t), false, false)) : [];
909
- if (!tree) throw new Error('settling a compressed prize needs the project tree it is minted into (tree, treeMinted)');
1280
+ const tables = drawn != null ? traitTables(item).map(t => meta(pda.table(project, t), false, false)) : [];
1281
+ if (!tree)
1282
+ throw new Error('settling a compressed prize needs the project tree it is minted into (tree, treeMinted)');
910
1283
  const asset = pda.compressedAsset(tree, minted);
911
1284
  if (!skips.has(item.collection)) minted += 1n;
912
1285
  return [
913
- meta(address, false, true), meta(pda.treeConfig(tree), false, true), meta(tree, false, true), meta(item.collection, false, true),
914
- meta(BUBBLEGUM_PROGRAM, false, false), meta(COMPRESSION_PROGRAM, false, false), meta(NOOP_PROGRAM, false, false), meta(MPL_CORE_PROGRAM, false, false), meta(BUBBLEGUM_CORE_SIGNER, false, false),
1286
+ meta(address, false, true),
1287
+ meta(pda.treeConfig(tree), false, true),
1288
+ meta(tree, false, true),
1289
+ meta(item.collection, false, true),
1290
+ meta(BUBBLEGUM_PROGRAM, false, false),
1291
+ meta(COMPRESSION_PROGRAM, false, false),
1292
+ meta(NOOP_PROGRAM, false, false),
1293
+ meta(MPL_CORE_PROGRAM, false, false),
1294
+ meta(BUBBLEGUM_CORE_SIGNER, false, false),
915
1295
  // A fresh one's traits are written in the project's Sheet for the NFT it mints.
916
1296
  ...(drawn != null ? [...tables, meta(pda.sheet(project, asset), false, true)] : []),
917
1297
  ];
@@ -923,13 +1303,23 @@ export function wonAccounts({ order, items = {}, nfts = {}, prizeTo, putBack = [
923
1303
  if (!item) throw new Error(`settling item ${id} needs the item (items), for its kind and collection`);
924
1304
  if (item.kind !== 'Collectible') return [meta(address, false, true)];
925
1305
  // A fresh unit's traits come from the rows its tables drew at the roll.
926
- const tables = drawn != null ? traitTables(item).map((t) => meta(pda.table(project, t), false, false)) : [];
927
- return [meta(address, false, true), meta(item.collection, false, true), meta(pda.itemAsset(address, serial), false, true), meta(MPL_CORE_PROGRAM, false, false), ...tables];
1306
+ const tables = drawn != null ? traitTables(item).map(t => meta(pda.table(project, t), false, false)) : [];
1307
+ return [
1308
+ meta(address, false, true),
1309
+ meta(item.collection, false, true),
1310
+ meta(pda.itemAsset(address, serial), false, true),
1311
+ meta(MPL_CORE_PROGRAM, false, false),
1312
+ ...tables,
1313
+ ];
928
1314
  }
929
1315
  if (won.Tokens) {
930
1316
  const { mint } = won.Tokens;
931
1317
  const program = tokenProgramOf(programs, mint);
932
- return [meta(mint, false, false), meta(vault(project, mint, program), false, true), meta(prizeTo ?? ata(order.player, mint, program), false, true)];
1318
+ return [
1319
+ meta(mint, false, false),
1320
+ meta(vault(project, mint, program), false, true),
1321
+ meta(prizeTo ?? ata(order.player, mint, program), false, true),
1322
+ ];
933
1323
  }
934
1324
  if (won.Nft) {
935
1325
  const { stash, nft: asset } = won.Nft;
@@ -975,21 +1365,24 @@ export function settlePartEnd(order, from = Number(order.settled ?? 0), items =
975
1365
  }
976
1366
 
977
1367
  /** How many draws one open of `box` makes: its slots' counts, or one. */
978
- export const boxDraws = (box) => (box.slots?.length ? box.slots.reduce((n, s) => n + Number(s.count), 0) : 1);
1368
+ export const boxDraws = box => (box.slots?.length ? box.slots.reduce((n, s) => n + Number(s.count), 0) : 1);
979
1369
 
980
1370
  /** How many settle parts an order of `opens` opens of `box` takes at most: each card, at its most calls, and with
981
1371
  * shares one more (the first call splits the payment, alone when no prize fits beside its shares: `upto` 0). */
982
1372
  export function settleParts(box, opens = 1) {
983
1373
  const draws = box.slots?.length ? box.slots.reduce((n, s) => n + Number(s.count), 0) : 1;
984
- const cards = Math.max(1, ...box.entries.map((e) => e.prizes.length)) * draws * Math.max(1, opens);
985
- return Math.max(1, Math.ceil(cards / Math.floor((TRACE_LIMIT - SETTLE_BASE_CALLS) / 7))) + ((box.shares ?? []).length ? 1 : 0);
1374
+ const cards = Math.max(1, ...box.entries.map(e => e.prizes.length)) * draws * Math.max(1, opens);
1375
+ return (
1376
+ Math.max(1, Math.ceil(cards / Math.floor((TRACE_LIMIT - SETTLE_BASE_CALLS) / 7))) +
1377
+ ((box.shares ?? []).length ? 1 : 0)
1378
+ );
986
1379
  }
987
1380
 
988
1381
  /** The heap a settle that moves three or more NFTs asks for (`settleHeapFrame`): all a transaction may have, 256 KiB. */
989
1382
  export const LARGE_SETTLE_HEAP = 256 * 1024;
990
1383
 
991
1384
  /** Whether an order rolled on an NFT: its `subject` names one (null for none). */
992
- const rolledOn = (order) => Boolean(order.subject) && order.subject !== NO_ADDRESS;
1385
+ const rolledOn = order => Boolean(order.subject) && order.subject !== NO_ADDRESS;
993
1386
 
994
1387
  /**
995
1388
  * The heap a settle of `order` asks for with Compute Budget's `RequestHeapFrame` (`requestHeapFrame`), in bytes; 0 for
@@ -1003,7 +1396,7 @@ const rolledOn = (order) => Boolean(order.subject) && order.subject !== NO_ADDRE
1003
1396
  * the settle when `settleHeapFrame(order)` is not 0. A prize step of a lottery gives one place a call and needs none.
1004
1397
  */
1005
1398
  export function settleHeapFrame(order) {
1006
- const moves = (order.won ?? []).filter((w) => w.Nft).length + (rolledOn(order) ? 1 : 0);
1399
+ const moves = (order.won ?? []).filter(w => w.Nft).length + (rolledOn(order) ? 1 : 0);
1007
1400
  return moves >= 3 ? LARGE_SETTLE_HEAP : 0;
1008
1401
  }
1009
1402
 
@@ -1020,7 +1413,7 @@ export function settleComputeUnits(order) {
1020
1413
  // An NFT moved, an NFT rolled on, a compressed mint (Bubblegum's 60,000 to 90,000 units each) or a pack's part of
1021
1414
  // many cards takes all a transaction may have.
1022
1415
  const won = order.won ?? [];
1023
- const moves = won.some((w) => w.Nft || w.Compressed) || won.length > 8 || rolledOn(order);
1416
+ const moves = won.some(w => w.Nft || w.Compressed) || won.length > 8 || rolledOn(order);
1024
1417
  return moves ? MAX_COMPUTE_UNITS : 800_000;
1025
1418
  }
1026
1419
 
@@ -1044,20 +1437,52 @@ export const SETTLE_LAST = 4_294_967_295;
1044
1437
  * (`settled`) up to it, and nothing more. The first call, whichever it is, splits the payment (its shares' accounts
1045
1438
  * ride on it).
1046
1439
  */
1047
- export function settleIx({ signer, orderAddress, order, projectState, items, nfts, prizeTo, putBack, subject, handBack = false, holders, programs, upto = SETTLE_LAST, tree, treeMinted, unminted, extra = [] }) {
1440
+ export function settleIx({
1441
+ signer,
1442
+ orderAddress,
1443
+ order,
1444
+ projectState,
1445
+ items,
1446
+ nfts,
1447
+ prizeTo,
1448
+ putBack,
1449
+ subject,
1450
+ handBack = false,
1451
+ holders,
1452
+ programs,
1453
+ upto = SETTLE_LAST,
1454
+ tree,
1455
+ treeMinted,
1456
+ unminted,
1457
+ extra = [],
1458
+ }) {
1048
1459
  const project = order.project;
1049
1460
  const from = Number(order.settled ?? 0);
1050
1461
  const last = Number(upto) > order.won.length;
1051
1462
  const end = last ? order.won.length : Math.max(from, Number(upto));
1052
1463
  // The payment is split once, by the first call (`drew` is 0 once it is made).
1053
1464
  const splits = Number(order.drew ?? 0) > 0;
1054
- const prizes = wonAccounts({ order, items, nfts, prizeTo, putBack, programs, from, upto: end, tree, treeMinted, unminted });
1465
+ const prizes = wonAccounts({
1466
+ order,
1467
+ items,
1468
+ nfts,
1469
+ prizeTo,
1470
+ putBack,
1471
+ programs,
1472
+ from,
1473
+ upto: end,
1474
+ tree,
1475
+ treeMinted,
1476
+ unminted,
1477
+ });
1055
1478
  const tokenProgram = tokenProgramOf(programs, order.mint);
1056
1479
  // The NFT it rolled on, as the settle moves it: given to a wallet that is an account of the loot program, it goes
1057
1480
  // back to its player (`handsBack`), into the player's token account for a Token Metadata NFT.
1058
1481
  // A part before the last gives only its prizes (and the first, the split): no NFT rolled on (its piece a stand-in).
1059
1482
  const receiver = last ? receiverOf(order) : null;
1060
- const on = last ? orderPiece(project, orderAddress, order, subject, handBack && order.fate?.then?.Give ? order.player : receiver) : standIn(orderAddress);
1483
+ const on = last
1484
+ ? orderPiece(project, orderAddress, order, subject, handBack && order.fate?.then?.Give ? order.player : receiver)
1485
+ : standIn(orderAddress);
1061
1486
  return lootIx(
1062
1487
  'settle',
1063
1488
  { upto: Number(upto), prize_accounts: prizes.length },
@@ -1076,7 +1501,14 @@ export function settleIx({ signer, orderAddress, order, projectState, items, nft
1076
1501
  player_token: BigInt(order.back ?? 0) > 0n && last ? ata(order.player, order.mint, tokenProgram) : null,
1077
1502
  },
1078
1503
  {
1079
- remaining: [...on.proof, ...prizes, ...(splits ? shareAccounts(project, order.shares, order.mint, { holders, programs, treasury: projectState.treasury }) : []), ...tailOf(extra)],
1504
+ remaining: [
1505
+ ...on.proof,
1506
+ ...prizes,
1507
+ ...(splits
1508
+ ? shareAccounts(project, order.shares, order.mint, { holders, programs, treasury: projectState.treasury })
1509
+ : []),
1510
+ ...tailOf(extra),
1511
+ ],
1080
1512
  writable: [...(sharesBurn(order.shares) && splits ? ['mint'] : []), ...on.writable],
1081
1513
  },
1082
1514
  );
@@ -1107,7 +1539,17 @@ function orderPiece(project, orderAddress, order, subject = {}, receiver) {
1107
1539
  const then = order.fate?.then;
1108
1540
  const burned = order.held && then === 'Burn';
1109
1541
  const stays = Boolean(then?.Stash);
1110
- return piece({ project, asset: order.subject, wallet: stays ? order.player : receiver ?? order.player, ...subject, moves: order.held && !stays, locks: false, writes: shifts, burns: burned, reads: shifts });
1542
+ return piece({
1543
+ project,
1544
+ asset: order.subject,
1545
+ wallet: stays ? order.player : (receiver ?? order.player),
1546
+ ...subject,
1547
+ moves: order.held && !stays,
1548
+ locks: false,
1549
+ writes: shifts,
1550
+ burns: burned,
1551
+ reads: shifts,
1552
+ });
1111
1553
  }
1112
1554
 
1113
1555
  /**
@@ -1116,7 +1558,7 @@ function orderPiece(project, orderAddress, order, subject = {}, receiver) {
1116
1558
  * own address, a record, a stash, a fee box), where nothing could move it again. A settle's fate and an NFT lottery
1117
1559
  * ticket's price take it as `handBack`.
1118
1560
  */
1119
- export const handsBack = (account) => account?.owner === LOOT_PROGRAM;
1561
+ export const handsBack = account => account?.owner === LOOT_PROGRAM;
1120
1562
 
1121
1563
  /** Where an NFT that is burned but can't be (any kind but Core) is sent: an address nobody holds the key to. */
1122
1564
  export const INCINERATOR = '1nc1nerator11111111111111111111111111111111';
@@ -1132,9 +1574,10 @@ export function refundIx({ signer, orderAddress, order, subject, programs, extra
1132
1574
  const project = order.project;
1133
1575
  const tokenProgram = tokenProgramOf(programs, order.mint);
1134
1576
  // A held NFT goes back to its player.
1135
- const on = order.held && rolledOn(order)
1136
- ? piece({ project, asset: order.subject, wallet: order.player, ...subject, ...ONLY_MOVES })
1137
- : standIn(orderAddress);
1577
+ const on =
1578
+ order.held && rolledOn(order)
1579
+ ? piece({ project, asset: order.subject, wallet: order.player, ...subject, ...ONLY_MOVES })
1580
+ : standIn(orderAddress);
1138
1581
  return lootIx(
1139
1582
  'refund',
1140
1583
  {},
@@ -1170,10 +1613,20 @@ export function refundIx({ signer, orderAddress, order, subject, programs, extra
1170
1613
  */
1171
1614
  export function askRollIx({ payer, orderAddress, order, box, settler = NOBODY_KEY, poolMints, items = {}, programs }) {
1172
1615
  poolMints = boxPoolMints(box, poolMints);
1173
- const opens = prizeMints(box.entries, poolMints).map(({ mint }) => createAssociatedTokenAccountIdempotent(payer, order.project, mint, tokenProgramOf(programs, mint)));
1174
- const ask = lootIx('ask_roll', { settler }, { payer, order: orderAddress, loot_box: order.loot_box, project: order.project }, {
1175
- remaining: [...entryAccounts(order.project, box.entries, items), ...prizeAccounts(order.project, box.entries, poolMints, programs)],
1176
- });
1616
+ const opens = prizeMints(box.entries, poolMints).map(({ mint }) =>
1617
+ createAssociatedTokenAccountIdempotent(payer, order.project, mint, tokenProgramOf(programs, mint)),
1618
+ );
1619
+ const ask = lootIx(
1620
+ 'ask_roll',
1621
+ { settler },
1622
+ { payer, order: orderAddress, loot_box: order.loot_box, project: order.project },
1623
+ {
1624
+ remaining: [
1625
+ ...entryAccounts(order.project, box.entries, items),
1626
+ ...prizeAccounts(order.project, box.entries, poolMints, programs),
1627
+ ],
1628
+ },
1629
+ );
1177
1630
  return [...opens, ask];
1178
1631
  }
1179
1632
 
@@ -1186,14 +1639,41 @@ export function askRollIx({ payer, orderAddress, order, box, settler = NOBODY_KE
1186
1639
  * wallet), a collectible's holder or 'Burn', `amount` { Fixed: [n] } or { Bps: [bps] } of a pool. The authority opens the
1187
1640
  * account it pays into when it is not there; a collectible nobody holds gets nothing.
1188
1641
  */
1189
- export function moveTokensIx({ authority, project, mint, from, to, amount, reference: ref, holders, programs, treasury, farm = null, extra = [] }) {
1642
+ export function moveTokensIx({
1643
+ authority,
1644
+ project,
1645
+ mint,
1646
+ from,
1647
+ to,
1648
+ amount,
1649
+ reference: ref,
1650
+ holders,
1651
+ programs,
1652
+ treasury,
1653
+ farm = null,
1654
+ extra = [],
1655
+ }) {
1190
1656
  const writes = Boolean(from.Mint) || to === 'Burn';
1191
1657
  return lootIx(
1192
1658
  'move_tokens',
1193
1659
  { from, to, amount, reference: reference(ref) },
1194
1660
  // What a farm holds free comes out of its rewards vault, passed as the pool, with the farm.
1195
- { authority, project, mint, pool: from.Pool ? pda.pool(project, from.Pool[0]) : from.Farm ? pda.rewards(pda.farm(project, from.Farm[0])) : null, farm: from.Farm ? pda.farm(project, from.Farm[0]) : null, farm_ends: from.Farm && farm?.keeps_ends ? pda.ends(pda.farm(project, from.Farm[0])) : null },
1196
- { remaining: [...destinationAccounts(project, to, mint, { holders, programs, treasury }), ...tailOf(extra)], writable: writes ? ['mint'] : [] },
1661
+ {
1662
+ authority,
1663
+ project,
1664
+ mint,
1665
+ pool: from.Pool
1666
+ ? pda.pool(project, from.Pool[0])
1667
+ : from.Farm
1668
+ ? pda.rewards(pda.farm(project, from.Farm[0]))
1669
+ : null,
1670
+ farm: from.Farm ? pda.farm(project, from.Farm[0]) : null,
1671
+ farm_ends: from.Farm && farm?.keeps_ends ? pda.ends(pda.farm(project, from.Farm[0])) : null,
1672
+ },
1673
+ {
1674
+ remaining: [...destinationAccounts(project, to, mint, { holders, programs, treasury }), ...tailOf(extra)],
1675
+ writable: writes ? ['mint'] : [],
1676
+ },
1197
1677
  );
1198
1678
  }
1199
1679
 
@@ -1207,9 +1687,21 @@ export function moveTokensIx({ authority, project, mint, from, to, amount, refer
1207
1687
  * what it writes goes in the NFT's Sheet, or on the NFT itself when its Attributes plugin names the project. A `Make`
1208
1688
  * goes to `makeIx`.
1209
1689
  */
1210
- export function collectibleIx({ authority, project, collection, asset, change, owner = null, hasAuthority = true, reference: ref, ...make }) {
1211
- if (change.Make) return makeIx({ authority, project, ...change.Make, ...make, owners: make.owners ?? [owner], reference: ref });
1212
- const sheet = change.Set && !hasAuthority ? pda.sheet(project, asset) : change.Profile ? pda.profile(project, asset) : null;
1690
+ export function collectibleIx({
1691
+ authority,
1692
+ project,
1693
+ collection,
1694
+ asset,
1695
+ change,
1696
+ owner = null,
1697
+ hasAuthority = true,
1698
+ reference: ref,
1699
+ ...make
1700
+ }) {
1701
+ if (change.Make)
1702
+ return makeIx({ authority, project, ...change.Make, ...make, owners: make.owners ?? [owner], reference: ref });
1703
+ const sheet =
1704
+ change.Set && !hasAuthority ? pda.sheet(project, asset) : change.Profile ? pda.profile(project, asset) : null;
1213
1705
  const burn = change === 'Burn' || Boolean(change.Burn);
1214
1706
  return lootIx(
1215
1707
  'collectible',
@@ -1227,10 +1719,16 @@ export function collectibleIx({ authority, project, collection, asset, change, o
1227
1719
  * refuses the whole call (`PrizeNotCovered`).
1228
1720
  */
1229
1721
  export function makeIx({ authority, project, item, itemState, owners, attributes = [], tree = null, reference: ref }) {
1230
- if (!itemState) throw new Error(`a make of item ${item} needs the item (itemState: its decoded Item), for its collection and next serial`);
1231
- if (!owners?.length || owners.some((o) => !o)) throw new Error('A make names who gets each one (`owners`).');
1722
+ if (!itemState)
1723
+ throw new Error(
1724
+ `a make of item ${item} needs the item (itemState: its decoded Item), for its collection and next serial`,
1725
+ );
1726
+ if (!owners?.length || owners.some(o => !o)) throw new Error('A make names who gets each one (`owners`).');
1232
1727
  const prize = tree ? { Compressed: { item, tree } } : { Item: [item] };
1233
- const remaining = owners.flatMap((owner, made) => [meta(owner, false, false), ...prizeMetas({ project, prize, winner: owner, items: { [item]: itemState }, made })]);
1728
+ const remaining = owners.flatMap((owner, made) => [
1729
+ meta(owner, false, false),
1730
+ ...prizeMetas({ project, prize, winner: owner, items: { [item]: itemState }, made }),
1731
+ ]);
1234
1732
  return lootIx(
1235
1733
  'collectible',
1236
1734
  { change: { Make: { item, tree, attributes } }, reference: reference(ref) },
@@ -1240,7 +1738,8 @@ export function makeIx({ authority, project, item, itemState, owners, attributes
1240
1738
  }
1241
1739
 
1242
1740
  /** The collectibles a `makeIx` of `count` mints (not compressed): at the item's next serials. */
1243
- export const madeAssets = (project, item, itemState, count = 1) => Array.from({ length: count }, (_, k) => pda.itemAsset(pda.item(project, item), Number(itemState.issued) + 1 + k));
1741
+ export const madeAssets = (project, item, itemState, count = 1) =>
1742
+ Array.from({ length: count }, (_, k) => pda.itemAsset(pda.item(project, item), Number(itemState.issued) + 1 + k));
1244
1743
 
1245
1744
  // ---------------------------------------------------------------- decide
1246
1745
 
@@ -1253,23 +1752,56 @@ export const madeAssets = (project, item, itemState, count = 1) => Array.from({
1253
1752
  * (`piece`): `asset` and `collection` for a Core asset, with `tokenMetadata` for a Token Metadata NFT, or `compressed`
1254
1753
  * (after a `proveLeafIx`).
1255
1754
  */
1256
- export function releaseIx({ signer, project, collection, asset, tokenMetadata: tm, compressed, hold, address = null, to, treasury, reference: ref, more = [] }) {
1755
+ export function releaseIx({
1756
+ signer,
1757
+ project,
1758
+ collection,
1759
+ asset,
1760
+ tokenMetadata: tm,
1761
+ compressed,
1762
+ hold,
1763
+ address = null,
1764
+ to,
1765
+ treasury,
1766
+ reference: ref,
1767
+ more = [],
1768
+ }) {
1257
1769
  // A hold whose player is the project's own address is owed to the treasury, as the project names it now (`treasury`,
1258
1770
  // the decoded Project's).
1259
- if (!to && hold.player === project && !treasury) throw new Error('This hold is owed to the treasury: pass treasury (the project’s, as it is now).');
1771
+ if (!to && hold.player === project && !treasury)
1772
+ throw new Error('This hold is owed to the treasury: pass treasury (the project’s, as it is now).');
1260
1773
  const receiver = to ?? (hold.player === project ? treasury : hold.player);
1261
1774
  // Never to the project's own address, nor any account of the program, where nothing could move it again (ToTheProject).
1262
1775
  if (receiver === project) throw new Error('A held NFT can’t go to the project’s own address: name a wallet.');
1263
1776
  // Several holds of one player's with one end, payer and rent wallet (`more`: each { asset, collection, tokenMetadata,
1264
1777
  // compressed }) go to one `to` together, in ascending order of their holds' addresses.
1265
- const [first, ...rest] = ascending([{ asset, collection, tokenMetadata: tm, compressed, address }, ...more].map((n) => {
1266
- const p = piece({ project, asset: n.asset, wallet: receiver, collection: n.collection, tokenMetadata: n.tokenMetadata, compressed: n.compressed, ...ONLY_MOVES });
1267
- return { p, hold: n.address ?? pda.hold(p.accounts.asset, hold.free_at) };
1268
- }), (x) => x.hold);
1269
- const p = withPrograms(first.p, rest.map((x) => x.p));
1270
- const groups = rest.flatMap((x) => [meta(x.hold, false, true), ...anotherMetas(x.p)]);
1778
+ const [first, ...rest] = ascending(
1779
+ [{ asset, collection, tokenMetadata: tm, compressed, address }, ...more].map(n => {
1780
+ const p = piece({
1781
+ project,
1782
+ asset: n.asset,
1783
+ wallet: receiver,
1784
+ collection: n.collection,
1785
+ tokenMetadata: n.tokenMetadata,
1786
+ compressed: n.compressed,
1787
+ ...ONLY_MOVES,
1788
+ });
1789
+ return { p, hold: n.address ?? pda.hold(p.accounts.asset, hold.free_at) };
1790
+ }),
1791
+ x => x.hold,
1792
+ );
1793
+ const p = withPrograms(
1794
+ first.p,
1795
+ rest.map(x => x.p),
1796
+ );
1797
+ const groups = rest.flatMap(x => [meta(x.hold, false, true), ...anotherMetas(x.p)]);
1271
1798
  // The hold's rent goes back to whoever made it (`payer`), a Token Metadata NFT's emptied account's to whoever paid it.
1272
- return lootIx('release', { reference: reference(ref) }, { signer, project, ...p.accounts, hold: first.hold, payer: hold.payer, rent_to: hold.rent_to, to: receiver }, { remaining: [...p.proof, ...groups], writable: p.writable });
1799
+ return lootIx(
1800
+ 'release',
1801
+ { reference: reference(ref) },
1802
+ { signer, project, ...p.accounts, hold: first.hold, payer: hold.payer, rent_to: hold.rent_to, to: receiver },
1803
+ { remaining: [...p.proof, ...groups], writable: p.writable },
1804
+ );
1273
1805
  }
1274
1806
 
1275
1807
  /** The mint of a contest's split prize (its `prize`, tokens): minted, or a pool's (`poolMint`). */
@@ -1288,17 +1820,39 @@ export const openContestIx = ({ authority, project, projectState, id, mint, args
1288
1820
  stored(args.shares);
1289
1821
  const contest = pda.contest(project, id);
1290
1822
  const split = Boolean(args.prize_split);
1291
- return lootIx('open_contest', { id, args: { deciders: [], min_pot: 0, stake: 0, prize: null, nft: null, prize_split: false, cap: null, ...args, gate: args.gate ?? null } }, {
1292
- authority, project, mint, contest,
1293
- prize_mint: split ? splitMint(args.prize, prizeMint) : null, prize_vault: split ? pda.prizeVault(contest) : null,
1294
- entries: pda.entries(contest, BigInt(projectState.incarnations) + 1n),
1295
- // A split prize from a pool names the pool at its index (`WrongPool` without it; a prize mint not the pool's is
1296
- // `WrongMint`).
1297
- prize_pool: split && args.prize?.Tokens?.from?.Pool ? pda.pool(project, args.prize.Tokens.from.Pool[0]) : null,
1298
- }, {
1299
- // A prize that is a roll: its box.
1300
- remaining: args.prize?.Roll ? boxesAccounts(project, [args.prize.Roll[0]]) : [],
1301
- });
1823
+ return lootIx(
1824
+ 'open_contest',
1825
+ {
1826
+ id,
1827
+ args: {
1828
+ deciders: [],
1829
+ min_pot: 0,
1830
+ stake: 0,
1831
+ prize: null,
1832
+ nft: null,
1833
+ prize_split: false,
1834
+ cap: null,
1835
+ ...args,
1836
+ gate: args.gate ?? null,
1837
+ },
1838
+ },
1839
+ {
1840
+ authority,
1841
+ project,
1842
+ mint,
1843
+ contest,
1844
+ prize_mint: split ? splitMint(args.prize, prizeMint) : null,
1845
+ prize_vault: split ? pda.prizeVault(contest) : null,
1846
+ entries: pda.entries(contest, BigInt(projectState.incarnations) + 1n),
1847
+ // A split prize from a pool names the pool at its index (`WrongPool` without it; a prize mint not the pool's is
1848
+ // `WrongMint`).
1849
+ prize_pool: split && args.prize?.Tokens?.from?.Pool ? pda.pool(project, args.prize.Tokens.from.Pool[0]) : null,
1850
+ },
1851
+ {
1852
+ // A prize that is a roll: its box.
1853
+ remaining: args.prize?.Roll ? boxesAccounts(project, [args.prize.Roll[0]]) : [],
1854
+ },
1855
+ );
1302
1856
  };
1303
1857
 
1304
1858
  /** A contest's list of entries, from its decoded Contest. */
@@ -1317,7 +1871,19 @@ export const VERDICT_DOMAIN = 'loot-verdict-v4';
1317
1871
  export function verdictBytes({ program = LOOT_PROGRAM, contest, incarnation, side, expires, recipient = null }) {
1318
1872
  if (incarnation == null) throw new Error('A verdict signs the contest’s incarnation (the decoded Contest’s).');
1319
1873
  const named = recipient == null ? new Uint8Array(0) : addressBytes(recipient);
1320
- return concat(utf8(VERDICT_DOMAIN), sha256(concat(addressBytes(program), addressBytes(contest), uintLE(incarnation, 8), uintLE(side ?? 0xffff, 2), intLE(expires, 8), named)));
1874
+ return concat(
1875
+ utf8(VERDICT_DOMAIN),
1876
+ sha256(
1877
+ concat(
1878
+ addressBytes(program),
1879
+ addressBytes(contest),
1880
+ uintLE(incarnation, 8),
1881
+ uintLE(side ?? 0xffff, 2),
1882
+ intLE(expires, 8),
1883
+ named,
1884
+ ),
1885
+ ),
1886
+ );
1321
1887
  }
1322
1888
 
1323
1889
  /**
@@ -1330,36 +1896,74 @@ export function verdictBytes({ program = LOOT_PROGRAM, contest, incarnation, sid
1330
1896
  * contest with a chosen share is decided naming its `recipient`, a wallet (a bounty's worker); a decider's verdict
1331
1897
  * signs it too (`verdictBytes`).
1332
1898
  */
1333
- export function decideIx({ signer, project, projectState, contestAddress, contest, side, recipient = null, verdict, reference: ref, holders, prizeMint, programs, extra = [] }) {
1899
+ export function decideIx({
1900
+ signer,
1901
+ project,
1902
+ projectState,
1903
+ contestAddress,
1904
+ contest,
1905
+ side,
1906
+ recipient = null,
1907
+ verdict,
1908
+ reference: ref,
1909
+ holders,
1910
+ prizeMint,
1911
+ programs,
1912
+ extra = [],
1913
+ }) {
1334
1914
  const address = contestAddress ?? pda.contest(project, contest.id);
1335
1915
  const named = recipient == null ? contest.shares : chosenShares(contest.shares, recipient);
1336
- const shares = side == null ? [] : shareAccounts(project, named, contest.mint, { holders, programs, treasury: projectState?.treasury });
1916
+ const shares =
1917
+ side == null
1918
+ ? []
1919
+ : shareAccounts(project, named, contest.mint, { holders, programs, treasury: projectState?.treasury });
1337
1920
  // A split prize is set aside when a side is named (minted, or from its pool). A decision never closes the contest:
1338
1921
  // `claim` does, entered or not.
1339
1922
  const split = Boolean(contest.prize_split);
1340
1923
  const from = contest.prize?.Tokens?.from;
1341
1924
  return lootIx(
1342
1925
  'decide',
1343
- { side: side ?? null, expires: toBigInt(verdict?.expires ?? 0), reference: reference(ref), incarnation: contest.incarnation, recipient },
1344
1926
  {
1345
- signer, project, contest: address, mint: contest.mint, instructions: verdict ? SYSVAR_INSTRUCTIONS : null,
1927
+ side: side ?? null,
1928
+ expires: toBigInt(verdict?.expires ?? 0),
1929
+ reference: reference(ref),
1930
+ incarnation: contest.incarnation,
1931
+ recipient,
1932
+ },
1933
+ {
1934
+ signer,
1935
+ project,
1936
+ contest: address,
1937
+ mint: contest.mint,
1938
+ instructions: verdict ? SYSVAR_INSTRUCTIONS : null,
1346
1939
  prize_mint: split ? splitMint(contest.prize, prizeMint) : null,
1347
1940
  // Its pool: the prize set aside from it when a side is named.
1348
1941
  prize_pool: split && side != null && from?.Pool ? pda.pool(project, from.Pool[0]) : null,
1349
1942
  prize_vault: split ? pda.prizeVault(address) : null,
1350
1943
  },
1351
1944
  // The mint is written by a burn share; the prize's mint when it is minted.
1352
- { remaining: [...shares, ...tailOf(extra)], writable: [...(side != null && sharesBurn(contest.shares) ? ['mint'] : []), ...(split && side != null && from?.Mint ? ['prize_mint'] : [])] },
1945
+ {
1946
+ remaining: [...shares, ...tailOf(extra)],
1947
+ writable: [
1948
+ ...(side != null && sharesBurn(contest.shares) ? ['mint'] : []),
1949
+ ...(split && side != null && from?.Mint ? ['prize_mint'] : []),
1950
+ ],
1951
+ },
1353
1952
  );
1354
1953
  }
1355
1954
 
1356
1955
  /** The authority asks the oracle for a random number, for a rule of the project's own. */
1357
- export const rollIx = ({ authority, project, reference: ref }) => lootIx('roll', { reference: reference(ref) }, { authority, project });
1956
+ export const rollIx = ({ authority, project, reference: ref }) =>
1957
+ lootIx('roll', { reference: reference(ref) }, { authority, project });
1358
1958
 
1359
1959
  /** The oracle's answer to a `roll` (for a local simulation). */
1360
1960
  export const callbackRandomIx = ({ project, reference: ref, randomness }) => ({
1361
1961
  programId: LOOT_PROGRAM,
1362
- keys: [meta(pda.oracleIdentity(), true, false), meta(project, false, false), meta(hashToAddress(reference(ref)), false, false)],
1962
+ keys: [
1963
+ meta(pda.oracleIdentity(), true, false),
1964
+ meta(project, false, false),
1965
+ meta(hashToAddress(reference(ref)), false, false),
1966
+ ],
1363
1967
  data: encodeInstruction(IDL, 'callback_random', { randomness: Uint8Array.from(randomness) }),
1364
1968
  });
1365
1969
 
@@ -1371,7 +1975,18 @@ export const callbackRandomIx = ({ project, reference: ref, randomness }) => ({
1371
1975
  * lapse; only a sealed schedule's fixed amount pays every run due, up to `upto` runs made (`scheduleCatchesUp`).
1372
1976
  * `poolMint`: the mint of the pool it moves from (when it moves from one). `extra`: a hook's or a memo's accounts.
1373
1977
  */
1374
- export function runScheduleIx({ payer, project, id, schedule, poolMint, holders, programs, treasury, upto = 0, extra = [] }) {
1978
+ export function runScheduleIx({
1979
+ payer,
1980
+ project,
1981
+ id,
1982
+ schedule,
1983
+ poolMint,
1984
+ holders,
1985
+ programs,
1986
+ treasury,
1987
+ upto = 0,
1988
+ extra = [],
1989
+ }) {
1375
1990
  const { from, to } = schedule.action;
1376
1991
  const address = pda.schedule(project, id);
1377
1992
  // A sealed schedule pays from its own vault (`pda.vest`), passed where a pool would be.
@@ -1383,11 +1998,13 @@ export function runScheduleIx({ payer, project, id, schedule, poolMint, holders,
1383
1998
  'run_schedule',
1384
1999
  { upto },
1385
2000
  { payer, project, schedule: address, pool, mint },
1386
- { remaining: [...destinationAccounts(project, to, mint, { holders, programs, treasury }), ...tailOf(extra)], writable: writes ? ['mint'] : [] },
2001
+ {
2002
+ remaining: [...destinationAccounts(project, to, mint, { holders, programs, treasury }), ...tailOf(extra)],
2003
+ writable: writes ? ['mint'] : [],
2004
+ },
1387
2005
  );
1388
2006
  }
1389
2007
 
1390
-
1391
2008
  // A lottery's rounds (`accounts.js`: `decodeRound`, `partsOwed`, `commonOwed`): each round is a record of its own, opened
1392
2009
  // by its first purchase. Its ask (`draw_lottery`) fixes its money and moves the lottery on; after the answer, `pull`
1393
2010
  // pays one part at a time, in any order, and the pull that leaves nothing owed closes it. Every builder takes the
@@ -1406,7 +2023,25 @@ export const currentRound = (lotteryAddress, lottery) => pda.round(lotteryAddres
1406
2023
  * `treasury` wallet. A lottery whose ticket costs an NFT sells one at a time, for `nft` ({ asset, collection,
1407
2024
  * tokenMetadata, compressed }, as `piece` takes it).
1408
2025
  */
1409
- export function buyTicketsIx({ player, project, id, lottery, round = null, mint, count, shown, listProof, chosenRecipient = null, chosen = null, nft, holders = {}, treasury, playerToken, programs, extra = [] }) {
2026
+ export function buyTicketsIx({
2027
+ player,
2028
+ project,
2029
+ id,
2030
+ lottery,
2031
+ round = null,
2032
+ mint,
2033
+ count,
2034
+ shown,
2035
+ listProof,
2036
+ chosenRecipient = null,
2037
+ chosen = null,
2038
+ nft,
2039
+ holders = {},
2040
+ treasury,
2041
+ playerToken,
2042
+ programs,
2043
+ extra = [],
2044
+ }) {
1410
2045
  const address = pda.lottery(project, id);
1411
2046
  const key = currentRound(address, lottery);
1412
2047
  // The terms the round copied, or for its first purchase the lottery's.
@@ -1420,13 +2055,25 @@ export function buyTicketsIx({ player, project, id, lottery, round = null, mint,
1420
2055
  'buy_tickets',
1421
2056
  { count, chosen_recipient: chosenRecipient, revision: round ? round.revision : lottery.revision },
1422
2057
  {
1423
- player, project, lottery: address, round: key,
2058
+ player,
2059
+ project,
2060
+ lottery: address,
2061
+ round: key,
1424
2062
  player_token: playerToken ?? ata(player, mint, tokenProgramOf(programs, mint)),
1425
- payment_vault: pda.roundTokens(key, mint), mint, chosen,
2063
+ payment_vault: pda.roundTokens(key, mint),
2064
+ mint,
2065
+ chosen,
1426
2066
  counts: Number(lottery.per_wallet) ? pda.counts(project, player) : null,
1427
2067
  associated_token_program: choices.length ? ASSOCIATED_TOKEN_PROGRAM : null,
1428
2068
  },
1429
- { remaining: [...gateAccounts(project, { gate: lottery.gate, wallet: player, shown, listProof }), ...recipients, ...(held ? [...pieceMetas(held), ...held.proof] : []), ...tailOf(extra)] },
2069
+ {
2070
+ remaining: [
2071
+ ...gateAccounts(project, { gate: lottery.gate, wallet: player, shown, listProof }),
2072
+ ...recipients,
2073
+ ...(held ? [...pieceMetas(held), ...held.proof] : []),
2074
+ ...tailOf(extra),
2075
+ ],
2076
+ },
1430
2077
  );
1431
2078
  }
1432
2079
 
@@ -1435,12 +2082,26 @@ export function buyTicketsIx({ player, project, id, lottery, round = null, mint,
1435
2082
  * round asked for (null when nobody bought into the current one: it moves on, or a lottery that draws once ends, with
1436
2083
  * no ask), and `roundAddress` its address when it is not the current round's (an older round still waiting for its
1437
2084
  * answer: asked again after `STALE_SECONDS`, or after the timeout cancelled, with no lottery account, so `lottery` may be
1438
- * null then; without `round`, the current round's, with the lottery's terms). A round's first ask fixes its money: it takes
1439
- * the lottery's pot, its mint and the round's account of it, then each run of token prizes in place order, its mint and
1440
- * for a pool, the pool and the round's account of the mint (`poolMints`: a pool's index to its mint). With `skipHooked`
1441
- * (the project's authority only) a hooked pool or pot gives the round nothing. `extra`: hook accounts.
2085
+ * null then; without `round`, the current round's, with the lottery's terms). A round's first ask fixes its money. Paid
2086
+ * from a pot, it takes the pot, its mint and the round's account of it, then, when the lottery names a round that may
2087
+ * still owe a pot (`lottery.owing`), that round, its account of the mint and its pot (`owing`: that round's decoded
2088
+ * record, whose terms name its pot; without it, or when the record is gone, the pot this round pays from). A round paid
2089
+ * from its own money takes none of these. Then each run of token prizes in place order, its mint and for a pool, the
2090
+ * pool and the round's account of the mint (`poolMints`: a pool's index to its mint). With `skipHooked` (the project's
2091
+ * authority only) a hooked pool or pot gives the round nothing. `extra`: hook accounts.
1442
2092
  */
1443
- export function drawLotteryIx({ payer, project, id, lottery, round = null, roundAddress = null, skipHooked = false, poolMints = {}, extra = [] }) {
2093
+ export function drawLotteryIx({
2094
+ payer,
2095
+ project,
2096
+ id,
2097
+ lottery,
2098
+ round = null,
2099
+ roundAddress = null,
2100
+ owing = null,
2101
+ skipHooked = false,
2102
+ poolMints = {},
2103
+ extra = [],
2104
+ }) {
1444
2105
  const address = id === undefined || id === null ? null : pda.lottery(project, id);
1445
2106
  const key = roundAddress ?? currentRound(address, lottery);
1446
2107
  const rest = [];
@@ -1449,20 +2110,49 @@ export function drawLotteryIx({ payer, project, id, lottery, round = null, round
1449
2110
  const current = !round || round.status === 'Open';
1450
2111
  if (current) {
1451
2112
  const { terms } = round ?? lottery;
1452
- rest.push(meta(pda.pot(address), false, true), meta(terms.mint, false, false), meta(pda.roundTokens(key, terms.mint), false, true));
2113
+ const potId = potIdOf(terms);
2114
+ if (potId != null) {
2115
+ rest.push(
2116
+ meta(pda.lotteryPot(address, potId), false, true),
2117
+ meta(terms.mint, false, false),
2118
+ meta(pda.roundTokens(key, terms.mint), false, true),
2119
+ );
2120
+ // What the round before left its pot moves in first: that round, its account of the mint, and its pot.
2121
+ const earlier = lottery?.owing;
2122
+ if (earlier && earlier !== NO_ADDRESS) {
2123
+ const earlierPot = potIdOf(owing?.terms) ?? potId;
2124
+ rest.push(
2125
+ meta(earlier, false, true),
2126
+ meta(pda.roundTokens(earlier, terms.mint), false, true),
2127
+ meta(pda.lotteryPot(address, earlierPot), false, true),
2128
+ );
2129
+ }
2130
+ }
1453
2131
  for (const place of terms.places) {
1454
2132
  const from = place.prize?.Tokens?.from;
1455
2133
  if (from?.Mint) rest.push(meta(from.Mint[0], false, false));
1456
2134
  else if (from?.Pool) {
1457
2135
  const mint = poolMints[from.Pool[0]];
1458
- if (!mint) throw new Error(`The ask of a round whose place gives tokens from pool ${from.Pool[0]} needs that pool's mint (poolMints).`);
1459
- rest.push(meta(mint, false, false), meta(pda.pool(project, from.Pool[0]), false, true), meta(pda.roundTokens(key, mint), false, true));
2136
+ if (!mint)
2137
+ throw new Error(
2138
+ `The ask of a round whose place gives tokens from pool ${from.Pool[0]} needs that pool's mint (poolMints).`,
2139
+ );
2140
+ rest.push(
2141
+ meta(mint, false, false),
2142
+ meta(pda.pool(project, from.Pool[0]), false, true),
2143
+ meta(pda.roundTokens(key, mint), false, true),
2144
+ );
1460
2145
  }
1461
2146
  }
1462
2147
  }
1463
2148
  // The lottery goes with its current round only: a round whose draw waits is asked again or cancelled without it, so a
1464
2149
  // lottery closed since never strands it.
1465
- return lootIx('draw_lottery', { skip_hooked: skipHooked }, { payer, project, lottery: current ? address : null, round: key }, { remaining: [...rest, ...tailOf(extra)] });
2150
+ return lootIx(
2151
+ 'draw_lottery',
2152
+ { skip_hooked: skipHooked },
2153
+ { payer, project, lottery: current ? address : null, round: key },
2154
+ { remaining: [...rest, ...tailOf(extra)] },
2155
+ );
1466
2156
  }
1467
2157
 
1468
2158
  /** The oracle's answer to a round's draw (for a local simulation): the round's record is its only account. */
@@ -1475,10 +2165,17 @@ export const callbackLotteryIx = ({ round, randomness }) => ({
1475
2165
  /** The accounts a round's close takes after a pull's own: each of its token accounts, its mint and who opened it (the
1476
2166
  * lottery, which gets its spare back, or a buyer who fronted it; `owner` for one the lottery opened, once it is closed). */
1477
2167
  export const roundCloseMetas = (key, r, owner = null) =>
1478
- r.accounts.filter((o) => o.mint !== NO_ADDRESS).flatMap((o) => [meta(pda.roundTokens(key, o.mint), false, true), meta(o.mint, false, true), meta(owner && o.by === r.lottery ? owner : o.by, false, true)]);
2168
+ r.accounts
2169
+ .filter(o => o.mint !== NO_ADDRESS)
2170
+ .flatMap(o => [
2171
+ meta(pda.roundTokens(key, o.mint), false, true),
2172
+ meta(o.mint, false, true),
2173
+ meta(owner && o.by === r.lottery ? owner : o.by, false, true),
2174
+ ]);
1479
2175
 
1480
2176
  /** The NFT a raffle's place draws from the lottery's own stash (`stash` decoded, as it is now) with the round's answer. */
1481
- export const raffledNft = (stash, answer, place) => stash?.nfts[weighted(stream(answer, [TAG.LOTTERY, place]), stash.weights)] ?? null;
2177
+ export const raffledNft = (stash, answer, place) =>
2178
+ stash?.nfts[weighted(stream(answer, [TAG.LOTTERY, place]), stash.weights)] ?? null;
1482
2179
 
1483
2180
  /**
1484
2181
  * The NFT of the lottery's own stash each place of a drawn round that holds or draws one gets, in place order, as the
@@ -1489,7 +2186,7 @@ export const raffledNft = (stash, answer, place) => stash?.nfts[weighted(stream(
1489
2186
  export function stashNfts(r, stash) {
1490
2187
  const nfts = r.nfts.map((n, k) => (k < Number(r.assigned) ? (n === NO_ADDRESS ? null : n) : undefined));
1491
2188
  const rows = { nfts: [...(stash?.nfts ?? [])], weights: [...(stash?.weights ?? [])] };
1492
- const take = (row) => {
2189
+ const take = row => {
1493
2190
  if (row == null || row < 0) return null;
1494
2191
  const nft = rows.nfts[row];
1495
2192
  const last = rows.nfts.length - 1;
@@ -1505,13 +2202,15 @@ export function stashNfts(r, stash) {
1505
2202
  if (ownStash(p)) {
1506
2203
  for (let place = first; place < first + times && place < w; place++, k++) {
1507
2204
  if (nfts[k] !== undefined) continue;
1508
- nfts[k] = p.prize?.Held ? take(rows.nfts.indexOf(p.prize.Held[0])) : take(weighted(stream(r.answer, [TAG.LOTTERY, place]), rows.weights));
2205
+ nfts[k] = p.prize?.Held
2206
+ ? take(rows.nfts.indexOf(p.prize.Held[0]))
2207
+ : take(weighted(stream(r.answer, [TAG.LOTTERY, place]), rows.weights));
1509
2208
  }
1510
2209
  k += Math.max(0, first + times - Math.max(first, w));
1511
2210
  }
1512
2211
  first += times;
1513
2212
  }
1514
- return nfts.map((n) => n ?? null);
2213
+ return nfts.map(n => n ?? null);
1515
2214
  }
1516
2215
 
1517
2216
  /** The program's rule for where a part nobody can take goes: the treasury's wallet, then its account of the mint. */
@@ -1523,13 +2222,14 @@ const treasuryAccounts = (treasury, mint, programs) => walletAccounts(treasury,
1523
2222
  * 'Own' entry `entry`'s own part: a refund, a no-loss ticket's price, or its NFT ticket back or where its
1524
2223
  * price sends it (`ticket` its kind as `piece` takes it; `handBack` when the wallet its price gives
1525
2224
  * it to is an account of the loot program; `hold`: made a hold for whoever it is owed to instead);
1526
- * { Pot: [run] } the entry's places in the run, their part of the pot, paid into the buyer's account;
2225
+ * { Pot: [run] } the entry's places in the run, their part of what the round pays from, paid into the buyer's account;
1527
2226
  * { Prize: [run] } their prize: tokens into the buyer's account, or each place's prize (`prize`: { stash, the lottery's
1528
2227
  * own stash decoded, for NFTs not taken out yet (`stashNfts`); stashes, a project stash's id to its
1529
2228
  * decoded stash; nfts, an NFT's kind by its asset; hold, NFTs made holds; most, the most places a
1530
2229
  * pull carries; and what `prizeMetas` takes: collection, poolMints, items, projectMint }), a roll's
1531
2230
  * order at `prizeRollNonce(nonce + place)` (ask it in the same transaction: `pullRolls`);
1532
- * { Common: [i] } a part owed in common: share `i`, the pot's (`i` = the shares' count), a pool's prize back after a
2231
+ * { Common: [i] } a part owed in common: share `i`, what the places leave (`i` = the shares' count: to the pot, or
2232
+ * to `rest` for a round paid from its own money; never owed when that burns), a pool's prize back after a
1533
2233
  * cancel (past it), paid where it goes (`holders`, `programs`); `toTreasury` when that can't take it
1534
2234
  * (a pool never opened, the pot closed since or made again in another mint, an account gone: a
1535
2235
  * holder nobody is is found here).
@@ -1537,7 +2237,25 @@ const treasuryAccounts = (treasury, mint, programs) => walletAccounts(treasury,
1537
2237
  * lottery is gone, so rent it lent goes to the owner). A round that owes nothing and waits only for its close is closed
1538
2238
  * by any pull.
1539
2239
  */
1540
- export function pullIx({ payer, project, roundAddress: key, round: r, entry = 0, part = 'Own', projectState, ticket = {}, handBack = false, hold = false, nonce = 0n, prize = {}, holders = {}, toTreasury = false, lotteryClosed = false, programs, extra = [] }) {
2240
+ export function pullIx({
2241
+ payer,
2242
+ project,
2243
+ roundAddress: key,
2244
+ round: r,
2245
+ entry = 0,
2246
+ part = 'Own',
2247
+ projectState,
2248
+ ticket = {},
2249
+ handBack = false,
2250
+ hold = false,
2251
+ nonce = 0n,
2252
+ prize = {},
2253
+ holders = {},
2254
+ toTreasury = false,
2255
+ lotteryClosed = false,
2256
+ programs,
2257
+ extra = [],
2258
+ }) {
1541
2259
  const rest = [];
1542
2260
  let [mint, to, settled, buyer, partial] = [null, null, 1, payer, false];
1543
2261
  const account = (m, wallet) => ata(wallet, m, tokenProgramOf(programs, m));
@@ -1554,8 +2272,17 @@ export function pullIx({ payer, project, roundAddress: key, round: r, entry = 0,
1554
2272
  } else {
1555
2273
  // Where its price sends it (a stash keeps it where it is), or back to its buyer.
1556
2274
  const receiver = then ? spentTo(project, then) : null;
1557
- const holder = then?.Stash || (handBack && then?.Give) ? e.buyer : receiver ?? e.buyer;
1558
- const p = piece({ project, asset: e.nft, wallet: holder, ...ticket, moves: !then?.Stash, locks: false, burns: then === 'Burn' || Boolean(then?.Burn), reads: false });
2275
+ const holder = then?.Stash || (handBack && then?.Give) ? e.buyer : (receiver ?? e.buyer);
2276
+ const p = piece({
2277
+ project,
2278
+ asset: e.nft,
2279
+ wallet: holder,
2280
+ ...ticket,
2281
+ moves: !then?.Stash,
2282
+ locks: false,
2283
+ burns: then === 'Burn' || Boolean(then?.Burn),
2284
+ reads: false,
2285
+ });
1559
2286
  rest.push(...pieceMetas(p), ...p.proof, ...(receiver ? [meta(receiver, false, true)] : []));
1560
2287
  }
1561
2288
  } else if (part === 'Own') [mint, to] = [paymentMint(r.terms), account(paymentMint(r.terms), e.buyer)];
@@ -1564,7 +2291,7 @@ export function pullIx({ payer, project, roundAddress: key, round: r, entry = 0,
1564
2291
  const n = r.terms.shares.length;
1565
2292
  let dest;
1566
2293
  if (i < n) [mint, dest] = [paymentMint(r.terms), r.terms.shares[i].to];
1567
- else if (i === n) [mint, dest] = [r.terms.mint, { Account: [pda.pot(r.lottery)] }];
2294
+ else if (i === n) [mint, dest] = [r.terms.mint, leftoverTo(r.lottery, r.terms)];
1568
2295
  else {
1569
2296
  const p = r.prizes[i - n - 1];
1570
2297
  [mint, dest] = [p.mint, r.terms.places[Number(p.run)].prize.Tokens.from];
@@ -1574,7 +2301,7 @@ export function pullIx({ payer, project, roundAddress: key, round: r, entry = 0,
1574
2301
  if (toTreasury || nobody) rest.push(...treasuryAccounts(projectState?.treasury, mint, programs));
1575
2302
  } else if (part.Pot || tokenRun(r, Number(part.Prize[0]))) {
1576
2303
  const run = Number((part.Pot ?? part.Prize)[0]);
1577
- const p = part.Prize ? r.prizes.find((x) => Number(x.run) === run) : null;
2304
+ const p = part.Prize ? r.prizes.find(x => Number(x.run) === run) : null;
1578
2305
  mint = p ? p.mint : r.terms.mint;
1579
2306
  to = account(mint, e.buyer);
1580
2307
  settled = placesPulled(r, entry, run, Boolean(part.Prize));
@@ -1586,10 +2313,13 @@ export function pullIx({ payer, project, roundAddress: key, round: r, entry = 0,
1586
2313
  const own = ownStash(r.terms.places[run]);
1587
2314
  const nfts = own ? stashNfts(r, prize.stash) : [];
1588
2315
  if (own) rest.push(meta(pda.lotteryStash(r.lottery), false, true));
1589
- const handed = (asset) => (prize.hold ? [meta(pda.hold(asset, 0), false, true)] : (() => {
1590
- const p = piece({ project, asset, wallet: e.buyer, ...(prize.nfts?.[asset] ?? {}), ...ONLY_MOVES });
1591
- return [...pieceMetas(p), ...p.proof];
1592
- })());
2316
+ const handed = asset =>
2317
+ prize.hold
2318
+ ? [meta(pda.hold(asset, 0), false, true)]
2319
+ : (() => {
2320
+ const p = piece({ project, asset, wallet: e.buyer, ...(prize.nfts?.[asset] ?? {}), ...ONLY_MOVES });
2321
+ return [...pieceMetas(p), ...p.proof];
2322
+ })();
1593
2323
  let [carried, made, stashed] = [0, 0, false];
1594
2324
  for (const place of places) {
1595
2325
  if (own && nfts[nftSlot(r, place)] == null) continue;
@@ -1607,20 +2337,45 @@ export function pullIx({ payer, project, roundAddress: key, round: r, entry = 0,
1607
2337
  if (holds && !stashed) rest.push(...handed(asset));
1608
2338
  stashed ||= holds;
1609
2339
  } else {
1610
- rest.push(...prizeMetas({ project, collection: prize.collection, prize: given, winner: e.buyer, nonce: BigInt.asUintN(64, toBigInt(nonce) + BigInt(place)), poolMints: prize.poolMints, items: prize.items, nft: prize.nfts?.[given.Item?.[0]], made, projectMint: prize.projectMint, programs }));
2340
+ rest.push(
2341
+ ...prizeMetas({
2342
+ project,
2343
+ collection: prize.collection,
2344
+ prize: given,
2345
+ winner: e.buyer,
2346
+ nonce: BigInt.asUintN(64, toBigInt(nonce) + BigInt(place)),
2347
+ poolMints: prize.poolMints,
2348
+ items: prize.items,
2349
+ nft: prize.nfts?.[given.Item?.[0]],
2350
+ made,
2351
+ projectMint: prize.projectMint,
2352
+ programs,
2353
+ }),
2354
+ );
1611
2355
  if (given.Item) made += 1;
1612
2356
  }
1613
2357
  }
1614
2358
  settled = places.length;
1615
2359
  }
1616
- const closes = finished || (!partial && (r.status === 'Drawn' || r.status === 'Cancelled') && Number(r.owed) <= settled);
2360
+ const closes =
2361
+ finished || (!partial && (r.status === 'Drawn' || r.status === 'Cancelled') && Number(r.owed) <= settled);
1617
2362
  const owner = lotteryClosed ? projectState?.owner : null;
1618
2363
  if (closes) rest.push(...roundCloseMetas(key, r, owner));
1619
2364
  const opener = owner && r.opener === r.lottery ? owner : r.opener;
1620
2365
  return lootIx(
1621
2366
  'pull',
1622
2367
  { entry: part.Common || finished ? 0 : entry, part, nonce: toBigInt(nonce) },
1623
- { payer, project, round: key, buyer, mint, vault: mint ? pda.roundTokens(key, mint) : null, to, opener: closes ? opener : null, treasury: closes ? projectState?.treasury : null },
2368
+ {
2369
+ payer,
2370
+ project,
2371
+ round: key,
2372
+ buyer,
2373
+ mint,
2374
+ vault: mint ? pda.roundTokens(key, mint) : null,
2375
+ to,
2376
+ opener: closes ? opener : null,
2377
+ treasury: closes ? projectState?.treasury : null,
2378
+ },
1624
2379
  { remaining: [...rest, ...tailOf(extra)] },
1625
2380
  );
1626
2381
  }
@@ -1632,7 +2387,10 @@ export function pullRolls({ project, round: r, entry, part, nonce = 0n }) {
1632
2387
  const given = r.terms.places[run]?.prize;
1633
2388
  if (!given?.Roll) return [];
1634
2389
  const buyer = r.entries[entry].buyer;
1635
- return prizePlacesOwed(r, entry, run).map((place) => ({ order: pda.order(project, buyer, prizeRollNonce(BigInt.asUintN(64, toBigInt(nonce) + BigInt(place)))), box: given.Roll[0] }));
2390
+ return prizePlacesOwed(r, entry, run).map(place => ({
2391
+ order: pda.order(project, buyer, prizeRollNonce(BigInt.asUintN(64, toBigInt(nonce) + BigInt(place)))),
2392
+ box: given.Roll[0],
2393
+ }));
1636
2394
  }
1637
2395
 
1638
2396
  // ---------------------------------------------------------------- farms
@@ -1656,20 +2414,36 @@ export function pullRolls({ project, round: r, entry, part, nonce = 0n }) {
1656
2414
  export const openFarmIx = ({ owner, project, id, args, rewardsMint, stakedMint = null }) => {
1657
2415
  if (!Array.isArray(args?.tiers)) throw new Error('A farm needs its lock tiers (`args.tiers`: [{ lock, bps }, …]).');
1658
2416
  const full = {
1659
- gates: [], ends_at: 0, lock_key: '', upkeep_key: '', exit_to: null, ...args,
2417
+ gates: [],
2418
+ ends_at: 0,
2419
+ lock_key: '',
2420
+ upkeep_key: '',
2421
+ exit_to: null,
2422
+ ...args,
1660
2423
  rate: args.rate ? { mints: false, shared: false, halve_every: 0, ...args.rate } : null,
1661
- tiers: args.tiers.map((t) => ({ prize: null, roll: null, exit_bps: 0, earns_for: 0, ...t })),
1662
- growth: (args.growth ?? []).map((g) => ({ profile: false, at_least: 0n, ...g })),
2424
+ tiers: args.tiers.map(t => ({ prize: null, roll: null, exit_bps: 0, earns_for: 0, ...t })),
2425
+ growth: (args.growth ?? []).map(g => ({ profile: false, at_least: 0n, ...g })),
1663
2426
  };
1664
- return lootIx('open_farm', { id, args: full }, {
1665
- owner, project, rewards_mint: rewardsMint, staked_mint: stakedMint,
1666
- staked_vault: stakedMint ? pda.staked(pda.farm(project, id)) : null,
1667
- exit_pool: sinkPool(project, full.exit_to),
1668
- ends: farmKeepsEnds(full) ? pda.ends(pda.farm(project, id)) : null,
1669
- }, {
1670
- // Each box its tiers name to roll, tier by tier (its roll, then a prize that is a roll), once.
1671
- remaining: boxesAccounts(project, full.tiers.flatMap((t) => [t.roll, t.prize?.Roll?.[0]].filter((x) => x != null))),
1672
- });
2427
+ return lootIx(
2428
+ 'open_farm',
2429
+ { id, args: full },
2430
+ {
2431
+ owner,
2432
+ project,
2433
+ rewards_mint: rewardsMint,
2434
+ staked_mint: stakedMint,
2435
+ staked_vault: stakedMint ? pda.staked(pda.farm(project, id)) : null,
2436
+ exit_pool: sinkPool(project, full.exit_to),
2437
+ ends: farmKeepsEnds(full) ? pda.ends(pda.farm(project, id)) : null,
2438
+ },
2439
+ {
2440
+ // Each box its tiers name to roll, tier by tier (its roll, then a prize that is a roll), once.
2441
+ remaining: boxesAccounts(
2442
+ project,
2443
+ full.tiers.flatMap(t => [t.roll, t.prize?.Roll?.[0]].filter(x => x != null)),
2444
+ ),
2445
+ },
2446
+ );
1673
2447
  };
1674
2448
 
1675
2449
  /**
@@ -1677,8 +2451,9 @@ export const openFarmIx = ({ owner, project, id, args, rewardsMint, stakedMint =
1677
2451
  * (`earns_for`) or the farm reads an upkeep (`upkeep_key`), and it pays something (no rate, or a rate above 0). The farm
1678
2452
  * and every move of it then pass the record (`WrongEnds` otherwise).
1679
2453
  */
1680
- export const farmKeepsEnds = (args) =>
1681
- ((args.tiers ?? []).some((t) => BigInt(t.earns_for ?? 0) > 0n) || Boolean(args.upkeep_key)) && (!args.rate || BigInt(args.rate.per_day ?? 0) > 0n);
2454
+ export const farmKeepsEnds = args =>
2455
+ ((args.tiers ?? []).some(t => BigInt(t.earns_for ?? 0) > 0n) || Boolean(args.upkeep_key)) &&
2456
+ (!args.rate || BigInt(args.rate.per_day ?? 0) > 0n);
1682
2457
 
1683
2458
  /** Where a farm's fee goes (`Sink`): the pool's address for `{ Pool: [index] }`, null for a burn, its own vault or none. */
1684
2459
  const sinkPool = (project, sink) => (sink?.Pool ? pda.pool(project, sink.Pool[0]) : null);
@@ -1699,7 +2474,7 @@ export function endsToWalk(farm, ends, now) {
1699
2474
  const clock = BigInt(farm.clock ?? 0);
1700
2475
  const over = BigInt(farm.ends_at ?? 0);
1701
2476
  const stop = over > 0n && over < BigInt(now) ? over : BigInt(now);
1702
- return ends.filter((e) => e.at > clock && e.at <= stop).length;
2477
+ return ends.filter(e => e.at > clock && e.at <= stop).length;
1703
2478
  }
1704
2479
 
1705
2480
  /** How many ends `walk_farm` walks in one transaction at `WALK_COMPUTE_UNITS`: an end takes at most some 2,300 units, with a
@@ -1725,7 +2500,8 @@ export const farmNeedsWalk = (farm, ends, now) => endsToWalk(farm, ends, now) >
1725
2500
  export function walkFarmParts({ project, id, farm, ends, now, perTx = WALK_PER_TX }) {
1726
2501
  const left = endsToWalk(farm, ends, now);
1727
2502
  const parts = [];
1728
- for (let done = 0; done < left; done += perTx) parts.push(walkFarmIx({ project, id, farm, most: Math.min(perTx, left - done) }));
2503
+ for (let done = 0; done < left; done += perTx)
2504
+ parts.push(walkFarmIx({ project, id, farm, most: Math.min(perTx, left - done) }));
1729
2505
  return parts;
1730
2506
  }
1731
2507
 
@@ -1738,11 +2514,13 @@ export const walkFarmIx = ({ project, id, farm, most = WALK_PER_TX }) =>
1738
2514
  lootIx('walk_farm', { most }, farmSide(project, id, farm));
1739
2515
  /** Whether a farm reads or writes its seats' players' profiles (`Farm::uses_profile`): a gate or a boost on a profile,
1740
2516
  * or a growth written on one. Its moves then pass the player's profile. */
1741
- const usesProfile = (farm) => [...(farm.gates ?? []), ...(farm.boosts ?? []).map((b) => b.rule)].some((r) => r?.of === 'Profile') || (farm.growth ?? []).some((g) => g.profile);
2517
+ const usesProfile = farm =>
2518
+ [...(farm.gates ?? []), ...(farm.boosts ?? []).map(b => b.rule)].some(r => r?.of === 'Profile') ||
2519
+ (farm.growth ?? []).some(g => g.profile);
1742
2520
  const profileOf = (project, farm, player) => (usesProfile(farm) ? pda.profile(project, player) : null);
1743
2521
 
1744
2522
  /** What a move that pays a seat writes: the rewards' mint, when the farm mints at a fixed rate (it mints at payout). */
1745
- const payWrites = (farm) => (farm.rate?.mints ? ['rewards_mint'] : []);
2523
+ const payWrites = farm => (farm.rate?.mints ? ['rewards_mint'] : []);
1746
2524
 
1747
2525
  /** A token seat's accounts; `paid`: rewards may be paid (an unstake), so the player's account of them comes. */
1748
2526
  function tokenSeat({ player, project, id, farm, programs, paid = false }) {
@@ -1767,7 +2545,9 @@ function tokenSeat({ player, project, id, farm, programs, paid = false }) {
1767
2545
  * (`stakeRefreshFirst`).
1768
2546
  */
1769
2547
  export const stakeTokensIx = ({ player, project, id, farm, amount, tier = 0, programs, shown, extra = [] }) =>
1770
- lootIx('stake_tokens', { amount, tier }, tokenSeat({ player, project, id, farm, programs }), { remaining: [...shownAccounts(project, shown), ...tailOf(extra)] });
2548
+ lootIx('stake_tokens', { amount, tier }, tokenSeat({ player, project, id, farm, programs }), {
2549
+ remaining: [...shownAccounts(project, shown), ...tailOf(extra)],
2550
+ });
1771
2551
 
1772
2552
  /**
1773
2553
  * `player` takes `amount` out (0: only collects what is owed); `forfeit` gives up rewards that can't move. Before the
@@ -1787,7 +2567,9 @@ export const unstakeTokensIx = ({ player, project, id, farm, amount, forfeit = f
1787
2567
  * what it grew since its last refresh is written before it goes. Never under `forfeit`.
1788
2568
  */
1789
2569
  export const refreshFirst = (farm, seat, amount, forfeit = false) =>
1790
- !forfeit && BigInt(amount) > 0n && (farm.growth ?? []).some((g) => {
2570
+ !forfeit &&
2571
+ BigInt(amount) > 0n &&
2572
+ (farm.growth ?? []).some(g => {
1791
2573
  const least = BigInt(g.at_least ?? 0) > 0n ? BigInt(g.at_least) : 1n;
1792
2574
  return BigInt(seat.amount) >= least && BigInt(seat.amount) - BigInt(amount) < least;
1793
2575
  });
@@ -1807,10 +2589,13 @@ export function stakeRefreshFirst(farm, seat, { tier = seat?.tier, now, compound
1807
2589
  if (!seat) return false;
1808
2590
  const growth = farm.growth ?? [];
1809
2591
  const booster = seat.asset !== undefined && seat.asset !== NO_ADDRESS;
1810
- const grows = BigInt(seat.amount) > 0n && growth.some((g) => g.profile || booster);
1811
- const amountGrows = growth.some((g) => g.profile || BigInt(g.at_least ?? 0) > 0n);
2592
+ const grows = BigInt(seat.amount) > 0n && growth.some(g => g.profile || booster);
2593
+ const amountGrows = growth.some(g => g.profile || BigInt(g.at_least ?? 0) > 0n);
1812
2594
  if (compound) return BigInt(seat.amount) > 0n && (booster || (grows && amountGrows));
1813
- const changes = Number(tier) !== Number(seat.tier) || amountGrows || (now !== undefined && BigInt(seat.earns_until ?? 0) <= BigInt(now));
2595
+ const changes =
2596
+ Number(tier) !== Number(seat.tier) ||
2597
+ amountGrows ||
2598
+ (now !== undefined && BigInt(seat.earns_until ?? 0) <= BigInt(now));
1814
2599
  return booster || (grows && changes);
1815
2600
  }
1816
2601
 
@@ -1819,7 +2604,7 @@ export function stakeRefreshFirst(farm, seat, { tier = seat?.tier, now, compound
1819
2604
  * (`stake_nft`'s `RefreshFirst`): in a farm of tokens whose growth writes its players' profiles, the booster's growth
1820
2605
  * starts from now, so what the seat grew before is written first.
1821
2606
  */
1822
- export const boosterRefreshFirst = (farm) => Boolean(farm.farmed?.Tokens) && (farm.growth ?? []).some((g) => g.profile);
2607
+ export const boosterRefreshFirst = farm => Boolean(farm.farmed?.Tokens) && (farm.growth ?? []).some(g => g.profile);
1823
2608
 
1824
2609
  /**
1825
2610
  * Whether a farm (decoded) takes a stake at lock tier `tier` at `now` (Unix seconds, the chain's clock), as the program
@@ -1838,10 +2623,28 @@ export const AUTH_RULES_PROGRAM = 'auth9SigNpDKz4sJJ1DfCTuZrZNSAgh9sFD3rboVmgg';
1838
2623
  // ---------------------------------------------------------------- an NFT on a route (a `Piece`)
1839
2624
 
1840
2625
  const NO_PIECE = {
1841
- sheet: null, collection: null, mpl_core_program: null, metadata: null, edition: null, token: null, token_record: null, project_token: null, project_record: null,
1842
- rule_set: null, rules_program: null, token_metadata_program: null, instructions: null, spl_token_program: null,
1843
- spl_ata_program: null, leaf: null, tree: null, tree_config: null, leaf_delegate: null, bubblegum_program: null,
1844
- compression_program: null, log_wrapper: null,
2626
+ sheet: null,
2627
+ collection: null,
2628
+ mpl_core_program: null,
2629
+ metadata: null,
2630
+ edition: null,
2631
+ token: null,
2632
+ token_record: null,
2633
+ project_token: null,
2634
+ project_record: null,
2635
+ rule_set: null,
2636
+ rules_program: null,
2637
+ token_metadata_program: null,
2638
+ instructions: null,
2639
+ spl_token_program: null,
2640
+ spl_ata_program: null,
2641
+ leaf: null,
2642
+ tree: null,
2643
+ tree_config: null,
2644
+ leaf_delegate: null,
2645
+ bubblegum_program: null,
2646
+ compression_program: null,
2647
+ log_wrapper: null,
1845
2648
  };
1846
2649
 
1847
2650
  /**
@@ -1859,19 +2662,40 @@ const NO_PIECE = {
1859
2662
  * `burns`: burns a Core asset (false when left out); `reads`: reads its Sheet; `held`: reads it in the project's token
1860
2663
  * account (an escrow farm).
1861
2664
  */
1862
- export function piece({ project, asset, wallet, collection, hasAuthority = true, tokenMetadata: tm, compressed: c, moves = true, locks = true, writes = true, burns = false, reads = true, held = moves }) {
1863
- const sheetOf = (nft) => (reads || writes || burns ? pda.sheet(project, nft) : null);
2665
+ export function piece({
2666
+ project,
2667
+ asset,
2668
+ wallet,
2669
+ collection,
2670
+ hasAuthority = true,
2671
+ tokenMetadata: tm,
2672
+ compressed: c,
2673
+ moves = true,
2674
+ locks = true,
2675
+ writes = true,
2676
+ burns = false,
2677
+ reads = true,
2678
+ held = moves,
2679
+ }) {
2680
+ const sheetOf = nft => (reads || writes || burns ? pda.sheet(project, nft) : null);
1864
2681
  if (c) {
1865
2682
  // Bubblegum is called (and writes the leaf, its tree and its config) only to lock or move it.
1866
2683
  const calls = moves || locks;
1867
2684
  const delegate = calls && c.delegate !== c.owner && c.delegate !== project ? c.delegate : null;
1868
2685
  const bubblegum = calls
1869
- ? { collection: c.collection ?? null, tree_config: pda.treeConfig(c.tree), leaf_delegate: delegate, bubblegum_program: BUBBLEGUM_PROGRAM, compression_program: COMPRESSION_PROGRAM, log_wrapper: NOOP_PROGRAM }
2686
+ ? {
2687
+ collection: c.collection ?? null,
2688
+ tree_config: pda.treeConfig(c.tree),
2689
+ leaf_delegate: delegate,
2690
+ bubblegum_program: BUBBLEGUM_PROGRAM,
2691
+ compression_program: COMPRESSION_PROGRAM,
2692
+ log_wrapper: NOOP_PROGRAM,
2693
+ }
1870
2694
  : {};
1871
2695
  return {
1872
2696
  kind: 'compressed',
1873
2697
  accounts: { ...NO_PIECE, asset: c.id, sheet: sheetOf(c.id), leaf: pda.leaf(c.id), tree: c.tree, ...bubblegum },
1874
- proof: c.proof.map((node) => meta(typeof node === 'string' ? node : encodeBase58(node), false, false)),
2698
+ proof: c.proof.map(node => meta(typeof node === 'string' ? node : encodeBase58(node), false, false)),
1875
2699
  writable: calls ? ['leaf', 'tree', 'tree_config'] : [],
1876
2700
  };
1877
2701
  }
@@ -1887,12 +2711,16 @@ export function piece({ project, asset, wallet, collection, hasAuthority = true,
1887
2711
  return {
1888
2712
  kind: 'tokenMetadata',
1889
2713
  accounts: {
1890
- ...NO_PIECE, asset, sheet: sheetOf(asset), metadata: tokenMetadata.metadata(asset), token,
2714
+ ...NO_PIECE,
2715
+ asset,
2716
+ sheet: sheetOf(asset),
2717
+ metadata: tokenMetadata.metadata(asset),
2718
+ token,
1891
2719
  edition: calls ? tokenMetadata.edition(asset) : null,
1892
2720
  token_record: records ? tokenMetadata.record(asset, token) : null,
1893
2721
  project_token: held || moves ? projectToken : null,
1894
2722
  project_record: records && moves ? tokenMetadata.record(asset, projectToken) : null,
1895
- rule_set: records ? tm.ruleSet ?? TOKEN_METADATA_PROGRAM : null,
2723
+ rule_set: records ? (tm.ruleSet ?? TOKEN_METADATA_PROGRAM) : null,
1896
2724
  rules_program: records ? (tm.ruleSet ? AUTH_RULES_PROGRAM : TOKEN_METADATA_PROGRAM) : null,
1897
2725
  token_metadata_program: calls ? TOKEN_METADATA_PROGRAM : null,
1898
2726
  instructions: moves || records ? SYSVAR_INSTRUCTIONS : null,
@@ -1910,8 +2738,11 @@ export function piece({ project, asset, wallet, collection, hasAuthority = true,
1910
2738
  return {
1911
2739
  kind: 'core',
1912
2740
  accounts: {
1913
- ...NO_PIECE, asset, sheet: hasAuthority ? null : sheetOf(asset),
1914
- collection: calls && !inNone ? collection : null, mpl_core_program: calls ? MPL_CORE_PROGRAM : null,
2741
+ ...NO_PIECE,
2742
+ asset,
2743
+ sheet: hasAuthority ? null : sheetOf(asset),
2744
+ collection: calls && !inNone ? collection : null,
2745
+ mpl_core_program: calls ? MPL_CORE_PROGRAM : null,
1915
2746
  },
1916
2747
  proof: [],
1917
2748
  writable: burns && !inNone ? ['collection'] : [],
@@ -1922,18 +2753,33 @@ export function piece({ project, asset, wallet, collection, hasAuthority = true,
1922
2753
  * needs, the program's own address for one left out, then its proof. The first NFT passes every program any needs. */
1923
2754
  const ANOTHER = {
1924
2755
  core: ['asset', 'sheet', 'collection'],
1925
- tokenMetadata: ['asset', 'sheet', 'metadata', 'edition', 'token', 'token_record', 'project_token', 'project_record', 'rule_set'],
2756
+ tokenMetadata: [
2757
+ 'asset',
2758
+ 'sheet',
2759
+ 'metadata',
2760
+ 'edition',
2761
+ 'token',
2762
+ 'token_record',
2763
+ 'project_token',
2764
+ 'project_record',
2765
+ 'rule_set',
2766
+ ],
1926
2767
  compressed: ['asset', 'sheet', 'collection', 'leaf', 'tree', 'tree_config', 'leaf_delegate'],
1927
2768
  };
1928
2769
  export function anotherMetas(p) {
1929
- const writable = new Set([...p.writable, ...PIECE.filter((a) => a.writable).map((a) => a.name)]);
1930
- return [...ANOTHER[p.kind].map((name) => (p.accounts[name] == null ? meta(LOOT_PROGRAM, false, false) : meta(p.accounts[name], false, writable.has(name)))), ...p.proof];
2770
+ const writable = new Set([...p.writable, ...PIECE.filter(a => a.writable).map(a => a.name)]);
2771
+ return [
2772
+ ...ANOTHER[p.kind].map(name =>
2773
+ p.accounts[name] == null ? meta(LOOT_PROGRAM, false, false) : meta(p.accounts[name], false, writable.has(name)),
2774
+ ),
2775
+ ...p.proof,
2776
+ ];
1931
2777
  }
1932
2778
 
1933
2779
  /** The programs every NFT of a call of several needs, on the first NFT's piece (the call's own): Core, Token Metadata's
1934
2780
  * and Bubblegum's, by what the others are. */
1935
2781
  function withPrograms(first, others) {
1936
- const kinds = new Set(others.map((p) => p.kind));
2782
+ const kinds = new Set(others.map(p => p.kind));
1937
2783
  const a = { ...first.accounts };
1938
2784
  if (kinds.has('core')) a.mpl_core_program ??= MPL_CORE_PROGRAM;
1939
2785
  if (kinds.has('tokenMetadata')) {
@@ -1941,9 +2787,18 @@ function withPrograms(first, others) {
1941
2787
  a.spl_token_program ??= TOKEN_PROGRAM;
1942
2788
  a.spl_ata_program ??= ASSOCIATED_TOKEN_PROGRAM;
1943
2789
  a.instructions ??= SYSVAR_INSTRUCTIONS;
1944
- a.rules_program ??= others.some((p) => p.accounts.rule_set && p.accounts.rule_set !== TOKEN_METADATA_PROGRAM) ? AUTH_RULES_PROGRAM : others.some((p) => p.accounts.token_record) ? TOKEN_METADATA_PROGRAM : null;
2790
+ a.rules_program ??= others.some(p => p.accounts.rule_set && p.accounts.rule_set !== TOKEN_METADATA_PROGRAM)
2791
+ ? AUTH_RULES_PROGRAM
2792
+ : others.some(p => p.accounts.token_record)
2793
+ ? TOKEN_METADATA_PROGRAM
2794
+ : null;
1945
2795
  }
1946
- if (kinds.has('compressed')) Object.assign(a, { bubblegum_program: a.bubblegum_program ?? BUBBLEGUM_PROGRAM, compression_program: a.compression_program ?? COMPRESSION_PROGRAM, log_wrapper: a.log_wrapper ?? NOOP_PROGRAM });
2796
+ if (kinds.has('compressed'))
2797
+ Object.assign(a, {
2798
+ bubblegum_program: a.bubblegum_program ?? BUBBLEGUM_PROGRAM,
2799
+ compression_program: a.compression_program ?? COMPRESSION_PROGRAM,
2800
+ log_wrapper: a.log_wrapper ?? NOOP_PROGRAM,
2801
+ });
1947
2802
  return { ...first, accounts: a };
1948
2803
  }
1949
2804
 
@@ -1951,7 +2806,8 @@ function withPrograms(first, others) {
1951
2806
  * Several NFTs on one call (a hold, a stash, a farm's seats): their groups sorted by key (`keyOf`), the first the
1952
2807
  * call's own and each further one after it, in ascending order (`OutOfOrder` otherwise). Returns [first, ...rest].
1953
2808
  */
1954
- const ascending = (items, keyOf) => [...items].sort((a, b) => compareBytes(addressBytes(keyOf(a)), addressBytes(keyOf(b))));
2809
+ const ascending = (items, keyOf) =>
2810
+ [...items].sort((a, b) => compareBytes(addressBytes(keyOf(a)), addressBytes(keyOf(b))));
1955
2811
 
1956
2812
  /** The account an NFT paid goes to: the wallet it is given to, the stash it goes into, or the
1957
2813
  * incinerator for a burn (any account stands in for a Core asset, which Core burns). `then`: 'Burn', { Give: [wallet] }
@@ -1972,15 +2828,58 @@ export function nftPaid({ project, owner, then, asset, collection, hasAuthority,
1972
2828
  const to = spentTo(project, then);
1973
2829
  // The route reads it first (its Sheet, for one the project has no authority over: `hasAuthority` false).
1974
2830
  const p = then.Stash
1975
- ? piece({ project, asset, wallet: owner, collection, hasAuthority, tokenMetadata: tm, compressed, ...ONLY_MOVES, reads: true })
1976
- : passedOn({ project, owner, receiver: to, burns: then === 'Burn' || Boolean(then.Burn), asset, collection, hasAuthority, tokenMetadata: tm, compressed, reads: true });
2831
+ ? piece({
2832
+ project,
2833
+ asset,
2834
+ wallet: owner,
2835
+ collection,
2836
+ hasAuthority,
2837
+ tokenMetadata: tm,
2838
+ compressed,
2839
+ ...ONLY_MOVES,
2840
+ reads: true,
2841
+ })
2842
+ : passedOn({
2843
+ project,
2844
+ owner,
2845
+ receiver: to,
2846
+ burns: then === 'Burn' || Boolean(then.Burn),
2847
+ asset,
2848
+ collection,
2849
+ hasAuthority,
2850
+ tokenMetadata: tm,
2851
+ compressed,
2852
+ reads: true,
2853
+ });
1977
2854
  return [...pieceMetas(p), ...p.proof, meta(to, false, Boolean(then.Stash))];
1978
2855
  }
1979
2856
 
1980
2857
  /** An NFT moving straight from `owner` to `receiver`, or burned by its owner (`burns`). */
1981
- function passedOn({ project, owner, receiver, burns, asset, collection, hasAuthority, tokenMetadata: tm, compressed, reads = false }) {
2858
+ function passedOn({
2859
+ project,
2860
+ owner,
2861
+ receiver,
2862
+ burns,
2863
+ asset,
2864
+ collection,
2865
+ hasAuthority,
2866
+ tokenMetadata: tm,
2867
+ compressed,
2868
+ reads = false,
2869
+ }) {
1982
2870
  // `tm.into` is the receiver's account here: the NFT leaves the owner's associated one.
1983
- const p = piece({ project, asset, wallet: owner, collection, hasAuthority, tokenMetadata: tm && { ...tm, into: undefined }, compressed, ...ONLY_MOVES, burns: burns && !tm && !compressed, reads });
2871
+ const p = piece({
2872
+ project,
2873
+ asset,
2874
+ wallet: owner,
2875
+ collection,
2876
+ hasAuthority,
2877
+ tokenMetadata: tm && { ...tm, into: undefined },
2878
+ compressed,
2879
+ ...ONLY_MOVES,
2880
+ burns: burns && !tm && !compressed,
2881
+ reads,
2882
+ });
1984
2883
  if (tm) {
1985
2884
  const token = tm.into ?? ata(receiver, asset, TOKEN_PROGRAM);
1986
2885
  p.accounts.project_token = token;
@@ -1994,12 +2893,38 @@ function passedOn({ project, owner, receiver, burns, asset, collection, hasAutho
1994
2893
  * place), or given to the wallet `to` (the treasury, a partner). The receipt (`PaidNft`) is the record: an app admits the player when the NFT, where it went (`to`: a payment to the player's own wallet pays nothing) and the reference
1995
2894
  * are the ones it asked for. `extra`: Core's transfer adapters.
1996
2895
  */
1997
- export function payNftIx({ player, project, asset, to = null, collection, hasAuthority, tokenMetadata: tm, compressed, reference: ref, extra = [] }) {
2896
+ export function payNftIx({
2897
+ player,
2898
+ project,
2899
+ asset,
2900
+ to = null,
2901
+ collection,
2902
+ hasAuthority,
2903
+ tokenMetadata: tm,
2904
+ compressed,
2905
+ reference: ref,
2906
+ extra = [],
2907
+ }) {
1998
2908
  // An NFT paid goes to a wallet, never to the project's own address, where nothing could ever move it (ToTheProject).
1999
2909
  if (to === project) throw new Error('An NFT paid can’t go to the project’s own address: name a wallet, or burn it.');
2000
2910
  const receiver = to ?? INCINERATOR;
2001
- const p = passedOn({ project, owner: player, receiver, burns: receiver === INCINERATOR, asset, collection, hasAuthority, tokenMetadata: tm, compressed });
2002
- return lootIx('pay_nft', { reference: reference(ref) }, { player, project, ...p.accounts, to: receiver }, { remaining: [...p.proof, ...tailOf(extra)], writable: p.writable });
2911
+ const p = passedOn({
2912
+ project,
2913
+ owner: player,
2914
+ receiver,
2915
+ burns: receiver === INCINERATOR,
2916
+ asset,
2917
+ collection,
2918
+ hasAuthority,
2919
+ tokenMetadata: tm,
2920
+ compressed,
2921
+ });
2922
+ return lootIx(
2923
+ 'pay_nft',
2924
+ { reference: reference(ref) },
2925
+ { player, project, ...p.accounts, to: receiver },
2926
+ { remaining: [...p.proof, ...tailOf(extra)], writable: p.writable },
2927
+ );
2003
2928
  }
2004
2929
 
2005
2930
  /**
@@ -2011,7 +2936,9 @@ export function payNftIx({ player, project, asset, to = null, collection, hasAut
2011
2936
  export function takesNft(account, holder, mint) {
2012
2937
  if (!account || account.owner !== TOKEN_PROGRAM || account.data.length < TOKEN_ACCOUNT_BYTES) return false;
2013
2938
  const data = account.data;
2014
- return encodeBase58(data.subarray(0, 32)) === mint && encodeBase58(data.subarray(32, 64)) === holder && data[108] === 1;
2939
+ return (
2940
+ encodeBase58(data.subarray(0, 32)) === mint && encodeBase58(data.subarray(32, 64)) === holder && data[108] === 1
2941
+ );
2015
2942
  }
2016
2943
 
2017
2944
  /**
@@ -2031,7 +2958,21 @@ export function freshNftAccount({ payer, holder, mint, index = 0, there = null }
2031
2958
  const seed = freshNftSeed(holder, mint, index);
2032
2959
  const address = addressWithSeed(payer, seed, TOKEN_PROGRAM);
2033
2960
  const init = initializeAccount3Ix({ account: address, mint, owner: holder });
2034
- if (!there) return { address, instructions: [createAccountWithSeedIx({ from: payer, address, seed, lamports: TOKEN_ACCOUNT_RENT, space: TOKEN_ACCOUNT_BYTES, owner: TOKEN_PROGRAM }), init] };
2961
+ if (!there)
2962
+ return {
2963
+ address,
2964
+ instructions: [
2965
+ createAccountWithSeedIx({
2966
+ from: payer,
2967
+ address,
2968
+ seed,
2969
+ lamports: TOKEN_ACCOUNT_RENT,
2970
+ space: TOKEN_ACCOUNT_BYTES,
2971
+ owner: TOKEN_PROGRAM,
2972
+ }),
2973
+ init,
2974
+ ],
2975
+ };
2035
2976
  if (takesNft(there, holder, mint)) return { address, instructions: [] };
2036
2977
  if (there.owner !== SYSTEM_PROGRAM || there.data.length) return null;
2037
2978
  // The RPC's figure only lowers the top-up, never raises it past the rent: lamports that are negative or not a whole
@@ -2052,7 +2993,8 @@ export function freshNftAccount({ payer, holder, mint, index = 0, there = null }
2052
2993
  }
2053
2994
 
2054
2995
  /** The seed of a holder's fresh account of a mint: `nft<index>:` and 26 base58 characters of sha256(holder | mint). */
2055
- const freshNftSeed = (holder, mint, index) => `nft${index}:${encodeBase58(sha256(concat(addressBytes(holder), addressBytes(mint)))).slice(0, 26)}`;
2996
+ const freshNftSeed = (holder, mint, index) =>
2997
+ `nft${index}:${encodeBase58(sha256(concat(addressBytes(holder), addressBytes(mint)))).slice(0, 26)}`;
2056
2998
 
2057
2999
  /** How many fresh accounts a payer may open for one holder and mint (`freshNftAccount`'s `index`). */
2058
3000
  export const FRESH_NFT_ACCOUNTS = 8;
@@ -2068,17 +3010,23 @@ const ONLY_MOVES = { moves: true, locks: false, writes: false, reads: false };
2068
3010
  const holds = (hold, asset) => (Array.isArray(hold) ? hold.includes(asset) : Boolean(hold));
2069
3011
 
2070
3012
  /** A piece that reads no NFT: `at` (an auction's or a seat's own address) stands in for one. */
2071
- const standIn = (at) => ({ accounts: { ...NO_PIECE, asset: at }, proof: [], writable: [] });
3013
+ const standIn = at => ({ accounts: { ...NO_PIECE, asset: at }, proof: [], writable: [] });
2072
3014
 
2073
3015
  /**
2074
3016
  * A compressed NFT's record (`pda.leaf`) closes, its rent back to `payer`, who paid it: at any time with `payer` signing
2075
3017
  * (`signs`, the default), or by anyone once it no longer matches its tree. A route that finds no record asks for a proof
2076
3018
  * again (`NotProven`), so a client closes it in the transaction that used it (`withLeafCloses`).
2077
3019
  */
2078
- export const closeLeafIx = ({ asset, tree, payer, signs = true }) => lootIx('close_leaf', {}, { record: pda.leaf(asset), tree, payer, project: null, edition: null }, { signer: signs ? ['payer'] : [] });
3020
+ export const closeLeafIx = ({ asset, tree, payer, signs = true }) =>
3021
+ lootIx(
3022
+ 'close_leaf',
3023
+ {},
3024
+ { record: pda.leaf(asset), tree, payer, project: null, edition: null },
3025
+ { signer: signs ? ['payer'] : [] },
3026
+ );
2079
3027
 
2080
- const PROVE_LEAF = leaves(instructionDef('prove_leaf').accounts).map((a) => a.name);
2081
- const CLOSE_LEAF = leaves(instructionDef('close_leaf').accounts).map((a) => a.name);
3028
+ const PROVE_LEAF = leaves(instructionDef('prove_leaf').accounts).map(a => a.name);
3029
+ const CLOSE_LEAF = leaves(instructionDef('close_leaf').accounts).map(a => a.name);
2082
3030
  const isLoot = (ix, name) => ix.programId === LOOT_PROGRAM && instructionOf(ix.data)?.name === name;
2083
3031
 
2084
3032
  /**
@@ -2088,13 +3036,19 @@ const isLoot = (ix, name) => ix.programId === LOOT_PROGRAM && instructionOf(ix.d
2088
3036
  */
2089
3037
  export function withLeafCloses(instructions) {
2090
3038
  const at = (ix, names, name) => ix.keys[names.indexOf(name)]?.pubkey;
2091
- const closed = new Set(instructions.filter((ix) => isLoot(ix, 'close_leaf')).map((ix) => at(ix, CLOSE_LEAF, 'record')));
3039
+ const closed = new Set(instructions.filter(ix => isLoot(ix, 'close_leaf')).map(ix => at(ix, CLOSE_LEAF, 'record')));
2092
3040
  const closes = [];
2093
- for (const ix of instructions.filter((x) => isLoot(x, 'prove_leaf'))) {
3041
+ for (const ix of instructions.filter(x => isLoot(x, 'prove_leaf'))) {
2094
3042
  const record = at(ix, PROVE_LEAF, 'record');
2095
3043
  if (closed.has(record)) continue;
2096
3044
  closed.add(record);
2097
- closes.push(closeLeafIx({ asset: at(ix, PROVE_LEAF, 'asset'), tree: at(ix, PROVE_LEAF, 'tree'), payer: at(ix, PROVE_LEAF, 'signer') }));
3045
+ closes.push(
3046
+ closeLeafIx({
3047
+ asset: at(ix, PROVE_LEAF, 'asset'),
3048
+ tree: at(ix, PROVE_LEAF, 'tree'),
3049
+ payer: at(ix, PROVE_LEAF, 'signer'),
3050
+ }),
3051
+ );
2098
3052
  }
2099
3053
  return closes.length ? [...instructions, ...closes] : instructions;
2100
3054
  }
@@ -2108,8 +3062,25 @@ export function withLeafCloses(instructions) {
2108
3062
  * (`pda.leaf`). A Token Metadata NFT's (`tokenMetadata`: true, or its kind) names its edition, which must be closed
2109
3063
  * too: its mint holds none, with no mint authority but its edition (`NotBurned` otherwise).
2110
3064
  */
2111
- export const closeSheetIx = ({ project, nft, tree = null, burned = tree ? pda.leaf(nft) : nft, treasury, tokenMetadata: tm = null }) =>
2112
- lootIx('close_leaf', {}, { record: pda.sheet(project, nft), tree: burned, payer: treasury, project, edition: tm ? tokenMetadata.edition(nft) : tree });
3065
+ export const closeSheetIx = ({
3066
+ project,
3067
+ nft,
3068
+ tree = null,
3069
+ burned = tree ? pda.leaf(nft) : nft,
3070
+ treasury,
3071
+ tokenMetadata: tm = null,
3072
+ }) =>
3073
+ lootIx(
3074
+ 'close_leaf',
3075
+ {},
3076
+ {
3077
+ record: pda.sheet(project, nft),
3078
+ tree: burned,
3079
+ payer: treasury,
3080
+ project,
3081
+ edition: tm ? tokenMetadata.edition(nft) : tree,
3082
+ },
3083
+ );
2113
3084
 
2114
3085
  /**
2115
3086
  * Anyone proves a compressed NFT's leaf (`compressed` from `compressedFromDas`): put it before the instruction that
@@ -2118,19 +3089,42 @@ export const closeSheetIx = ({ project, nft, tree = null, burned = tree ? pda.le
2118
3089
  * with it: only a tree Bubblegum made has one.
2119
3090
  */
2120
3091
  export const proveLeafIx = ({ signer, compressed: c }) =>
2121
- lootIx('prove_leaf', {
2122
- leaf: { root: c.root, data_hash: c.dataHash, creator_hash: c.creatorHash, asset_data_hash: c.assetDataHash ?? null, flags: c.flags, nonce: c.nonce },
2123
- }, {
2124
- signer, asset: c.id, record: pda.leaf(c.id), owner: c.owner, delegate: c.delegate, collection: c.collection ?? BUBBLEGUM_PROGRAM, tree: c.tree,
2125
- tree_config: pda.treeConfig(c.tree),
2126
- }, { remaining: c.proof.map((node) => meta(typeof node === 'string' ? node : encodeBase58(node), false, false)) });
3092
+ lootIx(
3093
+ 'prove_leaf',
3094
+ {
3095
+ leaf: {
3096
+ root: c.root,
3097
+ data_hash: c.dataHash,
3098
+ creator_hash: c.creatorHash,
3099
+ asset_data_hash: c.assetDataHash ?? null,
3100
+ flags: c.flags,
3101
+ nonce: c.nonce,
3102
+ },
3103
+ },
3104
+ {
3105
+ signer,
3106
+ asset: c.id,
3107
+ record: pda.leaf(c.id),
3108
+ owner: c.owner,
3109
+ delegate: c.delegate,
3110
+ collection: c.collection ?? BUBBLEGUM_PROGRAM,
3111
+ tree: c.tree,
3112
+ tree_config: pda.treeConfig(c.tree),
3113
+ },
3114
+ { remaining: c.proof.map(node => meta(typeof node === 'string' ? node : encodeBase58(node), false, false)) },
3115
+ );
2127
3116
 
2128
3117
  /** The project's owner or authority vouches for NFTs' off-chain traits: `root` of a `traitsTree`, under `key`. */
2129
- export const setTraitsIx = ({ signer, project, key, root }) => lootIx('set_traits', { key, root }, { signer, project, traits: pda.traits(project, key) });
3118
+ export const setTraitsIx = ({ signer, project, key, root }) =>
3119
+ lootIx('set_traits', { key, root }, { signer, project, traits: pda.traits(project, key) });
2130
3120
 
2131
3121
  /** Anyone writes `nft`'s traits ([{ key, value }]) into its Sheet in `project`, by its `proof` in the tree under `key`. */
2132
3122
  export const proveTraitsIx = ({ signer, project, key, nft, attributes, proof }) =>
2133
- lootIx('prove_traits', { attributes, proof }, { signer, traits: pda.traits(project, key), nft, sheet: pda.sheet(project, nft) });
3123
+ lootIx(
3124
+ 'prove_traits',
3125
+ { attributes, proof },
3126
+ { signer, traits: pda.traits(project, key), nft, sheet: pda.sheet(project, nft) },
3127
+ );
2134
3128
 
2135
3129
  /** What a farm does with a seat's NFT (`piece`'s flags): kept as its custody says, written on as it grows. */
2136
3130
  const farmKeeps = (farm, { writes = false, rolls = false } = {}) => ({
@@ -2140,9 +3134,32 @@ const farmKeeps = (farm, { writes = false, rolls = false } = {}) => ({
2140
3134
  held: farm.custody === 'Escrow',
2141
3135
  });
2142
3136
 
2143
- function nftSeat({ player, project, id, farm, asset, collection, hasAuthority = true, tokenMetadata: tm, compressed, previous = null, programs, leaving = false, keeps }) {
3137
+ function nftSeat({
3138
+ player,
3139
+ project,
3140
+ id,
3141
+ farm,
3142
+ asset,
3143
+ collection,
3144
+ hasAuthority = true,
3145
+ tokenMetadata: tm,
3146
+ compressed,
3147
+ previous = null,
3148
+ programs,
3149
+ leaving = false,
3150
+ keeps,
3151
+ }) {
2144
3152
  const farmAddress = pda.farm(project, id);
2145
- const p = piece({ project, asset, wallet: player, collection, hasAuthority, tokenMetadata: tm, compressed, ...keeps });
3153
+ const p = piece({
3154
+ project,
3155
+ asset,
3156
+ wallet: player,
3157
+ collection,
3158
+ hasAuthority,
3159
+ tokenMetadata: tm,
3160
+ compressed,
3161
+ ...keeps,
3162
+ });
2146
3163
  const key = compressed ? compressed.id : asset;
2147
3164
  return {
2148
3165
  kind: p.kind,
@@ -2153,7 +3170,10 @@ function nftSeat({ player, project, id, farm, asset, collection, hasAuthority =
2153
3170
  previous,
2154
3171
  seat: pda.seat(farmAddress, farm.farmed.Tokens ? player : key),
2155
3172
  // Rewards are paid only as an NFT's own seat leaves.
2156
- player_rewards: leaving && !farm.farmed.Tokens ? ata(player, farm.rewards_mint, tokenProgramOf(programs, farm.rewards_mint)) : null,
3173
+ player_rewards:
3174
+ leaving && !farm.farmed.Tokens
3175
+ ? ata(player, farm.rewards_mint, tokenProgramOf(programs, farm.rewards_mint))
3176
+ : null,
2157
3177
  profile: profileOf(project, farm, player),
2158
3178
  },
2159
3179
  proof: p.proof,
@@ -2163,7 +3183,13 @@ function nftSeat({ player, project, id, farm, asset, collection, hasAuthority =
2163
3183
 
2164
3184
  /** A further seat's group of a call of several (`NftSeat`): the seat, its NFT's accounts (`anotherMetas`), its box and
2165
3185
  * order leaving a mission that rolls (the program's address for none), then its proof (and an unstake's prize's). */
2166
- const seatGroup = (seat, p, box = null, order = null) => [meta(seat, false, true), ...anotherMetas({ ...p, proof: [] }), meta(box ?? LOOT_PROGRAM, false, false), meta(order ?? LOOT_PROGRAM, false, Boolean(order)), ...p.proof];
3186
+ const seatGroup = (seat, p, box = null, order = null) => [
3187
+ meta(seat, false, true),
3188
+ ...anotherMetas({ ...p, proof: [] }),
3189
+ meta(box ?? LOOT_PROGRAM, false, false),
3190
+ meta(order ?? LOOT_PROGRAM, false, Boolean(order)),
3191
+ ...p.proof,
3192
+ ];
2167
3193
 
2168
3194
  /**
2169
3195
  * `player` stakes an NFT: on a seat of its own in a farm of collectibles, or as the booster of the player's token seat
@@ -2173,13 +3199,36 @@ const seatGroup = (seat, p, box = null, order = null) => [meta(seat, false, true
2173
3199
  * collection, hasAuthority, tokenMetadata, compressed }) go in the same call at the same tier, one fee, by their seats'
2174
3200
  * addresses. `extra` carries reward token hooks/memos and Core transfer adapter accounts.
2175
3201
  */
2176
- export const stakeNftIx = ({ player, project, id, farm, asset, collection, hasAuthority = true, tokenMetadata: tm, compressed, previous = null, tier = 0, programs, more = [], extra = [] }) => {
3202
+ export const stakeNftIx = ({
3203
+ player,
3204
+ project,
3205
+ id,
3206
+ farm,
3207
+ asset,
3208
+ collection,
3209
+ hasAuthority = true,
3210
+ tokenMetadata: tm,
3211
+ compressed,
3212
+ previous = null,
3213
+ tier = 0,
3214
+ programs,
3215
+ more = [],
3216
+ extra = [],
3217
+ }) => {
2177
3218
  // Its lock's end, when the farm keeps one, is written on it.
2178
3219
  const keeps = farmKeeps(farm, { writes: Boolean(farm.lock_key) });
2179
- const [first, ...rest] = ascending([{ asset, collection, hasAuthority, tokenMetadata: tm, compressed }, ...more].map((n) => nftSeat({ player, project, id, farm, ...n, previous, programs, keeps })), (x) => x.accounts.seat);
3220
+ const [first, ...rest] = ascending(
3221
+ [{ asset, collection, hasAuthority, tokenMetadata: tm, compressed }, ...more].map(n =>
3222
+ nftSeat({ player, project, id, farm, ...n, previous, programs, keeps }),
3223
+ ),
3224
+ x => x.accounts.seat,
3225
+ );
2180
3226
  const seat = withPrograms({ ...first, kind: first.kind }, rest);
2181
- const groups = rest.flatMap((x) => seatGroup(x.accounts.seat, x));
2182
- return lootIx('stake_nft', { tier }, seat.accounts, { remaining: [...seat.proof, ...groups, ...tailOf(extra)], writable: seat.writable });
3227
+ const groups = rest.flatMap(x => seatGroup(x.accounts.seat, x));
3228
+ return lootIx('stake_nft', { tier }, seat.accounts, {
3229
+ remaining: [...seat.proof, ...groups, ...tailOf(extra)],
3230
+ writable: seat.writable,
3231
+ });
2183
3232
  };
2184
3233
 
2185
3234
  /**
@@ -2192,12 +3241,37 @@ export const stakeNftIx = ({ player, project, id, farm, asset, collection, hasAu
2192
3241
  * in the same call, one fee, one transfer of rewards: by their seats' addresses, the `i`th's roll at `nonce + 2i` and its
2193
3242
  * prize's at `nonce + 2i + 1` (`seatNonces` says which NFT has which). One still locked refuses the whole call.
2194
3243
  */
2195
- export const unstakeNftIx = ({ player, project, id, farm, asset, collection, hasAuthority = true, tokenMetadata: tm, compressed, forfeit = false, roll = null, nonce = 0n, tier, projectCollection, poolMints, items, nft, boxes = {}, programs, more = [], extra = [] }) => {
2196
- const seats = ascending([{ asset, collection, hasAuthority, tokenMetadata: tm, compressed, tier, roll }, ...more].map((n) => {
2197
- // It grows on its way out; a mission that rolls may hand it to the project.
2198
- const keeps = farmKeeps(farm, { writes: Boolean(farm.growth?.length), rolls: n.roll != null });
2199
- return { n, seat: nftSeat({ player, project, id, farm, ...n, programs, leaving: true, keeps }) };
2200
- }), (x) => x.seat.accounts.seat);
3244
+ export const unstakeNftIx = ({
3245
+ player,
3246
+ project,
3247
+ id,
3248
+ farm,
3249
+ asset,
3250
+ collection,
3251
+ hasAuthority = true,
3252
+ tokenMetadata: tm,
3253
+ compressed,
3254
+ forfeit = false,
3255
+ roll = null,
3256
+ nonce = 0n,
3257
+ tier,
3258
+ projectCollection,
3259
+ poolMints,
3260
+ items,
3261
+ nft,
3262
+ boxes = {},
3263
+ programs,
3264
+ more = [],
3265
+ extra = [],
3266
+ }) => {
3267
+ const seats = ascending(
3268
+ [{ asset, collection, hasAuthority, tokenMetadata: tm, compressed, tier, roll }, ...more].map(n => {
3269
+ // It grows on its way out; a mission that rolls may hand it to the project.
3270
+ const keeps = farmKeeps(farm, { writes: Boolean(farm.growth?.length), rolls: n.roll != null });
3271
+ return { n, seat: nftSeat({ player, project, id, farm, ...n, programs, leaving: true, keeps }) };
3272
+ }),
3273
+ x => x.seat.accounts.seat,
3274
+ );
2201
3275
  const several = seats.length > 1;
2202
3276
  // Each seat's mission: the box and the order its roll is made in, at `nonce + 2i`; its prize (its tier's) after its
2203
3277
  // proof, a roll's order at `nonce + 2i + 1`. A forfeit takes neither the prize nor the rewards (the program reads
@@ -2206,25 +3280,64 @@ export const unstakeNftIx = ({ player, project, id, farm, asset, collection, has
2206
3280
  const parts = seats.map(({ n, seat }, i) => {
2207
3281
  const at = BigInt.asUintN(64, toBigInt(nonce) + 2n * BigInt(i));
2208
3282
  const prize = n.tier != null && (!forfeit || several) ? farm.tiers[n.tier]?.prize : null;
2209
- const prized = prize ? prizeMetas({ project, collection: projectCollection ?? n.collection, prize, winner: player, nonce: BigInt.asUintN(64, at + 1n), poolMints, items, nft, programs }) : [];
2210
- const rolls = n.roll != null ? { box: pda.box(project, n.roll), order: pda.order(project, player, namedRollNonce(boxes[n.roll], seat.accounts.asset, at)) } : { box: null, order: null };
3283
+ const prized = prize
3284
+ ? prizeMetas({
3285
+ project,
3286
+ collection: projectCollection ?? n.collection,
3287
+ prize,
3288
+ winner: player,
3289
+ nonce: BigInt.asUintN(64, at + 1n),
3290
+ poolMints,
3291
+ items,
3292
+ nft,
3293
+ programs,
3294
+ })
3295
+ : [];
3296
+ const rolls =
3297
+ n.roll != null
3298
+ ? {
3299
+ box: pda.box(project, n.roll),
3300
+ order: pda.order(project, player, namedRollNonce(boxes[n.roll], seat.accounts.asset, at)),
3301
+ }
3302
+ : { box: null, order: null };
2211
3303
  return { seat, prize, prized, rolls };
2212
3304
  });
2213
3305
  const [first, ...rest] = parts;
2214
- const seat = withPrograms(first.seat, rest.map((x) => x.seat));
3306
+ const seat = withPrograms(
3307
+ first.seat,
3308
+ rest.map(x => x.seat),
3309
+ );
2215
3310
  if (forfeit) seat.accounts.player_rewards = null;
2216
- const groups = rest.flatMap((x) => [...seatGroup(x.seat.accounts.seat, x.seat, x.rolls.box, x.rolls.order), ...x.prized]);
2217
- return lootIx('unstake_nft', { forfeit, nonce }, { ...seat.accounts, loot_box: first.rolls.box, order: first.rolls.order, associated_token_program: parts.some((x) => x.prize?.Tokens) ? ASSOCIATED_TOKEN_PROGRAM : null }, {
2218
- remaining: [...seat.proof, ...first.prized, ...groups, ...tailOf(extra)],
2219
- writable: [...(forfeit ? [] : payWrites(farm)), ...seat.writable],
2220
- });
3311
+ const groups = rest.flatMap(x => [
3312
+ ...seatGroup(x.seat.accounts.seat, x.seat, x.rolls.box, x.rolls.order),
3313
+ ...x.prized,
3314
+ ]);
3315
+ return lootIx(
3316
+ 'unstake_nft',
3317
+ { forfeit, nonce },
3318
+ {
3319
+ ...seat.accounts,
3320
+ loot_box: first.rolls.box,
3321
+ order: first.rolls.order,
3322
+ associated_token_program: parts.some(x => x.prize?.Tokens) ? ASSOCIATED_TOKEN_PROGRAM : null,
3323
+ },
3324
+ {
3325
+ remaining: [...seat.proof, ...first.prized, ...groups, ...tailOf(extra)],
3326
+ writable: [...(forfeit ? [] : payWrites(farm)), ...seat.writable],
3327
+ },
3328
+ );
2221
3329
  };
2222
3330
 
2223
3331
  /** The nonce each NFT's mission roll is named by in an `unstakeNftIx` of several (`assets`), in the order the call
2224
3332
  * takes them: the `i`th by its seat's address `nonce + 2i` (its order at `namedRollNonce` of it), its prize's roll
2225
3333
  * `nonce + 2i + 1` (at `prizeRollNonce`). */
2226
3334
  export const seatNonces = (project, id, farm, player, assets, nonce = 0n) =>
2227
- Object.fromEntries(ascending(assets, (a) => pda.seat(pda.farm(project, id), farm.farmed.Tokens ? player : a)).map((a, i) => [a, toBigInt(nonce) + 2n * BigInt(i)]));
3335
+ Object.fromEntries(
3336
+ ascending(assets, a => pda.seat(pda.farm(project, id), farm.farmed.Tokens ? player : a)).map((a, i) => [
3337
+ a,
3338
+ toBigInt(nonce) + 2n * BigInt(i),
3339
+ ]),
3340
+ );
2228
3341
 
2229
3342
  /**
2230
3343
  * `player` collects what the seat of `by` owes (their own address for tokens, a collectible's asset); with `compound`, a
@@ -2234,20 +3347,44 @@ export const seatNonces = (project, id, farm, player, assets, nonce = 0n) =>
2234
3347
  * (`NotProven`). `more` seats of the player's in the farm (each { by, held, tree }) collect in the same call, one fee,
2235
3348
  * one transfer.
2236
3349
  */
2237
- export const harvestIx = ({ player, project, id, farm, by = player, held = null, tree = null, compound = false, programs, more = [], extra = [] }) => {
3350
+ export const harvestIx = ({
3351
+ player,
3352
+ project,
3353
+ id,
3354
+ farm,
3355
+ by = player,
3356
+ held = null,
3357
+ tree = null,
3358
+ compound = false,
3359
+ programs,
3360
+ more = [],
3361
+ extra = [],
3362
+ }) => {
2238
3363
  const farmAddress = pda.farm(project, id);
2239
- const [first, ...rest] = ascending([{ by, held, tree }, ...more].map((x) => ({ ...x, seat: pda.seat(farmAddress, x.by) })), (x) => x.seat);
2240
- const groups = rest.flatMap((x) => [meta(x.seat, false, true), meta(x.held ?? LOOT_PROGRAM, false, false), meta(x.tree ?? LOOT_PROGRAM, false, false)]);
2241
- return lootIx('harvest', { compound }, {
2242
- ...farmSide(project, id, farm),
2243
- player,
2244
- seat: first.seat,
2245
- player_rewards: ata(player, farm.rewards_mint, tokenProgramOf(programs, farm.rewards_mint)),
2246
- held: first.held,
2247
- tree: first.tree,
2248
- // Compounding moves what is owed into the staked vault (a farm of tokens whose rewards are the staked token).
2249
- staked_vault: compound ? pda.staked(farmAddress) : null,
2250
- }, { remaining: [...groups, ...tailOf(extra)], writable: payWrites(farm) });
3364
+ const [first, ...rest] = ascending(
3365
+ [{ by, held, tree }, ...more].map(x => ({ ...x, seat: pda.seat(farmAddress, x.by) })),
3366
+ x => x.seat,
3367
+ );
3368
+ const groups = rest.flatMap(x => [
3369
+ meta(x.seat, false, true),
3370
+ meta(x.held ?? LOOT_PROGRAM, false, false),
3371
+ meta(x.tree ?? LOOT_PROGRAM, false, false),
3372
+ ]);
3373
+ return lootIx(
3374
+ 'harvest',
3375
+ { compound },
3376
+ {
3377
+ ...farmSide(project, id, farm),
3378
+ player,
3379
+ seat: first.seat,
3380
+ player_rewards: ata(player, farm.rewards_mint, tokenProgramOf(programs, farm.rewards_mint)),
3381
+ held: first.held,
3382
+ tree: first.tree,
3383
+ // Compounding moves what is owed into the staked vault (a farm of tokens whose rewards are the staked token).
3384
+ staked_vault: compound ? pda.staked(farmAddress) : null,
3385
+ },
3386
+ { remaining: [...groups, ...tailOf(extra)], writable: payWrites(farm) },
3387
+ );
2251
3388
  };
2252
3389
 
2253
3390
  /**
@@ -2258,13 +3395,49 @@ export const harvestIx = ({ player, project, id, farm, by = player, held = null,
2258
3395
  * NFT's token account that holds it, `holder`'s (its staker's, or a watched NFT's buyer's). `signer` pays for what the
2259
3396
  * write adds. `extra`: the reward mint's hook accounts.
2260
3397
  */
2261
- export const refreshSeatIx = ({ signer, project, id, farm, by, seat, collection, hasAuthority = true, tokenMetadata: tm, compressed, holder = seat.player, extra = [] }) => {
3398
+ export const refreshSeatIx = ({
3399
+ signer,
3400
+ project,
3401
+ id,
3402
+ farm,
3403
+ by,
3404
+ seat,
3405
+ collection,
3406
+ hasAuthority = true,
3407
+ tokenMetadata: tm,
3408
+ compressed,
3409
+ holder = seat.player,
3410
+ extra = [],
3411
+ }) => {
2262
3412
  const address = pda.seat(pda.farm(project, id), by);
2263
3413
  // A token seat with no booster: its own address stands in for the NFT.
2264
- const p = seat.asset === NO_ADDRESS
2265
- ? standIn(address)
2266
- : piece({ project, asset: seat.asset, wallet: holder, collection, hasAuthority, tokenMetadata: tm, compressed, ...farmKeeps(farm, { writes: Boolean(farm.growth?.length) }), moves: false, locks: false });
2267
- return lootIx('refresh_seat', {}, { signer, ...farmSide(project, id, farm), seat: address, ...p.accounts, profile: profileOf(project, farm, seat.player) }, { remaining: tailOf(extra), writable: p.writable });
3414
+ const p =
3415
+ seat.asset === NO_ADDRESS
3416
+ ? standIn(address)
3417
+ : piece({
3418
+ project,
3419
+ asset: seat.asset,
3420
+ wallet: holder,
3421
+ collection,
3422
+ hasAuthority,
3423
+ tokenMetadata: tm,
3424
+ compressed,
3425
+ ...farmKeeps(farm, { writes: Boolean(farm.growth?.length) }),
3426
+ moves: false,
3427
+ locks: false,
3428
+ });
3429
+ return lootIx(
3430
+ 'refresh_seat',
3431
+ {},
3432
+ {
3433
+ signer,
3434
+ ...farmSide(project, id, farm),
3435
+ seat: address,
3436
+ ...p.accounts,
3437
+ profile: profileOf(project, farm, seat.player),
3438
+ },
3439
+ { remaining: tailOf(extra), writable: p.writable },
3440
+ );
2268
3441
  };
2269
3442
 
2270
3443
  // ---------------------------------------------------------------- recipes and holds
@@ -2278,7 +3451,9 @@ export const refreshSeatIx = ({ signer, project, id, farm, by, seat, collection,
2278
3451
  * it lowers a number: its seat and farm follow it, and the raise may not lower a number that farm reads; for a `Trade` line the NFT traded in, as `piece` takes it: `{ asset, collection, tokenMetadata,
2279
3452
  * compressed }`). A collectible used that is staked in a farm holding it for the player (escrow) or locking it in their
2280
3453
  * wallet (freeze) is `{ asset, farm }`, `farm` that farm's id: its seat and farm follow it (a raise on it may not lower a
2281
- * number its farm reads: `BadRecipe`). A `Staked` line names the farm its collectible
3454
+ * number its farm reads: `BadRecipe`). One that boosts the player's token seat in a farm of tokens is
3455
+ * `{ asset, farm, booster: true }`: the player's token seat (`["seat", farm, player]`) and that farm follow it, on a kept
3456
+ * `Use` line or a `Show` line alike. A `Staked` line names the farm its collectible
2282
3457
  * is staked in (`Input::Staked { item, farm }`, escrowed or frozen there): its address, or `{ asset, farm }` with that
2283
3458
  * farm, and its seat and that farm follow it. Each item line passes its item's collection
2284
3459
  * (`items[id].collection`, the decoded Items; `collection` when not given), written for a burn: Core writes it as it
@@ -2289,35 +3464,85 @@ export const refreshSeatIx = ({ signer, project, id, farm, by, seat, collection,
2289
3464
  * (`askRollIx`, in the same transaction when it fits).
2290
3465
  * A craft makes several Core calls: raise the compute limit. `extra`: a token's hook accounts.
2291
3466
  */
2292
- export function craftIx({ player, project, collection, id, recipe, assets = [], times = 1, nonce = 0n, holders, programs, treasury, poolMints, items = {}, nfts = {}, boxes = {}, extra = [] }) {
3467
+ export function craftIx({
3468
+ player,
3469
+ project,
3470
+ collection,
3471
+ id,
3472
+ recipe,
3473
+ assets = [],
3474
+ times = 1,
3475
+ nonce = 0n,
3476
+ holders,
3477
+ programs,
3478
+ treasury,
3479
+ poolMints,
3480
+ items = {},
3481
+ nfts = {},
3482
+ boxes = {},
3483
+ extra = [],
3484
+ }) {
2293
3485
  const remaining = [];
2294
3486
  const given = [...assets];
2295
- const tokens = (mint) => ata(player, mint, tokenProgramOf(programs, mint));
2296
- const itemLine = (itemId, burns = false) => [meta(pda.item(project, itemId), false, false), meta(items[itemId]?.collection ?? collection, false, burns)];
3487
+ const tokens = mint => ata(player, mint, tokenProgramOf(programs, mint));
3488
+ const itemLine = (itemId, burns = false) => [
3489
+ meta(pda.item(project, itemId), false, false),
3490
+ meta(items[itemId]?.collection ?? collection, false, burns),
3491
+ ];
2297
3492
  for (const input of recipe.inputs) {
2298
3493
  if (input.Burn) {
2299
3494
  remaining.push(...itemLine(input.Burn.item, true));
2300
- for (let i = 0; i < input.Burn.count; i++) remaining.push(meta(take(given, 'a collectible to burn'), false, true));
3495
+ for (let i = 0; i < input.Burn.count; i++)
3496
+ remaining.push(meta(take(given, 'a collectible to burn'), false, true));
2301
3497
  } else if (input.Use || input.Staked) {
2302
3498
  remaining.push(...itemLine((input.Use ?? input.Staked).item));
2303
3499
  const used = take(given, 'a collectible to use');
2304
3500
  // A staked one is on its seat in the farm its line names.
2305
- if (input.Staked && typeof used !== 'string' && used.farm !== undefined && Number(used.farm) !== input.Staked.farm)
2306
- throw new Error(`craft: the recipe takes this collectible staked in farm ${input.Staked.farm}, not farm ${used.farm}`);
2307
- const seated = input.Staked ? { asset: typeof used === 'string' ? used : used.asset, farm: input.Staked.farm } : used;
3501
+ if (
3502
+ input.Staked &&
3503
+ typeof used !== 'string' &&
3504
+ used.farm !== undefined &&
3505
+ Number(used.farm) !== input.Staked.farm
3506
+ )
3507
+ throw new Error(
3508
+ `craft: the recipe takes this collectible staked in farm ${input.Staked.farm}, not farm ${used.farm}`,
3509
+ );
3510
+ // A booster of a token seat is on the player's seat, not one of its own: a `Staked` line refuses it (NotFarmed).
3511
+ if (input.Staked && used?.booster)
3512
+ throw new Error(
3513
+ `craft: the recipe takes a collectible staked on a seat of its own in farm ${input.Staked.farm}; one boosting a token seat is taken by a kept line ("use")`,
3514
+ );
3515
+ const seated = input.Staked
3516
+ ? { asset: typeof used === 'string' ? used : used.asset, farm: input.Staked.farm }
3517
+ : used;
2308
3518
  if (typeof seated === 'string') remaining.push(meta(seated, false, true));
2309
3519
  else {
2310
- // On a seat: its seat and its farm follow it.
3520
+ // On a seat: its seat (its own, or the player's token seat it boosts) and its farm follow it.
2311
3521
  const farm = pda.farm(project, seated.farm);
2312
- remaining.push(meta(seated.asset, false, true), meta(pda.seat(farm, seated.asset), false, false), meta(farm, false, false));
3522
+ remaining.push(
3523
+ meta(seated.asset, false, true),
3524
+ meta(pda.seat(farm, seated.booster ? player : seated.asset), false, false),
3525
+ meta(farm, false, false),
3526
+ );
2313
3527
  }
2314
3528
  } else if (input.Pay) {
2315
3529
  const { mint, to } = input.Pay;
2316
- remaining.push(meta(mint, false, true), meta(tokens(mint), false, true), ...destinationAccounts(project, to, mint, { holders, programs, treasury }));
3530
+ remaining.push(
3531
+ meta(mint, false, true),
3532
+ meta(tokens(mint), false, true),
3533
+ ...destinationAccounts(project, to, mint, { holders, programs, treasury }),
3534
+ );
2317
3535
  } else if (input.Trade) {
2318
3536
  // Any NFT of the line's collection, traded in: burned, given on or stashed (`nftPaid`).
2319
3537
  const nft = take(given, 'an NFT to trade in');
2320
- remaining.push(...nftPaid({ project, owner: player, then: input.Trade.then, ...(typeof nft === 'string' ? { asset: nft, collection: input.Trade.collection } : nft) }));
3538
+ remaining.push(
3539
+ ...nftPaid({
3540
+ project,
3541
+ owner: player,
3542
+ then: input.Trade.then,
3543
+ ...(typeof nft === 'string' ? { asset: nft, collection: input.Trade.collection } : nft),
3544
+ }),
3545
+ );
2321
3546
  } else if (input.Show) {
2322
3547
  // Any NFT, kept: one of the project's (its address), a Core asset of another collection ({ asset, collection }:
2323
3548
  // with its Sheet, then its collection), a Token Metadata NFT ({ mint, owner }: its metadata, the holder's
@@ -2327,14 +3552,33 @@ export function craftIx({ player, project, collection, id, recipe, assets = [],
2327
3552
  // seat and farm after its accounts, when a raise on it lowers a number.
2328
3553
  const place = assets.length - given.length;
2329
3554
  const nft = take(given, 'an NFT to show');
2330
- if (typeof nft === 'string' || (nft.asset && nft.farm !== undefined && !('collection' in nft))) remaining.push(meta(nft.asset ?? nft, false, true), meta(input.Show.collection, false, false));
2331
- else if (nft.asset) remaining.push(meta(nft.asset, false, true), meta(pda.sheet(project, nft.asset), false, true), meta(nft.collection ?? MPL_CORE_PROGRAM, false, false));
2332
- else if (nft.compressed) remaining.push(...[nft.compressed, pda.leaf(nft.compressed), nft.tree].map((key) => meta(key, false, false)), meta(pda.sheet(project, nft.compressed), false, true));
2333
- else remaining.push(...[nft.mint, tokenMetadata.metadata(nft.mint), ata(nft.owner ?? player, nft.mint, TOKEN_PROGRAM)].map((key) => meta(key, false, false)), meta(pda.sheet(project, nft.mint), false, true));
2334
- const lowers = (recipe.raises ?? []).some((raise) => Number(raise.place) === place && BigInt(raise.shift.by) < 0n);
3555
+ if (typeof nft === 'string' || (nft.asset && nft.farm !== undefined && !('collection' in nft)))
3556
+ remaining.push(meta(nft.asset ?? nft, false, true), meta(input.Show.collection, false, false));
3557
+ else if (nft.asset)
3558
+ remaining.push(
3559
+ meta(nft.asset, false, true),
3560
+ meta(pda.sheet(project, nft.asset), false, true),
3561
+ meta(nft.collection ?? MPL_CORE_PROGRAM, false, false),
3562
+ );
3563
+ else if (nft.compressed)
3564
+ remaining.push(
3565
+ ...[nft.compressed, pda.leaf(nft.compressed), nft.tree].map(key => meta(key, false, false)),
3566
+ meta(pda.sheet(project, nft.compressed), false, true),
3567
+ );
3568
+ else
3569
+ remaining.push(
3570
+ ...[nft.mint, tokenMetadata.metadata(nft.mint), ata(nft.owner ?? player, nft.mint, TOKEN_PROGRAM)].map(key =>
3571
+ meta(key, false, false),
3572
+ ),
3573
+ meta(pda.sheet(project, nft.mint), false, true),
3574
+ );
3575
+ const lowers = (recipe.raises ?? []).some(raise => Number(raise.place) === place && BigInt(raise.shift.by) < 0n);
2335
3576
  if (lowers && typeof nft !== 'string' && nft.farm !== undefined && nft.farm !== null) {
2336
3577
  const farm = pda.farm(project, nft.farm);
2337
- remaining.push(meta(pda.seat(farm, nft.asset ?? nft.compressed ?? nft.mint), false, false), meta(farm, false, false));
3578
+ remaining.push(
3579
+ meta(pda.seat(farm, nft.booster ? player : (nft.asset ?? nft.compressed ?? nft.mint)), false, false),
3580
+ meta(farm, false, false),
3581
+ );
2338
3582
  }
2339
3583
  }
2340
3584
  }
@@ -2344,18 +3588,44 @@ export function craftIx({ player, project, collection, id, recipe, assets = [],
2344
3588
  // A prize (`prizeMetas`): collectibles of one item made in order, each at the next serial.
2345
3589
  const prize = output.Prize[0];
2346
3590
  const item = prize.Item?.[0];
2347
- remaining.push(...prizeMetas({ project, collection, prize, winner: player, nonce, poolMints, items, nft: nfts[prize.Nft?.nft], made: made[item] ?? 0, programs }));
3591
+ remaining.push(
3592
+ ...prizeMetas({
3593
+ project,
3594
+ collection,
3595
+ prize,
3596
+ winner: player,
3597
+ nonce,
3598
+ poolMints,
3599
+ items,
3600
+ nft: nfts[prize.Nft?.nft],
3601
+ made: made[item] ?? 0,
3602
+ programs,
3603
+ }),
3604
+ );
2348
3605
  if (item !== undefined) made[item] = (made[item] ?? 0) + 1;
2349
3606
  } else if (output.RollOn) {
2350
3607
  // The box, then the order its roll is made in (`namedRollNonce` of the craft's `nonce`).
2351
3608
  const on = assets[output.RollOn.place];
2352
- const order = pda.order(project, player, namedRollNonce(boxes[output.RollOn.box_id], typeof on === 'string' ? on : on?.asset ?? on?.mint ?? on?.compressed, nonce));
3609
+ const order = pda.order(
3610
+ project,
3611
+ player,
3612
+ namedRollNonce(
3613
+ boxes[output.RollOn.box_id],
3614
+ typeof on === 'string' ? on : (on?.asset ?? on?.mint ?? on?.compressed),
3615
+ nonce,
3616
+ ),
3617
+ );
2353
3618
  remaining.push(meta(pda.box(project, output.RollOn.box_id), false, false), meta(order, false, true));
2354
3619
  }
2355
3620
  }
2356
3621
  const address = pda.recipe(project, id);
2357
3622
  const counts = limits(recipe.per_wallet) ? pda.counts(project, player) : null;
2358
- return lootIx('craft', { version: recipe.version, times, nonce }, { player, project, recipe: address, counts }, { remaining: [...remaining, ...tailOf(extra)] });
3623
+ return lootIx(
3624
+ 'craft',
3625
+ { version: recipe.version, times, nonce },
3626
+ { player, project, recipe: address, counts },
3627
+ { remaining: [...remaining, ...tailOf(extra)] },
3628
+ );
2359
3629
  }
2360
3630
 
2361
3631
  function take(list, what) {
@@ -2364,23 +3634,52 @@ function take(list, what) {
2364
3634
  }
2365
3635
 
2366
3636
  /** The collectibles a craft of `recipe` makes: one per `Item` prize among its outputs. */
2367
- export const craftNewAssets = (recipe) => recipe.outputs.filter((o) => o.Prize?.[0]?.Item).length;
3637
+ export const craftNewAssets = recipe => recipe.outputs.filter(o => o.Prize?.[0]?.Item).length;
2368
3638
 
2369
3639
  /**
2370
3640
  * `player` hands an NFT to the project until `freeAt` (Unix seconds; the hold's address names it, `pda.hold`): a Core
2371
3641
  * asset (`collection` its own, Core's address for none), a Token Metadata NFT (`tokenMetadata`), or `compressed` (after
2372
3642
  * a `proveLeafIx`).
2373
3643
  */
2374
- export function holdIx({ player, project, collection, asset, tokenMetadata: tm, compressed, freeAt, reference: ref, more = [] }) {
3644
+ export function holdIx({
3645
+ player,
3646
+ project,
3647
+ collection,
3648
+ asset,
3649
+ tokenMetadata: tm,
3650
+ compressed,
3651
+ freeAt,
3652
+ reference: ref,
3653
+ more = [],
3654
+ }) {
2375
3655
  // Several NFTs (`more`: each { asset, collection, tokenMetadata, compressed }) go on holds of their own with one end, one
2376
3656
  // fee: by their holds' addresses, the first the call's own.
2377
- const [first, ...rest] = ascending([{ asset, collection, tokenMetadata: tm, compressed }, ...more].map((n) => {
2378
- const p = piece({ project, asset: n.asset, wallet: player, collection: n.collection, tokenMetadata: n.tokenMetadata, compressed: n.compressed, ...ONLY_MOVES });
2379
- return { p, hold: pda.hold(p.accounts.asset, freeAt) };
2380
- }), (x) => x.hold);
2381
- const p = withPrograms(first.p, rest.map((x) => x.p));
2382
- const groups = rest.flatMap((x) => [meta(x.hold, false, true), ...anotherMetas(x.p)]);
2383
- return lootIx('hold', { free_at: toBigInt(freeAt), reference: reference(ref) }, { player, project, ...p.accounts, hold: first.hold }, { remaining: [...p.proof, ...groups], writable: p.writable });
3657
+ const [first, ...rest] = ascending(
3658
+ [{ asset, collection, tokenMetadata: tm, compressed }, ...more].map(n => {
3659
+ const p = piece({
3660
+ project,
3661
+ asset: n.asset,
3662
+ wallet: player,
3663
+ collection: n.collection,
3664
+ tokenMetadata: n.tokenMetadata,
3665
+ compressed: n.compressed,
3666
+ ...ONLY_MOVES,
3667
+ });
3668
+ return { p, hold: pda.hold(p.accounts.asset, freeAt) };
3669
+ }),
3670
+ x => x.hold,
3671
+ );
3672
+ const p = withPrograms(
3673
+ first.p,
3674
+ rest.map(x => x.p),
3675
+ );
3676
+ const groups = rest.flatMap(x => [meta(x.hold, false, true), ...anotherMetas(x.p)]);
3677
+ return lootIx(
3678
+ 'hold',
3679
+ { free_at: toBigInt(freeAt), reference: reference(ref) },
3680
+ { player, project, ...p.accounts, hold: first.hold },
3681
+ { remaining: [...p.proof, ...groups], writable: p.writable },
3682
+ );
2384
3683
  }
2385
3684
 
2386
3685
  // ---------------------------------------------------------------- stashes
@@ -2393,7 +3692,21 @@ export function holdIx({ player, project, collection, asset, tokenMetadata: tm,
2393
3692
  * treasury in its hold (`["hold", nft, 0]`, the signer paying its rent until anyone `release`s it to the treasury, one
2394
3693
  * NFT a call). `out` is never the project's own address nor another account of the program (`ToTheProject`).
2395
3694
  */
2396
- export function stashIx({ signer, project, id, lottery = null, lotteryState = null, asset, out = null, drop = false, weight = 1, collection, tokenMetadata: tm, compressed, more = [] }) {
3695
+ export function stashIx({
3696
+ signer,
3697
+ project,
3698
+ id,
3699
+ lottery = null,
3700
+ lotteryState = null,
3701
+ asset,
3702
+ out = null,
3703
+ drop = false,
3704
+ weight = 1,
3705
+ collection,
3706
+ tokenMetadata: tm,
3707
+ compressed,
3708
+ more = [],
3709
+ }) {
2397
3710
  if (drop && more.length) throw new Error('A stash drops one NFT a call.');
2398
3711
  if (drop) out = pda.hold(compressed?.id ?? asset, 0);
2399
3712
  const to = out ?? signer;
@@ -2405,18 +3718,40 @@ export function stashIx({ signer, project, id, lottery = null, lotteryState = nu
2405
3718
  // box), where no record names the NFT and nothing could move it again (ToTheProject): the ones this call names are
2406
3719
  // refused before sending, and the program refuses any other (js/play only takes one out to the signer's wallet).
2407
3720
  if (out && !drop && [project, stash, own, pda.fees(project), pda.platform()].includes(out))
2408
- throw new Error('An NFT taken out of a stash can’t go to the project or an account of the program: name a wallet, or drop it (`drop`: it stays, owed to the treasury).');
3721
+ throw new Error(
3722
+ 'An NFT taken out of a stash can’t go to the project or an account of the program: name a wallet, or drop it (`drop`: it stays, owed to the treasury).',
3723
+ );
2409
3724
  // Several NFTs (`more`: each { asset, weight, collection, tokenMetadata, compressed }) go in or out in one call, one
2410
3725
  // fee, in ascending order of their addresses; put in, each weighs its `weight` in the stash's draws.
2411
- const [first, ...rest] = ascending([{ asset, weight, collection, tokenMetadata: tm, compressed }, ...more].map((n) => ({
2412
- n, p: piece({ project, asset: n.asset, wallet: drop ? project : to, collection: n.collection, tokenMetadata: n.tokenMetadata, compressed: n.compressed, ...ONLY_MOVES }),
2413
- })), (x) => x.p.accounts.asset);
2414
- const p = withPrograms(first.p, rest.map((x) => x.p));
2415
- const weights = out ? [] : [first, ...rest].map((x) => x.n.weight ?? 1);
3726
+ const [first, ...rest] = ascending(
3727
+ [{ asset, weight, collection, tokenMetadata: tm, compressed }, ...more].map(n => ({
3728
+ n,
3729
+ p: piece({
3730
+ project,
3731
+ asset: n.asset,
3732
+ wallet: drop ? project : to,
3733
+ collection: n.collection,
3734
+ tokenMetadata: n.tokenMetadata,
3735
+ compressed: n.compressed,
3736
+ ...ONLY_MOVES,
3737
+ }),
3738
+ })),
3739
+ x => x.p.accounts.asset,
3740
+ );
3741
+ const p = withPrograms(
3742
+ first.p,
3743
+ rest.map(x => x.p),
3744
+ );
3745
+ const weights = out ? [] : [first, ...rest].map(x => x.n.weight ?? 1);
2416
3746
  // A lottery's own stash names the lottery, and the round it waits on (`idleRound`; `lotteryState`: the decoded
2417
3747
  // Lottery; none for one never written), whose places say what it locks.
2418
3748
  const round = own && lotteryState ? idleRound(own, lotteryState) : null;
2419
- return lootIx('stash', { of: own ? { Lottery: [lottery] } : { Project: [id] }, out: Boolean(out), weights }, { signer, project, stash, to, ...p.accounts, lottery: own, round }, { remaining: [...p.proof, ...rest.flatMap((x) => anotherMetas(x.p))], writable: p.writable });
3749
+ return lootIx(
3750
+ 'stash',
3751
+ { of: own ? { Lottery: [lottery] } : { Project: [id] }, out: Boolean(out), weights },
3752
+ { signer, project, stash, to, ...p.accounts, lottery: own, round },
3753
+ { remaining: [...p.proof, ...rest.flatMap(x => anotherMetas(x.p))], writable: p.writable },
3754
+ );
2420
3755
  }
2421
3756
 
2422
3757
  // ---------------------------------------------------------------- contests
@@ -2427,7 +3762,20 @@ export function stashIx({ signer, project, id, lottery = null, lotteryState = nu
2427
3762
  * compressed }, as `piece` takes it), which the contest holds until it is claimed. `shown`/`listProof` pass its gate.
2428
3763
  * A new entry's record is the player's to pay (not refunded). `extra`: a hook's accounts.
2429
3764
  */
2430
- export function enterIx({ player, project, id, contest, side, amount, shown, listProof, nft, playerToken, programs, extra = [] }) {
3765
+ export function enterIx({
3766
+ player,
3767
+ project,
3768
+ id,
3769
+ contest,
3770
+ side,
3771
+ amount,
3772
+ shown,
3773
+ listProof,
3774
+ nft,
3775
+ playerToken,
3776
+ programs,
3777
+ extra = [],
3778
+ }) {
2431
3779
  const address = pda.contest(project, id);
2432
3780
  // Read for its rule (with its Sheet, for an NFT the project has no authority over), then handed to the project.
2433
3781
  const staked = contest.nft ? piece({ project, wallet: player, ...nft, ...ONLY_MOVES, reads: true }) : null;
@@ -2442,7 +3790,13 @@ export function enterIx({ player, project, id, contest, side, amount, shown, lis
2442
3790
  player_token: playerToken ?? ata(player, contest.mint, tokenProgramOf(programs, contest.mint)),
2443
3791
  mint: contest.mint,
2444
3792
  },
2445
- { remaining: [...gateAccounts(project, { gate: contest.gate, wallet: player, shown, listProof }), ...(staked ? [...pieceMetas(staked), ...staked.proof] : []), ...tailOf(extra)] },
3793
+ {
3794
+ remaining: [
3795
+ ...gateAccounts(project, { gate: contest.gate, wallet: player, shown, listProof }),
3796
+ ...(staked ? [...pieceMetas(staked), ...staked.proof] : []),
3797
+ ...tailOf(extra),
3798
+ ],
3799
+ },
2446
3800
  );
2447
3801
  }
2448
3802
 
@@ -2459,12 +3813,33 @@ export function enterIx({ player, project, id, contest, side, amount, shown, lis
2459
3813
  * closes the contest (`projectState`: its treasury); a contest that owes nothing (nobody entered, every part claimed,
2460
3814
  * off by itself with none in) is closed by any claim, the `payer`'s. `extra`: a hook's accounts.
2461
3815
  */
2462
- export function claimIx({ payer, project, projectState, id, contest, entries, player, nfts, kinds = {}, nonce, collection, poolMints, items, nft, prizeMint, forgo = false, end = false, hold = false, programs, extra = [] }) {
3816
+ export function claimIx({
3817
+ payer,
3818
+ project,
3819
+ projectState,
3820
+ id,
3821
+ contest,
3822
+ entries,
3823
+ player,
3824
+ nfts,
3825
+ kinds = {},
3826
+ nonce,
3827
+ collection,
3828
+ poolMints,
3829
+ items,
3830
+ nft,
3831
+ prizeMint,
3832
+ forgo = false,
3833
+ end = false,
3834
+ hold = false,
3835
+ programs,
3836
+ extra = [],
3837
+ }) {
2463
3838
  const address = pda.contest(project, id);
2464
3839
  // A contest that owes nothing (nobody entered, or every part claimed) is only closed: by any claim, entry 0, any player.
2465
3840
  const closing = BigInt(contest.owed) === 0n;
2466
3841
  player ??= payer;
2467
- const i = closing ? 0 : entries.findIndex((e) => e.player === player);
3842
+ const i = closing ? 0 : entries.findIndex(e => e.player === player);
2468
3843
  if (i < 0) throw new Error(`${player} has no entry in this contest.`);
2469
3844
  const me = closing ? { side: 0, bits: CLAIMED | ENDED, amount: 0n, taken: 0 } : entries[i];
2470
3845
  nonce = toBigInt(nonce ?? prizeNonce(address, player));
@@ -2473,8 +3848,23 @@ export function claimIx({ payer, project, projectState, id, contest, entries, pl
2473
3848
  // A winner's first claim of a contest with a prize: the prize's accounts, after the NFTs'. A split prize is paid from
2474
3849
  // the contest's vault (named accounts below), not given as a prize line.
2475
3850
  const split = Boolean(contest.prize_split);
2476
- const prize = contest.prize != null && first && !split && !forgo ? prizeMetas({ project, collection, prize: contest.prize, winner: player, nonce, poolMints, items, nft, programs }) : [];
2477
- const pMint = split ? splitMint(contest.prize, prizeMint ?? poolMints?.[contest.prize?.Tokens?.from.Pool?.[0]]) : null;
3851
+ const prize =
3852
+ contest.prize != null && first && !split && !forgo
3853
+ ? prizeMetas({
3854
+ project,
3855
+ collection,
3856
+ prize: contest.prize,
3857
+ winner: player,
3858
+ nonce,
3859
+ poolMints,
3860
+ items,
3861
+ nft,
3862
+ programs,
3863
+ })
3864
+ : [];
3865
+ const pMint = split
3866
+ ? splitMint(contest.prize, prizeMint ?? poolMints?.[contest.prize?.Tokens?.from.Pool?.[0]])
3867
+ : null;
2478
3868
  nfts ??= !closing && contest.nft && Number(me.amount) === 1 ? [i] : [];
2479
3869
  const moved = [];
2480
3870
  let taken = Number(me.taken);
@@ -2482,7 +3872,8 @@ export function claimIx({ payer, project, projectState, id, contest, entries, pl
2482
3872
  const theirs = entries[j];
2483
3873
  let to = contest.opener;
2484
3874
  if (contest.verdict !== 'Won' || Number(theirs.side) === Number(contest.winner)) to = theirs.player;
2485
- else if (won && payer === player && !(me.bits & ENDED) && taken < shareOf(contest, me)) [to, taken] = [player, taken + 1];
3875
+ else if (won && payer === player && !(me.bits & ENDED) && taken < shareOf(contest, me))
3876
+ [to, taken] = [player, taken + 1];
2486
3877
  moved.push(meta(theirs.player, false, true));
2487
3878
  if (holds(hold, theirs.nft)) moved.push(meta(pda.hold(theirs.nft, 0), false, true));
2488
3879
  else {
@@ -2492,9 +3883,14 @@ export function claimIx({ payer, project, projectState, id, contest, entries, pl
2492
3883
  }
2493
3884
  // What it leaves owed: at 0 it closes the contest, burning what its pot (and a split prize's vault) still hold.
2494
3885
  const owed = BigInt(contest.owed);
2495
- const left = closing ? 0n : !contest.nft ? owed - (contest.verdict === 'Won' ? (first ? BigInt(me.amount) : 0n) : 1n) : owed - BigInt(nfts.length) - (first && contest.prize ? 1n : 0n);
3886
+ const left = closing
3887
+ ? 0n
3888
+ : !contest.nft
3889
+ ? owed - (contest.verdict === 'Won' ? (first ? BigInt(me.amount) : 0n) : 1n)
3890
+ : owed - BigInt(nfts.length) - (first && contest.prize ? 1n : 0n);
2496
3891
  // The claim that leaves nothing owed closes the contest, its rent and what is left to the treasury: it names it.
2497
- if (left <= 0n && !projectState?.treasury) throw new Error('This claim closes the contest: pass projectState, for its treasury.');
3892
+ if (left <= 0n && !projectState?.treasury)
3893
+ throw new Error('This claim closes the contest: pass projectState, for its treasury.');
2498
3894
  return lootIx(
2499
3895
  'claim',
2500
3896
  { entry: i, nonce, forgo, end, nfts },
@@ -2509,12 +3905,16 @@ export function claimIx({ payer, project, projectState, id, contest, entries, pl
2509
3905
  mint: contest.mint,
2510
3906
  prize_mint: pMint,
2511
3907
  // A split prize from a pool: what its vault holds at the close goes back to that pool (`WrongPool` without it).
2512
- prize_pool: split && contest.prize?.Tokens?.from?.Pool ? pda.pool(project, contest.prize.Tokens.from.Pool[0]) : null,
3908
+ prize_pool:
3909
+ split && contest.prize?.Tokens?.from?.Pool ? pda.pool(project, contest.prize.Tokens.from.Pool[0]) : null,
2513
3910
  prize_vault: split ? pda.prizeVault(address) : null,
2514
3911
  prize_to: split ? ata(player, pMint, tokenProgramOf(programs, pMint)) : null,
2515
3912
  treasury: projectState?.treasury ?? null,
2516
3913
  },
2517
- { writable: left <= 0n ? ['mint', ...(split ? ['prize_mint'] : [])] : [], remaining: [...moved, ...prize, ...tailOf(extra)] },
3914
+ {
3915
+ writable: left <= 0n ? ['mint', ...(split ? ['prize_mint'] : [])] : [],
3916
+ remaining: [...moved, ...prize, ...tailOf(extra)],
3917
+ },
2518
3918
  );
2519
3919
  }
2520
3920
 
@@ -2539,14 +3939,18 @@ function recordTerms(name, record, span) {
2539
3939
  export function auctionTerms(auction) {
2540
3940
  if (auction instanceof Uint8Array) {
2541
3941
  const room = decode('Auction', auction).nft_bids ? 32 * MAX_BID_NFTS : 0;
2542
- return recordTerms('Auction', auction, (data) => [data.length - 60 - room, data.length]);
3942
+ return recordTerms('Auction', auction, data => [data.length - 60 - room, data.length]);
2543
3943
  }
2544
3944
  // Written back with no bid's NFTs, its bytes end at the bid's 60.
2545
- return recordTerms('Auction', { ...auction, gate: auction.gate ?? null, nft_bids: auction.nft_bids ?? null, held: [] }, (data) => [data.length - 60, data.length]);
3945
+ return recordTerms(
3946
+ 'Auction',
3947
+ { ...auction, gate: auction.gate ?? null, nft_bids: auction.nft_bids ?? null, held: [] },
3948
+ data => [data.length - 60, data.length],
3949
+ );
2546
3950
  }
2547
3951
 
2548
3952
  /** When bids close, as the program reckons it: `ends_at`, or `extend` seconds after the last bid when that is later. */
2549
- export const auctionEnds = (auction) => {
3953
+ export const auctionEnds = auction => {
2550
3954
  if (BigInt(auction.last_bid_at) === 0n) return BigInt(auction.ends_at);
2551
3955
  const late = BigInt(auction.last_bid_at) + BigInt(auction.extend);
2552
3956
  return late > BigInt(auction.ends_at) ? late : BigInt(auction.ends_at);
@@ -2575,11 +3979,21 @@ export function openAuctionIx({ seller, project, collection, tokenMetadata: tm,
2575
3979
  const auction = pda.auction(project, seller, id);
2576
3980
  const asset = args.lot.Asset?.[0] ?? null;
2577
3981
  // An item lot reads no NFT: the auction's own address stands in.
2578
- const p = asset ? piece({ project, asset, wallet: seller, collection, tokenMetadata: tm, compressed, ...ONLY_MOVES }) : standIn(auction);
3982
+ const p = asset
3983
+ ? piece({ project, asset, wallet: seller, collection, tokenMetadata: tm, compressed, ...ONLY_MOVES })
3984
+ : standIn(auction);
2579
3985
  return lootIx(
2580
3986
  'open_auction',
2581
3987
  { id, args: { ...args, gate: args.gate ?? null, nft_bids: args.nft_bids ?? null } },
2582
- { seller, project, auction, mint, item: args.lot.Item ? pda.item(project, args.lot.Item[0]) : null, loot_box: args.lot.Roll ? pda.box(project, args.lot.Roll[0]) : null, ...p.accounts },
3988
+ {
3989
+ seller,
3990
+ project,
3991
+ auction,
3992
+ mint,
3993
+ item: args.lot.Item ? pda.item(project, args.lot.Item[0]) : null,
3994
+ loot_box: args.lot.Roll ? pda.box(project, args.lot.Roll[0]) : null,
3995
+ ...p.accounts,
3996
+ },
2583
3997
  { remaining: p.proof, writable: p.writable },
2584
3998
  );
2585
3999
  }
@@ -2596,7 +4010,21 @@ export const MAX_BID_NFTS = 3;
2596
4010
  * (`held`, the kinds of `auction.held`, in order) back to its bidder; one that can't move now (`hold`: true for all, or a
2597
4011
  * list of assets) becomes a hold for them instead, `bidder` paying its rent until its release. `extra`: a hook's accounts.
2598
4012
  */
2599
- export function bidIx({ bidder, project, id, auction, amount, shown, listProof, nfts = [], held = [], hold = false, bidderToken, programs, extra = [] }) {
4013
+ export function bidIx({
4014
+ bidder,
4015
+ project,
4016
+ id,
4017
+ auction,
4018
+ amount,
4019
+ shown,
4020
+ listProof,
4021
+ nfts = [],
4022
+ held = [],
4023
+ hold = false,
4024
+ bidderToken,
4025
+ programs,
4026
+ extra = [],
4027
+ }) {
2600
4028
  const tokenProgram = tokenProgramOf(programs, auction.mint);
2601
4029
  const outbid = BigInt(auction.high) > 0n;
2602
4030
  const bundle = [];
@@ -2626,7 +4054,14 @@ export function bidIx({ bidder, project, id, auction, amount, shown, listProof,
2626
4054
  mint: auction.mint,
2627
4055
  },
2628
4056
  // The vault takes a bid in tokens; NFT bids leave it alone, unlocked.
2629
- { remaining: [...gateAccounts(project, { gate: auction.gate, wallet: bidder, shown, listProof }), ...bundle, ...tailOf(extra)], writable: auction.nft_bids ? [] : ['vault'] },
4057
+ {
4058
+ remaining: [
4059
+ ...gateAccounts(project, { gate: auction.gate, wallet: bidder, shown, listProof }),
4060
+ ...bundle,
4061
+ ...tailOf(extra),
4062
+ ],
4063
+ writable: auction.nft_bids ? [] : ['vault'],
4064
+ },
2630
4065
  );
2631
4066
  }
2632
4067
 
@@ -2643,7 +4078,24 @@ export function bidIx({ bidder, project, id, auction, amount, shown, listProof,
2643
4078
  * piece nor proof), and a winning NFT of a bid a hold for `proceeds` (its hold's address in its piece's place); anyone
2644
4079
  * then delivers each with `releaseIx`, and the end's sender gets the rent back then.
2645
4080
  */
2646
- export function endAuctionIx({ signer, project, projectState, id, auction, item = null, collection, tokenMetadata: tm, compressed, held = [], hold = false, reference: ref, nonce, holders, programs, extra = [] }) {
4081
+ export function endAuctionIx({
4082
+ signer,
4083
+ project,
4084
+ projectState,
4085
+ id,
4086
+ auction,
4087
+ item = null,
4088
+ collection,
4089
+ tokenMetadata: tm,
4090
+ compressed,
4091
+ held = [],
4092
+ hold = false,
4093
+ reference: ref,
4094
+ nonce,
4095
+ holders,
4096
+ programs,
4097
+ extra = [],
4098
+ }) {
2647
4099
  // The lot went at an earlier end, and its seller's account couldn't take the money then (`proceeds_owed`): this end pays
2648
4100
  // it and closes the auction, naming no lot, share or winning NFT.
2649
4101
  const owed = BigInt(auction.proceeds_owed ?? 0) > 0n;
@@ -2651,8 +4103,9 @@ export function endAuctionIx({ signer, project, projectState, id, auction, item
2651
4103
  const tokenProgram = tokenProgramOf(programs, auction.mint);
2652
4104
  const sold = BigInt(auction.high) > 0n;
2653
4105
  const itemId = owed ? undefined : auction.lot.Item?.[0];
2654
- const made = !owed && sold && item?.kind === 'Collectible' ? pda.itemAsset(pda.item(project, itemId), auction.pin) : null;
2655
- const asset = owed ? null : auction.lot.Asset?.[0] ?? made;
4106
+ const made =
4107
+ !owed && sold && item?.kind === 'Collectible' ? pda.itemAsset(pda.item(project, itemId), auction.pin) : null;
4108
+ const asset = owed ? null : (auction.lot.Asset?.[0] ?? made);
2656
4109
  const proceeds = auction.lot.Asset ? auction.seller : projectState.treasury;
2657
4110
  const address = pda.auction(project, auction.seller, id);
2658
4111
  const receiver = sold ? auction.high_bidder : auction.seller;
@@ -2665,20 +4118,24 @@ export function endAuctionIx({ signer, project, projectState, id, auction, item
2665
4118
  const p = owed
2666
4119
  ? standIn(address)
2667
4120
  : lotHeld
2668
- ? standIn(asset)
2669
- : auction.lot.Asset
2670
- ? piece({ project, asset, wallet: receiver, collection, tokenMetadata: tm, compressed, ...ONLY_MOVES })
2671
- : asset
2672
- ? { ...piece({ project, asset, collection: item.collection, moves: false, locks: false, reads: false }), writable: ['collection'] }
2673
- : standIn(address);
4121
+ ? standIn(asset)
4122
+ : auction.lot.Asset
4123
+ ? piece({ project, asset, wallet: receiver, collection, tokenMetadata: tm, compressed, ...ONLY_MOVES })
4124
+ : asset
4125
+ ? {
4126
+ ...piece({ project, asset, collection: item.collection, moves: false, locks: false, reads: false }),
4127
+ writable: ['collection'],
4128
+ }
4129
+ : standIn(address);
2674
4130
  // A winning bid of NFTs: each to where the rest of a bid in tokens would go (`held`, their kinds in order).
2675
- const winning = !owed && sold && auction.nft_bids
2676
- ? (auction.held ?? []).flatMap((nft, i) => {
2677
- if (holds(hold, nft)) return [meta(pda.hold(nft, 0), false, true)];
2678
- const q = piece({ project, asset: nft, wallet: proceeds, ...held[i], ...ONLY_MOVES });
2679
- return [...pieceMetas(q), ...q.proof];
2680
- })
2681
- : [];
4131
+ const winning =
4132
+ !owed && sold && auction.nft_bids
4133
+ ? (auction.held ?? []).flatMap((nft, i) => {
4134
+ if (holds(hold, nft)) return [meta(pda.hold(nft, 0), false, true)];
4135
+ const q = piece({ project, asset: nft, wallet: proceeds, ...held[i], ...ONLY_MOVES });
4136
+ return [...pieceMetas(q), ...q.proof];
4137
+ })
4138
+ : [];
2682
4139
  return lootIx(
2683
4140
  'end_auction',
2684
4141
  { reference: reference(ref), nonce },
@@ -2695,11 +4152,27 @@ export function endAuctionIx({ signer, project, projectState, id, auction, item
2695
4152
  item: itemId !== undefined ? pda.item(project, itemId) : null,
2696
4153
  ...p.accounts,
2697
4154
  loot_box: roll !== undefined ? pda.box(project, roll) : null,
2698
- order: roll !== undefined ? pda.order(project, auction.high_bidder, prizeRollNonce(nonce)) : lotHeld ? pda.hold(asset, 0) : null,
4155
+ order:
4156
+ roll !== undefined
4157
+ ? pda.order(project, auction.high_bidder, prizeRollNonce(nonce))
4158
+ : lotHeld
4159
+ ? pda.hold(asset, 0)
4160
+ : null,
2699
4161
  },
2700
4162
  {
2701
- remaining: [...p.proof, ...(!owed && sold && roll === undefined ? shareAccounts(project, auction.shares, auction.mint, { holders, programs, treasury: projectState.treasury }) : []), ...winning, ...tailOf(extra)],
2702
- writable: [...(!owed && sold && sharesBurn(auction.shares) ? ['mint'] : []), ...(sold && !auction.nft_bids ? ['vault'] : []), ...p.writable],
4163
+ remaining: [
4164
+ ...p.proof,
4165
+ ...(!owed && sold && roll === undefined
4166
+ ? shareAccounts(project, auction.shares, auction.mint, { holders, programs, treasury: projectState.treasury })
4167
+ : []),
4168
+ ...winning,
4169
+ ...tailOf(extra),
4170
+ ],
4171
+ writable: [
4172
+ ...(!owed && sold && sharesBurn(auction.shares) ? ['mint'] : []),
4173
+ ...(sold && !auction.nft_bids ? ['vault'] : []),
4174
+ ...p.writable,
4175
+ ],
2703
4176
  },
2704
4177
  );
2705
4178
  }
@@ -2712,10 +4185,10 @@ export function endAuctionIx({ signer, project, projectState, id, auction, item
2712
4185
 
2713
4186
  /** What a taker signs: the offer's bytes but `left`, its last 2 (what a take changes). `offer`: the decoded Offer, or its
2714
4187
  * account's bytes. */
2715
- export const offerTerms = (offer) => recordTerms('Offer', offer, (data) => [data.length - 2, data.length]);
4188
+ export const offerTerms = offer => recordTerms('Offer', offer, data => [data.length - 2, data.length]);
2716
4189
 
2717
4190
  /** What one take of a deal pays out of its offer's money: a bid's price, a loan's amount. */
2718
- export const dealUnit = (deal) => BigInt(deal.Buy?.price ?? deal.Lend.amount);
4191
+ export const dealUnit = deal => BigInt(deal.Buy?.price ?? deal.Lend.amount);
2719
4192
 
2720
4193
  /**
2721
4194
  * `maker` (any wallet, signing alone) opens offer `id` in `mint`, and puts all of its money (`left` times the deal's
@@ -2728,7 +4201,14 @@ export function openOfferIx({ maker, project, id, mint, args, makerToken, progra
2728
4201
  return lootIx(
2729
4202
  'open_offer',
2730
4203
  { id, args: { shares: [], ...args } },
2731
- { maker, project, offer: pda.offer(project, maker, id), mint, maker_token: makerToken ?? ata(maker, mint, tokenProgram), vault: vault(project, mint, tokenProgram) },
4204
+ {
4205
+ maker,
4206
+ project,
4207
+ offer: pda.offer(project, maker, id),
4208
+ mint,
4209
+ maker_token: makerToken ?? ata(maker, mint, tokenProgram),
4210
+ vault: vault(project, mint, tokenProgram),
4211
+ },
2732
4212
  { remaining: tailOf(extra) },
2733
4213
  );
2734
4214
  }
@@ -2739,7 +4219,14 @@ export function closeOfferIx({ maker, project, id, offer, programs, extra = [] }
2739
4219
  return lootIx(
2740
4220
  'close_offer',
2741
4221
  {},
2742
- { maker, project, offer: pda.offer(project, maker, id), mint: offer.mint, vault: vault(project, offer.mint, tokenProgram), maker_token: ata(maker, offer.mint, tokenProgram) },
4222
+ {
4223
+ maker,
4224
+ project,
4225
+ offer: pda.offer(project, maker, id),
4226
+ mint: offer.mint,
4227
+ vault: vault(project, offer.mint, tokenProgram),
4228
+ maker_token: ata(maker, offer.mint, tokenProgram),
4229
+ },
2743
4230
  { remaining: tailOf(extra) },
2744
4231
  );
2745
4232
  }
@@ -2750,15 +4237,53 @@ export function closeOfferIx({ maker, project, id, offer, programs, extra = [] }
2750
4237
  * any kind, as `piece` takes it (`collection`, `hasAuthority` false for a Core asset of another collection,
2751
4238
  * `tokenMetadata`, or `compressed` after a `proveLeafIx`).
2752
4239
  */
2753
- export function sellToOfferIx({ seller, project, projectState, id, offer, asset, collection, hasAuthority, tokenMetadata: tm, compressed, holders, programs, extra = [] }) {
4240
+ export function sellToOfferIx({
4241
+ seller,
4242
+ project,
4243
+ projectState,
4244
+ id,
4245
+ offer,
4246
+ asset,
4247
+ collection,
4248
+ hasAuthority,
4249
+ tokenMetadata: tm,
4250
+ compressed,
4251
+ holders,
4252
+ programs,
4253
+ extra = [],
4254
+ }) {
2754
4255
  const tokenProgram = tokenProgramOf(programs, offer.mint);
2755
- const p = passedOn({ project, owner: seller, receiver: offer.maker, burns: false, asset, collection, hasAuthority, tokenMetadata: tm, compressed, reads: true });
4256
+ const p = passedOn({
4257
+ project,
4258
+ owner: seller,
4259
+ receiver: offer.maker,
4260
+ burns: false,
4261
+ asset,
4262
+ collection,
4263
+ hasAuthority,
4264
+ tokenMetadata: tm,
4265
+ compressed,
4266
+ reads: true,
4267
+ });
2756
4268
  return lootIx(
2757
4269
  'sell_to_offer',
2758
4270
  { terms: offerTerms(offer) },
2759
- { seller, project, offer: pda.offer(project, offer.maker, id), maker: offer.maker, ...p.accounts, mint: offer.mint, vault: vault(project, offer.mint, tokenProgram), seller_token: ata(seller, offer.mint, tokenProgram) },
2760
4271
  {
2761
- remaining: [...p.proof, ...shareAccounts(project, offer.shares, offer.mint, { holders, programs, treasury: projectState?.treasury }), ...tailOf(extra)],
4272
+ seller,
4273
+ project,
4274
+ offer: pda.offer(project, offer.maker, id),
4275
+ maker: offer.maker,
4276
+ ...p.accounts,
4277
+ mint: offer.mint,
4278
+ vault: vault(project, offer.mint, tokenProgram),
4279
+ seller_token: ata(seller, offer.mint, tokenProgram),
4280
+ },
4281
+ {
4282
+ remaining: [
4283
+ ...p.proof,
4284
+ ...shareAccounts(project, offer.shares, offer.mint, { holders, programs, treasury: projectState?.treasury }),
4285
+ ...tailOf(extra),
4286
+ ],
2762
4287
  writable: [...(sharesBurn(offer.shares) ? ['mint'] : []), ...p.writable],
2763
4288
  },
2764
4289
  );
@@ -2770,15 +4295,45 @@ export function sellToOfferIx({ seller, project, projectState, id, offer, asset,
2770
4295
  * `pda.hold(asset, due)` until it is repaid (`repayIx`, then `releaseIx` sends it home) or, once due, sent to the
2771
4296
  * lender (`releaseIx` with `to` the lender); the loan goes to the borrower. The NFT is any kind, as `sellToOfferIx` takes it.
2772
4297
  */
2773
- export function borrowIx({ borrower, project, id, offer, due, asset, collection, hasAuthority, tokenMetadata: tm, compressed, programs, extra = [] }) {
4298
+ export function borrowIx({
4299
+ borrower,
4300
+ project,
4301
+ id,
4302
+ offer,
4303
+ due,
4304
+ asset,
4305
+ collection,
4306
+ hasAuthority,
4307
+ tokenMetadata: tm,
4308
+ compressed,
4309
+ programs,
4310
+ extra = [],
4311
+ }) {
2774
4312
  const tokenProgram = tokenProgramOf(programs, offer.mint);
2775
- const p = piece({ project, asset, wallet: borrower, collection, hasAuthority, tokenMetadata: tm, compressed, ...ONLY_MOVES, reads: true });
4313
+ const p = piece({
4314
+ project,
4315
+ asset,
4316
+ wallet: borrower,
4317
+ collection,
4318
+ hasAuthority,
4319
+ tokenMetadata: tm,
4320
+ compressed,
4321
+ ...ONLY_MOVES,
4322
+ reads: true,
4323
+ });
2776
4324
  return lootIx(
2777
4325
  'borrow',
2778
4326
  { due: toBigInt(due), terms: offerTerms(offer) },
2779
4327
  {
2780
- borrower, project, offer: pda.offer(project, offer.maker, id), maker: offer.maker, hold: pda.hold(p.accounts.asset, due), ...p.accounts,
2781
- mint: offer.mint, vault: vault(project, offer.mint, tokenProgram), borrower_token: ata(borrower, offer.mint, tokenProgram),
4328
+ borrower,
4329
+ project,
4330
+ offer: pda.offer(project, offer.maker, id),
4331
+ maker: offer.maker,
4332
+ hold: pda.hold(p.accounts.asset, due),
4333
+ ...p.accounts,
4334
+ mint: offer.mint,
4335
+ vault: vault(project, offer.mint, tokenProgram),
4336
+ borrower_token: ata(borrower, offer.mint, tokenProgram),
2782
4337
  },
2783
4338
  { remaining: [...p.proof, ...tailOf(extra)], writable: p.writable },
2784
4339
  );
@@ -2792,15 +4347,21 @@ export function borrowIx({ borrower, project, id, offer, due, asset, collection,
2792
4347
  * transfer hook's accounts.
2793
4348
  */
2794
4349
  export function repayIx({ payer, project, hold, repay, payerToken, programs, extra = [] }) {
2795
- if (repay === undefined || repay === null) throw new Error('A repay names the debt the payer was shown (repay: hold.debt.repay as read).');
4350
+ if (repay === undefined || repay === null)
4351
+ throw new Error('A repay names the debt the payer was shown (repay: hold.debt.repay as read).');
2796
4352
  const { lender, mint } = hold.debt;
2797
4353
  const tokenProgram = tokenProgramOf(programs, mint);
2798
4354
  return lootIx(
2799
4355
  'repay',
2800
4356
  { repay: toBigInt(repay) },
2801
4357
  {
2802
- payer, project, hold: pda.hold(hold.asset, hold.free_at), lender,
2803
- lender_token: ata(lender, mint, tokenProgram), payer_token: payerToken ?? ata(payer, mint, tokenProgram), mint,
4358
+ payer,
4359
+ project,
4360
+ hold: pda.hold(hold.asset, hold.free_at),
4361
+ lender,
4362
+ lender_token: ata(lender, mint, tokenProgram),
4363
+ payer_token: payerToken ?? ata(payer, mint, tokenProgram),
4364
+ mint,
2804
4365
  },
2805
4366
  { remaining: tailOf(extra) },
2806
4367
  );
@@ -2810,7 +4371,7 @@ export function repayIx({ payer, project, hold, repay, payerToken, programs, ext
2810
4371
 
2811
4372
  /** What a taker signs: the listing's bytes but `user`, `until` and `stopped` (bytes 129 to 170, what a rental changes).
2812
4373
  * `listing`: the decoded Listing, or its account's bytes. */
2813
- export const listingTerms = (listing) => recordTerms('Listing', listing, () => [129, 170]);
4374
+ export const listingTerms = listing => recordTerms('Listing', listing, () => [129, 170]);
2814
4375
 
2815
4376
  /**
2816
4377
  * `maker` (any wallet, signing alone) lists an NFT they hold, of any kind (as `piece` takes it), for sale
@@ -2819,9 +4380,35 @@ export const listingTerms = (listing) => recordTerms('Listing', listing, () => [
2819
4380
  * goes to the project until it is sold or the listing is closed. The listing's address names the NFT
2820
4381
  * (`pda.listing(project, asset)`), so a game finds it, and its renter, with one account read.
2821
4382
  */
2822
- export function openListingIx({ maker, project, asset, mint, deal, shares = [], collection, hasAuthority, tokenMetadata: tm, compressed, extra = [] }) {
2823
- const p = piece({ project, asset, wallet: maker, collection, hasAuthority, tokenMetadata: tm, compressed, ...ONLY_MOVES });
2824
- return lootIx('open_listing', { deal, shares: stored(shares) }, { maker, project, listing: pda.listing(project, p.accounts.asset), ...p.accounts, mint }, { remaining: [...p.proof, ...tailOf(extra)], writable: p.writable });
4383
+ export function openListingIx({
4384
+ maker,
4385
+ project,
4386
+ asset,
4387
+ mint,
4388
+ deal,
4389
+ shares = [],
4390
+ collection,
4391
+ hasAuthority,
4392
+ tokenMetadata: tm,
4393
+ compressed,
4394
+ extra = [],
4395
+ }) {
4396
+ const p = piece({
4397
+ project,
4398
+ asset,
4399
+ wallet: maker,
4400
+ collection,
4401
+ hasAuthority,
4402
+ tokenMetadata: tm,
4403
+ compressed,
4404
+ ...ONLY_MOVES,
4405
+ });
4406
+ return lootIx(
4407
+ 'open_listing',
4408
+ { deal, shares: stored(shares) },
4409
+ { maker, project, listing: pda.listing(project, p.accounts.asset), ...p.accounts, mint },
4410
+ { remaining: [...p.proof, ...tailOf(extra)], writable: p.writable },
4411
+ );
2825
4412
  }
2826
4413
 
2827
4414
  /**
@@ -2832,22 +4419,65 @@ export function openListingIx({ maker, project, asset, mint, deal, shares = [],
2832
4419
  * take's own accounts come a sale's compressed NFT's proof, then the shares' accounts, then (in `extra`) Core's
2833
4420
  * transfer adapters and any transfer hook's accounts, which the program finds by address after the shares'.
2834
4421
  */
2835
- export function takeListingIx({ taker, project, projectState, listing, periods = 1, collection, hasAuthority, tokenMetadata: tm, compressed, holders, programs, takerToken, extra = [] }) {
4422
+ export function takeListingIx({
4423
+ taker,
4424
+ project,
4425
+ projectState,
4426
+ listing,
4427
+ periods = 1,
4428
+ collection,
4429
+ hasAuthority,
4430
+ tokenMetadata: tm,
4431
+ compressed,
4432
+ holders,
4433
+ programs,
4434
+ takerToken,
4435
+ extra = [],
4436
+ }) {
2836
4437
  const tokenProgram = tokenProgramOf(programs, listing.mint);
2837
4438
  const sale = Boolean(listing.deal.Sell);
2838
4439
  // A rental moves nothing: a Core or Token Metadata NFT by its address; a compressed one by its record and tree
2839
4440
  // (`proveLeafIx` first, in the same transaction), with no proof.
2840
- const p = sale ? piece({ project, asset: listing.asset, wallet: taker, collection, hasAuthority, tokenMetadata: tm, compressed, ...ONLY_MOVES })
2841
- : compressed ? { ...piece({ project, asset: listing.asset, wallet: taker, compressed, ...ONLY_MOVES, moves: false }), proof: [] } : standIn(listing.asset);
4441
+ const p = sale
4442
+ ? piece({
4443
+ project,
4444
+ asset: listing.asset,
4445
+ wallet: taker,
4446
+ collection,
4447
+ hasAuthority,
4448
+ tokenMetadata: tm,
4449
+ compressed,
4450
+ ...ONLY_MOVES,
4451
+ })
4452
+ : compressed
4453
+ ? {
4454
+ ...piece({ project, asset: listing.asset, wallet: taker, compressed, ...ONLY_MOVES, moves: false }),
4455
+ proof: [],
4456
+ }
4457
+ : standIn(listing.asset);
2842
4458
  return lootIx(
2843
4459
  'take_listing',
2844
4460
  { terms: listingTerms(listing), periods },
2845
4461
  {
2846
- taker, project, listing: pda.listing(project, listing.asset), maker: listing.maker, maker_token: ata(listing.maker, listing.mint, tokenProgram),
2847
- ...p.accounts, mint: listing.mint, taker_token: takerToken ?? ata(taker, listing.mint, tokenProgram),
4462
+ taker,
4463
+ project,
4464
+ listing: pda.listing(project, listing.asset),
4465
+ maker: listing.maker,
4466
+ maker_token: ata(listing.maker, listing.mint, tokenProgram),
4467
+ ...p.accounts,
4468
+ mint: listing.mint,
4469
+ taker_token: takerToken ?? ata(taker, listing.mint, tokenProgram),
2848
4470
  },
2849
4471
  {
2850
- remaining: [...p.proof, ...shareAccounts(project, listing.shares ?? [], listing.mint, { holders, programs, treasury: projectState?.treasury }), ...tailOf(extra)],
4472
+ remaining: [
4473
+ ...p.proof,
4474
+ ...shareAccounts(project, listing.shares ?? [], listing.mint, {
4475
+ holders,
4476
+ programs,
4477
+ treasury: projectState?.treasury,
4478
+ }),
4479
+ ...tailOf(extra),
4480
+ ],
2851
4481
  writable: [...(sharesBurn(listing.shares ?? []) ? ['mint'] : []), ...p.writable],
2852
4482
  },
2853
4483
  );
@@ -2857,14 +4487,41 @@ export function takeListingIx({ taker, project, projectState, listing, periods =
2857
4487
  * Its maker closes a listing (`listing` the decoded Listing): the NFT goes home. While a rental runs it only stops (no
2858
4488
  * renter takes it again), and the same call once the rental is over takes it home. Free.
2859
4489
  */
2860
- export function closeListingIx({ maker, project, listing, collection, hasAuthority, tokenMetadata: tm, compressed, extra = [] }) {
2861
- const p = piece({ project, asset: listing.asset, wallet: maker, collection, hasAuthority, tokenMetadata: tm, compressed, ...ONLY_MOVES });
2862
- return lootIx('close_listing', {}, { maker, project, listing: pda.listing(project, listing.asset), ...p.accounts }, { remaining: [...p.proof, ...tailOf(extra)], writable: p.writable });
4490
+ export function closeListingIx({
4491
+ maker,
4492
+ project,
4493
+ listing,
4494
+ collection,
4495
+ hasAuthority,
4496
+ tokenMetadata: tm,
4497
+ compressed,
4498
+ extra = [],
4499
+ }) {
4500
+ const p = piece({
4501
+ project,
4502
+ asset: listing.asset,
4503
+ wallet: maker,
4504
+ collection,
4505
+ hasAuthority,
4506
+ tokenMetadata: tm,
4507
+ compressed,
4508
+ ...ONLY_MOVES,
4509
+ });
4510
+ return lootIx(
4511
+ 'close_listing',
4512
+ {},
4513
+ { maker, project, listing: pda.listing(project, listing.asset), ...p.accounts },
4514
+ { remaining: [...p.proof, ...tailOf(extra)], writable: p.writable },
4515
+ );
2863
4516
  }
2864
4517
 
2865
4518
  // ---------------------------------------------------------------- Token Metadata NFTs
2866
4519
 
2867
- const tmPda = (seeds) => findProgramAddress(seeds.map((s) => (typeof s === 'string' ? utf8(s) : s)), TOKEN_METADATA_PROGRAM)[0];
4520
+ const tmPda = seeds =>
4521
+ findProgramAddress(
4522
+ seeds.map(s => (typeof s === 'string' ? utf8(s) : s)),
4523
+ TOKEN_METADATA_PROGRAM,
4524
+ )[0];
2868
4525
 
2869
4526
  /**
2870
4527
  * Whether a classic mint's account data (`data`, 82 bytes) is an NFT's, as the program takes one:
@@ -2875,13 +4532,20 @@ export function isNftMint(mint, data) {
2875
4532
  if (!data || data.length !== MINT_BYTES || data[44] !== 0 || data[45] !== 1) return false;
2876
4533
  if (data.subarray(36, 44).some((b, i) => b !== (i === 0 ? 1 : 0))) return false;
2877
4534
  const tag = data.subarray(0, 4);
2878
- if (tag.every((b) => b === 0)) return true;
2879
- return tag[0] === 1 && tag[1] === 0 && tag[2] === 0 && tag[3] === 0 && encodeBase58(data.subarray(4, 36)) === tokenMetadata.edition(mint);
4535
+ if (tag.every(b => b === 0)) return true;
4536
+ return (
4537
+ tag[0] === 1 &&
4538
+ tag[1] === 0 &&
4539
+ tag[2] === 0 &&
4540
+ tag[3] === 0 &&
4541
+ encodeBase58(data.subarray(4, 36)) === tokenMetadata.edition(mint)
4542
+ );
2880
4543
  }
2881
4544
 
2882
4545
  /** A Token Metadata NFT's own addresses: its metadata, its edition, and a pNFT's record of a token account. */
2883
4546
  export const tokenMetadata = {
2884
- metadata: (mint) => tmPda(['metadata', addressBytes(TOKEN_METADATA_PROGRAM), addressBytes(mint)]),
2885
- edition: (mint) => tmPda(['metadata', addressBytes(TOKEN_METADATA_PROGRAM), addressBytes(mint), 'edition']),
2886
- record: (mint, token) => tmPda(['metadata', addressBytes(TOKEN_METADATA_PROGRAM), addressBytes(mint), 'token_record', addressBytes(token)]),
4547
+ metadata: mint => tmPda(['metadata', addressBytes(TOKEN_METADATA_PROGRAM), addressBytes(mint)]),
4548
+ edition: mint => tmPda(['metadata', addressBytes(TOKEN_METADATA_PROGRAM), addressBytes(mint), 'edition']),
4549
+ record: (mint, token) =>
4550
+ tmPda(['metadata', addressBytes(TOKEN_METADATA_PROGRAM), addressBytes(mint), 'token_record', addressBytes(token)]),
2887
4551
  };