hookr-sdk 0.2.1 → 1.0.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATING.md +89 -0
  4. package/README.md +286 -105
  5. package/dist/core/abi.d.ts +10930 -0
  6. package/dist/core/abi.js +455 -0
  7. package/dist/core/advisories.d.ts +184 -0
  8. package/dist/core/advisories.js +291 -0
  9. package/dist/core/client.d.ts +55 -0
  10. package/dist/core/client.js +107 -0
  11. package/dist/core/generated.d.ts +14295 -0
  12. package/dist/core/generated.js +1058 -0
  13. package/dist/core/index.d.ts +17 -0
  14. package/dist/core/index.js +17 -0
  15. package/dist/core/launch.d.ts +200 -0
  16. package/dist/core/launch.js +427 -0
  17. package/dist/core/pair.d.ts +64 -0
  18. package/dist/core/pair.js +136 -0
  19. package/dist/core/params.d.ts +415 -0
  20. package/dist/core/params.js +1235 -0
  21. package/dist/core/policies.d.ts +136 -0
  22. package/dist/core/policies.js +125 -0
  23. package/dist/core/proofs.d.ts +25 -0
  24. package/dist/core/proofs.js +118 -0
  25. package/dist/core/quotes.d.ts +185 -0
  26. package/dist/core/quotes.js +550 -0
  27. package/dist/core/referrals.d.ts +32 -0
  28. package/dist/core/referrals.js +36 -0
  29. package/dist/core/registry.d.ts +357 -0
  30. package/dist/core/registry.js +378 -0
  31. package/dist/core/rules.d.ts +675 -0
  32. package/dist/core/rules.js +1049 -0
  33. package/dist/core/schema.d.ts +358 -0
  34. package/dist/core/schema.js +105 -0
  35. package/dist/core/stocks.generated.d.ts +11 -0
  36. package/dist/core/stocks.generated.js +1228 -0
  37. package/dist/core/vault.d.ts +59 -0
  38. package/dist/core/vault.js +112 -0
  39. package/dist/core/wallet.d.ts +142 -0
  40. package/dist/core/wallet.js +304 -0
  41. package/dist/deployments.d.ts +8 -0
  42. package/dist/deployments.js +26 -0
  43. package/dist/index.d.ts +6 -15
  44. package/dist/index.js +6 -15
  45. package/dist/releases.d.ts +172 -0
  46. package/dist/releases.js +268 -0
  47. package/package.json +46 -10
  48. package/provenance.json +31 -0
  49. package/dist/abi.d.ts +0 -6404
  50. package/dist/abi.js +0 -8300
  51. package/dist/chain.d.ts +0 -87
  52. package/dist/chain.js +0 -86
  53. package/dist/claims.d.ts +0 -32
  54. package/dist/claims.js +0 -31
  55. package/dist/fees.d.ts +0 -59
  56. package/dist/fees.js +0 -142
  57. package/dist/links.d.ts +0 -48
  58. package/dist/links.js +0 -93
  59. package/dist/market.d.ts +0 -264
  60. package/dist/market.js +0 -310
  61. package/dist/reads.d.ts +0 -136
  62. package/dist/reads.js +0 -219
  63. package/dist/rules.d.ts +0 -84
  64. package/dist/rules.js +0 -296
  65. package/dist/swap.d.ts +0 -108
  66. package/dist/swap.js +0 -144
@@ -0,0 +1,17 @@
1
+ export * from "./schema.js";
2
+ export * from "./abi.js";
3
+ export * from "./generated.js";
4
+ export * from "./proofs.js";
5
+ export * from "./wallet.js";
6
+ export * from "./client.js";
7
+ export * from "./referrals.js";
8
+ export * from "./policies.js";
9
+ export * from "./rules.js";
10
+ export * from "./registry.js";
11
+ export * from "./pair.js";
12
+ export * from "./advisories.js";
13
+ export * from "./vault.js";
14
+ export * from "./params.js";
15
+ export * from "./launch.js";
16
+ export * from "./quotes.js";
17
+ export * from "./stocks.generated.js";
@@ -0,0 +1,17 @@
1
+ export * from "./schema.js";
2
+ export * from "./abi.js";
3
+ export * from "./generated.js";
4
+ export * from "./proofs.js";
5
+ export * from "./wallet.js";
6
+ export * from "./client.js";
7
+ export * from "./referrals.js";
8
+ export * from "./policies.js";
9
+ export * from "./rules.js";
10
+ export * from "./registry.js";
11
+ export * from "./pair.js";
12
+ export * from "./advisories.js";
13
+ export * from "./vault.js";
14
+ export * from "./params.js";
15
+ export * from "./launch.js";
16
+ export * from "./quotes.js";
17
+ export * from "./stocks.generated.js";
@@ -0,0 +1,200 @@
1
+ import { type Hex } from "viem";
2
+ import { type ParamContext } from "./params.js";
3
+ import { type MemberKnobs, type RulesKnobs } from "./rules.js";
4
+ import { type Address, type Call, type DeploymentManifest, type Hash } from "./schema.js";
5
+ import { type PoolKey } from "./wallet.js";
6
+ /**
7
+ * HookrLauncher.DevBuy: the optional first buy of a new-token launch, on member `member`'s pool, made right after
8
+ * that member's liquidity add and delivered to the launch caller. `quoteIn` is the exact quote spent, `minSubjectOut`
9
+ * the least subject received net of Auto Burn, and `lockBlocks` keeps the family's principal locked at least that many
10
+ * parent blocks past the launch block (fees stay collectable). Its knobs and bounds are `RULE_PARAMS.devBuy`.
11
+ */
12
+ export interface DevBuy {
13
+ member: number;
14
+ quoteIn: bigint;
15
+ minSubjectOut: bigint;
16
+ lockBlocks: number;
17
+ }
18
+ /** HookrLauncher.Token, as buildLaunch takes it. */
19
+ export interface LaunchToken {
20
+ existing: Address;
21
+ name: string;
22
+ symbol: string;
23
+ supply: bigint;
24
+ salt: Hash;
25
+ }
26
+ /** Facts a dev-buy launch is checked against beyond its arguments. */
27
+ export interface DevBuyOptions {
28
+ /** block.number the launch is expected to land in; the member's guard must end after it. */
29
+ blockNumber?: bigint;
30
+ /**
31
+ * The new token's address (`buildPredictToken`). With it the builder knows each member's subject side and refuses a
32
+ * launch whose unplaced supply alone is above the 5% the creator may hold.
33
+ */
34
+ subject?: Address;
35
+ }
36
+ /** Slippage `quoteDevBuy` takes off the quoted subjectOut by default: 1%. */
37
+ export declare const DEFAULT_DEV_BUY_SLIPPAGE_BPS = 100;
38
+ /** The minSubjectOut for a quoted subjectOut: `slippageBps` (0 to 9,999) off it, and never below 1. */
39
+ export declare function devBuyMinSubjectOut(subjectOut: bigint, slippageBps?: number): bigint;
40
+ /** The most subject the creator may hold when a dev-buy launch returns: floor(supply * 500 / 10,000), as the Launcher. */
41
+ export declare function devBuyHoldCap(supply: bigint): bigint;
42
+ /**
43
+ * The least unplaced supply the Launcher refunds the creator: the supply less every member's subject maximum. The
44
+ * Launcher refunds supply less the subject the bands actually took, so the refund is at least this.
45
+ */
46
+ export declare function unplacedSubjectFloor(token: LaunchToken, members: readonly Record<string, unknown>[], subject: Address): bigint;
47
+ /** The dev-buy knob context of a launch: every member's Anti-Snipe terms and whether its token is new. */
48
+ export declare function devBuyContext(token: LaunchToken, members: readonly Record<string, unknown>[], blockNumber?: bigint): ParamContext;
49
+ /**
50
+ * Refuses a dev buy HookrLauncher._checkBuy refuses before it swaps (InvalidDevBuy): an existing token, a zero quoteIn
51
+ * or minSubjectOut, an unknown member, a quoteIn above int128, a lock above MAX_DEV_BUY_LOCK_BLOCKS, or a guarded
52
+ * member whose guard ends by `blockNumber`, without a Snipe or with a per-block cap below quoteIn (an unguarded member
53
+ * takes any dev buy, r31). Then checks the knobs against `RULE_PARAMS.devBuy`.
54
+ */
55
+ export declare function checkDevBuy(buy: DevBuy, token: LaunchToken, members: readonly Record<string, unknown>[], blockNumber?: bigint): void;
56
+ /**
57
+ * launchAdvised with a dev buy (HookrLauncher.launchWithBuy). `nativeBudget` is the native members' liquidity maxima,
58
+ * as for buildLaunch; the call's value adds `buy.quoteIn` when the dev-buy member's quote is native, and the launch fee
59
+ * (`launchFee`, read at the launch block). An ERC-20 quote is pulled from the caller with that member's budget, so
60
+ * approve the Launcher for both. The Launcher delivers the dev-bought subject to the caller and reverts DevBuyAboveCap
61
+ * when the caller would hold more than 5% of the supply, dev-bought and unplaced together. Use `quoteDevBuy` to set
62
+ * `minSubjectOut`.
63
+ */
64
+ export declare function buildLaunchWithBuy(m: DeploymentManifest, token: LaunchToken, root: Address, members: readonly Record<string, unknown>[], advisoryConfigs: readonly Hash[], buy: DevBuy, nativeBudget: bigint, deadline: bigint, launchFee: bigint, opts?: DevBuyOptions): Call;
65
+ /** An eth_call executor: runs `tx` at the chosen block and returns the call's return data. It throws on a revert. */
66
+ export type EthCall = (tx: {
67
+ from: Address;
68
+ to: Address;
69
+ data: Hex;
70
+ value: bigint;
71
+ }) => Promise<Hex>;
72
+ /** A quoted dev-buy launch. `call` is the launchWithBuy to sign, with `minSubjectOut` set. */
73
+ export interface DevBuyQuote {
74
+ subjectOut: bigint;
75
+ minSubjectOut: bigint;
76
+ call: Call;
77
+ }
78
+ /**
79
+ * Quotes a dev-buy launch by eth_call from `from` (the creator: the Launcher delivers to the caller) with minSubjectOut
80
+ * 1, then sets minSubjectOut to the quoted subjectOut less `slippageBps` (default 1%) and returns the launch to sign.
81
+ * The member's pool opens inside the launch, so the quote moves only with the launch's own terms and the chain state
82
+ * it reads. The eth_call needs the creator's funding in place: the native value in its balance and ERC-20 allowances
83
+ * for every quote and the dev buy (or an ethCall that applies state overrides). `launchFunding` lists exactly what the
84
+ * Launcher pulls (an ERC-20 dev buy adds its quoteIn to its member's quote) and `buildApprovals` the approve calls to
85
+ * send, in order, before quoting. A launch the Launcher would refuse,
86
+ * DevBuyAboveCap included, surfaces as the ethCall's revert (`devBuyRevert` names it).
87
+ */
88
+ export declare function quoteDevBuy(ethCall: EthCall, q: {
89
+ manifest: DeploymentManifest;
90
+ from: Address;
91
+ token: LaunchToken;
92
+ root: Address;
93
+ members: readonly Record<string, unknown>[];
94
+ advisoryConfigs: readonly Hash[];
95
+ buy: Omit<DevBuy, "minSubjectOut">;
96
+ nativeBudget: bigint;
97
+ deadline: bigint;
98
+ launchFee: bigint;
99
+ slippageBps?: number;
100
+ } & DevBuyOptions): Promise<DevBuyQuote>;
101
+ /** Names a Launcher dev-buy revert in `data` (InvalidDevBuy, DevBuyAboveCap, DevBuySlippage, DevBuyPartialFill), or null. */
102
+ export declare function devBuyRevert(data: Hex): string | null;
103
+ /** HookrLauncher.OpeningCheck (r31): every member after the first names a reference pool; zero checks nothing. */
104
+ export interface OpeningCheck {
105
+ toleranceBps: number;
106
+ references: readonly PoolKey[];
107
+ }
108
+ /**
109
+ * HookrLauncher.LaunchParams (r31): launchFamily's input, a launch's Token, root, Members and advisory configs with each
110
+ * member's knobs (`MemberKnobs`), up to one dev buy per member, a retained-supply cap, an opening check and a new
111
+ * token's tagline and logo URI.
112
+ */
113
+ export interface LaunchFamilyParams {
114
+ token: LaunchToken;
115
+ root: Address;
116
+ members: readonly Record<string, unknown>[];
117
+ knobs: readonly MemberKnobs[];
118
+ advisoryConfigs: readonly Hash[];
119
+ buys: readonly DevBuy[];
120
+ maxRetainedBps: number;
121
+ opening: OpeningCheck;
122
+ deadline: bigint;
123
+ tagline: string;
124
+ logoURI: string;
125
+ }
126
+ /** A member's knobs at their defaults: no weight, the default divisor, no lock end, the default tempo, linear Snipe. */
127
+ export declare const NO_MEMBER_KNOBS: MemberKnobs;
128
+ /** launchFamily's bounds (HookrLaunchChecks.familyBounds and HookrToken's presentation bounds). */
129
+ export declare const LAUNCH_FAMILY_BOUNDS: {
130
+ readonly minDivisor: number;
131
+ readonly maxDivisor: number;
132
+ readonly maxToleranceBps: number;
133
+ readonly maxLockEndBlocks: bigint;
134
+ readonly maxTaglineBytes: number;
135
+ readonly maxLogoUriBytes: number;
136
+ };
137
+ /** Facts a launchFamily launch is checked against beyond its arguments. */
138
+ export interface LaunchFamilyOptions extends DevBuyOptions {
139
+ /**
140
+ * Each reference pool's sqrtPriceX96 on the PoolManager at the launch block, in reference order: with them the opening
141
+ * check runs offline exactly as HookrLaunchChecks runs it. Without them the launch checks it on chain.
142
+ */
143
+ referencePrices?: readonly bigint[];
144
+ }
145
+ /**
146
+ * The RulesKnobs HookrLauncher._rulesData passes for a launchFamily member (its Rules check them at bind): the five
147
+ * knob words of its MemberKnobs as given, and on a dynamic fee member its minimum liquidity and, when all four tempo
148
+ * knobs are zero, the default tempo.
149
+ */
150
+ export declare function familyRulesKnobs(member: Record<string, unknown>, knobs: MemberKnobs): RulesKnobs;
151
+ /**
152
+ * HookrLaunchChecks._checkOpening offline: member i's opening sqrt price converted into member 0's quote through its
153
+ * reference pool's sqrt price, compared on the squares with member 0's within `toleranceBps`. Refuses with the member
154
+ * and the band, as OpeningOutsideTolerance would.
155
+ */
156
+ export declare function checkOpeningPrices(subject: Address, members: readonly Record<string, unknown>[], references: readonly PoolKey[], referencePrices: readonly bigint[], toleranceBps: number): void;
157
+ /**
158
+ * Refuses a launchFamily launch the Launcher would refuse before any pool opens (r31): every launch's checks
159
+ * (`checkLaunch`, advisory configs), then HookrLaunchChecks.checkFamily's: one MemberKnobs per member, a retained cap
160
+ * of 0 to 10,000 (an existing token takes only 10,000), a tagline (at most 160 bytes) and logo URI (at most 300) only on
161
+ * a new token, supply weights on every member or none (new token only, each member's subject budget exactly floor(supply
162
+ * x weight / 10,000), at most 10,000 in all; `opts.subject` names the new token so the subject side is known), a
163
+ * divisor of zero or 2 to 10,000 on a dynamic fee member, a lock end of zero or from the last guard end to the launch
164
+ * block + 100,000, and an opening check of no references at a zero tolerance or one reference per later member, holding
165
+ * that member's quote and member 0's, at 1 to 1,000 bps (with `opts.referencePrices` the check itself). Each member's
166
+ * Rules knobs pass the Rules' bind checks (`checkRulesKnobs`), each dev buy `checkDevBuy`, at most one per member, and
167
+ * with `opts.subject` the unplaced supply alone stays within the dev buy cap (with any buy) and the retained cap.
168
+ */
169
+ export declare function checkLaunchFamily(m: DeploymentManifest, p: LaunchFamilyParams, nativeBudget: bigint, opts?: LaunchFamilyOptions): void;
170
+ /**
171
+ * HookrLauncher.launchFamily (r31). `nativeBudget` is the native members' liquidity maxima; the call's value adds every
172
+ * native member's dev buy and the launch fee (`launchFee`, read at the launch block). Each ERC-20 member pulls its
173
+ * budget plus its dev buy (`launchFunding`). The Launcher delivers every dev-bought subject to the caller and reverts
174
+ * DevBuyAboveCap or RetainedAboveCap when the caller would hold more than the dev buy bound or the retained cap. Use
175
+ * `quoteLaunchFamily` to set each buy's `minSubjectOut`.
176
+ */
177
+ export declare function buildLaunchFamily(m: DeploymentManifest, p: LaunchFamilyParams, nativeBudget: bigint, launchFee: bigint, opts?: LaunchFamilyOptions): Call;
178
+ /** A quoted launchFamily launch: each dev buy's quoted subject, its minimum and the call to sign. */
179
+ export interface LaunchFamilyQuote {
180
+ subjectOuts: bigint[];
181
+ minSubjectOuts: bigint[];
182
+ call: Call;
183
+ }
184
+ /**
185
+ * Quotes a launchFamily launch by eth_call from `from` (the creator: the Launcher delivers to the caller) and sets each
186
+ * dev buy's minSubjectOut to its quoted subject less `slippageBps` (default 1%). Each dev buy runs on its own member's
187
+ * pool right after that member's add, so no buy moves another's price: the launch is probed with the first k buys for
188
+ * k = 1..n at minSubjectOut 1, and buy k's subject is the difference of the returned totals. The eth_call needs the
189
+ * creator's funding in place (`launchFunding`), as for `quoteDevBuy`.
190
+ */
191
+ export declare function quoteLaunchFamily(ethCall: EthCall, q: {
192
+ manifest: DeploymentManifest;
193
+ from: Address;
194
+ params: LaunchFamilyParams;
195
+ nativeBudget: bigint;
196
+ launchFee: bigint;
197
+ slippageBps?: number;
198
+ } & LaunchFamilyOptions): Promise<LaunchFamilyQuote>;
199
+ /** Names a launchFamily revert in `data` (its own checks and the dev buy's), or null. */
200
+ export declare function launchFamilyRevert(data: Hex): string | null;
@@ -0,0 +1,427 @@
1
+ import { BaseError, decodeErrorResult, decodeFunctionResult } from "viem";
2
+ import { launcherAbi } from "./abi.js";
3
+ import { RELEASE_CONSTANTS } from "./generated.js";
4
+ import { MAX_DEV_BUY_BPS, checkRuleParams, devBuyMemberRefusal, } from "./params.js";
5
+ import { DYNAMIC_FEE_PARAMETERS, checkRulesKnobs, dynamicFeeMinLiquidity, } from "./rules.js";
6
+ import { address, assert, uint, ZERO } from "./schema.js";
7
+ import { buildCall, checkAdvisoryConfigs, checkLaunch, checkLaunchFee } from "./wallet.js";
8
+ /** Slippage `quoteDevBuy` takes off the quoted subjectOut by default: 1%. */
9
+ export const DEFAULT_DEV_BUY_SLIPPAGE_BPS = 100;
10
+ /** The minSubjectOut for a quoted subjectOut: `slippageBps` (0 to 9,999) off it, and never below 1. */
11
+ export function devBuyMinSubjectOut(subjectOut, slippageBps = DEFAULT_DEV_BUY_SLIPPAGE_BPS) {
12
+ assert(Number.isSafeInteger(slippageBps) && slippageBps >= 0 && slippageBps < 10_000, "Slippage must be 0 to 9,999 bps");
13
+ assert(uint(subjectOut) > 0n, "The dev buy quoted no subject");
14
+ const out = (subjectOut * BigInt(10_000 - slippageBps)) / 10000n;
15
+ return out > 0n ? out : 1n;
16
+ }
17
+ function small(value, bits, label) {
18
+ const v = typeof value === "number" ? value : typeof value === "bigint" ? Number(value) : NaN;
19
+ assert(Number.isSafeInteger(v) && v >= 0 && v < 2 ** bits, `Invalid ${label}`);
20
+ return v;
21
+ }
22
+ function devBuyTerms(member) {
23
+ const r = member.rules;
24
+ assert(r !== undefined && r !== null && typeof r === "object", "Launch member without rules");
25
+ return {
26
+ guardEndBlock: small(r.guardEndBlock, 40, "guardEndBlock"),
27
+ snipeTaxPips: small(r.snipeTaxPips, 24, "snipeTaxPips"),
28
+ maxBuyQuoteAmount: uint(r.maxBuyQuoteAmount, 128),
29
+ };
30
+ }
31
+ /** The most subject the creator may hold when a dev-buy launch returns: floor(supply * 500 / 10,000), as the Launcher. */
32
+ export function devBuyHoldCap(supply) {
33
+ return uint(supply) / (10000n / BigInt(MAX_DEV_BUY_BPS));
34
+ }
35
+ /**
36
+ * The least unplaced supply the Launcher refunds the creator: the supply less every member's subject maximum. The
37
+ * Launcher refunds supply less the subject the bands actually took, so the refund is at least this.
38
+ */
39
+ export function unplacedSubjectFloor(token, members, subject) {
40
+ const s = BigInt(address(subject));
41
+ let placed = 0n;
42
+ for (const m of members) {
43
+ // The pool sorts its currencies; the native quote (zero) always sorts first.
44
+ const subjectFirst = s < BigInt(address(m.quote, true));
45
+ placed += uint(subjectFirst ? m.amount0Max : m.amount1Max, 128);
46
+ }
47
+ return placed >= token.supply ? 0n : token.supply - placed;
48
+ }
49
+ /** The dev-buy knob context of a launch: every member's Anti-Snipe terms and whether its token is new. */
50
+ export function devBuyContext(token, members, blockNumber) {
51
+ return {
52
+ launchMembers: members.map(devBuyTerms),
53
+ newToken: address(token.existing, true) === ZERO,
54
+ ...(blockNumber === undefined ? {} : { blockNumber }),
55
+ };
56
+ }
57
+ /**
58
+ * Refuses a dev buy HookrLauncher._checkBuy refuses before it swaps (InvalidDevBuy): an existing token, a zero quoteIn
59
+ * or minSubjectOut, an unknown member, a quoteIn above int128, a lock above MAX_DEV_BUY_LOCK_BLOCKS, or a guarded
60
+ * member whose guard ends by `blockNumber`, without a Snipe or with a per-block cap below quoteIn (an unguarded member
61
+ * takes any dev buy, r31). Then checks the knobs against `RULE_PARAMS.devBuy`.
62
+ */
63
+ export function checkDevBuy(buy, token, members, blockNumber) {
64
+ assert(address(token.existing, true) === ZERO, "A dev buy needs a new token");
65
+ assert(Number.isSafeInteger(buy.member) && buy.member >= 0 && buy.member < members.length, "The dev buy names no family member");
66
+ assert(uint(buy.quoteIn, 128) > 0n, "A dev buy spends a positive quoteIn; launch without one instead");
67
+ const refusal = devBuyMemberRefusal(devBuyTerms(members[buy.member]), buy.quoteIn, blockNumber);
68
+ assert(refusal === null, refusal ?? "");
69
+ checkRuleParams("devBuy", buy, devBuyContext(token, members, blockNumber));
70
+ }
71
+ /**
72
+ * launchAdvised with a dev buy (HookrLauncher.launchWithBuy). `nativeBudget` is the native members' liquidity maxima,
73
+ * as for buildLaunch; the call's value adds `buy.quoteIn` when the dev-buy member's quote is native, and the launch fee
74
+ * (`launchFee`, read at the launch block). An ERC-20 quote is pulled from the caller with that member's budget, so
75
+ * approve the Launcher for both. The Launcher delivers the dev-bought subject to the caller and reverts DevBuyAboveCap
76
+ * when the caller would hold more than 5% of the supply, dev-bought and unplaced together. Use `quoteDevBuy` to set
77
+ * `minSubjectOut`.
78
+ */
79
+ export function buildLaunchWithBuy(m, token, root, members, advisoryConfigs, buy, nativeBudget, deadline, launchFee, opts = {}) {
80
+ checkLaunch(m, token, root, members, nativeBudget, deadline);
81
+ checkAdvisoryConfigs(members, advisoryConfigs);
82
+ checkDevBuy(buy, token, members, opts.blockNumber);
83
+ if (opts.subject !== undefined) {
84
+ const floor = unplacedSubjectFloor(token, members, opts.subject);
85
+ const cap = devBuyHoldCap(token.supply);
86
+ assert(floor <= cap, `The unplaced supply alone (${floor}) is above the ${cap} a dev-buy creator may hold; place more of the supply`);
87
+ }
88
+ const native = address(members[buy.member].quote, true) === ZERO;
89
+ return buildCall(m, "launcher", launcherAbi, "launchWithBuy", [
90
+ token,
91
+ root,
92
+ members,
93
+ advisoryConfigs,
94
+ { member: buy.member, quoteIn: buy.quoteIn, minSubjectOut: buy.minSubjectOut, lockBlocks: buy.lockBlocks },
95
+ deadline,
96
+ ], nativeBudget + (native ? buy.quoteIn : 0n) + checkLaunchFee(launchFee));
97
+ }
98
+ /**
99
+ * Quotes a dev-buy launch by eth_call from `from` (the creator: the Launcher delivers to the caller) with minSubjectOut
100
+ * 1, then sets minSubjectOut to the quoted subjectOut less `slippageBps` (default 1%) and returns the launch to sign.
101
+ * The member's pool opens inside the launch, so the quote moves only with the launch's own terms and the chain state
102
+ * it reads. The eth_call needs the creator's funding in place: the native value in its balance and ERC-20 allowances
103
+ * for every quote and the dev buy (or an ethCall that applies state overrides). `launchFunding` lists exactly what the
104
+ * Launcher pulls (an ERC-20 dev buy adds its quoteIn to its member's quote) and `buildApprovals` the approve calls to
105
+ * send, in order, before quoting. A launch the Launcher would refuse,
106
+ * DevBuyAboveCap included, surfaces as the ethCall's revert (`devBuyRevert` names it).
107
+ */
108
+ export async function quoteDevBuy(ethCall, q) {
109
+ const slippage = q.slippageBps ?? DEFAULT_DEV_BUY_SLIPPAGE_BPS;
110
+ devBuyMinSubjectOut(1n, slippage);
111
+ const opts = { blockNumber: q.blockNumber, subject: q.subject };
112
+ const build = (minSubjectOut) => buildLaunchWithBuy(q.manifest, q.token, q.root, q.members, q.advisoryConfigs, { ...q.buy, minSubjectOut }, q.nativeBudget, q.deadline, q.launchFee, opts);
113
+ const probe = build(1n);
114
+ const data = await ethCall({ from: address(q.from), to: probe.to, data: probe.data, value: probe.value });
115
+ const [, subject, subjectOut] = decodeFunctionResult({ abi: launcherAbi, functionName: "launchWithBuy", data });
116
+ if (q.subject !== undefined)
117
+ assert(address(subject) === address(q.subject), "The quoted launch created a different token");
118
+ const minSubjectOut = devBuyMinSubjectOut(subjectOut, slippage);
119
+ return { subjectOut, minSubjectOut, call: build(minSubjectOut) };
120
+ }
121
+ /** Names a Launcher dev-buy revert in `data` (InvalidDevBuy, DevBuyAboveCap, DevBuySlippage, DevBuyPartialFill), or null. */
122
+ export function devBuyRevert(data) {
123
+ try {
124
+ const e = decodeErrorResult({ abi: launcherAbi, data });
125
+ switch (e.errorName) {
126
+ case "InvalidDevBuy":
127
+ return "The Launcher refuses the dev buy's terms (member, amount, lock or guard)";
128
+ case "DevBuyAboveCap":
129
+ return `The creator would hold ${e.args[0]} of the subject, above the ${e.args[1]} (5% of supply) cap`;
130
+ case "DevBuySlippage":
131
+ return `The dev buy delivers ${e.args[0]}, below minSubjectOut ${e.args[1]}`;
132
+ case "DevBuyPartialFill":
133
+ return `The member's band filled ${e.args[0]} of the dev buy's ${e.args[1]}; widen the band or buy less`;
134
+ }
135
+ }
136
+ catch (error) {
137
+ if (!(error instanceof BaseError))
138
+ throw error;
139
+ }
140
+ return null;
141
+ }
142
+ /** A member's knobs at their defaults: no weight, the default divisor, no lock end, the default tempo, linear Snipe. */
143
+ export const NO_MEMBER_KNOBS = Object.freeze({
144
+ subjectWeightBps: 0,
145
+ dynamicFeeLiquidityDivisor: 0,
146
+ lockEndBlock: 0n,
147
+ windowSeconds: 0,
148
+ resetSeconds: 0,
149
+ carryBps: 0,
150
+ moveTicks: 0,
151
+ snipeCurve: 0,
152
+ });
153
+ /** launchFamily's bounds (HookrLaunchChecks.familyBounds and HookrToken's presentation bounds). */
154
+ export const LAUNCH_FAMILY_BOUNDS = {
155
+ minDivisor: Number(RELEASE_CONSTANTS.LAUNCH_MIN_DYNAMIC_FEE_LIQUIDITY_DIVISOR),
156
+ maxDivisor: Number(RELEASE_CONSTANTS.LAUNCH_MAX_DYNAMIC_FEE_LIQUIDITY_DIVISOR),
157
+ maxToleranceBps: Number(RELEASE_CONSTANTS.LAUNCH_MAX_OPENING_TOLERANCE_BPS),
158
+ maxLockEndBlocks: BigInt(RELEASE_CONSTANTS.LAUNCH_MAX_LOCK_END_BLOCKS),
159
+ maxTaglineBytes: Number(RELEASE_CONSTANTS.TOKEN_MAX_TAGLINE_BYTES),
160
+ maxLogoUriBytes: Number(RELEASE_CONSTANTS.TOKEN_MAX_LOGO_URI_BYTES),
161
+ };
162
+ const BPS = 10000n;
163
+ const Q96 = 1n << 96n;
164
+ const MIN_SQRT_PRICE = 4295128739n;
165
+ const MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342n;
166
+ function utf8Bytes(text) {
167
+ return new TextEncoder().encode(text).length;
168
+ }
169
+ /**
170
+ * The RulesKnobs HookrLauncher._rulesData passes for a launchFamily member (its Rules check them at bind): the five
171
+ * knob words of its MemberKnobs as given, and on a dynamic fee member its minimum liquidity and, when all four tempo
172
+ * knobs are zero, the default tempo.
173
+ */
174
+ export function familyRulesKnobs(member, knobs) {
175
+ const r = member.rules;
176
+ const k = {
177
+ minDynamicFeeLiquidity: 0n,
178
+ windowSeconds: knobs.windowSeconds,
179
+ resetSeconds: knobs.resetSeconds,
180
+ carryBps: knobs.carryBps,
181
+ moveTicks: knobs.moveTicks,
182
+ snipeCurve: knobs.snipeCurve,
183
+ };
184
+ if (Number(r.dynamicFeeSens) !== 0) {
185
+ k.minDynamicFeeLiquidity = dynamicFeeMinLiquidity(uint(member.liquidity, 128), knobs.dynamicFeeLiquidityDivisor);
186
+ if (k.windowSeconds === 0 && k.resetSeconds === 0 && k.carryBps === 0 && k.moveTicks === 0) {
187
+ k.windowSeconds = DYNAMIC_FEE_PARAMETERS.window;
188
+ k.resetSeconds = DYNAMIC_FEE_PARAMETERS.reset;
189
+ k.carryBps = DYNAMIC_FEE_PARAMETERS.carryBps;
190
+ k.moveTicks = DYNAMIC_FEE_PARAMETERS.moveTicks;
191
+ }
192
+ }
193
+ return k;
194
+ }
195
+ /** sqrtPriceX96, or floor(2^192 / sqrtPriceX96) when `invert` (HookrLaunchChecks._oriented). */
196
+ function oriented(sqrtPriceX96, invert) {
197
+ return invert ? (1n << 192n) / sqrtPriceX96 : sqrtPriceX96;
198
+ }
199
+ /** A member's opening sqrt price, refused outside [MIN_SQRT_PRICE, MAX_SQRT_PRICE) as the PoolManager refuses it. */
200
+ function memberPrice(member) {
201
+ const p = uint(member.sqrtPriceX96, 160);
202
+ assert(p >= MIN_SQRT_PRICE && p < MAX_SQRT_PRICE, "A member's opening price is outside the PoolManager's range");
203
+ return p;
204
+ }
205
+ /**
206
+ * HookrLaunchChecks._checkOpening offline: member i's opening sqrt price converted into member 0's quote through its
207
+ * reference pool's sqrt price, compared on the squares with member 0's within `toleranceBps`. Refuses with the member
208
+ * and the band, as OpeningOutsideTolerance would.
209
+ */
210
+ export function checkOpeningPrices(subject, members, references, referencePrices, toleranceBps) {
211
+ assert(referencePrices.length === references.length, "One reference price per reference pool");
212
+ const s = BigInt(address(subject));
213
+ const quote0 = address(members[0].quote, true);
214
+ const s0 = oriented(memberPrice(members[0]), s > BigInt(quote0));
215
+ const t = BigInt(toleranceBps);
216
+ const low = s0 * (BPS - t) * s0;
217
+ const high = s0 * (BPS + t) * s0;
218
+ for (let i = 1; i < members.length; i++) {
219
+ const ref = references[i - 1];
220
+ const quote = address(members[i].quote, true);
221
+ const c0 = address(ref.currency0, true);
222
+ const sqrtReference = uint(referencePrices[i - 1], 160);
223
+ assert(sqrtReference !== 0n, `Member ${i}'s reference pool is not initialized (OpeningReferenceUninitialized)`);
224
+ const converted = (oriented(memberPrice(members[i]), s > BigInt(quote)) * oriented(sqrtReference, c0 !== quote)) / Q96;
225
+ const square = converted * 100n * (converted * 100n);
226
+ assert(square >= low && square <= high, `Member ${i} opens outside ${toleranceBps} bps of member 0's price through its reference (OpeningOutsideTolerance)`);
227
+ }
228
+ }
229
+ /**
230
+ * Refuses a launchFamily launch the Launcher would refuse before any pool opens (r31): every launch's checks
231
+ * (`checkLaunch`, advisory configs), then HookrLaunchChecks.checkFamily's: one MemberKnobs per member, a retained cap
232
+ * of 0 to 10,000 (an existing token takes only 10,000), a tagline (at most 160 bytes) and logo URI (at most 300) only on
233
+ * a new token, supply weights on every member or none (new token only, each member's subject budget exactly floor(supply
234
+ * x weight / 10,000), at most 10,000 in all; `opts.subject` names the new token so the subject side is known), a
235
+ * divisor of zero or 2 to 10,000 on a dynamic fee member, a lock end of zero or from the last guard end to the launch
236
+ * block + 100,000, and an opening check of no references at a zero tolerance or one reference per later member, holding
237
+ * that member's quote and member 0's, at 1 to 1,000 bps (with `opts.referencePrices` the check itself). Each member's
238
+ * Rules knobs pass the Rules' bind checks (`checkRulesKnobs`), each dev buy `checkDevBuy`, at most one per member, and
239
+ * with `opts.subject` the unplaced supply alone stays within the dev buy cap (with any buy) and the retained cap.
240
+ */
241
+ export function checkLaunchFamily(m, p, nativeBudget, opts = {}) {
242
+ checkLaunch(m, p.token, p.root, p.members, nativeBudget, p.deadline);
243
+ checkAdvisoryConfigs(p.members, p.advisoryConfigs);
244
+ const n = p.members.length;
245
+ const b = LAUNCH_FAMILY_BOUNDS;
246
+ assert(p.knobs.length === n, "One MemberKnobs per member (InvalidFamily)");
247
+ const isNew = address(p.token.existing, true) === ZERO;
248
+ assert(Number.isSafeInteger(p.maxRetainedBps) &&
249
+ p.maxRetainedBps >= 0 &&
250
+ p.maxRetainedBps <= 10_000 &&
251
+ (isNew || p.maxRetainedBps === 10_000), "The retained cap is 0 to 10,000 bps, and 10,000 for an existing token (InvalidRetainedCap)");
252
+ if (p.tagline !== "" || p.logoURI !== "")
253
+ assert(isNew, "Only a new token takes a tagline and logo URI (InvalidFunding)");
254
+ assert(utf8Bytes(p.tagline) <= b.maxTaglineBytes, `The tagline is above ${b.maxTaglineBytes} bytes (InvalidMetadata)`);
255
+ assert(utf8Bytes(p.logoURI) <= b.maxLogoUriBytes, `The logo URI is above ${b.maxLogoUriBytes} bytes (InvalidMetadata)`);
256
+ const weighted = p.knobs[0].subjectWeightBps !== 0;
257
+ assert(!weighted || isNew, "Supply weights need a new token (InvalidWeights)");
258
+ const lastGuardEnd = p.members.reduce((max, member) => {
259
+ const end = BigInt(Number(member.rules.guardEndBlock));
260
+ return end > max ? end : max;
261
+ }, 0n);
262
+ let weightSum = 0;
263
+ p.knobs.forEach((k, i) => {
264
+ const member = p.members[i];
265
+ const rules = member.rules;
266
+ assert(Number.isSafeInteger(k.subjectWeightBps) && k.subjectWeightBps >= 0 && k.subjectWeightBps < 2 ** 16, `Invalid member ${i} weight`);
267
+ assert((k.subjectWeightBps !== 0) === weighted, "Supply weights on every member or none (InvalidWeights)");
268
+ if (weighted) {
269
+ assert(opts.subject !== undefined, "Pass the new token's address (buildPredictToken) to check supply weights");
270
+ weightSum += k.subjectWeightBps;
271
+ const subjectFirst = BigInt(address(opts.subject)) < BigInt(address(member.quote, true));
272
+ const budget = uint(subjectFirst ? member.amount0Max : member.amount1Max, 128);
273
+ const expected = (uint(p.token.supply) * BigInt(k.subjectWeightBps)) / BPS;
274
+ assert(budget === expected, `Member ${i}'s subject budget ${budget} is not its weight's ${expected} (WeightMismatch)`);
275
+ }
276
+ const divisor = k.dynamicFeeLiquidityDivisor;
277
+ assert(Number.isSafeInteger(divisor) && divisor >= 0 && divisor < 2 ** 16, `Invalid member ${i} divisor`);
278
+ assert(divisor === 0 || (Number(rules.dynamicFeeSens) !== 0 && divisor >= b.minDivisor && divisor <= b.maxDivisor), `Member ${i}'s divisor is zero, or ${b.minDivisor} to ${b.maxDivisor} on a dynamic fee member (InvalidDivisor)`);
279
+ const end = uint(k.lockEndBlock, 64);
280
+ if (end !== 0n)
281
+ assert(end >= lastGuardEnd && (opts.blockNumber === undefined || end <= opts.blockNumber + b.maxLockEndBlocks), `Member ${i}'s lock end is before the last guard end or past the launch block + 100,000 (InvalidLockEnd)`);
282
+ // The form the Launcher binds: recapture members the 30-word data, members with a knob the 22-word data.
283
+ const rulesKnobs = familyRulesKnobs(member, k);
284
+ const knobbed = rulesKnobs.minDynamicFeeLiquidity !== 0n ||
285
+ rulesKnobs.windowSeconds !== 0 ||
286
+ rulesKnobs.resetSeconds !== 0 ||
287
+ rulesKnobs.carryBps !== 0 ||
288
+ rulesKnobs.moveTicks !== 0 ||
289
+ rulesKnobs.snipeCurve !== 0;
290
+ checkRulesKnobs({ dynamicFeeSens: Number(rules.dynamicFeeSens), snipeDecaySeconds: Number(rules.snipeDecaySeconds) }, rulesKnobs, member.recapture.on ? "recapture" : knobbed ? "knobs" : "config");
291
+ });
292
+ assert(weightSum <= 10_000, "Supply weights sum above 10,000 bps (InvalidWeights)");
293
+ const { toleranceBps, references } = p.opening;
294
+ if (toleranceBps !== 0 || references.length !== 0) {
295
+ assert(Number.isSafeInteger(toleranceBps) && toleranceBps >= 1 && toleranceBps <= b.maxToleranceBps, `The opening tolerance is 1 to ${b.maxToleranceBps} bps (OpeningToleranceOutOfRange)`);
296
+ assert(references.length === n - 1, "One reference pool per member after the first (InvalidFamily)");
297
+ const quote0 = address(p.members[0].quote, true);
298
+ references.forEach((ref, j) => {
299
+ const quote = address(p.members[j + 1].quote, true);
300
+ const c0 = address(ref.currency0, true);
301
+ const c1 = address(ref.currency1, true);
302
+ assert((c0 === quote && c1 === quote0) || (c0 === quote0 && c1 === quote), `Member ${j + 1}'s reference pool must hold its quote and member 0's (OpeningReferenceMismatch)`);
303
+ });
304
+ if (opts.referencePrices !== undefined) {
305
+ assert(opts.subject !== undefined, "Pass the subject's address to run the opening check offline");
306
+ checkOpeningPrices(opts.subject, p.members, references, opts.referencePrices, toleranceBps);
307
+ }
308
+ }
309
+ const seen = new Set();
310
+ for (const buy of p.buys) {
311
+ checkDevBuy(buy, p.token, p.members, opts.blockNumber);
312
+ assert(!seen.has(buy.member), "At most one dev buy per member (InvalidDevBuy)");
313
+ seen.add(buy.member);
314
+ }
315
+ if (opts.subject !== undefined && isNew) {
316
+ const floor = unplacedSubjectFloor(p.token, p.members, opts.subject);
317
+ if (p.buys.length !== 0) {
318
+ const cap = devBuyHoldCap(p.token.supply);
319
+ assert(floor <= cap, `The unplaced supply alone (${floor}) is above the ${cap} a dev-buy creator may hold`);
320
+ }
321
+ const retained = (uint(p.token.supply) * BigInt(p.maxRetainedBps)) / BPS;
322
+ assert(floor <= retained, `The unplaced supply alone (${floor}) is above the retained cap ${retained} (RetainedAboveCap)`);
323
+ }
324
+ }
325
+ /**
326
+ * HookrLauncher.launchFamily (r31). `nativeBudget` is the native members' liquidity maxima; the call's value adds every
327
+ * native member's dev buy and the launch fee (`launchFee`, read at the launch block). Each ERC-20 member pulls its
328
+ * budget plus its dev buy (`launchFunding`). The Launcher delivers every dev-bought subject to the caller and reverts
329
+ * DevBuyAboveCap or RetainedAboveCap when the caller would hold more than the dev buy bound or the retained cap. Use
330
+ * `quoteLaunchFamily` to set each buy's `minSubjectOut`.
331
+ */
332
+ export function buildLaunchFamily(m, p, nativeBudget, launchFee, opts = {}) {
333
+ checkLaunchFamily(m, p, nativeBudget, opts);
334
+ const nativeBuys = p.buys.reduce((sum, buy) => (address(p.members[buy.member].quote, true) === ZERO ? sum + buy.quoteIn : sum), 0n);
335
+ return buildCall(m, "launcher", launcherAbi, "launchFamily", [
336
+ {
337
+ token: p.token,
338
+ root: p.root,
339
+ members: p.members,
340
+ knobs: p.knobs,
341
+ advisoryConfigs: p.advisoryConfigs,
342
+ buys: p.buys.map((buy) => ({
343
+ member: buy.member,
344
+ quoteIn: buy.quoteIn,
345
+ minSubjectOut: buy.minSubjectOut,
346
+ lockBlocks: buy.lockBlocks,
347
+ })),
348
+ maxRetainedBps: p.maxRetainedBps,
349
+ opening: p.opening,
350
+ deadline: p.deadline,
351
+ tagline: p.tagline,
352
+ logoURI: p.logoURI,
353
+ },
354
+ ], nativeBudget + nativeBuys + checkLaunchFee(launchFee));
355
+ }
356
+ /**
357
+ * Quotes a launchFamily launch by eth_call from `from` (the creator: the Launcher delivers to the caller) and sets each
358
+ * dev buy's minSubjectOut to its quoted subject less `slippageBps` (default 1%). Each dev buy runs on its own member's
359
+ * pool right after that member's add, so no buy moves another's price: the launch is probed with the first k buys for
360
+ * k = 1..n at minSubjectOut 1, and buy k's subject is the difference of the returned totals. The eth_call needs the
361
+ * creator's funding in place (`launchFunding`), as for `quoteDevBuy`.
362
+ */
363
+ export async function quoteLaunchFamily(ethCall, q) {
364
+ const slippage = q.slippageBps ?? DEFAULT_DEV_BUY_SLIPPAGE_BPS;
365
+ devBuyMinSubjectOut(1n, slippage);
366
+ const opts = {
367
+ blockNumber: q.blockNumber,
368
+ subject: q.subject,
369
+ referencePrices: q.referencePrices,
370
+ };
371
+ const build = (buys) => buildLaunchFamily(q.manifest, { ...q.params, buys }, q.nativeBudget, q.launchFee, opts);
372
+ const subjectOuts = [];
373
+ let previous = 0n;
374
+ for (let k = 1; k <= q.params.buys.length; k++) {
375
+ const probe = build(q.params.buys.slice(0, k).map((buy) => ({ ...buy, minSubjectOut: 1n })));
376
+ const data = await ethCall({ from: address(q.from), to: probe.to, data: probe.data, value: probe.value });
377
+ const [, subject, total] = decodeFunctionResult({ abi: launcherAbi, functionName: "launchFamily", data });
378
+ if (q.subject !== undefined)
379
+ assert(address(subject) === address(q.subject), "The quoted launch created a different token");
380
+ subjectOuts.push(total - previous);
381
+ previous = total;
382
+ }
383
+ const minSubjectOuts = subjectOuts.map((out) => devBuyMinSubjectOut(out, slippage));
384
+ const buys = q.params.buys.map((buy, i) => ({ ...buy, minSubjectOut: minSubjectOuts[i] }));
385
+ return { subjectOuts, minSubjectOuts, call: build(buys) };
386
+ }
387
+ /** Names a launchFamily revert in `data` (its own checks and the dev buy's), or null. */
388
+ export function launchFamilyRevert(data) {
389
+ const devBuy = devBuyRevert(data);
390
+ if (devBuy !== null)
391
+ return devBuy;
392
+ try {
393
+ const e = decodeErrorResult({ abi: launcherAbi, data });
394
+ const a = e.args;
395
+ switch (e.errorName) {
396
+ case "InvalidFamily":
397
+ return "The family's members, knobs, advisory configs or references do not line up";
398
+ case "InvalidFunding":
399
+ return "The launch's value is not the native budgets, the native dev buys and the launch fee, or an existing token took a tagline or logo";
400
+ case "InvalidRetainedCap":
401
+ return `The retained cap ${a?.[0]} is above 10,000 bps, or not 10,000 for an existing token`;
402
+ case "RetainedAboveCap":
403
+ return `The creator would hold ${a?.[0]} of the subject, above the retained cap ${a?.[1]}`;
404
+ case "InvalidWeights":
405
+ return "Supply weights go on every member of a new token or on none, at most 10,000 bps in all";
406
+ case "WeightMismatch":
407
+ return `Member ${a?.[0]}'s subject budget ${a?.[1]} is not its weight's ${a?.[2]}`;
408
+ case "InvalidDivisor":
409
+ return `Member ${a?.[0]}'s dynamic fee liquidity divisor ${a?.[1]} is out of bounds`;
410
+ case "InvalidLockEnd":
411
+ return `Member ${a?.[0]}'s lock end ${a?.[1]} is before the last guard end or more than 100,000 blocks out`;
412
+ case "OpeningToleranceOutOfRange":
413
+ return `The opening check's tolerance ${a?.[0]} bps is outside 1 to 1,000`;
414
+ case "OpeningReferenceMismatch":
415
+ return `Member ${a?.[0]}'s reference pool does not hold its quote and member 0's quote`;
416
+ case "OpeningReferenceUninitialized":
417
+ return `Member ${a?.[0]}'s reference pool has no price`;
418
+ case "OpeningOutsideTolerance":
419
+ return `Member ${a?.[0]} would open outside the tolerance of member 0's price`;
420
+ }
421
+ }
422
+ catch (error) {
423
+ if (!(error instanceof BaseError))
424
+ throw error;
425
+ }
426
+ return null;
427
+ }