For the complete documentation index, see llms.txt. This page is also available as Markdown.

Payment Distribution

Complete examples for distributing payments to multiple recipients including revenue sharing, affiliate payouts, and royalty distribution.

Overview

Payment distribution allows a single transaction to split funds among multiple recipients according to predefined rules. This is essential for revenue sharing, affiliate programs, royalty payments, and collaborative applications. BSV's low fees make micro-splits economically viable.

Related SDK Components:

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

See Also

SDK Components:

Learning Paths:

Last updated