Sending Bitcoins from Android (and Viewing Transactions)

Man

Professional
Messages
3,035
Reaction score
559
Points
113
Hi! Today I will tell you about my experience of writing a simple Android application for sending bitcoins from an existing wallet, displaying its balance and a list of transactions. It seems that what could be simpler? Yes, but there are nuances. Let's talk about them.

Disclaimer
This article is educational in nature and does not encourage anyone to engage in any cryptocurrency transactions. The author insists on the need to comply with current laws in the area of regulation of digital currencies and assets.

0. What to expect​

Beginner cryptocurrency enthusiasts may (like me) encounter a lot of non-obvious and difficult to understand things. In this article, I want to share my experience of stepping on a rake and make life easier for those who will later understand this topic.

You will be immersed in the world of blockchain transactions: from creating wallets and working with UTXO to signing transactions and sending them to the network. This is not only a great way to gain a deeper understanding of how Bitcoin works, but also an opportunity to learn how to write your own Android applications for working with cryptocurrencies.

Let me clarify that the application code works with the Signet test network. This is the second generation of the Bitcoin “sandbox”, which exactly replicates the “production”. The only difference is that the tokens in it do not represent any value. However, if you wish, you can easily switch to using the real Bitcoin network by changing just one parameter in the code.

First, I'll describe the process of creating and replenishing wallets, then we'll discuss the principles of the Bitcoin network, and then we'll move on directly to the code. If you already have a wallet in the Signet network, you can skip to point 4. And if you already know how and what works, you can go to point 7.

1. Create​

So, let's get ourselves a wallet. The most convenient way to do this is in the popular Electrum application, launching it with the flag --signet. After launching, create a wallet first_wallet.

A simple and clear interface allows you to quickly create a wallet

A simple and clear interface allows you to quickly create a wallet

Next, leave all the settings as default:

Standard wallet - Create a new seed - Encrypt wallet file.

Important : write down on paper the 12 words of the proposed seed sequence and the password for the wallet.

Now let's create another wallet - second_wallet, to which we will transfer cryptocurrency for testing the sending.

2. Replenish​

After the wallets are created, we will transfer some cryptocurrency to first_wallet. We go to a special service and ask for 0.01 BTC. Usually the transfer process takes 5-10 minutes, but several attempts may be required.

In Signet, block mining is managed by a group of signers who process each new block. This helps maintain network stability and prevents the chaos that characterized Testnet (an early version of the Bitcoin test network), where block mining is uncontrolled. However, this feature does not allow you to make much in the way of tokens.

3. Look inside the wallet​

In Electrum, go to the “Addresses” tab. If you don’t have it, click the View - Addresses menu item. The created wallet contains 30 addresses to which you can transfer money and change. When you transfer cryptocurrency through applications like Electrum, the wallet selects one of the pre-generated addresses to send change to, following the order in which they were created. This is done to increase anonymity, but for us it adds some inconveniences, which we will discuss below.

All addresses we use on the Signet network begin with tb1q

All addresses we use on the Signet network begin with "tb1q"

4. Export addresses and keys​

In Electrum, go to Wallet - Private keys - Export and save the list of wallets along with their private keys. Copy the addresses (without private keys) to the file addresses.txt, which we will later use in the application.

List of addresses with a hyphen at the end of the line

List of addresses with a hyphen at the end of the line

For the first address from our list, we copy the private key to the private_key.txt file.

The private key is in the same form as exported

The private key is in the same form as exported

Let's touch on two important points.

First, if you are going to use the real Bitcoin network, you need to think carefully about where to store your wallet's private key. You cannot revoke or reissue a private key, nor can you delete/deactivate your wallet. Therefore, anyone who gains access to this key will be able to permanently withdraw your money.

Secondly, if you are using an HD (Hierarchical Deterministic) wallet, just like the one we created in step 1, you can always generate new addresses from your seed phrase. In an ideal world, we would use a new address for each new transaction. But in our application, for simplicity, we will use only one.

5. Design UI​

For the application, we will use a simple design with two screens: a transaction screen and a currency sending screen.

The transaction screen displays the current confirmed balance, a navigation button to the Bitcoin send screen, a button to copy the current wallet to the clipboard, and a list of transactions. The list contains the last 25 confirmed transactions. Clicking on any of them takes the user to the browser, where he can view detailed information on the selected transaction on the mempool.space website.

2806caf91b692441561c947ba0149945.png


On the sending screen, we also display the current confirmed balance, the field for entering the transfer amount and the destination address. If everything is OK with the entered data, by clicking the Send button, we display a dialog with the id of the created transaction. By clicking on the id, we also send the user to the browser. If the server responded with an error, we display its text in the dialog.

3062a13802fd8e74af66fee380646c34.png


6. Analyze​

We will work with the mempool.space API. It allows you to receive all the necessary data from the Signet network, as well as send requests to create new transactions.

All the methods we use require specifying a wallet address. We need any receiving wallet from the list saved in addresses.txt. We will use the first one.

6.1 List balance​

Let's calculate the balance of the address based on the response from the address method. This method returns the following main parameters:

chain_stats: Statistics of transactions associated with this address that have already been confirmed on the blockchain.

Fields:
  • funded_txo_count: The number of UTXOs (Unspent Transaction Outputs) that have been received by this address.

    UTXOs (Unspent Transaction Outputs) are unspent outputs from previous transactions that can be used to create new ones. Essentially, UTXOs are “coins” that are in the address and have not yet been used.

    It is important to remember that when we send bitcoins, we spend the entire UTXO. If the UTXO exceeds the amount of the transfer, the remainder is returned to our address as a new UTXO (this is the “change”).
  • funded_txo_sum: the sum of all unspent funds received by this address (the total sum of all UTXOs).
  • spent_txo_count: This is the number of UTXOs that have been spent by this address.
  • spent_txo_sum: The sum of all spent UTXOs in satoshi for this address.
  • tx_count: The total number of transactions associated with this address (including both incoming and outgoing transactions).
  • mempool_stats: Statistics of transactions associated with this address that are in the mempool but not yet confirmed on the blockchain.

    mempool is a buffer storage on each node in the network for unconfirmed transactions, where they sit until they are included in a block and confirmed.

For our application, we will calculate the current balance using the formula funded_txo_sum - spent_txo_sum, based on confirmed transactions.

Let's convert 0.12345 mBTC ( 0.00012345 BTC ) to ours second_walletusing Electrum. Sometimes we will also use satoshi (sat) for amounts - the smallest monetary units of the Bitcoin network. 1 BTC = 100,000,000 sat.

The amount debited from the first wallet is greater by the amount of the commission

The amount debited from the first wallet is greater by the amount of the commission

Bitcoin transactions are executed with a delay that depends on the amount of the commission you leave for miners. In addition, a node may reject your request if it tries to spend the same UTXOs as a previous unconfirmed request from your address ( double-spending ). Therefore, you need to be patient when testing - delays in transaction confirmation are a normal part of the network.

6.2 List of transactions​

Okay, we've sorted out the balance. Now let's display the list of transactions.

A transaction in the Bitcoin network contains inputs, from which we want to spend funds, and outputs, where we want to transfer them. Outputs contain both the amount of the transfer and the "change" that we return to ourselves. The difference between the sum of all inputs and all outputs is the commission for the miners who confirm this transaction.

You can imagine that at the input we have 2 banknotes: 100 and 50 rubles. And at the output - payment to the seller for the goods, which costs 130 rubles, and 15 rubles change, which we return back to our wallet. The difference between the inputs (150 rubles) and the amount of funds sent (130 rubles and 15 rubles change) is 5 rubles, and this is the commission that we pay to the intermediary for confirming the purchase.

So, it's time to query the transactions here and look inside the response.

The main transaction parameters that the API returns upon request are:
  • txid (Transaction ID): This is a unique hash for identification on the blockchain.
  • vin (transaction inputs): a list of transaction inputs. Inputs represent the sources of funds - these are previous UTXOs. An input contains the following important fields:
    1. txid: ID of the previous transaction where the funds come from.
    2. vout: the index of the output element of the previous transaction (pointing to the specific output used as the current input). It contains the fields:
      • prevout.scriptpubkey_address- the address to which the funds were sent in the previous transaction, and prevout.value- its amount
      • witness: This is the signature and public key that confirm the right to spend the UTXO.
  • vout (transaction outputs): a list of transaction outputs. Each output represents a transfer of funds to a specific address. The scriptpubkey_addressand fields are also used here value.
  • fee: transaction fee in satoshi. This is the amount paid to miners for including a transaction in a block.

    Transactions with zero fee can be processed, but with a very low probability, since miners prefer transactions with a fee.

    The fee is correctly calculated based on the transaction size in bytes and the current network situation, but in the code we will limit ourselves to a fixed fee of 250 satoshi for simplicity of demonstration.
  • status.confirmed: a boolean value indicating whether the transaction was confirmed (included in a block).

Thus, the input of a transaction contains a reference to the previous output, creating a chain that is the basis for the operation of the blockchain.

6.3 Transfer of cryptocurrency​

Now we come to the most interesting part. To transfer cryptocurrency, we need to form a transaction, similar to the one we discussed above. It should include the transfer amount, change (if necessary), and the commission paid to the miners.

Every transaction must be signed. The signature is proof that we have the private key corresponding to the address from which we send funds. This process involves the use of the ECDSA (Elliptic Curve Digital Signature Algorithm) algorithm. The signature can be verified using the public key known to the network.

Once a transaction is signed, it must be converted into a HEX string (this is a binary data format represented in hexadecimal) and transmitted over the network.

You can send the string via API, for example, by making a request to this address. Once the transaction enters the network, it will be sent to the mempool and will wait for inclusion in the block by miners.

7. Let's code!​

Let's write an application on Compose using the MVVM + Repository architecture. I assume that the reader is already familiar with Android and has written UI on Compose using the specified architecture, so I will not touch upon these issues.

To simplify work with cryptography and internal checks, we will use the bitcoinj library. This is a fairly popular solution for working with Bitcoin in Java. To access the Signet network, you must use bitcoinj version no lower than 0.17-beta1.

The full project code can be viewed on my Github. In order for everything to work, you need to put the files created in step 4 in the path app/src/main/assets/so that the code has access to them.

Next, I will focus on some methods BitcoinWalletViewModelthat contain the main business logic of the application.

7.1 Preparing data for the transaction list item​


Code:
fun getTransactionDisplayData(transaction: TransactionDTO, ownAddresses: Set<String>): TransactionDisplayData {
// find out if our address is in the in list.
// If so, this is an expense transaction.
val isOutgoing = transaction.vIn.any { input ->
input.prevOut.scriptPublicKeyAddress in ownAddresses
}

// is there anyone else in the out list besides us
val hasOutputToOthers = transaction.vOut.any { out ->
out.scriptPublicKeyAddress !in ownAddresses
}

// determine that this is a receipt of funds to us
val isIncoming = !isOutgoing && transaction.vOut.any { out ->
out.scriptPublicKeyAddress in ownAddresses
}

// determine the transaction type
val transactionType: TransactionType = when {
isOutgoing && hasOutputToOthers -> TransactionType.EXPENSE
isIncoming -> TransactionType.INCOME
isOutgoing && !hasOutputToOthers -> TransactionType.SELF_TRANSFER
else -> TransactionType.UNKNOWN
}

val amount: Long = when (transactionType) {
// if we spend, we need to add the commission amount to the displayed amount
TransactionType.EXPENSE -> transaction.vOut
.filter { it.scriptPublicKeyAddress !in ownAddresses }
.sumOf { it.value } + transaction.fee

// if we receive, we simply display the transfer amount
TransactionType.INCOME -> transaction.vOut
.filter { it.scriptPublicKeyAddress in ownAddresses }
.sumOf { it.value }

else -> 0L
}

val amountInmBtc = amount / 100_000.0

val transactionAddressText = when (transactionType) {
// for receipts, we look for the wallet from which the money was transferred
TransactionType.INCOME -> {
 val senderAddress = transaction.vIn.firstOrNull { input ->
 input.prevOut.scriptPublicKeyAddress !in ownAddresses
 }?.prevOut?.scriptPublicKeyAddress

 if (senderAddress != null) "From: ${getShortAddress(senderAddress)}" else null
 }
 // for expenses - the wallet to which they were transferred
 TransactionType.EXPENSE -> {
 val receiverAddress = transaction.vOut.firstOrNull { out ->
 out.scriptPublicKeyAddress !in ownAddresses
 }?.scriptPublicKeyAddress

 if (receiverAddress != null) "To: ${getShortAddress(receiverAddress)}" else null
 }
 else -> null
 }

 // and return what we received
 return TransactionDisplayData(
 transactionType = transactionType,
 amountInmBtc = amountInmBtc,
 transactionAddressText = transactionAddressText
 )
}

7.2 Preparing HEX transactions​

The transaction preparation procedure itself is quite cumbersome, so I split it into several separate functions. The general algorithm is as follows:
  1. Request a list of all transactions
  2. find a UTXO with a suitable balance. The balance must be greater than the payment amount + commission + “dust” (minimum payment amount and account balance)
  3. create a Transaction object
  4. add transaction outputs to it (specify addresses and spending amounts)
  5. add inputs (UTXO, from the amount of which the transfer will be made). The sequence of actions must be exactly as follows. Trying to add an input before the outputs will result in a runtime error.
  6. Sign all inputs
  7. Get HEX representation of transaction

To perform step 2, use the following code:

Code:
private fun findSuitableUtxo(transactions: List<TransactionDTO>, amount: Long): Utxo? {
for (tx in transactions) {
// we are only interested in confirmed transactions
if (tx.status.confirmed) {
tx.vOut.forEachIndexed { index, vout ->
// in addition to the payment amounts and commission, we take into account “dust”
if (vout. value >= (amount + feeAmount + dustThreshold)) {
// check that this output was not used as an input (UTXO)
val isUsed = transactions.any { transaction ->
transaction.vIn.any { vin -> vin.txId = = tx.txId && vin.vOut == index }
}

// if all checks are passed, return this UTXO
if (!isUsed) {
return Utxo(tx.txId, index.toLong(), vout.value)
}
}
}
 }
 }
 return null
}

Steps 3 - 7 are implemented as follows:

Code:
private fun prepareTransaction(params: TransactionParams): String {
Context.propagate(Context())

// Basic settings of the network and the key used
val scriptType = ScriptType.P2WPKH
val network = BitcoinNetwork.SIGNET

// Preparing the key
val cleanKey = params.privateKey.substringAfter(':')
val key = DumpedPrivateKey.fromBase58(network, cleanKey).key

// getting the payment address
val addressParser = AddressParser.getDefault()
val toAddress = addressParser.parseAddress(params.destinationAddress)
// payment amount
val sendAmount = Coin.valueOf(params.amount)

// Amount available for spending in UTXO
val totalInput = Coin.valueOf(params.utxo.value)
// Commission miners.
val fee = Coin.valueOf(params.feeAmount)

// check if we have enough money
if (totalInput.subtract(sendAmount) < fee) {
throw IllegalArgumentException("Not enough funds to send transaction with fee")
}

// Step 3: create a transaction
val transaction = Transaction()
// Step 4: add an output - the recipient's address and amount
transaction.addOutput(sendAmount, toAddress)

// calculate the change (if any)
val change = totalInput.subtract(sendAmount).subtract(fee)
if (change.isPositive) {
// Important: you need to send the change back to the sender's wallet
transaction.addOutput(change, key.toAddress(scriptType, network))
}

// Add UTXO as an input
val utxo = Sha256Hash.wrap(params.utxo.txId)
 val outPoint = TransactionOutPoint(params.utxo.vOutIndex, utxo)
 val input = TransactionInput(transaction, byteArrayOf(), outPoint, Coin.valueOf(params.utxo.value))

 // Step 5: Add an input
 transaction.addInput(input)

 // Prepare scriptPubKey for signature
 val scriptCode = ScriptBuilder.createP2PKHOutputScript(key.pubKeyHash)

 // Sign the inputs
 for (i in 0 until transaction.inputs.size) {
 val txIn = transaction.getInput(i.toLong())
 val signature = transaction.calculateWitnessSignature(
 i,
 key
 scriptCode
 Coin.valueOf(params.utxo.value),
Transaction.SigHash.ALL,
false
)
// Step 6: Sign the inputs
txIn.witness = TransactionWitness.of(listOf(signature.encodeToBitcoin(), key.pubKey))
}

// Step 7: Get the HEX transactions for further sending.
return transaction.serialize().toHexString()

Then you can send the received HEX and show the result.

2850 satoshi sent!

2850 satoshi sent!

8. Conclusion​

Now that we’ve gone from creating a wallet to sending and viewing Bitcoin transactions, you have a small but functional cryptocurrency app for Android. Hopefully, this hands-on experience has helped you not only better understand the basics of Bitcoin transactions and interactions with the blockchain, but also inspired you to experiment further in this fascinating field.

For some, this may be the first step into the exciting world of decentralized finance (DeFi) and blockchain-based technologies.

In any case, I will be glad to receive Pull Requests and feedback! 🙂

9. Useful links​


Source
 
Top