Extending Auctions Without Imposing a Hard End Time
- Extending Auctions Without Imposing a Hard End Time
- The problem
- Background: why sniping matters
- The tension: bounded vs unbounded
- The key insight: each bid carries its own deadline
- How it works in practice
- Why this avoids the problems with pure unbounded auctions
- Exponential increments as an optional deterrent
- Implementation strategy: settlement policy versioning
- Open questions
- Summary
Extending Auctions Without Imposing a Hard End Time
Date: April 26, 2026
Branch: auctions/cashu-p2pk-path-oracle-v1
Commit reviewed: ed1baef (April 17, 2026)
Latest upstream: bce6a096 (April 24, 2026)
Note: This post is based on commit
ed1baeffrom April 17. The branch has since advanced tobce6a096(April 24) with additional commits including progress bar UI, dev mode grace periods, and orphaned token cleanup. Some or all of the design suggestions below may already be implemented or superseded on the current tip. Treat this as a snapshot of a conversation, not the final word.
The problem
The current auction implementation uses a bounded soft-close to prevent sniping. When a bid arrives near the end of the auction, the closing time extends by a fixed amount — but only up to a seller-defined max_end_at. The AUCTIONS codex is explicit about this:
Anti-sniping is only acceptable with a hard upper bound (
max_end_at). Without a hard upper bound, anti-sniping is not acceptable.
The concern is real. Without a bound, the auction could theoretically run forever, and lower bidders’ Cashu ecash would remain locked indefinitely.
But Chief Monkey wants auctions where the seller does not impose a hard end time on the bidders. The auction should last as long as bidders are genuinely competing. Setting max_end_at at creation time means the seller is declaring “this auction will definitely end by X” — and that constraint comes from the seller, not from bidder behavior.
This post explores whether we can remove that constraint while keeping the auction safe for all participants.
Background: why sniping matters
Sniping is when a bidder places a last-second bid just before a hard close, leaving no time for others to respond. In a fixed-deadline auction, sniping is the dominant strategy — you never want to show your hand early. The equilibrium is an auction where nobody bids until the final seconds, which breaks price discovery.
Soft-close auctions solve this by extending the clock when bids arrive near the end. The current implementation uses this approach with extension_rule: anti_sniping:<window_seconds>:<extension_seconds> plus a max_end_at cap.
The tension: bounded vs unbounded
The codex rejects unbounded extensions because of a specific concern: locktime.
Each bid locks Cashu ecash to a P2PK pubkey with a timelock. The locktime is baked into the proof at creation time — you can’t change it later. In the current design, the locktime is set to max_end_at + settlement_grace_seconds so that:
- The seller can always settle before the locktime expires
- Non-winners can reclaim after the locktime expires
Without max_end_at, there’s no single locktime that works for all bids. An early bid placed when the auction had 1 hour left needs a different locktime than a bid placed when the auction had 24 hours left.
The key insight: each bid carries its own deadline
Every bid already has its own locktime. That locktime is the deadline after which the bidder can reclaim their funds. What if we use that deadline as the auction’s effective end time?
Proposed rule:
effective_end_at = highest_bid_locktime - settlement_grace_period
The auction ends at a fixed interval before the current highest bidder’s locktime expires. When a new bid arrives, it comes with its own (later) locktime, which pushes the effective end forward.
This means:
- The bound exists — the auction definitely ends before the highest bid expires
- The bound is determined by bidders — not imposed by the seller at creation time
- The bound is moving — it shifts forward with each new bid
- The seller always has a settlement window — the grace period between effective end and locktime
How it works in practice
Bid flow
-
Bidder requests a path grant from the oracle (as today)
-
Oracle computes
current_effective_endfrom the latest bid -
Oracle enforces a locktime constraint:
locktime >= current_effective_end + settlement_grace + minimum_extensionThis guarantees each new bid genuinely extends the auction by at least
minimum_extension(e.g., 5 minutes), and the seller always gets the full grace period. -
Oracle returns
{ derivationPath, childPubkey, locktime }— the locktime is oracle-dictated, not bidder-chosen -
Bidder locks ecash with the specified locktime
-
Bidder publishes bid event
-
effective_end_atupdates tolocktime - grace
Settlement
After effective_end_at passes with no new bids, the seller requests a settlement plan. The oracle validates bids, resolves the winner, and the seller derives the child private key to redeem the locked ecash — all within the grace window.
If the seller misses the window, the winner can reclaim. This is correct — the seller failed to perform.
Non-winners
Lower bidders’ locktimes expire normally. They reclaim their ecash after their locktime passes, exactly as today. No funds are trapped.
Why this avoids the problems with pure unbounded auctions
No evaporating bids
In a naive unbounded design, the current highest bidder’s locktime could expire, leaving the auction with no valid standing bid. The price would need to “drop” to the next-lower bid, which is confusing and potentially exploitable.
With the moving bound, the auction ends before the highest bid expires. The standing bid never evaporates.
No free-extension attack
In a naive unbounded design, an attacker could place a bid, wait for their locktime to expire, reclaim (net cost: zero), and repeat — extending the auction indefinitely at no cost.
With the moving bound, each bid’s locktime must extend beyond the current effective end. The attacker’s bid becomes the new highest bid, and the auction doesn’t end until that locktime minus grace. The attacker can’t reclaim without ceasing to be the highest bidder — and if they do, the auction settles with whoever is.
No unbounded exposure
With a naive unbounded design plus a doubling increment, bidders face infinite potential exposure. “I’ll bid up to 10,000 sats” is meaningless if 20 more doubling rounds could follow.
With the moving bound, the maximum exposure is bounded by the locktime — which is always known at bid time. A bidder can see “the current effective end is in 30 minutes” and make an informed decision.
Exponential increments as an optional deterrent
The moving bound already prevents sniping by giving other bidders time to respond. But we can layer on an optional exponential increment for sellers who want faster-concluding auctions.
When enabled, the minimum bid increment doubles with each extension:
Bid 1: 1000 sats (increment: 1)
Bid 2: 1001 sats (increment: 1)
Bid 3: 1003 sats (increment: 2)
Bid 4: 1007 sats (increment: 4)
Bid 5: 1015 sats (increment: 8)
...
Bid 20: ~1M sats (increment: ~524k)
After ~20 rounds of sniping, the increment reaches millions of sats. No rational bidder continues. This accelerates the auction’s conclusion without imposing an artificial time cap.
This would be configurable per auction at creation time:
increment_mode: fixed— standard linear increment (current behavior)increment_mode: exponential— doubling per extensionincrement_mode: none— no minimum increment at all (fully open)
Implementation strategy: settlement policy versioning
The current branch (cashu_p2pk_path_oracle_v1) is being hardened for merge into main. The moving-bound design described above should not block that merge — it should be introduced as a follow-up.
The codebase already supports this cleanly. Every auction event carries a settlement_policy tag (currently cashu_p2pk_path_oracle_v1), and the oracle gates on it. This is the version discriminator.
The vast majority of the auction infrastructure is policy-agnostic:
- Path oracle (kind 30410, grant/redeem flow)
- HD key derivation (
auctionHd.ts,auctionP2pk.ts) - P2PK lock and ecash receive
- Token envelopes (bid, refund, release DMs)
- Settlement plan computation and winner resolution
- Bid event structure (kind 1023)
The version-specific code is small and localized:
getAuctionEffectiveEndAt()formula- Locktime source (client-set vs oracle-dictated)
- Auction creation validation (
max_end_at,extension_rulevsminimum_extension,increment_mode) - Oracle endpoint validation rules
A suggested path forward:
- Consider merging v1 as-is — bounded
max_end_at+anti_snipingextension rule. Solid, tested, and addresses the core use case. - Introduce
cashu_p2pk_path_oracle_v2as a separate PR. Same auction kind (30408), same bid flow, same settlement infrastructure — just a differentsettlement_policyvalue and the moving-bound logic. - Both policies coexist on relays. Old auctions settle under v1 rules forever (you can’t change rules retroactively). New auctions can use either policy at the seller’s choice.
This framing — settlement policies within the same auction kind, not “auctions v1 vs v2” — matches the existing settlement_policy tag design and avoids suggesting that the two approaches are fundamentally different products.
Open questions
-
What should
minimum_extensiondefault to? Something small like 5 minutes seems right — enough for a response but not so large that a single bid adds hours. -
What should
settlement_grace_periodbe? The currentAUCTION_SETTLEMENT_GRACE_SECONDSis 3600 (1 hour), which is very conservative. With the moving bound, the grace period directly adds to the auction duration (each bid extends bygrace + minimum_extension). A shorter grace (5-10 minutes) might be more appropriate. -
Should there be an absolute maximum duration? Even with the moving bound, an auction could theoretically run for weeks if bids keep arriving. A very generous absolute cap (e.g., 7 days from
start_at) might be worth considering as a safety net. -
How does this interact with the existing
AUCTIONS.mdcodex? Section 6.1 explicitly states “Without a hard upper bound, anti-sniping is not acceptable.” This proposal replaces the hard upper bound with a moving bound — the codex would need updating. -
Locktime oracle enforcement: currently the client computes the locktime. With this design, the oracle must dictate it. This shifts trust slightly — the oracle could set an unreasonably short locktime. But the oracle is already trusted (it holds the derivation paths), so this is consistent with the existing threat model.
Summary
The moving-bound approach gives us unbounded auctions that are safe for all participants:
- Bidders get an auction that lasts as long as genuine competition continues
- Seller gets a deterministic settlement window and can always claim the winner’s ecash
- Non-winners reclaim their funds after their locktime expires, as today
- Snipers face both a time response window (moving bound) and an optional economic deterrent (exponential increments)
- No party imposes an arbitrary end time on the others
The bound exists — it’s just determined by the bidders’ own locktimes rather than by a seller-chosen max_end_at.
Feedback welcome. This is a design exploration, not a committed implementation plan.
Write a comment