// Copyright 2023 @soul-wallet/extension-koni authors & contributors // SPDX-License-Identifier: Apache-2.0 import { _ChainInfo } from '@soul-wallet/chain-list/types' import { EvmTransactionArg, NestedArray, ParseEvmTransactionData, ResponseParseEvmContractInput, ResponseQrParseRLP } from '../../../background/KoniTypes' import { _ERC20_ABI, _ERC721_ABI } from '@soul-wallet/extension-base/services/chain-service/helper' import { _EvmApi } from '@soul-wallet/extension-base/services/chain-service/types' import { _getEvmAbiExplorer, _getEvmChainId, _isChainEvmCompatible } from '../../../services/chain-service/utils' import { createTransactionFromRLP, Transaction as QrTransaction } from '@soul-wallet/extension-base/utils/eth' import { InputDataDecoder } from '@soul-wallet/extension-base/utils/eth/parseTransaction/base' import axios from 'axios' import BigN from 'bignumber.js' import { t } from 'i18next' // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const ABIs = [_ERC20_ABI, _ERC721_ABI]; const genName = (name: NestedArray): string => { if (typeof name === 'string') { return name; } else { if (Array.isArray(name[1])) { const _name = name[0] as string; const children = genName(name[1]); return `${_name}(${children})`; } else { return name.join(', '); } } }; const genInput = (input: NestedArray): string => { if (Array.isArray(input)) { const arr: string[] = input.map(genInput); return `[${arr.join(', ')}]`; } else { // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-return return input.toString(); } }; const parseType = (_types: string): NestedArray => { const types = _types.trim(); if (types.indexOf('(') !== 0) { if (!types.includes(',')) { return types.trim(); } else { const arr: string[] = []; let s = types; do { if (s.indexOf('(') === 0) { const start = s.indexOf('('); const end = s.lastIndexOf(')'); const _new = s.slice(start, end + 1); arr.push(_new); s = s.replace(_new, ''); } else { const start = s.indexOf(','); if (start !== -1) { const str = s.slice(0, start); arr.push(str); s = s.slice(start + 1).trim(); } else { arr.push(s); s = ''; } } } while (s.length); return arr.map((s) => s.trim()); } } else { const start = types.indexOf('('); const end = types.lastIndexOf(')'); const _new = types.slice(start + 1, end); return parseType(_new); } }; const parseResult = (type: string, input: NestedArray, name: NestedArray): EvmTransactionArg => { const types = parseType(type); if (Array.isArray(types)) { const inputs = input as NestedArray[]; const _name = (name as NestedArray[])[0] as string; const names = (name as NestedArray[])[1]; const children: EvmTransactionArg[] = []; types.forEach((type, index) => { children.push(parseResult(type as string, inputs[index], names[index])); }); return { type: type, name: _name, value: genInput(input), children: children }; } else { return { type: types, name: genName(name), value: genInput(input) }; } }; export const isContractAddress = async (address: string, evmApi: _EvmApi): Promise => { if (!evmApi) { return false; } else { const code = await evmApi.api.eth.getCode(address); return code !== '0x'; } }; const parseInputWithAbi = (input: string, abi: any): ParseEvmTransactionData | null => { const decoder = new InputDataDecoder(abi); const raw = decoder.decodeData(input); if (raw.method && raw.methodName) { const temp: ParseEvmTransactionData = { method: raw.method, methodName: raw.methodName, args: [] }; raw.types.forEach((type, index) => { temp.args.push(parseResult(type, raw.inputs[index], raw.names[index])); }); return temp; } else { return null; } }; export const parseContractInput = async (input: string, contractAddress: string, network: _ChainInfo | null): Promise => { for (const abi of ABIs) { const temp = parseInputWithAbi(input, abi); if (temp) { return { result: temp }; } } if (contractAddress && network) { if (_getEvmAbiExplorer(network)) { try { const res = await axios.get(_getEvmAbiExplorer(network), { params: { address: contractAddress }, timeout: 3000 }); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (res.status === 200 && res.data.status === '1') { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const abi = res.data.result; const temp = parseInputWithAbi(input, abi); if (temp) { return { result: temp }; } } } catch (e) {} } } return { result: input }; }; const getChainInfoByChainId = (networkMap: Record, chainId: number): _ChainInfo | null => { if (!chainId) { for (const n in networkMap) { if (!Object.prototype.hasOwnProperty.call(networkMap, n)) { continue; } const networkInfo = networkMap[n]; if (_isChainEvmCompatible(networkInfo)) { return networkInfo; } } return null; } for (const n in networkMap) { if (!Object.prototype.hasOwnProperty.call(networkMap, n)) { continue; } const networkInfo = networkMap[n]; if (_getEvmChainId(networkInfo) === chainId) { return networkInfo; } } return null; }; export const parseEvmRlp = async (data: string, networkMap: Record, evmApiMap: Record): Promise => { const tx: QrTransaction | null = createTransactionFromRLP(data); if (!tx) { throw new Error(t('Failed to decode data. Please use a valid QR code')); } const result: ResponseQrParseRLP = { input: tx.data, data: tx.data, gasPrice: new BigN(tx.gasPrice).toNumber(), gas: new BigN(tx.gas).toNumber(), to: tx.to, value: new BigN(tx.value).toNumber(), nonce: new BigN(tx.nonce).toNumber() }; const network: _ChainInfo | null = getChainInfoByChainId(networkMap, parseInt(tx.ethereumChainId)); for (const abi of ABIs) { const temp = parseInputWithAbi(tx.data, abi); if (temp) { result.data = temp; return result; } } if (tx.to && network) { if (await isContractAddress(tx.to, evmApiMap[network.slug])) { if (_getEvmAbiExplorer(network) !== '') { try { const res = await axios.get(_getEvmAbiExplorer(network), { params: { address: tx.to }, timeout: 2000 }); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (res.status === 200 && res.data.status === '1') { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const abi = res.data.result; const temp = parseInputWithAbi(tx.data, abi); if (temp) { result.data = temp; return result; } } } catch (e) {} } } } return result; };