@peddles/sdk 0.2.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 (124) hide show
  1. package/CLAUDE_PROMPT.md +159 -0
  2. package/LICENSE +21 -0
  3. package/README.md +186 -0
  4. package/dist/abis.d.ts +19 -0
  5. package/dist/abis.d.ts.map +1 -0
  6. package/dist/abis.generated.d.ts +2029 -0
  7. package/dist/abis.generated.d.ts.map +1 -0
  8. package/dist/abis.generated.js +2723 -0
  9. package/dist/abis.generated.js.map +1 -0
  10. package/dist/abis.js +19 -0
  11. package/dist/abis.js.map +1 -0
  12. package/dist/artDex.d.ts +937 -0
  13. package/dist/artDex.d.ts.map +1 -0
  14. package/dist/artDex.js +155 -0
  15. package/dist/artDex.js.map +1 -0
  16. package/dist/client.d.ts +10 -0
  17. package/dist/client.d.ts.map +1 -0
  18. package/dist/client.js +2 -0
  19. package/dist/client.js.map +1 -0
  20. package/dist/clog.d.ts +75 -0
  21. package/dist/clog.d.ts.map +1 -0
  22. package/dist/clog.js +91 -0
  23. package/dist/clog.js.map +1 -0
  24. package/dist/clogInflow.d.ts +136 -0
  25. package/dist/clogInflow.d.ts.map +1 -0
  26. package/dist/clogInflow.js +177 -0
  27. package/dist/clogInflow.js.map +1 -0
  28. package/dist/deployments.d.ts +30 -0
  29. package/dist/deployments.d.ts.map +1 -0
  30. package/dist/deployments.generated.d.ts +92 -0
  31. package/dist/deployments.generated.d.ts.map +1 -0
  32. package/dist/deployments.generated.js +93 -0
  33. package/dist/deployments.generated.js.map +1 -0
  34. package/dist/deployments.js +52 -0
  35. package/dist/deployments.js.map +1 -0
  36. package/dist/feeSplit.d.ts +54 -0
  37. package/dist/feeSplit.d.ts.map +1 -0
  38. package/dist/feeSplit.js +69 -0
  39. package/dist/feeSplit.js.map +1 -0
  40. package/dist/feeTerms.d.ts +46 -0
  41. package/dist/feeTerms.d.ts.map +1 -0
  42. package/dist/feeTerms.js +80 -0
  43. package/dist/feeTerms.js.map +1 -0
  44. package/dist/index.d.ts +102 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +108 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/launch/abi.generated.d.ts +2667 -0
  49. package/dist/launch/abi.generated.d.ts.map +1 -0
  50. package/dist/launch/abi.generated.js +2472 -0
  51. package/dist/launch/abi.generated.js.map +1 -0
  52. package/dist/launch/addresses.d.ts +24 -0
  53. package/dist/launch/addresses.d.ts.map +1 -0
  54. package/dist/launch/addresses.js +35 -0
  55. package/dist/launch/addresses.js.map +1 -0
  56. package/dist/launch/feeTerms.d.ts +52 -0
  57. package/dist/launch/feeTerms.d.ts.map +1 -0
  58. package/dist/launch/feeTerms.js +72 -0
  59. package/dist/launch/feeTerms.js.map +1 -0
  60. package/dist/launch/index.d.ts +33 -0
  61. package/dist/launch/index.d.ts.map +1 -0
  62. package/dist/launch/index.js +24 -0
  63. package/dist/launch/index.js.map +1 -0
  64. package/dist/launch/revert.d.ts +23 -0
  65. package/dist/launch/revert.d.ts.map +1 -0
  66. package/dist/launch/revert.js +169 -0
  67. package/dist/launch/revert.js.map +1 -0
  68. package/dist/launch/salt.d.ts +90 -0
  69. package/dist/launch/salt.d.ts.map +1 -0
  70. package/dist/launch/salt.js +153 -0
  71. package/dist/launch/salt.js.map +1 -0
  72. package/dist/launch/stockCall.d.ts +122 -0
  73. package/dist/launch/stockCall.d.ts.map +1 -0
  74. package/dist/launch/stockCall.js +144 -0
  75. package/dist/launch/stockCall.js.map +1 -0
  76. package/dist/launch/tickMath.d.ts +39 -0
  77. package/dist/launch/tickMath.d.ts.map +1 -0
  78. package/dist/launch/tickMath.js +109 -0
  79. package/dist/launch/tickMath.js.map +1 -0
  80. package/dist/launch/variants.d.ts +64 -0
  81. package/dist/launch/variants.d.ts.map +1 -0
  82. package/dist/launch/variants.js +131 -0
  83. package/dist/launch/variants.js.map +1 -0
  84. package/dist/launch/wethCall.d.ts +133 -0
  85. package/dist/launch/wethCall.d.ts.map +1 -0
  86. package/dist/launch/wethCall.js +173 -0
  87. package/dist/launch/wethCall.js.map +1 -0
  88. package/dist/launch/wethPlan.d.ts +71 -0
  89. package/dist/launch/wethPlan.d.ts.map +1 -0
  90. package/dist/launch/wethPlan.js +146 -0
  91. package/dist/launch/wethPlan.js.map +1 -0
  92. package/dist/perps/abi.generated.d.ts +794 -0
  93. package/dist/perps/abi.generated.d.ts.map +1 -0
  94. package/dist/perps/abi.generated.js +738 -0
  95. package/dist/perps/abi.generated.js.map +1 -0
  96. package/dist/perps/index.d.ts +160 -0
  97. package/dist/perps/index.d.ts.map +1 -0
  98. package/dist/perps/index.js +147 -0
  99. package/dist/perps/index.js.map +1 -0
  100. package/package.json +105 -0
  101. package/src/abis.generated.ts +2738 -0
  102. package/src/abis.ts +35 -0
  103. package/src/artDex.ts +254 -0
  104. package/src/client.ts +10 -0
  105. package/src/clog.ts +171 -0
  106. package/src/clogInflow.ts +217 -0
  107. package/src/deployments.generated.ts +98 -0
  108. package/src/deployments.ts +60 -0
  109. package/src/feeSplit.ts +76 -0
  110. package/src/feeTerms.ts +137 -0
  111. package/src/index.ts +233 -0
  112. package/src/launch/abi.generated.ts +2481 -0
  113. package/src/launch/addresses.ts +58 -0
  114. package/src/launch/feeTerms.ts +93 -0
  115. package/src/launch/index.ts +93 -0
  116. package/src/launch/revert.ts +188 -0
  117. package/src/launch/salt.ts +216 -0
  118. package/src/launch/stockCall.ts +223 -0
  119. package/src/launch/tickMath.ts +139 -0
  120. package/src/launch/variants.ts +183 -0
  121. package/src/launch/wethCall.ts +240 -0
  122. package/src/launch/wethPlan.ts +257 -0
  123. package/src/perps/abi.generated.ts +741 -0
  124. package/src/perps/index.ts +263 -0
@@ -0,0 +1,223 @@
1
+ import type { ReadClient } from '../client.js';
2
+ import { encodeFunctionData } from 'viem';
3
+ import type { Abi, Address, Hex } from 'viem';
4
+ import { erc20Abi } from '../abis.js';
5
+ import { stockLaunchpadLaunchAbi } from './abi.generated.js';
6
+ import { validateFeeTerms, type FeeTerms } from './feeTerms.js';
7
+ import type { UnsignedLaunchTx } from './wethCall.js';
8
+
9
+ /**
10
+ * The stock-paired launch call — `PeddlesStockLaunchpad.createCoin` (two
11
+ * overloads) or `createCoinAndBuy`. A pure port of the web app's
12
+ * `buildStockLaunchCall` (`features/launch/useStockLaunch.ts`), held identical by test.
13
+ *
14
+ * dev buy, any salt → createCoinAndBuy(name, symbol, quote, tax, excess, salt, quoteIn, minOut)
15
+ * no dev buy, vanity salt → createCoin(name, symbol, quote, tax, excess, salt)
16
+ * no dev buy, no salt → createCoin(name, symbol, quote, tax, excess) (auto-salt)
17
+ *
18
+ * Every entrypoint is payable and requires `msg.value >= launchFee`. The fee is
19
+ * READ LIVE (`readStockLaunchFee`) and attached exactly; a dev buy pulls the
20
+ * quote asset through `transferFrom`, so the launchpad must be approved first.
21
+ */
22
+
23
+ export interface StockLaunchArgs {
24
+ readonly name: string;
25
+ readonly symbol: string;
26
+ readonly quote: `0x${string}`;
27
+ /** null → the auto-salt overload. */
28
+ readonly salt: `0x${string}` | null;
29
+ /** Quote base units to spend on the dev buy. 0n → no dev buy. */
30
+ readonly quoteIn: bigint;
31
+ /** Slippage floor for the dev buy, in the launched token's base units. */
32
+ readonly minTokensOut: bigint;
33
+ /** The creator's permanent fee terms. `null` is refused, never defaulted. */
34
+ readonly feeTerms: FeeTerms | null;
35
+ }
36
+
37
+ export type StockLaunchCall =
38
+ | {
39
+ readonly kind: 'buy';
40
+ readonly functionName: 'createCoinAndBuy';
41
+ readonly args: readonly [string, string, `0x${string}`, number, number, `0x${string}`, bigint, bigint];
42
+ }
43
+ | {
44
+ readonly kind: 'salt';
45
+ readonly functionName: 'createCoin';
46
+ readonly args: readonly [string, string, `0x${string}`, number, number, `0x${string}`];
47
+ }
48
+ | {
49
+ readonly kind: 'auto';
50
+ readonly functionName: 'createCoin';
51
+ readonly args: readonly [string, string, `0x${string}`, number, number];
52
+ };
53
+
54
+ /** Why these terms cannot ride in a launch, or null when they can. */
55
+ export function stockLaunchTermsIssue(feeTerms: FeeTerms | null): string | null {
56
+ if (feeTerms === null) return 'Choose the trading fee for this token before launching.';
57
+ if (validateFeeTerms(feeTerms) !== null) return 'The trading fee or its split is outside the range a launch can set.';
58
+ return null;
59
+ }
60
+
61
+ export function buildStockLaunchCall({ name, symbol, quote, salt, quoteIn, minTokensOut, feeTerms }: StockLaunchArgs): StockLaunchCall {
62
+ const issue = stockLaunchTermsIssue(feeTerms);
63
+ if (issue !== null) throw new Error(issue);
64
+ if (feeTerms === null) throw new Error('Choose the trading fee for this token before launching.');
65
+ if (quoteIn < 0n || minTokensOut < 0n) throw new Error('A dev-buy amount cannot be negative.');
66
+
67
+ // uint16 on-chain; both are bounded (at most 10000) by `validateFeeTerms`, so the conversion is exact.
68
+ const tax = Number(feeTerms.taxBps);
69
+ const excess = Number(feeTerms.excessToCreatorBps);
70
+ if (quoteIn > 0n) {
71
+ return { kind: 'buy', functionName: 'createCoinAndBuy', args: [name, symbol, quote, tax, excess, salt ?? `0x${'00'.repeat(32)}`, quoteIn, minTokensOut] };
72
+ }
73
+ if (minTokensOut !== 0n) throw new Error('Set a dev-buy amount, or clear the minimum.');
74
+ if (salt !== null) {
75
+ return { kind: 'salt', functionName: 'createCoin', args: [name, symbol, quote, tax, excess, salt] };
76
+ }
77
+ return { kind: 'auto', functionName: 'createCoin', args: [name, symbol, quote, tax, excess] };
78
+ }
79
+
80
+ /** The calldata of a stock launch call. `launchFeeWei` is the live `launchFee()`, attached exactly. */
81
+ export function encodeStockLaunchCall(call: StockLaunchCall, launchpad: `0x${string}`, launchFeeWei: bigint): UnsignedLaunchTx {
82
+ // viem selects the `createCoin` overload by argument count (5 or 6).
83
+ const data = encodeFunctionData({ abi: stockLaunchpadLaunchAbi as Abi, functionName: call.functionName, args: call.args as unknown as unknown[] });
84
+ return { to: launchpad, data, value: launchFeeWei };
85
+ }
86
+
87
+ /** ERC-20 `approve` / `allowance` — the standard shapes; the SDK's read-only `erc20Abi` carries neither. */
88
+ export const erc20ApprovalAbi = [
89
+ {
90
+ type: 'function',
91
+ name: 'approve',
92
+ stateMutability: 'nonpayable',
93
+ inputs: [
94
+ { name: 'spender', type: 'address' },
95
+ { name: 'value', type: 'uint256' },
96
+ ],
97
+ outputs: [{ type: 'bool' }],
98
+ },
99
+ {
100
+ type: 'function',
101
+ name: 'allowance',
102
+ stateMutability: 'view',
103
+ inputs: [
104
+ { name: 'owner', type: 'address' },
105
+ { name: 'spender', type: 'address' },
106
+ ],
107
+ outputs: [{ type: 'uint256' }],
108
+ },
109
+ ] as const;
110
+
111
+ /** The ERC-20 approval a dev buy needs: EXACTLY `quoteIn`, never unlimited. */
112
+ export function encodeStockApproval(quote: `0x${string}`, launchpad: `0x${string}`, quoteIn: bigint): UnsignedLaunchTx {
113
+ return { to: quote, data: encodeFunctionData({ abi: erc20ApprovalAbi, functionName: 'approve', args: [launchpad, quoteIn] }), value: 0n };
114
+ }
115
+
116
+ /** The launchpad's current allowance on `quote` from `owner`. */
117
+ export async function readStockAllowance(client: ReadClient, quote: Address, owner: Address, launchpad: Address): Promise<bigint> {
118
+ return (await client.readContract({ address: quote, abi: erc20ApprovalAbi, functionName: 'allowance', args: [owner, launchpad] })) as bigint;
119
+ }
120
+
121
+ export interface StockLaunchFee {
122
+ /** Exact wei to attach to the launch call. */
123
+ readonly launchFee: bigint;
124
+ /** Immutable per-deployment ceiling — disclosure, not something to send. */
125
+ readonly maxLaunchFee: bigint;
126
+ }
127
+
128
+ /** The launch fee, READ FROM THE CHAIN. Owner-tunable storage under an immutable cap; never a constant. */
129
+ export async function readStockLaunchFee(client: ReadClient, launchpad: Address): Promise<StockLaunchFee> {
130
+ const [launchFee, maxLaunchFee] = (await client.multicall({
131
+ allowFailure: false,
132
+ contracts: [
133
+ { address: launchpad, abi: stockLaunchpadLaunchAbi, functionName: 'launchFee' },
134
+ { address: launchpad, abi: stockLaunchpadLaunchAbi, functionName: 'maxLaunchFee' },
135
+ ],
136
+ })) as readonly [bigint, bigint];
137
+ return { launchFee, maxLaunchFee };
138
+ }
139
+
140
+ export interface StockMiningInputs {
141
+ /** `COIN_VARIANT()` — the variant byte the launchpad binds into the salt. */
142
+ readonly variant: number;
143
+ /** `coinInitCodeHash(name, symbol)` — depends on the name and symbol, so a rename re-mines. */
144
+ readonly initCodeHash: Hex;
145
+ }
146
+
147
+ /** What `mineSalt` needs for a stock coin: the variant and the name/symbol-dependent init code hash. */
148
+ export async function readStockMiningInputs(client: ReadClient, launchpad: Address, name: string, symbol: string): Promise<StockMiningInputs> {
149
+ const [variant, initCodeHash] = (await client.multicall({
150
+ allowFailure: false,
151
+ contracts: [
152
+ { address: launchpad, abi: stockLaunchpadLaunchAbi, functionName: 'COIN_VARIANT' },
153
+ { address: launchpad, abi: stockLaunchpadLaunchAbi, functionName: 'coinInitCodeHash', args: [name, symbol] },
154
+ ],
155
+ })) as readonly [number, Hex];
156
+ return { variant: Number(variant), initCodeHash };
157
+ }
158
+
159
+ /** The launchpad's own prediction for (creator, salt, name, symbol) — the confirmation a mined salt needs. */
160
+ export async function predictStockCoin(client: ReadClient, launchpad: Address, creator: Address, salt: Hex, name: string, symbol: string): Promise<Address> {
161
+ return (await client.readContract({ address: launchpad, abi: stockLaunchpadLaunchAbi, functionName: 'predictCoin', args: [creator, salt, name, symbol] })) as Address;
162
+ }
163
+
164
+ /** One whitelisted quote leg, as the launchpad and its own ERC-20 describe it. */
165
+ export interface StockQuote {
166
+ readonly address: Address;
167
+ /** Position in `allQuotes` — append-ordered, the only recency signal the chain offers. */
168
+ readonly registryIndex: number;
169
+ /** On-chain symbol; `null` when the ERC-20 did not answer. Never defaulted. */
170
+ readonly symbol: string | null;
171
+ readonly name: string | null;
172
+ /** On-chain decimals; `null` when unreadable. Never defaulted. */
173
+ readonly decimals: number | null;
174
+ /** `quoteConfig.startTick` — the launch price, set by the launchpad owner. */
175
+ readonly startTick: number;
176
+ }
177
+
178
+ /**
179
+ * The stock-paired launch whitelist — READ FROM THE CHAIN (`quoteCount` / `allQuotes`,
180
+ * each gated on `quoteConfig(quote).allowed`), identity read off each ERC-20. An entry whose
181
+ * identity cannot be read is kept and marked unresolved, never dropped or defaulted.
182
+ */
183
+ export async function readStockQuotes(client: ReadClient, launchpad: Address): Promise<readonly StockQuote[]> {
184
+ const count = Number(await client.readContract({ address: launchpad, abi: stockLaunchpadLaunchAbi, functionName: 'quoteCount' }));
185
+ if (count === 0) return [];
186
+ const addresses = (await client.multicall({
187
+ allowFailure: false,
188
+ contracts: Array.from({ length: count }, (_, i) => ({ address: launchpad, abi: stockLaunchpadLaunchAbi, functionName: 'allQuotes' as const, args: [BigInt(i)] as const })),
189
+ })) as readonly Address[];
190
+ const configs = (await client.multicall({
191
+ allowFailure: false,
192
+ contracts: addresses.map((quote) => ({ address: launchpad, abi: stockLaunchpadLaunchAbi, functionName: 'quoteConfig' as const, args: [quote] as const })),
193
+ })) as readonly (readonly [boolean, boolean, number])[];
194
+ const allowed = addresses.map((address, i) => ({ address, i, config: configs[i]! })).filter(({ config }) => config[0]);
195
+ if (allowed.length === 0) return [];
196
+ const identity = await client.multicall({
197
+ allowFailure: true,
198
+ contracts: allowed.flatMap(({ address }) => [
199
+ { address, abi: erc20Abi, functionName: 'symbol' as const },
200
+ { address, abi: erc20Abi, functionName: 'name' as const },
201
+ { address, abi: erc20Abi, functionName: 'decimals' as const },
202
+ ]),
203
+ });
204
+ return allowed.map(({ address, i, config }, k) => {
205
+ const sym = identity[k * 3]!;
206
+ const nm = identity[k * 3 + 1]!;
207
+ const dec = identity[k * 3 + 2]!;
208
+ return {
209
+ address,
210
+ registryIndex: i,
211
+ symbol: sym.status === 'success' ? String(sym.result) : null,
212
+ name: nm.status === 'success' ? String(nm.result) : null,
213
+ decimals: dec.status === 'success' ? Number(dec.result) : null,
214
+ startTick: Number(config[2]),
215
+ };
216
+ });
217
+ }
218
+
219
+ /** Whether the launchpad allows `quote` right now. */
220
+ export async function isQuoteAllowed(client: ReadClient, launchpad: Address, quote: Address): Promise<boolean> {
221
+ const [allowed] = (await client.readContract({ address: launchpad, abi: stockLaunchpadLaunchAbi, functionName: 'quoteConfig', args: [quote] })) as readonly [boolean, boolean, number];
222
+ return allowed;
223
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Uniswap v4 tick maths, in `bigint` — a pure port of the web app's
3
+ * `features/launch/tickMath.ts`.
4
+ *
5
+ * The WETH-type launch (`PeddlesLaunchOrchestratorV20.launch`) makes the CALLER do
6
+ * the pool maths: `LaunchInput` carries `sqrtPriceX96`, `tickLower`, `tickUpper`
7
+ * and `liquidity`, and the executor mints exactly the position those describe.
8
+ * Get `liquidity` one wei too high and `modifyLiquidities` reverts.
9
+ *
10
+ * - `getSqrtPriceAtTick` ← `StockTickMath.getSqrtPriceAtTick`
11
+ * - `liquidityForAmount` ← `_liquidityFor` / `_amountForLiquidity` (VerifyLive.s.sol),
12
+ * Uniswap's `getLiquidityForAmount0/1` plus the round-down
13
+ * shave the mint's round-UP demands.
14
+ *
15
+ * `number` appears nowhere below the tick index itself (`int24`). Every price and
16
+ * amount is `bigint`.
17
+ */
18
+
19
+ export const MIN_TICK = -887272;
20
+ export const MAX_TICK = 887272;
21
+ export const Q96 = 1n << 96n;
22
+
23
+ const UINT256_MAX = (1n << 256n) - 1n;
24
+ const UINT160_MAX = (1n << 160n) - 1n;
25
+ const UINT128_MAX = (1n << 128n) - 1n;
26
+
27
+ /** The exact magic-constant ladder from `StockTickMath`, in bit order. */
28
+ const RATIOS: readonly [bigint, bigint][] = [
29
+ [0x2n, 0xfff97272373d413259a46990580e213an],
30
+ [0x4n, 0xfff2e50f5f656932ef12357cf3c7fdccn],
31
+ [0x8n, 0xffe5caca7e10e4e61c3624eaa0941cd0n],
32
+ [0x10n, 0xffcb9843d60f6159c9db58835c926644n],
33
+ [0x20n, 0xff973b41fa98c081472e6896dfb254c0n],
34
+ [0x40n, 0xff2ea16466c96a3843ec78b326b52861n],
35
+ [0x80n, 0xfe5dee046a99a2a811c461f1969c3053n],
36
+ [0x100n, 0xfcbe86c7900a88aedcffc83b479aa3a4n],
37
+ [0x200n, 0xf987a7253ac413176f2b074cf7815e54n],
38
+ [0x400n, 0xf3392b0822b70005940c7a398e4b70f3n],
39
+ [0x800n, 0xe7159475a2c29b7443b29c7fa6e889d9n],
40
+ [0x1000n, 0xd097f3bdfd2022b8845ad8f792aa5825n],
41
+ [0x2000n, 0xa9f746462d870fdf8a65dc1f90e061e5n],
42
+ [0x4000n, 0x70d869a156d2a1b890bb3df62baf32f7n],
43
+ [0x8000n, 0x31be135f97d08fd981231505542fcfa6n],
44
+ [0x10000n, 0x9aa508b5b7a84e1c677de54f3e99bc9n],
45
+ [0x20000n, 0x5d6af8dedb81196699c329225ee604n],
46
+ [0x40000n, 0x2216e584f5fa1ea926041bedfe98n],
47
+ [0x80000n, 0x48a170391f7dc42444e8fa2n],
48
+ ];
49
+
50
+ /** `sqrt(1.0001^tick) * 2^96`, identical to the on-chain implementation. Throws on an out-of-range tick. */
51
+ export function getSqrtPriceAtTick(tick: number): bigint {
52
+ if (!Number.isInteger(tick)) throw new Error(`tick must be an integer, got ${tick}`);
53
+ const absTick = BigInt(Math.abs(tick));
54
+ if (absTick > BigInt(MAX_TICK)) throw new Error(`tick ${tick} is outside the v4 range`);
55
+
56
+ let price =
57
+ (absTick & 0x1n) !== 0n
58
+ ? 0xfffcb933bd6fad37aa2d162d1a594001n
59
+ : 0x100000000000000000000000000000000n;
60
+
61
+ for (const [bit, ratio] of RATIOS) {
62
+ if ((absTick & bit) !== 0n) price = (price * ratio) >> 128n;
63
+ }
64
+
65
+ if (tick > 0) price = UINT256_MAX / price;
66
+
67
+ const sqrtPriceX96 = (price >> 32n) + ((price & 0xffffffffn) === 0n ? 0n : 1n);
68
+ if (sqrtPriceX96 > UINT160_MAX) throw new Error('sqrtPriceX96 overflowed uint160');
69
+ return sqrtPriceX96;
70
+ }
71
+
72
+ function mulDivUp(a: bigint, b: bigint, denominator: bigint): bigint {
73
+ const product = a * b;
74
+ return product / denominator + (product % denominator === 0n ? 0n : 1n);
75
+ }
76
+
77
+ /** Token amount a position of `liquidity` over [sqrtA, sqrtB] costs, rounded UP — how the mint charges. */
78
+ export function amountForLiquidity(
79
+ tokenIsCurrency0: boolean,
80
+ sqrtA: bigint,
81
+ sqrtB: bigint,
82
+ liquidity: bigint,
83
+ ): bigint {
84
+ if (tokenIsCurrency0) {
85
+ const numerator = mulDivUp(liquidity << 96n, sqrtB - sqrtA, sqrtB);
86
+ return numerator / sqrtA + (numerator % sqrtA === 0n ? 0n : 1n);
87
+ }
88
+ return mulDivUp(liquidity, sqrtB - sqrtA, Q96);
89
+ }
90
+
91
+ export interface LiquidityQuote {
92
+ readonly liquidity: bigint;
93
+ readonly sqrtLower: bigint;
94
+ readonly sqrtUpper: bigint;
95
+ /** What the mint will actually pull, rounded up. Always <= the allocation. */
96
+ readonly amountUsed: bigint;
97
+ }
98
+
99
+ /**
100
+ * The largest liquidity whose rounded-up token requirement still fits `amount`.
101
+ * Returns `null` when no liquidity fits — the caller must then refuse the launch, never guess.
102
+ */
103
+ export function liquidityForAmount(
104
+ tokenIsCurrency0: boolean,
105
+ tickLower: number,
106
+ tickUpper: number,
107
+ amount: bigint,
108
+ ): LiquidityQuote | null {
109
+ if (amount <= 0n) return null;
110
+
111
+ const sqrtLower = getSqrtPriceAtTick(tickLower);
112
+ const sqrtUpper = getSqrtPriceAtTick(tickUpper);
113
+ if (sqrtUpper <= sqrtLower) return null;
114
+
115
+ let liquidity = tokenIsCurrency0
116
+ ? (amount * ((sqrtLower * sqrtUpper) / Q96)) / (sqrtUpper - sqrtLower)
117
+ : (amount * Q96) / (sqrtUpper - sqrtLower);
118
+
119
+ if (liquidity > UINT128_MAX) liquidity = UINT128_MAX;
120
+
121
+ for (
122
+ let i = 0;
123
+ i < 512 && liquidity > 0n && amountForLiquidity(tokenIsCurrency0, sqrtLower, sqrtUpper, liquidity) > amount;
124
+ i += 1
125
+ ) {
126
+ liquidity -= 1n;
127
+ }
128
+
129
+ if (liquidity === 0n) return null;
130
+ const amountUsed = amountForLiquidity(tokenIsCurrency0, sqrtLower, sqrtUpper, liquidity);
131
+ if (amountUsed > amount) return null;
132
+
133
+ return { liquidity, sqrtLower, sqrtUpper, amountUsed };
134
+ }
135
+
136
+ /** `sqrtPriceX96` for a token/quote pair at `startTick`, mirroring `startSqrtPriceFor`. */
137
+ export function startSqrtPrice(startTick: number, tokenIsCurrency0: boolean): bigint {
138
+ return getSqrtPriceAtTick(tokenIsCurrency0 ? startTick : -startTick);
139
+ }
@@ -0,0 +1,183 @@
1
+ import type { ReadClient } from '../client.js';
2
+ import { factoryAbi } from '../abis.js';
3
+ import { launchOrchestratorAbi } from './abi.generated.js';
4
+ import type { LaunchAddresses } from './addresses.js';
5
+
6
+ /**
7
+ * The launch types the factory will take a WETH-type launch for, READ FROM THE
8
+ * CHAIN. A port of the web's `useLaunchVariants` + `variantTerms`.
9
+ *
10
+ * The factory's registry is keyed by a uint8 with no enumerator, so the whole
11
+ * id space is read in ONE multicall (`variantInfo(1..255)`, batching off). Type 0
12
+ * is the built-in type; it is never registered and launches against the
13
+ * orchestrator's own owner-set defaults, so that is what is read for it.
14
+ *
15
+ * FAIL CLOSED: any failed read makes the whole list unavailable (the multicall
16
+ * throws). A partial list is not offered.
17
+ */
18
+
19
+ /** The factory's permanently reserved asset variant. */
20
+ export const VARIANT_ASSET = 0;
21
+
22
+ export const BPS_DENOMINATOR = 10_000n;
23
+
24
+ export interface VariantAllocation {
25
+ readonly liquidityBps: bigint;
26
+ readonly airdropBps: bigint;
27
+ readonly vestingBps: bigint;
28
+ readonly burnBps: bigint;
29
+ readonly clogBps: bigint;
30
+ /** Per-release cap, in bps of TOTAL supply. */
31
+ readonly clogSliceBps: bigint;
32
+ readonly clogMinIntervalSeconds: bigint;
33
+ }
34
+
35
+ export type LaunchTypeShape = 'full-liquidity' | 'clog';
36
+
37
+ export type ResolvedTerms =
38
+ | { readonly ok: true; readonly shape: LaunchTypeShape; readonly allocation: VariantAllocation }
39
+ | { readonly ok: false; readonly reason: string };
40
+
41
+ /** Can this split be launched and described? Mirrors what the contracts refuse. */
42
+ export function resolveTerms(allocation: VariantAllocation): ResolvedTerms {
43
+ const { liquidityBps, airdropBps, vestingBps, burnBps, clogBps, clogSliceBps, clogMinIntervalSeconds } = allocation;
44
+ const sum = liquidityBps + airdropBps + vestingBps + burnBps + clogBps;
45
+ if (sum !== BPS_DENOMINATOR) {
46
+ return { ok: false, reason: 'This launch type’s split does not add up to the whole supply, so it cannot launch.' };
47
+ }
48
+ if (liquidityBps <= 0n) {
49
+ return { ok: false, reason: 'This launch type puts nothing into the pool, so it cannot launch.' };
50
+ }
51
+ if (airdropBps !== 0n || vestingBps !== 0n || burnBps !== 0n) {
52
+ return { ok: false, reason: 'This launch type airdrops, vests or burns supply, which Peddles does not offer.' };
53
+ }
54
+ if (clogBps > 0n && (clogSliceBps <= 0n || clogMinIntervalSeconds <= 0n)) {
55
+ return { ok: false, reason: 'This launch type holds supply back with no limit on how it is sold, so it cannot launch.' };
56
+ }
57
+ return { ok: true, shape: clogBps > 0n ? 'clog' : 'full-liquidity', allocation };
58
+ }
59
+
60
+ /** A launch type, as the chain describes it. */
61
+ export interface LaunchVariant {
62
+ /** The factory's uint8 id. Carried into the launch; never shown as a name. */
63
+ readonly id: number;
64
+ /** Retired types stay registered but take no new launches. */
65
+ readonly enabled: boolean;
66
+ readonly allocation: VariantAllocation;
67
+ readonly terms: ResolvedTerms;
68
+ /** Null when this type can be launched on this chain right now. */
69
+ readonly refusal: string | null;
70
+ }
71
+
72
+ const ZERO = '0x0000000000000000000000000000000000000000';
73
+ /** Registrable ids are 1..255; 0 is reserved for the built-in type. */
74
+ const REGISTRABLE_IDS = Array.from({ length: 255 }, (_, i) => i + 1);
75
+
76
+ interface RawAllocation {
77
+ readonly liquidityBps: number;
78
+ readonly airdropBps: number;
79
+ readonly vestingBps: number;
80
+ readonly burnBps: number;
81
+ readonly clogBps: number;
82
+ readonly clogSliceBps: number;
83
+ readonly clogMinInterval: bigint;
84
+ }
85
+
86
+ function toAllocation(raw: RawAllocation): VariantAllocation {
87
+ return {
88
+ liquidityBps: BigInt(raw.liquidityBps),
89
+ airdropBps: BigInt(raw.airdropBps),
90
+ vestingBps: BigInt(raw.vestingBps),
91
+ burnBps: BigInt(raw.burnBps),
92
+ clogBps: BigInt(raw.clogBps),
93
+ clogSliceBps: BigInt(raw.clogSliceBps),
94
+ clogMinIntervalSeconds: BigInt(raw.clogMinInterval),
95
+ };
96
+ }
97
+
98
+ function refusalFor(enabled: boolean, terms: ResolvedTerms, clogVaultReady: boolean): string | null {
99
+ if (!enabled) return 'This launch type is not open for new launches.';
100
+ if (!terms.ok) return terms.reason;
101
+ if (terms.shape === 'clog' && !clogVaultReady) {
102
+ return 'This launch type withholds supply into a clog vault, and no clog vault factory is deployed on this chain.';
103
+ }
104
+ return null;
105
+ }
106
+
107
+ /** Every launch type on this chain, type 0 first, then registered ids ascending. */
108
+ export async function readLaunchVariants(client: ReadClient, addresses: LaunchAddresses): Promise<readonly LaunchVariant[]> {
109
+ const { factory, orchestrator } = addresses;
110
+ const infos = (await client.multicall({
111
+ allowFailure: false,
112
+ batchSize: 0,
113
+ contracts: REGISTRABLE_IDS.map((id) => ({ address: factory, abi: factoryAbi, functionName: 'variantInfo' as const, args: [id] as const })),
114
+ })) as readonly (readonly [`0x${string}`, `0x${string}`, boolean])[];
115
+
116
+ const registered = REGISTRABLE_IDS.map((id, i) => ({ id, info: infos[i]! })).filter(({ info }) => info[0] !== ZERO);
117
+
118
+ const [defaults, allocations] = await Promise.all([
119
+ client.multicall({
120
+ allowFailure: false,
121
+ contracts: [
122
+ { address: orchestrator, abi: launchOrchestratorAbi, functionName: 'defaultLiquidityBps' },
123
+ { address: orchestrator, abi: launchOrchestratorAbi, functionName: 'defaultAirdropBps' },
124
+ { address: orchestrator, abi: launchOrchestratorAbi, functionName: 'defaultVestingBps' },
125
+ { address: orchestrator, abi: launchOrchestratorAbi, functionName: 'defaultBurnBps' },
126
+ { address: orchestrator, abi: launchOrchestratorAbi, functionName: 'clogVaultFactory' },
127
+ ],
128
+ }) as Promise<readonly [number, number, number, number, `0x${string}`]>,
129
+ registered.length === 0
130
+ ? Promise.resolve([] as readonly RawAllocation[])
131
+ : (client.multicall({
132
+ allowFailure: false,
133
+ batchSize: 0,
134
+ contracts: registered.map(({ id }) => ({ address: factory, abi: factoryAbi, functionName: 'variantAllocation' as const, args: [id] as const })),
135
+ }) as Promise<readonly RawAllocation[]>),
136
+ ]);
137
+
138
+ const [liquidityBps, airdropBps, vestingBps, burnBps, clogVaultFactory] = defaults;
139
+ const clogVaultReady = clogVaultFactory !== ZERO && addresses.clogVaultFactory !== null;
140
+
141
+ const rows = [
142
+ {
143
+ id: VARIANT_ASSET,
144
+ enabled: true,
145
+ allocation: {
146
+ liquidityBps: BigInt(liquidityBps),
147
+ airdropBps: BigInt(airdropBps),
148
+ vestingBps: BigInt(vestingBps),
149
+ burnBps: BigInt(burnBps),
150
+ clogBps: 0n,
151
+ clogSliceBps: 0n,
152
+ clogMinIntervalSeconds: 0n,
153
+ },
154
+ },
155
+ ...registered.map(({ id, info }, i) => ({ id, enabled: info[2], allocation: toAllocation(allocations[i]!) })),
156
+ ];
157
+
158
+ return rows.map((row) => {
159
+ const terms = resolveTerms(row.allocation);
160
+ return { id: row.id, enabled: row.enabled, allocation: row.allocation, terms, refusal: refusalFor(row.enabled, terms, clogVaultReady) };
161
+ });
162
+ }
163
+
164
+ /** The public launch type names the REST API, the CLI and the widget speak. */
165
+ export type LaunchTypeName = 'standard' | 'clog' | 'stock';
166
+
167
+ /**
168
+ * The variant a public type name resolves to on this chain, or a refusal.
169
+ * `standard` is always type 0; `clog` is the ONE enabled registered clog-shaped
170
+ * type — two of them is ambiguous and is refused rather than picked between.
171
+ */
172
+ export function variantForType(variants: readonly LaunchVariant[], type: 'standard' | 'clog'): { variant: LaunchVariant } | { refusal: string } {
173
+ if (type === 'standard') {
174
+ const v = variants.find((x) => x.id === VARIANT_ASSET);
175
+ if (!v) return { refusal: 'The built-in launch type is not available on this chain.' };
176
+ return v.refusal ? { refusal: v.refusal } : { variant: v };
177
+ }
178
+ const clogs = variants.filter((x) => x.id !== VARIANT_ASSET && x.terms.ok && x.terms.shape === 'clog' && x.enabled);
179
+ if (clogs.length === 0) return { refusal: 'No Drip launch type is open on this chain.' };
180
+ if (clogs.length > 1) return { refusal: 'More than one Drip launch type is open on this chain; pass the variant id explicitly.' };
181
+ const v = clogs[0]!;
182
+ return v.refusal ? { refusal: v.refusal } : { variant: v };
183
+ }