Skip to content

Commit

Permalink
feat: add support for erc20 token approvals (#2565)
Browse files Browse the repository at this point in the history
* add token approval activity

* show approval activity in activity list

* show smart contract data for approval activities

* fix title

* fix: get confirmations from blockscout transaction

* rvert coin transfer generation

* revert rename

---------

Co-authored-by: Nicole O'Brien <[email protected]>
  • Loading branch information
MarkNerdi and nicole-obrien authored Jun 19, 2024
1 parent 2410a89 commit 6c3163b
Show file tree
Hide file tree
Showing 14 changed files with 138 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
tabs = getTabItems([PopupTab.Transaction])
break
case EvmActivityType.TokenTransfer:
case EvmActivityType.TokenApproval:
case EvmActivityType.TokenMinting:
case EvmActivityType.BalanceChange:
if (
Expand All @@ -32,7 +33,11 @@
} else {
tabs = getTabItems([PopupTab.Transaction])
}
if (activity.type === EvmActivityType.TokenTransfer || activity.type === EvmActivityType.TokenMinting) {
if (
activity.type === EvmActivityType.TokenTransfer ||
activity.type === EvmActivityType.TokenApproval ||
activity.type === EvmActivityType.TokenMinting
) {
tabs.push({ key: PopupTab.SmartContract, value: localize(`general.${PopupTab.SmartContract}`) })
}
break
Expand All @@ -54,7 +59,7 @@
<EvmGenericInformation {activity} />
{:else if selectedTab.key === PopupTab.NftMetadata && nft}
<NftMetadataTable {nft} />
{:else if selectedTab.key === PopupTab.SmartContract && (activity.type === EvmActivityType.ContractCall || activity.type === EvmActivityType.TokenTransfer || activity.type === EvmActivityType.TokenMinting)}
{:else if selectedTab.key === PopupTab.SmartContract && (activity.type === EvmActivityType.ContractCall || activity.type === EvmActivityType.TokenTransfer || activity.type === EvmActivityType.TokenApproval || activity.type === EvmActivityType.TokenMinting)}
<EvmSmartContractInformation {activity} />
{/if}
</activity-details>
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
<script lang="ts">
import { Table } from '@bloomwalletio/ui'
import { localize } from '@core/i18n'
import { EvmContractCallActivity, EvmTokenMintingActivity, EvmTokenTransferActivity } from '@core/activity'
import {
EvmContractCallActivity,
EvmTokenMintingActivity,
EvmTokenTransferActivity,
EvmTokenApprovalActivity,
} from '@core/activity'
import { openUrlInBrowser } from '@core/app'
import { ExplorerEndpoint, getExplorerUrl } from '@core/network'
export let activity: EvmContractCallActivity | EvmTokenTransferActivity | EvmTokenMintingActivity
export let activity:
| EvmContractCallActivity
| EvmTokenTransferActivity
| EvmTokenMintingActivity
| EvmTokenApprovalActivity
function onExplorerClick(address: string): void {
const url = getExplorerUrl(activity.destinationNetworkId, ExplorerEndpoint.Address, address)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export enum EvmActivityType {
BalanceChange = 'balanceChange',
TokenMinting = 'tokenMinting',
TokenTransfer = 'tokenTransfer',
TokenApproval = 'tokenApproval',
ContractCall = 'contractCall',
ContractCreation = 'contractCreation',
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { EvmBalanceChangeActivity } from './evm-balance-change-activity.type'
import { EvmCoinTransferActivity } from './evm-coin-transfer-activity.type'
import { EvmContractCallActivity } from './evm-contract-call-activity.type'
import { EvmTokenTransferActivity } from './evm-token-transfer-activity.type'
import { EvmTokenApprovalActivity } from './evm-token-approval-activity.type'
import { EvmTokenMintingActivity } from './evm-token-minting-activity.type'

export type EvmActivity =
| EvmCoinTransferActivity
| EvmTokenMintingActivity
| EvmTokenTransferActivity
| EvmTokenApprovalActivity
| EvmBalanceChangeActivity
| EvmContractCallActivity
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { EvmActivityType } from '@core/activity/enums/evm'
import { TokenStandard } from '@core/token'
import { NftStandard } from '@core/nfts'
import { BaseEvmActivity } from './base-evm-activity.type'
import { IParsedInput } from '@core/layer-2/interfaces'

export type EvmTokenApprovalActivity = BaseEvmActivity & {
type: EvmActivityType.TokenApproval
tokenTransfer: {
standard: TokenStandard.Erc20 | TokenStandard.Irc30 | NftStandard.Irc27 | NftStandard.Erc721
tokenId: string
rawAmount: bigint
}
rawData: string

methodId?: string
method?: string
inputs?: IParsedInput[]
}
1 change: 1 addition & 0 deletions packages/shared/src/lib/core/activity/types/evm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export * from './evm-activity.type'
export * from './evm-balance-change-activity.type'
export * from './evm-coin-transfer-activity.type'
export * from './evm-contract-call-activity.type'
export * from './evm-token-approval-activity.type'
export * from './evm-token-minting-activity.type'
export * from './evm-token-transfer-activity.type'
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { IAccountState } from '@core/account'
import { IEvmNetwork } from '@core/network'
import { LocalEvmTransaction, buildPersistedEvmTransactionFromBlockscoutTransaction } from '@core/transactions'
import { generateEvmActivityFromLocalEvmTransaction } from './generateEvmActivityFromLocalEvmTransaction'
import { BaseEvmActivity, EvmActivity, EvmCoinTransferActivity, EvmTokenTransferActivity } from '@core/activity/types'
import {
BaseEvmActivity,
EvmActivity,
EvmCoinTransferActivity,
EvmTokenApprovalActivity,
EvmTokenTransferActivity,
} from '@core/activity/types'
import { BASE_TOKEN_ID } from '@core/token'
import { generateBaseEvmActivity } from './generateBaseEvmActivity'
import { EvmActivityType } from '@core/activity/enums/evm'
Expand All @@ -19,20 +25,20 @@ export async function generateEvmActivityFromBlockscoutTransaction(
evmNetwork: IEvmNetwork,
account: IAccountState
): Promise<EvmActivity | undefined> {
if (blockscoutTransaction.tx_types.includes(BlockscoutTransactionType.CoinTransfer)) {
return generateEvmCoinTransferActivityFromBlockscoutTransaction(
blockscoutTransaction,
localTransaction,
evmNetwork,
account
)
} else if (
if (
blockscoutTransaction.tx_types.includes(BlockscoutTransactionType.TokenTransfer) ||
blockscoutTransaction.tx_types.includes(BlockscoutTransactionType.TokenMinting)
) {
// if it is a blockscout transaction and a token transfer we have already generated this activity
// there may be cases where thats not the case but we are unsure so we plan to add a fallback here at a lower priority
return undefined
} else if (blockscoutTransaction.tx_types.includes(BlockscoutTransactionType.CoinTransfer)) {
return generateEvmCoinTransferActivityFromBlockscoutTransaction(
blockscoutTransaction,
localTransaction,
evmNetwork,
account
)
} else if (blockscoutTransaction.tx_types.includes(BlockscoutTransactionType.ContractCall)) {
return generateEvmContractCallActivityFromBlockscoutTransaction(
blockscoutTransaction,
Expand Down Expand Up @@ -85,6 +91,16 @@ async function generateEvmContractCallActivityFromBlockscoutTransaction(
method: smartContractData.method,
inputs: smartContractData.inputs,
} as EvmTokenTransferActivity
case EvmActivityType.TokenApproval:
return {
...baseActivity,
type: EvmActivityType.TokenApproval,
tokenTransfer: smartContractData.tokenTransfer,
methodId,
method: smartContractData.method,
inputs: smartContractData.inputs,
direction: ActivityDirection.SelfTransaction,
} as EvmTokenApprovalActivity
case EvmActivityType.ContractCall:
default:
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
EvmActivity,
EvmCoinTransferActivity,
EvmContractCallActivity,
EvmTokenApprovalActivity,
EvmTokenTransferActivity,
} from '@core/activity/types'
import { parseSmartContractDataFromTransactionData } from '@core/layer-2/utils/parseSmartContractDataFromTransactionData'
Expand All @@ -15,16 +16,17 @@ import { SubjectType } from '@core/wallet'
import { generateBaseEvmActivity } from './generateBaseEvmActivity'
import { Converter } from '@core/utils'
import { ParsedSmartContractType } from '@core/layer-2'
import { ActivityDirection } from '@core/activity/enums'

export async function generateEvmActivityFromLocalEvmTransaction(
transaction: LocalEvmTransaction,
evmNetwork: IEvmNetwork,
account: IAccountState
): Promise<EvmActivity | undefined> {
let baseActivity = await generateBaseEvmActivity(transaction, evmNetwork, account)

if (!transaction.data) {
// i.e must be a coin transfer
const baseActivity = await generateBaseEvmActivity(transaction, evmNetwork, account)

return {
...baseActivity,
type: EvmActivityType.CoinTransfer,
Expand All @@ -44,7 +46,6 @@ export async function generateEvmActivityFromLocalEvmTransaction(
}

transaction.recipient = parsedData.recipientAddress ?? transaction.to?.toString().toLowerCase()
let baseActivity = await generateBaseEvmActivity(transaction, evmNetwork, account)

baseActivity = {
...baseActivity,
Expand Down Expand Up @@ -95,6 +96,17 @@ export async function generateEvmActivityFromLocalEvmTransaction(
rawAmount: BigInt(1),
},
} as EvmTokenTransferActivity
case ParsedSmartContractType.TokenApproval:
return {
...baseActivity,
type: EvmActivityType.TokenApproval,
tokenTransfer: {
standard: TokenStandard.Erc20,
tokenId: parsedData.tokenId,
rawAmount: parsedData.rawAmount,
},
direction: ActivityDirection.SelfTransaction,
} as EvmTokenApprovalActivity
default:
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,7 @@ export function getSmartContractDataFromBlockscoutTransaction(
}
| undefined

if (blockscoutTransaction.decoded_input) {
// if decoded input is available we know the method and parameters and contract is verified
const { method_id, method_call, parameters } = blockscoutTransaction.decoded_input
method = blockscoutTransaction.method
inputs = parameters

if (!getMethodFromRegistry(HEX_PREFIX + method_id)) {
const fourBytePrefix = HEX_PREFIX + method_id
addMethodToRegistry(fourBytePrefix, method_call)
}
} else if (blockscoutTransaction?.raw_input) {
if (blockscoutTransaction?.raw_input) {
const parsedData = parseSmartContractDataFromTransactionData(
{
to: blockscoutTransaction.to.hash.toLowerCase(),
Expand All @@ -64,6 +54,15 @@ export function getSmartContractDataFromBlockscoutTransaction(
case ParsedSmartContractType.TokenTransfer:
type = EvmActivityType.TokenTransfer

tokenTransfer = {
standard: parsedData.standard,
tokenId: parsedData.tokenId,
rawAmount: parsedData.rawAmount,
}
break
case ParsedSmartContractType.TokenApproval:
type = EvmActivityType.TokenApproval

tokenTransfer = {
standard: parsedData.standard,
tokenId: parsedData.tokenId,
Expand All @@ -85,5 +84,17 @@ export function getSmartContractDataFromBlockscoutTransaction(
}
}

if (blockscoutTransaction.decoded_input && !method && !inputs) {
// if decoded input is available we know the method and parameters and contract is verified
const { method_id, method_call, parameters } = blockscoutTransaction.decoded_input
method = blockscoutTransaction.method
inputs = parameters

if (!getMethodFromRegistry(HEX_PREFIX + method_id)) {
const fourBytePrefix = HEX_PREFIX + method_id
addMethodToRegistry(fourBytePrefix, method_call)
}
}

return { type, method, inputs, baseTokenTransfer, tokenTransfer }
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { truncateString } from '@core/utils'
import { getNameFromSubject } from './helper'
import { NetworkNamespace } from '@core/network/enums'
import { EvmActivityType } from '../enums/evm'
import { getTokenFromSelectedAccountTokens } from '@core/token/stores'

export async function getActivityDetailsTitle(activity: Activity): Promise<string> {
const localizationPrefix = 'popups.activityDetails.title'
Expand Down Expand Up @@ -67,6 +68,17 @@ export async function getActivityDetailsTitle(activity: Activity): Promise<strin
const displayedSubject = getNameFromSubject(activity.subject, true)

return localize('general.contractCall') + ` - ${displayedSubject}`
} else if (activity.type === EvmActivityType.TokenApproval) {
const key = `${localizationPrefix}.tokenApproval.${activity.inclusionState}`
const token = getTokenFromSelectedAccountTokens(activity.tokenTransfer.tokenId, activity.sourceNetworkId)

// We are looking for the spender by address because the input name does not have to be 'spender'
const spender = activity.inputs?.find((input) => input.type === 'address')?.value as string | undefined

return localize(key, {
address: spender ? truncateString(spender, 4, 6) : '',
assetName: token?.metadata?.name ?? '',
})
} else {
return localize(`${localizationPrefix}.fallback`)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export function getActivityTileAction(activity: Activity): string | undefined {
}
} else if (activity.type === EvmActivityType.TokenMinting) {
return 'general.minted'
} else if (activity.type === EvmActivityType.TokenApproval) {
return isConfirmed ? 'general.approved' : 'general.approving'
} else if (activity.type === EvmActivityType.ContractCall) {
return 'general.contractCall'
} else {
Expand Down
15 changes: 13 additions & 2 deletions packages/shared/src/lib/core/activity/utils/isEvmTokenActivity.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
import { NetworkNamespace } from '@core/network/enums'
import { EvmActivityType } from '../enums/evm'
import { Activity, EvmBalanceChangeActivity, EvmTokenMintingActivity, EvmTokenTransferActivity } from '../types'
import {
Activity,
EvmBalanceChangeActivity,
EvmTokenApprovalActivity,
EvmTokenMintingActivity,
EvmTokenTransferActivity,
} from '../types'

export function isEvmTokenActivity(
activity: Activity
): activity is EvmBalanceChangeActivity | EvmTokenMintingActivity | EvmTokenTransferActivity {
): activity is
| EvmBalanceChangeActivity
| EvmTokenMintingActivity
| EvmTokenTransferActivity
| EvmTokenApprovalActivity {
return (
activity.namespace === NetworkNamespace.Evm &&
(activity.type === EvmActivityType.TokenTransfer ||
activity.type === EvmActivityType.TokenMinting ||
activity.type === EvmActivityType.TokenApproval ||
activity.type === EvmActivityType.BalanceChange)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export function buildPersistedEvmTransactionFromBlockscoutTransaction(
transactionHash: blockscoutTransaction.hash,
transactionIndex: blockscoutTransaction.position,
blockNumber: blockscoutTransaction.block,
confirmations: blockscoutTransaction.confirmations,
from: blockscoutTransaction.from.hash.toLowerCase(),
to: blockscoutTransaction.to.hash.toLowerCase(),
gasUsed: Number(blockscoutTransaction.gas_used),
Expand Down
7 changes: 7 additions & 0 deletions packages/shared/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,11 @@
"Confirmed": "Burned",
"Conflicting": "Failed to burn"
},
"tokenApproval": {
"Pending": "Approving {address} to spend {assetName}",
"Confirmed": "Approved {address} to spend {assetName}",
"Conflicting": "Failed to approve to spend {assetName}"
},
"consolidation": {
"Pending": "Consolidating",
"Confirmed": "Consolidated",
Expand Down Expand Up @@ -1519,6 +1524,8 @@
"sending": "Sending",
"received": "Received",
"receiving": "Receiving",
"approving": "Approving",
"approved": "Approved",
"newVotingPower": "New voting power",
"votingPower": "Voting power",
"increased": "Voting power increased",
Expand Down

0 comments on commit 6c3163b

Please sign in to comment.