From d503263240b618fff4746ea829f9350701a00c8d Mon Sep 17 00:00:00 2001 From: Mark Nardi Date: Wed, 22 May 2024 11:22:47 +0200 Subject: [PATCH 1/9] improve decoded smart contract data typing Co-authored-by: Nicole O'Brien --- .../enums/parsed-smart-contract-type.enum.ts | 1 + .../erc20-transfer-method-inputs.interface.ts | 12 -- ...1-safe-transfer-method-inputs.interface.ts | 22 ---- .../src/lib/core/layer-2/interfaces/index.ts | 4 - .../isc-call-method-inputs.interface.ts | 36 ------ .../isc-send-method-inputs.interface.ts | 52 --------- .../parsed-contract-data.interface.ts | 7 ++ .../lib/core/layer-2/types/erc20-abi.type.ts | 42 +++++++ .../lib/core/layer-2/types/erc721-abi.type.ts | 36 ++++++ .../src/lib/core/layer-2/types/index.ts | 3 + .../lib/core/layer-2/types/isc-abi.type.ts | 104 ++++++++++++++++++ .../types/parsed-smart-contract-data.type.ts | 2 + ...rseSmartContractDataFromTransactionData.ts | 46 ++++---- .../shared/src/lib/core/utils/abiDecoder.ts | 27 +---- 14 files changed, 224 insertions(+), 170 deletions(-) delete mode 100644 packages/shared/src/lib/core/layer-2/interfaces/erc20-transfer-method-inputs.interface.ts delete mode 100644 packages/shared/src/lib/core/layer-2/interfaces/erc721-safe-transfer-method-inputs.interface.ts delete mode 100644 packages/shared/src/lib/core/layer-2/interfaces/isc-call-method-inputs.interface.ts delete mode 100644 packages/shared/src/lib/core/layer-2/interfaces/isc-send-method-inputs.interface.ts create mode 100644 packages/shared/src/lib/core/layer-2/types/erc20-abi.type.ts create mode 100644 packages/shared/src/lib/core/layer-2/types/erc721-abi.type.ts create mode 100644 packages/shared/src/lib/core/layer-2/types/isc-abi.type.ts diff --git a/packages/shared/src/lib/core/layer-2/enums/parsed-smart-contract-type.enum.ts b/packages/shared/src/lib/core/layer-2/enums/parsed-smart-contract-type.enum.ts index 708a8ec94b..c2c4ba406d 100644 --- a/packages/shared/src/lib/core/layer-2/enums/parsed-smart-contract-type.enum.ts +++ b/packages/shared/src/lib/core/layer-2/enums/parsed-smart-contract-type.enum.ts @@ -3,4 +3,5 @@ export enum ParsedSmartContractType { TokenTransfer, NftTransfer, SmartContract, + TokenApproval, } diff --git a/packages/shared/src/lib/core/layer-2/interfaces/erc20-transfer-method-inputs.interface.ts b/packages/shared/src/lib/core/layer-2/interfaces/erc20-transfer-method-inputs.interface.ts deleted file mode 100644 index 527bf6aede..0000000000 --- a/packages/shared/src/lib/core/layer-2/interfaces/erc20-transfer-method-inputs.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface Erc20TransferMethodInputs { - _to: { - name: string - type: string - value: string - } - _value: { - name: string - type: string - value: string - } -} diff --git a/packages/shared/src/lib/core/layer-2/interfaces/erc721-safe-transfer-method-inputs.interface.ts b/packages/shared/src/lib/core/layer-2/interfaces/erc721-safe-transfer-method-inputs.interface.ts deleted file mode 100644 index 6a455e2430..0000000000 --- a/packages/shared/src/lib/core/layer-2/interfaces/erc721-safe-transfer-method-inputs.interface.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface Erc721SafeTransferMethodInputs { - from: { - name: string - type: string - value: string - } - to: { - name: string - type: string - value: string - } - tokenId: { - name: string - type: string - value: string - } - data?: { - name: string - type: string - value: string - } -} diff --git a/packages/shared/src/lib/core/layer-2/interfaces/index.ts b/packages/shared/src/lib/core/layer-2/interfaces/index.ts index 4fe67ed87a..720ae75647 100644 --- a/packages/shared/src/lib/core/layer-2/interfaces/index.ts +++ b/packages/shared/src/lib/core/layer-2/interfaces/index.ts @@ -1,12 +1,8 @@ export * from './erc20-evm-transaction-options.interface' -export * from './erc20-transfer-method-inputs.interface' -export * from './erc721-safe-transfer-method-inputs.interface' export * from './evm-address.interface' export * from './evm-signature.interface' export * from './evm-transaction-data.interface' export * from './gas-prices-by-speed.interface' -export * from './isc-call-method-inputs.interface' -export * from './isc-send-method-inputs.interface' export * from './layer-2-asset-allowance.interface' export * from './layer-2-profile-balance.interface' export * from './layer-2-send-metadata-parameter-parameters.interface' diff --git a/packages/shared/src/lib/core/layer-2/interfaces/isc-call-method-inputs.interface.ts b/packages/shared/src/lib/core/layer-2/interfaces/isc-call-method-inputs.interface.ts deleted file mode 100644 index d3ee7f6b8f..0000000000 --- a/packages/shared/src/lib/core/layer-2/interfaces/isc-call-method-inputs.interface.ts +++ /dev/null @@ -1,36 +0,0 @@ -export interface IscCallMethodInputs { - contractHname: { - name: string - type: string - value: string - } - entryPoint: { - name: string - type: string - value: string - } - params: { - name: string - type: string - value: { - items: { - key: string - value: string - }[] - } - } - allowance: { - name: string - type: string - value: { - baseTokens: string - nativeTokens: { - ID: { - data: string - } - amount: string - }[] - nfts: string[] - } - } -} diff --git a/packages/shared/src/lib/core/layer-2/interfaces/isc-send-method-inputs.interface.ts b/packages/shared/src/lib/core/layer-2/interfaces/isc-send-method-inputs.interface.ts deleted file mode 100644 index 1384ee1705..0000000000 --- a/packages/shared/src/lib/core/layer-2/interfaces/isc-send-method-inputs.interface.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ILayer2SendMetadataParameterParameters } from './layer-2-send-metadata-parameter-parameters.interface' -import { ILayer2SendOptionsParameter } from './layer-2-send-options-parameter.interface' - -export interface IscSendMethodInputs { - targetAddress: { - name: string - type: string - value: { - data: string - } - } - assets: { - name: string - type: string - value: { - baseTokens: string - nativeTokens: { - ID: { - data: string - } - amount: string - }[] - nfts: string[] - } - } - adjustMinimumStorageDeposit: boolean - metadata: { - name: string - type: string - value: { - targetContract: number - entrypoint: number - params: ILayer2SendMetadataParameterParameters[] - allowance: { - baseTokens: string - nativeTokens: { - ID: { - data: string - } - amount: string - }[] - nfts: string[] - } - gasBudget: number - } - } - sendOptions: { - name: string - type: string - value: ILayer2SendOptionsParameter - } -} diff --git a/packages/shared/src/lib/core/layer-2/interfaces/parsed-contract-data.interface.ts b/packages/shared/src/lib/core/layer-2/interfaces/parsed-contract-data.interface.ts index a134c4cb6e..f5c4a908e8 100644 --- a/packages/shared/src/lib/core/layer-2/interfaces/parsed-contract-data.interface.ts +++ b/packages/shared/src/lib/core/layer-2/interfaces/parsed-contract-data.interface.ts @@ -20,6 +20,13 @@ export interface IParsedNftTransfer extends Omit { + type: ParsedSmartContractType.TokenApproval + standard: TokenStandard.Erc20 + tokenId: string + rawAmount: bigint +} + export interface IParsedSmartContractData { type: ParsedSmartContractType.SmartContract rawMethod: string diff --git a/packages/shared/src/lib/core/layer-2/types/erc20-abi.type.ts b/packages/shared/src/lib/core/layer-2/types/erc20-abi.type.ts new file mode 100644 index 0000000000..8d9acb2de1 --- /dev/null +++ b/packages/shared/src/lib/core/layer-2/types/erc20-abi.type.ts @@ -0,0 +1,42 @@ +import { IParsedInput } from '../interfaces' + +type Erc20KnownContractTypes = 'transfer' | 'approve' + +interface Erc20TransferMethod { + name: 'transfer' + inputs: { + _to: { + name: string + type: string + value: string + } + _value: { + name: string + type: string + value: string + } + } +} + +interface Erc20ApproveMethod { + name: 'approve' + inputs: { + _spender: { + name: string + type: string + value: string + } + _value: { + name: string + type: string + value: string + } + } +} + +interface Erc20UnknownMethod { + name: Exclude + inputs: Record +} + +export type Erc20Abi = Erc20TransferMethod | Erc20ApproveMethod | Erc20UnknownMethod diff --git a/packages/shared/src/lib/core/layer-2/types/erc721-abi.type.ts b/packages/shared/src/lib/core/layer-2/types/erc721-abi.type.ts new file mode 100644 index 0000000000..ee37fa3a43 --- /dev/null +++ b/packages/shared/src/lib/core/layer-2/types/erc721-abi.type.ts @@ -0,0 +1,36 @@ +import { IParsedInput } from '../interfaces' + +type Erc721KnownContractTypes = 'safeTransferFrom' + +interface Erc721SafeTransferMethod { + name: 'safeTransferFrom' + inputs: { + from: { + name: string + type: string + value: string + } + to: { + name: string + type: string + value: string + } + tokenId: { + name: string + type: string + value: string + } + data?: { + name: string + type: string + value: string + } + } +} + +interface Erc721UnknwonMethod { + name: Exclude + inputs: Record +} + +export type Erc721Abi = Erc721SafeTransferMethod | Erc721UnknwonMethod diff --git a/packages/shared/src/lib/core/layer-2/types/index.ts b/packages/shared/src/lib/core/layer-2/types/index.ts index 9841e70a0d..f0f8846aba 100644 --- a/packages/shared/src/lib/core/layer-2/types/index.ts +++ b/packages/shared/src/lib/core/layer-2/types/index.ts @@ -1,5 +1,8 @@ +export * from './erc20-abi.type' +export * from './erc721-abi.type' export * from './evm-transaction-options.type' export * from './evm-transaction-data.type' +export * from './isc-abi.type' export * from './layer-2-account-balance.type' export * from './smart-contract.type' export * from './transferred-asset.type' diff --git a/packages/shared/src/lib/core/layer-2/types/isc-abi.type.ts b/packages/shared/src/lib/core/layer-2/types/isc-abi.type.ts new file mode 100644 index 0000000000..884457dfda --- /dev/null +++ b/packages/shared/src/lib/core/layer-2/types/isc-abi.type.ts @@ -0,0 +1,104 @@ +import { IParsedInput } from '../interfaces' +import { ILayer2SendMetadataParameterParameters } from '../interfaces/layer-2-send-metadata-parameter-parameters.interface' +import { ILayer2SendOptionsParameter } from '../interfaces/layer-2-send-options-parameter.interface' + +interface IscCallMethod { + name: 'call' + inputs: { + contractHname: { + name: string + type: string + value: string + } + entryPoint: { + name: string + type: string + value: string + } + params: { + name: string + type: string + value: { + items: { + key: string + value: string + }[] + } + } + allowance: { + name: string + type: string + value: { + baseTokens: string + nativeTokens: { + ID: { + data: string + } + amount: string + }[] + nfts: string[] + } + } + } +} + +interface IscSendMethod { + name: 'send' + inputs: { + targetAddress: { + name: string + type: string + value: { + data: string + } + } + assets: { + name: string + type: string + value: { + baseTokens: string + nativeTokens: { + ID: { + data: string + } + amount: string + }[] + nfts: string[] + } + } + adjustMinimumStorageDeposit: boolean + metadata: { + name: string + type: string + value: { + targetContract: number + entrypoint: number + params: ILayer2SendMetadataParameterParameters[] + allowance: { + baseTokens: string + nativeTokens: { + ID: { + data: string + } + amount: string + }[] + nfts: string[] + } + gasBudget: number + } + } + sendOptions: { + name: string + type: string + value: ILayer2SendOptionsParameter + } + } +} + +type IscKnownContractTypes = 'send' | 'call' + +interface IscUnknownMethod { + name: Exclude + inputs: Record +} +export type IscAbi = IscCallMethod | IscSendMethod | IscUnknownMethod diff --git a/packages/shared/src/lib/core/layer-2/types/parsed-smart-contract-data.type.ts b/packages/shared/src/lib/core/layer-2/types/parsed-smart-contract-data.type.ts index acf63a00de..0711de11cb 100644 --- a/packages/shared/src/lib/core/layer-2/types/parsed-smart-contract-data.type.ts +++ b/packages/shared/src/lib/core/layer-2/types/parsed-smart-contract-data.type.ts @@ -1,4 +1,5 @@ import { + IParsedTokenApproval, IParsedCoinTransfer, IParsedNftTransfer, IParsedSmartContractData, @@ -9,4 +10,5 @@ export type ParsedSmartContractData = | IParsedCoinTransfer | IParsedTokenTransfer | IParsedNftTransfer + | IParsedTokenApproval | IParsedSmartContractData diff --git a/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts b/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts index d67542176b..c0aa721630 100644 --- a/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts +++ b/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts @@ -4,20 +4,14 @@ import { AbiDecoder, HEX_PREFIX } from '@core/utils' import { isTrackedNftAddress, isTrackedTokenAddress } from '@core/wallet/actions' import { ERC20_ABI, ERC721_ABI, ISC_SANDBOX_ABI } from '../abis' import { ISC_BASE_COIN_ADDRESS, ISC_MAGIC_CONTRACT_ADDRESS } from '../constants' -import { - Erc20TransferMethodInputs, - Erc721SafeTransferMethodInputs, - IscCallMethodInputs, - IscSendMethodInputs, - IParsedMethod, - IParsedInput, -} from '../interfaces' +import { IParsedMethod, IParsedInput } from '../interfaces' import { BigIntLike, BytesLike } from '@ethereumjs/util' import { lookupMethodSignature } from './lookupMethodSignature' import { ParsedSmartContractData } from '../types/parsed-smart-contract-data.type' import { ParsedSmartContractType } from '../enums' import { TokenStandard } from '@core/token' import { NftStandard } from '@core/nfts' +import { Erc20Abi, Erc721Abi, IscAbi } from '../types' export function parseSmartContractDataFromTransactionData( transaction: { to?: string; data: BytesLike; value?: BigIntLike }, @@ -50,8 +44,8 @@ function parseSmartContractDataWithIscMagicAbi( data: string, recipientAddress: string ): ParsedSmartContractData | undefined { - const iscMagicDecoder = new AbiDecoder(ISC_SANDBOX_ABI, network.provider) - const decodedData = iscMagicDecoder.decodeData(data) // TODO: Type this return + const iscMagicDecoder = new AbiDecoder(ISC_SANDBOX_ABI, network.provider) + const decodedData = iscMagicDecoder.decodeData(data) if (!decodedData) { return undefined @@ -69,7 +63,7 @@ function parseSmartContractDataWithIscMagicAbi( return undefined } - const inputs = decodedData.inputs as unknown as IscCallMethodInputs + const inputs = decodedData.inputs const nativeToken = inputs?.allowance?.value?.nativeTokens?.[0] const nftId = inputs?.allowance?.value?.nfts?.[0] const agentId = inputs?.params?.value.items?.find((item) => item.key === '0x61')?.value @@ -106,7 +100,7 @@ function parseSmartContractDataWithIscMagicAbi( return undefined } - const inputs = decodedData.inputs as unknown as IscSendMethodInputs + const inputs = decodedData.inputs const assets = inputs.assets?.value const nativeToken = assets?.nativeTokens?.[0] @@ -157,8 +151,8 @@ function parseSmartContractDataWithErc20Abi( data: string, recipientAddress: string ): ParsedSmartContractData | undefined { - const erc20Decoder = new AbiDecoder(ERC20_ABI, network.provider) - const decodedData = erc20Decoder.decodeData(data) // TODO: Type this return + const erc20Decoder = new AbiDecoder(ERC20_ABI, network.provider) + const decodedData = erc20Decoder.decodeData(data) if (!decodedData) { return undefined @@ -172,19 +166,27 @@ function parseSmartContractDataWithErc20Abi( switch (decodedData.name) { case 'transfer': { - const inputs = decodedData.inputs as unknown as Erc20TransferMethodInputs - return { type: ParsedSmartContractType.TokenTransfer, standard: TokenStandard.Erc20, tokenId: recipientAddress, - rawAmount: BigInt(inputs._value.value), + rawAmount: BigInt(decodedData.inputs._value.value), + rawMethod, + parsedMethod, + recipientAddress: decodedData.inputs._to.value, + } + } + case 'approve': { + return { + type: ParsedSmartContractType.TokenApproval, + standard: TokenStandard.Erc20, + tokenId: recipientAddress, + rawAmount: BigInt(decodedData.inputs._value.value), rawMethod, parsedMethod, - recipientAddress: inputs._to.value, + recipientAddress: decodedData.inputs._spender.value, } } - // TODO: Support more ERC20 methods default: { return { type: ParsedSmartContractType.SmartContract, @@ -201,8 +203,8 @@ function parseSmartContractDataWithErc721Abi( data: string, recipientAddress: string ): ParsedSmartContractData | undefined { - const erc721Decoder = new AbiDecoder(ERC721_ABI, network.provider) - const decodedData = erc721Decoder.decodeData(data) // TODO: Type this return + const erc721Decoder = new AbiDecoder(ERC721_ABI, network.provider) + const decodedData = erc721Decoder.decodeData(data) if (!decodedData) { return undefined @@ -217,7 +219,7 @@ function parseSmartContractDataWithErc721Abi( switch (decodedData.name) { case 'safeTransferFrom': { // Enum? - const inputs = decodedData.inputs as unknown as Erc721SafeTransferMethodInputs + const inputs = decodedData.inputs return { type: ParsedSmartContractType.NftTransfer, diff --git a/packages/shared/src/lib/core/utils/abiDecoder.ts b/packages/shared/src/lib/core/utils/abiDecoder.ts index a4ba9b3b52..1e7cfca6c6 100644 --- a/packages/shared/src/lib/core/utils/abiDecoder.ts +++ b/packages/shared/src/lib/core/utils/abiDecoder.ts @@ -1,6 +1,7 @@ +import { Erc20Abi, Erc721Abi, IscAbi } from '@core/layer-2' import type { AbiEventFragment, AbiFunctionFragment, AbiInput, AbiParameter, ContractAbi, Web3 } from 'web3' -export class AbiDecoder { +export class AbiDecoder { public abi: Record public web3: Web3 @@ -28,19 +29,7 @@ export class AbiDecoder { this.web3 = _web3 } - public decodeData(data: string): - | { - name: string - inputs: Record< - string, - { - name: string - type: string - value: unknown - } - > - } - | undefined { + public decodeData(data: string): T | undefined { const functionSignature = data.slice(2, 10) const abiItem = this.abi[functionSignature] @@ -50,13 +39,7 @@ export class AbiDecoder { const decoded = this.web3.eth.abi.decodeParameters((abiItem.inputs as AbiInput[]) ?? [], data.slice(10)) - const inputs: { - [key: string]: { - name: string - type: string - value: unknown - } - } = {} + const inputs: T['inputs'] = {} for (let i = 0; i < decoded.__length__; i++) { const dataInput = decoded[i] const abiInput = abiItem.inputs?.[i] @@ -76,7 +59,7 @@ export class AbiDecoder { return { name: abiItem.name, inputs, - } + } as T } private parseInputParameter(input: AbiParameter, value: unknown): unknown { From 189ae4ce5ce9b1d20761bc0417ac9448d692b8f4 Mon Sep 17 00:00:00 2001 From: Mark Nardi Date: Wed, 22 May 2024 12:26:17 +0200 Subject: [PATCH 2/9] add token approval to evm transaction popup --- .../popups/EvmTransactionFromDappPopup.svelte | 132 ++++++++++-------- .../parsed-contract-data.interface.ts | 1 + ...rseSmartContractDataFromTransactionData.ts | 3 +- packages/shared/src/locales/en.json | 5 + 4 files changed, 83 insertions(+), 58 deletions(-) diff --git a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte index 67ecf7bc85..99120a6a52 100644 --- a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte +++ b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte @@ -11,7 +11,6 @@ import { GasSpeed, IGasPricesBySpeed, - IParsedInput, getHexEncodedTransaction, parseSmartContractDataFromTransactionData, } from '@core/layer-2' @@ -34,6 +33,8 @@ import { onDestroy, onMount } from 'svelte' import PopupTemplate from '../PopupTemplate.svelte' import { ParsedSmartContractType } from '@core/layer-2/enums/parsed-smart-contract-type.enum' + import { ParsedSmartContractData } from '@core/layer-2/types/parsed-smart-contract-data.type' + import { formatTokenAmount } from '@core/token/utils' export let preparedTransaction: EvmTransactionData export let evmNetwork: IEvmNetwork @@ -42,21 +43,13 @@ export let method: RpcMethod.EthSendTransaction | RpcMethod.EthSignTransaction | RpcMethod.EthSendRawTransaction export let callback: (params: CallbackParameters) => void - $: localeKey = - method === RpcMethod.EthSignTransaction - ? 'signTransaction' - : isSmartContractCall - ? 'smartContractCall' - : 'sendTransaction' - let nft: Nft | undefined let tokenTransfer: TokenTransferData | undefined let baseCoinTransfer: TokenTransferData | undefined - let isSmartContractCall = false - let methodName: string | undefined = undefined - let inputs: IParsedInput[] | undefined = undefined let busy = false + let parsedData: ParsedSmartContractData | undefined + let selectedGasSpeed = GasSpeed.Required let gasPrices: IGasPricesBySpeed = { [GasSpeed.Required]: Converter.bigIntLikeToBigInt(preparedTransaction?.gasPrice as number), @@ -79,7 +72,7 @@ return } - const parsedData = parseSmartContractDataFromTransactionData( + parsedData = parseSmartContractDataFromTransactionData( { to: preparedTransaction.to?.toString(), data: String(preparedTransaction.data), @@ -87,46 +80,23 @@ }, evmNetwork ) + } - methodName = parsedData?.parsedMethod?.name - inputs = parsedData?.parsedMethod?.inputs - - switch (parsedData?.type) { - case ParsedSmartContractType.CoinTransfer: { - const transfer = { - token: getTokenFromSelectedAccountTokens(BASE_TOKEN_ID, evmNetwork.id), - rawAmount: parsedData.rawAmount, - } as TokenTransferData - if (!transfer.token) { - return - } - - baseCoinTransfer = transfer - break - } - case ParsedSmartContractType.TokenTransfer: { - const transfer = { - token: getTokenFromSelectedAccountTokens(parsedData.tokenId, evmNetwork.id), - rawAmount: parsedData.rawAmount, - } as TokenTransferData - if (!transfer.token) { - return - } - - tokenTransfer = transfer - break - } - case ParsedSmartContractType.NftTransfer: { - nft = getNftByIdForAccount($selectedAccount?.index as number, parsedData.nftId) - break - } - case ParsedSmartContractType.SmartContract: { - isSmartContractCall = true - break - } - default: { - break - } + $: localeKey = getLocalKey(parsedData?.type) + function getLocalKey(type?: ParsedSmartContractType): string { + if (method === RpcMethod.EthSignTransaction) { + return 'signTransaction' + } + switch (type) { + case ParsedSmartContractType.CoinTransfer: + case ParsedSmartContractType.TokenTransfer: + case ParsedSmartContractType.NftTransfer: + return 'sendTransaction' + case ParsedSmartContractType.TokenApproval: + return 'tokenApproval' + case ParsedSmartContractType.SmartContract: + default: + return 'smartContractCall' } } @@ -191,7 +161,7 @@ const recipient = truncateString(String(preparedTransaction.to), 6, 6) const assetName = tokenTransfer?.token?.metadata?.name ?? baseCoinTransfer?.token?.metadata?.name ?? nft?.name ?? '' - return localize(`popups.${localeKey}.success`, { recipient, assetName }) + return localize(`popups.${localeKey}}.success`, { recipient, assetName }) } function onCancelClick(): void { @@ -238,7 +208,57 @@ />
- {#if isSmartContractCall} + {#if parsedData?.type === ParsedSmartContractType.CoinTransfer} + {@const baseCoinTransfer = { + token: getTokenFromSelectedAccountTokens(BASE_TOKEN_ID, evmNetwork.id), + rawAmount: parsedData.rawAmount, + }} + + {:else if parsedData?.type === ParsedSmartContractType.TokenTransfer} + {@const tokenTransfer = { + token: getTokenFromSelectedAccountTokens(parsedData.tokenId, evmNetwork.id), + rawAmount: parsedData.rawAmount, + }} + + {:else if parsedData?.type === ParsedSmartContractType.NftTransfer} + {@const nft = getNftByIdForAccount($selectedAccount?.index, parsedData.nftId)} + + {:else if parsedData?.type === ParsedSmartContractType.TokenApproval} + {@const { spender, rawAmount, tokenId } = parsedData} + {@const tokenTransfer = { + token: getTokenFromSelectedAccountTokens(tokenId, evmNetwork.id), + rawAmount: rawAmount, + }} + +
+ + + {localize('popups.smartContractCall.unableToVerify')} + onExplorerClick(String(preparedTransaction.to))} + text={localize('popups.smartContractCall.viewSmartContract')} + /> + + onExplorerClick(spender), + }, + { + key: localize('general.value'), + value: formatTokenAmount(rawAmount, tokenTransfer.token?.metadata), + copyable: true, + }, + ]} + /> + + + {:else if parsedData?.type === ParsedSmartContractType.SmartContract}
@@ -258,15 +278,13 @@ value: truncateString(String(preparedTransaction.to), 16, 16), onClick: () => onExplorerClick(String(preparedTransaction.to)), }, - { key: localize('general.methodName'), value: methodName }, - { key: localize('general.parameters'), value: inputs }, + { key: localize('general.methodName'), value: parsedData.parsedMethod?.name }, + { key: localize('general.parameters'), value: parsedData.parsedMethod?.inputs }, { key: localize('general.data'), value: String(preparedTransaction.data), copyable: true }, ]} />
- {:else} - {/if} { type: ParsedSmartContractType.TokenApproval standard: TokenStandard.Erc20 + spender: string tokenId: string rawAmount: bigint } diff --git a/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts b/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts index c0aa721630..185dc410e7 100644 --- a/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts +++ b/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts @@ -181,10 +181,11 @@ function parseSmartContractDataWithErc20Abi( type: ParsedSmartContractType.TokenApproval, standard: TokenStandard.Erc20, tokenId: recipientAddress, + spender: decodedData.inputs._spender.value, rawAmount: BigInt(decodedData.inputs._value.value), rawMethod, parsedMethod, - recipientAddress: decodedData.inputs._spender.value, + recipientAddress, } } default: { diff --git a/packages/shared/src/locales/en.json b/packages/shared/src/locales/en.json index 3fefe4d93b..bd783f6126 100644 --- a/packages/shared/src/locales/en.json +++ b/packages/shared/src/locales/en.json @@ -1094,6 +1094,11 @@ "action": "Sign", "success": "Signed transaction" }, + "tokenApproval": { + "title": "Approve {contractName} to spend {assetName}", + "action": "Approve", + "success": "Successfully approved" + }, "sendTransaction": { "title": "Send transaction", "action": "Send", From a7f6bfa811d2d30b0e54830ccde02294d6057a55 Mon Sep 17 00:00:00 2001 From: Mark Nardi Date: Wed, 22 May 2024 14:42:16 +0200 Subject: [PATCH 3/9] create evm alerts --- .../EvmTokenApprovalAlert.svelte | 30 +++++++++++++++++++ .../EvmTransactionAlert.svelte | 27 +++++++++++++++++ .../components/evm-transactions/index.ts | 2 ++ packages/desktop/components/index.ts | 1 + 4 files changed, 60 insertions(+) create mode 100644 packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte create mode 100644 packages/desktop/components/evm-transactions/EvmTransactionAlert.svelte create mode 100644 packages/desktop/components/evm-transactions/index.ts diff --git a/packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte b/packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte new file mode 100644 index 0000000000..eeb3831d5b --- /dev/null +++ b/packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte @@ -0,0 +1,30 @@ + + + + + diff --git a/packages/desktop/components/evm-transactions/EvmTransactionAlert.svelte b/packages/desktop/components/evm-transactions/EvmTransactionAlert.svelte new file mode 100644 index 0000000000..75e6ad5713 --- /dev/null +++ b/packages/desktop/components/evm-transactions/EvmTransactionAlert.svelte @@ -0,0 +1,27 @@ + + + + + {message} + onExplorerClick(contractAddress)} + text={localize('popups.smartContractCall.viewSmartContract')} + /> + + + diff --git a/packages/desktop/components/evm-transactions/index.ts b/packages/desktop/components/evm-transactions/index.ts new file mode 100644 index 0000000000..80ec42f64a --- /dev/null +++ b/packages/desktop/components/evm-transactions/index.ts @@ -0,0 +1,2 @@ +export { default as EvmTokenApprovalAlert } from './EvmTokenApprovalAlert.svelte' +export { default as EvmTransactionAlert } from './EvmTransactionAlert.svelte' diff --git a/packages/desktop/components/index.ts b/packages/desktop/components/index.ts index 84ac1d67d9..6a09f1f733 100644 --- a/packages/desktop/components/index.ts +++ b/packages/desktop/components/index.ts @@ -1,4 +1,5 @@ export * from './drawers' +export * from './evm-transactions' export * from './filter' export * from './menus' export * from './modals' From 668733170f8915b6305337418e9e18c299ceae55 Mon Sep 17 00:00:00 2001 From: Mark Nardi Date: Wed, 22 May 2024 14:43:01 +0200 Subject: [PATCH 4/9] add title --- .../popups/EvmTransactionFromDappPopup.svelte | 69 ++++++------------- packages/shared/src/locales/en.json | 3 +- 2 files changed, 24 insertions(+), 48 deletions(-) diff --git a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte index 99120a6a52..12f56004ad 100644 --- a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte +++ b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte @@ -2,7 +2,7 @@ import { DappVerification, RpcMethod } from '@auxiliary/wallet-connect/enums' import { IConnectedDapp } from '@auxiliary/wallet-connect/interface' import { CallbackParameters } from '@auxiliary/wallet-connect/types' - import { Alert, Link, Table, Text } from '@bloomwalletio/ui' + import { Table } from '@bloomwalletio/ui' import { IAccountState } from '@core/account' import { getSelectedAccount, selectedAccount } from '@core/account/stores' import { openUrlInBrowser } from '@core/app' @@ -29,12 +29,12 @@ import { PopupId, closePopup, modifyPopupState, openPopup } from '@desktop/auxiliary/popup' import { LegacyTransaction } from '@ethereumjs/tx' import { DappInfo, TransactionAssetSection } from '@ui' + import { EvmTransactionAlert, EvmTokenApprovalAlert } from '@components' import { EvmTransactionDetails } from '@views/dashboard/send-flow/views/components' import { onDestroy, onMount } from 'svelte' import PopupTemplate from '../PopupTemplate.svelte' import { ParsedSmartContractType } from '@core/layer-2/enums/parsed-smart-contract-type.enum' import { ParsedSmartContractData } from '@core/layer-2/types/parsed-smart-contract-data.type' - import { formatTokenAmount } from '@core/token/utils' export let preparedTransaction: EvmTransactionData export let evmNetwork: IEvmNetwork @@ -100,6 +100,17 @@ } } + $: title = getTitle(parsedData) + function getTitle(data?: ParsedSmartContractData): string { + const locale = `popups.${localeKey}.title` + + if (data?.type === ParsedSmartContractType.TokenApproval) { + const token = getTokenFromSelectedAccountTokens(data.tokenId, evmNetwork.id) + return localize(locale, { dappName: dapp.metadata?.name, assetName: token?.metadata?.name }) + } + return localize(locale) + } + async function getSignedTransaction(account: IAccountState): Promise { if (preparedTransaction?.v && preparedTransaction?.s && preparedTransaction?.r) { const transaction = LegacyTransaction.fromTxData(preparedTransaction) @@ -185,9 +196,7 @@ {:else if parsedData?.type === ParsedSmartContractType.TokenApproval} - {@const { spender, rawAmount, tokenId } = parsedData} - {@const tokenTransfer = { - token: getTokenFromSelectedAccountTokens(tokenId, evmNetwork.id), - rawAmount: rawAmount, - }} -
- - - {localize('popups.smartContractCall.unableToVerify')} - onExplorerClick(String(preparedTransaction.to))} - text={localize('popups.smartContractCall.viewSmartContract')} - /> - -
onExplorerClick(spender), - }, - { - key: localize('general.value'), - value: formatTokenAmount(rawAmount, tokenTransfer.token?.metadata), - copyable: true, - }, - ]} - /> - + {:else if parsedData?.type === ParsedSmartContractType.SmartContract}
- - - {localize('popups.smartContractCall.unableToVerify')} - onExplorerClick(String(preparedTransaction.to))} - text={localize('popups.smartContractCall.viewSmartContract')} - /> - +
- + {/if} Date: Wed, 22 May 2024 15:12:02 +0200 Subject: [PATCH 5/9] cleanup --- .../popups/EvmTransactionFromDappPopup.svelte | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte index 68fb3b4baa..92787668f4 100644 --- a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte +++ b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte @@ -17,14 +17,12 @@ import { EvmTransactionData } from '@core/layer-2/types' import { LedgerAppName } from '@core/ledger' import { ExplorerEndpoint, IEvmNetwork, getExplorerUrl } from '@core/network' - import { Nft } from '@core/nfts' import { getNftByIdForAccount } from '@core/nfts/stores' import { checkActiveProfileAuth } from '@core/profile/actions' import { getActiveProfileId } from '@core/profile/stores' import { BASE_TOKEN_ID } from '@core/token/constants' import { getTokenFromSelectedAccountTokens } from '@core/token/stores' import { Converter, MILLISECONDS_PER_SECOND, truncateString } from '@core/utils' - import { TokenTransferData } from '@core/wallet' import { sendAndPersistTransactionFromEvm, signEvmTransaction } from '@core/wallet/actions' import { PopupId, closePopup, modifyPopupState, openPopup } from '@desktop/auxiliary/popup' import { LegacyTransaction } from '@ethereumjs/tx' @@ -43,9 +41,6 @@ export let method: RpcMethod.EthSendTransaction | RpcMethod.EthSignTransaction | RpcMethod.EthSendRawTransaction export let callback: (params: CallbackParameters) => void - let nft: Nft | undefined - let tokenTransfer: TokenTransferData | undefined - let baseCoinTransfer: TokenTransferData | undefined let busy = false let parsedData: ParsedSmartContractData | undefined @@ -62,13 +57,9 @@ } $: preparedTransaction.gasPrice = Converter.bigIntToHex(gasPrices?.[selectedGasSpeed] ?? gasPrices.required) - setTransactionInformation() - function setTransactionInformation(): void { + setParsedContractData() + function setParsedContractData(): void { if (!preparedTransaction.data) { - baseCoinTransfer = { - token: getTokenFromSelectedAccountTokens(BASE_TOKEN_ID, evmNetwork.id), - rawAmount: Converter.bigIntLikeToBigInt(preparedTransaction.value), - } as TokenTransferData return } @@ -154,10 +145,15 @@ modifyPopupState({ preventClose: false }, true) busy = false + + const successMessage = localize(`popups.${localeKey}}.success`, { + recipient: truncateString(String(preparedTransaction.to), 6, 6), + assetName: getAssetName(), + }) openPopup({ id: PopupId.SuccessfulDappInteraction, props: { - successMessage: getSuccessMessage(), + successMessage, dapp, }, }) @@ -168,11 +164,17 @@ } } - function getSuccessMessage(): string { - const recipient = truncateString(String(preparedTransaction.to), 6, 6) - const assetName = - tokenTransfer?.token?.metadata?.name ?? baseCoinTransfer?.token?.metadata?.name ?? nft?.name ?? '' - return localize(`popups.${localeKey}}.success`, { recipient, assetName }) + function getAssetName(): string { + switch (parsedData?.type) { + case ParsedSmartContractType.CoinTransfer: + return getTokenFromSelectedAccountTokens(BASE_TOKEN_ID, evmNetwork.id)?.metadata?.name ?? '' + case ParsedSmartContractType.TokenTransfer: + return getTokenFromSelectedAccountTokens(parsedData.tokenId, evmNetwork.id)?.metadata?.name ?? '' + case ParsedSmartContractType.NftTransfer: + return getNftByIdForAccount($selectedAccount?.index, parsedData.nftId)?.metadata?.name ?? '' + default: + return '' + } } function onCancelClick(): void { @@ -217,7 +219,13 @@ />
- {#if parsedData?.type === ParsedSmartContractType.CoinTransfer} + {#if !preparedTransaction.data} + {@const baseCoinTransfer = { + token: getTokenFromSelectedAccountTokens(BASE_TOKEN_ID, evmNetwork.id), + rawAmount: Converter.bigIntLikeToBigInt(preparedTransaction.value), + }} + + {:else if parsedData?.type === ParsedSmartContractType.CoinTransfer} {@const baseCoinTransfer = { token: getTokenFromSelectedAccountTokens(BASE_TOKEN_ID, evmNetwork.id), rawAmount: parsedData.rawAmount, From d50617ee95353aac063ec70cf83d438174b4ff64 Mon Sep 17 00:00:00 2001 From: Nicole O'Brien Date: Thu, 23 May 2024 10:50:26 +0100 Subject: [PATCH 6/9] enhancement: add fallback if token isn't currently owned --- .../EvmTokenApprovalAlert.svelte | 38 +++++++++++++++---- .../popups/EvmTransactionFromDappPopup.svelte | 5 ++- packages/shared/src/locales/en.json | 3 +- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte b/packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte index eeb3831d5b..945a7d712e 100644 --- a/packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte +++ b/packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte @@ -1,30 +1,54 @@ - + {#if tokenTransfer.token} + + {:else} +
onExplorerClick(tokenId), + }, + { key: localize('general.spender'), value: spender, onClick: () => onExplorerClick(spender) }, + { key: localize('general.amount'), value: String(rawAmount) }, + ]} + /> + {/if} diff --git a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte index 92787668f4..037295feeb 100644 --- a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte +++ b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte @@ -97,7 +97,10 @@ if (data?.type === ParsedSmartContractType.TokenApproval) { const token = getTokenFromSelectedAccountTokens(data.tokenId, evmNetwork.id) - return localize(locale, { dappName: dapp.metadata?.name, assetName: token?.metadata?.name }) + return localize(locale, { + dappName: dapp.metadata?.name, + assetName: token?.metadata?.name ?? truncateString(data.tokenId, 6, 6), + }) } return localize(locale) } diff --git a/packages/shared/src/locales/en.json b/packages/shared/src/locales/en.json index b6d1b51424..da9a6eae6a 100644 --- a/packages/shared/src/locales/en.json +++ b/packages/shared/src/locales/en.json @@ -1734,7 +1734,8 @@ "location": "Location", "slow": "Slow", "average": "Average", - "fast": "Fast" + "fast": "Fast", + "spender": "Spender" }, "filters":{ "title": "Filters", From e70c1b203bcc37ffc7a80da7d9c463ec01bb753c Mon Sep 17 00:00:00 2001 From: Nicole O'Brien Date: Thu, 23 May 2024 11:01:54 +0100 Subject: [PATCH 7/9] fix: success screen message --- .../components/popup/popups/EvmTransactionFromDappPopup.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte index 037295feeb..183a66f92a 100644 --- a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte +++ b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte @@ -149,7 +149,7 @@ modifyPopupState({ preventClose: false }, true) busy = false - const successMessage = localize(`popups.${localeKey}}.success`, { + const successMessage = localize(`popups.${localeKey}.success`, { recipient: truncateString(String(preparedTransaction.to), 6, 6), assetName: getAssetName(), }) From 52e3483cea5203ba478a01e829c4b4f1b140ba7f Mon Sep 17 00:00:00 2001 From: Nicole O'Brien Date: Thu, 23 May 2024 11:12:36 +0100 Subject: [PATCH 8/9] fix: add final fallback to parsing smart contract data --- .../utils/parseSmartContractDataFromTransactionData.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts b/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts index 185dc410e7..4f8f0e88cc 100644 --- a/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts +++ b/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts @@ -279,6 +279,10 @@ function parseSmartContractDataWithMethodRegistry( parsedMethod: { name, inputs }, } } catch (error) { - return undefined + return { + type: ParsedSmartContractType.SmartContract, + recipientAddress, + rawMethod: fourBytePrefix, + } } } From 4f07e6c31c0c57b51d2417c39408a250ab8de58e Mon Sep 17 00:00:00 2001 From: Nicole O'Brien Date: Thu, 23 May 2024 11:17:59 +0100 Subject: [PATCH 9/9] fix: dapp title --- .../components/popup/popups/EvmTransactionFromDappPopup.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte index 183a66f92a..7fba01cac5 100644 --- a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte +++ b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte @@ -102,7 +102,8 @@ assetName: token?.metadata?.name ?? truncateString(data.tokenId, 6, 6), }) } - return localize(locale) + + return localize(locale, { contractAddress: truncateString(String(preparedTransaction.to), 6, 6) }) } async function getSignedTransaction(account: IAccountState): Promise {