* sdk version update * point sdk to a newer commit * Update iframe message handler * ConfirmTransactionModal tweaks to support params * handle sendTransactionsWithParams, display safeTxGas in app tx modal * new sdk version * yarn lock update * install libudev in travis * update sdk version * Estimating safeTxGas for Safe Apps Txs WIP * safetxgas estimation warning wip * gas estimation in confirm transaction modal * yarn lock update * review fixes * Change estimation loading msg, use imported vars to index payload type Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>
|
@ -30,7 +30,7 @@ before_script:
|
|||
before_install:
|
||||
# Needed to deploy pull request and releases
|
||||
- sudo apt-get update
|
||||
- sudo apt-get -y install python-pip python-dev libusb-1.0-0-dev
|
||||
- sudo apt-get -y install python-pip python-dev libusb-1.0-0-dev libudev-dev
|
||||
- pip install awscli --upgrade --user
|
||||
script:
|
||||
- yarn lint:check
|
||||
|
|
|
@ -163,7 +163,7 @@
|
|||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@gnosis.pm/safe-apps-sdk": "0.4.2",
|
||||
"@gnosis.pm/safe-apps-sdk": "https://github.com/gnosis/safe-apps-sdk#a6c168b",
|
||||
"@gnosis.pm/safe-contracts": "1.1.1-dev.2",
|
||||
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#70e57bdd1e0fd5dfdf5768076577c1e000b5fe28",
|
||||
"@gnosis.pm/util-contracts": "2.0.6",
|
||||
|
|
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 337 B |
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 345 B |
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 324 B |
|
@ -1,52 +1,7 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M256,0C114.497,0,0,114.507,0,256c0,141.503,114.507,256,256,256c141.503,0,256-114.507,256-256
|
||||
C512,114.497,397.492,0,256,0z M256,472c-119.393,0-216-96.615-216-216c0-119.393,96.615-216,216-216
|
||||
c119.393,0,216,96.615,216,216C472,375.393,375.384,472,256,472z" fill="#ff0000"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<rect width="2" height="8" x="9" y="8" fill="#B2B5B2" rx="1"/>
|
||||
<rect width="2" height="2" x="9" y="4" fill="#B2B5B2" stroke="#B2B5B2" stroke-width=".5" rx="1"/>
|
||||
<circle cx="10" cy="10" r="9" stroke="#B2B5B2" stroke-width="2"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M256,214.33c-11.046,0-20,8.954-20,20v128.793c0,11.046,8.954,20,20,20s20-8.955,20-20.001V234.33
|
||||
C276,223.284,267.046,214.33,256,214.33z" fill="#ff0000" />
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<circle cx="256" cy="162.84" r="27" fill="#ff0000"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 391 B |
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M256,0C114.497,0,0,114.507,0,256c0,141.503,114.507,256,256,256c141.503,0,256-114.507,256-256
|
||||
C512,114.497,397.492,0,256,0z M256,472c-119.393,0-216-96.615-216-216c0-119.393,96.615-216,216-216
|
||||
c119.393,0,216,96.615,216,216C472,375.393,375.384,472,256,472z" fill="#ff0000"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M256,214.33c-11.046,0-20,8.954-20,20v128.793c0,11.046,8.954,20,20,20s20-8.955,20-20.001V234.33
|
||||
C276,223.284,267.046,214.33,256,214.33z" fill="#ff0000" />
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<circle cx="256" cy="162.84" r="27" fill="#ff0000"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 859 B |
|
@ -1,7 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<rect width="2" height="8" x="9" y="8" fill="#B2B5B2" rx="1"/>
|
||||
<rect width="2" height="2" x="9" y="4" fill="#B2B5B2" stroke="#B2B5B2" stroke-width=".5" rx="1"/>
|
||||
<circle cx="10" cy="10" r="9" stroke="#B2B5B2" stroke-width="2"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 391 B |
|
@ -5,10 +5,10 @@ import { useSelector } from 'react-redux'
|
|||
import { useRouteMatch, useHistory } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AlertIcon from './assets/alert.svg'
|
||||
import CheckIcon from './assets/check.svg'
|
||||
import ErrorIcon from './assets/error.svg'
|
||||
import InfoIcon from './assets/info.svg'
|
||||
import AlertIcon from 'src/assets/icons/alert.svg'
|
||||
import CheckIcon from 'src/assets/icons/check.svg'
|
||||
import ErrorIcon from 'src/assets/icons/error.svg'
|
||||
import InfoIcon from 'src/assets/icons/info.svg'
|
||||
|
||||
import AppLayout from 'src/components/AppLayout'
|
||||
import SafeListSidebarProvider, { SafeListSidebarContext } from 'src/components/SafeListSidebar'
|
||||
|
|
|
@ -107,6 +107,7 @@ interface CreateTransactionArgs {
|
|||
txData?: string
|
||||
txNonce?: number | string
|
||||
valueInWei: string
|
||||
safeTxGas?: number
|
||||
}
|
||||
|
||||
type CreateTransactionAction = ThunkAction<Promise<void>, AppReduxState, undefined, AnyAction>
|
||||
|
@ -124,6 +125,7 @@ const createTransaction = (
|
|||
operation = CALL,
|
||||
navigateToTransactionsTab = true,
|
||||
origin = null,
|
||||
safeTxGas: safeTxGasArg,
|
||||
}: CreateTransactionArgs,
|
||||
onUserConfirm?: ConfirmEventHandler,
|
||||
onError?: ErrorEventHandler,
|
||||
|
@ -143,7 +145,8 @@ const createTransaction = (
|
|||
const nonce = await getNewTxNonce(txNonce?.toString(), lastTx, safeInstance)
|
||||
const isExecution = await shouldExecuteTransaction(safeInstance, nonce, lastTx)
|
||||
const safeVersion = await getCurrentSafeVersion(safeInstance)
|
||||
const safeTxGas = await estimateSafeTxGas(safeInstance, safeAddress, txData, to, valueInWei, operation)
|
||||
const safeTxGas =
|
||||
safeTxGasArg || (await estimateSafeTxGas(safeInstance, safeAddress, txData, to, valueInWei, operation))
|
||||
|
||||
// https://docs.gnosis.io/safe/docs/docs5/#pre-validated-signatures
|
||||
const sigs = `0x000000000000000000000000${from.replace(
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Icon, Text, Title, GenericModal, ModalFooterConfirmation } from '@gnosis.pm/safe-react-components'
|
||||
import { Transaction } from '@gnosis.pm/safe-apps-sdk'
|
||||
import { Transaction, SendTransactionParams } from '@gnosis.pm/safe-apps-sdk'
|
||||
import styled from 'styled-components'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
import AddressInfo from 'src/components/AddressInfo'
|
||||
import DividerLine from 'src/components/DividerLine'
|
||||
|
@ -15,11 +16,13 @@ import Img from 'src/components/layout/Img'
|
|||
import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers'
|
||||
import { SafeApp } from 'src/routes/safe/components/Apps/types.d'
|
||||
import { fromTokenUnit } 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'
|
||||
import { estimateSafeTxGas } from 'src/logic/safe/transactions/gasNew'
|
||||
|
||||
import GasEstimationInfo from './GasEstimationInfo'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
|
||||
const isTxValid = (t: Transaction): boolean => {
|
||||
|
@ -67,6 +70,7 @@ type OwnProps = {
|
|||
isOpen: boolean
|
||||
app: SafeApp
|
||||
txs: Transaction[]
|
||||
params?: SendTransactionParams
|
||||
safeAddress: string
|
||||
safeName: string
|
||||
ethBalance: string
|
||||
|
@ -84,10 +88,39 @@ const ConfirmTransactionModal = ({
|
|||
safeAddress,
|
||||
ethBalance,
|
||||
safeName,
|
||||
params,
|
||||
onUserConfirm,
|
||||
onClose,
|
||||
onTxReject,
|
||||
}: OwnProps): React.ReactElement | null => {
|
||||
const [estimatedSafeTxGas, setEstimatedSafeTxGas] = useState(0)
|
||||
const [estimatingGas, setEstimatingGas] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const estimateGas = async () => {
|
||||
try {
|
||||
setEstimatingGas(true)
|
||||
const safeTxGas = await estimateSafeTxGas(
|
||||
undefined,
|
||||
safeAddress,
|
||||
encodeMultiSendCall(txs),
|
||||
MULTI_SEND_ADDRESS,
|
||||
'0',
|
||||
DELEGATE_CALL,
|
||||
)
|
||||
|
||||
setEstimatedSafeTxGas(safeTxGas)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
setEstimatingGas(false)
|
||||
}
|
||||
}
|
||||
if (params?.safeTxGas) {
|
||||
estimateGas()
|
||||
}
|
||||
}, [params, safeAddress, txs])
|
||||
|
||||
const dispatch = useDispatch()
|
||||
if (!isOpen) {
|
||||
return null
|
||||
|
@ -117,6 +150,7 @@ const ConfirmTransactionModal = ({
|
|||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||
origin: app.id,
|
||||
navigateToTransactionsTab: false,
|
||||
safeTxGas: Math.max(params?.safeTxGas || 0, estimatedSafeTxGas),
|
||||
},
|
||||
handleUserConfirmation,
|
||||
handleTxRejection,
|
||||
|
@ -162,6 +196,18 @@ const ConfirmTransactionModal = ({
|
|||
</Collapse>
|
||||
</Wrapper>
|
||||
))}
|
||||
<DividerLine withArrow={false} />
|
||||
{params?.safeTxGas && (
|
||||
<div className="section">
|
||||
<Heading tag="h3">SafeTxGas</Heading>
|
||||
<StyledTextBox>{params?.safeTxGas}</StyledTextBox>
|
||||
<GasEstimationInfo
|
||||
appEstimation={params.safeTxGas}
|
||||
internalEstimation={estimatedSafeTxGas}
|
||||
loading={estimatingGas}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Img from 'src/components/layout/Img'
|
||||
import CheckIcon from 'src/assets/icons/check.svg'
|
||||
import AlertIcon from 'src/assets/icons/alert.svg'
|
||||
|
||||
type OwnProps = {
|
||||
appEstimation: number
|
||||
internalEstimation: number
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const imgStyles = {
|
||||
marginRight: '5px',
|
||||
}
|
||||
|
||||
const GasEstimationInfo = ({ appEstimation, internalEstimation, loading }: OwnProps): React.ReactElement => {
|
||||
if (loading) {
|
||||
return <p>Checking transaction parameters...</p>
|
||||
}
|
||||
|
||||
let content: React.ReactElement | null = null
|
||||
if (appEstimation >= internalEstimation) {
|
||||
content = (
|
||||
<>
|
||||
<Img alt="Success" src={CheckIcon} style={imgStyles} /> Gas estimation is OK
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (internalEstimation === 0) {
|
||||
content = (
|
||||
<>
|
||||
<Img alt="Warning" src={AlertIcon} style={imgStyles} /> Error while estimating gas. The transaction may fail.
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return <Container>{content}</Container>
|
||||
}
|
||||
|
||||
export default GasEstimationInfo
|
|
@ -9,6 +9,7 @@ import {
|
|||
RequestId,
|
||||
Transaction,
|
||||
LowercaseNetworks,
|
||||
SendTransactionParams,
|
||||
} from '@gnosis.pm/safe-apps-sdk'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useEffect, useCallback, MutableRefObject } from 'react'
|
||||
|
@ -45,7 +46,7 @@ const NETWORK_NAME = getNetworkName()
|
|||
|
||||
const useIframeMessageHandler = (
|
||||
selectedApp: SafeApp | undefined,
|
||||
openConfirmationModal: (txs: Transaction[], requestId: RequestId) => void,
|
||||
openConfirmationModal: (txs: Transaction[], params: SendTransactionParams | undefined, requestId: RequestId) => void,
|
||||
closeModal: () => void,
|
||||
iframeRef: MutableRefObject<HTMLIFrameElement | null>,
|
||||
): ReturnType => {
|
||||
|
@ -70,17 +71,35 @@ const useIframeMessageHandler = (
|
|||
)
|
||||
|
||||
useEffect(() => {
|
||||
const handleIframeMessage = (msg: CustomMessageEvent) => {
|
||||
if (!msg?.data.messageId) {
|
||||
const handleIframeMessage = (
|
||||
messageId: SDKMessageIds,
|
||||
messagePayload: SDKMessageToPayload[typeof messageId],
|
||||
requestId: RequestId,
|
||||
) => {
|
||||
if (!messageId) {
|
||||
console.error('ThirdPartyApp: A message was received without message id.')
|
||||
return
|
||||
}
|
||||
const { requestId } = msg.data
|
||||
|
||||
switch (msg.data.messageId) {
|
||||
switch (messageId) {
|
||||
// typescript doesn't narrow type in switch/case statements
|
||||
// issue: https://github.com/microsoft/TypeScript/issues/20375
|
||||
// possible solution: https://stackoverflow.com/a/43879897/7820085
|
||||
case SDK_MESSAGES.SEND_TRANSACTIONS: {
|
||||
if (msg.data.data) {
|
||||
openConfirmationModal(msg.data.data, requestId)
|
||||
if (messagePayload) {
|
||||
openConfirmationModal(
|
||||
messagePayload as SDKMessageToPayload[typeof SDK_MESSAGES.SEND_TRANSACTIONS],
|
||||
undefined,
|
||||
requestId,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case SDK_MESSAGES.SEND_TRANSACTIONS_V2: {
|
||||
const payload = messagePayload as SDKMessageToPayload[typeof SDK_MESSAGES.SEND_TRANSACTIONS_V2]
|
||||
if (payload) {
|
||||
openConfirmationModal(payload.txs, payload.params, requestId)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
@ -106,7 +125,7 @@ const useIframeMessageHandler = (
|
|||
break
|
||||
}
|
||||
default: {
|
||||
console.error(`ThirdPartyApp: A message was received with an unknown message id ${msg.data.messageId}.`)
|
||||
console.error(`ThirdPartyApp: A message was received with an unknown message id ${messageId}.`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +138,7 @@ const useIframeMessageHandler = (
|
|||
console.error(`ThirdPartyApp: A message was received from an unknown origin ${message.origin}`)
|
||||
return
|
||||
}
|
||||
handleIframeMessage(message)
|
||||
handleIframeMessage(message.data.messageId, message.data.data, message.data.requestId)
|
||||
}
|
||||
|
||||
window.addEventListener('message', onIframeMessage)
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import React, { useCallback, useEffect, useRef, useState, useMemo } from 'react'
|
||||
import { INTERFACE_MESSAGES, Transaction, RequestId, LowercaseNetworks } from '@gnosis.pm/safe-apps-sdk'
|
||||
import {
|
||||
INTERFACE_MESSAGES,
|
||||
Transaction,
|
||||
RequestId,
|
||||
LowercaseNetworks,
|
||||
SendTransactionParams,
|
||||
} 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'
|
||||
|
@ -47,13 +53,15 @@ const CenteredMT = styled.div`
|
|||
type ConfirmTransactionModalState = {
|
||||
isOpen: boolean
|
||||
txs: Transaction[]
|
||||
requestId: RequestId | undefined
|
||||
requestId?: RequestId
|
||||
params?: SendTransactionParams
|
||||
}
|
||||
|
||||
const INITIAL_CONFIRM_TX_MODAL_STATE: ConfirmTransactionModalState = {
|
||||
isOpen: false,
|
||||
txs: [],
|
||||
requestId: undefined,
|
||||
params: undefined,
|
||||
}
|
||||
|
||||
const NETWORK_NAME = getNetworkName()
|
||||
|
@ -75,11 +83,12 @@ const Apps = (): React.ReactElement => {
|
|||
const ethBalance = useSelector(safeEthBalanceSelector)
|
||||
|
||||
const openConfirmationModal = useCallback(
|
||||
(txs: Transaction[], requestId: RequestId) =>
|
||||
(txs: Transaction[], params: SendTransactionParams | undefined, requestId: RequestId) =>
|
||||
setConfirmTransactionModal({
|
||||
isOpen: true,
|
||||
txs,
|
||||
requestId,
|
||||
params,
|
||||
}),
|
||||
[setConfirmTransactionModal],
|
||||
)
|
||||
|
@ -215,6 +224,7 @@ const Apps = (): React.ReactElement => {
|
|||
txs={confirmTransactionModal.txs}
|
||||
onClose={closeConfirmationModal}
|
||||
onUserConfirm={onUserTxConfirm}
|
||||
params={confirmTransactionModal.params}
|
||||
onTxReject={onTxReject}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -7,7 +7,7 @@ import TableContainer from '@material-ui/core/TableContainer'
|
|||
import TableRow from '@material-ui/core/TableRow'
|
||||
import { Skeleton } from '@material-ui/lab'
|
||||
|
||||
import InfoIcon from 'src/assets/icons/info.svg'
|
||||
import InfoIcon from 'src/assets/icons/info_red.svg'
|
||||
|
||||
import Img from 'src/components/layout/Img'
|
||||
import Table from 'src/components/Table'
|
||||
|
|