Merge with dev

This commit is contained in:
Mati Dastugue 2020-06-10 11:39:15 -03:00
commit 027b8ccc51
41 changed files with 1438 additions and 399 deletions

View File

@ -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"
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,
}),

View File

@ -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] },
]
],
}
}

View File

@ -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}`

View File

@ -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))
}

View File

@ -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) {

View File

@ -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,
}))

View File

@ -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) => ({
export const setSelectedCurrency = createAction(
SET_CURRENT_CURRENCY,
(safeAddress: string, selectedCurrency: AVAILABLE_CURRENCIES) => ({
safeAddress,
selectedCurrency,
}))
}),
)

View File

@ -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
}

View File

@ -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({

View File

@ -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)) || {}
}

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -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
}

View File

@ -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} />

View File

@ -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 },
]

View File

@ -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,

View File

@ -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}

View File

@ -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])

View File

@ -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>

View File

@ -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

View File

@ -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) => {

View File

@ -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) => {

View File

@ -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)

View File

@ -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,9 +132,8 @@ 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(
return {
[tx.dataDecoded.method]: tx.dataDecoded.parameters.reduce(
(acc, param) => ({
...acc,
[param.name]: param.value,
@ -125,8 +141,6 @@ export const getDecodedParams = (tx: TxServiceModel): DecodedMethods => {
{},
),
}
return acc
}, {})
}
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,

View File

@ -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

View File

@ -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,

View File

@ -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
}

View File

@ -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

View File

@ -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) {

1201
yarn.lock

File diff suppressed because it is too large Load Diff