Building a Distributed AppStore with 3Box

How to build an app with 3Box SDK and Plugins

Build an app with 3Box

To illustrate what is currently possible with 3Box, we have created a distributed app store demo. This tutorial will take you through the steps of creating the application, so that you can build your own application with 3Box. By the end, hopefully you will be as excited as we are by the ease and potential of building user-centric apps with 3Box.

๐Ÿ‘‰ Try the AppStore demoโ€‹

How it Works

Tech Stack

Our distributed application will include the following tech stack:

  • React โ€” frontend framework

  • โ€‹IPFS + OrbitDB โ€” where the data is stored (provided by 3Box, so we wonโ€™t need to touch this directly)

  • โ€‹MetaMask โ€” Web3 wallet integration (required to facilitate signing and encryption of data)

  • โ€‹3Box.js โ€” 3Box SDK that connects wallets to IPFS database storage via 3ID

  • โ€‹3Box Comments, Profile Hover, and Profile Edit Plugins โ€” drop-in React components that we will use to speed up UI development

We are using React for this app, but you can use 3Box with other frontend technologies as well. Using React also allows us to use some of the 3Box prebuilt React plugins. Data will be stored on IPFS in OrbitDB instances. If you arenโ€™t familiar with IPFS and Orbit: IPFS provides distributed file storage, while OrbitDB is used to create a database layer on top of IPFS.

Please note, this tutorial is 3Box and Web3 focused. We assume some react knowledge. For conciseness, we will skip over the details of creating all the react components. If you are completely new to react, we recommend spending some time to be come familiar before tackling this tutorial.

How 3Box authenticates

3Box works by deriving the database keys which control data in the 3Box system from a message signed by an Ethereum wallet. In this example, we are going integrate with MetaMask as the wallet provider, but you could also use any Ethereum wallet such as Fortmatic, WalletConnect, etc. The wallet will give access to an Ethereum provider, which will return the userโ€™s Ethereum address and allow messages to be be signed by its private key using the personal_sign method. 3Box does not write to the Ethereum blockchain directly, and only needs a signed message from the wallet key pair. This means that there are no associated gas costs and that the 3Box ecosystem is not tied exclusively to Ethereum, opening it up to integrating with other blockchains in the future.

Getting Started

  1. Set up the boilerplate repo

  2. Install 3Box.js

  3. Add basic 3Box Authentication

  4. Add posts to a thread

  5. Retrieve posts from a thread

  6. Add apps to the app store

  7. Add 3Box plugins

1. Set up the boilerplate repo

To get started, we 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

  • Members โ€” 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, we 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 we 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

  • โ€‹Profile Hover โ€” show users profile information on hover in the app

  • โ€‹Comments โ€” let users comment on apps in the app store

โ€‹Profile Edit Pluginโ€‹

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.

โ€‹Profile Hover Pluginโ€‹

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.

โ€‹Comments Pluginโ€‹

The last plugin we will use is the Comments Plugin. We will use this to let users add comments to the application listed. First we 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.

Conclusion

To finish up the application, we added styling and Bounce loader from react spinners.

This example illustrates the power of 3Box to provide distributed, user-controlled data storage as well as providing easy to work with social functionality such as profiles and comments. Hopefully you also feel inspired to start using our SDKs to build applications in a truly user-centric way. There is a whole world of self-sovereign web3 apps waiting to be built.

Questions? Get in Touch

Join us in discord to ask questions or share what you are building.

โ€‹

โ€‹

โ€‹