ERC-20 Token in Hyperledger Fabric!!

Viraz Malhotra
Zubi.io
Published in
7 min readNov 5, 2019

Yup, you read it right, recently I implemented ERC-20 Token Functionality in Hyperledger Fabric by using the Node SDK and the fabric rest API. If you are amused, don’t be:) As in this blog I would be explaining the complete steps/process to implement this.

Prerequisites

. Docker & Docker Compose

. Node JS

. Go

Additionally, you can refer to the official Fabric Documentation.

Network

I have used the Fabric Samples Template to accomplish this, and I haven’t changed the network configuration at all. By default, we have 2 Org’s and an Orderer which is using SOLO as ordering method and two peers and certificate authority per Org & a Cli Client to interact with our chain code.

So the Network Configuration looks like this.

Chaincode

The first thing that comes to your mind, when you think of implementing ERC-20 Token via Fabric is how will you handle the ownership of the token? Don’t worry I will explain each and every step in terms of the chain code in the next section.

Here is the code inside the init function, I will walk you through it, this function as the name suggests gets called from the startFabric shell file, the snippet is below where we pass Token Metadata like the initial supply, token symbol, and name just like an ERC-20 Token. To answer the question regarding the ownership of the token. * Drum Roll * that would be the MSP of the Org, via the ClientIdentity class in the shim package we can easily get the Id of that particular MSP, and we initialize the world state with this metadata.

Note -> the first snippet is the function call from .sh file and the next one is code snippet of the init chain code function.

echo “Submitting initLedger transaction to smart contract on mychannel”echo “The transaction is sent to all of the peers so that chaincode is built before receiving the following requests”docker exec \ -e CORE_PEER_LOCALMSPID=Org1MSP \ -e
CORE_PEER_MSPCONFIGPATH=${ORG1_MSPCONFIGPATH} \ cli \ peer chaincode invoke \ -o orderer.example.com:7050 \ -C mychannel \ -n SimpleToken \ -c ‘{“function”:”initLedger”,”Args”: [“{\”symbol\”:\”VIR\”,\”name\”:\”Simple Token\”, \”supply\”:\”100\”}”]}’ \ — waitForEvent \ — tls \ — cafile ${ORDERER_TLS_ROOTCERT_FILE} \ — peerAddresses peer0.org1.example.com:7051 \ — peerAddresses peer1.org1.example.com:8051 \ — peerAddresses peer0.org2.example.com:9051 \ — peerAddresses peer1.org2.example.com:10051 \ — tlsRootCertFiles ${ORG1_TLS_ROOTCERT_FILE} \ — tlsRootCertFiles ${ORG1_TLS_ROOTCERT_FILE} \ — tlsRootCertFiles ${ORG2_TLS_ROOTCERT_FILE} \ — tlsRootCertFiles ${ORG2_TLS_ROOTCERT_FILE}
const fnparam = await ctx.stub.getFunctionAndParameters();const args = fnparam.params;
if (args.length !=1) {
return shim.error("Length of the arguements isn't correct"); }
const coinConfigData = args[0];
let coinConfig;
try {
coinConfig = JSON.parse(coinConfigData);
}
catch (error) {
return shim.error("Unable to parse token metadata")
}
const ownerMSPId = new clientIdentity(ctx.stub).getMSPID();
const bufferSymbol = Buffer.from(coinConfig.symbol);
const bufferName = Buffer .from(coinConfig.name);
const initialSupply = Buffer.from(coinConfig.supply)
try {
await ctx.stub.putState("Symbol", bufferSymbol);
await ctx.stub.putState("Name", bufferName);
await ctx.stub.putState(ownerMSPId, initialSupply);
}

Moving on to the transfer function in the chain code(snippet below). We pass in the recieverMSPId and the amount to transfer to the function, initialize the senderMSPId and get both the receiver and the sender balance by the key which is their MSPId and after the basic validation, we are updating the world state by updating the sender and receiver balances.

async transfer(ctx, recieverMSPId, amount) {var amt = parseFloat(amount); 
var senderMSPId = new clientIdentity(ctx.stub).getMSPID();
var senderBalance = await ctx.stub.getState(senderMSPId);
var recieverBalance = await ctx.stub.getState(recieverMSPId);
if (senderBalance.toString() == “”) {
senderBalance = Buffer.from(“0”);
}

if (recieverBalance.toString() == “”) {
recieverBalance = Buffer.from(“0”);
}
var floatSenderBalance = parseFloat(senderBalance.toString());
var floatRecieverBalance = parseFloat(recieverBalance.toString());
if (floatSenderBalance < amt) {
throw new Error(`Low on amount`);
}

try {
var newSenderBalance = floatSenderBalance — amt;
var newRecieverBalance = floatRecieverBalance + amt;
await ctx.stub.putState(senderMSPId, Buffer.from(newSenderBalance.toString()));await ctx.stub.putState(recieverMSPId, Buffer.from(newRecieverBalance.toString()));
}
catch(error) {
throw new Error(error);
}}

Next up we have approve and transferFrom Functions for delegated transfers in ERC-20 Token. The approve function basically takes the spenderMSPid which will do the transfer on behalf of the sender and in the transferFrom method we take in the spender, owner and receiver id along with the amount, we get all the three balance by getState() method. We calculate the update balances after the validation like approved amount checks etc. and then update the world state.

async approve(ctx, spenderMSPId, value) {var val = parseFloat(value);
var senderMSPId = new clientIdentity(ctx.stub).getMSPID();
var senderBalance = await ctx.stub.getState(senderMSPId);
var floatSenderBalance = parseFloat(senderBalance.toString());
if (floatSenderBalance < val) {
throw new Error(“Balance insufficient”); \
}
var allowance = Buffer.from(val.toString());
try {
await ctx.stub.putState(`${senderMSPId}-${spenderMSPId}`, allowance) }
catch (error) {
throw new Error(error);
}
}
}
}
async transferFrom(ctx, spenderMSPId, ownerMSPId, recieverMSPId, value) {var val = parseFloat(value);
var senderBalance = await ctx.stub.getState(`${ownerMSPId}-${spenderMSPId}`);
var floatSenderBalance = parseFloat(senderBalance.toString());
if (floatSenderBalance<val) {
throw new Error("Balance insufficient")
}
var ownerBalance = await ctx.stub.getState(ownerMSPId);
var recieverBalance = await ctx.stub.getState(recieverMSPId);
if (recieverBalance.toString() == "") {
recieverBalance = Buffer.from("0");
}
var ownerBalance = parseFloat(ownerBalance.toString());
var floatRecieverBalance = parseFloat(recieverBalance.toString());
try {
var newSenderBalance = floatSenderBalance -val;
var newOwnerBalnce = ownerBalance - val;
var newRecieverBalance = floatRecieverBalance + val;
await ctx.stub.putState(ownerMSPId, Buffer.from(newOwnerBalnce.toString()));
await ctx.stub.putState(`${ownerMSPId}-${spenderMSPId}`, Buffer.from(newSenderBalance.toString()));
await ctx.stub.putState(recieverMSPId, Buffer.from(newRecieverBalance.toString()));
}
catch(error) {
throw new Error(error)
}
}

So, there it is complete ERC-20 Chaincode decoded, but this is just the half part what about the Node SDK that I talked above. I would be talking about that next.

Node SDK

Hyperledger Fabric has many SDKs like Java, Go, Python, etc. that you can use for your development purposes, I used Node SDK, for this purpose and I am interacting with the Chain Code via REST Apis.

There are two main functions that are used to interact with the chain code, evaluateTransaction & submitTransaction. For making any changes to the world state we would use submitTransaction and for querying the world state we would use evaluateTransaction.

To call transfer, transferFrom and approve function in the Chaincode described above we would be using evaluateTransaction since we are attempting to change the world state. Here is the code snippet from the API file. We start by checking if a wallet and exists and a user exists in that wallet, to create a gateway and get the network-related configurations and then parsing the request and calling the submitTransaction method. Note -> we pass in the function name of the Chaincode as a String and the arguments of that Chaincode function as submitTransaction’s signature.

app.post(‘/api/transfer’, async function(req, res){
try {
// Create a new file system based wallet for managing identities. const walletPath = path.join(process.cwd(), ‘wallet’);
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we’ve already enrolled the user.
const userExists = await wallet.exists(‘user1’);
if (!userExists) {
console.log(‘An identity for the user “user1” does not exist in the wallet’);
console.log(‘Run the registerUser.js application before retrying’); return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccpPath, { wallet, identity: ‘user1’, discovery: { enabled: true, asLocalhost: true } });// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork(‘mychannel’);
// Get the contract from the network.
const contract = network.getContract(‘SimpleToken’);
await contract.submitTransaction(‘transfer’, req.body.reciever, req.body.amount);
console.log(‘Transaction has been submitted’);
// Disconnect from the gateway.
await gateway.disconnect();
}
catch (error) {
console.error(`Failed to submit transaction: ${error}`); process.exit(1);
}})

Similarly to for calling the getBalance method in the Chaincode, we use evaluateTransaction method. Refer to the code snippet below. Here is the curl command you can to call these APIs.

curl -d ‘{“reciever” : “Org2MSP”, “amount” : “30”}’ -H “Content-Type: application/json” -X POST http://localhost:8080/api/transfapp.get(‘/api/getBalance/:balance’, async function(req, res){try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), ‘wallet’);
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we’ve already enrolled the user.const userExists = await wallet.exists(‘user1’);if (!userExists) {
console.log(‘An identity for the user “user1” does not exist in the wallet’);
console.log(‘Run the registerUser.js application before retrying’); return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccpPath, { wallet, identity: ‘user1’, discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork(‘mychannel’);
// Get the contract from the network.
const contract = network.getContract(‘SimpleToken’);
// Evaluate the specified transaction.
const result = await contract.evaluateTransaction(‘getBalance’, req.params.balance);
console.log(`Transaction has been evaluated, result is: ${result.toString()}`);
res.status(200).json({response: result.toString()});
}
catch (error) {
console.error(`Failed to evaluate transaction: ${error}`); process.exit(1);
}})

Here are some helpful docker commands to help you if you get stuck somewhere:

docker stop $(docker ps -aq) -> stop all running docker containers

docker stop $(docker ps -aq) -> remove all docker containers

docker rmi $(docker images dev-* -aq) -> remove all Chaincode images

That’s it guys follow this blog and you can easily implement an ERC-20 Token on Hyperledger Fabric :) I implemented this to get familiar with fabric concepts and get some confidence in. The code can be found on the Github link below. I hope you liked this blog, gone a bit technical this time. Do share this with your friends, clap this story and also star the GitHub Repository.

About Zubi

Zubi is a platform for emerging technologies that provide opportunities from learning to building a career. It bestows one with personalised opportunities in the space. Zubi started with building a blockchain ecosystem and is working on mass adoption of the technology.

--

--

Viraz Malhotra
Zubi.io
Writer for

Blockchain Developer & Enthusiast|Full Stack Developer|Aim to Decentralize the world|Travelling Addict|Cricket is love