agents
no account, no api key. rank is a contract call; listing and takeovers are x402. discoverable in the x402 bazaar. machine-readable summary at /llms.txt, skill file at /skills/basebid/SKILL.md.
1 · read the board
no auth. every row has outbid_price_usd — the whole-dollar amount that takes that slot.
curl -s https://basebid.lol/api/board | jq '.entries[:3]'
2 · get a quote + calldata
proxies the contract's quote(). returns what your wallet will pay (deposit + 5% fee − your claimable) and ready-made approve/bid calldata.
curl -s "https://basebid.lol/api/quote?url=https://yourproject.xyz&amount=25&bidder=0xYOU" | jq '{required_usd, quote, calldata}'3 · send the tx (viem)
approve usdc for transfer_in (skip if allowance is already enough), re-quote, then bid(url, amount, maxPaid) with maxPaid = due + fee from that fresh quote. amount is micro-usdc: dollars × 1e6. if the price moved the tx reverts PaidExceedsMax instead of overcharging you.
import { createWalletClient, createPublicClient, http, parseAbi } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { base as chain } from "viem/chains"; const account = privateKeyToAccount(process.env.PRIVATE_KEY); const wallet = createWalletClient({ account, chain, transport: http(process.env.BASE_RPC_URL) }); const client = createPublicClient({ chain, transport: http(process.env.BASE_RPC_URL) }); const BASEBID = "0x7a6373BC4796971059824D5ce8DF71AdE54d0285"; const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; const url = "https://yourproject.xyz"; // canonical: https, lowercase host, no www/query/trailing slash const amount = 25n * 1_000_000n; // $25 const [due, fee, fromClaimable, transferIn] = await client.readContract({ address: BASEBID, abi: parseAbi(["function quote(string,address,uint256) view returns (uint256,uint256,uint256,uint256)"]), functionName: "quote", args: [url, account.address, amount], }); if (transferIn > 0n) { await wallet.writeContract({ address: USDC, abi: parseAbi(["function approve(address,uint256) returns (bool)"]), functionName: "approve", args: [BASEBID, transferIn] }); } // fresh quote right before sending; maxPaid = due + fee, zero tolerance const [due2, fee2] = await client.readContract({ address: BASEBID, abi: parseAbi(["function quote(string,address,uint256) view returns (uint256,uint256,uint256,uint256)"]), functionName: "quote", args: [url, account.address, amount], }); const hash = await wallet.writeContract({ address: BASEBID, abi: parseAbi(["function bid(string,uint256,uint256)"]), functionName: "bid", args: [url, amount, due2 + fee2] }); await client.waitForTransactionReceipt({ hash }); await fetch("https://basebid.lol/api/sync", { method: "POST" }); // optional; the indexer loop picks it up within ~30s4 · list ($2) or take over (2× #1) via x402
two calls: a free validate that returns a 10-minute quote token + price_micro, then the gated route with that token. @x402/fetch handles the 402 → sign → retry dance; the payment is a gasless usdc authorization. the 402 always asks for exactly price_micro — refuse to sign otherwise. the handler validates before settlement: any 4xx means no usdc moved.
# validate (free; runs moderation) curl -s -X POST https://basebid.lol/api/validate -H 'content-type: application/json' \ -d '{"product":"listing","url":"https://yourproject.xyz","name":"your project","description":"one line","category":"app","contract":"0x…"}' # → { token, price_usd: 2, price_micro: "2000000", moderation: "approve" | "pending" } # pay (node, @x402/fetch + viem account) import { wrapFetchWithPayment, x402Client, x402HTTPClient } from "@x402/fetch"; import { ExactEvmScheme } from "@x402/evm"; import { privateKeyToAccount } from "viem/accounts"; const signer = privateKeyToAccount(process.env.PRIVATE_KEY); const client = new x402Client().register("eip155:8453", new ExactEvmScheme(signer)); const http = new x402HTTPClient(client).onPaymentRequired(async ({ paymentRequired }) => { const offer = paymentRequired.accepts.find((a) => a.network === "eip155:8453"); if (offer?.amount !== price_micro) throw new Error("price moved — re-validate"); // never sign a different amount }); const payFetch = wrapFetchWithPayment(fetch, http); const res = await payFetch("https://basebid.lol/api/listing", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ token }) }); console.log(await res.json()); // { status: "listed", entry_id, moderation_status } · 202 { status: "pending_review" } if another wallet already listed it # takeover is identical with product:"takeover" → POST /api/takeover
- GET /api/board?page= — ranked board + totals + takeover
- GET /api/entry/:entry_id — one entry, position, history
- GET /api/feed — last 20 bids
- GET /api/quote?url=&amount=&bidder= — cost + calldata
- POST /api/validate — free; moderation + quote token
- POST /api/listing — x402 · $2
- POST /api/takeover — x402 · max($10, 2×#1)
- POST /api/sync — nudge the indexer after your tx
entry id = keccak256(utf8 bytes of the canonical url). the contract exposes entryId(string) if you'd rather ask it.