Getting Started

Set up the boilerplate repo Install 3Box.js Add basic 3Box Authentication Add posts to a thread Retrieve posts from a thread Add apps to the app store Add 3Box plugins

1. Set up the boilerplate repo

To get started, I used this boilerplate, built with create react app. This sets up the frontend to easily integrate with 3Box by enabling the Ethereum provider injected into the frontend by MetaMask.

First, install the boilerplate:

git clone https://github.com/RachBLondon/metamask-3box-boilerplate.git

This boilerplate app already includes code (below) which gives the app access to the user’s MetaMask address, which is then saved to react state. We also set up some basic react state to handle browsers that do not come with a provider, and therefore are not web3 enabled.

This all happens in the below function:

async getAddressFromMetaMask() {

if (typeof window.ethereum == "undefined") {

this.setState({ needToAWeb3Browser: true });

} else {

const accounts = await window.ethereum.enable();

this.setState({ accounts });

}

} async componentDidMount() {

await this.getAddressFromMetaMask();

}

This function is called straight away once the page is loaded in componentDidMount . This is the simplest pattern to implement. We need to wait until after the page has mounted because MetaMask will not have had time to inject the provider into the page. This function also contains a flag to silence warnings about not auto refreshing on network change.

Step 1 can be viewed in the codebase within App.js.

2. Install 3Box.js

To use 3Box in your application, you will need to install 3Box:

npm i 3box

3. Add basic 3Box Authentication

Now that we have enabled the Ethereum provider and have access to the user’s Ethereum address, we can start working with 3box.js.

First, instantiate 3Box at the top of the file as the Box object. Then, we can return to the componentDidMount function, and after we call getAddressFrom MetaMask we can add the follow lines:

async componentDidMount() {

await this.getAddressFromMetaMask();

const box = await Box.openBox(this.state.accounts[0], window.ethereum);

const space = box.openSpace('distributed-app-store');

this.setState({ space, box });

}

The first thing we are doing here is calling the openBox method on the 3Box library, this is an important step as it is where we authenticate with 3Box. If this user does not already have a 3Box account, one will be created for them. This is the stage in which the user's cryptographic keys are generated from a signed message. We pass in the user's Ethereum address, and the provider from the wallet, to facilitate signing of this message.

After using openBox to authenticate with 3Box, the next step is to authenticate a space. We can do this with openSpace method. This only takes one argument: the name of the space. This name will be used to namespace your space, so chose something unique and specific to your application. For this tutorial 'distributed-app-store' is fine. After we have authenticated with both 3box and our space, we can save both to react state for use later.

Step 3 can be viewed in the codebase within in the App.js file.

4. Add Posts to a Thread

The 3Box Threads API allows you to store shared data and messages in a collaborative manner. Each thread lives within a space — for more detail visit our architecture blog.

We will use threads to store the list of applications shown in our distributed app store. 3Box allows the choice of two different thread types: persistent or ghost. In this case, we want to ensure that the data is persisted, so that it is accessible to all users. Persistent threads does this by storing the data on IPFS within persistent OrbitDB feed stores. Side note: Ghost threads are great for chatrooms and more ephemeral data.

const thread = await space.joinThread("application_list", {

firstModerator: moderatorsEthAddress,

members: false

});

Here we are joining the "application_list" thread, setting a moderator for the thread, and making it an open thread. 3Box provides three different types of persistent threads:

Open — any user that knows the thread name can post new messages, but moderators and authors can remove posts; any user can read all messages

— any user that knows the thread name can post new messages, but moderators and authors can remove posts; any user can read all messages Members — only invited members can post new messages, but moderators and authors can remove messages; any user can read all messages

— only invited members can post new messages, but moderators and authors can remove messages; any user can read all messages Personal — only a single user can post new messages and moderate their own thread; any user can read all messages

For our app store, we are using Open Threads as they give us the deserved functionality of allowing anyone to upload a new app. In the code snippet above, when we joinThread , this method will also create a thread the first time it is called with a unique name inside a space. Here we can see that we are setting the moderator to be the Ethereum address in the variable moderatorsEthAddress . The false Boolean flag on the members value will create the thread as an Open Thread.

Step 4 can be viewed in the codebase in App.js.

5. Retrieve Posts from a Thread

Now that we have successfully joined a thread, we can create a function to retrieve the previous posts, or data saved to that thread. In our case this will be the list of applications already saved to the thread.

const thread = await space.joinThread("application_list", {

firstModerator: moderatorsEthAddress,

members: false

});

this.setState({ thread }, ()=>(this.getAppsThread());

Here after we have created/ joined the thread, we save it to react state, and then call a getAppsThread function. Let’s have a look at that function.

async getAppsThread() {

if (!this.state.thread) {

console.error("apps thread not in react state");

return;

}

const posts = await this.state.thread.getPosts();

this.setState({posts});



await this.state.thread.onUpdate(async()=> {

const posts = await this.state.thread.getPosts();

this.setState({posts});

}); }

Here we use two different threads methods to get, and update the posts in the thread. First of all, after handling the case when there is no thread in react state, we call the getPosts method, as you would expect this method retrieves an array of posts in a thread. After saving to the react state, we make use of another method onUpdate . This method takes a callback that will be called when an update or a sync to the thread is made. This is useful to handle new posts, and re-synching when the thread did not pull the most up to-date array of posts. In this case, whenever an update is made to the thread, we call the getPosts method again, and set the results to react state.

Step 5 can be viewed in the codebase in App.js.

6. Add Apps to the App Store

Now that we have authenticated with 3Box, opened a space and a thread, we can write the code that handles contributing new applications to a thread. To do this, I created a component called AddApp (source code). This component will handle the UI and the method to add an application.

First let’s decide on an app schema. For this demo, storing the following object will give us enough information to list store and list the application.

{

name: "Test App",

url: "<https://exampleapp.com>",

appImage: "<https://example-image.png>",

description: "An example application.",

account: "0x2f4ce4f714c68a3fc871d1f543ffc24b9b3c2386"

//the account of the users who submitted

}

Next I created a react component AppForm (source code) with the following form.

Next in the AddApp file, we can add the following function to save the data the user inputs to 3Box as as an application. Note this is a naive solution, as it does not handle any validation of the data.

savePost = async formData => {

// add the loggedin account to the form data to be saved

formData.account = this.props.accounts[0];

await this.props.thread.post(formData);

this.props.getAppsThread();

};

Here the thread used in this application has been passed in as a prop, then we can us the post method on the thread to append the application object. This function will be called when the submit button is clicked on the form.

7. Add 3Box plugins

So far we have only been working with 3Box.js, however 3Box also provide a suite of drop-in react components. These plugins speed-up development by reducing the amount of frontend work that is required. To finish off this tutorial, we will be using three of these plugins:

Edit Profile — allow users to view and edit their 3Box profile directly from the app

— allow users to view and edit their 3Box profile directly from the app Profile Hover — show users profile information on hover in the app

— show users profile information on hover in the app Comments — let users comment on apps in the app store

Providing user profiles makes for a much more human and engaging user experience. Let’s get started by creating a profile page (using react router to handle routing, see App.js).

Next we can npm i 3box-profile-edit-react and import it to the top of our file. We also need to pass on the space, box and user Ethereum address as prop to the profile edit component that is rendering the Profile Edit component.

We can now start working with the Edit Profile component inside our render function.

{!this.state.hideEdit && (

<EditProfile

box={this.props.box}

space={this.props.space}

currentUserAddr={this.props.accounts[0]}

currentUser3BoxProfile={this.props.threeBoxProfile}

redirectFn={()=>(this.setState({hideEdit : true}))}

/>)

}

{this.state.hideEdit && (

<div>

<h2>{this.props.threeBoxProfile.name}</h2>

<img src={this.props.threeBoxProfile.image.contentUrl['/']}/>

<p>{this.props.threeBoxProfile.description}</p>

<p>{this.props.threeBoxProfile.emoji}</p>

<button onClick={()=>(this.setState({hideEdit : false}))}>

edit

</button>

</div>)

}

For more information, view our Profile Edit documentation.

Now that we have added the functionality for users to edit and view their profile, we can also use the Profile Hover component. Rather than sitting in its own route, this component will work nicely on the page where we display the applications.

Here we can see we are rendering a basic card for each application in the thread (you can see how this is handled in Home.js ).

To use the Profile Hover component, npm i profile-hover and import in to the file. Then its simply a case of using it like a regular component. The only required attribute is address , we have added some styling and the showName attribute. Read more about the other attributes here.

<ProfileHover

address={post.message.account}

style={{ width: "100%" }}

showName={true} />

For more information, view our Profile Hover documentation.

The last plugin we will use is the Comments Plugin. We will use this to let users add comments to the application listed. First I created a modal for each application.

Next we can npm i 3box-comments-react . Then it is as simple as:

<CommentBox

spaceName={'distributed-app-store'}

threadName={'application_list'}

box={this.props.box} // box created in step 2

currentUserAddr={this.props.usersAddress} //users address from step 1

adminEthAddr={"0x2f4cE4f714C68A3fC871d1f543FFC24b9b3c2386"} />

Here the adminEthAddr is the address of the administrator, who can delete posts. spaceName and threadName need to be consistent with those defined in step 2. Likewise box is the same as defined in 2. currentUserAddr is passed in as a prop to the comment, so it is referenced in that way.

For more information, view our Comments documentation.