Skip to main content
Status: Testnet, except where a row says otherwise. Example values are from the checked-in vector file crates/sealforge-core/tests/fixtures/vectors.json (Appendix: test vectors). All memos are ZIP 302 text memos in the 512-byte memo field (ZIP 302). On the receive side, text is extracted by zcash_protocol::memo::Memo::from_bytes, and only Memo::Text is considered (crates/sealforge-scan/src/lib.rs, crates/sealforge-chain/src/wallet.rs). sealforge-core then trims trailing 0x00 bytes before parsing (trim_end_matches('\0')), so NUL padding from any wallet is accepted.

Auction code

The code is not a security parameter for funds: a bid only counts if paid to that auction’s own escrow address (2.4).

Bid memo: SF1

Grammar (ABNF, RFC 5234):
Limits: the encoded memo (before padding) MUST be ≤ 512 bytes (MAX_MEMO_BYTES). With the fixed 80-byte prefix, the refund UA can be at most 432 bytes. Decoding uses splitn(4, ':'), so the refund UA is everything after the third colon, and a refund UA containing : is rejected. The coordinator then checks the refund address: it MUST decode as a Unified Address for the auction’s network with an Orchard receiver (crates/sealforge-coordinator/src/validate.rs shielded_ua). A bid with an unusable refund address still counts; its refund goes to manual review (SECURITY-REVIEW F2). Code: sealforge_core::memo::BidMemo::{encode, decode}; TS bidMemo in packages/bid/src/memo.ts. Tests: memo::tests::*.

Reveal memo: SF1R (sealed-reveal mode)

  • Split on : into exactly 5 fields, else BadShape.
  • price MUST be canonical decimal: empty, signed (-1), or leading-zero (042000000) values are rejected (reveal_memo_round_trips). It MUST parse as u64. Selection then requires 1 ≤ price ≤ deposit (2.5).
  • The reveal is a separate payment to the escrow. The web flow pays REVEAL_AMOUNT_ZAT = 10_000 zat (packages/bid/src/receipt.ts); a counting reveal’s amount is refunded with the deposit (see the testnet tables in evidence/testnet-genesis.md).
Code: sealforge_core::memo::RevealMemo; TS revealMemo.

Fee-buffer memo: SF1FEE

The organiser’s optional deposit to the escrow for ZIP 317 fees. It is never a bid (BidMemo::decode rejects it, test fee_buffer_memo_round_trips_and_is_not_a_bid). What isn’t spent on fees returns with the payout. Code: memo::fee_buffer_memo, memo::parse_fee_buffer_memo.

ZIP 321 payment URI

  • amount: ZEC decimal from integer zatoshi, trailing zeros trimmed (format_zec: 100_000_000 → "1", 1 → "0.00000001").
  • memo: base64url without padding (RFC 4648 §5), as ZIP 321 specifies for the memo parameter.
  • One URI per bid, single payment. Payable from any wallet that supports ZIP 321 and shielded memos.
Code: crates/sealforge-core/src/zip321.rs payment_uri; TS packages/bid/src/zip321.ts. Tests: zip321::tests::*, the TS vector test.

Receipt JSON

Status: Testnet. Created and kept in the browser (IndexedDB, apps/web/src/lib/receipts.ts) and offered as a download (sealforge-receipt-*.json). Never sent to the coordinator. Rust type: sealforge_core::receipt::Receipt. The web app’s stored form may add wallet_address_index and paid_txid (WALLET-INTEGRATION.md).

Published result

GET /v1/auctions/{code}/result on the coordinator (also proxied by the site at /api/auctions/{code}/result). Available once the auction is published, refunding or settled. Type: sealforge_api::types::PublishedResult (crates/sealforge-api/src/types.rs).

Versioning policy

  • Every memo starts with a version prefix. SF1, SF1R and SF1FEE are the only prefixes defined.
  • Who rejects unknown prefixes: sealforge-core decoders. BidMemo::decode returns BadPrefix for anything but SF1 (test rejects_bad_input uses SF2:), and the selection rules classify such a payment invalid(bad_memo) (2.5). An unparseable memo yields no refund address, so the payment goes to manual review rather than an automatic refund (crates/sealforge-coordinator/src/refunds.rs module doc). The coordinator, CLI auditor and WASM auditor all use the same decoder, so they agree.
  • How SF2 would coexist: a future version would get a new prefix and its own decoder, and the auction would record which version it accepts (as bid_mode does today). An SF1 auction MUST keep treating SF2 memos as invalid, so its result never changes when new versions ship. Commitment personalizations carry their own version (_v1, leaf1), so a format change also changes every hash. The published algorithm_version names the clearing rules. Specified: there is no SF2 today.