Duration: 30 minutes
Prerequisites: Completed “Your First BSV Transaction” tutorial, Node.js, basic TypeScript knowledge
WalletClient
for different environmentsWhile the previous tutorial taught you the basics of creating transaction structures, this tutorial takes you to the next level: creating and broadcasting actual transactions on the Bitcoin SV testnet. The testnet is a separate blockchain that functions almost identically to the main BSV network (mainnet), but with coins that have no real-world value, making it perfect for experimentation and learning.
Before we start, it’s important to understand the difference between testnet and mainnet:
Feature | Testnet | Mainnet |
---|---|---|
Purpose | Testing and development | Real-world transactions |
Coin value | None (free from faucets) | Real monetary value |
Network prefix | Different from mainnet | Production network |
Block difficulty | Lower than mainnet | Higher mining difficulty |
Explorer URLs | Testnet-specific | Mainnet-specific |
The code you write for testnet can usually work on mainnet with minimal changes (primarily network configuration), making testnet ideal for development and testing.
First, let’s create a new project for our testnet experiments:
# Create a new directory for your testnet project
mkdir bsv-testnet-transactions
cd bsv-testnet-transactions
# Initialize a new Node.js project
npm init -y
# Install TypeScript and ts-node
npm install typescript ts-node @types/node --save-dev
# Install the BSV SDK
npm install @bsv/sdk
Now, create a basic TypeScript configuration file (tsconfig.json
):
{
"compilerOptions": {
"target": "es2022",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"outDir": "./dist"
}
}
Let’s create a file to generate a testnet wallet. Create a new file called create-wallet.ts
:
import {
PrivateKey,
P2PKH
} from '@bsv/sdk'
// Create a new private key
const privateKey = PrivateKey.fromRandom()
// Derive the public key and create a testnet address
// Use the testnet prefix (0x6f) for the address
const address = privateKey.toAddress([0x6f])
// Display the information
console.log('===== TESTNET WALLET =====')
console.log(`Address: ${address.toString()}`)
console.log(`Private Key (WIF): ${privateKey.toWif()}`)
console.log('\nIMPORTANT: Save this information securely. You will need the private key to spend any received coins.')
console.log('=============================')
Run this script to generate your testnet wallet:
npx ts-node create-wallet.ts
You should see output similar to this:
===== TESTNET WALLET =====
Address: mzJR1zKcZCZvMJj87PUeQPPXRi3DEpyEDp
Private Key (WIF): cQcTnoUm6hQCSoGDQQiGfRxLEbsB6Sm3DnQPDrJdHZrP6ufuuJyp
IMPORTANT: Save this information securely. You will need the private key to spend any received coins.
=============================
Important: Save the displayed address and private key somewhere secure. You’ll need them for the rest of this tutorial.
Now that you have a testnet address, you need to get some testnet coins. Unlike mainnet, testnet coins are free and can be obtained from “faucets” - services that distribute testnet coins for development purposes.
Here are some BSV testnet faucets you can use:
Visit one of these faucets, enter your testnet address, and request some coins. The faucet will send a small amount of testnet BSV to your address.
After requesting coins from a faucet, you’ll need to verify that you received them and gather information about the UTXO (Unspent Transaction Output) you’re going to spend. You can use a testnet block explorer for this purpose:
Enter your testnet address in the search bar at the top and click the search icon
You’ll see your address details page with any transactions listed. Verify that you have received coins from the faucet. It may take a few minutes for the transaction to be confirmed.
Once you confirm that you’ve received coins with at least one confirmed transaction (unspent output/UTXO), you can proceed to gather the information needed for your transaction.
Look for the incoming transaction from the faucet
Click on the transaction ID (txid) to view the full transaction details
7f4e6ea49a847f557fccd9bf99d4a07ac103e5e8cb3464abb852af552516317e
)Transaction ID (txid): [your transaction id]
Output Index: [your output index, usually 0]
Output Amount: [amount in BSV shown on WhatsOnChain] = [converted amount in satoshis]
Example: 0.00010000 BSV = 10000 satoshis
Important: Make sure you’re looking at an unspent output. If the coins have already been spent, you won’t be able to use them in your transaction. WhatsOnChain typically shows if an output has been spent.
Write down this information as you’ll need it for the next step.
When you spend a UTXO in Bitcoin, the entire UTXO must be consumed. You can think of a UTXO like a bill in your wallet - if you want to spend a $20 bill, you must use the entire bill, not just part of it. If you only want to spend $5, you’ll spend the entire $20 bill and get $15 back as change.
In the same way, when creating a Bitcoin transaction:
To broadcast transactions via the ARC API, you’ll need a Taal API key:
Create a file send-transaction.ts
:
import { Transaction, PrivateKey, P2PKH, ARC, NodejsHttpClient } from '@bsv/sdk'
import https from 'https'
async function main() {
try {
// 1. Set up your wallet
const privateKey = PrivateKey.fromWif('your_testnet_private_key_here')
const myAddress = privateKey.toAddress([0x6f]) // 0x6f is the testnet prefix
const recipientAddress = 'testnet_address_to_send_coins_to'
// 2. Fetch the full transaction hex
const txid = 'source_transaction_id_here'
const response = await fetch(`https://api.whatsonchain.com/v1/bsv/test/tx/${txid}/hex`)
const sourceTxHex = await response.text()
console.log(`Retrieved transaction hex (first 50 chars): ${sourceTxHex.substring(0, 50)}...`)
// 3. Create a transaction
const tx = new Transaction()
// 4. Add the input
// For testnet, we need the hex of the transaction that contains our UTXO
tx.addInput({
sourceTransaction: Transaction.fromHex(sourceTxHex),
sourceOutputIndex: 0, // The output index from Step 4 (typically 0)
unlockingScriptTemplate: new P2PKH().unlock(privateKey)
})
// 5. Add the recipient output
tx.addOutput({
lockingScript: new P2PKH().lock(recipientAddress),
satoshis: 100 // Amount to send (must be less than input amount)
})
// 6. Add the change output back to our address
tx.addOutput({
lockingScript: new P2PKH().lock(myAddress),
change: true // SDK will automatically calculate the change amount
})
// 7. Calculate fee and sign the transaction
await tx.fee()
await tx.sign()
// 8. Broadcast the transaction to the testnet using ARC
// You need to provide your Taal API key here
// Get it by signing up at https://console.taal.com
const apiKey = 'your_taal_api_key_here' // Replace with your actual API key
// Create an HTTP client to ensure the transaction can be broadcast
const httpClient = new NodejsHttpClient(https)
// The SDK automatically appends '/v1/tx' to the base URL when broadcasting
// so we need to use the base URL without the '/arc' suffix
const arc = new ARC('https://arc-test.taal.com', {
apiKey,
httpClient, // Provide an HTTP client to avoid connectivity issues
deploymentId: 'testnet-tutorial-deployment' // Provide a fixed deployment ID to avoid random generation
})
const result = await tx.broadcast(arc)
console.log('ARC Response:', JSON.stringify(result, null, 2)) // Log the full response for debugging
// 9. Display the transaction ID
console.log(`Transaction ID: ${Buffer.from(tx.id()).toString('hex')}`)
console.log(`View on explorer: https://test.whatsonchain.com/tx/${Buffer.from(tx.id()).toString('hex')}`)
console.log('Transaction broadcast successfully!')
} catch (error) {
console.error('Error:', error)
}
}
// Run the main function
main()
Before running this script, make sure to replace these values in the code:
your_testnet_private_key_here
: Your testnet private key (WIF format) from Step 2testnet_address_to_send_coins_to
: The recipient’s testnet address - for the purpose of this tutorial, use your own addresssource_transaction_id_here
: The ID of the transaction containing your UTXOsourceOutputIndex
value if your output index is not 0satoshis
value (currently 100) to be less than your input amountOnce you’ve made these changes, run the script:
npx ts-node send-transaction.ts
After broadcasting your transaction, you can track its progress using a testnet explorer (note - it can take a few seconds for the transaction to show up). Our code already outputs the transaction ID and a direct link to view it on WhatsOnChain:
console.log(`Transaction ID: ${Buffer.from(tx.id()).toString('hex')}`)
console.log(`View on explorer: https://test.whatsonchain.com/tx/${Buffer.from(tx.id()).toString('hex')}`)
Simply click the link or copy the transaction ID to search for it on WhatsOnChain Testnet or another testnet explorer.
Initially, your transaction will be unconfirmed. Once it’s included in a block, it will show as confirmed. On testnet, confirmations typically happen faster than on mainnet due to lower mining difficulty.
Transaction fees are paid to miners to include your transaction in a block. In our simplified code example, we’re using the BSV SDK’s automatic fee calculation with:
await tx.fee()
This method automatically calculates the appropriate fee based on:
The change: true
parameter in our change output works with the fee calculation to:
This approach ensures your transaction pays an appropriate fee based on its size and current network conditions, without needing to manually calculate fees and change amounts.
Congratulations! You’ve successfully created, signed, and broadcast a real transaction on the Bitcoin SV testnet. In this tutorial, you’ve learned:
This practical experience with testnet transactions provides a solid foundation for working with real BSV transactions on the mainnet. With these skills, you’re ready to move on to more advanced topics in the BSV TypeScript SDK.
WalletClient
ApproachFeature | Low-Level | WalletClient |
---|---|---|
Control | Full control over transaction structure | Simplified, high-level interface |
Complexity | Requires manual UTXO management and fee calculation | Handles UTXO management and fee calculation automatically |
Use Cases | Advanced applications, custom transaction logic | Most production applications, simple transaction workflows |
The WalletClient
approach is generally recommended for production applications, while the low-level approach is valuable for:
Understanding both approaches will help you choose the best method for your specific use case and provide a deeper understanding of the Bitcoin SV ecosystem.
This tutorial focuses on low-level transaction construction, which gives you complete control over every aspect of the transaction. For simpler applications, consider using the WalletClient
approach covered in other tutorials.
Alternative Approach: For most applications, the
WalletClient
interface provides a simpler way to create transactions. This tutorial focuses on the low-level approach for educational purposes and specialized use cases.