Duration: 25 minutes
Prerequisites: Completed “Your First BSV Transaction” tutorial, Node.js, basic TypeScript knowledge
WalletClient
and direct broadcasting approaches📚 Related Concepts: Review Chain Tracking, Transaction Fees, and Wallet Integration for background on network interaction.
Transaction broadcasting is the process of submitting your signed transaction to the Bitcoin SV network so it can be included in a block. The BSV TypeScript SDK provides multiple approaches for broadcasting transactions, each suited for different use cases and deployment scenarios.
In this tutorial, you’ll learn about the two main broadcasting approaches:
When you use WalletClient
, the broadcasting flow looks like this:
Your App → WalletClient → Wallet → Mining Services → BSV Network
The Wallet acts as a proxy that:
Due to its simplicity, this is the recommended approach.
With direct broadcasting, your application connects directly to mining services:
Your App → Custom Broadcaster → Mining Service API → BSV Network
This approach gives you:
Due to its complexity and need to handle the low-level details of the broadcasting process, this is the less recommended approach.
Let’s start with the WalletClient
approach, which is the simplest for most applications. This is the same approach we have seen in the previous tutorials, where we used the WalletClient
to create and broadcast transactions.
First, create a new project for our broadcasting examples:
# Create a new directory
mkdir bsv-broadcasting-tutorial
cd bsv-broadcasting-tutorial
# Initialize project
npm init -y
# Install dependencies
npm install typescript ts-node @types/node --save-dev
npm install @bsv/sdk
Create a basic TypeScript configuration (tsconfig.json
):
{
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"outDir": "./dist"
}
}
Create wallet-broadcasting.ts
:
import { WalletClient } from '@bsv/sdk'
async function walletClientBroadcasting() {
try {
// Initialize the `WalletClient`, using localhost as wallet substrate
const wallet = new WalletClient('auto', 'localhost')
// Check if we're authenticated with the wallet
const { authenticated } = await wallet.isAuthenticated()
if (!authenticated) {
console.log('Please authenticate with your wallet')
await wallet.waitForAuthentication()
console.log('Successfully authenticated!')
}
// Get wallet version
const { version } = await wallet.getVersion()
console.log(`Wallet version: ${version}`)
// For this tutorial, we'll create a transaction with a simple OP_RETURN data output
// The wallet will handle input selection, change outputs and fees
console.log('\n🚀 Creating transaction...')
const actionResult = await wallet.createAction({
description: 'Broadcasting tutorial transaction',
outputs: [
{
satoshis: 100, // Amount in satoshis (very small amount)
// For this basic example, we'll use a standard OP_RETURN script
// Here we use a pre-defined script for simplicity (OP_RETURN with simple data)
lockingScript: '006a0461626364', // OP_RETURN with data 'abcd'
outputDescription: 'Broadcasting tutorial data'
}
]
})
console.log('Transaction created:')
if (actionResult.txid) {
// If the wallet auto-signed and broadcast the transaction
console.log('Full action result:', JSON.stringify(actionResult, null, 2))
console.log(`Transaction ID: ${actionResult.txid}`)
console.log(`View on explorer: https://whatsonchain.com/tx/${actionResult.txid}`)
console.log('Transaction was automatically signed and broadcast!')
}
else if (actionResult.signableTransaction) {
console.log('Created transaction that needs signing')
// Get the reference needed for signing
const txReference = actionResult.signableTransaction.reference
// Now sign the transaction
// Note: In a real application, you might prompt the user to sign
const signResult = await wallet.signAction({
// The wallet knows which inputs need to be spent based on the reference
spends: {},
// Use the reference from the createAction result
reference: txReference,
options: {
acceptDelayedBroadcast: true
}
})
console.log(`Transaction signed and broadcast!`)
console.log(`Transaction ID: ${signResult.txid}`)
console.log(`View on explorer: https://whatsonchain.com/tx/${signResult.txid}`)
}
else {
console.log('Transaction created but no actionable result returned')
}
} catch (error) {
console.error('❌ Broadcasting failed:', error)
console.log('Note: Make sure you have a compatible wallet running and are authenticated.')
}
}
// Run the example
walletClientBroadcasting()
WalletClient
Broadcasting WorksWhen you use WalletClient
:
http://localhost:3321
for MetaNet Desktop Wallet)The key advantage is that you don’t control the broadcasting directly - the BRC-100 wallet handles it based on its configuration. This means:
The WalletClient
approach in step 1 is the recommended approach. However, if you need more control, you can broadcast transactions directly using custom broadcaster implementations. We will demonstrate the main broadcaster implementations in the SDK: ARC and WhatsOnChain.
Important: By default, wallet.createAction()
automatically broadcasts transactions through the wallet’s configured broadcaster. To demonstrate manual broadcasting with specific services, you need to:
options: { noSend: true }
in createAction()
AtomicBEEF
to a Transaction
objectThis approach is useful when you need to:
All broadcasters in the SDK implement the Broadcaster
interface:
interface Broadcaster {
broadcast(tx: Transaction): Promise<BroadcastResponse | BroadcastFailure>
}
The response types are:
interface BroadcastResponse {
status: 'success'
txid: string
message?: string
}
interface BroadcastFailure {
status: 'error'
code: string
description: string
}
ARC (Application Resource Component) is TAAL’s enterprise-grade transaction processing service. Create arc-broadcasting.ts
:
import { WalletClient, ARC, NodejsHttpClient, Transaction } from '@bsv/sdk'
import https from 'https'
async function arcBroadcasting() {
try {
// 1. Set up wallet connection
const wallet = new WalletClient('auto', 'localhost')
// Check if wallet is connected
const isAuthenticated = await wallet.isAuthenticated()
if (!isAuthenticated) {
console.log('Please authenticate with your BRC-100 wallet first')
return
}
// 2. Create a transaction action WITHOUT automatic broadcasting
const actionResult = await wallet.createAction({
description: 'ARC broadcasting tutorial transaction',
outputs: [
{
satoshis: 100, // Small payment amount
lockingScript: '76a914f1c075a01882ae0972f95d3a4177c86c852b7d9188ac', // P2PKH script to a test address
outputDescription: 'ARC broadcasting tutorial payment'
}
],
options: {
noSend: true // Prevent automatic broadcasting - we'll broadcast manually with ARC
}
})
console.log('Transaction created successfully (not broadcast yet)')
console.log(`Transaction ID: ${actionResult.txid}`)
// 3. Convert AtomicBEEF to Transaction for manual broadcasting
if (!actionResult.tx) {
throw new Error('Transaction creation failed - no transaction returned')
}
const tx = Transaction.fromAtomicBEEF(actionResult.tx)
// 4. Set up ARC broadcaster for testnet
// You need to provide your Taal API key here
// Get it by signing up at https://console.taal.com
const apiKey = process.env.TAAL_API_KEY || 'your_taal_api_key_here'
const httpClient = new NodejsHttpClient(https)
const arc = new ARC('https://arc.taal.com', {
apiKey,
httpClient,
deploymentId: 'broadcasting-tutorial'
})
// 5. Manually broadcast the transaction using ARC
console.log('Broadcasting transaction with ARC...')
const result = await tx.broadcast(arc)
if (result.status === 'success') {
console.log('✅ Transaction broadcast successful!')
console.log(`Transaction ID: ${result.txid}`)
console.log(`View on explorer: https://www.whatsonchain.com/tx/${result.txid}`)
} else {
console.log('❌ Broadcasting failed:', result)
}
} catch (error: any) {
console.error('❌ Error during ARC broadcasting:', error)
// Common troubleshooting
if (error.message?.includes('Insufficient funds')) {
console.log('💡 Make sure your wallet has sufficient testnet coins')
} else if (error.message?.includes('no header should have returned false')) {
console.log('💡 Try restarting your wallet application and ensure it is fully synced')
}
}
}
// Run the example
arcBroadcasting()
WhatsOnChain provides a free broadcasting service. Create whatsonchain-broadcasting.ts
:
import { WalletClient, WhatsOnChainBroadcaster, NodejsHttpClient, Transaction } from '@bsv/sdk'
import https from 'https'
async function whatsOnChainBroadcasting() {
try {
// 1. Set up wallet connection
const wallet = new WalletClient('auto', 'localhost')
// Check if wallet is connected
const isAuthenticated = await wallet.isAuthenticated()
if (!isAuthenticated) {
console.log('Please authenticate with your BRC-100 wallet first')
return
}
// 2. Create a transaction action WITHOUT automatic broadcasting
const actionResult = await wallet.createAction({
description: 'WhatsOnChain broadcasting tutorial transaction',
outputs: [
{
satoshis: 100, // Small payment amount
lockingScript: '76a914f1c075a01882ae0972f95d3a4177c86c852b7d9188ac', // P2PKH script to a test address
outputDescription: 'WhatsOnChain broadcasting tutorial payment'
}
],
options: {
noSend: true // Prevent automatic broadcasting - we'll broadcast manually with WhatsOnChain
}
})
console.log('Transaction created successfully (not broadcast yet)')
console.log(`Transaction ID: ${actionResult.txid}`)
// 3. Convert AtomicBEEF to Transaction for manual broadcasting
if (!actionResult.tx) {
throw new Error('Transaction creation failed - no transaction returned')
}
const tx = Transaction.fromAtomicBEEF(actionResult.tx)
// 4. Set up WhatsOnChain broadcaster for mainnet
const httpClient = new NodejsHttpClient(https)
const broadcaster = new WhatsOnChainBroadcaster('main', httpClient)
// 5. Manually broadcast the transaction using WhatsOnChain
console.log('Broadcasting transaction with WhatsOnChain...')
const result = await tx.broadcast(broadcaster)
if (result.status === 'success') {
console.log('✅ Transaction broadcast successful!')
console.log(`Transaction ID: ${result.txid}`)
console.log(`View on explorer: https://www.whatsonchain.com/tx/${result.txid}`)
} else {
console.log('❌ Broadcasting failed:', result)
}
} catch (error: any) {
console.error('❌ Error during WhatsOnChain broadcasting:', error)
// Common troubleshooting
if (error.message?.includes('Insufficient funds')) {
console.log('💡 Make sure your wallet has sufficient mainnet coins')
} else if (error.message?.includes('no header should have returned false')) {
console.log('💡 Try restarting your wallet application and ensure it is fully synced')
}
}
}
// Run the example
whatsOnChainBroadcasting()
For advanced broadcasting scenarios like custom broadcaster implementations, see the Custom Broadcasters Guide.
Important: When using manual broadcasting, ensure your wallet and broadcasters are configured for the same network. If your BRC-100 wallet is connected to testnet, use testnet broadcasters. If it’s on mainnet, use mainnet broadcasters. Mismatched networks will cause broadcasting failures.
Different networks require different broadcaster configurations:
import { ARC, WhatsOnChainBroadcaster, Broadcaster } from '@bsv/sdk'
interface NetworkConfig {
name: string
arc: Broadcaster
whatsOnChain: Broadcaster
}
// Network configurations
const networks: Record<string, NetworkConfig> = {
testnet: {
name: 'BSV Testnet',
arc: new ARC('https://arc-test.taal.com'),
whatsOnChain: new WhatsOnChainBroadcaster('test')
},
mainnet: {
name: 'BSV Mainnet',
arc: new ARC('https://arc.taal.com', {
apiKey: process.env.TAAL_API_KEY // Use environment variable for production
}),
whatsOnChain: new WhatsOnChainBroadcaster('main')
}
}
After broadcasting, you should verify that your transaction was accepted:
import { Transaction } from '@bsv/sdk'
async function verifyTransaction(txid: string, network: 'test' | 'main' = 'test') {
const baseUrl = network === 'test'
? 'https://api.whatsonchain.com/v1/bsv/test'
: 'https://api.whatsonchain.com/v1/bsv/main'
try {
// Check if transaction exists
const response = await fetch(`${baseUrl}/tx/${txid}`)
if (response.ok) {
const txData = await response.json()
console.log('✅ Transaction found on network')
console.log('Transaction ID:', txData.txid)
console.log('Block height:', txData.blockheight || 'Unconfirmed')
console.log('Confirmations:', txData.confirmations || 0)
return txData
} else if (response.status === 404) {
console.log('⏳ Transaction not yet visible on network')
return null
} else {
throw new Error(`API error: ${response.status}`)
}
} catch (error) {
console.error('❌ Error verifying transaction:', error)
throw error
}
}
async function waitForTransaction(
txid: string,
network: 'test' | 'main' = 'test',
timeoutMs: number = 30000
): Promise<any> {
const startTime = Date.now()
while (Date.now() - startTime < timeoutMs) {
const txData = await verifyTransaction(txid, network)
if (txData) {
return txData
}
// Transactions can take a few seconds to show up in WhatsOnChain
// Wait 2 seconds before checking again
await new Promise(resolve => setTimeout(resolve, 2000))
}
throw new Error(`Transaction ${txid} not found within ${timeoutMs}ms`)
}
// Example usage
async function monitoringExample(txid: string) {
try {
console.log('Waiting for transaction to appear on network...')
const txData = await waitForTransaction(txid, 'main')
console.log('✅ Transaction confirmed:', txData)
} catch (error) {
console.error('❌ Transaction monitoring failed:', error)
}
}
// Run the example
monitoringExample('your-transaction-id-here')
In this tutorial, you learned about the two main approaches to transaction broadcasting in BSV:
WalletClient
ApproachThe broadcasting approach you choose depends on your application’s requirements, deployment environment, and control needs. Both approaches are valid and can be used effectively in different scenarios.