Shipped 2026-09-09. A paid, labelled row on the home page. It is advertising, and it is sealed off from everything organic. This page is the contract.
What a buyer gets
- A card in a row on the home page headed Sponsored · paid placement, under the disclosure Paid placement. Not a ranking; scores, feeds and the API ignore it. Each card carries Sponsored · ends in N days.
- Two slots exist. A placement lasts 3 or 7 days and then comes down automatically.
- Nothing else. No score, no status, no position in the listing, no feed entry, no API field, no mention on the project page.
What it costs and where it goes
Everything is an admin setting in Console → Placements (/admin/placements), stored in
site_settings under the key sponsored:
| Setting | Meaning |
|---|---|
| Treasury address | The address every payment is sent to. The founder sets it; Dawnscan never holds a key. |
| Payment token | An ERC-20 on Robinhood Chain (4663). Decided with the founder: USDG, 0x5fc5360d0400a0fd4f2af552add042d716f1d168, until $DAWN is live; then Use $DAWN as the payment token switches to the official contract and holders (M13-A tiers) pay their tier price. |
| Token check | Check token on chain reads symbol() and decimals() from the contract and stores them. A placement cannot open before this, and changing the token address clears it. |
| Prices | Whole tokens for 3 days and for 7 days (defaults 100 and 200). Base units are derived from the stored decimals at purchase time. |
| Enabled | Off by default. Can only be turned on once the treasury, a checked token and both prices exist. |
A buyer must have a wallet linked to their account (/account, M13-A): the route takes the payer
from that link and the worker requires the transfer to come from it.
A slot snapshots the token, treasury, decimals and amount it was quoted at, so changing a setting later never re-prices a placement someone already paid for.
How a purchase works
- Anyone signed in opens
/sponsor/<slug>for a published project (QUALIFIED or VERIFIED_BUILDER, approved, not a demo). No claim is required; the buyer's account and payer address are recorded. - The page quotes both tiers, names the token and the treasury with explorer links, and states the terms. The wallet flow (RainbowKit, WalletConnect) sends one ERC-20 transfer of exactly the quoted base units.
- The browser posts
{ txHash, payer, tierDays }toPOST /api/projects/<slug>/sponsor. The row is created asPAYMENT_SUBMITTED, which reserves the slot, andVERIFY_SPONSORED_PAYMENTis queued. - The worker reads the receipt from Robinhood Chain and accepts it only if it is successful,
on chain 4663, sent by the recorded payer, and contains a transfer of the recorded token
from the payer to the recorded treasury of at least the recorded amount. Then the slot
becomes
ACTIVE,starts_atis now andends_atis the tier later. Anything else isREJECTEDwith the reason stored on the row; a receipt that is not there yet retries (eight attempts). SPONSORED_EXPIREruns hourly:ACTIVEpastends_atbecomesENDED; aPAYMENT_SUBMITTEDrow older than 24 hours becomesREJECTEDand its slot and hash are released.
Refusals a buyer can meet: placements closed, project not published, both slots taken (the page says when the next frees up), the project already has a live or pending placement, or the same transaction hash submitted twice.
Rules
- No refunds. Stated on the request page before the wallet is offered.
- The payer is asserted, not proven. As with paid research requests, Dawnscan verifies that the
recorded payer sent the transfer, not that the person signed in controls that address. The
worst case is a stranger registering a public transfer to the treasury as a placement for a
project. See
PAID_REQUESTS_LAUNCH_GATE.md. - The founder can end a live placement early or reject a waiting submission from the console, each with a reason, and re-verify a submission whose job failed.
- Every state change is in
audit_logasplacement.settings.set,placement.token.checked,placement.submitted,placement.activated,placement.rejected,placement.ended_early,placement.expired. - Featured stays what it is: chosen by the team, never paid, a separate row.
Neutrality, enforced
packages/domain/src/hey/neutrality.test.tsfails the suite if any scoring, ranking, discovery or refresh module, or any public ranking page, references the placement service, the ledger or the payment evaluator. The home page may reference exactly one function,listSponsoredCards, which returns slugs and end times and nothing about who paid.packages/domain/src/moderation/rbac.test.ts: only ADMIN holdsplacement.manage.apps/web/e2e/moderation.spec.tsstill bans paid wording on the listing and project pages, and on the home page outside the labelled row.apps/web/e2e/sponsor.spec.ts: while unconfigured the API answers 409 and the request page shows plain text, no wallet, no address, no RPC traffic.sponsored.integration.test.tsandverify-sponsored-payment.integration.test.tscover the lifecycle and the receipt checks, and assert no score or research priority changes.
Rollout
- Deploy (migration
0050_sponsoredruns before restart).POST /api/projects/<slug>/sponsoranswers 409;/admin/placementsis 404 anonymously. - In the console: paste the treasury address and the USDG contract, Check token on chain
(expect
USDG, 6 decimals), set prices, leave enabled off. - Dry run: set both prices to
1, enable, buy a 3-day slot with a test wallet, watch the worker log activate it, confirm the row on/, confirm/api/projects,/api/shipsand the feeds are unchanged, end it early from the console. Then set the real prices.