Merge branch 'development' of github.com:gnosis/safe-react into fix/allow-repick
This commit is contained in:
commit
6611027b35
|
@ -1,5 +1,5 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
build/
|
./build
|
||||||
.DS_Store
|
.DS_Store
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
.env*
|
.env*
|
||||||
|
|
49
package.json
49
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "safe-react",
|
"name": "safe-react",
|
||||||
"version": "2.5.0",
|
"version": "2.5.2",
|
||||||
"description": "Allowing crypto users manage funds in a safer way",
|
"description": "Allowing crypto users manage funds in a safer way",
|
||||||
"website": "https://github.com/gnosis/safe-react#readme",
|
"website": "https://github.com/gnosis/safe-react#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -151,28 +151,29 @@
|
||||||
"@gnosis.pm/safe-contracts": "1.1.1-dev.2",
|
"@gnosis.pm/safe-contracts": "1.1.1-dev.2",
|
||||||
"@gnosis.pm/safe-react-components": "^0.1.3",
|
"@gnosis.pm/safe-react-components": "^0.1.3",
|
||||||
"@gnosis.pm/util-contracts": "2.0.6",
|
"@gnosis.pm/util-contracts": "2.0.6",
|
||||||
"@ledgerhq/hw-transport-node-hid": "5.16.0",
|
"@ledgerhq/hw-transport-node-hid": "5.19.0",
|
||||||
"@material-ui/core": "4.10.1",
|
"@material-ui/core": "4.11.0",
|
||||||
"@material-ui/icons": "4.9.1",
|
"@material-ui/icons": "4.9.1",
|
||||||
"@material-ui/lab": "4.0.0-alpha.39",
|
"@material-ui/lab": "4.0.0-alpha.39",
|
||||||
"@openzeppelin/contracts": "3.0.2",
|
"@openzeppelin/contracts": "3.1.0",
|
||||||
"async-sema": "^3.1.0",
|
"async-sema": "^3.1.0",
|
||||||
"axios": "0.19.2",
|
"axios": "0.19.2",
|
||||||
"bignumber.js": "9.0.0",
|
"bignumber.js": "9.0.0",
|
||||||
"bnc-onboard": "1.10.0",
|
"bnc-onboard": "1.10.2",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"concurrently": "^5.2.0",
|
"concurrently": "^5.2.0",
|
||||||
"connected-react-router": "6.8.0",
|
"connected-react-router": "6.8.0",
|
||||||
"currency-flags": "2.1.2",
|
"currency-flags": "2.1.2",
|
||||||
"date-fns": "2.14.0",
|
"date-fns": "2.14.0",
|
||||||
"electron-is-dev": "^1.1.0",
|
"electron-is-dev": "^1.1.0",
|
||||||
"electron-log": "4.2.1",
|
"electron-log": "4.2.2",
|
||||||
"electron-settings": "^4.0.0",
|
"electron-settings": "4.0.2",
|
||||||
"electron-updater": "4.3.1",
|
"electron-updater": "4.3.1",
|
||||||
"eth-sig-util": "^2.5.3",
|
"eth-sig-util": "^2.5.3",
|
||||||
"ethereum-blockies-base64": "^1.0.2",
|
"ethereum-blockies-base64": "^1.0.2",
|
||||||
|
"exponential-backoff": "^3.0.1",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"final-form": "^4.20.0",
|
"final-form": "4.20.1",
|
||||||
"final-form-calculate": "^1.3.1",
|
"final-form-calculate": "^1.3.1",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"immortal-db": "^1.0.2",
|
"immortal-db": "^1.0.2",
|
||||||
|
@ -182,9 +183,9 @@
|
||||||
"material-ui-search-bar": "^1.0.0-beta.13",
|
"material-ui-search-bar": "^1.0.0-beta.13",
|
||||||
"notistack": "https://github.com/gnosis/notistack.git#v0.9.4",
|
"notistack": "https://github.com/gnosis/notistack.git#v0.9.4",
|
||||||
"open": "^7.0.3",
|
"open": "^7.0.3",
|
||||||
"polished": "3.6.4",
|
"polished": "3.6.5",
|
||||||
"qrcode.react": "1.0.0",
|
"qrcode.react": "1.0.0",
|
||||||
"query-string": "6.13.0",
|
"query-string": "6.13.1",
|
||||||
"react": "16.13.1",
|
"react": "16.13.1",
|
||||||
"react-dom": "16.13.1",
|
"react-dom": "16.13.1",
|
||||||
"react-final-form": "^6.5.0",
|
"react-final-form": "^6.5.0",
|
||||||
|
@ -204,41 +205,41 @@
|
||||||
"semver": "7.3.2",
|
"semver": "7.3.2",
|
||||||
"styled-components": "^5.0.1",
|
"styled-components": "^5.0.1",
|
||||||
"truffle-contract": "4.0.31",
|
"truffle-contract": "4.0.31",
|
||||||
"web3": "1.2.8"
|
"web3": "1.2.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/jest-dom": "5.9.0",
|
"@testing-library/jest-dom": "5.11.0",
|
||||||
"@testing-library/react": "10.2.1",
|
"@testing-library/react": "10.4.3",
|
||||||
"@testing-library/user-event": "11.3.1",
|
"@testing-library/user-event": "11.3.1",
|
||||||
"@types/jest": "^25.2.1",
|
"@types/jest": "^25.2.1",
|
||||||
"@types/node": "14.0.12",
|
"@types/node": "14.0.14",
|
||||||
"@types/react": "^16.9.32",
|
"@types/react": "^16.9.32",
|
||||||
"@types/react-dom": "^16.9.6",
|
"@types/react-dom": "^16.9.6",
|
||||||
"@types/styled-components": "^5.1.0",
|
"@types/styled-components": "^5.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "3.2.0",
|
"@typescript-eslint/eslint-plugin": "3.5.0",
|
||||||
"@typescript-eslint/parser": "3.2.0",
|
"@typescript-eslint/parser": "3.5.0",
|
||||||
"autoprefixer": "9.8.0",
|
"autoprefixer": "9.8.4",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"dotenv-expand": "^5.1.0",
|
"dotenv-expand": "^5.1.0",
|
||||||
"electron": "7.1.8",
|
"electron": "7.2.4",
|
||||||
"electron-builder": "22.7.0",
|
"electron-builder": "22.7.0",
|
||||||
"electron-notarize": "0.3.0",
|
"electron-notarize": "0.3.0",
|
||||||
"eslint": "6.8.0",
|
"eslint": "6.8.0",
|
||||||
"eslint-config-prettier": "6.11.0",
|
"eslint-config-prettier": "6.11.0",
|
||||||
"eslint-plugin-import": "2.21.1",
|
"eslint-plugin-import": "2.22.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||||
"eslint-plugin-prettier": "^3.1.2",
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
"eslint-plugin-react": "^7.18.3",
|
"eslint-plugin-react": "7.20.3",
|
||||||
"eslint-plugin-sort-destructure-keys": "1.3.4",
|
"eslint-plugin-sort-destructure-keys": "1.3.5",
|
||||||
"ethereumjs-abi": "0.6.8",
|
"ethereumjs-abi": "0.6.8",
|
||||||
"husky": "^4.2.2",
|
"husky": "^4.2.2",
|
||||||
"lint-staged": "10.2.9",
|
"lint-staged": "10.2.11",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"prettier": "2.0.5",
|
"prettier": "2.0.5",
|
||||||
"react-app-rewired": "^2.1.6",
|
"react-app-rewired": "^2.1.6",
|
||||||
"truffle": "5.1.29",
|
"truffle": "5.1.33",
|
||||||
"typescript": "^3.9.5",
|
"typescript": "3.9.6",
|
||||||
"wait-on": "5.0.1",
|
"wait-on": "5.0.1",
|
||||||
"web3-eth-contract": "^1.2.9",
|
"web3-eth-contract": "^1.2.9",
|
||||||
"web3-utils": "^1.2.8"
|
"web3-utils": "^1.2.8"
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
|
@ -1,5 +1,6 @@
|
||||||
|
import { checksumAddress } from 'src/utils/checksumAddress';
|
||||||
import { ensureOnce } from 'src/utils/singleton'
|
import { ensureOnce } from 'src/utils/singleton'
|
||||||
import { ETHEREUM_NETWORK, getWeb3 } from 'src/logic/wallets/getWeb3'
|
import { ETHEREUM_NETWORK } from 'src/logic/wallets/getWeb3'
|
||||||
import {
|
import {
|
||||||
RELAY_API_URL,
|
RELAY_API_URL,
|
||||||
SIGNATURES_VIA_METAMASK,
|
SIGNATURES_VIA_METAMASK,
|
||||||
|
@ -90,7 +91,7 @@ export const getSafeLastVersion = () => process.env.REACT_APP_LATEST_SAFE_VERSIO
|
||||||
|
|
||||||
export const buildSafeCreationTxUrl = (safeAddress) => {
|
export const buildSafeCreationTxUrl = (safeAddress) => {
|
||||||
const host = getTxServiceHost()
|
const host = getTxServiceHost()
|
||||||
const address = getWeb3().utils.toChecksumAddress(safeAddress)
|
const address = checksumAddress(safeAddress)
|
||||||
const base = getSafeCreationTxUri(address)
|
const base = getSafeCreationTxUri(address)
|
||||||
|
|
||||||
return `${host}${base}`
|
return `${host}${base}`
|
||||||
|
|
|
@ -4,6 +4,58 @@ import { ETHEREUM_NETWORK } from 'src/logic/wallets/getWeb3'
|
||||||
import NFTIcon from 'src/routes/safe/components/Balances/assets/nft_icon.png'
|
import NFTIcon from 'src/routes/safe/components/Balances/assets/nft_icon.png'
|
||||||
import { OPENSEA_API_KEY } from 'src/utils/constants'
|
import { OPENSEA_API_KEY } from 'src/utils/constants'
|
||||||
|
|
||||||
|
export interface OpenSeaAssetContract {
|
||||||
|
address: string
|
||||||
|
name: string
|
||||||
|
image_url: string
|
||||||
|
symbol: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OpenSeaCollection {
|
||||||
|
name: string
|
||||||
|
slug: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OpenSeaAsset {
|
||||||
|
asset_contract: OpenSeaAssetContract
|
||||||
|
background_color: string
|
||||||
|
collection: OpenSeaCollection
|
||||||
|
description: string
|
||||||
|
image_thumbnail_url: string
|
||||||
|
name: string
|
||||||
|
token_id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OpenSeaAssets = Array<OpenSeaAsset>
|
||||||
|
|
||||||
|
export interface NFTAsset {
|
||||||
|
address: string
|
||||||
|
assetContract: OpenSeaAssetContract
|
||||||
|
collection: OpenSeaCollection
|
||||||
|
description: string
|
||||||
|
image: string
|
||||||
|
name: string
|
||||||
|
numberOfTokens: number
|
||||||
|
slug: string
|
||||||
|
symbol: string
|
||||||
|
}
|
||||||
|
export type NFTAssets = Record<string, NFTAsset>
|
||||||
|
|
||||||
|
export interface NFTToken {
|
||||||
|
assetAddress: string
|
||||||
|
color: string
|
||||||
|
description: string
|
||||||
|
image: string
|
||||||
|
name: string
|
||||||
|
tokenId: number | string
|
||||||
|
}
|
||||||
|
export type NFTTokens = Array<NFTToken>
|
||||||
|
|
||||||
|
export interface Collectibles {
|
||||||
|
nftAssets: NFTAssets
|
||||||
|
nftTokens: NFTTokens
|
||||||
|
}
|
||||||
|
|
||||||
class OpenSea {
|
class OpenSea {
|
||||||
_rateLimit = async () => {}
|
_rateLimit = async () => {}
|
||||||
|
|
||||||
|
@ -29,7 +81,7 @@ class OpenSea {
|
||||||
this._rateLimit = RateLimit(options.rps, { timeUnit: 60 * 1000, uniformDistribution: true })
|
this._rateLimit = RateLimit(options.rps, { timeUnit: 60 * 1000, uniformDistribution: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
static extractAssets(assets) {
|
static extractAssets(assets: OpenSeaAssets): NFTAssets {
|
||||||
const extractNFTAsset = (asset) => ({
|
const extractNFTAsset = (asset) => ({
|
||||||
address: asset.asset_contract.address,
|
address: asset.asset_contract.address,
|
||||||
assetContract: asset.asset_contract,
|
assetContract: asset.asset_contract,
|
||||||
|
@ -59,7 +111,7 @@ class OpenSea {
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
static extractTokens(assets) {
|
static extractTokens(assets: OpenSeaAssets): NFTTokens {
|
||||||
return assets.map((asset) => ({
|
return assets.map((asset) => ({
|
||||||
assetAddress: asset.asset_contract.address,
|
assetAddress: asset.asset_contract.address,
|
||||||
color: asset.background_color,
|
color: asset.background_color,
|
||||||
|
@ -70,7 +122,7 @@ class OpenSea {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
static extractCollectiblesInfo(assetResponseJson) {
|
static extractCollectiblesInfo(assetResponseJson: { assets: OpenSeaAssets }): Collectibles {
|
||||||
return {
|
return {
|
||||||
nftAssets: OpenSea.extractAssets(assetResponseJson.assets),
|
nftAssets: OpenSea.extractAssets(assetResponseJson.assets),
|
||||||
nftTokens: OpenSea.extractTokens(assetResponseJson.assets),
|
nftTokens: OpenSea.extractTokens(assetResponseJson.assets),
|
||||||
|
@ -82,9 +134,9 @@ class OpenSea {
|
||||||
* for the provided Safe Address in the specified Network
|
* for the provided Safe Address in the specified Network
|
||||||
* @param {string} safeAddress
|
* @param {string} safeAddress
|
||||||
* @param {string} network
|
* @param {string} network
|
||||||
* @returns {Promise<{ nftAssets: Map<string, NFTAsset>, nftTokens: Array<NFTToken> }>}
|
* @returns {Promise<Collectibles>}
|
||||||
*/
|
*/
|
||||||
async fetchAllUserCollectiblesByCategoryAsync(safeAddress, network) {
|
async fetchAllUserCollectiblesByCategoryAsync(safeAddress: string, network: string): Promise<Collectibles> {
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
// eslint-disable-next-line no-underscore-dangle
|
||||||
const metadataSourceUrl = this._endpointsUrls[network]
|
const metadataSourceUrl = this._endpointsUrls[network]
|
||||||
const url = `${metadataSourceUrl}/assets/?owner=${safeAddress}`
|
const url = `${metadataSourceUrl}/assets/?owner=${safeAddress}`
|
||||||
|
|
|
@ -2,9 +2,12 @@ import MockedOpenSea from 'src/logic/collectibles/sources/MockedOpenSea'
|
||||||
import OpenSea from 'src/logic/collectibles/sources/OpenSea'
|
import OpenSea from 'src/logic/collectibles/sources/OpenSea'
|
||||||
import { COLLECTIBLES_SOURCE } from 'src/utils/constants'
|
import { COLLECTIBLES_SOURCE } from 'src/utils/constants'
|
||||||
|
|
||||||
const sources = {
|
const SOURCES = {
|
||||||
opensea: new OpenSea({ rps: 4 }),
|
opensea: new OpenSea({ rps: 4 }),
|
||||||
mockedopensea: new MockedOpenSea({ rps: 4 }),
|
mockedopensea: new MockedOpenSea({ rps: 4 }),
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getConfiguredSource = () => sources[COLLECTIBLES_SOURCE.toLowerCase()]
|
type Sources = typeof SOURCES
|
||||||
|
|
||||||
|
export const getConfiguredSource = (): Sources['opensea'] | Sources['mockedopensea'] =>
|
||||||
|
SOURCES[COLLECTIBLES_SOURCE.toLowerCase()]
|
||||||
|
|
|
@ -3,18 +3,21 @@ import { batch } from 'react-redux'
|
||||||
import { getNetwork } from 'src/config'
|
import { getNetwork } from 'src/config'
|
||||||
import { getConfiguredSource } from 'src/logic/collectibles/sources'
|
import { getConfiguredSource } from 'src/logic/collectibles/sources'
|
||||||
import { addNftAssets, addNftTokens } from 'src/logic/collectibles/store/actions/addCollectibles'
|
import { addNftAssets, addNftTokens } from 'src/logic/collectibles/store/actions/addCollectibles'
|
||||||
import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors'
|
import { Dispatch } from 'redux'
|
||||||
|
|
||||||
const fetchCollectibles = () => async (dispatch, getState) => {
|
const fetchCollectibles = (safeAddress: string) => async (dispatch: Dispatch): Promise<void> => {
|
||||||
const network = getNetwork()
|
try {
|
||||||
const safeAddress = safeParamAddressFromStateSelector(getState()) || ''
|
const network = getNetwork()
|
||||||
const source = getConfiguredSource()
|
const source = getConfiguredSource()
|
||||||
const collectibles = await source.fetchAllUserCollectiblesByCategoryAsync(safeAddress, network)
|
const collectibles = await source.fetchAllUserCollectiblesByCategoryAsync(safeAddress, network)
|
||||||
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
dispatch(addNftAssets(collectibles.nftAssets))
|
dispatch(addNftAssets(collectibles.nftAssets))
|
||||||
dispatch(addNftTokens(collectibles.nftTokens))
|
dispatch(addNftTokens(collectibles.nftTokens))
|
||||||
})
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error fetching collectibles:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default fetchCollectibles
|
export default fetchCollectibles
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
import { createSelector } from 'reselect'
|
import { createSelector } from 'reselect'
|
||||||
|
import { NFTAssets, NFTTokens } from 'src/logic/collectibles/sources/OpenSea'
|
||||||
|
|
||||||
|
import { AppReduxState } from 'src/store'
|
||||||
import { NFT_ASSETS_REDUCER_ID, NFT_TOKENS_REDUCER_ID } from 'src/logic/collectibles/store/reducer/collectibles'
|
import { NFT_ASSETS_REDUCER_ID, NFT_TOKENS_REDUCER_ID } from 'src/logic/collectibles/store/reducer/collectibles'
|
||||||
import { safeActiveAssetsSelector } from 'src/routes/safe/store/selectors'
|
import { safeActiveAssetsSelector } from 'src/routes/safe/store/selectors'
|
||||||
|
|
||||||
export const nftAssetsSelector = (state) => state[NFT_ASSETS_REDUCER_ID]
|
export const nftAssetsSelector = (state: AppReduxState): NFTAssets => state[NFT_ASSETS_REDUCER_ID]
|
||||||
export const nftTokensSelector = (state) => state[NFT_TOKENS_REDUCER_ID]
|
export const nftTokensSelector = (state: AppReduxState): NFTTokens => state[NFT_TOKENS_REDUCER_ID]
|
||||||
|
|
||||||
export const nftAssetsListSelector = createSelector(nftAssetsSelector, (assets) => {
|
export const nftAssetsListSelector = createSelector(nftAssetsSelector, (assets) => {
|
||||||
return assets ? List(Object.entries(assets).map((item) => item[1])) : List([])
|
return assets ? List(Object.entries(assets).map((item) => item[1])) : List([])
|
||||||
|
|
|
@ -11,12 +11,18 @@ import { makeToken } from 'src/logic/tokens/store/model/token'
|
||||||
import { TOKEN_REDUCER_ID } from 'src/logic/tokens/store/reducer/tokens'
|
import { TOKEN_REDUCER_ID } from 'src/logic/tokens/store/reducer/tokens'
|
||||||
import updateSafe from 'src/routes/safe/store/actions/updateSafe'
|
import updateSafe from 'src/routes/safe/store/actions/updateSafe'
|
||||||
import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe'
|
import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe'
|
||||||
|
import { Dispatch } from 'redux'
|
||||||
|
import { backOff } from 'exponential-backoff'
|
||||||
|
import { AppReduxState } from 'src/store'
|
||||||
|
|
||||||
const humanReadableBalance = (balance, decimals) => new BigNumber(balance).times(`1e-${decimals}`).toFixed()
|
const humanReadableBalance = (balance, decimals) => new BigNumber(balance).times(`1e-${decimals}`).toFixed()
|
||||||
const noFunc = () => {}
|
const noFunc = () => {}
|
||||||
const updateSafeValue = (address) => (valueToUpdate) => updateSafe({ address, ...valueToUpdate })
|
const updateSafeValue = (address) => (valueToUpdate) => updateSafe({ address, ...valueToUpdate })
|
||||||
|
|
||||||
const fetchSafeTokens = (safeAddress) => async (dispatch, getState) => {
|
const fetchSafeTokens = (safeAddress: string) => async (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: () => AppReduxState,
|
||||||
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
const safe = state[SAFE_REDUCER_ID].getIn([SAFE_REDUCER_ID, safeAddress])
|
const safe = state[SAFE_REDUCER_ID].getIn([SAFE_REDUCER_ID, safeAddress])
|
||||||
|
@ -26,7 +32,7 @@ const fetchSafeTokens = (safeAddress) => async (dispatch, getState) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await fetchTokenCurrenciesBalances(safeAddress)
|
const result = await backOff(() => fetchTokenCurrenciesBalances(safeAddress))
|
||||||
const currentEthBalance = safe.get('ethBalance')
|
const currentEthBalance = safe.get('ethBalance')
|
||||||
const safeBalances = safe.get('balances')
|
const safeBalances = safe.get('balances')
|
||||||
const alreadyActiveTokens = safe.get('activeTokens')
|
const alreadyActiveTokens = safe.get('activeTokens')
|
||||||
|
@ -95,8 +101,6 @@ const fetchSafeTokens = (safeAddress) => async (dispatch, getState) => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching active token list', err)
|
console.error('Error fetching active token list', err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default fetchSafeTokens
|
export default fetchSafeTokens
|
||||||
|
|
|
@ -9,7 +9,7 @@ import saveTokens from './saveTokens'
|
||||||
|
|
||||||
import generateBatchRequests from 'src/logic/contracts/generateBatchRequests'
|
import generateBatchRequests from 'src/logic/contracts/generateBatchRequests'
|
||||||
import { fetchTokenList } from 'src/logic/tokens/api'
|
import { fetchTokenList } from 'src/logic/tokens/api'
|
||||||
import { makeToken } from 'src/logic/tokens/store/model/token'
|
import { makeToken, Token } from 'src/logic/tokens/store/model/token'
|
||||||
import { tokensSelector } from 'src/logic/tokens/store/selectors'
|
import { tokensSelector } from 'src/logic/tokens/store/selectors'
|
||||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||||
import { store } from 'src/store'
|
import { store } from 'src/store'
|
||||||
|
@ -57,7 +57,7 @@ const getTokenValues = (tokenAddress) =>
|
||||||
methods: ['decimals', 'name', 'symbol'],
|
methods: ['decimals', 'name', 'symbol'],
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getTokenInfos = async (tokenAddress) => {
|
export const getTokenInfos = async (tokenAddress: string): Promise<Token> => {
|
||||||
if (!tokenAddress) {
|
if (!tokenAddress) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { ALTERNATIVE_TOKEN_ABI } from 'src/logic/tokens/utils/alternativeAbi'
|
||||||
import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3'
|
import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3'
|
||||||
import { isEmptyData } from 'src/routes/safe/store/actions/transactions/utils/transactionHelpers'
|
import { isEmptyData } from 'src/routes/safe/store/actions/transactions/utils/transactionHelpers'
|
||||||
import { TxServiceModel } from 'src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions'
|
import { TxServiceModel } from 'src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions'
|
||||||
|
import { Map } from 'immutable'
|
||||||
|
|
||||||
export const ETH_ADDRESS = '0x000'
|
export const ETH_ADDRESS = '0x000'
|
||||||
export const SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH = '42842e0e'
|
export const SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH = '42842e0e'
|
||||||
|
@ -39,11 +40,15 @@ export const isAddressAToken = async (tokenAddress: string): Promise<boolean> =>
|
||||||
return call !== '0x'
|
return call !== '0x'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isTokenTransfer = (tx: any): boolean => {
|
export const isTokenTransfer = (tx: TxServiceModel): boolean => {
|
||||||
return !isEmptyData(tx.data) && tx.data.substring(0, 10) === '0xa9059cbb' && Number(tx.value) === 0
|
return !isEmptyData(tx.data) && tx.data.substring(0, 10) === '0xa9059cbb' && Number(tx.value) === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isSendERC721Transaction = (tx: any, txCode: string, knownTokens: any) => {
|
export const isSendERC721Transaction = (
|
||||||
|
tx: TxServiceModel,
|
||||||
|
txCode: string,
|
||||||
|
knownTokens: Map<string, Token>,
|
||||||
|
): boolean => {
|
||||||
// "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" - ens token contract, includes safeTransferFrom
|
// "0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85" - ens token contract, includes safeTransferFrom
|
||||||
// but no proper ERC721 standard implemented
|
// but no proper ERC721 standard implemented
|
||||||
return (
|
return (
|
||||||
|
@ -79,9 +84,9 @@ export const getERC20DecimalsAndSymbol = async (
|
||||||
address: tokenAddress,
|
address: tokenAddress,
|
||||||
methods: ['decimals', 'symbol'],
|
methods: ['decimals', 'symbol'],
|
||||||
})
|
})
|
||||||
|
|
||||||
return { decimals: Number(tokenDecimals), symbol: tokenSymbol }
|
return { decimals: Number(tokenDecimals), symbol: tokenSymbol }
|
||||||
}
|
}
|
||||||
|
return { decimals: storedTokenInfo.decimals as number, symbol: storedTokenInfo.symbol }
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to retrieve token info for ERC20 token ${tokenAddress}`)
|
console.error(`Failed to retrieve token info for ERC20 token ${tokenAddress}`)
|
||||||
}
|
}
|
||||||
|
@ -92,7 +97,7 @@ export const getERC20DecimalsAndSymbol = async (
|
||||||
export const isSendERC20Transaction = async (
|
export const isSendERC20Transaction = async (
|
||||||
tx: TxServiceModel,
|
tx: TxServiceModel,
|
||||||
txCode: string,
|
txCode: string,
|
||||||
knownTokens: any,
|
knownTokens: Map<string, Token>,
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
let isSendTokenTx = !isSendERC721Transaction(tx, txCode, knownTokens) && isTokenTransfer(tx)
|
let isSendTokenTx = !isSendERC721Transaction(tx, txCode, knownTokens) && isTokenTransfer(tx)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import selector from './selector'
|
||||||
import Page from 'src/components/layout/Page'
|
import Page from 'src/components/layout/Page'
|
||||||
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
||||||
import { SAFES_KEY, saveSafes } from 'src/logic/safe/utils'
|
import { SAFES_KEY, saveSafes } from 'src/logic/safe/utils'
|
||||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
|
||||||
import { getNamesFrom, getOwnersFrom } from 'src/routes/open/utils/safeDataExtractor'
|
import { getNamesFrom, getOwnersFrom } from 'src/routes/open/utils/safeDataExtractor'
|
||||||
import { SAFELIST_ADDRESS } from 'src/routes/routes'
|
import { SAFELIST_ADDRESS } from 'src/routes/routes'
|
||||||
import { buildSafe } from 'src/routes/safe/store/actions/fetchSafe'
|
import { buildSafe } from 'src/routes/safe/store/actions/fetchSafe'
|
||||||
|
@ -19,6 +18,7 @@ import { loadFromStorage } from 'src/utils/storage'
|
||||||
import { Dispatch } from 'redux'
|
import { Dispatch } from 'redux'
|
||||||
import { SafeOwner } from '../../safe/store/models/safe'
|
import { SafeOwner } from '../../safe/store/models/safe'
|
||||||
import { List } from 'immutable'
|
import { List } from 'immutable'
|
||||||
|
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||||
|
|
||||||
export const loadSafe = async (
|
export const loadSafe = async (
|
||||||
safeName: string,
|
safeName: string,
|
||||||
|
@ -39,14 +39,15 @@ export const loadSafe = async (
|
||||||
|
|
||||||
class Load extends React.Component<any> {
|
class Load extends React.Component<any> {
|
||||||
onLoadSafeSubmit = async (values) => {
|
onLoadSafeSubmit = async (values) => {
|
||||||
|
let safeAddress = values[FIELD_LOAD_ADDRESS]
|
||||||
|
if (safeAddress) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { addSafe } = this.props
|
const { addSafe } = this.props
|
||||||
const web3 = getWeb3()
|
|
||||||
const safeName = values[FIELD_LOAD_NAME]
|
const safeName = values[FIELD_LOAD_NAME]
|
||||||
let safeAddress = values[FIELD_LOAD_ADDRESS]
|
safeAddress = checksumAddress(safeAddress)
|
||||||
if (safeAddress) {
|
|
||||||
safeAddress = web3.utils.toChecksumAddress(safeAddress)
|
|
||||||
}
|
|
||||||
const ownerNames = getNamesFrom(values)
|
const ownerNames = getNamesFrom(values)
|
||||||
|
|
||||||
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress)
|
||||||
|
|
|
@ -47,7 +47,7 @@ const Balances = (props) => {
|
||||||
const address = useSelector(safeParamAddressFromStateSelector)
|
const address = useSelector(safeParamAddressFromStateSelector)
|
||||||
const featuresEnabled = useSelector(safeFeaturesEnabledSelector)
|
const featuresEnabled = useSelector(safeFeaturesEnabledSelector)
|
||||||
|
|
||||||
useFetchTokens()
|
useFetchTokens(address)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const erc721Enabled = featuresEnabled && featuresEnabled.includes('ERC721')
|
const erc721Enabled = featuresEnabled && featuresEnabled.includes('ERC721')
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { useEffect } from 'react'
|
|
||||||
import { batch, useDispatch, useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
import fetchCollectibles from 'src/logic/collectibles/store/actions/fetchCollectibles'
|
|
||||||
import fetchSafeTokens from 'src/logic/tokens/store/actions/fetchSafeTokens'
|
|
||||||
import fetchEtherBalance from 'src/routes/safe/store/actions/fetchEtherBalance'
|
|
||||||
import { checkAndUpdateSafe } from 'src/routes/safe/store/actions/fetchSafe'
|
|
||||||
import fetchTransactions from 'src/routes/safe/store/actions/transactions/fetchTransactions'
|
|
||||||
import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors'
|
|
||||||
import { TIMEOUT } from 'src/utils/constants'
|
|
||||||
|
|
||||||
export const useCheckForUpdates = () => {
|
|
||||||
const dispatch = useDispatch()
|
|
||||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
|
||||||
useEffect(() => {
|
|
||||||
if (safeAddress) {
|
|
||||||
const collectiblesInterval = setInterval(() => {
|
|
||||||
batch(() => {
|
|
||||||
dispatch(fetchEtherBalance(safeAddress))
|
|
||||||
dispatch(fetchSafeTokens(safeAddress))
|
|
||||||
dispatch(fetchTransactions(safeAddress))
|
|
||||||
dispatch(fetchCollectibles)
|
|
||||||
dispatch(checkAndUpdateSafe(safeAddress))
|
|
||||||
})
|
|
||||||
}, TIMEOUT * 3)
|
|
||||||
return () => {
|
|
||||||
clearInterval(collectiblesInterval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [dispatch, safeAddress])
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { batch, useDispatch, useSelector } from 'react-redux'
|
import { batch, useDispatch } from 'react-redux'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
import fetchCollectibles from 'src/logic/collectibles/store/actions/fetchCollectibles'
|
import fetchCollectibles from 'src/logic/collectibles/store/actions/fetchCollectibles'
|
||||||
|
@ -8,11 +8,9 @@ import activateAssetsByBalance from 'src/logic/tokens/store/actions/activateAsse
|
||||||
import fetchSafeTokens from 'src/logic/tokens/store/actions/fetchSafeTokens'
|
import fetchSafeTokens from 'src/logic/tokens/store/actions/fetchSafeTokens'
|
||||||
import { fetchTokens } from 'src/logic/tokens/store/actions/fetchTokens'
|
import { fetchTokens } from 'src/logic/tokens/store/actions/fetchTokens'
|
||||||
import { COINS_LOCATION_REGEX, COLLECTIBLES_LOCATION_REGEX } from 'src/routes/safe/components/Balances'
|
import { COINS_LOCATION_REGEX, COLLECTIBLES_LOCATION_REGEX } from 'src/routes/safe/components/Balances'
|
||||||
import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors'
|
|
||||||
|
|
||||||
export const useFetchTokens = (): void => {
|
export const useFetchTokens = (safeAddress: string): void => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const address: string | null = useSelector(safeParamAddressFromStateSelector)
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
|
@ -20,17 +18,17 @@ export const useFetchTokens = (): void => {
|
||||||
batch(() => {
|
batch(() => {
|
||||||
// fetch tokens there to get symbols for tokens in TXs list
|
// fetch tokens there to get symbols for tokens in TXs list
|
||||||
dispatch(fetchTokens())
|
dispatch(fetchTokens())
|
||||||
dispatch(fetchCurrencyValues(address))
|
dispatch(fetchCurrencyValues(safeAddress))
|
||||||
dispatch(fetchSafeTokens(address))
|
dispatch(fetchSafeTokens(safeAddress))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (COLLECTIBLES_LOCATION_REGEX.test(location.pathname)) {
|
if (COLLECTIBLES_LOCATION_REGEX.test(location.pathname)) {
|
||||||
batch(() => {
|
batch(() => {
|
||||||
dispatch(fetchCollectibles()).then(() => {
|
dispatch(fetchCollectibles(safeAddress)).then(() => {
|
||||||
dispatch(activateAssetsByBalance(address))
|
dispatch(activateAssetsByBalance(safeAddress))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [address, dispatch, location])
|
}, [dispatch, location.pathname, safeAddress])
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { batch, useDispatch } from 'react-redux'
|
||||||
|
|
||||||
|
import fetchCollectibles from 'src/logic/collectibles/store/actions/fetchCollectibles'
|
||||||
|
import fetchSafeTokens from 'src/logic/tokens/store/actions/fetchSafeTokens'
|
||||||
|
import fetchEtherBalance from 'src/routes/safe/store/actions/fetchEtherBalance'
|
||||||
|
import { checkAndUpdateSafe } from 'src/routes/safe/store/actions/fetchSafe'
|
||||||
|
import fetchTransactions from 'src/routes/safe/store/actions/transactions/fetchTransactions'
|
||||||
|
import { TIMEOUT } from 'src/utils/constants'
|
||||||
|
|
||||||
|
export const useSafeScheduledUpdates = (safeAddress: string): void => {
|
||||||
|
const dispatch = useDispatch()
|
||||||
|
const timer = useRef<number>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// using this variable to prevent setting a timeout when the component is already unmounted or the effect
|
||||||
|
// has to run again
|
||||||
|
let mounted = true
|
||||||
|
const fetchSafeData = async (address: string): Promise<void> => {
|
||||||
|
await batch(async () => {
|
||||||
|
await Promise.all([
|
||||||
|
dispatch(fetchEtherBalance(address)),
|
||||||
|
dispatch(fetchSafeTokens(address)),
|
||||||
|
dispatch(fetchTransactions(address)),
|
||||||
|
dispatch(fetchCollectibles(address)),
|
||||||
|
dispatch(checkAndUpdateSafe(address)),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
timer.current = setTimeout(() => {
|
||||||
|
fetchSafeData(safeAddress)
|
||||||
|
}, TIMEOUT * 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (safeAddress) {
|
||||||
|
fetchSafeData(safeAddress)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mounted = false
|
||||||
|
clearTimeout(timer.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [dispatch, safeAddress])
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import Page from 'src/components/layout/Page'
|
||||||
import Layout from 'src/routes/safe/components/Layout'
|
import Layout from 'src/routes/safe/components/Layout'
|
||||||
import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors'
|
import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors'
|
||||||
import { useLoadSafe } from './hooks/useLoadSafe'
|
import { useLoadSafe } from './hooks/useLoadSafe'
|
||||||
import { useCheckForUpdates } from './hooks/useCheckForUpdates'
|
import { useSafeScheduledUpdates } from './hooks/useSafeScheduledUpdates'
|
||||||
|
|
||||||
const INITIAL_STATE = {
|
const INITIAL_STATE = {
|
||||||
sendFunds: {
|
sendFunds: {
|
||||||
|
@ -17,12 +17,12 @@ const INITIAL_STATE = {
|
||||||
showReceive: false,
|
showReceive: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SafeView = () => {
|
const SafeView = (): JSX.Element => {
|
||||||
const [state, setState] = useState(INITIAL_STATE)
|
const [state, setState] = useState(INITIAL_STATE)
|
||||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||||
|
|
||||||
useLoadSafe(safeAddress)
|
useLoadSafe(safeAddress)
|
||||||
useCheckForUpdates()
|
useSafeScheduledUpdates(safeAddress)
|
||||||
|
|
||||||
const onShow = (action) => () => {
|
const onShow = (action) => () => {
|
||||||
setState((prevState) => ({
|
setState((prevState) => ({
|
|
@ -1,12 +1,18 @@
|
||||||
import { getBalanceInEtherOf } from 'src/logic/wallets/getWeb3'
|
import { getBalanceInEtherOf } from 'src/logic/wallets/getWeb3'
|
||||||
import updateSafe from 'src/routes/safe/store/actions/updateSafe'
|
import updateSafe from 'src/routes/safe/store/actions/updateSafe'
|
||||||
import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe'
|
import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe'
|
||||||
|
import { Dispatch } from 'redux'
|
||||||
|
import { backOff } from 'exponential-backoff'
|
||||||
|
import { AppReduxState } from 'src/store'
|
||||||
|
|
||||||
const fetchEtherBalance = (safeAddress) => async (dispatch, getState) => {
|
const fetchEtherBalance = (safeAddress: string) => async (
|
||||||
|
dispatch: Dispatch,
|
||||||
|
getState: () => AppReduxState,
|
||||||
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const state = getState()
|
const state = getState()
|
||||||
const ethBalance = state[SAFE_REDUCER_ID].getIn([SAFE_REDUCER_ID, safeAddress, 'ethBalance'])
|
const ethBalance = state[SAFE_REDUCER_ID].getIn([SAFE_REDUCER_ID, safeAddress, 'ethBalance'])
|
||||||
const newEthBalance = await getBalanceInEtherOf(safeAddress)
|
const newEthBalance = await backOff(() => getBalanceInEtherOf(safeAddress))
|
||||||
if (newEthBalance !== ethBalance) {
|
if (newEthBalance !== ethBalance) {
|
||||||
dispatch(updateSafe({ address: safeAddress, ethBalance: newEthBalance }))
|
dispatch(updateSafe({ address: safeAddress, ethBalance: newEthBalance }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { makeOwner } from 'src/routes/safe/store/models/owner'
|
||||||
|
|
||||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||||
import { SafeOwner } from '../models/safe'
|
import { SafeOwner } from '../models/safe'
|
||||||
|
import { Dispatch } from 'redux'
|
||||||
|
|
||||||
const buildOwnersFrom = (
|
const buildOwnersFrom = (
|
||||||
safeOwners,
|
safeOwners,
|
||||||
|
@ -71,7 +72,7 @@ export const buildSafe = async (safeAdd, safeName, latestMasterContractVersion?:
|
||||||
return safe
|
return safe
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkAndUpdateSafe = (safeAdd) => async (dispatch) => {
|
export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: Dispatch): Promise<void> => {
|
||||||
const safeAddress = checksumAddress(safeAdd)
|
const safeAddress = checksumAddress(safeAdd)
|
||||||
// Check if the owner's safe did change and update them
|
// Check if the owner's safe did change and update them
|
||||||
const safeParams = ['getThreshold', 'nonce', 'getOwners']
|
const safeParams = ['getThreshold', 'nonce', 'getOwners']
|
||||||
|
|
|
@ -7,28 +7,39 @@ import { loadOutgoingTransactions } from './loadOutgoingTransactions'
|
||||||
|
|
||||||
import { addOrUpdateCancellationTransactions } from 'src/routes/safe/store/actions/transactions/addOrUpdateCancellationTransactions'
|
import { addOrUpdateCancellationTransactions } from 'src/routes/safe/store/actions/transactions/addOrUpdateCancellationTransactions'
|
||||||
import { addOrUpdateTransactions } from 'src/routes/safe/store/actions/transactions/addOrUpdateTransactions'
|
import { addOrUpdateTransactions } from 'src/routes/safe/store/actions/transactions/addOrUpdateTransactions'
|
||||||
|
import { Dispatch } from 'redux'
|
||||||
|
import { backOff } from 'exponential-backoff'
|
||||||
|
|
||||||
const noFunc = () => {}
|
const noFunc = () => {}
|
||||||
|
|
||||||
export default (safeAddress: string) => async (dispatch) => {
|
export default (safeAddress: string) => async (dispatch: Dispatch): Promise<void> => {
|
||||||
const transactions = await loadOutgoingTransactions(safeAddress)
|
try {
|
||||||
|
const transactions = await backOff(() => loadOutgoingTransactions(safeAddress))
|
||||||
|
|
||||||
if (transactions) {
|
if (transactions) {
|
||||||
const { cancel, outgoing } = transactions
|
const { cancel, outgoing } = transactions
|
||||||
const updateCancellationTxs = cancel.size
|
const updateCancellationTxs = cancel.size
|
||||||
? addOrUpdateCancellationTransactions({ safeAddress, transactions: cancel })
|
? addOrUpdateCancellationTransactions({ safeAddress, transactions: cancel })
|
||||||
: noFunc
|
: noFunc
|
||||||
const updateOutgoingTxs = outgoing.size ? addOrUpdateTransactions({ safeAddress, transactions: outgoing }) : noFunc
|
const updateOutgoingTxs = outgoing.size
|
||||||
|
? addOrUpdateTransactions({
|
||||||
|
safeAddress,
|
||||||
|
transactions: outgoing,
|
||||||
|
})
|
||||||
|
: noFunc
|
||||||
|
|
||||||
batch(() => {
|
batch(() => {
|
||||||
dispatch(updateCancellationTxs)
|
dispatch(updateCancellationTxs)
|
||||||
dispatch(updateOutgoingTxs)
|
dispatch(updateOutgoingTxs)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const incomingTransactions = await loadIncomingTransactions(safeAddress)
|
const incomingTransactions = await loadIncomingTransactions(safeAddress)
|
||||||
|
|
||||||
if (incomingTransactions.get(safeAddress).size) {
|
if (incomingTransactions.get(safeAddress).size) {
|
||||||
dispatch(addIncomingTransactions(incomingTransactions))
|
dispatch(addIncomingTransactions(incomingTransactions))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error fetching transactions:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ export type OutgoingTxs = {
|
||||||
|
|
||||||
export type BatchProcessTxsProps = OutgoingTxs & {
|
export type BatchProcessTxsProps = OutgoingTxs & {
|
||||||
currentUser?: string
|
currentUser?: string
|
||||||
knownTokens: Record<string, Token>
|
knownTokens: Map<string, Token>
|
||||||
safe: SafeRecord
|
safe: SafeRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,10 @@ export type BatchProcessTxsProps = OutgoingTxs & {
|
||||||
const extractCancelAndOutgoingTxs = (safeAddress: string, outgoingTxs: TxServiceModel[]): OutgoingTxs => {
|
const extractCancelAndOutgoingTxs = (safeAddress: string, outgoingTxs: TxServiceModel[]): OutgoingTxs => {
|
||||||
return outgoingTxs.reduce(
|
return outgoingTxs.reduce(
|
||||||
(acc, transaction) => {
|
(acc, transaction) => {
|
||||||
if (isCancelTransaction(transaction, safeAddress)) {
|
if (
|
||||||
|
isCancelTransaction(transaction, safeAddress) &&
|
||||||
|
outgoingTxs.find((tx) => tx.nonce === transaction.nonce && !isCancelTransaction(tx, safeAddress))
|
||||||
|
) {
|
||||||
if (!isNaN(Number(transaction.nonce))) {
|
if (!isNaN(Number(transaction.nonce))) {
|
||||||
acc.cancellationTxs[transaction.nonce] = transaction
|
acc.cancellationTxs[transaction.nonce] = transaction
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ export const isCustomTransaction = async (
|
||||||
tx: TxServiceModel,
|
tx: TxServiceModel,
|
||||||
txCode: string,
|
txCode: string,
|
||||||
safeAddress: string,
|
safeAddress: string,
|
||||||
knownTokens: Record<string, Token>,
|
knownTokens: Map<string, Token>,
|
||||||
): Promise<boolean> => {
|
): Promise<boolean> => {
|
||||||
return (
|
return (
|
||||||
isOutgoingTransaction(tx, safeAddress) &&
|
isOutgoingTransaction(tx, safeAddress) &&
|
||||||
|
@ -340,7 +340,7 @@ export const mockTransaction = (tx: TxToMock, safeAddress: string, state): Promi
|
||||||
...tx,
|
...tx,
|
||||||
}
|
}
|
||||||
|
|
||||||
const knownTokens: Record<string, Token> = state[TOKEN_REDUCER_ID]
|
const knownTokens: Map<string, Token> = state[TOKEN_REDUCER_ID]
|
||||||
const safe: SafeRecord = state[SAFE_REDUCER_ID].getIn([SAFE_REDUCER_ID, safeAddress])
|
const safe: SafeRecord = state[SAFE_REDUCER_ID].getIn([SAFE_REDUCER_ID, safeAddress])
|
||||||
const cancellationTxs = state[CANCELLATION_TRANSACTIONS_REDUCER_ID].get(safeAddress) || Map()
|
const cancellationTxs = state[CANCELLATION_TRANSACTIONS_REDUCER_ID].get(safeAddress) || Map()
|
||||||
const outgoingTxs = state[TRANSACTIONS_REDUCER_ID].get(safeAddress) || List()
|
const outgoingTxs = state[TRANSACTIONS_REDUCER_ID].get(safeAddress) || List()
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { UPDATE_SAFE } from 'src/routes/safe/store/actions/updateSafe'
|
||||||
import { makeOwner } from 'src/routes/safe/store/models/owner'
|
import { makeOwner } from 'src/routes/safe/store/models/owner'
|
||||||
import makeSafe from 'src/routes/safe/store/models/safe'
|
import makeSafe from 'src/routes/safe/store/models/safe'
|
||||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||||
|
import { SafeReducerMap } from './types/safe'
|
||||||
|
|
||||||
export const SAFE_REDUCER_ID = 'safes'
|
export const SAFE_REDUCER_ID = 'safes'
|
||||||
export const DEFAULT_SAFE_INITIAL_STATE = 'NOT_ASKED'
|
export const DEFAULT_SAFE_INITIAL_STATE = 'NOT_ASKED'
|
||||||
|
@ -43,13 +44,17 @@ export const buildSafe = (storedSafe) => {
|
||||||
|
|
||||||
export default handleActions(
|
export default handleActions(
|
||||||
{
|
{
|
||||||
[UPDATE_SAFE]: (state, action) => {
|
[UPDATE_SAFE]: (state: SafeReducerMap, action) => {
|
||||||
const safe = action.payload
|
const safe = action.payload
|
||||||
const safeAddress = safe.address
|
const safeAddress = safe.address
|
||||||
|
|
||||||
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) => prevSafe.merge(safe))
|
return state.updateIn(
|
||||||
|
[SAFE_REDUCER_ID, safeAddress],
|
||||||
|
makeSafe({ name: 'LOADED SAFE', address: safeAddress }),
|
||||||
|
(prevSafe) => prevSafe.merge(safe),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
[ACTIVATE_TOKEN_FOR_ALL_SAFES]: (state, action) => {
|
[ACTIVATE_TOKEN_FOR_ALL_SAFES]: (state: SafeReducerMap, action) => {
|
||||||
const tokenAddress = action.payload
|
const tokenAddress = action.payload
|
||||||
|
|
||||||
return state.withMutations((map) => {
|
return state.withMutations((map) => {
|
||||||
|
@ -64,7 +69,7 @@ export default handleActions(
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
[ADD_SAFE]: (state, action) => {
|
[ADD_SAFE]: (state: SafeReducerMap, action) => {
|
||||||
const { safe } = action.payload
|
const { safe } = action.payload
|
||||||
|
|
||||||
// if you add a new Safe it needs to be set as a record
|
// if you add a new Safe it needs to be set as a record
|
||||||
|
@ -77,12 +82,12 @@ export default handleActions(
|
||||||
|
|
||||||
return state.setIn([SAFE_REDUCER_ID, safe.address], makeSafe(safe))
|
return state.setIn([SAFE_REDUCER_ID, safe.address], makeSafe(safe))
|
||||||
},
|
},
|
||||||
[REMOVE_SAFE]: (state, action) => {
|
[REMOVE_SAFE]: (state: SafeReducerMap, action) => {
|
||||||
const safeAddress = action.payload
|
const safeAddress = action.payload
|
||||||
|
|
||||||
return state.deleteIn([SAFE_REDUCER_ID, safeAddress])
|
return state.deleteIn([SAFE_REDUCER_ID, safeAddress])
|
||||||
},
|
},
|
||||||
[ADD_SAFE_OWNER]: (state, action) => {
|
[ADD_SAFE_OWNER]: (state: SafeReducerMap, action) => {
|
||||||
const { ownerAddress, ownerName, safeAddress } = action.payload
|
const { ownerAddress, ownerName, safeAddress } = action.payload
|
||||||
|
|
||||||
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) =>
|
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) =>
|
||||||
|
@ -91,7 +96,7 @@ export default handleActions(
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[REMOVE_SAFE_OWNER]: (state, action) => {
|
[REMOVE_SAFE_OWNER]: (state: SafeReducerMap, action) => {
|
||||||
const { ownerAddress, safeAddress } = action.payload
|
const { ownerAddress, safeAddress } = action.payload
|
||||||
|
|
||||||
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) =>
|
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) =>
|
||||||
|
@ -100,7 +105,7 @@ export default handleActions(
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[REPLACE_SAFE_OWNER]: (state, action) => {
|
[REPLACE_SAFE_OWNER]: (state: SafeReducerMap, action) => {
|
||||||
const { oldOwnerAddress, ownerAddress, ownerName, safeAddress } = action.payload
|
const { oldOwnerAddress, ownerAddress, ownerName, safeAddress } = action.payload
|
||||||
|
|
||||||
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) =>
|
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) =>
|
||||||
|
@ -111,7 +116,7 @@ export default handleActions(
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[EDIT_SAFE_OWNER]: (state, action) => {
|
[EDIT_SAFE_OWNER]: (state: SafeReducerMap, action) => {
|
||||||
const { ownerAddress, ownerName, safeAddress } = action.payload
|
const { ownerAddress, ownerName, safeAddress } = action.payload
|
||||||
|
|
||||||
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) => {
|
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) => {
|
||||||
|
@ -131,3 +136,5 @@ export default handleActions(
|
||||||
latestMasterContractVersion: '',
|
latestMasterContractVersion: '',
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export * from './types/safe.d'
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { SafeRecord } from 'src/routes/safe/store/models/safe'
|
||||||
|
import { Map } from 'immutable'
|
||||||
|
|
||||||
|
export type SafesMap = Map<string, SafeRecord>
|
||||||
|
|
||||||
|
export interface SafeReducerState {
|
||||||
|
defaultSafe: 'NOT_ASKED' | string | undefined
|
||||||
|
safes: SafesMap
|
||||||
|
latestMasterContractVersion: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SafeReducerStateSerialized extends SafeReducerState {
|
||||||
|
safes: Record<string, SafeRecordProps>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SafeReducerMap extends Map<string, any> {
|
||||||
|
toJS(): SafeReducerStateSerialized
|
||||||
|
get<K extends keyof SafeReducerState>(key: K): SafeReducerState[K]
|
||||||
|
}
|
|
@ -1,20 +1,20 @@
|
||||||
import { List, Map, Set } from 'immutable'
|
import { List, Map, Set } from 'immutable'
|
||||||
import { matchPath } from 'react-router-dom'
|
import { matchPath } from 'react-router-dom'
|
||||||
import { createSelector } from 'reselect'
|
import { createSelector } from 'reselect'
|
||||||
|
|
||||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
|
||||||
import { SAFELIST_ADDRESS, SAFE_PARAM_ADDRESS } from 'src/routes/routes'
|
import { SAFELIST_ADDRESS, SAFE_PARAM_ADDRESS } from 'src/routes/routes'
|
||||||
|
|
||||||
import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/cancellationTransactions'
|
import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/cancellationTransactions'
|
||||||
import { INCOMING_TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/incomingTransactions'
|
import { INCOMING_TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/incomingTransactions'
|
||||||
import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe'
|
import { SAFE_REDUCER_ID, SafesMap } from 'src/routes/safe/store/reducer/safe'
|
||||||
import { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transactions'
|
import { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transactions'
|
||||||
|
import { AppReduxState } from 'src/store'
|
||||||
|
|
||||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||||
|
import { SafeRecord } from 'src/routes/safe/store/models/safe'
|
||||||
|
|
||||||
const safesStateSelector = (state) => state[SAFE_REDUCER_ID]
|
const safesStateSelector = (state: AppReduxState) => state[SAFE_REDUCER_ID]
|
||||||
|
|
||||||
export const safesMapSelector = (state) => state[SAFE_REDUCER_ID].get('safes')
|
export const safesMapSelector = (state: AppReduxState): SafesMap => state[SAFE_REDUCER_ID].get('safes')
|
||||||
|
|
||||||
export const safesListSelector = createSelector(safesMapSelector, (safes) => safes.toList())
|
export const safesListSelector = createSelector(safesMapSelector, (safes) => safes.toList())
|
||||||
|
|
||||||
|
@ -26,18 +26,17 @@ export const latestMasterContractVersionSelector = createSelector(safesStateSele
|
||||||
safeState.get('latestMasterContractVersion'),
|
safeState.get('latestMasterContractVersion'),
|
||||||
)
|
)
|
||||||
|
|
||||||
const transactionsSelector = (state) => state[TRANSACTIONS_REDUCER_ID]
|
const transactionsSelector = (state: AppReduxState) => state[TRANSACTIONS_REDUCER_ID]
|
||||||
|
|
||||||
const cancellationTransactionsSelector = (state) => state[CANCELLATION_TRANSACTIONS_REDUCER_ID]
|
const cancellationTransactionsSelector = (state: AppReduxState) => state[CANCELLATION_TRANSACTIONS_REDUCER_ID]
|
||||||
|
|
||||||
const incomingTransactionsSelector = (state) => state[INCOMING_TRANSACTIONS_REDUCER_ID]
|
const incomingTransactionsSelector = (state: AppReduxState) => state[INCOMING_TRANSACTIONS_REDUCER_ID]
|
||||||
|
|
||||||
export const safeParamAddressFromStateSelector = (state): string | null => {
|
export const safeParamAddressFromStateSelector = (state: AppReduxState): string | null => {
|
||||||
const match = matchPath(state.router.location.pathname, { path: `${SAFELIST_ADDRESS}/:safeAddress` })
|
const match = matchPath(state.router.location.pathname, { path: `${SAFELIST_ADDRESS}/:safeAddress` })
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const web3 = getWeb3()
|
return checksumAddress(match.params.safeAddress)
|
||||||
return web3.utils.toChecksumAddress(match.params.safeAddress)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
@ -64,7 +63,7 @@ export const safeTransactionsSelector = createSelector(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export const addressBookQueryParamsSelector = (state) => {
|
export const addressBookQueryParamsSelector = (state: AppReduxState): string | null => {
|
||||||
const { location } = state.router
|
const { location } = state.router
|
||||||
let entryAddressToEditOrCreateNew = null
|
let entryAddressToEditOrCreateNew = null
|
||||||
if (location && location.query) {
|
if (location && location.query) {
|
||||||
|
@ -116,20 +115,26 @@ export const safeSelector = createSelector(safesMapSelector, safeParamAddressFro
|
||||||
return safe
|
return safe
|
||||||
})
|
})
|
||||||
|
|
||||||
export const safeActiveTokensSelector = createSelector(safeSelector, (safe) => {
|
export const safeActiveTokensSelector = createSelector(
|
||||||
if (!safe) {
|
safeSelector,
|
||||||
return List()
|
(safe): Set<string> => {
|
||||||
}
|
if (!safe) {
|
||||||
|
return Set()
|
||||||
|
}
|
||||||
|
|
||||||
return safe.activeTokens
|
return safe.activeTokens
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
export const safeActiveAssetsSelector = createSelector(safeSelector, (safe) => {
|
export const safeActiveAssetsSelector = createSelector(
|
||||||
if (!safe) {
|
safeSelector,
|
||||||
return List()
|
(safe): Set<string> => {
|
||||||
}
|
if (!safe) {
|
||||||
return safe.activeAssets
|
return Set()
|
||||||
})
|
}
|
||||||
|
return safe.activeAssets
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
export const safeActiveAssetsListSelector = createSelector(safeActiveAssetsSelector, (safeList) => {
|
export const safeActiveAssetsListSelector = createSelector(safeActiveAssetsSelector, (safeList) => {
|
||||||
if (!safeList) {
|
if (!safeList) {
|
||||||
|
@ -154,20 +159,21 @@ export const safeBlacklistedAssetsSelector = createSelector(safeSelector, (safe)
|
||||||
return safe.blacklistedAssets
|
return safe.blacklistedAssets
|
||||||
})
|
})
|
||||||
|
|
||||||
export const safeActiveAssetsSelectorBySafe = (safeAddress, safes) => safes.get(safeAddress).get('activeAssets')
|
export const safeActiveAssetsSelectorBySafe = (safeAddress: string, safes: SafesMap) =>
|
||||||
|
safes.get(safeAddress).get('activeAssets')
|
||||||
|
|
||||||
export const safeBlacklistedAssetsSelectorBySafe = (safeAddress, safes) =>
|
export const safeBlacklistedAssetsSelectorBySafe = (safeAddress, safes) =>
|
||||||
safes.get(safeAddress).get('blacklistedAssets')
|
safes.get(safeAddress).get('blacklistedAssets')
|
||||||
|
|
||||||
export const safeBalancesSelector = createSelector(safeSelector, (safe) => {
|
export const safeBalancesSelector = createSelector(safeSelector, (safe) => {
|
||||||
if (!safe) {
|
if (!safe) {
|
||||||
return List()
|
return Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
return safe.balances
|
return safe.balances
|
||||||
})
|
})
|
||||||
|
|
||||||
export const safeFieldSelector = (field) => (safe) => safe?.[field]
|
export const safeFieldSelector = (field: string) => (safe: SafeRecord) => safe?.[field]
|
||||||
|
|
||||||
export const safeNameSelector = createSelector(safeSelector, safeFieldSelector('name'))
|
export const safeNameSelector = createSelector(safeSelector, safeFieldSelector('name'))
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { connectRouter, routerMiddleware } from 'connected-react-router'
|
import { connectRouter, routerMiddleware, RouterState } from 'connected-react-router'
|
||||||
import { createHashHistory } from 'history'
|
import { createHashHistory } from 'history'
|
||||||
import { applyMiddleware, combineReducers, compose, createStore } from 'redux'
|
import { applyMiddleware, CombinedState, combineReducers, compose, createStore } from 'redux'
|
||||||
import thunk from 'redux-thunk'
|
import thunk from 'redux-thunk'
|
||||||
|
|
||||||
import addressBookMiddleware from 'src/logic/addressBook/store/middleware/addressBookMiddleware'
|
import addressBookMiddleware from 'src/logic/addressBook/store/middleware/addressBookMiddleware'
|
||||||
|
@ -27,8 +27,12 @@ import cancellationTransactions, {
|
||||||
import incomingTransactions, {
|
import incomingTransactions, {
|
||||||
INCOMING_TRANSACTIONS_REDUCER_ID,
|
INCOMING_TRANSACTIONS_REDUCER_ID,
|
||||||
} from 'src/routes/safe/store/reducer/incomingTransactions'
|
} from 'src/routes/safe/store/reducer/incomingTransactions'
|
||||||
import safe, { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe'
|
import safe, { SAFE_REDUCER_ID, SafeReducerMap } from 'src/routes/safe/store/reducer/safe'
|
||||||
import transactions, { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transactions'
|
import transactions, { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transactions'
|
||||||
|
import { Map } from 'immutable'
|
||||||
|
import { NFTAssets, NFTTokens } from '../logic/collectibles/sources/OpenSea'
|
||||||
|
import { ProviderRecord } from '../logic/wallets/store/model/provider'
|
||||||
|
import { Token } from 'src/logic/tokens/store/model/token'
|
||||||
|
|
||||||
export const history = createHashHistory({ hashType: 'slash' })
|
export const history = createHashHistory({ hashType: 'slash' })
|
||||||
|
|
||||||
|
@ -63,6 +67,23 @@ const reducers = combineReducers({
|
||||||
[CURRENT_SESSION_REDUCER_ID]: currentSession,
|
[CURRENT_SESSION_REDUCER_ID]: currentSession,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export type AppReduxState = CombinedState<{
|
||||||
|
[PROVIDER_REDUCER_ID]?: ProviderRecord
|
||||||
|
[SAFE_REDUCER_ID]: SafeReducerMap
|
||||||
|
[NFT_ASSETS_REDUCER_ID]?: NFTAssets
|
||||||
|
[NFT_TOKENS_REDUCER_ID]?: NFTTokens
|
||||||
|
[TOKEN_REDUCER_ID]?: Map<string, Token>
|
||||||
|
[TRANSACTIONS_REDUCER_ID]: Map<string, any>
|
||||||
|
[CANCELLATION_TRANSACTIONS_REDUCER_ID]: Map<string, any>
|
||||||
|
[INCOMING_TRANSACTIONS_REDUCER_ID]: Map<string, any>
|
||||||
|
[NOTIFICATIONS_REDUCER_ID]: Map<string, any>
|
||||||
|
[CURRENCY_VALUES_KEY]: Map<string, any>
|
||||||
|
[COOKIES_REDUCER_ID]: Map<string, any>
|
||||||
|
[ADDRESS_BOOK_REDUCER_ID]: Map<string, any>
|
||||||
|
[CURRENT_SESSION_REDUCER_ID]: Map<string, any>
|
||||||
|
router: RouterState
|
||||||
|
}>
|
||||||
|
|
||||||
export const store: any = createStore(reducers, finalCreateStore)
|
export const store: any = createStore(reducers, finalCreateStore)
|
||||||
|
|
||||||
export const aNewStore = (localState?: any) => createStore(reducers, localState, finalCreateStore)
|
export const aNewStore = (localState?: any) => createStore(reducers, localState, finalCreateStore)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||||
|
|
||||||
export const checksumAddress = (address) => {
|
export const checksumAddress = (address: string): string => {
|
||||||
if (!address) return null
|
if (!address) return null
|
||||||
return getWeb3().utils.toChecksumAddress(address)
|
return getWeb3().utils.toChecksumAddress(address)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue