clockwork-press 0.2.2 → 0.2.3
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/dist/index.js +5 -1
- package/dist/jobs/dividends.js +293 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/clockwork/SKILL.md +6 -4
package/dist/index.js
CHANGED
|
@@ -37,6 +37,10 @@ else if (job === 'log') {
|
|
|
37
37
|
const { runLog } = await import('./jobs/log.js');
|
|
38
38
|
await runLog();
|
|
39
39
|
}
|
|
40
|
+
else if (job === 'dividends') {
|
|
41
|
+
const { runDividends } = await import('./jobs/dividends.js');
|
|
42
|
+
await runDividends();
|
|
43
|
+
}
|
|
40
44
|
else if (job === 'holders') {
|
|
41
45
|
const { readHolders } = await import('./holders.js');
|
|
42
46
|
console.log(`holders: ${await readHolders()}`);
|
|
@@ -50,7 +54,7 @@ else if (job === 'launchcheck') {
|
|
|
50
54
|
await runLaunchCheck();
|
|
51
55
|
}
|
|
52
56
|
else {
|
|
53
|
-
console.error(`unknown job: ${job} (use init | press | dry | doctor | claimcheck | holders | tgcheck | log | buys | quotecheck | launchcheck | handback <address>)`);
|
|
57
|
+
console.error(`unknown job: ${job} (use init | press | dry | doctor | claimcheck | holders | tgcheck | log | buys | quotecheck | launchcheck | dividends | handback <address>)`);
|
|
54
58
|
process.exit(1);
|
|
55
59
|
}
|
|
56
60
|
export {};
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
// clockwork dividends: the holder side. some memestocks pay their holders in the stock. this job reads every
|
|
2
|
+
// payout a known distributor made, groups them into drops, prices each drop at the time it landed, and writes
|
|
3
|
+
// a public dataset the statement page and the leaderboard read: who pays, how much, to whom, worth what.
|
|
4
|
+
// read-only; no key involved. the registry of distributors grows by hand and, later, from the airdrop signature.
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join, resolve } from 'node:path';
|
|
7
|
+
import { createPublicClient, defineChain, fallback, formatUnits, http, parseAbi, parseAbiItem } from 'viem';
|
|
8
|
+
// this job runs anywhere, with no machine config and no key: its own client on the public rpc (RPC_URL to override)
|
|
9
|
+
const chain = defineChain({ id: 4663, name: 'Robinhood Chain', nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, rpcUrls: { default: { http: [process.env.RPC_URL || 'https://rpc.mainnet.chain.robinhood.com'] } } });
|
|
10
|
+
const o = { retryCount: 6, retryDelay: 1500, timeout: 30_000 };
|
|
11
|
+
const pub = createPublicClient({ chain, transport: process.env.RPC_URL_2 ? fallback([http(chain.rpcUrls.default.http[0], o), http(process.env.RPC_URL_2, o)]) : http(chain.rpcUrls.default.http[0], o) });
|
|
12
|
+
const TOKENS = {
|
|
13
|
+
GME: { address: '0x1b0E319c6A659F002271B69dB8A7df2F911c153E', decimals: 18, feed: '0x27C71df6A64fB476468EdF256CF72c038baB5B67' },
|
|
14
|
+
USDG: { address: '0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168', decimals: 6 },
|
|
15
|
+
};
|
|
16
|
+
const DEFAULT_REGISTRY = [
|
|
17
|
+
{ address: '0xe311d712bd0669896bcb47f38845ad71a641b8be', name: 'GameStop token, Diamond Drop', token: 'GME', memeToken: '0xc2362AfF2A2a4CC1f48cF3Dab2C4e2605eb94BA3', memeSymbol: 'GME (memecoin)', site: 'https://thegameneverstopped.com', note: 'hold $50+, lowest balance since the last drop is the base, a sell disqualifies, a streak multiplier to 2x' },
|
|
18
|
+
];
|
|
19
|
+
const ev = parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)');
|
|
20
|
+
const feedAbi = parseAbi(['function latestRoundData() view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)', 'function getRoundData(uint80 roundId) view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)', 'function decimals() view returns (uint8)']);
|
|
21
|
+
const GAP_BLOCKS = 70000n; // payouts closer than this belong to the same drop (about two hours at ten blocks a second)
|
|
22
|
+
const CHUNK = 10000000n;
|
|
23
|
+
const readJson = (p, d) => { try {
|
|
24
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return d;
|
|
28
|
+
} };
|
|
29
|
+
const writeJson = (p, v) => writeFileSync(p, JSON.stringify(v, null, 1) + '\n');
|
|
30
|
+
// the usd price of the pairing asset at a moment, from the chainlink feed's history: a binary search over the
|
|
31
|
+
// current phase's rounds, cached per job run. null when the token has no feed.
|
|
32
|
+
async function priceAt(sym, ts, cache) {
|
|
33
|
+
const t = TOKENS[sym];
|
|
34
|
+
if (!t?.feed)
|
|
35
|
+
return sym === 'USDG' ? 1 : null;
|
|
36
|
+
const key = `${sym}:${Math.floor(ts / 600)}`;
|
|
37
|
+
if (cache.has(key))
|
|
38
|
+
return cache.get(key);
|
|
39
|
+
try {
|
|
40
|
+
const dec = Number(await pub.readContract({ address: t.feed, abi: feedAbi, functionName: 'decimals' }));
|
|
41
|
+
const latest = await pub.readContract({ address: t.feed, abi: feedAbi, functionName: 'latestRoundData' });
|
|
42
|
+
const latestId = latest[0];
|
|
43
|
+
const phase = latestId >> 64n;
|
|
44
|
+
let lo = 1n, hi = latestId & ((1n << 64n) - 1n);
|
|
45
|
+
const at = async (agg) => pub.readContract({ address: t.feed, abi: feedAbi, functionName: 'getRoundData', args: [(phase << 64n) | agg] });
|
|
46
|
+
let best = Number(latest[1]) / 10 ** dec;
|
|
47
|
+
for (let i = 0; i < 40 && lo <= hi; i++) {
|
|
48
|
+
const mid = (lo + hi) / 2n;
|
|
49
|
+
let r;
|
|
50
|
+
try {
|
|
51
|
+
r = await at(mid);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
lo = mid + 1n;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const upd = Number(r[3]);
|
|
58
|
+
if (!upd) {
|
|
59
|
+
lo = mid + 1n;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (upd <= ts) {
|
|
63
|
+
best = Number(r[1]) / 10 ** dec;
|
|
64
|
+
lo = mid + 1n;
|
|
65
|
+
}
|
|
66
|
+
else
|
|
67
|
+
hi = mid - 1n;
|
|
68
|
+
}
|
|
69
|
+
cache.set(key, best);
|
|
70
|
+
return best;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
cache.set(key, null);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function marketCap(token) {
|
|
78
|
+
if (!token)
|
|
79
|
+
return null;
|
|
80
|
+
try {
|
|
81
|
+
const r = await fetch(`https://api.dexscreener.com/latest/dex/tokens/${token}`);
|
|
82
|
+
const j = await r.json();
|
|
83
|
+
const pairs = (j?.pairs || []).filter((p) => String(p.chainId).toLowerCase().includes('robinhood'));
|
|
84
|
+
const best = pairs.sort((a, b) => Number(b.liquidity?.usd || 0) - Number(a.liquidity?.usd || 0))[0];
|
|
85
|
+
const cap = best?.marketCap ?? best?.fdv;
|
|
86
|
+
return cap ? Number(cap) : null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export async function runDividends() {
|
|
93
|
+
const dir = resolve(process.env.DIVIDENDS_DIR || 'data/dividends');
|
|
94
|
+
mkdirSync(join(dir, 'drops'), { recursive: true });
|
|
95
|
+
mkdirSync(join(dir, 'wallets'), { recursive: true });
|
|
96
|
+
const registry = readJson(join(dir, 'distributors.json'), DEFAULT_REGISTRY);
|
|
97
|
+
if (!existsSync(join(dir, 'distributors.json')))
|
|
98
|
+
writeJson(join(dir, 'distributors.json'), registry);
|
|
99
|
+
const state = readJson(join(dir, 'state.json'), {});
|
|
100
|
+
const index = readJson(join(dir, 'index.json'), { updatedAt: '', tokens: TOKENS, distributors: [], drops: [] });
|
|
101
|
+
const head = await pub.getBlockNumber();
|
|
102
|
+
const priceCache = new Map();
|
|
103
|
+
const walletShards = {};
|
|
104
|
+
const shardOf = (a) => a.slice(2, 4).toLowerCase();
|
|
105
|
+
const loadShard = (s) => (walletShards[s] ??= readJson(join(dir, 'wallets', `${s}.json`), {}));
|
|
106
|
+
let newDrops = 0;
|
|
107
|
+
for (const d of registry) {
|
|
108
|
+
const tok = TOKENS[d.token];
|
|
109
|
+
if (!tok) {
|
|
110
|
+
console.log(`[dividends] ${d.name}: unknown token ${d.token}, skipped`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const key = `${d.address.toLowerCase()}:${d.token}`;
|
|
114
|
+
const fromBlock = BigInt(state[key] ?? 0) + (state[key] ? 1n : 0n);
|
|
115
|
+
if (fromBlock > head)
|
|
116
|
+
continue;
|
|
117
|
+
const logs = [];
|
|
118
|
+
for (let a = fromBlock; a <= head; a += CHUNK) {
|
|
119
|
+
const b = a + CHUNK - 1n > head ? head : a + CHUNK - 1n;
|
|
120
|
+
try {
|
|
121
|
+
const got = await pub.getLogs({ address: tok.address, event: ev, args: { from: d.address }, fromBlock: a, toBlock: b });
|
|
122
|
+
for (const l of got)
|
|
123
|
+
logs.push({ block: l.blockNumber, tx: l.transactionHash, to: String(l.args.to).toLowerCase(), value: l.args.value });
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
console.log(`[dividends] ${d.name}: chunk ${a}-${b} failed: ${(e.shortMessage || e.message).split('\n')[0]}`);
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
logs.sort((x, y) => (x.block < y.block ? -1 : x.block > y.block ? 1 : 0));
|
|
131
|
+
// group into drops by block gaps
|
|
132
|
+
const groups = [];
|
|
133
|
+
for (const l of logs) {
|
|
134
|
+
const g = groups[groups.length - 1];
|
|
135
|
+
if (g && l.block - g[g.length - 1].block <= GAP_BLOCKS)
|
|
136
|
+
g.push(l);
|
|
137
|
+
else
|
|
138
|
+
groups.push([l]);
|
|
139
|
+
}
|
|
140
|
+
for (const g of groups) {
|
|
141
|
+
const first = g[0].block, last = g[g.length - 1].block;
|
|
142
|
+
const id = `${d.address.slice(2, 10)}-${first}`;
|
|
143
|
+
if (index.drops.some((x) => x.id === id))
|
|
144
|
+
continue;
|
|
145
|
+
const blk = await pub.getBlock({ blockNumber: first });
|
|
146
|
+
const ts = Number(blk.timestamp);
|
|
147
|
+
// a payout is a batch: many transfers in one transaction. a transaction that pays one or two wallets is the
|
|
148
|
+
// distributor moving its own money (a prize, a refund, a top-up), not a dividend, and stays out of the ledger.
|
|
149
|
+
const perTx = {};
|
|
150
|
+
for (const l of g)
|
|
151
|
+
(perTx[l.tx] ??= new Set()).add(l.to);
|
|
152
|
+
const paid = g.filter((l) => perTx[l.tx].size >= 3);
|
|
153
|
+
const byTo = {};
|
|
154
|
+
for (const l of paid) {
|
|
155
|
+
if (l.value === 0n)
|
|
156
|
+
continue;
|
|
157
|
+
byTo[l.to] = (byTo[l.to] ?? 0n) + l.value;
|
|
158
|
+
}
|
|
159
|
+
const recips = Object.entries(byTo).filter(([, v]) => Number(formatUnits(v, tok.decimals)) >= 0.0001);
|
|
160
|
+
if (recips.length < 3)
|
|
161
|
+
continue; // three or fewer wallets is a transfer, not a drop
|
|
162
|
+
const total = recips.reduce((t, [, v]) => t + v, 0n);
|
|
163
|
+
const totalNum = Number(formatUnits(total, tok.decimals));
|
|
164
|
+
const price = await priceAt(d.token, ts, priceCache);
|
|
165
|
+
const drop = { id, distributor: d.address.toLowerCase(), name: d.name, token: tok.address, symbol: d.token, firstBlock: Number(first), lastBlock: Number(last), ts: new Date(ts * 1000).toISOString(), txs: new Set(paid.map((l) => l.tx)).size, recipients: recips.length, total: totalNum.toFixed(6), priceUsd: price, usd: price ? Number((totalNum * price).toFixed(2)) : null };
|
|
166
|
+
index.drops.push(drop);
|
|
167
|
+
newDrops++;
|
|
168
|
+
writeJson(join(dir, 'drops', `${id}.json`), recips.map(([to, v]) => [to, Number(formatUnits(v, tok.decimals)).toFixed(6)]));
|
|
169
|
+
for (const [to, v] of recips) {
|
|
170
|
+
const s = loadShard(shardOf(to));
|
|
171
|
+
(s[to] ??= []).push({ d: id, a: Number(formatUnits(v, tok.decimals)).toFixed(6) });
|
|
172
|
+
}
|
|
173
|
+
console.log(`[dividends] ${d.name}: drop ${drop.ts.slice(0, 16)} · ${totalNum.toFixed(2)} ${d.token} to ${recips.length} wallets${price ? ` · about $${(totalNum * price).toFixed(0)}` : ''}`);
|
|
174
|
+
}
|
|
175
|
+
state[key] = Number(head);
|
|
176
|
+
}
|
|
177
|
+
for (const [s, v] of Object.entries(walletShards))
|
|
178
|
+
writeJson(join(dir, 'wallets', `${s}.json`), v);
|
|
179
|
+
index.drops.sort((a, b) => a.ts.localeCompare(b.ts));
|
|
180
|
+
const cutoff = Date.now() - 30 * 86_400_000;
|
|
181
|
+
index.distributors = [];
|
|
182
|
+
for (const d of registry) {
|
|
183
|
+
const mine = index.drops.filter((x) => x.distributor === d.address.toLowerCase());
|
|
184
|
+
const uniq = new Set();
|
|
185
|
+
for (const x of mine) {
|
|
186
|
+
for (const [to] of readJson(join(dir, 'drops', `${x.id}.json`), []))
|
|
187
|
+
uniq.add(to);
|
|
188
|
+
}
|
|
189
|
+
const usd = mine.reduce((t, x) => t + (x.usd ?? 0), 0);
|
|
190
|
+
const usd30 = mine.filter((x) => new Date(x.ts).getTime() >= cutoff).reduce((t, x) => t + (x.usd ?? 0), 0);
|
|
191
|
+
const cap = await marketCap(d.memeToken);
|
|
192
|
+
index.distributors.push({ ...d, drops: mine.length, total: mine.reduce((t, x) => t + Number(x.total), 0).toFixed(4), usd: Number(usd.toFixed(2)), recipients: uniq.size, last: mine.at(-1)?.ts ?? null, last30dUsd: Number(usd30.toFixed(2)), marketCapUsd: cap, per100Usd30d: cap ? Number(((usd30 / cap) * 100).toFixed(4)) : null });
|
|
193
|
+
}
|
|
194
|
+
// every wallet ever paid, ranked: the leaderboard reads the top of it, a statement finds its own line for a rank
|
|
195
|
+
const priceOf = {};
|
|
196
|
+
for (const x of index.drops)
|
|
197
|
+
priceOf[x.id] = x.priceUsd;
|
|
198
|
+
const totals = {};
|
|
199
|
+
const tsOf = {};
|
|
200
|
+
for (const x of index.drops)
|
|
201
|
+
tsOf[x.id] = x.ts;
|
|
202
|
+
const shards = new Set();
|
|
203
|
+
for (const x of index.drops)
|
|
204
|
+
for (const [to] of readJson(join(dir, 'drops', `${x.id}.json`), []))
|
|
205
|
+
shards.add(shardOf(to));
|
|
206
|
+
for (const sh of shards) {
|
|
207
|
+
for (const [w, rows] of Object.entries(loadShard(sh))) {
|
|
208
|
+
for (const r of rows) {
|
|
209
|
+
const t = (totals[w] ??= { gme: 0, usd: 0, drops: 0, first: tsOf[r.d] ?? '', last: '' });
|
|
210
|
+
t.gme += Number(r.a);
|
|
211
|
+
t.usd += priceOf[r.d] ? Number(r.a) * priceOf[r.d] : 0;
|
|
212
|
+
t.drops++;
|
|
213
|
+
const ts = tsOf[r.d] ?? '';
|
|
214
|
+
if (ts && (!t.first || ts < t.first))
|
|
215
|
+
t.first = ts;
|
|
216
|
+
if (ts > t.last)
|
|
217
|
+
t.last = ts;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// in a row: counting back from each distributor's newest drop, how many paid this wallet without a miss
|
|
222
|
+
const paidIn = {};
|
|
223
|
+
for (const sh of shards)
|
|
224
|
+
for (const [w, rows] of Object.entries(loadShard(sh)))
|
|
225
|
+
paidIn[w] = new Set(rows.map((r) => r.d));
|
|
226
|
+
// a round is a distributor's drops on one utc day; paid in any drop of the day counts as paid in the round
|
|
227
|
+
const rounds = {};
|
|
228
|
+
for (const x of index.drops) {
|
|
229
|
+
const r = (rounds[x.distributor] ??= []);
|
|
230
|
+
const day = x.ts.slice(0, 10);
|
|
231
|
+
const last = r[r.length - 1];
|
|
232
|
+
if (last && last.day === day)
|
|
233
|
+
last.push(x.id);
|
|
234
|
+
else {
|
|
235
|
+
const g = [x.id];
|
|
236
|
+
g.day = day;
|
|
237
|
+
r.push(g);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const streakOf = (w) => Math.max(0, ...Object.values(rounds).map((rs) => { let n = 0; for (let i = rs.length - 1; i >= 0; i--) {
|
|
241
|
+
if (rs[i].some((id) => paidIn[w]?.has(id)))
|
|
242
|
+
n++;
|
|
243
|
+
else
|
|
244
|
+
break;
|
|
245
|
+
} return n; }));
|
|
246
|
+
const holders = Object.entries(totals).sort((a, b) => b[1].usd - a[1].usd || b[1].gme - a[1].gme).map(([w, t]) => [w, t.gme.toFixed(6), Number(t.usd.toFixed(2)), t.drops, t.first.slice(0, 10), t.last.slice(0, 10), streakOf(w)]);
|
|
247
|
+
writeJson(join(dir, 'holders.json'), { updatedAt: new Date().toISOString(), count: holders.length, rows: holders });
|
|
248
|
+
// the page loads the top of the book and a wallet's rank from small shards; the full ranking only on request
|
|
249
|
+
writeJson(join(dir, 'top.json'), { updatedAt: new Date().toISOString(), count: holders.length, rows: holders.slice(0, 500) });
|
|
250
|
+
mkdirSync(join(dir, 'ranks'), { recursive: true });
|
|
251
|
+
const rankShards = {};
|
|
252
|
+
holders.forEach((r, i) => { (rankShards[shardOf(String(r[0]))] ??= {})[String(r[0])] = i + 1; });
|
|
253
|
+
for (const [sh, m] of Object.entries(rankShards))
|
|
254
|
+
writeJson(join(dir, 'ranks', `${sh}.json`), m);
|
|
255
|
+
// and one ranked book per distributor, so a token's own page ranks its own holders
|
|
256
|
+
for (const d of registry) {
|
|
257
|
+
const dl = d.address.toLowerCase();
|
|
258
|
+
const mine = new Set(index.drops.filter((x) => x.distributor === dl).map((x) => x.id));
|
|
259
|
+
if (!mine.size)
|
|
260
|
+
continue;
|
|
261
|
+
const per = {};
|
|
262
|
+
for (const sh of shards)
|
|
263
|
+
for (const [w, rows] of Object.entries(loadShard(sh)))
|
|
264
|
+
for (const r of rows) {
|
|
265
|
+
if (!mine.has(r.d))
|
|
266
|
+
continue;
|
|
267
|
+
const t = (per[w] ??= { gme: 0, usd: 0, drops: 0, first: '', last: '' });
|
|
268
|
+
t.gme += Number(r.a);
|
|
269
|
+
t.usd += priceOf[r.d] ? Number(r.a) * priceOf[r.d] : 0;
|
|
270
|
+
t.drops++;
|
|
271
|
+
const ts = tsOf[r.d] ?? '';
|
|
272
|
+
if (ts && (!t.first || ts < t.first))
|
|
273
|
+
t.first = ts;
|
|
274
|
+
if (ts > t.last)
|
|
275
|
+
t.last = ts;
|
|
276
|
+
}
|
|
277
|
+
const rs = rounds[dl] ?? [];
|
|
278
|
+
const streak = (w) => { let n = 0; for (let i = rs.length - 1; i >= 0; i--) {
|
|
279
|
+
if (rs[i].some((id) => paidIn[w]?.has(id)))
|
|
280
|
+
n++;
|
|
281
|
+
else
|
|
282
|
+
break;
|
|
283
|
+
} return n; };
|
|
284
|
+
const rows = Object.entries(per).sort((a, b) => b[1].usd - a[1].usd || b[1].gme - a[1].gme).map(([w, t]) => [w, t.gme.toFixed(6), Number(t.usd.toFixed(2)), t.drops, t.first.slice(0, 10), t.last.slice(0, 10), streak(w)]);
|
|
285
|
+
writeJson(join(dir, `holders-${dl.slice(2, 10)}.json`), { updatedAt: new Date().toISOString(), distributor: dl, count: rows.length, rows });
|
|
286
|
+
}
|
|
287
|
+
index.distributors.sort((a, b) => b.usd - a.usd);
|
|
288
|
+
index.tokens = TOKENS;
|
|
289
|
+
index.updatedAt = new Date().toISOString();
|
|
290
|
+
writeJson(join(dir, 'index.json'), index);
|
|
291
|
+
writeJson(join(dir, 'state.json'), state);
|
|
292
|
+
console.log(`[dividends] ${index.drops.length} drops from ${registry.length} distributor(s), ${newDrops} new, through block ${head}`);
|
|
293
|
+
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clockwork-press",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "clockwork-press",
|
|
9
|
-
"version": "0.2.
|
|
9
|
+
"version": "0.2.3",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"viem": "2.56.3"
|
package/package.json
CHANGED
|
@@ -32,7 +32,7 @@ Docs: https://gmerald.xyz/clockwork/docs/
|
|
|
32
32
|
|
|
33
33
|
## The steps
|
|
34
34
|
1. **Write the config.** In a terminal with Node 20 or newer, in an empty folder:
|
|
35
|
-
`npx --yes clockwork-press@0.2.
|
|
35
|
+
`npx --yes clockwork-press@0.2.2 init <token address>`
|
|
36
36
|
It reads the launch from the pons factory and writes `clockwork.json`. Fill `wallets.machine`,
|
|
37
37
|
`wallets.treasury`, the `split`, and `slices.rule` (`{ "kind": "pace", "pace": "steady" }`). If they want
|
|
38
38
|
Telegram, set `telegram.mode` to `own` and `telegram.chatId` to their group id. Do not put a key or a token
|
|
@@ -48,12 +48,14 @@ Docs: https://gmerald.xyz/clockwork/docs/
|
|
|
48
48
|
the float, and `dry: would swap …`. The machine refuses to run if the key belongs to a different wallet
|
|
49
49
|
than `wallets.machine`; that is the guard working, not a bug.
|
|
50
50
|
5. **Point the fees at the machine.** First claim what is already owed to the current recipient
|
|
51
|
-
(`npx --yes clockwork-press@0.2.
|
|
51
|
+
(`npx --yes clockwork-press@0.2.2 claimcheck` from the folder with `clockwork.json` prints it), because a
|
|
52
52
|
recipient change does not move credited balances. Then the current recipient signs
|
|
53
53
|
`transferCreatorFeeRecipient(token, machineWallet)` on the pons factory
|
|
54
54
|
`0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e`. Prepare the calldata for them (function selector
|
|
55
55
|
`0x2931861b`, then the token address and the machine address each left-padded to 32 bytes) and tell them
|
|
56
|
-
to send it as a raw transaction from that wallet with value 0
|
|
56
|
+
to send it as a raw transaction from that wallet with value 0, or point them at
|
|
57
|
+
https://gmerald.xyz/clockwork/point/?token=<token>&machine=<machine>, which reads the chain, shows what is
|
|
58
|
+
owed, and prepares the same transaction for their own wallet. The pons UI does not show this function;
|
|
57
59
|
the contract has it and it takes effect immediately.
|
|
58
60
|
6. **Go live.** Set `claim.mode` to `auto` in `clockwork.json` if it is not already, commit, and let the
|
|
59
61
|
workflow's schedule run it on the quarter hour. Optional: a cron-job.org job that POSTs to
|
|
@@ -74,7 +76,7 @@ Docs: https://gmerald.xyz/clockwork/docs/
|
|
|
74
76
|
|
|
75
77
|
## Commands
|
|
76
78
|
`init <token>` write the config from the chain · `dry` a tick without signing · `press` the tick ·
|
|
77
|
-
`claimcheck` what the escrow holds and what is unswept · `doctor` RPC, wallet, gas, recipient, Telegram ·
|
|
79
|
+
`claimcheck` what the escrow holds and what is unswept · `dividends` (0.2.3) reads every payout the listed memestock distributors made to holders into a public book, no key, any folder, `DIVIDENDS_DIR` names where · `doctor` RPC, wallet, gas, recipient, Telegram ·
|
|
78
80
|
`handback <address>` claim what is credited, then move the fee recipient · `holders` the holder count.
|
|
79
81
|
|
|
80
82
|
## What ClockWorks costs
|