ERC20 Watcher Demo

01.31.2023-By — Zach Ramsay
  • Developers
  • Product
post image

In the last blog post, we introduced all the main components of the Laconic Stack. The first point of entry for any developer wishing to use Laconic is Stack Orchestrator. It allows you to stand up a local fixturenet for testing purposes. Integrated directly into Stack Orchestrator are several "stacks" which work out of the box. Today, we'll be going over the ERC20 "stack" to provide an overview of some key components of the Laconic Stack.

You will accomplish the following:

  • stand up the core stack using Stack Orchestrator

  • deploy an ERC20 token

  • deploy an ERC20 Watcher

  • send tokens to and from your local account to a new account on Metamask

  • use GraphQL to query the watcher for information about the token and accounts

Install

This tutorial assumes you are on a local machine (Mac or Linux). Trying it in the cloud requires additional configurations (e.g., opening ports) not covered here.

Pre-requisites

If using a fresh Ubuntu Digital Ocean droplet, check out this script for a quick setup.

WARNING: if installing docker-compose via package manager (as opposed to Docker Desktop), you must install the plugin, e.g., on Linux:

mkdir -p ~/.docker/cli-plugins
curl -SL https://github.com/docker/compose/releases/download/v2.11.2/docker-compose-linux-x86_64 -o ~/.docker/cli-plugins/docker-compose
chmod +x ~/.docker/cli-plugins/docker-compose

Next, install the latest release of Stack Orchestrator

curl -L -o laconic-so https://github.com/cerc-io/stack-orchestrator/releases/latest/download/laconic-so

Give it permission:

chmod +x laconic-so

Verify operation:

./laconic-so 
Usage: python -m laconic-so [OPTIONS] COMMAND [ARGS]...

  Laconic Stack Orchestrator

Options:
  --stack TEXT         specify a stack to build/deploy
  --quiet
  --verbose
  --dry-run
  --local-stack
  --debug
  --continue-on-error
  -h, --help           Show this message and exit.

Commands:
  build-containers    build the set of containers required for a complete...
  build-npms          build the set of npm packages required for a...
  deploy-system       deploy a stack
  setup-repositories  git clone the set of repositories required to build...
  version             print tool version

For a more permanent setup, move the binary to `~/bin` and add it your `PATH`.

Stack Orchestrator

The `laconic-so` CLI tool makes it easy to experiment with various components of the stack. It allows you to quickly and seamlessly experiment with watchers. Because it uses docker/docker-compose, several commands in this tutorial will leverage the ability to execute commands directly in the containers. This, for example, means that `yarn` doesn't need to be installed on your local machine.

Setup

Use the stack orchestrator to pull the core repositories:

./laconic-so --stack erc20 setup-repositories

You'll see something like:

Dev Root is: /root/cerc
Checking: /root/cerc/go-ethereum: Needs to be fetched
100%|####################################################################################################| 71.6k/71.6k [00:23<00:00, 3.10kB/s]
Checking: /root/cerc/ipld-eth-db: Needs to be fetched
100%|##########################################################################################################| 595/595 [00:00<00:00, 991B/s]
Checking: /root/cerc/ipld-eth-server: Needs to be fetched
100%|####################################################################################################| 25.5k/25.5k [00:06<00:00, 3.82kB/s]
Checking: /root/cerc/watcher-ts: Needs to be fetched
100%|####################################################################################################| 8.79k/8.79k [00:01<00:00, 4.49kB/s]

Next, we'll build the docker images for each repo we just fetched.

./laconic-so --stack erc20 build-containers 

This process will take 10-15 minutes, go make a pot of coffee. The output will give you an idea of what's going on. Eventually, you'll see:

Successfully built 77c75d57ad66
Successfully tagged cerc/watcher-erc20:local

Next, let's deploy this stack:

./laconic-so --stack erc20deploy-system up

The output will looks like this (ignore the warnings):

WARN[0000] The "eth_proxy_on_error" variable is not set. Defaulting to a blank string. 
WARN[0000] The "eth_forward_eth_calls" variable is not set. Defaulting to a blank string. 
WARN[0000] The "eth_http_path" variable is not set. Defaulting to a blank string. 
[+] Running 23/23
 ⠿ ipld-eth-db Pulled                                                                                                                   18.4s
   ⠿ 213ec9aee27d Already exists                                                                                                         0.0s
   ⠿ 85c3ef7cf9a6 Pull complete                                                                                                          0.7s
   ⠿ ac29cc04759a Pull complete                                                                                                          0.9s
   ⠿ 2a37e244d86b Pull complete                                                                                                         13.5s
   ⠿ 36d7202aa1cf Pull complete                                                                                                         13.8s
   ⠿ 3acdddb9790a Pull complete                                                                                                         13.9s
   ⠿ 9a938759f2bf Pull complete                                                                                                         14.1s
   ⠿ 5d65a6241248 Pull complete                                                                                                         14.2s
   ⠿ cee6999f074e Pull complete                                                                                                         14.4s
   ⠿ 20b12472cb73 Pull complete                                                                                                         14.8s
   ⠿ 65467bb36f5f Pull complete                                                                                                         16.2s
   ⠿ fe6050bae51d Pull complete                                                                                                         17.4s
   ⠿ 519306d43b4a Pull complete                                                                                                         17.9s
 ⠿ erc20-watcher-db Pulled                                                                                                              15.0s
   ⠿ 8921db27df28 Already exists                                                                                                         0.0s
   ⠿ eb286326f602 Pull complete                                                                                                          0.3s
   ⠿ 63139c77dd7e Pull complete                                                                                                          0.5s
   ⠿ 17baeacd3984 Pull complete                                                                                                         13.5s
   ⠿ 5f08b9782916 Pull complete                                                                                                         13.8s
   ⠿ a836be7ad658 Pull complete                                                                                                         14.0s
   ⠿ 1966853affaf Pull complete                                                                                                         14.2s
   ⠿ 4dc6d2c8dede Pull complete                                                                                                         14.4s
[+] Running 8/8
 ⠿ Network laconic-30c27a9be20b005274dfc23fd7e90256_default                 Created                                                      0.1s
 ⠿ Volume "laconic-30c27a9be20b005274dfc23fd7e90256_erc20_watcher_db_data"  Created                                                      0.0s
 ⠿ Container laconic-30c27a9be20b005274dfc23fd7e90256-ipld-eth-db-1         Healthy                                                     33.0s
 ⠿ Container laconic-30c27a9be20b005274dfc23fd7e90256-erc20-watcher-db-1    Healthy                                                     34.8s
 ⠿ Container laconic-30c27a9be20b005274dfc23fd7e90256-migrations-1          Started                                                     32.7s
 ⠿ Container laconic-30c27a9be20b005274dfc23fd7e90256-go-ethereum-1         Started                                                     33.1s
 ⠿ Container laconic-30c27a9be20b005274dfc23fd7e90256-ipld-eth-server-1     Healthy                                                     53.5s
 ⠿ Container laconic-30c27a9be20b005274dfc23fd7e90256-erc20-watcher-1       Started                                                     54.3s

Let's take stock of what just happened, we:

  • cloned a bunch of repos: `laconic-so setup-repositories`

  • built all of their docker images: `laconic-so build-containers`

  • deployed these images as services that know about each other: `laconic-so deploy-system up`

Take a look at all the running docker containers:

docker ps

You should see 6 containers:

CONTAINER ID   IMAGE                              COMMAND                  CREATED         STATUS                     PORTS                                            NAMES
605ccf0e4461   cerc/watcher-erc20:local           "docker-entrypoint.s…"   6 minutes ago   Up 5 minutes (unhealthy)   0.0.0.0:3002->3001/tcp, 0.0.0.0:9002->9001/tcp   laconic-30c27a9be20b005274dfc23fd7e90256-erc20-watcher-1
0a00a3a1bcd6   cerc/ipld-eth-db:local             "/app/startup_script…"   6 minutes ago   Up 5 minutes                                                                laconic-30c27a9be20b005274dfc23fd7e90256-migrations-1
f4aece866e48   cerc/ipld-eth-server:local         "/app/entrypoint.sh"     6 minutes ago   Up 5 minutes (healthy)     127.0.0.1:8081-8082->8081-8082/tcp               laconic-30c27a9be20b005274dfc23fd7e90256-ipld-eth-server-1
ebe0dc8cd2b4   cerc/go-ethereum-foundry:local     "./start-private-net…"   6 minutes ago   Up 5 minutes (healthy)     127.0.0.1:8545-8546->8545-8546/tcp               laconic-30c27a9be20b005274dfc23fd7e90256-go-ethereum-1
72263d100b8c   postgres:14-alpine                 "docker-entrypoint.s…"   6 minutes ago   Up 6 minutes (healthy)     0.0.0.0:15433->5432/tcp                          laconic-30c27a9be20b005274dfc23fd7e90256-erc20-watcher-db-1
d2effc54624c   timescale/timescaledb:2.8.1-pg14   "docker-entrypoint.s…"   6 minutes ago   Up 6 minutes (healthy)     127.0.0.1:8077->5432/tcp                         laconic-30c27a9be20b005274dfc23fd7e90256-ipld-eth-db-1

Finally, via the `watcher-erc20` container, the GraphQL playground is enabled on http://localhost:3002/graphql and you should check that it is there:

Great so now we have the core stack up and running, let's deploy an ERC20 token.

First, we need the `CONTAINER ID` of the ERC20 watcher:

docker ps | grep "watcher-erc20"

Using the `ID` from the example above, we'll export the `CONTAINER_ID` for use throughout the rest of the tutorial:

export CONTAINER_ID=605ccf0e4461

Next, we can deploy an ERC20 token (currency symbol GLD):

docker exec $CONTAINER_ID yarn token:deploy:docker

and your output should look like:

yarn run v1.22.19
$ hardhat --network docker token-deploy
Downloading compiler 0.8.0
Compiled 5 Solidity files successfully
GLD Token deployed to: 0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550
Deployed at block: 9087 0x4dc63b4b2695b644d7d390d70c9de0232399ea4d54b8c75911eee14c13f9ceaf
Done in 157.39s.

Great, now that we've deployed the GLD token, you'll want to export its address for later use:

export TOKEN_ADDRESS=0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550

Get your primary account address with:

docker exec $CONTAINER_ID yarn account:docker

and the following output:

yarn run v1.22.19
$ hardhat --network docker account
0x33AF7AB219be47367dfa5A3739e6B9CA1c40cDC8
Done in 21.63s.

export that address to your shell:

export PRIMARY_ADDRESS=0x33AF7AB219be47367dfa5A3739e6B9CA1c40cDC8

To get the latest block hash at any time, run:

docker exec $CONTAINER_ID yarn block:latest:docker

for an output like:

yarn run v1.22.19
$ hardhat --network docker block-latest
Block Number: 12783
Block Hash: 0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75
Done in 21.44s.

Next we'll configure MetaMask.

MetaMask

Open MetaMask in your browser:

  1. Click "Add Network"

  2. Scroll to the bottow and click "Add Network Manually"

  3. Put in this information:

If you see the error above "This URL is currently used by the Localhost 8545 Network", change `localhost` to `127.0.0.1`:

We will come back to MetaMask later and complete this process; for now, copy your new address

and export it for later:

export RECIPIENT_ADDRESS=0x988a070c97D33a9Dfcc134df5628b77e8B5214ad

GraphQL

Head on over to http://localhost:3002/graphql and paste the following (but with your variables):

query {
  name(
    blockHash: "0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75"
    token: "0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550"
  ) {
    value
    proof {
      data
    }
  }

  symbol(
    blockHash: "0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75"
    token: "0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550"
  ) {
    value
    proof {
      data
    }
  }

  totalSupply(
    blockHash: "0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75"
    token: "0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550"
  ) {
    value
    proof {
      data
    }
  }
}

then click "Run" and you'll see a response like this:

{
  "data": {
    "name": {
      "value": "Gold",
      "proof": {
        "data": "[{\"blockHash\":\"0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75\",\"account\":{\"address\":\"0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550\",\"storage\":{\"cid\":\"bagmacgzavwb52aq6smf6movgcimvuoggp3cifayb2vyidg3ar546pwtb3dea\",\"ipldBlock\":\"0xf843a032575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85ba1a0476f6c6400000000000000000000000000000000000000000000000000000008\"}}}]"
      }
    },
    "symbol": {
      "value": "GLD",
      "proof": {
        "data": "[{\"blockHash\":\"0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75\",\"account\":{\"address\":\"0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550\",\"storage\":{\"cid\":\"bagmacgzanp5bxcn6wqd2yptbbwo5o4rx3mhpji43yd7sfd42suq6hjuhuroq\",\"ipldBlock\":\"0xf843a03a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19ba1a0474c440000000000000000000000000000000000000000000000000000000006\"}}}]"
      }
    },
    "totalSupply": {
      "value": "1000000000000000000000",
      "proof": {
        "data": "{\"blockHash\":\"0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75\",\"account\":{\"address\":\"0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550\",\"storage\":{\"cid\":\"bagmacgzasfla7wzuessejihdtrxqd5lqxc57egukbbricizz2ssrltex4uvq\",\"ipldBlock\":\"0xeca0305787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace8a893635c9adc5dea00000\"}}}"
      }
    }
  }
}

Here's what it'll look like in the browser:

A lot has happened thus far, so let's review; we've:

  • downloaded the core repos, built their docker images, and launched a local network (all using stack orchestrator)

  • deployed an ERC20 token, added it to our MetaMask account

  • used the GraphQL playground to query the ERC20 watcher

  • exported a handful of shell variables which are about to come in handy

Next we'll use the playground to query account balances:

query {
  fromBalanceOf: balanceOf(
      # latest block hash
      blockHash: "0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75"
      token: "0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550"
      # primary account having all the balance initially
      # created by stack orchestrator
      owner: "0x33AF7AB219be47367dfa5A3739e6B9CA1c40cDC8"
    ) {
    value
    proof {
      data
    }
  }
  toBalanceOf: balanceOf(
      blockHash: "0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75"
      token: "0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550"
      # address copied from MetaMask, has no balance initially
      owner: "0x988a070c97D33a9Dfcc134df5628b77e8B5214ad"
    ) {
    value
    proof {
      data
    }
  }
}

the primary address should have value 1000000000000000000000 and the recipient address should have 0:

{
  "data": {
    "fromBalanceOf": {
      "value": "1000000000000000000000",
      "proof": {
        "data": "{\"blockHash\":\"0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75\",\"account\":{\"address\":\"0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550\",\"storage\":{\"cid\":\"bagmacgzarsopkngjoijjyktfhgckq7te4dsk25gfyj653uxu4kcwqoyuykiq\",\"ipldBlock\":\"0xeca031bcbb6b5a44c97488b8904a85b2396b1cf337ff3ee4efb0ea1ef5104325dbfc8a893635c9adc5dea00000\"}}}"
      }
    },
    "toBalanceOf": {
      "value": "0",
      "proof": {
        "data": "{\"blockHash\":\"0xb7b4b65dd5fe3800a6c38cb8a26249bbb82041d7e0b347a853b73efc7a473b75\",\"account\":{\"address\":\"0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550\",\"storage\":{\"cid\":\"\",\"ipldBlock\":\"0x\"}}}"
      }
    }
  }
}

Note also that the recipient address also does not yet have a `cid` or `ipldBlock`, which makes sense; this is a random account we just created and hasn't received any transactions. The network does not know about it.

Let's send it some GLD!

docker exec $CONTAINER_ID yarn token:transfer:docker --token $TOKEN_ADDRESS --to $RECIPIENT_ADDRESS --amount 100000000 

You'll see a familiar output:

yarn run v1.22.19
$ hardhat --network docker token-transfer --token 0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550 --to 0x988a070c97D33a9Dfcc134df5628b77e8B5214ad --amount 100000000
Nothing to compile
Transfer Event at block: 13371 0x412dbc25599773bfe929c67882e4a001b9d1b3b8e1c60ad4a495d5306608c77a
from: 0x33AF7AB219be47367dfa5A3739e6B9CA1c40cDC8
to: 0x988a070c97D33a9Dfcc134df5628b77e8B5214ad
value: 100000000
Done in 26.12s.

Now get the latest block hash:

docker exec $CONTAINER_ID yarn block:latest:docker 

and go back to the GraphQL playground. If you've changed nothing since the last query, update only the latest block hash and run the query again, you'll see the updated account balances:

{
  "data": {
    "fromBalanceOf": {
      "value": "999999999999900000000",
      "proof": {
        "data": "{\"blockHash\":\"0xec173c3aac86a533710569340a4ffb3f3e2d46080e35b034e93ec0049cc12174\",\"account\":{\"address\":\"0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550\",\"storage\":{\"cid\":\"bagmacgzahg5shtf2rr7pompx7yx6r22zu4lea7ftlnbqkqcabvndsrvoljhq\",\"ipldBlock\":\"0xeca031bcbb6b5a44c97488b8904a85b2396b1cf337ff3ee4efb0ea1ef5104325dbfc8a893635c9adc5d8aa1f00\"}}}"
      }
    },
    "toBalanceOf": {
      "value": "100000000",
      "proof": {
        "data": "{\"blockHash\":\"0xec173c3aac86a533710569340a4ffb3f3e2d46080e35b034e93ec0049cc12174\",\"account\":{\"address\":\"0x0Dcb65938A483547835e2ebB4FC6cBf7AEe77550\",\"storage\":{\"cid\":\"bagmacgzas6xotntgq4u3v4eui6pmtbyttgikzmu7mppknam2wrekhoynupjq\",\"ipldBlock\":\"0xe7a03305adb1a8efab310b21e03d5a9f08d8cf98815372c2c4d8068e1359b8f996bc858405f5e100\"}}}"
      }
    }
  }
}

Great, you've now used a watcher to see query token balances.

Let's send some tokens from the MetaMask recipient account back to the primary account.

Recall that when adding the network to MetaMask, we used the currency symbol "GLD". However, this does not mean that MetaMask can auto-detect the token, therefore, we will have to manually import it:

Copy the `TOKEN_ADDRESS` and paste it in the popup. The two other fields should auto-complete:

Click "Import Token"

and now you'll see your balance. Ignore the GLD token from earlier.

Finally, send some tokens back to the primary address using MetaMask:

Make the gas price `0`:

Grab the latest block hash (again) and fire off the GraphQL query for account balances to see the change.

Voila! You've successfully stood up the core Laconic stack, deployed an ERC20 token, and queried account balances.

Cleanup

Tear down your docker containers with:

./laconic-so deploy-system --stack erc20 down

Next steps

Try out the ERC721 demo

SHARE THIS ARTICLE