Feature: Send env info message on safe apps sdk initialization (#1349)

This commit is contained in:
Mikhail Mikheev 2020-09-22 22:01:55 +04:00 committed by GitHub
parent 59dc1f711c
commit eebe972bac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 48 additions and 63 deletions

View File

@ -111,7 +111,7 @@ interface CreateTransactionArgs {
type CreateTransactionAction = ThunkAction<Promise<void>, AppReduxState, undefined, AnyAction> type CreateTransactionAction = ThunkAction<Promise<void>, AppReduxState, undefined, AnyAction>
type ConfirmEventHandler = (safeTxHash: string) => void type ConfirmEventHandler = (safeTxHash: string) => void
type RejectEventHandler = () => void type ErrorEventHandler = () => void
const createTransaction = ( const createTransaction = (
{ {
@ -126,7 +126,7 @@ const createTransaction = (
origin = null, origin = null,
}: CreateTransactionArgs, }: CreateTransactionArgs,
onUserConfirm?: ConfirmEventHandler, onUserConfirm?: ConfirmEventHandler,
onUserReject?: RejectEventHandler, onError?: ErrorEventHandler,
): CreateTransactionAction => async (dispatch: Dispatch, getState: () => AppReduxState): Promise<void> => { ): CreateTransactionAction => async (dispatch: Dispatch, getState: () => AppReduxState): Promise<void> => {
const state = getState() const state = getState()
@ -172,6 +172,7 @@ const createTransaction = (
sender: from, sender: from,
sigs, sigs,
} }
const safeTxHash = generateSafeTxHash(safeAddress, txArgs)
try { try {
// Here we're checking that safe contract version is greater or equal 1.1.1, but // Here we're checking that safe contract version is greater or equal 1.1.1, but
@ -179,20 +180,19 @@ const createTransaction = (
const canTryOffchainSigning = const canTryOffchainSigning =
!isExecution && !smartContractWallet && semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES) !isExecution && !smartContractWallet && semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES)
if (canTryOffchainSigning) { if (canTryOffchainSigning) {
const signature = await tryOffchainSigning({ ...txArgs, safeAddress }, hardwareWallet) const signature = await tryOffchainSigning(safeTxHash, { ...txArgs, safeAddress }, hardwareWallet)
if (signature) { if (signature) {
dispatch(closeSnackbarAction({ key: beforeExecutionKey })) dispatch(closeSnackbarAction({ key: beforeExecutionKey }))
dispatch(enqueueSnackbar(notificationsQueue.afterExecution.moreConfirmationsNeeded))
dispatch(fetchTransactions(safeAddress))
await saveTxToHistory({ ...txArgs, signature, origin }) await saveTxToHistory({ ...txArgs, signature, origin })
dispatch(enqueueSnackbar(notificationsQueue.afterExecution.moreConfirmationsNeeded)) onUserConfirm?.(safeTxHash)
dispatch(fetchTransactions(safeAddress))
return return
} }
} }
const safeTxHash = generateSafeTxHash(safeAddress, txArgs)
const tx = isExecution const tx = isExecution
? await getExecutionTransaction(txArgs) ? await getExecutionTransaction(txArgs)
: await getApprovalTransaction(safeInstance, safeTxHash) : await getApprovalTransaction(safeInstance, safeTxHash)
@ -245,20 +245,7 @@ const createTransaction = (
removeTxFromStore(mockedTx, safeAddress, dispatch, state) removeTxFromStore(mockedTx, safeAddress, dispatch, state)
console.error('Tx error: ', error) console.error('Tx error: ', error)
// Different wallets return different error messages in this case. This is an assumption that if onError?.()
// error message includes "user" word, the tx was rejected by user
let errorIncludesUserWord = false
if (typeof error === 'string') {
errorIncludesUserWord = (error as string).includes('User') || (error as string).includes('user')
}
if (error.message) {
errorIncludesUserWord = error.message.includes('User') || error.message.includes('user')
}
if (errorIncludesUserWord) {
onUserReject?.()
}
}) })
.then(async (receipt) => { .then(async (receipt) => {
if (pendingExecutionKey) { if (pendingExecutionKey) {

View File

@ -78,7 +78,7 @@ const processTransaction = ({ approveAndExecute, notifiedTransaction, safeAddres
const canTryOffchainSigning = const canTryOffchainSigning =
!isExecution && !smartContractWallet && semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES) !isExecution && !smartContractWallet && semverSatisfies(safeVersion, SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES)
if (canTryOffchainSigning) { if (canTryOffchainSigning) {
const signature = await tryOffchainSigning({ ...txArgs, safeAddress }, hardwareWallet) const signature = await tryOffchainSigning(tx.safeTxHash, { ...txArgs, safeAddress }, hardwareWallet)
if (signature) { if (signature) {
dispatch(closeSnackbarAction(beforeExecutionKey)) dispatch(closeSnackbarAction(beforeExecutionKey))

View File

@ -103,7 +103,7 @@ const notificationsMiddleware = (store) => (next) => async (action) => {
} }
case ADD_INCOMING_TRANSACTIONS: { case ADD_INCOMING_TRANSACTIONS: {
action.payload.forEach((incomingTransactions, safeAddress) => { action.payload.forEach((incomingTransactions, safeAddress) => {
const { latestIncomingTxBlock } = state.safes.get('safes').get(safeAddress) const { latestIncomingTxBlock } = state.safes.get('safes').get(safeAddress, {})
const viewedSafes = state.currentSession['viewedSafes'] const viewedSafes = state.currentSession['viewedSafes']
const recurringUser = viewedSafes?.includes(safeAddress) const recurringUser = viewedSafes?.includes(safeAddress)

View File

@ -1,5 +1,6 @@
import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' import { AbstractProvider } from 'web3-core'
import { getWeb3 } from 'src/logic/wallets/getWeb3' import { getWeb3 } from 'src/logic/wallets/getWeb3'
import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
const EIP712_NOT_SUPPORTED_ERROR_MSG = "EIP712 is not supported by user's wallet" const EIP712_NOT_SUPPORTED_ERROR_MSG = "EIP712 is not supported by user's wallet"
@ -59,7 +60,7 @@ const generateTypedDataFrom = async ({
} }
export const getEIP712Signer = (version?: string) => async (txArgs) => { export const getEIP712Signer = (version?: string) => async (txArgs) => {
const web3: any = getWeb3() const web3 = getWeb3()
const typedData = await generateTypedDataFrom(txArgs) const typedData = await generateTypedDataFrom(txArgs)
let method = 'eth_signTypedData_v3' let method = 'eth_signTypedData_v3'
@ -80,13 +81,14 @@ export const getEIP712Signer = (version?: string) => async (txArgs) => {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
web3.currentProvider.sendAsync(signedTypedData, (err, signature) => { const provider = web3.currentProvider as AbstractProvider
provider.sendAsync(signedTypedData, (err, signature) => {
if (err) { if (err) {
reject(err) reject(err)
return return
} }
if (signature.result == null) { if (signature?.result == null) {
reject(new Error(EIP712_NOT_SUPPORTED_ERROR_MSG)) reject(new Error(EIP712_NOT_SUPPORTED_ERROR_MSG))
return return
} }

View File

@ -4,26 +4,13 @@ import { AbstractProvider } from 'web3-core/types'
const ETH_SIGN_NOT_SUPPORTED_ERROR_MSG = 'ETH_SIGN_NOT_SUPPORTED' const ETH_SIGN_NOT_SUPPORTED_ERROR_MSG = 'ETH_SIGN_NOT_SUPPORTED'
export const ethSigner = async ({ type EthSignerArgs = {
baseGas, safeTxHash: string
data, sender: string
gasPrice, }
gasToken,
nonce, export const ethSigner = async ({ safeTxHash, sender }: EthSignerArgs): Promise<string> => {
operation,
refundReceiver,
safeInstance,
safeTxGas,
sender,
to,
valueInWei,
}): Promise<string> => {
const web3 = await getWeb3() const web3 = await getWeb3()
const txHash = await safeInstance.methods
.getTransactionHash(to, valueInWei, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, nonce)
.call({
from: sender,
})
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
const provider = web3.currentProvider as AbstractProvider const provider = web3.currentProvider as AbstractProvider
@ -31,7 +18,7 @@ export const ethSigner = async ({
{ {
jsonrpc: '2.0', jsonrpc: '2.0',
method: 'eth_sign', method: 'eth_sign',
params: [sender, txHash], params: [sender, safeTxHash],
id: new Date().getTime(), id: new Date().getTime(),
}, },
async function (err, signature) { async function (err, signature) {

View File

@ -8,7 +8,7 @@ import { ethSigner } from './ethSigner'
const SIGNERS = { const SIGNERS = {
EIP712_V3: getEIP712Signer('v3'), EIP712_V3: getEIP712Signer('v3'),
EIP712_V4: getEIP712Signer('v4'), EIP712_V4: getEIP712Signer('v4'),
EIP712: getEIP712Signer() as any, EIP712: getEIP712Signer(),
ETH_SIGN: ethSigner, ETH_SIGN: ethSigner,
} }
@ -18,13 +18,13 @@ const getSignersByWallet = (isHW) =>
export const SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES = '>=1.1.1' export const SAFE_VERSION_FOR_OFFCHAIN_SIGNATURES = '>=1.1.1'
export const tryOffchainSigning = async (txArgs, isHW) => { export const tryOffchainSigning = async (safeTxHash: string, txArgs, isHW: boolean): Promise<string> => {
let signature let signature
const signerByWallet = getSignersByWallet(isHW) const signerByWallet = getSignersByWallet(isHW)
for (const signingFunc of signerByWallet) { for (const signingFunc of signerByWallet) {
try { try {
signature = await signingFunc(txArgs) signature = await signingFunc({ ...txArgs, safeTxHash })
break break
} catch (err) { } catch (err) {

View File

@ -69,9 +69,8 @@ type OwnProps = {
safeAddress: string safeAddress: string
safeName: string safeName: string
ethBalance: string ethBalance: string
onCancel: () => void
onUserConfirm: (safeTxHash: string) => void onUserConfirm: (safeTxHash: string) => void
onUserTxReject: () => void onTxReject: () => void
onClose: () => void onClose: () => void
} }
@ -82,16 +81,20 @@ const ConfirmTransactionModal = ({
safeAddress, safeAddress,
ethBalance, ethBalance,
safeName, safeName,
onCancel,
onUserConfirm, onUserConfirm,
onClose, onClose,
onUserTxReject, onTxReject,
}: OwnProps): React.ReactElement | null => { }: OwnProps): React.ReactElement | null => {
const dispatch = useDispatch() const dispatch = useDispatch()
if (!isOpen) { if (!isOpen) {
return null return null
} }
const handleTxRejection = () => {
onTxReject()
onClose()
}
const handleUserConfirmation = (safeTxHash: string): void => { const handleUserConfirmation = (safeTxHash: string): void => {
onUserConfirm(safeTxHash) onUserConfirm(safeTxHash)
onClose() onClose()
@ -113,10 +116,9 @@ const ConfirmTransactionModal = ({
navigateToTransactionsTab: false, navigateToTransactionsTab: false,
}, },
handleUserConfirmation, handleUserConfirmation,
onUserTxReject, handleTxRejection,
), ),
) )
onClose()
} }
const areTxsMalformed = txs.some((t) => !isTxValid(t)) const areTxsMalformed = txs.some((t) => !isTxValid(t))
@ -165,13 +167,13 @@ const ConfirmTransactionModal = ({
footer={ footer={
<ModalFooterConfirmation <ModalFooterConfirmation
cancelText="Cancel" cancelText="Cancel"
handleCancel={onCancel} handleCancel={handleTxRejection}
handleOk={confirmTransactions} handleOk={confirmTransactions}
okDisabled={areTxsMalformed} okDisabled={areTxsMalformed}
okText="Submit" okText="Submit"
/> />
} }
onClose={onClose} onClose={handleTxRejection}
/> />
) )
} }

View File

@ -12,6 +12,7 @@ import {
} from '@gnosis.pm/safe-apps-sdk' } from '@gnosis.pm/safe-apps-sdk'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { useEffect, useCallback, MutableRefObject } from 'react' import { useEffect, useCallback, MutableRefObject } from 'react'
import { getTxServiceHost } from 'src/config/'
import { import {
safeEthBalanceSelector, safeEthBalanceSelector,
safeNameSelector, safeNameSelector,
@ -85,7 +86,7 @@ const useIframeMessageHandler = (
} }
case SDK_MESSAGES.SAFE_APP_SDK_INITIALIZED: { case SDK_MESSAGES.SAFE_APP_SDK_INITIALIZED: {
const message = { const safeInfoMessage = {
messageId: INTERFACE_MESSAGES.ON_SAFE_INFO, messageId: INTERFACE_MESSAGES.ON_SAFE_INFO,
data: { data: {
safeAddress: safeAddress as string, safeAddress: safeAddress as string,
@ -93,8 +94,15 @@ const useIframeMessageHandler = (
ethBalance: ethBalance as string, ethBalance: ethBalance as string,
}, },
} }
const envInfoMessage = {
messageId: INTERFACE_MESSAGES.ENV_INFO,
data: {
txServiceUrl: getTxServiceHost(),
},
}
sendMessageToIframe(message) sendMessageToIframe(safeInfoMessage)
sendMessageToIframe(envInfoMessage)
break break
} }
default: { default: {

View File

@ -102,7 +102,7 @@ const Apps = (): React.ReactElement => {
) )
} }
const onUserTxReject = () => { const onTxReject = () => {
sendMessageToIframe( sendMessageToIframe(
{ messageId: INTERFACE_MESSAGES.TRANSACTION_REJECTED, data: {} }, { messageId: INTERFACE_MESSAGES.TRANSACTION_REJECTED, data: {} },
confirmTransactionModal.requestId, confirmTransactionModal.requestId,
@ -212,10 +212,9 @@ const Apps = (): React.ReactElement => {
ethBalance={ethBalance as string} ethBalance={ethBalance as string}
safeName={safeName as string} safeName={safeName as string}
txs={confirmTransactionModal.txs} txs={confirmTransactionModal.txs}
onCancel={closeConfirmationModal}
onClose={closeConfirmationModal} onClose={closeConfirmationModal}
onUserConfirm={onUserTxConfirm} onUserConfirm={onUserTxConfirm}
onUserTxReject={onUserTxReject} onTxReject={onTxReject}
/> />
</> </>
) )