@pickhooks/sdk 0.0.0-stage → 0.2.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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +309 -3
  3. package/dist/abis/any3.d.ts +2123 -0
  4. package/dist/abis/any3.js +3 -0
  5. package/dist/abis/anytax.d.ts +1786 -0
  6. package/dist/abis/anytax.js +3 -0
  7. package/dist/abis/calls.d.ts +2515 -0
  8. package/dist/abis/calls.js +4 -0
  9. package/dist/abis/common.d.ts +449 -0
  10. package/dist/abis/common.js +91 -0
  11. package/dist/abis/dips.d.ts +897 -0
  12. package/dist/abis/dips.js +2 -0
  13. package/dist/abis/index.d.ts +12 -0
  14. package/dist/abis/index.js +12 -0
  15. package/dist/abis/infra.d.ts +701 -0
  16. package/dist/abis/infra.js +4 -0
  17. package/dist/abis/lever.d.ts +1710 -0
  18. package/dist/abis/lever.js +5 -0
  19. package/dist/abis/lever2.d.ts +2710 -0
  20. package/dist/abis/lever2.js +5 -0
  21. package/dist/abis/pools.d.ts +1898 -0
  22. package/dist/abis/pools.js +4 -0
  23. package/dist/abis/sticky.d.ts +953 -0
  24. package/dist/abis/sticky.js +4 -0
  25. package/dist/abis/v1.d.ts +1627 -0
  26. package/dist/abis/v1.js +5 -0
  27. package/dist/abis/v2.d.ts +3264 -0
  28. package/dist/abis/v2.js +8 -0
  29. package/dist/addresses.d.ts +87 -0
  30. package/dist/addresses.js +62 -0
  31. package/dist/any.d.ts +447 -0
  32. package/dist/any.js +729 -0
  33. package/dist/api.d.ts +62 -0
  34. package/dist/api.js +30 -0
  35. package/dist/blocks.d.ts +107 -0
  36. package/dist/blocks.js +126 -0
  37. package/dist/coin-rules-config.d.ts +8 -0
  38. package/dist/coin-rules-config.js +13 -0
  39. package/dist/coin-rules.d.ts +383 -0
  40. package/dist/coin-rules.js +878 -0
  41. package/dist/coins.d.ts +217 -0
  42. package/dist/coins.js +520 -0
  43. package/dist/dips.d.ts +133 -0
  44. package/dist/dips.js +148 -0
  45. package/dist/errors.d.ts +35 -0
  46. package/dist/errors.js +347 -0
  47. package/dist/index.d.ts +73 -0
  48. package/dist/index.js +58 -0
  49. package/dist/infinity.d.ts +63 -0
  50. package/dist/infinity.js +96 -0
  51. package/dist/infra.d.ts +112 -0
  52. package/dist/infra.js +114 -0
  53. package/dist/lever.d.ts +391 -0
  54. package/dist/lever.js +419 -0
  55. package/dist/pools.d.ts +274 -0
  56. package/dist/pools.js +405 -0
  57. package/dist/sticky-rules.d.ts +179 -0
  58. package/dist/sticky-rules.js +580 -0
  59. package/dist/sticky.d.ts +216 -0
  60. package/dist/sticky.js +292 -0
  61. package/dist/tx.d.ts +70 -0
  62. package/dist/tx.js +69 -0
  63. package/dist/v3.d.ts +42 -0
  64. package/dist/v3.js +98 -0
  65. package/package.json +58 -4
package/dist/dips.js ADDED
@@ -0,0 +1,148 @@
1
+ import { encodeFunctionData, parseEventLogs } from "viem";
2
+ import { dipDeskAbi } from "./abis/dips.js";
3
+ import { PickhooksError, toPickhooksError } from "./errors.js";
4
+ import { minusBps, sendTx, signer } from "./tx.js";
5
+ import { approvalFor } from "./v3.js";
6
+ /** strike = BNB wei per whole coin, from a price in BNB per coin */
7
+ export function strikeFromPrice(priceBnb) {
8
+ if (!(priceBnb > 0) || !Number.isFinite(priceBnb))
9
+ return 0n;
10
+ return BigInt(Math.max(1, Math.round(priceBnb * 1e18)));
11
+ }
12
+ /** BNB wei a floor of `coins` units covers at `strike` (as the desk rounds: down) */
13
+ export const coverOf = (coins, strike) => (coins * strike) / 10n ** 18n;
14
+ /** coin units whose cover is `coverWei` at `strike` (rounded down) */
15
+ export const coinsFor = (coverWei, strike) => (strike > 0n ? (coverWei * 10n ** 18n) / strike : 0n);
16
+ export function dipsModule(ctx) {
17
+ const { pub, a } = ctx;
18
+ const D = dipDeskAbi;
19
+ const desk = a.dipsDesk;
20
+ const call = (functionName, args = []) => pub.readContract({ address: desk, abi: D, functionName, args });
21
+ const now = async () => Number((await pub.getBlock({ blockTag: "latest" })).timestamp);
22
+ const toBid = (id, b, t) => ({
23
+ id,
24
+ maker: b.maker,
25
+ token: b.token,
26
+ strike: b.strike,
27
+ duration: Number(b.duration),
28
+ premiumBps: Number(b.premiumBps),
29
+ until: Number(b.until),
30
+ closed: b.closed,
31
+ minCover: b.minCover,
32
+ free: b.free,
33
+ locked: b.locked,
34
+ live: !b.closed && Number(b.until) > t && b.free >= b.minCover,
35
+ });
36
+ /** The desk's fees and limits. */
37
+ async function terms() {
38
+ const fns = ["premiumFeeBps", "exerciseFeeBps", "MIN_DEPOSIT", "MIN_COVER", "MIN_DURATION", "MAX_DURATION", "MAX_PREMIUM_BPS"];
39
+ const r = await pub.multicall({ contracts: fns.map((functionName) => ({ address: desk, abi: D, functionName })), allowFailure: false });
40
+ return { premiumFeeBps: Number(r[0]), exerciseFeeBps: Number(r[1]), minDeposit: r[2], minCover: r[3], minDuration: Number(r[4]), maxDuration: Number(r[5]), maxPremiumBps: Number(r[6]) };
41
+ }
42
+ /** The newest bids on a coin (up to `limit`), with whether each takes a floor now. */
43
+ async function bids(token, o = {}) {
44
+ const [r, t] = await Promise.all([call("bidsOf", [token, BigInt(o.limit ?? 200)]), now()]);
45
+ return r[0].map((id, i) => toBid(id, r[1][i], t));
46
+ }
47
+ async function bid(id) {
48
+ const [b, t] = await Promise.all([call("bidOf", [id]), now()]);
49
+ if (/^0x0{40}$/i.test(b.maker))
50
+ throw new PickhooksError("There is no bid with this id.", { code: "NoBid" });
51
+ return toBid(id, b, t);
52
+ }
53
+ /** A holder's floors (still holding coins), newest last. */
54
+ async function floorsOf(holder) {
55
+ const [ids] = await call("floorIdsOf", [holder, 0n, 1000n]);
56
+ if (!ids.length)
57
+ return [];
58
+ const r = await pub.multicall({ contracts: ids.map((id) => ({ address: desk, abi: D, functionName: "floorOf", args: [id] })), allowFailure: true });
59
+ const out = [];
60
+ r.forEach((x, i) => {
61
+ if (x.status !== "success")
62
+ return;
63
+ const f = x.result;
64
+ if (f.coins === 0n || f.owner.toLowerCase() !== holder.toLowerCase())
65
+ return;
66
+ out.push({ id: ids[i], bid: f.bid, owner: f.owner, coins: f.coins, cover: f.cover, premium: f.premium, bought: Number(f.bought), expiry: Number(f.expiry), feeBps: Number(f.feeBps) });
67
+ });
68
+ return out;
69
+ }
70
+ /** What a floor of `coins` on a bid covers, costs (premium, the desk's fee in it) and pays the maker. */
71
+ async function quote(bidId, coins) {
72
+ try {
73
+ const [cover, premium, fee, payout] = await call("quote", [bidId, coins]);
74
+ return { cover, premium, fee, payout };
75
+ }
76
+ catch (e) {
77
+ throw toPickhooksError(e, "The desk refused this floor.");
78
+ }
79
+ }
80
+ /** What handing in `coins` of a floor would pay `owner` now (the coins must be there and approved), or the refusal. */
81
+ async function dryExercise(floorId, coins, owner) {
82
+ try {
83
+ const r = await pub.simulateContract({ address: desk, abi: D, functionName: "exercise", args: [floorId, coins, 0n], account: owner });
84
+ return { ok: true, out: r.result };
85
+ }
86
+ catch (e) {
87
+ return { ok: false, why: toPickhooksError(e, "The coin refused the hand in.").message };
88
+ }
89
+ }
90
+ const tx = (fn, args = [], value = 0n, label) => ({ to: desk, value, data: encodeFunctionData({ abi: D, functionName: fn, args }), label });
91
+ const build = {
92
+ post: (p) => tx("post", [p.token, p.strike ?? strikeFromPrice(p.price ?? 0), p.duration, p.premiumBps, p.until, p.minCover], p.deposit, "Posting the bid"),
93
+ fund: (id, wei) => tx("fund", [id], wei, "Funding the bid"),
94
+ withdraw: (id, wei) => tx("withdraw", [id, wei], 0n, "Taking free BNB back"),
95
+ close: (id) => tx("close", [id], 0n, "Closing the bid"),
96
+ /** value = the quoted premium */
97
+ buyFloor: (bidId, coins, premium) => tx("buy", [bidId, coins], premium, "Buying the floor"),
98
+ /** needs the coin approved to the desk */
99
+ exercise: (floorId, coins, minBnb) => tx("exercise", [floorId, coins, minBnb], 0n, "Handing coins in"),
100
+ end: (floorId) => tx("end", [floorId], 0n, "Ending the floor"),
101
+ give: (floorId, to) => tx("give", [floorId, to], 0n, "Giving the floor away"),
102
+ withdrawPending: () => tx("withdrawPending", [], 0n, "Taking pending BNB"),
103
+ };
104
+ const idFrom = (sent, eventName) => parseEventLogs({ abi: D, logs: sent.receipt.logs.filter((l) => l.address.toLowerCase() === desk.toLowerCase()) }).find((e) => e.eventName === eventName)?.args.id ?? 0n;
105
+ return {
106
+ desk,
107
+ terms,
108
+ bids,
109
+ bid,
110
+ floorsOf,
111
+ quote,
112
+ dryExercise,
113
+ pending: (who) => call("pending", [who]),
114
+ strikeFromPrice,
115
+ coverOf,
116
+ coinsFor,
117
+ build,
118
+ /** posts a bid with its deposit; returns the bid id */
119
+ post: async (p, s = {}) => {
120
+ const sent = await sendTx(ctx, build.post(p), s);
121
+ return { ...sent, id: idFrom(sent, "BidPosted") };
122
+ },
123
+ fund: (id, wei, s = {}) => sendTx(ctx, build.fund(id, wei), s),
124
+ withdraw: (id, wei, s = {}) => sendTx(ctx, build.withdraw(id, wei), s),
125
+ close: (id, s = {}) => sendTx(ctx, build.close(id), s),
126
+ /** pays exactly the quoted premium; returns the floor id */
127
+ buyFloor: async (bidId, coins, s = {}) => {
128
+ const q = await quote(bidId, coins);
129
+ const sent = await sendTx(ctx, build.buyFloor(bidId, coins, q.premium), s);
130
+ return { ...sent, id: idFrom(sent, "FloorBought"), premium: q.premium };
131
+ },
132
+ /** approves the coin to the desk if needed, then hands `coins` in for at least the dry run's payout less slippageBps */
133
+ exercise: async (o, s = {}) => {
134
+ const { address } = await signer(ctx, s.wallet);
135
+ const ap = await approvalFor(ctx, address, o.token, o.coins, desk, "Allowing the desk");
136
+ if (ap)
137
+ await sendTx(ctx, ap, s);
138
+ const dry = await dryExercise(o.floorId, o.coins, address);
139
+ if (!dry.ok)
140
+ throw new PickhooksError(dry.why, { code: "dips" });
141
+ const sent = await sendTx(ctx, build.exercise(o.floorId, o.coins, minusBps(dry.out, o.slippageBps ?? 100)), s);
142
+ return { ...sent, paid: dry.out };
143
+ },
144
+ end: (floorId, s = {}) => sendTx(ctx, build.end(floorId), s),
145
+ give: (floorId, to, s = {}) => sendTx(ctx, build.give(floorId, to), s),
146
+ withdrawPending: (s = {}) => sendTx(ctx, build.withdrawPending(), s),
147
+ };
148
+ }
@@ -0,0 +1,35 @@
1
+ import { type Hex } from "viem";
2
+ /** What hook any coin's factory.check() answers, in words. */
3
+ export declare const CHECK_TEXT: Record<number, string>;
4
+ /** An error of the SDK: `code` is the contract's error name (or "wallet", "reverted", "input"), `text` a sentence. */
5
+ export declare class PickhooksError extends Error {
6
+ readonly code: string;
7
+ readonly args?: readonly unknown[];
8
+ readonly hash?: Hex;
9
+ constructor(text: string, opts: {
10
+ code: string;
11
+ args?: readonly unknown[];
12
+ hash?: Hex;
13
+ cause?: unknown;
14
+ });
15
+ }
16
+ /** The revert data inside a viem error (it nests them), or null. */
17
+ export declare function findRevertData(err: unknown): Hex | null;
18
+ /** A decoded contract error, searched at the start of the revert data and, for wrapped errors, anywhere inside it. */
19
+ export declare function decodeRevert(data: Hex): {
20
+ name: string;
21
+ args: readonly unknown[];
22
+ } | null;
23
+ /** A contract error as a sentence. */
24
+ export declare function errorText(name: string, args: readonly unknown[]): string;
25
+ /**
26
+ * Any error as a code and a sentence: a wallet refusal, a pickhooks contract's error (wrapped by PancakeSwap or not), one
27
+ * of PancakeSwap's trade errors, or the fallback.
28
+ */
29
+ export declare function explain(err: unknown, fallback?: string): {
30
+ code: string;
31
+ text: string;
32
+ args?: readonly unknown[];
33
+ };
34
+ /** Wraps any error as a PickhooksError (keeps the original as `cause`). */
35
+ export declare function toPickhooksError(err: unknown, fallback?: string, hash?: Hex): PickhooksError;
package/dist/errors.js ADDED
@@ -0,0 +1,347 @@
1
+ import { decodeErrorResult, toFunctionSelector } from "viem";
2
+ import { formatAbiItem } from "viem/utils";
3
+ import { any3FactoryAbi, any3HookAbi } from "./abis/any3.js";
4
+ import { anyTaxFactoryAbi, anyTaxHookAbi } from "./abis/anytax.js";
5
+ import { callsDeskAbi, callsFactoryAbi, callsHookAbi } from "./abis/calls.js";
6
+ import { dipDeskAbi } from "./abis/dips.js";
7
+ import { hooksExecutorAbi, hooksFloorAbi, hooksFloorFactoryAbi } from "./abis/infra.js";
8
+ import { leverEngineAbi, leverFactoryAbi, leverHookAbi, leverRouterAbi } from "./abis/lever.js";
9
+ import { lever2EngineAbi, lever2FactoryAbi, lever2HookAbi } from "./abis/lever2.js";
10
+ import { poolsFactoryAbi, poolsHookAbi } from "./abis/pools.js";
11
+ import { stickyFactoryAbi, stickyTokenAbi, stickyValidatorAbi } from "./abis/sticky.js";
12
+ import { v1FactoryAbi, v1TokenAbi } from "./abis/v1.js";
13
+ import { arenaAbi, floorVaultAbi, payrollBoxAbi, v2FactoryAbi, v2TokenAbi, voteBoxAbi } from "./abis/v2.js";
14
+ /** sticky: what token.check() answers when no row stops a trade but the coins are not unlocked yet */
15
+ const STICKY_LOCKED = 254;
16
+ /** What hook any coin's factory.check() answers, in words. */
17
+ export const CHECK_TEXT = {
18
+ 0: "This coin can get a pickhooks pool.",
19
+ 1: "There is no contract at this address.",
20
+ 2: "BNB and stablecoins are the other side of a pool, not a coin to hook.",
21
+ 3: "This coin is still on its launch curve. Once it trades on PancakeSwap it can get a pool.",
22
+ 4: "This coin has a transfer tax. A pool here would be a way around it, so it gets none.",
23
+ 5: "This coin pays dividends to holders. The pool would count as a holder, so it gets none.",
24
+ 6: "No PancakeSwap pool of this coin against BNB holds at least 3 BNB, so there is no fair price to open at.",
25
+ };
26
+ /** hook any coin's factory and sticky's factory and validator: Bad(what) as a sentence */
27
+ const BAD = {
28
+ // hook any coin, open()
29
+ fee: "Pool fee: 0.2 to 1 % (feeBps 20 to 100).",
30
+ holderFee: "The holder fee cannot be higher than the pool fee.",
31
+ chest: "The fire: at most 10 % of a buy (chestBps up to 1000).",
32
+ bonus: "Bonus: at most 10 % (bonusBps up to 1000).",
33
+ bonusLock: "Bonus lock: 1 to 30 days.",
34
+ jackpotShare: "Jackpot share: 1 to 100 % of the pot (jackpotShareBps 100 to 10000).",
35
+ jackpotMinBuy: "Jackpot minimum buy: at most 10 BNB.",
36
+ silence: "Jackpot silence: 1 minute to 7 days.",
37
+ potShare: "Jackpot: potShareBps at most 10000.",
38
+ "no opener side for a pot": "A jackpot needs an opener side to fill it.",
39
+ maxMove: "maxMoveBps: at most 500 (5 %).",
40
+ slot: "That slot is out of range or taken. Check the coin again for a free slot.",
41
+ ticks: "The range is not valid for this slot.",
42
+ liquidity: "Put in coins (and BNB for a range around the price).",
43
+ partner: "That partner is not listed by pickhooks.",
44
+ "no pool": "There is no pool with this id.",
45
+ openerBps: "openerBps out of range.",
46
+ traderBps: "traderBps out of range.",
47
+ // sticky
48
+ seed: "The seed is out of range.",
49
+ creatorBps: "creatorBps out of range.",
50
+ startFdv: "startFdv out of range.",
51
+ "two sided": "The pool would be two sided.",
52
+ rows: "At most eight rows.",
53
+ "not a template": "Only the launcher's templates can launch right now (hook script is off).",
54
+ burn: "Auto burn: at most 10 % in total.",
55
+ release: "The unlock time must be 10 minutes to 30 days.",
56
+ age: "Hold time: at most 7 days.",
57
+ window: "Sell window: open at least a minute, the period at most a day.",
58
+ hour: "Market hours: whole hours from 1 to 23.",
59
+ terms: "The fee terms changed since they were read. Read them again.",
60
+ keep: "keepBps: at most 10000.",
61
+ value: "Send at least the seed.",
62
+ metadata: "The metadata link is too long.",
63
+ name: "Name or ticker too long.",
64
+ "sell deny must end": "A rule that stops sells or sends must let go by itself.",
65
+ windows: "One sell window at most, and not next to rows on hours or weekdays.",
66
+ hours: "The rows on hours close every hour of the day: leave one open.",
67
+ weekdays: "The rows on weekdays close every day: leave one open.",
68
+ take: "Burn and pot: at most 10 % of a buy together.",
69
+ pot: "Pot: 0.01 to 10 % of the buy.",
70
+ drop: "Drop: 0.01 to 100 % of the pot.",
71
+ "pot needs a drop": "A pot needs a drop row that pays it out.",
72
+ every: "Every n-th buy: n from 2 to 1,000,000.",
73
+ idle: "Time since the last buy: at most 30 days.",
74
+ };
75
+ /** every other error of the pickhooks contracts, by name */
76
+ const BY_NAME = {
77
+ // hook any coin, factory
78
+ NotOwner: "Only the factory's owner can do that.",
79
+ NotOpener: "Only the opener of this pool can do that.",
80
+ NoLiquidity: "That is too little to add any liquidity. Put in more.",
81
+ FeeOnTransfer: "This coin takes a fee on transfers, so it cannot go into a pool.",
82
+ Transfer: "The coin refused the transfer. Check the balance and the allowance.",
83
+ PriceMoved: "The coin's price moved more than allowed while the pool opened. Check again and open.",
84
+ TooShallow: "Not enough liquidity: the pool must be at least as deep as the coin's main pool.",
85
+ StillLocked: "This position is still locked. Each one stays 30 days after it went in.",
86
+ NoPosition: "That position is already out.",
87
+ TooMany: "A pool holds at most 8 positions.",
88
+ InvalidTick: "The range is not on the pool's tick spacing.",
89
+ // hook any coin, hook
90
+ UnknownPool: "There is no pickhooks pool with this id.",
91
+ NoPot: "This pool has no jackpot.",
92
+ NoBonus: "This pool has no bonus rule.",
93
+ NothingOwed: "Nothing to claim yet.",
94
+ NotBought: "Only coins bought in this pool can be locked, and no more than that.",
95
+ NoBonusLeft: "The bonus pot has nothing left for this wallet: it is empty, or the cap is reached.",
96
+ Locked: "The bonus is still locked.",
97
+ Reentry: "A call came back into the hook while it was busy.",
98
+ Pending: "A router still has a payment open in the vault.",
99
+ // sticky and the coins with rules
100
+ PoolPrice: "Someone opened this coin's pool first. Launch again (a new salt).",
101
+ NotCreator: "Only the coin's creator can do that.",
102
+ Nope: "The coin refused this.",
103
+ // hook any coin, tax line
104
+ PayFirst: "While this coin's anti-farmer tax runs, a sell in this pool must pay its coins in first (the SDK's sell does). Terminals sell it in its main pool.",
105
+ // pools, Lever
106
+ PartialFill: "The trade would not fill completely. Try a smaller amount.",
107
+ SwapNotFilled: "The trade would not fill completely. Try a smaller amount.",
108
+ // lever and Lever
109
+ MarginTooSmall: "The smallest position is 0.01 BNB of margin plus the open fee.",
110
+ InvalidLeverage: "Up to 3x in a coin's first 10 minutes, then up to 5x.",
111
+ TooManyPositions: "All position slots of this coin are taken. A bigger margin takes the smallest one's slot (Lever), or wait until one closes.",
112
+ OpenInterestLimit: "Longs are full for now: all longs together may borrow at most a quarter of the real BNB in the curve.",
113
+ LongsFull: "Longs are full for now: all longs together may borrow at most a quarter of the real BNB in the curve. Try a smaller position or wait for more buys.",
114
+ ShortsFull: "Shorts are full for now: all shorts together may sell coins for at most a quarter of the real BNB in the curve. Try a smaller position or wait for more buys.",
115
+ UnsafePosition: "This position would sit right at its liquidation point. Try a smaller one or a lower leverage.",
116
+ Unsafe: "This trade would leave a position at its liquidation point. Try a smaller margin or a lower leverage.",
117
+ BandFull: "The curve cannot lend that much right now. Try a smaller position.",
118
+ BadDebt: "This position cannot cover what it borrowed at this price. The next trade that reaches it liquidates it.",
119
+ Pushed: "The price was pushed in this second. Try again in a moment.",
120
+ TooSoon: "A position cannot be closed in the second it opened. Try again in a moment.",
121
+ Slippage: "The price moved past the limit while the transaction waited. Try again.",
122
+ Expired: "The transaction waited too long. Try again.",
123
+ InsufficientTokens: "That is more coins than this wallet holds.",
124
+ InsufficientCash: "The curve does not hold that much BNB.",
125
+ ZeroAmount: "That amount is too small to trade.",
126
+ UnsupportedSwap: "That amount is too small to trade.",
127
+ BadAmount: "That amount is too small to trade.",
128
+ ReserveLimit: "That amount is too big for the curve.",
129
+ AmountTooLarge: "That amount is too big for the curve.",
130
+ InactivePosition: "That position is already closed.",
131
+ NotExpired: "This has not run out yet: only its owner can close it now.",
132
+ UnknownMarket: "This is not a lever coin.",
133
+ InvalidMarket: "This is not a lever coin.",
134
+ NoLiquidityPositions: "Only the coin's curve can hold liquidity in this pool.",
135
+ Unauthorized: "This contract only takes that call from its own router or hook.",
136
+ // curve calls
137
+ TooEarly: "Calls open 10 minutes after the launch, and an owner can close a call 10 minutes after it opened.",
138
+ NotAbove: "That step is not fully above the price any more. Pick a step further up.",
139
+ TooFar: "A call can be at most 8 steps above the price.",
140
+ OverCap: "That is more than the step has left to rent: at most 10 % of a step is out at once.",
141
+ SameBlock: "Someone opened a call on this coin in the same block. Try again.",
142
+ PremiumTooHigh: "The premium went up since the quote. Try again.",
143
+ Paused: "PancakeSwap has paused its pools. Calls open again when it resumes.",
144
+ OffAverage: "The price is far from its 10 minute average right now. Anyone can close an ended call once it settles, or an hour after the end.",
145
+ NoCall: "That call is already closed.",
146
+ NothingPending: "Nothing waits for this wallet here.",
147
+ // curve calls and dip bids
148
+ TooSmall: "That is too small: below the smallest call or floor.",
149
+ Duration: "That duration is out of range (calls 10 minutes to 24 hours, floors 10 minutes to 7 days).",
150
+ Value: "The BNB sent does not match. Quote again.",
151
+ PayoutTooLow: "The payout fell below the minimum while the transaction waited. Try again.",
152
+ // dip bids
153
+ NoCoin: "That is not a coin contract.",
154
+ Strike: "Set a price for the bid.",
155
+ Premium: "The premium is between 0.01 % and 50 % of what a floor covers.",
156
+ Until: "The end of the bid has to be in the future.",
157
+ Deposit: "A new bid locks at least 0.01 BNB, and at least its smallest floor.",
158
+ IsClosed: "That bid is closed.",
159
+ BidOver: "That bid has ended: it takes no new floors.",
160
+ TooBig: "The bid has less BNB free than this floor covers. Protect fewer coins or pick another bid.",
161
+ Ended: "That floor has run out or was already handed in.",
162
+ NotOver: "Only the owner can end a floor before it runs out.",
163
+ NotYours: "That floor belongs to another wallet.",
164
+ NotMaker: "Only the wallet that posted the bid can do that.",
165
+ Delivery: "The coins could not be handed in: check that this wallet holds them and has approved the desk.",
166
+ NoBid: "There is no bid with this id.",
167
+ NoFloor: "There is no floor with this id.",
168
+ Reentrant: "The desk is busy. Try again.",
169
+ // HOOKS infra: floors and the protected buy
170
+ SupplyGrew: "The coin minted new coins since its floor opened, so the floor takes no more hand ins.",
171
+ Exists: "This coin already has a floor. Feed it instead.",
172
+ Amount: "Hand in more than zero coins.",
173
+ SendFailed: "The BNB could not be sent to that address.",
174
+ TooFewCoins: "The swap brought fewer coins than the minimum. Quote again or allow more slippage.",
175
+ WrongCoin: "The dip bid is for another coin.",
176
+ NothingToProtect: "No coins arrived to protect.",
177
+ Swap: "The swap through PancakeSwap failed.",
178
+ Target: "The executor only calls PancakeSwap's own routers.",
179
+ };
180
+ /** PancakeSwap's own errors that a trade can run into */
181
+ const PANCAKE = [
182
+ ["0x5bf6f916", "The trade waited too long and ran out of time (deadline). Try again."],
183
+ [toFunctionSelector("TooLittleReceived(uint256,uint256)"), "The price moved: the trade would get less than the minimum. Quote again or allow more slippage."],
184
+ [toFunctionSelector("V3TooLittleReceived()"), "The price moved: the trade would get less than the minimum. Quote again or allow more slippage."],
185
+ [toFunctionSelector("InsufficientToken()"), "The trade got less than the minimum. Quote again or allow more slippage."],
186
+ ["0xd964f528", "The pool could not fill the whole amount. Try a smaller one."],
187
+ ];
188
+ /** The pickhooks contracts' ABIs that errors are decoded with. */
189
+ const ABIS = [
190
+ any3FactoryAbi,
191
+ any3HookAbi,
192
+ stickyFactoryAbi,
193
+ stickyValidatorAbi,
194
+ stickyTokenAbi,
195
+ anyTaxFactoryAbi,
196
+ anyTaxHookAbi,
197
+ v1FactoryAbi,
198
+ v1TokenAbi,
199
+ v2FactoryAbi,
200
+ v2TokenAbi,
201
+ voteBoxAbi,
202
+ arenaAbi,
203
+ floorVaultAbi,
204
+ payrollBoxAbi,
205
+ poolsFactoryAbi,
206
+ poolsHookAbi,
207
+ leverFactoryAbi,
208
+ leverHookAbi,
209
+ leverEngineAbi,
210
+ leverRouterAbi,
211
+ lever2FactoryAbi,
212
+ lever2HookAbi,
213
+ lever2EngineAbi,
214
+ callsFactoryAbi,
215
+ callsHookAbi,
216
+ callsDeskAbi,
217
+ dipDeskAbi,
218
+ hooksExecutorAbi,
219
+ hooksFloorAbi,
220
+ hooksFloorFactoryAbi,
221
+ ];
222
+ /** every error selector the pickhooks ABIs know, for errors that PancakeSwap wraps (WrappedError, the quoter's revert) */
223
+ const SELECTORS = ABIS.flatMap((abi) => abi.filter((x) => x.type === "error").map((x) => ({ sel: toFunctionSelector(formatAbiItem(x)).slice(2).toLowerCase(), abi })));
224
+ /** An error of the SDK: `code` is the contract's error name (or "wallet", "reverted", "input"), `text` a sentence. */
225
+ export class PickhooksError extends Error {
226
+ code;
227
+ args;
228
+ hash;
229
+ constructor(text, opts) {
230
+ super(text, { cause: opts.cause });
231
+ this.name = "PickhooksError";
232
+ this.code = opts.code;
233
+ this.args = opts.args;
234
+ this.hash = opts.hash;
235
+ }
236
+ }
237
+ /** The revert data inside a viem error (it nests them), or null. */
238
+ export function findRevertData(err) {
239
+ let e = err;
240
+ for (let i = 0; i < 10 && e; i++) {
241
+ if (typeof e.data === "string" && e.data.startsWith("0x") && e.data.length >= 10)
242
+ return e.data;
243
+ if (e.data && typeof e.data.data === "string")
244
+ return e.data.data;
245
+ e = e.cause;
246
+ }
247
+ const m = String(err?.message ?? "").match(/0x[0-9a-f]{8,}/i);
248
+ return m ? m[0] : null;
249
+ }
250
+ /** A decoded contract error, searched at the start of the revert data and, for wrapped errors, anywhere inside it. */
251
+ export function decodeRevert(data) {
252
+ for (const abi of ABIS) {
253
+ try {
254
+ const d = decodeErrorResult({ abi, data });
255
+ return { name: d.errorName, args: (d.args ?? []) };
256
+ }
257
+ catch {
258
+ /* not this contract's error */
259
+ }
260
+ }
261
+ const hex = data.toLowerCase().slice(2);
262
+ // a wrapped error carries the inner revert as bytes: find a known selector on a byte boundary and decode from there
263
+ for (const { sel, abi } of SELECTORS) {
264
+ let i = hex.indexOf(sel);
265
+ while (i >= 0) {
266
+ if (i % 2 === 0) {
267
+ try {
268
+ const d = decodeErrorResult({ abi, data: `0x${hex.slice(i)}` });
269
+ return { name: d.errorName, args: (d.args ?? []) };
270
+ }
271
+ catch {
272
+ /* trailing bytes of the wrapper: try the selector with its own words only */
273
+ try {
274
+ const d = decodeErrorResult({ abi, data: `0x${hex.slice(i, i + 8 + 64 * 8)}` });
275
+ return { name: d.errorName, args: (d.args ?? []) };
276
+ }
277
+ catch {
278
+ /* keep looking */
279
+ }
280
+ }
281
+ }
282
+ i = hex.indexOf(sel, i + 1);
283
+ }
284
+ }
285
+ return null;
286
+ }
287
+ /** require() strings of PancakeSwap's V3 contracts that need a sentence */
288
+ const ERROR_STRING = {
289
+ "Unexpected error": "PancakeSwap's quoter could not run this trade: the coin refused it, and the quoter does not pass the reason on.",
290
+ "Transaction too old": "The trade's deadline passed before it was mined. Send it again.",
291
+ "Too little received": "The price moved more than the slippage allows. Quote again or allow more slippage.",
292
+ SPL: "The trade would move the price past its limit.",
293
+ };
294
+ /** A contract error as a sentence. */
295
+ export function errorText(name, args) {
296
+ if (name === "Error")
297
+ return ERROR_STRING[String(args[0])] ?? `The contract refused: ${String(args[0] ?? "")}`;
298
+ if (name === "Panic")
299
+ return `The contract stopped with panic code ${String(args[0] ?? "")}.`;
300
+ if (name === "Refused")
301
+ return CHECK_TEXT[Number(args[0])] ?? "The factory refused this coin.";
302
+ if (name === "Blocked") {
303
+ const row = Number(args[0]);
304
+ return row === STICKY_LOCKED ? "Those coins are still locked: they unlock over time." : `Refused by rule ${row + 1} of the coin.`;
305
+ }
306
+ if (name === "Bad") {
307
+ const what = String(args[0] ?? "");
308
+ if (!what)
309
+ return "The hook refused these numbers.";
310
+ return BAD[what] ?? `Refused: ${what}.`;
311
+ }
312
+ return BY_NAME[name] ?? `The contract refused (${name}).`;
313
+ }
314
+ /**
315
+ * Any error as a code and a sentence: a wallet refusal, a pickhooks contract's error (wrapped by PancakeSwap or not), one
316
+ * of PancakeSwap's trade errors, or the fallback.
317
+ */
318
+ export function explain(err, fallback = "The transaction would fail.") {
319
+ if (err instanceof PickhooksError)
320
+ return { code: err.code, text: err.message, args: err.args };
321
+ const msg = String(err?.message ?? "");
322
+ if (/user rejected|denied transaction|rejected the request|\b4001\b/i.test(msg))
323
+ return { code: "wallet", text: "Cancelled in the wallet. Nothing was sent." };
324
+ if (/insufficient funds/i.test(msg))
325
+ return { code: "funds", text: "Not enough BNB in this wallet for the amount plus gas." };
326
+ const data = findRevertData(err);
327
+ if (data) {
328
+ const d = decodeRevert(data);
329
+ if (d)
330
+ return { code: d.name, text: errorText(d.name, d.args), args: d.args };
331
+ const low = data.toLowerCase();
332
+ for (const [sel, text] of PANCAKE)
333
+ if (low.includes(sel.slice(2).toLowerCase()))
334
+ return { code: "pancake", text };
335
+ }
336
+ const reason = msg.match(/reverted with the following reason:\s*\n?\s*(.+)/);
337
+ if (reason)
338
+ return { code: "reverted", text: `The contract refused: ${reason[1].trim()}` };
339
+ return { code: "unknown", text: fallback };
340
+ }
341
+ /** Wraps any error as a PickhooksError (keeps the original as `cause`). */
342
+ export function toPickhooksError(err, fallback, hash) {
343
+ if (err instanceof PickhooksError)
344
+ return err;
345
+ const e = explain(err, fallback);
346
+ return new PickhooksError(e.text, { code: e.code, args: e.args, hash, cause: err });
347
+ }
@@ -0,0 +1,73 @@
1
+ import type { PublicClient } from "viem";
2
+ import type { Addresses } from "./addresses.js";
3
+ import { type AnyModule } from "./any.js";
4
+ import { type ApiModule } from "./api.js";
5
+ import { type Block, type BlocksCatalog } from "./blocks.js";
6
+ import { type CoinsModule } from "./coins.js";
7
+ import { type DipsModule } from "./dips.js";
8
+ import { type InfraModule } from "./infra.js";
9
+ import { type Lever2Module, type LeverModule } from "./lever.js";
10
+ import { type CallsModule, type PoolsModule } from "./pools.js";
11
+ import { type StickyModule } from "./sticky.js";
12
+ import { type PickhooksOptions, type SendOptions, type Sent, type TxRequest } from "./tx.js";
13
+ export type Pickhooks = {
14
+ addresses: Addresses;
15
+ publicClient: PublicClient;
16
+ /** hook any coin: pools next to coins that already trade (v3 with recapture, partners, routers; the tax line when set) */
17
+ any: AnyModule;
18
+ /** sticky rules: rules that stick to the coins */
19
+ sticky: StickyModule;
20
+ /** coins with their rules in the coin (v1 and v2, also find the dev and CZ mode coins) */
21
+ coins: CoinsModule;
22
+ /** pools: coins with their rules in a PancakeSwap Infinity pool hook */
23
+ pools: PoolsModule;
24
+ /** curve calls: rent a step of a coin's curve */
25
+ calls: CallsModule;
26
+ /** Lever (v2): real liquidity, longs and shorts */
27
+ lever: Lever2Module;
28
+ /** lever (v1): the engine curve with longs */
29
+ leverV1: LeverModule;
30
+ /** dip bids: floors at a strike */
31
+ dips: DipsModule;
32
+ /** HOOKS infra: BNB floors under coins and the protected buy (off while their addresses are zero) */
33
+ infra: InfraModule;
34
+ /** the building blocks: sticky rules' ready blocks with their numbers and rows, the hook script grammar, the rule book (offline; ph.api.blocks() asks the site) */
35
+ blocks: {
36
+ catalog: () => BlocksCatalog;
37
+ block: (id: string) => Block | null;
38
+ };
39
+ /** pickhooks.com's public JSON API */
40
+ api: ApiModule;
41
+ /** sends any TxRequest the build helpers return (estimate x gasPct + its extraGas) and waits for the receipt */
42
+ send: (tx: TxRequest, o?: SendOptions) => Promise<Sent>;
43
+ sendAll: (txs: TxRequest[], o?: SendOptions) => Promise<Sent[]>;
44
+ };
45
+ /**
46
+ * The pickhooks SDK. One client, a part per product:
47
+ * any hook any coin: quotes, trades with the recapture room, opening a pool with a partner, the payouts
48
+ * sticky sticky rules: launching from templates, checking a trade against the coin's table, trading, lifting
49
+ * coins coins with rules in the coin (v1, v2): rules as sentences, the rule that would refuse a trade, launches
50
+ * pools rules in the pool; calls: curve calls; lever: Lever longs and shorts (leverV1 the first lever); dips: dip bids
51
+ * api pickhooks.com's public JSON API (launch feed, board, activity)
52
+ *
53
+ * const ph = createPickhooks({ walletClient });
54
+ * const [pool] = await ph.any.pools({ limit: 1 });
55
+ * const r = await ph.any.buy({ pool, bnbIn: parseEther("0.05") });
56
+ */
57
+ export declare function createPickhooks(options?: PickhooksOptions): Pickhooks;
58
+ export { BSC_MAINNET, LIMITS, NATIVE, ZERO_ADDRESS, type Addresses } from "./addresses.js";
59
+ export { CHECK_TEXT, PickhooksError, decodeRevert, errorText, explain, findRevertData } from "./errors.js";
60
+ export { DEFAULT_RPC, deadlineIn, minusBps, type PickhooksOptions, type SendOptions, type Sent, type TxRequest } from "./tx.js";
61
+ export { blockedKind, buildInfiSwap, infiPriceBnb, infiQuote, infiSwapInput, permit2Approvals, poolIdOf, toKey, type PoolKey } from "./infinity.js";
62
+ export { approvalFor, buildV3Buy, buildV3Sell, receivedOf, v3PriceBnb, v3Quote } from "./v3.js";
63
+ export { MAX_TICK, MIN_TICK, TAX_QUOTE_MARK, afterTax, depthNeeds, fitLadder, partnerLink, rangeFor, tickOf, type AnyLine, type AnyModule, type AnyPool, type AnyQuote, type CoinCheck, type Earnings, type OpenOptions, type OpenParams, type PoolConfig, type PoolPosition, type PoolRef, type RangeKind, type Recapture, type TaxLive, type TradeResult, } from "./any.js";
64
+ export { describeRows, type StickyCheck, type StickyCoin, type StickyLaunch, type StickyLot, type StickyModule, type StickyRow, type StickyTerms, } from "./sticky.js";
65
+ export { ruleText, type CoinRule, type CoinsModule, type RuleCheck, type RuleCoin, type RuleCoinVersion, type RuleLaunch } from "./coins.js";
66
+ export { type CallRow, type CallStep, type CallsModule, type HookCoin, type HookLaunch, type PoolsModule } from "./pools.js";
67
+ export { LEVER2_REASON, type Lever2CloseQuote, type Lever2Module, type Lever2OpenQuote, type Lever2Row, type Lever2State, type LeverCoin, type LeverMarket, type LeverModule, type LeverPosition, } from "./lever.js";
68
+ export { coinsFor, coverOf, strikeFromPrice, type DipBid, type DipFloor, type DipPost, type DipsModule } from "./dips.js";
69
+ export { type ApiModule, type Launch, type LaunchFeed } from "./api.js";
70
+ export { type FloorState, type InfraModule, type ProtectOffer } from "./infra.js";
71
+ export { BLOCKS_VERSION, BLOCK_ACTIONS, BLOCK_CHECKS, BLOCK_MARKS, BLOCK_OPERATORS, BLOCK_TRIGGERS, blockLineVars, blocksCatalog, type Block, type BlockAction, type BlockParam, type BlockVariable, type BlocksCatalog, type CoinRuleBlock, type CoinRuleField, type GrammarItem, } from "./blocks.js";
72
+ export { ACT, FLAG, LOCKED, NONE, ON, OP, STICKY_LIMITS, TEMPLATES, TEMPLATE_BY_ID, VAR, VARS, VAR_BY_ID, compile, decodeRow, defaults, editToRow, emptyEditRow, encodeRow, paramProblems, rowProblems, rowText, scannerOf, tableProblems, templateOf, type Cond, type EditCond, type EditRow, type Pick, type Row, type Template, type TemplateId, } from "./sticky-rules.js";
73
+ export { GROUPS, RULES, RULE_BY_ID, RULE_BY_KIND, buildRules, emptyExtras, emptyRules, fieldProblem, kindsOf, refusal, selectionProblem, v2Costs, worstScanner, defaults as ruleDefaults, type Picked, type R as CoinRules, type RuleDef, type X as CoinExtras, } from "./coin-rules.js";