Skip to content

Auction Relayer Reference

Canonical WebSocket channels, payload shapes, validations, acknowledgments, broadcasts, and signature semantics for the Auction Relayer.

For a quick start and a minimal working bot, see Guides → Build a Auction Bot.

Endpoint

  • Production: wss://api.sapience.xyz/auction (confirm base URL)
  • Local dev (monorepo): ws://localhost:3001/auction

Message flow

  • auction.start → relayer replies auction.ack and broadcasts auction.started to all clients
  • auction.subscribe → client subscribes to a specific auctionId to receive auction.bids
  • bid.submit → relayer replies bid.ack and broadcasts auction.bids to subscribers of that auctionId

Auctions are short-lived (~60s TTL). The socket that sends auction.start is auto-subscribed to that auction channel.

Schemas

auction.start

Client → Relayer

{
  type: 'auction.start',
  payload: {
    maker: string,              // 0x... EOA
    wager: string,              // wei string (>= 1)
    resolver: string,           // 0x... resolver contract
    predictedOutcomes: string[] // bytes strings (non-empty)
  }
}

Acknowledgment

{ type: 'auction.ack', payload: { auctionId: string } }

Broadcast to all clients

{ type: 'auction.started', payload: {
    auctionId: string,
    maker: string,
    wager: string,
    predictedOutcomes: string[],
    resolver: string
}}

auction.subscribe

Client → Relayer

{
  type: 'auction.subscribe',
  payload: { auctionId: string }
}

If accepted, the relayer will immediately stream current bids, if any:

{ type: 'auction.bids', payload: { auctionId: string, bids: ValidatedBid[] } }

bid.submit

Taker → Relayer

{
  type: 'bid.submit',
  payload: {
    auctionId: string,
    taker: string,         // 0x... EOA
    takerWager: string,    // wei string (>0)
    takerDeadline: number, // unix seconds (future)
    takerSignature: string // 0x... hex (typed signature)
  }
}

Acknowledgment to submitting socket

{ type: 'bid.ack', payload: { error?: string } }

Broadcast to auction subscribers of this auctionId

{ type: 'auction.bids', payload: {
    auctionId: string,
    bids: Array<{
      auctionId: string,
      takerSignature: string,
      taker: string,
      takerWager: string,
      takerDeadline: number,
    }>
}}

Notes

  • auctionId is generated by the relayer (crypto.randomUUID()), with ~60s TTL.
  • The relayer buffers all valid bids; selection is client-side (e.g., highest takerWager among non-expired).

Validation

Server-side checks (summarized from packages/api/src/auction/sim.ts, helpers.ts, registry.ts, and ws.ts).

Auction payload

  • wager must parse to BigInt and be > 0
  • predictedOutcomes must be an array with ≥1 element, each a non-empty bytes string
  • resolver must be provided (0x address expected by downstream)
  • maker must be a valid 0x address (40 hex)

Bid payload

  • auctionId must be a non-empty string of an active auction
  • taker must be a valid 0x address (40 hex)
  • takerWager must parse to BigInt and be > 0
  • takerDeadline must be a finite number strictly greater than now (unix seconds)
  • takerSignature must be a hex string starting with 0x and a sensible length

If any check fails, bid.ack includes an error string. Common reasons:

  • invalid_payload
  • invalid_auction_id
  • invalid_taker
  • invalid_taker_wager
  • invalid_wager_values
  • quote_expired
  • invalid_taker_bid_signature_format
  • auction_not_found_or_expired

Signature semantics (strict mode)

Basic format checks are always applied. When chain and contract addresses are configured, the relayer performs optional EIP-712 verification (see verifyTakerBidStrict). This check is best-effort: failures are logged but do not reject the bid if basic validation passed.

Typed inner message hash (Solidity-compatible):

// encodeAbiParameters([
//   { type: 'bytes' },        // encodedPredictedOutcomes (first entry)
//   { type: 'uint256' },      // takerWager
//   { type: 'uint256' },      // maker wager
//   { type: 'address' },      // resolver
//   { type: 'address' },      // maker
//   { type: 'uint256' },      // takerDeadline
// ], [ ... ]) → keccak256(inner) → messageHash

EIP-712 domain:

{ name: 'SignatureProcessor', version: '1', chainId, verifyingContract }

Types and primary type:

Approve: [
  { name: 'messageHash', type: 'bytes32' },
  { name: 'owner', type: 'address' },
]
// message: { messageHash, owner: taker }

The taker signs this Approve typed data; the server verifies with verifyTypedData from viem.

Rate limits and sizes

  • 100 messages per 10s window; on exceed → close with code 1008, reason rate_limited
  • Message size > 64,000 bytes → close with code 1009, reason message_too_large

Expiration and subscriptions

  • Auctions expire after ~60 seconds (internal TTL); expired auctions are removed.
  • The socket that sends auction.start is auto-subscribed to that auctionId.
  • Other clients may call auction.subscribe to receive auction.bids for that auctionId.
  • auction.bids broadcasts only to subscribers of that auctionId.

On-chain flow context

Relayer focuses on off-chain matching and signature validation. On-chain minting and settlement are handled by PredictionMarket.sol (and related contracts). Key checks performed on-chain at mint include:

  • Maker must be the caller (MakerIsNotCaller)
  • takerDeadline must be in the future (TakerDeadlineExpired)
  • Non-zero collaterals and above minimum (MakerCollateralMustBeGreaterThanZero, TakerCollateralMustBeGreaterThanZero, CollateralBelowMinimum)
  • Non-empty encodedPredictedOutcomes (InvalidEncodedPredictedOutcomes)
  • Taker signature validity (EOA or ERC-1271) over the same preimage described above (InvalidTakerSignature)
  • Market validation via resolver (InvalidMarketsAccordingToResolver)

Both parties must set ERC-20 approvals to allow the contract to pull their collateral.

Example messages

auction.start

{
  type: 'auction.start',
  payload: {
    maker: '0xMaker123...',
    wager: '1000000000000000000',
    resolver: '0xResolver456...',
    predictedOutcomes: ['0xabc123']
  }
}

bid.submit

{
  type: 'bid.submit',
  payload: {
    auctionId: 'c6b2d5bb-...-1f25',
    taker: '0xTaker789...',
    takerWager: '500000000000000000',
    takerDeadline: Math.floor(Date.now()/1000) + 60,
    takerSignature: '0x' + '11'.repeat(32) + '22'.repeat(32)
  }
}

Implementation pointers

  • See packages/api/src/auction/botExample.ts for a working taker reference
  • Message and type definitions live in packages/api/src/auction/types.ts
  • Server behavior: ws.ts, registry.ts, sim.ts, helpers.ts