Blog

May 10, 2019

L2 Applications On Algorand: How to Make Your Own Coin

By: Evan Richard

THE IDEA

I often get asked this question:

WELL-MEANING FRIEND OR RELATIVE: Oh, Evan, you work in the cryptocurrencies industry? Oh-ho, are you making your own coin, when is EvanCoin coming, oh-ho?
ME: Ha-ha, oh, it’s not like that [blah blah blah]

Eventually I got fed up, now I answer:

ME: (suddenly intense) It will be coming very shortly and it will be built on Algorand.

This surprises people, but it really is that easy to build your own layer two application on Algorand! (A layer two application on Algorand will typically consist of an application that manipulates data using Algorand transaction’s note fields. This is as opposed to layer one code, the code that lives in the protocol itself.)

Today, I will show you a demo I made using the Algorand go SDK: examplecoin. The examplecoin demo lets you build your own personal coin on top of Algorand! In this demo I will initialize EvanCoin on the Algorand blockchain, and transfer some EvanCoin from one account to another. Checking out the Algorand developer documentation might help you follow along, too.

THE APPLICATION

You can find examplecoin on my GitHub. It allows a developer to initialize an example coin on the Algorand blockchain, and tabulate the current balances of any example coin on that chain, so long as the coin’s master key is known.

To do this, the examplecoin application uses the Note field of Algorand transactions. It places base64 encoded data in the note field, and uses Algorand’s account management to make sure the notes come from the correct accounts. The initialization of an examplecoin is a base64 blob placed in a transaction, as is the transfer of an examplecoin. Maybe it’s easier to explain with an example. Here are the examplecoin structs, which are barely more than “who’s sending to whom, and how much?”.

package examplecoin

// Initialize represents the start of an examplecoin ledger.
type Initialize struct {
	_struct struct{} `codec:",omitempty,omitemptyarray"`

	// Supply designates how much the examplecoin creator will start with.
	Supply uint64 `codec:"supply"`
}

// Transfer represents an examplecoin transfer.
type Transfer struct {
	_struct struct{} `codec:",omitempty,omitemptyarray"`

	// Destination designates who is sending examplecoin.
	Source string `codec:"source"`

	// Destination designates who is receiving examplecoin.
	Destination string `codec:"destination"`

	// Amount designates how much examplecoin is being transferred.
	Amount uint64 `codec:"Amount"`
}

// NoteFieldType indicates a type of examplecoin message encoded into a
// transaction's Note field.
type NoteFieldType string

const (
	// NoteInitialize indicates an Initialize message.
	NoteInitialize NoteFieldType = "i"

	// NoteTransfer indicates a Transfer message.
	NoteTransfer NoteFieldType = "t"
)

// NoteField is the struct that represents an examplecoin message.
type NoteField struct {
	_struct struct{} `codec:",omitempty,omitemptyarray"`

	// Type indicates which type of a message this is
	Type NoteFieldType `codec:"type"`

	// Initialize, for NoteInitialize type
	Initialize Initialize `codec:"i"`

	// Transfer, for NoteTransfer type
	Transfer Transfer `codec:"t"`
}

You can then take these structs and convert them into byte arrays, suitable for a transaction’s note field:

package examplecoin

import (
	"fmt"
	"github.com/algorand/go-algorand-sdk/client/algod/models"
	"github.com/algorand/go-algorand-sdk/encoding/msgpack"
)

// BytesBase64 is a base64-encoded binary blob (i.e., []byte), for
// use with text encodings like JSON.
type BytesBase64 []byte

// BuildInitializeNote takes in the desired supply and produces a blob for your note field
func BuildInitializeNote(supply uint64) (initializeBlob BytesBase64) {
	initializeBlob = BytesBase64(msgpack.Encode(NoteField{
		Type: NoteInitialize,
		Initialize: Initialize{
			Supply: supply,
		},
	}))
	return
}

// BuildInitializeNote takes in the desired recipient as well as amount to send, and produces a blob for your note field
func BuildTransferNote(amount uint64, from, to string) (transferBlob BytesBase64) {
	transferBlob = BytesBase64(msgpack.Encode(NoteField{
		Type: NoteTransfer,
		Transfer: Transfer{
			Amount:      amount,
			Source:      from,
			Destination: to,
		},
	}))
	return
}

You can see more on the full GitHub repo. The idea is that you can then read in these byte arrays and get the NoteField structs back using msgpack.Decode.

Algorand layer two is very flexible, so I want to rephrase and restate what we are doing here: we are taking arbitrary data structures and posting them to the Algorand blockchain, reading back these data structures later, and using that to inform new data operations. This means that you, as an app developer, can build any application you like on top of Algorand. The blockchain is providing you with verifiable secure storage. This also means that, if this application were handling sensitive or private data, we would have to handle the fact that the data is being posted to the public chain.

But we don’t have to care, because this is my own personal examplecoin, EvanCoin. Let’s go make some coin and move it around.

EXAMPLE USAGE

Below is a snippet that produces the blob for initializing an examplecoin and transferring examplecoin to another account. The coinKey variable is the address determining the master of the examplecoin: you could sort ofconsider it analogous to an ERC20 contract address. The receiverKey is the account receiving the examplecoin. Since this demonstration is just for me, and I intend to keep all EvanCoin to myself (everyone keeps asking me for EvanCoin, too bad, they can’t have any), I’ll just create uint64(100) coins, and transfer half to another account I control. Here’s the snippet for that:

*coinKey = "5AQU3SSNLTENO44PPKCFGEDDTSBPNEADK4UEXFDTQLQROR37CBY7VCFKDU"
receiverKey := "3Q7JX3TYSMF2MBPYZ2GM4GF3PXWL4Z34235DGTLQCPUPP3I64FWVLZ3Y34"
supply := uint64(100)
initNoteBytes64 := examplecoin.BuildInitializeNote(supply) // produces an Initialize struct, wrapped in the NoteField struct, and encoded in base64 bytes
initNoteString, _ := initNoteBytes64.MarshalText()
amountToSend := supply / 2
transferNoteBytes64 := examplecoin.BuildTransferNote(amountToSend, *coinKey, receiverKey) //produces a Transfer struct, wrapped and encoded just like Initialize 
transferNoteString, _ := transferNote.MarshalText()
fmt.Fprintf(os.Stderr, "initNote looks like %v    \n", string(initNoteString))
fmt.Fprintf(os.Stderr, "transferNote looks like %v   \n", string(transferNoteString))

I’ve omitted some things, such as imports.

Running the above code, I get the following output blobs:

initNote looks like gqFpgaZzdXBwbHlkpHR5cGWhaQ==
transferNote looks like 
gqF0g6ZBbW91bnQyq2Rlc3RpbmF0aW9u2TozUTdKWDNUWVNNRjJNQlBZWjJHTTRHRjNQ
WFdMNFozNDIzNURHVExRQ1BVUFAzSTY0RldWTFozWTM0pnNvdXJjZdk6NUFRVTNTU05M
VEVOTzQ0UFBLQ0ZHRUREVFNCUE5FQURLNFVFWEZEVFFMUVJPUjM3Q0JZN1ZDRktEVaR0
eXBloXQ=

Now that we have these notes, we can post them to the chain. The SDK lets you transact easily. The go doc documentation for the SDK can explain more about transacting. Here, we’re going to focus on the Note field, where we can place our data. We will place the initNotesBytes64 blob in the Notefield. (It’s important to know that the examplecoin demo will only inspect transactions that are to or from the master coinKey address. So, here bothto and from are set to the coinKey address. Just one of the two matching would be sufficient when reading data back later, though.)

// Make transaction. Evan has omitted some setup, see the godoc for general transacting information. 
fromAddr := coinKey 
toAddr := coinKey
initExamplecoinTxn, err := transaction.MakePaymentTxn(fromAddr, toAddr, fee, amount, firstRoundValid, lastRoundValid, initNoteBytes64)
if err != nil {
	fmt.Printf("Error creating transaction: %s\n", err)
	return
}
// Sign transaction. See above re: omitted setup.
signResponse, err := kmdClient.SignTransaction(exampleWalletHandleToken, "testpassword", tx)
if err != nil {
	fmt.Printf("Failed to sign transaction with kmd: %s\n", err)
	return
}
// Broadcast the transaction to the network
sendResponse, err := algodClient.SendRawTransaction(signResponse.SignedTransaction)
if err != nil {
	fmt.Printf("failed to send transaction: %s\n", err)
	return
}

The go doc can tell you more about building and signing transactions.

Nice! The initialization blob has been posted. Now let’s transfer some EvanCoin. To do so, we do exactly the same as above, but this time using the transferNotesBytes64 blob, building it into a transaction and sending it to the network.

(We could have also used the goal commandline utility to post these notes: something like goal clerk send --amount 1 --noteb64 gqFpgaZzdXBwbHlkpHR5cGWhaQ== --from 5AQU3SSNLTENO44PPKCFGEDDTSBPNEADK4UEXFDTQLQROR37CBY7VCFKDU --to 5AQU3SSNLTENO44PPKCFGEDDTSBPNEADK4UEXFDTQLQROR37CBY7VCFKDU. For this article, though, we’ll stay within go.)

Success! Our transactions have been submitted. (I’m running this against my own private test network, created using goal, so I don’t have to worry about the fee market, or wait for transaction confirmation: I know my transactions will be included in the next ~5 second block. If I wanted to explicitly wait for transaction confirmation, I could use the SDK to querywhether the transaction is pending or confirmed.)

I’ll run the examplecoin utility to tabulate the results. (I have omitted this step, but I also noted which the round range in which the transactions were confirmed, using algodClient’s Status() call, so I know both transactions got posted between rounds 500 and 550. To make things run faster, examplecoin uses this info to inform the utility which blocks to look at.)

$ algorand-l2-examplecoin -verbose=false -
coinkey=5AQU3SSNLTENO44PPKCFGEDDTSBPNEADK4UEXFDTQLQROR37CBY7VCFKDU -
firstround=500 lastround=550
Wrote results into results.csv

$ cat results.csv
5AQU3SSNLTENO44PPKCFGEDDTSBPNEADK4UEXFDTQLQROR37CBY7VCFKDU,50
3Q7JX3TYSMF2MBPYZ2GM4GF3PXWL4Z34235DGTLQCPUPP3I64FWVLZ3Y34,50

Success! I have 50 EvanCoin in one account and 50 EvanCoin in another account. 😎

GOTCHAS AND BRAINSTORMING

  • In this demo I assume the examplecoin utility will be used by very few users (just me) to manage very few accounts, so there’s a few scalability issues: using a csv rather than a database, for example, or trying to hold everyone’s running balance in memory as the results are tabulated.
  • Currently the utility starts from scratch every time you run it, and overwrites the output file entirely. One could change it to use a preexisting output, and just scan along the chain from where it left off.
  • Since the utility needs to read every single block in a potentially large range of blocks, it needs to be run on a machine with an Algorand archival node or relay, so that the blocks that the utility needs are never pruned.
  • I have not audited any of this code for correctness or security, as it’s a quick toy demonstration. I wonder if someone can steal my precious EvanCoin? 😱
  • I mentioned briefly that this functionality is sort of analogous to named fungible tokens, similar (kind of) to ERC-20. With some changes, one could probably tweak things to work like nonfungible tokens (NFTs).
  • Code and data can be one and the same thing, can’t it? Can you picture a system that puts code on the chain as encoded data, executes it, and puts new code on the chain in response? A self-amending program? 🤔

THE END

Thanks, everyone, I hope this little demo helped or educated you in some way. We’re all in this together! 🎉