Skip to content

Commit

Permalink
feat: add useBalance useBalanceOf
Browse files Browse the repository at this point in the history
  • Loading branch information
aboutlo committed Oct 8, 2021
1 parent 97e8707 commit 0f55b47
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 37 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ node_modules
dist
esm
demo
.yalc
*.log
*.tgz
.env
Expand All @@ -11,3 +12,4 @@ demo
examples/**/yarn.lock
package-lock.json
yarn.lock
yalc.lock
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ether-swr",
"version": "0.6.4",
"version": "1.0.0",
"description": "Ether-SWR is a React hook that fetches blockchain data, streamlines the chores to keep the internal state of Decentralized App (DApp) and optimize the RPC calls to an Ethereum node.",
"main": "./dist/index.js",
"module": "./esm/index.js",
Expand All @@ -25,6 +25,7 @@
"prepublishOnly": "yarn run build",
"build:cjs": "ncc build src/index.ts -s -o dist -m -e react -e react-dom -e swr -e web3-react -e Web3Provider",
"build:esm": "tsc --target ESNext --module ES6 --outDir esm",
"publish:local": "yalc publish",
"release:major": "yarn standard-version major",
"release:minor": "yarn standard-version minor",
"release:patch": "yarn standard-version patch",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from './ether-js-fetcher'
export * from './types'
export * from './useEtherSWR'
export * from './useBalance'
export * from './useBalanceOf'
import { default as useEtherSWR } from './useEtherSWR'
export default useEtherSWR
33 changes: 33 additions & 0 deletions src/useBalance.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import useEtherSWR from './useEtherSWR'
import { useWeb3React } from '@web3-react/core'
import { Web3Provider } from './types'
import { BigNumber } from 'ethers'

function sayName({
first,
last = 'Smith'
}: {
first: string
last?: string
}): void {
const name = first + ' ' + last
console.log(name)
}

type Options = {
block: string | number
}
export function useBalance(
address: string,
{ block }: Options = { block: 'latest' }
) {
return useEtherSWR(['getBalance', address, block])
}

export function useBalances(
addresses: string[],
{ block }: Options = { block: 'latest' }
) {
const keys = addresses.map(address => ['getBalance', address, block])
return useEtherSWR<BigNumber[]>(keys)
}
37 changes: 37 additions & 0 deletions src/useBalanceOf.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import useEtherSWR from './useEtherSWR'
import { useWeb3React } from '@web3-react/core'
import { Web3Provider } from './types'
import { BigNumber } from 'ethers'
import { useMemo, useState } from 'react'

export function useBalanceOf<T = BigNumber>(
contractOrContracts: string | string[],
ownerOrOwners: string | string[]
) {
if (Array.isArray(ownerOrOwners) && Array.isArray(contractOrContracts)) {
throw new Error('Either you pass multiple contracts or multiple owners')
}
const key = useMemo(() => {
const owners =
Array.isArray(ownerOrOwners) && typeof contractOrContracts === 'string'
? ownerOrOwners.map(own => [contractOrContracts, 'balanceOf', own])
: undefined

const contracts =
Array.isArray(contractOrContracts) && typeof ownerOrOwners === 'string'
? contractOrContracts.map(cntr => [cntr, 'balanceOf', ownerOrOwners])
: undefined

const keys = owners || contracts || []

const singleKey: [string, any, any] =
ownerOrOwners &&
typeof ownerOrOwners === 'string' &&
typeof contractOrContracts === 'string'
? [contractOrContracts as string, 'balanceOf', ownerOrOwners]
: undefined

return keys.length > 0 ? keys : singleKey
}, [contractOrContracts, ownerOrOwners])
return useEtherSWR<T>(key)
}
38 changes: 2 additions & 36 deletions src/useEtherSWR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,7 @@ import { Provider } from '@ethersproject/providers'

export type etherKeyFuncInterface = () => ethKeyInterface | ethKeysInterface
export type ethKeyInterface = [string, any?, any?, any?, any?]
export type ethKeysInterface = string[][]

const usePrevious = (value, initialValue) => {
const ref = useRef(initialValue)
useEffect(() => {
ref.current = value
})
return ref.current
}

const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => {
const previousDeps = usePrevious(dependencies, [])

const changedDeps = dependencies.reduce((accum, dependency, index) => {
if (dependency !== previousDeps[index]) {
const keyName = dependencyNames[index] || index
return {
...accum,
[keyName]: {
before: previousDeps[index],
after: dependency
}
}
}

return accum
}, {})

if (Object.keys(changedDeps).length) {
console.log('[use-effect-debugger] ', changedDeps)
}

useEffect(effectHook, dependencies)
}
export type ethKeysInterface = any[][]

const getSigner = (config: EthSWRConfigInterface) => {
if (config.signer) {
Expand Down Expand Up @@ -111,11 +78,10 @@ function useEtherSWR<Data = any, Error = any>(
? [_key[0][0]] // pick the first element of the list.
: _key

const { cache } = useSWRConfig()
// we need to serialize the key as string otherwise
// a new array is created everytime the component is rendered
// we follow SWR format
const { cache } = useSWRConfig()

const normalizeKey = isMulticall ? JSON.stringify(_key) : _key

// base methods (e.g. getBalance, getBlockNumber, etc)
Expand Down
80 changes: 80 additions & 0 deletions test/useBalance.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { mockFetcher, mockMultipleFetch, mockUseWeb3React } from './util/utils'
import useEtherSWR, { etherJsFetcher } from '../src'
import { render, waitFor } from '@testing-library/react'
import { DefaultContainer } from './util/components/DefaultContainer'
import * as React from 'react'
import { useWeb3React } from '@web3-react/core'
import { Contract } from '@ethersproject/contracts'
import { useBalance, useBalances } from '../src/useBalance'
import { BigNumber } from 'ethers'

jest.mock('../src/ether-js-fetcher')
jest.mock('@web3-react/core')
jest.mock('@ethersproject/contracts')

const mockedEthFetcher = etherJsFetcher as jest.Mock
const mockeduseWeb3React = useWeb3React as jest.Mock

describe('useBalance', () => {
const account = '0x001'

beforeEach(() => {
mockedEthFetcher.mockReset()
mockUseWeb3React(mockeduseWeb3React, { account })
})

it('returns the ether balance of an account', async () => {
const account = '0x002'
const mockData = 10
const fetcher = mockFetcher(mockedEthFetcher, mockData)

function Page() {
const { data } = useBalance(account)
return <div>Balance, {data}</div>
}

const { container } = render(
<DefaultContainer fetcher={mockedEthFetcher}>
<Page />
</DefaultContainer>
)

await waitFor(() => {
expect(container.firstChild.textContent).toEqual(`Balance, ${mockData}`)
expect(fetcher).toBeCalledWith('getBalance', account, 'latest')
})
})

it('returns the ether balances of a list of account', async () => {
const account = '0x002'
const mockData = BigNumber.from(10)
const fetcher = mockFetcher(mockedEthFetcher, [mockData])

function Page() {
const { data: balances } = useBalances([account])
if (!balances) {
return <div>Loading</div>
}
return (
<>
{balances.map((balance, index) => {
return <div key={index}>Balance, {balance.toString()}</div>
})}
</>
)
}

const { container } = render(
<DefaultContainer fetcher={mockedEthFetcher}>
<Page />
</DefaultContainer>
)

await waitFor(() => {
expect(container.textContent).toEqual(`Balance, ${mockData}`)
expect(fetcher).toBeCalledWith(
JSON.stringify([['getBalance', account, 'latest']])
)
})
})
})
131 changes: 131 additions & 0 deletions test/useBalanceOf.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {
mockContract,
mockFetcher,
mockMultipleFetch,
mockUseWeb3React
} from './util/utils'
import { etherJsFetcher } from '../src'
import { render, waitFor } from '@testing-library/react'
import { DefaultContainer } from './util/components/DefaultContainer'
import * as React from 'react'
import { useWeb3React } from '@web3-react/core'
import { Contract } from '@ethersproject/contracts'
import { BigNumber } from 'ethers'
import { useBalanceOf } from '../src/useBalanceOf'
import { contracts } from '../src/utils'

jest.mock('../src/ether-js-fetcher')
jest.mock('@web3-react/core')
jest.mock('@ethersproject/contracts')

const mockedEthFetcher = etherJsFetcher as jest.Mock
const mockeduseWeb3React = useWeb3React as jest.Mock
const mockedContract = (Contract as unknown) as jest.Mock

describe('useBalanceOf', () => {
const account = '0x001'
const contractAddr = '0x6126A4C0Eb7822C12Bea32327f1706F035b414bf'
let contractInstance

beforeEach(() => {
jest.clearAllMocks()
mockUseWeb3React(mockeduseWeb3React, { account })
contractInstance = mockContract(mockedContract)
contracts.clear()
})

it('returns the balanceOf from an owner', async () => {
const mockData = BigNumber.from(10)
const fetcher = mockFetcher(mockedEthFetcher, mockData)
const anotherAccount = '0x003'

function Page() {
const { data } = useBalanceOf(contractAddr, anotherAccount)
if (!data) {
return <div>Loading</div>
}
return <div>Balance, {data.toString()}</div>
}

const { container } = render(
<DefaultContainer contractAddr={contractAddr} fetcher={mockedEthFetcher}>
<Page />
</DefaultContainer>
)

await waitFor(() => {
expect(container.firstChild.textContent).toEqual(`Balance, ${mockData}`)
expect(fetcher).toBeCalledWith(contractAddr, 'balanceOf', anotherAccount)
})
})

it('returns the balanceOf from multiple owners', async () => {
const mockData = BigNumber.from(10)
const fetcher = mockFetcher(mockedEthFetcher, [mockData])

function Page() {
const { data: balances } = useBalanceOf<BigNumber[]>(contractAddr, [
account
])
if (!balances) {
return <div>Loading</div>
}
return (
<>
{balances.map((balance, index) => {
return <div key={index}>Balance, {balance.toString()}</div>
})}
</>
)
}

const { container } = render(
<DefaultContainer contractAddr={contractAddr} fetcher={mockedEthFetcher}>
<Page />
</DefaultContainer>
)

await waitFor(() => {
expect(container.textContent).toEqual(`Balance, ${mockData}`)
expect(fetcher).toBeCalledWith(
JSON.stringify([[contractAddr, 'balanceOf', account]])
)
})
})

it('returns the balanceOf from multiple contract of the same owner', async () => {
const anotherAccount = '0x003'
const mockData = BigNumber.from(10)
const fetcher = mockFetcher(mockedEthFetcher, [mockData])

function Page() {
const { data: balances } = useBalanceOf<BigNumber[]>(
[contractAddr],
anotherAccount
)
if (!balances) {
return <div>Loading</div>
}
return (
<>
{balances.map((balance, index) => {
return <div key={index}>Balance, {balance.toString()}</div>
})}
</>
)
}

const { container } = render(
<DefaultContainer contractAddr={contractAddr} fetcher={mockedEthFetcher}>
<Page />
</DefaultContainer>
)

await waitFor(() => {
expect(container.textContent).toEqual(`Balance, ${mockData}`)
expect(fetcher).toBeCalledWith(
JSON.stringify([[contractAddr, 'balanceOf', anotherAccount]])
)
})
})
})
Loading

0 comments on commit 0f55b47

Please sign in to comment.