@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/README.md +143 -4
- package/dist/abis/agents.d.ts +2418 -0
- package/dist/abis/agents.js +4 -0
- package/dist/abis/index.d.ts +3 -0
- package/dist/abis/index.js +3 -0
- package/dist/abis/sticky2.d.ts +3750 -0
- package/dist/abis/sticky2.js +8 -0
- package/dist/abis/sticky3.d.ts +2292 -0
- package/dist/abis/sticky3.js +8 -0
- package/dist/addresses.d.ts +25 -1
- package/dist/addresses.js +23 -2
- package/dist/agents-catalog.d.ts +318 -0
- package/dist/agents-catalog.js +353 -0
- package/dist/agents.d.ts +543 -0
- package/dist/agents.js +667 -0
- package/dist/desks.d.ts +298 -0
- package/dist/desks.js +343 -0
- package/dist/errors.js +97 -2
- package/dist/index.d.ts +59 -4
- package/dist/index.js +86 -9
- 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-sim.d.ts +125 -0
- package/dist/sticky-sim.js +637 -0
- package/dist/sticky.d.ts +93 -4
- package/dist/sticky.js +281 -43
- package/dist/text-launch.d.ts +52 -0
- package/dist/text-launch.js +178 -0
- package/dist/tlock.d.ts +77 -0
- package/dist/tlock.js +201 -0
- package/dist/tx.d.ts +2 -0
- package/dist/tx.js +9 -0
- package/dist/v2.d.ts +106 -0
- package/dist/v2.js +61 -0
- package/dist/wallet.d.ts +80 -0
- package/dist/wallet.js +115 -0
- package/dist/watch.d.ts +82 -0
- package/dist/watch.js +194 -0
- package/package.json +12 -2
package/dist/v2.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { encodeFunctionData, parseAbi } from "viem";
|
|
2
|
+
import { deadlineIn } from "./tx.js";
|
|
3
|
+
/**
|
|
4
|
+
* PancakeSwap V2: every sticky coin (and every rules coin since 03.10.) has a V2 pair next to its V3 pool, at the same
|
|
5
|
+
* start price. A dark launch puts the whole round's BNB into that pair, so right after the birth the pair is the venue
|
|
6
|
+
* with the BNB and the V3 curve holds only coins above the price: a sell into V3 would get almost nothing ("SPL").
|
|
7
|
+
* Terminals route to the better pool by themselves; the SDK quotes both and picks the better one (sticky.quote).
|
|
8
|
+
*/
|
|
9
|
+
export const v2RouterAbi = parseAbi([
|
|
10
|
+
"function getAmountsOut(uint256 amountIn, address[] path) view returns (uint256[] amounts)",
|
|
11
|
+
"function swapExactETHForTokensSupportingFeeOnTransferTokens(uint256 amountOutMin, address[] path, address to, uint256 deadline) payable",
|
|
12
|
+
"function swapExactTokensForETHSupportingFeeOnTransferTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline)",
|
|
13
|
+
]);
|
|
14
|
+
/** What a trade gets in the V2 pair, from the router (reverts when the coin has no pair or the pair is empty). */
|
|
15
|
+
export async function v2Quote(ctx, q) {
|
|
16
|
+
const path = q.side === "buy" ? [ctx.a.wbnb, q.token] : [q.token, ctx.a.wbnb];
|
|
17
|
+
const amounts = (await ctx.pub.readContract({ address: ctx.a.v2Router, abi: v2RouterAbi, functionName: "getAmountsOut", args: [q.amount, path] }));
|
|
18
|
+
return amounts[amounts.length - 1];
|
|
19
|
+
}
|
|
20
|
+
/** A buy through the V2 router, BNB in. */
|
|
21
|
+
export function buildV2Buy(a, b) {
|
|
22
|
+
return {
|
|
23
|
+
to: a.v2Router,
|
|
24
|
+
value: b.bnbIn,
|
|
25
|
+
data: encodeFunctionData({ abi: v2RouterAbi, functionName: "swapExactETHForTokensSupportingFeeOnTransferTokens", args: [b.minOut, [a.wbnb, b.token], b.recipient, b.deadline ?? deadlineIn()] }),
|
|
26
|
+
gasPct: b.gasPct,
|
|
27
|
+
label: "The buy",
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** A sell through the V2 router for native BNB. Needs the coin approved to the V2 router. */
|
|
31
|
+
export function buildV2Sell(a, s) {
|
|
32
|
+
return {
|
|
33
|
+
to: a.v2Router,
|
|
34
|
+
value: 0n,
|
|
35
|
+
data: encodeFunctionData({ abi: v2RouterAbi, functionName: "swapExactTokensForETHSupportingFeeOnTransferTokens", args: [s.amount, s.minOut, [s.token, a.wbnb], s.recipient, s.deadline ?? deadlineIn()] }),
|
|
36
|
+
gasPct: s.gasPct,
|
|
37
|
+
label: "The sell",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The better of the two venues for this trade: the V3 pool (quoter) and the V2 pair (router). A venue that cannot
|
|
42
|
+
* quote (no pair, no BNB below the price, a refusing rule) counts as nothing; `errors` keeps what each one said.
|
|
43
|
+
*/
|
|
44
|
+
export async function bestVenue(ctx, quotes) {
|
|
45
|
+
const errors = {};
|
|
46
|
+
const [v3, v2] = await Promise.all([
|
|
47
|
+
quotes.v3().catch((e) => {
|
|
48
|
+
errors.v3 = e;
|
|
49
|
+
return null;
|
|
50
|
+
}),
|
|
51
|
+
quotes.v2().catch((e) => {
|
|
52
|
+
errors.v2 = e;
|
|
53
|
+
return null;
|
|
54
|
+
}),
|
|
55
|
+
]);
|
|
56
|
+
void ctx;
|
|
57
|
+
if (v3 === null && v2 === null)
|
|
58
|
+
return { venue: "v3", amountOut: 0n, v3, v2, errors };
|
|
59
|
+
const venue = (v2 ?? 0n) > (v3 ?? 0n) ? "v2" : "v3";
|
|
60
|
+
return { venue, amountOut: venue === "v2" ? v2 : v3, v3, v2, errors };
|
|
61
|
+
}
|
package/dist/wallet.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type Address } from "viem";
|
|
2
|
+
import type { AgentLock, AgentsModule } from "./agents.js";
|
|
3
|
+
import type { AnyModule, Earnings } from "./any.js";
|
|
4
|
+
import type { CoinsModule } from "./coins.js";
|
|
5
|
+
import type { DipFloor, DipsModule } from "./dips.js";
|
|
6
|
+
import type { Lever2Module } from "./lever.js";
|
|
7
|
+
import type { CallsModule, PoolsModule } from "./pools.js";
|
|
8
|
+
import type { StickyModule } from "./sticky.js";
|
|
9
|
+
import type { Ctx } from "./tx.js";
|
|
10
|
+
import type { WatchMode } from "./watch.js";
|
|
11
|
+
/**
|
|
12
|
+
* One address in: everything it holds on pickhooks, what it can sell right now, and what it can claim.
|
|
13
|
+
*
|
|
14
|
+
* const w = await ph.wallet(address);
|
|
15
|
+
* w.coins // every pickhooks coin it holds, with mode, price, value and what is sellable now
|
|
16
|
+
* w.claims // BNB owed by the hooks (hook any coin, pools, curve calls, agents)
|
|
17
|
+
* w.locks // coins locked at the agents desk (vetoes, firing)
|
|
18
|
+
* w.floors // dip bid floors it holds
|
|
19
|
+
*/
|
|
20
|
+
export type WalletCoin = {
|
|
21
|
+
token: Address;
|
|
22
|
+
mode: WatchMode;
|
|
23
|
+
name: string;
|
|
24
|
+
symbol: string;
|
|
25
|
+
balance: bigint;
|
|
26
|
+
/** what a sell of the whole balance would be allowed right now: the balance, less (sticky: unlocked lots), or 0 with the reason */
|
|
27
|
+
sellable: bigint;
|
|
28
|
+
/** why not everything is sellable (a rule's sentence), or null */
|
|
29
|
+
refused: string | null;
|
|
30
|
+
priceBnb: number;
|
|
31
|
+
/** balance x price, in BNB */
|
|
32
|
+
valueBnb: number;
|
|
33
|
+
page: string;
|
|
34
|
+
};
|
|
35
|
+
export type WalletView = {
|
|
36
|
+
address: Address;
|
|
37
|
+
bnb: bigint;
|
|
38
|
+
coins: WalletCoin[];
|
|
39
|
+
/** the coins' value together, BNB */
|
|
40
|
+
valueBnb: number;
|
|
41
|
+
claims: {
|
|
42
|
+
/** hook any coin: recapture shares, opener and partner shares, router credits */
|
|
43
|
+
any: Earnings | null;
|
|
44
|
+
/** pools and curve calls: jackpot wins, rewards, a creator's share */
|
|
45
|
+
pools: bigint;
|
|
46
|
+
calls: bigint;
|
|
47
|
+
/** agents: jackpot wins, rewards, a creator's share */
|
|
48
|
+
agents: bigint;
|
|
49
|
+
/** all of the above in BNB (router credits counted as claimable) */
|
|
50
|
+
totalBnb: bigint;
|
|
51
|
+
};
|
|
52
|
+
/** coins locked at the agents desk, per agent coin */
|
|
53
|
+
locks: ({
|
|
54
|
+
token: Address;
|
|
55
|
+
symbol: string;
|
|
56
|
+
} & AgentLock)[];
|
|
57
|
+
floors: DipFloor[];
|
|
58
|
+
/** factories the scan covered and how many coins each has (the newest `limit` per factory are scanned) */
|
|
59
|
+
scanned: {
|
|
60
|
+
mode: WatchMode;
|
|
61
|
+
count: number;
|
|
62
|
+
scanned: number;
|
|
63
|
+
}[];
|
|
64
|
+
};
|
|
65
|
+
type Mods = {
|
|
66
|
+
sticky: StickyModule;
|
|
67
|
+
sticky2: StickyModule;
|
|
68
|
+
coins: CoinsModule;
|
|
69
|
+
pools: PoolsModule;
|
|
70
|
+
calls: CallsModule;
|
|
71
|
+
lever: Lever2Module;
|
|
72
|
+
agents: AgentsModule;
|
|
73
|
+
any: AnyModule;
|
|
74
|
+
dips: DipsModule;
|
|
75
|
+
};
|
|
76
|
+
export declare function walletModule(ctx: Ctx, mods: Mods): (who: Address, o?: {
|
|
77
|
+
limit?: number;
|
|
78
|
+
}) => Promise<WalletView>;
|
|
79
|
+
export type WalletModule = ReturnType<typeof walletModule>;
|
|
80
|
+
export {};
|
package/dist/wallet.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { formatEther, isAddressEqual } from "viem";
|
|
2
|
+
import { agentsFactoryAbi } from "./abis/agents.js";
|
|
3
|
+
import { callsFactoryAbi } from "./abis/calls.js";
|
|
4
|
+
import { erc20Abi } from "./abis/common.js";
|
|
5
|
+
import { lever2FactoryAbi } from "./abis/lever2.js";
|
|
6
|
+
import { poolsFactoryAbi } from "./abis/pools.js";
|
|
7
|
+
import { stickyFactoryAbi } from "./abis/sticky.js";
|
|
8
|
+
import { sticky2FactoryAbi } from "./abis/sticky2.js";
|
|
9
|
+
import { v1FactoryAbi } from "./abis/v1.js";
|
|
10
|
+
import { v2FactoryAbi } from "./abis/v2.js";
|
|
11
|
+
import { ZERO_ADDRESS } from "./addresses.js";
|
|
12
|
+
export function walletModule(ctx, mods) {
|
|
13
|
+
const { pub, a } = ctx;
|
|
14
|
+
const page = (mode, token) => {
|
|
15
|
+
const path = { sticky: "s", sticky2: "s", rules: "c", "rules-v1": "c", pools: "h", calls: "z", lever: "l", agents: "a", any: "any" }[mode];
|
|
16
|
+
return `${ctx.site}/${path}/${token}`;
|
|
17
|
+
};
|
|
18
|
+
const factories = [
|
|
19
|
+
{ mode: "sticky", address: a.stickyFactory, abi: stickyFactoryAbi },
|
|
20
|
+
{ mode: "sticky2", address: a.sticky2Factory, abi: sticky2FactoryAbi },
|
|
21
|
+
{ mode: "rules", address: a.coinsV2Factory, abi: v2FactoryAbi },
|
|
22
|
+
{ mode: "rules-v1", address: a.coinsV1Factory, abi: v1FactoryAbi },
|
|
23
|
+
{ mode: "pools", address: a.poolsFactory, abi: poolsFactoryAbi },
|
|
24
|
+
{ mode: "calls", address: a.callsFactory, abi: callsFactoryAbi },
|
|
25
|
+
{ mode: "lever", address: a.lever2Factory, abi: lever2FactoryAbi },
|
|
26
|
+
{ mode: "agents", address: a.agentsFactory, abi: agentsFactoryAbi },
|
|
27
|
+
];
|
|
28
|
+
/** The newest `limit` coins of every factory. */
|
|
29
|
+
async function tokens(limit) {
|
|
30
|
+
const live = factories.filter((f) => !isAddressEqual(f.address, ZERO_ADDRESS));
|
|
31
|
+
const counts = await pub.multicall({ contracts: live.map((f) => ({ address: f.address, abi: f.abi, functionName: "coinCount" })), allowFailure: true });
|
|
32
|
+
const calls = [];
|
|
33
|
+
const owners = [];
|
|
34
|
+
const scanned = [];
|
|
35
|
+
live.forEach((f, i) => {
|
|
36
|
+
const n = counts[i].status === "success" ? Number(counts[i].result) : 0;
|
|
37
|
+
const from = Math.max(0, n - limit);
|
|
38
|
+
scanned.push({ mode: f.mode, count: n, scanned: n - from });
|
|
39
|
+
for (let j = n - 1; j >= from; j--) {
|
|
40
|
+
calls.push({ address: f.address, abi: f.abi, functionName: "coins", args: [BigInt(j)] });
|
|
41
|
+
owners.push(f.mode);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
if (!calls.length)
|
|
45
|
+
return { list: [], scanned };
|
|
46
|
+
const res = await pub.multicall({ contracts: calls, allowFailure: true });
|
|
47
|
+
const list = [];
|
|
48
|
+
res.forEach((r, i) => {
|
|
49
|
+
if (r.status === "success")
|
|
50
|
+
list.push({ mode: owners[i], token: r.result });
|
|
51
|
+
});
|
|
52
|
+
return { list, scanned };
|
|
53
|
+
}
|
|
54
|
+
/** A held coin's name, symbol, price and what is sellable now, by mode. */
|
|
55
|
+
async function detail(mode, token, who, balance) {
|
|
56
|
+
const refusedOf = (r) => (r.ok ? null : (r.text ?? "The coin refused a sell right now."));
|
|
57
|
+
if (mode === "sticky" || mode === "sticky2") {
|
|
58
|
+
const m = mode === "sticky" ? mods.sticky : mods.sticky2;
|
|
59
|
+
const [c, w] = await Promise.all([m.coin(token), m.wallet(token, who)]);
|
|
60
|
+
return { name: c?.name ?? "", symbol: c?.symbol ?? "", sellable: w.sellable, refused: w.sellable < balance ? "Part of these coins is still locked by a rule; it unlocks over time." : null, priceBnb: c?.priceBnb ?? 0, valueBnb: (c?.priceBnb ?? 0) * Number(formatEther(balance)) };
|
|
61
|
+
}
|
|
62
|
+
if (mode === "rules" || mode === "rules-v1") {
|
|
63
|
+
const [c, r] = await Promise.all([mods.coins.coin(token), mods.coins.check({ token, side: "sell", wallet: who, amount: balance }).catch(() => ({ ok: false, kind: 0, name: "", text: "The coin could not be asked." }))]);
|
|
64
|
+
return { name: c?.name ?? "", symbol: c?.symbol ?? "", sellable: r.ok ? balance : 0n, refused: refusedOf(r), priceBnb: c?.priceBnb ?? 0, valueBnb: (c?.priceBnb ?? 0) * Number(formatEther(balance)) };
|
|
65
|
+
}
|
|
66
|
+
if (mode === "pools" || mode === "calls" || mode === "agents") {
|
|
67
|
+
const m = mode === "pools" ? mods.pools : mode === "calls" ? mods.calls : mods.agents;
|
|
68
|
+
const [c, r] = await Promise.all([m.coin(token), m.check({ token, side: "sell", wallet: who, amount: balance }).catch(() => ({ ok: false, kind: 0, name: "", text: "The pool could not be asked." }))]);
|
|
69
|
+
return { name: c?.name ?? "", symbol: c?.symbol ?? "", sellable: r.ok ? balance : 0n, refused: refusedOf(r), priceBnb: c?.priceBnb ?? 0, valueBnb: (c?.priceBnb ?? 0) * Number(formatEther(balance)) };
|
|
70
|
+
}
|
|
71
|
+
if (mode === "lever") {
|
|
72
|
+
const c = await mods.lever.coin(token);
|
|
73
|
+
return { name: c?.name ?? "", symbol: c?.symbol ?? "", sellable: balance, refused: null, priceBnb: c?.priceBnb ?? 0, valueBnb: (c?.priceBnb ?? 0) * Number(formatEther(balance)) };
|
|
74
|
+
}
|
|
75
|
+
const r = await pub.multicall({ contracts: [{ address: token, abi: erc20Abi, functionName: "name" }, { address: token, abi: erc20Abi, functionName: "symbol" }], allowFailure: true });
|
|
76
|
+
return { name: r[0].status === "success" ? String(r[0].result) : "", symbol: r[1].status === "success" ? String(r[1].result) : "", sellable: balance, refused: null, priceBnb: 0, valueBnb: 0 };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Everything a wallet holds on pickhooks: the newest `limit` coins of every factory (default 300) are checked for a
|
|
80
|
+
* balance, the held ones read in full. Claims and locks come from the hooks and the agents desk, floors from dip bids.
|
|
81
|
+
*/
|
|
82
|
+
async function wallet(who, o = {}) {
|
|
83
|
+
const { list, scanned } = await tokens(o.limit ?? 300);
|
|
84
|
+
const [bnb, balances] = await Promise.all([
|
|
85
|
+
pub.getBalance({ address: who }),
|
|
86
|
+
list.length ? pub.multicall({ contracts: list.map((t) => ({ address: t.token, abi: erc20Abi, functionName: "balanceOf", args: [who] })), allowFailure: true }) : [],
|
|
87
|
+
]);
|
|
88
|
+
const held = list.filter((_, i) => balances[i]?.status === "success" && balances[i].result > 0n).map((t, j) => ({ ...t, balance: balances[list.indexOf(t)].result, j }));
|
|
89
|
+
const coins = await Promise.all(held.map(async (h) => ({ token: h.token, mode: h.mode, balance: h.balance, page: page(h.mode, h.token), ...(await detail(h.mode, h.token, who, h.balance)) })));
|
|
90
|
+
coins.sort((x, y) => y.valueBnb - x.valueBnb);
|
|
91
|
+
const agentTokens = list.filter((t) => t.mode === "agents").map((t) => t.token);
|
|
92
|
+
const [any, pools, calls, agents, lockRows, floors] = await Promise.all([
|
|
93
|
+
mods.any.earnings(who).catch(() => null),
|
|
94
|
+
mods.pools.owed(who).catch(() => 0n),
|
|
95
|
+
mods.calls.owed(who).catch(() => 0n),
|
|
96
|
+
mods.agents.on ? mods.agents.owed(who).catch(() => 0n) : Promise.resolve(0n),
|
|
97
|
+
Promise.all(agentTokens.map((t) => mods.agents.locks(t, who).then((l) => ({ token: t, ...l })).catch(() => null))),
|
|
98
|
+
mods.dips.floorsOf(who).catch(() => []),
|
|
99
|
+
]);
|
|
100
|
+
const locks = lockRows.filter((l) => l !== null && l.amount > 0n);
|
|
101
|
+
const lockSymbols = locks.length ? await pub.multicall({ contracts: locks.map((l) => ({ address: l.token, abi: erc20Abi, functionName: "symbol" })), allowFailure: true }) : [];
|
|
102
|
+
const anyBnb = any ? any.owed + any.routerClaimable + any.taxOwed : 0n;
|
|
103
|
+
return {
|
|
104
|
+
address: who,
|
|
105
|
+
bnb,
|
|
106
|
+
coins,
|
|
107
|
+
valueBnb: coins.reduce((s, c) => s + c.valueBnb, 0),
|
|
108
|
+
claims: { any, pools, calls, agents, totalBnb: anyBnb + pools + calls + agents },
|
|
109
|
+
locks: locks.map((l, i) => ({ ...l, symbol: lockSymbols[i]?.status === "success" ? String(lockSymbols[i].result) : "" })),
|
|
110
|
+
floors,
|
|
111
|
+
scanned,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return wallet;
|
|
115
|
+
}
|
package/dist/watch.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type Address, type Hex } from "viem";
|
|
2
|
+
import type { AgentsModule } from "./agents.js";
|
|
3
|
+
import type { AnyModule } from "./any.js";
|
|
4
|
+
import type { CoinsModule } from "./coins.js";
|
|
5
|
+
import type { CallsModule, PoolsModule } from "./pools.js";
|
|
6
|
+
import type { StickyModule } from "./sticky.js";
|
|
7
|
+
import type { Ctx } from "./tx.js";
|
|
8
|
+
/**
|
|
9
|
+
* Live events, for terminals and bots: every launch on the pad, every trade of a coin, everything that happens to an
|
|
10
|
+
* agent coin. Each watcher polls the RPC (HTTP is enough) and returns a function that stops it.
|
|
11
|
+
*
|
|
12
|
+
* const stop = ph.watch.launches((l) => console.log(l.mode, l.symbol, l.token));
|
|
13
|
+
* const stop2 = await ph.watch.trades(token, (t) => console.log(t.buy ? "buy" : "sell", t.bnb, t.coins));
|
|
14
|
+
* const stop3 = await ph.watch.agent(token, (e) => console.log(e.event, e.args));
|
|
15
|
+
*/
|
|
16
|
+
export type WatchMode = "sticky" | "sticky2" | "rules" | "rules-v1" | "pools" | "calls" | "lever" | "agents" | "any";
|
|
17
|
+
export type WatchedLaunch = {
|
|
18
|
+
mode: WatchMode;
|
|
19
|
+
token: Address;
|
|
20
|
+
creator: Address;
|
|
21
|
+
/** read from the token when `details` is on (default) */
|
|
22
|
+
name: string;
|
|
23
|
+
symbol: string;
|
|
24
|
+
/** the V3 pool and V2 pair (sticky, rules), the Infinity pool id (pools, calls, lever, agents, any) */
|
|
25
|
+
pool?: Address;
|
|
26
|
+
pair?: Address;
|
|
27
|
+
poolId?: Hex;
|
|
28
|
+
factory: Address;
|
|
29
|
+
block: bigint;
|
|
30
|
+
tx: Hex;
|
|
31
|
+
/** the raw event as viem decoded it */
|
|
32
|
+
args: Record<string, unknown>;
|
|
33
|
+
};
|
|
34
|
+
export type WatchedTrade = {
|
|
35
|
+
token: Address;
|
|
36
|
+
buy: boolean;
|
|
37
|
+
/** the trader where the event names one (the hooks do; V3 and V2 name the router's sender and the recipient) */
|
|
38
|
+
trader: Address | null;
|
|
39
|
+
/** coins moved (18 decimals), BNB moved (wei); the hooks' events carry the fee or the BNB, the pools' Swap both sides */
|
|
40
|
+
coins: bigint;
|
|
41
|
+
bnb: bigint | null;
|
|
42
|
+
/** the hook's fee in BNB (pools, calls, agents) */
|
|
43
|
+
fee: bigint | null;
|
|
44
|
+
/** v3, v2 (PancakeSwap pools of sticky and rules coins), or the hook's event (pools, calls, agents, any) */
|
|
45
|
+
source: "v3" | "v2" | "hook";
|
|
46
|
+
block: bigint;
|
|
47
|
+
tx: Hex;
|
|
48
|
+
};
|
|
49
|
+
export type AgentEvent = {
|
|
50
|
+
token: Address;
|
|
51
|
+
/** Proposed, Cancelled, Activated, Vetoed, VetoBacked, FireBacked, PilotFired, PilotHandedOver, Locked, Withdrawn, TreasuryMoved (desk); Burned, LiquidityAdded, FloorFed, Moved, Given, ProgramSet, JackpotWon, RewardEarned, Traded (hook) */
|
|
52
|
+
event: string;
|
|
53
|
+
args: Record<string, unknown>;
|
|
54
|
+
block: bigint;
|
|
55
|
+
tx: Hex;
|
|
56
|
+
};
|
|
57
|
+
export type WatchOptions = {
|
|
58
|
+
/** ms between polls (default 3000, about one BNB Chain block) */
|
|
59
|
+
pollingInterval?: number;
|
|
60
|
+
/** errors of the poll (an RPC hiccup); default: ignored, the next poll tries again */
|
|
61
|
+
onError?: (e: Error) => void;
|
|
62
|
+
};
|
|
63
|
+
type Mods = {
|
|
64
|
+
sticky: StickyModule;
|
|
65
|
+
sticky2: StickyModule;
|
|
66
|
+
coins: CoinsModule;
|
|
67
|
+
pools: PoolsModule;
|
|
68
|
+
calls: CallsModule;
|
|
69
|
+
agents: AgentsModule;
|
|
70
|
+
any: AnyModule;
|
|
71
|
+
};
|
|
72
|
+
export declare function watchModule(ctx: Ctx, mods: Mods): {
|
|
73
|
+
launches: (cb: (l: WatchedLaunch) => void, o?: WatchOptions & {
|
|
74
|
+
modes?: WatchMode[];
|
|
75
|
+
details?: boolean;
|
|
76
|
+
}) => () => void;
|
|
77
|
+
trades: (token: Address, cb: (t: WatchedTrade) => void, o?: WatchOptions) => Promise<() => void>;
|
|
78
|
+
agent: (token: Address, cb: (e: AgentEvent) => void, o?: WatchOptions) => Promise<() => void>;
|
|
79
|
+
modes: WatchMode[];
|
|
80
|
+
};
|
|
81
|
+
export type WatchModule = ReturnType<typeof watchModule>;
|
|
82
|
+
export {};
|
package/dist/watch.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { isAddressEqual, parseAbi } from "viem";
|
|
2
|
+
import { agentsDeskAbi, agentsFactoryAbi, agentsHookAbi } from "./abis/agents.js";
|
|
3
|
+
import { any3FactoryAbi, any3HookAbi } from "./abis/any3.js";
|
|
4
|
+
import { callsFactoryAbi, callsHookAbi } from "./abis/calls.js";
|
|
5
|
+
import { lever2FactoryAbi } from "./abis/lever2.js";
|
|
6
|
+
import { poolsFactoryAbi, poolsHookAbi } from "./abis/pools.js";
|
|
7
|
+
import { stickyFactoryAbi } from "./abis/sticky.js";
|
|
8
|
+
import { sticky2FactoryAbi } from "./abis/sticky2.js";
|
|
9
|
+
import { v1FactoryAbi } from "./abis/v1.js";
|
|
10
|
+
import { v2FactoryAbi } from "./abis/v2.js";
|
|
11
|
+
import { ZERO_ADDRESS } from "./addresses.js";
|
|
12
|
+
import { PickhooksError } from "./errors.js";
|
|
13
|
+
const v3SwapAbi = parseAbi([
|
|
14
|
+
"event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint128 protocolFeesToken0, uint128 protocolFeesToken1)",
|
|
15
|
+
]);
|
|
16
|
+
const v2SwapAbi = parseAbi(["event Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to)"]);
|
|
17
|
+
const PLAIN = parseAbi(["function name() view returns (string)", "function symbol() view returns (string)"]);
|
|
18
|
+
export function watchModule(ctx, mods) {
|
|
19
|
+
const { pub, a } = ctx;
|
|
20
|
+
const abs = (v) => (v < 0n ? -v : v);
|
|
21
|
+
const factories = [
|
|
22
|
+
{ mode: "sticky", address: a.stickyFactory, abi: stickyFactoryAbi, event: "Launched" },
|
|
23
|
+
{ mode: "sticky2", address: a.sticky2Factory, abi: sticky2FactoryAbi, event: "Launched" },
|
|
24
|
+
{ mode: "rules", address: a.coinsV2Factory, abi: v2FactoryAbi, event: "Launched" },
|
|
25
|
+
{ mode: "rules-v1", address: a.coinsV1Factory, abi: v1FactoryAbi, event: "Launched" },
|
|
26
|
+
{ mode: "pools", address: a.poolsFactory, abi: poolsFactoryAbi, event: "Launched" },
|
|
27
|
+
{ mode: "calls", address: a.callsFactory, abi: callsFactoryAbi, event: "Launched" },
|
|
28
|
+
{ mode: "lever", address: a.lever2Factory, abi: lever2FactoryAbi, event: "Launched" },
|
|
29
|
+
{ mode: "agents", address: a.agentsFactory, abi: agentsFactoryAbi, event: "Launched" },
|
|
30
|
+
{ mode: "any", address: a.anyFactory, abi: any3FactoryAbi, event: "Opened" },
|
|
31
|
+
];
|
|
32
|
+
const watch = (p, o) => pub.watchContractEvent({
|
|
33
|
+
address: p.address,
|
|
34
|
+
abi: p.abi,
|
|
35
|
+
eventName: p.eventName,
|
|
36
|
+
args: p.args,
|
|
37
|
+
onLogs: p.onLogs,
|
|
38
|
+
onError: o.onError ?? (() => undefined),
|
|
39
|
+
poll: true,
|
|
40
|
+
pollingInterval: o.pollingInterval ?? 3000,
|
|
41
|
+
strict: false,
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Every new coin on the pad (and every new hook any coin pool), as it is mined. `modes` narrows it; `details` reads
|
|
45
|
+
* name and symbol from the token (one multicall per batch).
|
|
46
|
+
*/
|
|
47
|
+
function launches(cb, o = {}) {
|
|
48
|
+
const wanted = factories.filter((f) => !isAddressEqual(f.address, ZERO_ADDRESS) && (!o.modes || o.modes.includes(f.mode)));
|
|
49
|
+
const stops = wanted.map((f) => watch({
|
|
50
|
+
address: f.address,
|
|
51
|
+
abi: f.abi,
|
|
52
|
+
eventName: f.event,
|
|
53
|
+
onLogs: async (logs) => {
|
|
54
|
+
const rows = logs.map((log) => {
|
|
55
|
+
const args = (log.args ?? {});
|
|
56
|
+
return {
|
|
57
|
+
mode: f.mode,
|
|
58
|
+
token: args.token,
|
|
59
|
+
creator: (args.creator ?? args.opener),
|
|
60
|
+
name: "",
|
|
61
|
+
symbol: "",
|
|
62
|
+
pool: args.pool,
|
|
63
|
+
pair: args.pair,
|
|
64
|
+
poolId: (args.poolId ?? args.id),
|
|
65
|
+
factory: f.address,
|
|
66
|
+
block: log.blockNumber ?? 0n,
|
|
67
|
+
tx: log.transactionHash ?? "0x",
|
|
68
|
+
args,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
if (o.details !== false && rows.length) {
|
|
72
|
+
const meta = await pub
|
|
73
|
+
.multicall({ contracts: rows.flatMap((r) => [{ address: r.token, abi: PLAIN, functionName: "name" }, { address: r.token, abi: PLAIN, functionName: "symbol" }]), allowFailure: true })
|
|
74
|
+
.catch(() => null);
|
|
75
|
+
if (meta)
|
|
76
|
+
rows.forEach((r, i) => Object.assign(r, { name: meta[i * 2].status === "success" ? String(meta[i * 2].result) : "", symbol: meta[i * 2 + 1].status === "success" ? String(meta[i * 2 + 1].result) : "" }));
|
|
77
|
+
}
|
|
78
|
+
for (const r of rows)
|
|
79
|
+
cb(r);
|
|
80
|
+
},
|
|
81
|
+
}, o));
|
|
82
|
+
return () => stops.forEach((s) => s());
|
|
83
|
+
}
|
|
84
|
+
/** The hooks' Traded(id, trader, buy, tokens, fee|bnb) for a set of pool ids on one hook. */
|
|
85
|
+
const hookTrades = (token, hook, abi, ids, bnbField, cb, o) => watch({
|
|
86
|
+
address: hook,
|
|
87
|
+
abi,
|
|
88
|
+
eventName: "Traded",
|
|
89
|
+
args: { id: ids },
|
|
90
|
+
onLogs: (logs) => {
|
|
91
|
+
for (const log of logs) {
|
|
92
|
+
const g = (log.args ?? {});
|
|
93
|
+
cb({
|
|
94
|
+
token,
|
|
95
|
+
buy: Boolean(g.buy),
|
|
96
|
+
trader: g.trader ?? null,
|
|
97
|
+
coins: g.tokens,
|
|
98
|
+
bnb: bnbField === "bnb" ? g.bnb : null,
|
|
99
|
+
fee: bnbField === "fee" ? g.fee : null,
|
|
100
|
+
source: "hook",
|
|
101
|
+
block: log.blockNumber ?? 0n,
|
|
102
|
+
tx: log.transactionHash ?? "0x",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
}, o);
|
|
107
|
+
/** A PancakeSwap V3 pool's and V2 pair's Swap events of a coin (sticky and rules coins trade there). */
|
|
108
|
+
const pancakeTrades = (token, pool, pair, cb, o) => {
|
|
109
|
+
// the coin is token0 when its address sorts under WBNB's
|
|
110
|
+
const coinIs0 = token.toLowerCase() < a.wbnb.toLowerCase();
|
|
111
|
+
const stops = [
|
|
112
|
+
watch({
|
|
113
|
+
address: pool,
|
|
114
|
+
abi: v3SwapAbi,
|
|
115
|
+
eventName: "Swap",
|
|
116
|
+
onLogs: (logs) => {
|
|
117
|
+
for (const log of logs) {
|
|
118
|
+
const g = (log.args ?? {});
|
|
119
|
+
const coin = (coinIs0 ? g.amount0 : g.amount1);
|
|
120
|
+
const bnb = (coinIs0 ? g.amount1 : g.amount0);
|
|
121
|
+
// the pool's view: a negative coin amount left the pool, so someone bought
|
|
122
|
+
cb({ token, buy: coin < 0n, trader: g.recipient ?? null, coins: abs(coin), bnb: abs(bnb), fee: null, source: "v3", block: log.blockNumber ?? 0n, tx: log.transactionHash ?? "0x" });
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
}, o),
|
|
126
|
+
];
|
|
127
|
+
if (!isAddressEqual(pair, ZERO_ADDRESS)) {
|
|
128
|
+
stops.push(watch({
|
|
129
|
+
address: pair,
|
|
130
|
+
abi: v2SwapAbi,
|
|
131
|
+
eventName: "Swap",
|
|
132
|
+
onLogs: (logs) => {
|
|
133
|
+
for (const log of logs) {
|
|
134
|
+
const g = (log.args ?? {});
|
|
135
|
+
const coinOut = (coinIs0 ? g.amount0Out : g.amount1Out);
|
|
136
|
+
const coinIn = (coinIs0 ? g.amount0In : g.amount1In);
|
|
137
|
+
const bnbOut = (coinIs0 ? g.amount1Out : g.amount0Out);
|
|
138
|
+
const bnbIn = (coinIs0 ? g.amount1In : g.amount0In);
|
|
139
|
+
const buy = coinOut > 0n;
|
|
140
|
+
cb({ token, buy, trader: g.to ?? null, coins: buy ? coinOut : coinIn, bnb: buy ? bnbIn : bnbOut, fee: null, source: "v2", block: log.blockNumber ?? 0n, tx: log.transactionHash ?? "0x" });
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
}, o));
|
|
144
|
+
}
|
|
145
|
+
return () => stops.forEach((s) => s());
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* Every trade of a pickhooks coin, whatever its mode: the hook's Traded events (pools, calls, agents, hook any coin
|
|
149
|
+
* pools next to the coin) or the Swap events of its PancakeSwap V3 pool and V2 pair (sticky, rules).
|
|
150
|
+
*/
|
|
151
|
+
async function trades(token, cb, o = {}) {
|
|
152
|
+
if (await mods.agents.isCoin(token))
|
|
153
|
+
return hookTrades(token, await mods.agents.hook(), agentsHookAbi, [(await mods.agents.coin(token)).id], "fee", cb, o);
|
|
154
|
+
if (await mods.pools.isCoin(token))
|
|
155
|
+
return hookTrades(token, await mods.pools.hook(), poolsHookAbi, [(await mods.pools.coin(token)).id], "fee", cb, o);
|
|
156
|
+
if (await mods.calls.isCoin(token))
|
|
157
|
+
return hookTrades(token, await mods.calls.hook(), callsHookAbi, [(await mods.calls.coin(token)).id], "fee", cb, o);
|
|
158
|
+
for (const s of [mods.sticky, mods.sticky2]) {
|
|
159
|
+
if (await s.isCoin(token).catch(() => false)) {
|
|
160
|
+
const c = (await s.coin(token));
|
|
161
|
+
return pancakeTrades(token, c.pool, c.pair, cb, o);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (await mods.coins.versionOf(token).catch(() => null)) {
|
|
165
|
+
const c = (await mods.coins.coin(token));
|
|
166
|
+
return pancakeTrades(token, c.pool, c.pair, cb, o);
|
|
167
|
+
}
|
|
168
|
+
const pools = await mods.any.poolsOf(token).catch(() => []);
|
|
169
|
+
if (pools.length) {
|
|
170
|
+
const stops = [...new Set(pools.map((p) => p.hook.toLowerCase()))].map((h) => hookTrades(token, h, any3HookAbi, pools.filter((p) => p.hook.toLowerCase() === h).map((p) => p.id), "bnb", cb, o));
|
|
171
|
+
return () => stops.forEach((s) => s());
|
|
172
|
+
}
|
|
173
|
+
throw new PickhooksError("This is not a coin pickhooks knows.", { code: "input" });
|
|
174
|
+
}
|
|
175
|
+
/** Everything that happens to an agent coin: the desk's events (changes, vetoes, firing, treasury moves) and the hook's runs. */
|
|
176
|
+
async function agent(token, cb, o = {}) {
|
|
177
|
+
if (!(await mods.agents.isCoin(token)))
|
|
178
|
+
throw new PickhooksError("This is not an agent coin.", { code: "input" });
|
|
179
|
+
const [hook, desk, c] = await Promise.all([mods.agents.hook(), mods.agents.desk(), mods.agents.coin(token)]);
|
|
180
|
+
const emit = (logs) => {
|
|
181
|
+
for (const log of logs) {
|
|
182
|
+
const l = log;
|
|
183
|
+
cb({ token, event: l.eventName ?? "", args: l.args ?? {}, block: log.blockNumber ?? 0n, tx: log.transactionHash ?? "0x" });
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const stops = [
|
|
187
|
+
watch({ address: desk, abi: agentsDeskAbi, args: { token }, onLogs: emit }, o),
|
|
188
|
+
watch({ address: hook, abi: agentsHookAbi, args: { token }, onLogs: emit }, o),
|
|
189
|
+
watch({ address: hook, abi: agentsHookAbi, eventName: "Traded", args: { id: c.id }, onLogs: emit }, o),
|
|
190
|
+
];
|
|
191
|
+
return () => stops.forEach((s) => s());
|
|
192
|
+
}
|
|
193
|
+
return { launches, trades, agent, modes: factories.filter((f) => !isAddressEqual(f.address, ZERO_ADDRESS)).map((f) => f.mode) };
|
|
194
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pickhooks/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Read, quote, trade and launch on pickhooks (BNB Chain) from a bot, a launchpad or a trading terminal. Built on viem.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
"./coin-rules": {
|
|
23
23
|
"types": "./dist/coin-rules.d.ts",
|
|
24
24
|
"import": "./dist/coin-rules.js"
|
|
25
|
+
},
|
|
26
|
+
"./tlock": {
|
|
27
|
+
"types": "./dist/tlock.d.ts",
|
|
28
|
+
"import": "./dist/tlock.js"
|
|
25
29
|
}
|
|
26
30
|
},
|
|
27
31
|
"files": [
|
|
@@ -37,7 +41,13 @@
|
|
|
37
41
|
"fork-proof": "node test/fork-proof.mjs",
|
|
38
42
|
"fork-proof-2": "node test/fork-proof-2.mjs",
|
|
39
43
|
"fork-proof-infra": "node test/fork-proof-infra.mjs",
|
|
40
|
-
"
|
|
44
|
+
"mainnet-read-agents": "node test/mainnet-read-agents.mjs",
|
|
45
|
+
"release": "node scripts/publish.mjs",
|
|
46
|
+
"fork-proof-sticky3": "node test/fork-proof-sticky3.mjs"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@noble/curves": "^1.9.1",
|
|
50
|
+
"@noble/hashes": "^1.8.0"
|
|
41
51
|
},
|
|
42
52
|
"peerDependencies": {
|
|
43
53
|
"viem": "^2.0.0"
|