UNPKG

@imikailoby/sats

Version:

Tiny non-custodial Bitcoin SDK (TS) — keys, addresses, PSBT, provider chain

72 lines (71 loc) 2.72 kB
import * as bitcoin from "bitcoinjs-lib"; import { ECPairFactory } from "ecpair"; import * as ecc from "tiny-secp256k1"; import { network } from "../net/index"; import { ecPairToSigner } from "../crypto/signer"; import { InsufficientFundsError, PsbtBuildError } from "../core/errors"; import { toBuffer } from "../utils/bytes"; bitcoin.initEccLib(ecc); const ECPair = ECPairFactory(ecc); /** Sum input values in satoshis. */ export function sumInputs(utxos) { return utxos.reduce((s, u) => s + u.value, 0); } /** Sum output values plus a fixed fee. */ export function sumOutputs(outputs, fee) { return outputs.reduce((s, o) => s + o.value, 0) + fee; } /** Calculate non-negative change amount. */ export function calcChange(totalIn, totalOut) { return Math.max(0, totalIn - totalOut); } /** * Build a PSBT for P2WPKH-like inputs. * @throws PsbtBuildError | InsufficientFundsError */ export function buildPsbt(params) { const n = network(!!params.testnet); const psbt = new bitcoin.Psbt({ network: n }); if (params.fee < 0) throw new PsbtBuildError("fee must be >= 0"); params.outputs.forEach((o) => { if (o.value <= 0) throw new PsbtBuildError("output value must be > 0"); }); const totalIn = sumInputs(params.utxos); const totalOut = sumOutputs(params.outputs, params.fee); if (totalOut > totalIn) throw new InsufficientFundsError("insufficient funds"); params.utxos.forEach((u) => { psbt.addInput({ hash: u.txid, index: u.vout, witnessUtxo: { value: u.value, // Require provided scriptPubKey for correctness; fallback stays for BC but is discouraged. script: u.scriptPubKey ? Buffer.from(u.scriptPubKey, "hex") : bitcoin.address.toOutputScript(params.changeAddress, n), }, }); }); params.outputs.forEach((o) => psbt.addOutput({ address: o.address, value: o.value })); const change = calcChange(totalIn, totalOut); if (change > 0) psbt.addOutput({ address: params.changeAddress, value: change }); return psbt; } /** Sign PSBT with WIF or raw private key. */ export function signPsbt(psbt, priv, testnet) { const kp = typeof priv === "string" ? ECPair.fromWIF(priv, network(!!testnet)) : ECPair.fromPrivateKey(toBuffer(priv), { network: network(!!testnet) }); psbt.signAllInputs(ecPairToSigner(kp)); return psbt; } /** Finalize all inputs and return hex and txid. */ export function finalizePsbt(psbt) { psbt.finalizeAllInputs(); const tx = psbt.extractTransaction(); return { hex: tx.toHex(), txid: tx.getId() }; }