diff --git a/src/components/layout/Paragraph/index.scss b/src/components/layout/Paragraph/index.scss index ea3a5550..ed0652fd 100644 --- a/src/components/layout/Paragraph/index.scss +++ b/src/components/layout/Paragraph/index.scss @@ -4,15 +4,15 @@ } .soft { - color: #888888; + color: #888888; } .medium { - color: #686868; + color: #686868; } .dark { - color: black; + color: black; } .primary { diff --git a/src/routes/open/components/Layout.test.js b/src/routes/open/components/Layout.test.js index b41ed29f..4193d974 100644 --- a/src/routes/open/components/Layout.test.js +++ b/src/routes/open/components/Layout.test.js @@ -1,33 +1,18 @@ // @flow -import * as React from 'react' import TestUtils from 'react-dom/test-utils' -import Open from '~/routes/open/container/Open' -import { Provider } from 'react-redux' -import { ConnectedRouter } from 'react-router-redux' +import { store } from '~/store' import { FIELD_NAME, FIELD_OWNERS, FIELD_CONFIRMATIONS, getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields' import { DEPLOYED_COMPONENT_ID } from '~/routes/open/components/FormConfirmation' -import { history, store } from '~/store' import { sleep } from '~/utils/timer' import { getProviderInfo } from '~/wallets/getWeb3' -import addProvider from '~/wallets/store/actions/addProvider' -import { makeProvider } from '~/wallets/store/model/provider' +import { renderSafe } from '~/routes/safe/store/test/builder/deployedSafe.builder' describe('React DOM TESTS > Create Safe form', () => { let open let provider beforeEach(async () => { - // init app web3 instance provider = await getProviderInfo() - const walletRecord = makeProvider(provider) - store.dispatch(addProvider(walletRecord)) - - open = TestUtils.renderIntoDocument(( - - - - - - )) + open = await renderSafe(store) }) it('should create a 1 owner safe after rendering correctly the form', async () => { diff --git a/src/routes/safe/component/Layout.jsx b/src/routes/safe/component/Layout.jsx index 681b227e..e3b7f636 100644 --- a/src/routes/safe/component/Layout.jsx +++ b/src/routes/safe/component/Layout.jsx @@ -6,10 +6,10 @@ import GnoSafe from './Safe' type Props = SelectorProps -const Layout = ({ safe, provider }: Props) => ( +const Layout = ({ safe, balance, provider }: Props) => ( { safe - ? + ? : } diff --git a/src/routes/safe/component/Layout.stories.js b/src/routes/safe/component/Layout.stories.js index ab6db99d..83cfecea 100644 --- a/src/routes/safe/component/Layout.stories.js +++ b/src/routes/safe/component/Layout.stories.js @@ -2,7 +2,7 @@ import { storiesOf } from '@storybook/react' import * as React from 'react' import styles from '~/components/layout/PageFrame/index.scss' -import { SafeFactory } from '~/routes/safe/store/test/builder/index.builder' +import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder' import Component from './Layout' @@ -14,12 +14,31 @@ const FrameDecorator = story => ( storiesOf('Routes /safe:address', module) .addDecorator(FrameDecorator) - .add('Safe undefined being connected', () => ) - .add('Safe undefined NOT connected', () => ) + .add('Safe undefined being connected', () => ( + {}} + /> + )) + .add('Safe undefined NOT connected', () => ( + {}} + /> + )) .add('Safe with 2 owners', () => { const safe = SafeFactory.twoOwnersSafe return ( - + {}} + /> ) }) diff --git a/src/routes/safe/component/Safe.jsx b/src/routes/safe/component/Safe.jsx index 7c7b4dd9..69b86137 100644 --- a/src/routes/safe/component/Safe.jsx +++ b/src/routes/safe/component/Safe.jsx @@ -10,9 +10,10 @@ import { type Safe } from '~/routes/safe/store/model/safe' type SafeProps = { safe: Safe, + balance: string, } -const GnoSafe = ({ safe }: SafeProps) => ( +const GnoSafe = ({ safe, balance }: SafeProps) => ( @@ -21,6 +22,18 @@ const GnoSafe = ({ safe }: SafeProps) => ( + + + Balance + + + + + + {balance} - ETH + + + Address diff --git a/src/routes/safe/container/actions.js b/src/routes/safe/container/actions.js new file mode 100644 index 00000000..e489176c --- /dev/null +++ b/src/routes/safe/container/actions.js @@ -0,0 +1,10 @@ +// @flow +import fetchBalance from '~/routes/safe/store/actions/fetchBalance' + +export type Actions = { + fetchBalance: typeof fetchBalance, +} + +export default { + fetchBalance, +} diff --git a/src/routes/safe/container/index.jsx b/src/routes/safe/container/index.jsx index 348fd17f..e4575fac 100644 --- a/src/routes/safe/container/index.jsx +++ b/src/routes/safe/container/index.jsx @@ -4,19 +4,40 @@ import { connect } from 'react-redux' import Page from '~/components/layout/Page' import Layout from '~/routes/safe/component/Layout' import selector, { type SelectorProps } from './selector' +import actions, { type Actions } from './actions' -type Props = SelectorProps +type Props = Actions & SelectorProps class SafeView extends React.PureComponent { + componentDidMount() { + this.intervalId = setInterval(() => { + const { safe, fetchBalance } = this.props + if (!safe) { return } + + const safeAddress: string = safe.get('address') + fetchBalance(safeAddress) + }, 1500) + } + + componentWillUnmount() { + clearInterval(this.intervalId) + } + + intervalId: IntervalID + render() { - const { safe, provider } = this.props + const { safe, provider, balance } = this.props return ( - + ) } } -export default connect(selector)(SafeView) +export default connect(selector, actions)(SafeView) diff --git a/src/routes/safe/container/selector.js b/src/routes/safe/container/selector.js index 86af6918..3ac175f5 100644 --- a/src/routes/safe/container/selector.js +++ b/src/routes/safe/container/selector.js @@ -1,14 +1,16 @@ // @flow import { createStructuredSelector } from 'reselect' -import { safeSelector, type SafeSelectorProps } from '~/routes/safe/store/selectors' +import { balanceSelector, safeSelector, type SafeSelectorProps } from '~/routes/safe/store/selectors' import { providerNameSelector } from '~/wallets/store/selectors/index' export type SelectorProps = { safe: SafeSelectorProps, provider: string, + balance: string, } export default createStructuredSelector({ safe: safeSelector, provider: providerNameSelector, + balance: balanceSelector, }) diff --git a/src/routes/safe/store/actions/addBalance.js b/src/routes/safe/store/actions/addBalance.js new file mode 100644 index 00000000..7051769b --- /dev/null +++ b/src/routes/safe/store/actions/addBalance.js @@ -0,0 +1,19 @@ +// @flow +import { createAction } from 'redux-actions' + +export const ADD_BALANCE = 'ADD_BALANCE' + +type BalanceProps = { + safeAddress: string, + funds: string, +} + +const addBalance = createAction( + ADD_BALANCE, + (safeAddress: string, funds: string): BalanceProps => ({ + safeAddress, + funds, + }), +) + +export default addBalance diff --git a/src/routes/safe/store/actions/fetchBalance.js b/src/routes/safe/store/actions/fetchBalance.js new file mode 100644 index 00000000..430682d8 --- /dev/null +++ b/src/routes/safe/store/actions/fetchBalance.js @@ -0,0 +1,11 @@ +// @flow +import type { Dispatch as ReduxDispatch } from 'redux' +import { getBalanceInEtherOf } from '~/wallets/getWeb3' +import { type GlobalState } from '~/store/index' +import addBalance from './addBalance' + +export default (safeAddress: string) => async (dispatch: ReduxDispatch) => { + const balance = await getBalanceInEtherOf(safeAddress) + + return dispatch(addBalance(safeAddress, balance)) +} diff --git a/src/routes/safe/store/reducer/balances.js b/src/routes/safe/store/reducer/balances.js new file mode 100644 index 00000000..518db150 --- /dev/null +++ b/src/routes/safe/store/reducer/balances.js @@ -0,0 +1,13 @@ +// @flow +import { Map } from 'immutable' +import { handleActions, type ActionType } from 'redux-actions' +import addBalance, { ADD_BALANCE } from '~/routes/safe/store/actions/addBalance' + +export const BALANCE_REDUCER_ID = 'balances' + +export type State = Map + +export default handleActions({ + [ADD_BALANCE]: (state: State, action: ActionType): State => + state.set(action.payload.safeAddress, action.payload.funds), +}, Map()) diff --git a/src/routes/safe/store/selectors/index.js b/src/routes/safe/store/selectors/index.js index 4c403eb5..1bcd96f2 100644 --- a/src/routes/safe/store/selectors/index.js +++ b/src/routes/safe/store/selectors/index.js @@ -6,6 +6,7 @@ import { type GlobalState } from '~/store/index' import { SAFE_PARAM_ADDRESS } from '~/routes/routes' import { type Safe } from '~/routes/safe/store/model/safe' import { safesMapSelector } from '~/routes/safeList/store/selectors' +import { BALANCE_REDUCER_ID } from '~/routes/safe/store/reducer/balances' type RouterProps = { match: Match, @@ -13,6 +14,8 @@ type RouterProps = { const safeAddessSelector = (state: GlobalState, props: RouterProps) => props.match.params[SAFE_PARAM_ADDRESS] || '' +const balancesSelector = (state: GlobalState) => state[BALANCE_REDUCER_ID] + export type SafeSelectorProps = Safe | typeof undefined export const safeSelector: Selector = createSelector( @@ -27,6 +30,18 @@ export const safeSelector: Selector }, ) +export const balanceSelector: Selector = createSelector( + balancesSelector, + safeAddessSelector, + (balances: Map, address: string) => { + if (!address) { + return '0' + } + + return balances.get(address) || '0' + }, +) + export default createStructuredSelector({ safe: safeSelector, }) diff --git a/src/routes/safe/store/test/balance.reducer.js b/src/routes/safe/store/test/balance.reducer.js new file mode 100644 index 00000000..923fc449 --- /dev/null +++ b/src/routes/safe/store/test/balance.reducer.js @@ -0,0 +1,54 @@ +// @flow +import { BALANCE_REDUCER_ID } from '~/routes/safe/store/reducer/balances' +import fetchBalance from '~/routes/safe/store/actions/fetchBalance' +import { aNewStore } from '~/store' +import { getWeb3 } from '~/wallets/getWeb3' +import { promisify } from '~/utils/promisify' +import { aDeployedSafe } from './builder/deployedSafe.builder' + +const addEtherTo = async (address: string, eth: string) => { + const web3 = getWeb3() + const accounts = await promisify(cb => web3.eth.getAccounts(cb)) + const txData = { from: accounts[0], to: address, value: web3.toWei(eth, 'ether') } + return promisify(cb => web3.eth.sendTransaction(txData, cb)) +} + +const balanceReducerTests = () => { + describe('Safe Actions[fetchBalance]', () => { + let store + beforeEach(async () => { + store = aNewStore() + }) + + it('reducer should return 0 to just deployed safe', async () => { + // GIVEN + const safeTx = await aDeployedSafe(store) + const address = safeTx.contractAddress + + // WHEN + await store.dispatch(fetchBalance(address)) + + // THEN + const balances = store.getState()[BALANCE_REDUCER_ID] + expect(balances).not.toBe(undefined) + expect(balances.get(address)).toBe('0') + }) + + it('reducer should return 1.3456 ETH as funds to safe with 1 ETH', async () => { + // GIVEN + const safeTx = await aDeployedSafe(store) + const address = safeTx.contractAddress + + // WHEN + await addEtherTo(address, '1.3456') + await store.dispatch(fetchBalance(address)) + + // THEN + const balances = store.getState()[BALANCE_REDUCER_ID] + expect(balances).not.toBe(undefined) + expect(balances.get(address)).toBe('1.3456') + }) + }) +} + +export default balanceReducerTests diff --git a/src/routes/safe/store/test/balance.selector.js b/src/routes/safe/store/test/balance.selector.js new file mode 100644 index 00000000..b6b850e4 --- /dev/null +++ b/src/routes/safe/store/test/balance.selector.js @@ -0,0 +1,61 @@ +// @flow +import { type Match } from 'react-router-dom' +import addBalance from '~/routes/safe/store/actions/addBalance' +import { aNewStore } from '~/store' +import { balanceSelector } from '../selectors' + +const buildMathPropsFrom = (address): Match => ({ + params: { + address, + }, + isExact: true, + path: '', + url: '', +}) + +const balanceSelectorTests = () => { + describe('Safe Selector[balanceSelector]', () => { + it('should return 0 when safe address is not found', () => { + // GIVEN + const safeAddress = 'foo' + const match = buildMathPropsFrom(safeAddress) + const store = aNewStore() + + // WHEN + const balance = balanceSelector(store.getState(), { match }) + + // THEN + expect(balance).toBe('0') + }) + + it('should return 0 when safe has no funds', async () => { + // GIVEN + const safeAddress = 'foo' + const match = buildMathPropsFrom(safeAddress) + const store = aNewStore() + + // WHEN + await store.dispatch(addBalance('bar', '1')) + const balance = balanceSelector(store.getState(), { match }) + + // THEN + expect(balance).toBe('0') + }) + + it('should return safe funds', async () => { + // GIVEN + const safeAddress = 'foo' + const match = buildMathPropsFrom(safeAddress) + const store = aNewStore() + + // WHEN + await store.dispatch(addBalance(safeAddress, '1.3456')) + const balance = balanceSelector(store.getState(), { match }) + + // THEN + expect(balance).toBe('1.3456') + }) + }) +} + +export default balanceSelectorTests diff --git a/src/routes/safe/store/test/builder/deployedSafe.builder.js b/src/routes/safe/store/test/builder/deployedSafe.builder.js new file mode 100644 index 00000000..34bb71b0 --- /dev/null +++ b/src/routes/safe/store/test/builder/deployedSafe.builder.js @@ -0,0 +1,72 @@ +// @flow +import * as React from 'react' +import { type Store } from 'redux' +import TestUtils from 'react-dom/test-utils' +import { Provider } from 'react-redux' +import { ConnectedRouter } from 'react-router-redux' +import { DEPLOYED_COMPONENT_ID } from '~/routes/open/components/FormConfirmation' +import Open from '~/routes/open/container/Open' +import { history, type GlobalState } from '~/store' +import { sleep } from '~/utils/timer' +import { getProviderInfo } from '~/wallets/getWeb3' +import addProvider from '~/wallets/store/actions/addProvider' +import { makeProvider } from '~/wallets/store/model/provider' + +export const renderSafe = async (localStore: Store) => { + const provider = await getProviderInfo() + const walletRecord = makeProvider(provider) + localStore.dispatch(addProvider(walletRecord)) + + return ( + TestUtils.renderIntoDocument(( + + + + + + )) + ) +} + +const deploySafe = async (safe: React$Component<{}>) => { + const inputs = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'input') + const fieldName = inputs[0] + const fieldOwners = inputs[1] + const fieldConfirmations = inputs[2] + + TestUtils.Simulate.change(fieldOwners, { target: { value: '1' } }) + const inputsExpanded = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'input') + const ownerName = inputsExpanded[2] + + TestUtils.Simulate.change(fieldName, { target: { value: 'Adolfo Safe' } }) + TestUtils.Simulate.change(fieldConfirmations, { target: { value: '1' } }) + TestUtils.Simulate.change(ownerName, { target: { value: 'Adolfo Eth Account' } }) + + const form = TestUtils.findRenderedDOMComponentWithTag(safe, 'form') + + TestUtils.Simulate.submit(form) // fill the form + TestUtils.Simulate.submit(form) // confirming data + TestUtils.Simulate.submit(form) // Executing transaction + + // giving some time to the component for updating its state with safe + // before destroying its context + await sleep(1500) + + // THEN + const deployed = TestUtils.findRenderedDOMComponentWithClass(safe, DEPLOYED_COMPONENT_ID) + if (!deployed) { + throw new Error() + } + + const transactionHash = JSON.parse(deployed.getElementsByTagName('pre')[0].innerHTML) + delete transactionHash.logsBloom + + return transactionHash +} + +export const aDeployedSafe = async (specificStore: Store) => { + const safe: React$Component<{}> = await renderSafe(specificStore) + const deployedSafe = deploySafe(safe) + + return deployedSafe +} diff --git a/src/routes/safe/store/test/builder/index.builder.js b/src/routes/safe/store/test/builder/safe.builder.js similarity index 100% rename from src/routes/safe/store/test/builder/index.builder.js rename to src/routes/safe/store/test/builder/safe.builder.js diff --git a/src/routes/safe/store/test/safe.reducer.js b/src/routes/safe/store/test/safe.reducer.js index 72f58646..c9c241df 100644 --- a/src/routes/safe/store/test/safe.reducer.js +++ b/src/routes/safe/store/test/safe.reducer.js @@ -5,7 +5,7 @@ import safeReducer, { calculateInitialState, SAFE_REDUCER_ID } from '~/routes/sa import addSafe from '~/routes/safe/store/actions/addSafe' import * as SafeFields from '~/routes/open/components/fields' import { getAccountsFrom, getNamesFrom } from '~/routes/open/utils/safeDataExtractor' -import { SafeFactory } from './builder/index.builder' +import { SafeFactory } from './builder/safe.builder' const aStore = (initState) => { const reducers = combineReducers({ diff --git a/src/routes/safe/store/test/safe.selector.js b/src/routes/safe/store/test/safe.selector.js index 33f3d75b..c8ba8f71 100644 --- a/src/routes/safe/store/test/safe.selector.js +++ b/src/routes/safe/store/test/safe.selector.js @@ -3,7 +3,7 @@ import { Map } from 'immutable' import { type Match } from 'react-router-dom' import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe' import { type Safe } from '~/routes/safe/store/model/safe' -import { SafeFactory } from '~/routes/safe/store/test/builder/index.builder' +import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder' import { safeSelector } from '../selectors' const buildMathPropsFrom = (address): Match => ({ @@ -19,7 +19,11 @@ const safeSelectorTests = () => { describe('Safe Selector[safeSelector]', () => { it('should return empty list when no safes', () => { // GIVEN - const reduxStore = { [SAFE_REDUCER_ID]: Map(), providers: undefined } + const reduxStore = { + [SAFE_REDUCER_ID]: Map(), + providers: undefined, + balances: undefined, + } const match: Match = buildMathPropsFrom('fooAddress') // WHEN @@ -38,7 +42,11 @@ const safeSelectorTests = () => { const match: Match = buildMathPropsFrom('fooAddress') const undefMatch: Match = buildMathPropsFrom('inventedAddress') - const reduxStore = { [SAFE_REDUCER_ID]: map, providers: undefined } + const reduxStore = { + [SAFE_REDUCER_ID]: map, + providers: undefined, + balances: undefined, + } // WHEN const oneOwnerSafe = safeSelector(reduxStore, { match }) diff --git a/src/routes/safe/store/test/safe.spec.js b/src/routes/safe/store/test/safe.spec.js index 80febf06..ea07c3ee 100644 --- a/src/routes/safe/store/test/safe.spec.js +++ b/src/routes/safe/store/test/safe.spec.js @@ -1,11 +1,17 @@ // @flow +import balanceReducerTests from './balance.reducer' import safeReducerTests from './safe.reducer' +import balanceSelectorTests from './balance.selector' import safeSelectorTests from './safe.selector' describe('Safe Test suite', () => { // ACTIONS AND REDUCERS safeReducerTests() + balanceReducerTests() // SAFE SELECTOR safeSelectorTests() + + // BALANCE SELECTOR + balanceSelectorTests() }) diff --git a/src/routes/safeList/components/Layout.stories.js b/src/routes/safeList/components/Layout.stories.js index 74790891..2887270f 100644 --- a/src/routes/safeList/components/Layout.stories.js +++ b/src/routes/safeList/components/Layout.stories.js @@ -3,7 +3,7 @@ import { storiesOf } from '@storybook/react' import { List } from 'immutable' import * as React from 'react' import styles from '~/components/layout/PageFrame/index.scss' -import { SafeFactory } from '~/routes/safe/store/test/builder/index.builder' +import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder' import Component from './Layout' diff --git a/src/routes/safeList/store/test/safe.spec.js b/src/routes/safeList/store/test/safeList.spec.js similarity index 100% rename from src/routes/safeList/store/test/safe.spec.js rename to src/routes/safeList/store/test/safeList.spec.js diff --git a/src/routes/safeList/store/test/safes.selector.js b/src/routes/safeList/store/test/safes.selector.js index 4fa5cb4c..808547df 100644 --- a/src/routes/safeList/store/test/safes.selector.js +++ b/src/routes/safeList/store/test/safes.selector.js @@ -2,14 +2,18 @@ import { List, Map } from 'immutable' import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe' import { type Safe } from '~/routes/safe/store/model/safe' -import { SafeFactory } from '~/routes/safe/store/test/builder/index.builder' +import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder' import { safesListSelector } from '../selectors' const safesListSelectorTests = () => { describe('Safes Selector[safesSelector]', () => { it('should return empty list when no safes', () => { // GIVEN - const reduxStore = { [SAFE_REDUCER_ID]: Map(), providers: undefined } + const reduxStore = { + [SAFE_REDUCER_ID]: Map(), + providers: undefined, + balances: undefined, + } const emptyList = List([]) // WHEN @@ -25,7 +29,11 @@ const safesListSelectorTests = () => { map = map.set('fooAddress', SafeFactory.oneOwnerSafe) map = map.set('barAddress', SafeFactory.twoOwnersSafe) - const reduxStore = { [SAFE_REDUCER_ID]: map, providers: undefined } + const reduxStore = { + [SAFE_REDUCER_ID]: map, + providers: undefined, + balances: undefined, + } // WHEN const safes = safesListSelector(reduxStore) diff --git a/src/store/index.js b/src/store/index.js index 57d1701e..882978f1 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -5,6 +5,7 @@ import { combineReducers, createStore, applyMiddleware, compose, type Reducer, t import thunk from 'redux-thunk' import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/wallets/store/reducer/provider' import safe, { SAFE_REDUCER_ID, calculateInitialState, type State as SafeState } from '~/routes/safe/store/reducer/safe' +import balances, { BALANCE_REDUCER_ID, type State as BalancesState } from '~/routes/safe/store/reducer/balances' export const history = createBrowserHistory() @@ -18,14 +19,19 @@ const finalCreateStore = composeEnhancers(applyMiddleware( export type GlobalState = { providers: ProviderState, safes: SafeState, + balances: BalancesState, } const reducers: Reducer = combineReducers({ routing: routerReducer, [PROVIDER_REDUCER_ID]: provider, [SAFE_REDUCER_ID]: safe, + [BALANCE_REDUCER_ID]: balances, }) const initialState = { [SAFE_REDUCER_ID]: calculateInitialState() } export const store: Store = createStore(reducers, initialState, finalCreateStore) + +export const aNewStore = (localState?: Object): Store => + createStore(reducers, localState, finalCreateStore) diff --git a/src/utils/singleton.js b/src/utils/singleton.js new file mode 100644 index 00000000..5517a770 --- /dev/null +++ b/src/utils/singleton.js @@ -0,0 +1,14 @@ +// @flow +export const ensureOnce = (fn: Function): Function => { + let executed = false + let response + + return (...args) => { + if (executed) { return response } + + executed = true + response = fn(args) + + return response + } +} diff --git a/src/wallets/getWeb3.js b/src/wallets/getWeb3.js index 66460ebd..aa57987b 100644 --- a/src/wallets/getWeb3.js +++ b/src/wallets/getWeb3.js @@ -1,4 +1,5 @@ // @flow +import { BigNumber } from 'bignumber.js' import Web3 from 'web3' import type { ProviderProps } from '~/wallets/store/model/provider' import { promisify } from '~/utils/promisify' @@ -42,16 +43,11 @@ export const getProviderInfo: Function = async (): Promise => { } } -export const ensureOnce = (fn: Function): Function => { - let executed = false - let response - - return (...args) => { - if (executed) { return response } - - executed = true - response = fn(args) - - return response +export const getBalanceInEtherOf = async (safeAddress: string) => { + const funds: BigNumber = await promisify(cb => web3.eth.getBalance(safeAddress, cb)) + if (!funds) { + return '0' } + + return web3.fromWei(funds.toNumber(), 'ether').toString() }