liquid-sdk 1.5.0 → 1.5.2
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/AGENT_README.md +12 -13
- package/LICENSE +21 -0
- package/README.md +12 -12
- package/llms.txt +12 -11
- package/package.json +5 -2
- package/skills/README.md +51 -0
- package/skills/bid-in-auction.md +296 -0
- package/skills/deploy-token.md +260 -0
- package/skills/index-tokens.md +501 -0
package/AGENT_README.md
CHANGED
|
@@ -511,19 +511,18 @@ All addresses are exported as `ADDRESSES` and `EXTERNAL`:
|
|
|
511
511
|
import { ADDRESSES, EXTERNAL } from "liquid-sdk";
|
|
512
512
|
|
|
513
513
|
// Liquid Protocol contracts
|
|
514
|
-
ADDRESSES.FACTORY //
|
|
515
|
-
ADDRESSES.
|
|
516
|
-
ADDRESSES.FEE_LOCKER //
|
|
517
|
-
ADDRESSES.VAULT //
|
|
518
|
-
ADDRESSES.HOOK_DYNAMIC_FEE_V2 //
|
|
519
|
-
ADDRESSES.HOOK_STATIC_FEE_V2 //
|
|
520
|
-
ADDRESSES.AIRDROP_V2 //
|
|
521
|
-
ADDRESSES.SNIPER_AUCTION_V2 //
|
|
522
|
-
ADDRESSES.SNIPER_UTIL_V2 //
|
|
523
|
-
ADDRESSES.
|
|
524
|
-
ADDRESSES.UNIV4_ETH_DEV_BUY //
|
|
525
|
-
ADDRESSES.POOL_EXTENSION_ALLOWLIST //
|
|
526
|
-
ADDRESSES.LIQUID_DEPLOYER_LIB // 0x00000f88b2d37A2006F2F0C8552d22E0b8945202
|
|
514
|
+
ADDRESSES.FACTORY // 0x04F1a284168743759BE6554f607a10CEBdB77760
|
|
515
|
+
ADDRESSES.LP_LOCKER_FEE_CONVERSION // 0x77247fCD1d5e34A3703AcA898A591Dc7422435f3
|
|
516
|
+
ADDRESSES.FEE_LOCKER // 0xF7d3BE3FC0de76fA5550C29A8F6fa53667B876FF
|
|
517
|
+
ADDRESSES.VAULT // 0xdFCCC93257c20519A9005A2281CFBdF84836d50E
|
|
518
|
+
ADDRESSES.HOOK_DYNAMIC_FEE_V2 // 0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC
|
|
519
|
+
ADDRESSES.HOOK_STATIC_FEE_V2 // 0x9811f10Cd549c754Fa9E5785989c422A762c28cc
|
|
520
|
+
ADDRESSES.AIRDROP_V2 // 0x1423974d48f525462f1c087cBFdCC20BDBc33CdD
|
|
521
|
+
ADDRESSES.SNIPER_AUCTION_V2 // 0x187e8627c02c58F31831953C1268e157d3BfCefd
|
|
522
|
+
ADDRESSES.SNIPER_UTIL_V2 // 0x2B6cd5Be183c388Dd0074d53c52317df1414cd9f
|
|
523
|
+
ADDRESSES.MEV_DESCENDING_FEES // 0x8D6B080e48756A99F3893491D556B5d6907b6910
|
|
524
|
+
ADDRESSES.UNIV4_ETH_DEV_BUY // 0x5934097864dC487D21A7B4e4EEe201A39ceF728D
|
|
525
|
+
ADDRESSES.POOL_EXTENSION_ALLOWLIST // 0xb614167d79aDBaA9BA35d05fE1d5542d7316Ccaa
|
|
527
526
|
|
|
528
527
|
// External (Uniswap V4 / Base)
|
|
529
528
|
EXTERNAL.POOL_MANAGER // 0x498581fF718922c3f8e6A244956aF099B2652b2b
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Liquid Protocol
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -246,18 +246,18 @@ All contracts are deployed on **Base** (chain ID 8453):
|
|
|
246
246
|
|
|
247
247
|
| Contract | Address |
|
|
248
248
|
|----------|---------|
|
|
249
|
-
| Factory | `
|
|
250
|
-
| Hook Dynamic Fee V2 | `
|
|
251
|
-
| Hook Static Fee V2 | `
|
|
252
|
-
| Fee Locker | `
|
|
253
|
-
| LP Locker | `
|
|
254
|
-
|
|
|
255
|
-
|
|
|
256
|
-
| Sniper
|
|
257
|
-
|
|
|
258
|
-
|
|
|
259
|
-
|
|
|
260
|
-
|
|
|
249
|
+
| Factory | `0x04F1a284168743759BE6554f607a10CEBdB77760` |
|
|
250
|
+
| Hook Dynamic Fee V2 | `0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC` |
|
|
251
|
+
| Hook Static Fee V2 | `0x9811f10Cd549c754Fa9E5785989c422A762c28cc` |
|
|
252
|
+
| Fee Locker | `0xF7d3BE3FC0de76fA5550C29A8F6fa53667B876FF` |
|
|
253
|
+
| LP Locker Fee Conversion | `0x77247fCD1d5e34A3703AcA898A591Dc7422435f3` |
|
|
254
|
+
| Vault | `0xdFCCC93257c20519A9005A2281CFBdF84836d50E` |
|
|
255
|
+
| Sniper Auction V2 | `0x187e8627c02c58F31831953C1268e157d3BfCefd` |
|
|
256
|
+
| Sniper Util V2 | `0x2B6cd5Be183c388Dd0074d53c52317df1414cd9f` |
|
|
257
|
+
| MEV Descending Fees | `0x8D6B080e48756A99F3893491D556B5d6907b6910` |
|
|
258
|
+
| Airdrop V2 | `0x1423974d48f525462f1c087cBFdCC20BDBc33CdD` |
|
|
259
|
+
| Pool Extension Allowlist | `0xb614167d79aDBaA9BA35d05fE1d5542d7316Ccaa` |
|
|
260
|
+
| Univ4 ETH Dev Buy | `0x5934097864dC487D21A7B4e4EEe201A39ceF728D` |
|
|
261
261
|
|
|
262
262
|
## License
|
|
263
263
|
|
package/llms.txt
CHANGED
|
@@ -127,17 +127,18 @@ Every token gets 100 billion supply (18 decimals), a Uniswap V4 pool, and locked
|
|
|
127
127
|
|
|
128
128
|
## Contract Addresses (Base Mainnet)
|
|
129
129
|
|
|
130
|
-
- Factory:
|
|
131
|
-
- LP Locker:
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
- Hook (
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
- Airdrop V2:
|
|
140
|
-
- Dev Buy Extension:
|
|
130
|
+
- Factory: 0x04F1a284168743759BE6554f607a10CEBdB77760
|
|
131
|
+
- LP Locker Fee Conversion: 0x77247fCD1d5e34A3703AcA898A591Dc7422435f3 (default)
|
|
132
|
+
- Fee Locker: 0xF7d3BE3FC0de76fA5550C29A8F6fa53667B876FF
|
|
133
|
+
- Vault: 0xdFCCC93257c20519A9005A2281CFBdF84836d50E
|
|
134
|
+
- Hook (Dynamic Fee V2): 0x80E2F7dC8C2C880BbC4BDF80A5Fb0eB8B1DB68CC
|
|
135
|
+
- Hook (Static Fee V2): 0x9811f10Cd549c754Fa9E5785989c422A762c28cc
|
|
136
|
+
- Sniper Auction V2: 0x187e8627c02c58F31831953C1268e157d3BfCefd
|
|
137
|
+
- MEV Descending Fees: 0x8D6B080e48756A99F3893491D556B5d6907b6910
|
|
138
|
+
- Sniper Util V2: 0x2B6cd5Be183c388Dd0074d53c52317df1414cd9f
|
|
139
|
+
- Airdrop V2: 0x1423974d48f525462f1c087cBFdCC20BDBc33CdD
|
|
140
|
+
- Dev Buy Extension: 0x5934097864dC487D21A7B4e4EEe201A39ceF728D
|
|
141
|
+
- Pool Extension Allowlist: 0xb614167d79aDBaA9BA35d05fE1d5542d7316Ccaa
|
|
141
142
|
- WETH (Base): 0x4200000000000000000000000000000000000006
|
|
142
143
|
- Pool Manager (Uniswap V4): 0x498581fF718922c3f8e6A244956aF099B2652b2b
|
|
143
144
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "liquid-sdk",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
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
|
}
|
package/skills/README.md
ADDED
|
@@ -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 // 0x04F1a284168743759BE6554f607a10CEBdB77760
|
|
500
|
+
// All TokenCreated events are emitted from this address
|
|
501
|
+
```
|