@bordrless/shared 0.4.0 → 0.6.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 +442 -2
- package/dist/api.js +4 -1
- package/dist/policy.d.ts +39 -1
- package/dist/policy.js +79 -0
- package/dist/programs.d.ts +44 -1
- package/dist/programs.js +52 -1
- 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
|
}
|
|
@@ -244,6 +301,8 @@ export interface LaunchSummary {
|
|
|
244
301
|
protocolModel: ProtocolModel;
|
|
245
302
|
/** Under the share model, Bordrless's share of the cuts in basis points (2,500 = a quarter); null under the flat model. */
|
|
246
303
|
protocolShareBps: number | null;
|
|
304
|
+
/** The pool's LP fee (the sniper fee included) goes to Bordrless in SOL: a launch pool created at or after the DEX upgrade of 2026-10-08 (`LP_FEE_TO_PROTOCOL_FROM_SLOT`). False on one created before it, whose LP fee stays in the pool. */
|
|
305
|
+
lpFeeToProtocol: boolean;
|
|
247
306
|
/** Total fee of a buy by the §7.3 formula, outside the sniper window. */
|
|
248
307
|
buyFeeBps: number;
|
|
249
308
|
/** Total fee of a sell by the §7.3 formula, outside the sniper window. */
|
|
@@ -279,6 +338,8 @@ export interface LaunchDetail extends LaunchSummary {
|
|
|
279
338
|
ruleStats: RuleStats;
|
|
280
339
|
/** The bridged-SOL holding holder rewards are paid from; null without holder rewards. */
|
|
281
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;
|
|
282
343
|
}
|
|
283
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. */
|
|
284
345
|
export type LaunchSort = 'new' | 'mcap' | 'volume' | 'trending' | 'graduated' | 'rewards24h' | 'fees';
|
|
@@ -447,7 +508,8 @@ export interface PreparedTx {
|
|
|
447
508
|
stage: number;
|
|
448
509
|
label: string;
|
|
449
510
|
/** Keys the browser holds that must also sign (a launch's fresh mint). */
|
|
450
|
-
|
|
511
|
+
/** Keys the browser holds that sign after the wallet: a launch's fresh mint, a new launch config. */
|
|
512
|
+
extraSigners: ('mint' | 'config')[];
|
|
451
513
|
}
|
|
452
514
|
export interface SwapPrepareResponse {
|
|
453
515
|
transactions: PreparedTx[];
|
|
@@ -496,6 +558,94 @@ export interface LaunchPrepareRequest {
|
|
|
496
558
|
* (what the site showed is what launches), and a custom hook's accounts are resolved for `mint`.
|
|
497
559
|
*/
|
|
498
560
|
config?: Address;
|
|
561
|
+
/**
|
|
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.
|
|
570
|
+
*/
|
|
571
|
+
companion?: CompanionRequest;
|
|
572
|
+
}
|
|
573
|
+
/** A launch's companion as the launch request names it: a split of the creator fee, or one of the preset splits by name. */
|
|
574
|
+
export type CompanionRequest = {
|
|
575
|
+
split: CompanionSplitBps;
|
|
576
|
+
vestDays?: number;
|
|
577
|
+
} | {
|
|
578
|
+
template: CompanionTemplate;
|
|
579
|
+
vestDays?: number;
|
|
580
|
+
};
|
|
581
|
+
/** The companion templates (`COMPANION_SPLITS`; `COMPANION_TEMPLATES` in the SDK). */
|
|
582
|
+
export type CompanionTemplate = 'buysItself' | 'rugProofDev' | 'buybackAndReward';
|
|
583
|
+
/** What a companion does with every creator fee it claims, in basis points summing to 10,000. */
|
|
584
|
+
export interface CompanionSplitBps {
|
|
585
|
+
/** Bought back on the token's own pool and burned. */
|
|
586
|
+
buybackBps: number;
|
|
587
|
+
/** Streamed to holders through the kit (the launch has holder rewards). */
|
|
588
|
+
holdersBps: number;
|
|
589
|
+
/** Paid to the launcher (the beneficiary), in SOL. */
|
|
590
|
+
beneficiaryBps: number;
|
|
591
|
+
}
|
|
592
|
+
/** A launch's companion as its token page shows it (`LaunchDetail.companion`): lamports and token base units as strings. */
|
|
593
|
+
export interface CompanionSummary {
|
|
594
|
+
/** The companion's account, `PDA(["companion", mint])` under the companion program. */
|
|
595
|
+
address: Address;
|
|
596
|
+
/** The wallet that launched it: paid its part of the fees, and the dev bag as it vests. */
|
|
597
|
+
beneficiary: Address;
|
|
598
|
+
/** The template whose split this is; null for another split. */
|
|
599
|
+
template: CompanionTemplate | null;
|
|
600
|
+
split: CompanionSplitBps;
|
|
601
|
+
/** Creator fees the companion has claimed, all time. */
|
|
602
|
+
claimed: Amount;
|
|
603
|
+
/** Spent buying the token back on its pool. */
|
|
604
|
+
boughtBack: Amount;
|
|
605
|
+
/** Tokens those buybacks burned. */
|
|
606
|
+
burned: Amount;
|
|
607
|
+
/** Shared with holders through the kit. */
|
|
608
|
+
shared: Amount;
|
|
609
|
+
/** Paid to the launcher. */
|
|
610
|
+
paidToLauncher: Amount;
|
|
611
|
+
/** Claimed and waiting for its step: the next buyback, the next share, the launcher's next payout. */
|
|
612
|
+
pending: {
|
|
613
|
+
buyback: Amount;
|
|
614
|
+
holders: Amount;
|
|
615
|
+
launcher: Amount;
|
|
616
|
+
};
|
|
617
|
+
/** 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. */
|
|
618
|
+
devBag: {
|
|
619
|
+
total: Amount;
|
|
620
|
+
vested: Amount;
|
|
621
|
+
released: Amount;
|
|
622
|
+
vestEndsAt: number;
|
|
623
|
+
} | null;
|
|
624
|
+
/** When the next buyback may run while SOL waits for one (it also waits while the price runs above its reference); null otherwise. */
|
|
625
|
+
nextBuybackAt: number | null;
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* `GET /v1/companion/:mint`: a mint's companion as a launch that may not have finished needs it.
|
|
629
|
+
* `none`: no companion was set up for the mint; `set-up`: set up, its launch not made (its funding
|
|
630
|
+
* can be taken back, `refundable`); `launched`.
|
|
631
|
+
*/
|
|
632
|
+
export interface CompanionStatus {
|
|
633
|
+
mint: Address;
|
|
634
|
+
state: 'none' | 'set-up' | 'launched';
|
|
635
|
+
/** The wallet it pays; null with none. */
|
|
636
|
+
beneficiary: Address | null;
|
|
637
|
+
/** Lamports a refund returns now; null unless `set-up`. */
|
|
638
|
+
refundable: Amount | null;
|
|
639
|
+
}
|
|
640
|
+
/** `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. */
|
|
641
|
+
export interface CompanionRefundRequest {
|
|
642
|
+
wallet: Address;
|
|
643
|
+
mint: Address;
|
|
644
|
+
}
|
|
645
|
+
export interface CompanionRefundResponse {
|
|
646
|
+
transaction: PreparedTx;
|
|
647
|
+
/** Lamports it returns. */
|
|
648
|
+
refundable: Amount;
|
|
499
649
|
}
|
|
500
650
|
export interface LaunchPrepareResponse {
|
|
501
651
|
intentId: string;
|
|
@@ -539,6 +689,24 @@ export interface RewardsPrepareRequest {
|
|
|
539
689
|
/** Unwrap the bridged SOL to SOL and close the temporary holding. */
|
|
540
690
|
unwrap: boolean;
|
|
541
691
|
}
|
|
692
|
+
/**
|
|
693
|
+
* Send `amount` of `mint` from the owner's holding to `to`'s (made on the way when missing, the
|
|
694
|
+
* sender paying its rent). Wallets cannot build this themselves: the token program's transfer takes
|
|
695
|
+
* the mint's hook accounts, which the backend resolves. A holding under the creator lock or the
|
|
696
|
+
* early-buyer lock is refused with the lock (code `locked`), as the program would refuse it.
|
|
697
|
+
*/
|
|
698
|
+
export interface TransferPrepareRequest {
|
|
699
|
+
owner: Address;
|
|
700
|
+
mint: Address;
|
|
701
|
+
to: Address;
|
|
702
|
+
/** Base units of the token. */
|
|
703
|
+
amount: Amount;
|
|
704
|
+
}
|
|
705
|
+
export interface TransferPrepareResponse {
|
|
706
|
+
transactions: PreparedTx[];
|
|
707
|
+
/** Lamports the sender pays for the recipient's holding; 0 when it already exists. */
|
|
708
|
+
rentLamports: Amount;
|
|
709
|
+
}
|
|
542
710
|
/** Claim the creator fees of launches the owner created: up to about 4 mints per transaction, paid as SOL. */
|
|
543
711
|
export interface CreatorPrepareRequest {
|
|
544
712
|
owner: Address;
|
|
@@ -585,6 +753,13 @@ export interface BridgeAsset {
|
|
|
585
753
|
* opened. Absent from backends that predate it.
|
|
586
754
|
*/
|
|
587
755
|
pool?: BridgePool | null;
|
|
756
|
+
/**
|
|
757
|
+
* The platform token on the overview only: base units bought back from protocol revenue and held
|
|
758
|
+
* by the treasury (docs page /docs/tokenomics): its holding of the bridged version, plus the SPL
|
|
759
|
+
* version it holds that is still to be bridged. Read live from the chain; null when it could not
|
|
760
|
+
* be read. Absent from backends that predate it and from any other asset.
|
|
761
|
+
*/
|
|
762
|
+
boughtBack?: Amount | null;
|
|
588
763
|
}
|
|
589
764
|
/** A bridged token's pool on the DEX (`BridgeAsset.pool`): where it is, since when, and its last price. */
|
|
590
765
|
export interface BridgePool {
|
|
@@ -600,6 +775,8 @@ export interface BridgePool {
|
|
|
600
775
|
lpFeeBps: number;
|
|
601
776
|
/** The flat protocol rate of the quote on this pool (ordinary pools; 0 under the share model). */
|
|
602
777
|
protocolFeeBps: number;
|
|
778
|
+
/** The LP fee goes to Bordrless in SOL (a launch pool since the upgrade of 2026-10-08, `LP_FEE_TO_PROTOCOL_FROM_SLOT`); false when it stays in the pool, as an ordinary pool's always does. */
|
|
779
|
+
lpFeeToProtocol: boolean;
|
|
603
780
|
baseReserve: Amount;
|
|
604
781
|
quoteReserve: Amount;
|
|
605
782
|
volume24hUsd: number | null;
|
|
@@ -639,6 +816,8 @@ export interface PoolSummary {
|
|
|
639
816
|
protocolModel?: ProtocolModel;
|
|
640
817
|
/** Under the share model, Bordrless's share of what the hooks cut, in basis points; null (or absent) under the flat model. */
|
|
641
818
|
protocolShareBps?: number | null;
|
|
819
|
+
/** The LP fee goes to Bordrless in SOL (a launch pool since the upgrade of 2026-10-08); false when it stays in the pool. Absent from backends that predate it. */
|
|
820
|
+
lpFeeToProtocol?: boolean;
|
|
642
821
|
hookProgram: Address | null;
|
|
643
822
|
/** The base token's creator-written hook (§5.8), for a launch pool whose token has one; null or absent otherwise. */
|
|
644
823
|
customHook?: CustomHookInfo | null;
|
|
@@ -681,3 +860,264 @@ export interface ApiErrorBody {
|
|
|
681
860
|
error: string;
|
|
682
861
|
code?: string;
|
|
683
862
|
}
|
|
863
|
+
/** Which of the standard's programs emitted an event: the five of Bordrless, or any hook program (`hook`). */
|
|
864
|
+
export type ExplorerProgram = 'token' | 'swap' | 'bridge' | 'launch' | 'kit' | 'hook';
|
|
865
|
+
/** What an address turned out to be, in the order the backend looks: a mint, a pool, a bridge wrapper, a holding, a program, a wallet, a launch config, or nothing it knows. */
|
|
866
|
+
export type ExplorerKind = 'mint' | 'pool' | 'wrapper' | 'holding' | 'program' | 'wallet' | 'config' | 'unknown';
|
|
867
|
+
export interface ExplorerStats {
|
|
868
|
+
/** Mints of the standard the indexer has seen. */
|
|
869
|
+
mints: number;
|
|
870
|
+
/** Holdings with a balance. */
|
|
871
|
+
holdings: number;
|
|
872
|
+
/** Transactions indexed (distinct signatures), all time and in the last 24 hours. */
|
|
873
|
+
transactions: number;
|
|
874
|
+
transactions24h: number;
|
|
875
|
+
/** The newest slot indexed; null before the first event. */
|
|
876
|
+
newestSlot: number | null;
|
|
877
|
+
}
|
|
878
|
+
/** A token an event's amounts are in. */
|
|
879
|
+
export interface ExplorerToken {
|
|
880
|
+
mint: Address;
|
|
881
|
+
symbol: string;
|
|
882
|
+
decimals: number;
|
|
883
|
+
}
|
|
884
|
+
/** One decoded event of `chain_events`, as the explorer shows it. */
|
|
885
|
+
export interface ExplorerEvent {
|
|
886
|
+
signature: string;
|
|
887
|
+
ordinal: number;
|
|
888
|
+
slot: number;
|
|
889
|
+
blockTime: number | null;
|
|
890
|
+
/** The program id that emitted it. */
|
|
891
|
+
program: Address;
|
|
892
|
+
/** The IDL's event name in PascalCase (`Transferred`, `Swapped`, `KitInstalled`). */
|
|
893
|
+
name: string;
|
|
894
|
+
/** The account the event is mostly about (the mint, the pool, the holding, ...); null when it has none. */
|
|
895
|
+
subject: Address | null;
|
|
896
|
+
/** The decoded fields: keys as the SDK's coder gives them (camelCase), pubkeys as base58, integers as decimal strings. */
|
|
897
|
+
payload: Record<string, unknown>;
|
|
898
|
+
/** The tokens its amounts are in, where the indexer knows them: the event's own mint (or the pool's base), and the pool's quote. */
|
|
899
|
+
base: ExplorerToken | null;
|
|
900
|
+
quote: ExplorerToken | null;
|
|
901
|
+
}
|
|
902
|
+
export interface ExplorerEvents {
|
|
903
|
+
events: ExplorerEvent[];
|
|
904
|
+
/** The cursor for the page after this one (`before=`); null on the last page. */
|
|
905
|
+
next: string | null;
|
|
906
|
+
}
|
|
907
|
+
/** `GET /v1/explorer/tx/:signature`: a transaction the indexer has, with every event it decoded, in order. */
|
|
908
|
+
export interface ExplorerTx {
|
|
909
|
+
signature: string;
|
|
910
|
+
slot: number;
|
|
911
|
+
blockTime: number | null;
|
|
912
|
+
/** The program ids its events came from, first seen first. */
|
|
913
|
+
programs: Address[];
|
|
914
|
+
events: ExplorerEvent[];
|
|
915
|
+
}
|
|
916
|
+
export interface ExplorerMint {
|
|
917
|
+
mint: Address;
|
|
918
|
+
name: string;
|
|
919
|
+
symbol: string;
|
|
920
|
+
image: string | null;
|
|
921
|
+
decimals: number;
|
|
922
|
+
supply: Amount;
|
|
923
|
+
maxSupply: Amount;
|
|
924
|
+
creator: Address;
|
|
925
|
+
createdAt: number;
|
|
926
|
+
kind: 'launch' | 'bridged' | 'lp' | 'other';
|
|
927
|
+
mintAuthority: Address | null;
|
|
928
|
+
freezeAuthority: Address | null;
|
|
929
|
+
hookAuthority: Address | null;
|
|
930
|
+
metadataAuthority: Address | null;
|
|
931
|
+
hookProgram: Address | null;
|
|
932
|
+
hookFlags: number;
|
|
933
|
+
/** Holdings with a balance. */
|
|
934
|
+
holders: number;
|
|
935
|
+
}
|
|
936
|
+
export interface ExplorerHolder {
|
|
937
|
+
owner: Address;
|
|
938
|
+
holding: Address;
|
|
939
|
+
amount: Amount;
|
|
940
|
+
/** Of the supply, 0 to 1. */
|
|
941
|
+
share: number;
|
|
942
|
+
}
|
|
943
|
+
/** A pool as the explorer lists it on a mint's page, or shows it on its own. */
|
|
944
|
+
export interface ExplorerPool {
|
|
945
|
+
pool: Address;
|
|
946
|
+
baseMint: Address;
|
|
947
|
+
quoteMint: Address;
|
|
948
|
+
baseSymbol: string;
|
|
949
|
+
quoteSymbol: string;
|
|
950
|
+
baseDecimals: number;
|
|
951
|
+
quoteDecimals: number;
|
|
952
|
+
baseReserve: Amount;
|
|
953
|
+
quoteReserve: Amount;
|
|
954
|
+
lpMint: Address;
|
|
955
|
+
lpSupply: Amount;
|
|
956
|
+
lpFeeBps: number;
|
|
957
|
+
protocolFeeBps: number;
|
|
958
|
+
protocolModel: ProtocolModel;
|
|
959
|
+
protocolShareBps: number | null;
|
|
960
|
+
/** The LP fee goes to Bordrless in SOL (a launch pool since the upgrade of 2026-10-08); false when it stays in the pool. */
|
|
961
|
+
lpFeeToProtocol: boolean;
|
|
962
|
+
hookProgram: Address | null;
|
|
963
|
+
hookFlags: number;
|
|
964
|
+
curve: boolean;
|
|
965
|
+
creator: Address;
|
|
966
|
+
createdAt: number;
|
|
967
|
+
swapCount: number;
|
|
968
|
+
volume24hUsd: number | null;
|
|
969
|
+
/** The launch this is the pool of, if any. */
|
|
970
|
+
launchMint: Address | null;
|
|
971
|
+
}
|
|
972
|
+
export interface ExplorerHolding {
|
|
973
|
+
holding: Address;
|
|
974
|
+
mint: Address;
|
|
975
|
+
owner: Address;
|
|
976
|
+
amount: Amount;
|
|
977
|
+
symbol: string;
|
|
978
|
+
name: string;
|
|
979
|
+
image: string | null;
|
|
980
|
+
decimals: number;
|
|
981
|
+
/** Dollars, where the token has a pool against bridged SOL and SOL has a price. */
|
|
982
|
+
valueUsd: number | null;
|
|
983
|
+
}
|
|
984
|
+
export interface ExplorerProgramInfo extends ProgramInfo {
|
|
985
|
+
/** The program's name on the standard ("Token", "DEX", "Half-Life"); a hook the site does not know is "Hook". */
|
|
986
|
+
name: string;
|
|
987
|
+
/** Events indexed, by event name, most first. */
|
|
988
|
+
counts: {
|
|
989
|
+
name: string;
|
|
990
|
+
count: number;
|
|
991
|
+
}[];
|
|
992
|
+
}
|
|
993
|
+
export interface ExplorerWrapper {
|
|
994
|
+
wrapper: Address;
|
|
995
|
+
underlyingMint: Address;
|
|
996
|
+
underlyingProgram: Address;
|
|
997
|
+
wrappedMint: Address;
|
|
998
|
+
vault: Address;
|
|
999
|
+
decimals: number;
|
|
1000
|
+
native: boolean;
|
|
1001
|
+
totalWrapped: Amount;
|
|
1002
|
+
name: string;
|
|
1003
|
+
symbol: string;
|
|
1004
|
+
image: string | null;
|
|
1005
|
+
registrar: Address;
|
|
1006
|
+
registeredAt: number;
|
|
1007
|
+
}
|
|
1008
|
+
/** A `LaunchConfig` (docs/hooks-v2.md §5.7) as its `LaunchConfigCreated` event told it, and the launches made from it. */
|
|
1009
|
+
export interface ExplorerConfig {
|
|
1010
|
+
config: Address;
|
|
1011
|
+
creator: Address | null;
|
|
1012
|
+
label: string | null;
|
|
1013
|
+
creatorFeeBps: number | null;
|
|
1014
|
+
customHook: Address | null;
|
|
1015
|
+
customHookFlags: number | null;
|
|
1016
|
+
rules: Record<string, unknown> | null;
|
|
1017
|
+
launches: {
|
|
1018
|
+
mint: Address;
|
|
1019
|
+
name: string;
|
|
1020
|
+
symbol: string;
|
|
1021
|
+
image: string | null;
|
|
1022
|
+
}[];
|
|
1023
|
+
}
|
|
1024
|
+
/** A short reference to a token, for lists. */
|
|
1025
|
+
export interface ExplorerTokenRef {
|
|
1026
|
+
mint: Address;
|
|
1027
|
+
name: string;
|
|
1028
|
+
symbol: string;
|
|
1029
|
+
image: string | null;
|
|
1030
|
+
}
|
|
1031
|
+
/** `GET /v1/explorer/address/:address`: what lives at an address, by kind. Every kind carries the newest events touching it. */
|
|
1032
|
+
export type ExplorerAddress = {
|
|
1033
|
+
address: Address;
|
|
1034
|
+
events: ExplorerEvent[];
|
|
1035
|
+
} & ({
|
|
1036
|
+
kind: 'mint';
|
|
1037
|
+
mint: ExplorerMint;
|
|
1038
|
+
launch: {
|
|
1039
|
+
status: LaunchStatus;
|
|
1040
|
+
} | null;
|
|
1041
|
+
wrapper: {
|
|
1042
|
+
underlyingMint: Address;
|
|
1043
|
+
native: boolean;
|
|
1044
|
+
} | null;
|
|
1045
|
+
pools: ExplorerPool[];
|
|
1046
|
+
holders: ExplorerHolder[];
|
|
1047
|
+
} | {
|
|
1048
|
+
kind: 'pool';
|
|
1049
|
+
pool: ExplorerPool;
|
|
1050
|
+
trades: Trade[];
|
|
1051
|
+
} | {
|
|
1052
|
+
kind: 'wrapper';
|
|
1053
|
+
wrapper: ExplorerWrapper;
|
|
1054
|
+
} | {
|
|
1055
|
+
kind: 'holding';
|
|
1056
|
+
holding: ExplorerHolding;
|
|
1057
|
+
} | {
|
|
1058
|
+
kind: 'program';
|
|
1059
|
+
program: ExplorerProgramInfo;
|
|
1060
|
+
} | {
|
|
1061
|
+
kind: 'wallet';
|
|
1062
|
+
holdings: ExplorerHolding[];
|
|
1063
|
+
totalUsd: number | null;
|
|
1064
|
+
created: ExplorerTokenRef[];
|
|
1065
|
+
} | {
|
|
1066
|
+
kind: 'config';
|
|
1067
|
+
config: ExplorerConfig;
|
|
1068
|
+
} | {
|
|
1069
|
+
kind: 'unknown';
|
|
1070
|
+
});
|
|
1071
|
+
/** One answer to a search: an address resolved to its kind, a transaction, or a mint whose name or ticker matched. */
|
|
1072
|
+
export interface ExplorerHit {
|
|
1073
|
+
kind: ExplorerKind | 'tx';
|
|
1074
|
+
address: Address;
|
|
1075
|
+
name: string | null;
|
|
1076
|
+
symbol: string | null;
|
|
1077
|
+
image: string | null;
|
|
1078
|
+
}
|
|
1079
|
+
export interface ExplorerSearch {
|
|
1080
|
+
query: string;
|
|
1081
|
+
hits: ExplorerHit[];
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* `POST /v1/config/prepare`: a `LaunchConfig` made from the launch page (Build your own, §5.7), for a
|
|
1085
|
+
* hook program of the creator's own. `config` is the address of a keypair the browser made; it signs
|
|
1086
|
+
* after the wallet. The kit's rules can't go with a hook of one's own, so a config carries burn and a
|
|
1087
|
+
* creator fee only.
|
|
1088
|
+
*/
|
|
1089
|
+
export interface ConfigPrepareRequest {
|
|
1090
|
+
creator: Address;
|
|
1091
|
+
config: Address;
|
|
1092
|
+
customHook: Address;
|
|
1093
|
+
/** `TOKEN_HOOK_FLAGS`: at least one callback, no unknown bit. */
|
|
1094
|
+
customHookFlags: number;
|
|
1095
|
+
burnBuyBps: number;
|
|
1096
|
+
burnSellBps: number;
|
|
1097
|
+
creatorFeeBps: number;
|
|
1098
|
+
/** At most 32 bytes. */
|
|
1099
|
+
label: string;
|
|
1100
|
+
}
|
|
1101
|
+
export interface ConfigPrepareResponse {
|
|
1102
|
+
config: Address;
|
|
1103
|
+
transactions: PreparedTx[];
|
|
1104
|
+
}
|
|
1105
|
+
/** `GET /v1/hooks/:program?mint=`: a hook program as the launch page checks it, and whether it is prepared for the mint. */
|
|
1106
|
+
export interface HookInspection {
|
|
1107
|
+
program: Address;
|
|
1108
|
+
/** Deployed as a program on this cluster. */
|
|
1109
|
+
executable: boolean;
|
|
1110
|
+
/** None of Bordrless's own programs. */
|
|
1111
|
+
ownProgram: boolean;
|
|
1112
|
+
upgradeAuthority: Address | null;
|
|
1113
|
+
upgradeable: boolean | null;
|
|
1114
|
+
/** With a mint: the registry it must write for that mint, `["bordrless-hook-accounts", mint]` under the program. */
|
|
1115
|
+
registry: Address | null;
|
|
1116
|
+
/** With a mint: whether that registry exists and decodes. */
|
|
1117
|
+
registryReady: boolean | null;
|
|
1118
|
+
/** The registry's extra accounts, once it exists; a launch fits at most `maxExtras`. */
|
|
1119
|
+
extras: number | null;
|
|
1120
|
+
maxExtras: number;
|
|
1121
|
+
/** Why it can't be launched with yet, one sentence each. */
|
|
1122
|
+
problems: string[];
|
|
1123
|
+
}
|
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, LaunchRules, LaunchRulesInput, 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,44 @@ 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
|
+
/** Whether a split is the whole fee to the launcher (no companion needed). */
|
|
288
|
+
export declare const isLauncherSplit: (s: CompanionSplitBps) => boolean;
|
|
289
|
+
/** A split with a share for holders needs holder rewards on. */
|
|
290
|
+
export declare const splitPaysHolders: (s: CompanionSplitBps) => boolean;
|
|
291
|
+
/** Whether a split takes a first buy: every fee bought back and burned means no dev at all. */
|
|
292
|
+
export declare const splitTakesFirstBuy: (s: CompanionSplitBps) => boolean;
|
|
293
|
+
/** A template that pays holders needs holder rewards on. */
|
|
294
|
+
export declare const companionPaysHolders: (template: CompanionTemplate) => boolean;
|
|
295
|
+
/** Whether a template takes a first buy: the token that buys itself has no dev at all. */
|
|
296
|
+
export declare const companionTakesFirstBuy: (template: CompanionTemplate) => boolean;
|
|
297
|
+
/**
|
|
298
|
+
* The split a launch request's `companion` names: its own `split` (each share a whole number of
|
|
299
|
+
* basis points, 0 to 10,000; their sum is checked by `companionSplitProblem`), or a template's;
|
|
300
|
+
* `null` when it names neither (a malformed request).
|
|
301
|
+
*/
|
|
302
|
+
export declare function companionRequestSplit(raw: unknown): CompanionSplitBps | null;
|
|
303
|
+
/**
|
|
304
|
+
* Why a companion with this split can't run these rules, in the words the launch form and the
|
|
305
|
+
* backend use (`companion_rules`); null when it can. The program's own checks (`BadSplit`,
|
|
306
|
+
* `CreatorLockUnsupported`, `HolderRewardsOff`), and a creator fee for it to run.
|
|
307
|
+
*/
|
|
308
|
+
export declare function companionSplitProblem(split: CompanionSplitBps, rules: Pick<LaunchRulesInput, 'holderFeeBuyBps' | 'holderFeeSellBps' | 'creatorLockDays'>, creatorFeeBps: number): string | null;
|
|
309
|
+
/** The template whose split this is, or null for any other split. */
|
|
310
|
+
export declare function companionTemplateOf(split: CompanionSplitBps): CompanionTemplate | null;
|
|
273
311
|
/** Kit module bits (§4.2). */
|
|
274
312
|
export declare const KIT_MODULES: {
|
|
275
313
|
readonly HOLDER_REWARDS: 1;
|
package/dist/policy.js
CHANGED
|
@@ -346,6 +346,85 @@ 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
|
+
/** Whether a split is the whole fee to the launcher (no companion needed). */
|
|
372
|
+
export const isLauncherSplit = (s) => s.buybackBps === 0 && s.holdersBps === 0 && s.beneficiaryBps === 10_000;
|
|
373
|
+
/** A split with a share for holders needs holder rewards on. */
|
|
374
|
+
export const splitPaysHolders = (s) => s.holdersBps > 0;
|
|
375
|
+
/** Whether a split takes a first buy: every fee bought back and burned means no dev at all. */
|
|
376
|
+
export const splitTakesFirstBuy = (s) => s.buybackBps < 10_000;
|
|
377
|
+
/** A template that pays holders needs holder rewards on. */
|
|
378
|
+
export const companionPaysHolders = (template) => splitPaysHolders(COMPANION_SPLITS[template]);
|
|
379
|
+
/** Whether a template takes a first buy: the token that buys itself has no dev at all. */
|
|
380
|
+
export const companionTakesFirstBuy = (template) => splitTakesFirstBuy(COMPANION_SPLITS[template]);
|
|
381
|
+
const isShare = (v) => typeof v === 'number' && Number.isInteger(v) && v >= 0 && v <= 10_000;
|
|
382
|
+
/**
|
|
383
|
+
* The split a launch request's `companion` names: its own `split` (each share a whole number of
|
|
384
|
+
* basis points, 0 to 10,000; their sum is checked by `companionSplitProblem`), or a template's;
|
|
385
|
+
* `null` when it names neither (a malformed request).
|
|
386
|
+
*/
|
|
387
|
+
export function companionRequestSplit(raw) {
|
|
388
|
+
if (typeof raw !== 'object' || raw === null)
|
|
389
|
+
return null;
|
|
390
|
+
const r = raw;
|
|
391
|
+
if (r.split !== undefined) {
|
|
392
|
+
if (typeof r.split !== 'object' || r.split === null)
|
|
393
|
+
return null;
|
|
394
|
+
const s = r.split;
|
|
395
|
+
if (!isShare(s.buybackBps) || !isShare(s.holdersBps) || !isShare(s.beneficiaryBps))
|
|
396
|
+
return null;
|
|
397
|
+
return { buybackBps: s.buybackBps, holdersBps: s.holdersBps, beneficiaryBps: s.beneficiaryBps };
|
|
398
|
+
}
|
|
399
|
+
if (typeof r.template === 'string' && Object.hasOwn(COMPANION_SPLITS, r.template))
|
|
400
|
+
return { ...COMPANION_SPLITS[r.template] };
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Why a companion with this split can't run these rules, in the words the launch form and the
|
|
405
|
+
* backend use (`companion_rules`); null when it can. The program's own checks (`BadSplit`,
|
|
406
|
+
* `CreatorLockUnsupported`, `HolderRewardsOff`), and a creator fee for it to run.
|
|
407
|
+
*/
|
|
408
|
+
export function companionSplitProblem(split, rules, creatorFeeBps) {
|
|
409
|
+
if (![split.buybackBps, split.holdersBps, split.beneficiaryBps].every(isShare))
|
|
410
|
+
return 'Each share of the creator fee is a whole number of basis points, 0 to 10,000.';
|
|
411
|
+
if (split.buybackBps + split.holdersBps + split.beneficiaryBps !== 10_000)
|
|
412
|
+
return 'The creator fee’s shares must add up to 100% (10,000 basis points).';
|
|
413
|
+
if (rules.creatorLockDays > 0)
|
|
414
|
+
return 'A creator fee run by a program can’t come with the creator wallet lock: the program vests your first buy instead.';
|
|
415
|
+
if (creatorFeeBps === 0)
|
|
416
|
+
return 'There is no creator fee for a program to run: set one.';
|
|
417
|
+
if (splitPaysHolders(split) && !rewardsOn(rules))
|
|
418
|
+
return 'A share of the creator fee for holders needs holder rewards on.';
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
/** The template whose split this is, or null for any other split. */
|
|
422
|
+
export function companionTemplateOf(split) {
|
|
423
|
+
return COMPANION_TEMPLATE_LIST.find((t) => {
|
|
424
|
+
const s = COMPANION_SPLITS[t];
|
|
425
|
+
return s.buybackBps === split.buybackBps && s.holdersBps === split.holdersBps && s.beneficiaryBps === split.beneficiaryBps;
|
|
426
|
+
}) ?? null;
|
|
427
|
+
}
|
|
349
428
|
/** Kit module bits (§4.2). */
|
|
350
429
|
export const KIT_MODULES = { HOLDER_REWARDS: 1, MAX_WALLET: 2, CREATOR_LOCK: 4, EARLY_LOCK: 8 };
|
|
351
430
|
/** 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
|
@@ -14,6 +14,27 @@ export declare const PROGRAM_IDS: {
|
|
|
14
14
|
readonly kit: "14RJQXPdJfkehit6ezktjd3xujamf8nVSKw2shKamaEH";
|
|
15
15
|
/** Half-Life (programs/half_life): an exit fee that halves every six hours a token is held, burned. */
|
|
16
16
|
readonly halfLife: "53SpmtkdPWQ63mWoDeXk8P9tuwiT4ed2Wx4fwfy5NSF8";
|
|
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
|
+
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";
|
|
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";
|
|
17
38
|
};
|
|
18
39
|
/**
|
|
19
40
|
* Half-Life, Bordrless's own token hook (programs/half_life/README.md): the `LaunchConfig` the
|
|
@@ -39,6 +60,26 @@ export declare const HALF_LIFE: {
|
|
|
39
60
|
* (20% at 0, halved every six hours, linear within each, 0 from 48 h).
|
|
40
61
|
*/
|
|
41
62
|
export declare function halfLifeFeePpm(ageSecs: number): number;
|
|
63
|
+
/**
|
|
64
|
+
* The DEX upgrade of 2026-10-08 (docs/hooks-v2.md §3.1, "The LP fee of a launch pool is
|
|
65
|
+
* Bordrless's"): from this slot on mainnet a launch pool's LP fee (the sniper fee included) is
|
|
66
|
+
* taken in SOL and paid to Bordrless with the protocol fee; a launch pool created before it keeps
|
|
67
|
+
* its LP fee in the pool, compounding, as its traders were told. The slot is the DEX program's
|
|
68
|
+
* last upgrade, read from its ProgramData account; the time is that slot's block time
|
|
69
|
+
* (`getBlockTime`, unix seconds), which the backend compares a pool's `created_at` against (the
|
|
70
|
+
* pools table keeps a timestamp, not a slot). Devnet and localnet were deployed after the rule:
|
|
71
|
+
* every launch pool there pays Bordrless.
|
|
72
|
+
*/
|
|
73
|
+
export declare const LP_FEE_TO_PROTOCOL_FROM_SLOT = 454439142;
|
|
74
|
+
export declare const LP_FEE_TO_PROTOCOL_FROM_TIME = 1791434238;
|
|
75
|
+
/**
|
|
76
|
+
* Whether a pool's LP fee goes to Bordrless (`LaunchSummary.lpFeeToProtocol`): every launch pool's
|
|
77
|
+
* (the share model), whenever it was created. The upgraded DEX keys the fee's destination on the
|
|
78
|
+
* pool's fee model alone, so a launch pool opened before the upgrade compounded its LP fee until
|
|
79
|
+
* that slot and pays Bordrless since. An ordinary pool's LP fee is its liquidity's. `createdAt`
|
|
80
|
+
* and `cluster` are kept for the record of when the rule began (the constants above).
|
|
81
|
+
*/
|
|
82
|
+
export declare function lpFeeToProtocol(shareModel: boolean, _createdAt: number, _cluster: 'mainnet' | 'devnet' | 'localnet'): boolean;
|
|
42
83
|
/** The wSOL mint, which stands for native SOL on the bridge. */
|
|
43
84
|
export declare const NATIVE_MINT = "So11111111111111111111111111111111111111112";
|
|
44
85
|
export declare const TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
@@ -89,9 +130,11 @@ export declare const FIXED_ADDRESSES: {
|
|
|
89
130
|
readonly kitEventAuthority: "9abhxVTuwck5Ux79act3e2Vkfem7Q4zBwATMctUvHvuE";
|
|
90
131
|
/** `["hook-authority"]` under the kit: it signs the token program's `write_hook_data` in `claim`. */
|
|
91
132
|
readonly kitHookAuthority: "2repKA1JgDkBo4AffscVee342dcBcH6c2yfpUTRrAiEN";
|
|
133
|
+
/** `["__event_authority"]` under the companion program. */
|
|
134
|
+
readonly companionEventAuthority: "7QuoYRuD9MHzX5EN7qqj62iwNPVqfy74RzmwJ524cgDt";
|
|
92
135
|
};
|
|
93
136
|
/**
|
|
94
|
-
* The protocol lookup table (docs/hooks-v2.md §6; programs-summary §2.7): the
|
|
137
|
+
* The protocol lookup table (docs/hooks-v2.md §6; programs-summary §2.7): the 22 fixed addresses
|
|
95
138
|
* no top-level instruction invokes, in the order the programs' tests load them and `pnpm admin
|
|
96
139
|
* init` must write them (a v0 message names table entries by index). Programs a transaction
|
|
97
140
|
* invokes at top level (the kit for claims and shares) are kept in the static keys by the v0
|
package/dist/programs.js
CHANGED
|
@@ -14,6 +14,27 @@ export const PROGRAM_IDS = {
|
|
|
14
14
|
kit: '14RJQXPdJfkehit6ezktjd3xujamf8nVSKw2shKamaEH',
|
|
15
15
|
/** Half-Life (programs/half_life): an exit fee that halves every six hours a token is held, burned. */
|
|
16
16
|
halfLife: '53SpmtkdPWQ63mWoDeXk8P9tuwiT4ed2Wx4fwfy5NSF8',
|
|
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
|
+
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',
|
|
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',
|
|
17
38
|
};
|
|
18
39
|
/**
|
|
19
40
|
* Half-Life, Bordrless's own token hook (programs/half_life/README.md): the `LaunchConfig` the
|
|
@@ -49,6 +70,28 @@ export function halfLifeFeePpm(ageSecs) {
|
|
|
49
70
|
const lo = Math.floor(HALF_LIFE.maxFeePpm / 2 ** (halvings + 1));
|
|
50
71
|
return hi - Math.floor(((hi - lo) * into) / HALF_LIFE.halfLifeSecs);
|
|
51
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The DEX upgrade of 2026-10-08 (docs/hooks-v2.md §3.1, "The LP fee of a launch pool is
|
|
75
|
+
* Bordrless's"): from this slot on mainnet a launch pool's LP fee (the sniper fee included) is
|
|
76
|
+
* taken in SOL and paid to Bordrless with the protocol fee; a launch pool created before it keeps
|
|
77
|
+
* its LP fee in the pool, compounding, as its traders were told. The slot is the DEX program's
|
|
78
|
+
* last upgrade, read from its ProgramData account; the time is that slot's block time
|
|
79
|
+
* (`getBlockTime`, unix seconds), which the backend compares a pool's `created_at` against (the
|
|
80
|
+
* pools table keeps a timestamp, not a slot). Devnet and localnet were deployed after the rule:
|
|
81
|
+
* every launch pool there pays Bordrless.
|
|
82
|
+
*/
|
|
83
|
+
export const LP_FEE_TO_PROTOCOL_FROM_SLOT = 454_439_142;
|
|
84
|
+
export const LP_FEE_TO_PROTOCOL_FROM_TIME = 1_791_434_238;
|
|
85
|
+
/**
|
|
86
|
+
* Whether a pool's LP fee goes to Bordrless (`LaunchSummary.lpFeeToProtocol`): every launch pool's
|
|
87
|
+
* (the share model), whenever it was created. The upgraded DEX keys the fee's destination on the
|
|
88
|
+
* pool's fee model alone, so a launch pool opened before the upgrade compounded its LP fee until
|
|
89
|
+
* that slot and pays Bordrless since. An ordinary pool's LP fee is its liquidity's. `createdAt`
|
|
90
|
+
* and `cluster` are kept for the record of when the rule began (the constants above).
|
|
91
|
+
*/
|
|
92
|
+
export function lpFeeToProtocol(shareModel, _createdAt, _cluster) {
|
|
93
|
+
return shareModel;
|
|
94
|
+
}
|
|
52
95
|
/** The wSOL mint, which stands for native SOL on the bridge. */
|
|
53
96
|
export const NATIVE_MINT = 'So11111111111111111111111111111111111111112';
|
|
54
97
|
export const TOKEN_PROGRAM_ID = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
|
|
@@ -99,9 +142,11 @@ export const FIXED_ADDRESSES = {
|
|
|
99
142
|
kitEventAuthority: '9abhxVTuwck5Ux79act3e2Vkfem7Q4zBwATMctUvHvuE',
|
|
100
143
|
/** `["hook-authority"]` under the kit: it signs the token program's `write_hook_data` in `claim`. */
|
|
101
144
|
kitHookAuthority: '2repKA1JgDkBo4AffscVee342dcBcH6c2yfpUTRrAiEN',
|
|
145
|
+
/** `["__event_authority"]` under the companion program. */
|
|
146
|
+
companionEventAuthority: '7QuoYRuD9MHzX5EN7qqj62iwNPVqfy74RzmwJ524cgDt',
|
|
102
147
|
};
|
|
103
148
|
/**
|
|
104
|
-
* The protocol lookup table (docs/hooks-v2.md §6; programs-summary §2.7): the
|
|
149
|
+
* The protocol lookup table (docs/hooks-v2.md §6; programs-summary §2.7): the 22 fixed addresses
|
|
105
150
|
* no top-level instruction invokes, in the order the programs' tests load them and `pnpm admin
|
|
106
151
|
* init` must write them (a v0 message names table entries by index). Programs a transaction
|
|
107
152
|
* invokes at top level (the kit for claims and shares) are kept in the static keys by the v0
|
|
@@ -126,6 +171,12 @@ export const PROTOCOL_LOOKUP_TABLE_ADDRESSES = [
|
|
|
126
171
|
FIXED_ADDRESSES.bridgedSolMint,
|
|
127
172
|
SYSTEM_PROGRAM_ID,
|
|
128
173
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
174
|
+
// Appended 2026-10-08 for launches through a companion (docs/companions.md): the companion's event
|
|
175
|
+
// authority and the programs it invokes, so its launch transaction fits (1,142 bytes instead of 1,266).
|
|
176
|
+
FIXED_ADDRESSES.companionEventAuthority,
|
|
177
|
+
PROGRAM_IDS.launch,
|
|
178
|
+
PROGRAM_IDS.swap,
|
|
179
|
+
PROGRAM_IDS.token,
|
|
129
180
|
];
|
|
130
181
|
/** Token hook flags (`Mint.hookFlags`). */
|
|
131
182
|
export const TOKEN_HOOK_FLAGS = {
|
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.6.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",
|