Learn How To Create Your Own Crypto Coin

Currency is a medium of exchange for goods and services. Nowadays, currency takes the form of paper, coins, or an entry on a centralized digital ledger, usually issued by a government and generally accepted as a method of payment. In the past, currency took the form of various metals like gold or silver, or even colored beads and salt. Cryptocurrency is a digital form of currency underpinned by cryptography and blockchain technology, primarily used as a way of transferring value without having to rely on a single centralized platform, such as a bank.

In this technical tutorial, we’ll explore the difference between coins and tokens, and you’ll learn how to develop your own crypto coin.

Let’s get started!

The Difference Between Coins and Tokens

Bitcoin is the most popular cryptocurrency, and its main purpose is to serve as a medium of exchange. There are also many tokens that have value, but also have additional purposes beyond their utility as a form of exchange: Governance voting tokens grant holders certain governance privileges, for example. There are also NFTs: non-fungible tokens representing ownership of something unique. So, what’s the difference between all of these types of digital assets?

From an engineering perspective, the difference between coins and tokens is pretty simple. Coins are part of a single blockchain, while tokens operate on an existing blockchain in the form of smart contracts.

For instance, BTC is the coin of the Bitcoin blockchain, and ETH is the coin of the Ethereum blockchain. Both BTC and ETH are coins. USDC, AAVE, and WETH, to take just a few examples, are tokens, because they are essentially smart contracts hosted on top of the Ethereum blockchain. The same principle applies to NFTs: They are also tokens residing on various general-purpose blockchains.

To learn how to create your own token, check out this blog post. To learn more about creating NFTs, check out this guide. Keep reading to learn more about creating your own crypto coin.

Getting Started

This project is written in Go, but no previous experience in this language is required. To follow along, check out the full working example located in the Chainlink Smart Contract Examples repository under the My Crypto Coin folder.

git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/my-crypto-coin

The next step is to install Go on your local machine, which you can do by following the official guide. Since this process takes around 10 minutes, you’ll have time to make coffee while it completes.

Verify your $GOPATH is correctly set before continuing. This is a mandatory step.

The convention is to store the source code inside the $GOPATH/src and the compiled program binaries inside the $GOPATH/bin. Navigate to the $GOPATH/src and create a new folder called my-crypto-coin.

Let’s start with the development.

It All Starts With the Genesis Block

Coins are units inside a blockchain’s distributed ledger. Every blockchain has its initial state, also known as the genesis block. Inside your newly created my-crypto-coin project, create a new folder and name it ledger. Inside the ledger folder, create a new file, name it genesis.json, and paste the code below. We are going to allocate to Alice an initial 1M supply of our crypto coin.

{
  "genesis_time": "2022-04-12T00:00:00.000000000Z",
  "chain_id": "our-blockchain",
  "balances": {
    "alice": 1000000
  }
}

This is the original state. Transactions change the state. If our blockchain node goes down, we can use the genesis file and the history of transactions to recreate the whole ledger and sync the rest of the network to the latest state.

Accounts, Transactions, and Global State

Navigate to the ledger folder and create a tx.go file. Each account will be represented by Account structure. Each transaction will be represented by Transaction structure, with the following properties: “from”, “to”, and “value”. We are going to add a functionality for creating new accounts and transactions.

package ledger

type Account string

type Tx struct {
     From  Account `json:"from"`
     To    Account `json:"to"`
     Value uint    `json:"value"`
}

func NewAccount(value string) Account {
     return Account(value)
}

func NewTx(from Account, to Account, value uint) Tx {
     return Tx{from, to, value}
}

Transactions will be stored inside the ledger, so let’s add couple of them manually as a demo. Inside the ledger directory, create a new ledger.db file and paste the following content there.

{"from":"alice","to":"bob","value":10}
{"from":"alice","to":"alice","value":3}
{"from":"alice","to":"alice","value":500}
{"from":"alice","to":"bob","value":100}
{"from":"bob","to":"alice","value":5}
{"from":"bob","to":"carol","value":10}

The genesis state stays the same and remains inside the genesis.json file. Let’s add a way to load its state programmatically. Create a new file called genesis.go, which will store a map of accounts with corresponding coin balances in the genesis state.

package ledger

import (
     "io/ioutil"
     "encoding/json"
)

type Genesis struct {
     Balances map[Account]uint `json:"balances"`
}

func loadGenesis(path string) (Genesis, error) {
     genesisFileContent, err := ioutil.ReadFile(path)
     if err != nil {
          return Genesis{}, err
     }

     var loadedGenesis Genesis

     err = json.Unmarshal(genesisFileContent, &loadedGenesis)
     if err != nil {
          return Genesis{}, err
     }

return loadedGenesis, nil
} 

The core business logic will be stored inside the Store structure. Create a new file called state.go. The State structure will contain the details of all account balances, who transferred coins to whom, and how many coins were transferred. It must know how to read the initial state from the genesis file. Afterward, the genesis State balances are updated by sequentially replaying all the transactions from the ledger.db file. And finally, here we need to write a logic for adding new transactions to the ledger.

package ledger

import (
     "fmt"
     "os"
     "path/filepath"
     "bufio"
     "encoding/json"
)

type State struct {
     Balances  map[Account]uint
     txMempool []Tx

     dbFile *os.File
}

func SyncState() (*State, error)  {
     cwd, err := os.Getwd()
     if err != nil {
          return nil, err
     }

     gen, err := loadGenesis(filepath.Join(cwd, "ledger", "genesis.json"))
     if err != nil {
          return nil, err
     }

     balances := make(map[Account]uint)
     for account, balance := range gen.Balances {
          balances[account] = balance
     }

     file, err := os.OpenFile(filepath.Join(cwd, "ledger", "ledger.db"), 
os.O_APPEND|os.O_RDWR, 0600)
     if err != nil {
          return nil, err
     }

     scanner := bufio.NewScanner(file)

     state := &State{balances, make([]Tx, 0), file}

     for scanner.Scan() {
          if err := scanner.Err(); err != nil {
               return nil, err
          }

          var transaction Tx
          json.Unmarshal(scanner.Bytes(), &transaction)

          if err := state.writeTransaction(transaction); err != nil {
               return nil, err
          }
     }

     return state, nil
}

func (s *State) writeTransaction(tx Tx) error {
     if s.Balances[tx.From] < tx.Value {
          return fmt.Errorf("insufficient balance")
     }

     s.Balances[tx.From] -= tx.Value
     s.Balances[tx.To] += tx.Value

     return nil
}

func (s *State) Close() {
     s.dbFile.Close()
}

func (s *State) WriteToLedger(tx Tx) error {
     if err := s.writeTransaction(tx); err != nil {
          return err
     }

     s.txMempool = append(s.txMempool, tx)

     mempool := make([]Tx, len(s.txMempool))
     copy(mempool, s.txMempool)

     for i := 0; i < len(mempool); i++ {
          txJson, err := json.Marshal(mempool[i])
          if err != nil {
               return err
          }

          if _, err = s.dbFile.Write(append(txJson, '\n')); err != nil {
               return err
          }
          s.txMempool = s.txMempool[1:]
     }

     return nil
}

Develop a Command-Line Interface (CLI)

The easiest way to use our new crypto coin is to develop a command-line interface (CLI). The easiest way to develop CLI-based programs in Go is by using the next third party library. To use this library, we need to initialize Go’s built-in dependency manager for our project, called Go modules. The Go modules commands will automatically fetch any library you reference within your Go files.

echo $GOPATH
cd $GOPATH/src/my-crypto-coin 
go mod init

Now let’s create a new folder and name it cli. But wait, we haven’t officially named our crypto coin! Let’s call it My Crypto Coin for now. Navigate to the cli folder and create a new folder named mcc, which is short for My Crypto Coin. Navigate to the mcc folder.

Create a new file called main.go. This will be the main entry for our program.

package main

import (
     "github.com/spf13/cobra"
     "os"
     "fmt"
)

func main() {
     var mccCmd = &cobra.Command{
          Use:   "mcc",
          Short: "My Crypto Coin CLI",
          Run: func(cmd *cobra.Command, args []string) {
          },
     }

     mccCmd.AddCommand(versionCmd)
     mccCmd.AddCommand(balancesCmd())
     mccCmd.AddCommand(txCmd())

     err := mccCmd.Execute()
     if err != nil {
          fmt.Fprintln(os.Stderr, err)
          os.Exit(1)
     }
}

Next, create a version.go file and paste this content.

package main

import (
     "fmt"
     "github.com/spf13/cobra"
)

const Major = "0"
const Minor = "1"
const Fix = "0"
const Verbal = "Genesis"

var versionCmd = &cobra.Command{
     Use:   "version",
     Short: "Describes version.",
     Run: func(cmd *cobra.Command, args []string) {
          fmt.Println(fmt.Sprintf("Version: %s.%s.%s-beta %s", Major, Minor, Fix, Verbal))
     },
}

After that, let’s create a mechanism for reading all the account balances from the ledger. Create a new balances.go file.

package main

import (
     "github.com/spf13/cobra"
     "my-crypto-coin/ledger"
     "fmt"
     "os"
)

func balancesCmd() *cobra.Command {
     var balancesCmd = &cobra.Command{
          Use:   "balances",
          Short: "Interact with balances (list...).",
          Run: func(cmd *cobra.Command, args []string) {
          },
     }

     balancesCmd.AddCommand(balancesListCmd)

     return balancesCmd
}

var balancesListCmd = &cobra.Command{
     Use:   "list",
     Short: "Lists all balances.",
     Run: func(cmd *cobra.Command, args []string) {
          state, err := ledger.SyncState()
          if err != nil {
               fmt.Fprintln(os.Stderr, err)
               os.Exit(1)
          }
          defer state.Close()

          fmt.Println("Accounts balances:")
          fmt.Println("__________________")
          fmt.Println("")
          for account, balance := range state.Balances {
               fmt.Println(fmt.Sprintf("%s: %d", account, balance))
          }
     },
}

Finally, let’s add a command for writing transactions to the ledger. Create new tx.go file.

package main

import (
     "github.com/spf13/cobra"
     "my-crypto-coin/ledger"
     "fmt"
     "os"
)

func txCmd() *cobra.Command {
     var txsCmd = &cobra.Command{
          Use:   "tx",
          Short: "Interact with transactions (new...).",
          Run: func(cmd *cobra.Command, args []string) {
          },
     }

     txsCmd.AddCommand(newTxCmd())

     return txsCmd
}

func newTxCmd() *cobra.Command {
     var cmd = &cobra.Command{
          Use:   "new",
          Short: "Adds new TX to the ledger.",
          Run: func(cmd *cobra.Command, args []string) {
               from, _ := cmd.Flags().GetString("from")
               to, _ := cmd.Flags().GetString("to")
               value, _ := cmd.Flags().GetUint("value")

               tx := ledger.NewTx(ledger.NewAccount(from), ledger.NewAccount(to), 
value)

               state, err := ledger.SyncState()
               if err != nil {
                    fmt.Fprintln(os.Stderr, err)
                    os.Exit(1)
               }
               defer state.Close()
               err = state.WriteToLedger(tx)
               if err != nil {
                    fmt.Fprintln(os.Stderr, err)
                    os.Exit(1)
               }

               fmt.Println("TX successfully added to the ledger.")
          },
     }

     cmd.Flags().String("from", "", "From what account to send coins")
     cmd.MarkFlagRequired("from")

     cmd.Flags().String("to", "", "To what account to send coins")
     cmd.MarkFlagRequired("to")

     cmd.Flags().Uint("value", 0, "How many coins to send")
     cmd.MarkFlagRequired("value")

     return cmd
}

Compile the program using this command:

go install $GOPATH/src/my-crypto-coin/cli/mcc/…

Go will detect missing libraries and automatically fetch them before compiling the program. Depending on your $GOPATH, the resulting program will be saved in the $GOPATH/bin folder.

To verify that installation was successful, run this command:

which mcc

You should see a path similar to this one in the terminal: /Users/yourname/go/bin/mcc. Let’s see all the available commands. Run:

mcc --help

To see the current version of the CLI, run:

mcc version

To see current user balances, run the following command:

mcc balances list

The output should be:

Accounts balances:
__________________

alice: 999895
bob: 95
carol: 10

Now let’s make our first transaction using our CLI. Enter the following command:

mcc tx new --from alice --to carol --value 10

If you open the ledger/ledger.db file, you should be able to see an additional line:

{"from":"alice","to":"carol","value":10}

And let’s list balances once again using the mcc balances list command. The output should be:

Accounts balances:
__________________

alice: 999885
bob: 95
carol: 20

What’s Next?

At the moment, our users are represented with their names inside the ledger. But what happens if there are two Bobs? We need to add a way to represent accounts uniquely using a common hashing algorithm.

The next problem is that anyone can transfer anyone else’s coins using our CLI. We need a way to allow users to transfer only the coins they own using public key cryptography.

What would happen if our machine went down? There is no way to recreate our network since we are the only node in the network. We need to incentivize people to join our network and become nodes. Once our network grows, we’ll need a common way to determine which transactions are valid and which aren’t, and also verify the current state of the network. We’ll need a consensus algorithm and mechanisms.

Summary

In this article, you’ve learned how to develop a basic crypto coin using Go, and we’ve covered the main difference between coins and tokens. To learn more, head to the Chainlink Smart Contract Examples repository and start experimenting with this and the other example projects.

Learn more about Chainlink by visiting chain.link or reading the documentation at docs.chain.link. To discuss an integration, reach out to an expert.

Chainlink 2023 Fall Hackathon

Disclaimer

Level
Medium
Duration
15 min
Stack
Go
Services
No items found.
Requirements
  • Go Installed
Last updated: Aug 29, 2024