sealedbid
gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/sealedbid on onyx-1 testnet
realmapplicationauctionslive on onyx-1
Sell to the highest bidder, with every bid sealed until bidding closes.
Because the winner pays the runner-up's price, a bidder can name what the lot is really worth to them without overpaying for it.
In detail curated
SEALED-BID SECOND-PRICE (VICKREY) SALE by commit-reveal, with ESCROWED deposits and PULL settlement. It is the portfolio's second auction and deliberately the OTHER mechanism: r/auction is an open ascending-bid sale where every bid is public the moment it lands and the winner pays WHAT THEY BID, so a bidder's willingness to pay is revealed to rivals in time to be used against them. Here each bidder posts a hash during the commit phase, reveals in a second phase, and THE WINNER PAYS THE SECOND-HIGHEST REVEALED BID -- so truthful bidding is the dominant strategy and the price is set by the runner-up rather than by the winner's own number. Two phases with distinct minimum lengths (MinCommitBlocks 10, MinRevealBlocks 20) are what makes that possible on a public chain: a bid is a 32-byte sha256 commitment over a length-prefixed (amount, salt) pair, so the amount is not readable from chain state until its owner publishes the salt. CUSTODY: Held == sealed + revealed + feeledger.Liabilities(), deposits are escrowed at Commit (so a commitment is backed, not merely asserted), settlement credits inside the realm and every payout is PULL (Claim / ClaimAll), and a bidder who commits and never reveals FORFEITS their deposit to the seller -- the only sanction that makes the reveal phase self-enforcing. Close is PERMISSIONLESS and was exercised live by a LOSING BIDDER, neither seller nor winner. Audit PROCEED: 0 RED, two YELLOW both REMEDIATED before deploy (Y1 slot squatting had no price; Y2 two renderers disagreed about escaping). 39-test suite green, 53-case mutation sweep FULLY COVERED with 0 uncaught and the subject restored byte-identically. BYTE-MATCH PASSED; it is the second deploy over gnomcp #107's package_dir path.
Use it
A realm holds state. Reading it is a free query; changing it is a transaction you sign yourself.
Read it — no key, no account, no GNOT
gnokey query vm/qeval \
--data 'gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/sealedbid.FeeBps()' \
--remote https://rpc.onyx.testnets.gno.land:443FeeBps is read-only, so this is a query rather than a transaction: it costs nothing, signs nothing and needs no funded account. Every exported function is listed under the API below; swap the name and the arguments to read any of the others.
Change its state
Every state change is a transaction you sign with your own key — this site never asks for one and holds none. You do not have to hand-write the transaction: gnoweb generates the exact gnokey maketx call command for any of this realm's 13 crossing functions, with the argument names filled in.
Read the known limitations further down this page first. They record what was not verified on chain, which is usually the part that matters.
Import it from another realm
import "gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/sealedbid"The path is the address: there is no package manager, nothing to add to a manifest and no version to pin, so the package you compile against is the one the chain runs. It differs per chain because the namespace in it is the deploying account's.
Deployments
onyx-1 testnet onyx-1
RenderedSourceCall builderState
chain-attested| Import path | gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/sealedbid |
|---|---|
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Deployed at height | 558,757 |
| Deploy transaction | f188a58f83b1352f30ef0258a1daab7ccc07c91c870c908683868254f5a8709a look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 54,605,981 |
| Storage | not attested per package on this chain — see the limitations below |
| Files on chain | gnomod.toml sealedbid.gno |
| Deployed bytes | sealedbid.gno — 40,774 bytes |
| sha256 | f07f0c7fecad6368369521881d8e5dc1aa2f747be68f4ffd9a0fad84e7d78b03 |
| Realm address | g1hkzn29tgvl66hmx6pn8uqn0f87wu653cgt0zan |
Do not take the hash above on trust. $download returns the bytes onyx-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:
curl -sS 'https://onyx.testnets.gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/sealedbid$download&file=sealedbid.gno' | shasum -a 256Expected: f07f0c7fecad6368369521881d8e5dc1aa2f747be68f4ffd9a0fad84e7d78b03 — 40,774 bytes. Use curl: the gno.land edges answer Python's default user-agent with HTTP 403.
API
chain-derived 44 exported functions.
Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:
AcceptAdminBidsOnBlocksLeftCancelClaimClaimAllClaimableOfCloseCommitCommitHashCommitmentOfCreateSaleDepositOfFeeBpsFeesAccruedForfeitedOfForfeitedTotalHasRevealedHasSaleHeightHeldIsCloseableLiabilitiesLotMinDepositNumSalesPhasePriceProvisionalPriceRenderRevealRevealedRevealedAmountOfRevealedOnSalesOfSealedSecondBidSetFeeBpsSetFeeRecipientStatusSweepDenomTransferAdminWinningBidWithdrawFees
Overview
Realm sealedbid is a SEALED-BID SECOND-PRICE (Vickrey) auction settled in ugnot, holding real coins. A SELLER lists a lot; BIDDERS commit to a price they never disclose, by publishing only sha256(amount, salt) and escrowing a deposit; after the commit window closes they OPEN their bids by revealing (amount, salt); the highest revealed bid wins and the winner pays the SECOND highest price, their deposit refunded down to it.
It is a different clearing rule and a different threat model from this portfolio's other market realm. r/auction is ascending English open-outcry: every bid is public the instant it lands, bidders react to each other, and the winner pays their own bid. Here no bidder learns another's number until the commit window has shut, so there is nothing to react to and nothing to front-run; and because the winner pays the runner-up's price, bidding one's true valuation is the dominant strategy rather than a mistake. The two realms answer the same question — who gets the lot, at what price — by mechanisms that do not resemble each other.
COMMIT-REVEAL is the mechanism this portfolio did not have. Nothing in it hashed anything, on any of the three trees, before this realm.
Custody — the conservation invariant is the product
A custodial realm, so the monetary invariant governs it as it does r/loans, r/auction and r/deposits:
Held == sealed + revealed + feeledger.Liabilities()
where `sealed` is the sum of deposits behind commitments that have NOT been opened in a non-terminal sale, `revealed` the sum of deposits behind commitments that HAVE been opened and are awaiting close, and the ledger holds every claimable kind — losing-bid refunds, the winner's change, seller proceeds — plus the fee pot. The two held buckets are disjoint by construction: opening a bid moves its deposit from the first to the second, never duplicating it, and Close drains both into the ledger. Every value transition preserves the equality; see the DELTA-form conservation test.
CORRECTION, recorded rather than shipped silently: the §7 discovery gate for this realm predicted THREE disjoint held buckets, naming `forfeited` as the third. Architecture refuted that. A forfeited deposit is credited to the seller at Close in the same transaction that voids the commitment, so it is never held as a distinct balance across any two transactions — there is no third bucket to be disjoint from. `forfeited` survives as a CUMULATIVE STATISTIC (ForfeitedOf, ForfeitedTotal), which is a report and not a custody claim. The gate's file is not edited; this is the amendment.
Payouts are PULL, never push. Close credits balances and sends nothing, so a hostile or contract-dead loser cannot make Close fail for everyone else, and the settlement path has no cross-call reentrancy surface.
Non-revelation, and why withholding never pays
A sealed-bid auction has a liveness problem an ascending one does not: a bidder who dislikes the outcome can simply never open their bid, stalling settlement or distorting the price. The answer here is adopted by citation rather than invented — r/moul/agents/bubblerumble6 reached it independently, and its policy is the right one: a commitment not opened in its window is VOID, nothing is refunded, and the deposit is credited to the seller. So withholding a reveal costs the withholder their whole deposit and pays them nothing, and the auction always closes: Close is permissionless and needs no bidder to act.
Two consequences of that policy are hazards, stated rather than discovered:
- A SHORT REVEAL WINDOW IS A SELLER'S INCENTIVE TO SET. The seller keeps every unopened deposit, so a seller who picks the MinRevealBlocks floor profits from bidders who cannot act in time. The window is public before anyone commits and is reported by [SaleInfo] and Phase; a bidder who does not like it should not commit. The realm does not second-guess a disclosed term.
- A COMMITMENT ABOVE ONE'S DEPOSIT CAN NEVER BE OPENED. Reveal requires `amount <= deposit`, so a bidder who commits to more than they escrowed has no valid opening and forfeits. That is the deposit-as-ceiling rule, and it is what makes the escrow sufficient: commit to no more than you put up.
Privacy — what is hidden, and what is not
The BID AMOUNT is hidden until the bidder opens it. The DEPOSIT is not: it is a coin transfer and is public, and since a valid bid cannot exceed its deposit, the deposit leaks an UPPER BOUND on the bid. A bidder who wants more privacy than that over-deposits and takes the change back at Close. This is the real ceiling of the scheme and is stated here because no amount of hashing moves it.
The salt is what keeps the commitment from being brute-forced over the plausible round numbers, so MinSaltLen bytes are required. The realm can check a salt's LENGTH and can never check its ENTROPY: a 16-byte salt of "aaaaaaaaaaaaaaaa" satisfies the guard and protects nothing. Use random bytes. Compute the commitment locally, or with a READ-ONLY query against CommitHash — never in a transaction, which would publish the salt and defeat the whole mechanism.
What the realm settles, and what it does NOT
The realm settles PAYMENT and records the WINNER; it does not custody or deliver the lot, which is free text and changes hands off-chain (or by a downstream realm reading [Winner]). The same scope decision r/auction states, for the same reason: an on-chain-asset auction is a v2 and this v1 omits the asset-escrow surface deliberately.
REALM-CALLER CAVEAT, inherited from the siblings: coinio's receipt guard admits only EOA payers, so every BIDDER is an EOA. The SELLER is whoever calls CreateSale; a realm may sell, but a realm seller must expose its own crossing path to Claim or its proceeds strand — the seller's concern, not a third-party hazard. assertNoSend reads the ORIGIN envelope, so every non-payable function refuses a transaction that attached coins anywhere.
Why crypto/sha256 directly
The discovery gate found genuine prior art for the hashing scheme — p/moul/agents/commit/v0, whose Hash is LENGTH-PREFIXED so that Hash("ab","c") differs from Hash("a","bc") — and deferred the import-or- reimplement choice to architecture. It is resolved here in favour of calling crypto/sha256 directly and FOLLOWING that package's scheme, because the package resolves to a DIFFERENT IMPORT PATH on each chain this portfolio targets, and the namespace rewriter substitutes only our own address. An import of it would make the mainnet twin of this realm non-substitutable, which is a structural cost that 1.4 KB of saved code does not buy.
Imports
chainchain/runtimechain/runtime/unsafecrypto/sha256encoding/hexgno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coiniogno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledgergno.land/p/nt/avl/v0gno.land/p/nt/markdown/sanitize/v0strconv
Constants and variables
Sale status values. Only Open is non-terminal; the phase inside Open is derived from the chain height, never stored.
const (
SaleOpen = "open" // in its commit, reveal, or awaiting-close phase
SaleSettled = "settled" // closed WITH a winner; proceeds credited
SaleUnsold = "unsold" // closed with no valid opening, or below reserve
SaleCancelled = "cancelled" // seller cancelled before any commitment
)
Phase values returned by Phase for a sale that is still Open.
const (
PhaseCommit = "commit" // accepting commitments
PhaseReveal = "reveal" // accepting openings
PhaseCloseable = "closeable" // reveal window shut; Close will succeed
)
Input bounds.
const (
// MinBid is the dust floor for any revealed bid, any deposit and any
// non-zero reserve; MaxBid keeps every bid and the fee split far from
// int64 overflow. The bidder must ESCROW at least their bid, so the real
// ceiling is their balance.
MinBid = int64(1_000)
MaxBid = int64(1_000_000_000_000) // 1,000,000 GNOT
// Window bounds, in blocks. The reveal floor is higher than the commit
// floor on purpose: failing to commit costs nothing, while failing to
// reveal costs the whole deposit, so the window in which that can happen
// gets the wider floor. See the short-reveal-window hazard above.
MinCommitBlocks = int64(10)
MinRevealBlocks = int64(20)
MaxWindowBlocks = int64(7_500_000)
// CommitmentLen is the length of a commitment string: sha256 as 64
// lowercase hex characters, exactly as [CommitHash] emits it.
CommitmentLen = 2 * sha256.Size
// MinSaltLen is the required salt length. It bounds brute force over
// plausible bid amounts; it cannot bound a caller's choice of entropy.
MinSaltLen = 16
MaxSaltLen = 128
// MaxLotLen bounds the stored lot description.
MaxLotLen = 200
// MaxBiddersPerSale bounds the commitments on one sale. It is the reason
// the duplicate-commitment scan and the Close payout loop may be linear:
// both are O(MaxBiddersPerSale) and neither is caller-extensible.
//
// It is ALSO a scarce resource, which is why a sale carries minDeposit.
// A slot is never released — there is no withdraw-commitment path — so
// without a seller-sized entry price, 25 addresses committing MinBid
// each fill a sale for 25,000ugnot, reveal MinBid, and are refunded in
// full fee-free: an honest bidder is locked out for the cost of gas.
// Audit Y1, and the reason minDeposit exists. See [CreateSale].
MaxBiddersPerSale = 25
// MaxSalesPerSeller bounds one seller's stored sales; terminal sales do
// NOT free the slot, because a settled/unsold/cancelled sale is a
// permanent record a bidder and an auditor may need to read.
MaxSalesPerSeller = 50
// RenderLimit bounds every rendered list — an unbounded Render was YELLOW
// in three prior audits.
RenderLimit = 20
)
Denom is the only asset this realm accepts.
const Denom = "ugnot"
MaxFeeBps is the hard protocol-fee cap: 1000 bps = 10%. The fee applies only to the seller's PROCEEDS at close — never to a refund, never to the winner's change, and never to a forfeited deposit (see Close).
const MaxFeeBps = int64(1000)
Functions
AcceptAdmin
func AcceptAdmin(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asAcceptAdmin(cross(cur), ...).
BidsOn
func BidsOn(saleID int64) int64
BidsOn is how many commitments a sale holds; RevealedOn how many are opened.
BlocksLeft
func BlocksLeft(saleID int64) int64
BlocksLeft is how many blocks remain in the sale's current window (0 once the reveal window has shut or the sale is terminal).
Cancel
func Cancel(cur realm, saleID int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCancel(cross(cur), ...).
Cancel withdraws a sale that has taken no commitment. Seller only. Once any commitment stands the sale can only run to Close — a seller cannot pull the lot out from under an escrowed bidder, and cannot strand a deposit.
Claim
func Claim(cur realm, amount int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asClaim(cross(cur), ...).
Claim sends amount ugnot of the caller's claimable balance — refunds, winner's change, and seller proceeds — to the caller.
ClaimAll
func ClaimAll(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asClaimAll(cross(cur), ...).
ClaimAll sends the caller's entire claimable balance to the caller.
ClaimableOf
func ClaimableOf(a address) int64
Close
func Close(cur realm, saleID int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asClose(cross(cur), ...).
Close settles a sale whose reveal window has shut. Anyone may call it; no sale's terminal state may depend on a party acting.
The clearing rule: among the OPENED bids the highest wins and pays max(second-highest, reserve, MinBid) — a strict Vickrey price, floored so that a lone bidder does not win for nothing and so that a reserve is honoured as a price and not merely as a veto. Ties on the highest amount go to the EARLIEST opening, which makes the tie-break deterministic and independent of storage order, and a tie sets the second price equal to the first, so a tied winner pays their full bid.
The money, all of it credited and none of it sent:
- winner: change of `deposit - price`, fee-free (it is their own coin coming back, not a sale)
- seller: `price`, at the sale's snapshotted fee
- every other opened bid: its deposit back in full, fee-free
- every UNOPENED commitment: deposit credited to the SELLER, fee-free. Fee-free is deliberate: a fee is a cut of a SALE, and taking a cut of liquidated damages would give the fee recipient a revenue interest in bidders failing to reveal.
If no bid was opened, or the highest opened bid is below the reserve, the sale is Unsold: every opened bid is refunded in full and only the unopened commitments forfeit.
Commit
func Commit(cur realm, saleID int64, commitment string)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCommit(cross(cur), ...).
Commit escrows a deposit behind a sealed bid. The caller attaches the deposit and supplies only `commitment` — CommitHash(amount, salt) as 64 lowercase hex characters — so the realm learns the bid's CEILING (the deposit) and not the bid. The deposit is the ceiling on what can later be opened, so escrow at least what you intend to bid; escrow more to hide it better. One commitment per bidder per sale, and a commitment already present on this sale is refused (copying another bidder's hash without knowing its contents would otherwise be free and could never be opened).
CommitHash
func CommitHash(amount int64, salt string) string
CommitHash returns the commitment for a bid of `amount` under `salt`: the sha256 of a LENGTH-PREFIXED encoding of the two parts, as 64 lowercase hex characters. The length prefixes are what make the encoding unambiguous — without them a commitment to (12, "34salt...") and one to (1, "234salt...") could collide, and a bidder could open a commitment as a bid they never made. The scheme follows p/moul/agents/commit/v0; see the package doc for why it is reimplemented rather than imported.
This is a VIEW. Compute your commitment locally or with a read-only query — calling it inside a transaction publishes the salt and destroys the seal.
CommitmentOf
func CommitmentOf(saleID int64, bidder address) string
CommitmentOf returns the bidder's stored commitment on a sale ("" if none).
CreateSale
func CreateSale(cur realm, lot string, reserve, minDeposit, commitBlocks, revealBlocks int64) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCreateSale(cross(cur), ...).
CreateSale lists a lot for sealed-bid second-price sale. The caller becomes the seller. `reserve` is 0 (no reserve) or a floor in [MinBid, MaxBid] that the HIGHEST revealed bid must meet for the sale to settle. Commitments are accepted for `commitBlocks` blocks, then openings for `revealBlocks` blocks, after which anyone may Close. The current protocol fee is snapshotted in. Returns the id.
`minDeposit` is the ENTRY PRICE per commitment, and it is the seller's one defence against slot squatting: a sale has at most MaxBiddersPerSale slots, a slot is never released, and a squatter who reveals a dust bid is refunded in full fee-free — so with no entry price a rival locks an honest bidder out of any sale for the cost of gas. Size it to the lot, not to the dust floor. 0 means "no preference", resolved to the max(MinBid, reserve) floor, which is the weakest setting and leaves the squatting cost at MaxBiddersPerSale times that floor. The floor includes the reserve because a bid under the reserve cannot win, so escrowing less than the reserve buys only a slot.
DepositOf
func DepositOf(saleID int64, bidder address) int64
DepositOf returns the bidder's escrowed deposit on a sale (0 if none).
FeeBps
func FeeBps() int64
FeesAccrued
func FeesAccrued() int64
ForfeitedOf
func ForfeitedOf(saleID int64) int64
ForfeitedOf is the total deposit liquidated to the seller on a closed sale (0 before close). A cumulative statistic, not a held balance.
ForfeitedTotal
func ForfeitedTotal() int64
HasRevealed
func HasRevealed(saleID int64, bidder address) bool
HasRevealed reports whether the bidder has opened their bid on a sale.
HasSale
func HasSale(saleID int64) bool
Height
func Height() int64
Held
func Held() int64
IsCloseable
func IsCloseable(saleID int64) bool
IsCloseable reports whether Close would succeed right now.
Liabilities
func Liabilities() int64
Lot
func Lot(saleID int64) string
Lot returns the sale's free-text lot description.
MinDeposit
func MinDeposit(saleID int64) int64
MinDeposit is the resolved entry price for one commitment on this sale: the smallest deposit Commit accepts. It is at least max(MinBid, reserve) and is the seller's defence against slot squatting (audit Y1; see CreateSale).
NumSales
func NumSales() int64
Phase
func Phase(saleID int64) string
Phase reports where a sale is: "commit", "reveal" or "closeable" while it is open, else its terminal status. The phase is derived from the chain height and is never stored, so it cannot disagree with the guards.
Price
func Price(saleID int64) int64
Price is the SECOND price the winner actually paid once the sale has Settled, else 0. It is not the winning bid; see WinningBid.
ProvisionalPrice
func ProvisionalPrice(saleID int64) int64
ProvisionalPrice is what the winner would pay if the sale closed right now: max(second bid, reserve, MinBid), or 0 if nothing is opened yet or the highest opening is below the reserve.
Render
func Render(path string) string
Reveal
func Reveal(cur realm, saleID int64, amount int64, salt string)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asReveal(cross(cur), ...).
Reveal opens the caller's sealed bid on a sale whose commit window has shut. `amount` and `salt` must hash to the stored commitment, `amount` must be in [MinBid, MaxBid], and `amount` must not exceed the deposit — a bidder who committed above their escrow has no valid opening and forfeits at Close. Opening does not move coins; it moves the deposit from the `sealed` bucket to the `revealed` one and records the bid for the clearing scan.
Revealed
func Revealed() int64
RevealedAmountOf
func RevealedAmountOf(saleID int64, bidder address) int64
RevealedAmountOf returns the bidder's OPENED bid amount (0 while sealed). A revealed bid is public by construction — it travelled in a transaction.
RevealedOn
func RevealedOn(saleID int64) int64
SalesOf
func SalesOf(seller address) int64
Sealed
func Sealed() int64
SecondBid
func SecondBid(saleID int64) int64
SecondBid is the second-highest OPENED bid, or 0. With a tie on the highest it equals the highest.
SetFeeBps
func SetFeeBps(cur realm, bps int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSetFeeBps(cross(cur), ...).
SetFeeRecipient
func SetFeeRecipient(cur realm, recipient address)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSetFeeRecipient(cross(cur), ...).
Status
func Status(saleID int64) string
Status is the sale's STORED state -- one of "open", "settled", "unsold" or "cancelled". It answers a different question from Phase: an open sale has three distinct phases but only one status, and a closed one has a status that no longer moves with the chain height.
SweepDenom
func SweepDenom(cur realm, denom string)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSweepDenom(cross(cur), ...).
SweepDenom recovers out-of-band coins to the fee recipient. For the ledger denom the reserve is sealed + revealed + Liabilities() — escrowed deposits, claimable refunds, change, proceeds and the fee pot are all structurally unreachable.
TransferAdmin
func TransferAdmin(cur realm, successor address)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asTransferAdmin(cross(cur), ...).
WinningBid
func WinningBid(saleID int64) int64
WinningBid is the highest OPENED bid on a sale, available as soon as it is revealed (a revealed bid is public). It is what the winner bid, not what they pay.
WithdrawFees
func WithdrawFees(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asWithdrawFees(cross(cur), ...).
Doc text is reproduced as vm/qdoc returns it. The node markdown-escapes doc comments, so a bracket or angle bracket may carry a backslash the committed source does not have. The source itself is at source and in this repository.
Dependencies
chain-attested| Imports | chain, chain/runtime, chain/runtime/unsafe, crypto/sha256, encoding/hex, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv |
|---|---|
| First-party dependencies | gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger |
Known limitations
Recorded by the people who built and deployed it. This list is deliberately not empty where honesty costs something.
curated- THE LIVE RUN SETTLED ONE SALE WITH feeBps 0 AND TWO HONEST REVEALS. Exercised on chain: CreateSale, Commit by two distinct bidders (6,000,000 and 4,000,000ugnot deposits), Reveal by both (5,000,000 and 3,000,000), a PERMISSIONLESS Close by the LOSING bidder, a PARTIAL Claim, and ClaimAll by all three parties, with conservation closing to Held 0 == Liabilities 0. Second-price settlement is proven live and not merely tested: winningBid 5,000,000, secondBid 3,000,000, PRICE 3,000,000, seller credited 3,000,000, winner credited 3,000,000 change, loser credited their full 4,000,000. Suite- and sweep-covered ONLY: the protocol-fee path (fee was 0 bps snapshotted on this sale), FORFEITURE of a non-revealer's deposit, the unsold / below-reserve branch, the tie-break, Cancel, SweepDenom, the two-step admin transfer, and every ceiling (MaxBiddersPerSale 25, MaxSalesPerSeller 50, MaxLotLen 200, salt length bounds, MaxFeeBps 1000).
- FORFEITURE IS THE MECHANISM THAT MAKES THE REVEAL PHASE SELF-ENFORCING AND IT WAS NOT EXERCISED LIVE. Both live bidders revealed, so the one path that punishes silence is suite-only. It is covered by the test suite and by the mutation sweep, but a reader should know that the live run never let the clock run out on a committed bidder.
- PERMISSIONLESS CLOSE REQUIRES THE CALLER TO FUND THE SETTLEMENT'S STORAGE DEPOSIT, and that is a real usability constraint rather than a bug. A first Close as the key funder1 was REFUSED by the node with 'lockStorageDeposit failed ... insufficient coins': the chain charges the caller of any transaction that grows realm storage, and Close grew it by 5,197 bytes, 519,700ugnot, against that key's 151,030ugnot balance. gno_call refuses to broadcast a failing transaction, so there is no hash and no gas was spent. Close was then issued by the losing bidder, which preserves the permissionless property under test. The consequence for an operator: anyone MAY close a sale, but only someone holding roughly half a GNOT can.
- THERE IS NO FeeCollector CONSTANT, AND THIS ENTRY ASSERTED THERE WAS ONE BEFORE THE SOURCE WAS GREPPED. The claim was copied from the r/reimbursements template, where it is true, and is recorded here because a plausible inherited sentence is the kind of wrong thing a reader cannot see. What the source actually does: init() sets admin = unsafe.OriginCaller() and feeRecipient = admin, so the DEPLOYER holds both roles at height 558757, and feeBps is the int64 zero value until SetFeeBps is called. The consequence is the OPPOSITE of the reimbursements one and is worth stating positively: sealedbid.gno contains NO address literal at all -- the only namespace occurrences are the two first-party import paths at lines 129-130 -- so this realm is free of the address-as-data substitution hazard BY CONSTRUCTION rather than by declaration in catalog/address_data.json, and a port re-namespaces import paths and the module path and nothing else. The real coverage gap is unchanged by the correction: admin was the seller on the live sale, so the seller/fee-recipient separation is suite-only, and with feeBps 0 snapshotted into the sale NEITHER SetFeeBps NOR WithdrawFees was exercised on chain. MaxFeeBps 1000 is a compile-time ceiling passed to feeledger.MustNew and is enforced by the ledger, not by the caller.
- THE COMMITMENT HIDES THE AMOUNT, NOT THE BIDDER OR THE DEPOSIT. A commitment's 32 bytes do not reveal the bid, but the depositing address and the deposit SIZE are public the moment Commit lands, and a deposit is an upper bound on the bid it can cover. A bidder who posts exactly their bid as the deposit has published their bid. The realm cannot prevent this and does not claim to; minDeposit is a floor, and over-depositing is the privacy measure.
- SINGLE-DENOM (ugnot). A foreign denom reaching the realm is recoverable only through SweepDenom, and only to the FeeCollector.
- storage_bytes and storage_deposit are null for the reason in storage_measurement_note -- the figures ARE chain-attested (66,202 B / 6,620,200ugnot at height 558761, carried by the approver's /vm.m_enable_pkg transaction) and are withheld from these fields only because verify_catalog C2's one-block window cannot see an event four blocks later. Do not read the nulls as an unmeasured deploy.
- THE INTERMEDIATE LIVE-STATE FIGURES IN THE DEPLOYMENT RECORD ARE RAW-RPC vm/qeval AND CARRY NO SECTION 9a STAMP. Re-measured on this build (gnomcp #107, 0.12.3+pkgdir-107.867cc0e): gno_eval, gno_packages and gno_render return structuredContent {chain_id_bound: true} with NO payload at all, reproduced on CLAUDE.md section 9a's own published control reads. So the conservation and phase figures between the endpoints are raw-RPC and are cited as such; the byte-match (raw vm/qfile) is strong evidence and ALSO unstamped, while the terminal zero-balance gno_account read IS stamped.
- Selective-reveal shill ladder is near-capital-free because unrevealed deposits forfeit TO THE SELLER: sock-puppet commits laddered under the top honest bid, revealed selectively, inflate the second price while every withheld rung's deposit returns to the seller fee-free. The in-source dominant-strategy claim is false against a shilling seller; harm bounded by the winner's own revealed bid. Successor options: forfeits burned or routed to a non-seller sink. Audit 2026-11-04, disclosed per the operator's disclose-and-queue-successor rule; details and severity in [an unpublished record].
Source
| Source file | onyx/r/sealedbid/sealedbid.gno at commit c2e10d17ffbf in the project repository (not public — the digest command above is the check that needs no repository) |
|---|---|
| Matches the deployed bytes | yes — byte-identical |