Submitting a bid
This page covers two writes that happen before an auction can settle:
- Bidder:
placeBid— seal a bid amount into the auction's CDR vault. - Seller:
sealReserve— seal the reserve price into the auction's CDR reserve vault.
Both helpers are Node-only (WASM constraint; see Installation).
Placing a bid: placeBid
placeBid executes the full four-on-chain-transaction flow in one call:
WIP.approve(SealedAuction, deposit)— authorise the contract to pull the deposit.SealedAuction.allocateBidSlot(auctionId, deposit)— transfers WIP into escrow, allocates a CDR vault, emitsBidSlotAllocated(uuid).- Sign + encode the 149-byte
BidPayloadlocally (no network). - Encrypt with
encryptBid({uuid, plaintext})(TDH2). SealedAuction.submitEncryptedBid(auctionId, uuid, ciphertext)— writes ciphertext to the pre-allocated vault.
import {placeBid} from '@sealedip/sdk/bidder'
import {parseEther} from 'viem'
const result = await placeBid({
auctionId: 3n,
amount: parseEther('2.5'),
// deposit defaults to amount when omitted
bidderPrivateKey: process.env.BIDDER_KEY as `0x${string}`,
rpcUrl: 'https://aeneid.storyrpc.io',
})
console.log({
bidderAddress: result.bidderAddress,
uuid: result.uuid,
approveTxHash: result.approveTxHash,
allocateTxHash: result.allocateTxHash,
submitTxHash: result.submitTxHash,
})
PlaceBidInput
interface PlaceBidInput {
auctionId: bigint
amount: bigint // bid amount in wei (WIP)
/** Escrow deposit. Defaults to `amount` when omitted. */
deposit?: bigint
bidderPrivateKey: `0x${string}`
rpcUrl?: string // defaults to the Aeneid RPC
}
deposit can exceed amount — it is the ceiling visible to the contract.
The bid can never win more than deposit, so setting deposit > amount is
safe and hides the exact bid amount from observers who watch deposit events.
PlaceBidResult
interface PlaceBidResult {
bidderAddress: `0x${string}`
uuid: number // CDR vault uuid for this bid slot
nonce: `0x${string}` // 32-byte random nonce used in the payload
ciphertextHex: `0x${string}`
approveTxHash: `0x${string}`
allocateTxHash: `0x${string}`
submitTxHash: `0x${string}`
}
Multiple bids on the same auction
Call placeBid multiple times. Each call allocates a new slot with an
independent deposit. At settle, the contract picks the highest valid bid
across all slots.
Retry semantics
placeBid is not idempotent. If step 2 (allocate) succeeds and a later
step fails, a re-run allocates a second slot and transfers a second deposit.
To retry only the failed step, call submitEncryptedBid directly with the
existing uuid from the first attempt.
Sealing the reserve: sealReserve
The seller seals the reserve price into the CDR reserve vault that
createAuction allocated. This is optional: if the seller never calls
sealReserve, the contract defaults the floor to 0 at settle.
sealReserve mirrors placeBid without the deposit/approve steps, because
the reserve vault is allocated at auction creation.
import {sealReserve} from '@sealedip/sdk'
// also importable from '@sealedip/sdk/seller'
import {parseEther} from 'viem'
const result = await sealReserve({
auctionId: 3n,
reserve: parseEther('1.0'),
sellerPrivateKey: process.env.SELLER_KEY as `0x${string}`,
rpcUrl: 'https://aeneid.storyrpc.io',
})
console.log({
sellerAddress: result.sellerAddress,
reserveUuid: result.reserveUuid,
submitTxHash: result.submitTxHash,
})
What sealReserve does
- Calls
getAuction(auctionId)to readreserveUuid. - Signs the reserve amount with the same
signBidDigest/personal_signscheme as bids (signer field is the seller address). - Encodes the 149-byte payload and TDH2-encrypts it bound to
reserveUuid. - Calls
SealedAuction.submitEncryptedReserve(auctionId, ciphertext).
SealReserveInput
interface SealReserveInput {
auctionId: bigint
reserve: bigint // reserve price in wei (WIP)
sellerPrivateKey: `0x${string}`
rpcUrl?: string
}
SealReserveResult
interface SealReserveResult {
sellerAddress: `0x${string}`
reserveUuid: number // CDR vault uuid (same as Auction.reserveUuid)
nonce: `0x${string}`
ciphertextHex: `0x${string}`
submitTxHash: `0x${string}`
}
Reserve constraints
- Seller-only:
submitEncryptedReservereverts withNotSellerfor any other caller. - One-shot: once written, the reserve ciphertext cannot be overwritten
(
ReserveAlreadyWritten). - Must be called before
deadline. - Auction must be in
Openstate.
Error reference
| Error | When it occurs |
|---|---|
DepositTransferFailed | WIP balance or approval was insufficient |
BiddingClosed | Deadline has passed; no new bids accepted |
CiphertextAlreadyWritten | A bid for this slot was already submitted |
ZeroDeposit | Deposit amount was 0 |
NotSeller | submitEncryptedReserve called by a non-seller address |
ReserveAlreadyWritten | Seller attempted to overwrite the reserve ciphertext |
UnknownAuction | auctionId does not exist |
Reading auction state before bidding
import {getAuction} from '@sealedip/sdk/auction'
const auction = await getAuction(3n)
// auction.state must be 'Open'
// auction.deadline > Date.now() / 1000
// auction.reserveHasCiphertext is true once the seller has sealed the reserve
See Types: Auction for the full field list.