* Refactors grantedSelector with isUserOwner function Checks if the user is owner of the safe before sending notification * Adds safeParamAddressFromStateSelector Refactors notificationsMiddleware with new selector * Remove old size check * safe notifications middleware fixes
This commit is contained in:
parent
509000b9e6
commit
cbb3908f1b
|
@ -1,34 +1,41 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { List } from 'immutable'
|
import { Map, List } from 'immutable'
|
||||||
import type { Transaction } from '~/routes/safe/store/models/transaction'
|
import type { Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
|
|
||||||
export const getAwaitingTransactions = (allTransactions: List<Transaction>, userAccount: string): List<Transaction> => {
|
export const getAwaitingTransactions = (
|
||||||
|
allTransactions: Map<string, List<Transaction>>,
|
||||||
|
userAccount: string,
|
||||||
|
): Map<string, List<Transaction>> => {
|
||||||
if (!allTransactions) {
|
if (!allTransactions) {
|
||||||
return List([])
|
return Map({})
|
||||||
}
|
}
|
||||||
|
|
||||||
const allAwaitingTransactions = allTransactions.map((safeTransactions) => {
|
const allAwaitingTransactions = allTransactions.map((safeTransactions) => {
|
||||||
const nonCancelledTransactions = safeTransactions.filter((transaction: Transaction) => {
|
const nonCancelledTransactions = safeTransactions.filter(
|
||||||
// If transactions are not executed, but there's a transaction with the same nonce EXECUTED later
|
(transaction: Transaction) => {
|
||||||
// it means that the transaction was cancelled (Replaced) and shouldn't get executed
|
// If transactions are not executed, but there's a transaction with the same nonce EXECUTED later
|
||||||
if (!transaction.isExecuted) {
|
// it means that the transaction was cancelled (Replaced) and shouldn't get executed
|
||||||
const replacementTransaction = safeTransactions.findLast(
|
if (!transaction.isExecuted) {
|
||||||
(tx) => tx.isExecuted && tx.nonce === transaction.nonce,
|
const replacementTransaction = safeTransactions.findLast(
|
||||||
)
|
(tx) => tx.isExecuted && tx.nonce === transaction.nonce,
|
||||||
if (replacementTransaction) {
|
)
|
||||||
// eslint-disable-next-line no-param-reassign
|
if (replacementTransaction) {
|
||||||
transaction = transaction.set('cancelled', true)
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
transaction = transaction.set('cancelled', true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
// The transaction is not executed and is not cancelled, so it's still waiting confirmations
|
||||||
// The transaction is not executed and is not cancelled, so it's still waiting confirmations
|
if (!transaction.executionTxHash && !transaction.cancelled) {
|
||||||
if (!transaction.executionTxHash && !transaction.cancelled) {
|
// Then we check if the waiting confirmations are not from the current user, otherwise, filters this transaction
|
||||||
// Then we check if the waiting confirmations are not from the current user, otherwise, filters this transaction
|
const transactionWaitingUser = transaction.confirmations.filter(
|
||||||
const transactionWaitingUser = transaction.confirmations.filter((confirmation) => confirmation.owner && confirmation.owner.address !== userAccount)
|
(confirmation) => confirmation.owner && confirmation.owner.address !== userAccount,
|
||||||
|
)
|
||||||
|
|
||||||
return transactionWaitingUser.size > 0
|
return transactionWaitingUser.size > 0
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
},
|
||||||
|
)
|
||||||
return nonCancelledTransactions
|
return nonCancelledTransactions
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
import { List } from 'immutable'
|
||||||
|
import type { Safe } from '~/routes/safe/store/models/safe'
|
||||||
|
import type { Owner } from '~/routes/safe/store/models/owner'
|
||||||
|
|
||||||
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||||
|
|
||||||
export const sameAddress = (firstAddress: string, secondAddress: string): boolean => {
|
export const sameAddress = (firstAddress: string, secondAddress: string): boolean => {
|
||||||
|
@ -25,3 +29,20 @@ export const shortVersionOf = (value: string, cut: number) => {
|
||||||
|
|
||||||
return `${value.substring(0, cut)}...${value.substring(final)}`
|
return `${value.substring(0, cut)}...${value.substring(final)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isUserOwner = (safe: Safe, userAccount: string): boolean => {
|
||||||
|
if (!safe) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userAccount) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const { owners }: List<Owner> = safe
|
||||||
|
if (!owners) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return owners.find((owner: Owner) => sameAddress(owner.address, userAccount)) !== undefined
|
||||||
|
}
|
||||||
|
|
|
@ -90,15 +90,13 @@ const getTransactionTableData = (tx: Transaction): TransactionRow => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTxTableData = (transactions: List<Transaction | IncomingTransaction>): List<TransactionRow> => {
|
export const getTxTableData = (transactions: List<Transaction | IncomingTransaction>): List<TransactionRow> => transactions.map((tx) => {
|
||||||
return transactions.map((tx) => {
|
if (tx.type === INCOMING_TX_TYPE) {
|
||||||
if (tx.type === INCOMING_TX_TYPE) {
|
return getIncomingTxTableData(tx)
|
||||||
return getIncomingTxTableData(tx)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return getTransactionTableData(tx)
|
return getTransactionTableData(tx)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
export const generateColumns = () => {
|
export const generateColumns = () => {
|
||||||
const nonceColumn: Column = {
|
const nonceColumn: Column = {
|
||||||
|
|
|
@ -13,9 +13,8 @@ import {
|
||||||
} from '~/routes/safe/store/selectors'
|
} from '~/routes/safe/store/selectors'
|
||||||
import { providerNameSelector, userAccountSelector, networkSelector } from '~/logic/wallets/store/selectors'
|
import { providerNameSelector, userAccountSelector, networkSelector } from '~/logic/wallets/store/selectors'
|
||||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||||
import { type Owner } from '~/routes/safe/store/models/owner'
|
|
||||||
import { type GlobalState } from '~/store'
|
import { type GlobalState } from '~/store'
|
||||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
import { isUserOwner } from '~/logic/wallets/ethAddresses'
|
||||||
import { orderedTokenListSelector, tokensSelector } from '~/logic/tokens/store/selectors'
|
import { orderedTokenListSelector, tokensSelector } from '~/logic/tokens/store/selectors'
|
||||||
import { type Token } from '~/logic/tokens/store/model/token'
|
import { type Token } from '~/logic/tokens/store/model/token'
|
||||||
import { type Transaction, type TransactionStatus } from '~/routes/safe/store/models/transaction'
|
import { type Transaction, type TransactionStatus } from '~/routes/safe/store/models/transaction'
|
||||||
|
@ -63,22 +62,7 @@ const getTxStatus = (tx: Transaction, userAddress: string, safe: Safe): Transact
|
||||||
export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = createSelector(
|
export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = createSelector(
|
||||||
userAccountSelector,
|
userAccountSelector,
|
||||||
safeSelector,
|
safeSelector,
|
||||||
(userAccount: string, safe: Safe | typeof undefined): boolean => {
|
(userAccount: string, safe: Safe | typeof undefined): boolean => isUserOwner(safe, userAccount),
|
||||||
if (!safe) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userAccount) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const { owners }: List<Owner> = safe
|
|
||||||
if (!owners) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return owners.find((owner: Owner) => sameAddress(owner.address, userAccount)) !== undefined
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const safeEthAsTokenSelector: Selector<GlobalState, RouterProps, ?Token> = createSelector(
|
const safeEthAsTokenSelector: Selector<GlobalState, RouterProps, ?Token> = createSelector(
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { AnyAction, Store } from 'redux'
|
import type { Action, Store } from 'redux'
|
||||||
|
import { List } from 'immutable'
|
||||||
import { push } from 'connected-react-router'
|
import { push } from 'connected-react-router'
|
||||||
import { Map } from 'immutable'
|
|
||||||
import { type GlobalState } from '~/store/'
|
import { type GlobalState } from '~/store/'
|
||||||
import { ADD_TRANSACTIONS } from '~/routes/safe/store/actions/addTransactions'
|
import { ADD_TRANSACTIONS } from '~/routes/safe/store/actions/addTransactions'
|
||||||
import { ADD_INCOMING_TRANSACTIONS } from '~/routes/safe/store/actions/addIncomingTransactions'
|
import { ADD_INCOMING_TRANSACTIONS } from '~/routes/safe/store/actions/addIncomingTransactions'
|
||||||
import { getAwaitingTransactions } from '~/logic/safe/transactions/awaitingTransactions'
|
import { getAwaitingTransactions } from '~/logic/safe/transactions/awaitingTransactions'
|
||||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||||
import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar'
|
import enqueueSnackbar from '~/logic/notifications/store/actions/enqueueSnackbar'
|
||||||
import { enhanceSnackbarForAction, NOTIFICATIONS, SUCCESS } from '~/logic/notifications'
|
import { enhanceSnackbarForAction, NOTIFICATIONS } from '~/logic/notifications'
|
||||||
import closeSnackbarAction from '~/logic/notifications/store/actions/closeSnackbar'
|
import closeSnackbarAction from '~/logic/notifications/store/actions/closeSnackbar'
|
||||||
import { getIncomingTxAmount } from '~/routes/safe/components/Transactions/TxsTable/columns'
|
import { getIncomingTxAmount } from '~/routes/safe/components/Transactions/TxsTable/columns'
|
||||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||||
import { loadFromStorage } from '~/utils/storage'
|
import { loadFromStorage } from '~/utils/storage'
|
||||||
import { SAFES_KEY } from '~/logic/safe/utils'
|
import { SAFES_KEY } from '~/logic/safe/utils'
|
||||||
import { RECURRING_USER_KEY } from '~/utils/verifyRecurringUser'
|
import { RECURRING_USER_KEY } from '~/utils/verifyRecurringUser'
|
||||||
|
import { safesMapSelector } from '~/routes/safe/store/selectors'
|
||||||
|
import { isUserOwner } from '~/logic/wallets/ethAddresses'
|
||||||
|
|
||||||
const watchedActions = [
|
const watchedActions = [ADD_TRANSACTIONS, ADD_INCOMING_TRANSACTIONS]
|
||||||
ADD_TRANSACTIONS,
|
|
||||||
ADD_INCOMING_TRANSACTIONS,
|
|
||||||
]
|
|
||||||
|
|
||||||
const notificationsMiddleware = (store: Store<GlobalState>) => (next: Function) => async (action: AnyAction) => {
|
const notificationsMiddleware = (store: Store<GlobalState>) => (
|
||||||
|
next: Function,
|
||||||
|
) => async (action: Action<*>) => {
|
||||||
const handledAction = next(action)
|
const handledAction = next(action)
|
||||||
const { dispatch } = store
|
const { dispatch } = store
|
||||||
|
|
||||||
|
@ -31,28 +32,52 @@ const notificationsMiddleware = (store: Store<GlobalState>) => (next: Function)
|
||||||
case ADD_TRANSACTIONS: {
|
case ADD_TRANSACTIONS: {
|
||||||
const transactionsList = action.payload
|
const transactionsList = action.payload
|
||||||
const userAddress: string = userAccountSelector(state)
|
const userAddress: string = userAccountSelector(state)
|
||||||
const awaitingTransactions = getAwaitingTransactions(transactionsList, userAddress)
|
const safeAddress = action.payload.keySeq().get(0)
|
||||||
|
const awaitingTransactions = getAwaitingTransactions(
|
||||||
|
transactionsList,
|
||||||
|
userAddress,
|
||||||
|
)
|
||||||
|
const awaitingTransactionsList = awaitingTransactions.get(
|
||||||
|
safeAddress,
|
||||||
|
List([]),
|
||||||
|
)
|
||||||
|
const safes = safesMapSelector(state)
|
||||||
|
const currentSafe = safes.get(safeAddress)
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isUserOwner(currentSafe, userAddress)
|
||||||
|
|| awaitingTransactionsList.size === 0
|
||||||
|
) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationKey = `${safeAddress}-${userAddress}`
|
||||||
|
const onNotificationClicked = () => {
|
||||||
|
dispatch(closeSnackbarAction({ key: notificationKey }))
|
||||||
|
dispatch(push(`/safes/${safeAddress}/transactions`))
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
enqueueSnackbar(
|
||||||
|
enhanceSnackbarForAction(
|
||||||
|
NOTIFICATIONS.TX_WAITING_MSG,
|
||||||
|
notificationKey,
|
||||||
|
onNotificationClicked,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
awaitingTransactions.map((awaitingTransactionsList, safeAddress) => {
|
|
||||||
const convertedList = awaitingTransactionsList.toJS()
|
|
||||||
const notificationKey = `${safeAddress}-${userAddress}`
|
|
||||||
const onNotificationClicked = () => {
|
|
||||||
dispatch(closeSnackbarAction({ key: notificationKey }))
|
|
||||||
dispatch(push(`/safes/${safeAddress}/transactions`))
|
|
||||||
}
|
|
||||||
if (convertedList.length > 0) {
|
|
||||||
dispatch(enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.TX_WAITING_MSG, notificationKey, onNotificationClicked)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case ADD_INCOMING_TRANSACTIONS: {
|
case ADD_INCOMING_TRANSACTIONS: {
|
||||||
action.payload.forEach(async (incomingTransactions, safeAddress) => {
|
action.payload.forEach(async (incomingTransactions, safeAddress) => {
|
||||||
const storedSafes = await loadFromStorage(SAFES_KEY)
|
const storedSafes = await loadFromStorage(SAFES_KEY)
|
||||||
const latestIncomingTxBlock = storedSafes ? storedSafes[safeAddress].latestIncomingTxBlock : 0
|
const latestIncomingTxBlock = storedSafes
|
||||||
|
? storedSafes[safeAddress].latestIncomingTxBlock
|
||||||
|
: 0
|
||||||
|
|
||||||
const newIncomingTransactions = incomingTransactions.filter((tx) => tx.blockNumber > latestIncomingTxBlock)
|
const newIncomingTransactions = incomingTransactions.filter(
|
||||||
|
(tx) => tx.blockNumber > latestIncomingTxBlock,
|
||||||
|
)
|
||||||
const { message, ...TX_INCOMING_MSG } = NOTIFICATIONS.TX_INCOMING_MSG
|
const { message, ...TX_INCOMING_MSG } = NOTIFICATIONS.TX_INCOMING_MSG
|
||||||
const recurringUser = await loadFromStorage(RECURRING_USER_KEY)
|
const recurringUser = await loadFromStorage(RECURRING_USER_KEY)
|
||||||
|
|
||||||
|
@ -62,9 +87,9 @@ const notificationsMiddleware = (store: Store<GlobalState>) => (next: Function)
|
||||||
enqueueSnackbar(
|
enqueueSnackbar(
|
||||||
enhanceSnackbarForAction({
|
enhanceSnackbarForAction({
|
||||||
...TX_INCOMING_MSG,
|
...TX_INCOMING_MSG,
|
||||||
message: 'Multiple incoming transfers'
|
message: 'Multiple incoming transfers',
|
||||||
})
|
}),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
newIncomingTransactions.forEach((tx) => {
|
newIncomingTransactions.forEach((tx) => {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { Map, List, Set } from 'immutable'
|
import { Map, List, Set } from 'immutable'
|
||||||
import { type Match } from 'react-router-dom'
|
import { type Match, matchPath } from 'react-router-dom'
|
||||||
import { createSelector, createStructuredSelector, type Selector } from 'reselect'
|
import { createSelector, createStructuredSelector, type Selector } from 'reselect'
|
||||||
import { type GlobalState } from '~/store/index'
|
import { type GlobalState } from '~/store/index'
|
||||||
import { SAFE_PARAM_ADDRESS } from '~/routes/routes'
|
import { SAFE_PARAM_ADDRESS, SAFELIST_ADDRESS } from '~/routes/routes'
|
||||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||||
import { type State as TransactionsState, TRANSACTIONS_REDUCER_ID } from '~/routes/safe/store/reducer/transactions'
|
import { type State as TransactionsState, TRANSACTIONS_REDUCER_ID } from '~/routes/safe/store/reducer/transactions'
|
||||||
import {
|
import {
|
||||||
type IncomingState as IncomingTransactionsState,
|
type IncomingState as IncomingTransactionsState,
|
||||||
INCOMING_TRANSACTIONS_REDUCER_ID
|
INCOMING_TRANSACTIONS_REDUCER_ID,
|
||||||
} from '~/routes/safe/store/reducer/incomingTransactions'
|
} from '~/routes/safe/store/reducer/incomingTransactions'
|
||||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||||
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
||||||
|
@ -83,7 +83,7 @@ export const safeIncomingTransactionsSelector: Selector<GlobalState, RouterProps
|
||||||
}
|
}
|
||||||
|
|
||||||
return incomingTransactions.get(address) || List([])
|
return incomingTransactions.get(address) || List([])
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export const confirmationsTransactionSelector: Selector<GlobalState, TransactionProps, number> = createSelector(
|
export const confirmationsTransactionSelector: Selector<GlobalState, TransactionProps, number> = createSelector(
|
||||||
|
|
Loading…
Reference in New Issue