@xgamesfun/solana 0.25.4 → 0.26.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/README.md +8 -8
- package/js/approve/details.mjs +52 -2
- package/js/approve/live-check.mjs +0 -15
- package/js/approve/server.mjs +5 -4
- package/js/chain/token2022.js +3 -2
- package/js/client/accounts.js +67 -25
- package/js/client/grant.js +7 -2
- package/js/client/idl/loot.js +1 -1
- package/js/client/idl/loot.json +716 -183
- package/js/client/ix.js +617 -113
- package/js/client/sizes.js +210 -64
- package/js/economy/check.mjs +691 -261
- package/js/economy/cost.mjs +2 -4
- package/js/economy/explain.mjs +16 -2
- package/js/economy/guide.mjs +12 -12
- package/js/economy/model.mjs +133 -14
- package/js/economy/plan.mjs +68 -19
- package/js/economy/review.mjs +50 -8
- package/js/economy/schema.json +73 -29
- package/js/economy/simulate.mjs +116 -13
- package/js/mcp/tools.mjs +2 -2
- package/js/play/index.js +855 -296
- package/package.json +1 -1
- package/release.json +5 -5
- package/skills/xsolana-economy/SKILL.md +12 -4
- package/skills/xsolana-economy/references/after-launch.md +1 -1
- package/skills/xsolana-economy/references/apps.md +11 -2
- package/skills/xsolana-economy/references/sentence-to-spec.md +3 -3
- package/skills/xsolana-economy/references/telling-the-person.md +22 -4
- package/skills/xsolana-economy/references/trust-and-cheating.md +15 -5
- package/types/chain/token2022.d.ts +3 -2
- package/types/client/accounts.d.ts +34 -11
- package/types/client/idl/loot.d.ts +25 -4
- package/types/client/ix.d.ts +217 -45
- package/types/client/sizes.d.ts +42 -8
- package/types/economy/check.d.mts +11 -2
- package/types/economy/cost.d.mts +1 -0
- package/types/economy/live.d.mts +2 -0
- package/types/economy/model.d.mts +75 -12
- package/types/economy/plan.d.mts +31 -2
- package/types/economy/simulate.d.mts +12 -1
- package/types/mcp/server.d.mts +12 -1
- package/types/mcp/tools.d.mts +12 -1
- package/types/play/index.d.ts +210 -144
package/js/economy/plan.mjs
CHANGED
|
@@ -21,7 +21,12 @@ import {
|
|
|
21
21
|
} from '../chain/pda.js';
|
|
22
22
|
import { createTableIx, extendTableIx } from '../chain/lookup.js';
|
|
23
23
|
import { MEMO_PROGRAM } from '../chain/token2022.js';
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
computeUnitLimit,
|
|
26
|
+
createAssociatedTokenAccountIdempotent,
|
|
27
|
+
syncNative,
|
|
28
|
+
transferLamports,
|
|
29
|
+
} from '../chain/programs.js';
|
|
25
30
|
import {
|
|
26
31
|
createAccountIx,
|
|
27
32
|
initializeMint2Ix,
|
|
@@ -34,7 +39,7 @@ import { compileMessage, transactionSize } from '../chain/tx.js';
|
|
|
34
39
|
import { sha256 } from '../chain/sha256.js';
|
|
35
40
|
import { utf8 } from '../chain/bytes.js';
|
|
36
41
|
import * as loot from '../client/index.js';
|
|
37
|
-
import { check } from './check.mjs';
|
|
42
|
+
import { check, MAX_COMPUTE_UNITS } from './check.mjs';
|
|
38
43
|
import {
|
|
39
44
|
ACCOUNT_OVERHEAD,
|
|
40
45
|
boxBuys,
|
|
@@ -187,7 +192,7 @@ export function boxArgs(b, m) {
|
|
|
187
192
|
rise: rise(b.rise, t),
|
|
188
193
|
fall: fall(b.fall, t, m.now),
|
|
189
194
|
stock_odds: !!b.stockOdds,
|
|
190
|
-
when_out: b.whenOut
|
|
195
|
+
when_out: WHEN_OUT[b.whenOut ?? 'stopSales'],
|
|
191
196
|
active: b.active !== false,
|
|
192
197
|
shares: shares(b.split, m),
|
|
193
198
|
// What one open draws: each slot so many draws among the outcomes it names (all, when none), no outcome twice
|
|
@@ -371,9 +376,13 @@ export function lotteryArgs(l, m) {
|
|
|
371
376
|
shares: shares(lotterySplit(l), m),
|
|
372
377
|
// A place takes a share of the pot, a prize for its winner, or both.
|
|
373
378
|
// A run of `times` places alike: "places 2 to 201 each take 1%" is one.
|
|
374
|
-
places: (l.places ?? [])
|
|
375
|
-
.
|
|
376
|
-
|
|
379
|
+
places: (l.places ?? []).map(placeOf).map(p => ({
|
|
380
|
+
bps: Math.round(p.percent * 100),
|
|
381
|
+
prize: prizeOrNull(p.prize, m),
|
|
382
|
+
// What a place of tokens from a pool gets when the pool holds less than the places with a winner win.
|
|
383
|
+
when_short: WHEN_SHORT[p.whenShort ?? 'inOrder'],
|
|
384
|
+
times: p.times,
|
|
385
|
+
})),
|
|
377
386
|
ticket_mint: l.ticketCurrency ? `@mint:${l.ticketCurrency}` : '11111111111111111111111111111111',
|
|
378
387
|
draws_at: BigInt(l.firstDraw ? toTime(l.firstDraw, m.now) : m.now + every),
|
|
379
388
|
// A ticket paid with one NFT that passes the rule, in place of tokens.
|
|
@@ -382,6 +391,10 @@ export function lotteryArgs(l, m) {
|
|
|
382
391
|
returns: Boolean(l.noLoss),
|
|
383
392
|
// A least sale: a round that sold fewer tickets is cancelled at its draw and refunded.
|
|
384
393
|
min_sold: BigInt(l.minSold ?? 0),
|
|
394
|
+
// Sales close this long before the draw (0: at the draw).
|
|
395
|
+
closes_before: BigInt(toSeconds(l.closesBefore ?? 0)),
|
|
396
|
+
// A no-loss lottery's purchase counts for its tickets times the seconds from it to the close of sales.
|
|
397
|
+
time_weighted: Boolean(l.timeWeighted),
|
|
385
398
|
},
|
|
386
399
|
every: BigInt(every),
|
|
387
400
|
gate: gateOf(l.gate, m),
|
|
@@ -389,6 +402,14 @@ export function lotteryArgs(l, m) {
|
|
|
389
402
|
};
|
|
390
403
|
}
|
|
391
404
|
|
|
405
|
+
/** A box's `whenOut` in the spec, and the program's `WhenOut`: stop selling while an outcome is out (a paid miss is
|
|
406
|
+
* refunded), draw among what is left, or give nothing for a draw on an outcome that is out. */
|
|
407
|
+
export const WHEN_OUT = { stopSales: 'StopSales', drawLeft: 'DrawLeft', giveNothing: 'GiveNothing' };
|
|
408
|
+
/** A place's `whenShort` in the spec, and the program's `WhenShort`: what places of tokens from one pool get when the
|
|
409
|
+
* pool holds less than they win. Prizes of 1,000, 500 and 500 from a pool holding 1,700: 1,000, 500 and 0 in order;
|
|
410
|
+
* 850, 425 and 425 in proportion; or nothing. */
|
|
411
|
+
export const WHEN_SHORT = { inOrder: 'InOrder', inProportion: 'InProportion', nothing: 'Nothing' };
|
|
412
|
+
|
|
392
413
|
/**
|
|
393
414
|
* A lottery round's header as its first purchase sizes it (`LotteryRound::new`: its `head`), and the token accounts a
|
|
394
415
|
* round may open (`accounts`: the payment mint's, the pot's, one for each run whose prize comes from a pool). The
|
|
@@ -405,15 +426,17 @@ export function roundSize(l, m) {
|
|
|
405
426
|
.reduce((n, p) => n + Number(p.times ?? 1), 0);
|
|
406
427
|
const termsBytes = encodeBorsh(IDL, { defined: { name: 'LotteryTerms' } }, terms).length;
|
|
407
428
|
// The tag; project, lottery, id, incarnation, round, bump, opener, lent, head, status, revision; the terms; fee, sold,
|
|
408
|
-
// to_pot;
|
|
429
|
+
// to_pot; the weight sold (1 byte in a round that doesn't weigh, 17 in one that does); common; owns, owed, the draw's
|
|
430
|
+
// slots and times, answer and first place's point (16), the pot's four numbers; paid; prizes (59
|
|
409
431
|
// bytes each); accounts (64 each); nfts; assigned.
|
|
410
432
|
const head =
|
|
411
433
|
1 +
|
|
412
434
|
130 +
|
|
413
435
|
termsBytes +
|
|
414
436
|
17 +
|
|
437
|
+
(terms.time_weighted ? 17 : 1) +
|
|
415
438
|
(4 + 8 * terms.shares.length) +
|
|
416
|
-
|
|
439
|
+
120 +
|
|
417
440
|
(4 + Math.ceil((2 * places) / 8)) +
|
|
418
441
|
(4 + 59 * tokens) +
|
|
419
442
|
(4 + 64 * (2 + pools)) +
|
|
@@ -424,6 +447,15 @@ export function roundSize(l, m) {
|
|
|
424
447
|
return { head, accounts };
|
|
425
448
|
}
|
|
426
449
|
|
|
450
|
+
/** What decides the bytes of a purchase's entry in a lottery's round (`loot.entryWidth`'s arguments): whether it keeps
|
|
451
|
+
* what arrived (a share the buyer chooses, or a weighted round of paid tickets), whether the ticket is an NFT, and
|
|
452
|
+
* whether the round weighs its tickets by time. */
|
|
453
|
+
export const entryShape = l => {
|
|
454
|
+
const weighted = Boolean(l.timeWeighted);
|
|
455
|
+
const chosen = lotterySplit(l).some(s => String(s.to).startsWith('chosen'));
|
|
456
|
+
return [chosen || (weighted && !l.ticketNft), Boolean(l.ticketNft), weighted];
|
|
457
|
+
};
|
|
458
|
+
|
|
427
459
|
/** How many rounds of a lottery are open or still paying out at once, and how many purchases a round takes, as the
|
|
428
460
|
* spec expects them (`expect`): by default two rounds of a lottery that draws again (one selling, one paying out) and
|
|
429
461
|
* one of a lottery that draws once; a round's purchases from the scenario's purchases a day over a round, else 100. */
|
|
@@ -449,10 +481,7 @@ export function roundRentOf(l, m) {
|
|
|
449
481
|
if (l.roundRent !== undefined) return toUnits(l.roundRent, 9);
|
|
450
482
|
const { head, accounts } = roundSize(l, m);
|
|
451
483
|
const { rounds, entries } = roundsExpected(l, m);
|
|
452
|
-
const width = loot.entryWidth(
|
|
453
|
-
lotterySplit(l).some(s => String(s.to).startsWith('chosen')),
|
|
454
|
-
Boolean(l.ticketNft),
|
|
455
|
-
);
|
|
484
|
+
const width = loot.entryWidth(...entryShape(l));
|
|
456
485
|
// The header and its entries' list head (6 bytes), each token account, each entry's bytes.
|
|
457
486
|
const one = rent(head + 6) + BigInt(accounts) * rent(165) + BigInt(entries * width) * RENT_PER_BYTE;
|
|
458
487
|
const all = BigInt(rounds) * one;
|
|
@@ -496,7 +525,7 @@ export function contestArgs(c, m, at = m.now) {
|
|
|
496
525
|
// Each entry stakes one NFT that passes this rule, and the NFTs are the pot: each winner shares the losers', at most
|
|
497
526
|
// `losersEach` of them (none: no cap).
|
|
498
527
|
nft: c.stakeNft ? ruleOf(c.stakeNft, m) : null,
|
|
499
|
-
cap: c.stakeNft && c.losersEach ? c.losersEach : null,
|
|
528
|
+
cap: c.stakeNft && c.losersEach !== undefined ? c.losersEach : null,
|
|
500
529
|
};
|
|
501
530
|
}
|
|
502
531
|
|
|
@@ -920,6 +949,10 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
|
|
|
920
949
|
id: `${part}:${kind}`,
|
|
921
950
|
part,
|
|
922
951
|
says,
|
|
952
|
+
// A box's write finds each of its pools' and stashes' addresses (1,500 units a try) and compares its entries
|
|
953
|
+
// pair by pair: a box of 16 stashes takes some 170,000 units, more where its addresses take more tries, past
|
|
954
|
+
// the 200,000 an instruction has by default. Its transaction asks for all a transaction can have.
|
|
955
|
+
compute: Boolean(def.Box),
|
|
923
956
|
creates: [{ what, bytes }, ...pot],
|
|
924
957
|
build: ctx => [
|
|
925
958
|
loot.upsertIx({ owner: ctx.owner, project: project(ctx), def: resolve(def, ctx), ...options(ctx) }),
|
|
@@ -1598,6 +1631,7 @@ export const recordBytesOf = (kind, body) => {
|
|
|
1598
1631
|
revision: 0,
|
|
1599
1632
|
...placeholders(loot.withDefaults({ Lottery: { args } }).Lottery.args),
|
|
1600
1633
|
round: 0,
|
|
1634
|
+
last: 0,
|
|
1601
1635
|
pots: 0,
|
|
1602
1636
|
owing: NOBODY,
|
|
1603
1637
|
}),
|
|
@@ -1760,7 +1794,18 @@ export function setupContext(m, { owner: ownerKey, keys, live: given = {}, rent
|
|
|
1760
1794
|
export const stepBytes = (instructions, ctx) =>
|
|
1761
1795
|
transactionSize(compileMessage({ payer: ctx.owner, blockhash: placeholderKey('blockhash'), instructions }));
|
|
1762
1796
|
|
|
1763
|
-
/**
|
|
1797
|
+
/** `instructions`, led by a compute limit of all a transaction can have when `compute` (a box's write is in them). */
|
|
1798
|
+
const budgeted = (instructions, compute) =>
|
|
1799
|
+
compute ? [computeUnitLimit(MAX_COMPUTE_UNITS), ...instructions] : instructions;
|
|
1800
|
+
|
|
1801
|
+
/** The bytes of a transaction of `step` alone, as `pack` sends it (its compute limit included). */
|
|
1802
|
+
export const stepAloneBytes = (step, ctx) => stepBytes(budgeted(step.build(ctx), Boolean(step.compute)), ctx);
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* Packs steps into transactions under the size budget, one signer set at a time; each step whole. A transaction that
|
|
1806
|
+
* carries a step needing more compute than an instruction has by default (`compute`: a box's write) asks for all a
|
|
1807
|
+
* transaction can have, first.
|
|
1808
|
+
*/
|
|
1764
1809
|
export function pack(steps, ctx) {
|
|
1765
1810
|
const transactions = [];
|
|
1766
1811
|
let current = null;
|
|
@@ -1770,25 +1815,28 @@ export function pack(steps, ctx) {
|
|
|
1770
1815
|
// A step with nothing to send (the lookup table of a plan built with no recent slot) is left out.
|
|
1771
1816
|
if (!instructions.length) continue;
|
|
1772
1817
|
const signerKind = step.signers.includes('authority') ? 'authority' : 'owner';
|
|
1773
|
-
// A write that names boxes to roll takes all the compute a transaction has: alone.
|
|
1774
1818
|
if (current && current.signer === signerKind && !step.alone && !current.alone) {
|
|
1775
|
-
const
|
|
1819
|
+
const compute = current.compute || Boolean(step.compute);
|
|
1820
|
+
const next = budgeted([...current.built.flat(), ...instructions], compute);
|
|
1776
1821
|
if (sizeOf(next) <= BUDGET) {
|
|
1777
1822
|
current.instructions = next;
|
|
1823
|
+
current.compute = compute;
|
|
1778
1824
|
current.steps.push(step);
|
|
1779
1825
|
current.built.push(instructions);
|
|
1780
1826
|
current.signers = [...new Set([...current.signers, ...step.signers])];
|
|
1781
1827
|
continue;
|
|
1782
1828
|
}
|
|
1783
1829
|
}
|
|
1784
|
-
const
|
|
1830
|
+
const alone = budgeted(instructions, Boolean(step.compute));
|
|
1831
|
+
const size = sizeOf(alone);
|
|
1785
1832
|
if (size > BUDGET) throw new Error(`The step “${step.says}” is ${size} bytes: too big for one transaction.`);
|
|
1786
1833
|
// `built`: each step's own instructions, in the order of `steps`.
|
|
1787
1834
|
current = {
|
|
1788
1835
|
signer: signerKind,
|
|
1789
1836
|
steps: [step],
|
|
1790
1837
|
built: [instructions],
|
|
1791
|
-
instructions,
|
|
1838
|
+
instructions: alone,
|
|
1839
|
+
compute: Boolean(step.compute),
|
|
1792
1840
|
signers: [...step.signers],
|
|
1793
1841
|
alone: Boolean(step.alone),
|
|
1794
1842
|
};
|
|
@@ -1803,7 +1851,8 @@ export function pack(steps, ctx) {
|
|
|
1803
1851
|
* real instructions; without it they are built on stand-in addresses (for sizing, cost and showing).
|
|
1804
1852
|
*/
|
|
1805
1853
|
export function plan(economy, { now = Math.floor(Date.now() / 1000), ctx = null, only = null, live = null } = {}) {
|
|
1806
|
-
|
|
1854
|
+
// With a real context the project is known: a box's write is checked with its own address searches counted.
|
|
1855
|
+
const checked = check(economy, { now, ...(ctx?.project ? { project: ctx.project } : {}) });
|
|
1807
1856
|
if (!checked.ok) return { ok: false, refusals: checked.refusals, warnings: checked.warnings };
|
|
1808
1857
|
const { steps, model: m } = setupSteps(economy, { now, only, live });
|
|
1809
1858
|
if (ctx)
|
package/js/economy/review.mjs
CHANGED
|
@@ -384,7 +384,7 @@ export function review(economy, { now = Math.floor(Date.now() / 1000) } = {}) {
|
|
|
384
384
|
const free = price === 0;
|
|
385
385
|
const ceiling = free
|
|
386
386
|
? `${b.perWallet ? `${b.perWallet} per wallet, and wallets are free: ` : ''}${it?.supply ? `all ${it.supply}` : 'as many as they like'}, for only the fees.`
|
|
387
|
-
: `What they pay for, at the price: ${rolls ? 'nobody, the project included, can steer the roll' : 'nothing free'}.${b.whenOut === 'drawLeft' && rolls ? ' With "whenOut": "drawLeft", someone watching the stock can empty a common outcome, then open for the rare at better odds.' : ''}`;
|
|
387
|
+
: `What they pay for, at the price: ${rolls ? 'nobody, the project included, can steer the roll' : 'nothing free'}.${b.whenOut === 'drawLeft' && rolls ? ' With "whenOut": "drawLeft", someone watching the stock can empty a common outcome, then open for the rare at better odds.' : ''}${b.whenOut === 'giveNothing' && rolls ? ' With "whenOut": "giveNothing", a draw on an outcome that is out gives nothing and is not refunded.' : ''}`;
|
|
388
388
|
routes.push({
|
|
389
389
|
kind,
|
|
390
390
|
key,
|
|
@@ -526,15 +526,21 @@ export function review(economy, { now = Math.floor(Date.now() / 1000) } = {}) {
|
|
|
526
526
|
].filter(Boolean),
|
|
527
527
|
trusts: ['MagicBlock’s oracle (the draw)'],
|
|
528
528
|
trust: null,
|
|
529
|
-
ceiling:
|
|
529
|
+
ceiling: l.timeWeighted
|
|
530
|
+
? `A place by chance, by weight: each purchase counts its tickets times the seconds left until sales close, so a ticket held longer has a better chance; "perWallet" limits a wallet, not a person.`
|
|
531
|
+
: `A place by chance: each ticket is one chance, and "perWallet" limits a wallet, not a person. Tickets are not weighted by time, so one buyer may take most of a round’s tickets just before ${l.closesBefore ? 'sales close' : 'the draw'}.`,
|
|
530
532
|
feeds: [...new Set((l.places ?? []).flatMap(p => feedsOf(p.prize)))],
|
|
531
533
|
});
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
534
|
+
if (!l.timeWeighted)
|
|
535
|
+
find(
|
|
536
|
+
'R-LOTTERY-LATE',
|
|
537
|
+
l.noLoss && !l.closesBefore ? 'low' : 'info',
|
|
538
|
+
`lottery:${l.key}`,
|
|
539
|
+
`Anyone may buy most of ${label(l)}’s tickets right before ${l.closesBefore ? `sales close (${l.closesBefore} before the draw)` : 'the draw'}, risking only ${l.noLoss ? 'the fees: every ticket’s price comes back' : 'the price and fees'}; the draw itself is the oracle’s, which nobody steers.`,
|
|
540
|
+
l.noLoss
|
|
541
|
+
? 'Weigh tickets by the time they are held ("timeWeighted": true), or close sales a while before the draw ("closesBefore").'
|
|
542
|
+
: undefined,
|
|
543
|
+
);
|
|
538
544
|
}
|
|
539
545
|
for (const c of m.contests) {
|
|
540
546
|
const deciders = c.deciders ?? [];
|
|
@@ -670,6 +676,42 @@ export function review(economy, { now = Math.floor(Date.now() / 1000) } = {}) {
|
|
|
670
676
|
const key = /^economy\.tokens\.([^.]+)/.exec(w.where)?.[1];
|
|
671
677
|
find('R-MINT-POWERS', 'low', key ? `token:${key}` : 'economy', w.message, w.fix);
|
|
672
678
|
}
|
|
679
|
+
// What the program takes and a player, a partner or a typo can turn against the project (loot 0.26): each warning,
|
|
680
|
+
// as a finding on its route.
|
|
681
|
+
const routeOf = where => {
|
|
682
|
+
const [, list, key] = /^economy\.([a-z]+)\.([^.[]+)/.exec(where) ?? [];
|
|
683
|
+
const kinds = {
|
|
684
|
+
boxes: 'box',
|
|
685
|
+
lotteries: 'lottery',
|
|
686
|
+
auctions: 'auction',
|
|
687
|
+
contests: 'contest',
|
|
688
|
+
farms: 'farm',
|
|
689
|
+
schedules: 'schedule',
|
|
690
|
+
recipes: 'recipe',
|
|
691
|
+
grants: 'grant',
|
|
692
|
+
holds: 'hold',
|
|
693
|
+
stashes: 'stash',
|
|
694
|
+
tokens: 'token',
|
|
695
|
+
};
|
|
696
|
+
return kinds[list] ? `${kinds[list]}:${key}` : 'economy';
|
|
697
|
+
};
|
|
698
|
+
for (const [code, id, severity] of [
|
|
699
|
+
['W-FARM-AUTHORITY', 'R-FARM-AUTHORITY', 'high'],
|
|
700
|
+
['W-SCHEDULE-PAYEE', 'R-SCHEDULE-PAYEE', 'medium'],
|
|
701
|
+
['W-FAR-TIME', 'R-FAR-TIME', 'medium'],
|
|
702
|
+
['W-HOLD-UP', 'R-HOLD-UP', 'medium'],
|
|
703
|
+
['W-AUCTION-OUTSIDE', 'R-AUCTION-OUTSIDE', 'medium'],
|
|
704
|
+
['W-OUTSIDE-NFT', 'R-OUTSIDE-NFT', 'medium'],
|
|
705
|
+
['W-COLLECTION-PARTNER', 'R-COLLECTION-PARTNER', 'medium'],
|
|
706
|
+
['W-FARM-MISSION', 'R-FARM-MISSION', 'medium'],
|
|
707
|
+
['W-CONTEST-PRIZE', 'R-CONTEST-PRIZE', 'medium'],
|
|
708
|
+
['W-SHARED-SOURCE', 'R-SHARED-SOURCE', 'low'],
|
|
709
|
+
['W-LOTTERY-POOL', 'R-LOTTERY-POOL', 'low'],
|
|
710
|
+
['W-LOTTERY-WEIGHT', 'R-LOTTERY-WEIGHT', 'info'],
|
|
711
|
+
['W-ZERO', 'R-ZERO', 'low'],
|
|
712
|
+
['W-ANSWER', 'R-ANSWER', 'low'],
|
|
713
|
+
])
|
|
714
|
+
for (const w of warned(code)) find(id, severity, routeOf(w.where), w.message, w.fix);
|
|
673
715
|
|
|
674
716
|
findings.sort((a, b) => SEVERITIES.indexOf(a.severity) - SEVERITIES.indexOf(b.severity));
|
|
675
717
|
return {
|
package/js/economy/schema.json
CHANGED
|
@@ -278,7 +278,7 @@
|
|
|
278
278
|
},
|
|
279
279
|
"destination": {
|
|
280
280
|
"type": "string",
|
|
281
|
-
"description": "Where a share goes: \"burn\", \"treasury\" (the game's), \"pool:<key>\", \"wallet:<address>\" (a partner, a creator, a charity: the program pays their own account for the currency, and opens it when it is not there), \"account:<token account address>\" (an existing token account of that currency), \"holder:<collectible address>\" (whoever holds that collectible at the time), \"lottery:<key>\" (that lottery's pot), \"farm:<key>\" (that farm's rewards vault, shared by its stakers), or, in a box's or a lottery's split, \"chosen\" (a wallet the buyer names when they buy: a referrer, a streamer) or \"chosen:<item key>\" (whoever holds the collectible of that item the buyer names: the plot they build on; the buyer must name one a player holds, or the purchase is refused); in a payment's split, \"chosen\" only (a wallet the payer names each time they pay,
|
|
281
|
+
"description": "Where a share goes: \"burn\", \"treasury\" (the game's), \"pool:<key>\", \"wallet:<address>\" (a partner, a creator, a charity: the program pays their own account for the currency, and opens it when it is not there), \"account:<token account address>\" (an existing token account of that currency), \"holder:<collectible address>\" (whoever holds that collectible at the time), \"lottery:<key>\" (that lottery's pot), \"farm:<key>\" (that farm's rewards vault, shared by its stakers), or, in a box's or a lottery's split, \"chosen\" (a wallet the buyer names when they buy: a referrer, a streamer) or \"chosen:<item key>\" (whoever holds the collectible of that item the buyer names: the plot they build on; the buyer must name one a player holds, or the purchase is refused); in a payment's split, \"chosen\" only (a wallet the payer names each time they pay, their own too). A chosen share can come back to the buyer: it is a rebate they control, not a referral the program can prove. A share that can't be paid (its wallet has no account, nobody holds the collectible) goes to the treasury instead; a sale never fails over it.",
|
|
282
282
|
"pattern": "^(burn|treasury|chosen|chosen:[a-z][a-z0-9-]{0,31}|pool:[a-z][a-z0-9-]{0,31}|lottery:[a-z][a-z0-9-]{0,31}|farm:[a-z][a-z0-9-]{0,31}|wallet:[1-9A-HJ-NP-Za-km-z]{32,44}|account:[1-9A-HJ-NP-Za-km-z]{32,44}|holder:[1-9A-HJ-NP-Za-km-z]{32,44})$"
|
|
283
283
|
},
|
|
284
284
|
"share": {
|
|
@@ -427,6 +427,14 @@
|
|
|
427
427
|
"transferFeePercent": {
|
|
428
428
|
"$ref": "#/$defs/percent"
|
|
429
429
|
},
|
|
430
|
+
"feeChangeable": {
|
|
431
|
+
"type": "boolean",
|
|
432
|
+
"description": "Its issuer can still change its transfer fee (Token-2022's transfer fee config has an authority): raised to 100%, it takes every stake or payment as it leaves."
|
|
433
|
+
},
|
|
434
|
+
"closeAuthority": {
|
|
435
|
+
"type": "boolean",
|
|
436
|
+
"description": "Its mint has a close authority, who could close it and make it again with other powers."
|
|
437
|
+
},
|
|
430
438
|
"pausable": {
|
|
431
439
|
"type": "boolean"
|
|
432
440
|
},
|
|
@@ -435,14 +443,14 @@
|
|
|
435
443
|
},
|
|
436
444
|
"defaultState": {
|
|
437
445
|
"type": "boolean",
|
|
438
|
-
"description": "The mint carries Token-2022's DefaultAccountState, whatever it is set to. With a freeze authority it can be switched to frozen:
|
|
446
|
+
"description": "The mint carries Token-2022's DefaultAccountState, whatever it is set to. With a freeze authority it can be switched to frozen: the checker warns what that does to an auction, a loan, a lottery or a contest in it."
|
|
439
447
|
},
|
|
440
448
|
"nonTransferable": {
|
|
441
449
|
"type": "boolean"
|
|
442
450
|
},
|
|
443
451
|
"confidentialTransfers": {
|
|
444
452
|
"type": "boolean",
|
|
445
|
-
"description": "Its holders can turn plain transfers away (Token-2022 confidential transfers):
|
|
453
|
+
"description": "Its holders can turn plain transfers away (Token-2022 confidential transfers): the checker warns what that does to an auction, a loan, a lottery or a contest in it."
|
|
446
454
|
}
|
|
447
455
|
}
|
|
448
456
|
}
|
|
@@ -875,9 +883,9 @@
|
|
|
875
883
|
"description": "Each item's chance shrinks with what is left of its supply."
|
|
876
884
|
},
|
|
877
885
|
"whenOut": {
|
|
878
|
-
"enum": ["stopSales", "drawLeft"],
|
|
886
|
+
"enum": ["stopSales", "drawLeft", "giveNothing"],
|
|
879
887
|
"default": "stopSales",
|
|
880
|
-
"description": "What a box that rolls does once an outcome can't be given (an item sold out, a pool short of its prize, a stash empty). \"stopSales\" (the default): it isn't sold while any outcome couldn't cover this purchase's draws and those of purchases still waiting for their answer, so every roll is on the odds written, and the project restocks or edits the box to sell again. \"drawLeft\": it keeps selling, and a roll draws among the outcomes still there by their written weights, so the others' chances rise as one runs out: a player can empty a common outcome to make a rare one likelier. A box selling a rare prize keeps \"stopSales\"."
|
|
888
|
+
"description": "What a box that rolls does once an outcome can't be given (an item sold out, a pool short of its prize, a stash empty). \"stopSales\" (the default): it isn't sold while any outcome couldn't cover this purchase's draws and those of purchases still waiting for their answer, so every roll is on the odds written, a paid miss is refunded, and the project restocks or edits the box to sell again; a free roll (another box's, a recipe's, a grant's, a mission's, a lottery place's) is still given after it stops, and is asked again until it hits. \"drawLeft\": it keeps selling, and a roll draws among the outcomes still there by their written weights, so the others' chances rise as one runs out: a player can empty a common outcome to make a rare one likelier. \"giveNothing\": it keeps selling, a roll stays on the weights written, and a draw that lands on an outcome that is out gives nothing: a paid draw is not refunded, a free roll is over. A box selling a rare prize keeps \"stopSales\"."
|
|
881
889
|
},
|
|
882
890
|
"active": {
|
|
883
891
|
"type": "boolean",
|
|
@@ -1389,7 +1397,7 @@
|
|
|
1389
1397
|
"type": "integer",
|
|
1390
1398
|
"minimum": 0,
|
|
1391
1399
|
"maximum": 15,
|
|
1392
|
-
"description": "Pays its places from a pot it keeps from round to round, this one of its 16 (0 unless it moves to a fresh one): each place takes its percent of what the pot holds at the draw with the round's ticket money, so the seed, gifts and shares sent to it (\"lottery:<key>\"), yield, and what earlier rounds' places left all count, and the next round always starts with what the round before left. Write exactly one of \"pot\" or \"rest\".
|
|
1400
|
+
"description": "Pays its places from a pot it keeps from round to round, this one of its 16 (0 unless it moves to a fresh one): each place takes its percent of what the pot holds at the draw with the round's ticket money, so the seed, gifts and shares sent to it (\"lottery:<key>\"), yield, and what earlier rounds' places left all count, and the next round always starts with what the round before left. Write exactly one of \"pot\" or \"rest\". Changing the number between rounds starts a fresh pot; every pot it opened is emptied to the owner when it closes."
|
|
1393
1401
|
},
|
|
1394
1402
|
"rest": {
|
|
1395
1403
|
"$ref": "#/$defs/destination",
|
|
@@ -1422,7 +1430,12 @@
|
|
|
1422
1430
|
"type": "integer",
|
|
1423
1431
|
"minimum": 1,
|
|
1424
1432
|
"maximum": 40960,
|
|
1425
|
-
"description": "How many places in a row are alike (a run): \"places 2 to 201 each take 1%\" is one { \"percent\": 1, \"times\": 200 }. Not for a named NFT
|
|
1433
|
+
"description": "How many places in a row are alike (a run): \"places 2 to 201 each take 1%\" is one { \"percent\": 1, \"times\": 200 }. Not for a named NFT, which one place gives; a run of rolls gives each place its own roll. Default 1."
|
|
1434
|
+
},
|
|
1435
|
+
"whenShort": {
|
|
1436
|
+
"enum": ["inOrder", "inProportion", "nothing"],
|
|
1437
|
+
"default": "inOrder",
|
|
1438
|
+
"description": "For a prize of tokens from a pool: what each place gets when the pool holds less than the places with a winner win, read once when the draw is asked. \"inOrder\" (the default): whole prizes, first place first, while the pool covers one; \"inProportion\": every place the same share of its prize; \"nothing\": no place gets anything, and the pool keeps it. Prizes of 1,000, 500 and 500 from a pool holding 1,700 give 1,000, 500 and 0 in order, 850, 425 and 425 in proportion, or nothing. Every place paid from one pool says the same."
|
|
1426
1439
|
}
|
|
1427
1440
|
}
|
|
1428
1441
|
}
|
|
@@ -1437,6 +1450,14 @@
|
|
|
1437
1450
|
"firstDraw": {
|
|
1438
1451
|
"$ref": "#/$defs/time"
|
|
1439
1452
|
},
|
|
1453
|
+
"closesBefore": {
|
|
1454
|
+
"$ref": "#/$defs/duration",
|
|
1455
|
+
"description": "Sales close this long before each draw (default 0: at the draw); for a lottery that draws again, less than \"every\". A no-loss lottery wants one, so tickets bought just before the draw don't share the pot with tickets held all period."
|
|
1456
|
+
},
|
|
1457
|
+
"timeWeighted": {
|
|
1458
|
+
"type": "boolean",
|
|
1459
|
+
"description": "No-loss lotteries only: each purchase weighs its tickets times the seconds from it to the close of sales, and a place is won by weight, so a ticket held longer has a better chance. Set a sales close you mean (\"closesBefore\"); \"perWallet\" and \"minSold\" still count tickets. Off by default: every ticket counts one."
|
|
1460
|
+
},
|
|
1440
1461
|
"gate": {
|
|
1441
1462
|
"$ref": "#/$defs/gate"
|
|
1442
1463
|
},
|
|
@@ -1485,7 +1506,7 @@
|
|
|
1485
1506
|
},
|
|
1486
1507
|
"noLoss": {
|
|
1487
1508
|
"type": "boolean",
|
|
1488
|
-
"description": "A no-loss lottery: every ticket goes back to its buyer once the round is drawn, and the winners take only the pot, what is paid into it (`seed`, a sponsor, a share of other sales: \"lottery:<key>\")
|
|
1509
|
+
"description": "A no-loss lottery: every ticket goes back to its buyer once the round is drawn, and the winners take only the pot, what is paid into it (`seed`, a sponsor, a share of other sales: \"lottery:<key>\"), or with \"rest\" only its places' prizes. No split, no cut and no `ticketCurrency`; NFT tickets come back too. Set \"closesBefore\", or \"timeWeighted\", so a late buyer doesn't share the pot with tickets held all period."
|
|
1489
1510
|
},
|
|
1490
1511
|
"minSold": {
|
|
1491
1512
|
"type": "integer",
|
|
@@ -1619,8 +1640,8 @@
|
|
|
1619
1640
|
},
|
|
1620
1641
|
"losersEach": {
|
|
1621
1642
|
"type": "integer",
|
|
1622
|
-
"minimum":
|
|
1623
|
-
"description": "In a contest of NFTs (`stakeNft`), the most losers' NFTs one winner takes; what the winners leave goes to whoever opened it. Default: no cap, the winners share every loser's NFT (the first to enter on the winning side take one more when they don't divide evenly), each picking which."
|
|
1643
|
+
"minimum": 0,
|
|
1644
|
+
"description": "In a contest of NFTs (`stakeNft`), the most losers' NFTs one winner takes; what the winners leave goes to whoever opened it (0: every loser's NFT goes to the opener). Default: no cap, the winners share every loser's NFT (the first to enter on the winning side take one more when they don't divide evenly), each picking which."
|
|
1624
1645
|
}
|
|
1625
1646
|
}
|
|
1626
1647
|
},
|
|
@@ -2062,16 +2083,14 @@
|
|
|
2062
2083
|
"oneOf": {
|
|
2063
2084
|
"type": "array",
|
|
2064
2085
|
"minItems": 1,
|
|
2065
|
-
"maxItems": 16,
|
|
2066
2086
|
"items": { "type": "string" },
|
|
2067
|
-
"description": "The attribute is one of these texts
|
|
2087
|
+
"description": "The attribute is one of these texts: a tier of two, one species of forty. As many as the record holds; a lottery's NFT ticket takes at most 170, and a box's gate about 340 (what one answer of the oracle's reads)."
|
|
2068
2088
|
},
|
|
2069
2089
|
"items": {
|
|
2070
2090
|
"type": "array",
|
|
2071
2091
|
"minItems": 1,
|
|
2072
|
-
"maxItems": 16,
|
|
2073
2092
|
"items": { "$ref": "#/$defs/key" },
|
|
2074
|
-
"description": "Any of these items the game mints
|
|
2093
|
+
"description": "Any of these items the game mints, in its own collection: a farm for three tree species, a gate for holders of any of five plots. Read by the program as the collection's `item_id` being one of theirs; not with `item`, `collection` or `attribute`."
|
|
2075
2094
|
},
|
|
2076
2095
|
"time": {
|
|
2077
2096
|
"enum": ["ahead", "passed"],
|
|
@@ -2082,8 +2101,32 @@
|
|
|
2082
2101
|
"boost": {
|
|
2083
2102
|
"type": "number",
|
|
2084
2103
|
"minimum": 0,
|
|
2085
|
-
"maximum":
|
|
2086
|
-
"description": "A percent
|
|
2104
|
+
"maximum": 100000,
|
|
2105
|
+
"description": "A percent a farm's boost adds to a weight: 100 doubles it. At most 100,000 (1,001 times), alone and all of a farm's boosts together: enough for a stat that scales a rate, such as +100% a point of power up to 1,000."
|
|
2106
|
+
},
|
|
2107
|
+
"oddsBoost": {
|
|
2108
|
+
"type": "number",
|
|
2109
|
+
"minimum": 0,
|
|
2110
|
+
"maximum": 42949672.95,
|
|
2111
|
+
"description": "A percent a box's odds rule adds to an outcome's weight: 100 doubles it, 99,900 makes it a thousand times likelier. Any amount: a boosted weight stops at the largest weight (4,294,967,295), and two outcomes boosted past it are equally likely."
|
|
2112
|
+
},
|
|
2113
|
+
"oddsEffect": {
|
|
2114
|
+
"type": "object",
|
|
2115
|
+
"additionalProperties": false,
|
|
2116
|
+
"description": "How much a collectible that passes the rule adds to the outcome's weight: a flat percent, or a percent per point of its attribute (up to `upTo` points).",
|
|
2117
|
+
"properties": {
|
|
2118
|
+
"addPercent": {
|
|
2119
|
+
"$ref": "#/$defs/oddsBoost"
|
|
2120
|
+
},
|
|
2121
|
+
"perPointPercent": {
|
|
2122
|
+
"$ref": "#/$defs/oddsBoost"
|
|
2123
|
+
},
|
|
2124
|
+
"upTo": {
|
|
2125
|
+
"type": "integer",
|
|
2126
|
+
"minimum": 1,
|
|
2127
|
+
"maximum": 4294967295
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2087
2130
|
},
|
|
2088
2131
|
"effect": {
|
|
2089
2132
|
"type": "object",
|
|
@@ -2119,7 +2162,7 @@
|
|
|
2119
2162
|
"$ref": "#/$defs/rule"
|
|
2120
2163
|
},
|
|
2121
2164
|
"add": {
|
|
2122
|
-
"$ref": "#/$defs/
|
|
2165
|
+
"$ref": "#/$defs/oddsEffect"
|
|
2123
2166
|
}
|
|
2124
2167
|
}
|
|
2125
2168
|
},
|
|
@@ -2135,9 +2178,9 @@
|
|
|
2135
2178
|
"multiplier": {
|
|
2136
2179
|
"type": "number",
|
|
2137
2180
|
"exclusiveMinimum": 0,
|
|
2138
|
-
"maximum":
|
|
2181
|
+
"maximum": 100,
|
|
2139
2182
|
"default": 1,
|
|
2140
|
-
"description": "Its weight: above 0 and up to
|
|
2183
|
+
"description": "Its weight: above 0 and up to 100 times what it stakes (below 1: a flexible tier that earns less)."
|
|
2141
2184
|
},
|
|
2142
2185
|
"prize": {
|
|
2143
2186
|
"$ref": "#/$defs/prize",
|
|
@@ -2631,9 +2674,9 @@
|
|
|
2631
2674
|
},
|
|
2632
2675
|
"weight": {
|
|
2633
2676
|
"type": "integer",
|
|
2634
|
-
"minimum":
|
|
2677
|
+
"minimum": 0,
|
|
2635
2678
|
"maximum": 4294967295,
|
|
2636
|
-
"description": "Its weight in the stash's draws (1 when left out): an NFT that weighs 10 is drawn ten times as often as one that weighs 1."
|
|
2679
|
+
"description": "Its weight in the stash's draws (1 when left out): an NFT that weighs 10 is drawn ten times as often as one that weighs 1. 0: never drawn, given only by name."
|
|
2637
2680
|
}
|
|
2638
2681
|
}
|
|
2639
2682
|
},
|
|
@@ -2656,9 +2699,9 @@
|
|
|
2656
2699
|
},
|
|
2657
2700
|
"weight": {
|
|
2658
2701
|
"type": "integer",
|
|
2659
|
-
"minimum":
|
|
2702
|
+
"minimum": 0,
|
|
2660
2703
|
"maximum": 4294967295,
|
|
2661
|
-
"description": "Its weight in the stash's draws (1 when left out): an NFT that weighs 10 is drawn ten times as often as one that weighs 1."
|
|
2704
|
+
"description": "Its weight in the stash's draws (1 when left out): an NFT that weighs 10 is drawn ten times as often as one that weighs 1. 0: never drawn, given only by name."
|
|
2662
2705
|
}
|
|
2663
2706
|
}
|
|
2664
2707
|
},
|
|
@@ -2673,9 +2716,9 @@
|
|
|
2673
2716
|
},
|
|
2674
2717
|
"weight": {
|
|
2675
2718
|
"type": "integer",
|
|
2676
|
-
"minimum":
|
|
2719
|
+
"minimum": 0,
|
|
2677
2720
|
"maximum": 4294967295,
|
|
2678
|
-
"description": "Its weight in the stash's draws (1 when left out): an NFT that weighs 10 is drawn ten times as often as one that weighs 1."
|
|
2721
|
+
"description": "Its weight in the stash's draws (1 when left out): an NFT that weighs 10 is drawn ten times as often as one that weighs 1. 0: never drawn, given only by name."
|
|
2679
2722
|
}
|
|
2680
2723
|
}
|
|
2681
2724
|
}
|
|
@@ -2815,14 +2858,15 @@
|
|
|
2815
2858
|
},
|
|
2816
2859
|
"weight": {
|
|
2817
2860
|
"type": "integer",
|
|
2818
|
-
"minimum":
|
|
2819
|
-
"maximum": 4294967295
|
|
2861
|
+
"minimum": 0,
|
|
2862
|
+
"maximum": 4294967295,
|
|
2863
|
+
"description": "Its weight in the draws. 0: never drawn."
|
|
2820
2864
|
},
|
|
2821
2865
|
"stock": {
|
|
2822
2866
|
"type": "integer",
|
|
2823
|
-
"minimum":
|
|
2867
|
+
"minimum": 0,
|
|
2824
2868
|
"maximum": 4294967294,
|
|
2825
|
-
"description": "How many units may draw it: left out, never runs out."
|
|
2869
|
+
"description": "How many units may draw it: left out, never runs out. 0: never drawn."
|
|
2826
2870
|
}
|
|
2827
2871
|
}
|
|
2828
2872
|
}
|