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");
}
| 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);
}
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");
}
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)