Skip to content

Instantly share code, notes, and snippets.

@nguyer
Last active March 23, 2023 13:47
Show Gist options
  • Save nguyer/a5ea1cc1181de58ffb0d693d5ca50fb9 to your computer and use it in GitHub Desktop.
Save nguyer/a5ea1cc1181de58ffb0d693d5ca50fb9 to your computer and use it in GitHub Desktop.
Build Ethereum Web3 Apps Quickly Using the Latest Tools

Workshop Guide - Building Apps on FireFly

Welcome! We're glad you're here! This is the guide that we will be going through during the workshop.

Before the workshop

IMPORTANT: Please make sure you have installed the software listed in this section before the workshop so that we can hit the ground running when the workshop starts.

Install Docker

The FireFly CLI uses docker and docker-compose to create a local development environment on your machine for offline development. Please make sure you have the latest (or very recent) version of Docker and docker-compose on your machine.

For details on how to get started with Docker, please see: https://www.docker.com/get-started/

Linux Users

NOTE: For Linux users, it is recommended that you add your user to the docker group so that you do not have to run ff or docker as root or with sudo. For more information about Docker permissions on Linux, please see Docker’s documentation on the topic.

Windows Users

NOTE: For Windows users, we recommend that you use Windows Subsystem for Linux 2 (WSL2). FireFly binaries provided for Linux will work in this environment.

To verify installation, you can run:

docker --version
Docker version 20.10.14, build a224086
docker-compose --version
Docker Compose version v2.4.1

Install Open SSL

The FireFly CLI also uses Open SSL for certificate generation. You probably already have a version of it installed. If not, please use your system's package manager to install Open SSL

To verify installation you can run:

openssl version
LibreSSL 2.8.3

Install Node.js

We will be using Hardhat to compile and deploy a custom smart contract during the workshop, so you'll need to make sure you have a recent version of Node.js installed.

To install Node.js for your platform please visit: https://nodejs.org/en/

To verify installation you can run:

node --version
v16.14.0
npm --version
8.3.1

Getting started with FireFly

For this portion of this workshop we will be going through the first section of the Getting Started Guide in the FireFly Docs to Install the FireFly CLI.

We will then follow a similar approach to that which is outlined guide to Start your environment. However, I am going to use a slightly different command to initialize my FireFly Stack:

ff init workshop 3 -t erc20_erc721 --prompt-names

I'm going to name each of my members a color, Red, Green, and Blue so that I can keep track of them a bit better, and I'm just going to name their nodes Red_Node, Green_Node, and Blue_Node.

After this, you can start up your FireFly stack by running:

ff start workshop

Deploy an ERC-20 contract with Hardhat

Next, we're going to compile and deploy an ERC-20 contract that we will use with FireFly. For this workshop, we're just going to deploy an ERC-20 contract in the FireFly token connector Git repo. Start by cloning that repo:

git clone git@github.com:hyperledger/firefly-tokens-erc20-erc721.git

This repo contains a few different things, but we're only concerned with the smart contracts that are in the contracts directory for now. Change to that directory:

cd firefly-tokens-erc20-erc721/solidity

Now let's install dependencies:

npm install

Lastly, compile and deploy the contract with Hardhat:

npx hardhat run scripts/deploy.ts

Generating typings for: 18 artifacts in dir: typechain for target: ethers-v5
Successfully generated 33 typings!
Compiled 18 Solidity files successfully
ERC-20 contract deployed to: 0xA8e0C061C56430cf89243CEB19f8dbD68D1D8Fd4
ERC-721 contract deployed to: 0x44282af89ACc6FE5AcA828fF4D1F87CDC377f0Ee

Note the contract addresses printed from the output of the command above. We will use the address for the ERC-20 contract in the next step.

Use the FireFly Sandbox

Now that we have an ERC-20 contract on chain, let's play around in the FireFly Sandbox! The Sandbox is entirely web based, and has a very intuitive interface. You can read up about the types of things you can do in the Getting Started guide to Use the Sandbox. For this section of the workshop, you can simply follow along in your browser.

Create a contract interface

In this step, we're going to tell FireFly about our custom ERC-20 contract. To do this, we look into the ./solidity/artifacts/build-info directory and open the .json file there. We need to copy the ABI from our contract, so search the file for the string "contracts/ERC20WithData.sol". The ABI should be right after the second occurrence of this string.

For convenience, I've also included a copy of it here, which you can copy/paste without hunting through a large JSON file:

[
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "symbol",
        "type": "string"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Approval",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "previousOwner",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "OwnershipTransferred",
    "type": "event"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "indexed": true,
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "value",
        "type": "uint256"
      }
    ],
    "name": "Transfer",
    "type": "event"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "owner",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      }
    ],
    "name": "allowance",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "approve",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "approveWithData",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "account",
        "type": "address"
      }
    ],
    "name": "balanceOf",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "burnWithData",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "decimals",
    "outputs": [
      {
        "internalType": "uint8",
        "name": "",
        "type": "uint8"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "subtractedValue",
        "type": "uint256"
      }
    ],
    "name": "decreaseAllowance",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "spender",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "addedValue",
        "type": "uint256"
      }
    ],
    "name": "increaseAllowance",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "mintWithData",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "name",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "owner",
    "outputs": [
      {
        "internalType": "address",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "renounceOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "symbol",
    "outputs": [
      {
        "internalType": "string",
        "name": "",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "totalSupply",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "transfer",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "transferFrom",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "newOwner",
        "type": "address"
      }
    ],
    "name": "transferOwnership",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "from",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "to",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "transferWithData",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

Mint some tokens

Now let's mint some tokens. Open the Swagger UI for the mintWithData endpoint on our custom contract:

http://127.0.0.1:5000/api/v1/namespaces/default/apis/erc20-with-data/api#/default/invoke_mintWithData

Click the Try It Out button, and set the request body to the following:

{
  "input": {
    "amount": 1000000000000000000,
    "data": "0x00",
    "to": "0xa403f0ab1824b1a1577f94eb235b9a250fd21dea"
  }
}
@guilherme-funchal
Copy link

Hello, I'm trying to use to test, but it doesn't work.

The error after command "npx hardhat run scripts/deploy.ts"

Generating typings for: 23 artifacts in dir: typechain for target: ethers-v5
Successfully generated 43 typings!
Compiled 23 Solidity files successfully
/home/45637962049/git/firefly-tokens-erc20-erc721/samples/solidity/node_modules/ts-node/src/index.ts:820
return new TSError(diagnosticText, diagnosticCodes);
^
TSError: ⨯ Unable to compile TypeScript:
scripts/deploy.ts:23:31 - error TS2554: Expected 3-4 arguments, but got 2.

23 const erc721 = await ERC721.deploy('FFNFT', 'FFNFT');
~~~~~~~~~~~~~~~~~~~~~~~~

typechain/factories/ERC721WithData__factory.ts:596:5
596 baseTokenURI: string,
~~~~~~~~~~~~~~~~~~~~
An argument for 'baseTokenURI' was not provided.

at createTSError (/home/45637962049/git/firefly-tokens-erc20-erc721/samples/solidity/node_modules/ts-node/src/index.ts:820:12)
at reportTSError (/home/45637962049/git/firefly-tokens-erc20-erc721/samples/solidity/node_modules/ts-node/src/index.ts:824:19)
at getOutput (/home/45637962049/git/firefly-tokens-erc20-erc721/samples/solidity/node_modules/ts-node/src/index.ts:1014:36)
at Object.compile (/home/45637962049/git/firefly-tokens-erc20-erc721/samples/solidity/node_modules/ts-node/src/index.ts:1322:43)
at Module.m._compile (/home/45637962049/git/firefly-tokens-erc20-erc721/samples/solidity/node_modules/ts-node/src/index.ts:1454:30)
at Module._extensions..js (node:internal/modules/cjs/loader:1155:10)
at Object.require.extensions.<computed> [as .ts] (/home/45637962049/git/firefly-tokens-erc20-erc721/samples/solidity/node_modules/ts-node/src/index.ts:1458:12)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12) {

diagnosticCodes: [ 2554 ]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment