Skip to content

Instantly share code, notes, and snippets.

@ac12644
Created July 21, 2025 21:42
Show Gist options
  • Select an option

  • Save ac12644/5293c427e2f44f61cac7e2339b4d1d36 to your computer and use it in GitHub Desktop.

Select an option

Save ac12644/5293c427e2f44f61cac7e2339b4d1d36 to your computer and use it in GitHub Desktop.
import * as bitcoin from "bitcoinjs-lib";
import * as ECPairFactory from "ecpair";
import tinysecp from "tiny-secp256k1";
// Initialize ECPair for signing
const ECPair = ECPairFactory.ECPairFactory(tinysecp);
// Configuration (replace these with your real values or import from config)
const NETWORK = bitcoin.networks.testnet; // or bitcoin.networks.bitcoin for mainnet
const PLATFORM_KEY_WIF = "<YOUR_PLATFORM_WIF>"; // WIF of your platform's private key
const FEE_WALLET = "tb1q..."; // platform fee recipient
const FEE_BPS = 200; // 2%
interface Utxo {
tx_hash: string;
tx_pos: number;
value: number; // in satoshis
address: string;
}
/**
* Build, sign, and finalize a PSBT for a sticker purchase:
* - Input: buyer UTXO (paid to pool address)
* - Output1: platform fee
* - Output2: seller payout
* - Output3: OP_RETURN(metadata)
*/
export function createStickerPurchasePsbt(
utxo: Utxo,
sellerAddress: string,
productId: string,
productName: string
): string {
const platformKey = ECPair.fromWIF(PLATFORM_KEY_WIF, NETWORK);
// 1. Calculate sats
const grossSat = utxo.value;
const feeSat = Math.floor((grossSat * FEE_BPS) / 10000);
const netSat = grossSat - feeSat;
// 2. Create PSBT
const psbt = new bitcoin.Psbt({ network: NETWORK });
// 2a. Add input
psbt.addInput({
hash: utxo.tx_hash,
index: utxo.tx_pos,
witnessUtxo: {
script: bitcoin.address.toOutputScript(utxo.address, NETWORK),
value: utxo.value,
},
});
// 2b. Add fee output
psbt.addOutput({ address: FEE_WALLET, value: feeSat });
// 2c. Add seller payout
psbt.addOutput({ address: sellerAddress, value: netSat });
// 2d. Embed metadata via OP_RETURN (up to 80 bytes)
const metadata = `${productId}|${productName}`;
const data = Buffer.from(metadata, 'utf8');
if (data.length > 80) throw new Error('Metadata too long');
const embed = bitcoin.payments.embed({ data: [data] });
if (embed.output) psbt.addOutput({ script: embed.output, value: 0 });
// 3. Sign input with platform key
psbt.signInput(0, platformKey);
// 4. Finalize and extract transaction hex
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
return tx.toHex();
}
@ac12644
Copy link
Author

ac12644 commented Jul 21, 2025

PSBT example

c63bc5a3ee9c121c8c6c8eecfa74a3a9

This utility builds and signs a PSBT for a sticker purchase, automatically splitting a buyer’s payment into:

  • Platform fee (2%)
  • Seller payout (98%)
  • OP_RETURN metadata carrying productId|productName (up to 80 bytes)

Example Usage

import { createStickerPurchasePsbt } from './stickerPurchase';

// UTXO from buyer Bob’s payment (in sats)
const utxo = {
  tx_hash: 'abcd1234...',
  tx_pos: 0,
  value: 10000,
  address: 'tb1qbuyer...',
};

// Seller Alice’s address and product info
const sellerAddress = 'tb1qseller...';
const productId = 'sticker123';
const productName = 'CoolSticker';

// Create, sign, and finalize the PSBT
const rawTxHex = createStickerPurchasePsbt(
  utxo,
  sellerAddress,
  productId,
  productName
);

// rawTxHex can now be broadcast to the Bitcoin network

How It Works

  1. Calculates fee: 2% of the input UTXO value.
  2. Determines payout: input value minus fee.
  3. Builds PSBT:
    • Input: buyer’s UTXO
    • Output 1: platform fee address
    • Output 2: seller’s address
    • Output 3: OP_RETURN with metadata payload
  4. Signs with the platform’s private key.
  5. Finalizes and returns the raw transaction hex.

Perfect for on‑chain marketplaces, tip bots, and any BTC‑priced digital store!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment