First, an ugly diagram:
- Truffle
- Node v8.9.4 or later
- Ganache
- Git
- Chrome Browser
- MetaMask Chrome Extension
- Kovan Testnet Tokens (see detailed instructions below)
- Infura.io account
Windows Users: Try using VS Code with Truffle
- Basic JavaScript skills
- Truffle Pet Shop Tutorial - highly recommended
It makes life a lot easier if you install truffle globally:
npm install -g truffle
Ethereum has three main 'test networks' (testnets): Ropsten, Kovan, and Rinkeby. They are completely separate from each other and from the Ethereum Mainnet. Tokens on the testnets are worth nothing, they are meant for developing and testing applications.
TestNet coins are worthless, but useful. They are useful because they are worthless. If you will add value to them, they will be useless, therefore worthless. — aphorism from a popular Bitcoin testnet faucet
For this workshop we will use Kovan. The easiest way to obtain kovan tokens is to sign into Gitter using a valid GitLab, GitHub, or Twitter account and then paste your receiving address into the Kovan Testnet gitter room. Testnet tokens will automatically be sent to the address you pasted.
You must get your Kovan address from MetaMask. Be sure to select
Kovan Test Network
from the network drop-down
at the top of the extension. You can click Account 1
to easily copy the address.
MetaMask and Ganache need to be using the same seed words to use the same accounts. You can choose either of these routes:
- Set up MetaMask
- If you didn't copy the seed phrase (you should have!) go to the MetaMask Settings and select 'Reveal Seed Words'. Copy them.
- Open Ganache
- Navigate to "Accounts & Keys" in the Ganache settings
- Turn OFF 'autogenerate HD Mnemonic' and paste the words from step 2.
- Click 'Save & Restart'
- Start up Ganache
- Copy the MNEMONIC (12 random words near the top of the ganache window)
- Navigate to "Accounts & Keys" in the Ganache settings
- Turn OFF 'autogenerate HD Mnemonic' and paste the 12 words from step 2.
- Click 'Save & Restart'
- If you haven't used MetaMask before, use 'Import with Seed Phrase' during the intial MetaMask configuration
- If you have used MetaMask before, Log Out, then select 'Import using account seed phrase' at the bottom of the log in window.
- Use the words from step 2 to re-initialize MetaMask
To use MetaMask with Ganache, you will need to setup a 'Custom RPC' network connection. Follow these directions carefully:
- Start Ganache, don't change any configurations.
- In the Metamask extension, open the network drop-down menu (it should say
Main Ethereum Network
the first time you use Metamask). - Select
Custom RPC
- You should be on a
settings
tab, scroll down toNew Network
. - In the
New RPC URL
field enterhttp://127.0.0.1:7545
- Click
Show Advanced Options
. - In the
ChainID
field, enter5777
. - Click the
Save
button. - Click the
X
in upper right of the extension window if the settings don't go away. - You should see a balance of 100 ETH.
Debug Tip Resetting Ganache will confuse your MetaMask extension (blockchains
aren't supposed to change). When you reset Ganache, you also need to reset MetaMask.
There is a Reset Account
button located under Settings
for this purpose. Settings
can be found in the menu shown by clicking the colorful circle in the upper right of the MetaMask.
git clone https://github.com/destrys/IntroToDApps.git
cd IntroToDApps
Navigate to the pet-shop-tutorial directory and install the dependencies:
cd pet-shop-tutorial
npm install
Assuming installation and config is correct (see above), running the pet shop example dApp should be as easy as following these steps:
- Start Ganache
- Select the Custom RPC interface and 'Reset Account' in MetaMask
- From within the
pet-shop-tutorial
directory:truffle migrate --reset
- From within the
pet-shop-tutorial
directory:npm run dev
- Direct your browser to
localhost:3000
When you click 'adopt', MetaMask should pop up an authorization window. Authorizing the transaction should change the button from 'Adopt' to 'Success'.
The important files for this dApp are: pet-shop-tutorial/contracts/Adoption.sol, pet-shop-tutorial/src/index.html, and pet-shop-tutorial/src/js/app.js. Open them in you text editor and poke around.
All paths and commands assume you are in the pet-shop-tutorial
directory.
Open contracts/Adoption.sol
in your text editor.
We'll discuss the function and design of this 'smart' contract for a bit.
Some important points to cover:
- What is stored in the contract? a. State b. Logic c. Views (in this case)
- How is the contract initialized?
- How is the state changed, and who has access?
- What cannot be changed?
- What off-chain information is part of the app and how are those data joined?
- Who are antagonistic, and how does this contract resolve/prevent the dispute?
Note: Docs regarding the array getter function.
Open test/TestAdoption.sol
and test/TestAdoption.js
We'll discuss testing. Some important points to cover:
- Solidity vs. Javascript testing
- The importance of testing
truffle test
test/TestAdoption.sol test/TestAdoption.js
As-is, another address could adopt()
a pet that has already been adopted,
overwriting the first adopter's address. The UI doesn't allow this by
disabling the button, but it wouldn't be hard for someone to change the UI...
You can see this by commenting out the following line in src/js/app.js
(it's part of the markAdopted
function).
$('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
Implementing this restriction only requires a single line of code, but
think it through carefully. I've included a test file that you can use.
Copy the TestReAdopt.js
from solutions/
to test/
.
Running truffle test
will now test the an adopted pet can't be re-adopted.
The solution is provided in solutions/Adoption_stop_readopt.sol
.
Note: The tests for this exercise check that the contract reverts
.
This is an important result to test for (and should have been included in the initial
tests)
Now that our contract only allows a pet to be adopted once, it really can only handle 16 adoptions and then it's done. It would be nice if someone could reset a pet to unadopted, but we don't want anyone on the internet to be able to do that.
So now we need a privileged user.
The easiest way to set an admin user is to initialize the admin user as the
address that deployed the contract. Our contract currently doesn't run any function
as it's initialized. You'll need to create the constructor
function which is
a special function that is only called once, when the contract is created.
(Constructor Function Docs)
Tests are provided in solutions/TestAdmin.js
, copy to test/
to use.
The solution is provided in solutions/Adoption_admin.sol
.
Now that we have an admin address, let's create a function only the admin can use.
It's basically the reverse of the the adopt()
function, so let's call it
unadopt()
. It still needs to check that the index is valid, but it
also needs to check if the address executing the function is the admin.
The address executing the transaction is available at msg.sender
.
Give it a shot.
Tests are provided in solutions/TestUnadopt.js
, copy to test/
to use.
The solution is provided in solutions/Adoption_unadopt.sol
.
There are additional checks and functions you could add around this feature:
- Don't allow
unadopt
to be called if the pet hasn't been adopted. - Allow the admin or the address that adopted to unadopt
- Create an
unadoptAll
function so an admin could clear all the pets in one transaction.
- Add a price to adopting. (look up
payable
in the solidity docs) - Create a whitelist of addresses that are allowed to adopt.
- Add a pet identifier to the state
- Remix - alternative/complementary IDE
- The Ethernaut - learn smart contract security issues via blockchain-based 'game'
NOTE: Before moving on to the following section, be sure to truffle migrate --reset
once
the contract passes all the tests.
The term DApp ('decentralized app' or 'distributed app') is used as a catch-all for a lot of things. It is usually used to talk about a website that connects to a blockchain. Hopefully as the industry matures, the definition will harden. For this class, DApp refers to the entire system, both smart contract and interface.
So let's look at the included interface for this tutorial.
Note: If you haven't finished the exercies above,
copy solutions/Adoption_unadopt.sol
to contracts/Adoption.sol
. If you
want to re-work through the exercises, the original Adoption.sol
is
included at solutions/Adoption_original.sol
.
Points to cover:
- Glance at the html
- web3 package - where does it come from, what does it do? (window.ethereum)
- What data are available on load?
- How to request data.
- How to form and execute transactions.
Note: This app included web3
and truffle-contract
files. For your project,
you may want to use the npm to include the web3 and truffle-contract
Caution! The web3
included in this app is an older version. web3
v1.0
is still in beta and
has many breaking changes, so for this class, refer to the old web3 docs.
Let's edit the app to notify the viewer if they are the admin.
Copy solutions/index_admin_badge.html
to replace src/index.html
.
In src/js/app.js
take a shot at writing a setAdmin
function that
checks if the logged-in account matches the admin account of the contract.
If it doesn't match, hide the admin badge.
And be sure to run setAdmin()
in initContract
.
A solution is provided in solutions/app_admin_badge.js
.
Let's continue by providing the admin an interface to the unadopt
contract
function.
Copy solutions/index_unadopt.html
to src/index.html
(or write your own button
code).
Write a handleUnadopt
function to call unadopt
on the contract
when the Unadopt button is clicked. And don't forget to add the handler to
bindEvents
. (You'll also probably want to disable the Unadopt
buttons in markAdopted
)
As part of setAdmin
, also hide the unadopt
buttons from non-admins.
You can view the solution at solutions/app_unadopt.js
.
Important Note: While we are hiding the admin interface from non-admins, there
is nothing to stop and address from attempting to call unadopt
on our contract.
What is powerful about dApps is that the authorization is handled by ethereum.
If the transaction isn't signed by the admin's address, the contract will call
revert
and the transaction will be unsuccessful. We don't have to worry
about users or passwords.
- Admin
resetAll
function a. contract function that resets all adopters b. Admin-only button - Encode the Pet Name in the Smart Contract a. Add another array in the smart contract for pet name b. Choose how to initialize (and edit) the names in the contract c. Decide how to pull names from contract, and add that to the UI.
- Allow the adopter to also see the Unadopt button.
a. Edit the smart contract to allow the admin or the adopter call
unadopt
. b. Edit the javascript to show the Unadopt button for the adopter as well.
There are two parts of deploying our dApp: the smart contract and the web app.
So far, we've been deploying our smart contract to our local blockchain (Ganache) with Truffle. For our app to actually be distributed/decentralized, we need to deploy the contract to a external blockchain. And we don't want it to cost us real money to do this while were' developing. So we'll be using one of the ethereum testnets (Rinkeby, Kovan, or Ropsten).
Note: There are many services available to assist you in deploying your smart contract. Do your research before using any third-party service.
The safest route is to run your own full node, and what I recommend, but setting up a full node takes time for downloading the blockchain, and takes work to keep synced and running. So for development, and this class, we'll be using Infura.io for a hosted node.
Here are the steps:
- Sign up for Infura.io
- Create a project
- 'View Project' and copy the
Project ID
- Paste the
Project ID
into the.env
file in the pet shop directory asINFURA_API_KEY
. - Paste your seed words from ganache/MetaMask into
.env
- Edit
truffle-config.js
to include akovan
network. An example is provided insolutions/truffle-config_kovan.js
truffle migrate --network kovan
- Go find your contract on a block explorer!
Infura-Truffle reference 1 Infura-Truffle reference 2
Our web app has (hopefully) been working great using our locally running lite server. But that's not a real solution. We could deploy host this app on any of our favorite hosting sites, or our own server. But if we are able to bundle this app up, we have some more interesting options.
I've included a copy of index.html
and app.js
that we can use with
browserify to avoid doing calls to the server for .json
files.
Copy solutions/index_bundleable.html
to src/index.html
and
solutions/app_bundleable.js
to src/js/app.js
. We'll take a look at
what's going on there.
Assuming you've migrated your contract (with admin and unadopt features) to kovan already, this command will bundle up the javascript:
./node_modules/browserify/bin/cmd.js src/js/app.js > src/js/bundled.js
Now you should be able to point your browser at
file:///INSERT_PATH_TO_REPO_HERE/IntroToDApps/pet-shop-tutorial/src/index.html
and see a bunch of dog faces, but you won't be able to interact with the dApp.
The reason is that MetaMask is retricted to localhost
and https
sites.
file:
is neither of those, so MetaMask won't work.
We need to find somewhere to host these files. If you have a file server with https enabled, that should work. S3 or Azure also would work. My personal favorite for fast development and easy sharing is github pages.
- Fork this repo to your own github account
- Turn on github pages for the repo, using the
master
branch. - Run the browserify command above
- add
src/js/bundled.js
and all the other changed files to a git commit - push to your github
master branch
- Direct your browser to
https://INSERT_GITHUB_USERNAME_HERE.github.io/IntroToDApps/pet-shop-tutorial/src/index.html
- Instead of including the deployed contract address as part of the dApp, you could add a field where the user inputs the contract address. This decouples the web app from the smart contract.
- Blockchains are for antagonistic environments
- Smart contracts store state and the logic to change that state
- dApps are reference interfaces paired with smart contracts
- Test, Test, Test!
I'm Destry! I'm employee #0 at Unchained Capital. While early start-ups require everyone to work on everything, my focus has been on being our blockchain specialist. Address creation, authoring transactions, hardware wallet integrations, signing protocols, and our ethereum multisig dApp are examples of what I've been working on. Before Unchained, I was a data scientist/engineer/big data solution architect at InfoChimps and Cisco. And before that, I earned a Ph.D. in astrophyics.