Marketplace

Complete examples for implementing marketplace functionality with escrow, dispute resolution, and secure payment patterns.

Overview

Marketplace applications require secure payment handling, escrow mechanisms, and dispute resolution. BSV's programmable scripts enable trustless escrow, atomic exchanges, and sophisticated marketplace logic. This guide covers escrow creation, buyer-seller interactions, refunds, and dispute management.

Related SDK Components:

Basic Escrow System

import { Transaction, PrivateKey, PublicKey, Script, OP, P2PKH } from '@bsv/sdk'

/**
 * Escrow Manager
 *
 * Manage escrow transactions for marketplace transactions
 */
class EscrowManager {
  /**
   * Create escrow transaction
   *
   * Funds locked until buyer and seller both sign (2-of-2) or arbiter releases
   */
  async createEscrow(params: {
    buyerKey: PrivateKey
    sellerPubKey: PublicKey
    arbiterPubKey: PublicKey
    amount: number
    orderId: string
    utxo: {
      txid: string
      vout: number
      satoshis: number
      script: Script
    }
  }): Promise<{
    escrowTx: Transaction
    escrow: Escrow
  }> {
    try {
      console.log('Creating escrow for order:', params.orderId)
      console.log('Amount:', params.amount)

      const escrowTx = new Transaction()

      // Add buyer's input
      escrowTx.addInput({
        sourceTXID: params.utxo.txid,
        sourceOutputIndex: params.utxo.vout,
        unlockingScriptTemplate: new P2PKH().unlock(params.buyerKey),
        sequence: 0xffffffff
      })

      // Create 2-of-3 multisig escrow script
      const escrowScript = this.createEscrowScript(
        params.buyerKey.toPublicKey(),
        params.sellerPubKey,
        params.arbiterPubKey
      )

      // Add escrow output
      escrowTx.addOutput({
        satoshis: params.amount,
        lockingScript: escrowScript
      })

      // Add change output
      const fee = 1000
      const change = params.utxo.satoshis - params.amount - fee

      if (change >= 546) {
        escrowTx.addOutput({
          satoshis: change,
          lockingScript: new P2PKH().lock(params.buyerKey.toPublicKey().toHash())
        })
      }

      await escrowTx.sign()

      const escrow: Escrow = {
        orderId: params.orderId,
        escrowTxid: escrowTx.id('hex'),
        amount: params.amount,
        buyerAddress: params.buyerKey.toPublicKey().toAddress(),
        sellerAddress: params.sellerPubKey.toAddress(),
        arbiterAddress: params.arbiterPubKey.toAddress(),
        status: 'locked',
        createdAt: Date.now()
      }

      console.log('Escrow created')
      console.log('Escrow TXID:', escrow.escrowTxid)

      return { escrowTx, escrow }
    } catch (error) {
      throw new Error(`Escrow creation failed: ${error.message}`)
    }
  }

  /**
   * Release escrow to seller (successful transaction)
   */
  async releaseToSeller(
    escrow: Escrow,
    buyerKey: PrivateKey,
    sellerKey: PrivateKey,
    escrowUTXO: {
      txid: string
      vout: number
      satoshis: number
      lockingScript: Script
    }
  ): Promise<Transaction> {
    try {
      console.log('Releasing escrow to seller:', escrow.orderId)

      const releaseTx = new Transaction()

      releaseTx.addInput({
        sourceTXID: escrowUTXO.txid,
        sourceOutputIndex: escrowUTXO.vout,
        unlockingScript: new Script(),
        sequence: 0xffffffff
      })

      // Pay seller
      const fee = 500
      const sellerAmount = escrowUTXO.satoshis - fee

      releaseTx.addOutput({
        satoshis: sellerAmount,
        lockingScript: Script.fromAddress(escrow.sellerAddress)
      })

      // Create 2-of-3 multisig unlocking script
      // Both buyer and seller sign
      const input = releaseTx.inputs[0]
      const preimage = input.getPreimage(escrowUTXO.lockingScript)

      const buyerSig = buyerKey.sign(preimage)
      const sellerSig = sellerKey.sign(preimage)

      // Build unlocking script: OP_0 <sig1> <sig2>
      const unlockingScript = new Script()
      unlockingScript.writeOpCode(OP.OP_0) // OP_CHECKMULTISIG bug
      unlockingScript.writeBin(buyerSig.toDER())
      unlockingScript.writeBin(sellerSig.toDER())

      releaseTx.inputs[0].unlockingScript = unlockingScript

      console.log('Escrow released to seller')
      console.log('Release TXID:', releaseTx.id('hex'))

      escrow.status = 'released'
      escrow.releasedAt = Date.now()
      escrow.releaseTxid = releaseTx.id('hex')

      return releaseTx
    } catch (error) {
      throw new Error(`Escrow release failed: ${error.message}`)
    }
  }

  /**
   * Refund escrow to buyer (cancelled or disputed)
   */
  async refundToBuyer(
    escrow: Escrow,
    buyerKey: PrivateKey,
    arbiterKey: PrivateKey,
    escrowUTXO: {
      txid: string
      vout: number
      satoshis: number
      lockingScript: Script
    }
  ): Promise<Transaction> {
    try {
      console.log('Refunding escrow to buyer:', escrow.orderId)

      const refundTx = new Transaction()

      refundTx.addInput({
        sourceTXID: escrowUTXO.txid,
        sourceOutputIndex: escrowUTXO.vout,
        unlockingScript: new Script(),
        sequence: 0xffffffff
      })

      // Refund to buyer
      const fee = 500
      const refundAmount = escrowUTXO.satoshis - fee

      refundTx.addOutput({
        satoshis: refundAmount,
        lockingScript: Script.fromAddress(escrow.buyerAddress)
      })

      // Create unlocking script with buyer and arbiter signatures
      const input = refundTx.inputs[0]
      const preimage = input.getPreimage(escrowUTXO.lockingScript)

      const buyerSig = buyerKey.sign(preimage)
      const arbiterSig = arbiterKey.sign(preimage)

      const unlockingScript = new Script()
      unlockingScript.writeOpCode(OP.OP_0)
      unlockingScript.writeBin(buyerSig.toDER())
      unlockingScript.writeBin(arbiterSig.toDER())

      refundTx.inputs[0].unlockingScript = unlockingScript

      console.log('Escrow refunded to buyer')
      console.log('Refund TXID:', refundTx.id('hex'))

      escrow.status = 'refunded'
      escrow.refundedAt = Date.now()
      escrow.refundTxid = refundTx.id('hex')

      return refundTx
    } catch (error) {
      throw new Error(`Escrow refund failed: ${error.message}`)
    }
  }

  /**
   * Create 2-of-3 multisig escrow script
   */
  private createEscrowScript(
    buyerPubKey: PublicKey,
    sellerPubKey: PublicKey,
    arbiterPubKey: PublicKey
  ): Script {
    const script = new Script()

    // 2-of-3 multisig: 2 <pubkey1> <pubkey2> <pubkey3> 3 OP_CHECKMULTISIG
    script.writeOpCode(OP.OP_2)
    script.writeBin(buyerPubKey.encode())
    script.writeBin(sellerPubKey.encode())
    script.writeBin(arbiterPubKey.encode())
    script.writeOpCode(OP.OP_3)
    script.writeOpCode(OP.OP_CHECKMULTISIG)

    return script
  }
}

interface Escrow {
  orderId: string
  escrowTxid: string
  amount: number
  buyerAddress: string
  sellerAddress: string
  arbiterAddress: string
  status: 'locked' | 'released' | 'refunded' | 'disputed'
  createdAt: number
  releasedAt?: number
  refundedAt?: number
  releaseTxid?: string
  refundTxid?: string
}

/**
 * Usage Example
 */
async function escrowExample() {
  const escrowManager = new EscrowManager()

  const buyer = PrivateKey.fromRandom()
  const seller = PrivateKey.fromRandom()
  const arbiter = PrivateKey.fromRandom()

  const buyerUTXO = {
    txid: 'buyer-utxo...',
    vout: 0,
    satoshis: 200000,
    script: new P2PKH().lock(buyer.toPublicKey().toHash())
  }

  // Create escrow
  const { escrowTx, escrow } = await escrowManager.createEscrow({
    buyerKey: buyer,
    sellerPubKey: seller.toPublicKey(),
    arbiterPubKey: arbiter.toPublicKey(),
    amount: 100000,
    orderId: 'ORDER-001',
    utxo: buyerUTXO
  })

  console.log('Escrow created:', escrow)

  // Later: Release to seller
  const escrowUTXO = {
    txid: escrowTx.id('hex'),
    vout: 0,
    satoshis: 100000,
    lockingScript: escrowTx.outputs[0].lockingScript
  }

  const releaseTx = await escrowManager.releaseToSeller(
    escrow,
    buyer,
    seller,
    escrowUTXO
  )

  console.log('Released to seller:', releaseTx.id('hex'))
}

Marketplace Order Manager

Atomic Swap Marketplace

See Also

SDK Components:

Learning Paths:

Last updated