@pickhooks/sdk 0.4.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/README.md +32 -2
- package/dist/abis/index.d.ts +1 -0
- package/dist/abis/index.js +1 -0
- package/dist/abis/sticky3.d.ts +2292 -0
- package/dist/abis/sticky3.js +8 -0
- package/dist/addresses.d.ts +11 -0
- package/dist/addresses.js +12 -0
- package/dist/errors.js +28 -0
- package/dist/index.d.ts +26 -2
- package/dist/index.js +45 -1
- package/dist/modes-catalog.d.ts +59 -0
- package/dist/modes-catalog.js +77 -0
- package/dist/modes.d.ts +71 -0
- package/dist/modes.js +86 -0
- package/dist/simulate.d.ts +75 -0
- package/dist/simulate.js +225 -0
- package/dist/sticky.d.ts +21 -2
- package/dist/sticky.js +119 -9
- package/dist/text-launch.d.ts +52 -0
- package/dist/text-launch.js +178 -0
- package/dist/tlock.d.ts +10 -0
- package/dist/tlock.js +16 -0
- package/package.json +3 -2
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>;
|
package/dist/simulate.js
ADDED
|
@@ -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
|
+
}
|
package/dist/sticky.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type Address, type Hex } from "viem";
|
|
2
|
+
import { type ModeName } from "./addresses.js";
|
|
3
|
+
import { type ModeSettings } from "./modes-catalog.js";
|
|
2
4
|
import { type EditRow, type Pick as RulePick, compile } from "./sticky-rules.js";
|
|
3
5
|
import { type SimReport } from "./sticky-sim.js";
|
|
4
6
|
import { type Ctx, type SendOptions, type Sent, type TxRequest } from "./tx.js";
|
|
@@ -54,6 +56,13 @@ export type StickyCoin = {
|
|
|
54
56
|
/** BNB per coin at the V3 pool's price, and the market cap in BNB */
|
|
55
57
|
priceBnb: number;
|
|
56
58
|
mcapBnb: number;
|
|
59
|
+
/** sticky 3: the coin's mode (its name, or "other" for a mode the SDK does not know), its share of buys and the game pot */
|
|
60
|
+
mode?: {
|
|
61
|
+
name: ModeName | "other" | null;
|
|
62
|
+
address: Address;
|
|
63
|
+
gameBps: number;
|
|
64
|
+
gamePot: bigint;
|
|
65
|
+
};
|
|
57
66
|
};
|
|
58
67
|
export type StickyLot = {
|
|
59
68
|
amount: bigint;
|
|
@@ -102,6 +111,9 @@ export type StickyLaunch = {
|
|
|
102
111
|
*/
|
|
103
112
|
switchRows?: number[];
|
|
104
113
|
switcher?: Address;
|
|
114
|
+
/** sticky 3 only: the coin's mode (fixed for good) and its settings within the mode's bounds (defaults when left out) */
|
|
115
|
+
mode?: ModeName;
|
|
116
|
+
modeSettings?: ModeSettings;
|
|
105
117
|
};
|
|
106
118
|
export type StickyTerms = {
|
|
107
119
|
seed: bigint;
|
|
@@ -145,13 +157,16 @@ export declare function stickyTestRun(l: Pick<StickyLaunch, "picks" | "script" |
|
|
|
145
157
|
}): StickyTestRun;
|
|
146
158
|
/**
|
|
147
159
|
* The sticky module. `v2` points it at the sticky 2 factory (same coins, same rules, same calls; its coins also count
|
|
148
|
-
* the creator's sold coins and can carry switch rows, see ph.sticky2).
|
|
160
|
+
* the creator's sold coins and can carry switch rows, see ph.sticky2). `3` points it at sticky 3: the same again, plus a
|
|
161
|
+
* mode per coin (launch with `mode` and `modeSettings`; the game itself is ph.modes); a sticky 3 coin leaves its lot
|
|
162
|
+
* views to the lens.
|
|
149
163
|
*/
|
|
150
|
-
export declare function stickyModule(ctx: Ctx,
|
|
164
|
+
export declare function stickyModule(ctx: Ctx, version?: boolean | 3): {
|
|
151
165
|
factory: `0x${string}`;
|
|
152
166
|
terms: () => Promise<StickyTerms>;
|
|
153
167
|
validate: (rows: bigint[], release: number, custom?: boolean) => Promise<string | null>;
|
|
154
168
|
validateSwitch: (rows: bigint[], mask: number, validator?: Address) => Promise<string | null>;
|
|
169
|
+
validateMode: (mode: ModeName, settings: ModeSettings | undefined, rows: bigint[], validator?: Address, creator?: Address) => Promise<string | null>;
|
|
155
170
|
compile: typeof compile;
|
|
156
171
|
table: typeof stickyTable;
|
|
157
172
|
testRun: typeof stickyTestRun;
|
|
@@ -182,6 +197,7 @@ export declare function stickyModule(ctx: Ctx, v2?: boolean): {
|
|
|
182
197
|
balance: bigint;
|
|
183
198
|
sellable: bigint;
|
|
184
199
|
lots: StickyLot[];
|
|
200
|
+
held?: bigint;
|
|
185
201
|
}>;
|
|
186
202
|
check: (c: {
|
|
187
203
|
token: Address;
|
|
@@ -202,6 +218,7 @@ export declare function stickyModule(ctx: Ctx, v2?: boolean): {
|
|
|
202
218
|
v2: bigint | null;
|
|
203
219
|
}>;
|
|
204
220
|
estimateBuy: (token: Address, bnbIn: bigint) => Promise<bigint>;
|
|
221
|
+
buyTakeBps: (token: Address) => Promise<number>;
|
|
205
222
|
sellApproval: (owner: Address, token: Address, amount: bigint) => Promise<TxRequest | null>;
|
|
206
223
|
build: {
|
|
207
224
|
launch: (l: {
|
|
@@ -215,6 +232,8 @@ export declare function stickyModule(ctx: Ctx, v2?: boolean): {
|
|
|
215
232
|
minCreatorBps: number;
|
|
216
233
|
switcher?: Address;
|
|
217
234
|
switchMask?: number;
|
|
235
|
+
mode?: ModeName;
|
|
236
|
+
modeSettings?: ModeSettings;
|
|
218
237
|
}, value: bigint) => TxRequest;
|
|
219
238
|
buy: (b: {
|
|
220
239
|
token: Address;
|