DEV Community

Wilson
Wilson

Posted on • Originally published at gist.github.com

Time and Deadlines in Compact: Block Time, Counters & the Unit<16> Problem

Time and Deadlines in Compact: Block Time, Counters & the Unit<16> Problem

A practical guide to writing time-based smart contracts on Midnight Network

Why This Matters

Most smart contracts need time. Auctions end at a deadline. Escrows expire if unclaimed. On traditional chains, you read block.timestamp. On Midnight, privacy changes everything — you can't simply read the timestamp because that would leak information.

Midnight's Compact language provides block time comparison circuits that enforce deadlines without revealing the actual time.

The Block Time API

export circuit isBefore(deadline: Uint<64>): [] {
  return assert(blockTimeLt(disclose(deadline)), "Deadline has passed");
}

export circuit isAtOrAfter(start: Uint<64>): [] {
  return assert(blockTimeGte(disclose(start)), "Start time not yet reached");
}
Enter fullscreen mode Exit fullscreen mode
Function Returns true when
blockTimeLt(t) block_time < t
blockTimeGte(t) block_time >= t
blockTimeGt(t) block_time > t
blockTimeLte(t) block_time <= t

The Uint<16> Problem

Type Max Value Can Hold Unix Timestamp?
Uint<16> 65,535 No
Uint<32> 4,294,967,295 Yes (until 2106)
Uint<64> 18 quintillion Yes

Always use Uint<64> for timestamps. Current Unix timestamp is ~1.7 billion, far exceeding Uint<16>'s limit.

Practical Pattern: Timed Auction

export circuit placeBid(amount: Uint<64>, deadline: Uint<64>): [] {
  assert(disclose(auctionActive), "Auction not active");
  assert(blockTimeLt(disclose(deadline)), "Auction has ended");
  assert(disclose(amount > highestBid), "Bid too low");
  highestBid = disclose(amount);
}

export circuit endAuction(deadline: Uint<64>): [] {
  assert(blockTimeGte(disclose(deadline)), "Auction not yet ended");
  auctionActive = disclose(false);
}
Enter fullscreen mode Exit fullscreen mode

Workaround: Storing Deadlines On-Chain

Since ledger state is public, split the timestamp:

export ledger deadline_hi: Uint<16>;
export ledger deadline_lo: Uint<16>;

export circuit isBeforeDeadline(): [] {
  let fullTimestamp: Uint<64> = disclose(deadline_hi) * 65536 + disclose(deadline_lo);
  assert(blockTimeLt(disclose(fullTimestamp)), "Deadline passed");
}
Enter fullscreen mode Exit fullscreen mode

Quick Reference

Rule Why
Use Uint<64> for timestamps Uint<16> max is 65,535 — can't hold Unix timestamp
Always disclose() values Mandatory since Compact v0.16
Use Counter for phases Tracks progression, not wall-clock time
Pass deadlines as parameters Avoids leaking timing information on-chain

Full tutorial: https://gist.github.com/wilsonhoe/a59c6eb387f1193ca4d7f6c06a7b0c64

Midnight Network tutorial series. Bounty #306.

Top comments (0)