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