Skip to content

Commit

Permalink
Merge pull request #590 from hubiinetwork/feature/batch-balance-requests
Browse files Browse the repository at this point in the history
Batch token balance requests
  • Loading branch information
katat committed Nov 2, 2018
2 parents ec46411 + dd27dd0 commit c4113fd
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 921 deletions.
4 changes: 4 additions & 0 deletions src/containers/HubiiApiHoc/reducer.js
Expand Up @@ -18,6 +18,7 @@ import {
LOAD_TRANSACTIONS_SUCCESS,
LOAD_TRANSACTIONS_ERROR,
} from './constants';
import { ADD_NEW_WALLET } from '../WalletHoc/constants';

export const initialState = fromJS({
transactions: {},
Expand Down Expand Up @@ -85,6 +86,9 @@ function hubiiApiHocReducer(state = initialState, action) {
.setIn(['prices', 'loading'], true)
.set('transactions', fromJS({}))
.set('balances', fromJS({}));
case ADD_NEW_WALLET:
return state
.setIn(['balances', action.newWallet.address], fromJS({ loading: true, error: null, assets: [] }));
default:
return state;
}
Expand Down
45 changes: 37 additions & 8 deletions src/containers/HubiiApiHoc/saga.js
Expand Up @@ -12,11 +12,11 @@ import {

import { delay } from 'redux-saga';
import nahmii from 'nahmii-sdk';
import ethers from 'ethers';
import BigNumber from 'bignumber.js';
import { Contract } from 'ethers';

import request, { requestWalletAPI } from 'utils/request';
import erc20Abi from 'utils/abi/erc20Standard';
import rpcRequest from 'utils/rpcRequest';
import { CHANGE_NETWORK, INIT_NETWORK_ACTIVITY } from 'containers/App/constants';
import { makeSelectCurrentNetwork } from 'containers/App/selectors';
import { ADD_NEW_WALLET } from 'containers/WalletHoc/constants';
Expand All @@ -25,6 +25,7 @@ import {
makeSelectWallets,
} from 'containers/WalletHoc/selectors';


import {
LOAD_WALLET_BALANCES, LOAD_SUPPORTED_TOKENS_SUCCESS,
} from './constants';
Expand All @@ -43,6 +44,8 @@ import {
import { makeSelectSupportedAssets } from './selectors';


// https://stackoverflow.com/questions/48228662/get-token-balance-with-ethereum-rpc
const BALANCE_OF_FUNCTION_ID = ('0x70a08231');
export function* loadWalletBalances({ address, noPoll, onlyEth }, _network) {
const network = _network || (yield select(makeSelectCurrentNetwork()));
let supportedAssets = (yield select(makeSelectSupportedAssets())).toJS();
Expand All @@ -58,17 +61,43 @@ export function* loadWalletBalances({ address, noPoll, onlyEth }, _network) {
/**
* temporarily fetching balances from node until the backend is fixed
*/
// get ETH balance
const ethBal = yield network.provider.getBalance(address);
if (onlyEth) {
yield put(loadWalletBalancesSuccess(address, [{ currency: 'ETH', address, decimals: 18, balance: ethBal }]));
return;
}
// get all contract addresses. remove last entry because it's ETH

// get token balances, batching all the requests in an array and sending them all at once
// https://stackoverflow.com/questions/48228662/get-token-balance-with-ethereum-rpc
const tokenContractAddresses = supportedAssets.assets.map((a) => a.currency).slice(0, -1);
const tokenBals = yield all(
tokenContractAddresses.map((addr) => new Contract(addr, erc20Abi, network.provider).balanceOf(address))
);

// the first provider in network.provider.providers in an Infura node, which supports RPC calls
const jsonRpcProvider = network.provider.providers[0];

// pad the 20 byte address to 32 bytes
const paddedAddr = ethers.utils.hexlify(ethers.utils.padZeros(address, 32));

// concat the balanceOf('address') function identifier to the padded address. this shows our intention to call the
// balanceOf method with address as the parameter
const dataArr = ethers.utils.concat([BALANCE_OF_FUNCTION_ID, paddedAddr]);
const data = ethers.utils.hexlify(dataArr);

// send a batch of RPC requests asking for all token balances
// https://www.jsonrpc.org/specification#batch
const requestBatch = tokenContractAddresses.map((contractAddr) => {
const params = [{ from: address, to: contractAddr, data }, 'latest'];
return {
method: 'eth_call',
params,
id: 42,
jsonrpc: '2.0',
};
});
const response = yield rpcRequest(jsonRpcProvider.url, JSON.stringify(requestBatch));

// process and return the response
const tokenBals = response.map((item) => new BigNumber(item.result));
const formattedBalances = tokenBals.reduce((acc, bal, i) => {
if (!bal.gt('0')) return acc;
const { currency } = supportedAssets.assets[i];
Expand All @@ -78,8 +107,8 @@ export function* loadWalletBalances({ address, noPoll, onlyEth }, _network) {
} catch (err) {
yield put(loadWalletBalancesError(address, err));
} finally {
const ONE_MIN_SEC_IN_MS = 1000 * 60;
yield delay(ONE_MIN_SEC_IN_MS);
const TWENTY_SEC_IN_MS = 1000 * 20;
yield delay(TWENTY_SEC_IN_MS);
}
if (noPoll) break;
}
Expand Down
13 changes: 13 additions & 0 deletions src/containers/HubiiApiHoc/tests/reducer.test.js
Expand Up @@ -25,6 +25,7 @@ import {


import hubiiApiHocReducer from '../reducer';
import { addNewWallet } from '../../WalletHoc/actions';

describe('hubiiApiHocReducer', () => {
let state;
Expand Down Expand Up @@ -180,4 +181,16 @@ describe('hubiiApiHocReducer', () => {
expect(hubiiApiHocReducer(testState, changeNetwork('some network'))).toEqual(expected);
});
});

describe('a wallet is added', () => {
it('should reset its balance to loading state', () => {
const newWalletAddr = '0x00';
const testState = state
.setIn(['balances', newWalletAddr], fromJS({ assets: ['123'] }));
const expected = state
.setIn(['balances', newWalletAddr], fromJS({ loading: true, error: null, assets: [] }));

expect(hubiiApiHocReducer(testState, addNewWallet({ address: newWalletAddr }))).toEqual(expected);
});
});
});
11 changes: 1 addition & 10 deletions src/containers/WalletsOverview/index.js
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { createStructuredSelector } from 'reselect';
import { Row, Col, Alert } from 'antd';
import { Row, Col } from 'antd';
import { injectIntl } from 'react-intl';

import { getBreakdown } from 'utils/wallet';
Expand Down Expand Up @@ -113,15 +113,6 @@ export class WalletsOverview extends React.PureComponent { // eslint-disable-lin
<Row type="flex" align="top" gutter={16}>
{walletCards}
</Row>
<Alert
message={formatMessage({ id: 'why_balance_not_updated' })}
description={
<span>{formatMessage({ id: 'my_balances_notes' })}</span>
}
type="info"
showIcon
style={{ margin: '2rem 0' }}
/>
</Col>
<Col sm={24} md={12} lg={8}>
{
Expand Down

0 comments on commit c4113fd

Please sign in to comment.