diff --git a/src/tutorial/index.md b/src/tutorial/index.md
index 6b513ec12..af4598fa4 100644
--- a/src/tutorial/index.md
+++ b/src/tutorial/index.md
@@ -15,7 +15,7 @@ hide:
This tutorial will take you through the process of building your first dapp---an adoption tracking system for a pet shop!
-This tutorial is meant for those with a basic knowledge of Ethereum and smart contracts, who have some knowledge of HTML and JavaScript, but who are new to dapps.
+This tutorial is meant for those with a basic knowledge of Ethereum and smart contracts, who have some knowledge of React and JavaScript, but who are new to dapps.
Note: For Ethereum basics, please read the Truffle Ethereum Overview tutorial before proceeding.
@@ -25,7 +25,7 @@ In this tutorial we will be covering:
1. Setting up the development environment
2. Creating a Truffle project using a Truffle Box
-3. Writing the smart contract
+3. Writing a smart contract
4. Compiling and migrating the smart contract
5. Testing the smart contract
6. Creating a user interface to interact with the smart contract
@@ -34,7 +34,7 @@ In this tutorial we will be covering:
## Background
-Pete Scandlon of Pete's Pet Shop is interested in using Ethereum as an efficient way to handle their pet adoptions. The store has space for 16 pets at a given time, and they already have a database of pets. As an initial proof of concept, **Pete wants to see a dapp which associates an Ethereum address with a pet to be adopted.**
+Pete Scandalion of Pete's Pet Shop is interested in using Ethereum as an efficient way to handle their pet adoptions. The store has space for 16 pets at a given time, and they already have a database of pets. As an initial proof of concept, **Pete wants to see a dapp which associates an Ethereum address with a pet to be adopted.**
The website structure and styling will be supplied. **Our job is to write the smart contract and front-end logic for its usage.**
@@ -42,9 +42,11 @@ The website structure and styling will be supplied. **Our job is to write the sm
There are a few technical requirements before we start. Please install the following:
-* [Node.js v8+ LTS and npm](https://nodejs.org/en/) (comes with Node)
+* [Node.js v16+ LTS and npm](https://nodejs.org/en/) (comes with Node)
* [Git](https://git-scm.com/)
+**Note:** npm recommends installing Node and npm with a node version manager like nvm. You can see [this article from npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) which goes into more details about using Node and npm with a node version manager.
+
Once we have those installed, we only need one command to install Truffle:
```shell
@@ -53,26 +55,31 @@ npm install -g truffle
To verify that Truffle is installed properly, type `truffle version` on a terminal. If you see an error, make sure that your npm modules are added to your path.
-We also will be using [Ganache](/ganache), a personal blockchain for Ethereum development you can use to deploy contracts, develop applications, and run tests. You can download Ganache by navigating to https://trufflesuite.com/ganache and clicking the "Download" button.
+We also will be using [Ganache](/ganache), a personal blockchain for Ethereum development you can use to deploy contracts, develop applications, and run tests. You can install Ganache using npm as well.
+
+```shell
+npm install -g ganache
+```
+
-Note: If you are developing in an environment without a graphical interface, you can also use Truffle Develop, Truffle's built-in personal blockchain, instead of Ganache. You will need to change some settings---such as the port the blockchain runs on---to adapt the tutorial for Truffle Develop.
+Note: If you would like, you can also use Truffle Develop, Truffle's built-in personal blockchain, instead of Ganache. You will need to change some settings---such as the port the blockchain runs on---to adapt the tutorial for Truffle Develop.
## Creating a Truffle project using a Truffle Box
-1. Truffle initializes in the current directory, so first create a directory in your development folder of choice and then moving inside it.
+1. Truffle initializes in the current directory, so first create a directory in your development folder of choice and then move inside it.
```shell
- mkdir pet-shop-tutorial
+ mkdir react-pet-shop-tutorial
- cd pet-shop-tutorial
+ cd react-pet-shop-tutorial
```
-1. We've created a special [Truffle Box](/boxes) just for this tutorial called `pet-shop`, which includes the basic project structure as well as code for the user interface. Use the `truffle unbox` command to unpack this Truffle Box.
+1. We've created a special [Truffle Box](/boxes) just for this tutorial called `react-pet-shop`, which includes the basic project structure as well as code for the user interface. Use the `truffle unbox` command to unpack this Truffle Box.
```shell
- truffle unbox pet-shop
+ truffle unbox react-pet-shop
```
@@ -81,14 +88,14 @@ We also will be using [Ganache](/ganache), a personal blockchain for Ethereum de
### Directory structure
-The default Truffle directory structure contains the following:
+The `react-pet-shop` box's directory structure contains the following:
-* `contracts/`: Contains the [Solidity](https://solidity.readthedocs.io/) source files for our smart contracts. There is an important contract in here called `Migrations.sol`, which we'll talk about later.
-* `migrations/`: Truffle uses a migration system to handle smart contract deployments. A migration is an additional special smart contract that keeps track of changes.
-* `test/`: Contains both JavaScript and Solidity tests for our smart contracts
-* `truffle-config.js`: Truffle configuration file
+* `contracts/`: This is where we will put the [Solidity](https://solidity.readthedocs.io/) source files for our smart contracts.
+* `migrations/`: Truffle uses a migration system to handle smart contract deployments. A migration is a set of instructions for deploying contracts.
+* `test/`: This folder is where we will put JavaScript and/or Solidity tests for our smart contracts
+* `truffle-config.js`: This is the Truffle configuration file
-The `pet-shop` Truffle Box has extra files and folders in it, but we won't worry about those just yet.
+The `react-pet-shop` Truffle Box has some extra files and folders in it, but we won't worry about those just yet.
## Writing the smart contract
@@ -100,7 +107,8 @@ We'll start our dapp by writing the smart contract that acts as the back-end log
1. Add the following content to the file:
```solidity
- pragma solidity ^0.5.0;
+ // SPDX-License-Identifier: MIT
+ pragma solidity ^0.8.0;
contract Adoption {
@@ -109,8 +117,9 @@ We'll start our dapp by writing the smart contract that acts as the back-end log
Things to notice:
-* The minimum version of Solidity required is noted at the top of the contract: `pragma solidity ^0.5.0;`. The `pragma` command means "*additional information that only the compiler cares about*", while the caret symbol (^) means "*the version indicated or higher*".
-* Like JavaScript or PHP, statements are terminated with semicolons.
+* The first line denotes the license for the contract - in our case MIT. This is written in comments, `//` indicates that everything following on the same line is a comment in Solidity.
+* After the license identifier, the minimum version of Solidity required is noted: `pragma solidity ^0.8.0;`. The `pragma` command means "*additional information that only the compiler cares about*", while the caret symbol (^) means "*the version indicated or higher*".
+* Like JavaScript, statements are terminated with semicolons.
### Variable setup
@@ -135,7 +144,7 @@ Let's allow users to make adoption requests.
1. Add the following function to the smart contract after the variable declaration we set up above.
```solidity
- // Adopting a pet
+ // adopt a pet
function adopt(uint petId) public returns (uint) {
require(petId >= 0 && petId <= 15);
@@ -165,7 +174,7 @@ As mentioned above, array getters return only a single value from a given key. O
1. Add the following `getAdopters()` function to the smart contract, after the `adopt()` function we added above:
```solidity
- // Retrieving the adopters
+ // retrieve the adopters' addresses
function getAdopters() public view returns (address[16] memory) {
return adopters;
}
@@ -204,7 +213,7 @@ Solidity is a compiled language, meaning we need to compile our Solidity to byte
> Compiling ./contracts/Migrations.sol
> Artifacts written to /Users/cruzmolina/Code/truffle-projects/metacoin/build/contracts
> Compiled successfully using:
- - solc: 0.5.0+commit.1d4f565a.Emscripten.clang
+ - solc: 0.8.12+commit.f00d7308.Emscripten.clang
```
### Migration
@@ -226,25 +235,23 @@ Now we are ready to create our own migration script.
1. Add the following content to the `2_deploy_contracts.js` file:
```javascript
- var Adoption = artifacts.require("Adoption");
+ const Adoption = artifacts.require("Adoption");
- module.exports = function(deployer) {
- deployer.deploy(Adoption);
+ module.exports = async function (deployer) {
+ await deployer.deploy(Adoption);
};
```
-1. Before we can migrate our contract to the blockchain, we need to have a blockchain running. For this tutorial, we're going to use [Ganache](/ganache), a personal blockchain for Ethereum development you can use to deploy contracts, develop applications, and run tests. If you haven't already, [download Ganache](/ganache) and double click the icon to launch the application. This will generate a blockchain running locally on port 7545.
+1. Before we can migrate our contract to the blockchain, we need to have a blockchain running. For this tutorial, we're going to use [Ganache](/ganache), a personal blockchain for Ethereum development you can use to deploy contracts, develop applications, and run tests. If you haven't already, install Ganache. In a new terminal run `ganache`. You should see Ganache start up and display a list of funded addresses available along with some other settings. You now have a test blockchain running on your computer at port 8545!
- Note: Read more about Ganache in the Ganache documentation.
+ Note: Read more about Ganache in the Ganache documentation.
- 
-
1. Back in our terminal, migrate the contract to the blockchain.
```shell
- truffle migrate
+ truffle migrate --network ganache
```
You should see output similar to the following:
@@ -254,67 +261,63 @@ Now we are ready to create our own migration script.
1_initial_migration.js
======================
- Deploying 'Migrations'
- ----------------------
- > transaction hash: 0x3b558e9cdf1231d8ffb3445cb2f9fb01de9d0363e0b97a17f9517da318c2e5af
+ Deploying 'Adoption'
+ --------------------
+ > transaction hash: 0x6f83a3c6d5835faed06e2b350661b7c678318f860ed47eaf52264373a7821368
> Blocks: 0 Seconds: 0
- > contract address: 0x5ccb4dc04600cffA8a67197d5b644ae71856aEE4
- > account: 0x8d9606F90B6CA5D856A9f0867a82a645e2DfFf37
- > balance: 99.99430184
- > gas used: 284908
- > gas price: 20 gwei
+ > contract address: 0xcD66c6F5CFa95F71af4F93Ce0872cbC643694273
+ > block number: 3
+ > block timestamp: 1646929863
+ > account: 0x06A84665134b7c7FEB12dad3faea4f713100aA00
+ > balance: 999.998021783626535287
+ > gas used: 284241 (0x45651)
+ > gas price: 3.171967231 gwei
> value sent: 0 ETH
- > total cost: 0.00569816 ETH
+ > total cost: 0.000901603137706671 ETH
-
- > Saving migration to chain.
> Saving artifacts
-------------------------------------
- > Total cost: 0.00569816 ETH
-
+ > Total cost: 0.000901603137706671 ETH
- 2_deploy_contracts.js
- =====================
-
- Deploying 'Adoption'
- .............................
- .............................
+ Summary
+ =======
+ > Total deployments: 1
+ > Final cost: 0.000901603137706671 ETH
```
- You can see the migrations being executed in order, followed by some information related to each migration. (Your information will differ.)
-
-1. In Ganache, note that the state of the blockchain has changed. The blockchain now shows that the current block, previously `0`, is now `4`. In addition, while the first account originally had 100 ether, it is now lower, due to the transaction costs of migration. We'll talk more about transaction costs later.
+ You can see the migrations being executed alongside some information related to the migration. (Your information will differ.)
- 
+1. In the terminal where Ganache is running, you will note a lot of logging due to the rpc requests being sent from Truffle to alter the state.
You've now written your first smart contract and deployed it to a locally running blockchain. It's time to interact with our smart contract now to make sure it does what we want.
## Testing the smart contract using Solidity
-
+
Expand This Section
-Truffle is very flexible when it comes to smart contract testing, in that tests can be written either in JavaScript or Solidity. In this tutorial, we'll be writing our tests in Solidity.
+Truffle is very flexible when it comes to smart contract testing, in that tests can be written either in JavaScript or Solidity. In this section, we'll write some tests in Solidity.
1. Create a new file named `TestAdoption.sol` in the `test/` directory.
1. Add the following content to the `TestAdoption.sol` file:
```solidity
- pragma solidity ^0.5.0;
+ // SPDX-License-Identifier: MIT
+ pragma solidity ^0.8.0;
import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Adoption.sol";
contract TestAdoption {
- // The address of the adoption contract to be tested
+ // the address of the adoption contract to be tested
Adoption adoption = Adoption(DeployedAddresses.Adoption());
- // The id of the pet that will be used for testing
+ // the id of the pet that will be used for testing
uint expectedPetId = 8;
- // The expected owner of adopted pet is this contract
+ // the expected owner of adopted pet is this contract
address expectedAdopter = address(this);
}
@@ -337,12 +340,12 @@ Then we define three contract-wide variables:
### Testing the adopt() function
-To test the `adopt()` function, recall that upon success it returns the given `petId`. We can ensure an ID was returned and that it's correct by comparing the return value of `adopt()` to the ID we passed in.
+To test the `adopt` function, recall that upon success it returns the given `petId`. We can ensure an ID was returned and that it's correct by comparing the return value of `adopt` to the ID we passed in.
1. Add the following function within the `TestAdoption.sol` smart contract, after the declaration of `expectedPetId`:
```solidity
- // Testing the adopt() function
+ // test the adopt function
function testUserCanAdoptPet() public {
uint returnedId = adoption.adopt(expectedPetId);
@@ -353,16 +356,16 @@ To test the `adopt()` function, recall that upon success it returns the given `p
Things to notice:
* We call the smart contract we declared earlier with the ID of `expectedPetId`.
-* Finally, we pass the actual value, the expected value and a failure message (which gets printed to the console if the test does not pass) to `Assert.equal()`.
+* Finally, we pass the actual value, the expected value and a failure message (which gets printed to the console if the test does not pass) to `Assert.equal`.
### Testing retrieval of a single pet's owner
-Remembering from above that public variables have automatic getter methods, we can retrieve the address stored by our adoption test above. Stored data will persist for the duration of our tests, so our adoption of pet `expectedPetId` above can be retrieved by other tests.
+Remembering from above that public variables have automatic getter methods. We can retrieve the address stored by our adoption test above. Stored data will persist for the duration of our tests, so our adoption of pet `expectedPetId` above can be retrieved by other tests.
1. Add this function below the previously added function in `TestAdoption.sol`.
```solidity
- // Testing retrieval of a single pet's owner
+ // test retrieval of a single pet's owner
function testGetAdopterAddressByPetId() public {
address adopter = adoption.adopters(expectedPetId);
@@ -379,9 +382,9 @@ Since arrays can only return a single value given a single key, we create our ow
1. Add this function below the previously added function in `TestAdoption.sol`.
```solidity
- // Testing retrieval of all pet owners
+ // test retrieval of all pet owners
function testGetAdopterAddressByPetIdInArray() public {
- // Store adopters in memory rather than contract's storage
+ // store adopters in memory rather than contract's storage
address[16] memory adopters = adoption.getAdopters();
Assert.equal(adopters[expectedPetId], expectedAdopter, "Owner of the expected pet should be this contract");
@@ -391,24 +394,24 @@ Since arrays can only return a single value given a single key, we create our ow
Note the **memory** attribute on `adopters`. The memory attribute tells Solidity to temporarily store the value in memory, rather than saving it to the contract's storage. Since `adopters` is an array, and we know from the first adoption test that we adopted pet `expectedPetId`, we compare the testing contracts address with location `expectedPetId` in the array.
-## Testing the smart contract using JavaScript
+## Testing the smart contract using JavaScript
Expand This Section
-Truffle is very flexible when it comes to smart contract testing, in that tests can be written either in JavaScript or Solidity. In this tutorial, we'll be writing our tests in Javascript using the Chai and Mocha libraries.
+Truffle is very flexible when it comes to smart contract testing, in that tests can be written either in JavaScript or Solidity. In this section, we'll write some tests in Javascript using the Chain and Mocha libraries.
1. Create a new file named `testAdoption.test.js` in the `test/` directory.
2. Add the following content to the `testAdoption.test.js` file:
- ```
+ ```javascript
const Adoption = artifacts.require("Adoption");
-
+
contract("Adoption", (accounts) => {
let adoption;
let expectedAdopter;
-
+
before(async () => {
- adoption = await Adoption.deployed();
+ adoption = await Adoption.deployed();
});
describe("adopting a pet and retrieving account addresses", async () => {
@@ -420,30 +423,29 @@ Truffle is very flexible when it comes to smart contract testing, in that tests
});
```
- We start the contract by importing :
- * `Adoption`: The smart contract we want to test.
- We begin our test by importing our `Adoption` contract using `artifacts.require`.
+
+ We start the contract by importing the `Adoption` contract which is the smart contract we want to test. Note that `artifacts` is a variable provided in Truffle contexts which allows you to import your contracts using `artifacts.require`. This function takes the name of the contract you wish to import as an argument.
**Note**: When writing this test, our callback function take the argument `accounts`. This provides us with the accounts available on the network when using this test.
- Then, we make use of the `before` to provide initial setups for the following:
+ Then, we make use of the `before` to provide initial setups for the following:
* Adopt a pet with id 8 and assign it to the first account within the test accounts on the network.
* This function later is used to check whether the `petId: 8` has been adopted by `accounts[0]`.
- ### Testing the adopt function
+ ### Testing the adopt function
To test the `adopt` function, recall that upon success it returns the given `adopter`. We can ensure that the adopter based on given petID was returned and is compared with the `expectedAdopter` within the `adopt` function.
- 1. Add the following function within the `testAdoption.test.js` test file, after the declaration of `before` code block.
+ 1. Add the following function within the `testAdoption.test.js` test file, after the declaration of `before` code block like so.
- ```
+ ```javascript
describe("adopting a pet and retrieving account addresses", async () => {
before("adopt a pet using accounts[0]", async () => {
await adoption.adopt(8, { from: accounts[0] });
expectedAdopter = accounts[0];
});
- it("can fetch the address of an owner by pet id", async () => {
+ it("fetches the address of an owner by pet id", async () => {
const adopter = await adoption.adopters(8);
assert.equal(adopter, expectedAdopter, "The owner of the adopted pet should be the first account.");
});
@@ -460,9 +462,9 @@ Truffle is very flexible when it comes to smart contract testing, in that tests
Since arrays can only return a single value given a single key, we create our own getter for the entire array.
1. Add this function below the previously added function in `testAdoption.test.js`.
-
- ```
- it("can fetch the collection of all pet owners' addresses", async () => {
+
+ ```javascript
+ it("fetches the collection of all pet owners' addresses", async () => {
const adopters = await adoption.getAdopters();
assert.equal(adopters[8], expectedAdopter, "The owner of the adopted pet should be in the collection.");
});
@@ -481,172 +483,160 @@ Truffle is very flexible when it comes to smart contract testing, in that tests
1. If all the tests pass, you'll see console output similar to this:
- ```shell
- Using network 'development'.
+ ```shell
+ Using network 'development'.
- Compiling your contracts...
- ===========================
- > Compiling ./test/TestAdoption.sol
- > Artifacts written to /var/folders/z3/v0sd04ys11q2sh8tq38mz30c0000gn/T/test-11934-19747-g49sra.0ncrr
- > Compiled successfully using:
- - solc: 0.5.0+commit.1d4f565a.Emscripten.clang
+ Compiling your contracts...
+ ===========================
+ > Compiling ./test/TestAdoption.sol
+ > Artifacts written to /var/folders/z3/v0sd04ys11q2sh8tq38mz30c0000gn/T/test-11934-19747-g49sra.0ncrr
+ > Compiled successfully using:
+ - solc: 0.8.12+commit.f00d7308.Emscripten.clang
- TestAdoption
- ✓ testUserCanAdoptPet (91ms)
- ✓ testGetAdopterAddressByPetId (70ms)
- ✓ testGetAdopterAddressByPetIdInArray (89ms)
+ TestAdoption
+ ✓ testUserCanAdoptPet (91ms)
+ ✓ testGetAdopterAddressByPetId (70ms)
+ ✓ testGetAdopterAddressByPetIdInArray (89ms)
+ Contract: Adoption
+ adopting a pet and retrieving account addresses
+ ✓ fetches the address of an owner by pet id
+ ✓ fetches the collection of all pet owners\' addresses
- 3 passing (670ms)
- ```
+
+ 5 passing (27s)
+ ```
## Creating a user interface to interact with the smart contract
Now that we've created the smart contract, deployed it to our local test blockchain and confirmed we can interact with it via the console, it's time to create a UI so that Pete has something to use for his pet shop!
-Included with the `pet-shop` Truffle Box was code for the app's front-end. That code exists within the `src/` directory.
+Included with the `react-pet-shop` Truffle Box was code for the app's front-end. That code exists within the `src/` directory.
-The front-end doesn't use a build system (webpack, grunt, etc.) to be as easy as possible to get started. The structure of the app is already there; we'll be filling in the functions which are unique to Ethereum. This way, you can take this knowledge and apply it to your own front-end development.
+The front-end uses [create-react-app](https://create-react-app.dev/) to be as easy as possible to get started. The structure of the app is already there; we'll be filling in the functions which are unique to Ethereum. This way, you can take this knowledge and apply it to your own front-end development.
-### Instantiating web3
+### Initializing the app
-1. Open `/src/js/app.js` in a text editor.
+1. Open `/src/App.js` in a text editor.
-1. Examine the file. Note that there is a global `App` object to manage our application, load in the pet data in `init()` and then call the function `initWeb3()`. The [web3 JavaScript library](https://github.com/ethereum/web3.js/) interacts with the Ethereum blockchain. It can retrieve user accounts, send transactions, interact with smart contracts, and more.
+1. Examine the file. It contains an `App` component to manage our application and after the component mounts, the `componentDidMount` function gets called. In it we initialize the provider in `initProvider` and then call the function `initContract` to fetch our contract data. The contract is where we store the addresses that have adopted pets and we use this information to update the UI to reflect which pets have been adopted. The provider we gain a reference to in `initProvider` allows us to interact with the Ethereum blockchain. It can retrieve user accounts, send transactions, interact with smart contracts, and more.
-1. Remove the multi-line comment from within `initWeb3` and replace it with the following:
+1. Remove the multi-line comment from within `initProvider` and replace it with the following:
- ```javascript
- // Modern dapp browsers...
- if (window.ethereum) {
- App.web3Provider = window.ethereum;
- try {
- // Request account access
- await window.ethereum.request({ method: "eth_requestAccounts" });;
- } catch (error) {
- // User denied account access...
- console.error("User denied account access")
- }
- }
- // Legacy dapp browsers...
- else if (window.web3) {
- App.web3Provider = window.web3.currentProvider;
- }
- // If no injected web3 instance is detected, fall back to Ganache
- else {
- App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
- }
- web3 = new Web3(App.web3Provider);
- ```
+ ```javascript
+ // retrieve a reference to the provider
+ const provider = await detectEthereumProvider();
+
+ if (provider) {
+ // create a reference to the provider in the state
+ this.setState({
+ provider
+ });
+ } else {
+ // tell the user we cannot find a provider and they must install MetaMask
+ alert("You must install MetaMask to adopt a pet.");
+ }
+ ```
Things to notice:
-* First, we check if we are using modern dapp browsers or the more recent versions of [MetaMask](https://github.com/MetaMask) where an `ethereum` provider is injected into the `window` object. If so, we use it to create our web3 object, but we also need to explicitly request access to the accounts with `ethereum.enable()`.
+* First, we use MetaMask's package [`@metamask/detect-provider`](https://www.npmjs.com/package/@metamask/detect-provider) to obtain a reference to the provider.
-* If the `ethereum` object does not exist, we then check for an injected `web3` instance. If it exists, this indicates that we are using an older dapp browser (like [Mist](https://github.com/ethereum/mist) or an older version of MetaMask). If so, we get its provider and use it to create our web3 object.
+* We then create a reference to the provider in the state so that we can use it later.
-* If no injected web3 instance is present, we create our web3 object based on our local provider. (This fallback is fine for development environments, but insecure and not suitable for production.)
+* If there is no provider loaded, then we tell the user to install MetaMask.
### Instantiating the contract
-Now that we can interact with Ethereum via web3, we need to instantiate our smart contract so web3 knows where to find it and how it works. Truffle has a library to help with this called `@truffle/contract`. It keeps information about the contract in sync with migrations, so you don't need to change the contract's deployed address manually.
+Now that we can interact with the Ethereum blockchain via the provider, we need to instantiate our smart contract so the provider knows where to find it and how it works. Truffle has a library to help with this called `@truffle/contract`. It keeps information about the contract in sync with migrations, so you don't need to change the contract's deployed address manually.
-1. Still in `/src/js/app.js`, remove the multi-line comment from within `initContract` and replace it with the following:
+1. Still in `/src/App.js`, remove the multi-line comment from within `initContract` and replace it with the following:
- ```javascript
- $.getJSON('Adoption.json', function(data) {
- // Get the necessary contract artifact file and instantiate it with @truffle/contract
- var AdoptionArtifact = data;
- App.contracts.Adoption = TruffleContract(AdoptionArtifact);
+ ```javascript
+ // use the built artifact to instantiate a TruffleContract object
+ const AdoptionArtifact = window.TruffleContract(Adoption);
- // Set the provider for our contract
- App.contracts.Adoption.setProvider(App.web3Provider);
+ // set the provider for our contract
+ AdoptionArtifact.setProvider(this.state.provider);
- // Use our contract to retrieve and mark the adopted pets
- return App.markAdopted();
- });
- ```
+ this.setState({
+ contracts: {
+ Adoption: AdoptionArtifact
+ }
+ });
+
+ // use the Adoption contract to retrieve the adopters and mark the adopted pets
+ await this.markAdopted();
+ ```
Things to notice:
* We first retrieve the artifact file for our smart contract. **Artifacts are information about our contract such as its deployed address and Application Binary Interface (ABI)**. **The ABI is a JavaScript object defining how to interact with the contract including its variables, functions and their parameters.**
-* Once we have the artifacts in our callback, we pass them to `TruffleContract()`. This creates an instance of the contract we can interact with.
+* You can see at the top of the file we require the `Adoption` (which is JSON) artifact file from `/src/build/contracts`. We now pass it to `TruffleContract`. This creates an instance of the contract we can interact with.
-* With our contract instantiated, we set its web3 provider using the `App.web3Provider` value we stored earlier when setting up web3.
+* With our contract instantiated, we set its provider using the provider value we stored earlier in the state in `initProvider`.
-* We then call the app's `markAdopted()` function in case any pets are already adopted from a previous visit. We've encapsulated this in a separate function since we'll need to update the UI any time we make a change to the smart contract's data.
+* We then call `markAdopted` in case any pets are already adopted from a previous visit. We've encapsulated this in a separate function since we'll need to update the UI any time we make a change to the smart contract's data.
-### Getting The Adopted Pets and Updating The UI
+### Getting the adopted pets and updating the UI
-1. Still in `/src/js/app.js`, remove the multi-line comment from `markAdopted` and replace it with the following:
+1. Still in `/src/App.js`, remove the multi-line comment from `markAdopted` and replace it with the following:
- ```javascript
- var adoptionInstance;
-
- App.contracts.Adoption.deployed().then(function(instance) {
- adoptionInstance = instance;
-
- return adoptionInstance.getAdopters.call();
- }).then(function(adopters) {
- for (i = 0; i < adopters.length; i++) {
- if (adopters[i] !== '0x0000000000000000000000000000000000000000') {
- $('.panel-pet').eq(i).find('button').text('Success').attr('disabled', true);
- }
- }
- }).catch(function(err) {
- console.log(err.message);
- });
- ```
+ ```javascript
+ // create a reference to the deployed Adoption contract
+ const adoptionInstance = await this.state.contracts.Adoption.deployed();
+
+ // make a call to the Adoption contract's `getAdopters` function in order
+ // to determine which pets are already adopted - this retrieves the
+ // addresses that have adopted pets from the blockchain
+ const adopters = await adoptionInstance.getAdopters();
+
+ // set the adopters list in the state for this component
+ this.setState({ adopters });
+ ```
Things to notice:
-* We access the deployed `Adoption` contract, then call `getAdopters()` on that instance.
+* We first access the deployed `Adoption` contract in the component's state.
-* We first declare the variable `adoptionInstance` outside of the smart contract calls so we can access the instance after initially retrieving it.
+* We then call `getAdopters` on that instance which will give us an array containing the addresses that have adopted each pet. We initialized this array (in the constructor) filled with the zero address (`"0x0000000000000000000000000000000000000000"`). If a pet has the zero address as an adopter, it signifies that it has not yet been adopted.
-* Using **call()** allows us to read data from the blockchain without having to send a full transaction, meaning we won't have to spend any ether.
+* Since `getAdopters` is purely a "view" function, to access the data we only have to make a **call**. Because we are only reading state and not altering it, we do not need to spend any ether to execute it.
-* After calling `getAdopters()`, we then loop through all of them, checking to see if an address is stored for each pet. Since the array contains address types, Ethereum initializes the array with 16 empty addresses. This is why we check for an empty address string rather than null or other falsey value.
+* After calling `getAdopters`, we update the state with this new adopters array. Each Pet component that gets created in this component gets its adopter passed in the `props`. If it has no adopter, the button will be clickable. If it has an adopter, the button will be disabled because it cannot be adopted.
-* Once a `petId` with a corresponding address is found, we disable its adopt button and change the button text to "Success", so the user gets some feedback.
-* Any errors are logged to the console.
+### Creating the handleAdopt function
-### Handling the adopt() Function
+1. Still in `/src/App.js`, remove the multi-line comment from `handleAdopt` and replace it with the following:
-1. Still in `/src/js/app.js`, remove the multi-line comment from `handleAdopt` and replace it with the following:
+ ```javascript
+ // create a reference to the deployed Adoption contract
+ const adoptionInstance = await this.state.contracts.Adoption.deployed();
- ```javascript
- var adoptionInstance;
-
- web3.eth.getAccounts(function(error, accounts) {
- if (error) {
- console.log(error);
- }
-
- var account = accounts[0];
-
- App.contracts.Adoption.deployed().then(function(instance) {
- adoptionInstance = instance;
-
- // Execute adopt as a transaction by sending account
- return adoptionInstance.adopt(petId, {from: account});
- }).then(function(result) {
- return App.markAdopted();
- }).catch(function(err) {
- console.log(err.message);
- });
- });
- ```
+ // get the user's accounts
+ const accounts = await this.state.provider.request({
+ method: "eth_accounts"
+ });
+
+ // use the first address as the adopter for the pet - this address
+ // corresponds to the currently selected address in MetaMask
+ await adoptionInstance.adopt(petId, { from: accounts[0] });
+
+ // update the UI to show all adopted pets as "adopted"
+ await this.markAdopted();
+ ```
Things to notice:
-* We use web3 to get the user's accounts. In the callback after an error check, we then select the first account.
+* First, we get the deployed contract as we did above and store the instance in `adoptionInstance`.
+
+* We then use the provider to get the user's accounts.
-* From there, we get the deployed contract as we did above and store the instance in `adoptionInstance`. This time though, we're going to send a **transaction** instead of a call. Transactions require a "from" address and have an associated cost. This cost, paid in ether, is called **gas**. The gas cost is the fee for performing computation and/or storing data in a smart contract. We send the transaction by executing the `adopt()` function with both the pet's ID and an object containing the account address, which we stored earlier in `account`.
+* Now we are going to call a function in our `Adoption` contract which will change the state of the blockchain. In order to do this, we're going to send a **transaction** instead of a **call**. Transactions require a "from" address and have an associated cost. This cost, paid in ether, is called **gas**. The gas cost is the fee for performing computation and/or storing data in a smart contract. We send the transaction by executing the `adopt` function with both the pet's ID and an object containing the account address we want to have adopt the pet. The first address in the `accounts` array corresponds to the address that the user has active in MetaMask.
-* The result of sending a transaction is the transaction object. If there are no errors, we proceed to call our `markAdopted()` function to sync the UI with our newly stored data.
+* We then call `markAdopted` to update the state with the new state of our contract. This, in turn, will update our UI by updating the adopters that are passed to the Pet component.
## Interacting with the dapp in a browser
@@ -658,99 +648,73 @@ Now we're ready to use our dapp!
The easiest way to interact with our dapp in a browser is through [MetaMask](https://metamask.io/), a browser extension for both Chrome and Firefox.
-Note: If you already use Metamask, you'll need to switch accounts. Before doing that, make sure that you have your personal secret phrase backed up, since you'll need it to log back into your personal account (This secret phrase should definitely remain secret and you won't be able to access your account without it! Check [MetaMask's Basic Safety and Security Tips](https://metamask.zendesk.com/hc/en-us/articles/360015489591-Basic-Safety-and-Security-Tips-for-MetaMask) for more info.) After having your secret phrase safely backed up, you can safely switch to Ganache's test account. To do this, you can click your account logo and then "Lock". After this you can "import using Secret Recovery Phrase" to insert the Ganache mnemonic.
+Note: If you already use MetaMask, you'll need to switch accounts. Before doing that, make sure that you have your personal secret phrase backed up, since you'll need it to log back into your personal account (This secret phrase should definitely remain secret and you won't be able to access your account without it! Check MetaMask's Basic Safety and Security Tips for more info.) After having your secret phrase safely backed up, you can safely switch to Ganache's test account. To do this, you can click your account logo and then "Lock". After this you can "import using Secret Recovery Phrase" to insert the Ganache mnemonic.
1. Install MetaMask in your browser.
1. Once installed, a tab in your browser should open displaying the following:
- 
+ 
1. After clicking **Getting Started**, you should see the initial MetaMask screen. Click **Import Wallet**.
- 
+ 
1. Next, you should see a screen requesting anonymous analytics. Choose to decline or agree.
- 
+ 
1. In the box marked **Wallet Seed**, enter the mnemonic that is displayed in Ganache.
-
- **Warning**: Do not use this mnemonic on the main Ethereum network (mainnet). If you send ETH to any account generated from this mnemonic, you will lose it all!
-
+
+ **Warning**: Do not use this mnemonic on the main Ethereum network (mainnet). If you send ETH to any account generated from this mnemonic, you will lose it all!
+
- Enter a password below that and click **OK**.
+ Enter a password below that and click **OK**.
- 
+ 
1. If all goes well, MetaMask should display the following screen. Click **All Done**.
- 
+ 
1. Now we need to connect MetaMask to the blockchain created by Ganache. Click the menu that shows "Main Network" and select **Custom RPC**.
- 
+ 
1. In the box titled "New Network" enter `http://127.0.0.1:7545`, in the box titled "Chain ID" enter `1337` (Default Chain ID for Ganache) and click **Save**.
- 
+ 
- The network name at the top will switch to say `http://127.0.0.1:7545`.
+ The network name at the top will switch to say `http://127.0.0.1:7545`.
1. Click the top-right X to close out of Settings and return to the Accounts page.
- Each account created by Ganache is given 100 ether. You'll notice it's slightly less on the first account because some gas was used when the contract itself was deployed and when the tests were run.
-
- 
-
- Configuration is now complete.
-
-### Installing and configuring lite-server
-
-We can now start a local web server and use the dapp. We're using the `lite-server` library to serve our static files. This shipped with the `pet-shop` Truffle Box, but let's take a look at how it works.
+ Each account created by Ganache is given 100 ether. You'll notice it's slightly less on the first account because some gas was used when the contract itself was deployed and when the tests were run.
-1. Open `bs-config.json` in a text editor (in the project's root directory) and examine the contents:
-
- ```javascript
- {
- "server": {
- "baseDir": ["./src", "./build/contracts"]
- }
- }
- ```
+ 
- This tells `lite-server` which files to include in our base directory. We add the `./src` directory for our website files and `./build/contracts` directory for the contract artifacts.
+ Configuration is now complete.
- We've also added a `dev` command to the `scripts` object in the `package.json` file in the project's root directory. The `scripts` object allows us to alias console commands to a single npm command. In this case we're just doing a single command, but it's possible to have more complex configurations. Here's what yours should look like:
-
- ```javascript
- "scripts": {
- "dev": "lite-server",
- "test": "echo \"Error: no test specified\" && exit 1"
- },
- ```
-
- This tells npm to run our local install of `lite-server` when we execute `npm run dev` from the console.
### Using the dapp
1. Start the local web server:
- ```shell
- npm run dev
- ```
+ ```shell
+ npm start
+ ```
- The dev server will launch and automatically open a new browser tab containing your dapp.
+ The create-react-app server will then start up at http://localhost:3000. You can type that in your browser to navigate to the app.
- 
+ 
1. A MetaMask pop-up should appear requesting your approval to allow Pete's Pet Shop to connect to your MetaMask wallet. Without explicit approval, you will be unable to interact with the dapp. Click **Connect**.
- 
+ 
-1. To use the dapp, click the **Adopt** button on the pet of your choice.
+1. You will see a list of pets which you can adopt. To use the dapp, click the **Adopt** button on the pet of your choice.
1. You'll be automatically prompted to approve the transaction by MetaMask. Click **Submit** to approve the transaction.
@@ -770,4 +734,4 @@ We can now start a local web server and use the dapp. We're using the `lite-serv
You'll also see the same transaction listed in Ganache under the "Transactions" section.
-Congratulations! You have taken a huge step to becoming a full-fledged dapp developer. For developing locally, you have all the tools you need to start making more advanced dapps. If you'd like to make your dapp live for others to use, stay tuned for our future tutorial on deploying to the Ropsten testnet.
+Congratulations! You have taken a huge step to becoming a full-fledged dapp developer. For developing locally, you have all the tools you need to start making more advanced dapps.