src/tracking/calls/callsSaga.js
import {
takeEvery, call, select, put
} from 'redux-saga/effects';
import uuid4 from 'uuid/v4';
import * as callsAT from './AT';
import awaitCall from './awaitCall';
/**
* Initiate a web3 call, it starts waiting for a promise that is mapped
* to our redux call system.
*
* @param web3 The web3 instance to use when sending the actual TX.
* @param callID The ID of the transaction, used in the tracking system.
* @param blockNr The block number to use in the computation of the call, as call context.
* (Optional, web3 defaults to the "latest" block).
* @param callParams The call parameters
* @param callParams.from Senders address, optional. (default wallet otherwise)
* @param callParams.to Destination address, or undefined for contract creation.
* @param callParams.data Optional. TX data, i.e. abi encoded contract call,
* or contract code itself for contract creation. (99% of calls should have it though.)
* @returns {Promise} The redux saga channel.
*/
const initiateCall = (web3, callID, blockNr, {from, to, data}) => {
// Web3 returns a simple promise here, no complex emitter object.
// Now all we have to do is wait for it to complete, and then fire the corresponding event.
const callPromise = web3.eth.call({from, to, data}, blockNr);
// simply return the wrapped promise, it will be handled as a single-element iterable
return awaitCall(callPromise, callID);
};
function* forceCall(web3, {from, to, data, blockNr, callID}) {
// If the user does not specify any ID, than create a new one.
// This new ID is formatted differently from than the one used by contracts making cache-calls;
// users should use their own ID when sending direct raw transactions (recommended).
// If none was provided, then a random one will be sufficient
// (i.e. user can search for it in the redux store).
const id = callID || uuid4();
// Create TX channel, firing redux events based on all promises from web3.
yield call(initiateCall, web3, id, blockNr, {from, to, data});
}
function* cacheCall(getCallsState, {from, to, data, blockNr, callID, outputsABI}) {
const id = callID || uuid4();
// check if we hit the cache.
const cached = yield select(state => getCallsState(state)[id]);
if (!cached) {
yield put({type: callsAT.FORCE_CALL, from, to, data, blockNr, callID: id, outputsABI});
}
// TODO: the else case: we could fire a "cache is hit" event, but we probably don't need it.
}
function* decodeCall(web3, getCallsState, {callID, rawValue}) {
const outputsABI = yield select(state => getCallsState(state)[callID].outputsABI);
// if no outputsABI is available, then we can't decode it.
// The user will have to do with the rawValue.
if (outputsABI) {
try {
// We have the outputs ABI, let's decode the raw bytes
// The '0x' string is special: we know it just means that there is no data,
// don't try to decode (which would result in an error),
// just tell with the decoded data that it is non-existant.
const value = rawValue === '0x' ? null
: web3.eth.abi.decodeParameters(outputsABI, rawValue);
yield put({
type: callsAT.CALL_DECODE_SUCCESS,
callID,
value,
});
} catch (err) {
yield put({
type: callsAT.CALL_DECODE_FAIL,
callID,
err
});
}
}
}
/**
* Handles ReDApp call background processing.
* @param web3 The web3js 1.0 instance to use.
* @param {ReduxStateSelector} getCallsState Gets calls state.
* @return {ReduxSaga} Calls saga.
*/
function* callsSaga(web3, getCallsState) {
yield takeEvery(callsAT.CACHE_CALL, cacheCall, getCallsState);
yield takeEvery(callsAT.FORCE_CALL, forceCall, web3);
yield takeEvery(callsAT.CALL_RETURNED, decodeCall, web3, getCallsState);
}
export default callsSaga;