@bordrless/shared 0.7.0 → 0.8.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/dist/api.d.ts +220 -5
- package/dist/api.js +23 -0
- package/dist/policy.d.ts +52 -1
- package/dist/policy.js +104 -0
- package/dist/programs.d.ts +18 -0
- package/dist/programs.js +18 -0
- package/dist/studio.d.ts +117 -6
- package/dist/studio.js +199 -3
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -206,6 +206,12 @@ export interface ConfigInspection {
|
|
|
206
206
|
authorShareBps: number;
|
|
207
207
|
/** The hook was built and deployed by Studio: the launch prepares it for the mint itself (its standard `prepare`). */
|
|
208
208
|
studioHook?: boolean;
|
|
209
|
+
/**
|
|
210
|
+
* The hook is a game hook made in Studio (docs/games.md "Phase 2"): its kind and the constants
|
|
211
|
+
* its source fixes. The config then launches a jackpot or streak coin through the companion
|
|
212
|
+
* (`companion.game` of that kind); null or absent for any other hook.
|
|
213
|
+
*/
|
|
214
|
+
game?: import('./studio.ts').StudioGameSettings | null;
|
|
209
215
|
}
|
|
210
216
|
/** Live figures of a launch's token rules; each null when the launch lacks the rule or it is not tracked. */
|
|
211
217
|
export interface RuleStats {
|
|
@@ -248,6 +254,8 @@ export interface ProgramInfo {
|
|
|
248
254
|
upgradeAuthority: Address | null;
|
|
249
255
|
/** Whether the program can still be upgraded; null when unknown. */
|
|
250
256
|
upgradeable: boolean | null;
|
|
257
|
+
/** Phase 3a: present (true) when the upgrade authority is the program's own `hook_timelock` account. */
|
|
258
|
+
timelocked?: boolean;
|
|
251
259
|
}
|
|
252
260
|
export interface LaunchSummary {
|
|
253
261
|
mint: Address;
|
|
@@ -578,11 +586,40 @@ export interface LaunchPrepareRequest {
|
|
|
578
586
|
export type CompanionRequest = {
|
|
579
587
|
split: CompanionSplitBps;
|
|
580
588
|
vestDays?: number;
|
|
581
|
-
game?:
|
|
589
|
+
game?: GameRequest;
|
|
582
590
|
} | {
|
|
583
591
|
template: CompanionTemplate;
|
|
584
592
|
vestDays?: number;
|
|
585
593
|
};
|
|
594
|
+
/** The kinds of game a companion runs (the companion's `GameKind`): the lottery (phase 1), the last-buyer jackpot and the holding streak (phase 2). */
|
|
595
|
+
export type GameKindName = 'lottery' | 'jackpot' | 'streak' | 'strategy';
|
|
596
|
+
/**
|
|
597
|
+
* A game coin's game as the launch request names it: a lottery on Bordrless's lottery hook, or a
|
|
598
|
+
* jackpot or a streak on a game hook made in Studio (docs/games.md "Phase 2"), whose config the
|
|
599
|
+
* request's `config` must name (the kind's settings are the hook's constants: the timer and the
|
|
600
|
+
* minimum buy, the epoch, the minimum streak and weight; `ConfigInspection.game` shows them).
|
|
601
|
+
*/
|
|
602
|
+
export type GameRequest = LotteryRequest | JackpotRequest | StreakRequest;
|
|
603
|
+
/** A jackpot coin's game as the launch request names it (`create_game_v2`): the pot's part, the minimum pot and what a settle pays; the timer and the minimum buy are the hook's. */
|
|
604
|
+
export interface JackpotRequest {
|
|
605
|
+
kind: 'jackpot';
|
|
606
|
+
potBps: number;
|
|
607
|
+
/** A round is paid only while the pot holds at least this, lamports: 0.1 to 1,000 SOL (a round settled below it pays nothing). */
|
|
608
|
+
minPotLamports: Amount;
|
|
609
|
+
/** The part of the pot a settle pays the last buyer, basis points: 10% to 100%. */
|
|
610
|
+
prizeBps: number;
|
|
611
|
+
}
|
|
612
|
+
/** A streak coin's game as the launch request names it (`create_game_v2`): the pot's part, the minimum pot, what an epoch shares and the least a close leaves for claims; the epoch, the minimum streak and weight are the hook's. */
|
|
613
|
+
export interface StreakRequest {
|
|
614
|
+
kind: 'streak';
|
|
615
|
+
potBps: number;
|
|
616
|
+
/** An epoch is closed only while the pot holds at least this, lamports: 0.1 to 1,000 SOL. */
|
|
617
|
+
minPotLamports: Amount;
|
|
618
|
+
/** The part of the pot an epoch shares among its holders, basis points: 10% to 100%. */
|
|
619
|
+
prizeBps: number;
|
|
620
|
+
/** The least a close leaves for claims, seconds: 5 minutes to a day, at most half an epoch. */
|
|
621
|
+
claimWindowSecs: number;
|
|
622
|
+
}
|
|
586
623
|
/**
|
|
587
624
|
* A lottery coin's game as the launch request names it (docs/games.md "The companion's game"):
|
|
588
625
|
* every setting `create_game` fixes before the launch. Bounds: `LOTTERY_LIMITS` (`lotteryProblem`).
|
|
@@ -602,19 +639,28 @@ export interface LotteryRequest {
|
|
|
602
639
|
/** Attempts a draw makes before it rolls over, 1 to 16; all of them within half a round. */
|
|
603
640
|
maxAttempts: number;
|
|
604
641
|
}
|
|
605
|
-
/** A
|
|
642
|
+
/** A game coin's settings as its token page shows them (`CompanionSummary.game`): fixed at the launch, and the hook's terms. A jackpot's and a streak's kind settings (phase 2) are the hook's. */
|
|
606
643
|
export interface GameSummary {
|
|
607
|
-
kind:
|
|
644
|
+
kind: GameKindName;
|
|
608
645
|
/** The game's account, `PDA(["game", mint])` under the companion program. */
|
|
609
646
|
address: Address;
|
|
610
|
-
/** The coin's token hook, which keeps the tickets (the lottery hook). */
|
|
647
|
+
/** The coin's token hook, which keeps the tickets (the lottery hook, or a game hook made in Studio). */
|
|
611
648
|
hook: Address;
|
|
649
|
+
/** A lottery's round, a streak's epoch; 0 for a jackpot. */
|
|
612
650
|
roundSecs: number;
|
|
613
651
|
potBps: number;
|
|
614
652
|
minPot: Amount;
|
|
615
653
|
prizeBps: number;
|
|
654
|
+
/** A lottery's claim attempt window, the least a streak's close leaves for claims; 0 for a jackpot. */
|
|
616
655
|
claimWindowSecs: number;
|
|
656
|
+
/** A lottery's attempts per draw; 0 otherwise. */
|
|
617
657
|
maxAttempts: number;
|
|
658
|
+
/** Jackpot: a round ends this long after its last qualifying buy, and the least a qualifying buy delivers (base units). Absent or 0 for the other kinds. */
|
|
659
|
+
timerSecs?: number;
|
|
660
|
+
minTokens?: Amount;
|
|
661
|
+
/** Streak: by an epoch's end a holding must have sent nothing for this long, and the least weight that shares (base units). Absent or 0 for the other kinds. */
|
|
662
|
+
minStreakSecs?: number;
|
|
663
|
+
minWeight?: Amount;
|
|
618
664
|
/** The most the pot may hold while the hook is not audited (lamports); null for an audited hook. */
|
|
619
665
|
potCap: Amount | null;
|
|
620
666
|
audited: boolean;
|
|
@@ -660,8 +706,100 @@ export interface GameYou {
|
|
|
660
706
|
odds: number | null;
|
|
661
707
|
/** Its tickets in the round before (the one a draw may be for now). */
|
|
662
708
|
previousTickets: Amount;
|
|
709
|
+
/** A jackpot (phase 2): the wallet's place in it. Absent for the other kinds. */
|
|
710
|
+
jackpot?: JackpotYou | null;
|
|
711
|
+
/** A streak (phase 2): the wallet's weight and share. Absent for the other kinds. */
|
|
712
|
+
streak?: StreakYou | null;
|
|
713
|
+
}
|
|
714
|
+
/** The caller's place in a jackpot (`GameYou.jackpot`). */
|
|
715
|
+
export interface JackpotYou {
|
|
716
|
+
/** The number of the wallet's first qualifying buy since it last sent anything; '0' after any send (the mark). */
|
|
717
|
+
mark: Amount;
|
|
718
|
+
/** The wallet is the current round's last qualifying buyer. */
|
|
719
|
+
lastBuyer: boolean;
|
|
720
|
+
/** Among the rounds over and not settled, those this wallet would be paid for if settled now (it still holds what it bought). */
|
|
721
|
+
wins: number;
|
|
722
|
+
}
|
|
723
|
+
/** The caller's place in a streak (`GameYou.streak`). */
|
|
724
|
+
export interface StreakYou {
|
|
725
|
+
/** The wallet's weight in the current epoch, base units ('0' until registered: `enter` registers it, a trade writes it). */
|
|
726
|
+
weight: Amount;
|
|
727
|
+
/** Whether a holding that sends nothing until the epoch's end will share (it has held long enough, at least the minimum weight). */
|
|
728
|
+
qualifies: boolean;
|
|
729
|
+
/** The claim epoch (the one open for claims): the wallet's weight in it and the share it can claim now, lamports; '0' after a claim or a send. */
|
|
730
|
+
claimWeight: Amount;
|
|
731
|
+
claimShare: Amount;
|
|
732
|
+
/** The wallet's receipt of the claim epoch exists: its share was paid. */
|
|
733
|
+
claimed: boolean;
|
|
734
|
+
}
|
|
735
|
+
/** A jackpot round as the status lists it (`JackpotStatus.open`, `JackpotStatus.rounds`): its number, buyer, the amount bought and when, and what became of it. */
|
|
736
|
+
export interface JackpotRound {
|
|
737
|
+
/** The round's number: that of its last qualifying buy. */
|
|
738
|
+
number: number;
|
|
739
|
+
buyer: Address;
|
|
740
|
+
amount: Amount;
|
|
741
|
+
/** When its last qualifying buy landed (its timer ran out `timerSecs` later). */
|
|
742
|
+
boughtAt: number;
|
|
743
|
+
/** `open`: over, not settled yet; `paid`; `unfunded`: closed paying nothing (the pot below its minimum); `forfeited`: the buyer sold or sent, or can't be paid. */
|
|
744
|
+
status: 'open' | 'paid' | 'unfunded' | 'forfeited';
|
|
745
|
+
prize: Amount | null;
|
|
746
|
+
reason: 'notHeld' | 'unpayable' | 'stale' | null;
|
|
747
|
+
signature: string | null;
|
|
748
|
+
at: number | null;
|
|
749
|
+
}
|
|
750
|
+
/** A jackpot coin's live state (`GameStatus.jackpot`, docs/games.md "The jackpot"). */
|
|
751
|
+
export interface JackpotStatus {
|
|
752
|
+
/** The current round's last qualifying buyer, what they bought and when; null before the first qualifying buy. */
|
|
753
|
+
lastBuyer: Address | null;
|
|
754
|
+
lastAmount: Amount;
|
|
755
|
+
lastBuyAt: number | null;
|
|
756
|
+
/** When the current round's timer runs out (the last buyer is paid if they still hold); null without a buy. */
|
|
757
|
+
timerEndsAt: number | null;
|
|
758
|
+
/** Qualifying buys so far (the current round's number), and the last round settled. */
|
|
759
|
+
buys: number;
|
|
760
|
+
paidBuys: number;
|
|
761
|
+
/** Buys count only while the coin is on its curve: once graduated, the jackpot ends (the round under way is still paid). */
|
|
762
|
+
onCurve: boolean;
|
|
763
|
+
/** Rounds over and not settled yet, oldest first (a keeper settles them within seconds). */
|
|
764
|
+
open: JackpotRound[];
|
|
765
|
+
/** Rounds settled, as the indexer saw them, newest first. */
|
|
766
|
+
rounds: JackpotRound[];
|
|
767
|
+
}
|
|
768
|
+
/** A streak epoch as the status lists it (`StreakStatus.epochs`): what the indexer saw of it. */
|
|
769
|
+
export interface StreakEpoch {
|
|
770
|
+
epoch: number;
|
|
771
|
+
epochEndsAt: number;
|
|
772
|
+
/** `closed`: its pot fixed, claims open; `ended`: claims over; `rolledOver`: no pot (no weight, or closed too late). */
|
|
773
|
+
status: 'closed' | 'ended' | 'rolledOver';
|
|
774
|
+
/** Its weight total and its pot, lamports. */
|
|
775
|
+
total: Amount;
|
|
776
|
+
pot: Amount;
|
|
777
|
+
/** Paid out so far, lamports, and how many claims. */
|
|
778
|
+
paid: Amount;
|
|
779
|
+
claims: number;
|
|
780
|
+
signature: string | null;
|
|
781
|
+
at: number | null;
|
|
663
782
|
}
|
|
664
|
-
/** A
|
|
783
|
+
/** A streak coin's live state (`GameStatus.streak`, docs/games.md "The streak"). */
|
|
784
|
+
export interface StreakStatus {
|
|
785
|
+
/** The claim epoch: the one whose claims are open, its pot, total, what it paid so far and when its claims end; null while none is open. */
|
|
786
|
+
claim: {
|
|
787
|
+
epoch: number;
|
|
788
|
+
pot: Amount;
|
|
789
|
+
total: Amount;
|
|
790
|
+
paid: Amount;
|
|
791
|
+
claimsEndAt: number;
|
|
792
|
+
} | null;
|
|
793
|
+
/** The epoch before the current one: over, not closed yet (a keeper closes it); null once closed or rolled over. */
|
|
794
|
+
closing: {
|
|
795
|
+
epoch: number;
|
|
796
|
+
total: Amount;
|
|
797
|
+
closeBy: number;
|
|
798
|
+
} | null;
|
|
799
|
+
/** Epochs closed, ended or rolled over, as the indexer saw them, newest first. */
|
|
800
|
+
epochs: StreakEpoch[];
|
|
801
|
+
}
|
|
802
|
+
/** A game coin's live state (`CompanionStatus.game`, docs/games.md): the pot, the round (a streak's epoch), the caller's place in it and the history. A lottery's draw and draws; a jackpot's and a streak's under their own names. */
|
|
665
803
|
export interface GameStatus extends GameSummary {
|
|
666
804
|
/** Bridged SOL the pot holds, lamports. */
|
|
667
805
|
pot: Amount;
|
|
@@ -689,6 +827,21 @@ export interface GameStatus extends GameSummary {
|
|
|
689
827
|
lastWinner: Address | null;
|
|
690
828
|
you: GameYou | null;
|
|
691
829
|
draws: GameDraw[];
|
|
830
|
+
/** A jackpot's live state (phase 2); absent or null for the other kinds. */
|
|
831
|
+
jackpot?: JackpotStatus | null;
|
|
832
|
+
/** A streak's live state (phase 2); absent or null for the other kinds. */
|
|
833
|
+
streak?: StreakStatus | null;
|
|
834
|
+
}
|
|
835
|
+
/** `POST /v1/game/claim/prepare`: a streak holder's own `claim_share` of the claim epoch (docs/games.md "The streak"): paid to the wallet, which pays its receipt's rent (back once the epoch's claims end, `close_receipt`). */
|
|
836
|
+
export interface GameClaimRequest {
|
|
837
|
+
wallet: Address;
|
|
838
|
+
mint: Address;
|
|
839
|
+
}
|
|
840
|
+
export interface GameClaimResponse {
|
|
841
|
+
transactions: PreparedTx[];
|
|
842
|
+
/** The epoch claimed and the share it pays, lamports (before the sender's bounty, which is the wallet's own here). */
|
|
843
|
+
epoch: number;
|
|
844
|
+
share: Amount;
|
|
692
845
|
}
|
|
693
846
|
/** `POST /v1/game/enter/prepare`: a holder's own `enter` (docs/games.md): their whole balance registered as tickets for the current round, when the holding was not written this round yet. */
|
|
694
847
|
export interface GameEnterRequest {
|
|
@@ -1247,3 +1400,65 @@ export interface HookInspection {
|
|
|
1247
1400
|
/** Why it can't be launched with yet, one sentence each. */
|
|
1248
1401
|
problems: string[];
|
|
1249
1402
|
}
|
|
1403
|
+
/** Trading wallets an account may have registered at once. */
|
|
1404
|
+
export declare const MAX_TRADING_WALLETS = 5;
|
|
1405
|
+
/** The header the account session travels in, browser → site proxy → backend. The same token serves Studio (`STUDIO_SESSION_HEADER`). */
|
|
1406
|
+
export declare const ACCOUNT_SESSION_HEADER = "x-studio-session";
|
|
1407
|
+
/** The message a wallet signs to sign in to Bordrless: no transaction, nothing moves. */
|
|
1408
|
+
export declare function accountSignInMessage(wallet: Address, nonce: string, issuedAt: string): string;
|
|
1409
|
+
/** The message a trading wallet signs to be registered under an account: proof the key exists and is held by whoever holds the account. */
|
|
1410
|
+
export declare function tradingWalletMessage(account: Address, publicKey: Address): string;
|
|
1411
|
+
export interface AccountSessionRequest {
|
|
1412
|
+
wallet: Address;
|
|
1413
|
+
/** The exact message signed (`accountSignInMessage`). */
|
|
1414
|
+
message: string;
|
|
1415
|
+
/** Base58 ed25519 signature of the message's UTF-8 bytes. */
|
|
1416
|
+
signature: string;
|
|
1417
|
+
}
|
|
1418
|
+
export interface AccountSessionResponse {
|
|
1419
|
+
token: string;
|
|
1420
|
+
wallet: Address;
|
|
1421
|
+
/** Unix seconds. */
|
|
1422
|
+
expiresAt: number;
|
|
1423
|
+
}
|
|
1424
|
+
export interface AccountWallet {
|
|
1425
|
+
publicKey: Address;
|
|
1426
|
+
label: string;
|
|
1427
|
+
/** Unix seconds. */
|
|
1428
|
+
createdAt: number;
|
|
1429
|
+
revokedAt: number | null;
|
|
1430
|
+
}
|
|
1431
|
+
export interface AccountWallets {
|
|
1432
|
+
account: Address;
|
|
1433
|
+
/** Active registrations, oldest first. */
|
|
1434
|
+
wallets: AccountWallet[];
|
|
1435
|
+
max: number;
|
|
1436
|
+
}
|
|
1437
|
+
export interface AccountWalletRegisterRequest {
|
|
1438
|
+
publicKey: Address;
|
|
1439
|
+
/** At most 32 characters. */
|
|
1440
|
+
label: string;
|
|
1441
|
+
/** Base58 ed25519 signature by `publicKey` of `tradingWalletMessage(account, publicKey)`. */
|
|
1442
|
+
signature: string;
|
|
1443
|
+
}
|
|
1444
|
+
/** A plain SOL transfer out of a registered trading wallet (to the signup wallet, usually). */
|
|
1445
|
+
export interface AccountWithdrawPrepareRequest {
|
|
1446
|
+
/** The trading wallet that pays and signs. */
|
|
1447
|
+
wallet: Address;
|
|
1448
|
+
to: Address;
|
|
1449
|
+
lamports: Amount;
|
|
1450
|
+
}
|
|
1451
|
+
export interface AccountWithdrawPrepareResponse {
|
|
1452
|
+
transactions: PreparedTx[];
|
|
1453
|
+
/** The network fee the transfer pays, lamports. */
|
|
1454
|
+
feeLamports: Amount;
|
|
1455
|
+
}
|
|
1456
|
+
export interface AccountWithdrawSendRequest {
|
|
1457
|
+
/** Base64 of the signed transaction from `AccountWithdrawPrepareResponse`. */
|
|
1458
|
+
transaction: string;
|
|
1459
|
+
}
|
|
1460
|
+
export interface AccountWithdrawSendResponse {
|
|
1461
|
+
signature: string;
|
|
1462
|
+
state: 'confirmed' | 'failed' | 'expired' | 'pending';
|
|
1463
|
+
error: string | null;
|
|
1464
|
+
}
|
package/dist/api.js
CHANGED
|
@@ -7,3 +7,26 @@
|
|
|
7
7
|
export const HOOK_ABOUT_CHARS = 280;
|
|
8
8
|
/** The official summary's limits, in characters. */
|
|
9
9
|
export const HOOK_SUMMARY_LIMITS = { title: 40, text: 320 };
|
|
10
|
+
// ---- accounts and trading wallets --------------------------------------------------------------------
|
|
11
|
+
// POST /v1/account/session -> AccountSessionResponse the signup wallet signs `accountSignInMessage`
|
|
12
|
+
// GET /v1/account/wallets -> AccountWallets the session's trading wallets (public keys only)
|
|
13
|
+
// POST /v1/account/wallets -> AccountWallets register one; the trading wallet co-signs `tradingWalletMessage`
|
|
14
|
+
// POST /v1/account/wallets/:pubkey/revoke -> AccountWallets
|
|
15
|
+
// POST /v1/account/withdraw/prepare -> AccountWithdrawPrepareResponse a plain SOL transfer from a registered trading wallet
|
|
16
|
+
// POST /v1/account/withdraw/send -> AccountWithdrawSendResponse the signed transfer, sent and followed
|
|
17
|
+
//
|
|
18
|
+
// An account is a wallet: the one a visitor connected (their signup wallet), or, without one, a
|
|
19
|
+
// trading wallet generated in their browser. Trading wallets are keypairs made and kept in the
|
|
20
|
+
// browser; Bordrless stores their public keys and labels under the account, never a secret.
|
|
21
|
+
/** Trading wallets an account may have registered at once. */
|
|
22
|
+
export const MAX_TRADING_WALLETS = 5;
|
|
23
|
+
/** The header the account session travels in, browser → site proxy → backend. The same token serves Studio (`STUDIO_SESSION_HEADER`). */
|
|
24
|
+
export const ACCOUNT_SESSION_HEADER = 'x-studio-session';
|
|
25
|
+
/** The message a wallet signs to sign in to Bordrless: no transaction, nothing moves. */
|
|
26
|
+
export function accountSignInMessage(wallet, nonce, issuedAt) {
|
|
27
|
+
return ['Sign in to Bordrless', '', 'This signature proves you own this wallet. It does not move funds or approve any transaction.', '', `Wallet: ${wallet}`, `Nonce: ${nonce}`, `Issued: ${issuedAt}`].join('\n');
|
|
28
|
+
}
|
|
29
|
+
/** The message a trading wallet signs to be registered under an account: proof the key exists and is held by whoever holds the account. */
|
|
30
|
+
export function tradingWalletMessage(account, publicKey) {
|
|
31
|
+
return ['Bordrless trading wallet', '', 'This wallet was generated in your browser for trading on Bordrless and belongs to the account below.', '', `Account: ${account}`, `Wallet: ${publicKey}`].join('\n');
|
|
32
|
+
}
|
package/dist/policy.d.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* `programs/tests/vectors/launch-fees.json`, which the Rust tests render from the reference the
|
|
7
7
|
* programs are held to, and requires exact equality.
|
|
8
8
|
*/
|
|
9
|
-
import type { CompanionSplitBps, CompanionTemplate, GameRolloverReason, LaunchRules, LaunchRulesInput, LotteryRequest, Side } from './api.ts';
|
|
9
|
+
import type { CompanionSplitBps, CompanionTemplate, GameKindName, GameRequest, GameRolloverReason, JackpotRequest, LaunchRules, LaunchRulesInput, LotteryRequest, Side, StreakRequest } from './api.ts';
|
|
10
|
+
import type { StudioGameSettings } from './studio.ts';
|
|
10
11
|
export declare const BPS = 10000n;
|
|
11
12
|
export declare const WAD: bigint;
|
|
12
13
|
/** The largest u64, where the programs' checked arithmetic stops. */
|
|
@@ -347,6 +348,56 @@ export declare const LOTTERY_COPY: {
|
|
|
347
348
|
readonly cap: "Pot capped at 10 SOL until the game’s hook is audited.";
|
|
348
349
|
readonly oracle: "Randomness by ORAO VRF: ORAO’s three signers produce it. They could withhold or bias a draw; Bordrless can’t.";
|
|
349
350
|
};
|
|
351
|
+
/** The jackpot's timer and the streak's minimum streak: the companion's bounds (`GAME_LIMITS` in the SDK). */
|
|
352
|
+
export declare const GAME_KIND_LIMITS: {
|
|
353
|
+
readonly minTimerSecs: 300;
|
|
354
|
+
readonly maxTimerSecs: number;
|
|
355
|
+
readonly maxMinStreakSecs: number;
|
|
356
|
+
};
|
|
357
|
+
/** A jackpot as the launch form starts it (spec example 2, `JACKPOT_DEFAULTS` in the SDK): 70% of the fee to the pot, 30% bought back, half the pot to the last buyer from 0.5 SOL. */
|
|
358
|
+
export declare const JACKPOT_REQUEST_DEFAULTS: Readonly<JackpotRequest>;
|
|
359
|
+
/** A streak as the launch form starts it (spec example 3, `STREAK_DEFAULTS` in the SDK): 70% of the fee to the pot, 30% bought back, the whole pot shared each epoch from 0.1 SOL, an hour at least for claims. */
|
|
360
|
+
export declare const STREAK_REQUEST_DEFAULTS: Readonly<StreakRequest>;
|
|
361
|
+
/** The least a streak's close leaves for claims, as the form offers it (seconds); each must fit half the hook's epoch. */
|
|
362
|
+
export declare const STREAK_CLAIM_WINDOW_CHOICES: readonly {
|
|
363
|
+
secs: number;
|
|
364
|
+
label: string;
|
|
365
|
+
}[];
|
|
366
|
+
/** The game a launch request's `companion.game` names, of any kind, each field a whole number; null when malformed (bounds are `gameProblem`'s). */
|
|
367
|
+
export declare function gameRequestOf(raw: unknown): GameRequest | null;
|
|
368
|
+
/**
|
|
369
|
+
* Why `create_game_v2` (or the launch) would refuse this game with this split on these rules at
|
|
370
|
+
* this fee, with `hook` the Studio game hook the config names (`ConfigInspection.game`; null: the
|
|
371
|
+
* config names none), in one sentence; null when it would not. A lottery is `lotteryProblem`'s.
|
|
372
|
+
* The program's own checks (`gameArgsProblem` in the SDK, the same sentences) and what a game
|
|
373
|
+
* coin's launch needs besides: a creator fee, no token rules, no holders' share, and a hook of the
|
|
374
|
+
* game's kind.
|
|
375
|
+
*/
|
|
376
|
+
export declare function gameProblem(game: GameRequest, split: CompanionSplitBps, rules: LaunchRulesInput, creatorFeeBps: number, hook?: StudioGameSettings | null): string | null;
|
|
377
|
+
/** Each game kind in a word: "jackpot". */
|
|
378
|
+
export declare const GAME_KIND_NAMES: Readonly<Record<GameKindName, string>>;
|
|
379
|
+
/**
|
|
380
|
+
* The copy rules of a jackpot and a streak coin (docs/games.md "Phase 2", "Site"): said on the
|
|
381
|
+
* launch form, the review step and the token page. The pot is held by the companion and paid by
|
|
382
|
+
* code (no randomness in either kind), the 10 SOL cap until the hook is audited, "Bordrless can
|
|
383
|
+
* stop this game"; never "provably fair", "guaranteed" or "audited".
|
|
384
|
+
*/
|
|
385
|
+
export declare const JACKPOT_COPY: {
|
|
386
|
+
readonly held: "The pot is held by the companion program and paid out by code. No randomness: the last buyer wins.";
|
|
387
|
+
readonly rule: "The last buyer wins if they keep every token they bought: any sale or transfer forfeits.";
|
|
388
|
+
readonly unfunded: "If the pot is below its minimum when a round is settled, the round pays nothing and the pot carries on.";
|
|
389
|
+
readonly curve: "Buys count while the coin is on its curve: the jackpot ends at graduation (the round under way is still paid).";
|
|
390
|
+
readonly cap: "Pot capped at 10 SOL until the game’s hook is audited.";
|
|
391
|
+
readonly stop: "Bordrless can stop this game: its pot would be bought back and burned. Nobody is paid.";
|
|
392
|
+
};
|
|
393
|
+
export declare const STREAK_COPY: {
|
|
394
|
+
readonly held: "The pot is held by the companion program and paid out by code. No randomness: holders share in proportion to what they held.";
|
|
395
|
+
readonly rule: "Send anything, to anyone, and you lose this epoch’s and last epoch’s share. Claim before you sell.";
|
|
396
|
+
readonly weight: "Your weight is what you held since the epoch began: tokens that arrive during an epoch count from the next.";
|
|
397
|
+
readonly claims: "Shares are claimed during the next epoch; the keeper claims for everyone whose share covers the fee, the rest claim here. What is not claimed rolls over.";
|
|
398
|
+
readonly cap: "Pot capped at 10 SOL until the game’s hook is audited.";
|
|
399
|
+
readonly stop: "Bordrless can stop this game: its pot would be bought back and burned. Nobody is paid.";
|
|
400
|
+
};
|
|
350
401
|
/** Why a round paid no prize, in a few words each (`GameRolloverReason`). */
|
|
351
402
|
export declare const ROLLOVER_WORDS: Readonly<Record<GameRolloverReason, string>>;
|
|
352
403
|
/** Whether a split is the whole fee to the launcher (no companion needed). */
|
package/dist/policy.js
CHANGED
|
@@ -471,6 +471,110 @@ export const LOTTERY_COPY = {
|
|
|
471
471
|
cap: 'Pot capped at 10 SOL until the game’s hook is audited.',
|
|
472
472
|
oracle: 'Randomness by ORAO VRF: ORAO’s three signers produce it. They could withhold or bias a draw; Bordrless can’t.',
|
|
473
473
|
};
|
|
474
|
+
// ---- jackpot and streak coins (docs/games.md "Phase 2"), on game hooks made in Studio -----------------
|
|
475
|
+
/** The jackpot's timer and the streak's minimum streak: the companion's bounds (`GAME_LIMITS` in the SDK). */
|
|
476
|
+
export const GAME_KIND_LIMITS = { minTimerSecs: 300, maxTimerSecs: 30 * DAY_SECS, maxMinStreakSecs: 365 * DAY_SECS };
|
|
477
|
+
/** A jackpot as the launch form starts it (spec example 2, `JACKPOT_DEFAULTS` in the SDK): 70% of the fee to the pot, 30% bought back, half the pot to the last buyer from 0.5 SOL. */
|
|
478
|
+
export const JACKPOT_REQUEST_DEFAULTS = { kind: 'jackpot', potBps: 7_000, minPotLamports: '500000000', prizeBps: 5_000 };
|
|
479
|
+
/** A streak as the launch form starts it (spec example 3, `STREAK_DEFAULTS` in the SDK): 70% of the fee to the pot, 30% bought back, the whole pot shared each epoch from 0.1 SOL, an hour at least for claims. */
|
|
480
|
+
export const STREAK_REQUEST_DEFAULTS = { kind: 'streak', potBps: 7_000, minPotLamports: '100000000', prizeBps: 10_000, claimWindowSecs: 3_600 };
|
|
481
|
+
/** The least a streak's close leaves for claims, as the form offers it (seconds); each must fit half the hook's epoch. */
|
|
482
|
+
export const STREAK_CLAIM_WINDOW_CHOICES = [
|
|
483
|
+
{ secs: 1_800, label: '30 min' },
|
|
484
|
+
{ secs: 3_600, label: '1 hour' },
|
|
485
|
+
{ secs: 21_600, label: '6 hours' },
|
|
486
|
+
{ secs: DAY_SECS, label: '1 day' },
|
|
487
|
+
];
|
|
488
|
+
/** The game a launch request's `companion.game` names, of any kind, each field a whole number; null when malformed (bounds are `gameProblem`'s). */
|
|
489
|
+
export function gameRequestOf(raw) {
|
|
490
|
+
if (typeof raw !== 'object' || raw === null)
|
|
491
|
+
return null;
|
|
492
|
+
const r = raw;
|
|
493
|
+
if (r.kind === 'lottery')
|
|
494
|
+
return lotteryRequestOf(raw);
|
|
495
|
+
if (r.kind !== 'jackpot' && r.kind !== 'streak')
|
|
496
|
+
return null;
|
|
497
|
+
if (!isWhole(r.potBps, 0, 10_000) || !isWhole(r.prizeBps, 0, 10_000))
|
|
498
|
+
return null;
|
|
499
|
+
if (typeof r.minPotLamports !== 'string' || !/^\d{1,20}$/.test(r.minPotLamports))
|
|
500
|
+
return null;
|
|
501
|
+
if (r.kind === 'jackpot')
|
|
502
|
+
return { kind: 'jackpot', potBps: r.potBps, minPotLamports: r.minPotLamports, prizeBps: r.prizeBps };
|
|
503
|
+
if (!isWhole(r.claimWindowSecs, 0, 2 ** 32 - 1))
|
|
504
|
+
return null;
|
|
505
|
+
return { kind: 'streak', potBps: r.potBps, minPotLamports: r.minPotLamports, prizeBps: r.prizeBps, claimWindowSecs: r.claimWindowSecs };
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Why `create_game_v2` (or the launch) would refuse this game with this split on these rules at
|
|
509
|
+
* this fee, with `hook` the Studio game hook the config names (`ConfigInspection.game`; null: the
|
|
510
|
+
* config names none), in one sentence; null when it would not. A lottery is `lotteryProblem`'s.
|
|
511
|
+
* The program's own checks (`gameArgsProblem` in the SDK, the same sentences) and what a game
|
|
512
|
+
* coin's launch needs besides: a creator fee, no token rules, no holders' share, and a hook of the
|
|
513
|
+
* game's kind.
|
|
514
|
+
*/
|
|
515
|
+
export function gameProblem(game, split, rules, creatorFeeBps, hook = null) {
|
|
516
|
+
if (game.kind === 'lottery')
|
|
517
|
+
return lotteryProblem(game, split, rules, creatorFeeBps);
|
|
518
|
+
const L = LOTTERY_LIMITS;
|
|
519
|
+
const word = GAME_KIND_NAMES[game.kind];
|
|
520
|
+
if (!hook)
|
|
521
|
+
return `A ${word} coin launches from a game hook made in Studio: open your ${word} project there and press Launch with it.`;
|
|
522
|
+
if (hook.kind !== game.kind)
|
|
523
|
+
return `This config’s hook keeps a ${GAME_KIND_NAMES[hook.kind]}’s score, not a ${word}’s.`;
|
|
524
|
+
if (creatorFeeBps === 0)
|
|
525
|
+
return 'There is no creator fee to fill the pot with: set one.';
|
|
526
|
+
if (Object.keys(NO_RULES).some((k) => rules[k] !== 0))
|
|
527
|
+
return `A ${word} coin has no other token rules: its hook keeps the score.`;
|
|
528
|
+
if (split.holdersBps !== 0)
|
|
529
|
+
return 'A game coin runs its own hook, so it has no holder rewards: no holders’ part.';
|
|
530
|
+
if (!(game.potBps > 0) || split.buybackBps + split.holdersBps + split.beneficiaryBps + game.potBps !== 10_000)
|
|
531
|
+
return 'The pot’s part and the split must add up to 10,000 basis points, with a pot.';
|
|
532
|
+
const minPot = BigInt(game.minPotLamports);
|
|
533
|
+
if (minPot < L.minMinPotLamports || minPot > L.maxMinPotLamports)
|
|
534
|
+
return 'The minimum pot is 0.1 to 1,000 SOL.';
|
|
535
|
+
if (game.prizeBps < L.minPrizeBps || game.prizeBps > 10_000)
|
|
536
|
+
return 'A game pays 10% to 100% of the pot at a time.';
|
|
537
|
+
if (game.kind === 'jackpot') {
|
|
538
|
+
if (hook.timerSecs < GAME_KIND_LIMITS.minTimerSecs || hook.timerSecs > GAME_KIND_LIMITS.maxTimerSecs)
|
|
539
|
+
return 'A jackpot’s timer lasts 5 minutes to 30 days.';
|
|
540
|
+
if (BigInt(hook.minTokens) < 1n)
|
|
541
|
+
return 'A qualifying buy is at least one base unit.';
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
if (hook.roundSecs < L.minRoundSecs || hook.roundSecs > L.maxRoundSecs)
|
|
545
|
+
return 'An epoch lasts an hour to 30 days.';
|
|
546
|
+
if (game.claimWindowSecs < L.minClaimWindowSecs || game.claimWindowSecs > L.maxClaimWindowSecs || game.claimWindowSecs * L.claimsPerRound > hook.roundSecs)
|
|
547
|
+
return 'A streak leaves claims 5 minutes to a day, at most half an epoch.';
|
|
548
|
+
if (hook.minStreakSecs > GAME_KIND_LIMITS.maxMinStreakSecs)
|
|
549
|
+
return 'A streak asks at most a year without sending.';
|
|
550
|
+
if (BigInt(hook.minWeight) < 1n)
|
|
551
|
+
return 'The least weight that shares is at least one base unit.';
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
/** Each game kind in a word: "jackpot". */
|
|
555
|
+
export const GAME_KIND_NAMES = { lottery: 'lottery', jackpot: 'jackpot', streak: 'streak', strategy: 'strategy' };
|
|
556
|
+
/**
|
|
557
|
+
* The copy rules of a jackpot and a streak coin (docs/games.md "Phase 2", "Site"): said on the
|
|
558
|
+
* launch form, the review step and the token page. The pot is held by the companion and paid by
|
|
559
|
+
* code (no randomness in either kind), the 10 SOL cap until the hook is audited, "Bordrless can
|
|
560
|
+
* stop this game"; never "provably fair", "guaranteed" or "audited".
|
|
561
|
+
*/
|
|
562
|
+
export const JACKPOT_COPY = {
|
|
563
|
+
held: 'The pot is held by the companion program and paid out by code. No randomness: the last buyer wins.',
|
|
564
|
+
rule: 'The last buyer wins if they keep every token they bought: any sale or transfer forfeits.',
|
|
565
|
+
unfunded: 'If the pot is below its minimum when a round is settled, the round pays nothing and the pot carries on.',
|
|
566
|
+
curve: 'Buys count while the coin is on its curve: the jackpot ends at graduation (the round under way is still paid).',
|
|
567
|
+
cap: LOTTERY_COPY.cap,
|
|
568
|
+
stop: LOTTERY_COPY.stop,
|
|
569
|
+
};
|
|
570
|
+
export const STREAK_COPY = {
|
|
571
|
+
held: 'The pot is held by the companion program and paid out by code. No randomness: holders share in proportion to what they held.',
|
|
572
|
+
rule: 'Send anything, to anyone, and you lose this epoch’s and last epoch’s share. Claim before you sell.',
|
|
573
|
+
weight: 'Your weight is what you held since the epoch began: tokens that arrive during an epoch count from the next.',
|
|
574
|
+
claims: 'Shares are claimed during the next epoch; the keeper claims for everyone whose share covers the fee, the rest claim here. What is not claimed rolls over.',
|
|
575
|
+
cap: LOTTERY_COPY.cap,
|
|
576
|
+
stop: LOTTERY_COPY.stop,
|
|
577
|
+
};
|
|
474
578
|
/** Why a round paid no prize, in a few words each (`GameRolloverReason`). */
|
|
475
579
|
export const ROLLOVER_WORDS = {
|
|
476
580
|
noTickets: 'Nobody held tickets',
|
package/dist/programs.d.ts
CHANGED
|
@@ -22,7 +22,25 @@ export declare const PROGRAM_IDS: {
|
|
|
22
22
|
* (2026-10-08): companion v2 and this hook go live together, after their audit.
|
|
23
23
|
*/
|
|
24
24
|
readonly lotteryHook: "HqFWsCBQ416DAfevJ9TspyT5yXGGoYTCpcreiGkCgWcr";
|
|
25
|
+
/**
|
|
26
|
+
* `hook_timelock` (phase 3a, bordrless-programs docs/phase3a.md §3): a hook's or a strategy's
|
|
27
|
+
* upgrade authority behind a public delay of at least 3 days; its only exit is "immutable". Not
|
|
28
|
+
* deployed yet (2026-10-09).
|
|
29
|
+
*/
|
|
30
|
+
readonly hookTimelock: "BBUzaamchPWZpKENmn7bopiuQWvRGm2Vg8TqLLgzgGGZ";
|
|
31
|
+
/**
|
|
32
|
+
* `hook_vault` (phase 3a, §5): where a hook's cuts go, each slot with a policy fixed before the
|
|
33
|
+
* launch (burn, sell for SOL to a fixed wallet, sell and buy-and-burn another token), run by a
|
|
34
|
+
* permissionless crank under the companion's buyback guards. Not deployed yet (2026-10-09).
|
|
35
|
+
*/
|
|
36
|
+
readonly hookVault: "5cojoUStG7WFhHJiSncCUEwTqDuhDLKu4a9BqTbF8jzG";
|
|
25
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Bordrless Studio's attester (phase 3a, docs/phase3a.md §6): a hot key of Studio's build worker,
|
|
40
|
+
* the only signer of the companion's `HookAttestation` ("Bordrless rebuilt this source, its hash is
|
|
41
|
+
* the code on chain, the checks, the simulator and the review passed"). Not Studio's upgrade key.
|
|
42
|
+
*/
|
|
43
|
+
export declare const STUDIO_ATTESTER = "3uGLsTJNse7vE3pgRkAf6aKKBoKmCyPUcNNu1bw3KvP2";
|
|
26
44
|
/**
|
|
27
45
|
* The lottery hook's fixed facts (programs/lottery_hook/README.md): the flags a `LaunchConfig`
|
|
28
46
|
* names for it (`BEFORE_TRANSFER | BEFORE_BURN | WRITES_HOOK_DATA`, exactly what a companion game
|
package/dist/programs.js
CHANGED
|
@@ -22,7 +22,25 @@ export const PROGRAM_IDS = {
|
|
|
22
22
|
* (2026-10-08): companion v2 and this hook go live together, after their audit.
|
|
23
23
|
*/
|
|
24
24
|
lotteryHook: 'HqFWsCBQ416DAfevJ9TspyT5yXGGoYTCpcreiGkCgWcr',
|
|
25
|
+
/**
|
|
26
|
+
* `hook_timelock` (phase 3a, bordrless-programs docs/phase3a.md §3): a hook's or a strategy's
|
|
27
|
+
* upgrade authority behind a public delay of at least 3 days; its only exit is "immutable". Not
|
|
28
|
+
* deployed yet (2026-10-09).
|
|
29
|
+
*/
|
|
30
|
+
hookTimelock: 'BBUzaamchPWZpKENmn7bopiuQWvRGm2Vg8TqLLgzgGGZ',
|
|
31
|
+
/**
|
|
32
|
+
* `hook_vault` (phase 3a, §5): where a hook's cuts go, each slot with a policy fixed before the
|
|
33
|
+
* launch (burn, sell for SOL to a fixed wallet, sell and buy-and-burn another token), run by a
|
|
34
|
+
* permissionless crank under the companion's buyback guards. Not deployed yet (2026-10-09).
|
|
35
|
+
*/
|
|
36
|
+
hookVault: '5cojoUStG7WFhHJiSncCUEwTqDuhDLKu4a9BqTbF8jzG',
|
|
25
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* Bordrless Studio's attester (phase 3a, docs/phase3a.md §6): a hot key of Studio's build worker,
|
|
40
|
+
* the only signer of the companion's `HookAttestation` ("Bordrless rebuilt this source, its hash is
|
|
41
|
+
* the code on chain, the checks, the simulator and the review passed"). Not Studio's upgrade key.
|
|
42
|
+
*/
|
|
43
|
+
export const STUDIO_ATTESTER = '3uGLsTJNse7vE3pgRkAf6aKKBoKmCyPUcNNu1bw3KvP2';
|
|
26
44
|
/**
|
|
27
45
|
* The lottery hook's fixed facts (programs/lottery_hook/README.md): the flags a `LaunchConfig`
|
|
28
46
|
* names for it (`BEFORE_TRANSFER | BEFORE_BURN | WRITES_HOOK_DATA`, exactly what a companion game
|
package/dist/studio.d.ts
CHANGED
|
@@ -20,8 +20,65 @@ export interface StudioFile {
|
|
|
20
20
|
path: string;
|
|
21
21
|
content: string;
|
|
22
22
|
}
|
|
23
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* What a project becomes: a config of existing rules alone, or a config with its own hook program.
|
|
25
|
+
* A hook project whose state starts with the game ticket standard's header is a game hook
|
|
26
|
+
* (docs/games.md "Phase 2"): read from its source (`detectGameSettings`), never chosen, and its
|
|
27
|
+
* config launches a jackpot, streak or lottery coin through the companion.
|
|
28
|
+
*/
|
|
24
29
|
export type StudioKind = 'config' | 'hook';
|
|
30
|
+
/** The kinds of game a game hook keeps the score of (the companion's `GameKind`). */
|
|
31
|
+
export type StudioGameKind = 'lottery' | 'jackpot' | 'streak';
|
|
32
|
+
export declare const STUDIO_GAME_KINDS: readonly StudioGameKind[];
|
|
33
|
+
/**
|
|
34
|
+
* A game hook's companion draft (docs/games.md "The companion's game", phase 2): what the launch
|
|
35
|
+
* fixes with `create_game_v2` besides the hook's own constants. The split with the pot: the pot's
|
|
36
|
+
* part and the buyback's, the launcher's what is left (no holders' part: a game coin has no kit).
|
|
37
|
+
* The kind's settings (the timer, the minimum buy; the epoch, the minimum streak and weight; a
|
|
38
|
+
* lottery's rounds) are the hook's constants, read from its source (`detectGameSettings`), never
|
|
39
|
+
* typed: the companion checks them against the hook's kind header.
|
|
40
|
+
*/
|
|
41
|
+
export interface StudioGameDraft {
|
|
42
|
+
potBps: number;
|
|
43
|
+
buybackBps: number;
|
|
44
|
+
/** No settle, close or draw while the pot holds less: 0.1 to 1,000 SOL. */
|
|
45
|
+
minPotLamports: string;
|
|
46
|
+
/** The part of the pot a settle, an epoch or a draw pays: 10% to 100%. */
|
|
47
|
+
prizeBps: number;
|
|
48
|
+
/** Streak: the least a close leaves for claims (5 minutes to a day, at most half an epoch); lottery: each attempt's window; 0 for a jackpot. */
|
|
49
|
+
claimWindowSecs: number;
|
|
50
|
+
/** Lottery only: attempts per draw (1 to 16); 0 otherwise. */
|
|
51
|
+
maxAttempts: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A game hook's kind and settings as its source fixes them (p2-n: the starters' settings are their
|
|
55
|
+
* code's): the state struct's first field is the crate's `GameHeader`, its second the kind header
|
|
56
|
+
* (`JackpotHeader` / `StreakHeader`; neither: a lottery), and the settings are what the headers are
|
|
57
|
+
* made with (`JackpotHeader::new(timer, min_tokens)`, `StreakHeader::new(min_streak, min_weight)`,
|
|
58
|
+
* `GameHeader::new(mint, round_secs, now)`). Amounts are base units as strings.
|
|
59
|
+
*/
|
|
60
|
+
export interface StudioGameSettings {
|
|
61
|
+
kind: StudioGameKind;
|
|
62
|
+
/** Jackpot: a round ends this long after its last qualifying buy. 0 otherwise. */
|
|
63
|
+
timerSecs: number;
|
|
64
|
+
/** Jackpot: the least a qualifying buy delivers. '0' otherwise. */
|
|
65
|
+
minTokens: string;
|
|
66
|
+
/** Streak: the epoch; lottery: the round. 0 for a jackpot. */
|
|
67
|
+
roundSecs: number;
|
|
68
|
+
/** Streak: by an epoch's end a holding must have sent nothing for this long. 0 otherwise. */
|
|
69
|
+
minStreakSecs: number;
|
|
70
|
+
/** Streak: the least weight that shares. '0' otherwise. */
|
|
71
|
+
minWeight: string;
|
|
72
|
+
}
|
|
73
|
+
/** The hook flags every game hook runs on: `BEFORE_TRANSFER | BEFORE_BURN | WRITES_HOOK_DATA` (`GameKind::hook_flags`, 145), the only set the companion's launch accepts. */
|
|
74
|
+
export declare const GAME_HOOK_FLAGS = 145;
|
|
75
|
+
/**
|
|
76
|
+
* The companion draft a game hook starts with (the docs' examples: 70% of the fee to the pot, 30%
|
|
77
|
+
* bought back; a jackpot paying half the pot from 0.5 SOL; a streak sharing the whole pot from 0.1
|
|
78
|
+
* SOL with an hour at least for claims; a lottery paying the whole pot from 0.5 SOL, 8 attempts of
|
|
79
|
+
* 10 minutes).
|
|
80
|
+
*/
|
|
81
|
+
export declare function gameDraftDefaults(kind: StudioGameKind): StudioGameDraft;
|
|
25
82
|
/** The launch config a project makes (the arguments of `create_config` / `create_listed_config`). */
|
|
26
83
|
export interface StudioConfigDraft {
|
|
27
84
|
/** At most 32 bytes. */
|
|
@@ -38,8 +95,10 @@ export interface StudioConfigDraft {
|
|
|
38
95
|
earlyLockSecs: number;
|
|
39
96
|
/** `TOKEN_HOOK_FLAGS` the hook runs on, for a `hook` project; 0 for a `config` project. */
|
|
40
97
|
hookFlags: number;
|
|
41
|
-
/** List it on the marketplace (made with `create_listed_config`). */
|
|
98
|
+
/** List it on the marketplace (made with `create_listed_config`). Never for a game hook: the companion refuses a config that pays an author. */
|
|
42
99
|
listed: boolean;
|
|
100
|
+
/** A game hook's companion draft (the split with the pot, the minimum pot, the prize, the claim window); null or absent otherwise. The backend fills it with the kind's defaults when the source becomes a game hook. */
|
|
101
|
+
game?: StudioGameDraft | null;
|
|
43
102
|
/**
|
|
44
103
|
* The author's share of the creator fee on others' launches, bps of it. Not the author's to choose:
|
|
45
104
|
* the backend holds it at `AUTHOR_SHARE_BPS` when listed and 0 when not, whatever is sent.
|
|
@@ -65,6 +124,12 @@ export interface StudioProject extends StudioProjectSummary {
|
|
|
65
124
|
description: string;
|
|
66
125
|
files: StudioFile[];
|
|
67
126
|
draft: StudioConfigDraft;
|
|
127
|
+
/**
|
|
128
|
+
* A hook project's game, as its source reads (`detectGameSettings`): its kind and the constants
|
|
129
|
+
* the companion will check against the hook's kind header. Null when the source is not a game
|
|
130
|
+
* hook (no `GameHeader` state, or settings that can't be read); absent for a config project.
|
|
131
|
+
*/
|
|
132
|
+
game?: StudioGameSettings | null;
|
|
68
133
|
/** The last review of the current source; null when the source changed since. */
|
|
69
134
|
review: StudioReview | null;
|
|
70
135
|
/** The last build of the current source; null when the source changed since. */
|
|
@@ -75,7 +140,7 @@ export interface StudioProject extends StudioProjectSummary {
|
|
|
75
140
|
export interface StudioProjectCreateRequest {
|
|
76
141
|
name: string;
|
|
77
142
|
kind: StudioKind;
|
|
78
|
-
/** Start from a template (`STUDIO_STARTERS`), or blank. */
|
|
143
|
+
/** Start from a template (`STUDIO_STARTERS`), or blank; the game starters make a game hook. */
|
|
79
144
|
starter?: string;
|
|
80
145
|
}
|
|
81
146
|
/** A save: any of the fields; files replace the whole set. Changing files drops the review and the build. */
|
|
@@ -203,6 +268,12 @@ export interface StudioBuild {
|
|
|
203
268
|
at: number;
|
|
204
269
|
/** Studio's trading test of what was built (the worker's simulator): a deploy needs `pass`. Absent on builds from before it. */
|
|
205
270
|
sim?: StudioSim | null;
|
|
271
|
+
/**
|
|
272
|
+
* The Studio template's commit the worker built against (40 hex characters; the worker's
|
|
273
|
+
* `STUDIO_TEMPLATE_COMMIT`). Studio's attestation records it, so anyone can rebuild the hook.
|
|
274
|
+
* Absent on builds from before it, or from a worker that does not report it.
|
|
275
|
+
*/
|
|
276
|
+
templateCommit?: string | null;
|
|
206
277
|
}
|
|
207
278
|
/** The simulator's verdict on a build: launched, bought, sold and sent by several wallets over a simulated year. */
|
|
208
279
|
export interface StudioSim {
|
|
@@ -214,8 +285,23 @@ export interface StudioSim {
|
|
|
214
285
|
capBps: number | null;
|
|
215
286
|
/** Operations the hook refused. */
|
|
216
287
|
refusedTotal: number;
|
|
288
|
+
/** The simulator's version (recorded in Studio's attestation); absent from workers that don't report it. */
|
|
289
|
+
version?: number | null;
|
|
217
290
|
/** What the test can't see (amount triggers, wallets it never used, after a year). */
|
|
218
291
|
notes: string[];
|
|
292
|
+
/** A game hook's scenarios (the jackpot's or the streak's, docs/games-handover.md phase 2 item 4): absent on a hook that is not a game's, or on builds from before them. */
|
|
293
|
+
game?: StudioSimGame | null;
|
|
294
|
+
}
|
|
295
|
+
/** The simulator's game scenarios on a game hook: the header and the marks or weights checked against a plain model of the rules, and the fairness of the payouts the rules would make. */
|
|
296
|
+
export interface StudioSimGame {
|
|
297
|
+
kind: StudioGameKind;
|
|
298
|
+
pass: boolean;
|
|
299
|
+
/** Each check that failed, in the simulator's words (the first few). */
|
|
300
|
+
failed: string[];
|
|
301
|
+
/** Rounds (a jackpot) or epochs (a streak) the scenario ran. */
|
|
302
|
+
periods: number;
|
|
303
|
+
/** The largest difference between a wallet's share of the payouts and its share of what it held, in bps, over the fairness run; null when it did not run. */
|
|
304
|
+
fairnessMaxBps: number | null;
|
|
219
305
|
}
|
|
220
306
|
/** `POST /v1/studio/projects/:id/deploy/quote`: what deploying the built hook costs, in lamports. */
|
|
221
307
|
export interface StudioDeployQuote {
|
|
@@ -364,8 +450,10 @@ export declare const HOOK_AUTHORITY_CODE = "hook_upgrade_authority";
|
|
|
364
450
|
export declare const hookFinalCommand: (program?: string) => string;
|
|
365
451
|
/**
|
|
366
452
|
* The launch program's rule for a config's own hook (`create_config`, docs/hooks-v2.md §5.8), on its
|
|
367
|
-
* upgrade info: accepted when nobody can upgrade it (`upgradeable: false`),
|
|
368
|
-
* authority is one of `HOOK_UPGRADE_AUTHORITIES` (Bordrless Studio's key or the protocol's)
|
|
453
|
+
* upgrade info: accepted when nobody can upgrade it (`upgradeable: false`), when its upgrade
|
|
454
|
+
* authority is one of `HOOK_UPGRADE_AUTHORITIES` (Bordrless Studio's key or the protocol's), or when
|
|
455
|
+
* it is its own `hook_timelock` account (`timelocked`, phase 3a: the caller derives
|
|
456
|
+
* `PDA(["timelock", hook], hook_timelock)` and compares; the launch program also checks the delay). Only a
|
|
369
457
|
* hook some other key is known to be able to upgrade is refused: one whose upgrade info could not be
|
|
370
458
|
* read is not (the launch program checked it when the config was made; the SDK's
|
|
371
459
|
* `hookAuthorityProblem` and the marketplace watch read it the same way). A config without a hook
|
|
@@ -374,6 +462,7 @@ export declare const hookFinalCommand: (program?: string) => string;
|
|
|
374
462
|
export declare function hookAuthorityAccepted(customHook: string | null, hook: {
|
|
375
463
|
upgradeAuthority: string | null;
|
|
376
464
|
upgradeable: boolean | null;
|
|
465
|
+
timelocked?: boolean;
|
|
377
466
|
} | null): boolean;
|
|
378
467
|
/** `bordrless_launch::constants::MAX_AUTHOR_SHARE_BPS`: half the creator fee, the most the program lets a listed config pay its author. */
|
|
379
468
|
export declare const MAX_AUTHOR_SHARE_BPS = 5000;
|
|
@@ -400,12 +489,20 @@ export declare const STUDIO_LIMITS: {
|
|
|
400
489
|
readonly bytes: 120000;
|
|
401
490
|
readonly messageChars: 8000;
|
|
402
491
|
};
|
|
403
|
-
/**
|
|
492
|
+
/**
|
|
493
|
+
* Starters a new hook project can begin from (the template's `src/lib.rs` variants). Those with
|
|
494
|
+
* `game` make a game hook (the reference game hooks of docs/games.md "Studio game hooks", written on
|
|
495
|
+
* the `bordrless-game` crate). A Studio lottery starter is not written yet (it would be
|
|
496
|
+
* `lottery_hook`'s rules with the standard `prepare` and `ROUND_SECS` as a constant).
|
|
497
|
+
*/
|
|
404
498
|
export declare const STUDIO_STARTERS: readonly {
|
|
405
499
|
id: string;
|
|
406
500
|
name: string;
|
|
407
501
|
blurb: string;
|
|
502
|
+
game?: StudioGameKind;
|
|
408
503
|
}[];
|
|
504
|
+
/** What each game kind is called on the site: "Jackpot coin". */
|
|
505
|
+
export declare const GAME_KIND_WORDS: Readonly<Record<StudioGameKind | 'strategy', string>>;
|
|
409
506
|
/**
|
|
410
507
|
* The token hook flags a Studio hook needs, read from its own source (never chosen by hand): each
|
|
411
508
|
* callback whose body the author changed from the template's stub; cuts (TRANSFER_RETURNS_DELTA)
|
|
@@ -419,3 +516,17 @@ export declare function detectHookFlags(files: readonly {
|
|
|
419
516
|
path: string;
|
|
420
517
|
content: string;
|
|
421
518
|
}[]): number;
|
|
519
|
+
/**
|
|
520
|
+
* A game hook's kind and settings from its source (docs/games.md "Studio game hooks", p2-n):
|
|
521
|
+
* the state struct whose first field is the crate's `GameHeader` names the kind by its second
|
|
522
|
+
* field (`JackpotHeader`, `StreakHeader`; neither: a lottery), and the settings are the arguments
|
|
523
|
+
* the headers are made with in `prepare`: `JackpotHeader::new(timer_secs, min_tokens)`,
|
|
524
|
+
* `StreakHeader::new(min_streak_secs, min_weight)` and `GameHeader::new(mint, round_secs, now)`,
|
|
525
|
+
* each a constant expression (`const` names followed). Null when the source is not a game hook, or
|
|
526
|
+
* a setting can't be read from it (the backend then refuses the config: the companion would check
|
|
527
|
+
* the setting against the hook's header and refuse the setup).
|
|
528
|
+
*/
|
|
529
|
+
export declare function detectGameSettings(files: readonly {
|
|
530
|
+
path: string;
|
|
531
|
+
content: string;
|
|
532
|
+
}[]): StudioGameSettings | null;
|
package/dist/studio.js
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import { TOKEN_HOOK_FLAGS as HOOK_FLAG_BITS } from "./programs.js";
|
|
2
|
+
export const STUDIO_GAME_KINDS = ['lottery', 'jackpot', 'streak'];
|
|
3
|
+
/** The hook flags every game hook runs on: `BEFORE_TRANSFER | BEFORE_BURN | WRITES_HOOK_DATA` (`GameKind::hook_flags`, 145), the only set the companion's launch accepts. */
|
|
4
|
+
export const GAME_HOOK_FLAGS = 145;
|
|
5
|
+
/**
|
|
6
|
+
* The companion draft a game hook starts with (the docs' examples: 70% of the fee to the pot, 30%
|
|
7
|
+
* bought back; a jackpot paying half the pot from 0.5 SOL; a streak sharing the whole pot from 0.1
|
|
8
|
+
* SOL with an hour at least for claims; a lottery paying the whole pot from 0.5 SOL, 8 attempts of
|
|
9
|
+
* 10 minutes).
|
|
10
|
+
*/
|
|
11
|
+
export function gameDraftDefaults(kind) {
|
|
12
|
+
if (kind === 'jackpot')
|
|
13
|
+
return { potBps: 7_000, buybackBps: 3_000, minPotLamports: '500000000', prizeBps: 5_000, claimWindowSecs: 0, maxAttempts: 0 };
|
|
14
|
+
if (kind === 'streak')
|
|
15
|
+
return { potBps: 7_000, buybackBps: 3_000, minPotLamports: '100000000', prizeBps: 10_000, claimWindowSecs: 3_600, maxAttempts: 0 };
|
|
16
|
+
return { potBps: 7_000, buybackBps: 3_000, minPotLamports: '500000000', prizeBps: 10_000, claimWindowSecs: 600, maxAttempts: 8 };
|
|
17
|
+
}
|
|
2
18
|
/** The message a wallet signs to use Studio: no transaction, nothing moves. */
|
|
3
19
|
export function studioSignInMessage(wallet, nonce, issuedAt) {
|
|
4
20
|
return ['Sign in to Bordrless Studio', '', 'This signature proves you own this wallet. It does not move funds or approve any transaction.', '', `Wallet: ${wallet}`, `Nonce: ${nonce}`, `Issued: ${issuedAt}`].join('\n');
|
|
@@ -23,8 +39,10 @@ export const HOOK_AUTHORITY_CODE = 'hook_upgrade_authority';
|
|
|
23
39
|
export const hookFinalCommand = (program = '<PROGRAM_ID>') => `solana program set-upgrade-authority ${program} --final`;
|
|
24
40
|
/**
|
|
25
41
|
* The launch program's rule for a config's own hook (`create_config`, docs/hooks-v2.md §5.8), on its
|
|
26
|
-
* upgrade info: accepted when nobody can upgrade it (`upgradeable: false`),
|
|
27
|
-
* authority is one of `HOOK_UPGRADE_AUTHORITIES` (Bordrless Studio's key or the protocol's)
|
|
42
|
+
* upgrade info: accepted when nobody can upgrade it (`upgradeable: false`), when its upgrade
|
|
43
|
+
* authority is one of `HOOK_UPGRADE_AUTHORITIES` (Bordrless Studio's key or the protocol's), or when
|
|
44
|
+
* it is its own `hook_timelock` account (`timelocked`, phase 3a: the caller derives
|
|
45
|
+
* `PDA(["timelock", hook], hook_timelock)` and compares; the launch program also checks the delay). Only a
|
|
28
46
|
* hook some other key is known to be able to upgrade is refused: one whose upgrade info could not be
|
|
29
47
|
* read is not (the launch program checked it when the config was made; the SDK's
|
|
30
48
|
* `hookAuthorityProblem` and the marketplace watch read it the same way). A config without a hook
|
|
@@ -33,6 +51,9 @@ export const hookFinalCommand = (program = '<PROGRAM_ID>') => `solana program se
|
|
|
33
51
|
export function hookAuthorityAccepted(customHook, hook) {
|
|
34
52
|
if (customHook === null || hook === null || hook.upgradeable !== true || hook.upgradeAuthority === null)
|
|
35
53
|
return true;
|
|
54
|
+
// Phase 3a: a hook behind its own `hook_timelock` (a public delay of at least 3 days) is accepted too.
|
|
55
|
+
if (hook.timelocked === true)
|
|
56
|
+
return true;
|
|
36
57
|
return HOOK_UPGRADE_AUTHORITIES.includes(hook.upgradeAuthority);
|
|
37
58
|
}
|
|
38
59
|
/** `bordrless_launch::constants::MAX_AUTHOR_SHARE_BPS`: half the creator fee, the most the program lets a listed config pay its author. */
|
|
@@ -53,12 +74,21 @@ export const AUTHOR_SHARE_BPS = 5_000;
|
|
|
53
74
|
export const STUDIO_PREPARE = { instruction: 'prepare', stateSeed: 'state' };
|
|
54
75
|
/** Limits of a project's source. */
|
|
55
76
|
export const STUDIO_LIMITS = { files: 8, bytes: 120_000, messageChars: 8_000 };
|
|
56
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Starters a new hook project can begin from (the template's `src/lib.rs` variants). Those with
|
|
79
|
+
* `game` make a game hook (the reference game hooks of docs/games.md "Studio game hooks", written on
|
|
80
|
+
* the `bordrless-game` crate). A Studio lottery starter is not written yet (it would be
|
|
81
|
+
* `lottery_hook`'s rules with the standard `prepare` and `ROUND_SECS` as a constant).
|
|
82
|
+
*/
|
|
57
83
|
export const STUDIO_STARTERS = [
|
|
58
84
|
{ id: 'blank', name: 'Blank hook', blurb: 'The template: every callback stubbed, nothing taken, nothing refused.' },
|
|
59
85
|
{ id: 'sell-tax', name: 'Sell tax to a wallet', blurb: 'A cut of every sell sent to a wallet you choose; buys and transfers pass free.' },
|
|
60
86
|
{ id: 'cooldown', name: 'Transfer cooldown', blurb: 'A holding that received tokens cannot send them on for a while, stamped in its hook data.' },
|
|
87
|
+
{ id: 'jackpot', game: 'jackpot', name: 'Last-buyer jackpot', blurb: 'Every buy of at least MIN_TOKENS restarts a TIMER_SECS countdown; when it runs out, the last buyer who still holds is paid a share of the pot.' },
|
|
88
|
+
{ id: 'streak', game: 'streak', name: 'Diamond-hands streak', blurb: 'Each epoch, part of the pot is shared among the holders who held through it without sending a token, in proportion to what they held.' },
|
|
61
89
|
];
|
|
90
|
+
/** What each game kind is called on the site: "Jackpot coin". */
|
|
91
|
+
export const GAME_KIND_WORDS = { lottery: 'Lottery coin', jackpot: 'Jackpot coin', streak: 'Streak coin', strategy: 'Strategy coin' };
|
|
62
92
|
// ---- the hook's flags, read from its source ---------------------------------------------------------
|
|
63
93
|
/** Comments, string and char literals blanked (newlines kept), so only code is read. */
|
|
64
94
|
function codeOnly(src) {
|
|
@@ -168,3 +198,169 @@ export function detectHookFlags(files) {
|
|
|
168
198
|
flags |= HOOK_FLAG_BITS.WRITES_HOOK_DATA;
|
|
169
199
|
return flags;
|
|
170
200
|
}
|
|
201
|
+
// ---- a game hook's kind and settings, read from its source --------------------------------------------
|
|
202
|
+
/** The index just past the bracket matching the one at `open`, or the end. */
|
|
203
|
+
function closeOf(code, open) {
|
|
204
|
+
let depth = 0;
|
|
205
|
+
for (let j = open; j < code.length; j += 1) {
|
|
206
|
+
const ch = code[j];
|
|
207
|
+
if (ch === '(' || ch === '[' || ch === '{')
|
|
208
|
+
depth += 1;
|
|
209
|
+
else if (ch === ')' || ch === ']' || ch === '}') {
|
|
210
|
+
depth -= 1;
|
|
211
|
+
if (depth === 0)
|
|
212
|
+
return j + 1;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return code.length;
|
|
216
|
+
}
|
|
217
|
+
/** The arguments of the call whose opening parenthesis is at `open`, as written (split on commas at depth 0). */
|
|
218
|
+
function argsOf(code, open) {
|
|
219
|
+
const inner = code.slice(open + 1, closeOf(code, open) - 1);
|
|
220
|
+
const out = [];
|
|
221
|
+
let depth = 0;
|
|
222
|
+
let from = 0;
|
|
223
|
+
for (let j = 0; j < inner.length; j += 1) {
|
|
224
|
+
const ch = inner[j];
|
|
225
|
+
if (ch === '(' || ch === '[' || ch === '{')
|
|
226
|
+
depth += 1;
|
|
227
|
+
else if (ch === ')' || ch === ']' || ch === '}')
|
|
228
|
+
depth -= 1;
|
|
229
|
+
else if (ch === ',' && depth === 0) {
|
|
230
|
+
out.push(inner.slice(from, j));
|
|
231
|
+
from = j + 1;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (inner.slice(from).trim())
|
|
235
|
+
out.push(inner.slice(from));
|
|
236
|
+
return out.map((a) => a.trim());
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* A constant expression of the source, evaluated: integer literals (with `_` and a type suffix),
|
|
240
|
+
* `+ - * /` and parentheses, and the names of other `const`s (followed up to a depth of 8). Null
|
|
241
|
+
* for anything else (a call, a runtime value, an unknown name, a division by zero).
|
|
242
|
+
*/
|
|
243
|
+
function evalConst(code, expr, depth = 0) {
|
|
244
|
+
const text = expr.replace(/\s+/g, '');
|
|
245
|
+
if (!text || depth > 8)
|
|
246
|
+
return null;
|
|
247
|
+
let i = 0;
|
|
248
|
+
const peek = () => text[i] ?? '';
|
|
249
|
+
const primary = () => {
|
|
250
|
+
if (peek() === '(') {
|
|
251
|
+
i += 1;
|
|
252
|
+
const v = sum();
|
|
253
|
+
if (peek() !== ')')
|
|
254
|
+
return null;
|
|
255
|
+
i += 1;
|
|
256
|
+
return v;
|
|
257
|
+
}
|
|
258
|
+
if (peek() === '-') {
|
|
259
|
+
i += 1;
|
|
260
|
+
const v = primary();
|
|
261
|
+
return v === null ? null : -v;
|
|
262
|
+
}
|
|
263
|
+
const lit = /^(0x[0-9a-fA-F_]+|\d[\d_]*)(u8|u16|u32|u64|u128|i8|i16|i32|i64|i128|usize|isize)?/.exec(text.slice(i));
|
|
264
|
+
if (lit) {
|
|
265
|
+
i += lit[0].length;
|
|
266
|
+
try {
|
|
267
|
+
return BigInt(lit[1].replace(/_/g, ''));
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const name = /^(?:(?:crate|self|super)::)*([A-Za-z_][A-Za-z0-9_]*)/.exec(text.slice(i));
|
|
274
|
+
if (!name)
|
|
275
|
+
return null;
|
|
276
|
+
i += name[0].length;
|
|
277
|
+
const def = new RegExp(`\\bconst\\s+${name[1]}\\s*:\\s*[A-Za-z0-9_:<>]+\\s*=([^;]*);`).exec(code);
|
|
278
|
+
if (!def)
|
|
279
|
+
return null;
|
|
280
|
+
return evalConst(code, def[1], depth + 1);
|
|
281
|
+
};
|
|
282
|
+
const product = () => {
|
|
283
|
+
let v = primary();
|
|
284
|
+
while (v !== null && (peek() === '*' || peek() === '/')) {
|
|
285
|
+
const op = peek();
|
|
286
|
+
i += 1;
|
|
287
|
+
const r = primary();
|
|
288
|
+
if (r === null)
|
|
289
|
+
return null;
|
|
290
|
+
if (op === '/') {
|
|
291
|
+
if (r === 0n)
|
|
292
|
+
return null;
|
|
293
|
+
v /= r;
|
|
294
|
+
}
|
|
295
|
+
else
|
|
296
|
+
v *= r;
|
|
297
|
+
}
|
|
298
|
+
return v;
|
|
299
|
+
};
|
|
300
|
+
const sum = () => {
|
|
301
|
+
let v = product();
|
|
302
|
+
while (v !== null && (peek() === '+' || peek() === '-')) {
|
|
303
|
+
const op = peek();
|
|
304
|
+
i += 1;
|
|
305
|
+
const r = product();
|
|
306
|
+
if (r === null)
|
|
307
|
+
return null;
|
|
308
|
+
v = op === '+' ? v + r : v - r;
|
|
309
|
+
}
|
|
310
|
+
return v;
|
|
311
|
+
};
|
|
312
|
+
const v = sum();
|
|
313
|
+
return i === text.length ? v : null;
|
|
314
|
+
}
|
|
315
|
+
/** The arguments `Name::new(…)` is called with in `code` (the first call), evaluated; null when there is none or one can't be read. */
|
|
316
|
+
function newArgs(code, name, count) {
|
|
317
|
+
const m = new RegExp(`\\b${name}\\s*::\\s*new\\s*\\(`).exec(code);
|
|
318
|
+
if (!m)
|
|
319
|
+
return null;
|
|
320
|
+
const args = argsOf(code, m.index + m[0].length - 1);
|
|
321
|
+
if (args.length !== count)
|
|
322
|
+
return null;
|
|
323
|
+
return args.map((a) => evalConst(code, a));
|
|
324
|
+
}
|
|
325
|
+
const within = (v, max) => v !== null && v >= 0n && v <= max;
|
|
326
|
+
const U32 = 0xffffffffn;
|
|
327
|
+
const U64 = 0xffffffffffffffffn;
|
|
328
|
+
/**
|
|
329
|
+
* A game hook's kind and settings from its source (docs/games.md "Studio game hooks", p2-n):
|
|
330
|
+
* the state struct whose first field is the crate's `GameHeader` names the kind by its second
|
|
331
|
+
* field (`JackpotHeader`, `StreakHeader`; neither: a lottery), and the settings are the arguments
|
|
332
|
+
* the headers are made with in `prepare`: `JackpotHeader::new(timer_secs, min_tokens)`,
|
|
333
|
+
* `StreakHeader::new(min_streak_secs, min_weight)` and `GameHeader::new(mint, round_secs, now)`,
|
|
334
|
+
* each a constant expression (`const` names followed). Null when the source is not a game hook, or
|
|
335
|
+
* a setting can't be read from it (the backend then refuses the config: the companion would check
|
|
336
|
+
* the setting against the hook's header and refuse the setup).
|
|
337
|
+
*/
|
|
338
|
+
export function detectGameSettings(files) {
|
|
339
|
+
const code = files.filter((f) => f.path.endsWith('.rs')).map((f) => codeOnly(f.content)).join('\n');
|
|
340
|
+
const state = /\bstruct\s+\w+\s*(?:<[^>{]*>)?\s*\{\s*(?:#\s*\[[^\]]*\]\s*)*(?:pub(?:\s*\([^)]*\))?\s+)?\w+\s*:\s*(?:(?:::)?bordrless_game\s*::\s*)?GameHeader\s*,\s*(?:#\s*\[[^\]]*\]\s*)*(?:(?:pub(?:\s*\([^)]*\))?\s+)?\w+\s*:\s*(?:(?:::)?bordrless_game\s*::\s*)?(JackpotHeader|StreakHeader)\b)?/.exec(code);
|
|
341
|
+
if (!state)
|
|
342
|
+
return null;
|
|
343
|
+
const kind = state[1] === 'JackpotHeader' ? 'jackpot' : state[1] === 'StreakHeader' ? 'streak' : 'lottery';
|
|
344
|
+
const header = newArgs(code, 'GameHeader', 3);
|
|
345
|
+
if (!header)
|
|
346
|
+
return null;
|
|
347
|
+
const roundSecs = header[1] ?? null;
|
|
348
|
+
if (!within(roundSecs, U32))
|
|
349
|
+
return null;
|
|
350
|
+
const out = { kind, timerSecs: 0, minTokens: '0', roundSecs: Number(roundSecs), minStreakSecs: 0, minWeight: '0' };
|
|
351
|
+
if (kind === 'jackpot') {
|
|
352
|
+
const j = newArgs(code, 'JackpotHeader', 2);
|
|
353
|
+
if (!j || !within(j[0] ?? null, U32) || !within(j[1] ?? null, U64))
|
|
354
|
+
return null;
|
|
355
|
+
out.timerSecs = Number(j[0]);
|
|
356
|
+
out.minTokens = j[1].toString();
|
|
357
|
+
}
|
|
358
|
+
else if (kind === 'streak') {
|
|
359
|
+
const s = newArgs(code, 'StreakHeader', 2);
|
|
360
|
+
if (!s || !within(s[0] ?? null, U32) || !within(s[1] ?? null, U64))
|
|
361
|
+
return null;
|
|
362
|
+
out.minStreakSecs = Number(s[0]);
|
|
363
|
+
out.minWeight = s[1].toString();
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bordrless/shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Bordrless policy (the same math as crates/bordrless-core), program addresses and the API types shared by the server and the site. No Solana dependencies.",
|
|
6
6
|
"license": "Apache-2.0",
|