From 10a70d83261d13d0dd077ddcb4c9f2280342dd72 Mon Sep 17 00:00:00 2001 From: Kyrylo Klymenko Date: Tue, 7 Jan 2025 12:21:26 -0500 Subject: [PATCH 1/3] Leverage Viem's decodeFunctionData when Safe Decoder fails --- src/hooks/utils/useSafeDecoder.tsx | 75 ++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/src/hooks/utils/useSafeDecoder.tsx b/src/hooks/utils/useSafeDecoder.tsx index af2b379ef..5e4fee29f 100644 --- a/src/hooks/utils/useSafeDecoder.tsx +++ b/src/hooks/utils/useSafeDecoder.tsx @@ -1,6 +1,8 @@ import axios from 'axios'; +import detectProxyTarget from 'evm-proxy-detection'; import { useCallback } from 'react'; -import { Address, encodePacked, keccak256 } from 'viem'; +import { Address, decodeFunctionData, encodePacked, Hex, keccak256 } from 'viem'; +import { usePublicClient } from 'wagmi'; import { useNetworkConfigStore } from '../../providers/NetworkConfig/useNetworkConfigStore'; import { DecodedTransaction, DecodedTxParam } from '../../types'; import { buildSafeApiUrl, parseMultiSendTransactions } from '../../utils'; @@ -10,7 +12,8 @@ import { DBObjectKeys, useIndexedDB } from './cache/useLocalDB'; * Handles decoding and caching transactions via the Safe API. */ export const useSafeDecoder = () => { - const { safeBaseURL } = useNetworkConfigStore(); + const client = usePublicClient(); + const { safeBaseURL, etherscanAPIUrl } = useNetworkConfigStore(); const [setValue, getValue] = useIndexedDB(DBObjectKeys.DECODED_TRANSACTIONS); const decode = useCallback( async (value: string, to: Address, data?: string): Promise => { @@ -19,7 +22,7 @@ export const useSafeDecoder = () => { return [ { target: to, - value: value, + value, function: 'n/a', parameterTypes: ['n/a'], parameterValues: ['n/a'], @@ -35,29 +38,63 @@ export const useSafeDecoder = () => { let decoded: DecodedTransaction | DecodedTransaction[]; try { - const decodedData = ( - await axios.post(buildSafeApiUrl(safeBaseURL, '/data-decoder/'), { - to: to, - data: data, - }) - ).data; - if (decodedData.parameters && decodedData.method === 'multiSend') { - const internalTransactionsMap = new Map(); - parseMultiSendTransactions(internalTransactionsMap, decodedData.parameters); - decoded = [...internalTransactionsMap.values()].flat(); - } else { + try { + const decodedData = ( + await axios.post(buildSafeApiUrl(safeBaseURL, '/data-decoder/'), { + to, + data, + }) + ).data; + if (decodedData.parameters && decodedData.method === 'multiSend') { + const internalTransactionsMap = new Map(); + parseMultiSendTransactions(internalTransactionsMap, decodedData.parameters); + decoded = [...internalTransactionsMap.values()].flat(); + } else { + decoded = [ + { + target: to, + value, + function: decodedData.method, + parameterTypes: decodedData.parameters.map((param: DecodedTxParam) => param.type), + parameterValues: decodedData.parameters.map((param: DecodedTxParam) => param.value), + decodingFailed: false, + }, + ]; + } + } catch (e) { + console.error('Error decoding transaction using Safe API. Trying to decode with ABI', e); + if (!client) { + throw new Error('Client not found'); + } + const requestFunc = ({ method, params }: { method: any; params: any }) => + client.request({ method, params }); + const proxy = await detectProxyTarget(to, requestFunc); + const response = await axios.get( + `${etherscanAPIUrl}&module=contract&action=getabi&address=${proxy || to}`, + ); + const responseData = response.data; + const abi = JSON.parse(responseData.result); + const decodedData = decodeFunctionData({ + abi, + data: data as Hex, + }); + const functionAbi = abi.find((abiItem: any) => abiItem.name === decodedData.functionName); decoded = [ { target: to, - value: value, - function: decodedData.method, - parameterTypes: decodedData.parameters.map((param: DecodedTxParam) => param.type), - parameterValues: decodedData.parameters.map((param: DecodedTxParam) => param.value), + value, + function: decodedData.functionName, + parameterTypes: functionAbi.inputs.map((input: any) => input.type), + parameterValues: decodedData.args, decodingFailed: false, }, ]; } } catch (e) { + console.error( + 'Error decoding transaction using ABI. Returning empty decoded transaction', + e, + ); return [ { target: to, @@ -77,7 +114,7 @@ export const useSafeDecoder = () => { return decoded; }, - [getValue, safeBaseURL, setValue], + [getValue, safeBaseURL, etherscanAPIUrl, setValue, client], ); return decode; }; From 14584db106fcd3353d3d4fd05b54513314937454 Mon Sep 17 00:00:00 2001 From: Kyrylo Klymenko Date: Tue, 7 Jan 2025 12:21:26 -0500 Subject: [PATCH 2/3] proxy -> implementationAddress --- src/components/ui/forms/ABISelector.tsx | 4 ++-- src/hooks/utils/useSafeDecoder.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/ui/forms/ABISelector.tsx b/src/components/ui/forms/ABISelector.tsx index bfbd9e64f..62272903e 100644 --- a/src/components/ui/forms/ABISelector.tsx +++ b/src/components/ui/forms/ABISelector.tsx @@ -38,10 +38,10 @@ export default function ABISelector({ target, onChange }: IABISelector) { const requestFunc = ({ method, params }: { method: any; params: any }) => client.request({ method, params }); - const proxy = await detectProxyTarget(ensAddress || target, requestFunc); + const implementationAddress = await detectProxyTarget(ensAddress || target, requestFunc); const response = await axios.get( - `${etherscanAPIUrl}&module=contract&action=getabi&address=${proxy || ensAddress || target}`, + `${etherscanAPIUrl}&module=contract&action=getabi&address=${implementationAddress || ensAddress || target}`, ); const responseData = response.data; diff --git a/src/hooks/utils/useSafeDecoder.tsx b/src/hooks/utils/useSafeDecoder.tsx index 5e4fee29f..2863092c0 100644 --- a/src/hooks/utils/useSafeDecoder.tsx +++ b/src/hooks/utils/useSafeDecoder.tsx @@ -68,9 +68,9 @@ export const useSafeDecoder = () => { } const requestFunc = ({ method, params }: { method: any; params: any }) => client.request({ method, params }); - const proxy = await detectProxyTarget(to, requestFunc); + const implementationAddress = await detectProxyTarget(to, requestFunc); const response = await axios.get( - `${etherscanAPIUrl}&module=contract&action=getabi&address=${proxy || to}`, + `${etherscanAPIUrl}&module=contract&action=getabi&address=${implementationAddress || to}`, ); const responseData = response.data; const abi = JSON.parse(responseData.result); From 9741e2cd2c6bbc8e317121aed7f7f611c4451e95 Mon Sep 17 00:00:00 2001 From: Adam Gall Date: Tue, 7 Jan 2025 12:25:11 -0500 Subject: [PATCH 3/3] Bump version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 89b338932..16d3cce2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "decent-interface", - "version": "0.4.0", + "version": "0.4.1", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index 89ccd68d4..b7f838077 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "decent-interface", - "version": "0.4.0", + "version": "0.4.1", "private": true, "dependencies": { "@amplitude/analytics-browser": "^2.11.1",