@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
@@ -0,0 +1,637 @@
1
+ // Copied from web/src/lib/sticky-sim.ts (the coin's rule engine on bigints and the launcher's test run), with its import pointed
2
+ // at the SDK's copy of the encoder. Keep the two in step: node scripts/check-rules-copy.mjs compares them.
3
+ /**
4
+ * sticky rules in the browser: the coin's rule engine (src/sticky/StickyToken.sol) line for line, on bigints, and a test
5
+ * run of random trades on top. The engine is checked against the contract trade by trade (test/sticky/StickyTrace.t.sol
6
+ * writes a run, scripts/dev/sticky-sim-diff.mjs replays it here), so what the launcher shows is what the coin would do.
7
+ * The chain stays the truth: this only shows a creator what a table does before the launch.
8
+ */
9
+ import { ACT, FLAG, ON, OP, VAR, decodeRow } from "./sticky-rules.js";
10
+ export const SUPPLY = BigInt("1000000000000000000000000000"); // 1e9 coins of 18 decimals
11
+ const E6 = BigInt(1_000_000);
12
+ const BPS = BigInt(10_000);
13
+ const NONE = 255;
14
+ const LOCKED = 254;
15
+ const EXIT = 30 * 86_400;
16
+ const MAX_LOTS = 8;
17
+ const MAX_TAKE = BigInt(1000);
18
+ const USER_FLAGS = FLAG.SNIPER | FLAG.MARK;
19
+ const U40 = (BigInt(1) << BigInt(40)) - BigInt(1);
20
+ const Z = BigInt(0);
21
+ const ONE = BigInt(1);
22
+ /** The addresses the coin does not keep lots for. */
23
+ export const POOL = "pool";
24
+ export const POT = "pot";
25
+ export const DEAD = "dead";
26
+ const tracked = (a) => a !== POOL && a !== POT && a !== DEAD;
27
+ const min = (a, b) => (a < b ? a : b);
28
+ const bal = (s, a) => s.bal.get(a) ?? Z;
29
+ const lotsOf = (s, a) => s.lots.get(a) ?? [];
30
+ export function newCoin(rows, release, creator, curveSupply, start = 0, startBlock = 0) {
31
+ return {
32
+ rows: rows.map((r) => (typeof r === "bigint" ? decodeRow(r) : r)),
33
+ release,
34
+ off: 0,
35
+ creator: creator.toLowerCase(),
36
+ start,
37
+ startBlock,
38
+ curveSupply,
39
+ stats: { holders: 0, buys: 0, sells: 0, highAt: start, lastBuy: start, volume: Z, net: Z, peak: Z, high: Z },
40
+ bal: new Map(),
41
+ lots: new Map(),
42
+ };
43
+ }
44
+ // ---------------------------------------------------------------- rows
45
+ function ctx(s, kind, amount, balance, now) {
46
+ const c = { kind, amount: Z, balance: Z, price: Z, dip: Z, sold: Z, sinceLaunch: Z, blocks: Z, buys: Z, holders: Z, idle: Z, pot: Z };
47
+ if (s.rows.length === 0)
48
+ return c;
49
+ c.amount = (amount * E6) / SUPPLY;
50
+ c.balance = (balance * E6) / SUPPLY;
51
+ const sup = s.curveSupply;
52
+ const net = s.stats.net;
53
+ if (sup !== Z) {
54
+ if (net >= sup) {
55
+ c.price = U40;
56
+ c.dip = BPS;
57
+ c.sold = BPS;
58
+ }
59
+ else {
60
+ const left = sup - net;
61
+ c.price = min((sup * sup * BPS) / (left * left), U40);
62
+ const high = now.t >= s.stats.highAt + 3600 ? net : s.stats.high > net ? s.stats.high : net;
63
+ c.dip = ((sup - high) * (sup - high) * BPS) / (left * left);
64
+ c.sold = (net * BPS) / sup;
65
+ }
66
+ }
67
+ c.sinceLaunch = BigInt(now.t - s.start);
68
+ c.blocks = BigInt(now.b - s.startBlock);
69
+ c.buys = BigInt(s.stats.buys);
70
+ c.holders = BigInt(s.stats.holders);
71
+ c.idle = BigInt(now.t - s.stats.lastBuy);
72
+ c.pot = (bal(s, POT) * E6) / SUPPLY;
73
+ return c;
74
+ }
75
+ function value(v, c, age, flags, now) {
76
+ switch (v) {
77
+ case VAR.AGE:
78
+ return age;
79
+ case VAR.LAUNCH:
80
+ return c.sinceLaunch;
81
+ case VAR.BLOCKS:
82
+ return c.blocks;
83
+ case VAR.AMOUNT:
84
+ return c.amount;
85
+ case VAR.BALANCE:
86
+ return c.balance;
87
+ case VAR.PRICE:
88
+ return c.price;
89
+ case VAR.DIP:
90
+ return c.dip;
91
+ case VAR.SOLD:
92
+ return c.sold;
93
+ case VAR.HOUR:
94
+ return BigInt(Math.floor(now.t / 3600) % 24);
95
+ case VAR.WEEKDAY:
96
+ return BigInt((Math.floor(now.t / 86_400) + 4) % 7);
97
+ case VAR.FLAGS:
98
+ return BigInt(flags);
99
+ case VAR.BUYS:
100
+ return c.buys;
101
+ case VAR.HOLDERS:
102
+ return c.holders;
103
+ case VAR.IDLE:
104
+ return c.idle;
105
+ case VAR.POTSIZE:
106
+ return c.pot;
107
+ }
108
+ return Z;
109
+ }
110
+ function test(op, x, val) {
111
+ if (op === OP.LT)
112
+ return x < val;
113
+ if (op === OP.GE)
114
+ return x >= val;
115
+ if (op === OP.HAS)
116
+ return (x & val) !== Z;
117
+ if (op === OP.NOT)
118
+ return (x & val) === Z;
119
+ const period = val >> BigInt(20);
120
+ if (period === Z)
121
+ return false;
122
+ const phase = x % period;
123
+ const th = val & BigInt(0xfffff);
124
+ if (op === OP.MODLT)
125
+ return phase < th;
126
+ if (op === OP.MODGE)
127
+ return phase >= th;
128
+ return false;
129
+ }
130
+ function matches(r, c, age, flags, now) {
131
+ for (const k of r.conds)
132
+ if (!test(k.op, value(k.v, c, age, flags, now), k.val))
133
+ return false;
134
+ return true;
135
+ }
136
+ function buyRows(s, to, amount, now) {
137
+ const b = { why: NONE, flags: to === s.creator ? FLAG.DEV : 0, lockBps: Z, burn: Z, toPot: Z, drop: Z };
138
+ if (s.rows.length === 0)
139
+ return b;
140
+ const c = ctx(s, ON.BUY, amount, bal(s, to) + amount, now);
141
+ let burnBps = Z;
142
+ let potBps = Z;
143
+ let dropBps = Z;
144
+ for (let i = 0; i < s.rows.length; i++) {
145
+ if (s.off & (1 << i))
146
+ continue;
147
+ const r = s.rows[i];
148
+ if (!(r.on & ON.BUY) || !matches(r, c, Z, b.flags, now))
149
+ continue;
150
+ const x = BigInt(r.arg);
151
+ if (r.act === ACT.DENY) {
152
+ b.why = i;
153
+ return b;
154
+ }
155
+ if (r.act === ACT.FLAG)
156
+ b.flags |= r.arg & USER_FLAGS;
157
+ else if (r.act === ACT.LOCK)
158
+ b.lockBps = x > b.lockBps ? x : b.lockBps;
159
+ else if (r.act === ACT.BURN)
160
+ burnBps += x;
161
+ else if (r.act === ACT.POT)
162
+ potBps += x;
163
+ else if (r.act === ACT.DROP)
164
+ dropBps = x > dropBps ? x : dropBps;
165
+ }
166
+ if (burnBps > MAX_TAKE)
167
+ burnBps = MAX_TAKE;
168
+ if (burnBps + potBps > MAX_TAKE)
169
+ potBps = MAX_TAKE - burnBps;
170
+ if (b.lockBps > BPS)
171
+ b.lockBps = BPS;
172
+ if (dropBps > BPS)
173
+ dropBps = BPS;
174
+ b.burn = (amount * burnBps) / BPS;
175
+ b.toPot = (amount * potBps) / BPS;
176
+ if (dropBps !== Z && to !== s.creator && tracked(to)) {
177
+ const got = amount - b.burn - b.toPot;
178
+ b.drop = min((bal(s, POT) * dropBps) / BPS, got);
179
+ }
180
+ return b;
181
+ }
182
+ function denied(s, c, age, flags, now) {
183
+ for (let i = 0; i < s.rows.length; i++) {
184
+ if (s.off & (1 << i))
185
+ continue;
186
+ const r = s.rows[i];
187
+ if (r.act !== ACT.DENY || (r.on & c.kind) === 0)
188
+ continue;
189
+ if (matches(r, c, BigInt(age), flags, now))
190
+ return i;
191
+ }
192
+ return NONE;
193
+ }
194
+ // ---------------------------------------------------------------- lots
195
+ function lockedNow(s, l, t) {
196
+ const d = s.release;
197
+ if (d === 0 || l.lk === Z)
198
+ return Z;
199
+ const end = l.since + d;
200
+ if (t >= end)
201
+ return Z;
202
+ return (l.lk * BigInt(end - t) + BigInt(d) - ONE) / BigInt(d);
203
+ }
204
+ function free(s, l, c, now) {
205
+ const age = now.t - l.since;
206
+ if (age >= EXIT)
207
+ return l.amount;
208
+ if (denied(s, c, age, l.flags, now) !== NONE)
209
+ return Z;
210
+ const lk = lockedNow(s, l, now.t);
211
+ return lk >= l.amount ? Z : l.amount - lk;
212
+ }
213
+ function planSell(s, a, amount, c, now) {
214
+ const ls = lotsOf(s, a).map((l) => ({ ...l }));
215
+ let left = amount;
216
+ let why = NONE;
217
+ for (let i = 0; i < ls.length && left !== Z; i++) {
218
+ const l = ls[i];
219
+ const age = now.t - l.since;
220
+ let fr;
221
+ if (age >= EXIT) {
222
+ fr = l.amount;
223
+ }
224
+ else {
225
+ const r = denied(s, c, age, l.flags, now);
226
+ if (r !== NONE) {
227
+ if (why === NONE)
228
+ why = r;
229
+ continue;
230
+ }
231
+ const lk = lockedNow(s, l, now.t);
232
+ fr = lk >= l.amount ? Z : l.amount - lk;
233
+ }
234
+ const take = min(fr, left);
235
+ if (take === Z)
236
+ continue;
237
+ l.amount -= take; // the lock at purchase stays: the time decides what is still locked
238
+ left -= take;
239
+ }
240
+ if (left !== Z)
241
+ return { why: why === NONE ? LOCKED : why, ls };
242
+ return { why: NONE, ls };
243
+ }
244
+ function planSend(s, a, amount, c, now) {
245
+ const ls = lotsOf(s, a).map((l) => ({ ...l }));
246
+ const pieces = [];
247
+ let left = amount;
248
+ let why = NONE;
249
+ for (let i = ls.length - 1; i >= 0 && left !== Z; i--) {
250
+ const l = ls[i];
251
+ const age = now.t - l.since;
252
+ if (age < EXIT) {
253
+ const r = denied(s, c, age, l.flags, now);
254
+ if (r !== NONE) {
255
+ if (why === NONE)
256
+ why = r;
257
+ continue;
258
+ }
259
+ }
260
+ const am = l.amount;
261
+ const lk = l.lk;
262
+ const take = min(am, left);
263
+ const moved = take === am ? lk : min(lk, (lk * take + am - ONE) / am);
264
+ pieces.push({ amount: take, lk: moved, since: l.since, flags: l.flags });
265
+ ls[i] = { amount: am - take, lk: lk - moved, since: l.since, flags: l.flags };
266
+ left -= take;
267
+ }
268
+ if (left !== Z)
269
+ return { why: why === NONE ? LOCKED : why, ls, pieces: [] };
270
+ return { why: NONE, ls, pieces };
271
+ }
272
+ function insert(ls, p) {
273
+ let at = ls.length;
274
+ for (let i = 0; i < ls.length; i++) {
275
+ const l = ls[i];
276
+ if (l.since === p.since && l.flags === p.flags) {
277
+ ls[i] = { amount: l.amount + p.amount, lk: l.lk + p.lk, since: l.since, flags: l.flags };
278
+ return;
279
+ }
280
+ if (l.since > p.since && at === ls.length)
281
+ at = i;
282
+ }
283
+ ls.splice(at, 0, p);
284
+ }
285
+ /** Joins the cheapest pair of lots with the same flags: the younger time, both locks. */
286
+ function join(ls) {
287
+ let best = null;
288
+ let bi = 0;
289
+ let bj = 0;
290
+ for (let i = 0; i < ls.length; i++) {
291
+ for (let j = i + 1; j < ls.length; j++) {
292
+ if (ls[j].flags !== ls[i].flags)
293
+ continue;
294
+ const cost = ls[i].amount * BigInt(ls[j].since - ls[i].since);
295
+ if (best === null || cost < best)
296
+ [best, bi, bj] = [cost, i, j];
297
+ break;
298
+ }
299
+ }
300
+ const x = ls[bi];
301
+ const y = ls[bj];
302
+ ls[bj] = { amount: x.amount + y.amount, lk: x.lk + y.lk, since: y.since, flags: y.flags };
303
+ ls.splice(bi, 1);
304
+ }
305
+ function receive(s, a, pieces) {
306
+ const ls = lotsOf(s, a).map((l) => ({ ...l }));
307
+ for (const p of pieces) {
308
+ if (p.amount === Z)
309
+ continue;
310
+ insert(ls, p);
311
+ if (ls.length > MAX_LOTS)
312
+ join(ls);
313
+ }
314
+ store(s, a, ls);
315
+ }
316
+ function store(s, a, ls) {
317
+ const kept = ls.filter((l) => l.amount !== Z);
318
+ if (kept.length)
319
+ s.lots.set(a, kept);
320
+ else
321
+ s.lots.delete(a);
322
+ }
323
+ function high(st, net, t) {
324
+ if (net > st.high || t >= st.highAt + 3600) {
325
+ st.high = net;
326
+ st.highAt = t;
327
+ }
328
+ }
329
+ // ---------------------------------------------------------------- transfers
330
+ /** A transfer as the coin runs it: out of POOL is a buy, into POOL a sell, anything else a send. */
331
+ export function transfer(s, from, to, amount, now) {
332
+ from = from.toLowerCase();
333
+ to = to.toLowerCase();
334
+ const fromBal = bal(s, from);
335
+ if (fromBal < amount)
336
+ return { ok: false, why: "nope" };
337
+ let b = { why: NONE, flags: 0, lockBps: Z, burn: Z, toPot: Z, drop: Z };
338
+ if (amount !== Z) {
339
+ const isBuy = from === POOL;
340
+ const isSell = to === POOL;
341
+ if (isBuy && isSell)
342
+ return { ok: false, why: "nope" };
343
+ if (isBuy) {
344
+ b = buyRows(s, to, amount, now);
345
+ if (b.why !== NONE)
346
+ return { ok: false, why: b.why };
347
+ const got = amount - b.burn - b.toPot + b.drop;
348
+ if (got !== Z && tracked(to))
349
+ receive(s, to, [{ amount: got, lk: (got * b.lockBps) / BPS, since: now.t, flags: b.flags }]);
350
+ const st = s.stats;
351
+ st.buys++;
352
+ st.lastBuy = now.t;
353
+ st.volume += amount;
354
+ st.net += amount;
355
+ if (st.net > st.peak)
356
+ st.peak = st.net;
357
+ high(st, st.net, now.t);
358
+ }
359
+ else if (isSell) {
360
+ if (tracked(from)) {
361
+ const p = planSell(s, from, amount, ctx(s, ON.SELL, amount, fromBal, now), now);
362
+ if (p.why !== NONE)
363
+ return { ok: false, why: p.why };
364
+ store(s, from, p.ls);
365
+ }
366
+ const st = s.stats;
367
+ st.sells++;
368
+ st.volume += amount;
369
+ st.net = amount > st.net ? Z : st.net - amount;
370
+ high(st, st.net, now.t);
371
+ }
372
+ else {
373
+ let pieces;
374
+ if (tracked(from)) {
375
+ const p = planSend(s, from, amount, ctx(s, ON.SEND, amount, bal(s, to) + amount, now), now);
376
+ if (p.why !== NONE)
377
+ return { ok: false, why: p.why };
378
+ store(s, from, p.ls);
379
+ pieces = p.pieces;
380
+ }
381
+ else {
382
+ pieces = [{ amount, lk: Z, since: now.t, flags: 0 }];
383
+ }
384
+ if (tracked(to))
385
+ receive(s, to, pieces);
386
+ }
387
+ }
388
+ s.bal.set(from, fromBal - amount);
389
+ const got = amount - b.burn - b.toPot;
390
+ const toBefore = bal(s, to);
391
+ s.bal.set(to, toBefore + got);
392
+ if (b.burn !== Z)
393
+ s.bal.set(DEAD, bal(s, DEAD) + b.burn);
394
+ if (b.toPot !== Z)
395
+ s.bal.set(POT, bal(s, POT) + b.toPot);
396
+ if (b.drop !== Z) {
397
+ s.bal.set(POT, bal(s, POT) - b.drop);
398
+ s.bal.set(to, bal(s, to) + b.drop);
399
+ }
400
+ if (tracked(from) && fromBal !== Z && fromBal === amount && s.stats.holders > 0)
401
+ s.stats.holders--;
402
+ if (tracked(to) && toBefore === Z && got + b.drop !== Z)
403
+ s.stats.holders++;
404
+ return { ok: true, burn: b.burn, toPot: b.toPot, drop: b.drop };
405
+ }
406
+ /** What `a` could sell right now (size rows left out, like the coin's view). */
407
+ export function sellable(s, a, now) {
408
+ a = a.toLowerCase();
409
+ const c = ctx(s, ON.SELL, Z, bal(s, a), now);
410
+ return lotsOf(s, a).reduce((x, l) => x + free(s, l, c, now), Z);
411
+ }
412
+ export function balanceOf(s, a) {
413
+ return bal(s, a.toLowerCase());
414
+ }
415
+ export function lots(s, a) {
416
+ return lotsOf(s, a.toLowerCase());
417
+ }
418
+ // ---------------------------------------------------------------- a test run of random trades
419
+ /** The curve as the coin prices it: x * y = k from the start price, so price / start = (supply / coins left)². */
420
+ export const CURVE = {
421
+ supply: BigInt("998319327731092436974789916"), // 1e9 coins minus the seed coins of the V2 pair (0.01 BNB at 5.95 BNB)
422
+ startFdvBnb: 5.95,
423
+ };
424
+ function coinsFor(net, bnb) {
425
+ const sup = Number(CURVE.supply) / 1e18;
426
+ const left = sup - Number(net) / 1e18;
427
+ const k = (CURVE.startFdvBnb / 1e9) * sup * sup; // BNB * coins
428
+ const y = k / left;
429
+ const out = left - k / (y + bnb * 0.99);
430
+ return BigInt(Math.floor(Math.max(0, out))) * BigInt(1e18);
431
+ }
432
+ function rng(seed) {
433
+ let a = seed >>> 0;
434
+ return () => {
435
+ a = (a + 0x6d2b79f5) >>> 0;
436
+ let t = a;
437
+ t = Math.imul(t ^ (t >>> 15), t | 1);
438
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
439
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
440
+ };
441
+ }
442
+ /**
443
+ * Random trades on a fresh coin with this table: the creator's first buy, three snipers in the first blocks, wallets
444
+ * that come, buy, hold for a while and sell (and try again when refused), some bags split to fresh wallets that then
445
+ * try to sell. The same seed gives the same run.
446
+ */
447
+ export function simulate(rows, release, opts = {}) {
448
+ const R = rng(opts.seed ?? 7);
449
+ const N = opts.trades ?? 200;
450
+ // launch at the top of the current hour: rows on the hour and the weekday see a real clock
451
+ const T0 = opts.start ?? Math.floor(Date.now() / 3_600_000) * 3600;
452
+ const s = newCoin(rows, release, "creator", CURVE.supply, T0, 0);
453
+ // times below are seconds after the launch; blocks of 0.45 s
454
+ const now = (t) => ({ t: T0 + Math.floor(t), b: Math.floor(t / 0.45) });
455
+ // the pool holds everything the curve has, so every buy can be served
456
+ s.bal.set(POOL, SUPPLY);
457
+ const rep = {
458
+ trades: 0,
459
+ hours: 0,
460
+ buys: { ok: 0, refused: 0 },
461
+ sells: { ok: 0, refused: 0 },
462
+ sends: { ok: 0, refused: 0 },
463
+ refusals: [],
464
+ longestWait: 0,
465
+ stillWaiting: 0,
466
+ allFreeAfter: 0,
467
+ burnedPct: 0,
468
+ pot: { inPct: 0, drops: 0, paidPct: 0, biggestX: 0, leftPct: 0 },
469
+ snipers: { count: 3, firstSellAfter: null },
470
+ splits: { count: 0, refusedAfter: 0 },
471
+ scanner: { buy: null, sell: null },
472
+ };
473
+ const refused = new Map();
474
+ const firstRefusal = new Map();
475
+ const splitWallets = new Set();
476
+ let potIn = Z;
477
+ let potOut = Z;
478
+ const q = [];
479
+ const push = (e) => {
480
+ let i = q.length;
481
+ while (i > 0 && q[i - 1].t > e.t)
482
+ i--;
483
+ q.splice(i, 0, e);
484
+ };
485
+ const logUniform = (lo, hi) => Math.exp(Math.log(lo) + R() * (Math.log(hi) - Math.log(lo)));
486
+ // the launch: the creator's first buy
487
+ if ((opts.devBuyBnb ?? 0) > 0) {
488
+ const amt = coinsFor(s.stats.net, opts.devBuyBnb);
489
+ if (amt > Z)
490
+ transfer(s, POOL, "creator", amt, now(0));
491
+ }
492
+ // snipers in the first blocks, out as soon as they can
493
+ for (let k = 0; k < 3; k++) {
494
+ push({ t: 1 + k * 2, kind: "buy", w: `sniper${k}`, bnb: 0.1 + R() * 0.3 });
495
+ push({ t: 60 + R() * 300, kind: "sell", w: `sniper${k}`, part: 1 });
496
+ }
497
+ // wallets coming in, faster at first
498
+ let t = 30;
499
+ for (let k = 0; k < 120; k++) {
500
+ t += -Math.log(1 - R()) * (k < 30 ? 120 : 900);
501
+ const w = `w${k}`;
502
+ // at the start 0.1 BNB buys about 1.6 % of the supply
503
+ push({ t, kind: "buy", w, bnb: logUniform(0.005, 0.3) });
504
+ if (R() < 0.3)
505
+ push({ t: t + logUniform(120, 6 * 3600), kind: "buy", w, bnb: logUniform(0.005, 0.15) });
506
+ if (R() < 0.15)
507
+ push({ t: t + logUniform(60, 3600), kind: "split", w });
508
+ push({ t: t + logUniform(60, 2 * 86_400), kind: "sell", w, part: R() < 0.5 ? 1 : 0.3 + R() * 0.6 });
509
+ }
510
+ const note = (why) => refused.set(why, (refused.get(why) ?? 0) + 1);
511
+ let last = 0;
512
+ while (q.length && rep.trades < N) {
513
+ const e = q.shift();
514
+ last = e.t;
515
+ const n = now(e.t);
516
+ if (e.kind === "buy") {
517
+ rep.trades++;
518
+ const amt = coinsFor(s.stats.net, e.bnb);
519
+ if (amt === Z)
520
+ continue;
521
+ const r = transfer(s, POOL, e.w, amt, n);
522
+ if (r.ok) {
523
+ rep.buys.ok++;
524
+ potIn += r.toPot;
525
+ if (r.drop > Z) {
526
+ potOut += r.drop;
527
+ rep.pot.drops++;
528
+ const paid = amt - r.burn - r.toPot;
529
+ rep.pot.biggestX = Math.max(rep.pot.biggestX, Number(r.drop) / Number(paid));
530
+ }
531
+ }
532
+ else {
533
+ rep.buys.refused++;
534
+ if (typeof r.why === "number")
535
+ note(r.why);
536
+ }
537
+ }
538
+ else if (e.kind === "split") {
539
+ const b = balanceOf(s, e.w);
540
+ if (b === Z)
541
+ continue;
542
+ rep.trades++;
543
+ const fresh = `${e.w}-split`;
544
+ const r = transfer(s, e.w, fresh, b / BigInt(2), n);
545
+ if (r.ok) {
546
+ rep.sends.ok++;
547
+ rep.splits.count++;
548
+ splitWallets.add(fresh);
549
+ push({ t: e.t + 5 + R() * 60, kind: "sell", w: fresh, part: 1 });
550
+ }
551
+ else {
552
+ rep.sends.refused++;
553
+ if (typeof r.why === "number")
554
+ note(r.why);
555
+ }
556
+ }
557
+ else {
558
+ const b = balanceOf(s, e.w);
559
+ if (b === Z)
560
+ continue;
561
+ rep.trades++;
562
+ const amt = e.part === 1 ? b : BigInt(Math.floor(Number(b / BigInt(1e12)) * e.part)) * BigInt(1e12);
563
+ if (amt === Z)
564
+ continue;
565
+ const r = transfer(s, e.w, POOL, amt, n);
566
+ if (r.ok) {
567
+ rep.sells.ok++;
568
+ const since = firstRefusal.get(e.w);
569
+ if (since !== undefined) {
570
+ rep.longestWait = Math.max(rep.longestWait, e.t - since);
571
+ firstRefusal.delete(e.w);
572
+ }
573
+ if (e.w.startsWith("sniper") && rep.snipers.firstSellAfter === null)
574
+ rep.snipers.firstSellAfter = e.t;
575
+ }
576
+ else {
577
+ rep.sells.refused++;
578
+ if (typeof r.why === "number")
579
+ note(r.why);
580
+ if (!firstRefusal.has(e.w))
581
+ firstRefusal.set(e.w, e.t);
582
+ if (splitWallets.has(e.w))
583
+ rep.splits.refusedAfter++;
584
+ // try again later, the whole bag or what can go now
585
+ const retry = (e.retry ?? 0) + 1;
586
+ if (retry <= 6)
587
+ push({ t: e.t + logUniform(900, 6 * 3600), kind: "sell", w: e.w, part: e.part, retry });
588
+ }
589
+ }
590
+ }
591
+ rep.hours = last / 3600;
592
+ for (const [, since] of firstRefusal)
593
+ rep.longestWait = Math.max(rep.longestWait, last - since);
594
+ rep.stillWaiting = firstRefusal.size;
595
+ // after the run: when can every wallet sell everything?
596
+ const holders = [...s.bal.entries()].filter(([a, v]) => tracked(a) && v > Z).map(([a]) => a);
597
+ const steps = [...Array.from({ length: 180 }, (_, i) => i * 60), ...Array.from({ length: 564 }, (_, i) => 3 * 3600 + i * 300), ...Array.from({ length: 700 }, (_, i) => 2 * 86_400 + i * 3600)];
598
+ rep.allFreeAfter = null;
599
+ const pending = new Set(holders);
600
+ let worst = 0;
601
+ for (const dt of steps) {
602
+ const n = now(last + dt);
603
+ for (const a of [...pending]) {
604
+ if (sellable(s, a, n) < balanceOf(s, a))
605
+ continue;
606
+ pending.delete(a);
607
+ worst = dt;
608
+ }
609
+ if (!pending.size) {
610
+ rep.allFreeAfter = worst;
611
+ break;
612
+ }
613
+ }
614
+ // a scanner an hour after launch: a tiny test buy, then sell at once
615
+ const probe = newCoinCopy(s);
616
+ const tp = now(3600);
617
+ const small = coinsFor(Z, 0.001);
618
+ const rb = transfer(probe, POOL, "scanner", small, tp);
619
+ rep.scanner.buy = rb.ok ? null : typeof rb.why === "number" ? rb.why : null;
620
+ if (rb.ok) {
621
+ const rs = transfer(probe, "scanner", POOL, balanceOf(probe, "scanner"), { t: tp.t + 3, b: tp.b + 7 });
622
+ rep.scanner.sell = rs.ok ? null : typeof rs.why === "number" ? rs.why : null;
623
+ }
624
+ const pct = (x) => Number((x * BigInt(1_000_000)) / SUPPLY) / 10_000;
625
+ rep.burnedPct = pct(balanceOf(s, DEAD));
626
+ rep.pot.inPct = pct(potIn);
627
+ rep.pot.paidPct = pct(potOut);
628
+ rep.pot.leftPct = pct(balanceOf(s, POT));
629
+ rep.refusals = [...refused.entries()].map(([row, count]) => ({ row, count })).sort((a, b) => b.count - a.count);
630
+ return rep;
631
+ }
632
+ /** A coin with the same table, fresh from launch: the scanner probe must not see the run's history. */
633
+ function newCoinCopy(s) {
634
+ const c = newCoin(s.rows, s.release, s.creator, s.curveSupply, s.start, s.startBlock);
635
+ c.bal.set(POOL, SUPPLY);
636
+ return c;
637
+ }