Skip to content

Commit b1c3b6c

Browse files
r4topunkclaude
andauthored
chore(scripts): export creator liquidity to CSV for Clanker migration (#95)
Standalone tsx script that snapshots every Gnars DAO member's Zora-created coins plus per-pool TVL from GeckoTerminal, then writes two CSVs (creators + coins) to plan the liquidity migration to the new Gnars token on Clanker. 5 disk-cached stages (resumable on Ctrl+C): members, profileCoins, gecko pools, Farcaster + ENS, CSV write. Free-tier rate limiting with 429-aware exponential backoff so partial failures don't poison the cache. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent d90d402 commit b1c3b6c

2 files changed

Lines changed: 1282 additions & 0 deletions

File tree

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# export-creator-liquidity
2+
3+
Exports two CSVs that snapshot every Gnars DAO member's Zora-created coins and
4+
their pool liquidity, sourced from GeckoTerminal. The intended use is to
5+
prioritize creator outreach for the upcoming liquidity migration to the new
6+
Gnars token on Clanker.
7+
8+
## Run
9+
10+
```bash
11+
# from the main repo (where node_modules + .env.local live)
12+
pnpm tsx scripts/export-creator-liquidity.ts
13+
```
14+
15+
CSVs land in `output/`. Intermediate JSON caches land in `output/cache/`. To
16+
force a refresh of one stage, delete that stage's cache directory:
17+
18+
```bash
19+
rm -rf output/cache/profile-coins # re-fetch Zora coins per member
20+
rm -rf output/cache/gecko-pools # re-fetch pool TVL
21+
rm -f output/cache/members.json # re-fetch DAO members
22+
rm -f output/cache/farcaster.json # re-fetch Farcaster profiles
23+
rm -f output/cache/ens.json # re-fetch ENS names
24+
```
25+
26+
Hitting `Ctrl+C` is safe — every stage persists progress, so a rerun resumes
27+
from where it stopped.
28+
29+
## Required env (in `.env.local`)
30+
31+
| Variable | Required? | Notes |
32+
| ------------------------------ | --------- | ---------------------------------------------------- |
33+
| `NEXT_PUBLIC_GOLDSKY_PROJECT_ID` | yes | Builder DAO subgraph |
34+
| `NEXT_PUBLIC_ZORA_API_KEY` | recommended | raises Zora API rate limits |
35+
| `ALCHEMY_API_KEY` | recommended | mainnet ENS reverse lookup |
36+
| `NEYNAR_API_KEY` | optional | Farcaster username, bio, follower count, Twitter via verified accounts |
37+
| `GECKOTERMINAL_API_KEY` | optional | paid tier; without it the run is throttled to ~30 req/min |
38+
39+
Without an Alchemy or Neynar key, those columns are blank — the run still
40+
completes.
41+
42+
## Stages
43+
44+
1. **Members** — paginates `daotokenOwners` from the Builder DAO subgraph.
45+
2. **ProfileCoins**`getProfileCoins` per member, paginates via `after` cursor up to 10 pages × 50 coins.
46+
3. **Pools** — GeckoTerminal `/networks/base/tokens/{coin}/pools` for each unique coin, picks the pool with the highest `reserve_in_usd`.
47+
4. **Creators** — Neynar `bulk-by-address` and viem mainnet `getEnsName` only for members who have ≥1 created coin.
48+
5. **CSVs** — aggregates per creator, writes both files with a timestamp suffix.
49+
50+
## Output files
51+
52+
### `creators-YYYYMMDD-HHMMSS.csv`
53+
54+
One row per creator who has at least one created coin. Sorted by `total_tvl_usd` desc.
55+
56+
| Column | Notes |
57+
| -------------------------- | -------------------------------------------------------------------- |
58+
| `creator_address` | DAO member address (lowercase) |
59+
| `creator_ens` | mainnet ENS name (if any) |
60+
| `creator_farcaster` | Neynar-resolved Farcaster username |
61+
| `creator_twitter` | Zora-linked Twitter > Farcaster verified accounts > coin metadata |
62+
| `creator_email` | best-effort regex on Farcaster bio + Zora handles (often blank) |
63+
| `creator_gnars_held` | NFT count from subgraph |
64+
| `creator_delegate` | who they delegated to (lowercase) |
65+
| `coins_count` | total created coins matched to this creator |
66+
| `coins_with_liquidity` | coins where pool TVL > $0 |
67+
| `total_tvl_usd` | sum of `reserve_in_usd` across all their pools |
68+
| `gnars_paired_tvl_usd` | sum of TVL where pool currency is the Gnars creator coin |
69+
| `top_coin_symbol` | symbol of the coin with the highest TVL |
70+
| `top_coin_tvl_usd` | TVL of that top coin |
71+
| `migration_priority_tier` | `high` (≥$10k), `med` (≥$1k), `low` (>$0), `none` |
72+
73+
### `coins-YYYYMMDD-HHMMSS.csv`
74+
75+
One row per created coin. Sorted by `is_gnars_paired` desc, then `pool_tvl_usd` desc.
76+
77+
| Column | Notes |
78+
| --------------------------- | ---------------------------------------------------------------------- |
79+
| `creator_address` | |
80+
| `creator_ens` | |
81+
| `creator_farcaster` | |
82+
| `coin_address` | Zora content coin address (lowercase) |
83+
| `coin_name` | |
84+
| `coin_symbol` | |
85+
| `coin_created_at` | ISO timestamp from Zora |
86+
| `is_gnars_paired` | `true` if pool currency address = `0x0cf0c3b75d522290d7d12c74d7f1f0cc47ccb23b` |
87+
| `has_liquidity` | `true` if pool `reserve_in_usd` > 0 |
88+
| `pool_address` | Uni V4 / Uni V3 pool |
89+
| `pool_url` | GeckoTerminal explorer link |
90+
| `backing_currency_symbol` | from Zora `poolCurrencyToken.name` falling back to Gecko quote symbol |
91+
| `backing_currency_address` | |
92+
| `pool_tvl_usd` | sum of both sides of the pool, USD |
93+
| `base_reserve_usd` | one-side approx (≈ TVL/2; Gecko doesn't expose exact split) |
94+
| `quote_reserve_usd` | one-side approx (≈ TVL/2) |
95+
| `market_cap_usd` | from Gecko, falling back to Zora |
96+
| `volume_24h_usd` | |
97+
| `total_volume_usd` | lifetime, from Zora |
98+
| `price_usd` | Zora `tokenPrice.priceInUsdc` |
99+
| `holders_count` | Zora `uniqueHolders` |
100+
| `fee_bps` | pool fee in basis points |
101+
| `dex` | Gecko DEX id (e.g. `uniswap-v4`) |
102+
| `zora_url` | `https://zora.co/coin/base:{address}` |
103+
104+
## Caveats
105+
106+
- **Per-side reserves are approximations.** GeckoTerminal exposes the total
107+
pool USD reserve but not the split. The script reports `≈ TVL/2` for each
108+
side; this is accurate for balanced V3/V4 ranges but skewed for one-sided
109+
Doppler-style launches. For migration decisions, use `pool_tvl_usd` as the
110+
authoritative number.
111+
- **Creator filter.** The script only includes a coin if `coin.creatorAddress`
112+
matches the DAO member's address. Members who hold Gnars NFTs at one address
113+
but deploy Zora coins from a different wallet will be missed. If that
114+
becomes a gap, drop the `if (creatorAddr !== m.owner) continue;` check in
115+
`buildCoinRows` and let coins flow under the address Zora reports.
116+
- **Email column is best-effort.** Zora and Farcaster don't expose email
117+
directly; the script regex-scans bio text. Most rows will be blank.
118+
- **Free-tier GeckoTerminal is slow.** Expect ~3–5 minutes per 100 unique
119+
coins on the free tier. Set `GECKOTERMINAL_API_KEY` for a paid tier if you
120+
need it faster.

0 commit comments

Comments
 (0)