diff --git a/packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte b/packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte new file mode 100644 index 0000000000..945a7d712e --- /dev/null +++ b/packages/desktop/components/evm-transactions/EvmTokenApprovalAlert.svelte @@ -0,0 +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/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' diff --git a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte index 2865d4186f..7fba01cac5 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' @@ -11,29 +11,28 @@ import { GasSpeed, IGasPricesBySpeed, - IParsedInput, getHexEncodedTransaction, parseSmartContractDataFromTransactionData, } from '@core/layer-2' 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' 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' export let preparedTransaction: EvmTransactionData export let evmNetwork: IEvmNetwork @@ -42,21 +41,10 @@ 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: Record | undefined = undefined let busy = false + let parsedData: ParsedSmartContractData | undefined + let selectedGasSpeed = GasSpeed.Required let gasPrices: IGasPricesBySpeed = { [GasSpeed.Required]: Converter.bigIntLikeToBigInt(preparedTransaction?.gasPrice as number), @@ -69,17 +57,13 @@ } $: 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 } - const parsedData = parseSmartContractDataFromTransactionData( + parsedData = parseSmartContractDataFromTransactionData( { to: preparedTransaction.to?.toString(), data: String(preparedTransaction.data), @@ -87,50 +71,39 @@ }, evmNetwork ) + } - methodName = parsedData?.parsedMethod?.name - inputs = parsedData?.parsedMethod?.inputs.reduce((acc, input: IParsedInput) => { - acc[input.name] = input.value - return acc - }, {}) - - switch (parsedData?.type) { - case ParsedSmartContractType.CoinTransfer: { - const transfer = { - token: getTokenFromSelectedAccountTokens(BASE_TOKEN_ID, evmNetwork.id), - rawAmount: parsedData.rawAmount, - } as TokenTransferData - if (!transfer.token) { - return - } + $: 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' + } + } - baseCoinTransfer = transfer - break - } - case ParsedSmartContractType.TokenTransfer: { - const transfer = { - token: getTokenFromSelectedAccountTokens(parsedData.tokenId, evmNetwork.id), - rawAmount: parsedData.rawAmount, - } as TokenTransferData - if (!transfer.token) { - return - } + $: title = getTitle(parsedData) + function getTitle(data?: ParsedSmartContractData): string { + const locale = `popups.${localeKey}.title` - tokenTransfer = transfer - break - } - case ParsedSmartContractType.NftTransfer: { - nft = getNftByIdForAccount($selectedAccount?.index as number, parsedData.nftId) - break - } - case ParsedSmartContractType.SmartContract: { - isSmartContractCall = true - break - } - default: { - break - } + if (data?.type === ParsedSmartContractType.TokenApproval) { + const token = getTokenFromSelectedAccountTokens(data.tokenId, evmNetwork.id) + return localize(locale, { + dappName: dapp.metadata?.name, + assetName: token?.metadata?.name ?? truncateString(data.tokenId, 6, 6), + }) } + + return localize(locale, { contractAddress: truncateString(String(preparedTransaction.to), 6, 6) }) } async function getSignedTransaction(account: IAccountState): Promise { @@ -176,10 +149,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, }, }) @@ -190,11 +168,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 { @@ -218,9 +202,7 @@
- {#if isSmartContractCall} + {#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, + }} + + {: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} +
+ +
+ {:else if parsedData?.type === ParsedSmartContractType.SmartContract}
- - - {localize('popups.smartContractCall.unableToVerify')} - onExplorerClick(String(preparedTransaction.to))} - text={localize('popups.smartContractCall.viewSmartContract')} - /> - +
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.reduce((acc, input) => { + acc[input.name] = input.value + return acc + }, {}), + }, { key: localize('general.data'), value: String(preparedTransaction.data), copyable: true }, ]} /> - + - {:else} - {/if} { + type: ParsedSmartContractType.TokenApproval + standard: TokenStandard.Erc20 + spender: string + 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..4f8f0e88cc 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,28 @@ 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: inputs._to.value, + recipientAddress: decodedData.inputs._to.value, + } + } + case 'approve': { + return { + type: ParsedSmartContractType.TokenApproval, + standard: TokenStandard.Erc20, + tokenId: recipientAddress, + spender: decodedData.inputs._spender.value, + rawAmount: BigInt(decodedData.inputs._value.value), + rawMethod, + parsedMethod, + recipientAddress, } } - // TODO: Support more ERC20 methods default: { return { type: ParsedSmartContractType.SmartContract, @@ -201,8 +204,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 +220,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, @@ -276,6 +279,10 @@ function parseSmartContractDataWithMethodRegistry( parsedMethod: { name, inputs }, } } catch (error) { - return undefined + return { + type: ParsedSmartContractType.SmartContract, + recipientAddress, + rawMethod: fourBytePrefix, + } } } 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 { diff --git a/packages/shared/src/locales/en.json b/packages/shared/src/locales/en.json index 3fefe4d93b..da9a6eae6a 100644 --- a/packages/shared/src/locales/en.json +++ b/packages/shared/src/locales/en.json @@ -1094,6 +1094,12 @@ "action": "Sign", "success": "Signed transaction" }, + "tokenApproval": { + "title": "Approve {dappName} to access {assetName}", + "hint": "{address} is requesting approval to spend {assetName}", + "action": "Approve", + "success": "Successfully approved" + }, "sendTransaction": { "title": "Send transaction", "action": "Send", @@ -1728,7 +1734,8 @@ "location": "Location", "slow": "Slow", "average": "Average", - "fast": "Fast" + "fast": "Fast", + "spender": "Spender" }, "filters":{ "title": "Filters",