Welcome fren! In this tutorial, I will be guiding you through the process of building a DApp (decentralised app) that is connected to the Aavegotchi blockchain. The tutorial will be in React and Typescript, however, don't worry if you do not have any React experience as the real meat of the tutorial is using Web3 and the Aavegotchi Subgraph.
You can find the completed code here:
https://github.com/cgcbrown/aavegotchi-dex-tutorial
What is Web3?
When developing an app on the blockchain, there are 2 sides to it.
- Smart contract development - writing code that gets deployed to the blockchain with the Solidity programming language.
- Developing websites or clients that interact with the blockchain via smart contracts.
As the Aavegotchi smart contract is already deployed on the Matic network, all we have to worry about is using Web3 to fulfil the second responsibility. Essentially you can think of Web3 as an API for the blockchain where all you need is the address of the smart contract, an ABI and a Provider.
What is the Aavegotchi Subgraph?
The Subgraph is a GraphQL API built by the Pixelcraft team on The Graph that allows you to more efficiently get data from the Aavegotchi blockchain without having to worry about any Web3 setup. It is useful for viewing data from the contract, however, it has its limitations. You cannot call methods that require gas (like petting your Aavegotchi) and some data that you want may not be integrated into the subgraph yet.
The Build
For this tutorial, we are going to build an Aavegotchi Pokédex that allows the user to search and view all of the summoned Aavegotchis. The end result will look something like this:
Initial setup
Before you can start you will need to make sure you have node >= 10.16
and npm >= 5.6
installed on your machine. You will also need an Ethereum compatible browser (if you are using Chrome or Firefox you will need to install the Metamask browser extension) connected to the Matic Network.
Now that is done, let us create our React app. To do this, open up your terminal, and run the following lines:
mkdir tutorials
cd tutorials
npx create-react-app aavegotchi-dex --template typescript
This will build a react app for you called aavegotchi-dex
inside a newly created directory called tutorials. using --template typescript
configures the React app to use Typescript.
After it's done installing, in your terminal run:
cd aavegotchi-dex
npm start
This should automatically open up your browser and you should see your app running on localhost:3000/
. If not open up your browser and manually put http://localhost:3000/
in the address bar.
Now in your code editor of choice (I personally use Visual Studio code) open up aavegotchi-dex
In src/App.tsx
replace all the code with the following and save:
//App.tsx
import { useEffect } from 'react';
import './App.css';
function App() {
const fetchGotchis = () => {
console.log('Hello fren');
}
useEffect(() => {
fetchGotchis();
}, [])
return (
<div className="App">
</div>
);
}
export default App;
Your screen in the browser should now be blank. However, if you open up your dev tools in the browser, you should see Hello fren
in the console.
This isn’t a React tutorial, so don't worry if you don't fully understand what is happening. All you need to know is when the component is rendered, the useEffect()
hook is triggered which in turn is triggering the fetchGotchis()
function. This is where we are going to put our logic to fetch the Aavegotchi logic from the blockchain.
Using the Aavegotchi Subgraph
Now with the boring bits out of the way, let us start pulling in data from the blockchain!
The Subgraph
To make our lives easier we can use the Aavegotchi Subgraph to pull in our list of Aavegotchi's data. What's handy about the subgraph is you can open up the playground here to get your graphQL query before even writing any lines of code.
On the right, there is a Schema that allows you to visualise the data we can fetch.
The subgraph has a hard limit of 1000 on the amount of data you can fetch with a single query. This is not a problem as for performance reasons we will fetch 100, and then later, you can add the ability to fetch more data as you scroll.
We can now pick and choose what data we want to be returned from the query. For the Aavegotchidex we know we want the:
- name
- id
- collateral
- numeric traits
We are ordering the data by
gotchiId
instead ofid
becausegotchiId
is numeric. Try ordering it byid
and you will see why this is important.
So how come we also aren’t getting the SVG data? Well if you look at the Aavegotchi schema you will see that there is no corresponding property for the SVG (at the time of writing this tutorial that is). This is an example of where we will be using Web3 later.
Using our query in React
Now that we have our query let's use it in our app. For this, we need to install 2 packages, graphQL
and graphql-request
as our graphQL client. So open up a new terminal, and inside your aavegotchi-dex
directory run:
npm install graphql-request graphql
Once they have installed, in App.tsx
put in the following new lines of code
//App.tsx
import { useEffect } from 'react';
import { request } from "graphql-request"; // <-- New line
import './App.css';
const uri = 'https://api.thegraph.com/subgraphs/name/aavegotchi/aavegotchi-core-matic';
function App() {
// Make sure this function is now Asynchronous
const fetchGotchis = async () => {
const query = `
{
aavegotchis(first: 100, orderBy: gotchiId) {
id
name
collateral
withSetsNumericTraits
}
}
`
const response = await request(uri, query);
console.log(response);
}
...
If you are getting a Typescript runtime error, run
npm install @types/react --dev
.
If you have done everything correctly you should now see the aavegotchi data logged in your console exactly as you requested it. So what's happening?
Well, the imported request function requires 2 arguments, the target URL and the query. We get the URL from thegraph.com under Queries (HTTP), this tells the GraphQL request where to target.
The query is what we mocked up earlier and have now converted into a string. We then asynchronously waited for the response to return and logged it in the console.
Now that we know our request works, we need to store it in the Apps state so we can display it in the UI. For this, we use a React hook called useState()
. However, because we are using Typescript we need to first set up our interface.
Let us create a new folder under src
called types
and inside create an index.ts
file. Now in src/types/index.ts
put in the following code:
//types/index.ts
export interface Gotchi {
collateral: string;
id: string;
name: string;
withSetsNumericTraits: Array<Number>;
}
export interface QueryResponse {
aavegotchis: Array<Gotchi>
}
Again, I'm not going to go over what the syntax of the Typescript means. You just need to understand that we are copying what we expect the response to look like from our Subgraph query.
Now at the top of App.tsx
import our types and the useState
hook from React, and edit the fetchGotchis
function to store the response in the state:
//App.tsx
import { useEffect, useState } from 'react';
import { Gotchi, QueryResponse } from './types';
...
function App() {
const [ gotchis, setGotchis ] = useState<Array<Gotchi>>([]);
const fetchGotchis = async () => {
const query = `
{
aavegotchis(first: 100, orderBy: gotchiId) {
id
name
collateral
withSetsNumericTraits
}
}
`
const response = await request<QueryResponse>(uri, query);
setGotchis(response.aavegotchis)
}
...
}
Now that we have stored the data, we can map it onto the screen. If you type this out manually within the App
components return function you will be able to see Typescript coming into play and suggesting properties for you. It will also flag up any mistypes (the amount of time this will save you from bug fixing is dreamy).
//App.tsx
return (
<div className="App">
{gotchis.map((gotchi, i) => {
return (
<p key={i}>{gotchi.name}</p>
)
})}
</div>
);
We should now see a list of names on the screen.
However, this doesn't look very interesting. So what we are gonna do is create a new component for the Aavegotchi listing, that allows you to select an Aavegotchi.
Structuring our code
In App.tsx
replace the returned JSX with the following code:
//App.tsx
return (
<div className="App">
<div className="container">
<div className="selected-container">
</div>
<div className="gotchi-list">
</div>
</div>
</div>
);
and inside App.css
replace the CSS with:
.App {
display: block;
text-align: center;
height: 100vh;
background-color: #FA34F3;
box-sizing: border-box;
}
.container {
display: grid;
grid-template-rows: 50% 50%;
box-sizing: border-box;
height: 100%;
width: 100%;
}
.gotchi-list {
background-color: white;
border-left: 5px solid black;
border-right: 5px solid black;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
box-sizing: border-box;
}
@media (min-width: 768px) {
.container {
max-width: 1300px;
margin: 0 auto;
grid-template-columns: 1fr 1fr;
grid-template-rows: revert;
}
.selected-container {
box-sizing: border-box;
padding: 16px;
height: 100%;
}
}
We now want to create a new component for each Aavegotchi listing as well as for the selected Aavegotchi.
So within src
create a new folder called components
and inside create two more folders called GotchiListing
and SelectedGotchi
which both have an index.tsx
and a styles.css
file.
Your folder structure should now look like this:
Render our Aavegotchi listing
Inside GotchiListing/index.tsx
copy and paste in the following content:
//GotchiListing/index.tsx
import "./styles.css"
interface Props {
id: string;
name: string;
collateralColor: string;
selected: boolean;
selectGotchi: () => void;
}
export const GotchiListing = ({ id, name, collateralColor, selected, selectGotchi }: Props) => {
return (
<div className={`gotchi-listing ${selected && 'selected'}`} onClick={() => selectGotchi()}>
<div className="collateral-container">
<div className="collateral" style={{ backgroundColor: collateralColor }} />
</div>
<p className="id">{id}</p>
<p className="name">{name}</p>
</div>
)
}
The interface tells the editor that the GotchiListing component expects the following properties:
- name - Name of the Aavegotchi
- Id - Id of the Aavegotchi
- collateralColor - Primary color of the collateral (more on this later)
- selected - boolean of whether the item is selected or not
- selectGotchi - Function that passes the click event to the parent
Inside GotchiListing/styles.css
put:
.gotchi-listing {
display: flex;
cursor: pointer;
}
.gotchi-listing.selected,
.gotchi-listing:hover {
background-color: #fffa65;
}
.collateral-container {
width: 54px;
display: grid;
place-items: center;
}
.collateral {
width: 32px;
height: 32px;
border-radius: 50%;
}
.id {
padding-right: 12px;
width: 60px;
text-align: right;
}
.name {
text-transform: uppercase;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
Now inside App.tsx
lets import in and render our new component!
At the top with the other imports put:
//App.tsx
import { GotchiListing } from './components/GotchiListing';
And inside the div with a className of gotchi-list
we should map out our <GotchiListing />
component for each Aavegotchi stored in our state:
//App.tsx
<div className="gotchi-list">
{
gotchis.map((gotchi, i) => (
<GotchiListing
key={gotchi.id}
id={gotchi.id}
name={gotchi.name}
collateralColor="black"
selectGotchi={() => null}
selected={false}
/>
))
}
</div>
If you are getting a run time error, inside
SelectedGotchi/index.tsx
type and saveexport {}
as a temporary fix.
By doing this you should now be able to scroll through the list of Aavegotchis.
You may have noticed that we are not passing the
gotchi.collateral
to thecollateralColor
. That's because the collateral we get returned isn't a hex code, but a unique ID for the collateral. We will have to use Web3 later to call the aavegotchi contract to receive what the corresponding color should be.
Selecting an Aavegotchi
Time to put in our selection logic. First, we create another state within App.tsx
for the selected Aavegotchi's index:
//App.tsx
const [ selectedGotchi, setSelectedGotchi ] = useState<number>(0);
Now when we click on a listing, we want to set the index position of the selected gotchi inside the state. And then we can use that information to check whether a listed gotchi is selected or not:
//App.tsx
<GotchiListing
...
selectGotchi={() => setSelectedGotchi(i)}
selected={i === selectedGotchi}
/>
Great! When you click a listing you should now see the listing is highlighted.
Now let us display the selection in a new component called SelectedGotchi. Inside SelectedGotchi/index.tsx
paste in the following code:
//SelectedGotchi/index.tsx
import './styles.css'
interface Props {
name: string;
traits: Array<Number>;
}
export const SelectedGotchi = ({ name, traits }: Props) => {
return (
<div className="selected-gotchi-container">
<div className="name-container">
<h2>{name}</h2>
</div>
<div className="svg-container" />
<div className="traits-container">
<div className="trait">
<p>⚡ Energy</p>
<p>{traits[0]}</p>
</div>
<div className="trait">
<p>👹 Aggression</p>
<p>{traits[1]}</p>
</div>
<div className="trait">
<p>👻 Spookiness</p>
<p>{traits[2]}</p>
</div>
<div className="trait">
<p>🧠 Brain size</p>
<p>{traits[3]}</p>
</div>
<div className="trait">
<p>👀 Eye shape</p>
<p>{traits[4]}</p>
</div>
<div className="trait">
<p>👁 Eye color</p>
<p>{traits[5]}</p>
</div>
</div>
</div>
)
}
Inside SelectedGotchi/styles.css
:
.selected-gotchi-container {
display: grid;
grid-template-rows: 15% 35% 50%;
width: 100%;
height: 100%;
max-height: 100%;
}
.name-container {
display: grid;
place-items: center;
border: 5px solid #e5df40;
background-color: #fffa65;
text-transform: uppercase;
}
.name-container h2 {
margin: 0;
}
.svg-container {
display: grid;
place-items: center;
}
.svg-container > svg {
height: 100%;
}
.traits-container {
padding: 0.4rem;
background-color: white;
border: 5px solid black;
display: grid;
grid-template-columns: 1fr 1fr;
row-gap: 12px;
column-gap: 16px;
}
.trait {
display: flex;
align-items: center;
justify-content: space-between;
}
.trait p {
margin: 0;
text-transform: uppercase;
}
@media (min-width: 768px) {
.selected-gotchi-container {
grid-template-rows: 72px 1fr 170px;
}
.svg-container > svg {
height: revert;
max-height: 450px;
}
.traits-container {
padding: 1.6rem;
}
}
Now we render our new component in App.tsx
like so:
//App.tsx
...
import { SelectedGotchi } from './components/SelectedGotchi';
...
function App() {
...
return (
<div className="App">
...
<div className="selected-container">
{gotchis.length > 0 && (
<SelectedGotchi
name={gotchis[selectedGotchi].name}
traits={gotchis[selectedGotchi].withSetsNumericTraits}
/>
)}
</div>
...
</div>
);
}
export default App;
What we are doing is checking if any gotchis' exist in the array, then we're rendering in our <SelectedGotchi />
component. We then use the selectedGotchi
index to get the target gotchis name and traits from the gotchis
array.
You should now be able to select a gotchi and see the name and traits change in our new component!
Great! Well done for making it this far, for the final part of the tutorial we will be using Web3 to fetch the data that we couldn't get from the subgraph.
Using Web3
To view information on the blockchain you need 3 things:
A provider
The provider is your choice of the node which talks to the Matic Network. If you have Metamask installed or are you using an Ethereum compatible browser then that will be used as your line of communication.The smart contracts address
This is essentially the URL of the target smart contract, we can find what this is by going on the Aavegotchi Contracts GitHub for the contracts.
We want the Aavegotchi diamond address as it has access to all the facets we need.The ABI (application binary interface)
This is a JSON file whose job is to encode and decode the calls to and from the Solidity contract. We also can download/copy this from the Aavegotchi Github here.
Once we have located everything we need, we can start using it within our App.
Setting up the contract
Let us start by installing web3
:
npm install web3
Now within the src
folder of our app lets create a new folder called abi
and inside it create a JSON file called diamondABI.json
. In this file, we want to copy and paste the whole JSON object from the Github.
Inside App.tsx
we can now import the following:
//App.tsx
import Web3 from 'web3';
import diamondABI from './abi/diamondABI.json';
import { Contract } from 'web3-eth-contract';
import { AbiItem } from 'web3-utils/types'
const diamondAddress = '0x86935F11C86623deC8a25696E1C19a8659CbF95d';
Contract
andAbiItem
are used only for Typescript purposes.
We also set the diamondAddress
as a const using the address we found in the Aavegotchi Contract Github.
Now we have everything we need to view data from the Aavegotchi Blockchain. Inside App()
let's create a new function called connectToWeb3()
that will create our contract and save it in our state.
We want to call this function when the page first renders, therefore we put it in the useEffect()
after fetchGotchis()
.
// App.tsx
function App() {
...
const [ contract, setContract ] = useState<Contract | null>(null);
const connectToWeb3 = () => {
const web3 = new Web3(Web3.givenProvider);
const aavegotchiContract = new web3.eth.Contract(diamondABI as AbiItem[], diamondAddress);
setContract(aavegotchiContract);
}
...
useEffect(() => {
fetchGotchis();
connectToWeb3();
}, [])
For the provider we have used Web3.givenProvider
, this is automatically available if you are using an Ethereum compatible browser. If you don't have an Ethereum compatible browser then instead you can set up a remote or local node and use that as your provider.
Calling methods from the contract
Now that our contract is set up we can start calling methods off of it. You may be aware that calling methods on a contract may require gas. However, this only applies to methods that have to add, delete or change information on the contract. Just viewing data requires no manipulation of the contract and is therefore completely gas-free!
The first method we want to call is one to fetch the collateral primary colors so we can pass each <GotchiListing />
the correct color. By visiting the Aavegotchi Developer Documentation you can find the methods names for the various contracts. We want the getCollateralInfo()
function as located here.
You can also find all of the contracts functions as well as their outputs and inputs within the ABI!
We want to fetch all of the collateral information in one request, however, we need to make sure that the contract
is set up first.
To do this, create a new useEffect()
hook within App.tsx
which has the contract
as a dependency:
//App.tsx
useEffect(() => {
if (!!contract) {
const fetchAavegotchiCollaterals = async () => {
const collaterals = await contract.methods.getCollateralInfo().call();
console.log(collaterals);
};
fetchAavegotchiCollaterals();
}
}, [contract]);
As you can see, the fetchAavegotiCollaterals()
function will only be triggered if contract
is truthy. Therefore on initial render, it will not trigger as the contract wouldn't be set up yet. Therefore by adding contract as a dependency, useEffect()
will now trigger as a side effect to the contract
changing.
If everything has been put in correctly you should now see the different collaterals logged in your browser's console.
We can use the logged output to create our type definition so our code editor knows what we are expecting. So inside src/types/index.ts
let's create a new interface for Collateral
like so:
// types/index.ts
export interface Collateral {
collateralType: string;
collateralTypeInfo: {
cheekColor: string;
conversionRate: string;
delisted: boolean;
eyeShapeSvgId: string;
modifiers: Array<string>;
primaryColor: string;
secondaryColor: string;
svgId: string;
}
}
In App.tsx
lets import our new interface and create a new state which expects an array of collaterals and in our fetchAavegotchiCollaterals()
function we can set the state:
//App.tsx
function App() {
...
const [ collaterals, setCollaterals ] = useState<Array<Collateral>>([]);
...
useEffect(() => {
if (!!contract) {
const fetchAavegotchiCollaterals = async () => {
const collaterals = await contract.methods.getCollateralInfo().call();
setCollaterals(collaterals); // <- Replaced console.log()
};
fetchAavegotchiCollaterals();
}
}, [contract]);
We should now have all the collaterals stored in the state, so let's create a function that takes the gotchi.collateral
, finds it within collaterals
and returns the corresponding primaryColor
.
//App.tsx
function App() {
...
const getCollateralColor = (gotchiCollateral: string) => {
const collateral = collaterals.find(item => item.collateralType.toLowerCase() === gotchiCollateral);
if (collateral) {
return collateral.collateralTypeInfo.primaryColor.replace("0x", '#');
}
return "white";
}
...
return (
<div className="App">
...
<div className="gotchi-list">
{
gotchis.map((gotchi, i) => (
<GotchiListing
...
collateralColor={getCollateralColor(gotchi.collateral)}
...
/>
))
}
</div>
...
</div>
);
}
The replace operation correctly formats the
primaryColor
value into a hex code that the CSS can interpret.
Your gotchi listing should now have the color that represents the gotchis collateral (If you wanted to go a step further you could see if you can put in the logic to display the correct collateral icon).
Displaying the Aavegotchi SVG
All we have left to do is display the selected Aavegotchis image. This is arguably my favourite thing about Aavegotchi as all the SVG’s are stored within the blockchain itself!
If you go back to the Aavegotchi developer wiki you can locate the method we want, which is getAavegotchiSvg(tokenId)
. This method requires passing an Aavegotchis Id as a parameter.
Since writing this tutorial, the Aavegotchi's side and back SVG's are also available via calling
getAavegotchiSideSvgs(tokenId)
. This will return an Array of each SVG.`
Every time we select a gotchi we want to render the SVG within our SelectedGotchi component. Therefore we need a new useEffect()
hook that will trigger every time selectedGotchi
, gotchis
or contract
changes:
`javascript
//App.tsx
function App() {
...
const [ gotchiSVG, setGotchiSVG ] = useState('');
...
useEffect(() => {
const getAavegotchiSVG = async (tokenId: string) => {
const svg = await contract?.methods.getAavegotchiSvg(tokenId).call();
setGotchiSVG(svg);
};
if (contract && gotchis.length > 0) {
getAavegotchiSVG(gotchis[selectedGotchi].id)
}
}, [selectedGotchi, contract, gotchis]);
`
If you were to
console.log(gotchiSVG)
you would see that the output is HTML code. This is the SVG data that we want to inject into our code.
We then pass the SVG data into our <SelectedGotchi />
component like so:
`jsx
//App.tsx
svg={gotchiSVG}
name={gotchis[selectedGotchi].name}
traits={gotchis[selectedGotchi].withSetsNumericTraits}
/>
`
Within the SelectedGotchi component, we need to add the svg
property to the interface so we can use it as a prop.
So go to src/components/SelectedGotchi/index.tsx
and add the following changes:
`jsx
// SelectedGotchi/index.tsx
import './styles.css'
interface Props {
name: string;
traits: Array;
svg: string; //<-- New prop
}
export const SelectedGotchi = ({ name, traits, svg }: Props) => {
return (
...
...
`
If everything is done correctly you should now be able to see your selected Aavegotchi!
As the SVG data is being rendered in the DOM you can use your browsers element inspector to identify the class names of the different layers of the SVG. This is useful if you want to animate or hide certain layers of the Aavegotchi SVG.
To show this we are going to hide the SVG background by pasting into SelectedGotchi/styles.css
:
css
.svg-container .gotchi-bg,.wearable-bg {
display: none;
}
The background should now be hidden!
Conclusion
Nice one fren! In this tutorial, you have learnt how to use both the Subgraph and Web3 to create a decentralised React App!
You should now be equipped with the knowledge you need to take the app a step further. You could add in an infinite scroll that concats more Aavegotchi data into the listing... Or perhaps some filter or sorting functionality that sends a new query to the subgraph to fetch more data?
If you have any questions about Aavegotchi or want to build more DApps, then join Aavegotchi discord community where you will be welcomed with open arms!
You can find the completed code here:
https://github.com/cgcbrown/aavegotchi-dex-tutorial
Top comments (6)
Nice tutorial, really well explained & easy to follow, thanks :)
Great tutorial, looking forward to seeing more Web3 tutorials from you!
this is awesome ty!! cant wait to get some time and pour over this. cheers!!!!
I have this error, after calling getCollateralInfo method. Any directions? Thanks.
dev-to-uploads.s3.amazonaws.com/up...
Sorry for the late reply. Have you imported in the abi correctly?
It should be an array of objects like so:
I guess you have to switch to matic mainnet chain.