diff --git a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte index b1823034d6..d2e7d08e26 100644 --- a/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte +++ b/packages/desktop/components/popup/popups/EvmTransactionFromDappPopup.svelte @@ -11,20 +11,20 @@ import { EvmTransactionData } from '@core/layer-2/types' import { EvmTransactionDetails } from '@views/dashboard/send-flow/views/components' import { + IParsedInput, + ParsedSmartContractType, calculateEstimatedGasFeeFromTransactionData, calculateMaxGasFeeFromTransactionData, getHexEncodedTransaction, - getMethodForEvmTransaction, + parseSmartContractDataFromTransactionData, } from '@core/layer-2' import { getTokenFromSelectedAccountTokens } from '@core/token/stores' - import { getTransferInfoFromTransactionData } from '@core/layer-2/utils/getTransferInfoFromTransactionData' import { TokenTransferData } from '@core/wallet' import { Nft } from '@core/nfts' import { Alert, Link, Table, Text } from '@bloomwalletio/ui' import { PopupId, closePopup, modifyPopupState, openPopup } from '@desktop/auxiliary/popup' - import { truncateString } from '@core/utils' + import { Converter, truncateString } from '@core/utils' import { openUrlInBrowser } from '@core/app' - import { StardustActivityType } from '@core/activity' import { BASE_TOKEN_ID } from '@core/token/constants' import { checkActiveProfileAuth } from '@core/profile/actions' import { LedgerAppName } from '@core/ledger' @@ -53,34 +53,61 @@ let baseCoinTransfer: TokenTransferData | undefined let isSmartContractCall = false let methodName: string | undefined = undefined - let parameters: Record | undefined = undefined + let inputs: IParsedInput[] | undefined = undefined let busy = false - setTokenTransfer() - function setTokenTransfer(): void { - const transferInfo = getTransferInfoFromTransactionData(preparedTransaction, evmNetwork) - switch (transferInfo?.type) { - case StardustActivityType.Basic: { + setTransactionInformation() + function setTransactionInformation(): void { + if (!preparedTransaction.data) { + baseCoinTransfer = { + token: getTokenFromSelectedAccountTokens(BASE_TOKEN_ID, evmNetwork.id), + rawAmount: Converter.bigIntLikeToBigInt(preparedTransaction.value), + } as TokenTransferData + return + } + + const parsedData = parseSmartContractDataFromTransactionData( + { + to: preparedTransaction.to?.toString(), + data: String(preparedTransaction.data), + value: preparedTransaction.value, + }, + evmNetwork + ) + + methodName = parsedData?.parsedMethod?.name + inputs = parsedData?.parsedMethod?.inputs + + switch (parsedData?.type) { + case ParsedSmartContractType.CoinTransfer: { const transfer = { - token: getTokenFromSelectedAccountTokens(transferInfo.tokenId, evmNetwork.id), - rawAmount: transferInfo.rawAmount, + token: getTokenFromSelectedAccountTokens(BASE_TOKEN_ID, evmNetwork.id), + rawAmount: parsedData.rawAmount, } as TokenTransferData if (!transfer.token) { return } - if (transferInfo.tokenId === BASE_TOKEN_ID) { - baseCoinTransfer = transfer - } else { - tokenTransfer = transfer + 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 StardustActivityType.Nft: { - nft = getNftByIdForAccount($selectedAccount.index, transferInfo.nftId) + case ParsedSmartContractType.NftTransfer: { + nft = getNftByIdForAccount($selectedAccount?.index as number, parsedData.nftId) break } - case StardustActivityType.SmartContract: { + case ParsedSmartContractType.SmartContract: { isSmartContractCall = true break } @@ -147,13 +174,6 @@ } } - $: void setMethodName(preparedTransaction) - function setMethodName(preparedTransaction: EvmTransactionData): void { - const [method, _parameters] = getMethodForEvmTransaction(String(preparedTransaction.data ?? '')) ?? [] - methodName = method - parameters = _parameters - } - function getSuccessMessage(): string { const recipient = truncateString(String(preparedTransaction.to), 6, 6) const assetName = @@ -216,7 +236,7 @@ onClick: () => onExplorerClick(String(preparedTransaction.to)), }, { key: localize('general.methodName'), value: methodName }, - { key: localize('general.parameters'), value: parameters }, + { key: localize('general.parameters'), value: inputs }, { key: localize('general.data'), value: String(preparedTransaction.data), copyable: true }, ]} /> diff --git a/packages/desktop/components/popup/popups/SyncAccountsPopup.svelte b/packages/desktop/components/popup/popups/SyncAccountsPopup.svelte index fed8ada116..c62141e6c9 100644 --- a/packages/desktop/components/popup/popups/SyncAccountsPopup.svelte +++ b/packages/desktop/components/popup/popups/SyncAccountsPopup.svelte @@ -75,7 +75,7 @@ isDepthSearch = true recoverAccountsPayload = { accountStartIndex: isSingleAccountSearch ? $selectedAccountIndex : accountGapLimit, - accountGapLimit: 0, + accountGapLimit: 1, addressGapLimit: isSingleAccountSearch ? addressGapLimit : (searchCount - depthSearchCount) * addressGapLimit, diff --git a/packages/desktop/views/dashboard/campaigns/views/CampaignsGalleryView.svelte b/packages/desktop/views/dashboard/campaigns/views/CampaignsGalleryView.svelte index d60c5e194a..5fe72d3d9f 100644 --- a/packages/desktop/views/dashboard/campaigns/views/CampaignsGalleryView.svelte +++ b/packages/desktop/views/dashboard/campaigns/views/CampaignsGalleryView.svelte @@ -80,7 +80,7 @@ startTime: campaign.startTime, endTime: campaign.endTime, url: campaign.url, - chainId: campaign.evmNetwork, + chainId: campaign.chain, listingStatus: campaign.listingStatus, ERC20Reward: campaign.ERC20Reward, } as ICampaign diff --git a/packages/desktop/views/onboarding/views/restore-profile/views/BalanceFinderView.svelte b/packages/desktop/views/onboarding/views/restore-profile/views/BalanceFinderView.svelte index e347257156..fdc6b09d92 100644 --- a/packages/desktop/views/onboarding/views/restore-profile/views/BalanceFinderView.svelte +++ b/packages/desktop/views/onboarding/views/restore-profile/views/BalanceFinderView.svelte @@ -45,7 +45,7 @@ const recoverAccountsPayload: RecoverAccountsPayload = { accountStartIndex, accountGapLimit, - addressGapLimit: 0, + addressGapLimit: 1, syncOptions: { ...DEFAULT_SYNC_OPTIONS, addressStartIndex: 0 }, } @@ -72,7 +72,7 @@ depthSearch = true recoverAccountsPayload = { accountStartIndex: accountGapLimit, - accountGapLimit: 0, + accountGapLimit: 1, addressGapLimit: (searchCount - depthSearchCount) * addressGapLimit, syncOptions: { ...DEFAULT_SYNC_OPTIONS, addressStartIndex: 0 }, } diff --git a/packages/shared/package.json b/packages/shared/package.json index e3fedc49f9..644347ef3c 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -5,7 +5,7 @@ "author": "Bloom Labs Ltd ", "license": "PolyForm Strict License 1.0.0", "dependencies": { - "@bloomwalletio/ui": "0.20.22", + "@bloomwalletio/ui": "0.21.0", "@ethereumjs/common": "4.3.0", "@ethereumjs/rlp": "5.0.2", "@ethereumjs/tx": "5.3.0", diff --git a/packages/shared/src/components/activities/evm/info/EvmSmartContractInformation.svelte b/packages/shared/src/components/activities/evm/info/EvmSmartContractInformation.svelte index 10585f1736..e4b19d47ae 100644 --- a/packages/shared/src/components/activities/evm/info/EvmSmartContractInformation.svelte +++ b/packages/shared/src/components/activities/evm/info/EvmSmartContractInformation.svelte @@ -41,7 +41,7 @@ }, { key: localize('general.inputs'), - value: activity.parameters, + value: activity.inputs, }, { key: localize('general.data'), diff --git a/packages/shared/src/lib/auxiliary/blockscout/interfaces/blockscout-transaction.interface.ts b/packages/shared/src/lib/auxiliary/blockscout/interfaces/blockscout-transaction.interface.ts index 6d0eb2fa4a..ba931643d2 100644 --- a/packages/shared/src/lib/auxiliary/blockscout/interfaces/blockscout-transaction.interface.ts +++ b/packages/shared/src/lib/auxiliary/blockscout/interfaces/blockscout-transaction.interface.ts @@ -22,7 +22,7 @@ export interface IWatchlistName { interface IDecodedInput { method_call: string method_id: string - parameters: Record // IDecodedInputParameters + parameters: { name: string; type: string; value: unknown }[] // IDecodedInputParameters } interface ITokenTransfer { diff --git a/packages/shared/src/lib/core/activity/types/evm/evm-contract-call-activity.type.ts b/packages/shared/src/lib/core/activity/types/evm/evm-contract-call-activity.type.ts index 6e00dfb465..572461e293 100644 --- a/packages/shared/src/lib/core/activity/types/evm/evm-contract-call-activity.type.ts +++ b/packages/shared/src/lib/core/activity/types/evm/evm-contract-call-activity.type.ts @@ -1,5 +1,6 @@ import { EvmActivityType } from '@core/activity/enums/evm' import { BaseEvmActivity } from './base-evm-activity.type' +import { IParsedInput } from '@core/layer-2' export type EvmContractCallActivity = BaseEvmActivity & { type: EvmActivityType.ContractCall @@ -7,5 +8,5 @@ export type EvmContractCallActivity = BaseEvmActivity & { methodId?: string method?: string - parameters?: Record + inputs?: IParsedInput[] } diff --git a/packages/shared/src/lib/core/activity/types/evm/evm-token-minting-activity.type.ts b/packages/shared/src/lib/core/activity/types/evm/evm-token-minting-activity.type.ts index cbbb9033bb..c689f73f90 100644 --- a/packages/shared/src/lib/core/activity/types/evm/evm-token-minting-activity.type.ts +++ b/packages/shared/src/lib/core/activity/types/evm/evm-token-minting-activity.type.ts @@ -2,6 +2,7 @@ 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 EvmTokenMintingActivity = BaseEvmActivity & { type: EvmActivityType.TokenMinting @@ -14,5 +15,5 @@ export type EvmTokenMintingActivity = BaseEvmActivity & { methodId?: string method?: string - parameters?: Record + inputs?: IParsedInput[] } diff --git a/packages/shared/src/lib/core/activity/types/evm/evm-token-transfer-activity.type.ts b/packages/shared/src/lib/core/activity/types/evm/evm-token-transfer-activity.type.ts index 6c8f42c4c7..011346a7a1 100644 --- a/packages/shared/src/lib/core/activity/types/evm/evm-token-transfer-activity.type.ts +++ b/packages/shared/src/lib/core/activity/types/evm/evm-token-transfer-activity.type.ts @@ -2,6 +2,7 @@ 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 EvmTokenTransferActivity = BaseEvmActivity & { type: EvmActivityType.TokenTransfer @@ -14,5 +15,5 @@ export type EvmTokenTransferActivity = BaseEvmActivity & { methodId?: string method?: string - parameters?: Record + inputs?: IParsedInput[] } diff --git a/packages/shared/src/lib/core/activity/utils/evm/generateEvmActivityFromBlockscoutTransaction.ts b/packages/shared/src/lib/core/activity/utils/evm/generateEvmActivityFromBlockscoutTransaction.ts index a3de1961f7..b3abe6e15e 100644 --- a/packages/shared/src/lib/core/activity/utils/evm/generateEvmActivityFromBlockscoutTransaction.ts +++ b/packages/shared/src/lib/core/activity/utils/evm/generateEvmActivityFromBlockscoutTransaction.ts @@ -3,16 +3,15 @@ import { IAccountState } from '@core/account' import { IEvmNetwork } from '@core/network' import { LocalEvmTransaction, buildPersistedEvmTransactionFromBlockscoutTransaction } from '@core/transactions' import { generateEvmActivityFromLocalEvmTransaction } from './generateEvmActivityFromLocalEvmTransaction' -import { BaseEvmActivity, EvmActivity, EvmCoinTransferActivity } from '@core/activity/types' +import { BaseEvmActivity, EvmActivity, EvmCoinTransferActivity, EvmTokenTransferActivity } from '@core/activity/types' import { BASE_TOKEN_ID } from '@core/token' import { generateBaseEvmActivity } from './generateBaseEvmActivity' import { EvmActivityType } from '@core/activity/enums/evm' -import { Converter, HEX_PREFIX } from '@core/utils' +import { Converter } from '@core/utils' import { EvmContractCallActivity } from '@core/activity/types/evm/evm-contract-call-activity.type' import { SubjectType } from '@core/wallet' import { ActivityDirection } from '@core/activity/enums' -import { getMethodForEvmTransaction } from '@core/layer-2' -import { addMethodToRegistry, getMethodFromRegistry } from '@core/layer-2/stores/method-registry.store' +import { getSmartContractDataFromBlockscoutTransaction } from './getSmartContractDataFromBlockscoutTransaction' export async function generateEvmActivityFromBlockscoutTransaction( blockscoutTransaction: IBlockscoutTransaction, @@ -52,7 +51,7 @@ async function generateEvmContractCallActivityFromBlockscoutTransaction( localTransaction: LocalEvmTransaction | undefined, evmNetwork: IEvmNetwork, account: IAccountState -): Promise { +): Promise { const baseActivity = await generateBaseEvmActivityFromBlockscoutTransaction( blockscoutTransaction, localTransaction, @@ -60,34 +59,43 @@ async function generateEvmContractCallActivityFromBlockscoutTransaction( account ) - let method: string | undefined - let parameters: Record | 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: _parameters } = blockscoutTransaction.decoded_input - method = blockscoutTransaction.method - parameters = _parameters + const methodId = blockscoutTransaction.decoded_input?.method_id ?? blockscoutTransaction.method // `method` is the methodId if the inputs cannot be decoded + const rawData = blockscoutTransaction.raw_input - if (!getMethodFromRegistry(HEX_PREFIX + method_id)) { - const fourBytePrefix = HEX_PREFIX + method_id - addMethodToRegistry(fourBytePrefix, method_call) - } - } else { - const [_method, _parameters] = getMethodForEvmTransaction(blockscoutTransaction.raw_input) ?? [] - method = _method - parameters = _parameters - } + const smartContractData = blockscoutTransaction + ? getSmartContractDataFromBlockscoutTransaction(blockscoutTransaction, evmNetwork) + : undefined - return { - ...baseActivity, - type: EvmActivityType.ContractCall, - verified: blockscoutTransaction.to.is_verified, - methodId: blockscoutTransaction.decoded_input?.method_id ?? blockscoutTransaction.method, // `method` is the methodId if the inputs cannot be decoded - method, - parameters: parameters, - rawData: blockscoutTransaction.raw_input, - contractAddress: blockscoutTransaction.to?.hash.toLowerCase(), - } as EvmContractCallActivity + switch (smartContractData?.type) { + case EvmActivityType.CoinTransfer: + return { + ...baseActivity, + type: EvmActivityType.CoinTransfer, + baseTokenTransfer: smartContractData.baseTokenTransfer, + methodId, + method: smartContractData.method, + inputs: smartContractData.inputs, + } as EvmCoinTransferActivity + case EvmActivityType.TokenTransfer: + return { + ...baseActivity, + type: EvmActivityType.TokenTransfer, + tokenTransfer: smartContractData.tokenTransfer, + methodId, + method: smartContractData.method, + inputs: smartContractData.inputs, + } as EvmTokenTransferActivity + case EvmActivityType.ContractCall: + default: + return { + ...baseActivity, + type: EvmActivityType.ContractCall, + rawData, + methodId, + method: smartContractData?.method, + inputs: smartContractData?.inputs, + } as EvmContractCallActivity + } } async function generateEvmCoinTransferActivityFromBlockscoutTransaction( diff --git a/packages/shared/src/lib/core/activity/utils/evm/generateEvmActivityFromLocalEvmTransaction.ts b/packages/shared/src/lib/core/activity/utils/evm/generateEvmActivityFromLocalEvmTransaction.ts index 6ff205e778..a07fb558d9 100644 --- a/packages/shared/src/lib/core/activity/utils/evm/generateEvmActivityFromLocalEvmTransaction.ts +++ b/packages/shared/src/lib/core/activity/utils/evm/generateEvmActivityFromLocalEvmTransaction.ts @@ -1,5 +1,4 @@ import { IAccountState } from '@core/account' -import { StardustActivityType } from '@core/activity/enums' import { EvmActivityType } from '@core/activity/enums/evm' import { EvmActivity, @@ -7,8 +6,7 @@ import { EvmContractCallActivity, EvmTokenTransferActivity, } from '@core/activity/types' -import { getMethodForEvmTransaction } from '@core/layer-2/utils' -import { getTransferInfoFromTransactionData } from '@core/layer-2/utils/getTransferInfoFromTransactionData' +import { parseSmartContractDataFromTransactionData } from '@core/layer-2/utils/parseSmartContractDataFromTransactionData' import { IEvmNetwork } from '@core/network' import { NftStandard } from '@core/nfts' import { BASE_TOKEN_ID, TokenStandard } from '@core/token' @@ -16,77 +14,14 @@ import { LocalEvmTransaction } from '@core/transactions' import { SubjectType } from '@core/wallet' import { generateBaseEvmActivity } from './generateBaseEvmActivity' import { Converter } from '@core/utils' +import { ParsedSmartContractType } from '@core/layer-2' export async function generateEvmActivityFromLocalEvmTransaction( transaction: LocalEvmTransaction, evmNetwork: IEvmNetwork, account: IAccountState ): Promise { - if (transaction.data) { - const transferInfo = getTransferInfoFromTransactionData(transaction, evmNetwork) - if (!transferInfo) { - return - } - - const { to, from, gasUsed, estimatedGas, gasPrice, transactionHash, timestamp, blockNumber } = transaction - const baseActivity = await generateBaseEvmActivity( - { - recipient: transferInfo.recipientAddress ?? to?.toString().toLowerCase(), - from: from?.toString().toLowerCase(), - gasUsed: Number(gasUsed), - estimatedGas: estimatedGas ? BigInt(estimatedGas) : undefined, - gasPrice: gasPrice ?? undefined, - transactionHash, - timestamp, - blockNumber, - }, - evmNetwork, - account - ) - if (transferInfo.type === StardustActivityType.SmartContract) { - const data = String(transaction.data ?? '') - const [method, parameters] = getMethodForEvmTransaction(data) ?? [] - return { - ...baseActivity, - type: EvmActivityType.ContractCall, - method, - parameters, - methodId: data.substring(0, 10), - rawData: data, - contract: { - type: SubjectType.SmartContract, - address: to?.toString().toLowerCase(), - name: '', - verified: false, - }, - } as EvmContractCallActivity - } else { - const tokenTransfer = - transferInfo?.type === StardustActivityType.Basic - ? { - standard: TokenStandard.Erc20, - tokenId: transferInfo.tokenId, - rawAmount: transferInfo.rawAmount, - } - : { - standard: NftStandard.Erc721, - tokenId: transferInfo.nftId, - rawAmount: BigInt(1), - } - - return { - ...baseActivity, - type: EvmActivityType.TokenTransfer, - contract: { - type: SubjectType.SmartContract, - address: to?.toString().toLowerCase(), - name: '', - verified: false, - }, - tokenTransfer, - } as EvmTokenTransferActivity - } - } else { + if (!transaction.data) { const { to, from, gasUsed, estimatedGas, gasPrice, transactionHash, timestamp, blockNumber } = transaction // i.e must be a coin transfer const baseActivity = await generateBaseEvmActivity( @@ -113,4 +48,81 @@ export async function generateEvmActivityFromLocalEvmTransaction( }, } as EvmCoinTransferActivity } + + const parsedData = parseSmartContractDataFromTransactionData( + { to: transaction.to?.toString(), data: transaction.data, value: transaction.value }, + evmNetwork + ) + if (!parsedData) { + return + } + + const { to, from, gasUsed, estimatedGas, gasPrice, transactionHash, timestamp, blockNumber } = transaction + let baseActivity = await generateBaseEvmActivity( + { + recipient: parsedData.recipientAddress ?? to?.toString().toLowerCase(), + from: from?.toString().toLowerCase(), + gasUsed: Number(gasUsed), + estimatedGas: estimatedGas ? BigInt(estimatedGas) : undefined, + gasPrice: gasPrice ?? undefined, + transactionHash, + timestamp, + blockNumber, + }, + evmNetwork, + account + ) + + baseActivity = { + ...baseActivity, + method: parsedData.parsedMethod?.name, + inputs: parsedData.parsedMethod?.inputs, + methodId: parsedData.rawMethod, + rawData: String(transaction.data ?? ''), + contract: { + type: SubjectType.SmartContract, + address: to?.toString().toLowerCase(), + name: '', + verified: false, + }, + } as EvmContractCallActivity + + switch (parsedData.type) { + case ParsedSmartContractType.SmartContract: + return { + ...baseActivity, + type: EvmActivityType.ContractCall, + } as EvmContractCallActivity + case ParsedSmartContractType.CoinTransfer: + return { + ...baseActivity, + type: EvmActivityType.CoinTransfer, + baseTokenTransfer: { + tokenId: BASE_TOKEN_ID, + rawAmount: parsedData.rawAmount, + }, + } as EvmCoinTransferActivity + case ParsedSmartContractType.TokenTransfer: + return { + ...baseActivity, + type: EvmActivityType.TokenTransfer, + tokenTransfer: { + standard: TokenStandard.Erc20, + tokenId: parsedData.tokenId, + rawAmount: parsedData.rawAmount, + }, + } as EvmTokenTransferActivity + case ParsedSmartContractType.NftTransfer: + return { + ...baseActivity, + type: EvmActivityType.TokenTransfer, + tokenTransfer: { + standard: NftStandard.Erc721, + tokenId: parsedData.nftId, + rawAmount: BigInt(1), + }, + } as EvmTokenTransferActivity + default: + break + } } diff --git a/packages/shared/src/lib/core/activity/utils/evm/generateEvmTokenTransferActivityFromBlockscoutTokenTransfer.ts b/packages/shared/src/lib/core/activity/utils/evm/generateEvmTokenTransferActivityFromBlockscoutTokenTransfer.ts index e39b113236..99fa1412da 100644 --- a/packages/shared/src/lib/core/activity/utils/evm/generateEvmTokenTransferActivityFromBlockscoutTokenTransfer.ts +++ b/packages/shared/src/lib/core/activity/utils/evm/generateEvmTokenTransferActivityFromBlockscoutTokenTransfer.ts @@ -21,9 +21,7 @@ import { import { getOrRequestTokenFromPersistedTokens } from '@core/token/actions' import { isNftPersisted, persistErc721Nft } from '@core/nfts/actions' import { BASE_TOKEN_CONTRACT_ADDRESS } from '@core/layer-2/constants' -import { getMethodForEvmTransaction } from '@core/layer-2/utils' -import { addMethodToRegistry, getMethodFromRegistry } from '@core/layer-2/stores/method-registry.store' -import { HEX_PREFIX } from '@core/utils' +import { getSmartContractDataFromBlockscoutTransaction } from './getSmartContractDataFromBlockscoutTransaction' export async function generateEvmTokenTransferActivityFromBlockscoutTokenTransfer( blockscoutTokenTransfer: BlockscoutTokenTransfer, @@ -130,20 +128,9 @@ export async function generateEvmTokenTransferActivityFromBlockscoutTokenTransfe } } - const method: string = blockscoutTokenTransfer.method - let parameters: Record | undefined - if (blockscoutTransaction?.decoded_input) { - const { method_id, method_call, parameters: _parameters } = blockscoutTransaction.decoded_input - parameters = _parameters - - if (!getMethodFromRegistry(HEX_PREFIX + method_id)) { - const fourBytePrefix = HEX_PREFIX + method_id - addMethodToRegistry(fourBytePrefix, method_call) - } - } else if (blockscoutTransaction?.raw_input) { - const [, _parameters] = getMethodForEvmTransaction(blockscoutTransaction.raw_input) ?? [] - parameters = _parameters - } + const smartContractData = blockscoutTransaction + ? getSmartContractDataFromBlockscoutTransaction(blockscoutTransaction, evmNetwork) + : undefined return { ...baseActivity, @@ -156,8 +143,8 @@ export async function generateEvmTokenTransferActivityFromBlockscoutTokenTransfe }, methodId: blockscoutTransaction?.decoded_input?.method_id ?? blockscoutTransaction?.method, - method, - parameters, + method: smartContractData?.method, + inputs: smartContractData?.inputs, rawData: blockscoutTransaction?.raw_input ?? '', contract, } diff --git a/packages/shared/src/lib/core/activity/utils/evm/getSmartContractDataFromBlockscoutTransaction.ts b/packages/shared/src/lib/core/activity/utils/evm/getSmartContractDataFromBlockscoutTransaction.ts new file mode 100644 index 0000000000..af54e39d9d --- /dev/null +++ b/packages/shared/src/lib/core/activity/utils/evm/getSmartContractDataFromBlockscoutTransaction.ts @@ -0,0 +1,89 @@ +import { IBlockscoutTransaction } from '@auxiliary/blockscout/interfaces' +import { EvmActivityType } from '@core/activity/enums/evm' +import { ParsedSmartContractType } from '@core/layer-2/enums' +import { IParsedInput } from '@core/layer-2/interfaces' +import { addMethodToRegistry, getMethodFromRegistry } from '@core/layer-2/stores/method-registry.store' +import { parseSmartContractDataFromTransactionData } from '@core/layer-2/utils' +import { IEvmNetwork } from '@core/network' +import { NftStandard } from '@core/nfts/enums' +import { BASE_TOKEN_ID } from '@core/token' +import { TokenStandard } from '@core/token/enums' +import { HEX_PREFIX } from '@core/utils' + +export function getSmartContractDataFromBlockscoutTransaction( + blockscoutTransaction: IBlockscoutTransaction, + evmNetwork: IEvmNetwork +): { type; method; inputs; baseTokenTransfer; tokenTransfer } { + let type = EvmActivityType.ContractCall + let method: string | undefined + let inputs: IParsedInput[] | undefined + let baseTokenTransfer: + | { + tokenId: string + rawAmount: bigint + } + | undefined + let tokenTransfer: + | { + standard: TokenStandard.Erc20 | TokenStandard.Irc30 | NftStandard.Irc27 | NftStandard.Erc721 + tokenId: string + rawAmount: bigint + } + | 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) { + const parsedData = parseSmartContractDataFromTransactionData( + { + to: blockscoutTransaction.to.hash.toLowerCase(), + data: blockscoutTransaction.raw_input, + value: blockscoutTransaction.value, + }, + evmNetwork + ) + method = parsedData?.parsedMethod?.name + inputs = parsedData?.parsedMethod?.inputs + + switch (parsedData?.type) { + case ParsedSmartContractType.CoinTransfer: + type = EvmActivityType.CoinTransfer + baseTokenTransfer = { + tokenId: BASE_TOKEN_ID, + rawAmount: parsedData.rawAmount, + } + break + case ParsedSmartContractType.TokenTransfer: + type = EvmActivityType.TokenTransfer + + tokenTransfer = { + standard: parsedData.standard, + tokenId: parsedData.tokenId, + rawAmount: parsedData.rawAmount, + } + break + case ParsedSmartContractType.NftTransfer: + type = EvmActivityType.TokenTransfer + + tokenTransfer = { + standard: parsedData.standard, + tokenId: parsedData.nftId, + rawAmount: BigInt(1), + } + break + case ParsedSmartContractType.SmartContract: + type = EvmActivityType.ContractCall + break + } + } + + return { type, method, inputs, baseTokenTransfer, tokenTransfer } +} diff --git a/packages/shared/src/lib/core/layer-2/enums/index.ts b/packages/shared/src/lib/core/layer-2/enums/index.ts index 8f892be08e..ffb6f19706 100644 --- a/packages/shared/src/lib/core/layer-2/enums/index.ts +++ b/packages/shared/src/lib/core/layer-2/enums/index.ts @@ -3,4 +3,5 @@ export * from './allowance.enum' export * from './asset-type.enum' export * from './erc721-interface-id.enum' export * from './evm-error-message.enum' +export * from './parsed-smart-contract-type.enum' export * from './state-mutability-type.enum' 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 new file mode 100644 index 0000000000..708a8ec94b --- /dev/null +++ b/packages/shared/src/lib/core/layer-2/enums/parsed-smart-contract-type.enum.ts @@ -0,0 +1,6 @@ +export enum ParsedSmartContractType { + CoinTransfer, + TokenTransfer, + NftTransfer, + SmartContract, +} 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 index 2cd6af7e80..527bf6aede 100644 --- 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 @@ -1,4 +1,12 @@ export interface Erc20TransferMethodInputs { - _to: string - _value: string + _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 index 33dbaa3645..6a455e2430 100644 --- 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 @@ -1,6 +1,22 @@ export interface Erc721SafeTransferMethodInputs { - from: string - to: string - tokenId: string - data?: string + 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 381d338829..3331d84235 100644 --- a/packages/shared/src/lib/core/layer-2/interfaces/index.ts +++ b/packages/shared/src/lib/core/layer-2/interfaces/index.ts @@ -16,3 +16,4 @@ export * from './layer-2-smart-contract-call-data.interface' export * from './layer-2-target-address-parameter.interface' export * from './layer-2-transfer-allowance-metadata.interface' export * from './native-token-amount.interface' +export * from './parsed-contract-data.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 index 8c20797410..d3ee7f6b8f 100644 --- 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 @@ -1,20 +1,36 @@ export interface IscCallMethodInputs { - contractHname: string - entryPoint: string + contractHname: { + name: string + type: string + value: string + } + entryPoint: { + name: string + type: string + value: string + } params: { - items: { - key: string - value: string - }[] + name: string + type: string + value: { + items: { + key: string + value: string + }[] + } } allowance: { - baseTokens: string - nativeTokens: { - ID: { - data: string - } - amount: string - }[] - nfts: string[] + 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 index 8cffd081a7..1384ee1705 100644 --- 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 @@ -3,24 +3,16 @@ import { ILayer2SendOptionsParameter } from './layer-2-send-options-parameter.in export interface IscSendMethodInputs { targetAddress: { - data: string + name: string + type: string + value: { + data: string + } } assets: { - baseTokens: string - nativeTokens: { - ID: { - data: string - } - amount: string - }[] - nfts: string[] - } - adjustMinimumStorageDeposit: boolean - metadata: { - targetContract: number - entrypoint: number - params: ILayer2SendMetadataParameterParameters[] - allowance: { + name: string + type: string + value: { baseTokens: string nativeTokens: { ID: { @@ -30,7 +22,31 @@ export interface IscSendMethodInputs { }[] nfts: string[] } - gasBudget: number } - sendOptions: ILayer2SendOptionsParameter + 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 new file mode 100644 index 0000000000..a134c4cb6e --- /dev/null +++ b/packages/shared/src/lib/core/layer-2/interfaces/parsed-contract-data.interface.ts @@ -0,0 +1,40 @@ +import { TokenStandard } from '@core/token' +import { ParsedSmartContractType } from '../enums' +import { NftStandard } from '@core/nfts' + +export interface IParsedCoinTransfer extends Omit { + type: ParsedSmartContractType.CoinTransfer + rawAmount: bigint +} + +export interface IParsedTokenTransfer extends Omit { + type: ParsedSmartContractType.TokenTransfer + standard: TokenStandard.Erc20 | TokenStandard.Irc30 + tokenId: string + rawAmount: bigint +} + +export interface IParsedNftTransfer extends Omit { + type: ParsedSmartContractType.NftTransfer + standard: NftStandard.Erc721 | NftStandard.Irc27 + nftId: string +} + +export interface IParsedSmartContractData { + type: ParsedSmartContractType.SmartContract + rawMethod: string + parsedMethod?: IParsedMethod + additionalBaseTokenAmount?: bigint + recipientAddress: string +} + +export interface IParsedMethod { + name: string + inputs: IParsedInput[] +} + +export interface IParsedInput { + name: string + type: string + value: unknown +} 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 new file mode 100644 index 0000000000..acf63a00de --- /dev/null +++ b/packages/shared/src/lib/core/layer-2/types/parsed-smart-contract-data.type.ts @@ -0,0 +1,12 @@ +import { + IParsedCoinTransfer, + IParsedNftTransfer, + IParsedSmartContractData, + IParsedTokenTransfer, +} from '../interfaces/parsed-contract-data.interface' + +export type ParsedSmartContractData = + | IParsedCoinTransfer + | IParsedTokenTransfer + | IParsedNftTransfer + | IParsedSmartContractData diff --git a/packages/shared/src/lib/core/layer-2/utils/getMethodForEvmTransaction.ts b/packages/shared/src/lib/core/layer-2/utils/getMethodForEvmTransaction.ts deleted file mode 100644 index 99e18cc61e..0000000000 --- a/packages/shared/src/lib/core/layer-2/utils/getMethodForEvmTransaction.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { lookupMethodSignature } from './lookupMethodSignature' - -export function getMethodForEvmTransaction(rawData: string): [string, Record] | undefined { - const fourBytePrefix = rawData.substring(0, 10) - try { - const result = lookupMethodSignature(fourBytePrefix) - if (!result) { - throw Error('Method could not be found!') - } - - const matches = /(\w+)\((.*)\)$/.exec(result) - if (!matches) { - throw Error('Method signature could not be parsed!') - } - - const name = matches[1] - const parametersArr = matches[2] ?? '' - - const parameters: Record = parametersArr.split(',').reduce( - (acc, type, index) => { - acc[`param${index}`] = type - return acc - }, - {} as Record - ) - - return [name, parameters] - } catch (error) { - return undefined - } -} diff --git a/packages/shared/src/lib/core/layer-2/utils/getTransferInfoFromTransactionData.ts b/packages/shared/src/lib/core/layer-2/utils/getTransferInfoFromTransactionData.ts deleted file mode 100644 index 7e28ae3ae1..0000000000 --- a/packages/shared/src/lib/core/layer-2/utils/getTransferInfoFromTransactionData.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { StardustActivityType } from '@core/activity' -import { IEvmNetwork, isIscNetwork } from '@core/network' -import { BASE_TOKEN_ID } from '@core/token/constants' -import { LocalEvmTransaction } from '@core/transactions' -import { AbiDecoder, Converter, HEX_PREFIX } from '@core/utils' -import { isTrackedNftAddress, isTrackedTokenAddress } from '@core/wallet/actions' -import { ERC20_ABI, ERC721_ABI, ISC_SANDBOX_ABI } from '../abis' -import { ISC_MAGIC_CONTRACT_ADDRESS } from '../constants' -import { - Erc20TransferMethodInputs, - Erc721SafeTransferMethodInputs, - IscCallMethodInputs, - IscSendMethodInputs, -} from '../interfaces' - -type TransferInfo = - | { - type: StardustActivityType.Basic - tokenId: string - rawAmount: bigint - additionalBaseTokenAmount?: bigint - recipientAddress: string - } - | { type: StardustActivityType.Nft; nftId: string; additionalBaseTokenAmount?: bigint; recipientAddress: string } - | { type: StardustActivityType.SmartContract; recipientAddress: string } - -export function getTransferInfoFromTransactionData( - transaction: LocalEvmTransaction, - evmNetwork: IEvmNetwork -): TransferInfo | undefined { - const recipientAddress = transaction?.to?.toString()?.toLowerCase() - if (!recipientAddress) { - return undefined - } - - if (transaction.data) { - const isErc20 = isTrackedTokenAddress(evmNetwork.id, recipientAddress) - const isErc721 = isTrackedNftAddress(evmNetwork.id, recipientAddress) - const isIscContract = recipientAddress === ISC_MAGIC_CONTRACT_ADDRESS - - const abi = isErc721 ? ERC721_ABI : isErc20 ? ERC20_ABI : isIscContract ? ISC_SANDBOX_ABI : undefined - - if (!abi) { - return { type: StardustActivityType.SmartContract, recipientAddress } - } - - const abiDecoder = new AbiDecoder(abi, evmNetwork.provider) - const decoded = abiDecoder.decodeData(transaction.data as string) - - switch (decoded?.name) { - case 'call': { - if (!isIscNetwork(evmNetwork)) { - return { type: StardustActivityType.SmartContract, recipientAddress } - } - - const inputs = decoded.inputs as IscCallMethodInputs - const nativeToken = inputs?.allowance?.nativeTokens?.[0] - const nftId = inputs?.allowance?.nfts?.[0] - const agentId = inputs?.params.items?.find((item) => item.key === '0x61')?.value - - if (nativeToken) { - const rawAmount = - nativeToken.ID.data === BASE_TOKEN_ID - ? evmNetwork.denormaliseAmount(nativeToken.amount) - : BigInt(nativeToken.amount) - return { - type: StardustActivityType.Basic, - tokenId: nativeToken.ID.data, - rawAmount, - recipientAddress: HEX_PREFIX + agentId?.substring(agentId.length - 40), - } - } else if (nftId) { - return { - type: StardustActivityType.Nft, - nftId, - recipientAddress: HEX_PREFIX + agentId?.substring(agentId.length - 40), - } - } else { - return { type: StardustActivityType.SmartContract, recipientAddress } - } - } - case 'transfer': { - const inputs = decoded.inputs as Erc20TransferMethodInputs - - return { - type: StardustActivityType.Basic, - tokenId: recipientAddress, - rawAmount: BigInt(inputs._value), - recipientAddress: inputs._to, - } - } - case 'safeTransferFrom': { - const inputs = decoded.inputs as Erc721SafeTransferMethodInputs - - return { - type: StardustActivityType.Nft, - nftId: `${recipientAddress.toLowerCase()}:${inputs.tokenId}`, - recipientAddress: inputs.to, - } - } - case 'send': { - if (!isIscNetwork(evmNetwork)) { - return { type: StardustActivityType.SmartContract, recipientAddress } - } - - const inputs = decoded.inputs as IscSendMethodInputs - const nativeToken = inputs?.assets?.nativeTokens?.[0] - const nftId = inputs?.assets?.nfts?.[0] - const baseTokenAmount = BigInt(inputs.assets.baseTokens) - - if (nativeToken) { - return { - type: StardustActivityType.Basic, - tokenId: nativeToken.ID.data, - rawAmount: BigInt(nativeToken.amount), - additionalBaseTokenAmount: baseTokenAmount, - recipientAddress, // for now, set it to the magic contract address - } - } - if (nftId) { - return { - type: StardustActivityType.Nft, - nftId, - additionalBaseTokenAmount: baseTokenAmount, - recipientAddress, // for now, set it to the magic contract address - } - } else if (baseTokenAmount) { - return { - type: StardustActivityType.Basic, - tokenId: BASE_TOKEN_ID, - rawAmount: evmNetwork.denormaliseAmount(baseTokenAmount), - recipientAddress, // for now, set it to the magic contract address - } - } - - return { type: StardustActivityType.SmartContract, recipientAddress } - } - default: - return { type: StardustActivityType.SmartContract, recipientAddress } - } - } else { - return { - type: StardustActivityType.Basic, - tokenId: BASE_TOKEN_ID, - rawAmount: Converter.bigIntLikeToBigInt(transaction.value ?? 0), - recipientAddress, - } - } -} diff --git a/packages/shared/src/lib/core/layer-2/utils/index.ts b/packages/shared/src/lib/core/layer-2/utils/index.ts index 3ea996a5dc..66b04f9502 100644 --- a/packages/shared/src/lib/core/layer-2/utils/index.ts +++ b/packages/shared/src/lib/core/layer-2/utils/index.ts @@ -12,12 +12,12 @@ export * from './getErc20TransferSmartContractData' export * from './getErc721TransferSmartContractData' export * from './getEvmTransactionFromHexString' export * from './getHexEncodedTransaction' -export * from './getMethodForEvmTransaction' export * from './getL2ToL1StorageDepositBuffer' export * from './isErcAsset' export * from './lookupMethodSignature' export * from './parseLayer2Metadata' export * from './parseLayer2MetadataForTransfer' export * from './parseSiweMessage' +export * from './parseSmartContractDataFromTransactionData' export * from './prepareEvmTransaction' export * from './validateSiwe' diff --git a/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts b/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts new file mode 100644 index 0000000000..d67542176b --- /dev/null +++ b/packages/shared/src/lib/core/layer-2/utils/parseSmartContractDataFromTransactionData.ts @@ -0,0 +1,281 @@ +import { IEvmNetwork, isIscNetwork } from '@core/network' +import { BASE_TOKEN_ID } from '@core/token/constants' +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 { 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' + +export function parseSmartContractDataFromTransactionData( + transaction: { to?: string; data: BytesLike; value?: BigIntLike }, + evmNetwork: IEvmNetwork +): ParsedSmartContractData | undefined { + const recipientAddress = transaction?.to?.toLowerCase() + if (!recipientAddress) { + return undefined + } + + const rawData = transaction.data as string + const isErc20 = isTrackedTokenAddress(evmNetwork.id, recipientAddress) || recipientAddress === ISC_BASE_COIN_ADDRESS + const isErc721 = isTrackedNftAddress(evmNetwork.id, recipientAddress) + const isIscContract = recipientAddress === ISC_MAGIC_CONTRACT_ADDRESS + + let parsedData + if (isIscContract) { + parsedData = parseSmartContractDataWithIscMagicAbi(evmNetwork, rawData, recipientAddress) + } else if (isErc20) { + parsedData = parseSmartContractDataWithErc20Abi(evmNetwork, rawData, recipientAddress) + } else if (isErc721) { + parsedData = parseSmartContractDataWithErc721Abi(evmNetwork, rawData, recipientAddress) + } + + return parsedData ?? parseSmartContractDataWithMethodRegistry(rawData, recipientAddress) +} + +function parseSmartContractDataWithIscMagicAbi( + network: IEvmNetwork, + data: string, + recipientAddress: string +): ParsedSmartContractData | undefined { + const iscMagicDecoder = new AbiDecoder(ISC_SANDBOX_ABI, network.provider) + const decodedData = iscMagicDecoder.decodeData(data) // TODO: Type this return + + if (!decodedData) { + return undefined + } + + const rawMethod = data.substring(0, 10) + const parsedMethod: IParsedMethod = { + name: decodedData.name, + inputs: Object.values(decodedData.inputs), + } + + switch (decodedData.name) { + case 'call': { + if (!isIscNetwork(network)) { + return undefined + } + + const inputs = decodedData.inputs as unknown as IscCallMethodInputs + 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 + + if (nativeToken) { + const rawAmount = + nativeToken.ID.data === BASE_TOKEN_ID + ? network.denormaliseAmount(nativeToken.amount) + : BigInt(nativeToken.amount) + return { + type: ParsedSmartContractType.TokenTransfer, + standard: TokenStandard.Irc30, + tokenId: nativeToken.ID.data, + rawAmount, + parsedMethod, + rawMethod, + recipientAddress: HEX_PREFIX + agentId?.substring(agentId.length - 40), + } + } else if (nftId) { + return { + type: ParsedSmartContractType.NftTransfer, + standard: NftStandard.Irc27, + nftId, + parsedMethod, + rawMethod, + recipientAddress: HEX_PREFIX + agentId?.substring(agentId.length - 40), + } + } else { + return { type: ParsedSmartContractType.SmartContract, recipientAddress, rawMethod, parsedMethod } + } + } + case 'send': { + if (!isIscNetwork(network)) { + return undefined + } + + const inputs = decodedData.inputs as unknown as IscSendMethodInputs + const assets = inputs.assets?.value + + const nativeToken = assets?.nativeTokens?.[0] + const nftId = assets?.nfts?.[0] + const baseTokenAmount = BigInt(assets?.baseTokens ?? 0) + + if (nativeToken) { + return { + type: ParsedSmartContractType.TokenTransfer, + standard: TokenStandard.Irc30, + tokenId: nativeToken.ID.data, + rawAmount: BigInt(nativeToken.amount), + rawMethod, + parsedMethod, + additionalBaseTokenAmount: baseTokenAmount, + recipientAddress, // for now, set it to the magic contract address + } + } + if (nftId) { + return { + type: ParsedSmartContractType.NftTransfer, + standard: NftStandard.Irc27, + nftId, + rawMethod, + parsedMethod, + additionalBaseTokenAmount: baseTokenAmount, + recipientAddress, // for now, set it to the magic contract address + } + } else if (baseTokenAmount) { + return { + type: ParsedSmartContractType.CoinTransfer, + rawAmount: network.denormaliseAmount(baseTokenAmount), + rawMethod, + parsedMethod, + recipientAddress, // for now, set it to the magic contract address + } + } + + return { type: ParsedSmartContractType.SmartContract, recipientAddress, rawMethod, parsedMethod } + } + default: + return { type: ParsedSmartContractType.SmartContract, recipientAddress, rawMethod, parsedMethod } + } +} + +function parseSmartContractDataWithErc20Abi( + network: IEvmNetwork, + data: string, + recipientAddress: string +): ParsedSmartContractData | undefined { + const erc20Decoder = new AbiDecoder(ERC20_ABI, network.provider) + const decodedData = erc20Decoder.decodeData(data) // TODO: Type this return + + if (!decodedData) { + return undefined + } + + const rawMethod = data.substring(0, 10) + const parsedMethod: IParsedMethod = { + name: decodedData.name, + inputs: Object.values(decodedData.inputs), + } + + 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), + rawMethod, + parsedMethod, + recipientAddress: inputs._to.value, + } + } + // TODO: Support more ERC20 methods + default: { + return { + type: ParsedSmartContractType.SmartContract, + rawMethod, + parsedMethod, + recipientAddress, + } + } + } +} + +function parseSmartContractDataWithErc721Abi( + network: IEvmNetwork, + data: string, + recipientAddress: string +): ParsedSmartContractData | undefined { + const erc721Decoder = new AbiDecoder(ERC721_ABI, network.provider) + const decodedData = erc721Decoder.decodeData(data) // TODO: Type this return + + if (!decodedData) { + return undefined + } + + const rawMethod = data.substring(0, 10) + const parsedMethod: IParsedMethod = { + name: decodedData.name, + inputs: Object.values(decodedData.inputs), + } + + switch (decodedData.name) { + case 'safeTransferFrom': { + // Enum? + const inputs = decodedData.inputs as unknown as Erc721SafeTransferMethodInputs + + return { + type: ParsedSmartContractType.NftTransfer, + standard: NftStandard.Erc721, + nftId: `${recipientAddress}:${inputs.tokenId.value}`, + rawMethod, + parsedMethod, + recipientAddress: inputs.to.value, + } + } + // TODO: support more ERC721 methods + default: { + return { + type: ParsedSmartContractType.SmartContract, + recipientAddress, + rawMethod, + parsedMethod, + } + } + } +} + +function parseSmartContractDataWithMethodRegistry( + rawData: string, + recipientAddress: string +): ParsedSmartContractData | undefined { + const fourBytePrefix = rawData.substring(0, 10) + try { + const result = lookupMethodSignature(fourBytePrefix) + if (!result) { + throw Error('Method could not be found!') + } + + const matches = /(\w+)\((.*)\)$/.exec(result) + if (!matches) { + throw Error('Method signature could not be parsed!') + } + + const name = matches[1] + const inputsArr = matches[2] ?? '' + const inputs: IParsedInput[] = inputsArr.split(',').map((param, index) => { + // Method registry can either contain just `uint64` or `uint64 amount` + const [type, name] = param.trim().split(' ') + + return { + name: name ?? `param${index + 1}`, + type, + value: undefined, + } + }) + + return { + type: ParsedSmartContractType.SmartContract, + recipientAddress, + rawMethod: fourBytePrefix, + parsedMethod: { name, inputs }, + } + } catch (error) { + return undefined + } +} diff --git a/packages/shared/src/lib/core/network/classes/isc-chain.class.ts b/packages/shared/src/lib/core/network/classes/isc-chain.class.ts index 9320fedc05..42d0ab73f0 100644 --- a/packages/shared/src/lib/core/network/classes/isc-chain.class.ts +++ b/packages/shared/src/lib/core/network/classes/isc-chain.class.ts @@ -1,10 +1,10 @@ // Potential circular import so importing this first import { EvmNetwork } from './evm-network.class' - +// REQUIRED GAP FOR CIRCULAR IMPORTS ABOVE import { IAccountState } from '@core/account/interfaces' -import { StardustActivityType } from '@core/activity/enums' +import { ParsedSmartContractType } from '@core/layer-2' import { fetchIscAssetsForAccount } from '@core/layer-2/utils' -import { getTransferInfoFromTransactionData } from '@core/layer-2/utils/getTransferInfoFromTransactionData' +import { parseSmartContractDataFromTransactionData } from '@core/layer-2/utils/parseSmartContractDataFromTransactionData' import { NetworkType } from '@core/network/enums' import { Nft } from '@core/nfts/interfaces' import { getNftsFromNftIds } from '@core/nfts/utils' @@ -14,6 +14,7 @@ import { getPersistedTransactionsForChain } from '@core/transactions/stores' import { Converter } from '@core/utils' import { BigIntLike } from '@ethereumjs/util' import { IIscChain, IIscChainConfiguration, IIscChainMetadata } from '../interfaces' +import { NftStandard } from '@core/nfts/enums' export class IscChain extends EvmNetwork implements IIscChain { private readonly _chainApi: string @@ -54,19 +55,21 @@ export class IscChain extends EvmNetwork implements IIscChain { // Wrapped L1 IRC NFTs const transactionsOnChain = getPersistedTransactionsForChain(getActiveProfileId(), account.index, this) - const nftIdsOnChain: string[] = [] + const ircNftsIds: string[] = [] for (const transaction of transactionsOnChain) { - if (!transaction.local) { - continue - } - const transferInfo = getTransferInfoFromTransactionData(transaction.local, this) - if (transferInfo?.type !== StardustActivityType.Nft || transferInfo.nftId.includes(':')) { + if (!transaction.local || !transaction.local.data) { continue } + const parsedData = parseSmartContractDataFromTransactionData( + { to: transaction.local.to, data: transaction.local.data, value: transaction.local.value }, + this + ) - nftIdsOnChain.push(transferInfo.nftId) + if (parsedData?.type === ParsedSmartContractType.NftTransfer && parsedData.standard === NftStandard.Irc27) { + ircNftsIds.push(parsedData.nftId) + } } - const ircNfts = await getNftsFromNftIds(nftIdsOnChain, this.id) + const ircNfts = await getNftsFromNftIds(ircNftsIds, this.id) return [...ircNfts, ...erc721Nfts] } diff --git a/packages/shared/src/lib/core/profile/constants/default-account-recovery-configuration.constant.ts b/packages/shared/src/lib/core/profile/constants/default-account-recovery-configuration.constant.ts index a40bc12753..0415d50222 100644 --- a/packages/shared/src/lib/core/profile/constants/default-account-recovery-configuration.constant.ts +++ b/packages/shared/src/lib/core/profile/constants/default-account-recovery-configuration.constant.ts @@ -4,13 +4,13 @@ import { AccountRecoveryConfiguration } from '../types' export const DEFAULT_ACCOUNT_RECOVERY_CONFIGURATION: AccountRecoveryConfiguration = { [ProfileType.Ledger]: { initialAccountRange: 3, - accountGapLimit: 0, + accountGapLimit: 1, numberOfRoundsBetweenBreadthSearch: 1, addressGapLimit: 5, }, [ProfileType.Software]: { initialAccountRange: 10, - accountGapLimit: 0, + accountGapLimit: 1, numberOfRoundsBetweenBreadthSearch: 1, addressGapLimit: 100, }, diff --git a/packages/shared/src/lib/core/tide/apis/tide.api.ts b/packages/shared/src/lib/core/tide/apis/tide.api.ts index 2ee4bb9f41..db38008450 100644 --- a/packages/shared/src/lib/core/tide/apis/tide.api.ts +++ b/packages/shared/src/lib/core/tide/apis/tide.api.ts @@ -32,6 +32,7 @@ interface ICampaignsResponse { startTime: string endTime: string address: string + chain: number url: string evmNetwork: number projectId: number diff --git a/packages/shared/src/lib/core/utils/abiDecoder.ts b/packages/shared/src/lib/core/utils/abiDecoder.ts index 9d56d10fb9..a4ba9b3b52 100644 --- a/packages/shared/src/lib/core/utils/abiDecoder.ts +++ b/packages/shared/src/lib/core/utils/abiDecoder.ts @@ -28,7 +28,19 @@ export class AbiDecoder { this.web3 = _web3 } - public decodeData(data: string): { name: string; inputs: unknown } | undefined { + public decodeData(data: string): + | { + name: string + inputs: Record< + string, + { + name: string + type: string + value: unknown + } + > + } + | undefined { const functionSignature = data.slice(2, 10) const abiItem = this.abi[functionSignature] @@ -36,9 +48,15 @@ export class AbiDecoder { return undefined } - const decoded = this.web3.eth.abi.decodeParameters(abiItem.inputs as AbiInput[] ?? [], data.slice(10)) + const decoded = this.web3.eth.abi.decodeParameters((abiItem.inputs as AbiInput[]) ?? [], data.slice(10)) - const inputs: { [key: string]: unknown } = {} + const inputs: { + [key: string]: { + name: string + type: string + value: unknown + } + } = {} for (let i = 0; i < decoded.__length__; i++) { const dataInput = decoded[i] const abiInput = abiItem.inputs?.[i] @@ -48,7 +66,11 @@ export class AbiDecoder { } const parsedInput = this.parseInputParameter(abiInput, dataInput) - inputs[abiInput.name] = parsedInput + inputs[abiInput.name] = { + name: abiInput.name, + type: abiInput.type, + value: parsedInput, + } } return { diff --git a/yarn.lock b/yarn.lock index a1d3af65a3..2785adb726 100644 --- a/yarn.lock +++ b/yarn.lock @@ -348,10 +348,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@bloomwalletio/ui@0.20.22": - version "0.20.22" - resolved "https://npm.pkg.github.com/download/@bloomwalletio/ui/0.20.22/a8f22408fe57db48e02b36208261aa334c479971#a8f22408fe57db48e02b36208261aa334c479971" - integrity sha512-m/+zQGTKVkIxbKxcdZmskz95y+j5nrwyCSIxqzsPJy2GkubhRGTLMyzPQBrWOfxZjTis9ApqF2W/yRLLRKyxXQ== +"@bloomwalletio/ui@0.21.0": + version "0.21.0" + resolved "https://npm.pkg.github.com/download/@bloomwalletio/ui/0.21.0/86d45a11b362f98fe3b0088f6b2e9a30aacbdb5d#86d45a11b362f98fe3b0088f6b2e9a30aacbdb5d" + integrity sha512-fVDfsoeRQwTo/no4HRNBqr8Eqh0hh1i/NeVuH5R94MKvDasb/SzmlfOLiZGwoP/jzaA+g+OoBMXNz2NhKTU/tw== dependencies: "@floating-ui/dom" "1.4.3" "@popperjs/core" "2.11.8" @@ -9949,7 +9949,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10026,7 +10035,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11394,7 +11410,16 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==