On this page
Bitcoin (PSBT)
PSBT data handling for offline signing with animated UR codes.
Data Types
- Request/response UR type:
crypto-psbt - See also: Basic API (UR primitives and structures)
Flow
- Build an unsigned PSBT with your wallet backend
- Encode PSBT → UR (
crypto-psbt) and display as animated QR - Device scans → returns signed PSBT as
crypto-psbt - Finalize and broadcast
Tip: Keep PSBT size within practical QR limits; split across frames as needed.
Examples
Install @keystonehq/bc-ur-registry and @ngraveio/bc-ur. The examples require a Buffer implementation. Verify PSBT support on the target device and firmware.
The wallet supplies the unsigned transaction data and the actual account path and master fingerprint exported by the device. The QR renderer should call nextFrame() on a timer and stop when the signing dialog closes. These snippets encode and decode the transport; they do not submit a transaction.
Encode unsigned PSBT → animated QR
import { CryptoPSBT } from '@keystonehq/bc-ur-registry';
const psbtBytes = Buffer.from(unsignedPsbtBase64, 'base64');
const req = new CryptoPSBT(psbtBytes);
const encoder = req.toUREncoder(200);
const nextFrame = () => encoder.nextPart(); // Call from your QR animation timer.Decode animated QR → signed PSBT
import { URDecoder } from '@ngraveio/bc-ur';
import { CryptoPSBT } from '@keystonehq/bc-ur-registry';
const dec = new URDecoder();
// For each scanned frame string from camera:
// dec.receivePart(frameString)
if (dec.isError()) throw new Error('QR decoding failed');
if (dec.isSuccess()) {
const ur = dec.resultUR();
if (ur.type !== 'crypto-psbt') throw new Error('Expected crypto-psbt');
const psbt = CryptoPSBT.fromCBOR(ur.cbor);
const signedPsbt = psbt.getPSBT(); // Buffer of PSBT (signed or partially signed)
// Check that the unsigned transaction, inputs, and outputs match the original PSBT.
// Verify signatures before finalizing and broadcasting with your backend.
}Last updated on