@pickhooks/sdk 0.2.0 → 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.
- package/README.md +113 -4
- package/dist/abis/agents.d.ts +2418 -0
- package/dist/abis/agents.js +4 -0
- package/dist/abis/index.d.ts +2 -0
- package/dist/abis/index.js +2 -0
- package/dist/abis/sticky2.d.ts +3750 -0
- package/dist/abis/sticky2.js +8 -0
- package/dist/addresses.d.ts +14 -1
- package/dist/addresses.js +11 -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 +69 -2
- package/dist/index.d.ts +33 -2
- package/dist/index.js +41 -8
- package/dist/sticky-sim.d.ts +125 -0
- package/dist/sticky-sim.js +637 -0
- package/dist/sticky.d.ts +74 -4
- package/dist/sticky.js +171 -43
- package/dist/tlock.d.ts +67 -0
- package/dist/tlock.js +185 -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 +10 -1
package/dist/sticky.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Address, type Hex } from "viem";
|
|
2
|
-
import { type EditRow, type Pick, compile } from "./sticky-rules.js";
|
|
2
|
+
import { type EditRow, type Pick as RulePick, compile } from "./sticky-rules.js";
|
|
3
|
+
import { type SimReport } from "./sticky-sim.js";
|
|
3
4
|
import { type Ctx, type SendOptions, type Sent, type TxRequest } from "./tx.js";
|
|
5
|
+
import { type Venue } from "./v2.js";
|
|
4
6
|
/**
|
|
5
7
|
* sticky rules: a coin whose rules stick to the coins, not to the wallet. Every buy is a lot with its time and marks, a
|
|
6
8
|
* send moves lots along, so a fresh wallet takes the rules with it. The creator picks up to eight rows from templates; the
|
|
@@ -75,8 +77,10 @@ export type StickyLaunch = {
|
|
|
75
77
|
symbol: string;
|
|
76
78
|
/** a link to the coin's metadata JSON (name, symbol, description, image, website, twitter, telegram) */
|
|
77
79
|
metadata: string;
|
|
78
|
-
/** templates and their numbers; or rows + release for a table built elsewhere */
|
|
79
|
-
picks?:
|
|
80
|
+
/** templates and their numbers (numbers left out take the template's defaults); or rows + release for a table built elsewhere */
|
|
81
|
+
picks?: (Omit<RulePick, "params"> & {
|
|
82
|
+
params?: RulePick["params"];
|
|
83
|
+
})[];
|
|
80
84
|
/**
|
|
81
85
|
* hook script: the creator's own rows, after the templates (the factory's custom switch must be on). Built like the
|
|
82
86
|
* site's editor: emptyEditRow(), VARS for the variables and their units, editToRow() for the row on chain.
|
|
@@ -92,6 +96,12 @@ export type StickyLaunch = {
|
|
|
92
96
|
keepBps?: number;
|
|
93
97
|
/** a fixed salt (bytes32); default random */
|
|
94
98
|
salt?: Hex;
|
|
99
|
+
/**
|
|
100
|
+
* sticky 2 only: rules (0 based) that switch, 2 to 4 drop rows; one of them is on at a time (the first at launch) and
|
|
101
|
+
* the switcher turns another on. Default switcher: the rule auction, where holders burn coins to pick the game.
|
|
102
|
+
*/
|
|
103
|
+
switchRows?: number[];
|
|
104
|
+
switcher?: Address;
|
|
95
105
|
};
|
|
96
106
|
export type StickyTerms = {
|
|
97
107
|
seed: bigint;
|
|
@@ -104,11 +114,47 @@ export type StickyTerms = {
|
|
|
104
114
|
};
|
|
105
115
|
/** The rows of a coin as sentences, with the template each belongs to. */
|
|
106
116
|
export declare function describeRows(raw: readonly bigint[], release: number, off: number): StickyRow[];
|
|
107
|
-
|
|
117
|
+
/**
|
|
118
|
+
* The table a launch would send: the templates' rows, then the hook script rows (the site's order), one release time per
|
|
119
|
+
* coin (the longer one wins); or rows + release as given. Offline; throws the problems as sentences.
|
|
120
|
+
*/
|
|
121
|
+
export declare function stickyTable(l: Pick<StickyLaunch, "picks" | "script" | "scriptHours" | "rows" | "release">): {
|
|
122
|
+
rows: bigint[];
|
|
123
|
+
release: number;
|
|
124
|
+
};
|
|
125
|
+
export type StickyTestRun = {
|
|
126
|
+
rows: bigint[];
|
|
127
|
+
release: number;
|
|
128
|
+
/** the run's numbers, as the launcher's test run shows them */
|
|
129
|
+
report: SimReport;
|
|
130
|
+
/** the run as sentences, one per line of the launcher's card */
|
|
131
|
+
text: string[];
|
|
132
|
+
};
|
|
133
|
+
/** A test run as sentences: the launcher's card line by line. `names` are the rows' names (rule i + 1). */
|
|
134
|
+
export declare function testRunText(r: SimReport, names: string[]): string[];
|
|
135
|
+
/**
|
|
136
|
+
* The launcher's test run, offline: the table on a fresh coin with random trades (the creator's first buy, three snipers
|
|
137
|
+
* in the first blocks, wallets that buy, hold and sell, bags split to fresh wallets). It runs the coin's own rule engine,
|
|
138
|
+
* checked against the contract trade by trade. The same seed gives the same run; the chain stays the truth.
|
|
139
|
+
*/
|
|
140
|
+
export declare function stickyTestRun(l: Pick<StickyLaunch, "picks" | "script" | "scriptHours" | "rows" | "release"> & {
|
|
141
|
+
devBuyBnb?: number;
|
|
142
|
+
seed?: number;
|
|
143
|
+
trades?: number;
|
|
144
|
+
start?: number;
|
|
145
|
+
}): StickyTestRun;
|
|
146
|
+
/**
|
|
147
|
+
* The sticky module. `v2` points it at the sticky 2 factory (same coins, same rules, same calls; its coins also count
|
|
148
|
+
* the creator's sold coins and can carry switch rows, see ph.sticky2).
|
|
149
|
+
*/
|
|
150
|
+
export declare function stickyModule(ctx: Ctx, v2?: boolean): {
|
|
108
151
|
factory: `0x${string}`;
|
|
109
152
|
terms: () => Promise<StickyTerms>;
|
|
110
153
|
validate: (rows: bigint[], release: number, custom?: boolean) => Promise<string | null>;
|
|
154
|
+
validateSwitch: (rows: bigint[], mask: number, validator?: Address) => Promise<string | null>;
|
|
111
155
|
compile: typeof compile;
|
|
156
|
+
table: typeof stickyTable;
|
|
157
|
+
testRun: typeof stickyTestRun;
|
|
112
158
|
describeRows: typeof describeRows;
|
|
113
159
|
prepareMetadata: (m: {
|
|
114
160
|
name: string;
|
|
@@ -151,6 +197,9 @@ export declare function stickyModule(ctx: Ctx): {
|
|
|
151
197
|
wallet?: Address;
|
|
152
198
|
}) => Promise<{
|
|
153
199
|
amountOut: bigint;
|
|
200
|
+
venue: Venue;
|
|
201
|
+
v3: bigint | null;
|
|
202
|
+
v2: bigint | null;
|
|
154
203
|
}>;
|
|
155
204
|
estimateBuy: (token: Address, bnbIn: bigint) => Promise<bigint>;
|
|
156
205
|
sellApproval: (owner: Address, token: Address, amount: bigint) => Promise<TxRequest | null>;
|
|
@@ -164,6 +213,8 @@ export declare function stickyModule(ctx: Ctx): {
|
|
|
164
213
|
salt?: Hex;
|
|
165
214
|
keepBps?: number;
|
|
166
215
|
minCreatorBps: number;
|
|
216
|
+
switcher?: Address;
|
|
217
|
+
switchMask?: number;
|
|
167
218
|
}, value: bigint) => TxRequest;
|
|
168
219
|
buy: (b: {
|
|
169
220
|
token: Address;
|
|
@@ -180,6 +231,7 @@ export declare function stickyModule(ctx: Ctx): {
|
|
|
180
231
|
deadline?: bigint;
|
|
181
232
|
}) => TxRequest;
|
|
182
233
|
lift: (token: Address, row: number) => TxRequest;
|
|
234
|
+
collect: (token: Address) => Promise<TxRequest>;
|
|
183
235
|
};
|
|
184
236
|
launch: (l: StickyLaunch, s?: SendOptions) => Promise<Sent & {
|
|
185
237
|
token: Address;
|
|
@@ -194,10 +246,12 @@ export declare function stickyModule(ctx: Ctx): {
|
|
|
194
246
|
slippageBps?: number;
|
|
195
247
|
minOut?: bigint;
|
|
196
248
|
deadline?: bigint;
|
|
249
|
+
venue?: Venue;
|
|
197
250
|
}, s?: SendOptions) => Promise<Sent & {
|
|
198
251
|
quoted: bigint;
|
|
199
252
|
minOut: bigint;
|
|
200
253
|
amountOut: bigint;
|
|
254
|
+
venue: Venue;
|
|
201
255
|
}>;
|
|
202
256
|
sell: (x: {
|
|
203
257
|
token: Address;
|
|
@@ -205,11 +259,27 @@ export declare function stickyModule(ctx: Ctx): {
|
|
|
205
259
|
slippageBps?: number;
|
|
206
260
|
minOut?: bigint;
|
|
207
261
|
deadline?: bigint;
|
|
262
|
+
venue?: Venue;
|
|
208
263
|
}, s?: SendOptions) => Promise<Sent & {
|
|
209
264
|
quoted: bigint;
|
|
210
265
|
minOut: bigint;
|
|
266
|
+
venue: Venue;
|
|
211
267
|
}>;
|
|
212
268
|
lift: (token: Address, row: number, s?: SendOptions) => Promise<Sent>;
|
|
269
|
+
fees: (token: Address) => Promise<{
|
|
270
|
+
bnb: bigint;
|
|
271
|
+
burned: bigint;
|
|
272
|
+
toCreator: bigint;
|
|
273
|
+
creator: Address;
|
|
274
|
+
creatorBps: number;
|
|
275
|
+
keepBps: number;
|
|
276
|
+
pending: bigint;
|
|
277
|
+
}>;
|
|
278
|
+
collect: (token: Address, s?: SendOptions) => Promise<Sent & {
|
|
279
|
+
bnb: bigint;
|
|
280
|
+
toCreator: bigint;
|
|
281
|
+
burned: bigint;
|
|
282
|
+
}>;
|
|
213
283
|
/** the sender's address, for callers that check before they sign */
|
|
214
284
|
from: (s?: SendOptions) => Promise<`0x${string}` | undefined>;
|
|
215
285
|
};
|
package/dist/sticky.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { encodeFunctionData, parseEventLogs } from "viem";
|
|
2
2
|
import { stickyFactoryAbi, stickyTokenAbi, stickyValidatorAbi } from "./abis/sticky.js";
|
|
3
|
+
import { sticky2FactoryAbi, sticky2TokenAbi, sticky2ValidatorAbi } from "./abis/sticky2.js";
|
|
3
4
|
import { v3PoolAbi } from "./abis/common.js";
|
|
5
|
+
import { v2LockerAbi } from "./abis/v2.js";
|
|
4
6
|
import { ZERO_ADDRESS } from "./addresses.js";
|
|
5
7
|
import { PickhooksError, errorText, explain, toPickhooksError } from "./errors.js";
|
|
6
|
-
import { ACT, LOCKED, NONE, ON, compile, decodeRow, editToRow, rowProblems, rowText, scannerOf, TEMPLATE_BY_ID, templateOf } from "./sticky-rules.js";
|
|
7
|
-
import {
|
|
8
|
+
import { ACT, LOCKED, NONE, ON, compile, decodeRow, defaults, editToRow, rowProblems, rowText, scannerOf, TEMPLATE_BY_ID, templateOf } from "./sticky-rules.js";
|
|
9
|
+
import { simulate } from "./sticky-sim.js";
|
|
10
|
+
import { chainDeadline, minusBps, sendTx, senderAddress, signer } from "./tx.js";
|
|
11
|
+
import { bestVenue, buildV2Buy, buildV2Sell, v2Quote } from "./v2.js";
|
|
8
12
|
import { approvalFor, buildV3Buy, buildV3Sell, receivedOf, v3EstimateBuy, v3PriceBnb, v3Quote } from "./v3.js";
|
|
9
13
|
const randomSalt = () => {
|
|
10
14
|
const b = new Uint8Array(32);
|
|
@@ -30,15 +34,80 @@ export function describeRows(raw, release, off) {
|
|
|
30
34
|
return { i, name, text: rowText(r, release), template: id, sticky: t?.sticky ?? false, lock: r.act === ACT.LOCK, drop: r.act === ACT.DROP, lifted: (off & (1 << i)) !== 0, raw: x };
|
|
31
35
|
});
|
|
32
36
|
}
|
|
33
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The table a launch would send: the templates' rows, then the hook script rows (the site's order), one release time per
|
|
39
|
+
* coin (the longer one wins); or rows + release as given. Offline; throws the problems as sentences.
|
|
40
|
+
*/
|
|
41
|
+
export function stickyTable(l) {
|
|
42
|
+
if (!l.picks && !l.script?.length)
|
|
43
|
+
return { rows: l.rows ?? [], release: l.release ?? 0 };
|
|
44
|
+
// a template's numbers left out take its defaults
|
|
45
|
+
const picks = (l.picks ?? []).map((p) => {
|
|
46
|
+
const t = TEMPLATE_BY_ID.get(p.id);
|
|
47
|
+
if (!t)
|
|
48
|
+
throw new PickhooksError(`There is no template "${p.id}". Templates: ${[...TEMPLATE_BY_ID.keys()].join(", ")}.`, { code: "input" });
|
|
49
|
+
return { id: p.id, params: { ...defaults(t), ...p.params } };
|
|
50
|
+
});
|
|
51
|
+
const own = (l.script ?? []).map(editToRow);
|
|
52
|
+
const c = compile(picks, own);
|
|
53
|
+
const problems = [...c.problems, ...own.flatMap(rowProblems)];
|
|
54
|
+
const hours = l.scriptHours ?? 0;
|
|
55
|
+
const locks = own.some((r) => r.act === ACT.LOCK);
|
|
56
|
+
if (locks && !(hours * 3600 >= 600 && hours <= 720))
|
|
57
|
+
problems.push("Locked shares: unlock over 10 minutes to 30 days.");
|
|
58
|
+
if (problems.length)
|
|
59
|
+
throw new PickhooksError(problems.join(" "), { code: "input" });
|
|
60
|
+
return { rows: c.rows, release: locks ? Math.max(c.release, Math.round(hours * 3600)) : c.release };
|
|
61
|
+
}
|
|
62
|
+
const dur = (s) => s < 90 ? `${Math.round(s)} s` : s < 5400 ? `${Math.round(s / 60)} min` : s < 2 * 86_400 ? `${+(s / 3600).toFixed(1)} h` : `${+(s / 86_400).toFixed(1)} days`;
|
|
63
|
+
/** A test run as sentences: the launcher's card line by line. `names` are the rows' names (rule i + 1). */
|
|
64
|
+
export function testRunText(r, names) {
|
|
65
|
+
const rule = (row) => (row === LOCKED ? "still locked" : `rule ${row + 1}, ${names[row] ?? ""}`);
|
|
66
|
+
const pct = (x) => +x.toFixed(2);
|
|
67
|
+
const out = [
|
|
68
|
+
`Trades: ${r.buys.ok + r.buys.refused} buys, ${r.sells.ok + r.sells.refused} sells, ${r.sends.ok + r.sends.refused} sends in ${+r.hours.toFixed(1)} hours.`,
|
|
69
|
+
`Refused: ${r.refusals.length ? r.refusals.map((x) => `${rule(x.row)} ${x.count}x`).join(", ") : "nothing"}.`,
|
|
70
|
+
`Longest wait to sell: ${r.longestWait ? `${dur(r.longestWait)}${r.stillWaiting ? `, ${r.stillWaiting} wallets still waiting at the end` : ""}` : "nobody had to wait"}.`,
|
|
71
|
+
`Snipers: ${r.snipers.firstSellAfter !== null ? `first sold ${dur(r.snipers.firstSellAfter)} after launch` : "could not sell during the run"}.`,
|
|
72
|
+
`Split bags: ${r.splits.count ? `${r.splits.count} sent to fresh wallets, refused there again ${r.splits.refusedAfter} times` : "none"}.`,
|
|
73
|
+
`Everyone free: ${r.allFreeAfter === 0 ? "every wallet can sell everything right after the run" : r.allFreeAfter !== null ? `every wallet can sell everything ${dur(r.allFreeAfter)} after the run` : "at the latest when every lot is 30 days old"}.`,
|
|
74
|
+
];
|
|
75
|
+
if (r.burnedPct > 0)
|
|
76
|
+
out.push(`Burned: ${pct(r.burnedPct)} % of the supply.`);
|
|
77
|
+
if (r.pot.inPct > 0)
|
|
78
|
+
out.push(`Pot: ${pct(r.pot.inPct)} % of the supply in, ${r.pot.drops} drops paid ${pct(r.pot.paidPct)} %, ${pct(r.pot.leftPct)} % left.`);
|
|
79
|
+
out.push(`A scanner's test: ${r.scanner.buy !== null
|
|
80
|
+
? `its test buy is refused by ${rule(r.scanner.buy)}`
|
|
81
|
+
: r.scanner.sell !== null
|
|
82
|
+
? `its sell right after the buy is refused by ${rule(r.scanner.sell)}: it can show a honeypot`
|
|
83
|
+
: "a small buy and an instant sell pass"}.`);
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The launcher's test run, offline: the table on a fresh coin with random trades (the creator's first buy, three snipers
|
|
88
|
+
* in the first blocks, wallets that buy, hold and sell, bags split to fresh wallets). It runs the coin's own rule engine,
|
|
89
|
+
* checked against the contract trade by trade. The same seed gives the same run; the chain stays the truth.
|
|
90
|
+
*/
|
|
91
|
+
export function stickyTestRun(l) {
|
|
92
|
+
const { rows, release } = stickyTable(l);
|
|
93
|
+
const report = simulate(rows, release, { seed: l.seed ?? 7, trades: l.trades ?? 200, devBuyBnb: l.devBuyBnb ?? 0, start: l.start });
|
|
94
|
+
const names = describeRows(rows, release, 0).map((x) => x.name);
|
|
95
|
+
return { rows, release, report, text: testRunText(report, names) };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The sticky module. `v2` points it at the sticky 2 factory (same coins, same rules, same calls; its coins also count
|
|
99
|
+
* the creator's sold coins and can carry switch rows, see ph.sticky2).
|
|
100
|
+
*/
|
|
101
|
+
export function stickyModule(ctx, v2 = false) {
|
|
34
102
|
const { pub, a } = ctx;
|
|
35
|
-
const
|
|
36
|
-
const
|
|
103
|
+
const F = v2 ? a.sticky2Factory : a.stickyFactory;
|
|
104
|
+
const FA = (v2 ? sticky2FactoryAbi : stickyFactoryAbi);
|
|
105
|
+
const TA = (v2 ? sticky2TokenAbi : stickyTokenAbi);
|
|
37
106
|
const call = (address, abi, functionName, args = []) => pub.readContract({ address, abi, functionName, args });
|
|
38
107
|
/** The factory's terms now: the seed every launch pays into the V2 pair, the creator side, whether own rows are open. */
|
|
39
108
|
async function terms() {
|
|
40
109
|
const fns = ["seedBnb", "creatorBps", "custom", "validator", "startFdv", "platformWallet", "locker"];
|
|
41
|
-
const r = await pub.multicall({ contracts: fns.map((functionName) => ({ address:
|
|
110
|
+
const r = await pub.multicall({ contracts: fns.map((functionName) => ({ address: F, abi: FA, functionName })), allowFailure: false });
|
|
42
111
|
return { seed: r[0], creatorBps: Number(r[1]), custom: r[2], validator: r[3], startFdv: r[4], platformWallet: r[5], locker: r[6] };
|
|
43
112
|
}
|
|
44
113
|
/** Asks the validator on chain whether a table can launch: null when it can, else the reason. */
|
|
@@ -52,6 +121,19 @@ export function stickyModule(ctx) {
|
|
|
52
121
|
return explain(e, "The validator refused this table.").text;
|
|
53
122
|
}
|
|
54
123
|
}
|
|
124
|
+
const maskOf = (rowsOn) => (rowsOn ?? []).reduce((m, i) => m | (1 << i), 0);
|
|
125
|
+
/** sticky 2: asks the validator whether these rows can switch (drop rows only, 2 to 4): null when they can, else why not */
|
|
126
|
+
async function validateSwitch(rows, mask, validator) {
|
|
127
|
+
if (!v2)
|
|
128
|
+
return "Switch rows are a sticky 2 feature.";
|
|
129
|
+
try {
|
|
130
|
+
await call(validator ?? (await terms()).validator, sticky2ValidatorAbi, "switchRows", [rows, mask]);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
return explain(e, "The validator refused these switch rows.").text;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
55
137
|
/**
|
|
56
138
|
* Stores the image and metadata on pickhooks.com and returns the metadata link (the site's /api/sticky/prepare, rate
|
|
57
139
|
* limited, only while sticky rules are live on the site). A launchpad can host its own JSON and pass that link instead.
|
|
@@ -70,35 +152,31 @@ export function stickyModule(ctx) {
|
|
|
70
152
|
/** The launch call. value = the factory's seed + the creator's first buy. */
|
|
71
153
|
function buildLaunch(l, value) {
|
|
72
154
|
const args = { name: l.name, symbol: l.symbol, metadata: l.metadata, rows: l.rows, release: l.release, salt: l.salt ?? randomSalt(), keepBps: l.keepBps ?? 10_000, minCreatorBps: l.minCreatorBps };
|
|
73
|
-
|
|
155
|
+
if (!v2 && l.switchMask)
|
|
156
|
+
throw new PickhooksError("Switch rows are a sticky 2 feature.", { code: "input" });
|
|
157
|
+
// sticky 2 takes a switcher and its switch rows too: none from a plain launch
|
|
158
|
+
const full = v2 ? { ...args, switcher: l.switchMask ? (l.switcher ?? a.ruleAuction) : ZERO_ADDRESS, switchMask: l.switchMask ?? 0 } : args;
|
|
159
|
+
return { to: F, value, data: encodeFunctionData({ abi: FA, functionName: "launch", args: [full] }), label: "The launch" };
|
|
74
160
|
}
|
|
75
161
|
/** Builds the table from templates and hook script rows (or takes rows), checks it locally and on chain, launches, returns the coin. */
|
|
76
162
|
async function launch(l, s = {}) {
|
|
77
|
-
let rows = l.rows ?? [];
|
|
78
|
-
let release = l.release ?? 0;
|
|
79
163
|
const t = await terms();
|
|
80
164
|
if (l.script?.length && !t.custom)
|
|
81
165
|
throw new PickhooksError("The hook script is off on the factory: templates only for now.", { code: "input" });
|
|
82
|
-
|
|
83
|
-
// the site's order: the templates' rows, then the own rows; one release time per coin, the longer one wins
|
|
84
|
-
const own = (l.script ?? []).map(editToRow);
|
|
85
|
-
const c = compile(l.picks ?? [], own);
|
|
86
|
-
const problems = [...c.problems, ...own.flatMap(rowProblems)];
|
|
87
|
-
const hours = l.scriptHours ?? 0;
|
|
88
|
-
const locks = own.some((r) => r.act === ACT.LOCK);
|
|
89
|
-
if (locks && !(hours * 3600 >= 600 && hours <= 720))
|
|
90
|
-
problems.push("Locked shares: unlock over 10 minutes to 30 days.");
|
|
91
|
-
if (problems.length)
|
|
92
|
-
throw new PickhooksError(problems.join(" "), { code: "input" });
|
|
93
|
-
rows = c.rows;
|
|
94
|
-
release = locks ? Math.max(c.release, Math.round(hours * 3600)) : c.release;
|
|
95
|
-
}
|
|
166
|
+
const { rows, release } = stickyTable(l);
|
|
96
167
|
const refused = await validate(rows, release, t.custom);
|
|
97
168
|
if (refused)
|
|
98
169
|
throw new PickhooksError(refused, { code: "Bad" });
|
|
99
|
-
const
|
|
170
|
+
const switchMask = maskOf(l.switchRows);
|
|
171
|
+
if (switchMask) {
|
|
172
|
+
const no = await validateSwitch(rows, switchMask, t.validator);
|
|
173
|
+
if (no)
|
|
174
|
+
throw new PickhooksError(no, { code: "Bad" });
|
|
175
|
+
}
|
|
176
|
+
const tx = buildLaunch({ name: l.name, symbol: l.symbol, metadata: l.metadata, rows, release, salt: l.salt, keepBps: l.keepBps, minCreatorBps: t.creatorBps, switcher: l.switcher, switchMask }, t.seed + (l.devBuy ?? 0n));
|
|
100
177
|
const sent = await sendTx(ctx, tx, s);
|
|
101
|
-
|
|
178
|
+
// both factories emit the same Launched event
|
|
179
|
+
const ev = parseEventLogs({ abi: stickyFactoryAbi, logs: sent.receipt.logs.filter((x) => x.address.toLowerCase() === F.toLowerCase()), eventName: "Launched" })[0];
|
|
102
180
|
if (!ev)
|
|
103
181
|
throw new PickhooksError("Launched, but the coin was not found in the receipt.", { code: "receipt", hash: sent.hash });
|
|
104
182
|
return { ...sent, token: ev.args.token, pool: ev.args.pool, pair: ev.args.pair, rows, release };
|
|
@@ -150,7 +228,7 @@ export function stickyModule(ctx) {
|
|
|
150
228
|
return out;
|
|
151
229
|
}
|
|
152
230
|
/** How many coins the factory launched. */
|
|
153
|
-
const count = async () => Number(await call(
|
|
231
|
+
const count = async () => Number(await call(F, FA, "coinCount"));
|
|
154
232
|
/** Coins newest first: `limit` of them, skipping the newest `offset`. */
|
|
155
233
|
async function coins(o = {}) {
|
|
156
234
|
const n = await count();
|
|
@@ -160,12 +238,12 @@ export function stickyModule(ctx) {
|
|
|
160
238
|
return [];
|
|
161
239
|
const idx = Array.from({ length: end - from }, (_, i) => BigInt(end - 1 - i));
|
|
162
240
|
const tokens = (await pub.multicall({
|
|
163
|
-
contracts: idx.map((i) => ({ address:
|
|
241
|
+
contracts: idx.map((i) => ({ address: F, abi: FA, functionName: "coins", args: [i] })),
|
|
164
242
|
allowFailure: false,
|
|
165
243
|
}));
|
|
166
244
|
return summaries(tokens);
|
|
167
245
|
}
|
|
168
|
-
const isCoin = async (token) => (await call(
|
|
246
|
+
const isCoin = async (token) => (await call(F, FA, "isCoin", [token]).catch(() => false)) === true;
|
|
169
247
|
/** One sticky coin, or null when the address is not one of the factory's coins. */
|
|
170
248
|
async function coin(token) {
|
|
171
249
|
if (!(await isCoin(token)))
|
|
@@ -218,11 +296,17 @@ export function stickyModule(ctx) {
|
|
|
218
296
|
* does not pass the reason on: a refused buy is checked again with an estimate of its coins and the refusal names the
|
|
219
297
|
* rule (for `wallet`, by default the quoter the quote ran as).
|
|
220
298
|
*/
|
|
299
|
+
/**
|
|
300
|
+
* The better of the coin's two venues: its V3 pool and its V2 pair (every sticky coin has both at the same start price;
|
|
301
|
+
* after a dark launch the round's BNB sits in the pair, and a sell into the one sided V3 curve would get almost nothing).
|
|
302
|
+
* Terminals pick the pool by themselves; the SDK does the same here. The venue field says which one buy/sell will use.
|
|
303
|
+
*/
|
|
221
304
|
async function quote(q) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
305
|
+
const best = await bestVenue(ctx, { v3: () => v3Quote(ctx, q), v2: () => v2Quote(ctx, q) });
|
|
306
|
+
if (best.v3 !== null || best.v2 !== null)
|
|
307
|
+
return { amountOut: best.amountOut, venue: best.venue, v3: best.v3, v2: best.v2 };
|
|
308
|
+
const e = best.errors.v3 ?? best.errors.v2;
|
|
309
|
+
{
|
|
226
310
|
if (q.side === "buy") {
|
|
227
311
|
const est = await estimateBuy(q.token, q.amount).catch(() => 0n);
|
|
228
312
|
const c = est > 0n ? await check({ token: q.token, side: "buy", wallet: q.wallet ?? a.v3Quoter, amount: est }).catch(() => null) : null;
|
|
@@ -246,30 +330,72 @@ export function stickyModule(ctx) {
|
|
|
246
330
|
async function buy(b, s = {}) {
|
|
247
331
|
const { address } = await signer(ctx, s.wallet);
|
|
248
332
|
const q = await quote({ token: b.token, side: "buy", amount: b.bnbIn, wallet: address });
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
333
|
+
const venue = b.venue ?? q.venue;
|
|
334
|
+
const quoted = (venue === "v2" ? q.v2 : q.v3) ?? q.amountOut;
|
|
335
|
+
refuse(await check({ token: b.token, side: "buy", wallet: address, amount: quoted }));
|
|
336
|
+
const minOut = b.minOut ?? minusBps(quoted, b.slippageBps ?? 100);
|
|
337
|
+
const deadline = b.deadline ?? (await chainDeadline(ctx));
|
|
338
|
+
const tx = venue === "v2" ? buildV2Buy(a, { token: b.token, bnbIn: b.bnbIn, minOut, recipient: address, deadline }) : buildBuy({ token: b.token, bnbIn: b.bnbIn, minOut, recipient: address, deadline });
|
|
339
|
+
const sent = await sendTx(ctx, tx, s);
|
|
340
|
+
return { ...sent, quoted, minOut, amountOut: receivedOf(sent.receipt, b.token, address), venue };
|
|
253
341
|
}
|
|
254
342
|
/** Checks the rules, approves the router if needed, quotes and sells for BNB. */
|
|
255
343
|
async function sell(x, s = {}) {
|
|
256
344
|
const { address } = await signer(ctx, s.wallet);
|
|
257
345
|
refuse(await check({ token: x.token, side: "sell", wallet: address, amount: x.amount }));
|
|
258
|
-
const
|
|
346
|
+
const q = await quote({ token: x.token, side: "sell", amount: x.amount });
|
|
347
|
+
const venue = x.venue ?? q.venue;
|
|
348
|
+
const quoted = (venue === "v2" ? q.v2 : q.v3) ?? q.amountOut;
|
|
349
|
+
const ap = await approvalFor(ctx, address, x.token, x.amount, venue === "v2" ? a.v2Router : a.v3Router, "Allowing PancakeSwap");
|
|
259
350
|
if (ap)
|
|
260
351
|
await sendTx(ctx, ap, s);
|
|
261
|
-
const
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
|
|
352
|
+
const minOut = x.minOut ?? minusBps(quoted, x.slippageBps ?? 100);
|
|
353
|
+
const deadline = x.deadline ?? (await chainDeadline(ctx));
|
|
354
|
+
const tx = venue === "v2" ? buildV2Sell(a, { token: x.token, amount: x.amount, minOut, recipient: address, deadline }) : buildSell({ token: x.token, amount: x.amount, minOut, recipient: address, deadline });
|
|
355
|
+
const sent = await sendTx(ctx, tx, s);
|
|
356
|
+
return { ...sent, quoted, minOut, venue };
|
|
265
357
|
}
|
|
266
358
|
/** The creator lifts a row for good (a drop row cannot be lifted). */
|
|
267
359
|
const buildLift = (token, row) => ({ to: token, value: 0n, data: encodeFunctionData({ abi: TA, functionName: "lift", args: [row] }), label: `Lifting rule ${row + 1}` });
|
|
360
|
+
const lockerOf = (token) => call(token, TA, "locker");
|
|
361
|
+
/** Anyone: collects the coin's trading fees from its locked position; the locker pays the creator's side to the creator. */
|
|
362
|
+
const buildCollect = async (token) => ({
|
|
363
|
+
to: await lockerOf(token),
|
|
364
|
+
value: 0n,
|
|
365
|
+
data: encodeFunctionData({ abi: v2LockerAbi, functionName: "collect", args: [token] }),
|
|
366
|
+
label: "The collect",
|
|
367
|
+
});
|
|
368
|
+
/**
|
|
369
|
+
* What collect() would book now (simulated): the BNB fees of the coin's position and the coins it burns, what of it goes
|
|
370
|
+
* to the creator (creatorBps of the fees is the creator side, keepBps of that the creator keeps, the rest buys back and
|
|
371
|
+
* burns $hooks), and BNB the locker holds for the creator because a payout bounced (withdrawPending() takes it).
|
|
372
|
+
*/
|
|
373
|
+
async function fees(token) {
|
|
374
|
+
const locker = await lockerOf(token);
|
|
375
|
+
const pos = await call(locker, v2LockerAbi, "positions", [token]);
|
|
376
|
+
const [sim, pending] = await Promise.all([
|
|
377
|
+
pub.simulateContract({ address: locker, abi: v2LockerAbi, functionName: "collect", args: [token] }).then((r) => r.result),
|
|
378
|
+
call(locker, v2LockerAbi, "pending", [pos[2]]),
|
|
379
|
+
]);
|
|
380
|
+
// the locker's own split
|
|
381
|
+
const side = (sim[0] * BigInt(pos[3])) / 10000n;
|
|
382
|
+
const toCreator = ((side - (side * BigInt(pos[4])) / 10000n) * BigInt(pos[5])) / 10000n;
|
|
383
|
+
return { bnb: sim[0], burned: sim[1], toCreator, creator: pos[2], creatorBps: Number(pos[3]), keepBps: Number(pos[5]), pending };
|
|
384
|
+
}
|
|
385
|
+
/** Collects the coin's fees; returns what the locker booked (its Collected event). */
|
|
386
|
+
async function collect(token, s = {}) {
|
|
387
|
+
const sent = await sendTx(ctx, await buildCollect(token), s);
|
|
388
|
+
const ev = parseEventLogs({ abi: v2LockerAbi, logs: sent.receipt.logs, eventName: "Collected" })[0];
|
|
389
|
+
return { ...sent, bnb: ev?.args.bnb ?? 0n, toCreator: ev?.args.toCreator ?? 0n, burned: ev?.args.burned ?? 0n };
|
|
390
|
+
}
|
|
268
391
|
return {
|
|
269
|
-
factory:
|
|
392
|
+
factory: F,
|
|
270
393
|
terms,
|
|
271
394
|
validate,
|
|
395
|
+
validateSwitch,
|
|
272
396
|
compile,
|
|
397
|
+
table: stickyTable,
|
|
398
|
+
testRun: stickyTestRun,
|
|
273
399
|
describeRows,
|
|
274
400
|
prepareMetadata,
|
|
275
401
|
count,
|
|
@@ -281,11 +407,13 @@ export function stickyModule(ctx) {
|
|
|
281
407
|
quote,
|
|
282
408
|
estimateBuy,
|
|
283
409
|
sellApproval,
|
|
284
|
-
build: { launch: buildLaunch, buy: buildBuy, sell: buildSell, lift: buildLift },
|
|
410
|
+
build: { launch: buildLaunch, buy: buildBuy, sell: buildSell, lift: buildLift, collect: buildCollect },
|
|
285
411
|
launch,
|
|
286
412
|
buy,
|
|
287
413
|
sell,
|
|
288
414
|
lift: (token, row, s = {}) => sendTx(ctx, buildLift(token, row), s),
|
|
415
|
+
fees,
|
|
416
|
+
collect,
|
|
289
417
|
/** the sender's address, for callers that check before they sign */
|
|
290
418
|
from: (s = {}) => senderAddress(ctx, s.wallet),
|
|
291
419
|
};
|
package/dist/tlock.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Hex } from "viem";
|
|
2
|
+
export type Net = {
|
|
3
|
+
chainHash: string;
|
|
4
|
+
publicKey: string;
|
|
5
|
+
genesis: number;
|
|
6
|
+
period: number;
|
|
7
|
+
dst: string;
|
|
8
|
+
urls: string[];
|
|
9
|
+
};
|
|
10
|
+
export type Sealed = {
|
|
11
|
+
u: Hex;
|
|
12
|
+
v: Hex;
|
|
13
|
+
w: Hex;
|
|
14
|
+
round: number;
|
|
15
|
+
sigma: Hex;
|
|
16
|
+
};
|
|
17
|
+
export declare const QUICKNET: Net;
|
|
18
|
+
declare const G2: import("@noble/curves/abstract/weierstrass").ProjConstructor<import("@noble/curves/abstract/tower").Fp2>;
|
|
19
|
+
type G2P = InstanceType<typeof G2>;
|
|
20
|
+
/** r = (keccak(TAG3 || 0x01 || sigma || M) * 2^256 + keccak(TAG3 || 0x02 || sigma || M)) mod q, never 0. */
|
|
21
|
+
export declare function h3(sigma: Uint8Array, m: Uint8Array): bigint;
|
|
22
|
+
/** keccak(TAG4 || sigma). */
|
|
23
|
+
export declare function h4(sigma: Uint8Array): Uint8Array;
|
|
24
|
+
/** First second at which drand publishes the round. */
|
|
25
|
+
export declare function roundTime(round: number | bigint, net?: Net): number;
|
|
26
|
+
/** First round published at or after `unixSeconds`. */
|
|
27
|
+
export declare function roundAt(unixSeconds: number, net?: Net): number;
|
|
28
|
+
/** The round's identity point: hash_to_G1(sha256(round as 8 bytes big endian)), as drand quicknet signs it. */
|
|
29
|
+
export declare function roundPoint(round: number | bigint, net?: Net): import("@noble/curves/abstract/weierstrass").ProjPointType<bigint>;
|
|
30
|
+
/** True when `sigHex` is drand's signature on `round` under the network's key. */
|
|
31
|
+
export declare function verifyRoundSig(round: number | bigint, sigHex: string, net?: Net): boolean;
|
|
32
|
+
/** G2 point to the 256 byte EIP-2537 encoding: x.c0, x.c1, y.c0, y.c1, each 64 bytes (16 zero bytes + 48). */
|
|
33
|
+
export declare function g2ToEip(point: G2P): Hex;
|
|
34
|
+
export declare function g2FromEip(h: string): G2P;
|
|
35
|
+
/** The 32 byte message of a dark launch bid: the bidder (20 bytes), the step (1 byte), 11 zero bytes. */
|
|
36
|
+
export declare function darkMessage(bidder: string, step: number): Hex;
|
|
37
|
+
export declare function parseDarkMessage(m: string): {
|
|
38
|
+
bidder: Hex;
|
|
39
|
+
step: number;
|
|
40
|
+
clean: boolean;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Encrypts 32 bytes to a drand round. Returns hex ready for the contracts: u (256 bytes), v and w (32 each).
|
|
44
|
+
* `opts.sigma` and `opts.publicKey` exist for tests only.
|
|
45
|
+
*/
|
|
46
|
+
export declare function encrypt(round: number | bigint, message: string | Uint8Array, opts?: {
|
|
47
|
+
net?: Net;
|
|
48
|
+
sigma?: string | Uint8Array;
|
|
49
|
+
publicKey?: string;
|
|
50
|
+
}): Sealed;
|
|
51
|
+
/** Opens a ciphertext with drand's signature on its round: { sigma, message }, or null when it does not open. */
|
|
52
|
+
export declare function decrypt(c: {
|
|
53
|
+
u: string;
|
|
54
|
+
v: string;
|
|
55
|
+
w: string;
|
|
56
|
+
}, sigHex: string): {
|
|
57
|
+
sigma: Hex;
|
|
58
|
+
message: Hex;
|
|
59
|
+
} | null;
|
|
60
|
+
/** The contract's own check, in JS: U == H3(sigma, M) * G2 and W == M xor H4(sigma). */
|
|
61
|
+
export declare function opens(c: {
|
|
62
|
+
u: string;
|
|
63
|
+
w: string;
|
|
64
|
+
}, sigma: string | Uint8Array, message: string | Uint8Array): boolean;
|
|
65
|
+
/** drand's signature on `round` (hex, 48 bytes), checked against the network's key. Null while the round is not out. */
|
|
66
|
+
export declare function fetchRoundSig(round: number | bigint, net?: Net, fetcher?: typeof fetch): Promise<string | null>;
|
|
67
|
+
export {};
|