I finished my first dapp with (react-boilerplate)[https://github.com/mxstbr/] this week and here are the few things I learnt. I won’t get into the redux, redux-saga details, I let you play with the amazing boilerplate.

Interacting with constant functions

Let’s use the typical balanceOf function of the EIP20 contracts:

    function balanceOf(address _owner) constant returns (uint256 balance) {
        return balances[_owner];
    }

Here are the sagas (redux-sagas) I used to interact:

import { take, call, put, cancel, select, fork } from 'redux-saga/effects';
import {
  BALANCE_OF_GET,
} from './constants';
import { LOCATION_CHANGE } from 'react-router-redux';

import {
  balanceOfSuccess,
  balanceOfFailure,
} from './actions';

import { selectEthConnectWeb3Connection } from 'containers/EthConnect/selectors';

import HumanStandardToken from 'contracts/HumanStandardToken.sol.js';

function* balanceOfGet(ethAddress) {
  const web3Connection = yield select(selectEthConnectWeb3Connection());

  HumanStandardToken.setProvider(web3Connection.currentProvider);
  const token = HumanStandardToken.deployed();

  const balancePromise = yield call(token.balanceOf, ethAddress);

  // We return an object in a specific format, see utils/request.js for more information
  if (balancePromise.err === undefined || balancePromise.err === null) {
    yield put(balanceOfSuccess(ethAddress, balancePromise.valueOf()));
  } else {
    console.log(balancePromise.err.response); // eslint-disable-line no-console
    yield put(balanceOfFailure(ethAddress, balancePromise.err.response));
  }
}

/**
 * Watches for BALANCE_OF_GET action and calls handler
 */
export function* balanceOfWatcher() {
  while (true) { // eslint-disable-line no-constant-condition
    const { ethAddress } = yield take(BALANCE_OF_GET);
    // use fork and not call to be sure to fork all
    yield fork(balanceOfGet, ethAddress);
  }
}


/**
 * Root saga manages watcher lifecycle
 */
export function* balanceOfSaga() {
  // Fork watcher so we can continue execution
  const watcher = yield fork(balanceOfWatcher);

  // Suspend execution until location changes
  yield take(LOCATION_CHANGE);
  yield cancel(watcher);
}

// All sagas to be loaded
export default [
  balanceOfSaga,
];

Then, simply implement the right reducers, actions and you are done! you can easily get the balance in your UI.

Interacting with functions that needs transactions

Let’s use the same EIP20 contract again:

    event Approval(address indexed _owner, address indexed _spender, uint256 _value);

    function approve(address _spender, uint256 _value) returns (bool success) {
        allowed[msg.sender][_spender] = _value;
        Approval(msg.sender, _spender, _value);
        return true;
    }
import { take, call, put, select, cancel, fork } from 'redux-saga/effects';
import {
  GIVEMETOKEN_LAUNCH,
} from './constants';
import { LOCATION_CHANGE } from 'react-router-redux';

import {
  givemetokensSuccess,
  givemetokensFailure,
} from './actions';

import {
  balanceOfGet,
} from 'containers/Token/actions';

import { selectEthConnectWeb3Connection } from 'containers/EthConnect/selectors';
// import { selectRianEthAddress } from './selectors';

import HumanStandardToken from 'contracts/HumanStandardToken.sol.js';

function* givemetokens(ethAddress) {
  const web3Connection = yield select(selectEthConnectWeb3Connection());
  HumanStandardToken.setProvider(web3Connection.currentProvider);
  const tokens = HumanStandardToken.deployed();

  const tokensOwner = yield call(tokens.getOwner.call);

  try {
    yield call(tokens.approve, ethAddress, 100, { from: tokensOwner, gas: 200000 });
  } catch (e) {
    console.log(e); // eslint-disable-line no-console
    yield put(givemetokensFailure(ethAddress));
    return;
  }

  const approvedAmt = yield call(tokens.allowance.call, tokensOwner, ethAddress);
  console.log(`approved amount: ${approvedAmt}`);

  try {
    yield call(tokens.transferFrom, tokensOwner, ethAddress, 100, { from: ethAddress, gas: 200000 });
  } catch (e) {
    console.log(e); // eslint-disable-line no-console
    yield put(givemetokensFailure(ethAddress));
  }

  yield put(givemetokensSuccess(ethAddress, true));
  yield put(balanceOfGet(ethAddress));
}

/**
 * Watches for SIMPLEINSURANCE_givemetokens_LAUNCH action and calls handler
 */
export function* givemetokensWatcher() {
  while (true) { // eslint-disable-line no-constant-condition
    const { ethAddress } = yield take(GIVEMETOKEN_LAUNCH);
    yield call(givemetokens, ethAddress);
  }
}


/**
 * Root saga manages watcher lifecycle
 */
export function* givemetokensSaga() {
  // Fork watcher so we can continue execution
  const watcher = yield fork(givemetokensWatcher);

  // Suspend execution until location changes
  yield take(LOCATION_CHANGE);
  yield cancel(watcher);
}

// All sagas to be loaded
export default [
  givemetokensSaga,
];

At that point, you only know that the transaction has gone through, you don’t really know what happened. That’s when you need to listen to the event of your contracts.

Listening to events

Same example as above, we will listen to the Approval event. We need to create a eventChannel, as the event is coming from outside this time, it won’t be generated by our own UI.

const ethEvent = () => eventChannel((emitter) => {
  const tokensApproval = tokens.Approval({ fromBlock: 'latest' });
  tokensApproval.watch((error, results) => {
    const eventType = 'approve';
    const eventTime = new Date().toISOString();
    const eventEthAddress = results.args._owner;
    let eventDescription;
    if (error) {
      console.log(`Approval error ${error}`); // eslint-disable-line no-console
      eventDescription = `ERROR - ${results.args._owner.substring(0,6)} allowed ${results.args._spender.substring(0,6)} to spend ${results.args._value}Ʉ on his behalf`;
    } else {
      eventDescription = `${results.args._owner.substring(0,6)} allowed ${results.args._spender.substring(0,6)} to spend ${results.args._value}Ʉ on his behalf`;
    }

    emitter({ eventEthAddress, eventType, eventTime, eventDescription });
  });

  return () => {
    tokensApproval.stopWatching();
  };
});

function* handleEthEvent(event) {
  switch (event.eventType) {
    case 'approve':
      yield put(ethEventListenerReceiveAction(event.eventEthAddress, event.eventType, event.eventTime, event.eventDescription));
      break;
    default:
      console.log(event); // eslint-disable-line no-console
  }
}

export function* ethEventListenerSaga() {
  yield put(ethEventListenerCreateAction());

  const chan = yield call(ethEvent);
  try {
    while (true) {
      const event = yield take(chan);
      yield call(handleEthEvent, event);
    }
  } finally {
    if (yield cancelled()) {
      chan.close();
      console.log('listening cancelled');
    }
  }
} 

And here you go! You can now listen to the stream of events.

Getting a list of events

In Ethereum, you can get the list of past events, it can kind of act like a kind of storage for the history of your contracts (I won’t go into details for this, but some node might not keep the whole history).

How to get the history of events on the contract?

import { take, call, put, cancel, fork } from 'redux-saga/effects';
import {
  APPROVAL_HISTORY_GET,
} from './constants';
import { LOCATION_CHANGE } from 'react-router-redux';
import { web3Connect } from 'utils/web3.js';
import {
  receiveApprovalHistory,
} from './actions';
import {
  balanceOfGet,
} from 'containers/Token/actions';
import HumanStandardToken from 'contracts/HumanStandardToken.sol.js';

function* getApprovalHistory(ethAddress) {
  const web3Connection = web3Connect();

  HumanStandardToken.setProvider(web3Connection.currentProvider);
  const tokens = HumanStandardToken.deployed();

  const approvalHistory = [];
  // Getting the list of events from block 0 to latest block, with a filter on _owner as it is an indexed field
  const tokensApproval = tokens.Approval({ _owner: ethAddress }, { fromBlock: '0', toBlock: 'latest' });

  const getApprovalLogPromise = yield call(eventGetPromisified, tokensApproval);

  if (getApprovalLogPromise.err === null || getApprovalLogPromise.err === undefined) {
    for (const results of getApprovalLogPromise) {
      approvalHistory.push({
        trigger: results.args._trigger,
        measurement: results.args._measurement.valueOf(),
        premium: results.args._amount.valueOf(),
        refundedAmount: results.args._refundAmount.valueOf(),
        settled: results.args._timeEnded.valueOf(),
        refunded: results.args._due.valueOf(),
      });
    }
  } else {
    console.log(getApprovalLogPromise.err);
  }

  yield put(receiveApprovalHistory(ethAddress, approvalHistory));
}

// Event promisifier to turn the nasty web3 callback to a promise ES6 form
const eventGetPromisified = (event) => new Promise((resolve, reject) => {
  event.get((error, logs) => {
    if (error) {
      reject(error);
    } else {
      resolve(logs);
    }
  });
});

/**
 * Watches for APPROVAL_HISTORY_GET action and calls handler
 */
export function* getApprovalHistoryWatcher() {
  while (true) { // eslint-disable-line no-constant-condition
    const { ethAddress } = yield take(APPROVAL_HISTORY_GET);
    yield fork(getApprovalHistory, ethAddress);
  }
}

/**
 * Root saga manages watcher lifecycle
 */
export function* approvalHistorySaga() {
  // Fork watcher so we can continue execution
  const getApprovalHistoryW = yield fork(getApprovalHistoryWatcher);

  // Suspend execution until location changes
  yield take(LOCATION_CHANGE);
  yield cancel(getApprovalHistoryW);
}

// All sagas to be loaded
export default [
  approvalHistorySaga,
];

Et voila!