v1.9.3 (#744)
* Bug: Invalid V in signature with eth_sign (#728) * Fix invalid V with metamask/ledger * DONT FORGET TO REVERT BEFORE MERGING: test deployment * DONT FORGET TO REVERT BEFORE MERGING 2: test deployment * Revert "DONT FORGET TO REVERT BEFORE MERGING 2: test deployment" This reverts commit 8331f2a78f7fc8f53eb893899f16edd8238c68ff. * Revert "DONT FORGET TO REVERT BEFORE MERGING: test deployment" This reverts commit 03b81e31820ce4fe078a7131c2f0caa2af4870ac. * BUG: Only injected providers are cached as last used provider (#733) * cache every used provider, not only injected one * package json update * (Fix) Adapt app to back-end changes (#736) * refactor: Set success status to `201` (CREATED) * refactor: return `null` when there's no latestTx * (Fix) Transaction not automatically executed (#716) * feature: action/reducer to UPDATE_SAFE_NONCE * refactor: when processing txs returned from backend, extract latest tx nonce value and store it in the safe's state * chore: update `yarn.lock` * refactor: `UPDATE_SAFE_THRESHOLD` and `UPDATE_SAFE_NONCE` discarded in favor of `UPDATE_SAFE` * refactor: use `SAFE_REDUCER_ID` constant * refactor: remove `updateSafeNonce` file * (Fix) Change the order of the upgrade methods lookup (#740) * fix: change the order of the upgrade methods lookup The `isUpgradeTransaction` method was looking for the methods in an wrong order (#599). The proper order was set in #610, but `isUpgradeTransaction` wasn't updated. * fix: contract upgrade version lookup * Feature: Use eth_sign for hardware wallets connected via onboard.js (#742) * Use eth_sign for hardware wallets * install onboard.js with fix from forked repo * rebuild yarn.lock to fix cached onboard * update bnc-onboard * update package json (#743) Co-authored-by: Fernando <fernando.greco@gmail.com>
This commit is contained in:
parent
3942347b5a
commit
74d48c40ac
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "safe-react",
|
||||
"version": "1.9.2",
|
||||
"version": "1.9.3",
|
||||
"description": "Allowing crypto users manage funds in a safer way",
|
||||
"homepage": "https://github.com/gnosis/safe-react#readme",
|
||||
"bugs": {
|
||||
|
@ -52,7 +52,7 @@
|
|||
"async-sema": "^3.1.0",
|
||||
"axios": "0.19.2",
|
||||
"bignumber.js": "9.0.0",
|
||||
"bnc-onboard": "1.5.0",
|
||||
"bnc-onboard": "^1.7.1",
|
||||
"connected-react-router": "6.8.0",
|
||||
"currency-flags": "^2.1.1",
|
||||
"date-fns": "2.11.1",
|
||||
|
|
|
@ -4,7 +4,7 @@ import { getWeb3 } from '~/logic/wallets/getWeb3'
|
|||
|
||||
const ETH_SIGN_NOT_SUPPORTED_ERROR_MSG = 'ETH_SIGN_NOT_SUPPORTED'
|
||||
|
||||
export const getEthSigner = async ({
|
||||
export const ethSigner = async ({
|
||||
baseGas,
|
||||
data,
|
||||
gasPrice,
|
||||
|
|
|
@ -1,19 +1,30 @@
|
|||
// @flow
|
||||
|
||||
import { getEIP712Signer } from './EIP712Signer'
|
||||
import { getEthSigner } from './ethSigner'
|
||||
import { ethSigner } from './ethSigner'
|
||||
|
||||
// 1. we try to sign via EIP-712 if user's wallet supports it
|
||||
// 2. If not, try to use eth_sign (Safe version has to be >1.1.1)
|
||||
// If eth_sign, doesn't work continue with the regular flow (on-chain signatures, more in createTransaction.js)
|
||||
|
||||
const signingFuncs = [getEIP712Signer('v3'), getEIP712Signer('v4'), getEIP712Signer(), getEthSigner]
|
||||
const SIGNERS = {
|
||||
EIP712_V3: getEIP712Signer('v3'),
|
||||
EIP712_V4: getEIP712Signer('v4'),
|
||||
EIP712: getEIP712Signer(),
|
||||
ETH_SIGN: ethSigner,
|
||||
}
|
||||
|
||||
// hardware wallets support eth_sign only
|
||||
const getSignersByWallet = (isHW: boolean): Array<$Values<SIGNERS>> =>
|
||||
isHW ? [SIGNERS.ETH_SIGN] : [SIGNERS.EIP712_V3, SIGNERS.EIP712_V4, SIGNERS.EIP712, SIGNERS.ETH_SIGN]
|
||||
|
||||
export const SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES = '>=1.1.1'
|
||||
|
||||
export const tryOffchainSigning = async (txArgs) => {
|
||||
export const tryOffchainSigning = async (txArgs, isHW: boolean): Promise<string> | typeof undefined => {
|
||||
let signature
|
||||
for (let signingFunc of signingFuncs) {
|
||||
|
||||
const signerByWallet = getSignersByWallet(isHW)
|
||||
for (let signingFunc of signerByWallet) {
|
||||
try {
|
||||
signature = await signingFunc(txArgs)
|
||||
|
||||
|
|
|
@ -63,6 +63,8 @@ export const buildTxServiceUrl = (safeAddress: string) => {
|
|||
return `${host}${base}`
|
||||
}
|
||||
|
||||
const SUCCESS_STATUS = 201 // CREATED status
|
||||
|
||||
export const saveTxToHistory = async ({
|
||||
baseGas,
|
||||
data,
|
||||
|
@ -116,7 +118,7 @@ export const saveTxToHistory = async ({
|
|||
)
|
||||
const response = await axios.post(url, body)
|
||||
|
||||
if (response.status !== 202) {
|
||||
if (response.status !== SUCCESS_STATUS) {
|
||||
return Promise.reject(new Error('Error submitting the transaction'))
|
||||
}
|
||||
|
||||
|
|
|
@ -62,10 +62,10 @@ export const isTokenTransfer = (data: string, value: number): boolean =>
|
|||
export const isMultisendTransaction = (data: string, value: number): boolean =>
|
||||
!!data && data.substring(0, 10) === '0x8d80ff0a' && value === 0
|
||||
|
||||
// f08a0323 - setFallbackHandler (308, 8)
|
||||
// 7de7edef - changeMasterCopy (550, 8)
|
||||
// 7de7edef - changeMasterCopy (308, 8)
|
||||
// f08a0323 - setFallbackHandler (550, 8)
|
||||
export const isUpgradeTransaction = (data: string) =>
|
||||
!!data && data.substr(308, 8) === 'f08a0323' && data.substr(550, 8) === '7de7edef'
|
||||
!!data && data.substr(308, 8) === '7de7edef' && data.substr(550, 8) === 'f08a0323'
|
||||
|
||||
export const isERC721Contract = async (contractAddress: string): boolean => {
|
||||
const ERC721Token = await getStandardTokenContract()
|
||||
|
|
|
@ -18,7 +18,7 @@ type DecodedTxData = {
|
|||
}
|
||||
|
||||
const getSafeVersion = (data: string) => {
|
||||
const contractAddress = data.substr(582, 40).toLowerCase()
|
||||
const contractAddress = data.substr(340, 40).toLowerCase()
|
||||
|
||||
return (
|
||||
{
|
||||
|
|
|
@ -102,12 +102,9 @@ const createTransaction = ({
|
|||
// https://github.com/LedgerHQ/ledgerjs/issues/378
|
||||
// Couldn't find an issue for trezor but the error is almost the same
|
||||
const canTryOffchainSigning =
|
||||
!isExecution &&
|
||||
!smartContractWallet &&
|
||||
!hardwareWallet &&
|
||||
semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES)
|
||||
!isExecution && !smartContractWallet && semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES)
|
||||
if (canTryOffchainSigning) {
|
||||
const signature = await tryOffchainSigning({ ...txArgs, safeAddress })
|
||||
const signature = await tryOffchainSigning({ ...txArgs, safeAddress }, hardwareWallet)
|
||||
|
||||
if (signature) {
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
|
|
|
@ -10,7 +10,7 @@ import { getBalanceInEtherOf, getWeb3 } from '~/logic/wallets/getWeb3'
|
|||
import addSafe from '~/routes/safe/store/actions/addSafe'
|
||||
import addSafeOwner from '~/routes/safe/store/actions/addSafeOwner'
|
||||
import removeSafeOwner from '~/routes/safe/store/actions/removeSafeOwner'
|
||||
import updateSafeThreshold from '~/routes/safe/store/actions/updateSafeThreshold'
|
||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||
import { makeOwner } from '~/routes/safe/store/models/owner'
|
||||
import type { SafeProps } from '~/routes/safe/store/models/safe'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
|
@ -77,7 +77,7 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: ReduxDis
|
|||
localSafe.threshold = remoteThreshold.toNumber()
|
||||
|
||||
if (localThreshold !== remoteThreshold.toNumber()) {
|
||||
dispatch(updateSafeThreshold({ safeAddress, threshold: remoteThreshold.toNumber() }))
|
||||
dispatch(updateSafe({ address: safeAddress, threshold: remoteThreshold.toNumber() }))
|
||||
}
|
||||
|
||||
// If the remote owners does not contain a local address, we remove that local owner
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import axios from 'axios'
|
||||
import bn from 'bignumber.js'
|
||||
import { List, Map, type RecordInstance } from 'immutable'
|
||||
import { batch } from 'react-redux'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
|
||||
import { addIncomingTransactions } from './addIncomingTransactions'
|
||||
|
@ -24,6 +25,7 @@ import { ZERO_ADDRESS, sameAddress } from '~/logic/wallets/ethAddresses'
|
|||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { addCancellationTransactions } from '~/routes/safe/store/actions/addCancellationTransactions'
|
||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||
import { makeConfirmation } from '~/routes/safe/store/models/confirmation'
|
||||
import { type IncomingTransaction, makeIncomingTransaction } from '~/routes/safe/store/models/incomingTransaction'
|
||||
import { makeOwner } from '~/routes/safe/store/models/owner'
|
||||
|
@ -347,15 +349,48 @@ export const loadSafeIncomingTransactions = async (safeAddress: string) => {
|
|||
return Map().set(safeAddress, List(incomingTxsRecord))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns nonce from the last tx returned by the server or defaults to 0
|
||||
* @param outgoingTxs
|
||||
* @returns {number|*}
|
||||
*/
|
||||
const getLastTxNonce = (outgoingTxs) => {
|
||||
if (!outgoingTxs) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const mostRecentNonce = outgoingTxs.get(0).nonce
|
||||
|
||||
// if nonce is null, then we are in front of the creation-tx
|
||||
if (mostRecentNonce === null) {
|
||||
const tx = outgoingTxs.get(1)
|
||||
|
||||
if (tx) {
|
||||
// if there's other tx than the creation one, we return its nonce
|
||||
return tx.nonce
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
return mostRecentNonce
|
||||
}
|
||||
|
||||
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
web3 = await getWeb3()
|
||||
|
||||
const transactions: SafeTransactionsType | undefined = await loadSafeTransactions(safeAddress)
|
||||
if (transactions) {
|
||||
const { cancel, outgoing } = transactions
|
||||
dispatch(addCancellationTransactions(cancel))
|
||||
dispatch(addTransactions(outgoing))
|
||||
const nonce = getLastTxNonce(outgoing && outgoing.get(safeAddress))
|
||||
|
||||
batch(() => {
|
||||
dispatch(addCancellationTransactions(cancel))
|
||||
dispatch(addTransactions(outgoing))
|
||||
dispatch(updateSafe({ address: safeAddress, nonce }))
|
||||
})
|
||||
}
|
||||
|
||||
const incomingTransactions: Map<string, List<IncomingTransaction>> | undefined = await loadSafeIncomingTransactions(
|
||||
safeAddress,
|
||||
)
|
||||
|
|
|
@ -87,12 +87,9 @@ const processTransaction = ({
|
|||
// https://github.com/LedgerHQ/ledgerjs/issues/378
|
||||
// Couldn't find an issue for trezor but the error is almost the same
|
||||
const canTryOffchainSigning =
|
||||
!isExecution &&
|
||||
!smartContractWallet &&
|
||||
!hardwareWallet &&
|
||||
semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES)
|
||||
!isExecution && !smartContractWallet && semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES)
|
||||
if (canTryOffchainSigning) {
|
||||
const signature = await tryOffchainSigning({ ...txArgs, safeAddress })
|
||||
const signature = await tryOffchainSigning({ ...txArgs, safeAddress }, hardwareWallet)
|
||||
|
||||
if (signature) {
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
// @flow
|
||||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const UPDATE_SAFE_THRESHOLD = 'UPDATE_SAFE_THRESHOLD'
|
||||
|
||||
const updateSafeThreshold = createAction<string, *>(UPDATE_SAFE_THRESHOLD)
|
||||
|
||||
export default updateSafeThreshold
|
|
@ -8,7 +8,7 @@ export const getLastTx = async (safeAddress: string): Promise<TransactionProps>
|
|||
const url = buildTxServiceUrl(safeAddress)
|
||||
const response = await axios.get(url, { params: { limit: 1 } })
|
||||
|
||||
return response.data.results[0]
|
||||
return response.data.results[0] || null
|
||||
} catch (e) {
|
||||
console.error('failed to retrieve last Tx from server', e)
|
||||
return null
|
||||
|
|
|
@ -13,7 +13,6 @@ import { REPLACE_SAFE_OWNER } from '~/routes/safe/store/actions/replaceSafeOwner
|
|||
import { SET_DEFAULT_SAFE } from '~/routes/safe/store/actions/setDefaultSafe'
|
||||
import { SET_LATEST_MASTER_CONTRACT_VERSION } from '~/routes/safe/store/actions/setLatestMasterContractVersion'
|
||||
import { UPDATE_SAFE } from '~/routes/safe/store/actions/updateSafe'
|
||||
import { UPDATE_SAFE_THRESHOLD } from '~/routes/safe/store/actions/updateSafeThreshold'
|
||||
import { makeOwner } from '~/routes/safe/store/models/owner'
|
||||
import SafeRecord, { type SafeProps } from '~/routes/safe/store/models/safe'
|
||||
|
||||
|
@ -49,21 +48,29 @@ export default handleActions<SafeReducerState, *>(
|
|||
[UPDATE_SAFE]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState => {
|
||||
const safe = action.payload
|
||||
const safeAddress = safe.address
|
||||
const isNonceUpdate = safe.nonce !== undefined
|
||||
|
||||
return state.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.merge(safe))
|
||||
if (isNonceUpdate && safe.nonce <= state.getIn([SAFE_REDUCER_ID, safeAddress, 'nonce'])) {
|
||||
// update only when nonce is greater than the one already stored
|
||||
// this will prevent undesired changes in the safe's state when
|
||||
// txs pagination is implemented
|
||||
return state
|
||||
}
|
||||
|
||||
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) => prevSafe.merge(safe))
|
||||
},
|
||||
[ACTIVATE_TOKEN_FOR_ALL_SAFES]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState => {
|
||||
const tokenAddress = action.payload
|
||||
|
||||
return state.withMutations((map) => {
|
||||
map
|
||||
.get('safes')
|
||||
.get(SAFE_REDUCER_ID)
|
||||
.keySeq()
|
||||
.forEach((safeAddress) => {
|
||||
const safeActiveTokens = map.getIn(['safes', safeAddress, 'activeTokens'])
|
||||
const safeActiveTokens = map.getIn([SAFE_REDUCER_ID, safeAddress, 'activeTokens'])
|
||||
const activeTokens = safeActiveTokens.add(tokenAddress)
|
||||
|
||||
map.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.merge({ activeTokens }))
|
||||
map.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) => prevSafe.merge({ activeTokens }))
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -74,21 +81,21 @@ export default handleActions<SafeReducerState, *>(
|
|||
// in case of update it shouldn't, because a record would be initialized
|
||||
// with initial props and it would overwrite existing ones
|
||||
|
||||
if (state.hasIn(['safes', safe.address])) {
|
||||
return state.updateIn(['safes', safe.address], (prevSafe) => prevSafe.merge(safe))
|
||||
if (state.hasIn([SAFE_REDUCER_ID, safe.address])) {
|
||||
return state.updateIn([SAFE_REDUCER_ID, safe.address], (prevSafe) => prevSafe.merge(safe))
|
||||
}
|
||||
|
||||
return state.setIn(['safes', safe.address], SafeRecord(safe))
|
||||
return state.setIn([SAFE_REDUCER_ID, safe.address], SafeRecord(safe))
|
||||
},
|
||||
[REMOVE_SAFE]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState => {
|
||||
const safeAddress = action.payload
|
||||
|
||||
return state.deleteIn(['safes', safeAddress])
|
||||
return state.deleteIn([SAFE_REDUCER_ID, safeAddress])
|
||||
},
|
||||
[ADD_SAFE_OWNER]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState => {
|
||||
const { ownerAddress, ownerName, safeAddress } = action.payload
|
||||
|
||||
return state.updateIn(['safes', safeAddress], (prevSafe) =>
|
||||
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) =>
|
||||
prevSafe.merge({
|
||||
owners: prevSafe.owners.push(makeOwner({ address: ownerAddress, name: ownerName })),
|
||||
}),
|
||||
|
@ -97,7 +104,7 @@ export default handleActions<SafeReducerState, *>(
|
|||
[REMOVE_SAFE_OWNER]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState => {
|
||||
const { ownerAddress, safeAddress } = action.payload
|
||||
|
||||
return state.updateIn(['safes', safeAddress], (prevSafe) =>
|
||||
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) =>
|
||||
prevSafe.merge({
|
||||
owners: prevSafe.owners.filter((o) => o.address.toLowerCase() !== ownerAddress.toLowerCase()),
|
||||
}),
|
||||
|
@ -106,7 +113,7 @@ export default handleActions<SafeReducerState, *>(
|
|||
[REPLACE_SAFE_OWNER]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState => {
|
||||
const { oldOwnerAddress, ownerAddress, ownerName, safeAddress } = action.payload
|
||||
|
||||
return state.updateIn(['safes', safeAddress], (prevSafe) =>
|
||||
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) =>
|
||||
prevSafe.merge({
|
||||
owners: prevSafe.owners
|
||||
.filter((o) => o.address.toLowerCase() !== oldOwnerAddress.toLowerCase())
|
||||
|
@ -117,7 +124,7 @@ export default handleActions<SafeReducerState, *>(
|
|||
[EDIT_SAFE_OWNER]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState => {
|
||||
const { ownerAddress, ownerName, safeAddress } = action.payload
|
||||
|
||||
return state.updateIn(['safes', safeAddress], (prevSafe) => {
|
||||
return state.updateIn([SAFE_REDUCER_ID, safeAddress], (prevSafe) => {
|
||||
const ownerToUpdateIndex = prevSafe.owners.findIndex(
|
||||
(o) => o.address.toLowerCase() === ownerAddress.toLowerCase(),
|
||||
)
|
||||
|
@ -125,11 +132,6 @@ export default handleActions<SafeReducerState, *>(
|
|||
return prevSafe.merge({ owners: updatedOwners })
|
||||
})
|
||||
},
|
||||
[UPDATE_SAFE_THRESHOLD]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState => {
|
||||
const { safeAddress, threshold } = action.payload
|
||||
|
||||
return state.updateIn(['safes', safeAddress], (prevSafe) => prevSafe.set('threshold', threshold))
|
||||
},
|
||||
[SET_DEFAULT_SAFE]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState =>
|
||||
state.set('defaultSafe', action.payload),
|
||||
[SET_LATEST_MASTER_CONTRACT_VERSION]: (state: SafeReducerState, action: ActionType<Function>): SafeReducerState =>
|
||||
|
|
Loading…
Reference in New Issue