liquid-sdk 1.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liquid-sdk",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "TypeScript SDK to deploy ERC-20 tokens with Uniswap V4 liquidity on Base — zero API keys, one dependency (viem)",
5
5
  "author": "Liquid Protocol",
6
6
  "homepage": "https://github.com/craigbots/liquid-sdk#readme",
@@ -19,6 +19,7 @@
19
19
  },
20
20
  "files": [
21
21
  "dist",
22
+ "skills",
22
23
  "README.md",
23
24
  "AGENT_README.md",
24
25
  "CHANGELOG.md",
@@ -66,6 +67,8 @@
66
67
  "web3",
67
68
  "smart-contract",
68
69
  "liquidity",
69
- "mev-protection"
70
+ "mev-protection",
71
+ "ai-agent",
72
+ "llm-skills"
70
73
  ]
71
74
  }
@@ -0,0 +1,51 @@
1
+ # Liquid SDK — Agent Skills
2
+
3
+ Drop these files into your AI agent's context to give it the ability to interact with Liquid Protocol on Base.
4
+
5
+ ## Available Skills
6
+
7
+ | Skill | File | Description |
8
+ |-------|------|-------------|
9
+ | **Deploy Token** | [`deploy-token.md`](./deploy-token.md) | Deploy ERC-20 tokens with Uniswap V4 liquidity, custom fees, dev buys, and reward splits |
10
+ | **Bid in Auction** | [`bid-in-auction.md`](./bid-in-auction.md) | Participate in sniper auctions for early access to newly launched tokens |
11
+ | **Index Tokens** | [`index-tokens.md`](./index-tokens.md) | Discover, index, and monitor all Liquid Protocol token deployments |
12
+
13
+ ## How to Use
14
+
15
+ These are standalone markdown files designed to be loaded into an AI agent's system prompt or context window. Each skill contains:
16
+
17
+ - Complete setup instructions
18
+ - Step-by-step workflows with code examples
19
+ - Type definitions and parameter references
20
+ - Full working examples
21
+ - Error handling and edge cases
22
+
23
+ ### With Claude Code
24
+
25
+ Place a skill file in your project and reference it in your `CLAUDE.md`:
26
+
27
+ ```markdown
28
+ For token deployment, follow the instructions in skills/deploy-token.md
29
+ ```
30
+
31
+ ### With Other AI Agents
32
+
33
+ Load the skill file contents into your agent's system prompt or tool description:
34
+
35
+ ```python
36
+ with open("skills/deploy-token.md") as f:
37
+ skill = f.read()
38
+
39
+ agent.system_prompt += f"\n\n{skill}"
40
+ ```
41
+
42
+ ### With MCP Servers
43
+
44
+ Serve skill files as resources through an MCP server for on-demand agent access.
45
+
46
+ ## Requirements
47
+
48
+ All skills require:
49
+ - `liquid-sdk` and `viem` npm packages
50
+ - An RPC endpoint for Base mainnet (chain ID 8453)
51
+ - For write operations: a private key with ETH on Base
@@ -0,0 +1,296 @@
1
+ # Skill: Bid in a Sniper Auction (MEV)
2
+
3
+ You are an AI agent that participates in Liquid Protocol's sniper auction system. This skill teaches you how to bid for early access to newly launched tokens through the MEV auction mechanism on Base.
4
+
5
+ ## How the Sniper Auction Works
6
+
7
+ When a new token is deployed on Liquid Protocol, a **sniper auction** activates to price early trading activity and capture MEV. Here's the mechanism:
8
+
9
+ 1. **Fee decay**: The auction starts with an **80% fee** on swaps and decays linearly to **40% over 32 seconds**
10
+ 2. **Gas price bidding**: Bidders compete by setting their transaction gas price **above the pool's gas peg**. The difference between your gas price and the gas peg determines your bid amount
11
+ 3. **Rounds**: The auction runs in discrete rounds, each lasting a configurable number of blocks. Each round has its own auction window
12
+ 4. **Winner takes the swap**: The highest gas-price transaction in each block wins the right to swap at the current fee rate
13
+ 5. **Revenue distribution**: Auction revenue (bid amounts) flows to the protocol and LP holders
14
+
15
+ The auction is **not** a separate step from trading — it's a modified swap where your gas price encodes your bid.
16
+
17
+ ## Prerequisites
18
+
19
+ ```bash
20
+ npm install liquid-sdk viem
21
+ ```
22
+
23
+ You need:
24
+ - A **private key** with ETH on Base
25
+ - The **token address** or **pool ID** of a recently launched token
26
+ - An **RPC endpoint** for Base mainnet
27
+
28
+ ## Setup
29
+
30
+ ```typescript
31
+ import { createPublicClient, createWalletClient, http, parseEther } from "viem";
32
+ import { base } from "viem/chains";
33
+ import { privateKeyToAccount } from "viem/accounts";
34
+ import { LiquidSDK } from "liquid-sdk";
35
+
36
+ const account = privateKeyToAccount(PRIVATE_KEY);
37
+ const publicClient = createPublicClient({ chain: base, transport: http(RPC_URL) });
38
+ const walletClient = createWalletClient({ account, chain: base, transport: http(RPC_URL) });
39
+ const sdk = new LiquidSDK({ publicClient, walletClient });
40
+ ```
41
+
42
+ ## Step-by-Step: Bidding in an Auction
43
+
44
+ ### Step 1: Get the Auction State
45
+
46
+ ```typescript
47
+ // You need the pool ID — get it from deployment or token lookup
48
+ const tokenEvent = await sdk.getTokenEvent(tokenAddress);
49
+ const poolId = tokenEvent.poolId;
50
+
51
+ // Query current auction state
52
+ const auction = await sdk.getAuctionState(poolId);
53
+
54
+ console.log("Next auction block:", auction.nextAuctionBlock);
55
+ console.log("Current round:", auction.round);
56
+ console.log("Gas peg:", auction.gasPeg);
57
+ console.log("Current fee:", auction.currentFee); // in uniBps (800000 = 80%)
58
+ ```
59
+
60
+ **Key fields:**
61
+ | Field | Type | Description |
62
+ |-------|------|-------------|
63
+ | `nextAuctionBlock` | `bigint` | Block number when next auction round starts |
64
+ | `round` | `bigint` | Current round number (must match when bidding) |
65
+ | `gasPeg` | `bigint` | Base gas price reference — you bid by exceeding this |
66
+ | `currentFee` | `number` | Current MEV fee in uniBps (decays from 800000→400000) |
67
+
68
+ ### Step 2: Check Auction Fee Config
69
+
70
+ ```typescript
71
+ const feeConfig = await sdk.getAuctionFeeConfig(poolId);
72
+
73
+ console.log("Starting fee:", feeConfig.startingFee); // 800000 (80%)
74
+ console.log("Ending fee:", feeConfig.endingFee); // 400000 (40%)
75
+ console.log("Seconds to decay:", feeConfig.secondsToDecay); // 32n
76
+ ```
77
+
78
+ ### Step 3: Check Timing
79
+
80
+ ```typescript
81
+ // When did fee decay start?
82
+ const decayStart = await sdk.getAuctionDecayStartTime(poolId);
83
+ const now = BigInt(Math.floor(Date.now() / 1000));
84
+ const elapsed = now - decayStart;
85
+
86
+ console.log("Seconds since decay started:", elapsed);
87
+ // If elapsed > secondsToDecay, fee is at the floor (endingFee)
88
+
89
+ // How many rounds total?
90
+ const maxRounds = await sdk.getAuctionMaxRounds();
91
+ console.log("Max auction rounds:", maxRounds);
92
+
93
+ // Is this round still active?
94
+ const currentBlock = await publicClient.getBlockNumber();
95
+ console.log("Current block:", currentBlock);
96
+ console.log("Next auction block:", auction.nextAuctionBlock);
97
+ // If currentBlock < nextAuctionBlock, the current round is still active
98
+ ```
99
+
100
+ ### Step 4: Calculate Gas Price for Your Bid
101
+
102
+ ```typescript
103
+ const desiredBidAmount = parseEther("0.01"); // How much ETH you want to bid
104
+
105
+ // SDK calculates the exact gas price needed
106
+ const requiredGasPrice = await sdk.getAuctionGasPriceForBid(
107
+ auction.gasPeg,
108
+ desiredBidAmount,
109
+ );
110
+
111
+ console.log("Required gas price:", requiredGasPrice);
112
+ // This is the maxFeePerGas you must set on your transaction
113
+ ```
114
+
115
+ **The formula:** `bidAmount = (txGasPrice - gasPeg) * paymentPerGasUnit`. The utility contract solves for `txGasPrice` given your desired `bidAmount`.
116
+
117
+ ### Step 5: Get the Pool Key
118
+
119
+ ```typescript
120
+ // The pool key identifies the Uniswap V4 pool for the swap
121
+ const rewards = await sdk.getTokenRewards(tokenAddress);
122
+ const poolKey = rewards.poolKey;
123
+
124
+ // poolKey contains:
125
+ // .currency0 — lower-sorted token (WETH or the Liquid token)
126
+ // .currency1 — higher-sorted token
127
+ // .fee — fee tier
128
+ // .tickSpacing — tick spacing (200)
129
+ // .hooks — hook contract address
130
+ ```
131
+
132
+ ### Step 6: Execute the Bid
133
+
134
+ ```typescript
135
+ const result = await sdk.bidInAuction(
136
+ {
137
+ poolKey: rewards.poolKey,
138
+ zeroForOne: true, // true = buying tokens with ETH
139
+ amountIn: parseEther("0.1"), // amount of ETH to swap
140
+ amountOutMinimum: 0n, // set slippage protection (0 = no minimum)
141
+ round: auction.round, // must match current on-chain round
142
+ bidAmount: desiredBidAmount, // ETH bid (sent as msg.value)
143
+ },
144
+ requiredGasPrice, // calculated gas price from step 4
145
+ );
146
+
147
+ console.log("Bid tx:", result.txHash);
148
+
149
+ // Wait for confirmation
150
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: result.txHash });
151
+ console.log("Status:", receipt.status); // "success" or "reverted"
152
+ ```
153
+
154
+ ## Complete Example: Automated Auction Sniper
155
+
156
+ ```typescript
157
+ import { createPublicClient, createWalletClient, http, parseEther, formatEther } from "viem";
158
+ import { base } from "viem/chains";
159
+ import { privateKeyToAccount } from "viem/accounts";
160
+ import { LiquidSDK } from "liquid-sdk";
161
+
162
+ async function snipeToken(tokenAddress: `0x${string}`, bidETH: string, swapETH: string) {
163
+ const account = privateKeyToAccount(PRIVATE_KEY);
164
+ const publicClient = createPublicClient({ chain: base, transport: http(RPC_URL) });
165
+ const walletClient = createWalletClient({ account, chain: base, transport: http(RPC_URL) });
166
+ const sdk = new LiquidSDK({ publicClient, walletClient });
167
+
168
+ // 1. Look up the token
169
+ const tokenEvent = await sdk.getTokenEvent(tokenAddress);
170
+ if (!tokenEvent) throw new Error("Token not found");
171
+
172
+ console.log(`Sniping ${tokenEvent.tokenName} (${tokenEvent.tokenSymbol})`);
173
+ console.log(`Pool ID: ${tokenEvent.poolId}`);
174
+
175
+ // 2. Check auction state
176
+ const auction = await sdk.getAuctionState(tokenEvent.poolId);
177
+ console.log(`Auction round: ${auction.round}, Fee: ${Number(auction.currentFee) / 10000}%`);
178
+
179
+ // 3. Check if auction is still active
180
+ const maxRounds = await sdk.getAuctionMaxRounds();
181
+ if (auction.round >= maxRounds) {
182
+ console.log("Auction ended — trading at normal fees now");
183
+ return;
184
+ }
185
+
186
+ // 4. Get pool key for the swap
187
+ const rewards = await sdk.getTokenRewards(tokenAddress);
188
+
189
+ // 5. Calculate gas price for desired bid
190
+ const bidAmount = parseEther(bidETH);
191
+ const gasPrice = await sdk.getAuctionGasPriceForBid(auction.gasPeg, bidAmount);
192
+
193
+ console.log(`Bid amount: ${formatEther(bidAmount)} ETH`);
194
+ console.log(`Required gas price: ${gasPrice}`);
195
+
196
+ // 6. Execute the bid
197
+ const result = await sdk.bidInAuction(
198
+ {
199
+ poolKey: rewards.poolKey,
200
+ zeroForOne: true,
201
+ amountIn: parseEther(swapETH),
202
+ amountOutMinimum: 0n, // In production, calculate proper slippage!
203
+ round: auction.round,
204
+ bidAmount,
205
+ },
206
+ gasPrice,
207
+ );
208
+
209
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: result.txHash });
210
+ console.log(`Bid ${receipt.status === "success" ? "WON" : "FAILED"}: ${result.txHash}`);
211
+ }
212
+
213
+ // Usage
214
+ await snipeToken("0x...", "0.005", "0.1"); // bid 0.005 ETH, swap 0.1 ETH
215
+ ```
216
+
217
+ ## BidInAuctionParams Reference
218
+
219
+ ```typescript
220
+ interface BidInAuctionParams {
221
+ poolKey: PoolKey; // The Uniswap V4 pool key (get from getTokenRewards)
222
+ zeroForOne: boolean; // true = ETH→token, false = token→ETH
223
+ amountIn: bigint; // Amount of input token to swap (in wei)
224
+ amountOutMinimum: bigint;// Minimum output (slippage protection)
225
+ round: bigint; // Must match current on-chain auction round
226
+ bidAmount: bigint; // ETH bid amount (sent as msg.value)
227
+ }
228
+
229
+ interface BidInAuctionResult {
230
+ txHash: Hash;
231
+ }
232
+ ```
233
+
234
+ ## Auction Parameters (Defaults)
235
+
236
+ | Parameter | Value | Description |
237
+ |-----------|-------|-------------|
238
+ | Starting fee | 800,000 (80%) | Fee at auction start |
239
+ | Ending fee | 400,000 (40%) | Fee after decay completes |
240
+ | Decay period | 32 seconds | Time for fee to decay from start to end |
241
+ | Gas peg | Dynamic | Base gas price, updated per round |
242
+ | Max rounds | Contract-defined | Total auction rounds per token |
243
+
244
+ ## Timing Strategy
245
+
246
+ The auction fee **decays over time**, so there's a tradeoff:
247
+
248
+ - **Bid early** (high fee): You pay up to 80% of the swap as a fee, but you get the tokens before others. Useful if you expect rapid price appreciation.
249
+ - **Bid late** (lower fee): The fee decays to 40% over 32 seconds. You pay less in fees but risk being outbid or missing the auction window.
250
+ - **Wait for auction to end**: After all rounds complete, trading is at normal pool fees (typically 1%). No auction mechanics apply.
251
+
252
+ ```typescript
253
+ // Check current fee percentage
254
+ const feePercent = auction.currentFee / 10000; // e.g., 80.0, 60.5, 40.0
255
+ console.log(`Current fee: ${feePercent}%`);
256
+
257
+ // Check fee decay progress
258
+ const feeConfig = await sdk.getAuctionFeeConfig(poolId);
259
+ const decayStart = await sdk.getAuctionDecayStartTime(poolId);
260
+ const now = BigInt(Math.floor(Date.now() / 1000));
261
+ const decayProgress = Number(now - decayStart) / Number(feeConfig.secondsToDecay);
262
+ console.log(`Decay progress: ${Math.min(decayProgress * 100, 100).toFixed(1)}%`);
263
+ ```
264
+
265
+ ## Important Notes
266
+
267
+ 1. **`round` must be current**: If you submit a bid with a stale round number, the transaction will revert. Always fetch `getAuctionState()` right before bidding.
268
+
269
+ 2. **Gas price is the bid**: The auction uses `tx.gasprice` as the bidding mechanism. The SDK's `bidInAuction()` sets `maxFeePerGas` to the calculated value. On Base (L2), gas prices are low, so even small bids produce manageable gas costs.
270
+
271
+ 3. **Slippage protection**: Set `amountOutMinimum` to a non-zero value in production. Calculate it based on the current pool price and your acceptable slippage tolerance.
272
+
273
+ 4. **The bid amount is sent as `msg.value`**: This ETH goes to the auction contract, not to the swap. The `amountIn` is the separate ETH amount for the actual token swap.
274
+
275
+ 5. **`zeroForOne` direction**: Almost always `true` for sniping (buying tokens with ETH). Only set to `false` if selling tokens back through the auction.
276
+
277
+ ## Read-Only Auction Queries (No Wallet Needed)
278
+
279
+ ```typescript
280
+ const sdk = new LiquidSDK({ publicClient }); // read-only
281
+
282
+ const auction = await sdk.getAuctionState(poolId);
283
+ const feeConfig = await sdk.getAuctionFeeConfig(poolId);
284
+ const decayStart = await sdk.getAuctionDecayStartTime(poolId);
285
+ const maxRounds = await sdk.getAuctionMaxRounds();
286
+ const gasPrice = await sdk.getAuctionGasPriceForBid(auction.gasPeg, parseEther("0.01"));
287
+ ```
288
+
289
+ ## Contract Addresses
290
+
291
+ ```typescript
292
+ import { ADDRESSES } from "liquid-sdk";
293
+
294
+ ADDRESSES.SNIPER_AUCTION_V2 // Auction state contract
295
+ ADDRESSES.SNIPER_UTIL_V2 // Bid execution contract (called by bidInAuction)
296
+ ```
@@ -0,0 +1,260 @@
1
+ # Skill: Deploy a Token with Liquid Protocol
2
+
3
+ You are an AI agent that deploys ERC-20 tokens on Base using the `liquid-sdk`. This skill gives you everything you need to deploy tokens autonomously — from minimal launches to fully customized deployments with dev buys, custom fee structures, and multi-tranche liquidity positions.
4
+
5
+ ## Prerequisites
6
+
7
+ ```bash
8
+ npm install liquid-sdk viem
9
+ ```
10
+
11
+ You need:
12
+ - A **private key** with ETH on Base (for gas + optional dev buy)
13
+ - An **RPC endpoint** for Base mainnet (chain ID 8453)
14
+
15
+ ## Setup
16
+
17
+ ```typescript
18
+ import { createPublicClient, createWalletClient, http, parseEther } from "viem";
19
+ import { base } from "viem/chains";
20
+ import { privateKeyToAccount } from "viem/accounts";
21
+ import { LiquidSDK } from "liquid-sdk";
22
+
23
+ const account = privateKeyToAccount(PRIVATE_KEY);
24
+ const publicClient = createPublicClient({ chain: base, transport: http(RPC_URL) });
25
+ const walletClient = createWalletClient({ account, chain: base, transport: http(RPC_URL) });
26
+ const sdk = new LiquidSDK({ publicClient, walletClient });
27
+ ```
28
+
29
+ ## Deploying a Token
30
+
31
+ ### Minimal Deploy (2 required fields)
32
+
33
+ ```typescript
34
+ const result = await sdk.deployToken({
35
+ name: "My Token",
36
+ symbol: "MTK",
37
+ });
38
+
39
+ // result.tokenAddress → 0x... (the deployed ERC-20)
40
+ // result.txHash → 0x... (the transaction hash)
41
+ // result.event.poolId → 0x... (the Uniswap V4 pool ID)
42
+ ```
43
+
44
+ This creates a token with 100 billion supply, a Uniswap V4 pool, locked liquidity, 1% buy fee, and sniper auction MEV protection — all with sensible defaults.
45
+
46
+ ### Deploy with Dev Buy (buy tokens at launch)
47
+
48
+ ```typescript
49
+ const result = await sdk.deployToken({
50
+ name: "My Token",
51
+ symbol: "MTK",
52
+ image: "https://example.com/logo.png",
53
+ metadata: JSON.stringify({ description: "Launched by an AI agent" }),
54
+ devBuy: {
55
+ ethAmount: parseEther("0.01"), // ETH to spend buying tokens at launch
56
+ recipient: account.address, // who receives the purchased tokens
57
+ },
58
+ });
59
+ ```
60
+
61
+ The dev buy happens atomically in the same transaction as deployment. The ETH is swapped through the Uniswap V4 pool. The `msg.value` of the transaction equals the `ethAmount`.
62
+
63
+ ### Deploy with Custom Fees
64
+
65
+ ```typescript
66
+ import { encodeStaticFeePoolData, encodeDynamicFeePoolData, ADDRESSES } from "liquid-sdk";
67
+
68
+ // Static fees: 0% sell fee, 2% buy fee
69
+ const result = await sdk.deployToken({
70
+ name: "High Fee Token",
71
+ symbol: "HFT",
72
+ poolData: encodeStaticFeePoolData(0, 200), // (liquidFeeBps, pairedFeeBps)
73
+ });
74
+
75
+ // Dynamic fees (volatility-responsive)
76
+ const result2 = await sdk.deployToken({
77
+ name: "Dynamic Fee Token",
78
+ symbol: "DFT",
79
+ hook: ADDRESSES.HOOK_DYNAMIC_FEE_V2,
80
+ poolData: encodeDynamicFeePoolData({
81
+ baseFeeBps: 30, // 0.3% base
82
+ maxFeeBps: 500, // 5% max
83
+ referenceTickFilterPeriod: 300,
84
+ resetPeriod: 86400,
85
+ resetTickFilter: 600,
86
+ feeControlNumerator: 50000n,
87
+ decayFilterBps: 500,
88
+ }),
89
+ });
90
+ ```
91
+
92
+ ### Deploy with Custom Liquidity Positions
93
+
94
+ ```typescript
95
+ import { createPositionsUSD, createDefaultPositions } from "liquid-sdk";
96
+
97
+ // Option A: Default 3-tranche positions based on current ETH price
98
+ const positions = createDefaultPositions(20_000, 2500); // $20K starting cap, ETH=$2500
99
+ const result = await sdk.deployToken({
100
+ name: "Positioned Token",
101
+ symbol: "POS",
102
+ tickIfToken0IsLiquid: positions.tickIfToken0IsLiquid,
103
+ tickLower: positions.tickLower,
104
+ tickUpper: positions.tickUpper,
105
+ positionBps: positions.positionBps,
106
+ });
107
+
108
+ // Option B: Custom tranches
109
+ const custom = createPositionsUSD(20_000, 2500, [
110
+ { upperMarketCapUSD: 100_000, supplyPct: 30 },
111
+ { upperMarketCapUSD: 1_000_000, supplyPct: 40 },
112
+ { upperMarketCapUSD: 100_000_000, supplyPct: 30 },
113
+ ]);
114
+ ```
115
+
116
+ ### Deploy with Custom Reward Splits
117
+
118
+ ```typescript
119
+ const result = await sdk.deployToken({
120
+ name: "Split Token",
121
+ symbol: "SPLIT",
122
+ rewardAdmins: [walletA, walletB],
123
+ rewardRecipients: [walletA, walletB],
124
+ rewardBps: [7000, 3000], // 70% / 30% split
125
+ });
126
+ ```
127
+
128
+ ### Deploy with Context (attribution/tracking)
129
+
130
+ ```typescript
131
+ import { buildContext, buildMetadata } from "liquid-sdk";
132
+
133
+ const result = await sdk.deployToken({
134
+ name: "Social Token",
135
+ symbol: "SOC",
136
+ context: buildContext({
137
+ interface: "My Agent",
138
+ platform: "Farcaster",
139
+ messageId: "0x123abc",
140
+ }),
141
+ metadata: buildMetadata({
142
+ description: "A token launched from a Farcaster cast",
143
+ socialMediaUrls: [
144
+ { platform: "Twitter", url: "https://twitter.com/example" },
145
+ ],
146
+ }),
147
+ });
148
+ ```
149
+
150
+ ## Complete DeployTokenParams Reference
151
+
152
+ | Parameter | Type | Default | Description |
153
+ |-----------|------|---------|-------------|
154
+ | `name` | `string` | **required** | Token name |
155
+ | `symbol` | `string` | **required** | Token symbol |
156
+ | `image` | `string` | `""` | Image URL |
157
+ | `metadata` | `string` | `""` | JSON metadata string |
158
+ | `context` | `string` | `'{"interface":"SDK"}'` | JSON context for attribution |
159
+ | `tokenAdmin` | `Address` | wallet address | Can update image/metadata |
160
+ | `salt` | `Hex` | auto-generated | CREATE2 salt (unique per deploy) |
161
+ | `hook` | `Address` | Static Fee V2 | Fee logic hook contract |
162
+ | `pairedToken` | `Address` | WETH | Quote token |
163
+ | `tickIfToken0IsLiquid` | `number` | `-230400` | Starting tick (~10 ETH market cap) |
164
+ | `tickSpacing` | `number` | `200` | Uniswap V4 tick spacing |
165
+ | `poolData` | `Hex` | 0% sell / 1% buy | Encoded fee configuration |
166
+ | `locker` | `Address` | LP Locker Fee Conversion | LP locker contract |
167
+ | `rewardAdmins` | `Address[]` | `[wallet]` | Reward admin per recipient |
168
+ | `rewardRecipients` | `Address[]` | `[wallet]` | Fee recipients |
169
+ | `rewardBps` | `number[]` | `[10000]` | BPS per recipient (sum = 10000) |
170
+ | `tickLower` | `number[]` | 5-tranche Liquid default | Position lower bounds |
171
+ | `tickUpper` | `number[]` | 5-tranche Liquid default | Position upper bounds |
172
+ | `positionBps` | `number[]` | 5-tranche Liquid default | Supply % per position (sum = 10000) |
173
+ | `lockerData` | `Hex` | auto-encoded | Locker init data |
174
+ | `mevModule` | `Address` | Sniper Auction V2 | MEV protection module |
175
+ | `mevModuleData` | `Hex` | 80%→40% over 32s | Encoded auction config |
176
+ | `extensions` | `ExtensionConfig[]` | `[]` | Additional extensions |
177
+ | `devBuy` | `DevBuyParams` | none | Buy tokens at launch |
178
+
179
+ ## DeployTokenResult
180
+
181
+ ```typescript
182
+ interface DeployTokenResult {
183
+ tokenAddress: Address; // The deployed ERC-20 contract
184
+ txHash: Hash; // Transaction hash
185
+ event: TokenCreatedEvent; // Full on-chain event data
186
+ }
187
+
188
+ // event contains:
189
+ // .poolId — Uniswap V4 pool ID (bytes32)
190
+ // .poolHook — Hook contract used
191
+ // .locker — LP locker address
192
+ // .mevModule — MEV module address
193
+ // .extensions — Extension addresses
194
+ // .tokenAdmin — Admin address
195
+ // .startingTick — Initial tick
196
+ // .pairedToken — Quote token (WETH)
197
+ ```
198
+
199
+ ## What Happens On-Chain
200
+
201
+ When `deployToken()` executes, a single transaction:
202
+
203
+ 1. **Creates the ERC-20 token** with 100 billion supply (18 decimals)
204
+ 2. **Initializes a Uniswap V4 pool** paired with WETH
205
+ 3. **Locks all LP** in the LP Locker (non-ruggable)
206
+ 4. **Configures reward splits** for fee distribution
207
+ 5. **Activates MEV protection** (sniper auction: 80%→40% fee decay over 32 seconds)
208
+ 6. **Executes dev buy** if specified (swaps ETH→tokens in same tx)
209
+ 7. **Emits `TokenCreated` event** with all deployment data
210
+
211
+ ## Validation Rules
212
+
213
+ The SDK validates before sending the transaction:
214
+ - 1–7 positions allowed, BPS must sum to 10000
215
+ - All ticks must be aligned to `tickSpacing`
216
+ - All `tickLower` must be `≥ tickIfToken0IsLiquid`
217
+ - At least one position must have `tickLower == tickIfToken0IsLiquid`
218
+ - 1+ reward recipients, BPS must sum to 10000
219
+ - Max 10 extensions, max 90% of supply to extensions total
220
+
221
+ ## Post-Deploy Operations
222
+
223
+ After deployment, you can:
224
+
225
+ ```typescript
226
+ // Update token image or metadata (admin only)
227
+ await sdk.updateImage(result.tokenAddress, "https://new-image.png");
228
+ await sdk.updateMetadata(result.tokenAddress, '{"description":"Updated"}');
229
+
230
+ // Check deployment info
231
+ const info = await sdk.getDeploymentInfo(result.tokenAddress);
232
+ const tokenInfo = await sdk.getTokenInfo(result.tokenAddress);
233
+
234
+ // Check MEV lock status
235
+ const unlockTime = await sdk.getPoolUnlockTime(result.event.poolId);
236
+ ```
237
+
238
+ ## Common Errors
239
+
240
+ | Error | Cause | Fix |
241
+ |-------|-------|-----|
242
+ | `TickRangeLowerThanStartingTick` | `tickLower < tickIfToken0IsLiquid` | Ensure all position ticks ≥ starting tick |
243
+ | Insufficient funds | Not enough ETH for gas + devBuy | Check balance covers gas + `devBuy.ethAmount` |
244
+ | `rewardBps must sum to 10000` | BPS array doesn't total 10000 | Fix the array values |
245
+ | `positions and bps arrays must be same length` | Mismatched arrays | Ensure tickLower, tickUpper, positionBps are same length |
246
+
247
+ ## Contract Addresses (Base Mainnet)
248
+
249
+ ```typescript
250
+ import { ADDRESSES, EXTERNAL } from "liquid-sdk";
251
+
252
+ ADDRESSES.FACTORY // Token factory
253
+ ADDRESSES.HOOK_STATIC_FEE_V2 // Default hook (1% buy fee)
254
+ ADDRESSES.HOOK_DYNAMIC_FEE_V2 // Dynamic fee hook
255
+ ADDRESSES.LP_LOCKER_FEE_CONVERSION // Default locker (converts fees to ETH)
256
+ ADDRESSES.SNIPER_AUCTION_V2 // Default MEV module
257
+ ADDRESSES.UNIV4_ETH_DEV_BUY // Dev buy extension
258
+ EXTERNAL.WETH // Base WETH
259
+ EXTERNAL.POOL_MANAGER // Uniswap V4 Pool Manager
260
+ ```
@@ -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
+ ```