@hammerock/cca-sdk 0.1.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/LICENSE +21 -0
- package/README.md +145 -0
- package/dist/chunk-ABCX23D6.js +446 -0
- package/dist/chunk-ABCX23D6.js.map +1 -0
- package/dist/erc20-C3yff839.d.cts +231 -0
- package/dist/erc20-C3yff839.d.ts +231 -0
- package/dist/index.cjs +998 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2717 -0
- package/dist/index.d.ts +2717 -0
- package/dist/index.js +485 -0
- package/dist/index.js.map +1 -0
- package/dist/node.cjs +448 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +97 -0
- package/dist/node.d.ts +97 -0
- package/dist/node.js +277 -0
- package/dist/node.js.map +1 -0
- package/package.json +84 -0
package/dist/node.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { getAuctionInfo, scanAuctions, getTokenMetadata } from './chunk-ABCX23D6.js';
|
|
2
|
+
import { readFile, mkdir, writeFile } from 'fs/promises';
|
|
3
|
+
import { dirname } from 'path';
|
|
4
|
+
|
|
5
|
+
// src/enrich.ts
|
|
6
|
+
async function enrichStore(client, store, options = {}) {
|
|
7
|
+
const snapshot = await store.read();
|
|
8
|
+
if (!snapshot) {
|
|
9
|
+
return { enriched: 0, alreadyComplete: 0, failed: 0, total: 0 };
|
|
10
|
+
}
|
|
11
|
+
const needsWork = snapshot.auctions.filter(
|
|
12
|
+
(record) => !record.info || !record.tokenMeta || !record.currencyMeta
|
|
13
|
+
);
|
|
14
|
+
const alreadyComplete = snapshot.auctions.length - needsWork.length;
|
|
15
|
+
if (needsWork.length === 0) {
|
|
16
|
+
return { enriched: 0, alreadyComplete, failed: 0, total: snapshot.auctions.length };
|
|
17
|
+
}
|
|
18
|
+
options.onProgress?.(
|
|
19
|
+
`${needsWork.length} auction(s) need enrichment, ${alreadyComplete} already complete`
|
|
20
|
+
);
|
|
21
|
+
const metadataCache = /* @__PURE__ */ new Map();
|
|
22
|
+
const metadata = async (address) => {
|
|
23
|
+
const key = address.toLowerCase();
|
|
24
|
+
const cached = metadataCache.get(key);
|
|
25
|
+
if (cached) return cached;
|
|
26
|
+
const fetched = await getTokenMetadata(client, address);
|
|
27
|
+
metadataCache.set(key, fetched);
|
|
28
|
+
return fetched;
|
|
29
|
+
};
|
|
30
|
+
const concurrency = options.concurrency ?? 5;
|
|
31
|
+
const updated = [];
|
|
32
|
+
let failed = 0;
|
|
33
|
+
for (let i = 0; i < needsWork.length; i += concurrency) {
|
|
34
|
+
const batch = needsWork.slice(i, i + concurrency);
|
|
35
|
+
const settled = await Promise.all(
|
|
36
|
+
batch.map(async (record) => {
|
|
37
|
+
try {
|
|
38
|
+
const info = record.info ?? await getAuctionInfo(client, record.auction);
|
|
39
|
+
const [tokenMeta, currencyMeta] = await Promise.all([
|
|
40
|
+
record.tokenMeta ?? metadata(info.token),
|
|
41
|
+
record.currencyMeta ?? metadata(info.currency)
|
|
42
|
+
]);
|
|
43
|
+
return { ...record, info, tokenMeta, currencyMeta };
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
for (const record of settled) {
|
|
50
|
+
if (record) updated.push(record);
|
|
51
|
+
else failed++;
|
|
52
|
+
}
|
|
53
|
+
options.onProgress?.(
|
|
54
|
+
`enriched ${updated.length}/${needsWork.length}${failed > 0 ? ` (${failed} failed)` : ""}`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (updated.length > 0) {
|
|
58
|
+
await store.merge({
|
|
59
|
+
chainId: snapshot.chainId,
|
|
60
|
+
factoryAddress: snapshot.factoryAddress,
|
|
61
|
+
lastSyncedBlock: snapshot.lastSyncedBlock,
|
|
62
|
+
auctions: updated
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
enriched: updated.length,
|
|
67
|
+
alreadyComplete,
|
|
68
|
+
failed,
|
|
69
|
+
total: snapshot.auctions.length
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function serializeInfo(info) {
|
|
73
|
+
return {
|
|
74
|
+
auction: info.auction,
|
|
75
|
+
token: info.token,
|
|
76
|
+
currency: info.currency,
|
|
77
|
+
totalSupply: info.totalSupply.toString(),
|
|
78
|
+
startBlock: info.startBlock.toString(),
|
|
79
|
+
endBlock: info.endBlock.toString(),
|
|
80
|
+
floorPriceQ96: info.floorPriceQ96.toString(),
|
|
81
|
+
tickSpacing: info.tickSpacing.toString()
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function deserializeInfo(raw) {
|
|
85
|
+
return {
|
|
86
|
+
auction: raw.auction,
|
|
87
|
+
token: raw.token,
|
|
88
|
+
currency: raw.currency,
|
|
89
|
+
totalSupply: BigInt(raw.totalSupply),
|
|
90
|
+
startBlock: BigInt(raw.startBlock),
|
|
91
|
+
endBlock: BigInt(raw.endBlock),
|
|
92
|
+
floorPriceQ96: BigInt(raw.floorPriceQ96),
|
|
93
|
+
tickSpacing: BigInt(raw.tickSpacing)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function serializeRecord(record) {
|
|
97
|
+
return {
|
|
98
|
+
auction: record.auction,
|
|
99
|
+
token: record.token,
|
|
100
|
+
amount: record.amount.toString(),
|
|
101
|
+
configData: record.configData,
|
|
102
|
+
blockNumber: record.blockNumber.toString(),
|
|
103
|
+
transactionHash: record.transactionHash,
|
|
104
|
+
logIndex: record.logIndex,
|
|
105
|
+
...record.info ? { info: serializeInfo(record.info) } : {},
|
|
106
|
+
...record.tokenMeta ? { tokenMeta: record.tokenMeta } : {},
|
|
107
|
+
...record.currencyMeta ? { currencyMeta: record.currencyMeta } : {}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function deserializeRecord(raw) {
|
|
111
|
+
return {
|
|
112
|
+
auction: raw.auction,
|
|
113
|
+
token: raw.token,
|
|
114
|
+
amount: BigInt(raw.amount),
|
|
115
|
+
configData: raw.configData,
|
|
116
|
+
blockNumber: BigInt(raw.blockNumber),
|
|
117
|
+
transactionHash: raw.transactionHash,
|
|
118
|
+
logIndex: raw.logIndex,
|
|
119
|
+
info: raw.info ? deserializeInfo(raw.info) : void 0,
|
|
120
|
+
tokenMeta: raw.tokenMeta,
|
|
121
|
+
currencyMeta: raw.currencyMeta
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function sortRecords(records) {
|
|
125
|
+
return records.sort(
|
|
126
|
+
(a, b) => a.blockNumber === b.blockNumber ? a.logIndex - b.logIndex : a.blockNumber < b.blockNumber ? -1 : 1
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
function mergeRecords(existing, incoming) {
|
|
130
|
+
const byAddress = /* @__PURE__ */ new Map();
|
|
131
|
+
for (const record of existing) byAddress.set(record.auction.toLowerCase(), record);
|
|
132
|
+
let added = 0;
|
|
133
|
+
for (const record of incoming) {
|
|
134
|
+
const key = record.auction.toLowerCase();
|
|
135
|
+
const previous = byAddress.get(key);
|
|
136
|
+
if (!previous) added++;
|
|
137
|
+
byAddress.set(key, {
|
|
138
|
+
...previous,
|
|
139
|
+
...record,
|
|
140
|
+
info: record.info ?? previous?.info,
|
|
141
|
+
tokenMeta: record.tokenMeta ?? previous?.tokenMeta,
|
|
142
|
+
currencyMeta: record.currencyMeta ?? previous?.currencyMeta
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return { merged: sortRecords([...byAddress.values()]), added };
|
|
146
|
+
}
|
|
147
|
+
var JsonAuctionStore = class {
|
|
148
|
+
constructor(filePath) {
|
|
149
|
+
this.filePath = filePath;
|
|
150
|
+
}
|
|
151
|
+
filePath;
|
|
152
|
+
async read() {
|
|
153
|
+
let raw;
|
|
154
|
+
try {
|
|
155
|
+
raw = await readFile(this.filePath, "utf8");
|
|
156
|
+
} catch {
|
|
157
|
+
return void 0;
|
|
158
|
+
}
|
|
159
|
+
const parsed = JSON.parse(raw);
|
|
160
|
+
return {
|
|
161
|
+
chainId: parsed.chainId,
|
|
162
|
+
factoryAddress: parsed.factoryAddress,
|
|
163
|
+
lastSyncedBlock: BigInt(parsed.lastSyncedBlock),
|
|
164
|
+
auctions: sortRecords(parsed.auctions.map(deserializeRecord))
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async merge(update) {
|
|
168
|
+
const existing = await this.read();
|
|
169
|
+
if (existing && (existing.chainId !== update.chainId || existing.factoryAddress.toLowerCase() !== update.factoryAddress.toLowerCase())) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Auction store at ${this.filePath} holds chain ${existing.chainId}/${existing.factoryAddress}, refusing to merge chain ${update.chainId}/${update.factoryAddress}.`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const { merged, added } = mergeRecords(existing?.auctions ?? [], update.auctions);
|
|
175
|
+
const watermark = existing && existing.lastSyncedBlock > update.lastSyncedBlock ? existing.lastSyncedBlock : update.lastSyncedBlock;
|
|
176
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
177
|
+
await writeFile(
|
|
178
|
+
this.filePath,
|
|
179
|
+
JSON.stringify(
|
|
180
|
+
{
|
|
181
|
+
chainId: update.chainId,
|
|
182
|
+
factoryAddress: update.factoryAddress,
|
|
183
|
+
lastSyncedBlock: watermark.toString(),
|
|
184
|
+
auctions: merged.map(serializeRecord)
|
|
185
|
+
},
|
|
186
|
+
null,
|
|
187
|
+
2
|
|
188
|
+
) + "\n",
|
|
189
|
+
"utf8"
|
|
190
|
+
);
|
|
191
|
+
return { added, total: merged.length };
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
var MemoryAuctionStore = class {
|
|
195
|
+
snapshot;
|
|
196
|
+
async read() {
|
|
197
|
+
return this.snapshot;
|
|
198
|
+
}
|
|
199
|
+
async merge(update) {
|
|
200
|
+
const { merged, added } = mergeRecords(this.snapshot?.auctions ?? [], update.auctions);
|
|
201
|
+
const watermark = this.snapshot && this.snapshot.lastSyncedBlock > update.lastSyncedBlock ? this.snapshot.lastSyncedBlock : update.lastSyncedBlock;
|
|
202
|
+
this.snapshot = {
|
|
203
|
+
chainId: update.chainId,
|
|
204
|
+
factoryAddress: update.factoryAddress,
|
|
205
|
+
lastSyncedBlock: watermark,
|
|
206
|
+
auctions: merged
|
|
207
|
+
};
|
|
208
|
+
return { added, total: merged.length };
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// src/sync.ts
|
|
213
|
+
async function syncAuctions(client, store, options = {}) {
|
|
214
|
+
const head = await client.publicClient.getBlockNumber();
|
|
215
|
+
const existing = await store.read();
|
|
216
|
+
const passes = Math.max(1, options.passes ?? 1);
|
|
217
|
+
const from = options.full || !existing ? client.factoryDeployBlock : (
|
|
218
|
+
// Re-scan the last block already covered, in case it was mid-block.
|
|
219
|
+
existing.lastSyncedBlock
|
|
220
|
+
);
|
|
221
|
+
if (from > head) {
|
|
222
|
+
return {
|
|
223
|
+
added: 0,
|
|
224
|
+
total: existing?.auctions.length ?? 0,
|
|
225
|
+
scannedFrom: from,
|
|
226
|
+
scannedTo: head,
|
|
227
|
+
anomalies: 0,
|
|
228
|
+
passes: 0
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
let anomalies = 0;
|
|
232
|
+
const union = /* @__PURE__ */ new Map();
|
|
233
|
+
for (let pass = 1; pass <= passes; pass++) {
|
|
234
|
+
options.onProgress?.(`pass ${pass}/${passes}: scanning ${from} -> ${head}`);
|
|
235
|
+
const found = await scanAuctions(client, {
|
|
236
|
+
fromBlock: from,
|
|
237
|
+
toBlock: head,
|
|
238
|
+
chunkSize: options.chunkSize,
|
|
239
|
+
onAnomaly: () => {
|
|
240
|
+
anomalies++;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
let newThisPass = 0;
|
|
244
|
+
for (const auction of found) {
|
|
245
|
+
const key = auction.auction.toLowerCase();
|
|
246
|
+
if (!union.has(key)) newThisPass++;
|
|
247
|
+
union.set(key, auction);
|
|
248
|
+
}
|
|
249
|
+
options.onProgress?.(
|
|
250
|
+
`pass ${pass}/${passes}: ${found.length} found, ${newThisPass} new (union ${union.size})`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
const { added, total } = await store.merge({
|
|
254
|
+
chainId: client.chainId,
|
|
255
|
+
factoryAddress: client.factoryAddress,
|
|
256
|
+
lastSyncedBlock: head,
|
|
257
|
+
auctions: [...union.values()]
|
|
258
|
+
});
|
|
259
|
+
return { added, total, scannedFrom: from, scannedTo: head, anomalies, passes };
|
|
260
|
+
}
|
|
261
|
+
async function loadAuctions(client, store, options = {}) {
|
|
262
|
+
if (store) {
|
|
263
|
+
const snapshot = await store.read();
|
|
264
|
+
if (snapshot && snapshot.auctions.length > 0) {
|
|
265
|
+
return { auctions: snapshot.auctions, source: "store" };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (options.fallbackToScan) {
|
|
269
|
+
const auctions = await scanAuctions(client);
|
|
270
|
+
return { auctions, source: "scan" };
|
|
271
|
+
}
|
|
272
|
+
return { auctions: [], source: "empty" };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export { JsonAuctionStore, MemoryAuctionStore, enrichStore, loadAuctions, syncAuctions };
|
|
276
|
+
//# sourceMappingURL=node.js.map
|
|
277
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/enrich.ts","../src/store.ts","../src/sync.ts"],"names":[],"mappings":"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","file":"node.js","sourcesContent":["/**\n * Fill in the immutable detail the directory needs, once, and cache it.\n *\n * An auction's configuration (start/end block, token, currency, supply, floor\n * price) and its token/currency metadata (symbol, decimals) can never change.\n * Reading them on every page load would be hundreds of wasted RPC calls, so\n * they are fetched once here and persisted by the store.\n */\nimport { getTokenMetadata } from \"./erc20\";\nimport { getAuctionInfo } from \"./read\";\nimport type { BeaconClient } from \"./client\";\nimport type { TokenMetadata } from \"./erc20\";\nimport type { AuctionRecord, AuctionStore } from \"./store\";\nimport type { Address } from \"./types\";\n\nexport interface EnrichResult {\n enriched: number;\n alreadyComplete: number;\n failed: number;\n total: number;\n}\n\nexport async function enrichStore(\n client: BeaconClient,\n store: AuctionStore,\n options: { concurrency?: number; onProgress?: (message: string) => void } = {},\n): Promise<EnrichResult> {\n const snapshot = await store.read();\n if (!snapshot) {\n return { enriched: 0, alreadyComplete: 0, failed: 0, total: 0 };\n }\n\n const needsWork = snapshot.auctions.filter(\n (record) => !record.info || !record.tokenMeta || !record.currencyMeta,\n );\n const alreadyComplete = snapshot.auctions.length - needsWork.length;\n\n if (needsWork.length === 0) {\n return { enriched: 0, alreadyComplete, failed: 0, total: snapshot.auctions.length };\n }\n\n options.onProgress?.(\n `${needsWork.length} auction(s) need enrichment, ${alreadyComplete} already complete`,\n );\n\n // Token metadata repeats across auctions, so resolve each address once.\n const metadataCache = new Map<string, TokenMetadata>();\n const metadata = async (address: Address): Promise<TokenMetadata> => {\n const key = address.toLowerCase();\n const cached = metadataCache.get(key);\n if (cached) return cached;\n const fetched = await getTokenMetadata(client, address);\n metadataCache.set(key, fetched);\n return fetched;\n };\n\n const concurrency = options.concurrency ?? 5;\n const updated: AuctionRecord[] = [];\n let failed = 0;\n\n for (let i = 0; i < needsWork.length; i += concurrency) {\n const batch = needsWork.slice(i, i + concurrency);\n const settled = await Promise.all(\n batch.map(async (record): Promise<AuctionRecord | null> => {\n try {\n const info = record.info ?? (await getAuctionInfo(client, record.auction));\n const [tokenMeta, currencyMeta] = await Promise.all([\n record.tokenMeta ?? metadata(info.token),\n record.currencyMeta ?? metadata(info.currency),\n ]);\n return { ...record, info, tokenMeta, currencyMeta };\n } catch {\n return null;\n }\n }),\n );\n\n for (const record of settled) {\n if (record) updated.push(record);\n else failed++;\n }\n\n options.onProgress?.(\n `enriched ${updated.length}/${needsWork.length}${failed > 0 ? ` (${failed} failed)` : \"\"}`,\n );\n }\n\n if (updated.length > 0) {\n await store.merge({\n chainId: snapshot.chainId,\n factoryAddress: snapshot.factoryAddress,\n lastSyncedBlock: snapshot.lastSyncedBlock,\n auctions: updated,\n });\n }\n\n return {\n enriched: updated.length,\n alreadyComplete,\n failed,\n total: snapshot.auctions.length,\n };\n}\n","/**\n * A durable record of discovered auctions, plus their immutable configuration.\n *\n * Why this exists: discovery and state reads have incompatible RPC needs.\n *\n * - `eth_getLogs` over 1.5M blocks needs a provider that allows wide ranges.\n * Alchemy's free tier caps `eth_getLogs` at a TEN block range, which would be\n * ~153,000 requests for a full backfill.\n * - Public wide-range endpoints allow the range but are unreliable: an\n * identical query on ethereum-sepolia-rpc.publicnode.com returns the real\n * logs about half the time and an empty array the rest, silently. Four scans\n * of the same range returned 52, 60, 44 and 55 auctions.\n *\n * So the auction list is built up over repeated scans and persisted. The\n * directory then reads a complete list instantly instead of re-scanning the\n * chain (and re-rolling that dice) on every page load.\n *\n * The store also caches each auction's IMMUTABLE data - start/end block, token,\n * currency, supply, floor price, and the token/currency symbol and decimals.\n * None of that can change, and caching it means the directory can render status\n * badges and currency labels for every auction with zero RPC calls. Only live\n * state (clearing price, amount raised, graduated) is read from chain.\n *\n * Storage is a JSON file. For the ~130 auctions that exist this is simpler than\n * SQLite and needs no native module, which matters on Windows where pnpm blocks\n * build scripts by default. `AuctionStore` is an interface so a SQLite or\n * Postgres implementation can replace it without touching callers.\n */\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { Address } from \"viem\";\nimport type { TokenMetadata } from \"./erc20\";\nimport type { AuctionInfo, DiscoveredAuction } from \"./types\";\n\n/** A discovered auction plus whatever immutable detail has been cached for it. */\nexport interface AuctionRecord extends DiscoveredAuction {\n info?: AuctionInfo;\n tokenMeta?: TokenMetadata;\n currencyMeta?: TokenMetadata;\n}\n\nexport interface AuctionStoreSnapshot {\n chainId: number;\n factoryAddress: Address;\n /** Highest block that has been scanned. Discovery resumes from here. */\n lastSyncedBlock: bigint;\n auctions: AuctionRecord[];\n}\n\nexport interface StoreUpdate {\n chainId: number;\n factoryAddress: Address;\n lastSyncedBlock: bigint;\n auctions: readonly AuctionRecord[];\n}\n\nexport interface AuctionStore {\n read(): Promise<AuctionStoreSnapshot | undefined>;\n /** Merge new auctions in and advance the sync watermark. */\n merge(update: StoreUpdate): Promise<{ added: number; total: number }>;\n}\n\n// --- serialization ---------------------------------------------------------\n// bigints are not JSON, so every numeric field crosses as a decimal string.\n\ntype Serialized = Record<string, unknown>;\n\nfunction serializeInfo(info: AuctionInfo): Serialized {\n return {\n auction: info.auction,\n token: info.token,\n currency: info.currency,\n totalSupply: info.totalSupply.toString(),\n startBlock: info.startBlock.toString(),\n endBlock: info.endBlock.toString(),\n floorPriceQ96: info.floorPriceQ96.toString(),\n tickSpacing: info.tickSpacing.toString(),\n };\n}\n\nfunction deserializeInfo(raw: Serialized): AuctionInfo {\n return {\n auction: raw.auction as Address,\n token: raw.token as Address,\n currency: raw.currency as Address,\n totalSupply: BigInt(raw.totalSupply as string),\n startBlock: BigInt(raw.startBlock as string),\n endBlock: BigInt(raw.endBlock as string),\n floorPriceQ96: BigInt(raw.floorPriceQ96 as string),\n tickSpacing: BigInt(raw.tickSpacing as string),\n };\n}\n\nfunction serializeRecord(record: AuctionRecord): Serialized {\n return {\n auction: record.auction,\n token: record.token,\n amount: record.amount.toString(),\n configData: record.configData,\n blockNumber: record.blockNumber.toString(),\n transactionHash: record.transactionHash,\n logIndex: record.logIndex,\n ...(record.info ? { info: serializeInfo(record.info) } : {}),\n ...(record.tokenMeta ? { tokenMeta: record.tokenMeta } : {}),\n ...(record.currencyMeta ? { currencyMeta: record.currencyMeta } : {}),\n };\n}\n\nfunction deserializeRecord(raw: Serialized): AuctionRecord {\n return {\n auction: raw.auction as Address,\n token: raw.token as Address,\n amount: BigInt(raw.amount as string),\n configData: raw.configData as `0x${string}`,\n blockNumber: BigInt(raw.blockNumber as string),\n transactionHash: raw.transactionHash as `0x${string}`,\n logIndex: raw.logIndex as number,\n info: raw.info ? deserializeInfo(raw.info as Serialized) : undefined,\n tokenMeta: raw.tokenMeta as TokenMetadata | undefined,\n currencyMeta: raw.currencyMeta as TokenMetadata | undefined,\n };\n}\n\nfunction sortRecords(records: AuctionRecord[]): AuctionRecord[] {\n return records.sort((a, b) =>\n a.blockNumber === b.blockNumber\n ? a.logIndex - b.logIndex\n : a.blockNumber < b.blockNumber\n ? -1\n : 1,\n );\n}\n\n/**\n * Merge incoming records over existing ones without losing cached detail.\n * A plain discovery re-scan carries no `info`, and must not wipe it.\n */\nfunction mergeRecords(\n existing: readonly AuctionRecord[],\n incoming: readonly AuctionRecord[],\n): { merged: AuctionRecord[]; added: number } {\n const byAddress = new Map<string, AuctionRecord>();\n for (const record of existing) byAddress.set(record.auction.toLowerCase(), record);\n\n let added = 0;\n for (const record of incoming) {\n const key = record.auction.toLowerCase();\n const previous = byAddress.get(key);\n if (!previous) added++;\n byAddress.set(key, {\n ...previous,\n ...record,\n info: record.info ?? previous?.info,\n tokenMeta: record.tokenMeta ?? previous?.tokenMeta,\n currencyMeta: record.currencyMeta ?? previous?.currencyMeta,\n });\n }\n\n return { merged: sortRecords([...byAddress.values()]), added };\n}\n\n/** JSON-file backed store. */\nexport class JsonAuctionStore implements AuctionStore {\n constructor(private readonly filePath: string) {}\n\n async read(): Promise<AuctionStoreSnapshot | undefined> {\n let raw: string;\n try {\n raw = await readFile(this.filePath, \"utf8\");\n } catch {\n return undefined;\n }\n\n const parsed = JSON.parse(raw) as {\n chainId: number;\n factoryAddress: Address;\n lastSyncedBlock: string;\n auctions: Serialized[];\n };\n\n return {\n chainId: parsed.chainId,\n factoryAddress: parsed.factoryAddress,\n lastSyncedBlock: BigInt(parsed.lastSyncedBlock),\n auctions: sortRecords(parsed.auctions.map(deserializeRecord)),\n };\n }\n\n async merge(update: StoreUpdate): Promise<{ added: number; total: number }> {\n const existing = await this.read();\n\n // A store built against a different chain or factory is not ours to merge.\n if (\n existing &&\n (existing.chainId !== update.chainId ||\n existing.factoryAddress.toLowerCase() !== update.factoryAddress.toLowerCase())\n ) {\n throw new Error(\n `Auction store at ${this.filePath} holds chain ${existing.chainId}/${existing.factoryAddress}, ` +\n `refusing to merge chain ${update.chainId}/${update.factoryAddress}.`,\n );\n }\n\n const { merged, added } = mergeRecords(existing?.auctions ?? [], update.auctions);\n\n // Never move the watermark backwards - a partial re-scan must not cause a\n // later sync to skip blocks it has not actually covered.\n const watermark =\n existing && existing.lastSyncedBlock > update.lastSyncedBlock\n ? existing.lastSyncedBlock\n : update.lastSyncedBlock;\n\n await mkdir(dirname(this.filePath), { recursive: true });\n await writeFile(\n this.filePath,\n JSON.stringify(\n {\n chainId: update.chainId,\n factoryAddress: update.factoryAddress,\n lastSyncedBlock: watermark.toString(),\n auctions: merged.map(serializeRecord),\n },\n null,\n 2,\n ) + \"\\n\",\n \"utf8\",\n );\n\n return { added, total: merged.length };\n }\n}\n\n/** In-memory store, for tests. */\nexport class MemoryAuctionStore implements AuctionStore {\n private snapshot: AuctionStoreSnapshot | undefined;\n\n async read(): Promise<AuctionStoreSnapshot | undefined> {\n return this.snapshot;\n }\n\n async merge(update: StoreUpdate): Promise<{ added: number; total: number }> {\n const { merged, added } = mergeRecords(this.snapshot?.auctions ?? [], update.auctions);\n const watermark =\n this.snapshot && this.snapshot.lastSyncedBlock > update.lastSyncedBlock\n ? this.snapshot.lastSyncedBlock\n : update.lastSyncedBlock;\n\n this.snapshot = {\n chainId: update.chainId,\n factoryAddress: update.factoryAddress,\n lastSyncedBlock: watermark,\n auctions: merged,\n };\n return { added, total: merged.length };\n }\n}\n","/**\n * Keeping the auction store up to date.\n *\n * `syncAuctions` scans forward from the store's watermark and merges what it\n * finds. Because the store merges rather than replaces, running it repeatedly\n * against an unreliable endpoint converges on the true set: each pass can only\n * add auctions, never lose ones already recorded.\n */\nimport { scanAuctions } from \"./discover\";\nimport type { BeaconClient } from \"./client\";\nimport type { AuctionStore } from \"./store\";\nimport type { DiscoveredAuction } from \"./types\";\n\nexport interface SyncOptions {\n /**\n * Re-scan from the factory deploy block rather than the store's watermark.\n * Use when converging a list built on an unreliable RPC.\n */\n full?: boolean;\n /** Blocks per getLogs request. */\n chunkSize?: bigint;\n /** How many times to re-scan the range in one call, unioning results. */\n passes?: number;\n onProgress?: (message: string) => void;\n}\n\nexport interface SyncResult {\n added: number;\n total: number;\n scannedFrom: bigint;\n scannedTo: bigint;\n anomalies: number;\n passes: number;\n}\n\nexport async function syncAuctions(\n client: BeaconClient,\n store: AuctionStore,\n options: SyncOptions = {},\n): Promise<SyncResult> {\n const head = await client.publicClient.getBlockNumber();\n const existing = await store.read();\n const passes = Math.max(1, options.passes ?? 1);\n\n const from =\n options.full || !existing\n ? client.factoryDeployBlock\n : // Re-scan the last block already covered, in case it was mid-block.\n existing.lastSyncedBlock;\n\n if (from > head) {\n return {\n added: 0,\n total: existing?.auctions.length ?? 0,\n scannedFrom: from,\n scannedTo: head,\n anomalies: 0,\n passes: 0,\n };\n }\n\n let anomalies = 0;\n const union = new Map<string, DiscoveredAuction>();\n\n for (let pass = 1; pass <= passes; pass++) {\n options.onProgress?.(`pass ${pass}/${passes}: scanning ${from} -> ${head}`);\n\n const found = await scanAuctions(client, {\n fromBlock: from,\n toBlock: head,\n chunkSize: options.chunkSize,\n onAnomaly: () => {\n anomalies++;\n },\n });\n\n let newThisPass = 0;\n for (const auction of found) {\n const key = auction.auction.toLowerCase();\n if (!union.has(key)) newThisPass++;\n union.set(key, auction);\n }\n\n options.onProgress?.(\n `pass ${pass}/${passes}: ${found.length} found, ${newThisPass} new (union ${union.size})`,\n );\n }\n\n const { added, total } = await store.merge({\n chainId: client.chainId,\n factoryAddress: client.factoryAddress,\n lastSyncedBlock: head,\n auctions: [...union.values()],\n });\n\n return { added, total, scannedFrom: from, scannedTo: head, anomalies, passes };\n}\n\n/**\n * Read the auction list, preferring the store and falling back to a live scan.\n *\n * This is what the directory uses. A cached list is correct because auctions are\n * append-only; only very recently created auctions could be missing, which the\n * next sync picks up.\n */\nexport async function loadAuctions(\n client: BeaconClient,\n store: AuctionStore | undefined,\n options: { fallbackToScan?: boolean } = {},\n): Promise<{ auctions: DiscoveredAuction[]; source: \"store\" | \"scan\" | \"empty\" }> {\n if (store) {\n const snapshot = await store.read();\n if (snapshot && snapshot.auctions.length > 0) {\n return { auctions: snapshot.auctions, source: \"store\" };\n }\n }\n\n if (options.fallbackToScan) {\n const auctions = await scanAuctions(client);\n return { auctions, source: \"scan\" };\n }\n\n return { auctions: [], source: \"empty\" };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hammerock/cca-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for Uniswap Continuous Clearing Auctions (CCA): discovery, live state, Q96 math and bid construction. Testnet only.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"uniswap",
|
|
7
|
+
"cca",
|
|
8
|
+
"continuous-clearing-auction",
|
|
9
|
+
"token-launch",
|
|
10
|
+
"auction",
|
|
11
|
+
"viem",
|
|
12
|
+
"ethereum",
|
|
13
|
+
"sdk"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "Shubham Singh Shoora",
|
|
17
|
+
"homepage": "https://github.com/Shubham-Singh-Shoora/Beacon-/tree/master/packages/sdk#readme",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Shubham-Singh-Shoora/Beacon-.git",
|
|
21
|
+
"directory": "packages/sdk"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/Shubham-Singh-Shoora/Beacon-/issues"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"main": "./dist/index.cjs",
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"require": {
|
|
37
|
+
"types": "./dist/index.d.cts",
|
|
38
|
+
"default": "./dist/index.cjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"./node": {
|
|
42
|
+
"import": {
|
|
43
|
+
"types": "./dist/node.d.ts",
|
|
44
|
+
"default": "./dist/node.js"
|
|
45
|
+
},
|
|
46
|
+
"require": {
|
|
47
|
+
"types": "./dist/node.d.cts",
|
|
48
|
+
"default": "./dist/node.cjs"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"files": [
|
|
56
|
+
"dist",
|
|
57
|
+
"README.md",
|
|
58
|
+
"LICENSE"
|
|
59
|
+
],
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=18"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"viem": "^2.40.0"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"dotenv": "^17.2.3",
|
|
68
|
+
"tsup": "^8.5.0",
|
|
69
|
+
"tsx": "^4.20.6",
|
|
70
|
+
"typescript": "^5.9.3",
|
|
71
|
+
"viem": "^2.40.0",
|
|
72
|
+
"vitest": "^3.2.4"
|
|
73
|
+
},
|
|
74
|
+
"scripts": {
|
|
75
|
+
"build": "tsup",
|
|
76
|
+
"typecheck": "tsc --noEmit",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"test:watch": "vitest",
|
|
79
|
+
"check:live": "tsx test/live-check.ts",
|
|
80
|
+
"sync": "tsx test/sync-cli.ts",
|
|
81
|
+
"deploy:demo": "tsx scripts/deploy-demo-auction.ts"
|
|
82
|
+
},
|
|
83
|
+
"module": "./dist/index.js"
|
|
84
|
+
}
|