Flutter/Dart SDK - This guide uses our Dart package for mobile and Flutter
applications. For JavaScript/Node.js, see the JavaScript
SDK .
Built With Special thanks to Cake
Wallet for
their excellent Bitcoin base library.
Installation
Add to your pubspec.yaml:
dependencies :
bitcoin_base :
git :
url : https://github.com/shakesco/bitcoin_base
ref : cake-update-v5
Import the package:
import 'package:bitcoin_base/bitcoin_base.dart' as bitcoin_base;
Integration Workflow
Generate silent payment address
Create a reusable address for receiving payments
Create taproot destination
Generate unique address for each payment
Scan blockchain for funds
Detect incoming transactions
Spend received funds
Derive private keys and transfer Bitcoin
Step 1: Generate Silent Payment Address
From Private Keys
From Mnemonic (Wallets)
Best for: Apps where users control their own keysvoid main () {
final b_scan = "" ; // Scan private key
final b_spend = "" ; // Spend private key
final paymentOwner = bitcoin_base. SilentPaymentOwner . fromPrivateKeys (
network : bitcoin_base. BitcoinNetwork .testnet,
version : 0 ,
b_scan : bitcoin_base. ECPrivate . fromHex (b_scan),
b_spend : bitcoin_base. ECPrivate . fromHex (b_spend)
);
print (paymentOwner. toAddress ());
}
Derive from signatures: Have users sign a message, then use the ECDSA signature components:This ensures cryptographically secure randomness. If not using signature derivation, ensure your random number generator is cryptographically secure.
Best for: Wallet applications managing user fundsvoid main () {
final mnemonic = "" ; // 12, 15, or 24 word phrase
final paymentOwner = bitcoin_base. SilentPaymentOwner . fromMnemonic (mnemonic);
print (paymentOwner. toAddress ());
}
void main () {
final paymentOwner = bitcoin_base. SilentPaymentOwner . fromHd ();
print (paymentOwner. toAddress ());
}
Step 2: Create Taproot Destination
Generate a one-time taproot address for the payment:
void main () {
// Parse recipient's silent payment address
final B_scan = bitcoin_base. SilentPaymentAddress
. fromAddress (paymentOwner. toAddress ()). B_scan ;
final B_spend = bitcoin_base. SilentPaymentAddress
. fromAddress (paymentOwner. toAddress ()). B_spend ;
// Your UTXO details
final tx_hash = "" ;
final tx_id_output_index = 0 ;
final sender_privateKey = "" ;
final amount = 1000 ; // Satoshis
// Build the destination
final payto = bitcoin_base. SilentPaymentBuilder (
vinOutpoints : [
bitcoin_base. Outpoint (
txid : tx_hash,
index : tx_id_output_index
)
]
). createOutputs (
[
bitcoin_base. ECPrivateInfo (
bitcoin_base. ECPrivate . fromHex (sender_privateKey),
false // Set true if UTXO is from taproot
)
],
[
bitcoin_base. SilentPaymentDestination (
amount : amount,
network : bitcoin_base. BitcoinNetwork .testnet,
version : 0 ,
scanPubkey : B_scan ,
spendPubkey : B_spend
)
]
);
// Get the taproot address
final destinationAddress = payto.values.first.first.address
. toAddress (bitcoin_base. BitcoinNetwork .testnet);
print ( "Send $ amount sats to: $ destinationAddress " );
}
Required inputs: - UTXO transaction hash and output index - UTXO private
key - Amount in satoshis (1 BTC = 100,000,000 sats) - Recipient’s scan and
spend public keys
Step 3: Scan for Incoming Funds
Scanning overhead: This is the main limitation of silent payments - you
must scan the blockchain to detect incoming transactions.
Check if a transaction belongs to you:
void main () {
final tx_hash = "" ;
final tx_id_output_index = 0 ;
final senders_pubKey = "" ;
final amount = 1000 ;
final Script = "" ;
Map < String , bitcoin_base. SilentPaymentScanningOutput > output =
bitcoin_base. SilentPaymentBuilder (
vinOutpoints : [
bitcoin_base. Outpoint (
txid : tx_hash,
index : tx_id_output_index
)
],
pubkeys : [
bitcoin_base. ECPublic . fromHex (senders_pubKey),
],
). scanOutputs (
paymentOwner.b_scan, // Your scan private key
paymentOwner. B_spend , // Your spend public key
[
bitcoin_base. BitcoinScriptOutput (
script : bitcoin_base. Script (script : [ Script ]),
value : BigInt . from (amount)
)
]
);
final scannedAddress = payto.values.first.first.address
. toAddress (bitcoin_base. BitcoinNetwork .testnet);
print ( "Found address: $ scannedAddress " );
}
If scannedAddress matches the taproot output → funds are yours! 🎉
What you need for scanning
Required data:
Transaction input’s txid and output_index
Sender’s public key from the output
Script and amount from the taproot address
Learn more: BIP-352 Scanning Specification
Step 4: Spend the Funds
Once confirmed, derive the private key to spend:
void main () {
final tx_hash = "" ;
final tx_id_output_index = 0 ;
final senders_pubKey = "" ;
bitcoin_base. ECPrivate spendPrivKey = bitcoin_base. SilentPaymentBuilder (
vinOutpoints : [
bitcoin_base. Outpoint (
txid : tx_hash,
index : tx_id_output_index
),
],
pubkeys : [
bitcoin_base. ECPublic . fromHex (senders_pubKey)
],
). spendOutputs (
paymentOwner.b_scan, // Your scan private key
paymentOwner.b_spend // Your spend private key
);
print ( "Private key: ${ spendPrivKey } " );
// Use this to build and sign a Bitcoin transaction
}
Use spendPrivKey with Bitcoin transaction builders to create and broadcast
your spending transaction.
Complete
Your Flutter app now supports Bitcoin silent payments with:
✅ Reusable static addresses
✅ Transaction privacy
✅ No notification fees
✅ Cross-platform compatibility
Additional Resources