#BUIDLGUIDL — 0x1: guidlcoin

Mintable Token dApp powered by Clevis & Dapparatus

Where’s the best place to start in Ethereum development? Creating our very own sh!tcoin… 😅😅😅

Part 1
Part 2

Clevis

If you aren’t familiar with Clevis & Dapparatus, be sure to run through #BUIDLGUIDL — 0x0: Clevis & Dapparatus. There we explore basic concepts of the toolset.

We’ll point our container at an empty directory in our home folder (~/guidlcoin/). It will take a while to download and compile:

docker run -ti --rm --name clevis -p 3000:3000 -p 8545:8545 \
-v ~/guidlcoin:/dapp austingriffith/clevis

Remember: It doesn’t matter *where* you run this docker command. Your Dapp code will be available wherever you point the “-v ~/guidlcoin” part.

You can edit your code with your favorite editor with a command like:

atom ~/guidlcoin

Smart Contract

Let’s create our first smart contract:

clevis create GuidlCoin

When you initialized Clevis you were prompted for a contracts folder (defaults to “contracts/”). Your new contract will be created in this folder. If you look at what files that command created, you’ll see:

GuidlCoin.sol   arguments.js    dependencies.js

GuidlCoin.sol is your smart contract. It is just an empty shell with a constructor for now, but we’ll fix that soon.

arguments.js is an array of arguments that will be passed into your contract’s constructor when it is deployed.

dependencies.js is a list of other smart contracts you will need to import.

Note: Docker keeps the file system from your computer linked into the container. So you can access the files natively on your machine using your favorite code editor. Try it out by bringing up a terminal, navigating to ~/guidlcoin, and running “open .” or your favorite editor like “atom .” .

From a new terminal (not a Clevis prompt), try:

cd ~/guidlcoin
ls

This should list all the files in your project and you are free to edit them from here. (As stated above, they are linked into your container automatically with Docker.)

Before we make a coin, let’s just play around with a few contract mechanics. Let’s add in a name right above the constructor in the GuidlCoin.sol:

string public name = "GuidlCoin";

Note: If you ever lose your clevis prompt, Clevis installs two scripts in your project directory to run or attach to a container. Open up a normal terminal and navigate to your project directory: ~/guidlcoin Then, do a ./run.sh or ./attach.sh (attach.sh is for when the Docker container is still up)

Rad, now let’s compile our contract. From the Clevis prompt, run:

clevis compile GuidlCoin

Buried in the output you will see (if you don’t have any errors) the generated bytecode:

60c0604052600960808190527f477569646c436f696e000000000000000000000000000000000000000000000060a090815261003e9160009190610051565b5034801561004b57600080fd5b506100ec565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061009257805160ff19168380011785556100bf565b828001600101855582156100bf579182015b828111156100bf5782518255916020019190600101906100a4565b506100cb9291506100cf565b5090565b6100e991905b808211156100cb57600081556001016100d5565b90565b610189806100fb6000396000f3006080604052600436106100405763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde038114610045575b600080fd5b34801561005157600080fd5b5061005a6100cf565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009457818101518382015260200161007c565b50505050905090810190601f1680156100c15780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6000805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156101555780601f1061012a57610100808354040283529160200191610155565b820191906000526020600020905b81548152906001019060200180831161013857829003601f168201915b5050505050815600a165627a7a72305820b9f089614657e291ad8437d5746bf6a257df40b8c363767c633e531f5f0e5c0a0029

Some new files have appeared in the ~/guidlcoin/GuidlCoin directory too:

GuidlCoin.abi GuidlCoin.compiled GuidlCoin.bytecode 

GuidlCoin.abi is the json that tells us how to interface with the contract

GuidlCoin.compiled is the code you submit to Etherscan to verify a contract

GuidlCoin.bytecode is the byte code that will be deployed

Now we can deploy that bytecode to our local blockchain by running:

clevis deploy GuidlCoin 0

(This will cost account 0 a little testnet ETH)

Now we can interact with our contract from the command line:

clevis contract name GuidlCoin
GuidlCoin

Note: you may have a lot more output from clevis. You can suppress debugging information by editing the ~/guidlcoin/clevis.json. This file is used for general configuration and can be extended as you see fit. Change DEBUG to false for less output or true if you are having problems.

We can get information from the contract. What about setting information? That seems like the next logical progression. Let’s add in the rest of the coin information and also a url field that we can update if we need to.

string public name = "GuidlCoin";
string public symbol = "GC";
uint8 public decimals = 0;
string public url = "https://buidlguidl.io";

That means we need a setUrl function too:

function setUrl(string _url) public {
url=_url;
}

Now let’s compile and deploy this new contract:

clevis compile GuidlCoin
clevis deploy GuidlCoin 0

Watch for errors in your compile! There should be a line number supplied with the error. Tweet/Telegram me a screenshot or questions if you’re lost or having any problems: @austingriffith

Now, let’s try to get and set the url to prove everything works:

clevis contract url GuidlCoin
https://buidlguidl.io
clevis contract setUrl GuidlCoin 0 "https://buidlguidl.com"
clevis contract url GuidlCoin
https://buidlguidl.com

It’s working! Notice that in the setUrl command we had to add the 0 just like in the deploy. This means that account 0 did the setUrl.

Let’s get a little of this integrated into the frontend so it’s easier to visualize and mess around with. First, we need to tell Clevis to publish the contracts:

clevis test publish

This adds some files to the Create React App directory. This is usually src/, but it could be different depending on how you initialized Clevis. Let’s assume you are using the defaults and you should see the following files in the src/contracts/ folder:

GuidlCoin.abi.js         GuidlCoin.address.js     GuidlCoin.blocknumber.js GuidlCoin.bytecode.js    contracts.js

These are all the files your frontend will need to know about your contracts.

Next, we’ll switch over and edit our src/App.js file and add in a ContractLoader component to load our contracts into the frontend:

connectedDisplay.push(
<ContractLoader
key="ContractLoader"
config={{DEBUG:true}}
web3={web3}
require={path => {return require(`${__dirname}/${path}`)}}
onReady={(contracts,customLoader)=>{
console.log("contracts loaded",contracts)
this.setState({contracts:contracts})
}}
/>
)

Now over in the frontend, let’s look at the console to see it loaded the contract and what interfaces with have with the contract:

Warning! You may see a MetaMask RPC Error. That’s okay, we can ignore it for now. It’s because we are on a private network where ENS doesn’t work:

Just ignore this error :) It’s no big deal right now. Sorry!

Let’s say we want to display the address, name, and url in the frontend. This information isn’t going to change much so we can just check it when the contracts first load:

We’ll change the callback into an async so we can use await, and add the following lines for the ContractLoader onReady:

onReady={async (contracts,customLoader)=>{
console.log("contracts loaded",contracts)
let name = await contracts.GuidlCoin.name().call();
this.setState({contracts:contracts,name:name})
}}

Since we are updating the this.state.name variable, we should initialize it in the React constructor:

Now we are ready to create a React component in the render to show the name:

<div style={{padding:30}}>
{this.state.name}
</div>

The frontend should reload and show us our name at the top:

Let’s verify this is working and also test the full dev loop. Change the name in the GuidlCoin.sol to something else:

Then, run a full test and keep an eye on your frontend:

clevis test full

Note: How badass is that?! You run one command and it compiles, deploys, tests, and publishes your Dapp. Now you can tweak anything from your contracts to your frontend and run one command to see the changes.


Now it’s time to bring in the big guns and use the OpenZeppelin contracts. These guys have been auditing smart contracts for years and they provide a wide variety of battle-hardened, open source contracts for the community. We are going to borrow their Ownable.sol to make sure that only the owner of our contract can call the setUrl function.

First, let’s add the contract into our GuidlCoin/dependencies.js file:

const fs = require('fs');
module.exports = {
'openzeppelin-solidity/contracts/ownership/Ownable.sol': fs.readFileSync('openzeppelin-solidity/contracts/ownership/Ownable.sol', 'utf8')
}

Now we will add some inheritance to our contract. We need to import the Ownable.sol and add the “is Ownable” clause.

import "openzeppelin-solidity/contracts/ownership/Ownable.sol";

Then, we add the modifier called onlyOwner to the setUrl function.

That’s a lot to unpack, but here’s the contract so far:

pragma solidity ^0.4.24;import "openzeppelin-solidity/contracts/ownership/Ownable.sol";contract GuidlCoin is Ownable {  string public name = "BuidlGuidlCoin";
string public symbol = "GC";
uint8 public decimals = 0;
string public url = "https://buidlguidl.io";
constructor() public { } function setUrl(string _url) public onlyOwner {
url=_url;
}
}

Let’s throw it out on our local chain and poke at it to understand it. Save everything and let’s get it compiled and pushed:

clevis test full

Note: If anything fails here, try running just the “clevis compile GuidlCoin” to see if maybe you have errors in your contract.

Once that compiles, deploys, and publishes correctly, let’s try setting the url as an account that is not the owner:

clevis contract setUrl GuidlCoin 1 https://austingriffith.comReturned error: VM Exception while processing transaction: revert

(Good. It should fail. You can run “clevis contract url GuidlCoin” to prove that it is still the old value and not “https://austingriffith.com”)

Then, let’s try setting the url as the owner:

clevis contract setUrl GuidlCoin 0 https://buidlguidl.com
clevis contract url GuidlCoi
n
https://buidlguidl.com

Radical. Now, only the owner of the contract can change the url and we can trust that all the logic we borrowed from Zeppelin is heavily vetted. This is such a neat ecosystem to be part of.


Tests

Any good developer knows they should probably have a few tests along the way to make sure their creation still performs to spec. Luckily, Clevis works exactly the same in javascript as it does from the command line. Let’s build a test that will be part of our deployment that makes sure only the owner can set the url of the contract.

Open up the tests/clevis.js file and let’s write a new mocha test for calling setUrl from a specific account index:

setUrl:(accountindex,url)=>{
describe('#setUrl() ', function() {
it('should setUrl to '+(url+"").yellow+' as account '+accountindex, async function() {
this.timeout(120000)
const result = await clevis("contract","setUrl","GuidlCoin",accountindex,url)
printTxResult(result)
const currentUrl = await clevis("contract","url","GuidlCoin")
assert(currentUrl==url,"setUrl failed!".red)
});
});
},

This test runs a setUrl and then immediately gets the url to make sure that it is correct. Just like we did when we were running it from the command line.

Note: doing a setUrl from a Clevis test file is exactly the same format as running a setUrl from the Clevis CLI. This standard interface is by design. You can poke at your contracts from the CLI and then run the exact same things as mocha tests to orchestrate and verify that the code is working correctly.

We also want to create a new file that will run our tests at tests/setUrl.js:

const clevis = require("./clevis.js")
clevis.setUrl(0,"http://test.com")

With that file created we can run it with this command:

clevis test setUrl 

And we should see a pretty mocha output that it worked:

There is a chance that the url is already equal to what we are setting it to, so we should do two sets just to be sure. Also, it’s probably more important to test that a third party can’t set the url. Let’s build that test too:

failToSetUrl:(accountindex,url)=>{
describe('#failToSetUrl() ', function() {
it('should fail to setUrl to '+(url+"").yellow+' as account '+accountindex, async function() {
this.timeout(120000)
let errorMessage = ""
try{
const result = await clevis("contract","setUrl","GuidlCoin",accountindex,url)
printTxResult(result)
}catch(e){
errorMessage = e.toString()
}
console.log(tab,"Expect error message: ",errorMessage.green)
assert(errorMessage.indexOf("revert")>=0,"Did not get an error!?")
});
});
},

This test runs a setUrl on the contract and makes sure the contract throws a “revert” error message back at us. Let’s set up all the tests we need in the setUrl.js file:

const clevis = require("./clevis.js")clevis.setUrl(0,"http://test.com")
clevis.setUrl(0,"http://buidlguidl.io")
clevis.failToSetUrl(1,"https://austingriffith.com")

Then, let’s run the tests:

clevis test setUrl

Finally, let’s integrate this setUrl test into our full test suite so it runs with every ‘clevis test full’. To do this, add a new entry to the tests/clevis.js file down in the “fast:()=>{“ section that runs the setUrl test:

describe(bigHeader('SETURL'), function() {
it('should setUrl and fail to setUrl from different accounts', async function() {
this.timeout(6000000)
const result = await clevis("test","setUrl")
assert(result==0,"setUrl ERRORS")
});
});

Now, when we do a full test suite, it will compile, deploy, test, and publish:

clevis test full

Note: You should see a new “## SETURL ##” section. If this contract functionality ever fails, your tests will throw right here and alert you.


Frontend

Now it’s time to really hit our stride on the frontend of the Dapp. We are already bringing in our contracts using the ContractLoader. Now let’s build out some UI that will display once the contracts are loaded. To do this, we want to detect if the contracts state variable is set and display some new components. Let’s edit the src/App.js to include:

if(contracts){
contractsDisplay.push(
<div key="UI" style={{padding:30}}>
<div>
<Address
{...this.state}
address={contracts.GuidlCoin._address}
/>
</div>
</div>
)
}
Make sure {contractsDisplay} is in your Render function somewhere too.

This will use the Dapparatus Address component to display the address of the deployed GuidlCoin contract once it is loaded into the frontend:

Now if we redeploy, we should see the app reload and that address update to the new contract automatically:

clevis test full

What if we wanted to display the owner of the GuidlCoin contract right under the address of the contract. Let’s think through the steps in reverse starting with the display. We know we’ll want to have a state variable that holds the owner and we’ll want to plug that into the Address component:

<div>
<Address
{...this.state}
address={this.state.owner}
/>
</div>

This will fail in react because we also need to load that owner into the React state. It really shouldn’t change much right? Let’s load it the same way we loaded in the name earlier (in the onReady of the ContractLoader):

let owner = await contracts.GuidlCoin.owner().call();
this.setState({contracts:contracts,name:name,owner:owner})

Let’s see how it looks:

Sweet. This owner is our command line account we used to deploy the contract. What if we wanted to change the owner to our MetaMask account so we could make administrative actions from our browser? Let’s poke around from the command line first… We can explain the contract interface of the GuidlCoin contract to understand what functions we have available to call:

clevis explain GuidlCoin

This is the one we want:

(function) transferOwnership  (nonpayable)
inputs: address _newOwner

It takes in 1 argument and that’s the address of the new owner. Let’s try running it as account 0 with our MetaMask account as the _newOwner argument:

clevis contract transferOwnership GuidlCoin 0 0x2a906694d15df3...

Since the owner is only loaded once when the contracts load, we won’t see the change in the frontend unless we reload our app:

We successfully changed the ownership to our MetaMask account!

We want to do this every time we deploy our contracts and after we have run all our tests. Our mocha tests aren’t just for testing but also for orchestration. To reiterate, if you need a set of actions to take place every time you deploy your contracts, you can build tests to fulfill these needs.

Let’s create a new transferOwnership test in tests/clevis.js:

It should set the owner and then check to make sure the owner is set correctly.
transferOwnership:(accountindex,newOwner)=>{
describe('#transferOwnership() ', function() {
it('should transfer ownership to '+(newOwner+"").cyan+' as account '+accountindex, async function() {
this.timeout(120000)
const result = await clevis("contract","transferOwnership","GuidlCoin",accountindex,newOwner)
printTxResult(result)
const currentOwner = await clevis("contract","owner","GuidlCoin")
console.log(tab,"currentOwner of GuidlCoin is now:",currentOwner.green)
assert(currentOwner.toLowerCase()==newOwner.toLowerCase(),"transferOwnership failed!".red)
});
});
},

Then, we’ll call it in our tests/setUrl.js test so it happens every time:

Run a full compile, deploy, test, and publish to make sure the MetaMask user ends up being the final owner of the contract:

clevis test full

We should see the ownership transfer happen in the tests:

Now that our frontend MetaMask user owns the contract, it can also run the setUrl function. Let’s build that into the UI! First, we’ll need to introduce a new Dapparatus component called Transactions:

connectedDisplay.push(
<Transactions
key="Transactions"
account={account}
gwei={gwei}
web3={web3}
block={block}
avgBlockTime={avgBlockTime}
etherscan={etherscan}
onReady={(state)=>{
console.log("Transactions component is ready:",state)
this.setState(state)
}}
onReceipt={(transaction,receipt)=>{
console.log("Transaction Receipt",transaction,receipt)
}}
/>
)

This component exposes a new function called tx into our state and also renders a nice little display of blocks and transactions in the bottom right:

If we take a peek at the javascript console we can see that new tx function is added to our state once the Transaction component is loaded:

This tx function is a wrapper that we can pass all our contract calls to. We’ll get to that in a second…

On to the setUrl UI! First, we want to display the current url:

The url could change at any time. This means we are going to want to start loading it into the state on an interval so our frontend displays changes live.

async poll(){
if(this.state&&this.state.contracts){
let url = await this.state.contracts.GuidlCoin.url().call()
if(url!=this.state){
this.setState({url:url})
}
}
}

And we’ll fire off the interval once the contracts are loaded:

setInterval(this.poll.bind(this),10000);this.poll()

Let’s throw in an input and a Dapparatus Button.

<input
style={{verticalAlign:"middle",width:300,margin:6,maxHeight:20,padding:5,border:'2px solid #ccc',borderRadius:5}}
type="text" name="setUrl" value={this.state.setUrl} onChange={this.handleInput.bind(this)}
/>
<Button size="2" onClick={async ()=>{
tx(
contracts.GuidlCoin.setUrl(this.state.setUrl),
(receipt)=>{
console.log("url set:",receipt)
}
)
}}>
setUrl
</Button>

Check out the tx function and how it wraps our contract call. This actually makes the call to the blockchain, controls gas prices, tracks the transactions in the bottom right UI, and returns the receipt to the callback when the transaction completes.

Make sure you can handle that input too. (Learn more about controlled forms in React)

(There is a good chance this code is already in your app, check before adding it and causing errors with duplicate functions.)

handleInput(e){
let update = {}
update[e.target.name] = e.target.value
this.setState(update)
}

This UI looks a little gross, let’s use the power of React states and create an “edit” button that switches the url over to an input and displays a “setUrl” button the sends it to the chain:

let urlView = (
<div>
url: {this.state.url} <Button size="2" onClick={async ()=>{
this.setState({settingUrl:true})
}}>
edit
</Button>
</div>
)
if(this.state.settingUrl){
urlView = (
<div>
url:
<input
style={{verticalAlign:"middle",width:300,margin:6,maxHeight:20,padding:5,border:'2px solid #ccc',borderRadius:5}}
type="text" name="setUrl" value={this.state.setUrl} onChange={this.handleInput.bind(this)}
/>
<Button size="2" color="green" onClick={async ()=>{
tx(
contracts.GuidlCoin.setUrl(this.state.setUrl),
(receipt)=>{
this.setState({settingUrl:false},()=>{
this.poll()
})
}
)
}}>
setUrl
</Button>
</div>
)
}

The UI could still use some work because there is a period of time where it is waiting for the update to display, but it will work for now.

Also, if you switch MetaMask to a different account and try to edit the url it will fail because you aren’t the owner of the contract (that’s good!):

Fails and burns all the gas if you actually post it on-chain.

We are finally ready to make a token!

The groundwork has been laid, we understand our framework, and we are ready to get weird.

Creating a token is actually really easy from here. We just need to bring in some contracts from OpenZeppelin. Let’s start by creating our token as a plain old ERC20.

That simply means importing and inheriting the right contracts. Along with Ownable, let’s inherit the ERC20.sol too:

import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";contract GuidlCoin is Ownable, ERC20 {
'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/ERC20.sol', 'utf8'),
'openzeppelin-solidity/contracts/math/SafeMath.sol': fs.readFileSync('openzeppelin-solidity/contracts/math/SafeMath.sol', 'utf8'),
'openzeppelin-solidity/contracts/token/ERC20/IERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/IERC20.sol', 'utf8'),

Now let’s compile it and see how we did:

clevis compile GuidlCoin

Note: it’s good to run ‘clevis compile GuidlCoin’ instead of ‘clevis test full’ just because you will get more output so you can understand if you have any errors. Once the contract is solid then go back to running the full test all at once.

If everything compiles correctly we are ready to ship it. Here is a neat Clevis trick too… If we have already compiled the contract we don’t need to do a full test, we can do a fast test (skips compile, straight to deploy):

clevis test fast

Awesome. We have deployed a token. This token can be transferred, approved, and all that cool token stuff, but we have a problem. There are no tokens. The interface is there, but we have no way to create the tokens. Let’s try calling the internal _mint function in our constructor of GuidlCoin.sol:

clevis test full

Now account 0 (the contract deployer) should have 10 tokens:

Let’s send 5 of them over to our MetaMask account:

clevis contract transfer GuidlCoin 0 0x2a906694d15df38f59e76ed3a5735f8aabcce9cb 5

Then let’s check the balance of that account:

clevis contract balanceOf GuidlCoin 0x2a906694d15df38f59e76ed3a5735f8aabcce9cb
5

Awesome so the transfer worked and the MetaMask account holds 5 tokens. This is cool and we can mint tokens at the genesis of the contract, but it would be nice if we could mint new tokens as the owner whenever we need them.

There’s an OpenZeppelin contract for that! Check out the ERC20Mintable.sol.

Recently, OZ shipped a new Roles system. Instead of a simple Owner, there are new roles for different actions. Along with a contract Owner, we will now have a new level of permission for Minters too.

Let’s inherit ERC20Mintable.sol along with Ownable.sol:

Here is a copy and paste of your dependencies.js:

const fs = require('fs');
module.exports = {
'openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/ERC20Mintable.sol', 'utf8'),
'openzeppelin-solidity/contracts/math/SafeMath.sol': fs.readFileSync('openzeppelin-solidity/contracts/math/SafeMath.sol', 'utf8'),
'openzeppelin-solidity/contracts/token/ERC20/ERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/ERC20.sol', 'utf8'),
'openzeppelin-solidity/contracts/token/ERC20/IERC20.sol': fs.readFileSync('openzeppelin-solidity/contracts/token/ERC20/IERC20.sol', 'utf8'),
'openzeppelin-solidity/contracts/access/roles/MinterRole.sol': fs.readFileSync('openzeppelin-solidity/contracts/access/roles/MinterRole.sol', 'utf8'),
'openzeppelin-solidity/contracts/access/Roles.sol': fs.readFileSync('openzeppelin-solidity/contracts/access/Roles.sol', 'utf8'),
'openzeppelin-solidity/contracts/ownership/Ownable.sol': fs.readFileSync('openzeppelin-solidity/contracts/ownership/Ownable.sol', 'utf8'),
}
clevis compile GuidlCoin

Instead of transferring the ownership to from one account to another, we’ll just add a Minter to the whitelist.

We will need to change some of our tests too. Expect these kind of fast changes in the Ethereum development space; developers are always improving the ecosystem out from underneath us… it’s awesome. 😅

Let’s add a new addMinter test:

addMinter:(accountindex,newMinter)=>{
describe('#addMinter() ', function() {
it('should add account '+(newMinter+"").cyan+' as a '+'Minter'.blue, async function() {
this.timeout(120000)
const result = await clevis("contract","addMinter","GuidlCoin",accountindex,newMinter)
printTxResult(result)
const isMinter = await clevis("contract","isMinter","GuidlCoin",newMinter)
console.log(tab,"Can "+newMinter.green+" mint tokens?",isMinter)
assert(isMinter,"addMinter failed!".red)
});
});
},

Then, we need to change tests/setUrl.js to call addMinter instead:

Now all of our tests should pass again:

clevis test fast

Now, let’s figure out how to mint some of these tokens from the frontend. Instead of doing a “clevis explain GuidlCoin” let’s look that the contract from the javascript console after the ContractLoader brings it in.

Looks like there is a mint function that takes in an address (to) and a uint256 (amount).

This will only be for the Minter role of the contract, so let’s check to see if the current account is a Minter in the React state and then display a UI that lets the Minter mint coins. First, we need to check if the current account is a Minter just like we load in balance:

Here is a copy and paste of what your update poll() function should be:

async poll(){
if(this.state&&this.state.contracts){
let url = await this.state.contracts.GuidlCoin.url().call()
let balance = await this.state.contracts.GuidlCoin.balanceOf(this.state.account).call()
let isMinter = await this.state.contracts.GuidlCoin.isMinter(this.state.account).call()
if(url!=this.state.url||balance!=this.state.balance||isMinter!=this.state.isMinter){
if(this.state.settingUrl){
this.setState({url:url,balance:balance,isMinter:isMinter})
}else{
this.setState({url:url,setUrl:url,balance:balance,isMinter:isMinter})
}
}
}
}

Then we can build a UI if the current account is a Minter:

let minterView = ""
if(this.state.isMinter){
minterView = (
<div>
Mint <input
style={{verticalAlign:"middle",width:50,margin:6,maxHeight:20,padding:5,border:'2px solid #ccc',borderRadius:5}}
type="text" name="mintAmount" value={this.state.mintAmount} onChange={this.handleInput.bind(this)}
/> guidlcoins to <input
style={{verticalAlign:"middle",width:300,margin:6,maxHeight:20,padding:5,border:'2px solid #ccc',borderRadius:5}}
type="text" name="mintTo" value={this.state.mintTo} onChange={this.handleInput.bind(this)}
/>
<Button size="2" color="green" onClick={async ()=>{
tx(
contracts.GuidlCoin.mint(this.state.mintTo,this.state.mintAmount),
(receipt)=>{
this.setState({mintTo:"",mintAmount:""})
}
)
}}>
Mint
</Button>
</div>
)
}

Now let’s render the minterView under the current UI:

If you switch between accounts you will see the mint UI show up for Minter accounts.

We need to show the current balance of the current account and we could use the command line, but we’re not nerds... Let’s get it working in the frontend.

let balanceView = (
<div>
GuidlCoin Balance: <span style={{color:"#FFFFFF"}}>{this.state.balance}</span>
</div>
)

We’ll populate the balance state variable within our poll function:

Let’s mint ourselves some GuidlCoin:

There we go! The only thing we are missing now is the ability to transfer tokens to other accounts. Let’s go ahead and throw that in too. 🤣

Looking at the javascript console, I think the transfer function takes an address (to) and a uint256 (amount).

Let’s create the UI to transfer tokens… with everything we already know this should be pretty darn easy. Think you could do this without any hints? I’ll hold your beer… try to do it without looking at the code below…

.

.

.

.

.

.

.

🍺 👉 😲

.

.

.

.

.

.

.

…okay here it is:

let transferView = (
<div>
Transfer <input
style={{verticalAlign:"middle",width:50,margin:6,maxHeight:20,padding:5,border:'2px solid #ccc',borderRadius:5}}
type="text" name="transferAmount" value={this.state.transferAmount} onChange={this.handleInput.bind(this)}
/> guidlcoins to <input
style={{verticalAlign:"middle",width:300,margin:6,maxHeight:20,padding:5,border:'2px solid #ccc',borderRadius:5}}
type="text" name="transferTo" value={this.state.transferTo} onChange={this.handleInput.bind(this)}
/>
<Button size="2" color="green" onClick={async ()=>{
tx(
contracts.GuidlCoin.transfer(this.state.transferTo,this.state.transferAmount),
(receipt)=>{
this.setState({transferTo:"",transferAmount:""})
}
)
}}>
Send
</Button>
</div>
)

Try it out by sending one to another account:

Mission Accomplished! Holy Moly! We created a token Dapp from the ground up. We now have so many new tools in our toolbox! I can’t wait to see what we build next…


If you are interested in building dApps powered by meta transactions with Clevis & Dapparatus, I’m hosting a workshop at Devcon in Prague Oct 31 at 3:30PM in the Coquelicot room.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade