Skip to content

Ranked auctions

A ranked auction is a sealed-bid auction that runs fully on-chain. Bidders submit over the sale window; when the sale ends, the top maxTotal bids win. Optional: rebates, so all winners pay the same clearing price.

Ranked auctions are on-chain only — there’s no gated variant. They’re supported for LimitedEdition, standard Series, and Limited GenerativeSeries. See the support matrix for exact combinations.

const sale = (await client.collection.addSale({
highlightId,
type: "RANKED_AUCTION",
accessMode: "PUBLIC",
startAt: new Date().toISOString(),
endAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
price: "0.01", // reserve price, used for on-chain min
currency: "ETH",
maxPerTransaction: 1,
maxPerWallet: 5,
maxTotal: 100, // winners count
paymentRecipient: creatorAddress,
typeConfig: {
reserveBidAmount: "0.01",
rebateWinningBids: true,
},
})).data!;

typeConfig fields:

  • reserveBidAmount — minimum bid, as a decimal string of the currency’s major unit (e.g. "0.01" ETH).
  • rebateWinningBids — when true, all winners pay the clearing price (lowest winning bid); higher bids are rebated.

Each bid is a three-step flow: build, submit on-chain, confirm with the backend.

// 1. Build the bid transaction
const bid = (await client.collection.bidSale({
highlightId,
saleId: sale.id,
bidAmount: "0.05",
})).data!;
// bid is { chainId, to, data, value } — a flat contract-execution shape.
// 2. Submit it on-chain
const bidHash = await walletClient.sendTransaction({
to: (bid.to ?? undefined) as `0x${string}` | undefined,
data: bid.data as `0x${string}`,
value: BigInt(bid.value ?? "0"),
});
await publicClient.waitForTransactionReceipt({ hash: bidHash });
// 3. Tell the backend about the confirmed bid so it appears in auction state
await client.collection.confirmBid({
highlightId,
saleId: sale.id,
txHash: bidHash,
});

confirmBid is called after every bid (not at auction-end). It records the on-chain bid in the backend’s ranked-auction ledger so it shows up in collection/sale reads and is eligible to win.

To update an existing bid, include the bidId emitted by the on-chain bid event. The bidId isn’t in the bidSale SDK response — it’s in the EditionEdgeRankedAuctionBid log on the receipt of the first bid transaction. Read it from the receipt, persist it client-side, then pass it back:

await client.collection.bidSale({
highlightId,
saleId: sale.id,
bidAmount: "0.06",
bidId: existingBidIdFromReceipt,
});

Once endAt passes, winning bidders claim their tokens via claimSale — the same call used for fixed-price and Dutch auctions:

const claim = (await client.collection.claimSale({
highlightId,
saleId: sale.id,
amount: 1,
})).data!;
const claimHash = await walletClient.sendTransaction({
to: (claim.to ?? undefined) as `0x${string}` | undefined,
data: claim.data as `0x${string}`,
value: BigInt(claim.value ?? "0"),
gas: 500_000n,
});

The backend verifies the caller is on the winning ledger, builds a claim against the mechanic contract, and (if rebateWinningBids) issues the rebate in the same transaction.

Calling claimSale before endAt on a ranked auction returns AuctionNotEndedError (400).

The indexer tracks bids in real time. You can observe state via the collection get call — the sale object includes the current high bid and validity hash for the auction’s bid tree.

For server-side code that needs to react to bids, the packages/highlight backend maintains per-chain indexer DOs that watch RankedAuction events. You don’t call the indexer directly; you just poll client.collection.get at whatever cadence your UI needs.

ErrorWhen
SaleTypeNotSupportedError (400)Ranked auction on an unsupported collection (open editions, 1-of-1, collectors-choice series, open generative)
InvalidTypeConfigError (400)Missing reserveBidAmount or rebateWinningBids
AuctionNotEndedError (400)claimSale called before endAt on a ranked-auction sale
NoValidBidsError (400)Caller has no valid winning bids when trying to claim
Ranked + GatedRejected at validation — 400 with a schema error