Safe apps: return safe tx hash (#1245)
* apps refactoring wip * apps refactoring wip * type fixes * add useLegalConsent hook in apps * useAppList hook wip * dep nump * useAppList hook wip * fix selecting first app * Remove console.log * dep bump * update persisting app logic * update saveToStorage type * fix crash on apps tab * add appframe comp * add handleIframeLoad func * reuse selectedApp variable in hook * remove initialAppSelected * yarn regenration * useIframeCommunicator wip * add types for apps component * dep bump * fix history types * yarn regenration * extract useIframeMessenger hook * fix safe-react-components version * useIframeMessageHandler wip * fix types * send safe info on handshake * fix naming/types for url utils * remove operations * update safe-apps-sdk * wip * update safe-apps-sdk * requestId wip * cta snackbar usage fixes * notifications refactor wip * notifications refactor: use dispatch * tsc fixes * extract confirm transaction modal * Extract confirmation modal to a separate component * dep bump * ConfirmTransactionModal component * Return safeTxHash after user confirmed transaction * fix address validator, close modal when user confirms the tx * close modal after confirmation * update imports * update imports [2] * update imports [3] * update imports [4] * remove console.log in createTransaction * update safe-apps-sdk * yarn.lock * EditOwnerModal types
This commit is contained in:
parent
4dc28942c0
commit
66c5ae7f8e
10
package.json
10
package.json
|
@ -162,7 +162,7 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@gnosis.pm/safe-apps-sdk": "0.3.1",
|
||||
"@gnosis.pm/safe-apps-sdk": "https://github.com/gnosis/safe-apps-sdk.git#development",
|
||||
"@gnosis.pm/safe-contracts": "1.1.1-dev.2",
|
||||
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#fd4498f",
|
||||
"@gnosis.pm/util-contracts": "2.0.6",
|
||||
|
@ -174,7 +174,7 @@
|
|||
"async-sema": "^3.1.0",
|
||||
"axios": "0.19.2",
|
||||
"bignumber.js": "9.0.0",
|
||||
"bnc-onboard": "1.11.0",
|
||||
"bnc-onboard": "1.11.1",
|
||||
"classnames": "^2.2.6",
|
||||
"concurrently": "^5.2.0",
|
||||
"connected-react-router": "6.8.0",
|
||||
|
@ -236,14 +236,14 @@
|
|||
"@types/history": "4.6.2",
|
||||
"@types/jest": "^26.0.9",
|
||||
"@types/lodash.memoize": "^4.1.6",
|
||||
"@types/node": "14.0.27",
|
||||
"@types/node": "14.6.0",
|
||||
"@types/react": "^16.9.44",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@types/react-redux": "^7.1.9",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"@types/styled-components": "^5.1.2",
|
||||
"@typescript-eslint/eslint-plugin": "3.8.0",
|
||||
"@typescript-eslint/parser": "3.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "3.9.1",
|
||||
"@typescript-eslint/parser": "3.9.1",
|
||||
"autoprefixer": "9.8.6",
|
||||
"cross-env": "^7.0.2",
|
||||
"dotenv": "^8.2.0",
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
import { fetchProvider, removeProvider } from 'src/logic/wallets/store/actions'
|
||||
|
||||
export default {
|
||||
fetchProvider,
|
||||
removeProvider,
|
||||
}
|
|
@ -1,30 +1,33 @@
|
|||
import { withSnackbar } from 'notistack'
|
||||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import React, { useEffect } from 'react'
|
||||
import { useSelector, useDispatch } from 'react-redux'
|
||||
|
||||
import actions from './actions'
|
||||
import Layout from './components/Layout'
|
||||
import ConnectDetails from './components/ProviderDetails/ConnectDetails'
|
||||
import UserDetails from './components/ProviderDetails/UserDetails'
|
||||
import ProviderAccessible from './components/ProviderInfo/ProviderAccessible'
|
||||
import ProviderDisconnected from './components/ProviderInfo/ProviderDisconnected'
|
||||
import selector from './selector'
|
||||
import {
|
||||
availableSelector,
|
||||
loadedSelector,
|
||||
networkSelector,
|
||||
providerNameSelector,
|
||||
userAccountSelector,
|
||||
} from 'src/logic/wallets/store/selectors'
|
||||
import { removeProvider } from 'src/logic/wallets/store/actions'
|
||||
|
||||
import { onboard } from 'src/components/ConnectButton'
|
||||
import { NOTIFICATIONS, showSnackbar } from 'src/logic/notifications'
|
||||
import { loadLastUsedProvider } from 'src/logic/wallets/store/middlewares/providerWatcher'
|
||||
import { logComponentStack } from 'src/utils/logBoundaries'
|
||||
|
||||
class HeaderComponent extends React.PureComponent<any, any> {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const HeaderComponent = (): React.ReactElement => {
|
||||
const provider = useSelector(providerNameSelector)
|
||||
const userAddress = useSelector(userAccountSelector)
|
||||
const network = useSelector(networkSelector)
|
||||
const loaded = useSelector(loadedSelector)
|
||||
const available = useSelector(availableSelector)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
this.state = {
|
||||
hasError: false,
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
useEffect(() => {
|
||||
const tryToConnectToLastUsedProvider = async () => {
|
||||
const lastUsedProvider = await loadLastUsedProvider()
|
||||
if (lastUsedProvider) {
|
||||
const hasSelectedWallet = await onboard.walletSelect(lastUsedProvider)
|
||||
|
@ -34,41 +37,28 @@ class HeaderComponent extends React.PureComponent<any, any> {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidCatch(error, info) {
|
||||
const { closeSnackbar, enqueueSnackbar } = this.props
|
||||
tryToConnectToLastUsedProvider()
|
||||
}, [])
|
||||
|
||||
this.setState({ hasError: true })
|
||||
showSnackbar(NOTIFICATIONS.CONNECT_WALLET_ERROR_MSG, enqueueSnackbar, closeSnackbar)
|
||||
|
||||
logComponentStack(error, info)
|
||||
}
|
||||
|
||||
getOpenDashboard = () => {
|
||||
const openDashboard = () => {
|
||||
const { wallet } = onboard.getState()
|
||||
return wallet.type === 'sdk' && wallet.dashboard
|
||||
}
|
||||
onDisconnect = () => {
|
||||
const { closeSnackbar, enqueueSnackbar, removeProvider } = this.props
|
||||
|
||||
removeProvider(enqueueSnackbar, closeSnackbar)
|
||||
const onDisconnect = () => {
|
||||
dispatch(removeProvider())
|
||||
}
|
||||
|
||||
getProviderInfoBased = () => {
|
||||
const { hasError } = this.state
|
||||
const { available, loaded, provider, userAddress, network } = this.props
|
||||
|
||||
if (hasError || !loaded) {
|
||||
const getProviderInfoBased = () => {
|
||||
if (!loaded) {
|
||||
return <ProviderDisconnected />
|
||||
}
|
||||
|
||||
return <ProviderAccessible connected={available} provider={provider} network={network} userAddress={userAddress} />
|
||||
}
|
||||
|
||||
getProviderDetailsBased = () => {
|
||||
const { hasError } = this.state
|
||||
const { available, loaded, network, provider, userAddress } = this.props
|
||||
|
||||
if (hasError || !loaded) {
|
||||
const getProviderDetailsBased = () => {
|
||||
if (!loaded) {
|
||||
return <ConnectDetails />
|
||||
}
|
||||
|
||||
|
@ -76,20 +66,18 @@ class HeaderComponent extends React.PureComponent<any, any> {
|
|||
<UserDetails
|
||||
connected={available}
|
||||
network={network}
|
||||
onDisconnect={this.onDisconnect}
|
||||
openDashboard={this.getOpenDashboard()}
|
||||
onDisconnect={onDisconnect}
|
||||
openDashboard={openDashboard()}
|
||||
provider={provider}
|
||||
userAddress={userAddress}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
const info = this.getProviderInfoBased()
|
||||
const details = this.getProviderDetailsBased()
|
||||
const info = getProviderInfoBased()
|
||||
const details = getProviderDetailsBased()
|
||||
|
||||
return <Layout providerDetails={details} providerInfo={info} />
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(withSnackbar(HeaderComponent))
|
||||
export default HeaderComponent
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import { createStructuredSelector } from 'reselect'
|
||||
|
||||
import {
|
||||
availableSelector,
|
||||
loadedSelector,
|
||||
networkSelector,
|
||||
providerNameSelector,
|
||||
userAccountSelector,
|
||||
} from 'src/logic/wallets/store/selectors'
|
||||
|
||||
export default createStructuredSelector({
|
||||
provider: providerNameSelector,
|
||||
userAddress: userAccountSelector,
|
||||
network: networkSelector,
|
||||
loaded: loadedSelector,
|
||||
available: availableSelector,
|
||||
})
|
|
@ -59,7 +59,7 @@ export const ok = (): undefined => undefined
|
|||
|
||||
export const mustBeEthereumAddress = memoize(
|
||||
(address: string): ValidatorReturnType => {
|
||||
const startsWith0x = address.startsWith('0x')
|
||||
const startsWith0x = address?.startsWith('0x')
|
||||
const isAddress = getWeb3().utils.isAddress(address)
|
||||
|
||||
return startsWith0x && isAddress ? undefined : 'Address should be a valid Ethereum address or ENS name'
|
||||
|
|
|
@ -2,14 +2,14 @@ import { IconButton } from '@material-ui/core'
|
|||
import { Close as IconClose } from '@material-ui/icons'
|
||||
import * as React from 'react'
|
||||
|
||||
import { NOTIFICATIONS } from './notificationTypes'
|
||||
import { Notification, NOTIFICATIONS } from './notificationTypes'
|
||||
|
||||
import closeSnackbarAction from 'src/logic/notifications/store/actions/closeSnackbar'
|
||||
import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
|
||||
import { getAppInfoFromOrigin } from 'src/routes/safe/components/Apps/utils'
|
||||
import { store } from 'src/store'
|
||||
|
||||
const setNotificationOrigin = (notification, origin) => {
|
||||
const setNotificationOrigin = (notification: Notification, origin: string): Notification => {
|
||||
if (!origin) {
|
||||
return notification
|
||||
}
|
||||
|
@ -18,8 +18,9 @@ const setNotificationOrigin = (notification, origin) => {
|
|||
return { ...notification, message: `${appInfo.name}: ${notification.message}` }
|
||||
}
|
||||
|
||||
const getStandardTxNotificationsQueue = (origin) => {
|
||||
return {
|
||||
const getStandardTxNotificationsQueue = (
|
||||
origin: string,
|
||||
): Record<string, Record<string, Notification> | Notification> => ({
|
||||
beforeExecution: setNotificationOrigin(NOTIFICATIONS.SIGN_TX_MSG, origin),
|
||||
pendingExecution: setNotificationOrigin(NOTIFICATIONS.TX_PENDING_MSG, origin),
|
||||
afterRejection: setNotificationOrigin(NOTIFICATIONS.TX_REJECTED_MSG, origin),
|
||||
|
@ -28,8 +29,7 @@ const getStandardTxNotificationsQueue = (origin) => {
|
|||
moreConfirmationsNeeded: setNotificationOrigin(NOTIFICATIONS.TX_EXECUTED_MORE_CONFIRMATIONS_MSG, origin),
|
||||
},
|
||||
afterExecutionError: setNotificationOrigin(NOTIFICATIONS.TX_FAILED_MSG, origin),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const waitingTransactionNotificationsQueue = {
|
||||
beforeExecution: null,
|
||||
|
@ -40,7 +40,7 @@ const waitingTransactionNotificationsQueue = {
|
|||
afterExecutionError: null,
|
||||
}
|
||||
|
||||
const getConfirmationTxNotificationsQueue = (origin) => {
|
||||
const getConfirmationTxNotificationsQueue = (origin: string) => {
|
||||
return {
|
||||
beforeExecution: setNotificationOrigin(NOTIFICATIONS.SIGN_TX_MSG, origin),
|
||||
pendingExecution: setNotificationOrigin(NOTIFICATIONS.TX_CONFIRMATION_PENDING_MSG, origin),
|
||||
|
@ -53,7 +53,7 @@ const getConfirmationTxNotificationsQueue = (origin) => {
|
|||
}
|
||||
}
|
||||
|
||||
const getCancellationTxNotificationsQueue = (origin) => {
|
||||
const getCancellationTxNotificationsQueue = (origin: string) => {
|
||||
return {
|
||||
beforeExecution: setNotificationOrigin(NOTIFICATIONS.SIGN_TX_MSG, origin),
|
||||
pendingExecution: setNotificationOrigin(NOTIFICATIONS.TX_PENDING_MSG, origin),
|
||||
|
@ -199,9 +199,13 @@ export const getNotificationsFromTxType: any = (txType, origin) => {
|
|||
return notificationsQueue
|
||||
}
|
||||
|
||||
export const enhanceSnackbarForAction: any = (notification, key, onClick) => ({
|
||||
export const enhanceSnackbarForAction = (
|
||||
notification: Notification,
|
||||
key?: number | string,
|
||||
onClick?: () => void,
|
||||
): Notification => ({
|
||||
...notification,
|
||||
key,
|
||||
key: key || notification.key,
|
||||
options: {
|
||||
...notification.options,
|
||||
onClick,
|
||||
|
@ -213,14 +217,3 @@ export const enhanceSnackbarForAction: any = (notification, key, onClick) => ({
|
|||
),
|
||||
},
|
||||
})
|
||||
|
||||
export const showSnackbar: any = (notification, enqueueSnackbar, closeSnackbar) =>
|
||||
enqueueSnackbar(notification.message, {
|
||||
...notification.options,
|
||||
// eslint-disable-next-line react/display-name
|
||||
action: (key) => (
|
||||
<IconButton onClick={() => closeSnackbar(key)}>
|
||||
<IconClose />
|
||||
</IconButton>
|
||||
),
|
||||
})
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { OptionsObject } from 'notistack'
|
||||
|
||||
import { getNetwork } from 'src/config'
|
||||
import { capitalize } from 'src/utils/css'
|
||||
|
||||
|
@ -9,7 +11,50 @@ export const INFO = 'info'
|
|||
const shortDuration = 5000
|
||||
const longDuration = 10000
|
||||
|
||||
export const NOTIFICATIONS = {
|
||||
export type NotificationId = keyof typeof NOTIFICATION_IDS
|
||||
|
||||
export type Notification = {
|
||||
message: string
|
||||
options: OptionsObject
|
||||
key?: number | string
|
||||
}
|
||||
|
||||
const NOTIFICATION_IDS = {
|
||||
CONNECT_WALLET_MSG: 'CONNECT_WALLET_MSG',
|
||||
CONNECT_WALLET_READ_MODE_MSG: 'CONNECT_WALLET_READ_MODE_MSG',
|
||||
WALLET_CONNECTED_MSG: 'WALLET_CONNECTED_MSG',
|
||||
WALLET_DISCONNECTED_MSG: 'WALLET_DISCONNECTED_MSG',
|
||||
UNLOCK_WALLET_MSG: 'UNLOCK_WALLET_MSG',
|
||||
CONNECT_WALLET_ERROR_MSG: 'CONNECT_WALLET_ERROR_MSG',
|
||||
SIGN_TX_MSG: 'SIGN_TX_MSG',
|
||||
TX_PENDING_MSG: 'TX_PENDING_MSG',
|
||||
TX_REJECTED_MSG: 'TX_REJECTED_MSG',
|
||||
TX_EXECUTED_MSG: 'TX_EXECUTED_MSG',
|
||||
TX_CANCELLATION_EXECUTED_MSG: 'TX_CANCELLATION_EXECUTED_MSG',
|
||||
TX_FAILED_MSG: 'TX_FAILED_MSG',
|
||||
TX_EXECUTED_MORE_CONFIRMATIONS_MSG: 'TX_EXECUTED_MORE_CONFIRMATIONS_MSG',
|
||||
TX_WAITING_MSG: 'TX_WAITING_MSG',
|
||||
TX_INCOMING_MSG: 'TX_INCOMING_MSG',
|
||||
TX_CONFIRMATION_PENDING_MSG: 'TX_CONFIRMATION_PENDING_MSG',
|
||||
TX_CONFIRMATION_EXECUTED_MSG: 'TX_CONFIRMATION_EXECUTED_MSG',
|
||||
TX_CONFIRMATION_FAILED_MSG: 'TX_CONFIRMATION_FAILED_MSG',
|
||||
SAFE_NAME_CHANGED_MSG: 'SAFE_NAME_CHANGED_MSG',
|
||||
OWNER_NAME_CHANGE_EXECUTED_MSG: 'OWNER_NAME_CHANGE_EXECUTED_MSG',
|
||||
SIGN_SETTINGS_CHANGE_MSG: 'SIGN_SETTINGS_CHANGE_MSG',
|
||||
SETTINGS_CHANGE_PENDING_MSG: 'SETTINGS_CHANGE_PENDING_MSG',
|
||||
SETTINGS_CHANGE_REJECTED_MSG: 'SETTINGS_CHANGE_REJECTED_MSG',
|
||||
SETTINGS_CHANGE_EXECUTED_MSG: 'SETTINGS_CHANGE_EXECUTED_MSG',
|
||||
SETTINGS_CHANGE_EXECUTED_MORE_CONFIRMATIONS_MSG: 'SETTINGS_CHANGE_EXECUTED_MORE_CONFIRMATIONS_MSG',
|
||||
SETTINGS_CHANGE_FAILED_MSG: 'SETTINGS_CHANGE_FAILED_MSG',
|
||||
RINKEBY_VERSION_MSG: 'RINKEBY_VERSION_MSG',
|
||||
WRONG_NETWORK_MSG: 'WRONG_NETWORK_MSG',
|
||||
ADDRESS_BOOK_NEW_ENTRY_SUCCESS: 'ADDRESS_BOOK_NEW_ENTRY_SUCCESS',
|
||||
ADDRESS_BOOK_EDIT_ENTRY_SUCCESS: 'ADDRESS_BOOK_EDIT_ENTRY_SUCCESS',
|
||||
ADDRESS_BOOK_DELETE_ENTRY_SUCCESS: 'ADDRESS_BOOK_DELETE_ENTRY_SUCCESS',
|
||||
SAFE_NEW_VERSION_AVAILABLE: 'SAFE_NEW_VERSION_AVAILABLE',
|
||||
}
|
||||
|
||||
export const NOTIFICATIONS: Record<NotificationId, Notification> = {
|
||||
// Wallet Connection
|
||||
CONNECT_WALLET_MSG: {
|
||||
message: 'Please connect wallet to continue',
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const ENQUEUE_SNACKBAR = 'ENQUEUE_SNACKBAR'
|
||||
|
||||
const addSnackbar = createAction(ENQUEUE_SNACKBAR)
|
||||
|
||||
const enqueueSnackbar = (notification) => (dispatch) => {
|
||||
const newNotification = {
|
||||
...notification,
|
||||
key: notification.key || new Date().getTime(),
|
||||
}
|
||||
dispatch(addSnackbar(newNotification))
|
||||
}
|
||||
|
||||
export default enqueueSnackbar
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react'
|
||||
import { AnyAction } from 'redux'
|
||||
import { ThunkAction } from 'redux-thunk'
|
||||
import { createAction } from 'redux-actions'
|
||||
import { IconButton } from '@material-ui/core'
|
||||
import { Close as IconClose } from '@material-ui/icons'
|
||||
import { Notification } from 'src/logic/notifications/notificationTypes'
|
||||
import closeSnackbarAction from './closeSnackbar'
|
||||
import { Dispatch } from 'src/logic/safe/store/actions/types.d'
|
||||
import { AppReduxState } from 'src/store'
|
||||
|
||||
export const ENQUEUE_SNACKBAR = 'ENQUEUE_SNACKBAR'
|
||||
|
||||
const addSnackbar = createAction(ENQUEUE_SNACKBAR)
|
||||
|
||||
const enqueueSnackbar = (
|
||||
notification: Notification,
|
||||
key?: string | number,
|
||||
onClick?: () => void,
|
||||
): ThunkAction<string | number, AppReduxState, undefined, AnyAction> => (dispatch: Dispatch) => {
|
||||
key = notification.key || new Date().getTime() + Math.random()
|
||||
|
||||
const newNotification = {
|
||||
...notification,
|
||||
key,
|
||||
options: {
|
||||
...notification.options,
|
||||
onClick,
|
||||
// eslint-disable-next-line react/display-name
|
||||
action: (actionKey) => (
|
||||
<IconButton onClick={() => dispatch(closeSnackbarAction({ key: actionKey }))}>
|
||||
<IconClose />
|
||||
</IconButton>
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
dispatch(addSnackbar(newNotification))
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
export default enqueueSnackbar
|
|
@ -1,13 +1,12 @@
|
|||
import { push } from 'connected-react-router'
|
||||
import { List, Map } from 'immutable'
|
||||
import { WithSnackbarProps } from 'notistack'
|
||||
import { batch } from 'react-redux'
|
||||
import semverSatisfies from 'semver/functions/satisfies'
|
||||
import { ThunkAction } from 'redux-thunk'
|
||||
|
||||
import { onboardUser } from 'src/components/ConnectButton'
|
||||
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
||||
import { getNotificationsFromTxType, showSnackbar } from 'src/logic/notifications'
|
||||
import { getNotificationsFromTxType } from 'src/logic/notifications'
|
||||
import {
|
||||
CALL,
|
||||
getApprovalTransaction,
|
||||
|
@ -22,6 +21,8 @@ import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
|||
import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
|
||||
import { providerSelector } from 'src/logic/wallets/store/selectors'
|
||||
import { SAFELIST_ADDRESS } from 'src/routes/routes'
|
||||
import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar'
|
||||
import closeSnackbarAction from 'src/logic/notifications/store/actions/closeSnackbar'
|
||||
import { addOrUpdateCancellationTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateCancellationTransactions'
|
||||
import { addOrUpdateTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateTransactions'
|
||||
import { removeCancellationTransaction } from 'src/logic/safe/store/actions/transactions/removeCancellationTransaction'
|
||||
|
@ -95,7 +96,7 @@ export const storeTx = async (
|
|||
}
|
||||
}
|
||||
|
||||
interface CreateTransaction extends WithSnackbarProps {
|
||||
interface CreateTransactionArgs {
|
||||
navigateToTransactionsTab?: boolean
|
||||
notifiedTransaction: string
|
||||
operation?: number
|
||||
|
@ -108,23 +109,22 @@ interface CreateTransaction extends WithSnackbarProps {
|
|||
}
|
||||
|
||||
type CreateTransactionAction = ThunkAction<Promise<void>, AppReduxState, undefined, AnyAction>
|
||||
type ConfirmEventHandler = (safeTxHash: string) => void
|
||||
|
||||
const createTransaction = ({
|
||||
const createTransaction = (
|
||||
{
|
||||
safeAddress,
|
||||
to,
|
||||
valueInWei,
|
||||
txData = EMPTY_DATA,
|
||||
notifiedTransaction,
|
||||
enqueueSnackbar,
|
||||
closeSnackbar,
|
||||
txNonce,
|
||||
operation = CALL,
|
||||
navigateToTransactionsTab = true,
|
||||
origin = null,
|
||||
}: CreateTransaction): CreateTransactionAction => async (
|
||||
dispatch: Dispatch,
|
||||
getState: () => AppReduxState,
|
||||
): Promise<void> => {
|
||||
}: CreateTransactionArgs,
|
||||
onUserConfirm?: ConfirmEventHandler,
|
||||
): CreateTransactionAction => async (dispatch: Dispatch, getState: () => AppReduxState): Promise<void> => {
|
||||
const state = getState()
|
||||
|
||||
if (navigateToTransactionsTab) {
|
||||
|
@ -149,7 +149,7 @@ const createTransaction = ({
|
|||
)}000000000000000000000000000000000000000000000000000000000000000001`
|
||||
|
||||
const notificationsQueue = getNotificationsFromTxType(notifiedTransaction, origin)
|
||||
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
|
||||
const beforeExecutionKey = dispatch(enqueueSnackbar(notificationsQueue.beforeExecution))
|
||||
|
||||
let pendingExecutionKey
|
||||
|
||||
|
@ -179,17 +179,20 @@ const createTransaction = ({
|
|||
const signature = await tryOffchainSigning({ ...txArgs, safeAddress }, hardwareWallet)
|
||||
|
||||
if (signature) {
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
dispatch(closeSnackbarAction({ key: beforeExecutionKey }))
|
||||
|
||||
await saveTxToHistory({ ...txArgs, signature, origin })
|
||||
showSnackbar(notificationsQueue.afterExecution.moreConfirmationsNeeded, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(notificationsQueue.afterExecution.moreConfirmationsNeeded))
|
||||
|
||||
dispatch(fetchTransactions(safeAddress))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const tx = isExecution ? await getExecutionTransaction(txArgs) : await getApprovalTransaction(txArgs)
|
||||
const safeTxHash = generateSafeTxHash(safeAddress, txArgs)
|
||||
const tx = isExecution
|
||||
? await getExecutionTransaction(txArgs)
|
||||
: await getApprovalTransaction(safeInstance, safeTxHash)
|
||||
const sendParams: PayableTx = { from, value: 0 }
|
||||
|
||||
// if not set owner management tests will fail on ganache
|
||||
|
@ -201,7 +204,7 @@ const createTransaction = ({
|
|||
...txArgs,
|
||||
confirmations: [], // this is used to determine if a tx is pending or not. See `calculateTransactionStatus` helper
|
||||
value: txArgs.valueInWei,
|
||||
safeTxHash: generateSafeTxHash(safeAddress, txArgs),
|
||||
safeTxHash,
|
||||
submissionDate: new Date().toISOString(),
|
||||
}
|
||||
const mockedTx = await mockTransaction(txToMock, safeAddress, state)
|
||||
|
@ -209,11 +212,12 @@ const createTransaction = ({
|
|||
await tx
|
||||
.send(sendParams)
|
||||
.once('transactionHash', async (hash) => {
|
||||
onUserConfirm(safeTxHash)
|
||||
try {
|
||||
txHash = hash
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
dispatch(closeSnackbarAction({ key: beforeExecutionKey }))
|
||||
|
||||
pendingExecutionKey = showSnackbar(notificationsQueue.pendingExecution, enqueueSnackbar, closeSnackbar)
|
||||
pendingExecutionKey = dispatch(enqueueSnackbar(notificationsQueue.pendingExecution))
|
||||
|
||||
await Promise.all([
|
||||
saveTxToHistory({ ...txArgs, txHash, origin }),
|
||||
|
@ -233,21 +237,21 @@ const createTransaction = ({
|
|||
}
|
||||
})
|
||||
.on('error', (error) => {
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
dispatch(closeSnackbarAction({ key: pendingExecutionKey }))
|
||||
removeTxFromStore(mockedTx, safeAddress, dispatch, state)
|
||||
console.error('Tx error: ', error)
|
||||
})
|
||||
.then(async (receipt) => {
|
||||
if (pendingExecutionKey) {
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
dispatch(closeSnackbarAction({ key: pendingExecutionKey }))
|
||||
}
|
||||
|
||||
showSnackbar(
|
||||
dispatch(
|
||||
enqueueSnackbar(
|
||||
isExecution
|
||||
? notificationsQueue.afterExecution.noMoreConfirmationsNeeded
|
||||
: notificationsQueue.afterExecution.moreConfirmationsNeeded,
|
||||
enqueueSnackbar,
|
||||
closeSnackbar,
|
||||
),
|
||||
)
|
||||
|
||||
const toStoreTx = isExecution
|
||||
|
@ -283,13 +287,13 @@ const createTransaction = ({
|
|||
: notificationsQueue.afterExecutionError.message
|
||||
|
||||
console.error(`Error creating the TX: `, err)
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
dispatch(closeSnackbarAction({ key: beforeExecutionKey }))
|
||||
|
||||
if (pendingExecutionKey) {
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
dispatch(closeSnackbarAction({ key: pendingExecutionKey }))
|
||||
}
|
||||
|
||||
showSnackbar(errorMsg, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(errorMsg))
|
||||
|
||||
const executeDataUsedSignatures = safeInstance.methods
|
||||
.execTransaction(to, valueInWei, txData, operation, 0, 0, 0, ZERO_ADDRESS, ZERO_ADDRESS, sigs)
|
||||
|
|
|
@ -2,12 +2,14 @@ import { fromJS } from 'immutable'
|
|||
import semverSatisfies from 'semver/functions/satisfies'
|
||||
|
||||
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
||||
import { getNotificationsFromTxType, showSnackbar } from 'src/logic/notifications'
|
||||
import { getNotificationsFromTxType } from 'src/logic/notifications'
|
||||
import { generateSignaturesFromTxConfirmations } from 'src/logic/safe/safeTxSigner'
|
||||
import { getApprovalTransaction, getExecutionTransaction, saveTxToHistory } from 'src/logic/safe/transactions'
|
||||
import { SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES, tryOffchainSigning } from 'src/logic/safe/transactions/offchainSigner'
|
||||
import { getCurrentSafeVersion } from 'src/logic/safe/utils/safeVersion'
|
||||
import { providerSelector } from 'src/logic/wallets/store/selectors'
|
||||
import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar'
|
||||
import closeSnackbarAction from 'src/logic/notifications/store/actions/closeSnackbar'
|
||||
import fetchSafe from 'src/logic/safe/store/actions/fetchSafe'
|
||||
import fetchTransactions from 'src/logic/safe/store/actions/transactions/fetchTransactions'
|
||||
import {
|
||||
|
@ -22,15 +24,10 @@ import { makeConfirmation } from '../models/confirmation'
|
|||
import { storeTx } from './createTransaction'
|
||||
import { TransactionStatus } from '../models/types/transaction'
|
||||
|
||||
const processTransaction = ({
|
||||
approveAndExecute,
|
||||
closeSnackbar,
|
||||
enqueueSnackbar,
|
||||
notifiedTransaction,
|
||||
safeAddress,
|
||||
tx,
|
||||
userAddress,
|
||||
}) => async (dispatch, getState) => {
|
||||
const processTransaction = ({ approveAndExecute, notifiedTransaction, safeAddress, tx, userAddress }) => async (
|
||||
dispatch,
|
||||
getState,
|
||||
) => {
|
||||
const state = getState()
|
||||
|
||||
const { account: from, hardwareWallet, smartContractWallet } = providerSelector(state)
|
||||
|
@ -51,7 +48,7 @@ const processTransaction = ({
|
|||
}
|
||||
|
||||
const notificationsQueue = getNotificationsFromTxType(notifiedTransaction, tx.origin)
|
||||
const beforeExecutionKey = showSnackbar(notificationsQueue.beforeExecution, enqueueSnackbar, closeSnackbar)
|
||||
const beforeExecutionKey = dispatch(enqueueSnackbar(notificationsQueue.beforeExecution))
|
||||
let pendingExecutionKey
|
||||
|
||||
let txHash
|
||||
|
@ -85,17 +82,19 @@ const processTransaction = ({
|
|||
const signature = await tryOffchainSigning({ ...txArgs, safeAddress }, hardwareWallet)
|
||||
|
||||
if (signature) {
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
dispatch(closeSnackbarAction(beforeExecutionKey))
|
||||
|
||||
await saveTxToHistory({ ...txArgs, signature })
|
||||
showSnackbar(notificationsQueue.afterExecution.moreConfirmationsNeeded, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(notificationsQueue.afterExecution.moreConfirmationsNeeded))
|
||||
|
||||
dispatch(fetchTransactions(safeAddress))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
transaction = isExecution ? await getExecutionTransaction(txArgs) : await getApprovalTransaction(txArgs)
|
||||
transaction = isExecution
|
||||
? await getExecutionTransaction(txArgs)
|
||||
: await getApprovalTransaction(safeInstance, tx.safeTxHash)
|
||||
|
||||
const sendParams: any = { from, value: 0 }
|
||||
|
||||
|
@ -114,11 +113,11 @@ const processTransaction = ({
|
|||
|
||||
await transaction
|
||||
.send(sendParams)
|
||||
.once('transactionHash', async (hash) => {
|
||||
.once('transactionHash', async (hash: string) => {
|
||||
txHash = hash
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
dispatch(closeSnackbarAction(beforeExecutionKey))
|
||||
|
||||
pendingExecutionKey = showSnackbar(notificationsQueue.pendingExecution, enqueueSnackbar, closeSnackbar)
|
||||
pendingExecutionKey = dispatch(enqueueSnackbar(notificationsQueue.pendingExecution))
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
|
@ -135,27 +134,27 @@ const processTransaction = ({
|
|||
])
|
||||
dispatch(fetchTransactions(safeAddress))
|
||||
} catch (e) {
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
dispatch(closeSnackbarAction(pendingExecutionKey))
|
||||
await storeTx(tx, safeAddress, dispatch, state)
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
.on('error', (error) => {
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
dispatch(closeSnackbarAction(pendingExecutionKey))
|
||||
storeTx(tx, safeAddress, dispatch, state)
|
||||
console.error('Processing transaction error: ', error)
|
||||
})
|
||||
.then(async (receipt) => {
|
||||
if (pendingExecutionKey) {
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
dispatch(closeSnackbarAction(pendingExecutionKey))
|
||||
}
|
||||
|
||||
showSnackbar(
|
||||
dispatch(
|
||||
enqueueSnackbar(
|
||||
isExecution
|
||||
? notificationsQueue.afterExecution.noMoreConfirmationsNeeded
|
||||
: notificationsQueue.afterExecution.moreConfirmationsNeeded,
|
||||
enqueueSnackbar,
|
||||
closeSnackbar,
|
||||
),
|
||||
)
|
||||
|
||||
const toStoreTx = isExecution
|
||||
|
@ -207,13 +206,13 @@ const processTransaction = ({
|
|||
console.error(err)
|
||||
|
||||
if (txHash !== undefined) {
|
||||
closeSnackbar(beforeExecutionKey)
|
||||
dispatch(closeSnackbarAction(beforeExecutionKey))
|
||||
|
||||
if (pendingExecutionKey) {
|
||||
closeSnackbar(pendingExecutionKey)
|
||||
dispatch(closeSnackbarAction(pendingExecutionKey))
|
||||
}
|
||||
|
||||
showSnackbar(errorMsg, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(errorMsg))
|
||||
|
||||
const executeData = safeInstance.methods.approveHash(txHash).encodeABI()
|
||||
const errMsg = await getErrorMessage(safeInstance.options.address, 0, executeData, from)
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { Transaction } from '@gnosis.pm/safe-apps-sdk'
|
||||
import { AbiItem } from 'web3-utils'
|
||||
import { MultiSend } from 'src/types/contracts/MultiSend.d'
|
||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
import { MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts'
|
||||
|
||||
const multiSendAbi: AbiItem[] = [
|
||||
{
|
||||
type: 'function',
|
||||
name: 'multiSend',
|
||||
constant: false,
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [{ type: 'bytes', name: 'transactions' }],
|
||||
outputs: [],
|
||||
},
|
||||
]
|
||||
|
||||
export const encodeMultiSendCall = (txs: Transaction[]): string => {
|
||||
const web3 = getWeb3()
|
||||
const multiSend = (new web3.eth.Contract(multiSendAbi, MULTI_SEND_ADDRESS) as unknown) as MultiSend
|
||||
|
||||
const joinedTxs = txs
|
||||
.map((tx) =>
|
||||
[
|
||||
web3.eth.abi.encodeParameter('uint8', 0).slice(-2),
|
||||
web3.eth.abi.encodeParameter('address', tx.to).slice(-40),
|
||||
web3.eth.abi.encodeParameter('uint256', tx.value).slice(-64),
|
||||
web3.eth.abi.encodeParameter('uint256', web3.utils.hexToBytes(tx.data).length).slice(-64),
|
||||
tx.data.replace(/^0x/, ''),
|
||||
].join(''),
|
||||
)
|
||||
.join('')
|
||||
|
||||
const encodedMultiSendCallData = multiSend.methods.multiSend(`0x${joinedTxs}`).encodeABI()
|
||||
|
||||
return encodedMultiSendCallData
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
import { NonPayableTransactionObject } from 'src/types/contracts/types.d'
|
||||
import { TxArgs } from 'src/logic/safe/store/models/types/transaction'
|
||||
import { GnosisSafe } from 'src/types/contracts/GnosisSafe'
|
||||
|
||||
export const CALL = 0
|
||||
export const DELEGATE_CALL = 1
|
||||
export const TX_TYPE_EXECUTION = 'execution'
|
||||
export const TX_TYPE_CONFIRMATION = 'confirmation'
|
||||
|
||||
export const getApprovalTransaction = async ({
|
||||
export const getTransactionHash = async ({
|
||||
baseGas,
|
||||
data,
|
||||
gasPrice,
|
||||
|
@ -19,13 +20,20 @@ export const getApprovalTransaction = async ({
|
|||
sender,
|
||||
to,
|
||||
valueInWei,
|
||||
}: TxArgs): Promise<NonPayableTransactionObject<void>> => {
|
||||
}: TxArgs): Promise<string> => {
|
||||
const txHash = await safeInstance.methods
|
||||
.getTransactionHash(to, valueInWei, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, nonce)
|
||||
.call({
|
||||
from: sender,
|
||||
})
|
||||
|
||||
return txHash
|
||||
}
|
||||
|
||||
export const getApprovalTransaction = async (
|
||||
safeInstance: GnosisSafe,
|
||||
txHash: string,
|
||||
): Promise<NonPayableTransactionObject<void>> => {
|
||||
try {
|
||||
return safeInstance.methods.approveHash(txHash)
|
||||
} catch (err) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { Dispatch } from 'src/logic/safe/store/actions/types.d'
|
||||
import { createAction } from 'redux-actions'
|
||||
|
||||
import { onboard } from 'src/components/ConnectButton'
|
||||
|
@ -9,9 +10,10 @@ export const REMOVE_PROVIDER = 'REMOVE_PROVIDER'
|
|||
|
||||
const removeProvider = createAction(REMOVE_PROVIDER)
|
||||
|
||||
export default () => (dispatch) => {
|
||||
export default () => (dispatch: Dispatch): void => {
|
||||
onboard.walletReset()
|
||||
resetWeb3()
|
||||
|
||||
dispatch(removeProvider())
|
||||
dispatch(
|
||||
enqueueSnackbar(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Icon, ModalFooterConfirmation, Text, Title } from '@gnosis.pm/safe-react-components'
|
||||
import { Transaction } from '@gnosis.pm/safe-apps-sdk'
|
||||
import React from 'react'
|
||||
import { Icon, Text, Title, GenericModal, ModalFooterConfirmation } from '@gnosis.pm/safe-react-components'
|
||||
import { Transaction } from '@gnosis.pm/safe-apps-sdk'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AddressInfo from 'src/components/AddressInfo'
|
||||
|
@ -13,8 +13,26 @@ import Bold from 'src/components/layout/Bold'
|
|||
import Heading from 'src/components/layout/Heading'
|
||||
import Img from 'src/components/layout/Img'
|
||||
import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers'
|
||||
import { OpenModalArgs } from 'src/routes/safe/components/Layout/interfaces'
|
||||
import { SafeApp } from 'src/routes/safe/components/Apps/types.d'
|
||||
import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import createTransaction from 'src/logic/safe/store/actions/createTransaction'
|
||||
import { MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts'
|
||||
import { DELEGATE_CALL, TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
|
||||
import { encodeMultiSendCall } from 'src/logic/safe/transactions/multisend'
|
||||
|
||||
const isTxValid = (t: Transaction): boolean => {
|
||||
if (!['string', 'number'].includes(typeof t.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof t.value === 'string' && !/^(0x)?[0-9a-f]+$/i.test(t.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAddressValid = mustBeEthereumAddress(t.to) === undefined
|
||||
return isAddressValid && t.data && typeof t.data === 'string'
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin-bottom: 15px;
|
||||
|
@ -44,33 +62,61 @@ const StyledTextBox = styled(TextBox)`
|
|||
max-width: 444px;
|
||||
`
|
||||
|
||||
const isTxValid = (t: Transaction): boolean => {
|
||||
if (!['string', 'number'].includes(typeof t.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof t.value === 'string' && !/^(0x)?[0-9a-f]+$/i.test(t.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const isAddressValid = mustBeEthereumAddress(t.to) === undefined
|
||||
return isAddressValid && t.data && typeof t.data === 'string'
|
||||
type OwnProps = {
|
||||
isOpen: boolean
|
||||
app: SafeApp
|
||||
txs: Transaction[]
|
||||
safeAddress: string
|
||||
safeName: string
|
||||
ethBalance: string
|
||||
onCancel: () => void
|
||||
onUserConfirm: (safeTxHash: string) => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const confirmTransactions = (
|
||||
safeAddress: string,
|
||||
safeName: string,
|
||||
ethBalance: string,
|
||||
nameApp: string,
|
||||
iconApp: string,
|
||||
txs: Transaction[],
|
||||
openModal: (modalInfo: OpenModalArgs) => void,
|
||||
closeModal: () => void,
|
||||
onConfirm: () => void,
|
||||
): void => {
|
||||
const areTxsMalformed = txs.some((t) => !isTxValid(t))
|
||||
const ConfirmTransactionModal = ({
|
||||
isOpen,
|
||||
app,
|
||||
txs,
|
||||
safeAddress,
|
||||
ethBalance,
|
||||
safeName,
|
||||
onCancel,
|
||||
onUserConfirm,
|
||||
onClose,
|
||||
}: OwnProps): React.ReactElement => {
|
||||
const dispatch = useDispatch()
|
||||
if (!isOpen) {
|
||||
return null
|
||||
}
|
||||
|
||||
const title = <ModalTitle iconUrl={iconApp} title={nameApp} />
|
||||
const handleUserConfirmation = (safeTxHash: string): void => {
|
||||
onUserConfirm(safeTxHash)
|
||||
onClose()
|
||||
}
|
||||
|
||||
const confirmTransactions = async () => {
|
||||
const txData = encodeMultiSendCall(txs)
|
||||
|
||||
await dispatch(
|
||||
createTransaction(
|
||||
{
|
||||
safeAddress,
|
||||
to: MULTI_SEND_ADDRESS,
|
||||
valueInWei: '0',
|
||||
txData,
|
||||
operation: DELEGATE_CALL,
|
||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||
origin: app.id,
|
||||
navigateToTransactionsTab: false,
|
||||
},
|
||||
handleUserConfirmation,
|
||||
),
|
||||
)
|
||||
onClose()
|
||||
}
|
||||
|
||||
const areTxsMalformed = txs.some((t) => !isTxValid(t))
|
||||
|
||||
const body = areTxsMalformed ? (
|
||||
<>
|
||||
|
@ -111,22 +157,22 @@ const confirmTransactions = (
|
|||
</>
|
||||
)
|
||||
|
||||
const footer = (
|
||||
return (
|
||||
<GenericModal
|
||||
title={<ModalTitle title={app.name} iconUrl={app.iconUrl} />}
|
||||
body={body}
|
||||
footer={
|
||||
<ModalFooterConfirmation
|
||||
cancelText="Cancel"
|
||||
handleCancel={closeModal}
|
||||
handleOk={onConfirm}
|
||||
handleCancel={onCancel}
|
||||
handleOk={confirmTransactions}
|
||||
okDisabled={areTxsMalformed}
|
||||
okText="Submit"
|
||||
/>
|
||||
}
|
||||
onClose={onClose}
|
||||
/>
|
||||
)
|
||||
|
||||
openModal({
|
||||
title,
|
||||
body,
|
||||
footer,
|
||||
onClose: closeModal,
|
||||
})
|
||||
}
|
||||
|
||||
export default confirmTransactions
|
||||
export default ConfirmTransactionModal
|
|
@ -1,15 +1,16 @@
|
|||
import { useSnackbar } from 'notistack'
|
||||
import {
|
||||
InterfaceMessages,
|
||||
InterfaceMessageIds,
|
||||
InterfaceMessageToPayload,
|
||||
SDKMessages,
|
||||
SDKMessageIds,
|
||||
SDKMessageToPayload,
|
||||
SDK_MESSAGES,
|
||||
INTERFACE_MESSAGES,
|
||||
RequestId,
|
||||
Transaction,
|
||||
} from '@gnosis.pm/safe-apps-sdk'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useEffect, useCallback, MutableRefObject } from 'react'
|
||||
import { OpenModalArgs } from 'src/routes/safe/components/Layout/interfaces'
|
||||
import {
|
||||
safeEthBalanceSelector,
|
||||
safeNameSelector,
|
||||
|
@ -18,23 +19,30 @@ import {
|
|||
import { networkSelector } from 'src/logic/wallets/store/selectors'
|
||||
import { SafeApp } from 'src/routes/safe/components/Apps/types'
|
||||
|
||||
import sendTransactions from '../sendTransactions'
|
||||
import confirmTransactions from '../confirmTransactions'
|
||||
type InterfaceMessageProps<T extends InterfaceMessageIds> = {
|
||||
messageId: T
|
||||
data: InterfaceMessageToPayload[T]
|
||||
}
|
||||
|
||||
type ReturnType = {
|
||||
sendMessageToIframe: <T extends keyof InterfaceMessages>(messageId: T, data: InterfaceMessageToPayload[T]) => void
|
||||
sendMessageToIframe: <T extends InterfaceMessageIds>(message: InterfaceMessageProps<T>, requestId?: RequestId) => void
|
||||
}
|
||||
|
||||
interface CustomMessageEvent extends MessageEvent {
|
||||
data: {
|
||||
messageId: keyof SDKMessages
|
||||
data: SDKMessageToPayload[keyof SDKMessages]
|
||||
requestId: RequestId
|
||||
messageId: SDKMessageIds
|
||||
data: SDKMessageToPayload[SDKMessageIds]
|
||||
}
|
||||
}
|
||||
|
||||
interface InterfaceMessageRequest extends InterfaceMessageProps<InterfaceMessageIds> {
|
||||
requestId: number | string
|
||||
}
|
||||
|
||||
const useIframeMessageHandler = (
|
||||
selectedApp: SafeApp | undefined,
|
||||
openModal: (modal: OpenModalArgs) => void,
|
||||
openConfirmationModal: (txs: Transaction[], requestId: RequestId) => void,
|
||||
closeModal: () => void,
|
||||
iframeRef: MutableRefObject<HTMLIFrameElement>,
|
||||
): ReturnType => {
|
||||
|
@ -46,9 +54,14 @@ const useIframeMessageHandler = (
|
|||
const dispatch = useDispatch()
|
||||
|
||||
const sendMessageToIframe = useCallback(
|
||||
function <T extends keyof InterfaceMessages>(messageId: T, data: InterfaceMessageToPayload[T]) {
|
||||
function <T extends InterfaceMessageIds>(message: InterfaceMessageProps<T>, requestId?: RequestId) {
|
||||
const requestWithMessage = {
|
||||
...message,
|
||||
requestId: requestId || Math.trunc(window.performance.now()),
|
||||
}
|
||||
|
||||
if (iframeRef?.current && selectedApp) {
|
||||
iframeRef.current.contentWindow.postMessage({ messageId, data }, selectedApp.url)
|
||||
iframeRef.current.contentWindow.postMessage(requestWithMessage, selectedApp.url)
|
||||
}
|
||||
},
|
||||
[iframeRef, selectedApp],
|
||||
|
@ -60,32 +73,25 @@ const useIframeMessageHandler = (
|
|||
console.error('ThirdPartyApp: A message was received without message id.')
|
||||
return
|
||||
}
|
||||
const { requestId } = msg.data
|
||||
|
||||
switch (msg.data.messageId) {
|
||||
case SDK_MESSAGES.SEND_TRANSACTIONS: {
|
||||
const onConfirm = async () => {
|
||||
closeModal()
|
||||
await sendTransactions(dispatch, safeAddress, msg.data.data, enqueueSnackbar, closeSnackbar, selectedApp.id)
|
||||
}
|
||||
confirmTransactions(
|
||||
safeAddress,
|
||||
safeName,
|
||||
ethBalance,
|
||||
selectedApp.name,
|
||||
selectedApp.iconUrl,
|
||||
msg.data.data,
|
||||
openModal,
|
||||
closeModal,
|
||||
onConfirm,
|
||||
)
|
||||
openConfirmationModal(msg.data.data, requestId)
|
||||
break
|
||||
}
|
||||
|
||||
case SDK_MESSAGES.SAFE_APP_SDK_INITIALIZED: {
|
||||
sendMessageToIframe(INTERFACE_MESSAGES.ON_SAFE_INFO, {
|
||||
const message = {
|
||||
messageId: INTERFACE_MESSAGES.ON_SAFE_INFO,
|
||||
data: {
|
||||
safeAddress,
|
||||
network: network,
|
||||
ethBalance,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
sendMessageToIframe(message)
|
||||
break
|
||||
}
|
||||
default: {
|
||||
|
@ -116,7 +122,7 @@ const useIframeMessageHandler = (
|
|||
enqueueSnackbar,
|
||||
ethBalance,
|
||||
network,
|
||||
openModal,
|
||||
openConfirmationModal,
|
||||
safeAddress,
|
||||
safeName,
|
||||
selectedApp,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback, useEffect, useRef, useState, useMemo } from 'react'
|
||||
import { Networks, INTERFACE_MESSAGES } from '@gnosis.pm/safe-apps-sdk'
|
||||
import { INTERFACE_MESSAGES, Transaction, RequestId } from '@gnosis.pm/safe-apps-sdk'
|
||||
import { Card, IconText, Loader, Menu, Title } from '@gnosis.pm/safe-react-components'
|
||||
import { useSelector } from 'react-redux'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
@ -7,14 +7,18 @@ import styled, { css } from 'styled-components'
|
|||
import ManageApps from './components/ManageApps'
|
||||
import AppFrame from './components/AppFrame'
|
||||
import { useAppList } from './hooks/useAppList'
|
||||
import { OpenModalArgs } from 'src/routes/safe/components/Layout/interfaces'
|
||||
|
||||
import LCL from 'src/components/ListContentLayout'
|
||||
import { networkSelector } from 'src/logic/wallets/store/selectors'
|
||||
import { grantedSelector } from 'src/routes/safe/container/selector'
|
||||
import { safeEthBalanceSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
||||
import {
|
||||
safeEthBalanceSelector,
|
||||
safeParamAddressFromStateSelector,
|
||||
safeNameSelector,
|
||||
} from 'src/logic/safe/store/selectors'
|
||||
import { isSameURL } from 'src/utils/url'
|
||||
import { useIframeMessageHandler } from './hooks/useIframeMessageHandler'
|
||||
import ConfirmTransactionModal from './components/ConfirmTransactionModal'
|
||||
|
||||
const centerCSS = css`
|
||||
display: flex;
|
||||
|
@ -38,26 +42,62 @@ const CenteredMT = styled.div`
|
|||
margin-top: 5px;
|
||||
`
|
||||
|
||||
type AppsProps = {
|
||||
closeModal: () => void
|
||||
openModal: (modal: OpenModalArgs) => void
|
||||
type ConfirmTransactionModalState = {
|
||||
isOpen: boolean
|
||||
txs: Transaction[]
|
||||
requestId: RequestId | undefined
|
||||
}
|
||||
|
||||
const Apps = ({ closeModal, openModal }: AppsProps): React.ReactElement => {
|
||||
const INITIAL_CONFIRM_TX_MODAL_STATE: ConfirmTransactionModalState = {
|
||||
isOpen: false,
|
||||
txs: [],
|
||||
requestId: undefined,
|
||||
}
|
||||
|
||||
const Apps = (): React.ReactElement => {
|
||||
const { appList, loadingAppList, onAppToggle, onAppAdded, onAppRemoved } = useAppList()
|
||||
|
||||
const [appIsLoading, setAppIsLoading] = useState<boolean>(true)
|
||||
const [selectedAppId, setSelectedAppId] = useState<string>()
|
||||
const [confirmTransactionModal, setConfirmTransactionModal] = useState<ConfirmTransactionModalState>(
|
||||
INITIAL_CONFIRM_TX_MODAL_STATE,
|
||||
)
|
||||
const iframeRef = useRef<HTMLIFrameElement>()
|
||||
|
||||
const granted = useSelector(grantedSelector)
|
||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||
const safeName = useSelector(safeNameSelector)
|
||||
const network = useSelector(networkSelector)
|
||||
const ethBalance = useSelector(safeEthBalanceSelector)
|
||||
|
||||
const openConfirmationModal = useCallback(
|
||||
(txs: Transaction[], requestId: RequestId) =>
|
||||
setConfirmTransactionModal({
|
||||
isOpen: true,
|
||||
txs,
|
||||
requestId,
|
||||
}),
|
||||
[setConfirmTransactionModal],
|
||||
)
|
||||
const closeConfirmationModal = useCallback(() => setConfirmTransactionModal(INITIAL_CONFIRM_TX_MODAL_STATE), [
|
||||
setConfirmTransactionModal,
|
||||
])
|
||||
|
||||
const selectedApp = useMemo(() => appList.find((app) => app.id === selectedAppId), [appList, selectedAppId])
|
||||
const enabledApps = useMemo(() => appList.filter((a) => !a.disabled), [appList])
|
||||
const { sendMessageToIframe } = useIframeMessageHandler(selectedApp, openModal, closeModal, iframeRef)
|
||||
const { sendMessageToIframe } = useIframeMessageHandler(
|
||||
selectedApp,
|
||||
openConfirmationModal,
|
||||
closeConfirmationModal,
|
||||
iframeRef,
|
||||
)
|
||||
|
||||
const onUserTxConfirm = (safeTxHash: string) => {
|
||||
sendMessageToIframe(
|
||||
{ messageId: INTERFACE_MESSAGES.TRANSACTION_CONFIRMED, data: { safeTxHash } },
|
||||
confirmTransactionModal.requestId,
|
||||
)
|
||||
}
|
||||
|
||||
const onSelectApp = useCallback(
|
||||
(appId) => {
|
||||
|
@ -93,10 +133,13 @@ const Apps = ({ closeModal, openModal }: AppsProps): React.ReactElement => {
|
|||
}
|
||||
|
||||
setAppIsLoading(false)
|
||||
sendMessageToIframe(INTERFACE_MESSAGES.ON_SAFE_INFO, {
|
||||
sendMessageToIframe({
|
||||
messageId: INTERFACE_MESSAGES.ON_SAFE_INFO,
|
||||
data: {
|
||||
safeAddress,
|
||||
network: network as Networks,
|
||||
network,
|
||||
ethBalance,
|
||||
},
|
||||
})
|
||||
}, [ethBalance, network, safeAddress, selectedApp, sendMessageToIframe])
|
||||
|
||||
|
@ -144,6 +187,17 @@ const Apps = ({ closeModal, openModal }: AppsProps): React.ReactElement => {
|
|||
textSize="sm"
|
||||
/>
|
||||
</CenteredMT>
|
||||
<ConfirmTransactionModal
|
||||
isOpen={confirmTransactionModal.isOpen}
|
||||
app={selectedApp}
|
||||
safeAddress={safeAddress}
|
||||
ethBalance={ethBalance}
|
||||
safeName={safeName}
|
||||
txs={confirmTransactionModal.txs}
|
||||
onCancel={closeConfirmationModal}
|
||||
onClose={closeConfirmationModal}
|
||||
onUserConfirm={onUserTxConfirm}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
import { DELEGATE_CALL } from 'src/logic/safe/transactions/send'
|
||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
import createTransaction from 'src/logic/safe/store/actions/createTransaction'
|
||||
import { MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts'
|
||||
|
||||
const multiSendAbi = [
|
||||
{
|
||||
type: 'function',
|
||||
name: 'multiSend',
|
||||
constant: false,
|
||||
payable: false,
|
||||
stateMutability: 'nonpayable',
|
||||
inputs: [{ type: 'bytes', name: 'transactions' }],
|
||||
outputs: [],
|
||||
},
|
||||
]
|
||||
|
||||
const sendTransactions = (dispatch, safeAddress, txs, enqueueSnackbar, closeSnackbar, origin) => {
|
||||
const web3 = getWeb3()
|
||||
const multiSend: any = new web3.eth.Contract(multiSendAbi as any, MULTI_SEND_ADDRESS)
|
||||
|
||||
const joinedTxs = txs
|
||||
.map((tx) =>
|
||||
[
|
||||
web3.eth.abi.encodeParameter('uint8', 0).slice(-2),
|
||||
web3.eth.abi.encodeParameter('address', tx.to).slice(-40),
|
||||
web3.eth.abi.encodeParameter('uint256', tx.value).slice(-64),
|
||||
web3.eth.abi.encodeParameter('uint256', web3.utils.hexToBytes(tx.data).length).slice(-64),
|
||||
tx.data.replace(/^0x/, ''),
|
||||
].join(''),
|
||||
)
|
||||
.join('')
|
||||
|
||||
const encodeMultiSendCallData = multiSend.methods.multiSend(`0x${joinedTxs}`).encodeABI()
|
||||
|
||||
return dispatch(
|
||||
createTransaction({
|
||||
safeAddress,
|
||||
to: MULTI_SEND_ADDRESS,
|
||||
valueInWei: '0',
|
||||
txData: encodeMultiSendCallData,
|
||||
notifiedTransaction: 'STANDARD_TX',
|
||||
enqueueSnackbar,
|
||||
closeSnackbar,
|
||||
operation: DELEGATE_CALL,
|
||||
// navigateToTransactionsTab: false,
|
||||
origin,
|
||||
} as any),
|
||||
)
|
||||
}
|
||||
export default sendTransactions
|
|
@ -1,13 +1,11 @@
|
|||
import { GenericModal } from '@gnosis.pm/safe-react-components'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'
|
||||
|
||||
import Receive from '../Balances/Receive'
|
||||
|
||||
import { styles } from './style'
|
||||
import { ModalState, OpenModalArgs } from './interfaces'
|
||||
|
||||
import Modal from 'src/components/Modal'
|
||||
import NoSafe from 'src/components/NoSafe'
|
||||
|
@ -45,36 +43,12 @@ const Layout = (props: Props): React.ReactElement => {
|
|||
const { hideSendFunds, onHide, onShow, sendFunds, showReceive, showSendFunds } = props
|
||||
const match = useRouteMatch()
|
||||
|
||||
const [modal, setModal] = useState<ModalState>({
|
||||
isOpen: false,
|
||||
title: '',
|
||||
body: null,
|
||||
footer: null,
|
||||
onClose: null,
|
||||
})
|
||||
|
||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||
const provider = useSelector(providerNameSelector)
|
||||
if (!safeAddress) {
|
||||
return <NoSafe provider={provider} text="Safe not found" />
|
||||
}
|
||||
|
||||
const openGenericModal = (modalConfig: OpenModalArgs): void => {
|
||||
setModal({ ...modalConfig, isOpen: true })
|
||||
}
|
||||
|
||||
const closeGenericModal = (): void => {
|
||||
modal.onClose?.()
|
||||
|
||||
setModal({
|
||||
isOpen: false,
|
||||
title: null,
|
||||
body: null,
|
||||
footer: null,
|
||||
onClose: null,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayoutHeader onShow={onShow} showSendFunds={showSendFunds} />
|
||||
|
@ -83,15 +57,10 @@ const Layout = (props: Props): React.ReactElement => {
|
|||
<Switch>
|
||||
<Route exact path={`${match.path}/balances/:assetType?`} render={() => wrapInSuspense(<Balances />, null)} />
|
||||
<Route exact path={`${match.path}/transactions`} render={() => wrapInSuspense(<TxsTable />, null)} />
|
||||
<Route exact path={`${match.path}/apps`} render={() => wrapInSuspense(<Apps />, null)} />
|
||||
{process.env.REACT_APP_NEW_TX_TAB === 'enabled' && (
|
||||
<Route exact path={`${match.path}/all-transactions`} render={() => wrapInSuspense(<Transactions />, null)} />
|
||||
)}
|
||||
|
||||
<Route
|
||||
exact
|
||||
path={`${match.path}/apps`}
|
||||
render={() => wrapInSuspense(<Apps closeModal={closeGenericModal} openModal={openGenericModal} />, null)}
|
||||
/>
|
||||
<Route exact path={`${match.path}/settings`} render={() => wrapInSuspense(<Settings />, null)} />
|
||||
<Route exact path={`${match.path}/address-book`} render={() => wrapInSuspense(<AddressBookTable />, null)} />
|
||||
<Redirect to={`${match.path}/balances`} />
|
||||
|
@ -111,8 +80,6 @@ const Layout = (props: Props): React.ReactElement => {
|
|||
>
|
||||
<Receive onClose={onHide('Receive')} />
|
||||
</Modal>
|
||||
|
||||
{modal.isOpen && <GenericModal {...modal} onClose={closeGenericModal} />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
export interface ModalState {
|
||||
isOpen: boolean
|
||||
title: string | React.ReactElement
|
||||
body: React.ReactNode | null
|
||||
footer: React.ReactNode | null
|
||||
onClose: () => unknown
|
||||
}
|
||||
|
||||
export interface OpenModalArgs {
|
||||
title: string | React.ReactElement
|
||||
body: React.ReactNode | null
|
||||
footer: React.ReactNode | null
|
||||
onClose: () => unknown
|
||||
}
|
|
@ -4,7 +4,6 @@ import { makeStyles } from '@material-ui/core/styles'
|
|||
import Close from '@material-ui/icons/Close'
|
||||
import OpenInNew from '@material-ui/icons/OpenInNew'
|
||||
import cn from 'classnames'
|
||||
import { useSnackbar } from 'notistack'
|
||||
import React from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import styled from 'styled-components'
|
||||
|
@ -48,8 +47,6 @@ const RemoveModuleModal = ({ onClose, selectedModule }: RemoveModuleModal): Reac
|
|||
const classes = useStyles()
|
||||
|
||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const removeSelectedModule = async (): Promise<void> => {
|
||||
|
@ -65,8 +62,6 @@ const RemoveModuleModal = ({ onClose, selectedModule }: RemoveModuleModal): Reac
|
|||
valueInWei: '0',
|
||||
txData,
|
||||
notifiedTransaction: TX_NOTIFICATION_TYPES.SETTINGS_CHANGE_TX,
|
||||
enqueueSnackbar,
|
||||
closeSnackbar,
|
||||
}),
|
||||
)
|
||||
} catch (e) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import IconButton from '@material-ui/core/IconButton'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Close from '@material-ui/icons/Close'
|
||||
import { withSnackbar } from 'notistack'
|
||||
import React from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
|
@ -22,32 +21,34 @@ import Paragraph from 'src/components/layout/Paragraph'
|
|||
import Row from 'src/components/layout/Row'
|
||||
import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook'
|
||||
import { updateAddressBookEntry } from 'src/logic/addressBook/store/actions/updateAddressBookEntry'
|
||||
import { getNotificationsFromTxType, showSnackbar } from 'src/logic/notifications'
|
||||
import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
|
||||
import { NOTIFICATIONS } from 'src/logic/notifications'
|
||||
import editSafeOwner from 'src/logic/safe/store/actions/editSafeOwner'
|
||||
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
||||
import { sm } from 'src/theme/variables'
|
||||
import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar'
|
||||
|
||||
export const RENAME_OWNER_INPUT_TEST_ID = 'rename-owner-input'
|
||||
export const SAVE_OWNER_CHANGES_BTN_TEST_ID = 'save-owner-changes-btn'
|
||||
|
||||
const EditOwnerComponent = ({
|
||||
classes,
|
||||
closeSnackbar,
|
||||
enqueueSnackbar,
|
||||
isOpen,
|
||||
onClose,
|
||||
ownerAddress,
|
||||
selectedOwnerName,
|
||||
}) => {
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
type OwnProps = {
|
||||
isOpen: true
|
||||
onClose: () => void
|
||||
ownerAddress: string
|
||||
selectedOwnerName: string
|
||||
}
|
||||
|
||||
const EditOwnerComponent = ({ isOpen, onClose, ownerAddress, selectedOwnerName }: OwnProps): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
const dispatch = useDispatch()
|
||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||
const handleSubmit = (values) => {
|
||||
const { ownerName } = values
|
||||
|
||||
dispatch(editSafeOwner({ safeAddress, ownerAddress, ownerName }))
|
||||
dispatch(updateAddressBookEntry(makeAddressBookEntry({ address: ownerAddress, name: ownerName })))
|
||||
const notification = getNotificationsFromTxType(TX_NOTIFICATION_TYPES.OWNER_NAME_CHANGE_TX)
|
||||
showSnackbar(notification.afterExecution.noMoreConfirmationsNeeded, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(NOTIFICATIONS.OWNER_NAME_CHANGE_EXECUTED_MSG))
|
||||
|
||||
onClose()
|
||||
}
|
||||
|
@ -75,7 +76,6 @@ const EditOwnerComponent = ({
|
|||
<Block className={classes.container}>
|
||||
<Row margin="md">
|
||||
<Field
|
||||
className={classes.addressInput}
|
||||
component={TextField}
|
||||
initialValue={selectedOwnerName}
|
||||
name="ownerName"
|
||||
|
@ -87,7 +87,7 @@ const EditOwnerComponent = ({
|
|||
/>
|
||||
</Row>
|
||||
<Row>
|
||||
<Block className={classes.user} justify="center">
|
||||
<Block justify="center">
|
||||
<Identicon address={ownerAddress} diameter={32} />
|
||||
<Paragraph color="disabled" noMargin size="md" style={{ marginLeft: sm, marginRight: sm }}>
|
||||
{ownerAddress}
|
||||
|
@ -120,4 +120,4 @@ const EditOwnerComponent = ({
|
|||
)
|
||||
}
|
||||
|
||||
export default withStyles(styles as any)(withSnackbar(EditOwnerComponent))
|
||||
export default EditOwnerComponent
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { error, lg, md, sm } from 'src/theme/variables'
|
||||
import { createStyles } from '@material-ui/core'
|
||||
|
||||
export const styles = () => ({
|
||||
export const styles = createStyles({
|
||||
heading: {
|
||||
padding: lg,
|
||||
justifyContent: 'space-between',
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import cn from 'classnames'
|
||||
import { withSnackbar } from 'notistack'
|
||||
import React from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
|
@ -17,7 +15,8 @@ import Col from 'src/components/layout/Col'
|
|||
import Heading from 'src/components/layout/Heading'
|
||||
import Paragraph from 'src/components/layout/Paragraph'
|
||||
import Row from 'src/components/layout/Row'
|
||||
import { getNotificationsFromTxType, showSnackbar } from 'src/logic/notifications'
|
||||
import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar'
|
||||
import { getNotificationsFromTxType, enhanceSnackbarForAction } from 'src/logic/notifications'
|
||||
import { TX_NOTIFICATION_TYPES } from 'src/logic/safe/transactions'
|
||||
import UpdateSafeModal from 'src/routes/safe/components/Settings/UpdateSafeModal'
|
||||
import { grantedSelector } from 'src/routes/safe/container/selector'
|
||||
|
@ -35,9 +34,9 @@ export const SAFE_NAME_INPUT_TEST_ID = 'safe-name-input'
|
|||
export const SAFE_NAME_SUBMIT_BTN_TEST_ID = 'change-safe-name-btn'
|
||||
export const SAFE_NAME_UPDATE_SAFE_BTN_TEST_ID = 'update-safe-name-btn'
|
||||
|
||||
const useStyles = makeStyles(styles as any)
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
const SafeDetails = (props) => {
|
||||
const SafeDetails = (): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
const isUserOwner = useSelector(grantedSelector)
|
||||
const latestMasterContractVersion = useSelector(latestMasterContractVersionSelector)
|
||||
|
@ -45,7 +44,6 @@ const SafeDetails = (props) => {
|
|||
const safeName = useSelector(safeNameSelector)
|
||||
const safeNeedsUpdate = useSelector(safeNeedsUpdateSelector)
|
||||
const safeCurrentVersion = useSelector(safeCurrentVersionSelector)
|
||||
const { closeSnackbar, enqueueSnackbar } = props
|
||||
|
||||
const [isModalOpen, setModalOpen] = React.useState(false)
|
||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||
|
@ -58,7 +56,7 @@ const SafeDetails = (props) => {
|
|||
dispatch(updateSafe({ address: safeAddress, name: values.safeName }))
|
||||
|
||||
const notification = getNotificationsFromTxType(TX_NOTIFICATION_TYPES.SAFE_NAME_CHANGE_TX)
|
||||
showSnackbar(notification.afterExecution.noMoreConfirmationsNeeded, enqueueSnackbar, closeSnackbar)
|
||||
dispatch(enqueueSnackbar(enhanceSnackbarForAction(notification.afterExecution.noMoreConfirmationsNeeded)))
|
||||
}
|
||||
|
||||
const handleUpdateSafe = () => {
|
||||
|
@ -75,7 +73,7 @@ const SafeDetails = (props) => {
|
|||
<Row align="end" grow>
|
||||
<Paragraph className={classes.versionNumber}>
|
||||
<Link
|
||||
className={cn(classes.item, classes.link)}
|
||||
className={classes.link}
|
||||
color="black"
|
||||
target="_blank"
|
||||
to="https://github.com/gnosis/safe-contracts/releases"
|
||||
|
@ -145,4 +143,4 @@ const SafeDetails = (props) => {
|
|||
)
|
||||
}
|
||||
|
||||
export default withSnackbar(SafeDetails)
|
||||
export default SafeDetails
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { createStyles } from '@material-ui/core/styles'
|
||||
import { boldFont, border, lg, sm, connected } from 'src/theme/variables'
|
||||
|
||||
export const styles = () => ({
|
||||
export const styles = createStyles({
|
||||
formContainer: {
|
||||
padding: lg,
|
||||
},
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import Checkbox from '@material-ui/core/Checkbox'
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Close from '@material-ui/icons/Close'
|
||||
import { withSnackbar } from 'notistack'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
|
@ -24,6 +23,9 @@ import { userAccountSelector } from 'src/logic/wallets/store/selectors'
|
|||
import processTransaction from 'src/logic/safe/store/actions/processTransaction'
|
||||
|
||||
import { safeParamAddressFromStateSelector, safeThresholdSelector } from 'src/logic/safe/store/selectors'
|
||||
import { Transaction } from 'src/logic/safe/store/models/types/transaction'
|
||||
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
export const APPROVE_TX_MODAL_SUBMIT_BTN_TEST_ID = 'approve-tx-modal-submit-btn'
|
||||
export const REJECT_TX_MODAL_SUBMIT_BTN_TEST_ID = 'reject-tx-modal-submit-btn'
|
||||
|
@ -51,19 +53,26 @@ const getModalTitleAndDescription = (thresholdReached, isCancelTx) => {
|
|||
return modalInfo
|
||||
}
|
||||
|
||||
type Props = {
|
||||
canExecute: boolean
|
||||
isCancelTx?: boolean
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
thresholdReached: boolean
|
||||
tx: Transaction
|
||||
}
|
||||
|
||||
const ApproveTxModal = ({
|
||||
canExecute,
|
||||
classes,
|
||||
closeSnackbar,
|
||||
enqueueSnackbar,
|
||||
isCancelTx,
|
||||
isCancelTx = false,
|
||||
isOpen,
|
||||
onClose,
|
||||
thresholdReached,
|
||||
tx,
|
||||
}: any) => {
|
||||
}: Props): React.ReactElement => {
|
||||
const dispatch = useDispatch()
|
||||
const userAddress = useSelector(userAccountSelector)
|
||||
const classes = useStyles()
|
||||
const threshold = useSelector(safeThresholdSelector)
|
||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||
const [approveAndExecute, setApproveAndExecute] = useState(canExecute)
|
||||
|
@ -109,8 +118,6 @@ const ApproveTxModal = ({
|
|||
tx,
|
||||
userAddress,
|
||||
notifiedTransaction: TX_NOTIFICATION_TYPES.CONFIRMATION_TX,
|
||||
enqueueSnackbar,
|
||||
closeSnackbar,
|
||||
approveAndExecute: canExecute && approveAndExecute && isTheTxReadyToBeExecuted,
|
||||
}),
|
||||
)
|
||||
|
@ -181,4 +188,4 @@ const ApproveTxModal = ({
|
|||
)
|
||||
}
|
||||
|
||||
export default withStyles(styles as any)(withSnackbar(ApproveTxModal))
|
||||
export default ApproveTxModal
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { createStyles } from '@material-ui/core'
|
||||
import { border, lg, md, sm } from 'src/theme/variables'
|
||||
|
||||
export const styles = () => ({
|
||||
export const styles = createStyles({
|
||||
heading: {
|
||||
padding: `${sm} ${lg}`,
|
||||
justifyContent: 'space-between',
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import IconButton from '@material-ui/core/IconButton'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import Close from '@material-ui/icons/Close'
|
||||
import { withSnackbar } from 'notistack'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
|
@ -22,11 +21,22 @@ import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
|||
import createTransaction from 'src/logic/safe/store/actions/createTransaction'
|
||||
|
||||
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
|
||||
import { Transaction } from 'src/logic/safe/store/models/types/transaction'
|
||||
|
||||
const RejectTxModal = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClose, tx }) => {
|
||||
const useStyles = makeStyles(styles)
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
tx: Transaction
|
||||
}
|
||||
|
||||
const RejectTxModal = ({ isOpen, onClose, tx }: Props): React.ReactElement => {
|
||||
const [gasCosts, setGasCosts] = useState('< 0.001')
|
||||
const dispatch = useDispatch()
|
||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||
const classes = useStyles()
|
||||
|
||||
useEffect(() => {
|
||||
let isCurrent = true
|
||||
const estimateGasCosts = async () => {
|
||||
|
@ -55,8 +65,6 @@ const RejectTxModal = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClos
|
|||
to: safeAddress,
|
||||
valueInWei: '0',
|
||||
notifiedTransaction: TX_NOTIFICATION_TYPES.CANCELLATION_TX,
|
||||
enqueueSnackbar,
|
||||
closeSnackbar,
|
||||
txNonce: tx.nonce,
|
||||
origin: tx.origin,
|
||||
}),
|
||||
|
@ -111,4 +119,4 @@ const RejectTxModal = ({ classes, closeSnackbar, enqueueSnackbar, isOpen, onClos
|
|||
)
|
||||
}
|
||||
|
||||
export default withStyles(styles as any)(withSnackbar(RejectTxModal))
|
||||
export default RejectTxModal
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { createStyles } from '@material-ui/core'
|
||||
import { border, lg, md, sm } from 'src/theme/variables'
|
||||
|
||||
export const styles = () => ({
|
||||
export const styles = createStyles({
|
||||
heading: {
|
||||
padding: `${sm} ${lg}`,
|
||||
justifyContent: 'space-between',
|
||||
|
|
Loading…
Reference in New Issue