Merge with dev
This commit is contained in:
commit
027b8ccc51
32
package.json
32
package.json
|
@ -149,13 +149,13 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@gnosis.pm/safe-contracts": "1.1.1-dev.2",
|
||||
"@gnosis.pm/safe-react-components": "^0.1.2",
|
||||
"@gnosis.pm/safe-react-components": "^0.1.3",
|
||||
"@gnosis.pm/util-contracts": "2.0.6",
|
||||
"@ledgerhq/hw-transport-node-hid": "5.16.0",
|
||||
"@material-ui/core": "4.10.1",
|
||||
"@material-ui/icons": "4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.39",
|
||||
"@openzeppelin/contracts": "3.0.1",
|
||||
"@openzeppelin/contracts": "3.0.2",
|
||||
"async-sema": "^3.1.0",
|
||||
"axios": "0.19.2",
|
||||
"bignumber.js": "9.0.0",
|
||||
|
@ -183,12 +183,12 @@
|
|||
"open": "^7.0.3",
|
||||
"polished": "3.6.4",
|
||||
"qrcode.react": "1.0.0",
|
||||
"query-string": "6.12.1",
|
||||
"query-string": "6.13.0",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-final-form": "^6.5.0",
|
||||
"react-final-form-listeners": "^1.0.2",
|
||||
"react-ga": "^2.7.0",
|
||||
"react-ga": "3.0.0",
|
||||
"react-hot-loader": "4.12.21",
|
||||
"react-qr-reader": "^2.2.1",
|
||||
"react-redux": "7.2.0",
|
||||
|
@ -206,38 +206,38 @@
|
|||
"web3": "1.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@testing-library/jest-dom": "5.9.0",
|
||||
"@testing-library/react": "10.2.1",
|
||||
"@testing-library/user-event": "11.3.1",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/node": "14.0.10",
|
||||
"@types/node": "14.0.12",
|
||||
"@types/react": "^16.9.32",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@typescript-eslint/eslint-plugin": "3.1.0",
|
||||
"@typescript-eslint/parser": "3.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "3.2.0",
|
||||
"@typescript-eslint/parser": "3.2.0",
|
||||
"autoprefixer": "9.8.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"dotenv": "^8.2.0",
|
||||
"dotenv-expand": "^5.1.0",
|
||||
"electron": "7.1.8",
|
||||
"electron-builder": "22.7.0",
|
||||
"electron-notarize": "^0.2.1",
|
||||
"electron-notarize": "0.3.0",
|
||||
"eslint": "6.8.0",
|
||||
"eslint-config-prettier": "6.11.0",
|
||||
"eslint-plugin-import": "2.20.2",
|
||||
"eslint-plugin-import": "2.21.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-react": "^7.18.3",
|
||||
"eslint-plugin-sort-destructure-keys": "1.3.4",
|
||||
"ethereumjs-abi": "0.6.8",
|
||||
"husky": "^4.2.2",
|
||||
"lint-staged": "10.2.8",
|
||||
"lint-staged": "10.2.9",
|
||||
"node-sass": "^4.14.1",
|
||||
"prettier": "2.0.5",
|
||||
"react-app-rewired": "^2.1.6",
|
||||
"truffle": "5.1.23",
|
||||
"typescript": "~3.7.2",
|
||||
"wait-on": "5.0.0",
|
||||
"truffle": "5.1.29",
|
||||
"typescript": "^3.9.5",
|
||||
"wait-on": "5.0.1",
|
||||
"web3-utils": "^1.2.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,7 @@ import { OnChange } from 'react-final-form-listeners'
|
|||
import TextField from 'src/components/forms/TextField'
|
||||
import { composeValidators, mustBeEthereumAddress, required } from 'src/components/forms/validator'
|
||||
import { getAddressFromENS } from 'src/logic/wallets/getWeb3'
|
||||
|
||||
const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
|
||||
import { isValidEnsName } from 'src/logic/wallets/ethAddresses'
|
||||
|
||||
// an idea for second field was taken from here
|
||||
// https://github.com/final-form/react-final-form-listeners/blob/master/src/OnBlur.js
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
//
|
||||
import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL } from 'src/config/names'
|
||||
import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL, SAFE_APPS_URL } from 'src/config/names'
|
||||
|
||||
const devConfig = {
|
||||
[TX_SERVICE_HOST]: 'https://safe-transaction.staging.gnosisdev.com/api/v1/',
|
||||
[SIGNATURES_VIA_METAMASK]: false,
|
||||
[RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1/',
|
||||
[SAFE_APPS_URL]: 'https://safe-apps.dev.gnosisdev.com/'
|
||||
}
|
||||
|
||||
export default devConfig
|
||||
|
|
|
@ -3,7 +3,8 @@ import { ETHEREUM_NETWORK, getWeb3 } from 'src/logic/wallets/getWeb3'
|
|||
import {
|
||||
RELAY_API_URL,
|
||||
SIGNATURES_VIA_METAMASK,
|
||||
TX_SERVICE_HOST
|
||||
TX_SERVICE_HOST,
|
||||
SAFE_APPS_URL
|
||||
} from 'src/config/names'
|
||||
import devConfig from './development'
|
||||
import testConfig from './testing'
|
||||
|
@ -67,6 +68,12 @@ export const signaturesViaMetamask = () => {
|
|||
return config[SIGNATURES_VIA_METAMASK]
|
||||
}
|
||||
|
||||
export const getGnosisSafeAppsUrl = () => {
|
||||
const config = getConfig()
|
||||
|
||||
return config[SAFE_APPS_URL]
|
||||
}
|
||||
|
||||
export const getGoogleAnalyticsTrackingID = () =>
|
||||
getNetwork() === ETHEREUM_NETWORK.MAINNET
|
||||
? process.env.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//
|
||||
|
||||
export const TX_SERVICE_HOST = 'tsh'
|
||||
export const SIGNATURES_VIA_METAMASK = 'svm'
|
||||
export const RELAY_API_URL = 'rau'
|
||||
export const SAFE_APPS_URL = 'sau'
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
//
|
||||
import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL } from 'src/config/names'
|
||||
import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL, SAFE_APPS_URL } from 'src/config/names'
|
||||
|
||||
const prodConfig = {
|
||||
[TX_SERVICE_HOST]: 'https://safe-transaction.rinkeby.gnosis.io/api/v1/',
|
||||
[SIGNATURES_VIA_METAMASK]: false,
|
||||
[RELAY_API_URL]: 'https://safe-relay.rinkeby.gnosis.io/api/v1/',
|
||||
[SAFE_APPS_URL]: 'https://apps.gnosis-safe.io/'
|
||||
}
|
||||
|
||||
export default prodConfig
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
//
|
||||
import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL } from 'src/config/names'
|
||||
import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL, SAFE_APPS_URL } from 'src/config/names'
|
||||
|
||||
const stagingConfig = {
|
||||
[TX_SERVICE_HOST]: 'https://safe-transaction.staging.gnosisdev.com/api/v1/',
|
||||
[SIGNATURES_VIA_METAMASK]: false,
|
||||
[RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1/',
|
||||
[SAFE_APPS_URL]: 'https://safe-apps.staging.gnosisdev.com'
|
||||
}
|
||||
|
||||
export default stagingConfig
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
//
|
||||
import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL } from 'src/config/names'
|
||||
import { TX_SERVICE_HOST, SIGNATURES_VIA_METAMASK, RELAY_API_URL, SAFE_APPS_URL } from 'src/config/names'
|
||||
|
||||
const testConfig = {
|
||||
[TX_SERVICE_HOST]: 'http://localhost:8000/api/v1/',
|
||||
[SIGNATURES_VIA_METAMASK]: false,
|
||||
[RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1',
|
||||
[SAFE_APPS_URL]: 'http://localhost:3002/'
|
||||
}
|
||||
|
||||
export default testConfig
|
||||
|
|
|
@ -30,12 +30,20 @@ export const getMethodSignatureAndSignatureHash = (
|
|||
return { methodSignature, signatureHash }
|
||||
}
|
||||
|
||||
export const isAllowedMethod = ({ name, type }: AbiItem): boolean => {
|
||||
return type === 'function' && !!name
|
||||
}
|
||||
|
||||
export const getMethodAction = ({ stateMutability }: AbiItem): 'read' | 'write' => {
|
||||
return ['view', 'pure'].includes(stateMutability) ? 'read' : 'write'
|
||||
}
|
||||
|
||||
export const extractUsefulMethods = (abi: AbiItem[]): AbiItemExtended[] => {
|
||||
return abi
|
||||
.filter(({ constant, name, type }) => type === 'function' && !!name && typeof constant === 'boolean')
|
||||
.filter(isAllowedMethod)
|
||||
.map(
|
||||
(method): AbiItemExtended => ({
|
||||
action: method.constant ? 'read' : 'write',
|
||||
action: getMethodAction(method),
|
||||
...getMethodSignatureAndSignatureHash(method),
|
||||
...method,
|
||||
}),
|
||||
|
|
|
@ -57,6 +57,7 @@ type TokenMethods = 'transfer' | 'transferFrom' | 'safeTransferFrom'
|
|||
|
||||
type DecodedValues = Array<{
|
||||
name: string
|
||||
type?: string
|
||||
value: string
|
||||
}>
|
||||
|
||||
|
@ -70,7 +71,12 @@ type TokenDecodedParams = {
|
|||
|
||||
export type DecodedMethods = SafeDecodedParams | TokenDecodedParams | null
|
||||
|
||||
export const decodeParamsFromSafeMethod = (data: string): SafeDecodedParams | null => {
|
||||
export interface DataDecoded {
|
||||
method: SafeMethods | TokenMethods
|
||||
parameters: DecodedValues
|
||||
}
|
||||
|
||||
export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => {
|
||||
const [methodId, params] = [data.slice(0, 10) as keyof typeof METHOD_TO_ID | string, data.slice(10)]
|
||||
|
||||
switch (methodId) {
|
||||
|
@ -78,10 +84,11 @@ export const decodeParamsFromSafeMethod = (data: string): SafeDecodedParams | nu
|
|||
case '0xe318b52b': {
|
||||
const decodedParameters = web3.eth.abi.decodeParameters(['uint', 'address', 'address'], params)
|
||||
return {
|
||||
[METHOD_TO_ID[methodId]]: [
|
||||
method: METHOD_TO_ID[methodId],
|
||||
parameters: [
|
||||
{ name: 'oldOwner', value: decodedParameters[1] },
|
||||
{ name: 'newOwner', value: decodedParameters[2] },
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,10 +96,11 @@ export const decodeParamsFromSafeMethod = (data: string): SafeDecodedParams | nu
|
|||
case '0x0d582f13': {
|
||||
const decodedParameters = web3.eth.abi.decodeParameters(['address', 'uint'], params)
|
||||
return {
|
||||
[METHOD_TO_ID[methodId]]: [
|
||||
method: METHOD_TO_ID[methodId],
|
||||
parameters: [
|
||||
{ name: 'owner', value: decodedParameters[0] },
|
||||
{ name: '_threshold', value: decodedParameters[1] },
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,10 +108,11 @@ export const decodeParamsFromSafeMethod = (data: string): SafeDecodedParams | nu
|
|||
case '0xf8dc5dd9': {
|
||||
const decodedParameters = web3.eth.abi.decodeParameters(['address', 'address', 'uint'], params)
|
||||
return {
|
||||
[METHOD_TO_ID[methodId]]: [
|
||||
method: METHOD_TO_ID[methodId],
|
||||
parameters: [
|
||||
{ name: 'oldOwner', value: decodedParameters[1] },
|
||||
{ name: '_threshold', value: decodedParameters[2] },
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,9 +120,10 @@ export const decodeParamsFromSafeMethod = (data: string): SafeDecodedParams | nu
|
|||
case '0x694e80c3': {
|
||||
const decodedParameters = web3.eth.abi.decodeParameters(['uint'], params)
|
||||
return {
|
||||
[METHOD_TO_ID[methodId]]: [
|
||||
method: METHOD_TO_ID[methodId],
|
||||
parameters: [
|
||||
{ name: '_threshold', value: decodedParameters[0] },
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,7 +136,7 @@ const isSafeMethod = (methodId: string): boolean => {
|
|||
return !!METHOD_TO_ID[methodId]
|
||||
}
|
||||
|
||||
export const decodeMethods = (data: string): DecodedMethods => {
|
||||
export const decodeMethods = (data: string): DataDecoded | null => {
|
||||
const [methodId, params] = [data.slice(0, 10), data.slice(10)]
|
||||
|
||||
if (isSafeMethod(methodId)) {
|
||||
|
@ -138,10 +148,11 @@ export const decodeMethods = (data: string): DecodedMethods => {
|
|||
case '0xa9059cbb': {
|
||||
const decodeParameters = web3.eth.abi.decodeParameters(['address', 'uint'], params)
|
||||
return {
|
||||
transfer: [
|
||||
method: 'transfer',
|
||||
parameters: [
|
||||
{ name: 'to', value: decodeParameters[0] },
|
||||
{ name: 'value', value: decodeParameters[1] },
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,23 +160,25 @@ export const decodeMethods = (data: string): DecodedMethods => {
|
|||
case '0x23b872dd': {
|
||||
const decodeParameters = web3.eth.abi.decodeParameters(['address', 'address', 'uint'], params)
|
||||
return {
|
||||
transferFrom: [
|
||||
method: 'transferFrom',
|
||||
parameters: [
|
||||
{ name: 'from', value: decodeParameters[0] },
|
||||
{ name: 'to', value: decodeParameters[1] },
|
||||
{ name: 'value', value: decodeParameters[2] },
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// 42842e0e - safeTransferFrom(address,address,uint256)
|
||||
case '0x42842e0e':{
|
||||
case '0x42842e0e': {
|
||||
const decodedParameters = web3.eth.abi.decodeParameters(['address', 'address', 'uint'], params)
|
||||
return {
|
||||
safeTransferFrom: [
|
||||
method: 'safeTransferFrom',
|
||||
parameters: [
|
||||
{ name: 'from', value: decodedParameters[0] },
|
||||
{ name: 'to', value: decodedParameters[1] },
|
||||
{ name: 'value', value: decodedParameters[2] },
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,10 @@ import axios from 'axios'
|
|||
import { getExchangeRatesUrl } from 'src/config'
|
||||
import { AVAILABLE_CURRENCIES } from '../store/model/currencyValues'
|
||||
|
||||
type Currency = keyof typeof AVAILABLE_CURRENCIES
|
||||
|
||||
const fetchCurrenciesRates = async (baseCurrency: Currency, targetCurrencyValue: Currency): Promise<number> => {
|
||||
const fetchCurrenciesRates = async (
|
||||
baseCurrency: AVAILABLE_CURRENCIES,
|
||||
targetCurrencyValue: AVAILABLE_CURRENCIES,
|
||||
): Promise<number> => {
|
||||
let rate = 0
|
||||
const url = `${getExchangeRatesUrl()}?base=${baseCurrency}&symbols=${targetCurrencyValue}`
|
||||
|
||||
|
|
|
@ -2,16 +2,16 @@ import fetchCurrenciesRates from 'src/logic/currencyValues/api/fetchCurrenciesRa
|
|||
import { setCurrencyRate } from 'src/logic/currencyValues/store/actions/setCurrencyRate'
|
||||
import { AVAILABLE_CURRENCIES } from 'src/logic/currencyValues/store/model/currencyValues'
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const fetchCurrencyRate = (safeAddress, selectedCurrency) => async (dispatch) => {
|
||||
const fetchCurrencyRate = (safeAddress: string, selectedCurrency: AVAILABLE_CURRENCIES) => async (dispatch) => {
|
||||
if (AVAILABLE_CURRENCIES.USD === selectedCurrency) {
|
||||
return dispatch(setCurrencyRate(safeAddress, 1))
|
||||
}
|
||||
|
||||
const selectedCurrencyRateInBaseCurrency = await fetchCurrenciesRates(
|
||||
AVAILABLE_CURRENCIES.USD as any,
|
||||
selectedCurrency as any,
|
||||
const selectedCurrencyRateInBaseCurrency: number = await fetchCurrenciesRates(
|
||||
AVAILABLE_CURRENCIES.USD,
|
||||
selectedCurrency,
|
||||
)
|
||||
|
||||
dispatch(setCurrencyRate(safeAddress, selectedCurrencyRateInBaseCurrency))
|
||||
}
|
||||
|
||||
|
|
|
@ -4,12 +4,12 @@ import { batch } from 'react-redux'
|
|||
import { setCurrencyBalances } from 'src/logic/currencyValues/store/actions/setCurrencyBalances'
|
||||
import { setCurrencyRate } from 'src/logic/currencyValues/store/actions/setCurrencyRate'
|
||||
import { setSelectedCurrency } from 'src/logic/currencyValues/store/actions/setSelectedCurrency'
|
||||
import { AVAILABLE_CURRENCIES } from 'src/logic/currencyValues/store/model/currencyValues'
|
||||
import { AVAILABLE_CURRENCIES, CurrencyRateValue } from 'src/logic/currencyValues/store/model/currencyValues'
|
||||
import { loadCurrencyValues } from 'src/logic/currencyValues/store/utils/currencyValuesStorage'
|
||||
|
||||
export const fetchCurrencyValues = (safeAddress) => async (dispatch) => {
|
||||
export const fetchCurrencyValues = (safeAddress: string) => async (dispatch) => {
|
||||
try {
|
||||
const storedCurrencies = await loadCurrencyValues()
|
||||
const storedCurrencies: Map<string, CurrencyRateValue> | any = await loadCurrencyValues()
|
||||
const storedCurrency = storedCurrencies[safeAddress]
|
||||
if (!storedCurrency) {
|
||||
return batch(() => {
|
||||
|
@ -23,10 +23,17 @@ export const fetchCurrencyValues = (safeAddress) => async (dispatch) => {
|
|||
const safeAddr = kv[0]
|
||||
const value = kv[1]
|
||||
|
||||
const { currencyRate, selectedCurrency }: any = value
|
||||
let { currencyRate, selectedCurrency }: CurrencyRateValue = value
|
||||
|
||||
// Fallback for users that got an undefined saved on localStorage
|
||||
if (!selectedCurrency || selectedCurrency === AVAILABLE_CURRENCIES.USD) {
|
||||
currencyRate = 1
|
||||
selectedCurrency = AVAILABLE_CURRENCIES.USD
|
||||
}
|
||||
|
||||
batch(() => {
|
||||
dispatch(setSelectedCurrency(safeAddr, selectedCurrency || AVAILABLE_CURRENCIES.USD))
|
||||
dispatch(setCurrencyRate(safeAddr, currencyRate || 1))
|
||||
dispatch(setSelectedCurrency(safeAddr, selectedCurrency))
|
||||
dispatch(setCurrencyRate(safeAddr, currencyRate))
|
||||
})
|
||||
})
|
||||
} catch (err) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { createAction } from 'redux-actions'
|
|||
export const SET_CURRENCY_RATE = 'SET_CURRENCY_RATE'
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export const setCurrencyRate = createAction(SET_CURRENCY_RATE, (safeAddress, currencyRate) => ({
|
||||
export const setCurrencyRate = createAction(SET_CURRENCY_RATE, (safeAddress: string, currencyRate: number) => ({
|
||||
safeAddress,
|
||||
currencyRate,
|
||||
}))
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
import { AVAILABLE_CURRENCIES } from '../model/currencyValues'
|
||||
|
||||
export const SET_CURRENT_CURRENCY = 'SET_CURRENT_CURRENCY'
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
export const setSelectedCurrency = createAction(SET_CURRENT_CURRENCY, (safeAddress, selectedCurrency) => ({
|
||||
safeAddress,
|
||||
selectedCurrency,
|
||||
}))
|
||||
export const setSelectedCurrency = createAction(
|
||||
SET_CURRENT_CURRENCY,
|
||||
(safeAddress: string, selectedCurrency: AVAILABLE_CURRENCIES) => ({
|
||||
safeAddress,
|
||||
selectedCurrency,
|
||||
}),
|
||||
)
|
||||
|
|
|
@ -4,6 +4,8 @@ import { SET_CURRENCY_RATE } from 'src/logic/currencyValues/store/actions/setCur
|
|||
import { SET_CURRENT_CURRENCY } from 'src/logic/currencyValues/store/actions/setSelectedCurrency'
|
||||
import { currencyValuesSelector } from 'src/logic/currencyValues/store/selectors'
|
||||
import { saveCurrencyValues } from 'src/logic/currencyValues/store/utils/currencyValuesStorage'
|
||||
import { AVAILABLE_CURRENCIES, CurrencyRateValue } from '../model/currencyValues'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
const watchedActions = [SET_CURRENT_CURRENCY, SET_CURRENCY_RATE, SET_CURRENCY_BALANCES]
|
||||
|
||||
|
@ -21,14 +23,16 @@ const currencyValuesStorageMiddleware = (store) => (next) => async (action) => {
|
|||
case SET_CURRENCY_RATE:
|
||||
case SET_CURRENCY_BALANCES: {
|
||||
const currencyValues = currencyValuesSelector(state)
|
||||
const currencyValuesWithoutBalances = currencyValues.map((currencyValue) => {
|
||||
const currencyRate = currencyValue.get('currencyRate')
|
||||
const selectedCurrency = currencyValue.get('selectedCurrency')
|
||||
|
||||
const currencyValuesWithoutBalances: Map<string, CurrencyRateValue> = currencyValues.map((currencyValue) => {
|
||||
const currencyRate: number = currencyValue.get('currencyRate')
|
||||
const selectedCurrency: AVAILABLE_CURRENCIES = currencyValue.get('selectedCurrency')
|
||||
return {
|
||||
currencyRate,
|
||||
selectedCurrency,
|
||||
}
|
||||
})
|
||||
|
||||
await saveCurrencyValues(currencyValuesWithoutBalances)
|
||||
break
|
||||
}
|
||||
|
|
|
@ -1,39 +1,52 @@
|
|||
import { Record } from 'immutable'
|
||||
|
||||
export const AVAILABLE_CURRENCIES = {
|
||||
USD: 'USD',
|
||||
EUR: 'EUR',
|
||||
CAD: 'CAD',
|
||||
HKD: 'HKD',
|
||||
ISK: 'ISK',
|
||||
PHP: 'PHP',
|
||||
DKK: 'DKK',
|
||||
HUF: 'HUF',
|
||||
CZK: 'CZK',
|
||||
AUD: 'AUD',
|
||||
RON: 'RON',
|
||||
SEK: 'SEK',
|
||||
IDR: 'IDR',
|
||||
INR: 'INR',
|
||||
BRL: 'BRL',
|
||||
RUB: 'RUB',
|
||||
HRK: 'HRK',
|
||||
JPY: 'JPY',
|
||||
THB: 'THB',
|
||||
CHF: 'CHF',
|
||||
SGD: 'SGD',
|
||||
PLN: 'PLN',
|
||||
BGN: 'BGN',
|
||||
TRY: 'TRY',
|
||||
CNY: 'CNY',
|
||||
NOK: 'NOK',
|
||||
NZD: 'NZD',
|
||||
ZAR: 'ZAR',
|
||||
MXN: 'MXN',
|
||||
ILS: 'ILS',
|
||||
GBP: 'GBP',
|
||||
KRW: 'KRW',
|
||||
MYR: 'MYR',
|
||||
export enum AVAILABLE_CURRENCIES {
|
||||
USD = 'USD',
|
||||
EUR = 'EUR',
|
||||
CAD = 'CAD',
|
||||
HKD = 'HKD',
|
||||
ISK = 'ISK',
|
||||
PHP = 'PHP',
|
||||
DKK = 'DKK',
|
||||
HUF = 'HUF',
|
||||
CZK = 'CZK',
|
||||
AUD = 'AUD',
|
||||
RON = 'RON',
|
||||
SEK = 'SEK',
|
||||
IDR = 'IDR',
|
||||
INR = 'INR',
|
||||
BRL = 'BRL',
|
||||
RUB = 'RUB',
|
||||
HRK = 'HRK',
|
||||
JPY = 'JPY',
|
||||
THB = 'THB',
|
||||
CHF = 'CHF',
|
||||
SGD = 'SGD',
|
||||
PLN = 'PLN',
|
||||
BGN = 'BGN',
|
||||
TRY = 'TRY',
|
||||
CNY = 'CNY',
|
||||
NOK = 'NOK',
|
||||
NZD = 'NZD',
|
||||
ZAR = 'ZAR',
|
||||
MXN = 'MXN',
|
||||
ILS = 'ILS',
|
||||
GBP = 'GBP',
|
||||
KRW = 'KRW',
|
||||
MYR = 'MYR',
|
||||
}
|
||||
|
||||
type BalanceCurrencyRecord = {
|
||||
currencyName?: string
|
||||
tokenAddress?: string
|
||||
balanceInBaseCurrency: string
|
||||
balanceInSelectedCurrency: string
|
||||
}
|
||||
|
||||
export type CurrencyRateValue = {
|
||||
currencyRate?: number
|
||||
selectedCurrency?: AVAILABLE_CURRENCIES
|
||||
currencyBalances?: BalanceCurrencyRecord[]
|
||||
}
|
||||
|
||||
export const makeBalanceCurrency = Record({
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { loadFromStorage, saveToStorage } from 'src/utils/storage'
|
||||
import { CurrencyRateValue } from '../model/currencyValues'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
const CURRENCY_VALUES_STORAGE_KEY = 'CURRENCY_VALUES_STORAGE_KEY'
|
||||
export const saveCurrencyValues = async (currencyValues) => {
|
||||
export const saveCurrencyValues = async (currencyValues: Map<string, CurrencyRateValue>) => {
|
||||
try {
|
||||
await saveToStorage(CURRENCY_VALUES_STORAGE_KEY, currencyValues)
|
||||
} catch (err) {
|
||||
|
@ -9,6 +11,6 @@ export const saveCurrencyValues = async (currencyValues) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const loadCurrencyValues = async () => {
|
||||
export const loadCurrencyValues = async (): Promise<Map<string, CurrencyRateValue> | any> => {
|
||||
return (await loadFromStorage(CURRENCY_VALUES_STORAGE_KEY)) || {}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
||||
|
||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
import { TxArgs } from 'src/routes/safe/store/models/types/transaction'
|
||||
|
||||
export const CALL = 0
|
||||
export const DELEGATE_CALL = 1
|
||||
|
@ -20,7 +21,7 @@ export const getApprovalTransaction = async ({
|
|||
sender,
|
||||
to,
|
||||
valueInWei,
|
||||
}) => {
|
||||
}: TxArgs) => {
|
||||
const txHash = await safeInstance.getTransactionHash(
|
||||
to,
|
||||
valueInWei,
|
||||
|
@ -61,7 +62,7 @@ export const getExecutionTransaction = async ({
|
|||
sigs,
|
||||
to,
|
||||
valueInWei,
|
||||
}) => {
|
||||
}: TxArgs) => {
|
||||
try {
|
||||
const web3 = getWeb3()
|
||||
const contract: any = new web3.eth.Contract(GnosisSafeSol.abi as any, safeInstance.address)
|
||||
|
|
|
@ -43,3 +43,5 @@ export const isUserOwner = (safe, userAccount) => {
|
|||
}
|
||||
|
||||
export const isUserOwnerOnAnySafe = (safes, userAccount) => safes.some((safe) => isUserOwner(safe, userAccount))
|
||||
|
||||
export const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
import { Record } from 'immutable'
|
||||
import { Record, RecordOf } from 'immutable'
|
||||
|
||||
export const makeProvider = Record({
|
||||
export type ProviderProps = {
|
||||
name: string
|
||||
loaded: boolean
|
||||
available: boolean
|
||||
account: string
|
||||
network: number
|
||||
smartContractWallet: boolean
|
||||
hardwareWallet: boolean
|
||||
}
|
||||
|
||||
export const makeProvider = Record<ProviderProps>({
|
||||
name: '',
|
||||
loaded: false,
|
||||
available: false,
|
||||
|
@ -10,4 +20,6 @@ export const makeProvider = Record({
|
|||
hardwareWallet: false,
|
||||
})
|
||||
|
||||
// Useage const someProvider: Provider = makeProvider({ name: 'METAMASK', loaded: false, available: false })
|
||||
// Usage const someProvider: Provider = makeProvider({ name: 'METAMASK', loaded: false, available: false })
|
||||
|
||||
export type ProviderRecord = RecordOf<ProviderProps>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, { SyntheticEvent } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Button from 'src/components/layout/Button'
|
||||
|
@ -36,14 +36,20 @@ export const ContinueFooter = ({
|
|||
onContinue,
|
||||
}: {
|
||||
continueButtonDisabled: boolean
|
||||
onContinue: () => void
|
||||
onContinue: (event: SyntheticEvent) => void
|
||||
}) => (
|
||||
<Button color="primary" disabled={continueButtonDisabled} onClick={onContinue} variant="contained">
|
||||
Continue
|
||||
</Button>
|
||||
)
|
||||
|
||||
export const ErrorFooter = ({ onCancel, onRetry }: { onCancel: () => void; onRetry: () => void }) => (
|
||||
export const ErrorFooter = ({
|
||||
onCancel,
|
||||
onRetry,
|
||||
}: {
|
||||
onCancel: (event: SyntheticEvent) => void
|
||||
onRetry: (event: SyntheticEvent) => void
|
||||
}) => (
|
||||
<>
|
||||
<ButtonWithMargin onClick={onCancel} variant="contained">
|
||||
Cancel
|
||||
|
|
|
@ -102,7 +102,7 @@ const curriedSafeAppValidator = memoize((appList) => async (value: string) => {
|
|||
}
|
||||
})
|
||||
|
||||
const composeValidatorsApps = (...validators: Function[]) => (value, values, meta) => {
|
||||
const composeValidatorsApps = (...validators) => (value, values, meta) => {
|
||||
if (!meta.modified) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Icon, ModalFooterConfirmation, Text, Title } from '@gnosis.pm/safe-react-components'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import React from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
import AddressInfo from 'src/components/AddressInfo'
|
||||
import DividerLine from 'src/components/DividerLine'
|
||||
import Collapse from 'src/components/Collapse'
|
||||
|
@ -14,6 +15,20 @@ import Heading from 'src/components/layout/Heading'
|
|||
import Img from 'src/components/layout/Img'
|
||||
import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers'
|
||||
|
||||
export type SafeAppTx = {
|
||||
to: string
|
||||
value: string | number
|
||||
data: string
|
||||
}
|
||||
|
||||
// TODO: This should be exported by safe-rect-components
|
||||
type GenericModalProps = {
|
||||
title: ReactElement
|
||||
body: ReactElement
|
||||
footer: ReactElement
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const humanReadableBalance = (balance, decimals) => new BigNumber(balance).times(`1e-${decimals}`).toFixed()
|
||||
|
||||
const Wrapper = styled.div`
|
||||
|
@ -40,22 +55,35 @@ const IconText = styled.div`
|
|||
margin-right: 4px;
|
||||
}
|
||||
`
|
||||
const isTxValid = (t) => {
|
||||
const isTxValid = (t): boolean => {
|
||||
try {
|
||||
if (!['string', 'number'].includes(typeof t.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof t.value === 'string') {
|
||||
const web3 = getWeb3()
|
||||
web3.eth.abi.decodeParameter('uint256', t.value)
|
||||
}
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAddressValid = mustBeEthereumAddress(t.to) === undefined
|
||||
return isAddressValid && t.value !== undefined && typeof t.value === 'number' && t.data && typeof t.data === 'string'
|
||||
return isAddressValid && t.data && typeof t.data === 'string'
|
||||
}
|
||||
|
||||
const confirmTransactions = (
|
||||
safeAddress,
|
||||
safeName,
|
||||
ethBalance,
|
||||
nameApp,
|
||||
iconApp,
|
||||
txs,
|
||||
openModal,
|
||||
closeModal,
|
||||
onConfirm,
|
||||
) => {
|
||||
safeAddress: string,
|
||||
safeName: string,
|
||||
ethBalance: string,
|
||||
nameApp: string,
|
||||
iconApp: string,
|
||||
txs: Array<SafeAppTx>,
|
||||
openModal: (modalInfo: GenericModalProps) => void,
|
||||
closeModal: () => void,
|
||||
onConfirm: () => void,
|
||||
): any => {
|
||||
const areTxsMalformed = txs.some((t) => !isTxValid(t))
|
||||
|
||||
const title = <ModalTitle iconUrl={iconApp} title={nameApp} />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import axios from 'axios'
|
||||
|
||||
import appsIconSvg from 'src/routes/safe/components/Transactions/TxsTable/TxType/assets/appsIcon.svg'
|
||||
|
||||
import { getGnosisSafeAppsUrl } from 'src/config/index'
|
||||
import { SafeApp } from './types'
|
||||
|
||||
const removeLastTrailingSlash = (url) => {
|
||||
|
@ -11,13 +11,14 @@ const removeLastTrailingSlash = (url) => {
|
|||
return url
|
||||
}
|
||||
|
||||
const gnosisAppsUrl = removeLastTrailingSlash(process.env.REACT_APP_GNOSIS_APPS_URL)
|
||||
const gnosisAppsUrl = removeLastTrailingSlash(getGnosisSafeAppsUrl())
|
||||
export const staticAppsList: Array<{ url: string; disabled: boolean }> = [
|
||||
{ url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQapdJP6zERqpDKKPECNeMDDgwmGUqbKk1PjHpYj8gfDJ`, disabled: false },
|
||||
{ url: `${gnosisAppsUrl}/compound`, disabled: false },
|
||||
{ url: `${gnosisAppsUrl}/tx-builder`, disabled: false },
|
||||
{ url: `${gnosisAppsUrl}/aave`, disabled: false },
|
||||
{ url: `${gnosisAppsUrl}/pool-together`, disabled: false },
|
||||
{ url: `${gnosisAppsUrl}/open-zeppelin`, disabled: false },
|
||||
{ url: `${gnosisAppsUrl}/request`, disabled: false },
|
||||
{ url: `${gnosisAppsUrl}/synthetix`, disabled: false },
|
||||
]
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import Identicon from 'src/components/Identicon'
|
|||
import { mustBeEthereumAddress, mustBeEthereumContractAddress } from 'src/components/forms/validator'
|
||||
import { getAddressBookListSelector } from 'src/logic/addressBook/store/selectors'
|
||||
import { getAddressFromENS } from 'src/logic/wallets/getWeb3'
|
||||
import { isValidEnsName } from 'src/logic/wallets/ethAddresses'
|
||||
|
||||
const textFieldLabelStyle = makeStyles(() => ({
|
||||
root: {
|
||||
|
@ -38,8 +39,6 @@ const filterAddressBookWithContractAddresses = async (addressBook) => {
|
|||
return addressBook.filter((adbkEntry, index) => abFlags[index])
|
||||
}
|
||||
|
||||
const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
|
||||
|
||||
const AddressBookInput = ({
|
||||
classes,
|
||||
fieldMutator,
|
||||
|
|
|
@ -18,14 +18,15 @@ import Header from './Header'
|
|||
import MethodsDropdown from './MethodsDropdown'
|
||||
import RenderInputParams from './RenderInputParams'
|
||||
import RenderOutputParams from './RenderOutputParams'
|
||||
import { abiExtractor, createTxObject, formMutators, handleSubmitError, isReadMethod } from './utils'
|
||||
import { abiExtractor, createTxObject, formMutators, handleSubmitError, isReadMethod, ensResolver } from './utils'
|
||||
import { TransactionReviewType } from './Review'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
export interface CreatedTx {
|
||||
contractAddress: string
|
||||
data: string
|
||||
selectedMethod: any
|
||||
selectedMethod: TransactionReviewType
|
||||
value: string | number
|
||||
}
|
||||
|
||||
|
@ -73,7 +74,7 @@ const ContractInteraction = ({ contractAddress, initialValues, onClose, onNext }
|
|||
<Header onClose={onClose} subTitle="1 of 2" title="Contract Interaction" />
|
||||
<Hairline />
|
||||
<GnoForm
|
||||
decorators={[abiExtractor]}
|
||||
decorators={[abiExtractor, ensResolver]}
|
||||
formMutators={formMutators}
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
|
|
|
@ -6,8 +6,9 @@ import { mustBeEthereumAddress, mustBeEthereumContractAddress } from 'src/compon
|
|||
import { getNetwork } from 'src/config'
|
||||
import { getConfiguredSource } from 'src/logic/contractInteraction/sources'
|
||||
import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIService'
|
||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
import { getAddressFromENS, getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
import { TransactionReviewType } from '../Review'
|
||||
import { isValidEnsName } from 'src/logic/wallets/ethAddresses'
|
||||
|
||||
export const NO_CONTRACT = 'no contract'
|
||||
|
||||
|
@ -20,7 +21,7 @@ export const abiExtractor = createDecorator({
|
|||
mustBeEthereumAddress(contractAddress) ||
|
||||
(await mustBeEthereumContractAddress(contractAddress))
|
||||
) {
|
||||
return NO_CONTRACT
|
||||
return
|
||||
}
|
||||
const network = getNetwork()
|
||||
const source = getConfiguredSource()
|
||||
|
@ -29,6 +30,26 @@ export const abiExtractor = createDecorator({
|
|||
},
|
||||
})
|
||||
|
||||
export const ensResolver = createDecorator({
|
||||
field: 'contractAddress',
|
||||
updates: {
|
||||
contractAddress: async (contractAddress) => {
|
||||
try {
|
||||
const resolvedAddress = isValidEnsName(contractAddress) && (await getAddressFromENS(contractAddress))
|
||||
|
||||
if (resolvedAddress) {
|
||||
return resolvedAddress
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.message)
|
||||
return contractAddress
|
||||
}
|
||||
|
||||
return contractAddress
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const formMutators = {
|
||||
setMax: (args, state, utils) => {
|
||||
utils.changeValue(state, 'value', () => args[0])
|
||||
|
|
|
@ -34,10 +34,10 @@ const AddressBookTable = React.lazy(() => import('src/routes/safe/components/Add
|
|||
interface Props {
|
||||
sendFunds: Record<string, any>
|
||||
showReceive: boolean
|
||||
onShow: Function
|
||||
onHide: Function
|
||||
showSendFunds: Function
|
||||
hideSendFunds: Function
|
||||
onShow: (value: string) => void
|
||||
onHide: (value: string) => void
|
||||
showSendFunds: (value: string) => void
|
||||
hideSendFunds: () => void
|
||||
match: Record<string, any>
|
||||
location: Record<string, any>
|
||||
history: Record<string, any>
|
||||
|
|
|
@ -51,6 +51,11 @@ export const getTxData = (tx) => {
|
|||
txData.addedOwner = newOwner
|
||||
}
|
||||
}
|
||||
if (tx.multiSendTx) {
|
||||
txData.recipient = tx.recipient
|
||||
txData.data = tx.data
|
||||
txData.customTx = true
|
||||
}
|
||||
} else if (tx.customTx) {
|
||||
txData.recipient = tx.recipient
|
||||
txData.data = tx.data
|
||||
|
|
|
@ -27,13 +27,14 @@ import { removeTransaction } from 'src/routes/safe/store/actions/transactions/re
|
|||
import {
|
||||
generateSafeTxHash,
|
||||
mockTransaction,
|
||||
TxToMock,
|
||||
} from 'src/routes/safe/store/actions/transactions/utils/transactionHelpers'
|
||||
import { getLastTx, getNewTxNonce, shouldExecuteTransaction } from 'src/routes/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/routes/safe/store/selectors'
|
||||
import { TransactionStatus } from 'src/routes/safe/store/models/types/transaction'
|
||||
import { TransactionStatus, TxArgs } from 'src/routes/safe/store/models/types/transaction'
|
||||
|
||||
export const removeTxFromStore = (tx, safeAddress, dispatch, state) => {
|
||||
if (tx.isCancellationTx) {
|
||||
|
@ -120,7 +121,7 @@ const createTransaction = ({
|
|||
|
||||
let txHash
|
||||
let tx
|
||||
const txArgs = {
|
||||
const txArgs: TxArgs = {
|
||||
safeInstance,
|
||||
to,
|
||||
valueInWei,
|
||||
|
@ -129,7 +130,7 @@ const createTransaction = ({
|
|||
nonce,
|
||||
safeTxGas,
|
||||
baseGas: 0,
|
||||
gasPrice: 0,
|
||||
gasPrice: '0',
|
||||
gasToken: ZERO_ADDRESS,
|
||||
refundReceiver: ZERO_ADDRESS,
|
||||
sender: from,
|
||||
|
@ -164,7 +165,7 @@ const createTransaction = ({
|
|||
sendParams.gas = '7000000'
|
||||
}
|
||||
|
||||
const txToMock = {
|
||||
const txToMock: TxToMock = {
|
||||
...txArgs,
|
||||
confirmations: [], // this is used to determine if a tx is pending or not. See `calculateTransactionStatus` helper
|
||||
value: txArgs.valueInWei,
|
||||
|
@ -223,9 +224,9 @@ const createTransaction = ({
|
|||
.set('executor', from)
|
||||
.set('isExecuted', true)
|
||||
.set('isSuccessful', receipt.status)
|
||||
.set('status', receipt.status ? 'success' : 'failed')
|
||||
.set('status', receipt.status ? TransactionStatus.SUCCESS : TransactionStatus.FAILED)
|
||||
})
|
||||
: mockedTx.set('status', 'awaiting_confirmations')
|
||||
: mockedTx.set('status', TransactionStatus.AWAITING_CONFIRMATIONS)
|
||||
|
||||
await storeTx(
|
||||
toStoreTx.withMutations((record) => {
|
||||
|
|
|
@ -13,12 +13,14 @@ import fetchTransactions from 'src/routes/safe/store/actions/transactions/fetchT
|
|||
import {
|
||||
isCancelTransaction,
|
||||
mockTransaction,
|
||||
TxToMock,
|
||||
} from 'src/routes/safe/store/actions/transactions/utils/transactionHelpers'
|
||||
import { getLastTx, getNewTxNonce, shouldExecuteTransaction } from 'src/routes/safe/store/actions/utils'
|
||||
|
||||
import { getErrorMessage } from 'src/test/utils/ethereumErrors'
|
||||
import { makeConfirmation } from '../models/confirmation'
|
||||
import { storeTx } from './createTransaction'
|
||||
import { TransactionStatus } from '../models/types/transaction'
|
||||
|
||||
const processTransaction = ({
|
||||
approveAndExecute,
|
||||
|
@ -101,7 +103,7 @@ const processTransaction = ({
|
|||
sendParams.gas = '7000000'
|
||||
}
|
||||
|
||||
const txToMock = {
|
||||
const txToMock: TxToMock = {
|
||||
...txArgs,
|
||||
confirmations: [], // this is used to determine if a tx is pending or not. See `calculateTransactionStatus` helper
|
||||
value: txArgs.valueInWei,
|
||||
|
@ -165,11 +167,15 @@ const processTransaction = ({
|
|||
.set('isSuccessful', receipt.status)
|
||||
.set(
|
||||
'status',
|
||||
receipt.status ? (isCancelTransaction(record, safeAddress) ? 'cancelled' : 'success') : 'failed',
|
||||
receipt.status
|
||||
? isCancelTransaction(record, safeAddress)
|
||||
? TransactionStatus.CANCELLED
|
||||
: TransactionStatus.SUCCESS
|
||||
: TransactionStatus.FAILED,
|
||||
)
|
||||
.updateIn(['ownersWithPendingActions', 'reject'], (prev) => prev.clear())
|
||||
})
|
||||
: mockedTx.set('status', 'awaiting_confirmations')
|
||||
: mockedTx.set('status', TransactionStatus.AWAITING_CONFIRMATIONS)
|
||||
|
||||
await storeTx(
|
||||
toStoreTx.withMutations((record) => {
|
||||
|
|
|
@ -7,14 +7,18 @@ import { PROVIDER_REDUCER_ID } from 'src/logic/wallets/store/reducer/provider'
|
|||
import { buildTx, isCancelTransaction } from 'src/routes/safe/store/actions/transactions/utils/transactionHelpers'
|
||||
import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe'
|
||||
import { store } from 'src/store'
|
||||
import { DecodedMethods } from 'src/logic/contracts/methodIds'
|
||||
import { DataDecoded } from 'src/logic/contracts/methodIds'
|
||||
import fetchTransactions from 'src/routes/safe/store/actions/transactions/fetchTransactions/fetchTransactions'
|
||||
import { TransactionTypes } from 'src/routes/safe/store/models/types/transaction'
|
||||
import { Transaction, TransactionTypes } from 'src/routes/safe/store/models/types/transaction'
|
||||
import { Token } from 'src/logic/tokens/store/model/token'
|
||||
import { SafeRecord } from 'src/routes/safe/store/models/safe'
|
||||
|
||||
export type ConfirmationServiceModel = {
|
||||
confirmationType: string
|
||||
owner: string
|
||||
submissionDate: Date
|
||||
submissionDate: string
|
||||
signature: string
|
||||
signatureType: string
|
||||
transactionHash: string
|
||||
}
|
||||
|
||||
|
@ -22,25 +26,32 @@ export type TxServiceModel = {
|
|||
baseGas: number
|
||||
blockNumber?: number | null
|
||||
confirmations: ConfirmationServiceModel[]
|
||||
confirmationsRequired: number
|
||||
creationTx?: boolean | null
|
||||
data?: string | null
|
||||
dataDecoded?: DecodedMethods
|
||||
dataDecoded?: DataDecoded
|
||||
ethGasPrice: string
|
||||
executionDate?: string | null
|
||||
executor: string
|
||||
gasPrice: number
|
||||
fee: string
|
||||
gasPrice: string
|
||||
gasToken: string
|
||||
gasUsed: number
|
||||
isExecuted: boolean
|
||||
isSuccessful: boolean
|
||||
modified: string
|
||||
nonce?: number | null
|
||||
operation: number
|
||||
origin?: string | null
|
||||
refundReceiver: string
|
||||
safe: string
|
||||
safeTxGas: number
|
||||
safeTxHash: string
|
||||
signatures: string
|
||||
submissionDate?: string | null
|
||||
to: string
|
||||
transactionHash?: string | null
|
||||
value: number
|
||||
value: string
|
||||
}
|
||||
|
||||
export type SafeTransactionsType = {
|
||||
|
@ -55,8 +66,8 @@ export type OutgoingTxs = {
|
|||
|
||||
export type BatchProcessTxsProps = OutgoingTxs & {
|
||||
currentUser?: string
|
||||
knownTokens: any
|
||||
safe: any
|
||||
knownTokens: Record<string, Token>
|
||||
safe: SafeRecord
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -127,8 +138,8 @@ const batchProcessOutgoingTransactions = async ({
|
|||
outgoingTxs,
|
||||
safe,
|
||||
}: BatchProcessTxsProps): Promise<{
|
||||
cancel: any
|
||||
outgoing: any
|
||||
cancel: Record<string, Transaction>
|
||||
outgoing: Array<Transaction>
|
||||
}> => {
|
||||
// cancellation transactions
|
||||
const cancelTxsValues = Object.values(cancellationTxs)
|
||||
|
|
|
@ -27,18 +27,32 @@ import { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transacti
|
|||
import { store } from 'src/store'
|
||||
import { safeSelector, safeTransactionsSelector } from 'src/routes/safe/store/selectors'
|
||||
import { addOrUpdateTransactions } from 'src/routes/safe/store/actions/transactions/addOrUpdateTransactions'
|
||||
import { TxServiceModel } from 'src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions'
|
||||
import {
|
||||
BatchProcessTxsProps,
|
||||
TxServiceModel,
|
||||
} from 'src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions'
|
||||
import { TypedDataUtils } from 'eth-sig-util'
|
||||
import { Token } from 'src/logic/tokens/store/model/token'
|
||||
import { ProviderRecord } from 'src/logic/wallets/store/model/provider'
|
||||
import { SafeRecord } from 'src/routes/safe/store/models/safe'
|
||||
|
||||
export const isEmptyData = (data?: string | null): boolean => {
|
||||
return !data || data === EMPTY_DATA
|
||||
}
|
||||
|
||||
export const isInnerTransaction = (tx: TxServiceModel, safeAddress: string): boolean => {
|
||||
return sameAddress(tx.to, safeAddress) && Number(tx.value) === 0
|
||||
export const isInnerTransaction = (tx: TxServiceModel | Transaction, safeAddress: string): boolean => {
|
||||
let isSameAddress = false
|
||||
|
||||
if ((tx as TxServiceModel).to !== undefined) {
|
||||
isSameAddress = sameAddress((tx as TxServiceModel).to, safeAddress)
|
||||
} else if ((tx as Transaction).recipient !== undefined) {
|
||||
isSameAddress = sameAddress((tx as Transaction).recipient, safeAddress)
|
||||
}
|
||||
|
||||
return isSameAddress && Number(tx.value) === 0
|
||||
}
|
||||
|
||||
export const isCancelTransaction = (tx: TxServiceModel, safeAddress: string): boolean => {
|
||||
export const isCancelTransaction = (tx: TxServiceModel | Transaction, safeAddress: string): boolean => {
|
||||
return isInnerTransaction(tx, safeAddress) && isEmptyData(tx.data)
|
||||
}
|
||||
|
||||
|
@ -71,7 +85,7 @@ export const isCustomTransaction = async (
|
|||
tx: TxServiceModel,
|
||||
txCode: string,
|
||||
safeAddress: string,
|
||||
knownTokens: any,
|
||||
knownTokens: Record<string, Token>,
|
||||
): Promise<boolean> => {
|
||||
return (
|
||||
isOutgoingTransaction(tx, safeAddress) &&
|
||||
|
@ -82,12 +96,13 @@ export const isCustomTransaction = async (
|
|||
}
|
||||
|
||||
export const getRefundParams = async (
|
||||
tx: any,
|
||||
tx: TxServiceModel,
|
||||
tokenInfo: (string) => Promise<{ decimals: number; symbol: string } | null>,
|
||||
): Promise<any> => {
|
||||
const txGasPrice = Number(tx.gasPrice)
|
||||
let refundParams = null
|
||||
|
||||
if (tx.gasPrice > 0) {
|
||||
if (txGasPrice > 0) {
|
||||
let refundSymbol = 'ETH'
|
||||
let refundDecimals = 18
|
||||
|
||||
|
@ -100,7 +115,9 @@ export const getRefundParams = async (
|
|||
}
|
||||
}
|
||||
|
||||
const feeString = (tx.gasPrice * (tx.baseGas + tx.safeTxGas)).toString().padStart(refundDecimals, '0')
|
||||
const feeString = (txGasPrice * (Number(tx.baseGas) + Number(tx.safeTxGas)))
|
||||
.toString()
|
||||
.padStart(refundDecimals, '0')
|
||||
const whole = feeString.slice(0, feeString.length - refundDecimals) || '0'
|
||||
const fraction = feeString.slice(feeString.length - refundDecimals)
|
||||
|
||||
|
@ -115,18 +132,15 @@ export const getRefundParams = async (
|
|||
|
||||
export const getDecodedParams = (tx: TxServiceModel): DecodedMethods => {
|
||||
if (tx.dataDecoded) {
|
||||
return Object.keys(tx.dataDecoded).reduce((acc, key) => {
|
||||
acc[key] = {
|
||||
...tx.dataDecoded[key].reduce(
|
||||
(acc, param) => ({
|
||||
...acc,
|
||||
[param.name]: param.value,
|
||||
}),
|
||||
{},
|
||||
),
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
return {
|
||||
[tx.dataDecoded.method]: tx.dataDecoded.parameters.reduce(
|
||||
(acc, param) => ({
|
||||
...acc,
|
||||
[param.name]: param.value,
|
||||
}),
|
||||
{},
|
||||
),
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
@ -160,7 +174,7 @@ export const isTransactionCancelled = (
|
|||
|
||||
export const calculateTransactionStatus = (
|
||||
tx: Transaction,
|
||||
{ owners, threshold }: any,
|
||||
{ owners, threshold }: SafeRecord,
|
||||
currentUser?: string | null,
|
||||
): TransactionStatusValues => {
|
||||
let txStatus
|
||||
|
@ -213,6 +227,11 @@ export const calculateTransactionType = (tx: Transaction): TransactionTypeValues
|
|||
return txType
|
||||
}
|
||||
|
||||
export type BuildTx = BatchProcessTxsProps & {
|
||||
tx: TxServiceModel
|
||||
txCode: string | null
|
||||
}
|
||||
|
||||
export const buildTx = async ({
|
||||
cancellationTxs,
|
||||
currentUser,
|
||||
|
@ -221,7 +240,7 @@ export const buildTx = async ({
|
|||
safe,
|
||||
tx,
|
||||
txCode,
|
||||
}): Promise<Transaction> => {
|
||||
}: BuildTx): Promise<Transaction> => {
|
||||
const safeAddress = safe.address
|
||||
const isModifySettingsTx = isModifySettingsTransaction(tx, safeAddress)
|
||||
const isTxCancelled = isTransactionCancelled(tx, outgoingTxs, cancellationTxs)
|
||||
|
@ -236,7 +255,7 @@ export const buildTx = async ({
|
|||
const confirmations = getConfirmations(tx)
|
||||
const { decimals = 18, symbol = 'ETH' } = isSendERC20Tx ? await getERC20DecimalsAndSymbol(tx.to) : {}
|
||||
|
||||
const txToStore: Transaction = makeTransaction({
|
||||
const txToStore = makeTransaction({
|
||||
baseGas: tx.baseGas,
|
||||
blockNumber: tx.blockNumber,
|
||||
cancelled: isTxCancelled,
|
||||
|
@ -277,7 +296,13 @@ export const buildTx = async ({
|
|||
.set('type', calculateTransactionType(txToStore))
|
||||
}
|
||||
|
||||
export const mockTransaction = (tx, safeAddress: string, state): Promise<any> => {
|
||||
export type TxToMock = TxArgs & {
|
||||
confirmations: []
|
||||
safeTxHash: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export const mockTransaction = (tx: TxToMock, safeAddress: string, state): Promise<Transaction> => {
|
||||
const submissionDate = new Date().toISOString()
|
||||
|
||||
const transactionStructure: TxServiceModel = {
|
||||
|
@ -302,8 +327,8 @@ export const mockTransaction = (tx, safeAddress: string, state): Promise<any> =>
|
|||
...tx,
|
||||
}
|
||||
|
||||
const knownTokens = state[TOKEN_REDUCER_ID]
|
||||
const safe = state[SAFE_REDUCER_ID].getIn([SAFE_REDUCER_ID, safeAddress])
|
||||
const knownTokens: Record<string, Token> = state[TOKEN_REDUCER_ID]
|
||||
const safe: SafeRecord = state[SAFE_REDUCER_ID].getIn([SAFE_REDUCER_ID, safeAddress])
|
||||
const cancellationTxs = state[CANCELLATION_TRANSACTIONS_REDUCER_ID].get(safeAddress) || Map()
|
||||
const outgoingTxs = state[TRANSACTIONS_REDUCER_ID].get(safeAddress) || List()
|
||||
|
||||
|
@ -318,7 +343,7 @@ export const mockTransaction = (tx, safeAddress: string, state): Promise<any> =>
|
|||
})
|
||||
}
|
||||
|
||||
export const updateStoredTransactionsStatus = (dispatch, walletRecord): void => {
|
||||
export const updateStoredTransactionsStatus = (dispatch: (any) => void, walletRecord: ProviderRecord): void => {
|
||||
const state = store.getState()
|
||||
const safe = safeSelector(state)
|
||||
|
||||
|
@ -352,7 +377,8 @@ export function generateSafeTxHash(safeAddress: string, txArgs: TxArgs): string
|
|||
{ type: 'uint256', name: 'nonce' },
|
||||
],
|
||||
}
|
||||
const primaryType = 'SafeTx' as const
|
||||
|
||||
const primaryType: 'SafeTx' = 'SafeTx' as const
|
||||
|
||||
const typedData = {
|
||||
types: messageTypes,
|
||||
|
|
|
@ -1,6 +1,25 @@
|
|||
import { List, Map, Record, Set } from 'immutable'
|
||||
import { List, Map, Record, RecordOf, Set } from 'immutable'
|
||||
|
||||
const SafeRecord = Record({
|
||||
export type SafeRecordProps = {
|
||||
name: string
|
||||
address: string
|
||||
threshold: number
|
||||
ethBalance: number
|
||||
owners: List<{ name: string; address: string }>
|
||||
activeTokens: Set<string>
|
||||
activeAssets: Set<string>
|
||||
blacklistedTokens: Set<string>
|
||||
blacklistedAssets: Set<string>
|
||||
balances: Map<string, string>
|
||||
nonce: number
|
||||
latestIncomingTxBlock: number
|
||||
recurringUser?: boolean
|
||||
currentVersion: string
|
||||
needsUpdate: boolean
|
||||
featuresEnabled: Array<string>
|
||||
}
|
||||
|
||||
const makeSafe = Record<SafeRecordProps>({
|
||||
name: '',
|
||||
address: '',
|
||||
threshold: 0,
|
||||
|
@ -19,4 +38,6 @@ const SafeRecord = Record({
|
|||
featuresEnabled: [],
|
||||
})
|
||||
|
||||
export default SafeRecord
|
||||
export type SafeRecord = RecordOf<SafeRecordProps>
|
||||
|
||||
export default makeSafe
|
||||
|
|
|
@ -2,8 +2,8 @@ import { List, Map, Record } from 'immutable'
|
|||
|
||||
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||
import {
|
||||
TransactionProps,
|
||||
PendingActionType,
|
||||
TransactionProps,
|
||||
TransactionStatus,
|
||||
TransactionTypes,
|
||||
} from 'src/routes/safe/store/models/types/transaction'
|
||||
|
@ -24,7 +24,7 @@ export const makeTransaction = Record<TransactionProps>({
|
|||
executionTxHash: undefined,
|
||||
executor: '',
|
||||
factoryAddress: '',
|
||||
gasPrice: 0,
|
||||
gasPrice: '0',
|
||||
gasToken: ZERO_ADDRESS,
|
||||
isCancellationTx: false,
|
||||
isCollectibleTransfer: false,
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import { List, Map, RecordOf } from 'immutable'
|
||||
import { DecodedMethods } from 'src/logic/contracts/methodIds'
|
||||
import { Confirmation } from './confirmation'
|
||||
|
||||
export enum TransactionTypes {
|
||||
INCOMING = 'incoming',
|
||||
OUTGOING = 'outgoing',
|
||||
|
@ -32,19 +36,19 @@ export type TransactionProps = {
|
|||
baseGas: number
|
||||
blockNumber?: number | null
|
||||
cancelled?: boolean
|
||||
confirmations: import('immutable').List<any>
|
||||
confirmations: List<Confirmation>
|
||||
created: boolean
|
||||
creator: string
|
||||
creationTx: boolean
|
||||
customTx: boolean
|
||||
data?: string | null
|
||||
decimals?: (number | string) | null
|
||||
decodedParams: import('src/logic/contracts/methodIds').DecodedMethods
|
||||
decodedParams: DecodedMethods
|
||||
executionDate?: string | null
|
||||
executionTxHash?: string | null
|
||||
executor: string
|
||||
factoryAddress: string
|
||||
gasPrice: number
|
||||
gasPrice: string
|
||||
gasToken: string
|
||||
isCancellationTx: boolean
|
||||
isCollectibleTransfer: boolean
|
||||
|
@ -58,7 +62,7 @@ export type TransactionProps = {
|
|||
nonce?: number | null
|
||||
operation: number
|
||||
origin: string | null
|
||||
ownersWithPendingActions: import('immutable').Map<PendingActionValues, import('immutable').List<any>>
|
||||
ownersWithPendingActions: Map<PendingActionValues, List<any>>
|
||||
recipient: string
|
||||
refundParams: any
|
||||
refundReceiver: string
|
||||
|
@ -68,26 +72,26 @@ export type TransactionProps = {
|
|||
status?: TransactionStatus
|
||||
submissionDate?: string | null
|
||||
symbol?: string | null
|
||||
transactionHash: string
|
||||
transactionHash: string | null
|
||||
type: TransactionTypes
|
||||
upgradeTx: boolean
|
||||
value: string
|
||||
}
|
||||
|
||||
export type Transaction = import('immutable').RecordOf<TransactionProps>
|
||||
export type Transaction = RecordOf<TransactionProps>
|
||||
|
||||
export type TxArgs = {
|
||||
data: any
|
||||
baseGas: number
|
||||
data?: string | null
|
||||
gasPrice: string
|
||||
gasToken: string
|
||||
safeInstance: any
|
||||
nonce: number
|
||||
valueInWei: any
|
||||
safeTxGas: number
|
||||
operation: number
|
||||
refundReceiver: string
|
||||
sender: any
|
||||
safeInstance: any
|
||||
safeTxGas: number
|
||||
sender?: string
|
||||
sigs: string
|
||||
to: any
|
||||
operation: any
|
||||
gasPrice: number
|
||||
to: string
|
||||
valueInWei: string
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { SET_DEFAULT_SAFE } from 'src/routes/safe/store/actions/setDefaultSafe'
|
|||
import { SET_LATEST_MASTER_CONTRACT_VERSION } from 'src/routes/safe/store/actions/setLatestMasterContractVersion'
|
||||
import { UPDATE_SAFE } from 'src/routes/safe/store/actions/updateSafe'
|
||||
import { makeOwner } from 'src/routes/safe/store/models/owner'
|
||||
import SafeRecord from 'src/routes/safe/store/models/safe'
|
||||
import makeSafe from 'src/routes/safe/store/models/safe'
|
||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||
|
||||
export const SAFE_REDUCER_ID = 'safes'
|
||||
|
@ -74,7 +74,7 @@ export default handleActions(
|
|||
return state.updateIn([SAFE_REDUCER_ID, safe.address], (prevSafe) => prevSafe.merge(safe))
|
||||
}
|
||||
|
||||
return state.setIn([SAFE_REDUCER_ID, safe.address], SafeRecord(safe))
|
||||
return state.setIn([SAFE_REDUCER_ID, safe.address], makeSafe(safe))
|
||||
},
|
||||
[REMOVE_SAFE]: (state, action) => {
|
||||
const safeAddress = action.payload
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
//
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import SafeRecord, { } from 'src/routes/safe/store/models/safe'
|
||||
import addSafe, { buildOwnersFrom } from 'src/routes/safe/store/actions/addSafe'
|
||||
import makeSafe from 'src/routes/safe/store/models/safe'
|
||||
import { buildOwnersFrom } from 'src/routes/safe/store/actions/addSafe'
|
||||
import {
|
||||
FIELD_NAME,
|
||||
FIELD_CONFIRMATIONS,
|
||||
|
@ -11,7 +9,6 @@ import {
|
|||
} from 'src/routes/open/components/fields'
|
||||
import { getWeb3, getProviderInfo } from 'src/logic/wallets/getWeb3'
|
||||
import { createSafe, } from 'src/routes/open/container/Open'
|
||||
import { } from 'src/store/index'
|
||||
import { makeProvider } from 'src/logic/wallets/store/model/provider'
|
||||
import addProvider from 'src/logic/wallets/store/actions/addProvider'
|
||||
|
||||
|
@ -19,7 +16,7 @@ class SafeBuilder {
|
|||
safe
|
||||
|
||||
constructor() {
|
||||
this.safe = SafeRecord()
|
||||
this.safe = makeSafe()
|
||||
}
|
||||
|
||||
withAddress(address) {
|
||||
|
@ -90,7 +87,7 @@ export const aMinedSafe = async (
|
|||
form[getOwnerNameBy(i)] = `Adol ${i + 1} Eth Account`
|
||||
form[getOwnerAddressBy(i)] = accounts[i]
|
||||
}
|
||||
|
||||
|
||||
const openSafeProps = await createSafe(form, accounts[0])
|
||||
|
||||
return openSafeProps.safeAddress
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//
|
||||
//
|
||||
import { } from 'react-router-dom'
|
||||
import { buildMatchPropsFrom } from 'src/test/utils/buildReactRouterProps'
|
||||
import { safeSelector } from 'src/routes/safe/store/selectors/index'
|
||||
|
|
Loading…
Reference in New Issue