Skip to content

Add seller cancel action before the first bid - #6

Merged
Rigidity merged 3 commits into
mainfrom
rigidity/seller-cancel-before-first-bid
Oct 27, 2026
Merged

Rigidity merged 3 commits into
mainfrom
rigidity/seller-cancel-before-first-bid

Conversation

@Rigidity

@Rigidity Rigidity commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Adds a cancel action that lets the seller cancel an auction before the first bid, returning the NFT to the payout address.

Puzzle

puzzles/actions/cancel_action.rue (hash 3711cd50a0332a4b8f46bb2f1eb66d9c1d1ed658d104b94a4cf6e424906c782e) is added as a third merkle leaf, after the bid and end actions.

  • Asserts the auction hasn't ended and has no bids (winning_bid.amount == 0). There are no timing restrictions.
  • Authorization: outputs ReceiveMessage with mode 0x17 (SENDER_PUZZLE | RECEIVER_COIN), message "c" (a single byte), sender PAYOUT_PUZZLE_HASH. Since the message is tied to the current auction coin, it can't be replayed.
  • Effect is the same as ending with no bids: ended: true, reserve_amount: 0, winning_bid unchanged, and the NFT is unlocked to the payout puzzle hash via the existing NFT unlocker. No payments are made. Like the end action, it creates the $ coin announcement.

Every auction puzzle hash changes because the merkle root changes. The memo format is unchanged.

Driver

  • CancelActionArgs, CANCEL_MESSAGE, CANCEL_MESSAGE_MODE
  • AuctionInfo::cancel_action / cancel_action_hash; merkle_leaves now returns 3 leaves
  • AuctionExt::spend_cancel_action(ctx, nft) and AuctionExt::cancel_authorization(ctx), which returns the conditions a coin at the payout address must output
  • unlock_nft, child_state, spend, and parse_child work with a cancel spend as-is

README covers the lifecycle, a new Cancelling section (including that a cancel, like an end, needs an empty reserve coin, which for CAT reserves may require holding some of the CAT), and the payout address trust assumption.

Tests

tests/cancel.rs:

  • An authorized cancel returns the NFT to the seller and blocks further bids, ending, and cancelling again (XCH, CAT, and revocable CAT reserves).
  • A cancel with no message, with the message from a different puzzle, or with the wrong mode, receiver, or message body is rejected (MessageNotSentOrReceived).
  • A cancel after a bid or after the end is rejected, both by the driver and on chain. The on-chain cancel-after-bid test also runs with a royalty of 0, where nothing but the cancel action's own check stops it. The on-chain tests use a harness helper (spend_unchecked) that skips the driver's checks, so only consensus validation runs the auction puzzle; a control test shows the same path succeeds without bids.
  • Combining cancel with a bid, an end, or a second cancel in the same spend is rejected on chain.
  • A cancel can spend a stand-in empty reserve coin in place of the launcher's (XCH, CAT, and revocable CAT).
  • A cancel well before the end time, with a grace period set, succeeds.
  • parse_child follows a cancel (XCH, CAT, and revocable CAT).

tests/puzzles.rs also checks the new puzzle against its hash.


Note

High Risk
Changes core auction singleton puzzles (new merkle root) and authorization semantics; integrators must adopt new puzzle hashes and understand payout-address cancel control and mempool races with first bids.

Overview
Adds a seller cancel path before any bid: the auctioneer can abort and recover the locked NFT to the payout address, with the same on-chain outcome as ending with no bids (ended, empty reserve, $ announcement).

The new cancel_action puzzle becomes a third action-layer merkle leaf, so all auction singleton puzzle hashes change. Cancel requires a coin at the payout address to send message c with mode 0x17 to the current auction coin; the Rust driver exposes spend_cancel_action, cancel_authorization, and wires cancel into unlock_nft / state parsing. Cancels are blocked once there is a bid or after end; they race with a pending first bid in the mempool.

README documents lifecycle, cancel mechanics (including empty reserve / stand-in reserve), mempool behavior, and a new payout-address trust assumption. Tests cover authorization, timing, on-chain rejection when combined with bid/end or after bids, and reserve types (XCH/CAT/revocable CAT).

Reviewed by Cursor Bugbot for commit 1300908. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread puzzles/actions/cancel_action.rue Outdated
@cursor

cursor Bot commented Oct 27, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 0d7557e1-ab2d-4652-9077-857eabf2d579)

@Rigidity
Rigidity merged commit 4c03955 into main Oct 27, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant