> For the complete documentation index, see [llms.txt](https://leather.gitbook.io/developers/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://leather.gitbook.io/developers/bitcoin-methods/signpsbt.md).

# signPsbt

Request the signature and broadcast of a Partially Signed Bitcoin Transaction (PSBT)

## Method name

`signPsbt`

## Parameters

<table><thead><tr><th width="169">Parameter</th><th width="348">Description</th><th width="96" data-type="checkbox">Required</th><th width="123">Type</th></tr></thead><tbody><tr><td>hex</td><td>Hex of PSBT payload for signing</td><td>true</td><td>string</td></tr><tr><td>allowedSighash</td><td>Sighash types allowed for signing and finalizing inputs (defaults to type ALL)</td><td>false</td><td>SignatureHash[]</td></tr><tr><td>signAtIndex</td><td>Input index(es) that should be signed (defaults to sign all inputs)</td><td>false</td><td>number | number[]</td></tr><tr><td>network</td><td>Network for signing: <code>mainnet</code>, <code>testnet</code>, <code>signet</code>, <code>sbtcDevenv</code> or <code>devnet</code> (defaults to the user's current network)</td><td>false</td><td>string</td></tr><tr><td>account</td><td>Index of account for signing (defaults to active account)</td><td>false</td><td>uint</td></tr><tr><td>broadcast</td><td>Whether to broadcast upon signing (default <code>false</code>). <code>txid</code> returned on successful broadcast.</td><td>false</td><td>boolean</td></tr><tr><td>descriptor</td><td>A <code>wsh(...)</code> output descriptor. Leather signs the inputs it locks with the active account's key. See <a href="#sign-inputs-locked-by-a-wsh-descriptor">Sign inputs locked by a wsh() descriptor</a>. Leather browser extension 6.103.1 and later.</td><td>false</td><td>string</td></tr></tbody></table>

## Example request

### Sign PSBT

<pre class="language-typescript"><code class="lang-typescript"><strong>interface SignPsbtRequestParams {
</strong>  hex: string;
  allowedSighash?: SignatureHash[];
  signAtIndex?: number | number[];
  network?: NetworkModes;         // default is user's current network
  account?: number;               // default is user's current account
  broadcast?: boolean;            // default is false - finalize/broadcast tx
  descriptor?: string;            // wsh(...) descriptor, extension 6.103.1 and later
}

const requestParams: SignPsbtRequestParams = { ...params };

const response = await window.LeatherProvider.request('signPsbt', requestParams);
</code></pre>

### Broadcast PSBT

Our API returns a PSBT back to the app, with the newly applied signatures. If your use-case is to spend the transaction immediately, you'll first need to `finalize` it, then broadcast it to the network.

See this example below how to extract a signed transaction from the PSBT

```typescript
import * as btc from '@scure/btc-signer';
const hex = response.result.hex;
const tx = btc.Transaction.fromPSBT(hexToBytes(hex));
tx.finalize();
await broadcast(tx.hex);
```

### Types and helper functions

```tsx
interface BitcoinNetwork {
  bech32: string;
  pubKeyHash: number;
  scriptHash: number;
  wif: number;
}

const bitcoinTestnet: BitcoinNetwork = {
  bech32: 'tb',
  pubKeyHash: 0x6f,
  scriptHash: 0xc4,
  wif: 0xef,
};

const ecdsaPublicKeyLength = 33;

function ecdsaPublicKeyToSchnorr(pubKey: Uint8Array) {
  if (pubKey.byteLength !== ecdsaPublicKeyLength) throw new Error('Invalid public key length');
  return pubKey.slice(1);
}

function getTaprootPayment(publicKey: Uint8Array) {
  return btc.p2tr(ecdsaPublicKeyToSchnorr(publicKey), undefined, bitcoinTestnet);
}
```

### Helper functions for Native SegWit `PsbtRequestOptions`

```tsx
// Example request options for Native SegWit
function buildNativeSegwitPsbtRequest(pubKey: Uint8Array): PsbtRequestOptions {
  // SegWit pubKey used here is provided through auth response
  const p2wpkh = btc.p2wpkh(pubKey, bitcoinTestnet);

  const tx = new btc.Transaction();

  // Example inputs with included witness
  tx.addInput({
    index: 0,
    txid: '15f34b3bd2aab555a003cd1c6959ac09b36239c6af1cb16ff8198cef64f8db9c',
    witnessUtxo: {
      amount: BigInt(1000),
      script: p2wpkh.script,
    },
  });
  tx.addInput({
    index: 1,
    txid: 'dca5179afaa63eae112d8a97794de2d30dd823315bcabe6d8b8a6b98e3567705',
    witnessUtxo: {
      amount: BigInt(2000),
      script: p2wpkh.script,
    },
  });
  
  // Add outputs and/or other psbt data...

  const psbt = tx.toPSBT();

  // This example is choosing to sign all inputs bc no index(es) are provided
  return { hex: bytesToHex(psbt) };
}
```

### Helper functions for Taproot `PsbtRequestOptions`

```tsx
// Similar example request options for Taproot
function buildTaprootPsbtRequest(pubKey: Uint8Array): PsbtRequestOptions {
  // Taproot pubKey used here is provided through auth response
  const payment = getTaprootPayment(pubKey);

  const tx = new btc.Transaction();

  tx.addInput({
    index: 0,
    tapInternalKey: payment.tapInternalKey,
    txid: '15f34b3bd2aab555a003cd1c6959ac09b36239c6af1cb16ff8198cef64f8db9c',
    witnessUtxo: {
      amount: BigInt(1000),
      script: payment.script,
    },
  });
  tx.addInput({
    index: 1,
    tapInternalKey: payment.tapInternalKey,
    txid: 'dca5179afaa63eae112d8a97794de2d30dd823315bcabe6d8b8a6b98e3567705',
    witnessUtxo: {
      amount: BigInt(2000),
      script: payment.script,
    },
  });
  
  // Add outputs and/or other psbt data...

  const psbt = tx.toPSBT();

  return { hex: bytesToHex(psbt) };
}
```

## Sign inputs locked by a wsh() descriptor

Pass a `wsh(...)` output descriptor as `descriptor` to sign PSBT inputs locked by a P2WSH miniscript policy, such as a multisig or a timelocked script, in which the active account holds one of the keys. Available in the Leather browser extension from [6.103.1](https://github.com/leather-io/mono/releases/tag/%40leather.io%2Fextension-v6.103.1).

```typescript
const response = await window.LeatherProvider.request('signPsbt', {
  hex: psbtHex,
  descriptor: policyDescriptor, // a wsh(...) descriptor that includes the user's key
});
```

The descriptor must include the active account's Native SegWit key in one of two forms:

* The account's extended public key with a `/0/N` or `/1/N` key path. Every extended key in the descriptor must use the same key path, and Leather signs with the account key at that path.
* The raw public key of the account's first Native SegWit address (`m/84'/0'/0'/0/0` for account 0 on mainnet), the `publicKey` that `getAddresses` returns for the `p2wpkh` entry.

With a descriptor, Leather compiles it to its witness script and output script, signs every PSBT input whose output script matches, and adds the witness script to those inputs. `signAtIndex` is not used in this mode. The matched inputs must use `SIGHASH_DEFAULT` or `SIGHASH_ALL`.

The response `hex` is the PSBT with the account's partial signature. With `broadcast: true`, Leather also tries to satisfy the whole policy from what the PSBT already carries: the other partial signatures, and hash preimages in the inputs' BIP-174 preimage fields. If that succeeds, it finalizes and broadcasts the transaction and returns `txid`. If not, it returns the partially signed PSBT without broadcasting, so your app can collect the remaining signatures.

On a Ledger account, the device signs. Each co-signer key given as a raw public key must resolve to an extended public key from the PSBT's `PSBT_GLOBAL_XPUB` or `PSBT_IN_BIP32_DERIVATION` fields, or the request fails.

| Error                                                                                            | When                                                                                                                   |
| ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `-32602`, `Only wsh() descriptors are supported`                                                 | `descriptor` does not start with `wsh(`                                                                                |
| `-32602`, `Request rejected: an input to sign uses a sighash type not covered by allowedSighash` | A matched input uses a sighash type other than `SIGHASH_DEFAULT` or `SIGHASH_ALL`                                      |
| `-32600`, `Error signing transaction`                                                            | The active account's key is not in the descriptor, no PSBT input is locked by it, or a matched input was left unsigned |

## Multisig accounts

A user can add a Bitcoin multisig vault created at [leather.io/multisig](https://leather.io/multisig) to the Leather browser extension, from 6.112.0. Vaults are not yet available in the mobile app. When the active account is a Bitcoin vault, `signPsbt` does not sign. Leather proposes the PSBT to the vault's co-signers and returns the PSBT you sent, unsigned, with a proposal ID:

```json5
{
  "hex": "<the PSBT you sent>",
  "proposalId": "...",
  "status": "proposed"
}
```

Check `status` before you treat `hex` as signed. With a vault active, Leather only accepts requests that spend from it with its bond-exit template:

* `descriptor` is required and must be a `wsh(and_v(v:or_i(after(<unlock height>),and_v(v:sha256(<hash>),pk(<counterparty key>))),<vault multisig>))` descriptor whose multisig part matches the connected vault.
* Every PSBT input must be locked by that descriptor and use `SIGHASH_DEFAULT` or `SIGHASH_ALL`.

| Error                                                                         | When                                                         |
| ----------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `-32602`, `Proposing a transaction requires a descriptor and PSBT`            | No `descriptor`                                              |
| `-32602`, `Descriptor is not a supported bond template`                       | The descriptor does not follow the bond-exit template        |
| `-32600`, `Connected multisig account does not match this bond descriptor`    | The descriptor's multisig keys are not the connected vault's |
| `-32600`, `All PSBT inputs must be locked by the bond descriptor`             | An input is locked by something else                         |
| `-32600`, `Bond proposals only support SIGHASH_DEFAULT or SIGHASH_ALL inputs` | An input uses another sighash type                           |
| `-32603`, `Failed to propose transaction`                                     | The proposal could not be submitted                          |

## Custom scripts

For more complex PSBTs, such as those using custom Bitcoin scripts, we recommend you read the supporting documentation of the `@scure/btc-signer` library.

[Bitcoin script examples →](https://github.com/paulmillr/scure-btc-signer?tab=readme-ov-file#script)

## Debugging

If you're running into issues signing a PSBT with Leather, the best way to debug the problem is to sign it without Leather.

Starting with a test-only mnemonic, drive a BTC address, and try sign the transaction. This helps ensure your PSBT is constructed correctly.

Here's a [Codesandbox with a PSBT debugging environment](https://codesandbox.io/p/sandbox/debug-leather-psbt-signing-x2349s) set up for you to test. When asking for PSBT signing support, provide a test case forked from this.

## Preview

<figure><img src="https://2085786563-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F41U87FfDFo2dIKYr0ZNX%2Fuploads%2FxzH4T2l5x5nWpxS0NnFT%2FSCR-20240419-qtxp.png?alt=media&amp;token=c6d5f052-801d-4e08-afe4-b4bc5e734c1b" alt=""><figcaption></figcaption></figure>


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://leather.gitbook.io/developers/bitcoin-methods/signpsbt.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
