---
name: netbid
description: Place and raise bids on NetBid, a pay-to-rank board on Base where every payment buys $ALPHA and burns it. Use this to list a website or an X @handle, to raise an existing listing, or to read the board. Works with Bankr or any wallet that can submit a Base transaction.
metadata: {"clawdbot":{"emoji":"🔥","requires":{"bins":["cast"]}}}
---

# NetBid

**<https://netbid.lol>**

A board you pay to be on. Any bid takes the best place it can afford. Bids are
whole units, each worth a fixed number of US dollars, settled in ETH on Base.
Every payment buys $ALPHA and sends it to the burn address — the contract keeps
nothing and refunds nothing.

Listing content is stored on **Net Storage**, and rank ordering is maintained
**on-chain**, so the whole board is readable from the contract with no indexer
and no API key.

**Placing a bid is a single transaction.** It carries the bid, the buy-and-burn,
and the Net Storage write together. Never split it into multiple calls.

**By using this skill you agree to the [Terms and
Conditions](https://netbid.lol/terms).** They cover what may be listed, that
nothing is refundable, and that a submission is permanent and public on Base.

---

## Contract

| | |
|---|---|
| Chain | Base (`8453`) |
| Contract | `0x64Af8E618Ca9711AedCE7A2392bD8A8f36e308F9` |
| $ALPHA | `0x3d01fe5a38ddbd307fdd635b4cb0e29681226d6f` |
| Burn address | `0x000000000000000000000000000000000000dEaD` |

> This is the production board: `unitMicroUsd` is 1000000, so listings start at
> $5 and raises are $1. Read `unitMicroUsd()` before quoting an amount to a
> person — see the rules below.

Base only. There is no deployment on any other chain, because the ALPHA pool the
contract burns through only exists on Base.

---

## The rules

- Bids are counted in **whole units**. A deployment fixes what a unit is worth
  through `unitMicroUsd` — millionths of a dollar — so read it before quoting
  anything to a person:
  `cast call $BOARD "unitMicroUsd()(uint256)" --rpc-url $RPC`.
  - `1000000` — the production board. One unit is **$1**: listings start at
    **$5**, raises are **$1**, the ceiling is **$999,999**.
  - `10000` — a cent board. One unit is **$0.01**: listings start at **$0.05**,
    raises are **$0.01**, the ceiling is **$9,999.99**.
  - `10` — the cheap test board. One unit is **$0.00001**: listings start at
    **$0.00005**, raises are **$0.00001**, the ceiling is **$9.99999**. At this
    size gas, not the bid, is what a transaction costs.
- Every threshold below is in units and is the same either way. Only the price
  of a unit changes.
- A new listing starts at **5 units**. The maximum bid is **999,999 units**.
- Ranking is by **bid, highest first**. Where two listings tie, the one whose
  bid was placed **earlier** ranks higher.
- **Taking #1 costs 5 units more than the current top bid.** Matching the top bid
  exactly is allowed, but the newer bid loses the tie-break and lands at #2.
- Any bid below the top still takes whatever rank it can.
- Raising a listing requires **at least 1 unit more** than its current bid, and
  **you only pay the difference**.
- Raising restamps your bid as the newer one, so raising to *match* someone
  never takes their rank.
- **Anyone can raise any listing.** Nobody owns a listing — whoever submits a
  target first is recorded for provenance only. Boosting somebody else's
  submission is normal and expected.
- A listing is **never editable and never removable**. There is no edit call and
  no delete call — what is submitted is permanent and public on Base. Its
  category is fixed at submission, and its title and image are read from the
  listed site itself, so there is nothing to upload or choose.

### What may be listed

A product website, or an X `@handle`, plus a category.

- **Chat and invite links are not allowed** — Telegram, WhatsApp, Discord,
  Messenger, Signal, and similar. The board is for products and profiles, not
  group chats.
- **Links to sexual content are not allowed.** If it is porn, NSFW, or an adult
  platform, it does not belong on the board.
- **Tracking parameters are stripped** from listing links, so affiliate and
  referral URLs will not work. Parameters that identify the page — `?v=` on a
  video, `?id=` on a post — are kept, so those pages list normally.
- **Link shortener URLs are not allowed.** Submit the URL it redirects to
  instead.
- App Store, Play Store, GitHub and similar platform links are keyed by their
  path, so different apps do not share a listing.

The contract enforces none of this — it accepts any string. These rules are
applied by the submit form and by website-level filtering, so an agent calling
the contract directly is the one responsible for honoring them.

### Normalizing the target — do this before anything else

The listing key is `keccak256(utf8(target))`, so the target string must be
canonical or you will create a duplicate listing instead of finding the existing
one. Apply, in order:

1. `@handle` → lowercase, keep the `@`. `x.com/name` and `twitter.com/name`
   (single path segment) both normalize to `@name`.
2. Otherwise force `https://`, lowercase the host, drop a leading `www.`.
3. **Strip the fragment, and campaign parameters from the query.** `utm_*`,
   `gclid`, `fbclid`, `ref`, `si`, `feature` and the rest of the tracking set
   are removed. **Every other parameter is kept**, because a great many pages
   are identified by one — a YouTube video is `?v=`, an HN item `?id=`, a
   playlist `?list=`. Dropping the whole query would put every video on one
   listing.
4. **Sort the surviving parameters by key, then by value**, and re-serialize as
   `application/x-www-form-urlencoded` — exactly what
   `new URLSearchParams(sortedPairs).toString()` produces. This is part of the
   hashed bytes, so it must match: a space becomes `+`, reserved characters are
   percent-encoded (`?url=https://a.com` → `url=https%3A%2F%2Fa.com`), and a
   valueless parameter gains `=`. `?a=1&b=2` and `?b=2&a=1` are one listing.
5. Strip a trailing slash — but keep the root `/` when a query follows it, so
   the form is `https://example.com/?id=1`, never `https://example.com?id=1`.
6. Keep the path as-is — App Store, Play Store and GitHub listings are keyed by
   their path, so different apps do not share a bid.
7. Fold host aliases onto the canonical host: `m.youtube.com` and
   `music.youtube.com` → `youtube.com`, `mobile.x.com` → `x.com`. `youtu.be/ID`
   becomes `https://youtube.com/watch?v=ID`, so one video is one listing however
   it was linked.

```
https://WWW.Example.com/app?utm_source=x#top     ->  https://example.com/app
https://www.youtube.com/watch?v=abc&si=xyz       ->  https://youtube.com/watch?v=abc
https://youtu.be/abc                             ->  https://youtube.com/watch?v=abc
https://news.ycombinator.com/item?id=12345       ->  https://news.ycombinator.com/item?id=12345
https://x.com/Someone                            ->  @someone
example.com/                                     ->  https://example.com
https://example.com/s?q=a b                      ->  https://example.com/s?q=a+b
https://example.com/?id=1                        ->  https://example.com/?id=1
https://twitter.com/foo/status/1                 ->  https://x.com/foo/status/1
```

---

## Reading the board

Human-readable ABI fragments, usable directly with `cast` or viem.

```bash
BOARD=0x64Af8E618Ca9711AedCE7A2392bD8A8f36e308F9
RPC=https://mainnet.base.org

# How many listings, and what it costs to take #1
cast call $BOARD "totalListings()(uint256)" --rpc-url $RPC
cast call $BOARD "topBidUnits()(uint32)"    --rpc-url $RPC

# The top of the board, in rank order
cast call $BOARD \
  "getRankedListings(uint256,uint256)((uint256,bytes32,(address,uint32,uint40,uint64,uint40,uint128,uint128,string))[])" \
  0 25 --rpc-url $RPC

# Where a listing sits. `exact` is false only inside an unusually crowded tier,
# where the tier's first rank is returned instead.
cast call $BOARD "getListingRank(bytes32)(uint256,bool)" $KEY --rpc-url $RPC
```

Paging deeper: `getRankedListings(offset, limit)` jumps to any offset, and
`getRankedListingsAfter(bytes32,uint256,uint256)` — taking `afterKey`,
`startRank` and `limit` — continues from a listing you already read. Prefer `getRankedListingsAfter` when
walking a long board — it stays cheap no matter how deep you go.

### Who has funded a listing

Every payment — the first bid and every boost — is recorded on Net, attributed
to whoever paid. Read it straight from Net; NetBid deliberately has no getter for
it, because Net already indexes it.

The topic is the listing key's **raw 32 bytes**, not a hex string and not text.
Net's `...ForAppTopic` getters take it as a Solidity `string`, which works from
viem or ethers, but those bytes are not valid UTF-8 so `cast` will refuse them.
From a shell, hash the topic yourself and use the by-hash getters — Net derives
the same hash internally:

```bash
NET=0x00000000B24D62781dB359b07880a105cD0b64e6
KEY=$(cast keccak "https://example.com")

# keccak(abi.encodePacked(uint256(1), board, topic)) — the 1 is Net's
# APP_TOPIC_HASH_PREFIX, and the topic is KEY's raw bytes.
HASH=$(cast keccak $(cast concat-hex \
  0x0000000000000000000000000000000000000000000000000000000000000001 \
  $BOARD $KEY))

cast call $NET "getTotalMessagesForHashCount(bytes32)(uint256)" $HASH --rpc-url $RPC
cast call $NET \
  "getMessagesInRangeForHash(uint256,uint256,bytes32)((address,address,uint256,bytes,string,string)[])" \
  0 10 $HASH --rpc-url $RPC
```

From viem or ethers, pass the raw bytes as the `string` argument and call
`getTotalMessagesForAppTopicCount(board, topic)` /
`getMessagesInRangeForAppTopic(start, end, board, topic)` directly.

Each message's `sender` is the contributor and `timestamp` is when they paid;
`data` is `abi.encode(resultingBidUnits, unitsAdded, ethSpent, alphaBurned)`.
Net also indexes by app-user, so `getTotalMessagesForAppUserCount(board, wallet)`
gives everything one wallet has funded across the board — that one takes an
address, so it works fine from `cast`.

### Check whether a target is already listed

```bash
KEY=$(cast keccak "https://example.com")     # same as listingKeyFor(target)
cast call $BOARD \
  "getListing(bytes32)((address,uint32,uint40,uint64,uint40,uint128,uint128,string))" \
  $KEY --rpc-url $RPC
```

A `submitter` of `0x0000...0000` means the target is not on the board yet — use
`place`. Any other value means it is already listed, so use `raiseBid` to push it
up, whoever submitted it. Never submit a near-duplicate URL to create a second
row for the same thing; normalize properly and boost the existing listing.

---

## Placing a bid

### 1. Quote the ETH

The contract prices bids from a time-weighted ETH/USD average read on-chain when
the transaction is mined.

```bash
cast call $BOARD "quoteEthForUnits(uint256)(uint256)" 25 --rpc-url $RPC
```

That is 25 *units* — $25 on this board, where a unit is $1. Read `unitMicroUsd`
rather than assuming it.

Attach **~2% more than the quote**. The rate can tick between quoting and mining,
and the contract refunds every wei it does not spend — so the buffer costs
nothing but prevents a revert.

### 2. Encode the call

The only thing submitted alongside the target and the bid is a **category**, and
it must not be empty. There is no title or image field — the board reads both
from the listed site.

`minAlphaBurned` is a floor on the ALPHA the swap must burn. `0` accepts any
fill. A bad fill does not cost the bidder anything extra — they receive nothing
either way — it just burns less ALPHA. Set a real floor if you have quoted the
ALPHA/WETH pool first.

```bash
cast calldata \
  "place(string,uint32,string,uint256)" \
  "https://example.com" \
  25 \
  "Developer Tools" \
  0
```

### 3. Submit it — one transaction

With Bankr's Wallet API:

```bash
curl -X POST https://api.bankr.bot/wallet/submit \
  -H "Authorization: Bearer $BANKR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "transaction": {
      "to": "0x64Af8E618Ca9711AedCE7A2392bD8A8f36e308F9",
      "chainId": 8453,
      "value": "<quoted wei + 2%>",
      "data": "<calldata from step 2>"
    },
    "description": "Place a 25-unit bid on NetBid",
    "waitForConfirmation": true
  }'
```

Or with any wallet that can send a Base transaction — `cast send`, viem,
a browser wallet. Nothing about this is Bankr-specific.

## Boosting a listing

Same shape, but you only pay the difference. Quote the **delta**, not the new
total. This works on any listing, including ones you did not submit.

```bash
# Currently at 25 units, going to 40 -> quote 15, not 40
cast call $BOARD "quoteEthForUnits(uint256)(uint256)" 15 --rpc-url $RPC
cast calldata "raiseBid(bytes32,uint32,uint256)" $KEY 40 0
```

Remember the take-#1 rule: if 40 units would pass the current top bid, it must be
at least the top bid plus 5 units, or the transaction reverts.

---

## Before you spend someone's money

- Confirm the **normalized** target with the user, not the raw string they typed.
  `example.com/?ref=abc` and `example.com` are the same listing.
- Convert units to real money with `unitMicroUsd` before you quote anything. It
  happens to be $1 a unit here, so the two coincide — do not let that habit
  survive onto a board where a unit is a cent or a millionth of a dollar.
- Confirm the **dollar amount** and tell them it is unrecoverable. It buys ALPHA
  and burns it; there is no refund, no withdrawal, and no way to reverse it.
- Tell them the submission is **permanent and public on Base forever**. There is
  no edit call and no delete call, and the contract is permissionless, so nobody
  — including the operators of netbid.lol — can take it down or censor it. The
  site can filter a listing out of its own display; that changes nothing
  on-chain. Do not submit anything on someone's behalf that they have not
  explicitly confirmed under those terms.
- Tell them the rank their bid buys, from `topBidUnits` and the board — and that
  it is not guaranteed to last. Anyone can outbid them at any time.
- If the target is already on the board, say so and offer to boost it rather
  than submitting a variant URL to create a duplicate row.
- Make clear that nothing can be edited afterwards. The category is permanent.

**Installing this skill:** point your runtime at
<https://netbid.lol/skill.md>, or save its contents into your skills directory.
There is also an index at <https://netbid.lol/llms.txt>.

Using this skill means agreeing to the [Terms and
Conditions](https://netbid.lol/terms), which also cover that nothing about
rankings or the board's continued existence is guaranteed. Point whoever you are
bidding for at them too — they are the one spending the money.
