Skip to main content
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

1

Generate silent payment address

Create a reusable address for receiving payments
2

Create taproot destination

Generate unique address for each payment
3

Scan blockchain for funds

Detect incoming transactions
4

Spend received funds

Derive private keys and transfer Bitcoin

Step 1: Generate Silent Payment Address

Best for: Apps where users control their own keys
void 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:
  • rb_scan
  • sb_spend
This ensures cryptographically secure randomness.
If not using signature derivation, ensure your random number generator is cryptographically secure.

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! 🎉
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