@pickhooks/sdk 0.0.0-stage → 0.4.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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +418 -3
  3. package/dist/abis/agents.d.ts +2418 -0
  4. package/dist/abis/agents.js +4 -0
  5. package/dist/abis/any3.d.ts +2123 -0
  6. package/dist/abis/any3.js +3 -0
  7. package/dist/abis/anytax.d.ts +1786 -0
  8. package/dist/abis/anytax.js +3 -0
  9. package/dist/abis/calls.d.ts +2515 -0
  10. package/dist/abis/calls.js +4 -0
  11. package/dist/abis/common.d.ts +449 -0
  12. package/dist/abis/common.js +91 -0
  13. package/dist/abis/dips.d.ts +897 -0
  14. package/dist/abis/dips.js +2 -0
  15. package/dist/abis/index.d.ts +14 -0
  16. package/dist/abis/index.js +14 -0
  17. package/dist/abis/infra.d.ts +701 -0
  18. package/dist/abis/infra.js +4 -0
  19. package/dist/abis/lever.d.ts +1710 -0
  20. package/dist/abis/lever.js +5 -0
  21. package/dist/abis/lever2.d.ts +2710 -0
  22. package/dist/abis/lever2.js +5 -0
  23. package/dist/abis/pools.d.ts +1898 -0
  24. package/dist/abis/pools.js +4 -0
  25. package/dist/abis/sticky.d.ts +953 -0
  26. package/dist/abis/sticky.js +4 -0
  27. package/dist/abis/sticky2.d.ts +3750 -0
  28. package/dist/abis/sticky2.js +8 -0
  29. package/dist/abis/v1.d.ts +1627 -0
  30. package/dist/abis/v1.js +5 -0
  31. package/dist/abis/v2.d.ts +3264 -0
  32. package/dist/abis/v2.js +8 -0
  33. package/dist/addresses.d.ts +100 -0
  34. package/dist/addresses.js +71 -0
  35. package/dist/agents-catalog.d.ts +318 -0
  36. package/dist/agents-catalog.js +353 -0
  37. package/dist/agents.d.ts +543 -0
  38. package/dist/agents.js +667 -0
  39. package/dist/any.d.ts +447 -0
  40. package/dist/any.js +729 -0
  41. package/dist/api.d.ts +62 -0
  42. package/dist/api.js +30 -0
  43. package/dist/blocks.d.ts +107 -0
  44. package/dist/blocks.js +126 -0
  45. package/dist/coin-rules-config.d.ts +8 -0
  46. package/dist/coin-rules-config.js +13 -0
  47. package/dist/coin-rules.d.ts +383 -0
  48. package/dist/coin-rules.js +878 -0
  49. package/dist/coins.d.ts +217 -0
  50. package/dist/coins.js +520 -0
  51. package/dist/desks.d.ts +298 -0
  52. package/dist/desks.js +343 -0
  53. package/dist/dips.d.ts +133 -0
  54. package/dist/dips.js +148 -0
  55. package/dist/errors.d.ts +35 -0
  56. package/dist/errors.js +414 -0
  57. package/dist/index.d.ts +104 -0
  58. package/dist/index.js +91 -0
  59. package/dist/infinity.d.ts +63 -0
  60. package/dist/infinity.js +96 -0
  61. package/dist/infra.d.ts +112 -0
  62. package/dist/infra.js +114 -0
  63. package/dist/lever.d.ts +391 -0
  64. package/dist/lever.js +419 -0
  65. package/dist/pools.d.ts +274 -0
  66. package/dist/pools.js +405 -0
  67. package/dist/sticky-rules.d.ts +179 -0
  68. package/dist/sticky-rules.js +580 -0
  69. package/dist/sticky-sim.d.ts +125 -0
  70. package/dist/sticky-sim.js +637 -0
  71. package/dist/sticky.d.ts +286 -0
  72. package/dist/sticky.js +420 -0
  73. package/dist/tlock.d.ts +67 -0
  74. package/dist/tlock.js +185 -0
  75. package/dist/tx.d.ts +72 -0
  76. package/dist/tx.js +78 -0
  77. package/dist/v2.d.ts +106 -0
  78. package/dist/v2.js +61 -0
  79. package/dist/v3.d.ts +42 -0
  80. package/dist/v3.js +98 -0
  81. package/dist/wallet.d.ts +80 -0
  82. package/dist/wallet.js +115 -0
  83. package/dist/watch.d.ts +82 -0
  84. package/dist/watch.js +194 -0
  85. package/package.json +67 -4
package/dist/coins.js ADDED
@@ -0,0 +1,520 @@
1
+ import { encodeFunctionData, isAddressEqual, parseAbi, parseEventLogs } from "viem";
2
+ import { pancakeV2FactoryAbi, v3PoolAbi } from "./abis/common.js";
3
+ import { v1BuySimCode, v1FactoryAbi, v1LockerAbi, v1TokenAbi } from "./abis/v1.js";
4
+ import { arenaAbi, floorVaultAbi, payrollBoxAbi, v2FactoryAbi, v2LockerAbi, v2TokenAbi, voteBoxAbi } from "./abis/v2.js";
5
+ import { ZERO_ADDRESS } from "./addresses.js";
6
+ import { RULE_BY_KIND, buildRules, emptyExtras, kindsOf, launchPots, selectionProblem, worstScanner } from "./coin-rules.js";
7
+ import { PickhooksError, explain, toPickhooksError } from "./errors.js";
8
+ import { blockedKind } from "./infinity.js";
9
+ import { minusBps, sendTx, signer } from "./tx.js";
10
+ import { approvalFor, buildV3Buy, buildV3Sell, receivedOf, v3EstimateBuy, v3PriceBnb, v3Quote } from "./v3.js";
11
+ const simAbi = parseAbi(["function sim(address token, address to, uint256 amount)"]);
12
+ const pancakeV2RouterAbi = parseAbi(["function getAmountsIn(uint256 amountOut, address[] path) view returns (uint256[] amounts)"]);
13
+ const erc20Transfer = parseAbi(["function transfer(address to, uint256 amount) returns (bool)"]);
14
+ const randomSalt = () => {
15
+ const b = new Uint8Array(32);
16
+ globalThis.crypto.getRandomValues(b);
17
+ return `0x${Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("")}`;
18
+ };
19
+ /** A rule's sentence for a refusal: its name and the rule with the coin's numbers. */
20
+ export function ruleText(kind, rules, extras) {
21
+ const def = RULE_BY_KIND.get(kind);
22
+ if (!def)
23
+ return { name: `Rule ${kind}`, text: `Refused by rule ${kind}.` };
24
+ let line = def.line;
25
+ if (rules) {
26
+ try {
27
+ line = def.describe(rules, extras ?? undefined);
28
+ }
29
+ catch {
30
+ /* a v2 rule read without its extras */
31
+ }
32
+ }
33
+ return { name: def.name, text: `Refused by the rule "${def.name}". ${line}` };
34
+ }
35
+ export function coinsModule(ctx) {
36
+ const { pub, a } = ctx;
37
+ const ABI = { 1: v1TokenAbi, 2: v2TokenAbi };
38
+ const FACTORY = {
39
+ 1: { address: a.coinsV1Factory, abi: v1FactoryAbi },
40
+ 2: { address: a.coinsV2Factory, abi: v2FactoryAbi },
41
+ };
42
+ const versions = new Map();
43
+ const call = (address, abi, functionName, args = []) => pub.readContract({ address, abi, functionName, args });
44
+ /** 2 for a v2 coin, 1 for a v1 coin, null for neither. */
45
+ async function versionOf(token) {
46
+ const hit = versions.get(token.toLowerCase());
47
+ if (hit)
48
+ return hit;
49
+ for (const v of [2, 1]) {
50
+ const is = await call(FACTORY[v].address, FACTORY[v].abi, "isCoin", [token]).catch(() => false);
51
+ if (is) {
52
+ versions.set(token.toLowerCase(), v);
53
+ return v;
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ async function need(token) {
59
+ const v = await versionOf(token);
60
+ if (!v)
61
+ throw new PickhooksError("This is not a pickhooks coin with rules in the coin.", { code: "input" });
62
+ return v;
63
+ }
64
+ /** How many coins a factory launched. */
65
+ const count = async (version = 2) => Number(await call(FACTORY[version].address, FACTORY[version].abi, "coinCount"));
66
+ async function tokensOf(version, limit, offset) {
67
+ const n = await count(version);
68
+ const end = Math.max(0, n - offset);
69
+ const from = Math.max(0, end - limit);
70
+ if (end <= from)
71
+ return [];
72
+ const list = (await pub.multicall({
73
+ contracts: Array.from({ length: end - from }, (_, i) => ({ address: FACTORY[version].address, abi: FACTORY[version].abi, functionName: "coins", args: [BigInt(end - 1 - i)] })),
74
+ allowFailure: false,
75
+ }));
76
+ for (const t of list)
77
+ versions.set(t.toLowerCase(), version);
78
+ return list;
79
+ }
80
+ /** Coins newest first: `limit` of each version (v2 and v1 merged by launch time, or one version). */
81
+ async function coins(o = {}) {
82
+ const vs = o.version ? [o.version] : [2, 1];
83
+ const lists = await Promise.all(vs.map(async (v) => summaries(v, await tokensOf(v, o.limit ?? 50, o.offset ?? 0))));
84
+ return lists.flat().sort((x, y) => y.start - x.start);
85
+ }
86
+ /** One coin, or null when it is not one of the two factories' coins. */
87
+ async function coin(token) {
88
+ const v = await versionOf(token);
89
+ if (!v)
90
+ return null;
91
+ return (await summaries(v, [token]))[0] ?? null;
92
+ }
93
+ const FIELDS = ["name", "symbol", "metadata", "creator", "pool", "pool2", "start", "rules", "stats", "curveSupply", "jackpotPot", "jackpotLast", "jackpotTime", "rewardsPot"];
94
+ async function summaries(version, tokens) {
95
+ if (!tokens.length)
96
+ return [];
97
+ const abi = ABI[version];
98
+ const per = FIELDS.length;
99
+ const [res, head] = await Promise.all([
100
+ pub.multicall({ contracts: tokens.flatMap((t) => FIELDS.map((functionName) => ({ address: t, abi, functionName }))), allowFailure: true }),
101
+ pub.getBlock({ blockTag: "latest" }),
102
+ ]);
103
+ const now = Number(head.timestamp);
104
+ const pools = tokens.map((_, i) => res[i * per + 4].result);
105
+ const masks = tokens.map((_, i) => (res[i * per + 7].result?.mask ?? 0n));
106
+ const kindsAll = masks.map((m) => kindsOf(m));
107
+ const [slots, untils, offs] = await Promise.all([
108
+ pub.multicall({ contracts: pools.map((p) => ({ address: (p ?? a.wbnb), abi: v3PoolAbi, functionName: "slot0" })), allowFailure: true }),
109
+ pub.multicall({
110
+ contracts: tokens.flatMap((t, i) => kindsAll[i].map((k) => ({ address: t, abi, functionName: "until", args: [k] }))),
111
+ allowFailure: true,
112
+ }),
113
+ version === 2 ? pub.multicall({ contracts: tokens.map((t) => ({ address: t, abi, functionName: "off" })), allowFailure: true }) : Promise.resolve([]),
114
+ ]);
115
+ const extrasAll = version === 2 ? await Promise.all(tokens.map((t, i) => extrasOf(t, masks[i]).catch(() => null))) : tokens.map(() => null);
116
+ const out = [];
117
+ let u = 0;
118
+ tokens.forEach((token, i) => {
119
+ const g = (k) => res[i * per + k];
120
+ const kinds = kindsAll[i];
121
+ const untilOf = kinds.map(() => untils[u++]);
122
+ if (g(0).status !== "success" || !pools[i] || g(7).status !== "success")
123
+ return;
124
+ const rules = g(7).result;
125
+ const stats = g(8).result;
126
+ const curve = g(9).result ?? 0n;
127
+ const off = version === 2 && offs[i]?.status === "success" ? kindsOf(offs[i].result) : [];
128
+ const x = extrasAll[i];
129
+ const sl = slots[i];
130
+ const priceBnb = sl.status === "success" ? v3PriceBnb(sl.result[0], token, a.wbnb) : 0;
131
+ out.push({
132
+ token,
133
+ version,
134
+ name: String(g(0).result ?? ""),
135
+ symbol: String(g(1).result ?? ""),
136
+ metadata: String(g(2).result ?? ""),
137
+ creator: g(3).result,
138
+ pool: pools[i],
139
+ pair: g(5).result ?? ZERO_ADDRESS,
140
+ start: Number(g(6).result ?? 0),
141
+ rules: kinds.map((kind, j) => {
142
+ const def = RULE_BY_KIND.get(kind);
143
+ const until = untilOf[j]?.status === "success" ? Number(untilOf[j].result) : 0;
144
+ let text = def?.line ?? "";
145
+ try {
146
+ if (def)
147
+ text = def.describe(rules, x ?? undefined);
148
+ }
149
+ catch {
150
+ /* keep the line */
151
+ }
152
+ return { kind, name: def?.name ?? `Rule ${kind}`, text, scanner: def?.scanner.level ?? "green", until, ended: off.includes(kind) || (until !== 0 && now >= until) };
153
+ }),
154
+ scanner: worstScanner(kinds),
155
+ raw: rules,
156
+ extras: x,
157
+ holders: Number(stats[0]),
158
+ buys: Number(stats[1]),
159
+ sells: Number(stats[2]),
160
+ soldBps: curve ? Number((stats[11] * 10000n) / curve) : 0,
161
+ jackpotPot: g(10).result ?? 0n,
162
+ jackpotLast: g(11).result ?? ZERO_ADDRESS,
163
+ jackpotTime: Number(g(12).result ?? 0),
164
+ rewardsPot: g(13).result ?? 0n,
165
+ priceBnb,
166
+ mcapBnb: priceBnb * 1e9,
167
+ chainNow: now,
168
+ });
169
+ });
170
+ return out;
171
+ }
172
+ let termsCache = null;
173
+ /** The v2 factory's terms and its companions (vote box, duel arena, floor vault, payroll box, locker). */
174
+ function terms() {
175
+ termsCache ??= (async () => {
176
+ const fns = ["hooksBurn", "creatorBps", "seedBnb", "hooks", "voteBox", "arena", "floorVault", "payrollBox", "locker"];
177
+ const r = await pub.multicall({ contracts: fns.map((functionName) => ({ address: a.coinsV2Factory, abi: FACTORY[2].abi, functionName })), allowFailure: false });
178
+ return { hooksBurn: r[0], creatorBps: Number(r[1]), seedBnb: r[2], hooks: r[3], voteBox: r[4], arena: r[5], floorVault: r[6], payrollBox: r[7], locker: r[8] };
179
+ })().catch((e) => {
180
+ termsCache = null;
181
+ throw e;
182
+ });
183
+ return termsCache;
184
+ }
185
+ /** The v2 settings a coin launched with: what its v2 rules' sentences and buttons need. */
186
+ async function extrasOf(token, mask) {
187
+ const has = (k) => (mask & (1n << BigInt(k))) !== 0n;
188
+ const fns = ["buyoutMask", "votable", "quorumBps", "voteSeconds", "feed", "target", "sellsAtSeconds", "deadManSeconds", "payrollMin"];
189
+ const r = await pub.multicall({ contracts: fns.map((functionName) => ({ address: token, abi: ABI[2], functionName })), allowFailure: false });
190
+ const x = {
191
+ ...emptyExtras(),
192
+ buyoutMask: r[0],
193
+ votable: r[1],
194
+ quorumBps: Number(r[2]),
195
+ voteSeconds: Number(r[3]),
196
+ feed: r[4],
197
+ target: r[5],
198
+ sellsAtSeconds: Number(r[6]),
199
+ deadManSeconds: Number(r[7]),
200
+ payrollMin: r[8],
201
+ };
202
+ const buyable = kindsOf(x.buyoutMask);
203
+ if (buyable.length) {
204
+ const prices = (await pub.multicall({ contracts: buyable.map((k) => ({ address: token, abi: ABI[2], functionName: "buyoutPrice", args: [k] })), allowFailure: false }));
205
+ x.buyoutPrice = [0, 1, 2, 3, 4, 5, 6, 7].map((j) => prices[j] ?? 0n);
206
+ }
207
+ if (has(41) || has(45) || has(46)) {
208
+ const t = await terms();
209
+ if (has(41)) {
210
+ const id = await call(t.arena, arenaAbi, "duelOf", [token]);
211
+ if (id > 0n) {
212
+ const d = await call(t.arena, arenaAbi, "get", [id]);
213
+ x.duelStake = d.stake;
214
+ x.duelOpponent = isAddressEqual(d.b, token) ? d.a : ZERO_ADDRESS;
215
+ }
216
+ }
217
+ if (has(45)) {
218
+ const f = await call(t.floorVault, floorVaultAbi, "floors", [token]);
219
+ x.floorDeposit = f[0];
220
+ x.floorBps = Number(f[2]);
221
+ }
222
+ if (has(46)) {
223
+ const c = await call(t.payrollBox, payrollBoxAbi, "coins", [token]);
224
+ x.shiftSeconds = Number(c[5]);
225
+ }
226
+ }
227
+ return x;
228
+ }
229
+ // ------------------------------------------------ the dry runs
230
+ /**
231
+ * v2 coins count a transfer with the V3 pool as a trade only when the pool's price moved since the last counted trade,
232
+ * so the dry runs nudge the pool's slot0 by one through a state override, like a swap would.
233
+ */
234
+ async function nudged(pool) {
235
+ const slot0 = await pub.getStorageAt({ address: pool, slot: "0x0" });
236
+ const value = `0x${(BigInt(slot0 ?? "0x0") + 1n).toString(16).padStart(64, "0")}`;
237
+ return [{ address: pool, stateDiff: [{ slot: `0x${"0".repeat(64)}`, value }] }];
238
+ }
239
+ /**
240
+ * Would this trade go through: the coin's own transfer of the trade in an eth_call (a buy: the coins leaving the pool
241
+ * to the wallet, a sell: the wallet's coins into the pool, a send: wallet to `to`). For a buy pass the coins it would get
242
+ * (quote()). The refusal names the rule with the coin's numbers.
243
+ */
244
+ async function check(c) {
245
+ const v = await need(c.token);
246
+ const abi = ABI[v];
247
+ const pool = await call(c.token, abi, "pool");
248
+ const transfer = (to, amount) => encodeFunctionData({ abi: erc20Transfer, functionName: "transfer", args: [to, amount] });
249
+ try {
250
+ if (c.side === "send") {
251
+ await pub.call({ account: c.wallet, to: c.token, data: transfer(c.to ?? ZERO_ADDRESS, c.amount) });
252
+ }
253
+ else if (v === 2) {
254
+ const override = await nudged(pool);
255
+ if (c.side === "buy")
256
+ await pub.call({ account: pool, to: c.token, data: transfer(c.wallet, c.amount), stateOverride: override });
257
+ else
258
+ await pub.call({ account: c.wallet, to: c.token, data: transfer(pool, c.amount), stateOverride: override });
259
+ }
260
+ else if (c.side === "buy") {
261
+ // v1: a tiny forwarder on the pool's address moves the coins out exactly as a real buy would (msg.sender = pool,
262
+ // tx.origin = buyer), for this one eth_call
263
+ await pub.call({
264
+ account: c.wallet,
265
+ to: pool,
266
+ data: encodeFunctionData({ abi: simAbi, functionName: "sim", args: [c.token, c.wallet, c.amount] }),
267
+ stateOverride: [{ address: pool, code: v1BuySimCode }],
268
+ });
269
+ }
270
+ else {
271
+ await pub.call({ account: c.wallet, to: c.token, data: transfer(pool, c.amount) });
272
+ }
273
+ return { ok: true };
274
+ }
275
+ catch (e) {
276
+ const kind = blockedKind(e);
277
+ if (kind === null)
278
+ return { ok: false, kind: 0, name: "", text: explain(e, "The coin refused this transfer.").text };
279
+ const rules = await call(c.token, abi, "rules");
280
+ const x = v === 2 ? await extrasOf(c.token, rules.mask).catch(() => null) : null;
281
+ return { ok: false, kind, ...ruleText(kind, rules, x) };
282
+ }
283
+ }
284
+ /** a contract refusal of one of these coins as a sentence that names the rule */
285
+ async function refusalOf(token, e, fallback) {
286
+ const kind = blockedKind(e);
287
+ if (kind === null)
288
+ return toPickhooksError(e, fallback);
289
+ const v = await need(token);
290
+ const rules = await call(token, ABI[v], "rules").catch(() => null);
291
+ const r = ruleText(kind, rules);
292
+ return new PickhooksError(r.text, { code: "Blocked", args: [kind], cause: e });
293
+ }
294
+ /** Coins a buy of `bnbIn` would get, estimated from the pool's liquidity at its price (see v3EstimateBuy); not a minOut. */
295
+ async function estimateBuy(token, bnbIn) {
296
+ const pool = await call(token, ABI[await need(token)], "pool");
297
+ return v3EstimateBuy(ctx, pool, token, bnbIn);
298
+ }
299
+ /**
300
+ * What a trade gets in the V3 pool, from PancakeSwap's QuoterV2. A buy a rule refuses fails in the quoter without the
301
+ * reason, so it is checked again with an estimate of its coins and the refusal names the rule (for `wallet`, by default
302
+ * the quoter the quote ran as).
303
+ */
304
+ async function quote(q) {
305
+ try {
306
+ return { amountOut: await v3Quote(ctx, q) };
307
+ }
308
+ catch (e) {
309
+ if (q.side === "buy" && blockedKind(e) === null) {
310
+ const est = await estimateBuy(q.token, q.amount).catch(() => 0n);
311
+ const c = est > 0n ? await check({ token: q.token, side: "buy", wallet: q.wallet ?? a.v3Quoter, amount: est }).catch(() => null) : null;
312
+ if (c && !c.ok && c.kind > 0)
313
+ throw new PickhooksError(c.text, { code: "Blocked", args: [c.kind], cause: e });
314
+ }
315
+ throw await refusalOf(q.token, e, "The quoter refused this trade.");
316
+ }
317
+ }
318
+ const refuse = (c) => {
319
+ if (!c.ok)
320
+ throw new PickhooksError(c.text, { code: "Blocked", args: [c.kind] });
321
+ };
322
+ /** Quotes, checks the coin's rules for this wallet, and buys through PancakeSwap V3. A refusing rule stops it before anything is signed. */
323
+ async function buy(b, s = {}) {
324
+ const { address } = await signer(ctx, s.wallet);
325
+ const q = await quote({ token: b.token, side: "buy", amount: b.bnbIn, wallet: address });
326
+ refuse(await check({ token: b.token, side: "buy", wallet: address, amount: q.amountOut }));
327
+ const minOut = b.minOut ?? minusBps(q.amountOut, b.slippageBps ?? 100);
328
+ try {
329
+ const sent = await sendTx(ctx, buildV3Buy(a, { token: b.token, bnbIn: b.bnbIn, minOut, recipient: address, deadline: b.deadline, gasPct: 150 }), s);
330
+ return { ...sent, quoted: q.amountOut, minOut, amountOut: receivedOf(sent.receipt, b.token, address) };
331
+ }
332
+ catch (e) {
333
+ throw await refusalOf(b.token, e, "The buy would fail.");
334
+ }
335
+ }
336
+ /** Checks the rules, approves the V3 router if needed, quotes and sells for BNB. */
337
+ async function sell(x, s = {}) {
338
+ const { address } = await signer(ctx, s.wallet);
339
+ refuse(await check({ token: x.token, side: "sell", wallet: address, amount: x.amount }));
340
+ const ap = await approvalFor(ctx, address, x.token, x.amount, a.v3Router, "Allowing PancakeSwap");
341
+ if (ap)
342
+ await sendTx(ctx, ap, s);
343
+ const q = await quote({ token: x.token, side: "sell", amount: x.amount });
344
+ const minOut = x.minOut ?? minusBps(q.amountOut, x.slippageBps ?? 100);
345
+ try {
346
+ const sent = await sendTx(ctx, buildV3Sell(a, { token: x.token, amount: x.amount, minOut, recipient: address, deadline: x.deadline, gasPct: 150 }), s);
347
+ return { ...sent, quoted: q.amountOut, minOut };
348
+ }
349
+ catch (e) {
350
+ throw await refusalOf(x.token, e, "The sell would fail.");
351
+ }
352
+ }
353
+ // ------------------------------------------------ launch (v2 factory)
354
+ /** $hooks the launch burns for these rules, and the BNB the factory needs to buy them (quote + 3 % tax + 5 % room). */
355
+ async function burnFor(mask) {
356
+ const hooks = await call(a.coinsV2Factory, FACTORY[2].abi, "burnFor", [mask]);
357
+ if (hooks === 0n)
358
+ return { hooks, bnb: 0n };
359
+ const want = (hooks * 10000n) / 9500n;
360
+ const [bnbIn] = await call(a.v2Router, pancakeV2RouterAbi, "getAmountsIn", [want, [a.wbnb, a.hooks]]);
361
+ return { hooks, bnb: (bnbIn * 105n) / 100n };
362
+ }
363
+ /**
364
+ * Launches a coin with up to eight rules on the v2 factory: checks the selection like the factory, builds the Rules and
365
+ * Extras structs, pays the $hooks burn of hooks rules (in BNB or $hooks), sends the seed, the deposits (duel stake,
366
+ * floor, payroll) and the first buy, then funds a jackpot or rewards pot the rules ask for. Gas: estimate + 5 %, never
367
+ * above BNB Chain's cap (a launch needs about 14.4M).
368
+ */
369
+ async function launch(l, s = {}) {
370
+ const problem = selectionProblem(l.rules);
371
+ if (problem)
372
+ throw new PickhooksError(problem, { code: "input" });
373
+ const { address } = await signer(ctx, s.wallet);
374
+ const now = Number((await pub.getBlock({ blockTag: "latest" })).timestamp);
375
+ const built = buildRules(l.rules, now, l.keepBps ?? 10_000);
376
+ const t = await terms();
377
+ const burn = await burnFor(built.rules.mask);
378
+ const payInHooks = burn.hooks > 0n && l.payBurnIn === "hooks";
379
+ if (payInHooks) {
380
+ const ap = await approvalFor(ctx, address, a.hooks, burn.hooks, a.coinsV2Factory, "Allowing the factory to burn $hooks");
381
+ if (ap)
382
+ await sendTx(ctx, ap, s);
383
+ }
384
+ const burnBnb = burn.hooks > 0n && !payInHooks ? burn.bnb : 0n;
385
+ const deposits = await call(a.coinsV2Factory, FACTORY[2].abi, "depositsFor", [built.rules.mask, built.extras, burnBnb]);
386
+ const args = {
387
+ name: l.name,
388
+ symbol: l.symbol,
389
+ metadata: l.metadata,
390
+ rules: built.rules,
391
+ untilIds: built.untilIds,
392
+ untilTimes: built.untilTimes,
393
+ salt: l.salt ?? randomSalt(),
394
+ extras: built.extras,
395
+ burnBnb,
396
+ maxBurn: burn.hooks,
397
+ minCreatorBps: t.creatorBps,
398
+ };
399
+ const tx = { to: a.coinsV2Factory, value: (l.devBuy ?? 0n) + deposits, data: encodeFunctionData({ abi: v2FactoryAbi, functionName: "launch", args: [args] }), gasPct: 105, label: "The launch" };
400
+ const sent = await sendTx(ctx, tx, s);
401
+ const ev = parseEventLogs({ abi: v2FactoryAbi, logs: sent.receipt.logs.filter((x) => isAddressEqual(x.address, a.coinsV2Factory)) });
402
+ const token = ev.find((e) => e.eventName === "Launched")?.args.token;
403
+ if (!token)
404
+ throw new PickhooksError("Launched, but the coin was not found in the receipt.", { code: "receipt", hash: sent.hash });
405
+ versions.set(token.toLowerCase(), 2);
406
+ const pots = [];
407
+ const p = launchPots(l.rules);
408
+ if (p.jackpot > 0)
409
+ pots.push(await fundPot(token, "jackpot", BigInt(Math.round(p.jackpot * 1e9)) * 10n ** 9n, s));
410
+ if (p.rewards > 0)
411
+ pots.push(await fundPot(token, "rewards", BigInt(Math.round(p.rewards * 1e9)) * 10n ** 9n, s));
412
+ return { ...sent, token, burned: ev.find((e) => e.eventName === "HooksBurned")?.args.amount ?? 0n, pots };
413
+ }
414
+ // ------------------------------------------------ pots, claims, fees, v2 games
415
+ const tokenTx = async (token, fn, args = [], value = 0n, label) => {
416
+ const v = await need(token);
417
+ return { to: token, value, data: encodeFunctionData({ abi: ABI[v], functionName: fn, args }), gasPct: 150, label };
418
+ };
419
+ const at = (to, abi, fn, args = [], value = 0n, label) => ({
420
+ to,
421
+ value,
422
+ data: encodeFunctionData({ abi, functionName: fn, args }),
423
+ gasPct: 150,
424
+ label,
425
+ });
426
+ /** Funds a coin's jackpot or buyer rewards pot. */
427
+ async function fundPot(token, pot, wei, s = {}) {
428
+ const v = await need(token);
429
+ const fn = pot === "rewards" ? "fundRewards" : v === 2 ? "fundPot" : "fundJackpot";
430
+ return sendTx(ctx, await tokenTx(token, fn, [], wei, `Funding the ${pot}`), s);
431
+ }
432
+ async function lockerOf(token) {
433
+ const v = await need(token);
434
+ const address = v === 2 ? (await terms()).locker : await call(a.coinsV1Factory, FACTORY[1].abi, "locker");
435
+ return { address, abi: (v === 2 ? v2LockerAbi : v1LockerAbi) };
436
+ }
437
+ const build = {
438
+ buy: (b) => buildV3Buy(a, { ...b, gasPct: 150 }),
439
+ sell: (x) => buildV3Sell(a, { ...x, gasPct: 150 }),
440
+ /** jackpot wins and buyer rewards the coin owes the caller */
441
+ claim: (token) => tokenTx(token, "claim", [], 0n, "The claim"),
442
+ settleJackpot: (token) => tokenTx(token, "settleJackpot", [], 0n, "Settling the jackpot"),
443
+ /** the creator takes back rewards it paid in and nobody earned */
444
+ withdrawRewards: (token, wei) => tokenTx(token, "withdrawRewards", [wei], 0n, "Taking rewards back"),
445
+ /** anyone: collects the coin's trading fees from its locked position (creator, pot, platform; coins burned or into the pair) */
446
+ collect: async (token) => {
447
+ const l = await lockerOf(token);
448
+ return at(l.address, l.abi, "collect", [token], 0n, "The collect");
449
+ },
450
+ /** v2: buys a listed rule off (half to the creator, half burns $hooks); the price is the coin's buyoutPrice(kind) */
451
+ buyout: async (token, kind) => tokenTx(token, "buyout", [kind], await call(token, ABI[2], "buyoutPrice", [kind]), "Buying the rule off"),
452
+ /** v2: moves the milestone level and sells at along without a trade (anyone) */
453
+ poke: (token) => tokenTx(token, "poke", [], 0n, "Poke"),
454
+ /** v2 dead man: the creator says it is still here; release: anyone burns the bag of a quiet creator */
455
+ alive: (token) => tokenTx(token, "alive", [], 0n, "Still here"),
456
+ release: (token) => tokenTx(token, "release", [], 0n, "Release"),
457
+ fundPayroll: (token, wei) => tokenTx(token, "fundPayroll", [], wei, "Funding the payroll"),
458
+ payrollClaim: async (token) => at((await terms()).payrollBox, payrollBoxAbi, "claim", [token], 0n, "The payroll claim"),
459
+ payShift: async (token) => at((await terms()).payrollBox, payrollBoxAbi, "payShift", [token], 0n, "Paying the shift"),
460
+ floorDefend: async (token) => at((await terms()).floorVault, floorVaultAbi, "defend", [token], 0n, "Defending the floor"),
461
+ duelClose: async (id) => at((await terms()).arena, arenaAbi, "close", [id], 0n, "Closing the duel"),
462
+ duelSettle: async (id) => at((await terms()).arena, arenaAbi, "settle", [id], 0n, "Settling the duel"),
463
+ duelRefund: async (id) => at((await terms()).arena, arenaAbi, "refund", [id], 0n, "Refunding the duel"),
464
+ /** votes move coins into the vote box: approve the coin to the box first (terms().voteBox) */
465
+ voteStart: async (token, kind, amount) => at((await terms()).voteBox, voteBoxAbi, "start", [token, kind, amount], 0n, "Opening a vote"),
466
+ voteCast: async (token, kind, amount, yes) => at((await terms()).voteBox, voteBoxAbi, "vote", [token, kind, amount, yes], 0n, "The vote"),
467
+ voteFinish: async (token, kind) => at((await terms()).voteBox, voteBoxAbi, "finish", [token, kind], 0n, "Finishing the vote"),
468
+ voteWithdraw: async (token, kind, round) => at((await terms()).voteBox, voteBoxAbi, "withdraw", [token, kind, round, 0n], 0n, "Taking the vote coins back"),
469
+ };
470
+ return {
471
+ v1Factory: a.coinsV1Factory,
472
+ v2Factory: a.coinsV2Factory,
473
+ versionOf,
474
+ count,
475
+ coins,
476
+ coin,
477
+ terms,
478
+ extrasOf,
479
+ check,
480
+ quote,
481
+ estimateBuy,
482
+ burnFor,
483
+ build,
484
+ buy,
485
+ sell,
486
+ launch,
487
+ fundPot,
488
+ /** a wallet's jackpot wins and buyer rewards in a coin, and its counters there */
489
+ wallet: async (token, who) => {
490
+ const v = await need(token);
491
+ const r = await pub.multicall({
492
+ contracts: [
493
+ { address: token, abi: ABI[v], functionName: "balanceOf", args: [who] },
494
+ { address: token, abi: ABI[v], functionName: "owed", args: [who] },
495
+ { address: token, abi: ABI[v], functionName: "wallets", args: [who] },
496
+ ],
497
+ allowFailure: false,
498
+ });
499
+ const w = r[2];
500
+ return { balance: r[0], owed: r[1], buys: Number(w[0]), lastBuy: Number(w[1]), lastTrade: Number(w[2]), jailUntil: Number(w[3]) };
501
+ },
502
+ /** what collect() would book now: BNB fees and coins burned (simulated) */
503
+ pendingFees: async (token) => {
504
+ const l = await lockerOf(token);
505
+ const r = await pub.simulateContract({ address: l.address, abi: l.abi, functionName: "collect", args: [token] });
506
+ const [bnb, burned] = r.result;
507
+ return { bnb, burned };
508
+ },
509
+ claim: async (token, s = {}) => sendTx(ctx, await build.claim(token), s),
510
+ settleJackpot: async (token, s = {}) => sendTx(ctx, await build.settleJackpot(token), s),
511
+ withdrawRewards: async (token, wei, s = {}) => sendTx(ctx, await build.withdrawRewards(token, wei), s),
512
+ collect: async (token, s = {}) => sendTx(ctx, await build.collect(token), s),
513
+ buyout: async (token, kind, s = {}) => sendTx(ctx, await build.buyout(token, kind), s),
514
+ poke: async (token, s = {}) => sendTx(ctx, await build.poke(token), s),
515
+ alive: async (token, s = {}) => sendTx(ctx, await build.alive(token), s),
516
+ release: async (token, s = {}) => sendTx(ctx, await build.release(token), s),
517
+ /** the coin's PancakeSwap V2 pair against WBNB */
518
+ pairOf: (token) => call(a.v2Factory, pancakeV2FactoryAbi, "getPair", [token, a.wbnb]),
519
+ };
520
+ }