go get github.com/btcsuite/btcutil go get github.com/btcsuite/btcd

How to make a Bitcoin Transaction in Go Lang

Jan 22, 2022

Bitcoin is all the rage in recent years. So does Golang - a programming language developed by Google. Today I am going to instruct you how to make a simple Bitcoin transaction with Golang. Hopefully, you will be able to transfer Bitcoin without using any DApps after this tutorial.

The tutorial requires you to have some basic understanding of Bitcoin as well as Golang.  I recommend you should visit these two sites below if you are unfamiliar with the aforementioned topics:

+ Bitcoin: https://developer.bitcoin.org/reference/

+ Golang:  https://go.dev/doc/tutorial/getting-started

To begin with, you need to install two libraries for working with Bitcoin in Golang.

+ btcutil: https://github.com/btcsuite/btcutil

+ btcd: https://github.com/btcsuite/btcd

You can simply get these libraries by executing the following commands:

go get github.com/btcsuite/btcutil
go get github.com/btcsuite/btcd

Step 1: You need to initialize required variables for your bitcoin transaction inputs. Here's the example inputs:

var fromAddress string = "XXXXXXXXXXXXXXXXXXXX"
var fromAddressPrivateKey string = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
var toAddress string = "XXXXXXXXXXXXXXXXXXXXXX"
var sentAmount int64 = 10000000 // in Satoshi unit 

In addition, you need to specify the Bitcoin network you will broadcast your transaction. There are two types of Bitcoin network: mainnet and testnet. In this tutorial, I will set it to the testnet network since Bitcoin doesn't hold any value in this environment.

var network *chaincfg.Params = &chaincfg.TestNet3Params

As for the fee rate, you can refer to various sites to get this data. For this tutorial, I will get the fee rate from mempool. Make sure that you get the rate in the correct network and convert it into Satoshi unit.

var feeRate int = 100000 // in Satoshi unit

Step 2: You will create a list of transaction input from your UTXO (unspent transaction output) from previous transactions. To get the UTXO, you can use this endpoint from the mempool. Here's the example:

https://mempool.space/api/address/1KFHE7w8BhaENAswwryaoccDb6qcT6DbYY/utxo

After you get the response from mempool, you will convert it into the following struct:

type UTXO struct {
 TxID   string `json:"txid"`
 Vout   uint32 `json:"vout"`
 Status bool   `json:"status"`
 Value  int64  `json:"value"` // value is in Satoshi unit
}

Here's the example array of UTXO fetched from mempool.

var UTXO = []UTXO{
		{
			TxID:   "XXXXXXXXXXXXXXX",
			Vout:   300,
			Status: true,
		},
		{
			TxID:   "XXXXXXXXXXXXXXX",
			Vout:   100,
			Status: false,
		},
}

Now, you will create a variable - txMsg that contains the tx inputs and outputs for your transaction.  

var txMsg *wire.MsgTx = wire.NewMsgTx(wire.TxVersion)

Now, you will loop through the UTXO array to add the required tx inputs into txMsg. You will need to create a variable - requiredTxInAmount to keep track of the total input amount. The iteration will stop when requiredTxInAmount is greater than sentAmount. Besides, you will need to skip the item in UTXO whose status is false.

var requiredTxInAmount int64

for index, v := range UTXO {
		if !v.Status || requiredTxInAmount > sentAmount {
			continue
		}

		chainHash, _ := chainhash.NewHashFromStr(v.TxID)
		outPoint := wire.NewOutPoint(chainHash, uint32(index))
		txIn := wire.NewTxIn(outPoint, nil, nil)
		txMsg.AddTxIn(txIn)
		requiredTxInAmount += v.Value
}

Step 3: This is a tricky step. The way Bitcoin calculates the fee is based on two factors: fee rate, the size of transaction message. Regarding the latter, you will need the length of the tx inputs and outputs. In this tutorial, we will set the tx output length to 2 by default. Here's the formula to calculate the transaction size:

txSize = txInputNo*180 + txOutputNo*34 + 10

Once the tx size is calculated, we will determine the transaction fee. If the fee and the sentAmount is greater than requiredTxInAmount, you will need to add more item from the UTXO array.

var fee = txInputSize * feeRate

Step 4: As mentioned earlier, you will need to set two transaction outputs. One is for the toAddress and the other is for fromAddress. I guess this might get you feel confused. The reason why we need to create an output for the sender's address is because we need to transfer the remaining amount from  requiredTxInAmount back to the sender's. Otherwise, this amount will be considered as transaction fee.

// For recipient
toAddressScript, _ := GetPayToAddrScript(toAddress, network)
txMsg.AddTxOut(wire.NewTxOut(sentAmount, toAddressScript))

// For sender
remainingUTX0 := requiredTxInAmount - int64(fee) - sentAmount
fromAddrScript, _ := GetPayToAddrScript(fromAddress, network)
txMsg.AddTxOut(wire.NewTxOut(remainingUTX0, fromAddrScript))

Step 5: You are going to validate the txMsg by signing it with your private key in this step. You will need to convert the string-typed private key into the required format due to the library's requirement.

// Process private key
privateKeyBytes, err := hex.DecodeString(fromAddressPrivateKey)
	if err != nil {
		return
	}
privateKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), privateKeyBytes)

for i, v := range txMsg.TxIn {
		sigScript, _ := txscript.SignatureScript(
			txMsg,               // The tx to be signed.
			i,                   // The index of the txin the signature is for.
			fromAddrScript,      // The other half of the script from the PubKeyHash.
			txscript.SigHashAll, // The signature flags that indicate what the sig covers.
			privateKey,          // The key to generate the signature with.
			false)               // The compress sig flag. This saves space on the blockchain.

		v.SignatureScript = sigScript
}

Step 6: The txMsg will be executed by the installed Bitcoin library. If succeed, you will convert it into the raw transaction string. To broadcast the transaction into Bitcoin network, you will need a third-party site or a Bitcoin node. In this tutorial, we will use mempool again to do so.  

flags := txscript.StandardVerifyFlags
vm, err := txscript.NewEngine(fromAddrScript, txMsg, len(txMsg.TxIn), flags, nil, nil, requiredTxInAmount)
if err != nil {
	return
}
vm.Execute()

// Convert raw transaction into hex string
buf := bytes.NewBuffer(make([]byte, 0, txMsg.SerializeSize()))
txMsg.Serialize(buf)
rawTxHex := hex.EncodeToString(buf.Bytes())
fmt.Println(rawTxHex)

Once the transaction is broadcast, you can check its details by using either the fromAddress or toAddress on any block explorer site. Bear in mind that you need to check the transaction details in the Bitcoin network you broadcast the transaction.

In summary, I hope you can succeed in making a transaction in Golang with your own computer. By doing so, you can feel assured that your transaction will not be corrupted like in DApps.

[ Want to discuss this article with other readers? Use our commenting box below to be redirected to the forums or click here for a direct link  ]

go get github.com/btcsuite/btcutil go get github.com/btcsuite/btcd