@xgamesfun/solana 0.24.0 → 0.25.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DISCLAIMER.md +1 -1
- package/NOTICE +4 -4
- package/README.md +100 -52
- package/bin/xsolana.mjs +32 -11
- 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/ideas.json +788 -788
- package/examples/nft-raffle.json +1 -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 +4 -7
- 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 +2 -2
- 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 +3884 -690
- package/js/economy/cost.mjs +381 -57
- package/js/economy/examples.mjs +176 -35
- package/js/economy/explain.mjs +468 -110
- package/js/economy/guide.mjs +282 -59
- package/js/economy/legal.mjs +12 -6
- package/js/economy/live.mjs +62 -20
- package/js/economy/model.mjs +320 -97
- package/js/economy/plan.mjs +1022 -203
- package/js/economy/review.mjs +760 -0
- package/js/economy/schema.json +134 -372
- package/js/economy/secrets.mjs +17 -7
- package/js/economy/simulate.mjs +771 -178
- package/js/economy/trust.mjs +15 -0
- package/js/economy/txsize.mjs +15 -3
- package/js/economy/validate.mjs +61 -24
- package/js/index.js +1 -0
- package/js/mcp/server.mjs +43 -15
- package/js/mcp/tools.mjs +318 -80
- package/js/play/index.js +1960 -461
- package/js/play/keys.js +35 -9
- package/js/results/index.js +225 -32
- 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 +52 -22
- package/skills/xsolana-economy/references/after-launch.md +32 -21
- package/skills/xsolana-economy/references/apps.md +47 -36
- package/skills/xsolana-economy/references/sentence-to-spec.md +149 -129
- package/skills/xsolana-economy/references/telling-the-person.md +15 -6
- package/skills/xsolana-economy/references/trust-and-cheating.md +126 -0
- 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/cost.d.mts +1 -1
- package/types/economy/explain.d.mts +3 -0
- 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/economy/review.d.mts +45 -0
- package/types/economy/trust.d.mts +8 -0
- package/types/index.d.ts +1 -0
- package/types/mcp/tools.d.mts +62 -0
- package/types/play/index.d.ts +14 -25
- package/types/results/index.d.ts +8 -7
- package/types/results/server.d.ts +126 -0
- package/types/results/wire.d.ts +1 -1
package/js/economy/cost.mjs
CHANGED
|
@@ -4,14 +4,42 @@
|
|
|
4
4
|
// Rent is (bytes + 128) × 5,080 lamports (model.mjs); the loot program's own records are sized byte for byte from
|
|
5
5
|
// their Borsh layout, others (a Core collection, a farm) roughly, and marked so.
|
|
6
6
|
import { boxOrderBytes, plan as compile, contestBytes, farmEnds, roundRentOf } from './plan.mjs';
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
ACCOUNT_BYTES,
|
|
9
|
+
CONTEST_ENTRY_BYTES,
|
|
10
|
+
END_BYTES,
|
|
11
|
+
entryWidth,
|
|
12
|
+
LIMIT_RECORD_BYTES,
|
|
13
|
+
ROUND_RECORD_BYTES,
|
|
14
|
+
} from '../client/index.js';
|
|
15
|
+
import {
|
|
16
|
+
boxBuys,
|
|
17
|
+
COLLECTIBLE_RENT,
|
|
18
|
+
drawsOf,
|
|
19
|
+
settleUnits,
|
|
20
|
+
holdsRent,
|
|
21
|
+
outcomeParts,
|
|
22
|
+
SETTLE_RENT_UNIT,
|
|
23
|
+
FEES,
|
|
24
|
+
LAMPORTS_PER_SOL,
|
|
25
|
+
ORACLE_FEE,
|
|
26
|
+
RENT_PER_BYTE,
|
|
27
|
+
SIGNATURE_FEE,
|
|
28
|
+
model,
|
|
29
|
+
lotterySplit,
|
|
30
|
+
prizeLine,
|
|
31
|
+
prizeRolls,
|
|
32
|
+
prizes,
|
|
33
|
+
prizeWords,
|
|
34
|
+
rent,
|
|
35
|
+
toUnits,
|
|
36
|
+
} from './model.mjs';
|
|
9
37
|
import { inAll, round, simulate } from './simulate.mjs';
|
|
10
38
|
|
|
11
39
|
// The options of a call made without any: read-only, and typed so the generated declarations take any option.
|
|
12
40
|
const NO_OPTIONS = Object.freeze(/** @type {Record<string, any>} */ ({}));
|
|
13
41
|
|
|
14
|
-
const sol =
|
|
42
|
+
const sol = lamports => round(Number(lamports) / Number(LAMPORTS_PER_SOL));
|
|
15
43
|
/** Setup steps that are no loot instruction, and so charge no fee: a token minted, the collection made, SOL put in a
|
|
16
44
|
* grant's funds. Every other step is the owner or the authority calling loot, which charges the project fee. */
|
|
17
45
|
const NO_FEE_STEP = /^(token:|collection$|fund:|rent:)/;
|
|
@@ -19,17 +47,20 @@ const NO_FEE_STEP = /^(token:|collection$|fund:|rent:)/;
|
|
|
19
47
|
export function setupCost(plan, economy) {
|
|
20
48
|
const m = model(economy);
|
|
21
49
|
// An account that grows (a live stash taking more NFTs) pays rent for its new bytes only.
|
|
22
|
-
const rentOf =
|
|
23
|
-
const accounts = plan.transactions
|
|
50
|
+
const rentOf = c => (c.grows ? BigInt(c.bytes) * RENT_PER_BYTE : rent(c.bytes));
|
|
51
|
+
const accounts = plan.transactions
|
|
52
|
+
.flatMap(t => t.steps)
|
|
53
|
+
.flatMap(s => s.creates ?? [])
|
|
54
|
+
.map(c => ({ ...c, lamports: rentOf(c).toString(), sol: sol(rentOf(c)) }));
|
|
24
55
|
const rentLamports = accounts.reduce((n, a) => n + BigInt(a.lamports), 0n);
|
|
25
|
-
const charged = plan.transactions.flatMap(
|
|
56
|
+
const charged = plan.transactions.flatMap(t => t.steps).filter(s => !NO_FEE_STEP.test(s.id));
|
|
26
57
|
const feeLamports = BigInt(charged.length) * FEES.project;
|
|
27
58
|
const networkLamports = plan.transactions.reduce((n, t) => n + BigInt(t.signers.length) * SIGNATURE_FEE, 0n);
|
|
28
59
|
// Only the seeds and grant funds this plan puts in: a change to a live economy doesn't pay its seeds again.
|
|
29
|
-
const steps = new Set(plan.transactions.flatMap(
|
|
60
|
+
const steps = new Set(plan.transactions.flatMap(t => t.steps).map(s => s.id));
|
|
30
61
|
const seeds = {};
|
|
31
62
|
let seedLamports = 0n;
|
|
32
|
-
const funded = m.grants.filter(
|
|
63
|
+
const funded = m.grants.filter(g => Number(g.fund ?? 0) > 0 && steps.has(`fund:${g.key}`));
|
|
33
64
|
for (const g of funded) seedLamports += toUnits(g.fund, 9);
|
|
34
65
|
// What the owner puts in from their own wallet: a pool's seed, a lottery's seed (into its pot), a farm's seed (into its
|
|
35
66
|
// rewards vault), a sealed schedule's funds (into its vault, from the wallet).
|
|
@@ -44,13 +75,24 @@ export function setupCost(plan, economy) {
|
|
|
44
75
|
for (const f of m.farms) putIn(f.rewardsToken, f.seed, `seed:farm:${f.key}`);
|
|
45
76
|
// SOL a lottery holds for its rounds' rent: lent to each round, back at its close.
|
|
46
77
|
for (const l of m.lotteries) if (steps.has(`rent:lottery:${l.key}`)) seedLamports += roundRentOf(l, m);
|
|
47
|
-
for (const sc of m.schedules)
|
|
78
|
+
for (const sc of m.schedules)
|
|
79
|
+
if (sc.sealed && (sc.fundFrom ?? 'wallet') === 'wallet') putIn(sc.currency, sc.fund, `fund:${sc.key}`);
|
|
48
80
|
const total = rentLamports + feeLamports + networkLamports + seedLamports;
|
|
49
81
|
const notes = [];
|
|
50
|
-
if (accounts.some(
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
|
|
82
|
+
if (accounts.some(a => a.approx))
|
|
83
|
+
notes.push('Accounts marked “about” are sized roughly (a Core collection or an external token account).');
|
|
84
|
+
if (Object.entries(seeds).some(([symbol]) => [...m.tokens.values()].some(t => t.wsol && t.symbol === symbol)))
|
|
85
|
+
notes.push(
|
|
86
|
+
'The total includes the SOL you put in: pools’, lotteries’ and farms’ seeds, and sealed schedules’ funds.',
|
|
87
|
+
);
|
|
88
|
+
if (m.lotteries.some(l => steps.has(`rent:lottery:${l.key}`)))
|
|
89
|
+
notes.push(
|
|
90
|
+
'The total includes the SOL lotteries hold for their rounds’ rent and their tickets’ entries (`roundRent`, sized by default for the rounds and purchases expected at once): each round borrows it and gives it back when it closes; it comes back to you when the lottery closes.',
|
|
91
|
+
);
|
|
92
|
+
if (funded.length)
|
|
93
|
+
notes.push(
|
|
94
|
+
'The total includes the SOL put in grants’ funds, which pays their claims; what is left goes back to you when a grant closes.',
|
|
95
|
+
);
|
|
54
96
|
notes.push('The network fee is 5,000 lamports a signature; a wallet may add a priority fee of its own.');
|
|
55
97
|
return {
|
|
56
98
|
accounts,
|
|
@@ -66,7 +108,7 @@ export function setupCost(plan, economy) {
|
|
|
66
108
|
}
|
|
67
109
|
|
|
68
110
|
/**
|
|
69
|
-
* What each action costs whoever signs it while the economy runs:
|
|
111
|
+
* What each action costs whoever signs it while the economy runs: xgames.fun's fee, the network's, and for anything
|
|
70
112
|
* that asks MagicBlock's oracle for a random number (a box's open, a lottery's draw, the authority's `roll`) the
|
|
71
113
|
* oracle's own fee (`oracleSol`, model.mjs ORACLE_FEE).
|
|
72
114
|
*/
|
|
@@ -74,85 +116,350 @@ export function actionCosts(economy, { now } = NO_OPTIONS) {
|
|
|
74
116
|
const m = model(economy);
|
|
75
117
|
// A player's real cost of one action, from the simulation (simulate.mjs realCostOf): the number to quote.
|
|
76
118
|
const sim = simulate(economy, { now });
|
|
77
|
-
const real = new Map([
|
|
119
|
+
const real = new Map([
|
|
120
|
+
...sim.items.map(x => [`item:${x.key}`, x.realCost]),
|
|
121
|
+
...sim.boxes.map(x => [`box:${x.key}`, x.realCost]),
|
|
122
|
+
...sim.payments.map(x => [`payment:${x.key}`, x.realCost]),
|
|
123
|
+
...sim.lotteries.map(x => [`lottery:${x.key}`, x.realCost]),
|
|
124
|
+
]);
|
|
78
125
|
const rows = [];
|
|
79
126
|
const row = (part, action, who, fee, deposits = [], extra, oracle = 0n) => {
|
|
80
127
|
const r = real.get(part);
|
|
81
|
-
rows.push({
|
|
128
|
+
rows.push({
|
|
129
|
+
part,
|
|
130
|
+
action,
|
|
131
|
+
who,
|
|
132
|
+
feeSol: sol(fee),
|
|
133
|
+
...(oracle ? { oracleSol: sol(oracle) } : {}),
|
|
134
|
+
networkSol: sol(SIGNATURE_FEE),
|
|
135
|
+
deposits,
|
|
136
|
+
...(extra ? { note: extra } : {}),
|
|
137
|
+
...(r ? { inAllSol: r.totalSol ?? null, onTopSol: r.extrasSol, holdSol: r.holdSol, inAll: inAll(r) } : {}),
|
|
138
|
+
});
|
|
82
139
|
};
|
|
83
140
|
const back = (what, lamports, when) => ({ what, sol: sol(lamports), back: when });
|
|
84
141
|
// An order holds its rent and the fee; the open lends its throwaway settle key what the settle may open or mint (a
|
|
85
142
|
// collectible's rent for each item the richest outcome gives, each card of a pack), back unspent.
|
|
86
|
-
const itemsOf =
|
|
87
|
-
|
|
143
|
+
const itemsOf = b =>
|
|
144
|
+
drawsOf(b) * Math.max(0, ...(b.outcomes ?? []).map(o => outcomeParts(o).filter(p => holdsRent(p, m)).length));
|
|
145
|
+
const order = b => [
|
|
88
146
|
back('the order', rent(boxOrderBytes(b, m, 1, { opened: true })), 'at settle or refund'),
|
|
89
|
-
...(settleUnits(b, m)
|
|
147
|
+
...(settleUnits(b, m)
|
|
148
|
+
? [
|
|
149
|
+
back(
|
|
150
|
+
`the SOL the open lends the throwaway key that settles it, for what its settle may open (${settleUnits(b, m)} × ${sol(SETTLE_RENT_UNIT)} SOL: each collectible, NFT of a stash, coin it may pay and NFT it rolls on)`,
|
|
151
|
+
SETTLE_RENT_UNIT * BigInt(settleUnits(b, m)),
|
|
152
|
+
`once the key has settled, less what it opened (a collectible’s rent is about ${sol(COLLECTIBLE_RENT)} SOL); all of it when the roll fails`,
|
|
153
|
+
),
|
|
154
|
+
]
|
|
155
|
+
: []),
|
|
90
156
|
];
|
|
91
157
|
// A box that needs no roll is bought in one step (`buy`): no order, and a collectible's rent is paid at once.
|
|
92
|
-
const bought =
|
|
93
|
-
|
|
158
|
+
const bought = b =>
|
|
159
|
+
itemsOf(b)
|
|
160
|
+
? [
|
|
161
|
+
back(
|
|
162
|
+
`${itemsOf(b)} new collectible${itemsOf(b) > 1 ? 's' : ''}`,
|
|
163
|
+
COLLECTIBLE_RENT * BigInt(itemsOf(b)),
|
|
164
|
+
`never: it is the collectible’s own rent (about ${sol(COLLECTIBLE_RENT)} SOL each)`,
|
|
165
|
+
),
|
|
166
|
+
]
|
|
167
|
+
: [];
|
|
168
|
+
for (const b of m.shop)
|
|
169
|
+
row(
|
|
170
|
+
`item:${b.shopOf}`,
|
|
171
|
+
`buy “${b.name}”`,
|
|
172
|
+
'player',
|
|
173
|
+
FEES.player,
|
|
174
|
+
bought(b),
|
|
175
|
+
'One transaction: the price is split and the item given at once.',
|
|
176
|
+
);
|
|
94
177
|
for (const b of m.boxes) {
|
|
95
|
-
if (boxBuys(b, m))
|
|
96
|
-
|
|
178
|
+
if (boxBuys(b, m))
|
|
179
|
+
row(
|
|
180
|
+
`box:${b.key}`,
|
|
181
|
+
`buy “${b.name}”`,
|
|
182
|
+
'player',
|
|
183
|
+
FEES.player,
|
|
184
|
+
bought(b),
|
|
185
|
+
'One transaction: the price is split and what it gives handed over at once.',
|
|
186
|
+
);
|
|
187
|
+
else
|
|
188
|
+
row(
|
|
189
|
+
`box:${b.key}`,
|
|
190
|
+
`open “${b.name}”`,
|
|
191
|
+
'player',
|
|
192
|
+
FEES.player,
|
|
193
|
+
order(b),
|
|
194
|
+
'The oracle’s fee is paid by whoever asks for the roll (the player, at the open) and a refund doesn’t return it.',
|
|
195
|
+
ORACLE_FEE,
|
|
196
|
+
);
|
|
97
197
|
}
|
|
98
198
|
for (const p of m.payments) row(`payment:${p.key}`, `pay “${p.name}”`, 'player', FEES.player);
|
|
99
199
|
for (const r of m.recipes) {
|
|
100
|
-
const made = (r.out ?? []).filter(
|
|
101
|
-
row(
|
|
200
|
+
const made = (r.out ?? []).filter(l => l.gives === 'item' && !l.compressed).length;
|
|
201
|
+
row(
|
|
202
|
+
`recipe:${r.key}`,
|
|
203
|
+
`craft “${r.name}”`,
|
|
204
|
+
'player',
|
|
205
|
+
FEES.player,
|
|
206
|
+
made
|
|
207
|
+
? [
|
|
208
|
+
back(
|
|
209
|
+
`${made} new collectible${made > 1 ? 's' : ''}`,
|
|
210
|
+
COLLECTIBLE_RENT * BigInt(made),
|
|
211
|
+
`never: it is the collectible’s own rent (about ${sol(COLLECTIBLE_RENT)} SOL each)`,
|
|
212
|
+
),
|
|
213
|
+
]
|
|
214
|
+
: [],
|
|
215
|
+
);
|
|
102
216
|
}
|
|
103
217
|
for (const l of m.lotteries) {
|
|
104
218
|
// A purchase adds its entry's bytes to the round. In a round the lottery opened, the SOL it holds for its rounds
|
|
105
219
|
// (`roundRent`, on by default) pays them, back to it at the close: the buyer pays only the fee and the price. Without
|
|
106
220
|
// it, or once it runs short, the buyer pays them (to the treasury at the close), and a round's first buyer fronts its
|
|
107
221
|
// header. A share the buyer chooses makes each entry keep what arrived.
|
|
108
|
-
const chosen = lotterySplit(l).some(
|
|
222
|
+
const chosen = lotterySplit(l).some(s => String(s.to).startsWith('chosen'));
|
|
109
223
|
const entry = BigInt(entryWidth(chosen, Boolean(l.ticketNft))) * RENT_PER_BYTE;
|
|
110
224
|
const held = roundRentOf(l, m);
|
|
111
225
|
const funded = held > 0n;
|
|
112
|
-
row(
|
|
226
|
+
row(
|
|
227
|
+
`lottery:${l.key}`,
|
|
228
|
+
`buy tickets for “${l.name}”`,
|
|
229
|
+
'player',
|
|
230
|
+
FEES.player,
|
|
231
|
+
funded
|
|
232
|
+
? []
|
|
233
|
+
: [
|
|
234
|
+
back(
|
|
235
|
+
'the purchase’s entry in the round (its bytes)',
|
|
236
|
+
entry,
|
|
237
|
+
'never: it goes to the treasury when the round closes',
|
|
238
|
+
),
|
|
239
|
+
],
|
|
240
|
+
funded
|
|
241
|
+
? `A round’s record, its token accounts and each purchase’s entry are paid from the SOL the lottery holds for them (${sol(held)} SOL), back to it when the round closes: a buyer pays no program rent. When that runs short, a buyer pays their entry’s bytes (about ${sol(entry)} SOL, to the treasury at the close) and a round’s first buyer fronts its header, back to them at the close.`
|
|
242
|
+
: 'A round’s first buyer also fronts its record’s header and token accounts (about 0.003 SOL and up, more with many places), back to them when the round closes, once every part is pulled; `roundRent` (on by default) has the lottery pay them, and each entry too.',
|
|
243
|
+
);
|
|
113
244
|
}
|
|
114
245
|
for (const f of m.farms) {
|
|
115
246
|
// A seat that can stop earning writes its end in the farm's record: a new time costs whoever writes it, spare rent first.
|
|
116
247
|
const endRent = BigInt(END_BYTES) * RENT_PER_BYTE;
|
|
117
|
-
row(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
248
|
+
row(
|
|
249
|
+
`farm:${f.key}`,
|
|
250
|
+
`stake in “${f.name}”`,
|
|
251
|
+
'player',
|
|
252
|
+
FEES.player,
|
|
253
|
+
[
|
|
254
|
+
back('the seat', rent(ACCOUNT_BYTES.Seat), 'when they take everything out'),
|
|
255
|
+
...(farmEnds(f)
|
|
256
|
+
? [
|
|
257
|
+
back(
|
|
258
|
+
`a new end time in the farm’s record of ends (${END_BYTES} bytes; none when another seat ends at that second, or spare rent in the record covers it)`,
|
|
259
|
+
endRent,
|
|
260
|
+
'never: it stays with the record for the next time written',
|
|
261
|
+
),
|
|
262
|
+
]
|
|
263
|
+
: []),
|
|
264
|
+
],
|
|
265
|
+
farmEnds(f)
|
|
266
|
+
? 'Whatever writes a new end time (a stake, a booster put on, a refresh that reads new upkeep) pays its bytes, its sender’s.'
|
|
267
|
+
: undefined,
|
|
268
|
+
);
|
|
269
|
+
row(
|
|
270
|
+
`farm:${f.key}`,
|
|
271
|
+
`unstake or harvest from “${f.name}”`,
|
|
272
|
+
'player',
|
|
273
|
+
FEES.player,
|
|
274
|
+
[],
|
|
275
|
+
[
|
|
276
|
+
(f.tiers ?? []).some(t => t.exitFee) &&
|
|
277
|
+
'Leaving a tier before its lock leaves its exit fee of what is taken out, and keeps the rewards the seat was credited.',
|
|
278
|
+
]
|
|
279
|
+
.filter(Boolean)
|
|
280
|
+
.join(' ') || undefined,
|
|
281
|
+
);
|
|
121
282
|
}
|
|
122
|
-
for (const h of m.holds)
|
|
283
|
+
for (const h of m.holds)
|
|
284
|
+
row(`hold:${h.key}`, `hand in for “${h.name}”`, 'player', FEES.player, [
|
|
285
|
+
back('the hold', rent(ACCOUNT_BYTES.Hold), 'when it is released'),
|
|
286
|
+
]);
|
|
123
287
|
// What giving a prize opens, paid by whoever sends the instruction that gives it (prize.rs).
|
|
124
|
-
const opens =
|
|
125
|
-
|
|
288
|
+
const opens = p =>
|
|
289
|
+
prizeLine(p)?.compressed
|
|
290
|
+
? ', and the compressed NFT, which has no rent'
|
|
291
|
+
: ({
|
|
292
|
+
item: ', plus the collectible’s rent',
|
|
293
|
+
tokens: ', plus the winner’s token account for the prize when they have none',
|
|
294
|
+
share: ', plus the winner’s token account for the prize when they have none',
|
|
295
|
+
nft: ', and the NFT is handed over in the claim (a Token Metadata NFT opens the winner’s token account of it)',
|
|
296
|
+
roll: ', plus the roll’s order (below)',
|
|
297
|
+
}[prizeLine(p)?.gives] ?? '');
|
|
298
|
+
for (const g of m.grants)
|
|
299
|
+
row(
|
|
300
|
+
`grant:${g.key}`,
|
|
301
|
+
`claim “${g.name}”`,
|
|
302
|
+
'the winner, who signs every claim (anyone may pay its fees)',
|
|
303
|
+
FEES.player,
|
|
304
|
+
[],
|
|
305
|
+
`Only the winning wallet can claim: it signs, whatever proves it won (a voucher, a list, an open grant). Whoever pays the transaction pays the player fee from its own SOL; its records (${LIMIT_RECORD_BYTES} bytes a wallet, ${ROUND_RECORD_BYTES} a round it is paid)${opens(g.prize ?? g.gives)} come from the grant’s funds (the sender pays what they can’t), and go back to the owner when its shards close after the grant: xsolana_simulate gives what to put in its funds per claim.`,
|
|
306
|
+
);
|
|
126
307
|
// A prize of a collectible given when a place is paid or a winning entry claimed: its rent, from whoever sends it.
|
|
127
|
-
for (const p of prizes(m).filter(
|
|
128
|
-
|
|
308
|
+
for (const p of prizes(m).filter(
|
|
309
|
+
p => p.prize.gives === 'item' && !p.prize.compressed && !['grant', 'recipe'].includes(p.route),
|
|
310
|
+
))
|
|
311
|
+
row(
|
|
312
|
+
p.by,
|
|
313
|
+
`give ${prizeWords(p.prize, m)} (${p.route})`,
|
|
314
|
+
p.route === 'mission'
|
|
315
|
+
? 'the player leaving the mission'
|
|
316
|
+
: p.route === 'contest'
|
|
317
|
+
? 'whoever claims the winning entry'
|
|
318
|
+
: 'whoever pays the place',
|
|
319
|
+
0n,
|
|
320
|
+
[back('the new collectible', COLLECTIBLE_RENT, 'never: it is the collectible’s own rent')],
|
|
321
|
+
);
|
|
129
322
|
for (const c of m.contests) {
|
|
130
|
-
row(
|
|
131
|
-
|
|
323
|
+
row(
|
|
324
|
+
`contest:${c.key}`,
|
|
325
|
+
`back a side in “${c.name}”`,
|
|
326
|
+
'player',
|
|
327
|
+
FEES.player,
|
|
328
|
+
[
|
|
329
|
+
back(
|
|
330
|
+
'the entry’s bytes in the contest’s list',
|
|
331
|
+
BigInt(c.stakeNft ? CONTEST_ENTRY_BYTES.nfts : CONTEST_ENTRY_BYTES.tokens) * RENT_PER_BYTE,
|
|
332
|
+
'never: they go to the treasury when the contest closes',
|
|
333
|
+
),
|
|
334
|
+
],
|
|
335
|
+
'A loser of a contest of tokens sends nothing more; every other entry is paid by a claim anyone may send.',
|
|
336
|
+
);
|
|
337
|
+
row(`contest:${c.key}`, `open “${c.name}”`, 'authority', FEES.project, [
|
|
338
|
+
back('the contest and its pot', rent(contestBytes(c, m)) + rent(165), 'when it closes'),
|
|
339
|
+
]);
|
|
132
340
|
row(`contest:${c.key}`, `decide “${c.name}”`, 'authority', FEES.project);
|
|
133
341
|
}
|
|
134
342
|
for (const a of m.auctions) {
|
|
135
|
-
row(
|
|
343
|
+
row(
|
|
344
|
+
`auction:${a.key}`,
|
|
345
|
+
`bid on “${a.name}”`,
|
|
346
|
+
'player',
|
|
347
|
+
FEES.player,
|
|
348
|
+
[],
|
|
349
|
+
'Nothing to deposit: the bid waits in the game’s vault, and goes back at once when it is topped.',
|
|
350
|
+
);
|
|
136
351
|
const minted = a.lot?.item && (m.item.get(a.lot.item)?.kind ?? 'collectible') === 'collectible';
|
|
137
|
-
row(
|
|
352
|
+
row(
|
|
353
|
+
`auction:${a.key}`,
|
|
354
|
+
`end “${a.name}”`,
|
|
355
|
+
'anyone (the winner, a keeper)',
|
|
356
|
+
0n,
|
|
357
|
+
minted
|
|
358
|
+
? [
|
|
359
|
+
back(
|
|
360
|
+
'the new collectible',
|
|
361
|
+
COLLECTIBLE_RENT,
|
|
362
|
+
'never: it is the collectible’s own rent, paid by whoever ends it',
|
|
363
|
+
),
|
|
364
|
+
]
|
|
365
|
+
: [],
|
|
366
|
+
'Its own rent goes back to whoever opened it.',
|
|
367
|
+
);
|
|
138
368
|
}
|
|
139
|
-
for (const s of m.stashes)
|
|
369
|
+
for (const s of m.stashes)
|
|
370
|
+
row(`stash:${s.key}`, `put an NFT in “${s.name}”, or take one out`, 'the owner or the authority', FEES.project, [
|
|
371
|
+
back(
|
|
372
|
+
'32 bytes more of the stash for each NFT in it',
|
|
373
|
+
32n * RENT_PER_BYTE,
|
|
374
|
+
'to whoever takes one out; a roll leaves it with the stash, for the next one put in',
|
|
375
|
+
),
|
|
376
|
+
]);
|
|
140
377
|
// A prize that is a roll of a box: whoever sends the claim, the payment, the end (or a grant's funds) makes its order.
|
|
141
|
-
for (const r of prizeRolls(m).filter(
|
|
378
|
+
for (const r of prizeRolls(m).filter(r => !['mission', 'recipe'].includes(r.route))) {
|
|
142
379
|
const name = m.box.get(r.box)?.name ?? r.box;
|
|
143
|
-
const who =
|
|
380
|
+
const who =
|
|
381
|
+
r.route === 'grant'
|
|
382
|
+
? 'the grant’s funds (the sender, what they can’t)'
|
|
383
|
+
: r.route === 'auction'
|
|
384
|
+
? 'whoever ends it'
|
|
385
|
+
: r.route === 'contest'
|
|
386
|
+
? 'whoever claims the winning entry'
|
|
387
|
+
: 'whoever pays the place';
|
|
144
388
|
// Whoever settles it pays a collectible's rent when its box gives one (an item outcome).
|
|
145
|
-
const mints = (m.box.get(r.box)?.outcomes ?? []).flatMap(outcomeParts).some(
|
|
146
|
-
row(
|
|
147
|
-
|
|
389
|
+
const mints = (m.box.get(r.box)?.outcomes ?? []).flatMap(outcomeParts).some(o => holdsRent(o, m));
|
|
390
|
+
row(
|
|
391
|
+
r.by,
|
|
392
|
+
`make a winner’s roll of “${name}” (${r.route})`,
|
|
393
|
+
who,
|
|
394
|
+
0n,
|
|
395
|
+
[back('the roll’s order', rent(boxOrderBytes(m.box.get(r.box), m)), 'to the winner when it is settled')],
|
|
396
|
+
`Then the winner or the project’s keys ask the oracle for the roll (its fee, below) and settle it${mints ? `, paying the collectible’s rent when it gives one (about ${sol(COLLECTIBLE_RENT)} SOL)` : ''}.`,
|
|
397
|
+
);
|
|
398
|
+
row(
|
|
399
|
+
r.by,
|
|
400
|
+
`ask for the roll of “${name}”`,
|
|
401
|
+
'the winner or the project’s keys',
|
|
402
|
+
0n,
|
|
403
|
+
[],
|
|
404
|
+
'It asks MagicBlock’s oracle, which takes its fee from whoever asks; a refund doesn’t return it.',
|
|
405
|
+
ORACLE_FEE,
|
|
406
|
+
);
|
|
148
407
|
}
|
|
149
|
-
for (const d of m.decisions)
|
|
150
|
-
|
|
151
|
-
|
|
408
|
+
for (const d of m.decisions)
|
|
409
|
+
row(
|
|
410
|
+
`decision:${d.key}`,
|
|
411
|
+
d.when,
|
|
412
|
+
'authority',
|
|
413
|
+
FEES.project,
|
|
414
|
+
[],
|
|
415
|
+
d.does === 'roll' ? 'The roll asks MagicBlock’s oracle, which takes its own fee from the authority.' : undefined,
|
|
416
|
+
d.does === 'roll' ? ORACLE_FEE : 0n,
|
|
417
|
+
);
|
|
418
|
+
if (m.allBoxes.some(b => !boxBuys(b, m)))
|
|
419
|
+
row(
|
|
420
|
+
'orders',
|
|
421
|
+
'settle an order',
|
|
422
|
+
'the player, a key they named, or the project’s keys',
|
|
423
|
+
0n,
|
|
424
|
+
[],
|
|
425
|
+
'The sender pays what the settle opens or mints (a collectible’s rent, a token account the player lacks); the order’s rent goes back to the player. js/play’s throwaway key sends it with what the open lent it.',
|
|
426
|
+
);
|
|
427
|
+
if (m.schedules.length)
|
|
428
|
+
row(
|
|
429
|
+
'schedules',
|
|
430
|
+
'run a schedule when it is due',
|
|
431
|
+
'the project’s keys, or who it pays (a sealed one’s recipient, from their page)',
|
|
432
|
+
0n,
|
|
433
|
+
[],
|
|
434
|
+
`A run missed is missed: one sent late pays once, for the latest run due, and the runs before it lapse.${m.schedules.some(sc => sc.sealed) ? ' A sealed schedule pays from its own vault, and its fixed amount pays every run due, in one transaction.' : ''}`,
|
|
435
|
+
);
|
|
152
436
|
if (m.lotteries.length) {
|
|
153
|
-
row(
|
|
154
|
-
|
|
155
|
-
|
|
437
|
+
row(
|
|
438
|
+
'lotteries',
|
|
439
|
+
'ask for a draw',
|
|
440
|
+
'anyone (a keeper, or a player)',
|
|
441
|
+
0n,
|
|
442
|
+
[],
|
|
443
|
+
'The draw asks MagicBlock’s oracle for the winning numbers, and fixes the round’s money first (the next round sells at once); the oracle takes its fee from whoever sends the draw. An oracle that never answers within about a week: the next ask cancels the round, and every ticket is refunded.',
|
|
444
|
+
ORACLE_FEE,
|
|
445
|
+
);
|
|
446
|
+
row(
|
|
447
|
+
'lotteries',
|
|
448
|
+
'pull a part: a refund, a place’s part of the pot or its prize, a share owed in common',
|
|
449
|
+
'anyone (the winner’s page, a keeper)',
|
|
450
|
+
0n,
|
|
451
|
+
[],
|
|
452
|
+
'Each part is pulled by itself, in any order, and the pull that leaves nothing owed closes the round; whoever pulls opens the payee’s token account when it is missing.',
|
|
453
|
+
);
|
|
454
|
+
if (m.lotteries.some(l => l.minSold))
|
|
455
|
+
row(
|
|
456
|
+
'lotteries',
|
|
457
|
+
'a short round',
|
|
458
|
+
'anyone',
|
|
459
|
+
0n,
|
|
460
|
+
[],
|
|
461
|
+
'A round that sold fewer tickets than its least is cancelled at its draw, with no oracle fee, and every ticket refunded.',
|
|
462
|
+
);
|
|
156
463
|
}
|
|
157
464
|
return rows;
|
|
158
465
|
}
|
|
@@ -162,23 +469,40 @@ export function actionCosts(economy, { now } = NO_OPTIONS) {
|
|
|
162
469
|
export function cost(economy, { now = Math.floor(Date.now() / 1000), only = null, live = null } = {}) {
|
|
163
470
|
const plan = compile(economy, { now, only, live });
|
|
164
471
|
if (!plan.ok) return plan;
|
|
165
|
-
return {
|
|
472
|
+
return {
|
|
473
|
+
ok: true,
|
|
474
|
+
setup: setupCost(plan, economy),
|
|
475
|
+
actions: actionCosts(economy, { now }),
|
|
476
|
+
fees: { playerSol: sol(FEES.player), projectSol: sol(FEES.project), oracleSol: sol(ORACLE_FEE) },
|
|
477
|
+
};
|
|
166
478
|
}
|
|
167
479
|
|
|
168
480
|
/** The cost in plain words. */
|
|
169
481
|
export function explainCost(c) {
|
|
170
482
|
const s = c.setup;
|
|
171
|
-
const lines = [
|
|
172
|
-
|
|
483
|
+
const lines = [
|
|
484
|
+
`Going live takes ${s.transactions} transaction${s.transactions === 1 ? '' : 's'} (${s.approvals} wallet approval${s.approvals === 1 ? '' : 's'}) and about ${s.totalSol} SOL:`,
|
|
485
|
+
];
|
|
486
|
+
lines.push(
|
|
487
|
+
` rent ${s.rentSol} SOL for ${s.accounts.length} accounts; program fees ${s.programFeesSol} SOL; network fees ${s.networkFeesSol} SOL${
|
|
488
|
+
Object.keys(s.seeds).length
|
|
489
|
+
? `; seeds ${Object.entries(s.seeds)
|
|
490
|
+
.map(([k, v]) => `${v} ${k}`)
|
|
491
|
+
.join(', ')}`
|
|
492
|
+
: ''
|
|
493
|
+
}.`,
|
|
494
|
+
);
|
|
173
495
|
for (const a of s.accounts) lines.push(` ${a.approx ? 'about ' : ''}${a.sol} SOL: ${a.what} (${a.bytes} bytes)`);
|
|
174
496
|
lines.push('While it runs (for a player, quote the “in all” number: it is what leaves their wallet for good):');
|
|
175
497
|
for (const r of c.actions) {
|
|
176
|
-
const deposits = r.deposits.map(
|
|
498
|
+
const deposits = r.deposits.map(d => `${d.sol} SOL for ${d.what} (back: ${d.back})`).join('; ');
|
|
177
499
|
if (r.inAll) {
|
|
178
500
|
lines.push(` ${r.action} (${r.who}): ${r.inAll}${r.note ? `. ${r.note}` : ''}`);
|
|
179
501
|
continue;
|
|
180
502
|
}
|
|
181
|
-
lines.push(
|
|
503
|
+
lines.push(
|
|
504
|
+
` ${r.action} (${r.who}): ${r.feeSol ? `${r.feeSol} SOL fee + ` : ''}${r.oracleSol ? `${r.oracleSol} SOL to the oracle + ` : ''}${r.networkSol} SOL network${deposits ? `; ${deposits}` : ''}${r.note ? `. ${r.note}` : ''}`,
|
|
505
|
+
);
|
|
182
506
|
}
|
|
183
507
|
for (const n of s.notes) lines.push(n);
|
|
184
508
|
return lines.join('\n');
|