diff --git a/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx b/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx index 3afcb8ca1e..09a17f1894 100644 --- a/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx +++ b/src/components/tx/SignOrExecuteForm/ExecuteForm.tsx @@ -26,8 +26,6 @@ import commonCss from '@/components/tx-flow/common/styles.module.css' import { TxSecurityContext } from '../security/shared/TxSecurityContext' import useIsSafeOwner from '@/hooks/useIsSafeOwner' import NonOwnerError from '@/components/tx/SignOrExecuteForm/NonOwnerError' -import { useAppSelector } from '@/store' -import { selectQueuedTransactionById } from '@/store/txQueueSlice' const ExecuteForm = ({ safeTx, @@ -52,8 +50,6 @@ const ExecuteForm = ({ const { setTxFlow } = useContext(TxModalContext) const { needsRiskConfirmation, isRiskConfirmed, setIsRiskIgnored } = useContext(TxSecurityContext) - const tx = useAppSelector((state) => selectQueuedTransactionById(state, txId)) - // Check that the transaction is executable const isExecutionLoop = useIsExecutionLoop() @@ -89,7 +85,7 @@ const ExecuteForm = ({ const txOptions = getTxOptions(advancedParams, currentChain) try { - const executedTxId = await executeTx(txOptions, safeTx, txId, origin, willRelay, tx) + const executedTxId = await executeTx(txOptions, safeTx, txId, origin, willRelay) setTxFlow(, undefined, false) } catch (_err) { const err = asError(_err) diff --git a/src/components/tx/SignOrExecuteForm/SignForm.tsx b/src/components/tx/SignOrExecuteForm/SignForm.tsx index 7f9b48675f..01f6cc9215 100644 --- a/src/components/tx/SignOrExecuteForm/SignForm.tsx +++ b/src/components/tx/SignOrExecuteForm/SignForm.tsx @@ -14,8 +14,6 @@ import commonCss from '@/components/tx-flow/common/styles.module.css' import { TxSecurityContext } from '../security/shared/TxSecurityContext' import NonOwnerError from '@/components/tx/SignOrExecuteForm/NonOwnerError' import BatchButton from './BatchButton' -import { useAppSelector } from '@/store' -import { selectQueuedTransactionById } from '@/store/txQueueSlice' const SignForm = ({ safeTx, @@ -40,8 +38,6 @@ const SignForm = ({ const { needsRiskConfirmation, isRiskConfirmed, setIsRiskIgnored } = useContext(TxSecurityContext) const hasSigned = useAlreadySigned(safeTx) - const tx = useAppSelector((state) => selectQueuedTransactionById(state, txId)) - // On modal submit const handleSubmit = async (e: SyntheticEvent, isAddingToBatch = false) => { e.preventDefault() @@ -57,7 +53,7 @@ const SignForm = ({ setSubmitError(undefined) try { - await (isAddingToBatch ? addToBatch(safeTx, origin) : signTx(safeTx, txId, origin, tx)) + await (isAddingToBatch ? addToBatch(safeTx, origin) : signTx(safeTx, txId, origin)) } catch (_err) { const err = asError(_err) trackError(Errors._805, err) diff --git a/src/components/tx/SignOrExecuteForm/hooks.ts b/src/components/tx/SignOrExecuteForm/hooks.ts index a0a4a712c0..ca40563468 100644 --- a/src/components/tx/SignOrExecuteForm/hooks.ts +++ b/src/components/tx/SignOrExecuteForm/hooks.ts @@ -18,18 +18,17 @@ import type { OnboardAPI } from '@web3-onboard/core' import { getSafeTxGas, getRecommendedNonce } from '@/services/tx/tx-sender/recommendedNonce' import useAsync from '@/hooks/useAsync' import { useUpdateBatch } from '@/hooks/useDraftBatch' -import { type Transaction, type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk' +import { type TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk' type TxActions = { addToBatch: (safeTx?: SafeTransaction, origin?: string) => Promise - signTx: (safeTx?: SafeTransaction, txId?: string, origin?: string, transaction?: Transaction) => Promise + signTx: (safeTx?: SafeTransaction, txId?: string, origin?: string) => Promise executeTx: ( txOptions: TransactionOptions, safeTx?: SafeTransaction, txId?: string, origin?: string, isRelayed?: boolean, - transaction?: Transaction, ) => Promise } @@ -85,30 +84,28 @@ export const useTxActions = (): TxActions => { return await dispatchTxSigning(safeTx, version, onboard, chainId, txId) } - const signTx: TxActions['signTx'] = async (safeTx, txId, origin, transaction) => { + const signTx: TxActions['signTx'] = async (safeTx, txId, origin) => { assertTx(safeTx) assertWallet(wallet) assertOnboard(onboard) - const humanDescription = transaction?.transaction?.txInfo?.humanDescription - // Smart contract wallets must sign via an on-chain tx if (await isSmartContractWallet(wallet)) { // If the first signature is a smart contract wallet, we have to propose w/o signatures // Otherwise the backend won't pick up the tx // The signature will be added once the on-chain signature is indexed const id = txId || (await proposeTx(wallet.address, safeTx, txId, origin)).txId - await dispatchOnChainSigning(safeTx, id, onboard, chainId, humanDescription) + await dispatchOnChainSigning(safeTx, id, onboard, chainId) return id } // Otherwise, sign off-chain - const signedTx = await dispatchTxSigning(safeTx, version, onboard, chainId, txId, humanDescription) + const signedTx = await dispatchTxSigning(safeTx, version, onboard, chainId, txId) const tx = await proposeTx(wallet.address, signedTx, txId, origin) return tx.txId } - const executeTx: TxActions['executeTx'] = async (txOptions, safeTx, txId, origin, isRelayed, transaction) => { + const executeTx: TxActions['executeTx'] = async (txOptions, safeTx, txId, origin, isRelayed) => { assertTx(safeTx) assertWallet(wallet) assertOnboard(onboard) @@ -127,13 +124,11 @@ export const useTxActions = (): TxActions => { txId = tx.txId } - const humanDescription = tx?.txInfo?.humanDescription || transaction?.transaction?.txInfo?.humanDescription - // Relay or execute the tx via connected wallet if (isRelayed) { - await dispatchTxRelay(safeTx, safe, txId, txOptions.gasLimit, humanDescription) + await dispatchTxRelay(safeTx, safe, txId, txOptions.gasLimit) } else { - await dispatchTxExecution(safeTx, txOptions, txId, onboard, chainId, safeAddress, humanDescription) + await dispatchTxExecution(safeTx, txOptions, txId, onboard, chainId, safeAddress) } return txId diff --git a/src/hooks/useTxNotifications.ts b/src/hooks/useTxNotifications.ts index cf3924fb37..e2b5db3657 100644 --- a/src/hooks/useTxNotifications.ts +++ b/src/hooks/useTxNotifications.ts @@ -14,6 +14,7 @@ import useIsSafeOwner from '@/hooks/useIsSafeOwner' import useWallet from './wallets/useWallet' import useSafeAddress from './useSafeAddress' import { getExplorerLink } from '@/utils/gateway' +import { getTxDetails } from '@/services/tx/txDetails' const TxNotifications = { [TxEvent.SIGN_FAILED]: 'Failed to sign. Please try again.', @@ -69,15 +70,22 @@ const useTxNotifications = (): void => { const entries = Object.entries(TxNotifications) as [keyof typeof TxNotifications, string][] const unsubFns = entries.map(([event, baseMessage]) => - txSubscribe(event, (detail) => { + txSubscribe(event, async (detail) => { const isError = 'error' in detail const isSuccess = successEvents.includes(event) const message = isError ? `${baseMessage} ${formatError(detail.error)}` : baseMessage const txId = 'txId' in detail ? detail.txId : undefined const txHash = 'txHash' in detail ? detail.txHash : undefined const groupKey = 'groupKey' in detail && detail.groupKey ? detail.groupKey : txId || '' - const humanDescription = - 'humanDescription' in detail && detail.humanDescription ? detail.humanDescription : 'Transaction' + + let humanDescription = 'Transaction' + const id = txId || txHash + if (id) { + try { + const txDetails = await getTxDetails(chain.chainId, id) + humanDescription = txDetails.txInfo.humanDescription || humanDescription + } catch {} + } dispatch( showNotification({ diff --git a/src/hooks/useTxTracking.ts b/src/hooks/useTxTracking.ts index 1efc9adde2..d62aa118ce 100644 --- a/src/hooks/useTxTracking.ts +++ b/src/hooks/useTxTracking.ts @@ -1,23 +1,43 @@ import { trackEvent, WALLET_EVENTS } from '@/services/analytics' +import { getTxDetails } from '@/services/tx/txDetails' import { TxEvent, txSubscribe } from '@/services/tx/txEvents' import { useEffect } from 'react' +import useChainId from './useChainId' const events = { - [TxEvent.SIGNED]: WALLET_EVENTS.OFF_CHAIN_SIGNATURE, - [TxEvent.PROCESSING]: WALLET_EVENTS.ON_CHAIN_INTERACTION, - [TxEvent.RELAYING]: WALLET_EVENTS.RELAYED_EXECUTION, + [TxEvent.SIGNED]: WALLET_EVENTS.OFFCHAIN_SIGNATURE, + [TxEvent.PROCESSING]: WALLET_EVENTS.ONCHAIN_INTERACTION, + [TxEvent.PROCESSING_MODULE]: WALLET_EVENTS.ONCHAIN_INTERACTION, + [TxEvent.RELAYING]: WALLET_EVENTS.ONCHAIN_INTERACTION, } export const useTxTracking = (): void => { + const chainId = useChainId() + useEffect(() => { const unsubFns = Object.entries(events).map(([txEvent, analyticsEvent]) => - txSubscribe(txEvent as TxEvent, () => { - trackEvent(analyticsEvent) + txSubscribe(txEvent as TxEvent, async (detail) => { + const txId = 'txId' in detail ? detail.txId : undefined + const txHash = 'txHash' in detail ? detail.txHash : undefined + const id = txId || txHash + + let origin = '' + if (id) { + try { + const txDetails = await getTxDetails(chainId, id) + origin = txDetails.safeAppInfo?.url || '' + } catch {} + } + + trackEvent({ + ...analyticsEvent, + label: origin, + }) }), ) return () => { unsubFns.forEach((unsub) => unsub()) } - }, []) + }, [chainId]) } diff --git a/src/services/analytics/events/wallet.ts b/src/services/analytics/events/wallet.ts index d634562d68..00266e46ca 100644 --- a/src/services/analytics/events/wallet.ts +++ b/src/services/analytics/events/wallet.ts @@ -13,14 +13,12 @@ export const WALLET_EVENTS = { action: 'WalletConnect peer', category: WALLET_CATEGORY, }, - OFF_CHAIN_SIGNATURE: { + OFFCHAIN_SIGNATURE: { event: EventType.META, action: 'Off-chain signature', category: WALLET_CATEGORY, }, - // Please note that this event isn't triggered for any on-chain interaction. - // It's only triggered on safe tx execution and batch execution. - ON_CHAIN_INTERACTION: { + ONCHAIN_INTERACTION: { event: EventType.META, action: 'On-chain interaction', category: WALLET_CATEGORY, diff --git a/src/services/tx/tx-sender/dispatch.ts b/src/services/tx/tx-sender/dispatch.ts index a5a885d0e5..30f758e74f 100644 --- a/src/services/tx/tx-sender/dispatch.ts +++ b/src/services/tx/tx-sender/dispatch.ts @@ -63,7 +63,6 @@ export const dispatchTxProposal = async ({ txDispatch(txId ? TxEvent.SIGNATURE_PROPOSED : TxEvent.PROPOSED, { txId: proposedTx.txId, signerAddress: txId ? sender : undefined, - humanDescription: proposedTx?.txInfo?.humanDescription, }) } @@ -79,7 +78,6 @@ export const dispatchTxSigning = async ( onboard: OnboardAPI, chainId: SafeInfo['chainId'], txId?: string, - humanDescription?: string, ): Promise => { const sdk = await getSafeSDKWithSigner(onboard, chainId) @@ -90,7 +88,6 @@ export const dispatchTxSigning = async ( txDispatch(TxEvent.SIGN_FAILED, { txId, error: asError(error), - humanDescription, }) throw error } @@ -108,11 +105,10 @@ export const dispatchOnChainSigning = async ( txId: string, onboard: OnboardAPI, chainId: SafeInfo['chainId'], - humanDescription?: string, ) => { const sdkUnchecked = await getUncheckedSafeSDK(onboard, chainId) const safeTxHash = await sdkUnchecked.getTransactionHash(safeTx) - const eventParams = { txId, humanDescription } + const eventParams = { txId } try { // With the unchecked signer, the contract call resolves once the tx @@ -140,10 +136,9 @@ export const dispatchTxExecution = async ( onboard: OnboardAPI, chainId: SafeInfo['chainId'], safeAddress: string, - humanDescription?: string, ): Promise => { const sdkUnchecked = await getUncheckedSafeSDK(onboard, chainId) - const eventParams = { txId, humanDescription } + const eventParams = { txId } // Execute the tx let result: TransactionResult | undefined @@ -327,7 +322,6 @@ export const dispatchTxRelay = async ( safe: SafeInfo, txId: string, gasLimit?: string | number, - humanDescription?: string, ) => { const readOnlySafeContract = getReadOnlyCurrentGnosisSafeContract(safe) @@ -356,9 +350,9 @@ export const dispatchTxRelay = async ( txDispatch(TxEvent.RELAYING, { taskId, txId }) // Monitor relay tx - waitForRelayedTx(taskId, [txId], safe.address.value, humanDescription) + waitForRelayedTx(taskId, [txId], safe.address.value) } catch (error) { - txDispatch(TxEvent.FAILED, { txId, error: asError(error), humanDescription }) + txDispatch(TxEvent.FAILED, { txId, error: asError(error) }) throw error } } diff --git a/src/services/tx/txDetails.ts b/src/services/tx/txDetails.ts new file mode 100644 index 0000000000..79b40397d7 --- /dev/null +++ b/src/services/tx/txDetails.ts @@ -0,0 +1,15 @@ +import memoize from 'lodash/memoize' +import { getTransactionDetails } from '@safe-global/safe-gateway-typescript-sdk' + +/** + * Get and memoize transaction details from Safe Gatewa + * @param id Transaction id or hash + * @param chainId Chain id + * @returns Transaction details + */ +export const getTxDetails = memoize( + (id: string, chainId: string) => { + return getTransactionDetails(id, chainId) + }, + (id: string, chainId: string) => `${id}-${chainId}`, +) diff --git a/src/services/tx/txEvents.ts b/src/services/tx/txEvents.ts index 6e63e527b1..0adc2102de 100644 --- a/src/services/tx/txEvents.ts +++ b/src/services/tx/txEvents.ts @@ -24,26 +24,25 @@ export enum TxEvent { } type Id = { txId: string; groupKey?: string } | { txId?: string; groupKey: string } -type HumanDescription = { humanDescription?: string } interface TxEvents { [TxEvent.SIGNED]: { txId?: string } - [TxEvent.SIGN_FAILED]: HumanDescription & { txId?: string; error: Error } - [TxEvent.PROPOSE_FAILED]: HumanDescription & { error: Error } - [TxEvent.PROPOSED]: HumanDescription & { txId: string } - [TxEvent.SIGNATURE_PROPOSE_FAILED]: HumanDescription & { txId: string; error: Error } - [TxEvent.SIGNATURE_PROPOSED]: HumanDescription & { txId: string; signerAddress: string } + [TxEvent.SIGN_FAILED]: { txId?: string; error: Error } + [TxEvent.PROPOSE_FAILED]: { error: Error } + [TxEvent.PROPOSED]: { txId: string } + [TxEvent.SIGNATURE_PROPOSE_FAILED]: { txId: string; error: Error } + [TxEvent.SIGNATURE_PROPOSED]: { txId: string; signerAddress: string } [TxEvent.SIGNATURE_INDEXED]: { txId: string } - [TxEvent.ONCHAIN_SIGNATURE_REQUESTED]: Id & HumanDescription - [TxEvent.ONCHAIN_SIGNATURE_SUCCESS]: Id & HumanDescription - [TxEvent.EXECUTING]: Id & HumanDescription - [TxEvent.PROCESSING]: Id & HumanDescription & { txHash: string } - [TxEvent.PROCESSING_MODULE]: Id & HumanDescription & { txHash: string } - [TxEvent.PROCESSED]: Id & HumanDescription & { safeAddress: string } - [TxEvent.REVERTED]: Id & HumanDescription & { error: Error } + [TxEvent.ONCHAIN_SIGNATURE_REQUESTED]: Id + [TxEvent.ONCHAIN_SIGNATURE_SUCCESS]: Id + [TxEvent.EXECUTING]: Id + [TxEvent.PROCESSING]: Id & { txHash: string } + [TxEvent.PROCESSING_MODULE]: Id & { txHash: string } + [TxEvent.PROCESSED]: Id & { safeAddress: string } + [TxEvent.REVERTED]: Id & { error: Error } [TxEvent.RELAYING]: Id & { taskId: string } - [TxEvent.FAILED]: Id & HumanDescription & { error: Error } - [TxEvent.SUCCESS]: Id & HumanDescription + [TxEvent.FAILED]: Id & { error: Error } + [TxEvent.SUCCESS]: Id [TxEvent.SAFE_APPS_REQUEST]: { safeAppRequestId: RequestId; safeTxHash: string } [TxEvent.BATCH_ADD]: Id } diff --git a/src/services/tx/txMonitor.ts b/src/services/tx/txMonitor.ts index 878b47d61a..b75ac03f8d 100644 --- a/src/services/tx/txMonitor.ts +++ b/src/services/tx/txMonitor.ts @@ -91,13 +91,7 @@ const getRelayTxStatus = async (taskId: string): Promise<{ task: TransactionStat const WAIT_FOR_RELAY_TIMEOUT = 3 * 60_000 // 3 minutes -export const waitForRelayedTx = ( - taskId: string, - txIds: string[], - safeAddress: string, - groupKey?: string, - humanDescription?: string, -): void => { +export const waitForRelayedTx = (taskId: string, txIds: string[], safeAddress: string, groupKey?: string): void => { let intervalId: NodeJS.Timeout let failAfterTimeoutId: NodeJS.Timeout @@ -116,7 +110,6 @@ export const waitForRelayedTx = ( txId, groupKey, safeAddress, - humanDescription, }), ) break @@ -126,7 +119,6 @@ export const waitForRelayedTx = ( txId, error: new Error(`Relayed transaction reverted by EVM.`), groupKey, - humanDescription, }), ) break @@ -136,7 +128,6 @@ export const waitForRelayedTx = ( txId, error: new Error(`Relayed transaction was blacklisted by relay provider.`), groupKey, - humanDescription, }), ) break @@ -146,7 +137,6 @@ export const waitForRelayedTx = ( txId, error: new Error(`Relayed transaction was cancelled by relay provider.`), groupKey, - humanDescription, }), ) break @@ -156,7 +146,6 @@ export const waitForRelayedTx = ( txId, error: new Error(`Relayed transaction was not found.`), groupKey, - humanDescription, }), ) break @@ -179,7 +168,6 @@ export const waitForRelayedTx = ( } minutes. Be aware that it might still be relayed.`, ), groupKey, - humanDescription, }), ) diff --git a/src/store/txHistorySlice.ts b/src/store/txHistorySlice.ts index 4a771de1d3..d9717d0a3b 100644 --- a/src/store/txHistorySlice.ts +++ b/src/store/txHistorySlice.ts @@ -28,12 +28,9 @@ export const txHistoryListener = (listenerMiddleware: typeof listenerMiddlewareI const txId = result.transaction.id if (pendingTxs[txId]) { - const humanDescription = result.transaction.txInfo?.humanDescription - txDispatch(TxEvent.SUCCESS, { txId, groupKey: pendingTxs[txId].groupKey, - humanDescription, }) } } diff --git a/src/store/txQueueSlice.ts b/src/store/txQueueSlice.ts index 6ec1a50c27..5d84b62437 100644 --- a/src/store/txQueueSlice.ts +++ b/src/store/txQueueSlice.ts @@ -27,14 +27,6 @@ export const selectQueuedTransactionsByNonce = createSelector( }, ) -export const selectQueuedTransactionById = createSelector( - selectQueuedTransactions, - (_: RootState, txId?: string) => txId, - (queuedTransactions, txId?: string) => { - return (queuedTransactions || []).find((item) => item.transaction.id === txId) - }, -) - export const txQueueListener = (listenerMiddleware: typeof listenerMiddlewareInstance) => { listenerMiddleware.startListening({ actionCreator: txQueueSlice.actions.set,