54
loading...
This website collects cookies to deliver better user experience
contracts
and create a new file called SimpleContract.sol
:class
is contract
. There are many more subtle differences, but they are beyond the scope of this tutorial! (I'll add some links for learning more about Solidity at the end of this tutorial).SimpleContract.sol
, add the following code:pragma solidity >0.5; // pragma is always required
contract SimpleContract {
uint256 public count;
function incrementCount() public {}
function setCount(uint256 _count) public {}
}
count
as a uint256 integer type. When we declare this variable it is automatically set to 0.count
, Solidity will automatically create a "getter" function with the same name for that variable - this contract now includes a function called count
that we can use to read the count
variable.SimpleContract.sol
:pragma solidity > 0.5;
contract SimpleContract {
uint256 public count;
function incrementCount() public {
count++;
}
function setCount(uint256 _count) public {
require(_count > 0);
count = _count;
}
}
incrementCounter
function will increment the count variable when we call it, and the setCounter
function lets us set the count
variable. require(_count > 0);
- require is a Solidity function that is essentially a conditional, whereby the condition _count > 0
must be true for the program flow to continue - if the condition is not true, the program execution will halt with an error.SimpleContract.sol
:// SPDX-License-Identifier: MIT
SimpleContract
. In the Remix console you'll see that the contract creation is pending:count
variable. In the Remix console you'll also see that a CALL was made to our deployed contract, triggering the function and returning the variable value!count
variable has indeed been incremented :) setCount
function - add the number 99 in the field then click "setCount" - once again, MetaMask will open since we are writing to the smart contract state and therefore need to pay for the computation. After the transaction has been processed by the network, reading the count
variable will now return 99 - everything is working as expected.require
statement in our setCount
function? Trying sending the number 0 as input to setCount
and see what happens..git clone https://github.com/jacobedawson/connect-metamask-react-dapp
connect-metamask-react-dapp
git checkout -b send-transactions
yarn install
connect-metamask-react-dapp
, on a branch called send-transactions
. Run the following command to start up the project:yarn start
SimpleContract
contract to our React UIvariable
and display it in our interfacecount
variableuseContractCall
and useContractCalls
hooks to make custom calls to a smart contract.src
directory called contracts
, and then create an index.ts
file in that folder (remember, we are using TypeScript, not pure JavaScript).index.ts
file:export const simpleContractAddress = "0x5711aCCC813db3E27091acD1cae9Cc483721717C";
src
called abi
, and inside that create a new .json file called SimpleContract.json
. In Remix, go to the "Compile" tab, and down below "Compilation Details" you will see a copy icon next to "ABI". Click it to copy the ABI, then paste it into abi/SimpleContract.json
. It should look like this:[
{
"inputs": [],
"name": "incrementCount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_count",
"type": "uint256"
}
],
"name": "setCount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "count",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]
components
directory, create a file called Count.tsx
- this is also where we'll add our inputs. Let's add a basic layout:import { Flex, Text, Button } from "@chakra-ui/react";
export default function Count() {
return (
<Flex direction="column" align="center" mt="4">
<Text color="white" fontSize="8xl"></Text>
<Button colorScheme="teal" size="lg">
Increment
</Button>
</Flex>
);
}
Count
into App
:import Count from "./components/Count";
// ...other code
<Layout>
<ConnectButton handleOpenModal={onOpen} />
<AccountModal isOpen={isOpen} onClose={onClose} />
<Count />
</Layout
hooks
inside the src
directory, and then create the file index.ts
inside the hooks
folder. Inside hooks/index.ts
we'll import our contract address & ABI, as well as a useContractCall
hook from useDapp and a utils
object from Ethers:// hooks/index.ts
import { ethers } from "ethers";
import { useContractCall } from "@usedapp/core";
import simpleContractAbi from "../abi/SimpleContract.json";
import { simpleContractAddress } from "../contracts"
const simpleContractInterface = new ethers.utils.Interface(simpleContractAbi);
export function useCount() {
const [count]: any = useContractCall({
abi: simpleContractInterface,
address: simpleContractAddress,
method: "count",
args: [],
}) ?? [];
return count;
}
useContractCall
is undefined, the count
variable we destructure will be undefined (since the right-hand operand is an empty array).Interface
constructor from Ethers to create a new Interface instance, which we'll need to pass to the useDapp hook useContractCall
. Let's import the hook into our Count.tsx
file:// Count.tsx
import { Flex, Text, Button } from "@chakra-ui/react";
import { useCount } from "../hooks";
export default function Count() {
const count = useCount();
return (
<Flex direction="column" align="center" mt="4">
<Text color="white" fontSize="8xl">
{count ? count.toNumber() : 0}
</Text>
<Button colorScheme="teal" size="lg">
Increment
</Button>
</Flex>
);
}
count
variable is undefined, we'll show 0, otherwise we'll display the count
variable. Notice that we're converting the value returned by useCount into a regular JavaScript number using the .toNumber
method - our smart contract function is actually returning a BigNumber
object, so we need to transform it so that React can display it.hooks/index.ts
file. We're also going to import useContractFunction
from useDapp, and the Contract
constructor from ethers. The imports in hooks/index.ts
should look like this:import { ethers } from "ethers";
import { Contract } from "@ethersproject/contracts";
import { useContractCall, useContractFunction } from "@usedapp/core";
import simpleContractAbi from "../abi/SimpleContract.json";
import { simpleContractAddress } from "../contracts";
Contract
constructor to create an instance of our contract, and to do that we need to pass in both our contract address and our newly created interface:// hooks/index.ts
const contract = new Contract(simpleContractAddress, simpleContractInterface);
useContractFunction
hook:export function useIncrement() {
const { state, send } = useContractFunction(contract, "incrementCount", {});
return { state, send };
}
useContractFunction
hook takes in our contract instance, the name of the method that we'd like to call, and an options object. It returns an object with two variables - state and send, which we are destructuring from the function call. Lastly, we export our useIncrement hook so that we can use it anywhere in our dapp.Count.tsx
, import the new useIncrement
hook, and create a "handleIncrement" function, which we'll add to our Button component's onClick handler:// Count.tsx
import { Flex, Text, Button } from "@chakra-ui/react";
import { useCount, useIncrement } from "../hooks";
export default function Count() {
const count = useCount();
const { state, send: incrementCount } = useIncrement();
function handleIncrement() {
incrementCount();
}
return (
<Flex direction="column" align="center" mt="4">
<Text color="white" fontSize="8xl">
{count ? count.toNumber() : 0}
</Text>
<Button
colorScheme="teal"
size="lg"
onClick={handleIncrement}>
Increment
</Button>
</Flex>
);
}
setCount
smart contract function. hooks/index.ts
called "useSetCount":// hooks/index.ts
export function useSetCount() {
const { state, send } = useContractFunction(contract, "setCount", {});
return { state, send };
}
useIncrement
hook - the only difference is the method name. That gives us an opportunity to create a more generic hook that takes a method name and returns { state, send } - let's create a single hook "useContractMethod" that will replace both useIncrement
and useSetCount
:// hooks/index.ts
export function useContractMethod(methodName: string) {
const { state, send } = useContractFunction(contract, methodName, {});
return { state, send };
}
useIncrement
and useSetCount
and import useContractMethod
into our Count.tsx
component:// Count.tsx
import { useCount, useContractMethod } from "../hooks";
// pass method names to useContractMethod
const { state, send: incrementCount } = useContractMethod("incrementCount");
const { state: setCountState, send: setCount } = useContractMethod("setCount");
useContractMethod
. Now that we've imported the methods, we'll import useState to hold the input state, add an input component and another button in order to collect user input, and send it to the setCount
method. Here's the final, complete code for Count.tsx
:// Count.tsx
import { useState } from "react";
import {
Box,
Flex,
Text,
Button,
NumberInput,
NumberInputField,
} from "@chakra-ui/react";
import { useCount, useContractMethod } from "../hooks";
export default function Count() {
const count = useCount();
const { state, send: incrementCount } = useContractMethod("incrementCount");
const { state: setCountState, send: setCount } =
useContractMethod("setCount");
const [input, setInput] = useState("");
function handleIncrement() {
incrementCount();
}
function handleSetCount() {
const _count = parseInt(input);
if (_count) {
setCount(_count);
}
}
function handleInput(valueAsString: string, valueAsNumber: number) {
setInput(valueAsString);
}
return (
<Flex direction="column" align="center" mt="4">
<Text color="white" fontSize="8xl">
{count ? count.toNumber() : 0}
</Text>
<Button colorScheme="teal" size="lg" onClick={handleIncrement}>
Increment
</Button>
<Box mt={4}>
<NumberInput
mb={2}
min={1}
value={input}
onChange={handleInput}
color="white"
clampValueOnBlur={false}
>
<NumberInputField />
</NumberInput>
<Button isFullWidth colorScheme="purple" onClick={handleSetCount}>
Set Count
</Button>
</Box>
</Flex>
);
}
setCount
method if our count value is 1 or more. Let's test it out by setting the count to 500 and clicking "Set Count":