Merge branch 'development' of github.com:gnosis/safe-react into development
This commit is contained in:
commit
79bf102166
|
@ -6,6 +6,7 @@ REACT_APP_GOOGLE_ANALYTICS=
|
|||
REACT_APP_INFURA_TOKEN=
|
||||
REACT_APP_IPFS_GATEWAY=https://ipfs.io/ipfs
|
||||
PUBLIC_URL=/app/
|
||||
REACT_APP_SENTRY_DSN=
|
||||
|
||||
# For production environments
|
||||
REACT_APP_BLOCKNATIVE_KEY=
|
||||
|
|
35
.travis.yml
35
.travis.yml
|
@ -1,4 +1,4 @@
|
|||
if: (branch = development) OR (branch = master) OR (branch = release/2.14.0) OR (type = pull_request) OR (tag IS present)
|
||||
if: (branch = development) OR (branch = master) OR (type = pull_request) OR (tag IS present)
|
||||
sudo: required
|
||||
dist: bionic
|
||||
language: node_js
|
||||
|
@ -10,30 +10,38 @@ matrix:
|
|||
include:
|
||||
- env:
|
||||
- REACT_APP_NETWORK='mainnet'
|
||||
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET}
|
||||
- STAGING_BUCKET_NAME=${STAGING_MAINNET_BUCKET_NAME}
|
||||
- REACT_APP_SENTRY_DSN=${SENTRY_DSN_MAINNET}
|
||||
- SENTRY_PROJECT=${SENTRY_PROJECT_MAINNET}
|
||||
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET}
|
||||
- REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_PROD}
|
||||
if: (branch = master AND NOT type = pull_request) OR tag IS present
|
||||
- env:
|
||||
- REACT_APP_NETWORK='rinkeby'
|
||||
- REACT_APP_SENTRY_DSN=${SENTRY_DSN_RINKEBY}
|
||||
- SENTRY_PROJECT=${SENTRY_PROJECT_RINKEBY}
|
||||
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY}
|
||||
- REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_STAGING}
|
||||
- env:
|
||||
- REACT_APP_NETWORK='xdai'
|
||||
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_XDAI}
|
||||
- STAGING_BUCKET_NAME=${STAGING_XDAI_BUCKET_NAME}
|
||||
if: (branch = master AND NOT type = pull_request) OR tag IS present
|
||||
- REACT_APP_SENTRY_DSN=${SENTRY_DSN_XDAI}
|
||||
- SENTRY_PROJECT=${SENTRY_PROJECT_XDAI}
|
||||
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_XDAI}
|
||||
if: (branch = master) OR tag IS present
|
||||
- env:
|
||||
- REACT_APP_NETWORK='volta'
|
||||
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_VOLTA}
|
||||
- STAGING_BUCKET_NAME=${STAGING_VOLTA_BUCKET_NAME}
|
||||
if: (branch = master AND NOT type = pull_request) OR tag IS present
|
||||
- REACT_APP_SENTRY_DSN=${SENTRY_DSN_VOLTA}
|
||||
- SENTRY_PROJECT=${SENTRY_PROJECT_VOLTA}
|
||||
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_VOLTA}
|
||||
- env:
|
||||
- REACT_APP_NETWORK='energy_web_chain'
|
||||
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_EWC}
|
||||
- STAGING_BUCKET_NAME=${STAGING_EWC_BUCKET_NAME}
|
||||
if: ((branch = master OR branch = release/2.14.0) AND NOT type = pull_request) OR tag IS present
|
||||
|
||||
- REACT_APP_SENTRY_DSN=${SENTRY_DSN_EWC}
|
||||
- SENTRY_PROJECT=${SENTRY_PROJECT_EWC}
|
||||
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_EWC}
|
||||
if: (branch = master AND NOT type = pull_request) OR tag IS present
|
||||
cache:
|
||||
yarn: true
|
||||
before_script:
|
||||
|
@ -49,7 +57,12 @@ script:
|
|||
- yarn prettier:check
|
||||
- yarn test:coverage
|
||||
- yarn build
|
||||
#- bash ./config/travis/build.sh
|
||||
- if [[ $TRAVIS_BRANCH == "master" && $TRAVIS_PULL_REQUEST == "false" ]] || [ -n "$TRAVIS_TAG" ]; then
|
||||
echo "Upload sentry source maps";
|
||||
yarn sentry-upload-sourcemaps;
|
||||
else
|
||||
echo "Skip source map upload";
|
||||
fi;
|
||||
after_success:
|
||||
# Pull Request - Deploy it to a review environment
|
||||
# Travis doesn't do deploy step with pull requests builds
|
||||
|
@ -93,7 +106,7 @@ deploy:
|
|||
upload_dir: current/app
|
||||
region: $AWS_DEFAULT_REGION
|
||||
on:
|
||||
branch: release/2.14.0
|
||||
branch: release/v2.14.0
|
||||
condition: $REACT_APP_NETWORK = energy_web_chain
|
||||
|
||||
# Prepare production deployment
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
export NODE_ENV=production;
|
||||
|
||||
if [[ -n "$TRAVIS_TAG" ]]; then export REACT_APP_ENV='production'; fi
|
||||
|
||||
yarn lint:check
|
||||
yarn prettier:check
|
||||
yarn test:coverage
|
||||
yarn build
|
|
@ -0,0 +1,359 @@
|
|||
# Network Configuration
|
||||
|
||||
## Network configuration structure
|
||||
|
||||
We have currently this structure for the network configuration:
|
||||
|
||||
- This is the main configuration that you need to provide in order to add a new network.
|
||||
```typescript
|
||||
export interface NetworkConfig {
|
||||
network: NetworkSettings
|
||||
disabledFeatures?: SafeFeatures
|
||||
disabledWallets?: Wallets
|
||||
environment: SafeEnvironments
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### NetworkSettings
|
||||
|
||||
- It contains the Ethereum compatible network id, the network name, information about the native coin of that network and a boolean to indicate if the network is a testnet or a production network.
|
||||
|
||||
```typescript
|
||||
export type NetworkSettings = {
|
||||
id: ETHEREUM_NETWORK,
|
||||
backgroundColor: string,
|
||||
textColor: string,
|
||||
label: string,
|
||||
isTestNet: boolean,
|
||||
nativeCoin: Token,
|
||||
}
|
||||
```
|
||||
|
||||
- Currently supported Ethereum compatible networks:
|
||||
|
||||
```typescript
|
||||
export enum ETHEREUM_NETWORK {
|
||||
MAINNET = 1,
|
||||
MORDEN = 2,
|
||||
ROPSTEN = 3,
|
||||
RINKEBY = 4,
|
||||
GOERLI = 5,
|
||||
KOVAN = 42,
|
||||
XDAI = 100,
|
||||
ENERGY_WEB_CHAIN = 246,
|
||||
VOLTA = 73799,
|
||||
UNKNOWN = 0,
|
||||
LOCAL = 4447,
|
||||
}
|
||||
```
|
||||
|
||||
- This is the structure to define the native coin:
|
||||
|
||||
```typescript
|
||||
type Token = {
|
||||
address: string
|
||||
name: string
|
||||
symbol: string
|
||||
decimals: number
|
||||
logoUri?: string
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### SafeFeatures
|
||||
|
||||
It's an array that contains a list of features that should be disabled for the network. It's empty by default.
|
||||
|
||||
```typescript
|
||||
export type SafeFeatures = FEATURES[]
|
||||
|
||||
export enum FEATURES {
|
||||
ERC721 = 'ERC721',
|
||||
ERC1155 = 'ERC1155',
|
||||
SAFE_APPS = 'SAFE_APPS',
|
||||
CONTRACT_INTERACTION = 'CONTRACT_INTERACTION'
|
||||
}
|
||||
```
|
||||
|
||||
#### Wallets
|
||||
|
||||
It's an array that contains a list of wallets that will be disabled for the network. It's empty by default.
|
||||
|
||||
```typescript
|
||||
export type Wallets = WALLETS[]
|
||||
```
|
||||
|
||||
```typescript
|
||||
export enum WALLETS {
|
||||
METAMASK = 'metamask',
|
||||
WALLET_CONNECT = 'walletConnect',
|
||||
TREZOR = 'trezor',
|
||||
LEDGER = 'ledger',
|
||||
TRUST = 'trust',
|
||||
DAPPER = 'dapper',
|
||||
FORTMATIC = 'fortmatic',
|
||||
PORTIS = 'portis',
|
||||
AUTHEREUM = 'authereum',
|
||||
TORUS = 'torus',
|
||||
UNILOGIN = 'unilogin',
|
||||
COINBASE = 'coinbase',
|
||||
WALLET_LINK = 'walletLink',
|
||||
OPERA = 'opera',
|
||||
OPERA_TOUCH = 'operaTouch'
|
||||
}
|
||||
```
|
||||
|
||||
#### SafeEnviroments
|
||||
|
||||
If the network has different enviroments, you can add them here, otherwise you should only add production settings
|
||||
|
||||
```typescript
|
||||
type SafeEnvironments = {
|
||||
dev?: EnvironmentSettings
|
||||
staging?: EnvironmentSettings
|
||||
production: EnvironmentSettings
|
||||
}
|
||||
```
|
||||
|
||||
We use a transaction service (**txServiceUrl**) to fetch transactions and balances of each safe and also to POST messages with the created transactions, this should be provided by Gnosis.
|
||||
|
||||
The **networkExplorer** parameters are used to provide information related to the networkExplorer used for the given network (Blockscout for xDai, Etherscan for mainnet, etc). This is used for link transaction hashes and addresses to the given network explorer.
|
||||
|
||||
```typescript
|
||||
export type EnvironmentSettings = GasPrice & {
|
||||
txServiceUrl: string
|
||||
relayApiUrl?: string
|
||||
safeAppsUrl: string
|
||||
rpcServiceUrl: string
|
||||
networkExplorerName: string
|
||||
networkExplorerUrl: string
|
||||
networkExplorerApiUrl: string
|
||||
}
|
||||
```
|
||||
|
||||
The **gasPrice** is used to indicate a fixed amount for some networks (like xDai), otherwise you can provide an oracle we can use to fetch the current gas price.
|
||||
|
||||
```typescript
|
||||
type GasPrice = {
|
||||
gasPrice: number
|
||||
gasPriceOracle?: GasPriceOracle
|
||||
} | {
|
||||
gasPrice?: number
|
||||
// for infura there's a REST API Token required stored in: `REACT_APP_INFURA_TOKEN`
|
||||
gasPriceOracle: GasPriceOracle
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
export type GasPriceOracle = {
|
||||
url: string
|
||||
// Different gas api providers can use a different name to reflect different gas levels based on tx speed
|
||||
// For example in ethGasStation for ETHEREUM_MAINNET = safeLow | average | fast
|
||||
gasParameter: string
|
||||
}
|
||||
```
|
||||
|
||||
### Enviroment variables:
|
||||
|
||||
- **REACT_APP_NETWORK**: name of the used network (ex: xDai, mainnet, rinkeby)
|
||||
- **REACT_APP_GOOGLE_ANALYTICS**: Used for enabling google analytics
|
||||
- **REACT_APP_PORTIS_ID**: Portis ID for enabling it on given network
|
||||
- **REACT_APP_FORTMATIC_KEY**: Formatic yey for given network
|
||||
- **REACT_APP_BLOCKNATIVE_KEY**: Blocknative key for given network
|
||||
|
||||
---
|
||||
## How to add a network
|
||||
|
||||
1) In case that it is not already supported, add the network on the **ETHEREUM_NETWORK** enum in [`src/config/networks/network.d.ts`](/src/config/networks/network.d.ts)
|
||||
|
||||
```typescript
|
||||
export enum ETHEREUM_NETWORK {
|
||||
MAINNET = 1,
|
||||
MORDEN = 2,
|
||||
ROPSTEN = 3,
|
||||
RINKEBY = 4,
|
||||
GOERLI = 5,
|
||||
KOVAN = 42,
|
||||
XDAI = 100,
|
||||
ENERGY_WEB_CHAIN = 246,
|
||||
VOLTA = 73799,
|
||||
UNKNOWN = 0,
|
||||
LOCAL = 4447,
|
||||
}
|
||||
```
|
||||
|
||||
2) Add **env variables**:
|
||||
|
||||
* REACT_APP_NETWORK
|
||||
* REACT_APP_GOOGLE_ANALYTICS
|
||||
* REACT_APP_PORTIS_ID
|
||||
* REACT_APP_FORTMATIC_KEY
|
||||
* REACT_APP_BLOCKNATIVE_KEY
|
||||
|
||||
3) Add the **NetworkSettings** in [`src/config/networks`](/src/config/networks) as `<network_name>.ts`:
|
||||
|
||||
```typescript
|
||||
import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkConfig } from 'src/config/networks/network.d'
|
||||
|
||||
const baseConfig: EnvironmentSettings = {
|
||||
txServiceUrl: '',
|
||||
safeAppsUrl: '',
|
||||
gasPriceOracleUrl: '',
|
||||
gasPriceOracle: {
|
||||
url: '',
|
||||
gasParameter: '',
|
||||
},
|
||||
rpcServiceUrl: '',
|
||||
networkExplorerName: '',
|
||||
networkExplorerUrl: '',
|
||||
networkExplorerApiUrl: '',
|
||||
}
|
||||
|
||||
const rinkeby: NetworkConfig = {
|
||||
environment: {
|
||||
dev: {
|
||||
...baseConfig,
|
||||
},
|
||||
staging: {
|
||||
...baseConfig,
|
||||
safeAppsUrl: '',
|
||||
},
|
||||
production: {
|
||||
...baseConfig,
|
||||
txServiceUrl: '',
|
||||
safeAppsUrl: '',
|
||||
},
|
||||
},
|
||||
network: {
|
||||
id: ETHEREUM_NETWORK.<NETWORK_NAME>,
|
||||
backgroundColor: '',
|
||||
textColor: '',
|
||||
label: '',
|
||||
isTestNet: true/false,
|
||||
nativeCoin: {
|
||||
address: '',
|
||||
name: '',
|
||||
symbol: '',
|
||||
decimals: ?,
|
||||
logoUri: '',
|
||||
},
|
||||
},
|
||||
disabledFeatures: [],
|
||||
disabledWallets: []
|
||||
}
|
||||
|
||||
export default <NETWORK_NAME>
|
||||
```
|
||||
|
||||
## Configuration example (xDai) - fixed gas price
|
||||
|
||||
1) **ETHEREUM_NETWORK**
|
||||
```typescript
|
||||
export enum ETHEREUM_NETWORK {
|
||||
MAINNET = 1,
|
||||
MORDEN = 2,
|
||||
ROPSTEN = 3,
|
||||
RINKEBY = 4,
|
||||
GOERLI = 5,
|
||||
KOVAN = 42,
|
||||
XDAI = 100, -> ADDED XDAI
|
||||
ENERGY_WEB_CHAIN = 246,
|
||||
VOLTA = 73799,
|
||||
UNKNOWN = 0,
|
||||
LOCAL = 4447,
|
||||
}
|
||||
```
|
||||
|
||||
2) **Network file** [xdai](/src/config/networks/xdai.ts)
|
||||
|
||||
```typescript
|
||||
import { ETHEREUM_NETWORK, NetworkConfig } from 'src/config/networks/network.d'
|
||||
|
||||
const xDai: NetworkConfig = {
|
||||
environment: {
|
||||
production: {
|
||||
txServiceUrl: 'https://safe-transaction.xdai.gnosis.io/api/v1',
|
||||
safeAppsUrl: 'https://safe-apps-xdai.staging.gnosisdev.com',
|
||||
gasPrice: 1e9,
|
||||
rpcServiceUrl: 'https://dai.poa.network/',
|
||||
networkExplorerName: 'Blockscout',
|
||||
networkExplorerUrl: 'https://blockscout.com/poa/xdai',
|
||||
networkExplorerApiUrl: 'https://blockscout.com/poa/xdai/api',
|
||||
},
|
||||
},
|
||||
network: {
|
||||
id: ETHEREUM_NETWORK.XDAI,
|
||||
backgroundColor: '#48A8A6',
|
||||
textColor: '#ffffff',
|
||||
label: 'xDai',
|
||||
isTestNet: false,
|
||||
nativeCoin: {
|
||||
address: '0x000',
|
||||
name: 'xDai',
|
||||
symbol: 'xDai',
|
||||
decimals: 18,
|
||||
logoUri: xDaiLogo,
|
||||
},
|
||||
},
|
||||
disabledWallets:[
|
||||
WALLETS.TREZOR,
|
||||
WALLETS.LEDGER
|
||||
]
|
||||
}
|
||||
|
||||
export default xDai
|
||||
```
|
||||
|
||||
## Configuration example (Mainnet) - gas price retrieven by oracle
|
||||
|
||||
|
||||
**Network file** [mainnet](/src/config/networks/mainnet.ts)
|
||||
|
||||
```typescript
|
||||
const baseConfig: EnvironmentSettings = {
|
||||
txServiceUrl: 'https://safe-transaction.mainnet.staging.gnosisdev.com/api/v1',
|
||||
safeAppsUrl: 'https://safe-apps.dev.gnosisdev.com',
|
||||
gasPriceOracle: {
|
||||
url: 'https://ethgasstation.info/json/ethgasAPI.json',
|
||||
gasParameter: 'average',
|
||||
},
|
||||
rpcServiceUrl: 'https://mainnet.infura.io:443/v3',
|
||||
networkExplorerName: 'Etherscan',
|
||||
networkExplorerUrl: 'https://etherscan.io',
|
||||
networkExplorerApiUrl: 'https://api.etherscan.io/api',
|
||||
}
|
||||
|
||||
const mainnet: NetworkConfig = {
|
||||
environment: {
|
||||
dev: {
|
||||
...baseConfig,
|
||||
},
|
||||
staging: {
|
||||
...baseConfig,
|
||||
safeAppsUrl: 'https://safe-apps.staging.gnosisdev.com',
|
||||
},
|
||||
production: {
|
||||
...baseConfig,
|
||||
txServiceUrl: 'https://safe-transaction.mainnet.gnosis.io/api/v1',
|
||||
safeAppsUrl: 'https://apps.gnosis-safe.io',
|
||||
},
|
||||
},
|
||||
network: {
|
||||
id: ETHEREUM_NETWORK.MAINNET,
|
||||
backgroundColor: '#E8E7E6',
|
||||
textColor: '#001428',
|
||||
label: 'Mainnet',
|
||||
isTestNet: false,
|
||||
nativeCoin: {
|
||||
address: '0x000',
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
logoUri: EtherLogo,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default mainnet
|
||||
```
|
12
package.json
12
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "safe-react",
|
||||
"version": "2.13.1",
|
||||
"version": "2.14.0",
|
||||
"description": "Allowing crypto users manage funds in a safer way",
|
||||
"website": "https://github.com/gnosis/safe-react#readme",
|
||||
"bugs": {
|
||||
|
@ -42,7 +42,8 @@
|
|||
"test:coverage": "yarn test --coverage --watchAll=false",
|
||||
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
||||
"storybook": "start-storybook -p 9009 -s public",
|
||||
"build-storybook": "build-storybook -s public"
|
||||
"build-storybook": "build-storybook -s public",
|
||||
"sentry-upload-sourcemaps": "sentry-cli --auth-token $SENTRY_AUTH_TOKEN releases -o $SENTRY_ORG -p $SENTRY_PROJECT files $npm_package_version upload-sourcemaps ./build/static/js/"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
@ -167,18 +168,20 @@
|
|||
"dependencies": {
|
||||
"@gnosis.pm/safe-apps-sdk": "https://github.com/gnosis/safe-apps-sdk.git#3f0689f",
|
||||
"@gnosis.pm/safe-contracts": "1.1.1-dev.2",
|
||||
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#70e57bdd1e0fd5dfdf5768076577c1e000b5fe28",
|
||||
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#8d8508e",
|
||||
"@gnosis.pm/util-contracts": "2.0.6",
|
||||
"@ledgerhq/hw-transport-node-hid": "5.26.0",
|
||||
"@material-ui/core": "4.11.0",
|
||||
"@material-ui/icons": "4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.56",
|
||||
"@openzeppelin/contracts": "3.1.0",
|
||||
"@sentry/react": "^5.27.1",
|
||||
"@sentry/tracing": "^5.27.1",
|
||||
"@truffle/contract": "4.2.26",
|
||||
"async-sema": "^3.1.0",
|
||||
"axios": "0.20.0",
|
||||
"bignumber.js": "9.0.1",
|
||||
"bnc-onboard": "1.13.2",
|
||||
"bnc-onboard": "1.14.0",
|
||||
"classnames": "^2.2.6",
|
||||
"concurrently": "^5.3.0",
|
||||
"connected-react-router": "6.8.0",
|
||||
|
@ -232,6 +235,7 @@
|
|||
"web3-utils": "^1.2.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/cli": "^1.58.0",
|
||||
"@storybook/addon-actions": "^5.3.19",
|
||||
"@storybook/addon-links": "^5.3.19",
|
||||
"@storybook/addons": "^5.3.19",
|
||||
|
|
|
@ -101,6 +101,10 @@ Give an example
|
|||
|
||||
Add additional notes about how to deploy this on a live system
|
||||
|
||||
## Configuring the app for running on different networks
|
||||
|
||||
[Please check the network configuration documentation](./docs/networks.md)
|
||||
|
||||
## Built With
|
||||
|
||||
* [Truffle React Box](https://github.com/truffle-box/react-box) - The web framework used
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import React from 'react'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Bold from 'src/components/layout/Bold'
|
||||
import Paragraph from 'src/components/layout/Paragraph'
|
||||
import { border, xs } from 'src/theme/variables'
|
||||
import styled from 'styled-components'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
|
@ -61,7 +61,7 @@ const AddressInfo = ({ ethBalance, safeAddress, safeName }: Props): React.ReactE
|
|||
{safeAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={safeAddress} />
|
||||
<EtherscanBtn value={safeAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(safeAddress)} />
|
||||
</div>
|
||||
{ethBalance && (
|
||||
<StyledBlock>
|
||||
|
|
|
@ -5,7 +5,6 @@ import QRCode from 'qrcode.react'
|
|||
import React, { ReactElement } from 'react'
|
||||
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
|
@ -15,7 +14,8 @@ import Paragraph from 'src/components/layout/Paragraph'
|
|||
import Row from 'src/components/layout/Row'
|
||||
import { border, fontColor, lg, md, screenSm, secondaryText, sm } from 'src/theme/variables'
|
||||
import { copyToClipboard } from 'src/utils/clipboard'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
const networkInfo = getNetworkInfo()
|
||||
const useStyles = makeStyles(
|
||||
|
@ -126,7 +126,7 @@ const ReceiveModal = ({ onClose, safeAddress, safeName }: Props): ReactElement =
|
|||
{safeAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={safeAddress} />
|
||||
<EtherscanBtn value={safeAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(safeAddress)} />
|
||||
</Block>
|
||||
</Col>
|
||||
<Hairline />
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<svg width="41px" height="41px" viewBox="0 0 41 41" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
||||
<!-- Generated by Pixelmator Pro 1.8 -->
|
||||
<defs>
|
||||
<image id="image" width="41px" height="41px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAApCAYAAACoYAD2AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAKaADAAQAAAABAAAAKQAAAADAIIRfAAAFp0lEQVRYCdVYbUyVZRi+DwqEiimO9NBwwEnIlqwgk5D5AZsJLlrQ0rnVUFvrB8TmDwJqY8aYQA3b+tOf5mbFxjI/Go1+tMH6Ia0NB9iPwlMgoHw4VCZw+JDzdF2PvKeX46FzDnA8dG/Xnq/7ue/rfT7u53kfkf+BWBbJcT36xQNJQDqwA9gKbAZocwjoBX4HfgX+AP4CRoGAyzp4+Bi4DtwHnIDyAuqMAX8DpwB+YEDkaVj9CLgJeCPlrX0ANkg2Flg2eRGWOH3enPvbPgKbGQCXx6JlNXp+AkwC/hLwVZ+2a4EwwG9Zgx7fAL46W6red/DF9e6zcAS/BmaApTr3tf8D+LoAhANeheujEvDV+HLrfeaVIRReAiaCSHIKvvcCC0oMWgKxi/0d7XvgEWdmGWIqHEf+KVM5WNkn4fhdT865s/oBf786UPq3wWWTO1GeJoFyuFi7n5pJRqJgX4Eke8ApChCuyWcA3l6CJomvFagtL+x29x+NChsrSTIRWMtCsOTZw8Vi3Znl7p6nXjIrebq8ArgO+aioKMnMzJRt27ZJSEiI9PX1SUtLi/T29lJfoqOjpbCwUMLCwsRiscj09LR0dXVJY2Oj3LvH6IFjIzxcysvLdfnMmTO6bvv27XKsoED3YUVNba1Yc45jvVpk9ZpI2fTcy7LjnQ/VXXuH9F/5yeDDu+pX1P8Z0IsbBJTdbldOp1PBuZqamlKzs7Oqra3NtfiTkpLU2NiYejAzo8bHx5XD4VCU1tZWBXJaLzIyUo2OjqrOzk5Xv5ycHCf1Jicndf+EBJvKu2BXeeevq7d+vKXe/OGGeuN8l0p5v9J8R/2NBCldgIqLi1O3bt7UBkpLS1VaWppKTUlReXl5Kjs72+XMIHnp4kXdJzk5WZ09e5b+VWVlpVeSFRUVKj4+XoWGhqqITZudxOv1Hc6dH9Toctja9S5f4MW7p57uLczk5uaKNSZGysrKpLq6mlVa2q5eNbLz0vGJCenp6dF1J0+elKNHj8r+/fvn6Xgq3BkZke7ubt00MzKkp3ViuF9N3L4ljrmyqZ++xXNNakWMiGAwpLm52aTjW/bQoUOCkZH+fp4H/ssvFW9bnNM8tj0LdzfPa02QKTeLWfbt3SsOjJr7KKXt2iWc5qamJjl37pwMDAzI6dOnzV3n5zEAnsRqtcpAt12qK095auZ/lJ7uPqS29vZ2vfOysrIEm8DVwQLST0REuMhztLnPEmw2Qs8CNpjk5+dLR0eHq99DPVfx3wwiglkyMjJk48aNOoqY6+fy5KbjJH875fLly1qRa7KqqkrS09MlJSVFhyO2u0tDQ4MOQyUlJTo9dqzAXcVjOTY2VrAhZTfsFxcXS11dnQwODsrFS5c86WtubDgC6OHZsGGDDhsMP4bMINTAiELc1DqJiYmIAPdVfX29LiNe6j7UP3L4sELsVAxBiJnzQ1B2tg5Bhl2mhu1XDxx4OD1zPAw+SN8D9HT/iZT/xesYjPfs2SOpqanCL6YMDQ0JlwLXnFEuLCxyBXcG84MHDwrClDgmJ2XVqlWCWChFRUUyPkazD6Xz2jXLiRMndAH8BAS1bS6R4eFhQ82cOlBwrR/e3/i6sNDXBKue61Ff17iV+fTxLbDS5HsQ4r+5Sziag0CwRs3dL8nxFqTFCIoczS/n6lZCwksFb+ePyFbU8Avcv+pxlxnAbY+wM1XwasTz6XETM/xNw3eOic+C2ZogkvwCvucfSQvQ5OMRdxafPowvDHQ6C1+NQATgs/AX9zwQaHKG/Sb40tcynxnOKYYj5dsMn+cMY8udcg1yiv0aQejPE66PfcBdYLkJchdnA8smcbBUBfCQXSpZhrla4D/DDNoXLfxZ58vCDcCf1zfqss/ngN/vTT5tdxh2F5Llo8IOgLH1eYCHgbH4OZX8l7gGXAF4m+El5g7AmfBL/gFhgZO179JGyAAAAABJRU5ErkJggg=="/>
|
||||
</defs>
|
||||
<use id="image-1" xlink:href="#image" x="0px" y="0px" width="41px" height="41px"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -3,6 +3,7 @@ import metamaskIcon from './icon-metamask.png'
|
|||
import walletConnectIcon from './icon-wallet-connect.svg'
|
||||
import trezorIcon from './icon-trezor.svg'
|
||||
import ledgerIcon from './icon-ledger.svg'
|
||||
import latticeIcon from './icon-lattice.svg'
|
||||
import dapperIcon from './icon-dapper.png'
|
||||
import fortmaticIcon from './icon-fortmatic.svg'
|
||||
import portisIcon from './icon-portis.svg'
|
||||
|
@ -42,6 +43,10 @@ const WALLET_ICONS: WalletObjectsProps<IconValue> = {
|
|||
src: ledgerIcon,
|
||||
height: 25,
|
||||
},
|
||||
[WALLET_PROVIDER.LATTICE]: {
|
||||
src: latticeIcon,
|
||||
height: 41,
|
||||
},
|
||||
[WALLET_PROVIDER.DAPPER]: {
|
||||
src: dapperIcon,
|
||||
height: 25,
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<path fill="#B2B5B2" fill-rule="nonzero" d="M17 17v-2a1 1 0 0 1 2 0v2a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2a1 1 0 1 1 0 2H7v10h10z"/>
|
||||
<path fill="#B2B5B2" d="M15.586 7H13a1 1 0 0 1 0-2h5a.997.997 0 0 1 1 1v5a1 1 0 0 1-2 0V8.414l-6.243 6.243a1 1 0 1 1-1.414-1.414L15.586 7z"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 442 B |
|
@ -1,59 +0,0 @@
|
|||
import Tooltip from '@material-ui/core/Tooltip'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import cn from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
import EtherscanOpenIcon from './img/etherscan-open.svg'
|
||||
|
||||
import Img from 'src/components/layout/Img'
|
||||
import { xs } from 'src/theme/variables'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
|
||||
const useStyles = makeStyles({
|
||||
container: {
|
||||
alignItems: 'center',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: `0 ${xs}`,
|
||||
padding: '0',
|
||||
transition: 'background-color .2s ease-in-out',
|
||||
'&:hover': {
|
||||
backgroundColor: '#F0EFEE',
|
||||
},
|
||||
},
|
||||
increasedPopperZindex: {
|
||||
zIndex: 2001,
|
||||
},
|
||||
})
|
||||
|
||||
interface EtherscanBtnProps {
|
||||
className?: string
|
||||
increaseZindex?: boolean
|
||||
value: string
|
||||
}
|
||||
|
||||
const EtherscanBtn = ({ className = '', increaseZindex = false, value }: EtherscanBtnProps): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
const customClasses = increaseZindex ? { popper: classes.increasedPopperZindex } : {}
|
||||
|
||||
const explorerInfo = getExplorerInfo(value)
|
||||
const { url } = explorerInfo()
|
||||
|
||||
return (
|
||||
<Tooltip classes={customClasses} placement="top" title="Show details on Etherscan">
|
||||
<a
|
||||
aria-label="Show details on Etherscan"
|
||||
className={cn(classes.container, className)}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
href={url}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<Img alt="Show on Etherscan" height={20} src={EtherscanOpenIcon} />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default EtherscanBtn
|
|
@ -5,11 +5,12 @@ import React from 'react'
|
|||
import { styles } from './style'
|
||||
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Span from 'src/components/layout/Span'
|
||||
import { shortVersionOf } from 'src/logic/wallets/ethAddresses'
|
||||
import EllipsisTransactionDetails from 'src/routes/safe/components/AddressBook/EllipsisTransactionDetails'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
|
@ -20,7 +21,7 @@ interface EtherscanLinkProps {
|
|||
value: string
|
||||
}
|
||||
|
||||
const EtherscanLink = ({ className, cut, knownAddress, value }: EtherscanLinkProps): React.ReactElement => {
|
||||
export const EtherscanLink = ({ className, cut, knownAddress, value }: EtherscanLinkProps): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
|
||||
return (
|
||||
|
@ -29,10 +30,8 @@ const EtherscanLink = ({ className, cut, knownAddress, value }: EtherscanLinkPro
|
|||
{cut ? shortVersionOf(value, cut) : value}
|
||||
</Span>
|
||||
<CopyBtn className={cn(classes.button, classes.firstButton)} content={value} />
|
||||
<EtherscanBtn className={classes.button} value={value} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(value)} />
|
||||
{knownAddress !== undefined ? <EllipsisTransactionDetails address={value} knownAddress={knownAddress} /> : null}
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
|
||||
export default EtherscanLink
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Text, Link, Icon, FixedIcon, Title } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
import { IS_PRODUCTION } from 'src/utils/constants'
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
`
|
||||
|
||||
const Content = styled.div`
|
||||
width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
> * {
|
||||
margin-top: 10px;
|
||||
}
|
||||
`
|
||||
|
||||
const LinkWrapper = styled.div`
|
||||
display: inline-flex;
|
||||
margin-bottom: 10px;
|
||||
|
||||
> :first-of-type {
|
||||
margin-right: 5px;
|
||||
}
|
||||
`
|
||||
|
||||
const LinkContent = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> span {
|
||||
margin-right: 5px;
|
||||
}
|
||||
`
|
||||
|
||||
type Props = {
|
||||
error: Error
|
||||
componentStack: string
|
||||
resetError: () => void
|
||||
}
|
||||
|
||||
const GlobalErrorBoundaryFallback = ({ error, componentStack }: Props): React.ReactElement => {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Content>
|
||||
<Title size="md">Something went wrong, please try again.</Title>
|
||||
<FixedIcon type="networkError" />
|
||||
{IS_PRODUCTION && (
|
||||
<div>
|
||||
<Text size="xl" as="span">
|
||||
In case the problem persists, please reach out to us via{' '}
|
||||
</Text>
|
||||
<LinkWrapper>
|
||||
<a target="_blank" href="email: mailto:safe@gnosis.io" rel="noopener noreferrer">
|
||||
<Text color="primary" size="lg" as="span">
|
||||
Email
|
||||
</Text>
|
||||
</a>
|
||||
<Icon type="externalLink" color="primary" size="sm" />
|
||||
</LinkWrapper>
|
||||
or{' '}
|
||||
<LinkWrapper>
|
||||
<a target="_blank" href="https://discordapp.com/invite/FPMRAwK" rel="noopener noreferrer">
|
||||
<Text color="primary" size="lg" as="span">
|
||||
Discord
|
||||
</Text>
|
||||
</a>
|
||||
<Icon type="externalLink" color="primary" size="sm" />
|
||||
</LinkWrapper>
|
||||
</div>
|
||||
)}
|
||||
{!IS_PRODUCTION && (
|
||||
<>
|
||||
<Text size="xl" color="error">
|
||||
{error.toString()}
|
||||
</Text>
|
||||
<Text size="md" color="error">
|
||||
{componentStack}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
<Link size="lg" color="primary" href="/app/">
|
||||
<LinkContent>
|
||||
<Icon size="md" type="home" color="primary" />
|
||||
Go to Home
|
||||
</LinkContent>
|
||||
</Link>
|
||||
</Content>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default GlobalErrorBoundaryFallback
|
|
@ -4,10 +4,11 @@ import { ConnectedRouter } from 'connected-react-router'
|
|||
import React from 'react'
|
||||
import { Provider } from 'react-redux'
|
||||
import { ThemeProvider } from 'styled-components'
|
||||
import * as Sentry from '@sentry/react'
|
||||
|
||||
import Loader from 'src/components/Loader'
|
||||
import App from 'src/components/App'
|
||||
|
||||
import GlobalErrorBoundary from 'src/components/GlobalErrorBoundary'
|
||||
import AppRoutes from 'src/routes'
|
||||
import { history, store } from 'src/store'
|
||||
import theme from 'src/theme/mui'
|
||||
|
@ -20,7 +21,11 @@ const Root = (): React.ReactElement => (
|
|||
<ThemeProvider theme={styledTheme}>
|
||||
<Provider store={store}>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<ConnectedRouter history={history}>{<App>{wrapInSuspense(<AppRoutes />, <Loader />)}</App>}</ConnectedRouter>
|
||||
<ConnectedRouter history={history}>
|
||||
<Sentry.ErrorBoundary fallback={GlobalErrorBoundary}>
|
||||
<App>{wrapInSuspense(<AppRoutes />, <Loader />)}</App>
|
||||
</Sentry.ErrorBoundary>
|
||||
</ConnectedRouter>
|
||||
</MuiThemeProvider>
|
||||
</Provider>
|
||||
</ThemeProvider>
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import EwcLogo from 'src/config/assets/token_ewc.svg'
|
||||
import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkConfig } from 'src/config/networks/network.d'
|
||||
import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkConfig, WALLETS } from 'src/config/networks/network.d'
|
||||
|
||||
// @todo (agustin) we need to use fixed gasPrice because the oracle is not working right now and it's returning 0
|
||||
// once the oracle is fixed we need to remove the fixed value
|
||||
const baseConfig: EnvironmentSettings = {
|
||||
txServiceUrl: 'https://safe-transaction.ewc.gnosis.io/api/v1',
|
||||
safeAppsUrl: 'https://safe-apps.dev.gnosisdev.com',
|
||||
txServiceUrl: 'https://safe-transaction.ewc.gnosis.io/api/v1',
|
||||
safeAppsUrl: 'https://safe-apps-ewc.staging.gnosisdev.com',
|
||||
gasPriceOracle: {
|
||||
url: 'https://station.energyweb.org',
|
||||
gasParameter: 'standard',
|
||||
},
|
||||
gasPrice: 1e6,
|
||||
rpcServiceUrl: 'https://rpc.energyweb.org',
|
||||
networkExplorerName: 'Energy web explorer',
|
||||
networkExplorerUrl: 'https://explorer.energyweb.org',
|
||||
|
@ -21,11 +24,10 @@ const mainnet: NetworkConfig = {
|
|||
},
|
||||
staging: {
|
||||
...baseConfig,
|
||||
safeAppsUrl: 'https://safe-apps.staging.gnosisdev.com',
|
||||
},
|
||||
production: {
|
||||
...baseConfig,
|
||||
safeAppsUrl: 'https://apps.gnosis-safe.io',
|
||||
safeAppsUrl: 'https://apps-ewc.gnosis-safe.io',
|
||||
},
|
||||
},
|
||||
network: {
|
||||
|
@ -41,7 +43,24 @@ const mainnet: NetworkConfig = {
|
|||
decimals: 18,
|
||||
logoUri: EwcLogo,
|
||||
},
|
||||
}
|
||||
},
|
||||
disabledWallets:[
|
||||
WALLETS.TREZOR,
|
||||
WALLETS.LEDGER,
|
||||
WALLETS.COINBASE,
|
||||
WALLETS.DAPPER,
|
||||
WALLETS.FORTMATIC,
|
||||
WALLETS.OPERA,
|
||||
WALLETS.OPERA_TOUCH,
|
||||
WALLETS.PORTIS,
|
||||
WALLETS.TORUS,
|
||||
WALLETS.TRUST,
|
||||
WALLETS.UNILOGIN,
|
||||
WALLETS.WALLET_CONNECT,
|
||||
WALLETS.WALLET_LINK,
|
||||
WALLETS.AUTHEREUM,
|
||||
WALLETS.LATTICE
|
||||
]
|
||||
}
|
||||
|
||||
export default mainnet
|
||||
|
|
|
@ -15,7 +15,8 @@ export enum WALLETS {
|
|||
COINBASE = 'coinbase',
|
||||
WALLET_LINK = 'walletLink',
|
||||
OPERA = 'opera',
|
||||
OPERA_TOUCH = 'operaTouch'
|
||||
OPERA_TOUCH = 'operaTouch',
|
||||
LATTICE = 'lattice',
|
||||
}
|
||||
|
||||
export enum FEATURES {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import EwcLogo from 'src/config/assets/token_ewc.svg'
|
||||
import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkConfig } from 'src/config/networks/network.d'
|
||||
import { EnvironmentSettings, ETHEREUM_NETWORK, WALLETS, NetworkConfig } from 'src/config/networks/network.d'
|
||||
|
||||
const baseConfig: EnvironmentSettings = {
|
||||
txServiceUrl: 'https://safe-transaction.volta.gnosis.io/api/v1',
|
||||
safeAppsUrl: 'https://safe-apps.dev.gnosisdev.com',
|
||||
safeAppsUrl: 'https://safe-apps-volta.staging.gnosisdev.com',
|
||||
gasPriceOracle: {
|
||||
url: 'https://station.energyweb.org',
|
||||
gasParameter: 'standard',
|
||||
|
@ -21,11 +21,10 @@ const mainnet: NetworkConfig = {
|
|||
},
|
||||
staging: {
|
||||
...baseConfig,
|
||||
safeAppsUrl: 'https://safe-apps.staging.gnosisdev.com',
|
||||
},
|
||||
production: {
|
||||
...baseConfig,
|
||||
safeAppsUrl: 'https://apps.gnosis-safe.io',
|
||||
safeAppsUrl: 'https://apps-volta.gnosis-safe.io',
|
||||
},
|
||||
},
|
||||
network: {
|
||||
|
@ -41,7 +40,24 @@ const mainnet: NetworkConfig = {
|
|||
decimals: 18,
|
||||
logoUri: EwcLogo,
|
||||
},
|
||||
}
|
||||
},
|
||||
disabledWallets:[
|
||||
WALLETS.TREZOR,
|
||||
WALLETS.LEDGER,
|
||||
WALLETS.COINBASE,
|
||||
WALLETS.DAPPER,
|
||||
WALLETS.FORTMATIC,
|
||||
WALLETS.OPERA,
|
||||
WALLETS.OPERA_TOUCH,
|
||||
WALLETS.PORTIS,
|
||||
WALLETS.TORUS,
|
||||
WALLETS.TRUST,
|
||||
WALLETS.UNILOGIN,
|
||||
WALLETS.WALLET_CONNECT,
|
||||
WALLETS.WALLET_LINK,
|
||||
WALLETS.AUTHEREUM,
|
||||
WALLETS.LATTICE
|
||||
]
|
||||
}
|
||||
|
||||
export default mainnet
|
||||
|
|
|
@ -38,7 +38,19 @@ const xDai: NetworkConfig = {
|
|||
},
|
||||
disabledWallets:[
|
||||
WALLETS.TREZOR,
|
||||
WALLETS.LEDGER
|
||||
WALLETS.LEDGER,
|
||||
WALLETS.COINBASE,
|
||||
WALLETS.DAPPER,
|
||||
WALLETS.FORTMATIC,
|
||||
WALLETS.OPERA,
|
||||
WALLETS.OPERA_TOUCH,
|
||||
WALLETS.TORUS,
|
||||
WALLETS.TRUST,
|
||||
WALLETS.UNILOGIN,
|
||||
WALLETS.WALLET_CONNECT,
|
||||
WALLETS.WALLET_LINK,
|
||||
WALLETS.AUTHEREUM,
|
||||
WALLETS.LATTICE
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { BigNumber } from 'bignumber.js'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import { Integrations } from '@sentry/tracing'
|
||||
|
||||
import Root from 'src/components/Root'
|
||||
import loadCurrentSessionFromStorage from 'src/logic/currentSession/store/actions/loadCurrentSessionFromStorage'
|
||||
|
@ -8,6 +10,7 @@ import loadActiveTokens from 'src/logic/tokens/store/actions/loadActiveTokens'
|
|||
import loadDefaultSafe from 'src/logic/safe/store/actions/loadDefaultSafe'
|
||||
import loadSafesFromStorage from 'src/logic/safe/store/actions/loadSafesFromStorage'
|
||||
import { store } from 'src/store'
|
||||
import { SENTRY_DSN } from './utils/constants'
|
||||
|
||||
BigNumber.set({ EXPONENTIAL_AT: [-7, 255] })
|
||||
|
||||
|
@ -16,6 +19,13 @@ store.dispatch(loadSafesFromStorage())
|
|||
store.dispatch(loadDefaultSafe())
|
||||
store.dispatch(loadCurrentSessionFromStorage())
|
||||
|
||||
Sentry.init({
|
||||
dsn: SENTRY_DSN,
|
||||
release: `safe-react@${process.env.REACT_APP_APP_VERSION}`,
|
||||
integrations: [new Integrations.BrowserTracing()],
|
||||
sampleRate: 1,
|
||||
})
|
||||
|
||||
const root = document.getElementById('root')
|
||||
|
||||
if (root !== null) {
|
||||
|
|
|
@ -35,7 +35,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null =>
|
|||
return {
|
||||
method: METHOD_TO_ID[methodId],
|
||||
parameters: [
|
||||
{ name: 'oldOwner', type: 'address', value: decodedParameters[1] },
|
||||
{ name: 'owner', type: 'address', value: decodedParameters[1] },
|
||||
{ name: '_threshold', type: 'uint', value: decodedParameters[2] },
|
||||
],
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import { push } from 'connected-react-router'
|
||||
import { List, Map } from 'immutable'
|
||||
import { batch } from 'react-redux'
|
||||
import semverSatisfies from 'semver/functions/satisfies'
|
||||
import { ThunkAction } from 'redux-thunk'
|
||||
|
||||
|
@ -24,10 +22,11 @@ import { providerSelector } from 'src/logic/wallets/store/selectors'
|
|||
import { SAFELIST_ADDRESS } from 'src/routes/routes'
|
||||
import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar'
|
||||
import closeSnackbarAction from 'src/logic/notifications/store/actions/closeSnackbar'
|
||||
import { addOrUpdateCancellationTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateCancellationTransactions'
|
||||
import { addOrUpdateTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateTransactions'
|
||||
import { removeCancellationTransaction } from 'src/logic/safe/store/actions/transactions/removeCancellationTransaction'
|
||||
import { removeTransaction } from 'src/logic/safe/store/actions/transactions/removeTransaction'
|
||||
import {
|
||||
removeTxFromStore,
|
||||
storeSignedTx,
|
||||
storeExecutedTx,
|
||||
} from 'src/logic/safe/store/actions/transactions/pendingTransactions'
|
||||
import {
|
||||
generateSafeTxHash,
|
||||
mockTransaction,
|
||||
|
@ -35,68 +34,13 @@ import {
|
|||
} from 'src/logic/safe/store/actions/transactions/utils/transactionHelpers'
|
||||
import { getLastTx, getNewTxNonce, shouldExecuteTransaction } from 'src/logic/safe/store/actions/utils'
|
||||
import { getErrorMessage } from 'src/test/utils/ethereumErrors'
|
||||
import { makeConfirmation } from '../models/confirmation'
|
||||
import fetchTransactions from './transactions/fetchTransactions'
|
||||
import { safeTransactionsSelector } from 'src/logic/safe/store/selectors'
|
||||
import { Transaction, TransactionStatus, TxArgs } from 'src/logic/safe/store/models/types/transaction'
|
||||
import { TxArgs } from 'src/logic/safe/store/models/types/transaction'
|
||||
import { AnyAction } from 'redux'
|
||||
import { PayableTx } from 'src/types/contracts/types.d'
|
||||
import { AppReduxState } from 'src/store'
|
||||
import { Dispatch, DispatchReturn } from './types'
|
||||
|
||||
export const removeTxFromStore = (
|
||||
tx: Transaction,
|
||||
safeAddress: string,
|
||||
dispatch: Dispatch,
|
||||
state: AppReduxState,
|
||||
): void => {
|
||||
if (tx.isCancellationTx) {
|
||||
const newTxStatus = TransactionStatus.AWAITING_YOUR_CONFIRMATION
|
||||
const transactions = safeTransactionsSelector(state)
|
||||
const txsToUpdate = transactions
|
||||
.filter((transaction) => Number(transaction.nonce) === Number(tx.nonce))
|
||||
.withMutations((list) => list.map((tx) => tx.set('status', newTxStatus)))
|
||||
|
||||
batch(() => {
|
||||
dispatch(addOrUpdateTransactions({ safeAddress, transactions: txsToUpdate }))
|
||||
dispatch(removeCancellationTransaction({ safeAddress, transaction: tx }))
|
||||
})
|
||||
} else {
|
||||
dispatch(removeTransaction({ safeAddress, transaction: tx }))
|
||||
}
|
||||
}
|
||||
|
||||
export const storeTx = async (
|
||||
tx: Transaction,
|
||||
safeAddress: string,
|
||||
dispatch: Dispatch,
|
||||
state: AppReduxState,
|
||||
): Promise<void> => {
|
||||
if (tx.isCancellationTx) {
|
||||
let newTxStatus: TransactionStatus = TransactionStatus.AWAITING_YOUR_CONFIRMATION
|
||||
|
||||
if (tx.isExecuted) {
|
||||
newTxStatus = TransactionStatus.CANCELLED
|
||||
} else if (tx.status === TransactionStatus.PENDING) {
|
||||
newTxStatus = tx.status
|
||||
}
|
||||
|
||||
const transactions = safeTransactionsSelector(state)
|
||||
const txsToUpdate = transactions
|
||||
.filter((transaction) => Number(transaction.nonce) === Number(tx.nonce))
|
||||
.withMutations((list) =>
|
||||
list.map((tx) => tx.set('status', newTxStatus).set('cancelled', newTxStatus === TransactionStatus.CANCELLED)),
|
||||
)
|
||||
|
||||
batch(() => {
|
||||
dispatch(addOrUpdateCancellationTransactions({ safeAddress, transactions: Map({ [`${tx.nonce}`]: tx }) }))
|
||||
dispatch(addOrUpdateTransactions({ safeAddress, transactions: txsToUpdate }))
|
||||
})
|
||||
} else {
|
||||
dispatch(addOrUpdateTransactions({ safeAddress, transactions: List([tx]) }))
|
||||
}
|
||||
}
|
||||
|
||||
interface CreateTransactionArgs {
|
||||
navigateToTransactionsTab?: boolean
|
||||
notifiedTransaction: string
|
||||
|
@ -228,15 +172,7 @@ const createTransaction = (
|
|||
|
||||
await Promise.all([
|
||||
saveTxToHistory({ ...txArgs, txHash, origin }),
|
||||
storeTx(
|
||||
mockedTx.updateIn(
|
||||
['ownersWithPendingActions', mockedTx.isCancellationTx ? 'reject' : 'confirm'],
|
||||
(previous) => previous.push(from),
|
||||
),
|
||||
safeAddress,
|
||||
dispatch,
|
||||
state,
|
||||
),
|
||||
storeSignedTx({ transaction: mockedTx, from, isExecution, safeAddress, dispatch, state }),
|
||||
])
|
||||
dispatch(fetchTransactions(safeAddress))
|
||||
} catch (e) {
|
||||
|
@ -263,29 +199,8 @@ const createTransaction = (
|
|||
),
|
||||
)
|
||||
|
||||
const toStoreTx = isExecution
|
||||
? mockedTx.withMutations((record) => {
|
||||
record
|
||||
.set('executionTxHash', receipt.transactionHash)
|
||||
.set('executor', from)
|
||||
.set('isExecuted', true)
|
||||
.set('isSuccessful', receipt.status)
|
||||
.set('status', receipt.status ? TransactionStatus.SUCCESS : TransactionStatus.FAILED)
|
||||
})
|
||||
: mockedTx.set('status', TransactionStatus.AWAITING_CONFIRMATIONS)
|
||||
await storeExecutedTx({ transaction: mockedTx, from, safeAddress, isExecution, receipt, dispatch, state })
|
||||
|
||||
await storeTx(
|
||||
toStoreTx.withMutations((record) => {
|
||||
record
|
||||
.set('confirmations', List([makeConfirmation({ owner: from })]))
|
||||
.updateIn(['ownersWithPendingActions', toStoreTx.isCancellationTx ? 'reject' : 'confirm'], (previous) =>
|
||||
previous.pop(from),
|
||||
)
|
||||
}),
|
||||
safeAddress,
|
||||
dispatch,
|
||||
state,
|
||||
)
|
||||
dispatch(fetchTransactions(safeAddress))
|
||||
|
||||
return receipt.transactionHash
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { AnyAction } from 'redux'
|
||||
import { ThunkAction } from 'redux-thunk'
|
||||
import semverSatisfies from 'semver/functions/satisfies'
|
||||
|
||||
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
||||
|
@ -6,27 +8,41 @@ import { generateSignaturesFromTxConfirmations } from 'src/logic/safe/safeTxSign
|
|||
import { getApprovalTransaction, getExecutionTransaction, saveTxToHistory } from 'src/logic/safe/transactions'
|
||||
import { SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES, tryOffchainSigning } from 'src/logic/safe/transactions/offchainSigner'
|
||||
import { getCurrentSafeVersion } from 'src/logic/safe/utils/safeVersion'
|
||||
import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
|
||||
import { providerSelector } from 'src/logic/wallets/store/selectors'
|
||||
import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar'
|
||||
import closeSnackbarAction from 'src/logic/notifications/store/actions/closeSnackbar'
|
||||
import fetchSafe from 'src/logic/safe/store/actions/fetchSafe'
|
||||
import fetchTransactions from 'src/logic/safe/store/actions/transactions/fetchTransactions'
|
||||
import {
|
||||
isCancelTransaction,
|
||||
mockTransaction,
|
||||
TxToMock,
|
||||
} from 'src/logic/safe/store/actions/transactions/utils/transactionHelpers'
|
||||
import { mockTransaction, TxToMock } from 'src/logic/safe/store/actions/transactions/utils/transactionHelpers'
|
||||
import { getLastTx, getNewTxNonce, shouldExecuteTransaction } from 'src/logic/safe/store/actions/utils'
|
||||
|
||||
import { AppReduxState } from 'src/store'
|
||||
import { getErrorMessage } from 'src/test/utils/ethereumErrors'
|
||||
import { storeTx } from './createTransaction'
|
||||
import { TransactionStatus } from 'src/logic/safe/store/models/types/transaction'
|
||||
import { makeConfirmation } from 'src/logic/safe/store/models/confirmation'
|
||||
import { storeExecutedTx, storeSignedTx, storeTx } from 'src/logic/safe/store/actions/transactions/pendingTransactions'
|
||||
import { Transaction } from 'src/logic/safe/store/models/types/transaction'
|
||||
|
||||
const processTransaction = ({ approveAndExecute, notifiedTransaction, safeAddress, tx, userAddress }) => async (
|
||||
dispatch,
|
||||
getState,
|
||||
) => {
|
||||
import { Dispatch, DispatchReturn } from './types'
|
||||
|
||||
interface ProcessTransactionArgs {
|
||||
approveAndExecute: boolean
|
||||
notifiedTransaction: string
|
||||
safeAddress: string
|
||||
tx: Transaction
|
||||
userAddress: string
|
||||
}
|
||||
|
||||
type ProcessTransactionAction = ThunkAction<Promise<void | string>, AppReduxState, DispatchReturn, AnyAction>
|
||||
|
||||
const processTransaction = ({
|
||||
approveAndExecute,
|
||||
notifiedTransaction,
|
||||
safeAddress,
|
||||
tx,
|
||||
userAddress,
|
||||
}: ProcessTransactionArgs): ProcessTransactionAction => async (
|
||||
dispatch: Dispatch,
|
||||
getState: () => AppReduxState,
|
||||
): Promise<DispatchReturn> => {
|
||||
const state = getState()
|
||||
|
||||
const { account: from, hardwareWallet, smartContractWallet } = providerSelector(state)
|
||||
|
@ -57,7 +73,7 @@ const processTransaction = ({ approveAndExecute, notifiedTransaction, safeAddres
|
|||
safeInstance,
|
||||
to: tx.recipient,
|
||||
valueInWei: tx.value,
|
||||
data: tx.data,
|
||||
data: tx.data ?? EMPTY_DATA,
|
||||
operation: tx.operation,
|
||||
nonce: tx.nonce,
|
||||
safeTxGas: tx.safeTxGas,
|
||||
|
@ -121,30 +137,18 @@ const processTransaction = ({ approveAndExecute, notifiedTransaction, safeAddres
|
|||
try {
|
||||
await Promise.all([
|
||||
saveTxToHistory({ ...txArgs, txHash }),
|
||||
storeTx(
|
||||
mockedTx.withMutations((record) => {
|
||||
record
|
||||
.updateIn(
|
||||
['ownersWithPendingActions', mockedTx.isCancellationTx ? 'reject' : 'confirm'],
|
||||
(previous) => previous.push(from),
|
||||
)
|
||||
.set('status', TransactionStatus.PENDING)
|
||||
}),
|
||||
safeAddress,
|
||||
dispatch,
|
||||
state,
|
||||
),
|
||||
storeSignedTx({ transaction: mockedTx, from, isExecution, safeAddress, dispatch, state }),
|
||||
])
|
||||
dispatch(fetchTransactions(safeAddress))
|
||||
} catch (e) {
|
||||
dispatch(closeSnackbarAction(pendingExecutionKey))
|
||||
await storeTx(tx, safeAddress, dispatch, state)
|
||||
await storeTx({ transaction: tx, safeAddress, dispatch, state })
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
.on('error', (error) => {
|
||||
dispatch(closeSnackbarAction(pendingExecutionKey))
|
||||
storeTx(tx, safeAddress, dispatch, state)
|
||||
storeTx({ transaction: tx, safeAddress, dispatch, state })
|
||||
console.error('Processing transaction error: ', error)
|
||||
})
|
||||
.then(async (receipt) => {
|
||||
|
@ -160,43 +164,7 @@ const processTransaction = ({ approveAndExecute, notifiedTransaction, safeAddres
|
|||
),
|
||||
)
|
||||
|
||||
const toStoreTx = isExecution
|
||||
? mockedTx.withMutations((record) => {
|
||||
record
|
||||
.set('executionTxHash', receipt.transactionHash)
|
||||
.set('blockNumber', receipt.blockNumber)
|
||||
.set('executionDate', record.submissionDate)
|
||||
.set('executor', from)
|
||||
.set('isExecuted', true)
|
||||
.set('isSuccessful', receipt.status)
|
||||
.set(
|
||||
'status',
|
||||
receipt.status
|
||||
? isCancelTransaction(record, safeAddress)
|
||||
? TransactionStatus.CANCELLED
|
||||
: TransactionStatus.SUCCESS
|
||||
: TransactionStatus.FAILED,
|
||||
)
|
||||
.updateIn(['ownersWithPendingActions', 'reject'], (prev) => prev.clear())
|
||||
.updateIn(['ownersWithPendingActions', 'confirm'], (prev) => prev.clear())
|
||||
})
|
||||
: mockedTx.withMutations((record) => {
|
||||
record
|
||||
.updateIn(['ownersWithPendingActions', toStoreTx.isCancellationTx ? 'reject' : 'confirm'], (previous) =>
|
||||
previous.pop(),
|
||||
)
|
||||
.set('status', TransactionStatus.AWAITING_CONFIRMATIONS)
|
||||
})
|
||||
|
||||
await storeTx(
|
||||
toStoreTx.update('confirmations', (confirmations) => {
|
||||
const index = confirmations.findIndex(({ owner }) => owner === from)
|
||||
return index === -1 ? confirmations.push(makeConfirmation({ owner: from })) : confirmations
|
||||
}),
|
||||
safeAddress,
|
||||
dispatch,
|
||||
state,
|
||||
)
|
||||
await storeExecutedTx({ transaction: mockedTx, from, safeAddress, isExecution, receipt, dispatch, state })
|
||||
|
||||
dispatch(fetchTransactions(safeAddress))
|
||||
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
import { List, Map } from 'immutable'
|
||||
import { batch } from 'react-redux'
|
||||
import { TransactionReceipt } from 'web3-core'
|
||||
|
||||
import { addOrUpdateCancellationTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateCancellationTransactions'
|
||||
import { addOrUpdateTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateTransactions'
|
||||
import { removeCancellationTransaction } from 'src/logic/safe/store/actions/transactions/removeCancellationTransaction'
|
||||
import { removeTransaction } from 'src/logic/safe/store/actions/transactions/removeTransaction'
|
||||
import { Dispatch } from 'src/logic/safe/store/actions/types.d'
|
||||
import { makeConfirmation } from 'src/logic/safe/store/models/confirmation'
|
||||
import { Transaction, TransactionStatus } from 'src/logic/safe/store/models/types/transaction'
|
||||
import { safeTransactionsSelector } from 'src/logic/safe/store/selectors'
|
||||
import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
import { web3ReadOnly } from 'src/logic/wallets/getWeb3'
|
||||
import { AppReduxState } from 'src/store'
|
||||
|
||||
type SetPendingTransactionParams = {
|
||||
transaction: Transaction
|
||||
from: string
|
||||
}
|
||||
|
||||
const setTxStatusAsPending = ({ transaction, from }: SetPendingTransactionParams): Transaction =>
|
||||
transaction.withMutations((transaction) => {
|
||||
transaction
|
||||
// setting user as the one who has triggered the tx
|
||||
// this allows to display the owner's "pending" status
|
||||
.updateIn(['ownersWithPendingActions', transaction.isCancellationTx ? 'reject' : 'confirm'], (previous) =>
|
||||
previous.push(from),
|
||||
)
|
||||
// global transaction status
|
||||
.set('status', TransactionStatus.PENDING)
|
||||
})
|
||||
|
||||
type SetOptimisticTransactionParams = {
|
||||
transaction: Transaction
|
||||
from: string
|
||||
isExecution: boolean
|
||||
receipt: TransactionReceipt
|
||||
}
|
||||
|
||||
const updateTxBasedOnReceipt = ({
|
||||
transaction,
|
||||
from,
|
||||
isExecution,
|
||||
receipt,
|
||||
}: SetOptimisticTransactionParams): Transaction => {
|
||||
const txToStore = isExecution
|
||||
? transaction.withMutations((tx) => {
|
||||
tx.set('executionTxHash', receipt.transactionHash)
|
||||
.set('blockNumber', receipt.blockNumber)
|
||||
.set('executionDate', tx.submissionDate)
|
||||
.set('fee', web3ReadOnly.utils.toWei(`${receipt.gasUsed}`, 'gwei'))
|
||||
.set('executor', from)
|
||||
.set('isExecuted', true)
|
||||
.set('isSuccessful', receipt.status)
|
||||
.set('status', receipt.status ? TransactionStatus.SUCCESS : TransactionStatus.FAILED)
|
||||
})
|
||||
: transaction.set('status', TransactionStatus.AWAITING_CONFIRMATIONS)
|
||||
|
||||
return txToStore.withMutations((tx) => {
|
||||
const senderHasAlreadyConfirmed = tx.confirmations.findIndex(({ owner }) => sameAddress(owner, from)) !== -1
|
||||
|
||||
if (!senderHasAlreadyConfirmed) {
|
||||
// updates confirmations status
|
||||
tx.update('confirmations', (confirmations) => confirmations.push(makeConfirmation({ owner: from })))
|
||||
}
|
||||
|
||||
tx.updateIn(['ownersWithPendingActions', 'reject'], (prev) => prev.clear()).updateIn(
|
||||
['ownersWithPendingActions', 'confirm'],
|
||||
(prev) => prev.clear(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
type StoreTxParams = {
|
||||
transaction: Transaction
|
||||
safeAddress: string
|
||||
dispatch: Dispatch
|
||||
state: AppReduxState
|
||||
}
|
||||
|
||||
export const storeTx = async ({ transaction, safeAddress, dispatch, state }: StoreTxParams): Promise<void> => {
|
||||
if (transaction.isCancellationTx) {
|
||||
// `transaction` is the Cancellation tx
|
||||
// So we need to decide the `status` for the main transaction this `transaction` is cancelling
|
||||
let status: TransactionStatus = TransactionStatus.AWAITING_YOUR_CONFIRMATION
|
||||
// `cancelled`, will become true if its corresponding Cancellation tx was successfully executed
|
||||
let cancelled = false
|
||||
|
||||
switch (transaction.status) {
|
||||
case TransactionStatus.SUCCESS:
|
||||
status = TransactionStatus.CANCELLED
|
||||
cancelled = true
|
||||
break
|
||||
case TransactionStatus.PENDING:
|
||||
status = TransactionStatus.PENDING
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
const safeTransactions = safeTransactionsSelector(state)
|
||||
|
||||
const transactions = safeTransactions.withMutations((txs) => {
|
||||
const txIndex = txs.findIndex(({ nonce }) => Number(nonce) === Number(transaction.nonce))
|
||||
txs.update(txIndex, (tx) => tx.set('status', status).set('cancelled', cancelled))
|
||||
})
|
||||
|
||||
batch(() => {
|
||||
dispatch(
|
||||
addOrUpdateCancellationTransactions({
|
||||
safeAddress,
|
||||
transactions: Map({ [`${transaction.nonce}`]: transaction }),
|
||||
}),
|
||||
)
|
||||
dispatch(addOrUpdateTransactions({ safeAddress, transactions }))
|
||||
})
|
||||
} else {
|
||||
dispatch(addOrUpdateTransactions({ safeAddress, transactions: List([transaction]) }))
|
||||
}
|
||||
}
|
||||
|
||||
type StoreSignedTxParams = StoreTxParams & {
|
||||
from: string
|
||||
isExecution: boolean
|
||||
}
|
||||
|
||||
export const storeSignedTx = ({ transaction, from, isExecution, ...rest }: StoreSignedTxParams): Promise<void> =>
|
||||
storeTx({
|
||||
transaction: isExecution ? setTxStatusAsPending({ transaction, from }) : transaction,
|
||||
...rest,
|
||||
})
|
||||
|
||||
type StoreExecParams = StoreTxParams & {
|
||||
from: string
|
||||
isExecution: boolean
|
||||
safeAddress: string
|
||||
receipt: TransactionReceipt
|
||||
}
|
||||
|
||||
export const storeExecutedTx = ({ safeAddress, dispatch, state, ...rest }: StoreExecParams): Promise<void> =>
|
||||
storeTx({
|
||||
transaction: updateTxBasedOnReceipt({ ...rest }),
|
||||
safeAddress,
|
||||
dispatch,
|
||||
state,
|
||||
})
|
||||
|
||||
export const removeTxFromStore = (
|
||||
transaction: Transaction,
|
||||
safeAddress: string,
|
||||
dispatch: Dispatch,
|
||||
state: AppReduxState,
|
||||
): void => {
|
||||
if (transaction.isCancellationTx) {
|
||||
const safeTransactions = safeTransactionsSelector(state)
|
||||
const transactions = safeTransactions.withMutations((txs) => {
|
||||
const txIndex = txs.findIndex(({ nonce }) => Number(nonce) === Number(transaction.nonce))
|
||||
txs[txIndex].set('status', TransactionStatus.AWAITING_YOUR_CONFIRMATION)
|
||||
})
|
||||
|
||||
batch(() => {
|
||||
dispatch(addOrUpdateTransactions({ safeAddress, transactions }))
|
||||
dispatch(removeCancellationTransaction({ safeAddress, transaction }))
|
||||
})
|
||||
} else {
|
||||
dispatch(removeTransaction({ safeAddress, transaction }))
|
||||
}
|
||||
}
|
|
@ -21,11 +21,12 @@ import {
|
|||
TxArgs,
|
||||
RefundParams,
|
||||
} from 'src/logic/safe/store/models/types/transaction'
|
||||
import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from 'src/logic/safe/store/reducer/cancellationTransactions'
|
||||
import { SAFE_REDUCER_ID } from 'src/logic/safe/store/reducer/safe'
|
||||
import { TRANSACTIONS_REDUCER_ID } from 'src/logic/safe/store/reducer/transactions'
|
||||
import { AppReduxState, store } from 'src/store'
|
||||
import { safeSelector, safeTransactionsSelector } from 'src/logic/safe/store/selectors'
|
||||
import {
|
||||
safeSelector,
|
||||
safeTransactionsSelector,
|
||||
safeCancellationTransactionsSelector,
|
||||
} from 'src/logic/safe/store/selectors'
|
||||
import { addOrUpdateTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateTransactions'
|
||||
import {
|
||||
BatchProcessTxsProps,
|
||||
|
@ -323,9 +324,13 @@ export type TxToMock = TxArgs & {
|
|||
|
||||
export const mockTransaction = (tx: TxToMock, safeAddress: string, state: AppReduxState): Promise<Transaction> => {
|
||||
const knownTokens: Map<string, Token> = state[TOKEN_REDUCER_ID]
|
||||
const safe: SafeRecord = state[SAFE_REDUCER_ID].getIn(['safes', safeAddress])
|
||||
const cancellationTxs = state[CANCELLATION_TRANSACTIONS_REDUCER_ID].get(safeAddress) || Map()
|
||||
const outgoingTxs = state[TRANSACTIONS_REDUCER_ID].get(safeAddress) || List()
|
||||
const safe = safeSelector(state)
|
||||
const cancellationTxs = safeCancellationTransactionsSelector(state)
|
||||
const outgoingTxs = safeTransactionsSelector(state)
|
||||
|
||||
if (!safe) {
|
||||
throw new Error('Failed to recover Safe from the store')
|
||||
}
|
||||
|
||||
return buildTx({
|
||||
cancellationTxs,
|
||||
|
|
|
@ -59,7 +59,7 @@ export type TransactionProps = {
|
|||
isCollectibleTransfer: boolean
|
||||
isExecuted: boolean
|
||||
isPending?: boolean
|
||||
isSuccessful: boolean
|
||||
isSuccessful?: boolean
|
||||
isTokenTransfer: boolean
|
||||
masterCopy: string
|
||||
modifySettingsTx: boolean
|
||||
|
|
|
@ -4,7 +4,6 @@ import { BigNumber } from 'bignumber.js'
|
|||
import { getWeb3, web3ReadOnly } from 'src/logic/wallets/getWeb3'
|
||||
import { getGasPrice, getGasPriceOracle } from 'src/config'
|
||||
|
||||
// const MAINNET_NETWORK = 1
|
||||
export const EMPTY_DATA = '0x'
|
||||
|
||||
export const checkReceiptStatus = async (hash) => {
|
||||
|
@ -28,16 +27,6 @@ export const checkReceiptStatus = async (hash) => {
|
|||
}
|
||||
|
||||
export const calculateGasPrice = async (): Promise<string> => {
|
||||
/*
|
||||
const web3 = getWeb3()
|
||||
const { network } = web3.version
|
||||
const isMainnet = MAINNET_NETWORK === network
|
||||
|
||||
const url = isMainnet
|
||||
? 'https://safe-relay.staging.gnosisdev.com/api/v1/gas-station/'
|
||||
: 'https://safe-relay.dev.gnosisdev.com/'
|
||||
*/
|
||||
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return '20000000000'
|
||||
}
|
||||
|
@ -61,7 +50,7 @@ export const calculateGasPrice = async (): Promise<string> => {
|
|||
}
|
||||
}
|
||||
|
||||
export const calculateGasOf = async (data, from, to) => {
|
||||
export const calculateGasOf = async (data: string, from: string, to: string): Promise<number> => {
|
||||
const web3 = getWeb3()
|
||||
try {
|
||||
const gas = await web3.eth.estimateGas({ data, from, to })
|
||||
|
|
|
@ -24,6 +24,7 @@ export const WALLET_PROVIDER = {
|
|||
AUTHEREUM: 'AUTHEREUM',
|
||||
LEDGER: 'LEDGER',
|
||||
TREZOR: 'TREZOR',
|
||||
LATTICE: 'LATTICE',
|
||||
}
|
||||
|
||||
// With some wallets from web3connect you have to use their provider instance only for signing
|
||||
|
|
|
@ -40,6 +40,12 @@ const wallets: Wallet[] = [
|
|||
},
|
||||
{ walletName: WALLETS.TRUST, preferred: true, desktop: false },
|
||||
{ walletName: WALLETS.DAPPER, desktop: false },
|
||||
{
|
||||
walletName: WALLETS.LATTICE,
|
||||
rpcUrl,
|
||||
appName: 'Gnosis Safe',
|
||||
desktop: false,
|
||||
},
|
||||
{
|
||||
walletName: WALLETS.FORTMATIC,
|
||||
apiKey: FORTMATIC_KEY,
|
||||
|
|
|
@ -5,7 +5,6 @@ import React, { useEffect, useState } from 'react'
|
|||
import { useSelector } from 'react-redux'
|
||||
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Field from 'src/components/forms/Field'
|
||||
import TextField from 'src/components/forms/TextField'
|
||||
import { composeValidators, minMaxLength, required } from 'src/components/forms/validator'
|
||||
|
@ -24,6 +23,8 @@ import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
|||
import { FIELD_LOAD_ADDRESS, THRESHOLD } from 'src/routes/load/components/fields'
|
||||
import { getOwnerAddressBy, getOwnerNameBy } from 'src/routes/open/components/fields'
|
||||
import { styles } from './styles'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
const calculateSafeValues = (owners, threshold, values) => {
|
||||
const initialValues = { ...values }
|
||||
|
@ -112,7 +113,7 @@ const OwnerListComponent = (props) => {
|
|||
{address}
|
||||
</Paragraph>
|
||||
<CopyBtn content={address} />
|
||||
<EtherscanBtn value={address} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(address)} />
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -3,7 +3,6 @@ import classNames from 'classnames'
|
|||
import React from 'react'
|
||||
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Col from 'src/components/layout/Col'
|
||||
|
@ -16,6 +15,8 @@ import { FIELD_LOAD_ADDRESS, FIELD_LOAD_NAME, THRESHOLD } from 'src/routes/load/
|
|||
import { getNumOwnersFrom, getOwnerAddressBy, getOwnerNameBy } from 'src/routes/open/components/fields'
|
||||
import { getAccountsFrom } from 'src/routes/open/utils/safeDataExtractor'
|
||||
import { useStyles } from './styles'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
const checkIfUserAddressIsAnOwner = (values: Record<string, string>, userAddress: string): boolean => {
|
||||
let isOwner = false
|
||||
|
@ -76,7 +77,7 @@ const ReviewComponent = ({ userAddress, values }: Props): React.ReactElement =>
|
|||
{shortVersionOf(safeAddress, 4)}
|
||||
</Paragraph>
|
||||
<CopyBtn content={safeAddress} />
|
||||
<EtherscanBtn value={safeAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(safeAddress)} />
|
||||
</Row>
|
||||
</Block>
|
||||
<Block margin="lg">
|
||||
|
@ -121,7 +122,7 @@ const ReviewComponent = ({ userAddress, values }: Props): React.ReactElement =>
|
|||
{address}
|
||||
</Paragraph>
|
||||
<CopyBtn content={address} />
|
||||
<EtherscanBtn value={address} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(address)} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
|
|
@ -2,9 +2,8 @@ import TableContainer from '@material-ui/core/TableContainer'
|
|||
import classNames from 'classnames'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Col from 'src/components/layout/Col'
|
||||
|
@ -18,6 +17,7 @@ import { getAccountsFrom, getNamesFrom } from 'src/routes/open/utils/safeDataExt
|
|||
|
||||
import { FIELD_CONFIRMATIONS, FIELD_NAME, getNumOwnersFrom } from '../fields'
|
||||
import { useStyles } from './styles'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
type ReviewComponentProps = {
|
||||
userAccount: string
|
||||
|
@ -118,7 +118,7 @@ const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => {
|
|||
{addresses[index]}
|
||||
</Paragraph>
|
||||
<CopyBtn content={addresses[index]} />
|
||||
<EtherscanBtn value={addresses[index]} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(addresses[index])} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
|
|
@ -25,7 +25,7 @@ export const staticAppsList: Array<{ url: string; disabled: boolean; networks: n
|
|||
},
|
||||
// Aave
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmY1MUZo44UkT8EokYHs7xDvWEziYSn7n3c4ojVB6qo3SM`,
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmX1NUtvm9WjbvT79sTdeg3sw1NxZAM273y44nBy5d2jZb`,
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET],
|
||||
},
|
||||
|
|
|
@ -161,7 +161,7 @@ const SendModal = ({
|
|||
onClose={onClose}
|
||||
onNext={handleSendCollectible}
|
||||
recipientAddress={recipientAddress}
|
||||
selectedToken={selectedToken as NFTToken}
|
||||
selectedToken={selectedToken as NFTToken | undefined}
|
||||
/>
|
||||
)}
|
||||
{activeScreen === 'reviewCollectible' && (
|
||||
|
|
|
@ -4,10 +4,9 @@ import IconButton from '@material-ui/core/IconButton'
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Close from '@material-ui/icons/Close'
|
||||
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import { fromTokenUnit, toTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
|
@ -29,6 +28,7 @@ import { sm } from 'src/theme/variables'
|
|||
import ArrowDown from '../../assets/arrow-down.svg'
|
||||
|
||||
import { styles } from './style'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
export type CustomTx = {
|
||||
contractAddress?: string
|
||||
|
@ -132,7 +132,7 @@ const ReviewCustomTx = ({ onClose, onPrev, tx }: Props): React.ReactElement => {
|
|||
{tx.contractAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={tx.contractAddress as string} />
|
||||
<EtherscanBtn value={tx.contractAddress as string} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(tx.contractAddress as string)} />
|
||||
</Block>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -8,7 +8,6 @@ import Close from '@material-ui/icons/Close'
|
|||
|
||||
import QRIcon from 'src/assets/icons/qrcode.svg'
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Field from 'src/components/forms/Field'
|
||||
import GnoForm from 'src/components/forms/GnoForm'
|
||||
import TextareaField from 'src/components/forms/TextareaField'
|
||||
|
@ -32,7 +31,8 @@ import { sm } from 'src/theme/variables'
|
|||
import ArrowDown from '../../assets/arrow-down.svg'
|
||||
|
||||
import { styles } from './style'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
export interface CreatedTx {
|
||||
contractAddress: string
|
||||
|
@ -184,7 +184,7 @@ const SendCustomTx: React.FC<Props> = ({ initialValues, onClose, onNext, contrac
|
|||
</Paragraph>
|
||||
</Block>
|
||||
<CopyBtn content={selectedEntry.address} />
|
||||
<EtherscanBtn value={selectedEntry.address} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(selectedEntry.address)} />
|
||||
</Block>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -5,9 +5,8 @@ import { makeStyles } from '@material-ui/core/styles'
|
|||
import Close from '@material-ui/icons/Close'
|
||||
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
|
@ -32,6 +31,7 @@ import { textShortener } from 'src/utils/strings'
|
|||
import ArrowDown from '../assets/arrow-down.svg'
|
||||
|
||||
import { styles } from './style'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
|
@ -153,7 +153,7 @@ const ReviewCollectible = ({ onClose, onPrev, tx }: Props): React.ReactElement =
|
|||
{tx.recipientAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={tx.recipientAddress} />
|
||||
<EtherscanBtn value={tx.recipientAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(tx.recipientAddress)} />
|
||||
</Block>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -5,10 +5,9 @@ import { BigNumber } from 'bignumber.js'
|
|||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { toTokenUnit, fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
|
@ -32,6 +31,7 @@ import { sm } from 'src/theme/variables'
|
|||
import ArrowDown from '../assets/arrow-down.svg'
|
||||
|
||||
import { styles } from './style'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
|
@ -162,7 +162,7 @@ const ReviewTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactElement =>
|
|||
{tx.recipientAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={tx.recipientAddress} />
|
||||
<EtherscanBtn value={tx.recipientAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(tx.recipientAddress)} />
|
||||
</Block>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -13,10 +13,16 @@ import Img from 'src/components/layout/Img'
|
|||
import Paragraph from 'src/components/layout/Paragraph'
|
||||
import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils'
|
||||
import { textShortener } from 'src/utils/strings'
|
||||
import { NFTToken } from 'src/logic/collectibles/sources/collectibles'
|
||||
|
||||
const useSelectedCollectibleStyles = makeStyles(selectedTokenStyles)
|
||||
|
||||
const SelectedCollectible = ({ tokenId, tokens }) => {
|
||||
type SelectedCollectibleProps = {
|
||||
tokenId?: number | string
|
||||
tokens: NFTToken[]
|
||||
}
|
||||
|
||||
const SelectedCollectible = ({ tokenId, tokens }: SelectedCollectibleProps): React.ReactElement => {
|
||||
const classes = useSelectedCollectibleStyles()
|
||||
const token = tokenId && tokens ? tokens.find(({ tokenId: id }) => tokenId === id) : null
|
||||
const shortener = textShortener({ charsStart: 40, charsEnd: 0 })
|
||||
|
@ -31,7 +37,7 @@ const SelectedCollectible = ({ tokenId, tokens }) => {
|
|||
<ListItemText
|
||||
className={classes.tokenData}
|
||||
primary={shortener(token.name)}
|
||||
secondary={`token ID: ${shortener(token.tokenId)}`}
|
||||
secondary={`token ID: ${shortener(token.tokenId.toString())}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
@ -45,7 +51,12 @@ const SelectedCollectible = ({ tokenId, tokens }) => {
|
|||
|
||||
const useCollectibleSelectFieldStyles = makeStyles(selectStyles)
|
||||
|
||||
const CollectibleSelectField = ({ initialValue, tokens }) => {
|
||||
type CollectibleSelectFieldProps = {
|
||||
initialValue?: number | string
|
||||
tokens: NFTToken[]
|
||||
}
|
||||
|
||||
export const CollectibleSelectField = ({ initialValue, tokens }: CollectibleSelectFieldProps): React.ReactElement => {
|
||||
const classes = useCollectibleSelectFieldStyles()
|
||||
|
||||
return (
|
||||
|
@ -69,5 +80,3 @@ const CollectibleSelectField = ({ initialValue, tokens }) => {
|
|||
</Field>
|
||||
)
|
||||
}
|
||||
|
||||
export default CollectibleSelectField
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { sm } from 'src/theme/variables'
|
||||
import { createStyles } from '@material-ui/core'
|
||||
|
||||
export const selectedTokenStyles = () => ({
|
||||
export const selectedTokenStyles = createStyles({
|
||||
container: {
|
||||
minHeight: '55px',
|
||||
padding: 0,
|
||||
|
@ -16,7 +17,7 @@ export const selectedTokenStyles = () => ({
|
|||
},
|
||||
})
|
||||
|
||||
export const selectStyles = () => ({
|
||||
export const selectStyles = createStyles({
|
||||
selectMenu: {
|
||||
paddingRight: 0,
|
||||
},
|
||||
|
|
|
@ -14,10 +14,16 @@ import Paragraph from 'src/components/layout/Paragraph'
|
|||
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
|
||||
import { setImageToPlaceholder } from 'src/routes/safe/components/Balances/utils'
|
||||
import { textShortener } from 'src/utils/strings'
|
||||
import { NFTAssets } from 'src/logic/collectibles/sources/collectibles'
|
||||
|
||||
const useSelectedTokenStyles = makeStyles(selectedTokenStyles)
|
||||
|
||||
const SelectedToken = ({ assetAddress, assets }) => {
|
||||
type SelectedTokenProps = {
|
||||
assetAddress?: string
|
||||
assets: NFTAssets
|
||||
}
|
||||
|
||||
const SelectedToken = ({ assetAddress, assets }: SelectedTokenProps): React.ReactElement => {
|
||||
const classes = useSelectedTokenStyles()
|
||||
const asset = assetAddress ? assets[assetAddress] : null
|
||||
const shortener = textShortener({ charsStart: 40, charsEnd: 0 })
|
||||
|
@ -32,7 +38,7 @@ const SelectedToken = ({ assetAddress, assets }) => {
|
|||
<ListItemText
|
||||
className={classes.tokenData}
|
||||
primary={shortener(asset.name)}
|
||||
secondary={`${formatAmount(asset.numberOfTokens)} ${asset.symbol}`}
|
||||
secondary={`${formatAmount(asset.numberOfTokens.toString())} ${asset.symbol}`}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
@ -46,7 +52,12 @@ const SelectedToken = ({ assetAddress, assets }) => {
|
|||
|
||||
const useTokenSelectFieldStyles = makeStyles(selectStyles)
|
||||
|
||||
const TokenSelectField = ({ assets, initialValue }) => {
|
||||
type TokenSelectFieldProps = {
|
||||
assets: NFTAssets
|
||||
initialValue?: string
|
||||
}
|
||||
|
||||
const TokenSelectField = ({ assets, initialValue }: TokenSelectFieldProps): React.ReactElement => {
|
||||
const classes = useTokenSelectFieldStyles()
|
||||
const assetsAddresses = Object.keys(assets)
|
||||
|
||||
|
@ -70,7 +81,7 @@ const TokenSelectField = ({ assets, initialValue }) => {
|
|||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={asset.name}
|
||||
secondary={`Count: ${formatAmount(asset.numberOfTokens)} ${asset.symbol}`}
|
||||
secondary={`Count: ${formatAmount(asset.numberOfTokens.toString())} ${asset.symbol}`}
|
||||
/>
|
||||
</MenuItem>
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { sm } from 'src/theme/variables'
|
||||
import { createStyles } from '@material-ui/core'
|
||||
|
||||
export const selectedTokenStyles = () => ({
|
||||
export const selectedTokenStyles = createStyles({
|
||||
container: {
|
||||
minHeight: '55px',
|
||||
padding: 0,
|
||||
|
@ -16,7 +17,7 @@ export const selectedTokenStyles = () => ({
|
|||
},
|
||||
})
|
||||
|
||||
export const selectStyles = () => ({
|
||||
export const selectStyles = createStyles({
|
||||
selectMenu: {
|
||||
paddingRight: 0,
|
||||
},
|
||||
|
|
|
@ -5,7 +5,6 @@ import React, { useState } from 'react'
|
|||
import { useSelector } from 'react-redux'
|
||||
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import GnoForm from 'src/components/forms/GnoForm'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
|
@ -21,7 +20,7 @@ import { getNameFromAddressBook } from 'src/logic/addressBook/utils'
|
|||
import { nftTokensSelector, safeActiveSelectorMap } from 'src/logic/collectibles/store/selectors'
|
||||
import SafeInfo from 'src/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||
import AddressBookInput from 'src/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
|
||||
import CollectibleSelectField from 'src/routes/safe/components/Balances/SendModal/screens/SendCollectible/CollectibleSelectField'
|
||||
import { CollectibleSelectField } from 'src/routes/safe/components/Balances/SendModal/screens/SendCollectible/CollectibleSelectField'
|
||||
import TokenSelectField from 'src/routes/safe/components/Balances/SendModal/screens/SendCollectible/TokenSelectField'
|
||||
import { sm } from 'src/theme/variables'
|
||||
|
||||
|
@ -29,6 +28,8 @@ import ArrowDown from '../assets/arrow-down.svg'
|
|||
|
||||
import { styles } from './style'
|
||||
import { NFTToken } from 'src/logic/collectibles/sources/collectibles'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
|
||||
const formMutators = {
|
||||
setMax: (args, state, utils) => {
|
||||
|
@ -49,7 +50,7 @@ type SendCollectibleProps = {
|
|||
onClose: () => void
|
||||
onNext: (txInfo: SendCollectibleTxInfo) => void
|
||||
recipientAddress?: string
|
||||
selectedToken: NFTToken
|
||||
selectedToken?: NFTToken
|
||||
}
|
||||
|
||||
export type SendCollectibleTxInfo = {
|
||||
|
@ -187,7 +188,7 @@ const SendCollectible = ({
|
|||
</Paragraph>
|
||||
</Block>
|
||||
<CopyBtn content={selectedEntry.address} />
|
||||
<EtherscanBtn value={selectedEntry.address} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(selectedEntry.address)} />
|
||||
</Block>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -219,7 +220,7 @@ const SendCollectible = ({
|
|||
</Row>
|
||||
<Row margin="sm">
|
||||
<Col>
|
||||
<TokenSelectField assets={nftAssets} initialValue={(selectedToken as any).assetAddress} />
|
||||
<TokenSelectField assets={nftAssets} initialValue={selectedToken?.assetAddress} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row margin="xs">
|
||||
|
@ -231,7 +232,7 @@ const SendCollectible = ({
|
|||
</Row>
|
||||
<Row margin="md">
|
||||
<Col>
|
||||
<CollectibleSelectField initialValue={(selectedToken as any).tokenId} tokens={selectedNFTTokens} />
|
||||
<CollectibleSelectField initialValue={selectedToken?.tokenId} tokens={selectedNFTTokens} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Block>
|
||||
|
|
|
@ -2,13 +2,12 @@ import IconButton from '@material-ui/core/IconButton'
|
|||
import InputAdornment from '@material-ui/core/InputAdornment'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Close from '@material-ui/icons/Close'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import React, { useState } from 'react'
|
||||
import { OnChange } from 'react-final-form-listeners'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Field from 'src/components/forms/Field'
|
||||
import GnoForm from 'src/components/forms/GnoForm'
|
||||
import TextField from 'src/components/forms/TextField'
|
||||
|
@ -34,6 +33,7 @@ import { sm } from 'src/theme/variables'
|
|||
import ArrowDown from '../assets/arrow-down.svg'
|
||||
|
||||
import { styles } from './style'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
const formMutators = {
|
||||
setMax: (args, state, utils) => {
|
||||
|
@ -189,7 +189,7 @@ const SendFunds = ({
|
|||
</Paragraph>
|
||||
</Block>
|
||||
<CopyBtn content={selectedEntry.address} />
|
||||
<EtherscanBtn value={selectedEntry.address} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(selectedEntry.address)} />
|
||||
</Block>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -5,9 +5,8 @@ import classNames from 'classnames'
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
|
@ -21,6 +20,7 @@ import { estimateTxGasCosts } from 'src/logic/safe/transactions/gas'
|
|||
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
|
||||
|
||||
import { styles } from './style'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
export const ADD_OWNER_SUBMIT_BTN_TEST_ID = 'add-owner-submit-btn'
|
||||
|
||||
|
@ -118,7 +118,7 @@ const ReviewAddOwner = ({ classes, onClickBack, onClose, onSubmit, values }) =>
|
|||
{owner.address}
|
||||
</Paragraph>
|
||||
<CopyBtn content={owner.address} />
|
||||
<EtherscanBtn value={owner.address} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(owner.address)} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
@ -146,7 +146,7 @@ const ReviewAddOwner = ({ classes, onClickBack, onClose, onSubmit, values }) =>
|
|||
{values.ownerAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={values.ownerAddress} />
|
||||
<EtherscanBtn value={values.ownerAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(values.ownerAddress)} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
|
|
@ -5,7 +5,6 @@ import React from 'react'
|
|||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Field from 'src/components/forms/Field'
|
||||
import GnoForm from 'src/components/forms/GnoForm'
|
||||
import TextField from 'src/components/forms/TextField'
|
||||
|
@ -26,6 +25,8 @@ import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selector
|
|||
import { sm } from 'src/theme/variables'
|
||||
|
||||
import { styles } from './style'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
export const RENAME_OWNER_INPUT_TEST_ID = 'rename-owner-input'
|
||||
export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn'
|
||||
|
@ -93,7 +94,7 @@ const EditOwnerComponent = ({ isOpen, onClose, ownerAddress, selectedOwnerName }
|
|||
{ownerAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={safeAddress} />
|
||||
<EtherscanBtn value={safeAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(safeAddress)} />
|
||||
</Block>
|
||||
</Row>
|
||||
</Block>
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import EtherScanLink from 'src/components/EtherscanLink'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Paragraph from 'src/components/layout/Paragraph'
|
||||
import { getValidAddressBookName } from 'src/logic/addressBook/utils'
|
||||
import { useWindowDimensions } from 'src/logic/hooks/useWindowDimensions'
|
||||
import { EtherscanLink } from 'src/components/EtherscanLink'
|
||||
|
||||
type OwnerAddressTableCellProps = {
|
||||
address: string
|
||||
|
@ -36,7 +36,7 @@ const OwnerAddressTableCell = (props: OwnerAddressTableCellProps): React.ReactEl
|
|||
{showLinks ? (
|
||||
<div style={{ marginLeft: 10, flexShrink: 1, minWidth: 0 }}>
|
||||
{userName && getValidAddressBookName(userName)}
|
||||
<EtherScanLink knownAddress={knownAddress} value={address} cut={cut} />
|
||||
<EtherscanLink knownAddress={knownAddress} value={address} cut={cut} />
|
||||
</div>
|
||||
) : (
|
||||
<Paragraph style={{ marginLeft: 10 }}>{address}</Paragraph>
|
||||
|
|
|
@ -5,7 +5,6 @@ import classNames from 'classnames/bind'
|
|||
import React from 'react'
|
||||
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
|
@ -15,6 +14,8 @@ import Paragraph from 'src/components/layout/Paragraph'
|
|||
import Row from 'src/components/layout/Row'
|
||||
|
||||
import { styles } from './style'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
|
||||
export const REMOVE_OWNER_MODAL_NEXT_BTN_TEST_ID = 'remove-owner-next-btn'
|
||||
|
||||
|
@ -53,7 +54,7 @@ const CheckOwner = ({ classes, onClose, onSubmit, ownerAddress, ownerName }) =>
|
|||
{ownerAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={ownerAddress} />
|
||||
<EtherscanBtn value={ownerAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(ownerAddress)} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
|
|
@ -5,9 +5,8 @@ import classNames from 'classnames'
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
|
@ -21,6 +20,7 @@ import { estimateTxGasCosts } from 'src/logic/safe/transactions/gas'
|
|||
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
|
||||
|
||||
import { styles } from './style'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
export const REMOVE_OWNER_REVIEW_BTN_TEST_ID = 'remove-owner-review-btn'
|
||||
|
||||
|
@ -119,7 +119,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre
|
|||
{owner.address}
|
||||
</Paragraph>
|
||||
<CopyBtn content={owner.address} />
|
||||
<EtherscanBtn value={owner.address} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(owner.address)} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
@ -148,7 +148,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre
|
|||
{ownerAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={ownerAddress} />
|
||||
<EtherscanBtn value={ownerAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(ownerAddress)} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
|
|
@ -6,7 +6,6 @@ import React from 'react'
|
|||
import { useSelector } from 'react-redux'
|
||||
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import AddressInput from 'src/components/forms/AddressInput'
|
||||
import Field from 'src/components/forms/Field'
|
||||
import GnoForm from 'src/components/forms/GnoForm'
|
||||
|
@ -23,6 +22,8 @@ import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper'
|
|||
import { safeOwnersSelector } from 'src/logic/safe/store/selectors'
|
||||
|
||||
import { styles } from './style'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
export const REPLACE_OWNER_NAME_INPUT_TEST_ID = 'replace-owner-name-input'
|
||||
export const REPLACE_OWNER_ADDRESS_INPUT_TEST_ID = 'replace-owner-address-testid'
|
||||
|
@ -94,7 +95,7 @@ const OwnerForm = ({ classes, onClose, onSubmit, ownerAddress, ownerName }) => {
|
|||
{ownerAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={ownerAddress} />
|
||||
<EtherscanBtn value={ownerAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(ownerAddress)} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
|
|
@ -4,10 +4,12 @@ import Close from '@material-ui/icons/Close'
|
|||
import classNames from 'classnames'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { List } from 'immutable'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import EtherscanBtn from 'src/components/EtherscanBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
|
@ -24,6 +26,8 @@ import {
|
|||
} from 'src/logic/safe/store/selectors'
|
||||
import { estimateTxGasCosts } from 'src/logic/safe/transactions/gas'
|
||||
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
|
||||
import { getOwnersWithNameFromAddressBook } from 'src/logic/addressBook/utils'
|
||||
import { addressBookSelector } from 'src/logic/addressBook/store/selectors'
|
||||
|
||||
import { styles } from './style'
|
||||
|
||||
|
@ -37,6 +41,8 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre
|
|||
const safeName = useSelector(safeNameSelector)
|
||||
const owners = useSelector(safeOwnersSelector)
|
||||
const threshold = useSelector(safeThresholdSelector)
|
||||
const addressBook = useSelector(addressBookSelector)
|
||||
const ownersWithAddressBookName = owners ? getOwnersWithNameFromAddressBook(addressBook, owners) : List([])
|
||||
|
||||
useEffect(() => {
|
||||
let isCurrent = true
|
||||
|
@ -106,7 +112,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre
|
|||
</Paragraph>
|
||||
</Row>
|
||||
<Hairline />
|
||||
{owners?.map(
|
||||
{ownersWithAddressBookName?.map(
|
||||
(owner) =>
|
||||
owner.address !== ownerAddress && (
|
||||
<React.Fragment key={owner.address}>
|
||||
|
@ -124,7 +130,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre
|
|||
{owner.address}
|
||||
</Paragraph>
|
||||
<CopyBtn content={owner.address} />
|
||||
<EtherscanBtn value={owner.address} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(owner.address)} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
@ -153,7 +159,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre
|
|||
{ownerAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={ownerAddress} />
|
||||
<EtherscanBtn value={ownerAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(ownerAddress)} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
@ -178,7 +184,7 @@ const ReviewRemoveOwner = ({ classes, onClickBack, onClose, onSubmit, ownerAddre
|
|||
{values.ownerAddress}
|
||||
</Paragraph>
|
||||
<CopyBtn content={values.ownerAddress} />
|
||||
<EtherscanBtn value={values.ownerAddress} />
|
||||
<ExplorerButton explorerUrl={getExplorerInfo(values.ownerAddress)} />
|
||||
</Block>
|
||||
</Block>
|
||||
</Col>
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import { List } from 'immutable'
|
||||
import { TableColumn } from 'src/components/Table/types.d'
|
||||
import { SafeOwner } from 'src/logic/safe/store/models/safe'
|
||||
|
||||
export const OWNERS_TABLE_NAME_ID = 'name'
|
||||
export const OWNERS_TABLE_ADDRESS_ID = 'address'
|
||||
export const OWNERS_TABLE_ACTIONS_ID = 'actions'
|
||||
|
||||
export const getOwnerData = (owners) => {
|
||||
const rows = owners.map((owner) => ({
|
||||
export const getOwnerData = (owners: List<SafeOwner>): List<{ address: string; name: string }> => {
|
||||
return owners.map((owner) => ({
|
||||
[OWNERS_TABLE_NAME_ID]: owner.name,
|
||||
[OWNERS_TABLE_ADDRESS_ID]: owner.address,
|
||||
}))
|
||||
|
||||
return rows
|
||||
}
|
||||
|
||||
export const generateColumns = (): List<TableColumn> => {
|
||||
|
|
|
@ -84,8 +84,8 @@ const ManageOwners = ({ addressBook, granted, owners }: Props): React.ReactEleme
|
|||
|
||||
const columns = generateColumns()
|
||||
const autoColumns = columns.filter((c) => !c.custom)
|
||||
const ownersAdbk = getOwnersWithNameFromAddressBook(addressBook, owners)
|
||||
const ownerData = getOwnerData(ownersAdbk)
|
||||
const ownersWithAddressBookName = getOwnersWithNameFromAddressBook(addressBook, owners)
|
||||
const ownerData = getOwnerData(ownersWithAddressBookName)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { makeStyles } from '@material-ui/core/styles'
|
|||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
import EtherscanLink from 'src/components/EtherscanLink'
|
||||
import { EtherscanLink } from 'src/components/EtherscanLink'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Bold from 'src/components/layout/Bold'
|
||||
import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import EtherscanLink from 'src/components/EtherscanLink'
|
||||
import { EtherscanLink } from 'src/components/EtherscanLink'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Bold from 'src/components/layout/Bold'
|
||||
import Paragraph from 'src/components/layout/Paragraph'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import EtherscanLink from 'src/components/EtherscanLink'
|
||||
import { EtherscanLink } from 'src/components/EtherscanLink'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Bold from 'src/components/layout/Bold'
|
||||
import { getNameFromAddressBookSelector } from 'src/logic/addressBook/store/selectors'
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import * as React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
|
||||
import AwaitingIcon from './assets/awaiting.svg'
|
||||
import ErrorIcon from './assets/error.svg'
|
||||
import OkIcon from './assets/ok.svg'
|
||||
import { styles } from './style'
|
||||
import { useStyles } from './style'
|
||||
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Img from 'src/components/layout/Img'
|
||||
|
@ -19,7 +18,7 @@ const statusToIcon = {
|
|||
awaiting_confirmations: AwaitingIcon,
|
||||
awaiting_execution: AwaitingIcon,
|
||||
pending: <CircularProgress size={14} />,
|
||||
}
|
||||
} as const
|
||||
|
||||
const statusToLabel = {
|
||||
success: 'Success',
|
||||
|
@ -29,15 +28,16 @@ const statusToLabel = {
|
|||
awaiting_confirmations: 'Awaiting confirmations',
|
||||
awaiting_execution: 'Awaiting execution',
|
||||
pending: 'Pending',
|
||||
}
|
||||
} as const
|
||||
|
||||
const statusIconStyle = {
|
||||
height: '14px',
|
||||
width: '14px',
|
||||
}
|
||||
|
||||
const Status = ({ classes, status }) => {
|
||||
const Icon = statusToIcon[status]
|
||||
const Status = ({ status }: { status: keyof typeof statusToLabel }): ReactElement => {
|
||||
const classes = useStyles()
|
||||
const Icon: typeof statusToIcon[keyof typeof statusToIcon] = statusToIcon[status]
|
||||
|
||||
return (
|
||||
<Block className={`${classes.container} ${classes[status]}`}>
|
||||
|
@ -49,4 +49,4 @@ const Status = ({ classes, status }) => {
|
|||
)
|
||||
}
|
||||
|
||||
export default withStyles(styles as any)(Status)
|
||||
export default Status
|
||||
|
|
|
@ -1,49 +1,52 @@
|
|||
import { createStyles, makeStyles } from '@material-ui/core/styles'
|
||||
import { boldFont, disabled, error, extraSmallFontSize, lg, secondary, sm } from 'src/theme/variables'
|
||||
|
||||
export const styles = () => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
fontSize: extraSmallFontSize,
|
||||
fontWeight: boldFont,
|
||||
padding: sm,
|
||||
alignItems: 'center',
|
||||
boxSizing: 'border-box',
|
||||
height: lg,
|
||||
marginTop: sm,
|
||||
marginBottom: sm,
|
||||
borderRadius: '3px',
|
||||
},
|
||||
success: {
|
||||
backgroundColor: '#A1D2CA',
|
||||
color: secondary,
|
||||
},
|
||||
cancelled: {
|
||||
backgroundColor: 'transparent',
|
||||
color: error,
|
||||
border: `1px solid ${error}`,
|
||||
},
|
||||
failed: {
|
||||
backgroundColor: 'transparent',
|
||||
color: error,
|
||||
border: `1px solid ${error}`,
|
||||
},
|
||||
awaiting_your_confirmation: {
|
||||
backgroundColor: '#d4d5d3',
|
||||
color: disabled,
|
||||
},
|
||||
awaiting_confirmations: {
|
||||
backgroundColor: '#d4d5d3',
|
||||
color: disabled,
|
||||
},
|
||||
awaiting_execution: {
|
||||
backgroundColor: '#d4d5d3',
|
||||
color: disabled,
|
||||
},
|
||||
pending: {
|
||||
backgroundColor: '#fff3e2',
|
||||
color: '#e8673c',
|
||||
},
|
||||
statusText: {
|
||||
padding: '0 7px',
|
||||
},
|
||||
})
|
||||
export const useStyles = makeStyles(
|
||||
createStyles({
|
||||
container: {
|
||||
display: 'flex',
|
||||
fontSize: extraSmallFontSize,
|
||||
fontWeight: boldFont,
|
||||
padding: sm,
|
||||
alignItems: 'center',
|
||||
boxSizing: 'border-box',
|
||||
height: lg,
|
||||
marginTop: sm,
|
||||
marginBottom: sm,
|
||||
borderRadius: '3px',
|
||||
},
|
||||
success: {
|
||||
backgroundColor: '#A1D2CA',
|
||||
color: secondary,
|
||||
},
|
||||
cancelled: {
|
||||
backgroundColor: 'transparent',
|
||||
color: error,
|
||||
border: `1px solid ${error}`,
|
||||
},
|
||||
failed: {
|
||||
backgroundColor: 'transparent',
|
||||
color: error,
|
||||
border: `1px solid ${error}`,
|
||||
},
|
||||
awaiting_your_confirmation: {
|
||||
backgroundColor: '#d4d5d3',
|
||||
color: disabled,
|
||||
},
|
||||
awaiting_confirmations: {
|
||||
backgroundColor: '#d4d5d3',
|
||||
color: disabled,
|
||||
},
|
||||
awaiting_execution: {
|
||||
backgroundColor: '#d4d5d3',
|
||||
color: disabled,
|
||||
},
|
||||
pending: {
|
||||
backgroundColor: '#fff3e2',
|
||||
color: '#e8673c',
|
||||
},
|
||||
statusText: {
|
||||
padding: '0 7px',
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -101,7 +101,7 @@ const Container = (): React.ReactElement => {
|
|||
path={`${matchSafeWithAddress?.path}/address-book`}
|
||||
render={() => wrapInSuspense(<AddressBookTable />, null)}
|
||||
/>
|
||||
<Redirect to={`${matchSafeWithAddress?.path}/balances`} />
|
||||
<Redirect to={`${matchSafeWithAddress?.url}/balances`} />
|
||||
</Switch>
|
||||
{modal.isOpen && <GenericModal {...modal} onClose={closeGenericModal} />}
|
||||
</>
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
export const APP_ENV = process.env.REACT_APP_ENV
|
||||
export const NODE_ENV = process.env.NODE_ENV
|
||||
export const IS_PRODUCTION = process.env.NODE_ENV === 'production'
|
||||
export const NETWORK = process.env.REACT_APP_NETWORK?.toUpperCase() || 'RINKEBY'
|
||||
export const INTERCOM_ID = APP_ENV === 'production' ? process.env.REACT_APP_INTERCOM_ID : 'plssl1fl'
|
||||
export const GOOGLE_ANALYTICS_ID = process.env.REACT_APP_GOOGLE_ANALYTICS || ''
|
||||
export const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN || ''
|
||||
export const PORTIS_ID = process.env.REACT_APP_PORTIS_ID ?? '852b763d-f28b-4463-80cb-846d7ec5806b'
|
||||
export const FORTMATIC_KEY = process.env.REACT_APP_FORTMATIC_KEY ?? 'pk_test_CAD437AA29BE0A40'
|
||||
export const BLOCKNATIVE_KEY = process.env.REACT_APP_BLOCKNATIVE_KEY ?? '7fbb9cee-7e97-4436-8770-8b29a9a8814c'
|
||||
|
|
Loading…
Reference in New Issue