Sign PSBT
Sign a Partially-Signed Bitcoin Transaction with the user's passkey. Supports selective input signing, sighash flags, and optional broadcast.
utxo.signPsbt signs the inputs your session owns in a PSBT, leaving all other inputs untouched. Optionally broadcasts the fully-signed result.
import { utxo } from '@oviato/connect';
const r = await utxo.signPsbt({
psbt: '70736274ff01007d...', // PSBT as hex
broadcast: true,
});
if (r.status === 'success') {
if (r.type === 'signPsbtBroadcastSuccess') {
console.log('TxID:', r.data.txid);
} else if (r.type === 'signPsbtSuccess') {
console.log('Signed PSBT:', r.data.psbt);
}
}Parameters
| Field | Type | Notes |
|---|---|---|
psbt | string | PSBT as hex (not base64). Required. |
signInputs | SignPsbtInput[]? | If omitted, every owned input is signed. |
broadcast | boolean? | If true, broadcasts after signing. Default false. |
method | 'popup' | 'iframe'? | How the approval UI opens. |
SignPsbtInput
type SignPsbtInput = {
index: number; // Which input to sign (0-based)
address: string; // The address that owns that input — used for key derivation
sighashTypes?: number[]; // Optional SIGHASH flags (default: SIGHASH_ALL)
};Response shape
Discriminated by whether you asked to broadcast.
type Result =
| { status: 'success'; type: 'signPsbtSuccess'; data: { psbt: string } }
| { status: 'error'; message: string };Use this when a different party (a marketplace, aggregator, second cosigner) will broadcast after you sign.
type Result =
| { status: 'success'; type: 'signPsbtBroadcastSuccess'; data: { txid: string; explorerUrl?: string } }
| { status: 'error'; message: string };Use this for straightforward "sign + send" flows where you built the PSBT yourself.
Common flows
Sign all owned inputs + broadcast
The simplest case. psbt in, txid out.
const r = await utxo.signPsbt({
psbt: rawPsbtHex,
broadcast: true,
});
if (r.status === 'success' && r.type === 'signPsbtBroadcastSuccess') {
window.open(r.data.explorerUrl!, '_blank');
}Sign without broadcasting
Useful for marketplace flows — you sign your half, the counterparty signs + broadcasts.
const r = await utxo.signPsbt({
psbt: marketplaceBuiltPsbt,
broadcast: false,
});
if (r.status === 'success' && r.type === 'signPsbtSuccess') {
await fetch('/api/marketplace/complete', {
method: 'POST',
body: JSON.stringify({ signedPsbt: r.data.psbt }),
});
}Sign specific inputs only
If the PSBT has inputs owned by multiple addresses and you want to sign only some:
import { SigHashTypes } from '@oviato/connect';
const r = await utxo.signPsbt({
psbt: rawPsbtHex,
signInputs: [
{ index: 0, address: session.address },
{ index: 2, address: session.address, sighashTypes: [SigHashTypes.SINGLE | SigHashTypes.ANYONECANPAY] },
],
broadcast: false,
});Default sighash is SIGHASH_ALL — signs over all inputs + outputs. Use
SIGHASH_SINGLE | SIGHASH_ANYONECANPAY for marketplace flows where your
signature covers only your input and its matching output.
Getting a PSBT
PSBTs usually come from one of two places:
- Your own backend. You do coin-selection + construct the transaction server-side and pass the PSBT hex to the frontend for signing.
- A counterparty or protocol. A different party builds the PSBT and hands it to you to add your signature.
Either way, the PSBT arrives as a hex string that you pass directly to utxo.signPsbt.
Validation before signing
The bridge UI decodes the PSBT before showing the user, flagging:
- Which inputs they own (green), which they don't (grey)
- Which outputs go to them (change), which go elsewhere
- Estimated network fee
- Any unusual characteristics (OP_RETURN, large opcodes, etc.)
The user can always see the full decoded tx before approving.
Related
- Send Transfers — simpler native BTC sends
- Recipes — common UTXO flows