liquid-sdk 1.4.0 → 1.5.1

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.
@@ -0,0 +1,501 @@
1
+ # Skill: Index Liquid Protocol Tokens
2
+
3
+ You are an AI agent that indexes and tracks token deployments on Liquid Protocol. This skill teaches you how to discover tokens, build an index, track new launches in real-time, and query the full on-chain state of any Liquid token on Base.
4
+
5
+ ## What You Can Index
6
+
7
+ Every token deployed through Liquid Protocol emits a `TokenCreated` event with rich on-chain data:
8
+
9
+ - Token address, name, symbol, image URL
10
+ - Deployer address and admin address
11
+ - Metadata (description, social links, audit URLs)
12
+ - Context (originating interface, platform, social post ID)
13
+ - Uniswap V4 pool ID, hook contract, paired token
14
+ - LP locker address and MEV module
15
+ - Extensions (dev buy, vault, airdrop, etc.)
16
+ - Block number (for pagination/ordering)
17
+
18
+ All of this is queryable directly from Base mainnet — no backend, no API keys, no database required.
19
+
20
+ ## Prerequisites
21
+
22
+ ```bash
23
+ npm install liquid-sdk viem
24
+ ```
25
+
26
+ ## Setup
27
+
28
+ ```typescript
29
+ import { createPublicClient, http } from "viem";
30
+ import { base } from "viem/chains";
31
+ import { LiquidSDK } from "liquid-sdk";
32
+
33
+ // Read-only — no wallet needed for indexing
34
+ const publicClient = createPublicClient({ chain: base, transport: http(RPC_URL) });
35
+ const sdk = new LiquidSDK({ publicClient });
36
+ ```
37
+
38
+ ## Core Indexing Methods
39
+
40
+ ### 1. Get All Tokens
41
+
42
+ ```typescript
43
+ const allTokens = await sdk.getTokens();
44
+
45
+ console.log(`Total tokens: ${allTokens.length}`);
46
+ for (const token of allTokens) {
47
+ console.log(`${token.tokenName} (${token.tokenSymbol}) — ${token.tokenAddress}`);
48
+ console.log(` Deployed by: ${token.msgSender}`);
49
+ console.log(` Pool ID: ${token.poolId}`);
50
+ console.log(` Block: ${token.blockNumber}`);
51
+ }
52
+ ```
53
+
54
+ ### 2. Get Tokens by Deployer
55
+
56
+ ```typescript
57
+ // All tokens launched by a specific wallet
58
+ const myTokens = await sdk.getTokens({ deployer: "0x1234..." });
59
+
60
+ // Or use the convenience wrapper
61
+ const myTokens2 = await sdk.getDeployedTokens("0x1234...");
62
+ ```
63
+
64
+ Note: `msgSender` (deployer) is **not indexed** on-chain, so the SDK fetches all events and filters client-side. For large ranges, use block pagination to keep RPC calls manageable.
65
+
66
+ ### 3. Look Up a Single Token
67
+
68
+ ```typescript
69
+ // Fast O(1) lookup — tokenAddress IS indexed on-chain
70
+ const token = await sdk.getTokenEvent("0xTokenAddress...");
71
+
72
+ if (token) {
73
+ console.log(`${token.tokenName} (${token.tokenSymbol})`);
74
+ console.log(`Pool: ${token.poolId}`);
75
+ console.log(`Hook: ${token.poolHook}`);
76
+ console.log(`Locker: ${token.locker}`);
77
+ console.log(`MEV Module: ${token.mevModule}`);
78
+ console.log(`Extensions: ${token.extensions}`);
79
+ console.log(`Image: ${token.tokenImage}`);
80
+ } else {
81
+ console.log("Not a Liquid Protocol token");
82
+ }
83
+ ```
84
+
85
+ ### 4. Paginate with Block Ranges
86
+
87
+ ```typescript
88
+ // Page through tokens using block numbers
89
+ const BLOCK_SIZE = 100_000n;
90
+ let fromBlock = 20_000_000n; // start from factory deployment block
91
+ let allTokens: TokenCreatedEvent[] = [];
92
+
93
+ while (true) {
94
+ const toBlock = fromBlock + BLOCK_SIZE;
95
+ const page = await sdk.getTokens({ fromBlock, toBlock });
96
+
97
+ allTokens.push(...page);
98
+ console.log(`Fetched ${page.length} tokens from blocks ${fromBlock}–${toBlock}`);
99
+
100
+ if (page.length === 0) break; // no more tokens
101
+ fromBlock = toBlock + 1n;
102
+ }
103
+
104
+ console.log(`Total indexed: ${allTokens.length} tokens`);
105
+ ```
106
+
107
+ ### 5. Cursor-Based Pagination
108
+
109
+ ```typescript
110
+ // Use the last token's blockNumber as cursor for next page
111
+ let cursor = 0n;
112
+ const PAGE_SIZE = 50;
113
+
114
+ async function getNextPage() {
115
+ const tokens = await sdk.getTokens({ fromBlock: cursor + 1n });
116
+
117
+ if (tokens.length > 0) {
118
+ cursor = tokens[tokens.length - 1].blockNumber!;
119
+ }
120
+
121
+ return tokens;
122
+ }
123
+ ```
124
+
125
+ ## TokenCreatedEvent Schema
126
+
127
+ ```typescript
128
+ interface TokenCreatedEvent {
129
+ // Addresses
130
+ msgSender: Address; // Wallet that called deployToken()
131
+ tokenAddress: Address; // The deployed ERC-20 (indexed on-chain)
132
+ tokenAdmin: Address; // Can update image/metadata (indexed on-chain)
133
+
134
+ // Token Metadata
135
+ tokenName: string; // e.g., "My Token"
136
+ tokenSymbol: string; // e.g., "MTK"
137
+ tokenImage: string; // Image URL or empty string
138
+ tokenMetadata: string; // JSON string — parse with parseMetadata()
139
+ tokenContext: string; // JSON string — parse with parseContext()
140
+
141
+ // Pool Configuration
142
+ startingTick: number; // Initial Uniswap V4 tick (int24)
143
+ poolHook: Address; // Hook contract (static or dynamic fee)
144
+ poolId: Hex; // Uniswap V4 pool identifier (bytes32)
145
+ pairedToken: Address; // Quote token (usually WETH)
146
+
147
+ // Infrastructure
148
+ locker: Address; // LP locker contract
149
+ mevModule: Address; // MEV module (usually Sniper Auction V2)
150
+ extensionsSupply: bigint; // Total supply allocated to extensions (wei)
151
+ extensions: Address[]; // Active extension contracts
152
+
153
+ // Block Info
154
+ blockNumber?: bigint; // Block where event was emitted
155
+ }
156
+ ```
157
+
158
+ ## Parsing Metadata and Context
159
+
160
+ The `tokenMetadata` and `tokenContext` fields are JSON strings. Use the SDK's parsers:
161
+
162
+ ```typescript
163
+ import { parseMetadata, parseContext } from "liquid-sdk";
164
+
165
+ const token = await sdk.getTokenEvent(tokenAddress);
166
+
167
+ // Parse metadata
168
+ const meta = parseMetadata(token.tokenMetadata);
169
+ if (meta) {
170
+ console.log("Description:", meta.description);
171
+ console.log("Social links:", meta.socialMediaUrls); // [{ platform, url }]
172
+ console.log("Audit URLs:", meta.auditUrls);
173
+ }
174
+
175
+ // Parse context (deployment provenance)
176
+ const ctx = parseContext(token.tokenContext);
177
+ if (ctx) {
178
+ console.log("Interface:", ctx.interface); // "SDK", "Rainbow Wallet", etc.
179
+ console.log("Platform:", ctx.platform); // "Farcaster", "Twitter", etc.
180
+ console.log("Message ID:", ctx.messageId); // Social post ID
181
+ console.log("User ID:", ctx.id);
182
+ }
183
+ ```
184
+
185
+ **Context types:**
186
+ ```typescript
187
+ interface LiquidContext {
188
+ interface: string; // System that deployed (e.g., "SDK", "My App")
189
+ platform?: string; // Social platform
190
+ messageId?: string; // Social post/cast ID
191
+ id?: string; // User ID on platform
192
+ }
193
+
194
+ interface LiquidMetadata {
195
+ description?: string;
196
+ socialMediaUrls?: { platform: string; url: string }[];
197
+ auditUrls?: string[];
198
+ }
199
+ ```
200
+
201
+ ## Enriching Token Data
202
+
203
+ Once you have the token event, you can query additional on-chain state:
204
+
205
+ ### Full Token Info
206
+
207
+ ```typescript
208
+ const info = await sdk.getTokenInfo(tokenAddress);
209
+ console.log(`${info.name} (${info.symbol})`);
210
+ console.log(`Decimals: ${info.decimals}`); // always 18
211
+ console.log(`Supply: ${info.totalSupply}`); // 100 billion * 10^18
212
+ console.log(`Hook: ${info.deployment.hook}`);
213
+ console.log(`Locker: ${info.deployment.locker}`);
214
+ console.log(`Extensions: ${info.deployment.extensions}`);
215
+ ```
216
+
217
+ ### Reward Configuration
218
+
219
+ ```typescript
220
+ const rewards = await sdk.getTokenRewards(tokenAddress);
221
+ console.log("Recipients:", rewards.rewardRecipients);
222
+ console.log("Splits (bps):", rewards.rewardBps); // e.g., [7000, 3000]
223
+ console.log("Admins:", rewards.rewardAdmins);
224
+ console.log("Pool key:", rewards.poolKey);
225
+ console.log("Position ID:", rewards.positionId);
226
+ console.log("Num positions:", rewards.numPositions);
227
+ ```
228
+
229
+ ### Pool State
230
+
231
+ ```typescript
232
+ const poolConfig = await sdk.getPoolConfig(poolId);
233
+ console.log("Base fee:", poolConfig.baseFee);
234
+ console.log("Max LP fee:", poolConfig.maxLpFee);
235
+
236
+ const feeState = await sdk.getPoolFeeState(poolId);
237
+ console.log("Reference tick:", feeState.referenceTick);
238
+ console.log("Last swap:", feeState.lastSwapTimestamp);
239
+
240
+ const createdAt = await sdk.getPoolCreationTimestamp(poolId);
241
+ console.log("Pool created:", new Date(Number(createdAt) * 1000));
242
+
243
+ const isToken0 = await sdk.isLiquidToken0(poolId);
244
+ console.log("Liquid is token0:", isToken0);
245
+ ```
246
+
247
+ ### MEV / Auction State
248
+
249
+ ```typescript
250
+ const auction = await sdk.getAuctionState(poolId);
251
+ console.log("Auction round:", auction.round);
252
+ console.log("Current fee:", auction.currentFee);
253
+ console.log("Gas peg:", auction.gasPeg);
254
+
255
+ const unlockTime = await sdk.getPoolUnlockTime(poolId);
256
+ const now = BigInt(Math.floor(Date.now() / 1000));
257
+ console.log("Pool locked:", now < unlockTime);
258
+ ```
259
+
260
+ ### Fee & Reward Balances
261
+
262
+ ```typescript
263
+ // Check accrued fees
264
+ const fees = await sdk.getAvailableFees(feeOwner, tokenAddress);
265
+ const claimable = await sdk.getFeesToClaim(feeOwner, tokenAddress);
266
+ console.log("Total fees:", fees);
267
+ console.log("Claimable now:", claimable);
268
+ ```
269
+
270
+ ### Vault State (if token has vault extension)
271
+
272
+ ```typescript
273
+ const vault = await sdk.getVaultAllocation(tokenAddress);
274
+ console.log("Total locked:", vault.amountTotal);
275
+ console.log("Claimed:", vault.amountClaimed);
276
+ console.log("Lockup ends:", new Date(Number(vault.lockupEndTime) * 1000));
277
+ console.log("Vesting ends:", new Date(Number(vault.vestingEndTime) * 1000));
278
+ ```
279
+
280
+ ### Airdrop State (if token has airdrop extension)
281
+
282
+ ```typescript
283
+ const airdrop = await sdk.getAirdropInfo(tokenAddress);
284
+ console.log("Merkle root:", airdrop.merkleRoot);
285
+ console.log("Total supply:", airdrop.totalSupply);
286
+ console.log("Claimed:", airdrop.totalClaimed);
287
+ ```
288
+
289
+ ## Complete Example: Build a Token Index
290
+
291
+ ```typescript
292
+ import { createPublicClient, http, formatEther } from "viem";
293
+ import { base } from "viem/chains";
294
+ import { LiquidSDK, parseMetadata, parseContext, ADDRESSES } from "liquid-sdk";
295
+
296
+ async function buildIndex() {
297
+ const publicClient = createPublicClient({ chain: base, transport: http(RPC_URL) });
298
+ const sdk = new LiquidSDK({ publicClient });
299
+
300
+ // Fetch all tokens
301
+ const tokens = await sdk.getTokens();
302
+ console.log(`Indexing ${tokens.length} tokens...\n`);
303
+
304
+ const index = [];
305
+
306
+ for (const token of tokens) {
307
+ const meta = parseMetadata(token.tokenMetadata);
308
+ const ctx = parseContext(token.tokenContext);
309
+
310
+ const entry = {
311
+ address: token.tokenAddress,
312
+ name: token.tokenName,
313
+ symbol: token.tokenSymbol,
314
+ image: token.tokenImage,
315
+ description: meta?.description ?? null,
316
+ socialLinks: meta?.socialMediaUrls ?? [],
317
+ deployer: token.msgSender,
318
+ deployedAt: token.blockNumber,
319
+ poolId: token.poolId,
320
+ pairedToken: token.pairedToken,
321
+ hook: token.poolHook,
322
+ locker: token.locker,
323
+ mevModule: token.mevModule,
324
+ extensions: token.extensions,
325
+ launchedVia: ctx?.interface ?? "unknown",
326
+ platform: ctx?.platform ?? null,
327
+ castId: ctx?.messageId ?? null,
328
+ };
329
+
330
+ index.push(entry);
331
+ }
332
+
333
+ return index;
334
+ }
335
+
336
+ // Usage
337
+ const index = await buildIndex();
338
+ console.log(JSON.stringify(index, null, 2));
339
+ ```
340
+
341
+ ## Complete Example: Real-Time Token Monitor
342
+
343
+ ```typescript
344
+ import { createPublicClient, http } from "viem";
345
+ import { base } from "viem/chains";
346
+ import { LiquidSDK, parseContext, ADDRESSES } from "liquid-sdk";
347
+
348
+ async function monitorNewTokens(callback: (token: any) => void) {
349
+ const publicClient = createPublicClient({ chain: base, transport: http(RPC_URL) });
350
+ const sdk = new LiquidSDK({ publicClient });
351
+
352
+ let lastBlock = await publicClient.getBlockNumber();
353
+
354
+ console.log(`Monitoring from block ${lastBlock}...`);
355
+
356
+ setInterval(async () => {
357
+ try {
358
+ const currentBlock = await publicClient.getBlockNumber();
359
+ if (currentBlock <= lastBlock) return;
360
+
361
+ const newTokens = await sdk.getTokens({
362
+ fromBlock: lastBlock + 1n,
363
+ toBlock: currentBlock,
364
+ });
365
+
366
+ for (const token of newTokens) {
367
+ const ctx = parseContext(token.tokenContext);
368
+ console.log(`\nNew token: ${token.tokenName} (${token.tokenSymbol})`);
369
+ console.log(` Address: ${token.tokenAddress}`);
370
+ console.log(` Deployer: ${token.msgSender}`);
371
+ console.log(` Pool ID: ${token.poolId}`);
372
+ console.log(` Launched via: ${ctx?.interface ?? "unknown"}`);
373
+ callback(token);
374
+ }
375
+
376
+ lastBlock = currentBlock;
377
+ } catch (err) {
378
+ console.error("Poll error:", err);
379
+ }
380
+ }, 2000); // poll every 2 seconds (Base block time)
381
+ }
382
+
383
+ // Usage
384
+ monitorNewTokens((token) => {
385
+ // Process new token — save to DB, send alert, etc.
386
+ });
387
+ ```
388
+
389
+ ## Complete Example: Enrich a Token for Display
390
+
391
+ ```typescript
392
+ async function enrichToken(tokenAddress: `0x${string}`) {
393
+ const publicClient = createPublicClient({ chain: base, transport: http(RPC_URL) });
394
+ const sdk = new LiquidSDK({ publicClient });
395
+
396
+ // Parallel queries for speed
397
+ const [event, info, rewards] = await Promise.all([
398
+ sdk.getTokenEvent(tokenAddress),
399
+ sdk.getTokenInfo(tokenAddress),
400
+ sdk.getTokenRewards(tokenAddress),
401
+ ]);
402
+
403
+ if (!event) return null;
404
+
405
+ const meta = parseMetadata(event.tokenMetadata);
406
+
407
+ // Pool state (parallel)
408
+ const [poolConfig, feeState, createdAt, auctionState] = await Promise.all([
409
+ sdk.getPoolConfig(event.poolId),
410
+ sdk.getPoolFeeState(event.poolId),
411
+ sdk.getPoolCreationTimestamp(event.poolId),
412
+ sdk.getAuctionState(event.poolId),
413
+ ]);
414
+
415
+ return {
416
+ token: {
417
+ address: tokenAddress,
418
+ name: info.name,
419
+ symbol: info.symbol,
420
+ decimals: info.decimals,
421
+ totalSupply: info.totalSupply.toString(),
422
+ image: event.tokenImage,
423
+ description: meta?.description,
424
+ socialLinks: meta?.socialMediaUrls,
425
+ },
426
+ deployment: {
427
+ deployer: event.msgSender,
428
+ admin: event.tokenAdmin,
429
+ block: event.blockNumber,
430
+ hook: event.poolHook,
431
+ locker: event.locker,
432
+ extensions: event.extensions,
433
+ },
434
+ pool: {
435
+ id: event.poolId,
436
+ pairedToken: event.pairedToken,
437
+ baseFee: poolConfig.baseFee,
438
+ maxLpFee: poolConfig.maxLpFee,
439
+ createdAt: Number(createdAt),
440
+ referenceTick: feeState.referenceTick,
441
+ },
442
+ rewards: {
443
+ recipients: rewards.rewardRecipients,
444
+ splits: rewards.rewardBps,
445
+ numPositions: Number(rewards.numPositions),
446
+ },
447
+ auction: {
448
+ round: Number(auctionState.round),
449
+ currentFee: auctionState.currentFee,
450
+ gasPeg: auctionState.gasPeg.toString(),
451
+ },
452
+ };
453
+ }
454
+ ```
455
+
456
+ ## On-Chain Event Signature
457
+
458
+ The `TokenCreated` event emitted by the Liquid Factory:
459
+
460
+ ```solidity
461
+ event TokenCreated(
462
+ address msgSender, // NOT indexed — filtered client-side
463
+ address indexed tokenAddress, // indexed — efficient single lookup
464
+ address indexed tokenAdmin, // indexed — filter by admin
465
+ string tokenImage,
466
+ string tokenName,
467
+ string tokenSymbol,
468
+ string tokenMetadata,
469
+ string tokenContext,
470
+ int24 startingTick,
471
+ address poolHook,
472
+ bytes32 poolId,
473
+ address pairedToken,
474
+ address locker,
475
+ address mevModule,
476
+ uint256 extensionsSupply,
477
+ address[] extensions
478
+ );
479
+ ```
480
+
481
+ **Indexing implications:**
482
+ - `getTokenEvent(address)` is a single RPC call (uses indexed `tokenAddress`)
483
+ - `getTokens({ deployer })` requires fetching all events then filtering (msgSender not indexed)
484
+ - Block-range pagination is the primary scaling mechanism for large datasets
485
+
486
+ ## Performance Tips
487
+
488
+ 1. **Use `getTokenEvent()` for single lookups** — it's O(1) via the indexed field
489
+ 2. **Paginate with block ranges** for bulk indexing — avoid fetching the entire history in one call
490
+ 3. **Parallelize enrichment queries** with `Promise.all()` — pool config, rewards, auction state are all independent reads
491
+ 4. **Cache block numbers** to avoid re-indexing already-seen tokens
492
+ 5. **Base block time is ~2s** — poll at this interval for near-real-time monitoring
493
+
494
+ ## Contract Address
495
+
496
+ ```typescript
497
+ import { ADDRESSES } from "liquid-sdk";
498
+
499
+ ADDRESSES.FACTORY // 0x0000003482fe299E72d4908368044A8A173BE576
500
+ // All TokenCreated events are emitted from this address
501
+ ```