@xgamesfun/solana 0.25.3 → 0.26.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 +145 -228
- 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 +714 -182
- package/js/client/ix.js +617 -113
- package/js/client/sizes.js +35 -3
- package/js/economy/check.mjs +655 -242
- 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 +36 -11
- 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 +847 -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 +21 -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/economy/check.d.mts +5 -0
- package/types/economy/model.d.mts +75 -12
- package/types/economy/plan.d.mts +22 -1
- 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/client/accounts.js
CHANGED
|
@@ -107,6 +107,9 @@ const RECORDS = [
|
|
|
107
107
|
// The totals, split at each purchase: what each share paid in common takes, the entries that own a part of
|
|
108
108
|
// their own, and the parts owed now.
|
|
109
109
|
['sold', 'u64'],
|
|
110
|
+
// In a weighted round (`time_weighted`), the weight sold: each purchase's tickets times the seconds from it
|
|
111
|
+
// to the close of sales. Null: each ticket weighs one, and the draw is over `sold`.
|
|
112
|
+
['weight', { option: 'u128' }],
|
|
110
113
|
['to_pot', 'u64'],
|
|
111
114
|
['common', { vec: 'u64' }],
|
|
112
115
|
['owns', 'u32'],
|
|
@@ -117,7 +120,8 @@ const RECORDS = [
|
|
|
117
120
|
['first_asked_at', 'i64'],
|
|
118
121
|
['asked_at', 'i64'],
|
|
119
122
|
['answer', { array: ['u8', 32] }],
|
|
120
|
-
|
|
123
|
+
// First place's point: below the draw's total (`roundTotal`), so in a weighted round a point of its weight.
|
|
124
|
+
['first', 'u128'],
|
|
121
125
|
['pot_total', 'u64'],
|
|
122
126
|
['pot_sent', 'u64'],
|
|
123
127
|
['pot_arrived', 'u64'],
|
|
@@ -506,55 +510,94 @@ export const lotteryPots = lottery =>
|
|
|
506
510
|
/** A share the buyer chooses (a referrer, a collectible's holder): paid out of the price as they buy, from their own account. */
|
|
507
511
|
export const chosenAtPurchase = share => Boolean(share.to?.Chosen);
|
|
508
512
|
|
|
509
|
-
/** The bytes of one entry of a round: buyer, word, what arrived (`fee`: a fee mint,
|
|
510
|
-
|
|
513
|
+
/** The bytes of one entry of a round: buyer, word, what arrived (`fee`: a fee mint, a chosen share, or a weighted
|
|
514
|
+
* round of paid tickets), where its weight ends (`weighted`: a round that weighs its tickets by time), the NFT ticket. */
|
|
515
|
+
export const entryWidth = (fee, nft, weighted = false) => 40 + (fee ? 8 : 0) + (weighted ? 16 : 0) + (nft ? 32 : 0);
|
|
516
|
+
|
|
517
|
+
/** What a round's draw picks a point below: the weight sold in a weighted round, the tickets sold otherwise. */
|
|
518
|
+
export const roundTotal = r => (r.weight != null ? BigInt(r.weight) : BigInt(r.sold));
|
|
511
519
|
|
|
512
520
|
/**
|
|
513
521
|
* A lottery round (`["round", lottery, incarnation, round]`): its header (`status` 'Open' | 'Drawing' | 'Drawn' |
|
|
514
|
-
* 'Cancelled', the terms it copied, `sold`, `
|
|
515
|
-
* lottery's own stash each such place got), and `entries`, one a
|
|
516
|
-
*
|
|
522
|
+
* 'Cancelled', the terms it copied, `sold`, `weight` (null in a round that doesn't weigh its tickets), `owed`, the draw
|
|
523
|
+
* and the money the ask fixed, `nfts` the NFT of the lottery's own stash each such place got), and `entries`, one a
|
|
524
|
+
* purchase: { buyer, start, end (its slice of the draw: its tickets, or in a weighted round its weight), tickets (how
|
|
525
|
+
* many it bought), paid (its own part's bit), amount (what reached the round), nft (its NFT ticket) }.
|
|
517
526
|
*/
|
|
518
527
|
export function decodeRound(data) {
|
|
519
528
|
const r = decode('LotteryRound', data);
|
|
520
529
|
const at = Number(r.head);
|
|
521
530
|
const width = Number(readUintLE(data, at, 2));
|
|
522
531
|
const count = Number(readUintLE(data, at + 2, 4));
|
|
523
|
-
|
|
532
|
+
const weighted = r.weight != null;
|
|
533
|
+
if (width !== entryWidth(r.fee, r.terms.nft_ticket != null, weighted) || data.length !== at + 6 + width * count)
|
|
524
534
|
throw new Error('LotteryRound entries do not match its header');
|
|
525
535
|
let start = 0n;
|
|
536
|
+
let sold = 0n;
|
|
526
537
|
const entries = Array.from({ length: count }, (_, i) => {
|
|
527
538
|
const e = data.subarray(at + 6 + width * i, at + 6 + width * (i + 1));
|
|
528
539
|
const word = readUintLE(e, 32, 8);
|
|
529
|
-
|
|
540
|
+
// Its tickets end at the word's low 48 bits; its slice of the draw ends there too, or at its weight's end.
|
|
541
|
+
const last = word & END_BITS;
|
|
542
|
+
const tickets = last - sold;
|
|
530
543
|
let k = 40;
|
|
531
544
|
const take = n => e.subarray((k += n) - n, k);
|
|
532
|
-
const amount = r.fee ? readUintLE(take(8), 0, 8) :
|
|
545
|
+
const amount = r.fee ? readUintLE(take(8), 0, 8) : tickets * BigInt(r.terms.price);
|
|
546
|
+
const end = weighted ? readUintLE(take(16), 0, 16) : last;
|
|
533
547
|
const entry = {
|
|
534
548
|
buyer: encodeBase58(e.subarray(0, 32)),
|
|
535
549
|
start,
|
|
536
550
|
end,
|
|
551
|
+
tickets,
|
|
537
552
|
paid: word & ~END_BITS,
|
|
538
553
|
amount,
|
|
539
554
|
nft: r.terms.nft_ticket ? encodeBase58(take(32)) : NO_ADDRESS,
|
|
540
555
|
};
|
|
541
556
|
start = end;
|
|
557
|
+
sold = last;
|
|
542
558
|
return entry;
|
|
543
559
|
});
|
|
544
560
|
return { ...r, entries };
|
|
545
561
|
}
|
|
546
562
|
|
|
563
|
+
/** When a lottery's sales close (`terms`: a Lottery's or a round's): `closes_before` seconds before its draw. */
|
|
564
|
+
export const salesCloseAt = terms => BigInt(terms.draws_at) - BigInt(terms.closes_before ?? 0);
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* What a purchase of `count` tickets at `now` (Unix seconds, the chain's clock) weighs in a round's draw: one a ticket,
|
|
568
|
+
* or in a lottery that weighs tickets by time (`terms.time_weighted`) each ticket the seconds from the purchase to the
|
|
569
|
+
* close of sales (`salesCloseAt`). 0 once sales have closed: nothing more is sold. The same tickets bought later weigh less.
|
|
570
|
+
*/
|
|
571
|
+
export function ticketWeight(terms, count, now) {
|
|
572
|
+
const left = salesCloseAt(terms) - BigInt(now);
|
|
573
|
+
if (left <= 0n) return 0n;
|
|
574
|
+
return BigInt(count) * (terms.time_weighted ? left : 1n);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* A buyer's chance before they buy: { weight, total, share } of a purchase of `count` tickets at `now` into `round` (its
|
|
579
|
+
* decoded record, null when nobody bought into it yet; `terms` the lottery's then): what it would weigh, the round's
|
|
580
|
+
* weight with it, and its share of that (a number from 0 to 1). Each place is drawn over the round's whole weight, so a
|
|
581
|
+
* share is the purchase's chance at one place as things stand: later purchases make it smaller.
|
|
582
|
+
*/
|
|
583
|
+
export function purchaseOdds(round, terms, count, now) {
|
|
584
|
+
const weight = ticketWeight(round?.terms ?? terms, count, now);
|
|
585
|
+
const total = (round ? roundTotal(round) : 0n) + weight;
|
|
586
|
+
return { weight, total, share: total > 0n ? Number((weight * 1_000_000n) / total) / 1_000_000 : 0 };
|
|
587
|
+
}
|
|
588
|
+
|
|
547
589
|
/** How many places a lottery's (or a round's) `places` are, every run counted (`Place.times`). */
|
|
548
590
|
export const placeCount = places => places.reduce((n, p) => n + Number(p.times ?? 1), 0);
|
|
549
591
|
|
|
550
592
|
/** The places of a round that have a winner: every place, but no more than tickets. */
|
|
551
593
|
export const roundWinners = r => Math.min(placeCount(r.terms.places), Number(r.sold));
|
|
552
594
|
|
|
553
|
-
/** The
|
|
595
|
+
/** The point that wins `place` of a drawn round: even steps of the draw's total around the list from first place's
|
|
596
|
+
* (a ticket's number, or in a weighted round a point of its weight). */
|
|
554
597
|
export function ticketOfPlace(r, place) {
|
|
555
|
-
const
|
|
556
|
-
const ahead = BigInt(place) * (
|
|
557
|
-
const toEnd =
|
|
598
|
+
const total = roundTotal(r);
|
|
599
|
+
const ahead = BigInt(place) * (total / BigInt(roundWinners(r) || 1));
|
|
600
|
+
const toEnd = total - BigInt(r.first);
|
|
558
601
|
return ahead < toEnd ? BigInt(r.first) + ahead : ahead - toEnd;
|
|
559
602
|
}
|
|
560
603
|
|
|
@@ -575,18 +618,18 @@ export function runPlaces(r, run) {
|
|
|
575
618
|
return [Math.min(first, w), Math.min(first + Number(r.terms.places[run].times), w)];
|
|
576
619
|
}
|
|
577
620
|
|
|
578
|
-
/** The places of run `run` whose
|
|
621
|
+
/** The places of run `run` whose point is in `start..end` (an entry's slice of the draw), in order. */
|
|
579
622
|
export function placesIn(r, run, start, end) {
|
|
580
623
|
const [from, to] = runPlaces(r, run);
|
|
581
624
|
const out = [];
|
|
582
625
|
if (from >= to || BigInt(start) >= BigInt(end)) return out;
|
|
583
|
-
const
|
|
584
|
-
const step =
|
|
585
|
-
const d = (BigInt(start) +
|
|
626
|
+
const total = roundTotal(r);
|
|
627
|
+
const step = total / BigInt(roundWinners(r));
|
|
628
|
+
const d = (BigInt(start) + total - BigInt(r.first)) % total;
|
|
586
629
|
const len = BigInt(end) - BigInt(start);
|
|
587
630
|
for (const [a, b] of [
|
|
588
|
-
[d, d + len <
|
|
589
|
-
[0n, d + len >
|
|
631
|
+
[d, d + len < total ? d + len : total],
|
|
632
|
+
[0n, d + len > total ? d + len - total : 0n],
|
|
590
633
|
]) {
|
|
591
634
|
let p = Number((a + step - 1n) / step);
|
|
592
635
|
for (p = Math.max(p, from); p < to && BigInt(p) * step < b; p++) out.push(p);
|
|
@@ -761,14 +804,13 @@ export const placeSet = place => Number(place.bps) > 0 || place.prize != null;
|
|
|
761
804
|
/** The address of a lottery's current round's record (there or not). */
|
|
762
805
|
export const roundAddress = (lotteryAddress, lottery) => pda.round(lotteryAddress, lottery.incarnation, lottery.round);
|
|
763
806
|
|
|
764
|
-
/** Whether a lottery (decoded) drew
|
|
765
|
-
export const lotteryEnded = lottery => Number(lottery.every) === 0 && Number(lottery.round) > 0;
|
|
807
|
+
/** Whether a lottery (decoded) drew its last round and has ended: it draws once (`every` 0) and is past round `last`. */
|
|
808
|
+
export const lotteryEnded = lottery => Number(lottery.every) === 0 && Number(lottery.round) > Number(lottery.last ?? 0);
|
|
766
809
|
|
|
767
|
-
/** The round a lottery's close, its write again and its own stash's moves name (`
|
|
768
|
-
* round's, or once
|
|
769
|
-
* them out). */
|
|
810
|
+
/** The round a lottery's close, its write again and its own stash's moves name (`Lottery::waits_on`): its current
|
|
811
|
+
* round's, or once it has ended, its last round's (which keeps its stash's NFTs until its places took them out). */
|
|
770
812
|
export const idleRound = (lotteryAddress, lottery) =>
|
|
771
|
-
pda.round(lotteryAddress, lottery.incarnation, lotteryEnded(lottery) ?
|
|
813
|
+
pda.round(lotteryAddress, lottery.incarnation, lotteryEnded(lottery) ? lottery.last : lottery.round);
|
|
772
814
|
|
|
773
815
|
/**
|
|
774
816
|
* Who rents a listed NFT at `now` (Unix seconds), from its decoded Listing (or null for none): the renter's wallet while
|
package/js/client/grant.js
CHANGED
|
@@ -19,7 +19,7 @@ import { compareBytes, concat, fromHex, intLE, readUintLE, toBigInt, uintLE, utf
|
|
|
19
19
|
import { meta } from '../chain/programs.js';
|
|
20
20
|
import { sha256 } from '../chain/sha256.js';
|
|
21
21
|
import { recordKey16 } from './accounts.js';
|
|
22
|
-
import { ASSOCIATED_TOKEN_PROGRAM, gateAccounts, lootIx, prizeMetas, prizeNonce, tailOf } from './ix.js';
|
|
22
|
+
import { ASSOCIATED_TOKEN_PROGRAM, gateAccounts, lootIx, prizeMetas, prizeNonce, shownFor, tailOf } from './ix.js';
|
|
23
23
|
import { LOOT_PROGRAM, pda } from './pda.js';
|
|
24
24
|
import IDL from './idl/loot.js';
|
|
25
25
|
|
|
@@ -400,7 +400,12 @@ export function claimGrantIx({
|
|
|
400
400
|
},
|
|
401
401
|
{
|
|
402
402
|
remaining: [
|
|
403
|
-
...gateAccounts(project, {
|
|
403
|
+
...gateAccounts(project, {
|
|
404
|
+
gate: grant.gate,
|
|
405
|
+
wallet,
|
|
406
|
+
shown: shownFor(project, grant.gate, wallet, shown),
|
|
407
|
+
listProof,
|
|
408
|
+
}),
|
|
404
409
|
...shards,
|
|
405
410
|
...prizes,
|
|
406
411
|
...tailOf(extra),
|