Beersy
BRC-177

Wallet-Enforced Expiry for `noSend` Actions

Handing an unbroadcast payment transaction to someone else means the money it uses is locked up in your wallet with no way to get it back until that person decides to broadcast it, or never does. Application-level timers cannot undo a transaction that already exists in someone else's hands. This makes it possible to offer a payment with a real, wallet-enforced deadline: if it is not broadcast in time, the wallet reclaims the funds itself.

Ty Everettchanged 1 Sept 202612 min read

Reference for an AI

Everything an assistant needs to answer questions about BRC-177 accurately, including what it depends on.

Summary

Why
A wallet has no way to release funds reserved for a transaction it handed to an outside party, because timers in the app cannot invalidate a transaction that is already signed and out of its control.
What
BRC-177 defines a wallet that creates a `noSend` transaction whose funding can be automatically reclaimed by the wallet if it is not broadcast before a deadline.
How
A developer adds one `p nosend expiry ...` label (seconds, timestamp, or block height) to a BRC-100 `` call with `options.noSend: true`, and the wallet funds the action from a dedicated output that it will spend back to itself if no external broadcast is seen by the deadline.

What this lets you do

  • Set a hard expiry on an unbroadcast
  • Express expiry as relative seconds, a Unix timestamp, or a block height
  • Let the wallet reclaim reserved funds automatically after the deadline
  • Bound exactly how much value a handed-off transaction can tie up
  • Complete the flow later via signAction while expiry still applies

Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.

noSend actionexpiry deadlinreclaim or bro

The specification

Abstract

This specification defines the BRC-111 module p nosend expiry, which asks a supporting wallet to create a BRC-100 noSend action that can be revoked after a deadline. The wallet first broadcasts a funding transaction containing one dedicated wallet-controlled output. The protected noSend action spends that output, but the wallet never broadcasts the itself. If the wallet has not observed an external broadcast by the deadline, it spends the same funding output back to its default , invalidating the protected transaction once the is mined.

Expiry may be expressed as a relative number of seconds, an absolute Unix timestamp, or an absolute block height. The same behavior applies when createAction returns a transaction that is completed later through signAction.

Motivation

A noSend transaction may be handed to an application, recipient, or other broadcaster and remain unbroadcast indefinitely. During that time its inputs are reserved. If ordinary wallet coin selection gave the transaction a large input and expected significant change, most of that input's value can remain unavailable even though only a small payment was intended.

Applications can implement timers, but an application timer does not release the wallet's reservation and cannot invalidate a signed transaction already given to another party. Bitcoin timelocks provide a lower bound on when a transaction may be mined; they do not make a transaction invalid after a deadline.

This proposal uses the model directly. A small, purpose-built funding output bounds the value reserved by the protected action. Spending that output in a reclaim transaction makes the protected action a double spend. No new wallet method, transaction field, or script template is required.

Normative Language

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document are to be interpreted as described in RFC 2119 and RFC 8174 when they appear in all capitals.

Specification

Label namespace

This specification defines BRC-111 nosend and the following exact action-label forms:

LabelMeaning
p nosend expiry seconds <duration>Expire <duration> seconds after activation
p nosend expiry timestamp <unixSeconds>Expire at the stated Unix time
p nosend expiry blockheight <height>Expire when the wallet observes a best-chain height greater than or equal to <height>

Examples:

p nosend expiry seconds 300
p nosend expiry timestamp 1798761600
p nosend expiry blockheight 1000000

The fixed words are lowercase ASCII and separated by one space. Each value MUST be a canonical unsigned base-10 integer: digits only, no sign, and no leading zero unless the value is zero. duration MUST be greater than zero. unixSeconds is the number of elapsed seconds since 1970-01-01T00:00:00Z, ignoring leap seconds.

An action MUST contain exactly one p nosend expiry ... label. A wallet MUST reject malformed forms, unknown expiry modes, duplicate expiry labels, or values outside its safely supported integer and scheduling range. Other ordinary and supported module labels MAY appear on the action.

Per BRC-111, a wallet that does not support module ID nosend MUST reject the request rather than treating the label as ordinary metadata.

In a distributed wallet, the component that owns durable action state and deadline monitoring MUST advertise or otherwise establish BRC-177 support before prefunding begins. A client MUST NOT create the funding transaction when its authoritative storage or monitor cannot persist and enforce the protected lifecycle.

Protected action

An action carrying a valid label from this specification is a protected action. Its dedicated wallet-controlled input is the revocation anchor. The protected action is released once the wallet has returned transaction bytes containing a valid for the . A transaction that later spends the anchor back to the wallet is the reclaim transaction.

The application's createAction request MUST set options.noSend to true. The request MUST NOT use sendWith, noSendChange, or returnTXIDOnly. A protected action MUST NOT participate in a BRC-100 sendWith or noSendChange batch. A wallet MUST reject a later request that attempts to broadcast a protected action through sendWith.

All ordinary BRC-100 validation, spending authorization, and permission checks still apply. This module adds expiry behavior; it does not grant permission to spend.

Prefunding

Before it returns any signed or signable representation of the protected action, the wallet MUST:

  1. Determine the value required by the requested outputs, supplied explicit inputs, and a fee-safe serialization of the protected action.
  2. Create a normal funding transaction with one dedicated output controlled by the wallet and tracked in its internal default basket. The output MUST be large enough both to fund the protected action and to be reclaimed, net of a reclaim fee, under the wallet's current fee and output policies.
  3. Broadcast the funding transaction immediately and obtain acceptance from at least one transaction processor under the wallet's ordinary immediate-broadcast policy.
  4. Construct the protected action with the revocation anchor as its only automatically selected wallet input.

The protected action MUST contain no automatically generated wallet change. A wallet SHOULD size the anchor so the protected action pays its intended network fee without material overpayment. If the requested outputs and explicit inputs do not permit a no-change construction within the wallet's fee policy, the wallet MUST reject the request rather than reserve an ordinary wallet input or place surplus value in protected change.

The funding transaction itself MAY have ordinary change because it is broadcast before the protected action is released. That change is not reserved by the protected action.

The anchor MUST be reserved exclusively for the protected action until either the protected action is observed on the network or the anchor is reclaimed. The returned for the protected action MUST contain the funding transaction and the supporting data required by BRC-62 and BRC-100.

When the protected action is finalized, the wallet's signature for the anchor input MUST commit to the complete input and output set, normally with SIGHASH_ALL. Caller-supplied inputs MAY still require external unlocking scripts.

If the funding transaction cannot be accepted, the wallet MUST fail the request and MUST NOT release the protected action. If the funding transaction is later found to be invalid or double-spent, the protected action is also invalid and the wallet MUST stop presenting it as available for broadcast.

Activation and deadline

The expiry becomes active when the wallet durably commits the protected action, immediately before returning the first signed or signable representation to the caller. The wallet MUST persist the label, action reference, anchor , computed deadline, whether an anchor signature has been released, and lifecycle state before returning. It MAY also persist the activation time; the computed deadline remains authoritative.

For seconds, the wallet MUST add duration to the activation time and persist the resulting absolute Unix-second deadline. Restarting the wallet MUST NOT restart the duration.

For timestamp, the supplied time MUST be strictly later than the wallet's current time at activation. For blockheight, the supplied height MUST be greater than the wallet's current best-chain height at activation. If an absolute deadline is no longer in the future after prefunding, the wallet MUST fail without releasing the protected action; the anchor remains ordinary wallet-controlled value.

The deadline is immutable after activation. Extending a deadline requires a new protected action and revocation of the old one.

Wallets SHOULD use a clock that is resistant to simple application manipulation. A block-height deadline MUST use the wallet's canonical best-chain tracker. Once a height deadline has triggered, a later reorganization MUST NOT reactivate the protected action.

signAction

A protected createAction MAY return signableTransaction when one or more caller-supplied inputs require unlocking scripts. The expiry is already active; it does not wait for signAction. The signable transaction returned by createAction MUST NOT contain a valid unlocking script for the revocation anchor. Until signAction releases that signature, the caller cannot make a valid transaction that spends the anchor.

For a protected action, signAction MUST:

  • inherit the original expiry label and anchor reservation through the BRC-100 action reference;
  • retain options.noSend: true;
  • reject sendWith, any request to broadcast, or completion at or after the deadline;
  • reject completion after reclamation has begun; and
  • preserve the committed input and output set.

Before returning a completed transaction, the wallet MUST durably record that the anchor signature has been released. From that point, abort or expiry requires an on-chain reclaim rather than only local reservation cleanup.

The wallet MUST apply the same monitoring and reclamation behavior whether the protected action is completed directly by createAction or later by signAction, regardless of which caller supplies the non-anchor unlocking scripts.

Before expiry

The wallet MUST NOT broadcast the protected action, enqueue it for delayed broadcast, or include it in another action's broadcast batch. It MAY return the transaction to the application as required by BRC-100.

The wallet MUST monitor the protected transaction when its txid is known and MUST monitor the revocation anchor in all cases. If, before expiry, the wallet observes that the protected action has been mined or accepted for processing by a transaction processor, it MUST cancel reclamation and move the action into ordinary BRC-100 broadcast/proof tracking. Expiry is a deadline for broadcast, not a deadline for confirmation.

If the anchor is observed spent but the wallet cannot obtain or validate the spending transaction, it MUST stop treating the anchor as reclaimable and investigate through its normal transaction-status process. It MUST NOT report successful reclamation merely because the protected txid is unknown.

Expiry and reclamation

When the deadline is reached without an observed broadcast, the wallet MUST:

  1. Reject further signAction and wallet-broadcast requests for the protected action.
  2. Check the protected action and anchor through its configured transaction-status services.
  3. If no valid anchor signature has ever been released, abort the unsigned action and return the still-unspent anchor to ordinary default-basket coin selection. No reclaim transaction is needed because the caller cannot produce a valid spend of the anchor.
  4. Otherwise, if the anchor still appears unspent, construct or retrieve a previously signed reclaim transaction that spends it to a fresh wallet-controlled output in the internal default basket.
  5. Broadcast the reclaim transaction immediately, without delayed-broadcast behavior.
  6. Persist and retry reclamation after transient failures until the reclaim transaction is proven, the protected action is observed, or another transaction is proven to have spent the anchor.

The reclaim transaction SHOULD use the anchor as its only input. Its output equals the anchor value less its network fee. Funding and reclamation fees are borne by the wallet owner.

A wallet MAY construct, sign, validate, and durably store the reclaim transaction before it releases the protected action. This permits an authoritative storage monitor without wallet keys to enforce the deadline after a restart or while wallet clients are offline. When a deployment relies on such a keyless monitor, it SHOULD pre-sign the reclaim before release. A pre-signed reclaim MUST remain internal to the wallet or its trusted storage services until reclamation is authorized, and the wallet MUST verify that it spends only the anchor to wallet-controlled value under its recorded fee policy.

Once the reclaim transaction is mined, the protected action conflicts with an already-spent outpoint and is invalid on that chain. Before then, expiry and reclaim broadcast do not create consensus finality: a protected action broadcast at nearly the same time may race the reclaim transaction. Wallets MUST report the transaction that actually wins and MUST NOT describe a pending or merely submitted reclaim as final.

If the protected action is first observed after a reclaim submission may already have occurred, the wallet MUST stop initiating or retrying reclaim submissions but MUST retain proof tracking for both transactions. Neither an observation nor a processor response resolves the race; only the wallet's normally validated proof evidence may expose the winning transaction's outputs as spendable.

Normal proof depth and reorganization handling apply to the reclaim transaction. Applications and recipients SHOULD broadcast with enough margin for the wallet to observe acceptance before the deadline.

Abort and early revocation

After activation, abortAction for a released protected action MUST start the same reclamation process immediately. Merely clearing the wallet's local reservation is insufficient because a valid transaction may already be outside the wallet.

If the protected action has not been released, abortAction MUST permanently prevent signAction for that reference and MAY return the anchor to ordinary default-basket coin selection without a reclaim transaction.

Before activation, an implementation MAY discard an unreleased candidate and return an accepted funding output to ordinary default-basket coin selection without creating a reclaim transaction, because no protected transaction has been exposed.

Persistence and coordination

Scheduling MUST be durable across restarts and wallet synchronization. A wallet with multiple devices or storage writers MUST coordinate anchor ownership so that only one reclaim transaction is intentionally produced, while every device enforces the same deadline and broadcast prohibition.

Only the authoritative active storage or monitor SHOULD initiate reclamation. Synchronized lifecycle merges MUST NOT regress an action from a state in which expiry or reclamation has already begun merely because another device reports a later wall-clock update. A reclaim output MUST remain unavailable for wallet coin selection until the wallet has validated proof that the reclaim won; synchronization alone is not proof.

This specification introduces no new BRC-100 method or response field and no new public action status. Implementations MAY keep detailed states such as active, broadcast-observed, reclaiming, reclaimed, and conflicted internally. Existing listActions behavior and BRC-111 permission checks apply to queries using the module label.

Application Communication

Action labels are wallet metadata; they are not committed into the Bitcoin transaction and are not automatically delivered in BEEF. An application that gives the transaction to a recipient MUST communicate the deadline separately using its own protocol. If the recipient must authenticate that deadline, the application SHOULD bind it to the transaction or anchor outpoint with an authenticated message or signature.

The off-chain representation of the deadline is outside this specification. Its meaning MUST agree with the wallet label: seconds is relative to wallet activation, while timestamp and blockheight are absolute.

Security Considerations

  • Race at expiry: This mechanism cannot guarantee that a reclaim transaction wins against a concurrent external broadcast. Only the transaction actually mined determines which spend is valid.
  • Observation gaps: A recipient may have broadcast through a service the wallet has not yet observed. Applications should use a safety margin and, where useful, communicate processor acceptance through their own protocol.
  • Clock integrity: A manipulated or badly skewed clock can trigger timestamp expiry early or late. Block height avoids wall-clock dependence but remains subject to ordinary chain reorganizations.
  • Fee changes: The anchor must remain economical to reclaim under plausible fee conditions. Wallets should include a conservative reclaim-fee allowance and keep retrying transient failures.
  • Reservation recovery: Clearing only local spentBy or equivalent state does not invalidate valid transaction bytes held elsewhere. Released actions require an actual anchor spend.
  • Bounded exposure: Prefunding limits the reserved wallet value to the action's intended value and fees. Caller-supplied inputs remain governed by their own owners and are not recovered by spending the anchor.
  • No on-chain expiry claim: The label is a wallet instruction, not a script condition, miner rule, or consensus timestamp.

Example Flow

An application requests a five-minute protected action with:

{
  "description": "Offer valid for five minutes",
  "outputs": [
    {
      "satoshis": 1000,
      "lockingScript": "...",
      "outputDescription": "Offer payment"
    }
  ],
  "labels": ["p nosend expiry seconds 300", "offer 42"],
  "options": { "noSend": true }
}

The wallet broadcasts a funding transaction whose dedicated anchor covers the 1,000 output and the protected action's fee. After processor acceptance, it creates and returns the protected action, persists the absolute deadline, and never broadcasts that action itself.

If the recipient broadcasts the action and the wallet observes acceptance within five minutes, normal proof tracking takes over. Otherwise the wallet spends the anchor to a fresh default output. Once that reclaim is mined, the recipient's transaction can no longer be valid on the same chain.

References

Was this helpful?

Search Beersy

Search standards by number, title, author or topic