Marketplace
Overview
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
Related Examples
See Also
Last updated
