Let them speak — A simple guide on how to build a social platform for an ERC-721 token

Kasper Kondzielski
userfeeds
Published in
9 min readApr 26, 2018

In a previous post we discussed a variety of potential uses for a social layer built on top of ERC-721 tokens, and explored how a platform like this might work. This post takes the form of a short tutorial which will show you how to build a social platform for an existing ERC-721 token. To keep things simple, we’re going to build a basic version of cryptopurr. Making sure we’ve covered all the bases, the first chapter also describes how to build your own ERC-721 token, if you don’t already have one. Next we’ll try to figure out how to attach unique avatars to our token. If you know how to do this, or you’re planning to add a social layer to a pre-existing token, then you can skip this section.

Building your own ERC-721 token

To make our life easier, we’re going to use Truffle. If you’re not familiar with Truffle, take a look at the documentation to get yourself up to speed.

To get started, install Truffle:

$ npm install -g truffle

Now let’s create a project. For this tutorial I’m going to use a RoboHashToken, so I’ve named the project’s directory ‘robohash’.

$ mkdir robohash && cd robohash$ truffle init$ npm init #confirm all default values

Since the complete standard of ERC-721 isn’t what you’d call small, we’re going to use an implementation of the ERC-721 token standard from the zeppelin-solidity library. To do that, we need to add zeppelin-solidity to our dependencies:

$ npm install zeppelin-solidity --save-dev

Once we have zeppelin, we can create our contract:

$ touch contracts/RoboHashToken.sol

To maintain compatibility with truffle’s development pipeline, we also need to create a migration file:

$ touch migrations/2_deploy_contracts.js

and fill it as follows:

var RoboHashToken = artifacts.require("RoboHashToken");

module.exports = function(deployer) {
deployer.deploy(RoboHashToken);
};

For more info about truffle migrations, take a look at the Truffle documentation.

Back to the contract. First let’s inherit from ERC-721Token

pragma solidity ^0.4.21;

import 'zeppelin-solidity/contracts/token/ERC721/ERC721Token.sol';

contract RoboHashToken is ERC721Token("RoboHashToken", "RHT") {
...

The two arguments of the base class constructor are the token’s name and token’s symbol respectively. Next we have to figure out how these tokens will be issued. Because I want to make it as simple as possible, I’ll create my token in a way that allows anybody to create their own unique instance of this token:

contract RoboHashToken is ERC721Token("RoboHashToken", "RHT") {

function create() public {
uint256 tokenId = allTokens.length + 1;
_mint(msg.sender, tokenId);
}

As you can see in the code snippet, all we need to do is invoke the internal function _mint which takes care of the whole creation process for us. Congratulations! You’ve just implemented your own ERC-721 token!

But let’s face it, the token itself isn’t particularly interesting. Fortunately, it doesn’t take much more work to turn it into a naming service. Thanks to this, users of your service will be able to claim their names, identifiers, emails etc. All we need to do is to allow users to pass their identifiers to the create function and store them into some kind of mapping.

contract RoboHashToken is ERC721Token("RoboHashToken", "RHT") {

mapping(uint256 => string) internal tokenIdToName;
mapping(string => uint256) internal nameToTokenId;

function create(string name) public {
require(nameToTokenId[name] == 0);
uint256 tokenId = allTokens.length + 1;
_mint(msg.sender, tokenId);
tokenIdToName[tokenId] = name;
nameToTokenId[name] = tokenId;
}

function getTokenName(uint256 tokenId) view public returns (string){
return tokenIdToName[tokenId];
}

function getTokenId(string name) view public returns (uint) {
return nameToTokenId[name];
}

I’ve also created a reversed mapping, so it’s be easy to check the identifier of a particular tokenId.

Token’s avatar

Tokens are kind of abstract, so it’s much more satisfying for your users if your token can be visualized. Cryptokitties tokens are represented by their png/svg images, which are served from a backend, and the same story goes for pretty much all collectibles; the underlying token is represented by a remotely-served graphic. Following that theme, we’ll use the https://robohash.org/ website to generate images from strings. We’ll be simply concatenating “http://robohash.org/” with token’s name and “.png” postfix to create an url for each token’s image which can be later displayed on our interface.

Ok, so let’s summarize what we have by now.

Every user can establish a unique identity, which is implemented as a non-fungible token, and see a visual representation of their character based on the assets from http://robohash.org. Isn’t that great?

You can check the full source code here.

If you want to see how I wrote tests for this contract, you can check that out here.

Deploying a contract

Deploying contracts with Truffle is easy; all we need to do is to configure our network of destination.

To access the Ethereum network, let’s use Infura.

Because deploying contracts costs some amount of Ether, we need to grant Truffle access to our wallet. To prevent leaking our private key into the source code, I’m going to use a dotenv module.

So let’s get started by installing all of the modules needed to deploy to Infura.

$ npm install --save-dev dotenv truffle-wallet-provider ethereumjs-wallet

Now edit truffle.js and add the following:

require('dotenv').config({path: '.env.local'});
const Web3 = require("web3");
const web3 = new Web3();
const WalletProvider = require("truffle-wallet-provider");
const Wallet = require('ethereumjs-wallet');

module.exports = {
networks: {
ropsten: {
provider: function(){
var ropstenPrivateKey = new Buffer(process.env["ROPSTEN_PRIVATE_KEY"], "hex")
var ropstenWallet = Wallet.fromPrivateKey(ropstenPrivateKey);
return new WalletProvider(ropstenWallet, "https://ropsten.infura.io/");
},
gas: 4600000,
gasPrice: web3.toWei("20", "gwei"),
network_id: '3',
}
}
};

Next open up your .env.local and paste in your private key like so:

ROPSTEN_PRIVATE_KEY="123YourPrivateKeyHere"

Don’t forget to add .env.local to your .gitignore!

Various ethereum test networks are available for you to use when testing your contracts, such as Kovan or Rinkeby. I chose Ropsten for this tutorial because Ropsten ETH is the easiest to get at the moment. All networks are fairly similar and you can use whichever testnet you like, but for the remainder of the tutorial I’ll assume you’re using Ropsten just for convenience.

Visit https://faucet.metamask.io/ to request some test ETH. Once you’ve got some ETH from the faucet, you should be ready to deploy!

Once you are ready to deploy you can execute:

$ truffle deploy --network ropsten

Copy and paste the relevant address from the output into the Ropsten Etherscan search box and you should see your newly deployed contract!

For more info about Truffle networks configuration, click here.

If you want to claim your token, you can go to https://remix.ethereum.org/. Remove all the existing code, and paste in the following snippet:

pragma solidity ^0.4.21;

contract RoboHashToken {
function create(string name) public;
}

Having a contract interface is the only requirement to be able to call its methods. Once you have that, switch to the “Run” tab on the right side of the Remix screen. Paste your contract address in the appropriate field and click “At address”. This generates a create method which takes one parameter. If you call it and everything passes successfully, you’ll have become the owner of your own unique ERC-721 token instance.

Building a social layer

Rather than build a complete website from scratch, let’s use an existing one: https://userfeeds.github.io/cryptopurr/. I’m not going to explain all the inner workings of this website in detail. Instead, I’ll focus on the changes we need to make to support our robohashTokens.

First let’s clone cryptopurr to a new directory:

$ git clone git@github.com:Userfeeds/cryptopurr.git
$ cd cryptopurr
$ git reset --hard 0626acd6bb7ce87bcd72cca4d8f1049241e8c0e6

We’re particularly interested in the following files:

  1. .env — File with all the necessary environment variables to get the app working.
  2. package.json — Standard create-react-app package.json file with all the application dependencies and scripts.
  3. src/entityApi.js — Defines how the details about particular token are fetched from network and displayed on website.
  4. public/index.html — Here we need to set the title of the html page.

Ad 1. .env

Set the following properties:

  • REACT_APP_NAME — Your app name (in my case RoboHash).
  • REACT_APP_INTERFACE_VALUE — Url where your website will be hosted.
  • REACT_APP_ERC_721_NETWORK — Name of the network where your ERC-721 contract is deployed. Please note that even that your contract is deployed on particular network users will be able to create messages also on other networks (The power of the cross-chain!!).
  • REACT_APP_ERC_721_ADDRESS — Address of your ERC721 contract.
  • REACT_APP_BASENAME — Base url (in my case /robohash-book/)

Ad 2. package.json

  • Change name and home_page and insert the same values here as you did in the previous file.

Ad 3. src/entityApi.js

The main thing we need to change here is the getEntityData function. This function fetches details about a particular entity from the backend. It takes tokenId as a parameter and returns a custom object which is then used to display our entity on the page. Since we don’t have our own backend, we’ll have to connect it to our contract on the ethereum network instead. Luckily for us,Infura can take care of this too.

We’ll use web3js to talk to Infura, but to invoke methods on our contract we also need its ABI (Application Binary Interface). We can obtain it from the robohash-token project by invoking the following command:

$ truffle compile \
&& cat build/contracts/RoboHashToken.json | jq ‘.abi’ | xclip

Next, paste it into the robohash-book project:

$ mkdir src/abi && xclip -o src/abi/RoboHashToken.json

Once you have your contract’s ABI, you can use it together with the contract’s address to create an instance of your javascript contract’s representation.

import Web3 from 'web3';
const roboHashTokenArtifacts = require('./abi/RoboHashToken.json');

const web3 = new Web3(new Web3.providers.HttpProvider('https://ropsten.infura.io/));
const contractInstance = new web3.eth.Contract(roboHashTokenArtifacts.abi, '0xfa9d471300b0a4cc40ad4dfa5846864973520f45');

At this point we should be able to call the contract’s methods, so let’s implement the getEntityData function.

We will invoke getTokenName with entityId to obtain the name of that token, and then use getTokenUrl to get the robohash image of our token.

export const getEntityData = async entityId => {
try {
const responseTokenName = await contractInstance.methods.getTokenName(entityId).call();
const tokenName = responseTokenName.valueOf();
const responseTokenUrl = await contractInstance.methods.getTokenUrl(tokenName).call();
const tokenUrl = responseTokenUrl.valueOf();
return {
id: entityId,
name: tokenName,
image_url: tokenUrl, // image of our entity
url: `https://robohash.org`, // website with details about particular entity
color: '#333333' // background color
};
} catch (e) {
console.error(e);
return undefined;
}
};

Next we have entityTranslations which defines the texts displayed across the application. You can change them to suit your website theme and local language. I defined them as follows:

export const entityTranslations = {
commentPlaceholder: 'Hash your story',
replyPlaceholder: 'Hash your reply',
noEntitiesError: 'No robohashes found',
entityName: 'RoboHash'
};

The colors map from line 4 defines the appropriate background color behind the kitty. Since we will have only one background color across the site, you can remove this entirely.

Finally, we need to adjust avatarSizes. The best approach to do this is to open our application, find the right values using the web browser console, and then write them into the file. Once all of that’s taken care of, your application is finally ready to launch.

To start the application:

$ yarn
$ yarn start

For me the correct values for avatarSizes were as follow:

export const avatarSizes = {
verySmall: { containerSize: '32px', imgSize: '32px', imgTopOffset: '50%', imgLeftOffset: '50%' },
small: { containerSize: '44px', imgSize: '44px', imgTopOffset: '50%', imgLeftOffset: '50%' },
medium: { containerSize: '54px', imgSize: '54px', imgTopOffset: '50%', imgLeftOffset: '50%' },
large: { containerSize: '64px', imgSize: '64px', imgTopOffset: '50%', imgLeftOffset: '50%' }
};

Ad 4. public/index.html

  • In the line 23 you can change the title.

Congratulations! You’ve built your own social platform for your collectibles! Because the application only consists of frontend (the backend is taken care of by the ethereum network), you can even deploy it using github pages!

That wraps up this tutorial. I hope that you enjoyed reading, and if you want to duplicate it for yourself, all the code can be found in the following repositories:

If you look closely at the https://github.com/Userfeeds/robohash-book repository, you’ll find some additional commits. They show how we adjusted the code to add the option of issuing new tokens directly from our interface, plus how we configured Truffle to deploy to mainnet. And remember, even if your contract was deployed to mainnet, your users don’t have to write messages on the same network. Thanks to the Userfeeds platform they can also post on other test networks such as Kovan, where Ether is free! If you are interested how this whole solution works under the hood, stay tuned for the next article which is coming soon.

--

--