Payment Distribution
Overview
Simple Payment Splitting
import { Transaction, PrivateKey, P2PKH, Script } from '@bsv/sdk'
/**
* Payment Splitter
*
* Split a payment among multiple recipients
*/
class PaymentSplitter {
/**
* Split payment by percentages
*/
async splitByPercentage(params: {
senderKey: PrivateKey
totalAmount: number
splits: Array<{
address: string
percentage: number // 0-100
}>
utxo: {
txid: string
vout: number
satoshis: number
script: Script
}
}): Promise<{
tx: Transaction
distributions: Array<{
address: string
amount: number
percentage: number
}>
}> {
try {
// Validate percentages
const totalPercentage = params.splits.reduce((sum, s) => sum + s.percentage, 0)
if (Math.abs(totalPercentage - 100) > 0.01) {
throw new Error(`Percentages must sum to 100, got ${totalPercentage}`)
}
const tx = new Transaction()
// Add input
tx.addInput({
sourceTXID: params.utxo.txid,
sourceOutputIndex: params.utxo.vout,
unlockingScriptTemplate: new P2PKH().unlock(params.senderKey),
sequence: 0xffffffff
})
const distributions: Array<{
address: string
amount: number
percentage: number
}> = []
// Calculate and add recipient outputs
let totalDistributed = 0
for (let i = 0; i < params.splits.length; i++) {
const split = params.splits[i]
let amount: number
// Last recipient gets remainder to handle rounding
if (i === params.splits.length - 1) {
amount = params.totalAmount - totalDistributed
} else {
amount = Math.floor((params.totalAmount * split.percentage) / 100)
totalDistributed += amount
}
// Check dust threshold
if (amount < 546) {
throw new Error(`Split amount ${amount} below dust threshold for ${split.address}`)
}
tx.addOutput({
satoshis: amount,
lockingScript: Script.fromAddress(split.address)
})
distributions.push({
address: split.address,
amount,
percentage: split.percentage
})
}
// Add change output
const fee = 500 + (params.splits.length * 50)
const change = params.utxo.satoshis - params.totalAmount - fee
if (change >= 546) {
tx.addOutput({
satoshis: change,
lockingScript: new P2PKH().lock(params.senderKey.toPublicKey().toHash())
})
}
await tx.sign()
console.log('Payment split completed')
console.log('Recipients:', params.splits.length)
console.log('Total distributed:', params.totalAmount)
console.log('Transaction ID:', tx.id('hex'))
return { tx, distributions }
} catch (error) {
throw new Error(`Payment split failed: ${error.message}`)
}
}
/**
* Split payment by fixed amounts
*/
async splitByAmount(params: {
senderKey: PrivateKey
splits: Array<{
address: string
amount: number
}>
utxo: {
txid: string
vout: number
satoshis: number
script: Script
}
}): Promise<Transaction> {
try {
const totalAmount = params.splits.reduce((sum, s) => sum + s.amount, 0)
const fee = 500 + (params.splits.length * 50)
if (params.utxo.satoshis < totalAmount + fee) {
throw new Error('Insufficient funds')
}
const tx = new Transaction()
tx.addInput({
sourceTXID: params.utxo.txid,
sourceOutputIndex: params.utxo.vout,
unlockingScriptTemplate: new P2PKH().unlock(params.senderKey),
sequence: 0xffffffff
})
// Add recipient outputs
for (const split of params.splits) {
if (split.amount < 546) {
throw new Error(`Amount ${split.amount} below dust threshold for ${split.address}`)
}
tx.addOutput({
satoshis: split.amount,
lockingScript: Script.fromAddress(split.address)
})
}
// Add change
const change = params.utxo.satoshis - totalAmount - fee
if (change >= 546) {
tx.addOutput({
satoshis: change,
lockingScript: new P2PKH().lock(params.senderKey.toPublicKey().toHash())
})
}
await tx.sign()
console.log('Fixed amount split completed')
console.log('Recipients:', params.splits.length)
console.log('Total:', totalAmount)
return tx
} catch (error) {
throw new Error(`Fixed split failed: ${error.message}`)
}
}
/**
* Equal split among recipients
*/
async splitEqually(params: {
senderKey: PrivateKey
totalAmount: number
recipientAddresses: string[]
utxo: {
txid: string
vout: number
satoshis: number
script: Script
}
}): Promise<Transaction> {
try {
const amountPerRecipient = Math.floor(
params.totalAmount / params.recipientAddresses.length
)
if (amountPerRecipient < 546) {
throw new Error('Split amount below dust threshold')
}
const splits = params.recipientAddresses.map(address => ({
address,
amount: amountPerRecipient
}))
return await this.splitByAmount({
senderKey: params.senderKey,
splits,
utxo: params.utxo
})
} catch (error) {
throw new Error(`Equal split failed: ${error.message}`)
}
}
}
/**
* Usage Example
*/
async function paymentSplitterExample() {
const splitter = new PaymentSplitter()
const senderKey = PrivateKey.fromRandom()
const utxo = {
txid: 'funding-tx...',
vout: 0,
satoshis: 200000,
script: new P2PKH().lock(senderKey.toPublicKey().toHash())
}
// Split by percentage
const { distributions } = await splitter.splitByPercentage({
senderKey,
totalAmount: 100000,
splits: [
{ address: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', percentage: 50 },
{ address: '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2', percentage: 30 },
{ address: '1HLoD9E4SDFFPDiYfNYnkBLQ85Y51J3Zb1', percentage: 20 }
],
utxo
})
console.log('Distributions:', distributions)
// Equal split
await splitter.splitEqually({
senderKey,
totalAmount: 90000,
recipientAddresses: [
'1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
'1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2',
'1HLoD9E4SDFFPDiYfNYnkBLQ85Y51J3Zb1'
],
utxo
})
}Revenue Sharing
Affiliate Payouts
Related Examples
See Also
Last updated
