# List a UTXO

Listing is three steps: **prepare** a PSBT, **sign** it with SIGHASH_SINGLE|ANYONECANPAY (`0x83`), **submit** it. Your key never leaves your wallet or script; the server keeps only the signature, and never shows it to anyone.

## Before you start

- The UTXO must be confirmed, hold tracked rare sats of a market row, and carry no inscription or runes. Find yours with `GET /api/address/{address}`.
- It must belong to a **Taproot**, **native SegWit** or **nested SegWit** address. Legacy (`1…`) is refused with `legacy_address`.
- Choose a `price` in sats: what you receive. It must be above the UTXO's own value and at least 546. Buyers pay 2.5% on top; you pay nothing.

## 1. Prepare

`POST /api/market/listings/prepare` with your address, its public key and the UTXOs (up to 50 in one PSBT, one signature prompt):

```json title="Request"
{
  "seller_address": "bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
  "seller_public_key": "0283dfe85a3151d2517290da461fe2815591ef69f2b18a2ce63f01697a8b313145",
  "items": [{ "outpoint": "8b09d6914ae17431940ce8b6cadae378a5d9b3c90c73c3a5b0b8c8e7edc9e86a:0", "price": 94200 }]
}
```

- `seller_public_key`: for Taproot, the **internal** (untweaked) key, x-only (32 bytes) or compressed (33 bytes); for SegWit, the compressed key. Wallets return it with the address.
- `payout_address` (optional) is where the price goes; it defaults to `seller_address`. Xverse-style wallets usually pay out to their payment address.

The answer is the PSBT, the inputs to sign (`sighash: 131` = `0x83`) and what each listing will show:

```json title="Response"
{
  "psbt": "cHNidP8BAF4CAAAAAWroye3n…",
  "sign": [{ "index": 0, "address": "bc1p4qhj…fqzwh", "sighash": 131 }],
  "items": [{ "outpoint": "8b09…e86a:0", "value": 6280, "price": 94200, "unit_price": 14, "row": "block9", "count": 6280 }]
}
```

The PSBT pairs input *i* (your UTXO) with output *i* (your price to your payout address). Version 2, locktime 0, sequence `0xfffffffd`. Check those outputs before signing.

## 2. Sign with `0x83`

Sign every input in `sign` with sighash `0x83` (SIGHASH_SINGLE|ANYONECANPAY). Your signature then commits only to your UTXO and to your payment output: a buyer can add their inputs and outputs, but cannot change what you receive. Do not finalize or broadcast.

- **UniSat:** `unisat.signPsbt(hex, {autoFinalized: false, toSignInputs: [{index, address, sighashTypes: [0x83]}]})`.
- **Xverse (Sats Connect):** `signPsbt` with `signInputs: {[address]: [indexes]}` and `broadcast: false`; the sighash comes from the PSBT input's `sighashType`, which prepare set to `0x83`.
- **Your own key**: the Node script below.

## 3. Submit

`POST /api/market/listings` with `{"psbt": "<signed base64>"}`. The server checks every rule again, verifies each signature against the chain's amount and script, and answers your new listings, `active`:

```json title="Response"
{ "listings": [{ "id": "3324d08569806818", "status": "active", "price": 94200, "unit_price": 14, "row": "block9", "count": 6280, "…": "…" }] }
```

To **change a price**, list the same UTXO again: the new listing replaces the old one (now `cancelled`, its signature deleted). To take it off sale, [cancel it](https://raresats.net/docs/guide-cancel.md).

## A complete Node example

A script that lists a UTXO of a Taproot key: Node 18+, `npm install bitcoinjs-lib tiny-secp256k1 ecpair`.

```js title="list.mjs"
// node list.mjs <txid:vout> <price in sats>
// WIF: the private key (mainnet WIF) of the Taproot address holding the UTXO.
import * as bitcoin from 'bitcoinjs-lib';
import * as ecc from 'tiny-secp256k1';
import { ECPairFactory } from 'ecpair';

bitcoin.initEccLib(ecc);
const ECPair = ECPairFactory(ecc);
const BASE = process.env.RARESATS_URL ?? 'https://raresats.net';
const [outpoint, price] = process.argv.slice(2);

const key = ECPair.fromWIF(process.env.WIF);
const internalKey = key.publicKey.subarray(1, 33);
const { address } = bitcoin.payments.p2tr({ internalPubkey: internalKey });

async function post(path, body) {
  const response = await fetch(`${BASE}${path}`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(body)
  });
  const json = await response.json();
  if (!response.ok) {
    throw Object.assign(new Error(`${path}: ${response.status} ${json.code}: ${json.error}`), { code: json.code });
  }
  return json;
}

// 1. Prepare: the server checks the UTXO and builds the PSBT.
const prepared = await post('/api/market/listings/prepare', {
  seller_address: address,
  seller_public_key: Buffer.from(key.publicKey).toString('hex'),
  items: [{ outpoint, price: Number(price) }]
});

// 2. Check what you sign, then sign every input with 0x83 (SINGLE|ANYONECANPAY).
const psbt = bitcoin.Psbt.fromBase64(prepared.psbt);
psbt.txOutputs.forEach((output, i) => {
  if (Number(output.value) !== prepared.items[i].price || bitcoin.address.fromOutputScript(output.script) !== address) {
    throw new Error(`output ${i} does not pay the price to ${address}`);
  }
});
const signer = key.tweak(bitcoin.crypto.taggedHash('TapTweak', internalKey));
for (const { index, sighash } of prepared.sign) {
  psbt.signInput(index, signer, [sighash]);
}

// 3. Submit: the listing is live.
const { listings } = await post('/api/market/listings', { psbt: psbt.toBase64() });
for (const listing of listings) {
  console.log(`${listing.id} ${listing.outpoint}: ${listing.price} sats, ${listing.unit_price} per sat of ${listing.row} x${listing.count}`);
}
```

For a native SegWit (P2WPKH) key, use `bitcoin.payments.p2wpkh({ pubkey: key.publicKey })` for the address and sign with `key` itself (no tweak).

## Errors you may meet

| Code | What to do |
| --- | --- |
| `not_owner`, `key_mismatch` | The UTXO or the key is not the address's. |
| `untracked`, `no_row` | It holds no rare sat a market row tracks. |
| `inscribed`, `runes` | Only plain rare sats are listed. |
| `unconfirmed`, `unindexed`, `indexer_behind`, `fork_behind` | Wait for a confirmation and the indexers; try again in a minute. |
| `price_too_low` | Price above the UTXO's value and at least 546 (and the payout's dust limit). |
| `spent`, `changed`, `utxo_mismatch` | The UTXO moved, or the PSBT's amount is not the chain's. Prepare again. |
| `pending` | It is being bought right now. |
| `bad_signature`, `bad_sighash`, `missing_signature` | Sign every input in `sign`, with `0x83`. |

Every code: [Errors](https://raresats.net/docs/errors.md).
