Minimum Viable Ethereum Mobile Wallet – Part 4: Smart Contract Execution

This is part 4 of the step-by-step tutorial to walk the reader through the development of a Minimum Viable Ethereum Mobile Wallet in React Native. I call it the Minimum Viable Wallet or MVW in short.

MVW’s source codes can be found here.

In this part, I will explain the codes that lets me execute functions in a smart contract.

Tutorial Structure

This is the last of the 4-parts tutorial.

  • Part 1: For folks who just want to test MVW, I wrote about how MVW works here.
  • Part 2: For folks who want to build an MVW from codes, I will explain how it is done and the libraries that I used. 
  • Part 3: Here I explain the codes behind wallet account creation and private key storage. 
  • Part 4: In this part, I will walk through the codes for reading from and running a smart contract within the app. Keep reading.

Executing a Smart Contract

To execute a Smart Contract, the user selects “Contract” from the MVW’s side menu. MVW has been hard-coded to access this Smart Contract on the Ropsten Network. You can view it’s source codes here.

This is a simple “Hello World” contract that changes a string of text based on what you provide. 

  • String in contract: This is the string of text currently stored in the contract.
  • Contract Address: This is the address of the contract on the Ropsten network.
  • Wallet Balance: This is the amount of ETH that is currently left in your wallet.

To change the String in Contract, provide your new string and press [Save].

Notice that your wallet balance reduces after you have saved the new string because you have paid ETH to execute this transaction.

Reading from the Smart Contract

The Smart Contract’s ABI (Application Binary Interface) is a definition that lets you make calls to a Smart Contract that is deployed on the Ethereum Blockchain. It is defined as a constant myinterface in the contract screen.

const myinterface = [{"constant":false,"inputs":[{"name":"x","type":"string"}],"name":"set","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"get","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"x","type":"string"}],"payable":true,"stateMutability":"payable","type":"constructor"}];

Properties of the Smart Contract are stored as Props in the contract screen and gets initialized to empty values when the screen loads.

constructor(props) {
  super(props);
  this.state = {  
    key: '', 
    address:'', 
    wallet:'',
    status:'',
    storevalue:'',
    buttonstate:false,
    ethbalance:0,
  };
}

The contract address, wallet address and the string in the contract are bound to the UI of the contract screen that displays these values.

<Text>String in contract: {this.state.storevalue}</Text>
<Text>Contract Address: {this.state.address}</Text>
<Text>Wallet Balance: {this.state.ethbalance}</Text>

componentDidMount() is invoked immediately after the contract screen is mounted. Here, we add the listeners didFocus and didBlur. When the screen is in focus, didFocus triggers and it executes _fetchData().

sync componentDidMount() {
  this.props.navigation.addListener('didFocus', this._fetchData);
  this.props.navigation.addListener('didBlur', this._cancelFetch);
}

Here’s what _fetchData() does.

It goes to the app’s Secure Storage to pick up the smart contract’s address, the wallet’s address and its private key and updates the props with these values.

  _fetchData = async () => {
    const addressstore = await Expo.SecureStore.getItemAsync('address');
    if (addressstore !== null) {
      // We have data!!
      this.setState({address: addressstore});
      console.log(addressstore);
    }

    const keystore = await Expo.SecureStore.getItemAsync('key');
    if (keystore !== null) {
      this.setState({key: keystore});
      console.log(keystore);
    }

    const walletstore = await Expo.SecureStore.getItemAsync('wallet');
    if (walletstore !== null) {
      this.setState({wallet: walletstore});
      console.log(walletstore);
    }
//(...to be continued)

Next it finds out what’s the latest block on the Ethereum Ropsten testnet. This doesn’t actually make any difference to MVW. I did this just to find out if I am successfully connected to the Blockchain.

//(continue)

    global.web3.eth.getBlock('latest').then((blockstatus) => {
      this.setState({status: blockstatus});
    });
//(...to be continued)

And then I initialize a new Contract instance of the contract at the address stored in Secure Store. Note that this is where you provide the contract’s ABI that was initialized at myinterface.

//(continue)

    let contract = new global.web3.eth.Contract(myinterface, JSON.parse(JSON.stringify(this.state.address)));
    contract.methods.get().call((error, result) => {
      
    }).then((receipt) => {
      console.log(receipt);
      this.setState({storevalue: receipt});
    });
//(...to be continued)

Finally I execute the getethBalance() function to find out how many ETH I currently have in my Wallet.

//(continue)

    this.getethbalance();
  };

The getethbalance() function reads the Ethereum Ropsten Blockchain for the amount of ETHs that are available in the wallet and updates the ethbalance state with this value. 

getethbalance = () => {
  global.web3.eth.getBalance(this.state.wallet).then((balance) => {
    this.setState({ethbalance: global.web3.utils.fromWei(balance, 'ether')});
  });
};

Executing the Smart Contract

This is what happens when the user presses [Save] to update the Smart Contract’s string with a new value.

The smart contract function I am calling is the function set(), which takes a new string, and updates the smart contract with this new string’s value.

The codes below encodes this function call in the constant encodedABI.

const query = contract.methods.set(values.mystring);
const encodedABI = query.encodeABI();
console.log(encodedABI);

The transaction options are prepared with default values for nonce, gasLimit, gasPrice and gas for this call. Here’s a quick explanation:

  • nonce: nonce is the number of transactions sent from the wallet. The function getTransactionCount provides this value.
  • gasLimit: This is the maximum amount of gas that we are willing to pay for this transaction of writing a new value to the Smart Contract.
  • gasPrice: Here we define the amount of ETH that we are willing to pay for every unit of gas.
  • gas: Here we state the amount of gas to be used in this transaction. Unused gas will be refunded.

I also disable the [Save] button because I do not want the user to keep pressing it while the transaction is writing to the Blockchain.

this.setState({buttonstate:true});

Here, I provide the wallet’s address, the smart contract’s address and the encodedABI which contains the encoded function call. 

global.web3.eth.getTransactionCount(this.state.wallet).then(txCount => {
  const txOptions = {
    from: this.state.wallet,
    to: this.state.address,
    nonce: global.web3.utils.toHex(txCount),
    gasLimit: global.web3.utils.toHex(800000),
    gasPrice: global.web3.utils.toHex(20000000000),
    gas: global.web3.utils.toHex(2000000),
    data: encodedABI
  }
//<...to be continued>

Here, I prepare the transaction by providing the txOptions that I have setup above. I also provide the privatekey to sign this transaction.

//<continue>
  var tx = new global.Tx(txOptions);
  var privateKey = new Buffer(this.state.key, 'hex');
  tx.sign(privateKey);
  var serializedTx = tx.serialize();
  this.setState({buttonstate:true});
//<...to be continued>

Finally, I execute sendSignedTransaction(). sendSignedTransaction then emits a series of events.

Upon each confirmation, I log the confirmation number.

//<continue>
  global.web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'))
      .on('confirmation', (confirmationNumber, receipt) => {
        console.log('=> confirmation: ' + confirmationNumber);
      })
//<...to be continued>

Upon each transactionHash, I log the hash value.

//<continue>
      .on('transactionHash', hash => {
        console.log('=> hash');
        console.log(hash);
      })
//<...to be continued>

And when sendSignedTransaction() emits receipt, I know that we are done. I execute get() on the Smart Contract to retrieve the new string value. 

//<continue>
      .on('receipt', receipt => {
        console.log('=> reciept');
        console.log(receipt);
        let contract = new global.web3.eth.Contract(myinterface, JSON.parse(JSON.stringify(this.state.address)));
        contract.methods.get().call((error, result) => {
//<...to be continued>

I update the storevalue state with the new string value that the Smart Contract returns and this in turns displays the new value on the UI. 

I also make the [Save] button clickable again and updates my wallet’s ETH balance with its new value.

//<continue>              
        }).then((receipt) => {
          console.log(receipt);
          this.setState({storevalue: receipt});
          this.setState({buttonstate:false});
          this.getethbalance();
        });
}

Conclusion

MVW (Minimum Viable Wallet) is a boilerplate-type app project that I started so that it’s easy for me (and anyone else) to start building React Native-based mobile applications that interacts with the Ethereum Blockchain. The objectives of MVW are to:

  1. Let the user create his own wallet account with a Secure Storage for his private key
  2. Connect to an Ethereum node
  3. Import a wallet from elsewhere
  4. Receive and spend ETH from his wallet
  5. Run a smart contract

I hope MVW is as useful to you as it was for me!

Photo by S O C I A L . C U T on Unsplash