zora-coins
Typed Python SDK for Zora Coins, plus an onchain indexer for the creator and referral rewards Zora pays on every trade.
Zora's official SDK is TypeScript-only. zora-coins covers the same public API from Python (all 30 endpoints) with typed, documented models generated from Zora's own OpenAPI spec, and adds something neither the SDK nor the API offers: how much an address has actually earned as a creator, platform referrer or trade referrer, read directly from Base.
Part of zora-coins-sdks: the same types, names and docs in Python, TypeScript, Go, Rust, C#, Java and C++, plus a GraphQL gateway over the whole API.
Unofficial and community-maintained. Not affiliated with Zora.
pip install zora-coins
Python 3.10+. Dependencies: httpx, pydantic.
Quick start
from zora_coins import ZoraCoins, ListType, eth, erc20
with ZoraCoins() as zora: # reads ZORA_API_KEY; or ZoraCoins(api_key="...")
coin = zora.get_coin("0x0b8590d3c0b1ee6c797e184a4afbb15f8f58a46b")
print(coin.name, coin.market_cap, coin.unique_holders, coin.creator_profile.handle)
for c in zora.iter_explore(ListType.top_volume_24_h, limit=10): # follows pagination
print(c.symbol, c.volume24h)
for holder in zora.iter_coin_holders(coin.address, limit=100):
print(holder.owner_address, holder.balance)
# Build a trade. Nothing is signed or sent: pass quote.call to your wallet library.
quote = zora.quote_trade(eth(), erc20(coin.address), amount_in=10**15, sender="0xYourWallet")
tx = {"to": quote.call.target, "data": quote.call.data, "value": int(quote.call.value)}
Async works the same way:
from zora_coins import AsyncZoraCoins
async with AsyncZoraCoins() as zora:
profile = await zora.get_profile("rebelstudios")
async for coin in zora.iter_profile_coins("rebelstudios"):
print(coin.symbol)
What's covered
Every endpoint, with an iter_* for every paginated one (method names in every language):
| Area | Methods |
|---|---|
| Coins | get_coin, get_coins, get_coin_holders / iter_coin_holders, get_coin_swaps / iter_coin_swaps, get_coin_comments / iter_coin_comments, get_coin_merged_comments / iter_coin_merged_comments, get_coin_price_history, get_coins_list / iter_coins_list, get_token_info |
| Explore | explore / iter_explore (all 25 list types), search / iter_search, get_trader_leaderboard / iter_trader_leaderboard, get_featured_creators / iter_featured_creators, get_trend_coin, get_trends_by_name / iter_trends_by_name |
| Live | get_latest_live_streams / iter_latest_live_streams, get_top_live_streams / iter_top_live_streams, get_creator_livestream_comments / iter_creator_livestream_comments |
| Profiles | get_profile, get_profile_coins / iter_profile_coins (filter by platform_referrer), get_profile_balances / iter_profile_balances, get_profile_social, get_profile_by_social_handle, get_wallet_trade_activity / iter_wallet_trade_activity |
| Transactions | quote_trade (buy/sell calldata with optional trade referrer), create_content_coin (creation calls + predicted address, optional platform_referrer), create_upload_jwt, pool configs, get_api_key |
Every response is a documented pydantic model (zora_coins.models), and your editor shows each field's meaning on hover, units included: marketCap is a USD decimal string, but balance is an 18-decimal integer string. Rate limits (429) and 5xx responses are retried with backoff; API errors raise ZoraAPIError, whose error_type says why a quote failed (insufficient_liquidity for a pool that can't fill the trade).
Why every field is Optional: the live API omits fields its spec marks as required. For example, V4 coins have no uniswapV3PoolAddress, and zoraComments is absent unless requested. Strict models rejected real responses from 9 of 24 endpoints, so the models accept what the API actually returns. Enum fields also accept strings, so a list type Zora adds later doesn't fail validation.
GraphQL
from zora_coins.graphql import ZoraGraphQL
from zora_coins.models import Zora20Token
gql = ZoraGraphQL("http://localhost:8080/graphql") # the zora-coins GraphQL gateway
data = gql.query('query($a: String!) { coin(address: $a) { name symbol marketCap } }', {"a": "0x…"})
coin = Zora20Token.model_validate(data["coin"]) # the same models as REST
AsyncZoraGraphQL has the same interface. The gateway is at zora-coins-sdks/graphql.
Rewards: what did an address earn?
Every trade on a Zora coin pays out to the coin's creator, the platform that launched it, the interface that routed the trade, and the protocol. On V4 coins those payouts are recorded in CoinMarketRewardsV4 events, and none of that event's fields are indexed. You can't ask a node for "rewards paid to my address"; you have to read every reward event and filter. zora_coins.rewards does that, keeps a local SQLite index, and only fetches blocks it hasn't seen.
zora-rewards 0xYourAddress --days 30 --html rewards.html
Real output for a busy platform-referrer address over the last 6 hours:
Zora rewards for 0x55c88bb05602da94fce8feadc1cbebf5b72c2453
291 reward events, blocks 51426603–51437322
Platform referral 328.5866 ZORA $2.52 (34 payouts)
Platform referral 0.000114798 WETH $0.28 (6 payouts)
Platform referral 5.4734 USDC $5.47 (98 payouts)
Trade referral 1.07698e-07 ETH $0.0002655 (2 payouts)
Trade referral 136.7589 ZORA $1.05 (143 payouts)
Trade referral 0.000130539 WETH $0.32 (16 payouts)
Trade referral 0.737777 USDC $0.74 (44 payouts)
Total (current prices) $10.39
from zora_coins.rewards import RewardsIndexer, build_report, to_text
with RewardsIndexer("rewards.sqlite", rpc_url="https://mainnet.base.org") as idx:
idx.scan(["0xYourAddress"], days=30) # resumable; re-runs fetch only new blocks
report = build_report(idx.events_for(["0xYourAddress"]), ["0xYourAddress"])
print(to_text(report), report.by_role_usd())
- Roles: creator (
payoutRecipient), platform referrer, trade referrer, protocol, and Doppler. - Amounts: kept as exact integers, in both the backing currency (ZORA, ETH, USDC or a creator coin) and the coin itself.
- USD values: use current token prices from the Zora API, not the price at payout time.
- Coverage: legacy V3 coins (
CoinTradeRewards) with--v3. - Speed: on the public Base RPC, six hours of blocks (about 10,800) scans in about 8 seconds. A private RPC (
--rpc) is faster for long histories.
Development
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest # offline tests (mocked HTTP and RPC, real recorded payloads)
.venv/bin/python scripts/validate_live.py # every endpoint against production
./scripts/generate_models.sh # refresh models from Zora's OpenAPI spec
License
MIT © Rebel Studios Software
Release files for zora-coins 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| zora_coins-0.2.0.tar.gz | 92.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zora_coins-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 134.5 kB
Release files / zora_coins-0.2.0.tar.gz
| Download URL | zora_coins-0.2.0.tar.gz |
|---|---|
| Size | 92.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
33fa6e18b62cc96642d3f9d05540a86276271d15226d4a7adb8be91a544c3aa7
|
|
BLAKE2b-256 checksum How to use checksums |
aa9d9ab766fa5a0a4f3c87068060878b144e32e2d5aa92e3d899b596241fec93
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|
Release files / zora_coins-0.2.0-py3-none-any.whl
| Download URL | zora_coins-0.2.0-py3-none-any.whl |
|---|---|
| Size | 41.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3cd12928ab07b312da85c9b2b68c9e3a6c2f047ace9ac84bb4ca9130b90277b7
|
|
BLAKE2b-256 checksum How to use checksums |
8c85143431a66ccc2d319edde23438181664ba74400ea179373955b332dd14df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.12
|