@bordrless/shared 0.5.0 → 0.7.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 +278 -11
- package/dist/api.js +4 -1
- package/dist/policy.d.ts +104 -1
- package/dist/policy.js +193 -0
- package/dist/programs.d.ts +29 -0
- package/dist/programs.js +33 -0
- package/dist/studio.d.ts +67 -5
- package/dist/studio.js +130 -0
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -107,7 +107,63 @@ export interface CustomHookInfo {
|
|
|
107
107
|
upgradeAuthority: Address | null;
|
|
108
108
|
/** True: `upgradeAuthority` can upgrade it. False: nobody can (final). Null: not read, so nothing is claimed either way. */
|
|
109
109
|
upgradeable: boolean | null;
|
|
110
|
+
/** What the hook does, for a hook Studio built (`HookAbout`); null (or absent) for any other, which the site describes from its flags. */
|
|
111
|
+
about?: HookAbout | null;
|
|
110
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Bordrless's official summary of a hook Studio deployed: written once by the assistant from the
|
|
115
|
+
* deployed source (comments and strings blanked), its flags and its review, then never changed and
|
|
116
|
+
* never the author's to edit. A new build is a new program, with a summary of its own.
|
|
117
|
+
*/
|
|
118
|
+
export interface HookSummary {
|
|
119
|
+
/** At most `HOOK_SUMMARY_LIMITS.title` characters. */
|
|
120
|
+
title: string;
|
|
121
|
+
/** One or two plain sentences: what the hook does to transfers, buys and sells. */
|
|
122
|
+
text: string;
|
|
123
|
+
/** The build it was written for (sha256 of the deployed binary); null when the build had none. */
|
|
124
|
+
buildHash: string | null;
|
|
125
|
+
at: number;
|
|
126
|
+
}
|
|
127
|
+
/** Figures the behaviour simulator measured on a hook (numbers, never model text). Reserved: not filled yet. */
|
|
128
|
+
export interface HookMeasured {
|
|
129
|
+
/** The largest cut seen on one transfer, bps of it. */
|
|
130
|
+
maxCutBps: number | null;
|
|
131
|
+
/** Transfers it refused, of `runs`. */
|
|
132
|
+
refusals: number | null;
|
|
133
|
+
runs: number | null;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* What a token hook Studio built does (`GET /v1/hooks/:program/about`, and on `CustomHookInfo.about`,
|
|
137
|
+
* `LaunchConfigData.customHook.about`, `MarketplaceListing.hookAbout`): Bordrless's official summary
|
|
138
|
+
* first, the author's own words second. Null for a hook Studio didn't build.
|
|
139
|
+
*/
|
|
140
|
+
export interface HookAbout {
|
|
141
|
+
program: Address;
|
|
142
|
+
/** The Studio project's name, as its author called it. */
|
|
143
|
+
title: string;
|
|
144
|
+
/** Bordrless's summary; null until it is written (the site then says what the flags allow). */
|
|
145
|
+
summary: HookSummary | null;
|
|
146
|
+
/** "What your hook does", in the author's words (at most `HOOK_ABOUT_CHARS`); empty when none was written. */
|
|
147
|
+
about: string;
|
|
148
|
+
studioProject: string;
|
|
149
|
+
/** The worst verdict of Studio's automated checks of the deployed source (not an audit); null when none is kept. */
|
|
150
|
+
reviewVerdict: 'pass' | 'warn' | null;
|
|
151
|
+
/** sha256 of the deployed binary. */
|
|
152
|
+
buildHash: string | null;
|
|
153
|
+
/** Studio keeps its source for anyone to read. */
|
|
154
|
+
sourceAvailable: boolean;
|
|
155
|
+
/** The marketplace listing that shows its source (its Studio config, when listed); null when not listed. */
|
|
156
|
+
listing: Address | null;
|
|
157
|
+
/** Reserved for the behaviour simulator's figures; absent for now. */
|
|
158
|
+
measured?: HookMeasured | null;
|
|
159
|
+
}
|
|
160
|
+
/** "What your hook does": the most an author writes, in characters (the Studio field and what is served). */
|
|
161
|
+
export declare const HOOK_ABOUT_CHARS = 280;
|
|
162
|
+
/** The official summary's limits, in characters. */
|
|
163
|
+
export declare const HOOK_SUMMARY_LIMITS: {
|
|
164
|
+
readonly title: 40;
|
|
165
|
+
readonly text: 320;
|
|
166
|
+
};
|
|
111
167
|
/** How a pool's protocol fee is taken (§3.1): a flat rate of the quote (`protocolFeeBps`, ordinary pools), or a share of what the hooks cut (`protocolShareBps`, launch pools). */
|
|
112
168
|
export type ProtocolModel = 'flat' | 'share';
|
|
113
169
|
/**
|
|
@@ -120,7 +176,8 @@ export interface LaunchConfigData {
|
|
|
120
176
|
creatorFeeBps: number;
|
|
121
177
|
customHook: {
|
|
122
178
|
program: Address;
|
|
123
|
-
flags: number;
|
|
179
|
+
flags: number; /** As `CustomHookInfo.about`. */
|
|
180
|
+
about?: HookAbout | null;
|
|
124
181
|
} | null;
|
|
125
182
|
label: string;
|
|
126
183
|
}
|
|
@@ -281,6 +338,8 @@ export interface LaunchDetail extends LaunchSummary {
|
|
|
281
338
|
ruleStats: RuleStats;
|
|
282
339
|
/** The bridged-SOL holding holder rewards are paid from; null without holder rewards. */
|
|
283
340
|
holderVault: Address | null;
|
|
341
|
+
/** The program that is this launch's creator (docs/companions.md), read live; null for a launch whose creator is a wallet. */
|
|
342
|
+
companion: CompanionSummary | null;
|
|
284
343
|
}
|
|
285
344
|
/** `GET /v1/launches?sort=`: newest first by default; `rewards24h` most paid to holders in 24 h first; `fees` lowest buy-then-sell fee first. */
|
|
286
345
|
export type LaunchSort = 'new' | 'mcap' | 'volume' | 'trending' | 'graduated' | 'rewards24h' | 'fees';
|
|
@@ -500,19 +559,220 @@ export interface LaunchPrepareRequest {
|
|
|
500
559
|
*/
|
|
501
560
|
config?: Address;
|
|
502
561
|
/**
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
562
|
+
* Where the creator fee goes when it isn't all paid to the launcher (docs/companions.md): the
|
|
563
|
+
* launch's creator is then a program, a companion, that buys the token back and burns it,
|
|
564
|
+
* streams to holders or pays the launcher, by `split` (basis points summing to 10,000), with no
|
|
565
|
+
* keeper. A `template` names one of the three preset splits (kept for older clients). The
|
|
566
|
+
* launcher is the beneficiary; a first buy is then the companion's, vesting to them over
|
|
567
|
+
* `vestDays` (0 to 365, default 30). Refused (`companion_rules`) with the creator wallet lock, a
|
|
568
|
+
* launch config (listed or not, with or without a hook), no creator fee, a share for holders
|
|
569
|
+
* without holder rewards, or a split that doesn't add up.
|
|
508
570
|
*/
|
|
509
|
-
companion?:
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
571
|
+
companion?: CompanionRequest;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* A launch's companion as the launch request names it: a split of the creator fee, or one of the
|
|
575
|
+
* preset splits by name; with `game`, a lottery coin (docs/games.md): the split's holders' share is
|
|
576
|
+
* then 0 and the pot's part (`game.potBps`) makes the four add up to 10,000.
|
|
577
|
+
*/
|
|
578
|
+
export type CompanionRequest = {
|
|
579
|
+
split: CompanionSplitBps;
|
|
580
|
+
vestDays?: number;
|
|
581
|
+
game?: LotteryRequest;
|
|
582
|
+
} | {
|
|
583
|
+
template: CompanionTemplate;
|
|
584
|
+
vestDays?: number;
|
|
585
|
+
};
|
|
586
|
+
/**
|
|
587
|
+
* A lottery coin's game as the launch request names it (docs/games.md "The companion's game"):
|
|
588
|
+
* every setting `create_game` fixes before the launch. Bounds: `LOTTERY_LIMITS` (`lotteryProblem`).
|
|
589
|
+
*/
|
|
590
|
+
export interface LotteryRequest {
|
|
591
|
+
kind: 'lottery';
|
|
592
|
+
/** A round's length, seconds: an hour to 30 days. Every holder's tokens held since a round began are their tickets in it. */
|
|
593
|
+
roundSecs: number;
|
|
594
|
+
/** The pot's part of every creator fee claim, basis points: with the split's buyback and launcher parts it adds up to 10,000. */
|
|
595
|
+
potBps: number;
|
|
596
|
+
/** No draw while the pot holds less, lamports: 0.1 to 1,000 SOL. */
|
|
597
|
+
minPotLamports: Amount;
|
|
598
|
+
/** The part of the pot one draw pays, basis points: 10% to 100%. */
|
|
599
|
+
prizeBps: number;
|
|
600
|
+
/** How long each claim attempt stays open, seconds: 5 minutes to a day. */
|
|
601
|
+
claimWindowSecs: number;
|
|
602
|
+
/** Attempts a draw makes before it rolls over, 1 to 16; all of them within half a round. */
|
|
603
|
+
maxAttempts: number;
|
|
604
|
+
}
|
|
605
|
+
/** A lottery coin's settings as its token page shows them (`CompanionSummary.game`): fixed at the launch, and the hook's terms. */
|
|
606
|
+
export interface GameSummary {
|
|
607
|
+
kind: 'lottery';
|
|
608
|
+
/** The game's account, `PDA(["game", mint])` under the companion program. */
|
|
609
|
+
address: Address;
|
|
610
|
+
/** The coin's token hook, which keeps the tickets (the lottery hook). */
|
|
611
|
+
hook: Address;
|
|
612
|
+
roundSecs: number;
|
|
613
|
+
potBps: number;
|
|
614
|
+
minPot: Amount;
|
|
615
|
+
prizeBps: number;
|
|
616
|
+
claimWindowSecs: number;
|
|
617
|
+
maxAttempts: number;
|
|
618
|
+
/** The most the pot may hold while the hook is not audited (lamports); null for an audited hook. */
|
|
619
|
+
potCap: Amount | null;
|
|
620
|
+
audited: boolean;
|
|
621
|
+
/** Bordrless stopped this hook's games: the pot and its share go to the buyback and are burned. */
|
|
622
|
+
blocked: boolean;
|
|
623
|
+
/** Prizes paid so far, lamports, and how many. */
|
|
624
|
+
prizesTotal: Amount;
|
|
625
|
+
prizesPaid: number;
|
|
626
|
+
}
|
|
627
|
+
/** Why a round paid no prize (the companion's `RolloverReason`). */
|
|
628
|
+
export type GameRolloverReason = 'noTickets' | 'roundForgotten' | 'noClaim' | 'oracleSilent' | 'blocked' | 'late' | 'oracleUnreadable' | 'oracleUnpaid';
|
|
629
|
+
/** One round's draw as the indexer saw it (`GameStatus.draws`), newest first. */
|
|
630
|
+
export interface GameDraw {
|
|
631
|
+
round: number;
|
|
632
|
+
/** When the round ended (its tickets final), unix seconds. */
|
|
633
|
+
roundEndsAt: number;
|
|
634
|
+
/** `requested`: ORAO asked; `revealed`: its answer stored, claims open; `paid`: the prize paid; `rolledOver`: no prize. */
|
|
635
|
+
status: 'requested' | 'revealed' | 'paid' | 'rolledOver';
|
|
636
|
+
/** The round's ticket total, base units. */
|
|
637
|
+
total: Amount;
|
|
638
|
+
/** ORAO's request account for the round's seed; null when no seed was committed. */
|
|
639
|
+
request: Address | null;
|
|
640
|
+
winner: Address | null;
|
|
641
|
+
/** Lamports paid; null unless paid. */
|
|
642
|
+
prize: Amount | null;
|
|
643
|
+
/** The ticket attempt that paid, 0-based; null unless paid. */
|
|
644
|
+
attempt: number | null;
|
|
645
|
+
reason: GameRolloverReason | null;
|
|
646
|
+
/** The transaction of the draw's last step, and when it landed. */
|
|
647
|
+
signature: string;
|
|
648
|
+
at: number | null;
|
|
649
|
+
}
|
|
650
|
+
/** The caller's place in the current round (`GameStatus.you`, with `?wallet=`). */
|
|
651
|
+
export interface GameYou {
|
|
652
|
+
wallet: Address;
|
|
653
|
+
/** Tokens the wallet holds, base units. */
|
|
654
|
+
balance: Amount;
|
|
655
|
+
/** Its tickets in the current round, base units (its range's weight); 0 until it is written this round. */
|
|
656
|
+
tickets: Amount;
|
|
657
|
+
/** The wallet's holding has been written this round (it traded, or was entered): its tickets are registered. */
|
|
658
|
+
entered: boolean;
|
|
659
|
+
/** Its share of the round's tickets so far, 0 to 1; null without tickets. */
|
|
660
|
+
odds: number | null;
|
|
661
|
+
/** Its tickets in the round before (the one a draw may be for now). */
|
|
662
|
+
previousTickets: Amount;
|
|
663
|
+
}
|
|
664
|
+
/** A lottery coin's live state (`CompanionStatus.game`, docs/games.md): the pot, the round, the draw, the caller's tickets and the draw history. */
|
|
665
|
+
export interface GameStatus extends GameSummary {
|
|
666
|
+
/** Bridged SOL the pot holds, lamports. */
|
|
667
|
+
pot: Amount;
|
|
668
|
+
/** Chain time the status was read at, unix seconds. */
|
|
669
|
+
now: number;
|
|
670
|
+
/** The current round (`floor(now / roundSecs)`) and when it ends. */
|
|
671
|
+
round: number;
|
|
672
|
+
roundEndsAt: number;
|
|
673
|
+
/** Tickets registered in the current round so far, base units (the hook's header). */
|
|
674
|
+
tickets: Amount;
|
|
675
|
+
/** The round before, and its tickets: what the next draw is for once the round ends. */
|
|
676
|
+
previousRound: number;
|
|
677
|
+
previousTickets: Amount;
|
|
678
|
+
/** The least the pot must hold for a round to be drawn (the minimum, or the cap when lower; 0.1 SOL once dormant). */
|
|
679
|
+
drawThreshold: Amount;
|
|
680
|
+
/** The draw in progress, when one is. */
|
|
681
|
+
draw: {
|
|
682
|
+
round: number;
|
|
683
|
+
status: 'requested' | 'revealed';
|
|
684
|
+
request: Address;
|
|
685
|
+
prize: Amount;
|
|
686
|
+
revealedAt: number | null;
|
|
687
|
+
claimsEndAt: number;
|
|
688
|
+
} | null;
|
|
689
|
+
lastWinner: Address | null;
|
|
690
|
+
you: GameYou | null;
|
|
691
|
+
draws: GameDraw[];
|
|
692
|
+
}
|
|
693
|
+
/** `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
|
+
export interface GameEnterRequest {
|
|
695
|
+
wallet: Address;
|
|
696
|
+
mint: Address;
|
|
697
|
+
}
|
|
698
|
+
export interface GameEnterResponse {
|
|
699
|
+
transactions: PreparedTx[];
|
|
700
|
+
/** The round the tickets count in. */
|
|
701
|
+
round: number;
|
|
513
702
|
}
|
|
514
|
-
/** The companion templates (`COMPANION_TEMPLATES` in the SDK). */
|
|
703
|
+
/** The companion templates (`COMPANION_SPLITS`; `COMPANION_TEMPLATES` in the SDK). */
|
|
515
704
|
export type CompanionTemplate = 'buysItself' | 'rugProofDev' | 'buybackAndReward';
|
|
705
|
+
/** What a companion does with every creator fee it claims, in basis points summing to 10,000. */
|
|
706
|
+
export interface CompanionSplitBps {
|
|
707
|
+
/** Bought back on the token's own pool and burned. */
|
|
708
|
+
buybackBps: number;
|
|
709
|
+
/** Streamed to holders through the kit (the launch has holder rewards). */
|
|
710
|
+
holdersBps: number;
|
|
711
|
+
/** Paid to the launcher (the beneficiary), in SOL. */
|
|
712
|
+
beneficiaryBps: number;
|
|
713
|
+
}
|
|
714
|
+
/** A launch's companion as its token page shows it (`LaunchDetail.companion`): lamports and token base units as strings. */
|
|
715
|
+
export interface CompanionSummary {
|
|
716
|
+
/** The companion's account, `PDA(["companion", mint])` under the companion program. */
|
|
717
|
+
address: Address;
|
|
718
|
+
/** The wallet that launched it: paid its part of the fees, and the dev bag as it vests. */
|
|
719
|
+
beneficiary: Address;
|
|
720
|
+
/** The template whose split this is; null for another split. */
|
|
721
|
+
template: CompanionTemplate | null;
|
|
722
|
+
split: CompanionSplitBps;
|
|
723
|
+
/** Creator fees the companion has claimed, all time. */
|
|
724
|
+
claimed: Amount;
|
|
725
|
+
/** Spent buying the token back on its pool. */
|
|
726
|
+
boughtBack: Amount;
|
|
727
|
+
/** Tokens those buybacks burned. */
|
|
728
|
+
burned: Amount;
|
|
729
|
+
/** Shared with holders through the kit. */
|
|
730
|
+
shared: Amount;
|
|
731
|
+
/** Paid to the launcher. */
|
|
732
|
+
paidToLauncher: Amount;
|
|
733
|
+
/** Claimed and waiting for its step: the next buyback, the next share, the launcher's next payout. */
|
|
734
|
+
pending: {
|
|
735
|
+
buyback: Amount;
|
|
736
|
+
holders: Amount;
|
|
737
|
+
launcher: Amount;
|
|
738
|
+
};
|
|
739
|
+
/** The launcher's first buy, held by the companion: its tokens, the part vested now, the part sent to the launcher, and when the vest ends; null without one. */
|
|
740
|
+
devBag: {
|
|
741
|
+
total: Amount;
|
|
742
|
+
vested: Amount;
|
|
743
|
+
released: Amount;
|
|
744
|
+
vestEndsAt: number;
|
|
745
|
+
} | null;
|
|
746
|
+
/** When the next buyback may run while SOL waits for one (it also waits while the price runs above its reference); null otherwise. */
|
|
747
|
+
nextBuybackAt: number | null;
|
|
748
|
+
/** The lottery this companion runs (docs/games.md): its settings and the hook's terms; null for a companion without a game. */
|
|
749
|
+
game: GameSummary | null;
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* `GET /v1/companion/:mint`: a mint's companion as a launch that may not have finished needs it.
|
|
753
|
+
* `none`: no companion was set up for the mint; `set-up`: set up, its launch not made (its funding
|
|
754
|
+
* can be taken back, `refundable`); `launched`.
|
|
755
|
+
*/
|
|
756
|
+
export interface CompanionStatus {
|
|
757
|
+
mint: Address;
|
|
758
|
+
state: 'none' | 'set-up' | 'launched';
|
|
759
|
+
/** The wallet it pays; null with none. */
|
|
760
|
+
beneficiary: Address | null;
|
|
761
|
+
/** Lamports a refund returns now; null unless `set-up`. */
|
|
762
|
+
refundable: Amount | null;
|
|
763
|
+
/** The lottery this companion runs, live (docs/games.md), the caller's tickets with `?wallet=`; null for a companion without a game (absent from older servers). */
|
|
764
|
+
game?: GameStatus | null;
|
|
765
|
+
}
|
|
766
|
+
/** `POST /v1/companion/refund/prepare`: a companion's funding back to the wallet that set it up, for a launch that never happened. The mint's key signs it after the wallet. */
|
|
767
|
+
export interface CompanionRefundRequest {
|
|
768
|
+
wallet: Address;
|
|
769
|
+
mint: Address;
|
|
770
|
+
}
|
|
771
|
+
export interface CompanionRefundResponse {
|
|
772
|
+
transaction: PreparedTx;
|
|
773
|
+
/** Lamports it returns. */
|
|
774
|
+
refundable: Amount;
|
|
775
|
+
}
|
|
516
776
|
export interface LaunchPrepareResponse {
|
|
517
777
|
intentId: string;
|
|
518
778
|
mint: Address;
|
|
@@ -619,6 +879,13 @@ export interface BridgeAsset {
|
|
|
619
879
|
* opened. Absent from backends that predate it.
|
|
620
880
|
*/
|
|
621
881
|
pool?: BridgePool | null;
|
|
882
|
+
/**
|
|
883
|
+
* The platform token on the overview only: base units bought back from protocol revenue and held
|
|
884
|
+
* by the treasury (docs page /docs/tokenomics): its holding of the bridged version, plus the SPL
|
|
885
|
+
* version it holds that is still to be bridged. Read live from the chain; null when it could not
|
|
886
|
+
* be read. Absent from backends that predate it and from any other asset.
|
|
887
|
+
*/
|
|
888
|
+
boughtBack?: Amount | null;
|
|
622
889
|
}
|
|
623
890
|
/** A bridged token's pool on the DEX (`BridgeAsset.pool`): where it is, since when, and its last price. */
|
|
624
891
|
export interface BridgePool {
|
package/dist/api.js
CHANGED
|
@@ -3,4 +3,7 @@
|
|
|
3
3
|
* (`Amount`), dollar figures as numbers, times as unix seconds. A figure the backend could not
|
|
4
4
|
* read is null, never zero.
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
/** "What your hook does": the most an author writes, in characters (the Studio field and what is served). */
|
|
7
|
+
export const HOOK_ABOUT_CHARS = 280;
|
|
8
|
+
/** The official summary's limits, in characters. */
|
|
9
|
+
export const HOOK_SUMMARY_LIMITS = { title: 40, text: 320 };
|
package/dist/policy.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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 { LaunchRules, LaunchRulesInput, Side } from './api.ts';
|
|
9
|
+
import type { CompanionSplitBps, CompanionTemplate, GameRolloverReason, LaunchRules, LaunchRulesInput, LotteryRequest, Side } from './api.ts';
|
|
10
10
|
export declare const BPS = 10000n;
|
|
11
11
|
export declare const WAD: bigint;
|
|
12
12
|
/** The largest u64, where the programs' checked arithmetic stops. */
|
|
@@ -270,6 +270,109 @@ export declare const RULE_PATHS: readonly RulePath[];
|
|
|
270
270
|
export declare const DEFAULT_RULE_PRESET: RulePresetName;
|
|
271
271
|
/** The preset of this name. */
|
|
272
272
|
export declare function presetNamed(name: RulePresetName): RulePreset;
|
|
273
|
+
/**
|
|
274
|
+
* The companion templates' splits of every creator fee claim: the one list the launch form, the
|
|
275
|
+
* token page and the backend read (`COMPANION_TEMPLATES` in the SDK is this object).
|
|
276
|
+
*/
|
|
277
|
+
export declare const COMPANION_SPLITS: Readonly<Record<CompanionTemplate, Readonly<CompanionSplitBps>>>;
|
|
278
|
+
/** The templates in the order the launch form offers them. */
|
|
279
|
+
export declare const COMPANION_TEMPLATE_LIST: readonly CompanionTemplate[];
|
|
280
|
+
/** How long the launcher's first buy vests over, as the launch form offers it (days), and the default (the backend's too). */
|
|
281
|
+
export declare const COMPANION_VEST_CHOICES_DAYS: readonly [7, 30, 90];
|
|
282
|
+
export declare const COMPANION_DEFAULT_VEST_DAYS = 30;
|
|
283
|
+
/** The longest a first buy may vest over, days (the program's limit is a year). */
|
|
284
|
+
export declare const COMPANION_MAX_VEST_DAYS = 365;
|
|
285
|
+
/** The whole creator fee to the launcher: an ordinary launch, with no companion. */
|
|
286
|
+
export declare const LAUNCHER_SPLIT: Readonly<CompanionSplitBps>;
|
|
287
|
+
/**
|
|
288
|
+
* The companion program's game limits (`constants.rs`; `GAME_LIMITS` in the SDK, the same numbers):
|
|
289
|
+
* a round's length, the minimum pot, the prize's least share, a claim window's bounds, the most
|
|
290
|
+
* attempts, and the half of a round the attempts may take. The launch form holds its choices to
|
|
291
|
+
* them and the backend refuses anything outside them (`lotteryProblem`).
|
|
292
|
+
*/
|
|
293
|
+
export declare const LOTTERY_LIMITS: {
|
|
294
|
+
readonly minRoundSecs: 3600;
|
|
295
|
+
readonly maxRoundSecs: number;
|
|
296
|
+
readonly minMinPotLamports: 100000000n;
|
|
297
|
+
readonly maxMinPotLamports: 1000000000000n;
|
|
298
|
+
readonly minPrizeBps: 1000;
|
|
299
|
+
readonly minClaimWindowSecs: 300;
|
|
300
|
+
readonly maxClaimWindowSecs: 86400;
|
|
301
|
+
readonly maxAttempts: 16;
|
|
302
|
+
readonly claimsPerRound: 2;
|
|
303
|
+
/** The most a pot holds while its hook is not audited: 10 SOL, the rest of the pot's share bought back. */
|
|
304
|
+
readonly potCapLamports: 10000000000n;
|
|
305
|
+
/** Lamports the setup pays for the hook's state and registry and the game's account (mainnet rent, 2026-10-08). */
|
|
306
|
+
readonly rentLamports: bigint;
|
|
307
|
+
};
|
|
308
|
+
/** The lottery the launch form starts with (docs/games.md, spec example 1): 6-hour rounds, 70% of the fee to the pot, 30% bought back, draws from 0.5 SOL paying the whole pot, 8 claim attempts of 10 minutes. */
|
|
309
|
+
export declare const LOTTERY_DEFAULTS: Readonly<LotteryRequest>;
|
|
310
|
+
/** The round lengths the form offers (seconds), each with its label. */
|
|
311
|
+
export declare const LOTTERY_ROUND_CHOICES: readonly {
|
|
312
|
+
secs: number;
|
|
313
|
+
label: string;
|
|
314
|
+
}[];
|
|
315
|
+
/** The least a pot holds before a round is drawn, as the form offers it (lamports). */
|
|
316
|
+
export declare const LOTTERY_MIN_POT_CHOICES: readonly {
|
|
317
|
+
lamports: string;
|
|
318
|
+
label: string;
|
|
319
|
+
}[];
|
|
320
|
+
/** What one draw pays of the pot, as the form offers it. */
|
|
321
|
+
export declare const LOTTERY_PRIZE_CHOICES_BPS: readonly [10000, 5000, 2500];
|
|
322
|
+
/** The pot's share moves in steps of 10%, like the Split control's. */
|
|
323
|
+
export declare const LOTTERY_POT_STEP_BPS = 1000;
|
|
324
|
+
/**
|
|
325
|
+
* The claim windows and attempts the form fixes for a round length: 8 attempts of 10 minutes for a
|
|
326
|
+
* round of 6 hours or more, 6 of 5 minutes for an hour (the attempts must fit in half a round).
|
|
327
|
+
*/
|
|
328
|
+
export declare function lotteryClaimsFor(roundSecs: number): {
|
|
329
|
+
claimWindowSecs: number;
|
|
330
|
+
maxAttempts: number;
|
|
331
|
+
};
|
|
332
|
+
/** The lottery a launch request's `companion.game` names, each field a whole number; null when it is malformed (bounds are `lotteryProblem`'s). */
|
|
333
|
+
export declare function lotteryRequestOf(raw: unknown): LotteryRequest | null;
|
|
334
|
+
/**
|
|
335
|
+
* Why `create_game` would refuse this lottery with this split on these rules, in the words the
|
|
336
|
+
* launch form and the backend use (`companion_rules`); null when it would not. The program's own
|
|
337
|
+
* checks (`gameArgsProblem` in the SDK, the same sentences), and what a lottery coin's launch
|
|
338
|
+
* needs besides: a creator fee, no token rules (it launches from Bordrless's lottery config, which
|
|
339
|
+
* has none, and runs its own hook in the kit's place), and no holders' share.
|
|
340
|
+
*/
|
|
341
|
+
export declare function lotteryProblem(game: LotteryRequest, split: CompanionSplitBps, rules: LaunchRulesInput, creatorFeeBps: number): string | null;
|
|
342
|
+
/** The copy rules of a lottery coin (docs/games.md): said on the launch form, the review step and the token page, never "provably fair", "guaranteed" or "audited". */
|
|
343
|
+
export declare const LOTTERY_COPY: {
|
|
344
|
+
readonly held: "The pot is held by the companion program and paid out by code. Randomness by ORAO VRF.";
|
|
345
|
+
readonly odds: "Your odds = your tokens in the round. Sell before the payout and you forfeit.";
|
|
346
|
+
readonly stop: "Bordrless can stop this game: its pot would be bought back and burned. Nobody is paid.";
|
|
347
|
+
readonly cap: "Pot capped at 10 SOL until the game’s hook is audited.";
|
|
348
|
+
readonly oracle: "Randomness by ORAO VRF: ORAO’s three signers produce it. They could withhold or bias a draw; Bordrless can’t.";
|
|
349
|
+
};
|
|
350
|
+
/** Why a round paid no prize, in a few words each (`GameRolloverReason`). */
|
|
351
|
+
export declare const ROLLOVER_WORDS: Readonly<Record<GameRolloverReason, string>>;
|
|
352
|
+
/** Whether a split is the whole fee to the launcher (no companion needed). */
|
|
353
|
+
export declare const isLauncherSplit: (s: CompanionSplitBps) => boolean;
|
|
354
|
+
/** A split with a share for holders needs holder rewards on. */
|
|
355
|
+
export declare const splitPaysHolders: (s: CompanionSplitBps) => boolean;
|
|
356
|
+
/** Whether a split takes a first buy: every fee bought back and burned means no dev at all. */
|
|
357
|
+
export declare const splitTakesFirstBuy: (s: CompanionSplitBps) => boolean;
|
|
358
|
+
/** A template that pays holders needs holder rewards on. */
|
|
359
|
+
export declare const companionPaysHolders: (template: CompanionTemplate) => boolean;
|
|
360
|
+
/** Whether a template takes a first buy: the token that buys itself has no dev at all. */
|
|
361
|
+
export declare const companionTakesFirstBuy: (template: CompanionTemplate) => boolean;
|
|
362
|
+
/**
|
|
363
|
+
* The split a launch request's `companion` names: its own `split` (each share a whole number of
|
|
364
|
+
* basis points, 0 to 10,000; their sum is checked by `companionSplitProblem`), or a template's;
|
|
365
|
+
* `null` when it names neither (a malformed request).
|
|
366
|
+
*/
|
|
367
|
+
export declare function companionRequestSplit(raw: unknown): CompanionSplitBps | null;
|
|
368
|
+
/**
|
|
369
|
+
* Why a companion with this split can't run these rules, in the words the launch form and the
|
|
370
|
+
* backend use (`companion_rules`); null when it can. The program's own checks (`BadSplit`,
|
|
371
|
+
* `CreatorLockUnsupported`, `HolderRewardsOff`), and a creator fee for it to run.
|
|
372
|
+
*/
|
|
373
|
+
export declare function companionSplitProblem(split: CompanionSplitBps, rules: Pick<LaunchRulesInput, 'holderFeeBuyBps' | 'holderFeeSellBps' | 'creatorLockDays'>, creatorFeeBps: number): string | null;
|
|
374
|
+
/** The template whose split this is, or null for any other split. */
|
|
375
|
+
export declare function companionTemplateOf(split: CompanionSplitBps): CompanionTemplate | null;
|
|
273
376
|
/** Kit module bits (§4.2). */
|
|
274
377
|
export declare const KIT_MODULES: {
|
|
275
378
|
readonly HOLDER_REWARDS: 1;
|
package/dist/policy.js
CHANGED
|
@@ -346,6 +346,199 @@ export const DEFAULT_RULE_PRESET = 'Plain';
|
|
|
346
346
|
export function presetNamed(name) {
|
|
347
347
|
return RULE_PRESETS.find((p) => p.name === name) ?? RULE_PRESETS[0];
|
|
348
348
|
}
|
|
349
|
+
// ---- companions (docs/companions.md) -----------------------------------------------------------------
|
|
350
|
+
/**
|
|
351
|
+
* The companion templates' splits of every creator fee claim: the one list the launch form, the
|
|
352
|
+
* token page and the backend read (`COMPANION_TEMPLATES` in the SDK is this object).
|
|
353
|
+
*/
|
|
354
|
+
export const COMPANION_SPLITS = {
|
|
355
|
+
/** No dev at all: every creator fee buys the token back and burns it. */
|
|
356
|
+
buysItself: { buybackBps: 10_000, holdersBps: 0, beneficiaryBps: 0 },
|
|
357
|
+
/** Half to holders through the kit, half to the launcher, whose first buy vests (holder rewards needed). */
|
|
358
|
+
rugProofDev: { buybackBps: 0, holdersBps: 5_000, beneficiaryBps: 5_000 },
|
|
359
|
+
/** Half bought back and burned, half to holders (holder rewards needed). */
|
|
360
|
+
buybackAndReward: { buybackBps: 5_000, holdersBps: 5_000, beneficiaryBps: 0 },
|
|
361
|
+
};
|
|
362
|
+
/** The templates in the order the launch form offers them. */
|
|
363
|
+
export const COMPANION_TEMPLATE_LIST = ['buysItself', 'rugProofDev', 'buybackAndReward'];
|
|
364
|
+
/** How long the launcher's first buy vests over, as the launch form offers it (days), and the default (the backend's too). */
|
|
365
|
+
export const COMPANION_VEST_CHOICES_DAYS = [7, 30, 90];
|
|
366
|
+
export const COMPANION_DEFAULT_VEST_DAYS = 30;
|
|
367
|
+
/** The longest a first buy may vest over, days (the program's limit is a year). */
|
|
368
|
+
export const COMPANION_MAX_VEST_DAYS = 365;
|
|
369
|
+
/** The whole creator fee to the launcher: an ordinary launch, with no companion. */
|
|
370
|
+
export const LAUNCHER_SPLIT = { buybackBps: 0, holdersBps: 0, beneficiaryBps: 10_000 };
|
|
371
|
+
// ---- lottery coins (docs/games.md) -----------------------------------------------------------------
|
|
372
|
+
/**
|
|
373
|
+
* The companion program's game limits (`constants.rs`; `GAME_LIMITS` in the SDK, the same numbers):
|
|
374
|
+
* a round's length, the minimum pot, the prize's least share, a claim window's bounds, the most
|
|
375
|
+
* attempts, and the half of a round the attempts may take. The launch form holds its choices to
|
|
376
|
+
* them and the backend refuses anything outside them (`lotteryProblem`).
|
|
377
|
+
*/
|
|
378
|
+
export const LOTTERY_LIMITS = {
|
|
379
|
+
minRoundSecs: 3_600,
|
|
380
|
+
maxRoundSecs: 30 * DAY_SECS,
|
|
381
|
+
minMinPotLamports: 100000000n,
|
|
382
|
+
maxMinPotLamports: 1000000000000n,
|
|
383
|
+
minPrizeBps: 1_000,
|
|
384
|
+
minClaimWindowSecs: 300,
|
|
385
|
+
maxClaimWindowSecs: DAY_SECS,
|
|
386
|
+
maxAttempts: 16,
|
|
387
|
+
claimsPerRound: 2,
|
|
388
|
+
/** The most a pot holds while its hook is not audited: 10 SOL, the rest of the pot's share bought back. */
|
|
389
|
+
potCapLamports: 10000000000n,
|
|
390
|
+
/** Lamports the setup pays for the hook's state and registry and the game's account (mainnet rent, 2026-10-08). */
|
|
391
|
+
rentLamports: 3515360n + 3119120n,
|
|
392
|
+
};
|
|
393
|
+
/** The lottery the launch form starts with (docs/games.md, spec example 1): 6-hour rounds, 70% of the fee to the pot, 30% bought back, draws from 0.5 SOL paying the whole pot, 8 claim attempts of 10 minutes. */
|
|
394
|
+
export const LOTTERY_DEFAULTS = { kind: 'lottery', roundSecs: 21_600, potBps: 7_000, minPotLamports: '500000000', prizeBps: 10_000, claimWindowSecs: 600, maxAttempts: 8 };
|
|
395
|
+
/** The round lengths the form offers (seconds), each with its label. */
|
|
396
|
+
export const LOTTERY_ROUND_CHOICES = [
|
|
397
|
+
{ secs: 3_600, label: '1 hour' },
|
|
398
|
+
{ secs: 21_600, label: '6 hours' },
|
|
399
|
+
{ secs: DAY_SECS, label: '1 day' },
|
|
400
|
+
{ secs: 7 * DAY_SECS, label: '7 days' },
|
|
401
|
+
];
|
|
402
|
+
/** The least a pot holds before a round is drawn, as the form offers it (lamports). */
|
|
403
|
+
export const LOTTERY_MIN_POT_CHOICES = [
|
|
404
|
+
{ lamports: '100000000', label: '0.1 SOL' },
|
|
405
|
+
{ lamports: '500000000', label: '0.5 SOL' },
|
|
406
|
+
{ lamports: '1000000000', label: '1 SOL' },
|
|
407
|
+
{ lamports: '5000000000', label: '5 SOL' },
|
|
408
|
+
];
|
|
409
|
+
/** What one draw pays of the pot, as the form offers it. */
|
|
410
|
+
export const LOTTERY_PRIZE_CHOICES_BPS = [10_000, 5_000, 2_500];
|
|
411
|
+
/** The pot's share moves in steps of 10%, like the Split control's. */
|
|
412
|
+
export const LOTTERY_POT_STEP_BPS = 1_000;
|
|
413
|
+
/**
|
|
414
|
+
* The claim windows and attempts the form fixes for a round length: 8 attempts of 10 minutes for a
|
|
415
|
+
* round of 6 hours or more, 6 of 5 minutes for an hour (the attempts must fit in half a round).
|
|
416
|
+
*/
|
|
417
|
+
export function lotteryClaimsFor(roundSecs) {
|
|
418
|
+
return roundSecs >= 21_600 ? { claimWindowSecs: 600, maxAttempts: 8 } : { claimWindowSecs: 300, maxAttempts: 6 };
|
|
419
|
+
}
|
|
420
|
+
const isWhole = (v, min, max) => typeof v === 'number' && Number.isInteger(v) && v >= min && v <= max;
|
|
421
|
+
/** The lottery a launch request's `companion.game` names, each field a whole number; null when it is malformed (bounds are `lotteryProblem`'s). */
|
|
422
|
+
export function lotteryRequestOf(raw) {
|
|
423
|
+
if (typeof raw !== 'object' || raw === null)
|
|
424
|
+
return null;
|
|
425
|
+
const r = raw;
|
|
426
|
+
if (r.kind !== 'lottery')
|
|
427
|
+
return null;
|
|
428
|
+
if (!isWhole(r.roundSecs, 0, 2 ** 32 - 1) || !isWhole(r.potBps, 0, 10_000) || !isWhole(r.prizeBps, 0, 10_000) || !isWhole(r.claimWindowSecs, 0, 2 ** 32 - 1) || !isWhole(r.maxAttempts, 0, 255))
|
|
429
|
+
return null;
|
|
430
|
+
if (typeof r.minPotLamports !== 'string' || !/^\d{1,20}$/.test(r.minPotLamports))
|
|
431
|
+
return null;
|
|
432
|
+
return { kind: 'lottery', roundSecs: r.roundSecs, potBps: r.potBps, minPotLamports: r.minPotLamports, prizeBps: r.prizeBps, claimWindowSecs: r.claimWindowSecs, maxAttempts: r.maxAttempts };
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Why `create_game` would refuse this lottery with this split on these rules, in the words the
|
|
436
|
+
* launch form and the backend use (`companion_rules`); null when it would not. The program's own
|
|
437
|
+
* checks (`gameArgsProblem` in the SDK, the same sentences), and what a lottery coin's launch
|
|
438
|
+
* needs besides: a creator fee, no token rules (it launches from Bordrless's lottery config, which
|
|
439
|
+
* has none, and runs its own hook in the kit's place), and no holders' share.
|
|
440
|
+
*/
|
|
441
|
+
export function lotteryProblem(game, split, rules, creatorFeeBps) {
|
|
442
|
+
const L = LOTTERY_LIMITS;
|
|
443
|
+
if (creatorFeeBps === 0)
|
|
444
|
+
return 'There is no creator fee to fill the pot with: set one.';
|
|
445
|
+
if (Object.keys(NO_RULES).some((k) => rules[k] !== 0))
|
|
446
|
+
return 'A lottery coin has no other token rules: its hook keeps the tickets.';
|
|
447
|
+
if (split.holdersBps !== 0)
|
|
448
|
+
return 'A game coin runs its own hook, so it has no holder rewards: no holders’ part.';
|
|
449
|
+
if (!(game.potBps > 0) || split.buybackBps + split.holdersBps + split.beneficiaryBps + game.potBps !== 10_000)
|
|
450
|
+
return 'The pot’s part and the split must add up to 10,000 basis points, with a pot.';
|
|
451
|
+
if (game.roundSecs < L.minRoundSecs || game.roundSecs > L.maxRoundSecs)
|
|
452
|
+
return 'A round lasts an hour to 30 days.';
|
|
453
|
+
const minPot = BigInt(game.minPotLamports);
|
|
454
|
+
if (minPot < L.minMinPotLamports || minPot > L.maxMinPotLamports)
|
|
455
|
+
return 'The minimum pot is 0.1 to 1,000 SOL.';
|
|
456
|
+
if (game.prizeBps < L.minPrizeBps || game.prizeBps > 10_000)
|
|
457
|
+
return 'A draw pays 10% to 100% of the pot.';
|
|
458
|
+
if (game.claimWindowSecs < L.minClaimWindowSecs || game.claimWindowSecs > L.maxClaimWindowSecs)
|
|
459
|
+
return 'A claim window lasts 5 minutes to a day.';
|
|
460
|
+
if (!(game.maxAttempts >= 1 && game.maxAttempts <= L.maxAttempts))
|
|
461
|
+
return 'A draw makes 1 to 16 attempts.';
|
|
462
|
+
if (game.claimWindowSecs * game.maxAttempts * L.claimsPerRound > game.roundSecs)
|
|
463
|
+
return 'A draw’s attempts must fit in half a round.';
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
/** The copy rules of a lottery coin (docs/games.md): said on the launch form, the review step and the token page, never "provably fair", "guaranteed" or "audited". */
|
|
467
|
+
export const LOTTERY_COPY = {
|
|
468
|
+
held: 'The pot is held by the companion program and paid out by code. Randomness by ORAO VRF.',
|
|
469
|
+
odds: 'Your odds = your tokens in the round. Sell before the payout and you forfeit.',
|
|
470
|
+
stop: 'Bordrless can stop this game: its pot would be bought back and burned. Nobody is paid.',
|
|
471
|
+
cap: 'Pot capped at 10 SOL until the game’s hook is audited.',
|
|
472
|
+
oracle: 'Randomness by ORAO VRF: ORAO’s three signers produce it. They could withhold or bias a draw; Bordrless can’t.',
|
|
473
|
+
};
|
|
474
|
+
/** Why a round paid no prize, in a few words each (`GameRolloverReason`). */
|
|
475
|
+
export const ROLLOVER_WORDS = {
|
|
476
|
+
noTickets: 'Nobody held tickets',
|
|
477
|
+
roundForgotten: 'The round was forgotten',
|
|
478
|
+
noClaim: 'No winner claimed in time',
|
|
479
|
+
oracleSilent: 'ORAO never answered',
|
|
480
|
+
blocked: 'Stopped by Bordrless',
|
|
481
|
+
late: 'Drawn too late',
|
|
482
|
+
oracleUnreadable: 'ORAO’s answer unreadable',
|
|
483
|
+
oracleUnpaid: 'The pot could not pay ORAO',
|
|
484
|
+
};
|
|
485
|
+
/** Whether a split is the whole fee to the launcher (no companion needed). */
|
|
486
|
+
export const isLauncherSplit = (s) => s.buybackBps === 0 && s.holdersBps === 0 && s.beneficiaryBps === 10_000;
|
|
487
|
+
/** A split with a share for holders needs holder rewards on. */
|
|
488
|
+
export const splitPaysHolders = (s) => s.holdersBps > 0;
|
|
489
|
+
/** Whether a split takes a first buy: every fee bought back and burned means no dev at all. */
|
|
490
|
+
export const splitTakesFirstBuy = (s) => s.buybackBps < 10_000;
|
|
491
|
+
/** A template that pays holders needs holder rewards on. */
|
|
492
|
+
export const companionPaysHolders = (template) => splitPaysHolders(COMPANION_SPLITS[template]);
|
|
493
|
+
/** Whether a template takes a first buy: the token that buys itself has no dev at all. */
|
|
494
|
+
export const companionTakesFirstBuy = (template) => splitTakesFirstBuy(COMPANION_SPLITS[template]);
|
|
495
|
+
const isShare = (v) => typeof v === 'number' && Number.isInteger(v) && v >= 0 && v <= 10_000;
|
|
496
|
+
/**
|
|
497
|
+
* The split a launch request's `companion` names: its own `split` (each share a whole number of
|
|
498
|
+
* basis points, 0 to 10,000; their sum is checked by `companionSplitProblem`), or a template's;
|
|
499
|
+
* `null` when it names neither (a malformed request).
|
|
500
|
+
*/
|
|
501
|
+
export function companionRequestSplit(raw) {
|
|
502
|
+
if (typeof raw !== 'object' || raw === null)
|
|
503
|
+
return null;
|
|
504
|
+
const r = raw;
|
|
505
|
+
if (r.split !== undefined) {
|
|
506
|
+
if (typeof r.split !== 'object' || r.split === null)
|
|
507
|
+
return null;
|
|
508
|
+
const s = r.split;
|
|
509
|
+
if (!isShare(s.buybackBps) || !isShare(s.holdersBps) || !isShare(s.beneficiaryBps))
|
|
510
|
+
return null;
|
|
511
|
+
return { buybackBps: s.buybackBps, holdersBps: s.holdersBps, beneficiaryBps: s.beneficiaryBps };
|
|
512
|
+
}
|
|
513
|
+
if (typeof r.template === 'string' && Object.hasOwn(COMPANION_SPLITS, r.template))
|
|
514
|
+
return { ...COMPANION_SPLITS[r.template] };
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Why a companion with this split can't run these rules, in the words the launch form and the
|
|
519
|
+
* backend use (`companion_rules`); null when it can. The program's own checks (`BadSplit`,
|
|
520
|
+
* `CreatorLockUnsupported`, `HolderRewardsOff`), and a creator fee for it to run.
|
|
521
|
+
*/
|
|
522
|
+
export function companionSplitProblem(split, rules, creatorFeeBps) {
|
|
523
|
+
if (![split.buybackBps, split.holdersBps, split.beneficiaryBps].every(isShare))
|
|
524
|
+
return 'Each share of the creator fee is a whole number of basis points, 0 to 10,000.';
|
|
525
|
+
if (split.buybackBps + split.holdersBps + split.beneficiaryBps !== 10_000)
|
|
526
|
+
return 'The creator fee’s shares must add up to 100% (10,000 basis points).';
|
|
527
|
+
if (rules.creatorLockDays > 0)
|
|
528
|
+
return 'A creator fee run by a program can’t come with the creator wallet lock: the program vests your first buy instead.';
|
|
529
|
+
if (creatorFeeBps === 0)
|
|
530
|
+
return 'There is no creator fee for a program to run: set one.';
|
|
531
|
+
if (splitPaysHolders(split) && !rewardsOn(rules))
|
|
532
|
+
return 'A share of the creator fee for holders needs holder rewards on.';
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
/** The template whose split this is, or null for any other split. */
|
|
536
|
+
export function companionTemplateOf(split) {
|
|
537
|
+
return COMPANION_TEMPLATE_LIST.find((t) => {
|
|
538
|
+
const s = COMPANION_SPLITS[t];
|
|
539
|
+
return s.buybackBps === split.buybackBps && s.holdersBps === split.holdersBps && s.beneficiaryBps === split.beneficiaryBps;
|
|
540
|
+
}) ?? null;
|
|
541
|
+
}
|
|
349
542
|
/** Kit module bits (§4.2). */
|
|
350
543
|
export const KIT_MODULES = { HOLDER_REWARDS: 1, MAX_WALLET: 2, CREATOR_LOCK: 4, EARLY_LOCK: 8 };
|
|
351
544
|
/** The kit modules a launch with these rules installs (§5.1); 0 means no hook at all (a burn alone runs in the pool hook). */
|
package/dist/programs.d.ts
CHANGED
|
@@ -16,7 +16,36 @@ export declare const PROGRAM_IDS: {
|
|
|
16
16
|
readonly halfLife: "53SpmtkdPWQ63mWoDeXk8P9tuwiT4ed2Wx4fwfy5NSF8";
|
|
17
17
|
/** Companions (programs/bordrless_companion, docs/companions.md): a launch whose creator is a program, its fees bought back, shared with holders or vested by code. */
|
|
18
18
|
readonly companion: "6ZUM1gWBH9hBBNoJoaVAGwSftyZ6CUda6vUZTW9MsJuo";
|
|
19
|
+
/**
|
|
20
|
+
* The lottery hook (programs/lottery_hook, docs/companions.md "Games"): a lottery coin's token
|
|
21
|
+
* hook under the game ticket standard; its companion holds the pot and draws. Not deployed yet
|
|
22
|
+
* (2026-10-08): companion v2 and this hook go live together, after their audit.
|
|
23
|
+
*/
|
|
24
|
+
readonly lotteryHook: "HqFWsCBQ416DAfevJ9TspyT5yXGGoYTCpcreiGkCgWcr";
|
|
19
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* The lottery hook's fixed facts (programs/lottery_hook/README.md): the flags a `LaunchConfig`
|
|
28
|
+
* names for it (`BEFORE_TRANSFER | BEFORE_BURN | WRITES_HOOK_DATA`, exactly what a companion game
|
|
29
|
+
* launch accepts), the token program's signer of its callbacks (`["hook-authority", hook]` under
|
|
30
|
+
* the token program) and its own `["hook-authority"]`, which signs `write_hook_data` in `enter`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const LOTTERY_HOOK: {
|
|
33
|
+
readonly program: "HqFWsCBQ416DAfevJ9TspyT5yXGGoYTCpcreiGkCgWcr";
|
|
34
|
+
readonly flags: 145;
|
|
35
|
+
readonly tokenHookSigner: "CFyuaxvKmpgnSMCoNqDKCcwxnTUeW8Mm1go1t3UMsvLH";
|
|
36
|
+
readonly hookAuthority: "6oZ9LkAfgmhmPYjXj3okjYK5sderddEo8fp8MR4H6Gx1";
|
|
37
|
+
readonly readme: "https://github.com/BordrlessDex/bordrless-programs/tree/main/programs/lottery_hook";
|
|
38
|
+
/**
|
|
39
|
+
* The `LaunchConfig`s a lottery coin launches from, one per creator fee (basis points): each
|
|
40
|
+
* names this hook with `flags`, no token rules and that creator fee, made by the protocol with
|
|
41
|
+
* `pnpm admin lottery-configs` (which prints the lines to paste here). A creator fee without a
|
|
42
|
+
* config can't launch a lottery coin yet: the launch form says so, the backend refuses it
|
|
43
|
+
* (`lottery_off`). Created on mainnet 2026-10-09 by `pnpm admin lottery-configs`.
|
|
44
|
+
*/
|
|
45
|
+
readonly launchConfigs: Readonly<Partial<Record<number, string>>>;
|
|
46
|
+
};
|
|
47
|
+
/** The creator fees (basis points) a lottery coin can launch with: those with a `LaunchConfig` in `LOTTERY_HOOK.launchConfigs`. */
|
|
48
|
+
export declare const lotteryFeeChoices: () => number[];
|
|
20
49
|
/**
|
|
21
50
|
* Half-Life, Bordrless's own token hook (programs/half_life/README.md): the `LaunchConfig` the
|
|
22
51
|
* launch form's Half-Life path launches from (no kit rules, creator fee 1%, the hook with
|
package/dist/programs.js
CHANGED
|
@@ -16,7 +16,40 @@ export const PROGRAM_IDS = {
|
|
|
16
16
|
halfLife: '53SpmtkdPWQ63mWoDeXk8P9tuwiT4ed2Wx4fwfy5NSF8',
|
|
17
17
|
/** Companions (programs/bordrless_companion, docs/companions.md): a launch whose creator is a program, its fees bought back, shared with holders or vested by code. */
|
|
18
18
|
companion: '6ZUM1gWBH9hBBNoJoaVAGwSftyZ6CUda6vUZTW9MsJuo',
|
|
19
|
+
/**
|
|
20
|
+
* The lottery hook (programs/lottery_hook, docs/companions.md "Games"): a lottery coin's token
|
|
21
|
+
* hook under the game ticket standard; its companion holds the pot and draws. Not deployed yet
|
|
22
|
+
* (2026-10-08): companion v2 and this hook go live together, after their audit.
|
|
23
|
+
*/
|
|
24
|
+
lotteryHook: 'HqFWsCBQ416DAfevJ9TspyT5yXGGoYTCpcreiGkCgWcr',
|
|
19
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* The lottery hook's fixed facts (programs/lottery_hook/README.md): the flags a `LaunchConfig`
|
|
28
|
+
* names for it (`BEFORE_TRANSFER | BEFORE_BURN | WRITES_HOOK_DATA`, exactly what a companion game
|
|
29
|
+
* launch accepts), the token program's signer of its callbacks (`["hook-authority", hook]` under
|
|
30
|
+
* the token program) and its own `["hook-authority"]`, which signs `write_hook_data` in `enter`.
|
|
31
|
+
*/
|
|
32
|
+
export const LOTTERY_HOOK = {
|
|
33
|
+
program: PROGRAM_IDS.lotteryHook,
|
|
34
|
+
flags: 145,
|
|
35
|
+
tokenHookSigner: 'CFyuaxvKmpgnSMCoNqDKCcwxnTUeW8Mm1go1t3UMsvLH',
|
|
36
|
+
hookAuthority: '6oZ9LkAfgmhmPYjXj3okjYK5sderddEo8fp8MR4H6Gx1',
|
|
37
|
+
readme: 'https://github.com/BordrlessDex/bordrless-programs/tree/main/programs/lottery_hook',
|
|
38
|
+
/**
|
|
39
|
+
* The `LaunchConfig`s a lottery coin launches from, one per creator fee (basis points): each
|
|
40
|
+
* names this hook with `flags`, no token rules and that creator fee, made by the protocol with
|
|
41
|
+
* `pnpm admin lottery-configs` (which prints the lines to paste here). A creator fee without a
|
|
42
|
+
* config can't launch a lottery coin yet: the launch form says so, the backend refuses it
|
|
43
|
+
* (`lottery_off`). Created on mainnet 2026-10-09 by `pnpm admin lottery-configs`.
|
|
44
|
+
*/
|
|
45
|
+
launchConfigs: {
|
|
46
|
+
50: 'CYm9FNY49gV7wjukpYFqkm9u7FQLrjp2GexeHwbncf1L',
|
|
47
|
+
100: 'GViQSt6znkGdiCrUYNTo3dJKMgfGS8eBHPSBjxfN2VS9',
|
|
48
|
+
200: 'Ejo4Ehg4x16MeBKn3Y2f1NU31CoBjnby4kv75azUD8hU',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
/** The creator fees (basis points) a lottery coin can launch with: those with a `LaunchConfig` in `LOTTERY_HOOK.launchConfigs`. */
|
|
52
|
+
export const lotteryFeeChoices = () => Object.keys(LOTTERY_HOOK.launchConfigs).map(Number).filter((bps) => bps > 0);
|
|
20
53
|
/**
|
|
21
54
|
* Half-Life, Bordrless's own token hook (programs/half_life/README.md): the `LaunchConfig` the
|
|
22
55
|
* launch form's Half-Life path launches from (no kit rules, creator fee 1%, the hook with
|
package/dist/studio.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* for a new mint without knowing its code. The backend refuses source that leaves the template's
|
|
14
14
|
* limits (`StaticCheck`) before it is built, reviewed or paid for.
|
|
15
15
|
*/
|
|
16
|
-
import type { Address, PreparedTx } from './api.ts';
|
|
16
|
+
import type { Address, HookAbout, PreparedTx } from './api.ts';
|
|
17
17
|
/** One source file of a hook. Only `src/**.rs` files; `Cargo.toml` is the template's and never sent. */
|
|
18
18
|
export interface StudioFile {
|
|
19
19
|
/** Relative path, e.g. `src/lib.rs`. */
|
|
@@ -167,9 +167,15 @@ export interface ReviewFinding {
|
|
|
167
167
|
/** `src/lib.rs:42`, when it points at a line. */
|
|
168
168
|
location: string | null;
|
|
169
169
|
}
|
|
170
|
-
/**
|
|
170
|
+
/**
|
|
171
|
+
* `POST /v1/studio/projects/:id/review`: the static checks, then the automated security review (two
|
|
172
|
+
* independent model runs, the worse kept). Every run of a source (and the hook flags read from it,
|
|
173
|
+
* `detectHookFlags`) is kept: the review shown is the worst of them, its findings merged, and a
|
|
174
|
+
* source that failed once stays failed (only a change to the source clears it). Automated checks,
|
|
175
|
+
* not an audit.
|
|
176
|
+
*/
|
|
171
177
|
export interface StudioReview {
|
|
172
|
-
/** `fail`: a static check failed or a critical/high finding; deploy is refused. */
|
|
178
|
+
/** `fail`: a static check failed or a critical/high finding; deploy is refused. The worst of every run of this source and these flags. */
|
|
173
179
|
verdict: 'pass' | 'warn' | 'fail';
|
|
174
180
|
checks: StaticCheck[];
|
|
175
181
|
findings: ReviewFinding[];
|
|
@@ -177,6 +183,10 @@ export interface StudioReview {
|
|
|
177
183
|
summary: string;
|
|
178
184
|
/** sha256 of the source reviewed. */
|
|
179
185
|
sourceHash: string;
|
|
186
|
+
/** The hook flags the source was reviewed with, read from the source itself (`detectHookFlags`). Absent on reviews made before flags were recorded. */
|
|
187
|
+
hookFlags?: number;
|
|
188
|
+
/** How many review runs of this source and these flags the verdict and findings merge. */
|
|
189
|
+
runs?: number;
|
|
180
190
|
at: number;
|
|
181
191
|
}
|
|
182
192
|
/** `POST /v1/studio/projects/:id/build`: compiled by the build worker in the verifiable-build image. */
|
|
@@ -191,6 +201,21 @@ export interface StudioBuild {
|
|
|
191
201
|
log: string;
|
|
192
202
|
sourceHash: string;
|
|
193
203
|
at: number;
|
|
204
|
+
/** Studio's trading test of what was built (the worker's simulator): a deploy needs `pass`. Absent on builds from before it. */
|
|
205
|
+
sim?: StudioSim | null;
|
|
206
|
+
}
|
|
207
|
+
/** The simulator's verdict on a build: launched, bought, sold and sent by several wallets over a simulated year. */
|
|
208
|
+
export interface StudioSim {
|
|
209
|
+
pass: boolean;
|
|
210
|
+
/** Why it failed, in the simulator's words (the first few). */
|
|
211
|
+
reasons: string[];
|
|
212
|
+
/** The most the hook took from one transfer, in bps, and the cap it was held to. */
|
|
213
|
+
cutMaxBps: number | null;
|
|
214
|
+
capBps: number | null;
|
|
215
|
+
/** Operations the hook refused. */
|
|
216
|
+
refusedTotal: number;
|
|
217
|
+
/** What the test can't see (amount triggers, wallets it never used, after a year). */
|
|
218
|
+
notes: string[];
|
|
194
219
|
}
|
|
195
220
|
/** `POST /v1/studio/projects/:id/deploy/quote`: what deploying the built hook costs, in lamports. */
|
|
196
221
|
export interface StudioDeployQuote {
|
|
@@ -272,8 +297,13 @@ export interface MarketplaceListing {
|
|
|
272
297
|
studioSource: boolean;
|
|
273
298
|
/** Who can upgrade the hook now (null: final, or no hook). */
|
|
274
299
|
hookUpgradeAuthority: Address | null;
|
|
275
|
-
/**
|
|
276
|
-
|
|
300
|
+
/**
|
|
301
|
+
* The worst verdict of the automated checks over every review run of the deployed source (not an
|
|
302
|
+
* audit, and not a seal); null when Studio did not build the hook or has no review of it.
|
|
303
|
+
*/
|
|
304
|
+
reviewVerdict: 'pass' | 'warn' | 'fail' | null;
|
|
305
|
+
/** What its hook does, when Studio built it: Bordrless's summary and the author's words (`HookAbout`); null or absent otherwise. */
|
|
306
|
+
hookAbout?: HookAbout | null;
|
|
277
307
|
launches: number;
|
|
278
308
|
/** Paid to the author so far, lamports. */
|
|
279
309
|
authorEarned: string;
|
|
@@ -326,6 +356,25 @@ export declare const PROTOCOL_UPGRADE_AUTHORITY = "5xsibKwtiN6ruxsYrEyWVpV3Kcwuz
|
|
|
326
356
|
* may hold a custom hook's upgrade authority for `create_config` to accept it (docs/hooks-v2.md §5.8).
|
|
327
357
|
*/
|
|
328
358
|
export declare const HOOK_UPGRADE_AUTHORITIES: readonly string[];
|
|
359
|
+
/** The one sentence the site gives a config whose hook someone outside Bordrless can upgrade. */
|
|
360
|
+
export declare const HOOK_AUTHORITY_PROBLEM = "This config\u2019s hook can be upgraded by someone outside Bordrless, so it can\u2019t launch here.";
|
|
361
|
+
/** The error code the backend refuses such a config with (`POST /v1/launch/prepare`). */
|
|
362
|
+
export declare const HOOK_AUTHORITY_CODE = "hook_upgrade_authority";
|
|
363
|
+
/** The Solana CLI command that makes a deployed hook immutable (signed by its current upgrade authority): the way a hook built outside Studio can launch. */
|
|
364
|
+
export declare const hookFinalCommand: (program?: string) => string;
|
|
365
|
+
/**
|
|
366
|
+
* 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`), or when its upgrade
|
|
368
|
+
* authority is one of `HOOK_UPGRADE_AUTHORITIES` (Bordrless Studio's key or the protocol's). Only a
|
|
369
|
+
* hook some other key is known to be able to upgrade is refused: one whose upgrade info could not be
|
|
370
|
+
* read is not (the launch program checked it when the config was made; the SDK's
|
|
371
|
+
* `hookAuthorityProblem` and the marketplace watch read it the same way). A config without a hook
|
|
372
|
+
* (`customHook` null: the launchpad's and the kit's own programs) passes.
|
|
373
|
+
*/
|
|
374
|
+
export declare function hookAuthorityAccepted(customHook: string | null, hook: {
|
|
375
|
+
upgradeAuthority: string | null;
|
|
376
|
+
upgradeable: boolean | null;
|
|
377
|
+
} | null): boolean;
|
|
329
378
|
/** `bordrless_launch::constants::MAX_AUTHOR_SHARE_BPS`: half the creator fee, the most the program lets a listed config pay its author. */
|
|
330
379
|
export declare const MAX_AUTHOR_SHARE_BPS = 5000;
|
|
331
380
|
/**
|
|
@@ -357,3 +406,16 @@ export declare const STUDIO_STARTERS: readonly {
|
|
|
357
406
|
name: string;
|
|
358
407
|
blurb: string;
|
|
359
408
|
}[];
|
|
409
|
+
/**
|
|
410
|
+
* The token hook flags a Studio hook needs, read from its own source (never chosen by hand): each
|
|
411
|
+
* callback whose body the author changed from the template's stub; cuts (TRANSFER_RETURNS_DELTA)
|
|
412
|
+
* when before_transfer runs and the code builds a `Delta` or fills `deltas`; hook data
|
|
413
|
+
* (WRITES_HOOK_DATA) when the code sets a holding's `source_hook_data`/`destination_hook_data`
|
|
414
|
+
* (reading `args.…_hook_data` needs no flag). It leans to including a bit: a flag the code doesn't
|
|
415
|
+
* use is harmless, a missing one makes the token program refuse the hook's answer. 0: the hook does
|
|
416
|
+
* nothing yet.
|
|
417
|
+
*/
|
|
418
|
+
export declare function detectHookFlags(files: readonly {
|
|
419
|
+
path: string;
|
|
420
|
+
content: string;
|
|
421
|
+
}[]): number;
|
package/dist/studio.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TOKEN_HOOK_FLAGS as HOOK_FLAG_BITS } from "./programs.js";
|
|
1
2
|
/** The message a wallet signs to use Studio: no transaction, nothing moves. */
|
|
2
3
|
export function studioSignInMessage(wallet, nonce, issuedAt) {
|
|
3
4
|
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');
|
|
@@ -14,6 +15,26 @@ export const PROTOCOL_UPGRADE_AUTHORITY = '5xsibKwtiN6ruxsYrEyWVpV3KcwuzSPbQd1n2
|
|
|
14
15
|
* may hold a custom hook's upgrade authority for `create_config` to accept it (docs/hooks-v2.md §5.8).
|
|
15
16
|
*/
|
|
16
17
|
export const HOOK_UPGRADE_AUTHORITIES = [STUDIO_UPGRADE_AUTHORITY, PROTOCOL_UPGRADE_AUTHORITY];
|
|
18
|
+
/** The one sentence the site gives a config whose hook someone outside Bordrless can upgrade. */
|
|
19
|
+
export const HOOK_AUTHORITY_PROBLEM = 'This config’s hook can be upgraded by someone outside Bordrless, so it can’t launch here.';
|
|
20
|
+
/** The error code the backend refuses such a config with (`POST /v1/launch/prepare`). */
|
|
21
|
+
export const HOOK_AUTHORITY_CODE = 'hook_upgrade_authority';
|
|
22
|
+
/** The Solana CLI command that makes a deployed hook immutable (signed by its current upgrade authority): the way a hook built outside Studio can launch. */
|
|
23
|
+
export const hookFinalCommand = (program = '<PROGRAM_ID>') => `solana program set-upgrade-authority ${program} --final`;
|
|
24
|
+
/**
|
|
25
|
+
* 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`), or when its upgrade
|
|
27
|
+
* authority is one of `HOOK_UPGRADE_AUTHORITIES` (Bordrless Studio's key or the protocol's). Only a
|
|
28
|
+
* hook some other key is known to be able to upgrade is refused: one whose upgrade info could not be
|
|
29
|
+
* read is not (the launch program checked it when the config was made; the SDK's
|
|
30
|
+
* `hookAuthorityProblem` and the marketplace watch read it the same way). A config without a hook
|
|
31
|
+
* (`customHook` null: the launchpad's and the kit's own programs) passes.
|
|
32
|
+
*/
|
|
33
|
+
export function hookAuthorityAccepted(customHook, hook) {
|
|
34
|
+
if (customHook === null || hook === null || hook.upgradeable !== true || hook.upgradeAuthority === null)
|
|
35
|
+
return true;
|
|
36
|
+
return HOOK_UPGRADE_AUTHORITIES.includes(hook.upgradeAuthority);
|
|
37
|
+
}
|
|
17
38
|
/** `bordrless_launch::constants::MAX_AUTHOR_SHARE_BPS`: half the creator fee, the most the program lets a listed config pay its author. */
|
|
18
39
|
export const MAX_AUTHOR_SHARE_BPS = 5_000;
|
|
19
40
|
/**
|
|
@@ -38,3 +59,112 @@ export const STUDIO_STARTERS = [
|
|
|
38
59
|
{ 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.' },
|
|
39
60
|
{ id: 'cooldown', name: 'Transfer cooldown', blurb: 'A holding that received tokens cannot send them on for a while, stamped in its hook data.' },
|
|
40
61
|
];
|
|
62
|
+
// ---- the hook's flags, read from its source ---------------------------------------------------------
|
|
63
|
+
/** Comments, string and char literals blanked (newlines kept), so only code is read. */
|
|
64
|
+
function codeOnly(src) {
|
|
65
|
+
let out = '';
|
|
66
|
+
let i = 0;
|
|
67
|
+
while (i < src.length) {
|
|
68
|
+
const c = src[i];
|
|
69
|
+
const n = src[i + 1];
|
|
70
|
+
if (c === '/' && n === '/') {
|
|
71
|
+
while (i < src.length && src[i] !== '\n')
|
|
72
|
+
i += 1;
|
|
73
|
+
}
|
|
74
|
+
else if (c === '/' && n === '*') {
|
|
75
|
+
let depth = 1;
|
|
76
|
+
i += 2;
|
|
77
|
+
while (i < src.length && depth > 0) {
|
|
78
|
+
if (src[i] === '/' && src[i + 1] === '*')
|
|
79
|
+
(depth += 1), (i += 2);
|
|
80
|
+
else if (src[i] === '*' && src[i + 1] === '/')
|
|
81
|
+
(depth -= 1), (i += 2);
|
|
82
|
+
else {
|
|
83
|
+
if (src[i] === '\n')
|
|
84
|
+
out += '\n';
|
|
85
|
+
i += 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if (c === 'r' && (n === '"' || n === '#') && !/[A-Za-z0-9_]/.test(src[i - 1] ?? '')) {
|
|
90
|
+
let j = i + 1;
|
|
91
|
+
let hashes = 0;
|
|
92
|
+
while (src[j] === '#')
|
|
93
|
+
(hashes += 1), (j += 1);
|
|
94
|
+
if (src[j] !== '"') {
|
|
95
|
+
out += c;
|
|
96
|
+
i += 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const end = '"' + '#'.repeat(hashes);
|
|
100
|
+
const close = src.indexOf(end, j + 1);
|
|
101
|
+
i = close < 0 ? src.length : close + end.length;
|
|
102
|
+
out += ' ';
|
|
103
|
+
}
|
|
104
|
+
else if (c === '"') {
|
|
105
|
+
i += 1;
|
|
106
|
+
while (i < src.length && src[i] !== '"')
|
|
107
|
+
i += src[i] === '\\' ? 2 : 1;
|
|
108
|
+
i += 1;
|
|
109
|
+
out += ' ';
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
out += c;
|
|
113
|
+
i += 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
/** A function's body (between its braces), or null when it isn't there. */
|
|
119
|
+
function bodyOf(code, name) {
|
|
120
|
+
const m = new RegExp(`\\bfn\\s+${name}\\s*\\(`).exec(code);
|
|
121
|
+
if (!m)
|
|
122
|
+
return null;
|
|
123
|
+
const open = code.indexOf('{', m.index);
|
|
124
|
+
if (open < 0)
|
|
125
|
+
return null;
|
|
126
|
+
let depth = 0;
|
|
127
|
+
for (let k = open; k < code.length; k += 1) {
|
|
128
|
+
if (code[k] === '{')
|
|
129
|
+
depth += 1;
|
|
130
|
+
else if (code[k] === '}') {
|
|
131
|
+
depth -= 1;
|
|
132
|
+
if (depth === 0)
|
|
133
|
+
return code.slice(open + 1, k);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
/** The Studio template's untouched callbacks: a callback left like this does nothing, so it isn't subscribed. */
|
|
139
|
+
const STUB_BODIES = new Set(['check_call(&ctx,&args)?;Ok(HookReturn::default())', 'check_call(&ctx,&args)']);
|
|
140
|
+
const CALLBACK_FLAGS = [
|
|
141
|
+
['before_transfer', HOOK_FLAG_BITS.BEFORE_TRANSFER],
|
|
142
|
+
['after_transfer', HOOK_FLAG_BITS.AFTER_TRANSFER],
|
|
143
|
+
['before_mint', HOOK_FLAG_BITS.BEFORE_MINT],
|
|
144
|
+
['after_mint', HOOK_FLAG_BITS.AFTER_MINT],
|
|
145
|
+
['before_burn', HOOK_FLAG_BITS.BEFORE_BURN],
|
|
146
|
+
['after_burn', HOOK_FLAG_BITS.AFTER_BURN],
|
|
147
|
+
];
|
|
148
|
+
/**
|
|
149
|
+
* The token hook flags a Studio hook needs, read from its own source (never chosen by hand): each
|
|
150
|
+
* callback whose body the author changed from the template's stub; cuts (TRANSFER_RETURNS_DELTA)
|
|
151
|
+
* when before_transfer runs and the code builds a `Delta` or fills `deltas`; hook data
|
|
152
|
+
* (WRITES_HOOK_DATA) when the code sets a holding's `source_hook_data`/`destination_hook_data`
|
|
153
|
+
* (reading `args.…_hook_data` needs no flag). It leans to including a bit: a flag the code doesn't
|
|
154
|
+
* use is harmless, a missing one makes the token program refuse the hook's answer. 0: the hook does
|
|
155
|
+
* nothing yet.
|
|
156
|
+
*/
|
|
157
|
+
export function detectHookFlags(files) {
|
|
158
|
+
const code = files.filter((f) => f.path.endsWith('.rs')).map((f) => codeOnly(f.content)).join('\n');
|
|
159
|
+
let flags = 0;
|
|
160
|
+
for (const [name, bit] of CALLBACK_FLAGS) {
|
|
161
|
+
const body = bodyOf(code, name);
|
|
162
|
+
if (body !== null && !STUB_BODIES.has(body.replace(/\s+/g, '')))
|
|
163
|
+
flags |= bit;
|
|
164
|
+
}
|
|
165
|
+
if (flags & HOOK_FLAG_BITS.BEFORE_TRANSFER && (/\bDelta\s*\{/.test(code) || /\bdeltas\s*:/.test(code) || /\.deltas\b/.test(code)))
|
|
166
|
+
flags |= HOOK_FLAG_BITS.TRANSFER_RETURNS_DELTA;
|
|
167
|
+
if (flags && /(?<!args\.)\b(source|destination)_hook_data\b/.test(code))
|
|
168
|
+
flags |= HOOK_FLAG_BITS.WRITES_HOOK_DATA;
|
|
169
|
+
return flags;
|
|
170
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bordrless/shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|