Read and Write to the Transaction Note Field with Go
Up to 1kb of arbitrary data can be stored in any Transaction. This data can be stored and read from the transactions note field. If this data is encoded using the SDKs encode function you can decode the data from the note field using the decode function. This example walks through both the process of writing to and reading from the transaction note field.
Requirements
Background
When creating an application using Algorand, you can use the note field for arbitrary data storage. This data can be up to 1kb in length and the structure can be determined by the application. The SDKs provide methods to properly encode and decode this data. The complete example on reading and writing a note can be found here.
Steps
- 1. Create an Account and Add Funds
- 2. Create a String and Copy into Transaction
- 3. Utility Function to Verify Transaction and Print
- 4. Send Transaction with Note to Blockchain
- 5. Read Transaction from Blockchain and Recover String Note
- 6. Confirm the Output
- 7. Using Indexer to Query the Note Field
- 8. Completed Code
1. Create an Account and Add Funds
Use goal or the Go SDK to create an account and add funds.
Using goal:
$goal account new -d <your-data-directory> -w <your-wallet>
Using goal or the Go SDK will return an address that we will need to recover in the tutorial. Export the account mnemonic using the following goal
command.
$goal account export -a <account-created-above> -d <your-data-directory> -w <your-wallet>
Using the Go SDK:
To create accounts using the Go SDK see this tutorial to create a standalone account.
This will export the account mnemonic string of random words that you should copy for later use. Note that this is just a tutorial. Hard coding a mnemonic is not advised and proper key management should be used in production applications.
Use the dispenser to add funds to the account. The TestNet dispenser is here.
Learn More
- Add Funds using Dispenser
2. Create a String and Copy into Transaction
In this tutorial, we are just using a string for the note field. A note field can contain a structure as well. Before doing that we must connect to the server and recover the account we created and funded in the previous step. In the code below, specify your token, server and port. Additionally set the mnemonic to the string you recovered in step 1.
package main
import (
"context"
"crypto/ed25519"
json "encoding/json"
"errors"
"fmt"
"strings"
"github.com/algorand/go-algorand-sdk/client/v2/algod"
"github.com/algorand/go-algorand-sdk/client/v2/common/models"
"github.com/algorand/go-algorand-sdk/crypto"
"github.com/algorand/go-algorand-sdk/mnemonic"
"github.com/algorand/go-algorand-sdk/transaction"
"github.com/algorand/go-algorand-sdk/types"
)
const algodAddress = "http://localhost:4001"
const algodToken = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
func main() {
algodClient, err := algod.MakeClient(algodAddress, algodToken)
if err != nil {
fmt.Printf("Issue with creating algod client: %s\n", err)
return
}
passphrase := "price clap dilemma swim genius fame lucky crack torch hunt maid palace ladder unlock symptom rubber scale load acoustic drop oval cabbage review abstract embark"
privateKey, err := mnemonic.ToPrivateKey(passphrase)
if err != nil {
fmt.Printf("Issue with mnemonic conversion: %s\n", err)
return
}
var myAddress types.Address
publicKey := privateKey.Public()
cpk := publicKey.(ed25519.PublicKey)
copy(myAddress[:], cpk[:])
fmt.Printf("My address: %s\n", myAddress.String())
// Check account balance
accountInfo, err := algodClient.AccountInformation(myAddress.String()).Do(context.Background())
if err != nil {
fmt.Printf("Error getting account info: %s\n", err)
return
}
fmt.Printf("Account balance: %d microAlgos\n", accountInfo.Amount)
// Construct the transaction
txParams, err := algodClient.SuggestedParams().Do(context.Background())
if err != nil {
fmt.Printf("Error getting suggested tx params: %s\n", err)
return
}
// comment out the next two (2) lines to use suggested fees
txParams.FlatFee = true
txParams.Fee = 1000
fromAddr := myAddress.String()
toAddr := "GD64YIY3TWGDMCNPP553DZPPR6LDUSFQOIJVFDPPXWEG3FVOJCCDBBHU5A"
var amount uint64 = 1000000
var minFee uint64 = 1000
note := []byte("Hello World")
genID := txParams.GenesisID
genHash := txParams.GenesisHash
firstValidRound := uint64(txParams.FirstRoundValid)
lastValidRound := uint64(txParams.LastRoundValid)
txn, err := transaction.MakePaymentTxnWithFlatFee(fromAddr, toAddr, minFee, amount, firstValidRound, lastValidRound, note, "", genID, genHash)
if err != nil {
fmt.Printf("Error creating transaction: %s\n", err)
return
}
}
3. Utility Function to Verify Transaction and Print
The signed transaction can now be sent to the network. Before doing that we need to add a utility function to the code that will verify the transaction has been written to the blockchain.
Add this waitForConfirmation
function to the code
// Function that waits for a given txId to be confirmed by the network
func waitForConfirmation(txID string, client *algod.Client, timeout uint64) (models.PendingTransactionInfoResponse, error) {
pt := new(models.PendingTransactionInfoResponse)
if client == nil || txID == "" || timeout < 0 {
fmt.Printf("Bad arguments for waitForConfirmation")
var msg = errors.New("Bad arguments for waitForConfirmation")
return *pt, msg
}
status, err := client.Status().Do(context.Background())
if err != nil {
fmt.Printf("error getting algod status: %s\n", err)
var msg = errors.New(strings.Join([]string{"error getting algod status: "}, err.Error()))
return *pt, msg
}
startRound := status.LastRound + 1
currentRound := startRound
for currentRound < (startRound + timeout) {
*pt, _, err = client.PendingTransactionInformation(txID).Do(context.Background())
if err != nil {
fmt.Printf("error getting pending transaction: %s\n", err)
var msg = errors.New(strings.Join([]string{"error getting pending transaction: "}, err.Error()))
return *pt, msg
}
if pt.ConfirmedRound > 0 {
fmt.Printf("Transaction "+txID+" confirmed in round %d\n", pt.ConfirmedRound)
return *pt, nil
}
if pt.PoolError != "" {
fmt.Printf("There was a pool error, then the transaction has been rejected!")
var msg = errors.New("There was a pool error, then the transaction has been rejected")
return *pt, msg
}
fmt.Printf("waiting for confirmation\n")
status, err = client.StatusAfterBlock(currentRound).Do(context.Background())
currentRound++
}
msg := errors.New("Tx not found in round range")
return *pt, msg
}
4. Send Transaction with Note to Blockchain
At the end of the code in main()
, we can add a function call to broadcast the transaction to the network and also wait for the transaction to be confirmed.
// Sign the transaction
txID, signedTxn, err := crypto.SignTransaction(privateKey, txn)
if err != nil {
fmt.Printf("Failed to sign transaction: %s\n", err)
return
}
fmt.Printf("Signed txid: %s\n", txID)
// Submit the transaction
sendResponse, err := algodClient.SendRawTransaction(signedTxn).Do(context.Background())
if err != nil {
fmt.Printf("failed to send transaction: %s\n", err)
return
}
fmt.Printf("Submitted transaction %s\n", sendResponse)
// Wait for confirmation
confirmedTxn, err := waitForConfirmation(txID, algodClient, 4)
if err != nil {
fmt.Printf("Error wating for confirmation on txID: %s\n", txID)
return
}
5. Read Transaction from Blockchain and Recover String Note
Now that the transaction is confirmed, we can modify the code to read the transaction returned from the waitForConfirmation
method and decode the string from the note field. Add this to the bottom of the code in the main()
method.
txnJSON, err := json.MarshalIndent(confirmedTxn.Transaction.Txn, "", "\t")
if err != nil {
fmt.Printf("Can not marshall txn data: %s\n", err)
}
fmt.Printf("Transaction information: %s\n", txnJSON)
fmt.Printf("Decoded note: %s\n", string(confirmedTxn.Transaction.Txn.Note))
6. Confirm the Output
Your output should look similar to this:
My address: 7DCJZKC4JDUKM25W7TDJ5XRTWGUTH6DOG5WARVA47DOCXQOTB4GMLNVW7I
Account balance: 321874000 microAlgos
Signed txid: SLRBGBAZUIESY2OMGNKSVOZWE2N5OFDJ7CU5DXH72Z2GGQJ5UCIA
Submitted transaction SLRBGBAZUIESY2OMGNKSVOZWE2N5OFDJ7CU5DXH72Z2GGQJ5UCIA
Transaction SLRBGBAZUIESY2OMGNKSVOZWE2N5OFDJ7CU5DXH72Z2GGQJ5UCIA confirmed in round 11576582
Transaction information: {
"Type": "pay",
"Sender": [
...
],
"Fee": 1000,
"FirstValid": 11576578,
"LastValid": 11577578,
"Note": "UnVzcyBGdXN0aW5v",
"GenesisID": "testnet-v1.0",
"GenesisHash": [
...
}
Decoded note: Russ Fustino
7. Using Indexer to Query the Note Field
The following complete code can be used to query the note field with Indexer.
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/algorand/go-algorand-sdk/client/v2/indexer"
)
// indexer host
const indexerAddress = "http://localhost:8981"
const indexerToken = ""
// query parameters
var minAmount uint64 = 10
// var data = "showing prefix"
// var encodedNote = base64.StdEncoding.EncodeToString([]byte(data))
var notePrefix = "Russ"
var round uint64 = 11563027
func main() {
// Create an indexer client
indexerClient, err := indexer.MakeClient(indexerAddress, indexerToken)
if err != nil {
return
}
// Query
result, err := indexerClient.SearchForTransactions().MinRound(round).NotePrefix([]byte(notePrefix)).Do(context.Background())
// Print the results
JSON, err := json.MarshalIndent(string(result.Transactions[0].Note), "", "\t")
fmt.Printf(string(JSON) + "\n")
}
8. Completed Code
The complete example on reading and writing a note can be found here and searching on a note using indexer here.