As much of a buzz term as blockchain has become, it's still a great technology. To take some of the magic out of it, blockchain is essentially a distributed database. A blockchain is made of blocks that contain data and those blocks are chained together across multiple machines, which make up the distributed network.
In this article, we'll build a distributed app in the Redwood framework that handles video data. We'll make a smart contract to handle our blockchain interactions. Then we'll set up the Redwood distributed app that will work with the blockchain.
Background on DApps
A DApp is very similar to a regular app. The only difference is that the back-end runs on a decentralized network instead of a central server that might host APIs or other services. It uses the Ethereum blockchain to store data and it has smart contracts to handle the business logic.
By using smart contracts, DApps don't have an owner. A smart contract is like a set of rules that every part of the network has to follow. Once the smart contract has been deployed to Ethereum, you can't change it. That means no companies or individuals can change the instructions for that set of rules in any way.
Ethereum
For a little context, Ethereum is a decentralized, open-source blockchain that has smart contract functionality. This is used as the network to host smart contracts. Anybody can join an Ethereum network and they will automatically follow the rules in the smart contract.
Setting up a Redwood app
Now that we have a little background on what we'll be making, let's start by making the Redwood app. To do this, open a terminal and run:
yarn create redwood-app user-dapp
This will generate new directories and files for the project that covers everything from the front-end to the back-end, including a database. Even when you're working with a DApp, there might be reasons you want to track data in a more common way, like easier data processing.
With the app created, you should see two main directories: api
and web
. The api
folder holds all of the logic for the GraphQL back-end and the code to manage the database transactions. The web
folder, which is where we'll be focused, holds all of the front-end logic and user interface.
Some Installations
You'll need to download Ganache to set up a local Ethereum network that you can develop on. Once you have it installed, you can use the "Quickstart" option.
You'll also need to install Truffle with the following command in the web
directory.
yarn add truffle
This is how we'll deploy the smart contract to the local Ethereum network.
There are a few dependencies that need to be installed too. In the web
directory run:
yarn add truffle truffle-contract web3
This gives us everything we need for the smart contract.
Initializing the Truffle project
Now run the following in the web
directory:
yarn truffle init
This will create an initial migration and the smart contract for it in the web
directory. Now we'll jump straight into some code since we have everything set up.
Writing the smart contract
Inside the web > contracts
directory, add a new file called VideoList.sol
. This is where we'll write some Solidity code to define the rules for this contract. It looks very similar to JavaScript, but it's not the same language. Copy and paste the following code into this new file.
pragma solidity ^0.5.0;
contract VideoList {
uint public videoCount = 0;
struct Video {
uint id;
string url;
}
mapping(uint => Video) public videos;
constructor() public {
createVideo("https://res.cloudinary.com/milecia/video/upload/c_pad,h_360,w_480,q_70,du_10/elephant_herd.mp4");
}
function createVideo(string memory _content) public {
videoCount ++;
videos[videoCount] = Video(videoCount, _content);
}
}
Let's walk through what happened here. We start by defining the version of Solidity we want to work with. Then we create the definition for the contract. In this example, the contract is called VideoList
.
In the contract, we have a publically exposed videoCount
variable that holds how many videos have been added to the blockchain.
Next, there's a struct
that defines a Video
record. It has an integer id
value and a string url
value. Then we map all of the videos into a public videos
array.
Using the constructor
, we add a default video to the blockchain when the smart contract is deployed. Lastly, we have a function that will let us add new videos to the blockchain.
This is the whole smart contract! All that's left to do is create a migration for it and deploy it to the local blockchain.
Migrations and deploys
Before we actually do the migration and deploy, there's a little config file we need to add. In the web
directory, add a new file called truffle-config.js
. Inside the file, paste the following code.
module.exports = {
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*" // so it can match any network id
}
},
solc: {
optimizer: {
enabled: true,
runs: 200
}
}
}
This is how we connect to the local Ethereum blockchain that's running through Ganache. These are the default values in Ganache and you can see them directly in the app.
Then we'll need to add a new migration file to the migrations
folder. Create a new migration file called 2_deploy_contracts.js
. We put the number at the beginning of the migration file so the network knows which order they should be run in.
Open this file and add the following code.
const VideoList = artifacts.require("./VideoList.sol");
module.exports = function(deployer) {
deployer.deploy(VideoList);
};
This is how we deploy the VideoList
smart contract we created.
With this in place, we can run the migration for the smart contract. In the web
directory, run:
truffle migrate
You should see that the migration has run successfully. Now let's take a look at the smart contract in the Truffle console. We'll need the address of the smart contract for a later part of this article, so let's grab it while we're here.
In the web
directory, run:
truffle console
This should bring you to an instance of the smart contract we deployed with the migration. Let's run so code in the console to get an instance of the contract with a function.
videoList = await VideoList.deployed()
We have to interact with the blockchain in an async manner, that's why we're using the await
keyword in the Truffle console. Now let's get the address for this contract because we'll need it in the DApp.
videoList.address
This should return a string similiar to this:
0x82293fe0BE6cCbA6Eb9bd6d5824fC6ACeB6d3957
Since we have the smart contract built and deployed, we can turn our attention to the DApp which uses the data from the blockchain.
Working on the front-end
We'll take advantage of a Redwood command to get started on the front-end. We need a page that lets users interact with the DApp. Leave the web
directory and go to the project root in the terminal and run the following command.
yarn rw g page dapp /
This will generate the page component, a Storybook component for the page, and a test for the page. It'll also add the route for this new page to the Routes.tsx
file. Since we added the /
in the generate command above, it makes this new page the root page for the app.
Back in the web
directory, take a look inside src > pages > DappPage
. This has all the files we talked about. Our focus will be on DappPage.tsx
. Open this file and delete everything out of it.
Connecting to the Ethereum blockchain with Web3.js
The first thing we need to do is make a new file called config.tsx
in the web > src
directory. This is where we'll add some config values that let the front-end connect to the blockchain network we have running with Ganache.
Remember that address we got for our smart contract earlier? It's ok if you don't. All you have to do is open a terminal in the web
directory and run the following commands to get that address again.
truffle console
videoList = await VideoList.deployed()
videoList.address
In the config.tsx
file, add the following line with your own smart contract address.
export const VIDEO_LIST_ADDRESS = '0x82293fe0BE6cCbA6Eb9bd6d5824fC6ACeB6d3957'
Next, you'll have to get an array of values for the ABI of the smart contract. This describes the smart contract behavior and functionality to the front-end. You'll find this in web > build > contracts > VideoList.json
. In the json file, you'll see a key-value pair named "abi"
. Copy that whole array and add the following code to config.tsx
like this.
export const VIDEO_LIST_ABI: any = [
{
"constant": true,
"inputs": [],
"name": "videoCount",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function",
"signature": "0xc61b5f4c"
},
{
"constant": true,
"inputs": [
{
"name": "",
"type": "uint256"
}
],
"name": "videos",
"outputs": [
{
"name": "id",
"type": "uint256"
},
{
"name": "url",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function",
"signature": "0xe6821bf5"
},
{
"inputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor",
"signature": "constructor"
},
{
"constant": false,
"inputs": [
{
"name": "_content",
"type": "string"
}
],
"name": "createVideo",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function",
"signature": "0x8e878969"
}
]
With this config file in place, let's import a few things in DappPage.tsx
.
import { useState, useEffect } from 'react'
import Web3 from 'web3'
import { VIDEO_LIST_ABI, VIDEO_LIST_ADDRESS } from '../../config'
Now let's build the DappPage
component. We'll start with just the outline of the component and a few states.
export const DappPage = () => {
const [account, setAccount] = useState<string>('')
const [videoList, setVideoList] = useState<any>()
const [videos, setVideos] = useState([])
}
Next, we'll call the function that will connect to the Ethereum network. We're using the useEffect
hook here because we only want this to happen when the page initially loads. So right below the states we defined, add this code.
useEffect(() => {
loadData()
}, [])
Let's jump into writing the loadData
method. This is where we'll actually connect to the network and read data from the blockchain.
const loadData = async () => {
const web3 = new Web3('http://localhost:7545')
const accounts = await web3.eth?.getAccounts()
setAccount(accounts[0])
const videoList = new web3.eth.Contract(VIDEO_LIST_ABI, VIDEO_LIST_ADDRESS)
setVideoList(videoList)
const videoCount = await videoList.methods.videoCount().call()
for (var i = 1; i <= videoCount; i++) {
const video = await videoList.methods.videos(i).call()
setVideos([...videos, video])
}
}
This is an async function because we want to wait for the blockchain to handle the request. We make a new instance of Web3
that connects to the local Ethereum blockchain we have running. Then we get the accounts associated with this blockchain and grab the first one. We'll need this a little later.
Next, we pass in the config values we wrote earlier to interact with the smart contract. We're getting the videoList
contract and fetching the videoCount
to see how many videos are available.
Then we loop through all of the videos in the blockchain and save them to the app's state.
Now we can return a view to the page that displays the smart contract id and the videos we've fetched from the blockchain. Right below the loadData
method, add the following return statement.
return (
<div>
Dapp account id: {account}
<ul id="videoList">
{videos.map((video, key) => {
return (
<div key={key}>
<label>
<video src={video.url}></video>
</label>
</div>
)
})}
</ul>
</div>
)
If you go to the root of the project in a terminal and run yarn rw dev
, you should see something similar to this in the browser.
You've officially connected an app to an Ethereum blockchain using a smart contract you wrote! All that's left is adding a way for users to add new videos to the blockchain.
Adding the videos to the blockchain
For the last bit of functionality, we'll use the Cloudinary upload widget to add videos directly to the blockchain.
We need to add the react-cloudinary-upload-widget
package in a terminal in the web
directory.
yarn add react-cloudinary-upload-widget
Then we'll add another import line to DappPage.tsx
below the others.
import { WidgetLoader, Widget } from 'react-cloudinary-upload-widget'
Now we can write the function to add a video to the blockchain. After the loadData
function, add this code.
const createVideo = (content) => {
videoList.methods.createVideo(content).send({ from: account, gas: 4712388 })
.once('receipt', (receipt) => {
console.log(receipt)
})
}
This is how we use the createVideo
method we defined in the smart contract. One thing to note is that we need to send a certain amount of gas with this request. The amount of gas you have is tied to the account you're connected with. Each transaction in a smart contract has a gas price, but since we didn't change any default values 4712388
is what the contract expects as a gas payment.
Now we'll define the callback that will give us a response from Cloudinary when the video has successfully uploaded. Right below the createVideo
function we just wrote, add this.
const successCallBack = (results) => {
const videoInfo = results.info
const url = videoInfo.url
createVideo(url)
}
This will be called in the Widget
when we get a success response from Cloudinary. It'll take the URL from Cloudinary and add it to the blockchain. That means it's finally time to add that widget to the return statement. Below the line that displays the account id, add the following code.
<WidgetLoader />
<Widget
sources={['local', 'camera']}
cloudName={'test_name'}
uploadPreset={'fwe9ewffw'}
buttonText={'Open'}
style={{
color: 'white',
border: 'none',
width: '120px',
backgroundColor: 'green',
borderRadius: '4px',
height: '25px',
}}
folder={'test0'}
onSuccess={successCallBack}
/>
This is how we connect to Cloudinary and get URLs for videos we upload. You'll need to log in to your Cloudinary account and get the cloudName
and uploadPreset
name from the dashboard. Now you should see a new "Open" button in the browser.
If you click that button, you'll see the upload widget.
Go ahead and upload a new video and then reload the page. You should see your new video similar to this.
You have a fully functioning DApp now! Feel free to add more functionality to the smart contract!
Finished code
You can check out some of the code in this Code Sandbox or clone the project from the user-dapp
folder of this repo.
Conclusion
DApps are a cool way to interact with blockchains. Some good next steps would be learning more about Solidity and smart contracts or just making more DApps with other frameworks!