> ## Documentation Index
> Fetch the complete documentation index at: https://docs.handcash.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Collectables

> Read, mint, transfer, receive, and verify 1Sat items with the BRC wallet

<Warning>
  **Beta — BRC wallet only.** Desktop / Mobile over [BRC-100](https://brc.dev/100), not Connect Items.
</Warning>

## The model

A collectable is a **one-satoshi ordinal output**. The wallet holds it in storage basket `1sat`, and your app addresses it three different ways depending on what you are doing:

| Concept | Value | Used for |
| - | - | - |
| Origin | The genesis outpoint, `<txid>_0` | Stable identity of the item across owners |
| Tip | The current unspent one-satoshi outpoint | The thing a transfer spends |
| Row id | A wallet-assigned key, tag `id:<key>` | Naming one held row in reads and spends |

Your app builds the inscription script. The wallet funds the transaction, signs it, files the output, and shows the user what they are approving. Item calls always prompt — auto-pay never covers them.

Prerequisites: a [connected origin](/brc-wallet/permissions#connect-an-origin) and, for reads, an item view grant.

## Read inventory

Reads use scoped permission baskets, never the storage basket. See [Permissions and scopes](/brc-wallet/permissions#scoped-inventory-views) for the full grammar and grant behaviour.

| Basket | Required tag |
| - | - |
| `p 1sat all` | none |
| `p 1sat collection` | `collection:<id>` |
| `p 1sat app` | `app:<id>` |
| `p 1sat creator` | `creator:<id>` |
| `p 1sat id` | `id:<rowId>` |

### All items

```typescript theme={null}
const inventory = await wallet.listOutputs({
  basket: 'p 1sat all',
  includeCustomInstructions: true,
  limit: 50,
  offset: 0
});

console.log(`${inventory.totalOutputs} rows visible to this app`);
```

`totalOutputs` is what your grant exposes, not the wallet's true holdings. A user who approved two collections sees two collections here, with HTTP `200` and no error.

### One collection

```typescript theme={null}
const robots = await wallet.listOutputs({
  basket: 'p 1sat collection',
  tags: ['collection:robots'],
  tagQueryMode: 'all',
  includeCustomInstructions: true,
  limit: 50
});
```

### One exact row, with its transaction

```typescript theme={null}
const item = await wallet.listOutputs({
  basket: 'p 1sat id',
  tags: [`id:${rowId}`],
  tagQueryMode: 'all',
  includeCustomInstructions: true,
  include: 'entire transactions',
  limit: 1
});
```

A specific row id resolves without a prompt, which makes it the right call for a detail screen after the user already picked an item. Tags are always returned for item reads.

## Item vocabulary

### Tags

| Tag | Meaning | Written by |
| - | - | - |
| `ordinal` | Protocol marker | Your app; the wallet adds it on its own imports |
| `origin:<txid>.<vout>` | Genesis outpoint | Your app |
| `name:<text>` | Display name, shown in prompts | Your app |
| `app:<id>` | Owning app, up to 40 characters | Your app |
| `collection:<id>` | Collection, up to 80 characters | Your app |
| `creator:<id>` | Creator, a view filter axis only | Your app |
| `content:<outpoint>` | Shared media output for derivative items | Your app |
| `id:<key>` | Held-row key | **The wallet**, unless you supply one |

An item with none of `name`, `app`, `collection`, `content`, or `id` is invisible to third-party apps, so always tag at least a name.

### `customInstructions`

Send a JSON string. The wallet reads these fields:

| Field | Meaning |
| - | - |
| `origin` | Genesis outpoint in `<txid>_<vout>` form |
| `name` | Display name |
| `app` | Owning app id |
| `collectionId` | Collection binding used when matching view grants |
| `content` | Shared media outpoint for derivative items |
| `creator` | Creator, used as a view-matching fallback |
| `provenance` | BRC-150 authenticity proof — wallet-written on send, see below |

## Mint an item

An item mint is a `createAction` with **no inputs** and at least one one-satoshi output in an item basket:

```typescript theme={null}
const { txid } = await wallet.createAction({
  description: 'Mint Robot #42',
  labels: ['1sat', 'item', 'app:robot-foundry'],
  outputs: [
    {
      lockingScript: ordinalInscriptionScript,
      satoshis: 1,
      outputDescription: 'Robot #42 collectable',
      basket: '1sat',
      tags: ['ordinal', 'app:robot-foundry', 'collection:robots', 'name:Robot #42'],
      customInstructions: JSON.stringify({
        name: 'Robot #42',
        app: 'robot-foundry',
        collection: 'robots',
        mediaType: 'image/png'
      })
    }
  ]
});
```

The wallet stamps a row id onto the output tags so you can address this exact item later. Baskets and tags index the item; they never create the inscription, so validate the script against the 1Sat protocol before you send it.

The user sees a **Mint item** prompt with your description and the item name from the `name:` tag. Refusal returns `403 ACTION_DENIED`; funding problems return `400 INSUFFICIENT_FUNDS` or `INSUFFICIENT_OR_STALE_FUNDS`.

## Transfer an item

A transfer spends the held tip and creates the next one. Name the held row with a spend label so the wallet knows which item you mean:

```typescript theme={null}
const { txid } = await wallet.createAction({
  description: 'Send Robot #42',
  labels: ['1sat', 'send-collectable', `p 1sat input id ${rowId}`],
  inputBEEF: itemInputBeef,
  inputs: [
    {
      outpoint: heldItemOutpoint,
      inputDescription: 'Robot #42 1Sat tip',
      unlockingScript: itemUnlockingScript
    }
  ],
  outputs: [
    {
      lockingScript: recipientItemScript,
      satoshis: 1,
      outputDescription: 'Robot #42 transfer'
    }
  ]
});
```

The label grammar is exactly `p 1sat input id <rowId>`. Each label must resolve to exactly one held row **and** that row's outpoint must appear in `inputs`, or the call fails with `400 INVALID_P1SAT_SPEND` before the user is ever prompted.

Supply `inputBEEF` for the tip's source transactions. If you send `unlockingScriptLength` instead of a script, you get a staged transaction back and must finish that exact reference with [`signAction`](/brc-wallet/payments#stage-and-sign-an-action) — the wallet does not hold your unlock recipe and never exposes a root private key.

Covenant-locked tips cannot be spent with a plain P2PKH unlock; the wallet refuses rather than guessing. If a tip has gone missing on chain, that is a wallet-side abandon in HandCash, not something an app resolves with `relinquishOutput`.

### Burning

There is no burn method for apps. A burn is a transfer whose output is a protocol-valid terminal spend that your app constructs. Never turn a failed item path into a plain payment.

## Receive an item

```typescript theme={null}
const { accepted } = await wallet.internalizeAction({
  tx: atomicBeef,
  description: 'Receive Robot #42',
  labels: ['1sat', 'receive-collectable'],
  outputs: [
    {
      outputIndex: 0,
      protocol: 'basket insertion',
      insertionRemittance: {
        basket: '1sat',
        tags: ['ordinal', `origin:${originTxid}.${originVout}`, 'name:Robot #42', 'app:robot-foundry'],
        customInstructions: JSON.stringify({
          origin: `${originTxid}_${originVout}`,
          name: 'Robot #42',
          app: 'robot-foundry'
        })
      }
    }
  ]
});
```

`protocol` must be the string `basket insertion`, and the remittance basket is the storage basket `1sat`.

<Warning>
  Keep `insertionRemittance.customInstructions` small — identity fields only. The receive path caps it near 1,000 characters, so a full BRC-150 proof does not fit. The wallet rebuilds and verifies provenance locally instead.
</Warning>

The wallet validates the transaction before accepting it. Do not tell the sender a delivery succeeded until this call resolves. The user sees a **Receive item** prompt the first time; after that a stored receive grant can carry it.

## Authenticity

HandCash shows an item as verified only when a complete **BRC-150 v2** proof checks out. The proof is a local object in `customInstructions.provenance`:

```typescript theme={null}
type Provenance = {
  v: 2;
  origin: string;      // genesis outpoint
  tip: string;         // current outpoint
  path: string[];      // tip → origin lineage
  beefB64: string;     // BEEF covering every hop
  contentType?: string;
};
```

What the wallet checks: the path starts at the tip and ends at the origin, every hop is an exact parent spend, one-satoshi continuity holds throughout, and the origin carries a valid ordinal envelope.

| Outcome | What the user sees |
| - | - |
| Complete BRC-150 proof | Verified |
| Missing or incomplete proof | Unproven, with identity resolved by an indexer |

Two rules matter for app authors. Provenance is **local metadata** — it does not ride along on a P2PKH lock, so a recipient's wallet reconstructs it. And an oversized proof is **omitted, never truncated**: the wallet's cap is 400,000 characters of BEEF, above which the item stays unproven rather than carrying a partial lineage.

## Release a row

```typescript theme={null}
await wallet.relinquishOutput({
  basket: 'p 1sat all',
  output: heldItemOutpoint
});
```

This drops the wallet's record of the output. It is not a burn, it moves no coins, and it prompts as **Release item**. Use `p 1sat all` here: scoped forms that require a tag cannot carry one on this call.

## Not available to apps

| Attempt | Result |
| - | - |
| `listOutputs({ basket: '1sat' })` | `400 USE_P1SAT_SCOPE` |
| A persistent "all items" grant | Converted to a filtered collection and app ceiling |
| Auto-pay covering an item mint, send, or release | Always prompts |
| Wallet-side burn or abandon | HandCash UI only |
| Market listing and purchase methods | HandCash hosts only, `403 MARKET_ORIGIN_DENIED` |
| BRC-156 soft-latch | Removed; authenticity is BRC-150 only |

## Next steps

* [Tokens](/brc-wallet/tokens)
* [Permissions and scopes](/brc-wallet/permissions)
* [Signing and encryption](/brc-wallet/signing)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.