Meteor Ethereum Transaction Server

Sergei Sevriugin
Coinmonks
4 min readAug 19, 2018

--

It probably kills the idea of decentralisation but for some application we need a server to make transactions in the blockchain. If we will use Meteor as a server then thanks to DDP technology clients receive updates just in time when a new transaction is included in the block and became confirmed.

To make a Meteor Ethereum Transaction Server we include in the project smart contract artifacts that in our case were produced by Truffle. We also need Web3 library and a keystore to keep server accounts. In our case we use truffle-hdwallet-provider as the keystore where we would like to have two addresses that the server can use to sign Ethereum transactions:

import { Meteor }           from 'meteor/meteor';
import Web3 from 'web3';
import contract from 'truffle-contract';
import HDWalletProvider from 'truffle-hdwallet-provider';
import TokenLoyaltyArtifact from './build/contracts/TokenLoyalty.json';
export default class TokenLoyalty {
constructor() {
this.instance = undefined;
this.data = undefined;
this.web3Provider = new HDWalletProvider(Meteor.settings.mnemonic, Meteor.settings.networks.rinkeby, 0, 2);
this.address = this.web3Provider.addresses[0]; // contract owner
this.tokenAddr = this.web3Provider.addresses[1]; // token owner
// ...}

The last parameter in HDWalletProvider constructor asks truffle-hdwallet-provider to generate two accounts and after that the provider can sign transactions from both of them. Please note that if you don’t like to receive an error like this

TypeError: private key should be a Buffer

during the transaction signing then you better be sure that all letters in a from parameter value are small ones.

instance.payment(data.tokenId, {from: this.tokenAddr, gas:700000, gasPrice:"20000000000"})
.then(result => cb(result))
.catch(error => console.error(error));

For example, in our case the ‘private key should be a Buffer’ error was produced when we try to use the following constant instead of this.tokenAddr

const ownerAddress = "0x2952920b5813447f86D6c30Ad1e5C0975Fe563dd";

There are some capital letters here but not all of them and that is why it’s difficult to notice this. This constant is just copy and past from Ganache account section:

Ganache 1.1.0 ACCOUNTS

Now when our keystore is ready and we can use the HDWalletProvider to sing transactions we can write methods to call smart contract functions like in the previous example when we call payment and start listening to events to update MongoDb collections on our server. For example, when Paid event is arrived we use _setData method to call callback function that updates the database state:

instance.Paid().watch((err, responce) => {
if(err) {
console.error(err)
}
else {
that._setData({ event:responce.event,
tx:responce.transactionHash,
tokenId:responce.args.tokenId.toString(),
supPoolId:responce.args.subPoolId.toString(),
});
}
});
_setData(data) {
if(this.data !== data) {
this.data = data;
if(this.watch !== undefined) {
this.watch(data);
}
}
}
setWatch(cb) {
this.watch = cb;
}
// ...let tokenLoyalty;if (Meteor.isServer) {
tokenLoyalty = new TokenLoyalty();
tokenLoyalty.setWatch(Meteor.bindEnvironment((data) => {
// ...
if(data.event === "Paid" &&
Tokens.collection.findOne({ nft_id: data.tokenId })) {
Tokens.collection.update({ nft_id: data.tokenId },
{ $set: { paid:true, inprogress:false } });
//...
}
// ...
}));
}

In our case our smart contract has the following event that we process:

/// payment eventevent Paid(uint subPoolId, uint tokenId, uint value, uint debit, uint payment, uint timeStamp, uint closure);

So, we use responce.args.tokenId for collection update. In the event the type of event subject is uint and in this case the value of responce.args.tokenId will be a big number looking like this

BigNumber { s: 1, e: 0, c: [ 3 ] }

and that is why we use toString() function to have more convenient object type to store and seach. We also need to wrap callback function on server side in Meteor.bindEnvironment() method call otherwise you can get the following error message:

Error: Meteor code must always run within a Fiber.

Another very important discovery that we have found during smart contract test using our server was the gas amount for function call must be set to the maximum what function can consume in all cases. For example, the following code woks without problem for most cases but will give an error when we need to add sub pool to our structure during the create call.

instance.create(data.member, data.clientId, {from: that.address,
gas:500000, gasPrice:"20000000000"})
.then(result => cb(result))
.catch(error => console.error(error));

Unfortunately the error message that we can receive in this case

Transaction: 0x4dbcf824185eb06d68e94f01b2542c134409b6793cadf9b3a1eb325a731769e2 exited with an error (status 0). Please check that the transaction:
- satisfies all conditions set by Solidity `require` statements.
- does not trigger a Solidity `revert` statement.

says nothing about the gas issue and we have spent some time in etherscan.io to undestand that SSTORE instruction has spent most of remaining gas.

etherscan.io GETH Trace for TxHash

So, after we have changed the gas amount from 500000 to 900000 the create transaction passes without any problem. Need to say that in DAPP environment thanks to Metamask.io gas prediction function we will not have such problem and gas amount will be adjusted accordingly.

You can find the code for Meteor Ethereum Transaction Server in our REGA Risk Sharing repository and all smart contracts are in here.

Get Best Software Deals Directly In Your Inbox

--

--