---
name: basebid
description: Stake USDC on Base to rank a project on basebid.lol (refundable when outbid), list it ($2, x402) or buy a 3h takeover (x402). Use when the user wants their Base project ranked, wants to outbid a competitor, check rank/price, or withdraw refunds.
---

# basebid

basebid.lol is a pay-to-rank leaderboard for Base projects. Rank = USDC locked in the
BaseBid escrow contract. When you are outbid, your entire deposit becomes claimable.
Only the 5% fee on new money is non-refundable. Monthly seasons; everything is released
at season end.

Env this skill expects:
- `PRIVATE_KEY` — 0x… key of a wallet holding USDC (and a little ETH for gas) on Base
- `BASE_RPC_URL` — optional RPC (defaults to the chain's public RPC)
- `BASEBID_URL` — optional, defaults to https://basebid.lol
- `NEXT_PUBLIC_CHAIN` — `base` (default) or `baseSepolia`

## Read first

```bash
curl -s https://basebid.lol/api/board | jq '{season, totals, top: .entries[:5]}'
curl -s "https://basebid.lol/api/quote?url=https://yourproject.xyz&amount=25&bidder=0xYOU"
```

`outbid_price_usd` on a row is the whole-dollar amount that takes its slot.
`required_usd` on a quote is the minimum for that url right now.

## Bid (on-chain, refundable)

```bash
node scripts/bid.mjs https://yourproject.xyz 25        # lock $25
node scripts/bid.mjs --withdraw                         # pull your claimable refunds
node scripts/bid.mjs --release <season> https://url     # after a season ends
```

What bid.mjs does: canonicalizes the url (`scripts/url.mjs`, same rules as the site),
calls `quote(url, you, amount)`, approves exactly `transferIn` USDC if the allowance is
short, re-quotes, sends `bid(url, amount, maxPaid)` with `maxPaid = due + fee` from that
fresh quote, waits for the receipt, then POSTs `/api/sync` so the board updates immediately.

`maxPaid` is a slippage bound on your total cost: if someone overtakes you or the fee
changes between your quote and your tx, the contract reverts `PaidExceedsMax(owed, maxPaid)`
instead of charging you more than you saw. Always take the quote right before sending and
pass exactly `due + fee` (zero tolerance).

Rules the contract enforces: whole dollars (`amount % 1e6 == 0`), `>= $1`, `>= holder + $1`
unless you are the holder (then you pay only the increment), season must be active.

## List ($2) or take over (2× #1, 3h) — x402

```bash
node scripts/pay.mjs listing https://yourproject.xyz --name "Your Project" --description "one line" --category app --contract 0x…
node scripts/pay.mjs takeover https://yourproject.xyz
```

pay.mjs: `POST /api/validate` (free, runs moderation, returns a 10-min quote token and
`price_micro`) then the gated route via `@x402/fetch` (402 → check the asked amount equals
`price_micro` → sign a gasless USDC authorization → retry). The script refuses to sign if
the 402 asks for anything other than the quoted amount. A takeover buyer pays the price
quoted at validate time (the token binds it for its 10-minute TTL). The handler validates
BEFORE the USDC settles: any rejection (bad/expired token, not eligible, underpaid) returns
4xx and no money moves.
Listing is metadata only (name/logo/link/badge). Takeover requires the entry to be listed
and approved; one takeover at a time. If the url is already listed by a different wallet,
your $2 listing is recorded as a proposal (`202 {status:"pending_review"}`) for a human to
apply — the existing listing is never overwritten.

## Canonical url

`https://` + lowercase host (no `www.`) + path without trailing slash; no query, no
fragment. `entry_id = keccak256(utf8(url))`. The API canonicalizes for you; the contract
does not — send the canonical string to `bid()` or you create a different entry.

## Gotchas

- Quote before every bid; someone may have outbid you between reads (`MustOutbid(required)`
  revert tells you the new minimum) and pass `maxPaid = due + fee` (`PaidExceedsMax` revert
  means the price moved — re-quote and retry). `EnforcedPause` means bidding is paused.
- Fee is charged on NEW money only: a $24 → $25 top-up costs $1 + $0.05.
- Your claimable balance is netted automatically; withdrawing first is optional.
- Unlisted entries show as plain host text and are not clickable until someone pays the $2.
