Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Tutorial Thumbnail
Beginner · 30 minutes

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

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.