> For the complete documentation index, see [llms.txt](https://bsv.brc.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bsv.brc.dev/wallet/0177.md).

# Wallet-Enforced Expiry for noSend Actions

Ty Everett (<ty@projectbabbage.com>)

## Abstract

This specification defines the BRC-111 module label `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 protected action itself. If the wallet has not observed an external broadcast by the deadline, it spends the same funding output back to its `default` basket, invalidating the protected transaction once the reclaim transaction 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 UTXO 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 module ID `nosend` and the following exact action-label forms:

| Label                                     | Meaning                                                                                 |
| ----------------------------------------- | --------------------------------------------------------------------------------------- |
| `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 unlocking script for the anchor. 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 BEEF 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 outpoint, 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:

```json
{
  "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 satoshi 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

* [BRC-62: Background Evaluation Extended Format (BEEF) Transactions](/transactions/0062.md)
* [BRC-65: Transaction Labels and List Actions](/wallet/0065.md)
* [BRC-100: Unified Wallet-to-Application Interface](/wallet/0100.md)
* [BRC-111: P Labels](/wallet/0111.md)
* RFC 2119 and RFC 8174: Key words for use in RFCs to indicate requirement levels


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://bsv.brc.dev/wallet/0177.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
