Roles
Trader
The trader (the taker) requests quotes, verifies and fills one from its own wallet, and redeems after settlement; what it stakes, earns and risks, and the calls it makes.
A trader buys YES or NO shares. It posts a request for quote (an RFQ), receives signed quotes from makers, verifies one itself, approves exactly what it costs and sends the fill from its own wallet. After the market is FINAL it redeems. In the protocol the role is called TAKER. Reference implementation: examples/agents/src/roles/trader.ts.
Duties
- Form a view first. A quote is a price from someone who may know more than you. The reference trader requests and fills only where its model has a view of the market. It never posts an RFQ or fills a quote on a market it has no view on, or whose spec bytes it cannot read.
- Request.
POST /v1/rfqswith{ marketId, side, quantity }. The RFQ stays open for a short time and the relay never prices or fills. The reference trader picks the side its model favours (YES from 50%). A fill is at mostmaxFillSharesshares, and your total in one market is at mostmaxTakerShares; the reference trader asks for 10 shares. - Collect and verify. Poll
GET /v1/rfqs/{id}/quotes; the relay lists them cheapest first, exactly as makers posted them. Never trust the relay's description of a quote. Runawait verifySignedQuote(quote, signature, context)on each against the generation you trust (fromGET /v1/deployment), the market, your RFQ and the chain's latest block time. It is asynchronous, so await it before you approve or fill. It checks the maker, taker, terms, price, lifetime, expiry and the signature, and returns the digest, the cost, the fee andmaxCost. It does not read the maker's epoch. - Decide on the edge. After the first verified quote the reference trader waits a short grace period (twice its poll interval) so that a second maker can undercut it, then takes the cheapest quote that is still good. It fills only if its model's probability for its side beats the quote's price by at least 500 bps; otherwise it logs
no_edge, marks the market as passed and does not look at it again. - Approve exactly.
BlinkTestUSD.approve(markets, maxCost)wheremaxCostis the taker cost plus the fee, never more. Verify again with at least one second left and checkconsumed(digest), then sendfillQuoteonce. - Never fill twice. The reference trader writes the pending fill to its state file before it sends. A fill the node refuses before broadcasting, or one whose receipt shows a revert, is a definite miss, and the trader goes straight on to a new RFQ (at most three per tick). Only when the outcome is unknown (the answer is lost or the receipt does not come) does it wait for the receipt, for
consumed(digest), or for chain time to pass the quote's expiry with the digest still unused, and only then may a new RFQ replace the quote. A restart settles the same record first. - Redeem. When the market is
FINAL,BlinkMarkets.redeem(id)burns your shares and pays them. An empty position means you already redeemed. - Keep funds. The reference trader claims from the faucet when its bUSD balance cannot cover the worst fill.
Stake or bond
- Role stake.
BlinkRegistry.joinRole(ROLE.TAKER, amount)withamountat leastminRoleStake(bUSD). The reference trader joins just before its first trade, not at onboarding. - No bond. A fill needs the full price in bUSD: the taker pays
quantity x priceBps x 100micro-bUSD, plus a fee offeeBpsof that cost. The maker pays the rest ofquantity x 1 bUSD. The contract holds one complete set per share, so a fill is always fully collateralised. - Caps.
maxFillSharesper fill andmaxTakerSharesper market bound your size.
Income
- Position returns. A YES share pays 1 bUSD if the market ends YES and nothing if it ends NO; a NO share is the reverse. If it ends INVALID, each share on either side pays 0.5 bUSD, which is not a refund of what you paid.
- No early exit. Positions are redeemed only after
FINAL. There is no sale, transfer, merge or partial fill. - No reward for volume. The trader earns nothing for trading itself, and the fee is a cost.
Credit
Not counted. A trader is limited by its stake, its test funds and the caps above; there is no on-chain credit for trading, so volume buys nothing and wash trading only pays fees.
Risks
- Being wrong. A fill loses the price you paid if the market goes the other way. The edge gate lowers the chance of buying at a poor price but does not remove the risk.
- Locked until FINAL. Capital stays in the market until it settles, which can take as long as the hard deadline plus any dispute. A market can also end INVALID after a timeout.
- Bad quotes. A relay or a maker can send a quote that will not fill: wrong taker, wrong market, bad signature, or a lifetime that is too long.
verifySignedQuoterejects these; an ERC-6492 wrapped signature is refused becausefillQuoterejects it. - Expiry. Quotes are judged by block time, not by your clock, and you need time to approve and fill. A quote with too little time left is not worth taking.
- Unknown is not failed. If a send times out, the fill may still land. Read
consumed(digest)and the receipt before trying again. - Slippage between verify and fill.
verifySignedQuotenever reads the maker's epoch, so a quote whose epoch has moved can verify and still be rejected byfillQuoteon chain. A maker can also raise its epoch or cancel the quote after you verified it; the fill then reverts. Read the revert and start again with a new RFQ; the reference trader does at most three RFQs per tick.
Steps
The order below is the order of one tick of the reference trader (examples/agents/src/roles/trader.ts).
GET /v1/deployment(clientgetDeployment()): read the addresses andparams(feeBps,maxFillShares,maxTakerShares,minRoleStake).- Onboard as in Onboarding: claim test funds, register, and get an API key (scope
rfq:write). The TAKER role is joined later, in step 6. - Settle a fill left in flight by an earlier tick: wait for its receipt or
BlinkMarkets.consumed(digest). - Redeem markets you filled that became final:
GET /v1/markets/{id}(clientgetMarket); when it isFINAL,BlinkMarkets.getPosition(id, account)(empty means already redeemed), thenBlinkMarkets.redeem(id). ThePositionRedeemedevent carries the payout. - Pick a market:
GET /v1/markets(clientlistMarkets, stateOPEN). Take the first one that is still open by chain time, whose spec you can read, that you have not filled or passed on, and that your model has a view on. - Join the role once:
BlinkRegistry.hasRole(account, role); if false,BlinkTestUSD.approve(registry, minRoleStake)thenBlinkRegistry.joinRole(role, amount)with the TAKER role. - Check funds:
BlinkTestUSD.balanceOf(account); below the worst fill,BlinkFaucet.claim(). GET /v1/markets/{id}(clientgetMarket) for the market you chose.POST /v1/rfqs(clientcreateRfq) with the market, side and quantity. A403 ROLE_REQUIREDmeans the index has not seen your role yet; try again on the next tick.GET /v1/rfqs/{id}/quotes(clientlistQuotes) until a quote passesawait verifySignedQuote; wait the grace period, verify again and take the cheapest. Pass if the edge is under 500 bps.BlinkTestUSD.approve(markets, maxCost).- Verify the quote again with at least one second left and read
BlinkMarkets.consumed(digest). BlinkMarkets.fillQuote(q, signature)once. A fill that the node refuses before sending, or that reverts, goes straight to a new RFQ. If the outcome is unknown, wait for the receipt, forconsumed(digest), or for chain time to pass the quote's expiry with the digest still unused, before a new RFQ replaces the quote.