@pickhooks/sdk 0.2.0 → 0.5.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/dist/modes.js ADDED
@@ -0,0 +1,86 @@
1
+ import { encodeFunctionData, } from "viem";
2
+ import { comboModeAbi, forwardModeAbi, sticky3FactoryAbi, sticky3TokenAbi, } from "./abis/sticky3.js";
3
+ import { MODE_NAMES, ZERO_ADDRESS } from "./addresses.js";
4
+ import { PickhooksError } from "./errors.js";
5
+ import { MODE_LIST, MODES, encodeModeData, modeBps } from "./modes-catalog.js";
6
+ import { sendTx, } from "./tx.js";
7
+ export function modesModule(ctx) {
8
+ const { pub, a } = ctx;
9
+ const call = (address, abi, functionName, args = []) => pub.readContract({ address, abi: abi, functionName, args });
10
+ const tx = (to, abi, functionName, args, label, value = 0n) => ({
11
+ to,
12
+ value,
13
+ data: encodeFunctionData({ abi: abi, functionName, args }),
14
+ label,
15
+ });
16
+ const playTx = (coin, data, label) => tx(coin, sticky3TokenAbi, "play", [data], label);
17
+ const pokeTx = (coin, data, label) => tx(coin, sticky3TokenAbi, "poke", [data], label);
18
+ const send = (t, s = {}) => sendTx(ctx, t, s);
19
+ const now = async () => Number((await pub.getBlock()).timestamp);
20
+ const modeAddr = (n) => {
21
+ const m = a.modes[n];
22
+ if (m.toLowerCase() === ZERO_ADDRESS)
23
+ throw new PickhooksError(`The mode ${MODES[n].title} is not deployed yet.`, { code: "input" });
24
+ return m;
25
+ };
26
+ /** The coin's mode: its name (null: no mode, "other": a mode the SDK does not know), address, share of buys, game pot. */
27
+ async function of(coin) {
28
+ const [m, bps, pot] = await Promise.all([
29
+ call(coin, sticky3TokenAbi, "mode"),
30
+ call(coin, sticky3TokenAbi, "gameBps"),
31
+ call(coin, sticky3TokenAbi, "gamePot"),
32
+ ]);
33
+ const name = m.toLowerCase() === ZERO_ADDRESS ? null : (MODE_NAMES.find((n) => a.modes[n].toLowerCase() === m.toLowerCase()) ?? "other");
34
+ return { name, address: m, gameBps: Number(bps), gamePot: pot, info: name && name !== "other" ? MODES[name] : null };
35
+ }
36
+ /** A holder's custody in a coin: coins, of them DEV, until, since, the shadow address they sit at, and whether anyone may release it now. */
37
+ async function custody(coin, who) {
38
+ const [h, shadow, t] = await Promise.all([
39
+ call(coin, sticky3TokenAbi, "heldOf", [who]),
40
+ call(coin, sticky3TokenAbi, "shadow", [who]),
41
+ now(),
42
+ ]);
43
+ return { amount: h[0], dev: h[1], until: Number(h[2]), since: Number(h[3]), shadow, releasable: h[0] > 0n && t >= Number(h[2]) };
44
+ }
45
+ const buildRelease = (coin, who) => tx(coin, sticky3TokenAbi, "releaseHeld", [who], "Releasing the custody");
46
+ /** Is the mode open for launches on the factory now (listed). */
47
+ const listed = async (n) => a.modes[n].toLowerCase() !== ZERO_ADDRESS && (await call(a.sticky3Factory, sticky3FactoryAbi, "modes", [a.modes[n]]).catch(() => false));
48
+ // ---------------------------------------------------------------- the modes that live on trades
49
+ const combo = {
50
+ async state(coin) {
51
+ const g = await call(modeAddr("combo"), comboModeAbi, "games", [coin]);
52
+ const c = Number(g[0]);
53
+ const next = Number(await call(modeAddr("combo"), comboModeAbi, "nextAt", [BigInt(c)]));
54
+ const share = Number(await call(modeAddr("combo"), comboModeAbi, "shareAt", [BigInt(next)]));
55
+ return { combo: c, best: Number(g[1]), hits: Number(g[2]), bps: Number(g[3]), minPpm: Number(g[4]), breaker: g[5], brokeAt: Number(g[6]), next, nextSharePct: share / 100 };
56
+ },
57
+ };
58
+ const forward = {
59
+ async state(coin) {
60
+ const g = await call(modeAddr("forward"), forwardModeAbi, "games", [coin]);
61
+ return { last: g[0], lastGot: g[1], bps: Number(g[2]), minPpm: Number(g[3]), forwards: Number(g[4]) };
62
+ },
63
+ };
64
+ return {
65
+ /** the modes: line, how they play, share of buys and bounds, actions (offline) */
66
+ catalog: MODES,
67
+ list: MODE_LIST,
68
+ info: (n) => MODES[n],
69
+ /** a mode's settings encoded for the launch (zeros: defaults) */
70
+ encode: encodeModeData,
71
+ /** the share of buys a mode takes with these settings */
72
+ bps: modeBps,
73
+ /** is the mode open for launches on the factory now */
74
+ listed,
75
+ of,
76
+ custody,
77
+ build: { release: buildRelease, play: playTx, poke: pokeTx },
78
+ /** anyone: a holder's custody back to the wallet once its time is up */
79
+ release: (coin, who, s) => send(buildRelease(coin, who), s),
80
+ /** a raw play or poke (data: abi.encode(action, ...)) for modes the SDK does not know */
81
+ play: (coin, data, s) => send(playTx(coin, data, "The play"), s),
82
+ poke: (coin, data, s) => send(pokeTx(coin, data, "The poke"), s),
83
+ combo,
84
+ forward,
85
+ };
86
+ }
@@ -0,0 +1,75 @@
1
+ import { type Address, type Hex } from "viem";
2
+ import { type Ctx, type TxRequest } from "./tx.js";
3
+ import { type Venue } from "./v2.js";
4
+ export type SimResult = {
5
+ ok: boolean;
6
+ /** why it would fail, as a sentence (the rule, the mode, PancakeSwap) */
7
+ refused?: {
8
+ code: string;
9
+ text: string;
10
+ };
11
+ /** what reaches the wallet: coins on a buy, BNB (wei) on a sell, 0 on a send or a play */
12
+ out: bigint;
13
+ /** coins that burn (takes of the rows, the mode's burns) */
14
+ burned: bigint;
15
+ /** coins into the coin's pots (the rows' pot and the game pot together) */
16
+ toPots: bigint;
17
+ /** the game pot before and after (sticky 3) */
18
+ gamePot?: {
19
+ before: bigint;
20
+ after: bigint;
21
+ };
22
+ /** coins the pots pay to this wallet (a drop row, the mode's bonus) */
23
+ bonus: bigint;
24
+ /** coins that go into this wallet's custody */
25
+ intoCustody: bigint;
26
+ /** coins that leave this wallet's custody */
27
+ outOfCustody: bigint;
28
+ /** pot payouts to other wallets (pay it forward pays the last buyer) */
29
+ paidOthers: {
30
+ to: Address;
31
+ amount: bigint;
32
+ }[];
33
+ /** the mode's events and the coin's custody events, as sentences */
34
+ events: string[];
35
+ gas: bigint;
36
+ venue?: Venue;
37
+ };
38
+ /** The shadow address a sticky 3 coin keeps `who`'s custody at. */
39
+ export declare function shadowOf(who: Address): Address;
40
+ /** A mode event or a coin event as a sentence (null for events nobody needs to read). */
41
+ export declare function eventText(name: string, args: Record<string, unknown>): string | null;
42
+ export declare function simulateModule(ctx: Ctx): {
43
+ buy: (b: {
44
+ token: Address;
45
+ wallet: Address;
46
+ bnbIn: bigint;
47
+ venue?: Venue;
48
+ }) => Promise<SimResult>;
49
+ sell: (x: {
50
+ token: Address;
51
+ wallet: Address;
52
+ amount: bigint;
53
+ venue?: Venue;
54
+ }) => Promise<SimResult>;
55
+ send: (x: {
56
+ token: Address;
57
+ wallet: Address;
58
+ to: Address;
59
+ amount: bigint;
60
+ }) => Promise<SimResult>;
61
+ play: (x: {
62
+ token: Address;
63
+ wallet: Address;
64
+ data: Hex;
65
+ poke?: boolean;
66
+ }) => Promise<SimResult>;
67
+ tx: (x: {
68
+ token: Address;
69
+ wallet: Address;
70
+ tx: TxRequest;
71
+ }) => Promise<SimResult>;
72
+ shadowOf: typeof shadowOf;
73
+ eventText: typeof eventText;
74
+ };
75
+ export type SimulateModule = ReturnType<typeof simulateModule>;
@@ -0,0 +1,225 @@
1
+ import { concat, decodeEventLog, encodeFunctionData, erc20Abi, getAddress, keccak256, maxUint256, slice, toHex } from "viem";
2
+ import { comboModeAbi, forwardModeAbi, sticky3TokenAbi, } from "./abis/sticky3.js";
3
+ import { stickyTokenAbi } from "./abis/sticky.js";
4
+ import { ZERO_ADDRESS } from "./addresses.js";
5
+ import { errorText, explain } from "./errors.js";
6
+ import { describeRows } from "./sticky.js";
7
+ import { v3EstimateBuy } from "./v3.js";
8
+ import {} from "./tx.js";
9
+ import { buildV2Buy, buildV2Sell } from "./v2.js";
10
+ import { buildV3Buy, buildV3Sell } from "./v3.js";
11
+ /**
12
+ * ph.simulate: every buy, sell, send or play tried first against the chain as it is now (eth_simulateV1, nothing is
13
+ * sent). What comes back is what the coin would do: the coins or BNB that arrive, what burns, what goes into the pots,
14
+ * what a pot pays out (a drop row, the mode's bonus), what goes into custody, the mode's own events as sentences, or the
15
+ * rule, the mode or PancakeSwap reason that would stop it. For agents: trade safely. For terminals: "this is a rule, not
16
+ * a honeypot", in black and white.
17
+ */
18
+ const DEAD = "0x000000000000000000000000000000000000dEaD";
19
+ const FAR = 2n ** 40n;
20
+ const MODE_ABIS = [
21
+ comboModeAbi,
22
+ forwardModeAbi,
23
+ ];
24
+ const short = (x) => `${x.slice(0, 6)}…${x.slice(-4)}`;
25
+ const coins = (x) => {
26
+ const n = Number(x) / 1e18;
27
+ return n >= 1e6 ? `${+(n / 1e6).toFixed(2)}M` : n >= 1e3 ? `${+(n / 1e3).toFixed(1)}k` : `${+n.toFixed(2)}`;
28
+ };
29
+ /** The shadow address a sticky 3 coin keeps `who`'s custody at. */
30
+ export function shadowOf(who) {
31
+ return getAddress(slice(keccak256(concat([toHex("pickhooks.custody"), who])), 12));
32
+ }
33
+ /** A mode event or a coin event as a sentence (null for events nobody needs to read). */
34
+ export function eventText(name, args) {
35
+ const a = (k) => args[k];
36
+ switch (name) {
37
+ case "ModeSkipped":
38
+ return "The mode failed on this trade: the trade goes through without its effect.";
39
+ case "Held":
40
+ return `${short(a("who"))} has ${coins(a("amount"))} coins in custody until ${new Date(Number(a("until")) * 1000).toISOString().slice(0, 16).replace("T", " ")} UTC.`;
41
+ case "ComboUp":
42
+ return `The combo is at ${a("combo")}.`;
43
+ case "ComboHit":
44
+ return `Combo ${a("combo")}: this buy wins ${coins(a("paid"))} coins from the pot.`;
45
+ case "ComboBroken":
46
+ return `This sell breaks the combo at ${a("combo")}.`;
47
+ case "Forwarded":
48
+ return a("amount") > 0n ? `${coins(a("amount"))} coins go to the last buyer ${short(a("paid"))}; ${coins(a("burned"))} burn.` : `${coins(a("burned"))} coins burn (nobody to pay forward).`;
49
+ default:
50
+ return null;
51
+ }
52
+ }
53
+ export function simulateModule(ctx) {
54
+ const { pub, a } = ctx;
55
+ async function run(o) {
56
+ const { token, wallet } = o;
57
+ const v3 = await pub
58
+ .readContract({ address: token, abi: sticky3TokenAbi, functionName: "gamePot" })
59
+ .then(() => true)
60
+ .catch(() => false);
61
+ const potCall = { to: token, data: encodeFunctionData({ abi: sticky3TokenAbi, functionName: "gamePot" }) };
62
+ const bal = await pub.getBalance({ address: wallet });
63
+ const need = (o.bnb ?? 0n) + 10n ** 18n;
64
+ const calls = [...(v3 ? [potCall] : []), ...o.calls.map((c) => ({ to: c.to, data: c.data, value: c.value })), ...(v3 ? [potCall] : [])];
65
+ let r;
66
+ try {
67
+ r = (await pub.simulateCalls({ account: wallet, calls, stateOverrides: bal < need ? [{ address: wallet, balance: need }] : undefined }));
68
+ }
69
+ catch (e) {
70
+ return { ok: false, refused: explain(e, "The trade would fail."), out: 0n, burned: 0n, toPots: 0n, bonus: 0n, intoCustody: 0n, outOfCustody: 0n, paidOthers: [], events: [], gas: 0n };
71
+ }
72
+ const results = r.results;
73
+ const mine = v3 ? results.slice(1, -1) : results;
74
+ const fail = mine.find((x) => x.status !== "success");
75
+ const gas = mine.reduce((s, x) => s + (x.gasUsed ?? 0n), 0n);
76
+ if (fail) {
77
+ let e = explain(fail.error ?? Object.assign(new Error("reverted"), { data: fail.data }), "The trade would fail.");
78
+ // PancakeSwap's routers pass a refused transfer on as STF or TF: ask the coin which rule it was
79
+ if (o.check && !/^(Blocked|Bad|LowGas|Busy|BadOrder|NoMode)$/.test(e.code))
80
+ e = (await whyNot(token, o.check)) ?? e;
81
+ return { ok: false, refused: { code: e.code, text: e.text }, out: 0n, burned: 0n, toPots: 0n, bonus: 0n, intoCustody: 0n, outOfCustody: 0n, paidOthers: [], events: [], gas };
82
+ }
83
+ const logs = mine.flatMap((x) => x.logs ?? []);
84
+ const res = { ok: true, out: 0n, burned: 0n, toPots: 0n, bonus: 0n, intoCustody: 0n, outOfCustody: 0n, paidOthers: [], events: [], gas };
85
+ if (v3) {
86
+ const before = BigInt(results[0].data);
87
+ const after = BigInt(results[results.length - 1].data);
88
+ res.gamePot = { before, after };
89
+ }
90
+ const pools = o.pools.map((p) => p.toLowerCase());
91
+ const me = wallet.toLowerCase();
92
+ const sh = shadowOf(wallet).toLowerCase();
93
+ const coin = token.toLowerCase();
94
+ for (const l of logs) {
95
+ const at = l.address.toLowerCase();
96
+ if (at === coin) {
97
+ let ev;
98
+ try {
99
+ ev = decodeEventLog({ abi: sticky3TokenAbi, data: l.data, topics: l.topics });
100
+ }
101
+ catch {
102
+ try {
103
+ ev = decodeEventLog({ abi: stickyTokenAbi, data: l.data, topics: l.topics });
104
+ }
105
+ catch {
106
+ continue;
107
+ }
108
+ }
109
+ if (ev.eventName === "Transfer") {
110
+ const from = String(ev.args.from).toLowerCase();
111
+ const to = String(ev.args.to).toLowerCase();
112
+ const v = ev.args.value;
113
+ if (to === DEAD.toLowerCase())
114
+ res.burned += v;
115
+ else if (to === coin && from !== coin)
116
+ res.toPots += v;
117
+ else if (from === coin && to === me)
118
+ res.bonus += v;
119
+ else if (from === coin && to === sh)
120
+ res.intoCustody += v;
121
+ else if (from === coin)
122
+ res.paidOthers.push({ to: getAddress(to), amount: v });
123
+ else if (to === sh)
124
+ res.intoCustody += v;
125
+ else if (from === sh)
126
+ res.outOfCustody += v;
127
+ if (to === me && pools.includes(from))
128
+ res.out += v;
129
+ continue;
130
+ }
131
+ const t = eventText(ev.eventName, ev.args);
132
+ if (t)
133
+ res.events.push(t);
134
+ continue;
135
+ }
136
+ for (const abi of MODE_ABIS) {
137
+ try {
138
+ const ev = decodeEventLog({ abi, data: l.data, topics: l.topics });
139
+ const t = eventText(ev.eventName, ev.args);
140
+ if (t)
141
+ res.events.push(t);
142
+ break;
143
+ }
144
+ catch {
145
+ /* another mode's event */
146
+ }
147
+ }
148
+ }
149
+ return res;
150
+ }
151
+ /** The coin's own answer for a trade (check()), as a sentence, or null when it would let it through. */
152
+ async function whyNot(token, c) {
153
+ try {
154
+ const amount = typeof c.amount === "function" ? await c.amount() : c.amount;
155
+ const r = Number(await pub.readContract({ address: token, abi: stickyTokenAbi, functionName: "check", args: [c.kind, c.from, c.to, amount] }));
156
+ if (r === 255)
157
+ return null;
158
+ if (r === 254)
159
+ return { code: "Blocked", text: errorText("Blocked", [254]) };
160
+ const [raw, release, off] = (await pub.readContract({ address: token, abi: stickyTokenAbi, functionName: "script" }));
161
+ const row = describeRows(raw, Number(release), Number(off))[r];
162
+ return { code: "Blocked", text: `Refused by rule ${r + 1}${row ? `: ${row.name}. ${row.text}` : "."}` };
163
+ }
164
+ catch {
165
+ return null;
166
+ }
167
+ }
168
+ const poolsOf = async (token) => {
169
+ const [p, p2] = await Promise.all([
170
+ pub.readContract({ address: token, abi: stickyTokenAbi, functionName: "pool" }).catch(() => ZERO_ADDRESS),
171
+ pub.readContract({ address: token, abi: stickyTokenAbi, functionName: "pool2" }).catch(() => ZERO_ADDRESS),
172
+ ]);
173
+ return [p, p2];
174
+ };
175
+ /** A buy of `bnbIn` (wei) by `wallet` through PancakeSwap (V3 by default, `venue` "v2" for the pair). */
176
+ async function buy(b) {
177
+ const venue = b.venue ?? "v3";
178
+ const t = venue === "v2" ? buildV2Buy(a, { token: b.token, bnbIn: b.bnbIn, minOut: 0n, recipient: b.wallet, deadline: FAR }) : buildV3Buy(a, { token: b.token, bnbIn: b.bnbIn, minOut: 0n, recipient: b.wallet, deadline: FAR });
179
+ const pools = await poolsOf(b.token);
180
+ const check = { kind: 1, from: ZERO_ADDRESS, to: b.wallet, amount: () => v3EstimateBuy(ctx, pools[0], b.token, b.bnbIn) };
181
+ return { ...(await run({ token: b.token, wallet: b.wallet, calls: [t], bnb: b.bnbIn, pools, check })), venue };
182
+ }
183
+ /** A sell of `amount` coins by `wallet` for BNB (the approval to the router included in the run). */
184
+ async function sell(x) {
185
+ const venue = x.venue ?? "v3";
186
+ const router = venue === "v2" ? a.v2Router : a.v3Router;
187
+ const approve = { to: x.token, value: 0n, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [router, maxUint256] }) };
188
+ const t = venue === "v2" ? buildV2Sell(a, { token: x.token, amount: x.amount, minOut: 0n, recipient: x.wallet, deadline: FAR }) : buildV3Sell(a, { token: x.token, amount: x.amount, minOut: 0n, recipient: x.wallet, deadline: FAR });
189
+ const before = await pub.getBalance({ address: x.wallet });
190
+ const pools = await poolsOf(x.token);
191
+ const r = await run({ token: x.token, wallet: x.wallet, calls: [approve, t], pools, check: { kind: 2, from: x.wallet, to: pools[0], amount: x.amount } });
192
+ // the BNB out: the router unwraps to the wallet (native BNB, no Transfer log); ask the chain for the balance after
193
+ if (r.ok) {
194
+ const sim = await pub
195
+ .simulateCalls({
196
+ account: x.wallet,
197
+ calls: [{ to: approve.to, data: approve.data }, { to: t.to, data: t.data }, { to: "0xcA11bde05977b3631167028862bE2a173976CA11", data: encodeFunctionData({ abi: [{ type: "function", name: "getEthBalance", stateMutability: "view", inputs: [{ name: "a", type: "address" }], outputs: [{ type: "uint256" }] }], functionName: "getEthBalance", args: [x.wallet] }) }],
198
+ })
199
+ .catch(() => null);
200
+ const last = sim?.results[2];
201
+ if (last?.status === "success") {
202
+ const after = BigInt(last.data);
203
+ const spent = sim.results[0].gasUsed + sim.results[1].gasUsed;
204
+ r.out = after > before ? after - before : 0n;
205
+ void spent; // eth_simulateV1 runs without a gas price: the balance moves by the trade only
206
+ }
207
+ }
208
+ return { ...r, venue };
209
+ }
210
+ /** A send of `amount` coins from `wallet` to `to` (a forward of a router, too). */
211
+ async function send(x) {
212
+ const t = { to: x.token, value: 0n, data: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [x.to, x.amount] }) };
213
+ return run({ token: x.token, wallet: x.wallet, calls: [t], pools: await poolsOf(x.token), check: { kind: 4, from: x.wallet, to: x.to, amount: x.amount } });
214
+ }
215
+ /** A play (abi.encode(action, ...)) or a poke by `wallet`: what the mode would do with it. */
216
+ async function play(x) {
217
+ const t = { to: x.token, value: 0n, data: encodeFunctionData({ abi: sticky3TokenAbi, functionName: x.poke ? "poke" : "play", args: [x.data] }) };
218
+ return run({ token: x.token, wallet: x.wallet, calls: [t], pools: await poolsOf(x.token) });
219
+ }
220
+ /** Any request the SDK builds (ph.modes.*.build, ph.sticky3.build.*), as `wallet`. */
221
+ async function tx(x) {
222
+ return run({ token: x.token, wallet: x.wallet, calls: [x.tx], bnb: x.tx.value, pools: await poolsOf(x.token) });
223
+ }
224
+ return { buy, sell, send, play, tx, shadowOf, eventText };
225
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * sticky rules in the browser: the coin's rule engine (src/sticky/StickyToken.sol) line for line, on bigints, and a test
3
+ * run of random trades on top. The engine is checked against the contract trade by trade (test/sticky/StickyTrace.t.sol
4
+ * writes a run, scripts/dev/sticky-sim-diff.mjs replays it here), so what the launcher shows is what the coin would do.
5
+ * The chain stays the truth: this only shows a creator what a table does before the launch.
6
+ */
7
+ import { type Row } from "./sticky-rules.js";
8
+ export declare const SUPPLY: bigint;
9
+ /** The addresses the coin does not keep lots for. */
10
+ export declare const POOL = "pool";
11
+ export declare const POT = "pot";
12
+ export declare const DEAD = "dead";
13
+ export type Lot = {
14
+ amount: bigint;
15
+ lk: bigint;
16
+ since: number;
17
+ flags: number;
18
+ };
19
+ export type Stats = {
20
+ holders: number;
21
+ buys: number;
22
+ sells: number;
23
+ highAt: number;
24
+ lastBuy: number;
25
+ volume: bigint;
26
+ net: bigint;
27
+ peak: bigint;
28
+ high: bigint;
29
+ };
30
+ export type Now = {
31
+ t: number;
32
+ b: number;
33
+ };
34
+ export type CoinState = {
35
+ rows: Row[];
36
+ release: number;
37
+ off: number;
38
+ creator: string;
39
+ start: number;
40
+ startBlock: number;
41
+ curveSupply: bigint;
42
+ stats: Stats;
43
+ bal: Map<string, bigint>;
44
+ lots: Map<string, Lot[]>;
45
+ };
46
+ export type Result = {
47
+ ok: true;
48
+ burn: bigint;
49
+ toPot: bigint;
50
+ drop: bigint;
51
+ } | {
52
+ ok: false;
53
+ why: number | "nope";
54
+ };
55
+ export declare function newCoin(rows: (bigint | Row)[], release: number, creator: string, curveSupply: bigint, start?: number, startBlock?: number): CoinState;
56
+ /** A transfer as the coin runs it: out of POOL is a buy, into POOL a sell, anything else a send. */
57
+ export declare function transfer(s: CoinState, from: string, to: string, amount: bigint, now: Now): Result;
58
+ /** What `a` could sell right now (size rows left out, like the coin's view). */
59
+ export declare function sellable(s: CoinState, a: string, now: Now): bigint;
60
+ export declare function balanceOf(s: CoinState, a: string): bigint;
61
+ export declare function lots(s: CoinState, a: string): Lot[];
62
+ /** The curve as the coin prices it: x * y = k from the start price, so price / start = (supply / coins left)². */
63
+ export declare const CURVE: {
64
+ supply: bigint;
65
+ startFdvBnb: number;
66
+ };
67
+ export type SimReport = {
68
+ trades: number;
69
+ hours: number;
70
+ buys: {
71
+ ok: number;
72
+ refused: number;
73
+ };
74
+ sells: {
75
+ ok: number;
76
+ refused: number;
77
+ };
78
+ sends: {
79
+ ok: number;
80
+ refused: number;
81
+ };
82
+ /** refusals by row (0 based) and LOCKED (254) */
83
+ refusals: {
84
+ row: number;
85
+ count: number;
86
+ }[];
87
+ /** longest a wallet that wanted to sell had to wait until a sell went through (seconds) */
88
+ longestWait: number;
89
+ /** wallets still unable to sell when the run ended */
90
+ stillWaiting: number;
91
+ /** after the run: seconds until every wallet can sell everything (null: longer than 31 days) */
92
+ allFreeAfter: number | null;
93
+ burnedPct: number;
94
+ pot: {
95
+ inPct: number;
96
+ drops: number;
97
+ paidPct: number;
98
+ biggestX: number;
99
+ leftPct: number;
100
+ };
101
+ snipers: {
102
+ count: number;
103
+ firstSellAfter: number | null;
104
+ };
105
+ splits: {
106
+ count: number;
107
+ refusedAfter: number;
108
+ };
109
+ /** a scanner's test: a tiny buy and an instant sell an hour after launch */
110
+ scanner: {
111
+ buy: number | null;
112
+ sell: number | null;
113
+ };
114
+ };
115
+ /**
116
+ * Random trades on a fresh coin with this table: the creator's first buy, three snipers in the first blocks, wallets
117
+ * that come, buy, hold for a while and sell (and try again when refused), some bags split to fresh wallets that then
118
+ * try to sell. The same seed gives the same run.
119
+ */
120
+ export declare function simulate(rows: bigint[], release: number, opts?: {
121
+ seed?: number;
122
+ trades?: number;
123
+ devBuyBnb?: number;
124
+ start?: number;
125
+ }): SimReport;