@pickhooks/sdk 0.0.0-stage → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +418 -3
  3. package/dist/abis/agents.d.ts +2418 -0
  4. package/dist/abis/agents.js +4 -0
  5. package/dist/abis/any3.d.ts +2123 -0
  6. package/dist/abis/any3.js +3 -0
  7. package/dist/abis/anytax.d.ts +1786 -0
  8. package/dist/abis/anytax.js +3 -0
  9. package/dist/abis/calls.d.ts +2515 -0
  10. package/dist/abis/calls.js +4 -0
  11. package/dist/abis/common.d.ts +449 -0
  12. package/dist/abis/common.js +91 -0
  13. package/dist/abis/dips.d.ts +897 -0
  14. package/dist/abis/dips.js +2 -0
  15. package/dist/abis/index.d.ts +14 -0
  16. package/dist/abis/index.js +14 -0
  17. package/dist/abis/infra.d.ts +701 -0
  18. package/dist/abis/infra.js +4 -0
  19. package/dist/abis/lever.d.ts +1710 -0
  20. package/dist/abis/lever.js +5 -0
  21. package/dist/abis/lever2.d.ts +2710 -0
  22. package/dist/abis/lever2.js +5 -0
  23. package/dist/abis/pools.d.ts +1898 -0
  24. package/dist/abis/pools.js +4 -0
  25. package/dist/abis/sticky.d.ts +953 -0
  26. package/dist/abis/sticky.js +4 -0
  27. package/dist/abis/sticky2.d.ts +3750 -0
  28. package/dist/abis/sticky2.js +8 -0
  29. package/dist/abis/v1.d.ts +1627 -0
  30. package/dist/abis/v1.js +5 -0
  31. package/dist/abis/v2.d.ts +3264 -0
  32. package/dist/abis/v2.js +8 -0
  33. package/dist/addresses.d.ts +100 -0
  34. package/dist/addresses.js +71 -0
  35. package/dist/agents-catalog.d.ts +318 -0
  36. package/dist/agents-catalog.js +353 -0
  37. package/dist/agents.d.ts +543 -0
  38. package/dist/agents.js +667 -0
  39. package/dist/any.d.ts +447 -0
  40. package/dist/any.js +729 -0
  41. package/dist/api.d.ts +62 -0
  42. package/dist/api.js +30 -0
  43. package/dist/blocks.d.ts +107 -0
  44. package/dist/blocks.js +126 -0
  45. package/dist/coin-rules-config.d.ts +8 -0
  46. package/dist/coin-rules-config.js +13 -0
  47. package/dist/coin-rules.d.ts +383 -0
  48. package/dist/coin-rules.js +878 -0
  49. package/dist/coins.d.ts +217 -0
  50. package/dist/coins.js +520 -0
  51. package/dist/desks.d.ts +298 -0
  52. package/dist/desks.js +343 -0
  53. package/dist/dips.d.ts +133 -0
  54. package/dist/dips.js +148 -0
  55. package/dist/errors.d.ts +35 -0
  56. package/dist/errors.js +414 -0
  57. package/dist/index.d.ts +104 -0
  58. package/dist/index.js +91 -0
  59. package/dist/infinity.d.ts +63 -0
  60. package/dist/infinity.js +96 -0
  61. package/dist/infra.d.ts +112 -0
  62. package/dist/infra.js +114 -0
  63. package/dist/lever.d.ts +391 -0
  64. package/dist/lever.js +419 -0
  65. package/dist/pools.d.ts +274 -0
  66. package/dist/pools.js +405 -0
  67. package/dist/sticky-rules.d.ts +179 -0
  68. package/dist/sticky-rules.js +580 -0
  69. package/dist/sticky-sim.d.ts +125 -0
  70. package/dist/sticky-sim.js +637 -0
  71. package/dist/sticky.d.ts +286 -0
  72. package/dist/sticky.js +420 -0
  73. package/dist/tlock.d.ts +67 -0
  74. package/dist/tlock.js +185 -0
  75. package/dist/tx.d.ts +72 -0
  76. package/dist/tx.js +78 -0
  77. package/dist/v2.d.ts +106 -0
  78. package/dist/v2.js +61 -0
  79. package/dist/v3.d.ts +42 -0
  80. package/dist/v3.js +98 -0
  81. package/dist/wallet.d.ts +80 -0
  82. package/dist/wallet.js +115 -0
  83. package/dist/watch.d.ts +82 -0
  84. package/dist/watch.js +194 -0
  85. package/package.json +67 -4
package/dist/tx.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ import { type Account, type Address, type Hex, type PublicClient, type TransactionReceipt, type WalletClient } from "viem";
2
+ import { type Addresses } from "./addresses.js";
3
+ /**
4
+ * A transaction as plain data: send it with any wallet, wrap it in a multicall, or route it through your own contract.
5
+ * `extraGas` is what to add on top of your own gas estimate (the recapture room of a hook any coin trade); it never
6
+ * changes what the call does, only whether the hook has gas left to close a gap after the swap.
7
+ */
8
+ export type TxRequest = {
9
+ to: Address;
10
+ data: Hex;
11
+ value: bigint;
12
+ extraGas?: bigint;
13
+ /** the estimate is taken times this percentage (default 130); trades that liquidate or count rules can grow while they wait */
14
+ gasPct?: number;
15
+ /** what it does, in words (for logs and confirmations) */
16
+ label?: string;
17
+ };
18
+ export type SendOptions = {
19
+ /** sends from this wallet instead of the client's */
20
+ wallet?: WalletClient;
21
+ /** a fixed gas limit instead of estimate x 1.3 + extraGas */
22
+ gas?: bigint;
23
+ /** called with the hash as soon as the wallet returns it */
24
+ onHash?: (hash: Hex) => void;
25
+ /** how long to wait for the receipt, ms (default 120 s) */
26
+ timeout?: number;
27
+ };
28
+ export type Sent = {
29
+ hash: Hex;
30
+ receipt: TransactionReceipt;
31
+ };
32
+ export type PickhooksOptions = {
33
+ /** a viem public client on BNB Chain (or a fork of it); without it the SDK makes one from rpcUrl */
34
+ publicClient?: PublicClient;
35
+ /** used when no publicClient is given; default BNB Chain's public RPC */
36
+ rpcUrl?: string;
37
+ /** signs and sends; only the write methods need it */
38
+ walletClient?: WalletClient;
39
+ /** other contract addresses (a fork rehearsal, a new factory); merged over BSC_MAINNET */
40
+ addresses?: Partial<Addresses>;
41
+ /** the pickhooks site for its public API and partner links (default https://pickhooks.com) */
42
+ site?: string;
43
+ };
44
+ /** What every module of the SDK shares. */
45
+ export type Ctx = {
46
+ pub: PublicClient;
47
+ wallet?: WalletClient;
48
+ a: Addresses;
49
+ site: string;
50
+ };
51
+ export declare const DEFAULT_RPC = "https://bsc-dataseed.bnbchain.org";
52
+ export declare function makeCtx(o?: PickhooksOptions): Ctx;
53
+ /** The wallet a write goes out from, and its account (a local account signs locally, an address goes to the wallet). */
54
+ export declare function signer(ctx: Ctx, w?: WalletClient): Promise<{
55
+ wallet: WalletClient;
56
+ account: Account | Address;
57
+ address: Address;
58
+ }>;
59
+ /** The address a write would go out from (for quotes and checks before it), or undefined without a wallet. */
60
+ export declare function senderAddress(ctx: Ctx, w?: WalletClient): Promise<Address | undefined>;
61
+ /** The gas limit a request goes out with: estimate x gasPct (default 130 %) plus its extraGas, never above what BNB Chain accepts. */
62
+ export declare function gasFor(ctx: Ctx, tx: TxRequest, from: Address): Promise<bigint>;
63
+ /** Estimates, sends and waits for the receipt; a failure comes back as a PickhooksError with a sentence. */
64
+ export declare function sendTx(ctx: Ctx, tx: TxRequest, o?: SendOptions): Promise<Sent>;
65
+ /** Sends requests one after the other (approvals before a trade). */
66
+ export declare function sendAll(ctx: Ctx, txs: TxRequest[], o?: SendOptions): Promise<Sent[]>;
67
+ /** A deadline 10 minutes from now (seconds), for router calls. */
68
+ export declare const deadlineIn: (seconds?: number) => bigint;
69
+ /** A deadline from the later of this machine's clock and the chain's (a skewed clock or a fork ahead of real time). */
70
+ export declare function chainDeadline(ctx: Ctx, seconds?: number): Promise<bigint>;
71
+ /** amount less a slippage in basis points */
72
+ export declare const minusBps: (amount: bigint, bps: number) => bigint;
package/dist/tx.js ADDED
@@ -0,0 +1,78 @@
1
+ import { createPublicClient, http, } from "viem";
2
+ import { bsc } from "viem/chains";
3
+ import { BSC_MAINNET, LIMITS } from "./addresses.js";
4
+ import { PickhooksError, toPickhooksError } from "./errors.js";
5
+ export const DEFAULT_RPC = "https://bsc-dataseed.bnbchain.org";
6
+ export function makeCtx(o = {}) {
7
+ const pub = o.publicClient ?? createPublicClient({ chain: bsc, transport: http(o.rpcUrl ?? DEFAULT_RPC, { batch: true }) });
8
+ return { pub, wallet: o.walletClient, a: { ...BSC_MAINNET, ...o.addresses }, site: (o.site ?? "https://pickhooks.com").replace(/\/$/, "") };
9
+ }
10
+ /** The wallet a write goes out from, and its account (a local account signs locally, an address goes to the wallet). */
11
+ export async function signer(ctx, w) {
12
+ const wallet = w ?? ctx.wallet;
13
+ if (!wallet)
14
+ throw new PickhooksError("This needs a wallet: pass walletClient to createPickhooks or { wallet } to the call.", { code: "input" });
15
+ const account = wallet.account ?? (await wallet.getAddresses())[0];
16
+ if (!account)
17
+ throw new PickhooksError("The wallet has no account.", { code: "input" });
18
+ return { wallet, account, address: typeof account === "string" ? account : account.address };
19
+ }
20
+ /** The address a write would go out from (for quotes and checks before it), or undefined without a wallet. */
21
+ export async function senderAddress(ctx, w) {
22
+ const wallet = w ?? ctx.wallet;
23
+ if (!wallet)
24
+ return undefined;
25
+ if (wallet.account)
26
+ return wallet.account.address;
27
+ return (await wallet.getAddresses())[0];
28
+ }
29
+ /** The gas limit a request goes out with: estimate x gasPct (default 130 %) plus its extraGas, never above what BNB Chain accepts. */
30
+ export async function gasFor(ctx, tx, from) {
31
+ let est;
32
+ try {
33
+ est = await ctx.pub.estimateGas({ account: from, to: tx.to, data: tx.data, value: tx.value });
34
+ }
35
+ catch (e) {
36
+ throw toPickhooksError(e, `${tx.label ?? "The transaction"} would fail.`);
37
+ }
38
+ const gas = (est * BigInt(tx.gasPct ?? 130)) / 100n + (tx.extraGas ?? 0n);
39
+ return gas > LIMITS.gasCap ? LIMITS.gasCap : gas;
40
+ }
41
+ /** Estimates, sends and waits for the receipt; a failure comes back as a PickhooksError with a sentence. */
42
+ export async function sendTx(ctx, tx, o = {}) {
43
+ const { wallet, account, address } = await signer(ctx, o.wallet);
44
+ const gas = o.gas ?? (await gasFor(ctx, tx, address));
45
+ let hash;
46
+ try {
47
+ hash = await wallet.sendTransaction({ account, chain: (wallet.chain ?? bsc), to: tx.to, data: tx.data, value: tx.value, gas });
48
+ }
49
+ catch (e) {
50
+ throw toPickhooksError(e, `${tx.label ?? "The transaction"} was not sent.`);
51
+ }
52
+ o.onHash?.(hash);
53
+ const receipt = await ctx.pub.waitForTransactionReceipt({ hash, timeout: o.timeout ?? 120_000 });
54
+ if (receipt.status !== "success") {
55
+ throw new PickhooksError(`${tx.label ?? "The transaction"} failed on chain (${hash}).`, { code: "reverted", hash });
56
+ }
57
+ return { hash, receipt };
58
+ }
59
+ /** Sends requests one after the other (approvals before a trade). */
60
+ export async function sendAll(ctx, txs, o = {}) {
61
+ const out = [];
62
+ for (const tx of txs)
63
+ out.push(await sendTx(ctx, tx, o));
64
+ return out;
65
+ }
66
+ /** A deadline 10 minutes from now (seconds), for router calls. */
67
+ export const deadlineIn = (seconds = 600) => BigInt(Math.floor(Date.now() / 1000) + seconds);
68
+ /** A deadline from the later of this machine's clock and the chain's (a skewed clock or a fork ahead of real time). */
69
+ export async function chainDeadline(ctx, seconds = 600) {
70
+ const local = Math.floor(Date.now() / 1000);
71
+ const chain = await ctx.pub
72
+ .getBlock()
73
+ .then((b) => Number(b.timestamp))
74
+ .catch(() => local);
75
+ return BigInt(Math.max(local, chain) + seconds);
76
+ }
77
+ /** amount less a slippage in basis points */
78
+ export const minusBps = (amount, bps) => (amount * BigInt(10_000 - Math.max(0, Math.min(10_000, Math.round(bps))))) / 10000n;
package/dist/v2.d.ts ADDED
@@ -0,0 +1,106 @@
1
+ import { type Address } from "viem";
2
+ import type { Addresses } from "./addresses.js";
3
+ import { type Ctx, type TxRequest } from "./tx.js";
4
+ /**
5
+ * PancakeSwap V2: every sticky coin (and every rules coin since 03.10.) has a V2 pair next to its V3 pool, at the same
6
+ * start price. A dark launch puts the whole round's BNB into that pair, so right after the birth the pair is the venue
7
+ * with the BNB and the V3 curve holds only coins above the price: a sell into V3 would get almost nothing ("SPL").
8
+ * Terminals route to the better pool by themselves; the SDK quotes both and picks the better one (sticky.quote).
9
+ */
10
+ export declare const v2RouterAbi: readonly [{
11
+ readonly name: "getAmountsOut";
12
+ readonly type: "function";
13
+ readonly stateMutability: "view";
14
+ readonly inputs: readonly [{
15
+ readonly type: "uint256";
16
+ readonly name: "amountIn";
17
+ }, {
18
+ readonly type: "address[]";
19
+ readonly name: "path";
20
+ }];
21
+ readonly outputs: readonly [{
22
+ readonly type: "uint256[]";
23
+ readonly name: "amounts";
24
+ }];
25
+ }, {
26
+ readonly name: "swapExactETHForTokensSupportingFeeOnTransferTokens";
27
+ readonly type: "function";
28
+ readonly stateMutability: "payable";
29
+ readonly inputs: readonly [{
30
+ readonly type: "uint256";
31
+ readonly name: "amountOutMin";
32
+ }, {
33
+ readonly type: "address[]";
34
+ readonly name: "path";
35
+ }, {
36
+ readonly type: "address";
37
+ readonly name: "to";
38
+ }, {
39
+ readonly type: "uint256";
40
+ readonly name: "deadline";
41
+ }];
42
+ readonly outputs: readonly [];
43
+ }, {
44
+ readonly name: "swapExactTokensForETHSupportingFeeOnTransferTokens";
45
+ readonly type: "function";
46
+ readonly stateMutability: "nonpayable";
47
+ readonly inputs: readonly [{
48
+ readonly type: "uint256";
49
+ readonly name: "amountIn";
50
+ }, {
51
+ readonly type: "uint256";
52
+ readonly name: "amountOutMin";
53
+ }, {
54
+ readonly type: "address[]";
55
+ readonly name: "path";
56
+ }, {
57
+ readonly type: "address";
58
+ readonly name: "to";
59
+ }, {
60
+ readonly type: "uint256";
61
+ readonly name: "deadline";
62
+ }];
63
+ readonly outputs: readonly [];
64
+ }];
65
+ export type Venue = "v3" | "v2";
66
+ /** What a trade gets in the V2 pair, from the router (reverts when the coin has no pair or the pair is empty). */
67
+ export declare function v2Quote(ctx: Ctx, q: {
68
+ token: Address;
69
+ side: "buy" | "sell";
70
+ amount: bigint;
71
+ }): Promise<bigint>;
72
+ /** A buy through the V2 router, BNB in. */
73
+ export declare function buildV2Buy(a: Addresses, b: {
74
+ token: Address;
75
+ bnbIn: bigint;
76
+ minOut: bigint;
77
+ recipient: Address;
78
+ deadline?: bigint;
79
+ gasPct?: number;
80
+ }): TxRequest;
81
+ /** A sell through the V2 router for native BNB. Needs the coin approved to the V2 router. */
82
+ export declare function buildV2Sell(a: Addresses, s: {
83
+ token: Address;
84
+ amount: bigint;
85
+ minOut: bigint;
86
+ recipient: Address;
87
+ deadline?: bigint;
88
+ gasPct?: number;
89
+ }): TxRequest;
90
+ /**
91
+ * The better of the two venues for this trade: the V3 pool (quoter) and the V2 pair (router). A venue that cannot
92
+ * quote (no pair, no BNB below the price, a refusing rule) counts as nothing; `errors` keeps what each one said.
93
+ */
94
+ export declare function bestVenue(ctx: Ctx, quotes: {
95
+ v3: () => Promise<bigint>;
96
+ v2: () => Promise<bigint>;
97
+ }): Promise<{
98
+ venue: Venue;
99
+ amountOut: bigint;
100
+ v3: bigint | null;
101
+ v2: bigint | null;
102
+ errors: {
103
+ v3?: unknown;
104
+ v2?: unknown;
105
+ };
106
+ }>;
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/v3.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { type Address, type TransactionReceipt } from "viem";
2
+ import { type Addresses } from "./addresses.js";
3
+ import { type Ctx, type TxRequest } from "./tx.js";
4
+ /** A buy through the V3 SwapRouter, BNB in (the router wraps it). */
5
+ export declare function buildV3Buy(a: Addresses, b: {
6
+ token: Address;
7
+ bnbIn: bigint;
8
+ minOut: bigint;
9
+ recipient: Address;
10
+ deadline?: bigint;
11
+ fee?: number;
12
+ gasPct?: number;
13
+ }): TxRequest;
14
+ /** A sell through the V3 SwapRouter for native BNB: swap to the router, unwrap to the seller. Needs the coin approved to the router. */
15
+ export declare function buildV3Sell(a: Addresses, s: {
16
+ token: Address;
17
+ amount: bigint;
18
+ minOut: bigint;
19
+ recipient: Address;
20
+ deadline?: bigint;
21
+ fee?: number;
22
+ gasPct?: number;
23
+ }): TxRequest;
24
+ /** What a trade gets in the V3 pool, from the QuoterV2 (it runs the swap, so a coin's refusing rule fails it: raw error). */
25
+ export declare function v3Quote(ctx: Ctx, q: {
26
+ token: Address;
27
+ side: "buy" | "sell";
28
+ amount: bigint;
29
+ fee?: number;
30
+ }): Promise<bigint>;
31
+ /** The approval of `spender` (default the V3 router) a sell needs, or null when it is in place. */
32
+ export declare function approvalFor(ctx: Ctx, owner: Address, token: Address, amount: bigint, spender?: Address, label?: string): Promise<TxRequest | null>;
33
+ /** Coins of `token` that reached `to` in a receipt (its Transfer events). */
34
+ export declare function receivedOf(receipt: TransactionReceipt, token: Address, to: Address): bigint;
35
+ /**
36
+ * Coins a buy of `bnbIn` would get from a V3 pool, estimated as a constant product on the pool's active liquidity at its
37
+ * current price, the fee taken off the input. PancakeSwap's QuoterV2 cannot run a buy a coin's rule refuses, and it does
38
+ * not pass the coin's error on ("Unexpected error"), so this estimate is what names the rule then. Never use it as a minOut.
39
+ */
40
+ export declare function v3EstimateBuy(ctx: Ctx, pool: Address, token: Address, bnbIn: bigint, fee?: number): Promise<bigint>;
41
+ /** BNB per coin at a V3 pool's sqrt price (V3 prices token1 per token0). */
42
+ export declare function v3PriceBnb(sqrtPriceX96: bigint, token: Address, wbnb: Address): number;
package/dist/v3.js ADDED
@@ -0,0 +1,98 @@
1
+ import { encodeFunctionData, isAddressEqual, maxUint256, parseAbi, parseEventLogs } from "viem";
2
+ import { erc20Abi, v3QuoterAbi, v3RouterAbi } from "./abis/common.js";
3
+ import { LIMITS } from "./addresses.js";
4
+ import { deadlineIn } from "./tx.js";
5
+ /**
6
+ * PancakeSwap V3, shared by the products whose coins trade in a V3 pool of 1 % (coins with rules in the coin, v1 and v2,
7
+ * and sticky coins): the SwapRouter's buy and sell, the QuoterV2, the sell approval, and what a receipt delivered.
8
+ */
9
+ const transferAbi = [
10
+ {
11
+ type: "event",
12
+ name: "Transfer",
13
+ inputs: [
14
+ { name: "from", type: "address", indexed: true },
15
+ { name: "to", type: "address", indexed: true },
16
+ { name: "value", type: "uint256", indexed: false },
17
+ ],
18
+ anonymous: false,
19
+ },
20
+ ];
21
+ /** A buy through the V3 SwapRouter, BNB in (the router wraps it). */
22
+ export function buildV3Buy(a, b) {
23
+ return {
24
+ to: a.v3Router,
25
+ value: b.bnbIn,
26
+ data: encodeFunctionData({
27
+ abi: v3RouterAbi,
28
+ functionName: "exactInputSingle",
29
+ args: [{ tokenIn: a.wbnb, tokenOut: b.token, fee: b.fee ?? LIMITS.stickyPoolFee, recipient: b.recipient, deadline: b.deadline ?? deadlineIn(), amountIn: b.bnbIn, amountOutMinimum: b.minOut, sqrtPriceLimitX96: 0n }],
30
+ }),
31
+ gasPct: b.gasPct,
32
+ label: "The buy",
33
+ };
34
+ }
35
+ /** A sell through the V3 SwapRouter for native BNB: swap to the router, unwrap to the seller. Needs the coin approved to the router. */
36
+ export function buildV3Sell(a, s) {
37
+ const swap = encodeFunctionData({
38
+ abi: v3RouterAbi,
39
+ functionName: "exactInputSingle",
40
+ args: [{ tokenIn: s.token, tokenOut: a.wbnb, fee: s.fee ?? LIMITS.stickyPoolFee, recipient: a.v3Router, deadline: s.deadline ?? deadlineIn(), amountIn: s.amount, amountOutMinimum: s.minOut, sqrtPriceLimitX96: 0n }],
41
+ });
42
+ const unwrap = encodeFunctionData({ abi: v3RouterAbi, functionName: "unwrapWETH9", args: [s.minOut, s.recipient] });
43
+ return { to: a.v3Router, value: 0n, data: encodeFunctionData({ abi: v3RouterAbi, functionName: "multicall", args: [[swap, unwrap]] }), gasPct: s.gasPct, label: "The sell" };
44
+ }
45
+ /** What a trade gets in the V3 pool, from the QuoterV2 (it runs the swap, so a coin's refusing rule fails it: raw error). */
46
+ export async function v3Quote(ctx, q) {
47
+ const buy = q.side === "buy";
48
+ const r = await ctx.pub.simulateContract({
49
+ address: ctx.a.v3Quoter,
50
+ abi: v3QuoterAbi,
51
+ functionName: "quoteExactInputSingle",
52
+ args: [{ tokenIn: buy ? ctx.a.wbnb : q.token, tokenOut: buy ? q.token : ctx.a.wbnb, amountIn: q.amount, fee: q.fee ?? LIMITS.stickyPoolFee, sqrtPriceLimitX96: 0n }],
53
+ });
54
+ return r.result[0];
55
+ }
56
+ /** The approval of `spender` (default the V3 router) a sell needs, or null when it is in place. */
57
+ export async function approvalFor(ctx, owner, token, amount, spender = ctx.a.v3Router, label = "The approval") {
58
+ const have = await ctx.pub.readContract({ address: token, abi: erc20Abi, functionName: "allowance", args: [owner, spender] });
59
+ if (have >= amount)
60
+ return null;
61
+ return { to: token, value: 0n, data: encodeFunctionData({ abi: erc20Abi, functionName: "approve", args: [spender, maxUint256] }), label };
62
+ }
63
+ /** Coins of `token` that reached `to` in a receipt (its Transfer events). */
64
+ export function receivedOf(receipt, token, to) {
65
+ const logs = parseEventLogs({ abi: transferAbi, logs: receipt.logs.filter((l) => isAddressEqual(l.address, token)), eventName: "Transfer" });
66
+ return logs.filter((l) => isAddressEqual(l.args.to, to)).reduce((x, l) => x + l.args.value, 0n);
67
+ }
68
+ const poolStateAbi = parseAbi([
69
+ "function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16, uint16, uint16, uint32, bool)",
70
+ "function liquidity() view returns (uint128)",
71
+ ]);
72
+ /**
73
+ * Coins a buy of `bnbIn` would get from a V3 pool, estimated as a constant product on the pool's active liquidity at its
74
+ * current price, the fee taken off the input. PancakeSwap's QuoterV2 cannot run a buy a coin's rule refuses, and it does
75
+ * not pass the coin's error on ("Unexpected error"), so this estimate is what names the rule then. Never use it as a minOut.
76
+ */
77
+ export async function v3EstimateBuy(ctx, pool, token, bnbIn, fee = LIMITS.stickyPoolFee) {
78
+ const [slot0, liquidity] = await Promise.all([
79
+ ctx.pub.readContract({ address: pool, abi: poolStateAbi, functionName: "slot0" }),
80
+ ctx.pub.readContract({ address: pool, abi: poolStateAbi, functionName: "liquidity" }),
81
+ ]);
82
+ const sqrtP = slot0[0];
83
+ if (sqrtP === 0n || liquidity === 0n)
84
+ return 0n;
85
+ const Q96 = 1n << 96n;
86
+ const r0 = (liquidity * Q96) / sqrtP;
87
+ const r1 = (liquidity * sqrtP) / Q96;
88
+ const [coins, bnb] = token.toLowerCase() < ctx.a.wbnb.toLowerCase() ? [r0, r1] : [r1, r0];
89
+ const dIn = (bnbIn * BigInt(1_000_000 - fee)) / 1000000n;
90
+ return (coins * dIn) / (bnb + dIn);
91
+ }
92
+ /** BNB per coin at a V3 pool's sqrt price (V3 prices token1 per token0). */
93
+ export function v3PriceBnb(sqrtPriceX96, token, wbnb) {
94
+ const p = (Number(sqrtPriceX96) / 2 ** 96) ** 2;
95
+ if (p === 0)
96
+ return 0;
97
+ return token.toLowerCase() < wbnb.toLowerCase() ? p : 1 / p;
98
+ }
@@ -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
+ }