hookr-sdk 0.2.1 → 1.1.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/CHANGELOG.md +70 -0
- package/LICENSE +21 -0
- package/MIGRATING.md +89 -0
- package/README.md +308 -105
- package/dist/core/abi.d.ts +10930 -0
- package/dist/core/abi.js +455 -0
- package/dist/core/advisories.d.ts +184 -0
- package/dist/core/advisories.js +291 -0
- package/dist/core/client.d.ts +55 -0
- package/dist/core/client.js +107 -0
- package/dist/core/generated.d.ts +14295 -0
- package/dist/core/generated.js +1058 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +17 -0
- package/dist/core/launch.d.ts +200 -0
- package/dist/core/launch.js +427 -0
- package/dist/core/pair.d.ts +64 -0
- package/dist/core/pair.js +136 -0
- package/dist/core/params.d.ts +415 -0
- package/dist/core/params.js +1235 -0
- package/dist/core/policies.d.ts +136 -0
- package/dist/core/policies.js +125 -0
- package/dist/core/proofs.d.ts +25 -0
- package/dist/core/proofs.js +118 -0
- package/dist/core/quotes.d.ts +185 -0
- package/dist/core/quotes.js +550 -0
- package/dist/core/referrals.d.ts +32 -0
- package/dist/core/referrals.js +36 -0
- package/dist/core/registry.d.ts +357 -0
- package/dist/core/registry.js +378 -0
- package/dist/core/rules.d.ts +675 -0
- package/dist/core/rules.js +1049 -0
- package/dist/core/schema.d.ts +358 -0
- package/dist/core/schema.js +105 -0
- package/dist/core/stocks.generated.d.ts +11 -0
- package/dist/core/stocks.generated.js +1228 -0
- package/dist/core/vault.d.ts +59 -0
- package/dist/core/vault.js +112 -0
- package/dist/core/wallet.d.ts +142 -0
- package/dist/core/wallet.js +304 -0
- package/dist/deployments.d.ts +8 -0
- package/dist/deployments.js +26 -0
- package/dist/index.d.ts +42 -15
- package/dist/index.js +40 -15
- package/dist/releases.d.ts +219 -0
- package/dist/releases.js +365 -0
- package/dist/wallet-cap.d.ts +159 -0
- package/dist/wallet-cap.js +234 -0
- package/package.json +46 -10
- package/provenance.json +31 -0
- package/dist/abi.d.ts +0 -6404
- package/dist/abi.js +0 -8300
- package/dist/chain.d.ts +0 -87
- package/dist/chain.js +0 -86
- package/dist/claims.d.ts +0 -32
- package/dist/claims.js +0 -31
- package/dist/fees.d.ts +0 -59
- package/dist/fees.js +0 -142
- package/dist/links.d.ts +0 -48
- package/dist/links.js +0 -93
- package/dist/market.d.ts +0 -264
- package/dist/market.js +0 -310
- package/dist/reads.d.ts +0 -136
- package/dist/reads.js +0 -219
- package/dist/rules.d.ts +0 -84
- package/dist/rules.js +0 -296
- package/dist/swap.d.ts +0 -108
- 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
|
+
}
|