From 6e1d657c03861ad276cf6c1162a3de2c91804c75 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 17 Nov 2024 03:29:02 +0200 Subject: [PATCH 1/3] TW-1587: [research] Activity History on Alchemy. Transfers --- package.json | 1 + .../templates/activity/EvmActivityList.tsx | 27 +- src/lib/activity/evm/fetch.ts | 168 ++++++- src/lib/activity/evm/parse/alchemy.ts | 186 +++++++ src/lib/activity/types.ts | 1 + yarn.lock | 455 +++++++++++++++++- 6 files changed, 810 insertions(+), 28 deletions(-) create mode 100644 src/lib/activity/evm/parse/alchemy.ts diff --git a/package.json b/package.json index 0bb04685a..b81a409f1 100644 --- a/package.json +++ b/package.json @@ -103,6 +103,7 @@ "@types/webextension-polyfill": "^0.9.1", "@types/webpack": "^5", "@types/ws": "^7.4.6", + "alchemy-sdk": "^3.4.8", "assert": "1.5.0", "async-mutex": "^0.4.0", "async-retry": "1.3.3", diff --git a/src/app/templates/activity/EvmActivityList.tsx b/src/app/templates/activity/EvmActivityList.tsx index 74f3b1c5f..58c34406c 100644 --- a/src/app/templates/activity/EvmActivityList.tsx +++ b/src/app/templates/activity/EvmActivityList.tsx @@ -1,12 +1,9 @@ import React, { FC, useMemo } from 'react'; -import { AxiosError } from 'axios'; - import { DeadEndBoundaryError } from 'app/ErrorBoundary'; import { useLoadPartnersPromo } from 'app/hooks/use-load-partners-promo'; import { EvmActivity } from 'lib/activity'; -import { getEvmAssetTransactions } from 'lib/activity/evm'; -import { useSafeState } from 'lib/ui/hooks'; +import { getEvmActivities } from 'lib/activity/evm/fetch'; import { useAccountAddressForEvm } from 'temple/front'; import { useEvmChainByChainId } from 'temple/front/chains'; @@ -30,43 +27,33 @@ export const EvmActivityList: FC = ({ chainId, assetSlug, filterKind }) = useLoadPartnersPromo(); - const [nextPage, setNextPage] = useSafeState(undefined); - const { activities, isLoading, reachedTheEnd, setActivities, setIsLoading, setReachedTheEnd, loadNext } = useActivitiesLoadingLogic( async (initial, signal) => { - const page = initial ? undefined : nextPage; - if (page === null) return; + if (reachedTheEnd) return; setIsLoading(true); const currActivities = initial ? [] : activities; try { - const { activities: newActivities, nextPage: newNextPage } = await getEvmAssetTransactions( - accountAddress, - chainId, - assetSlug, - page, - signal - ); + const olderThanBlockHeight = activities.at(activities.length - 1)?.blockHeight; + + const newActivities = await getEvmActivities(chainId, accountAddress, olderThanBlockHeight, signal); if (signal.aborted) return; setActivities(currActivities.concat(newActivities)); - setNextPage(newNextPage); - if (newNextPage === null || newActivities.length === 0) setReachedTheEnd(true); + if (newActivities.length === 0) setReachedTheEnd(true); } catch (error) { if (signal.aborted) return; console.error(error); - if (error instanceof AxiosError && error.status === 501) setReachedTheEnd(true); } setIsLoading(false); }, - [chainId, accountAddress, assetSlug], - () => setNextPage(undefined) + [chainId, accountAddress, assetSlug] ); const displayActivities = useMemo( diff --git a/src/lib/activity/evm/fetch.ts b/src/lib/activity/evm/fetch.ts index b8c0afae3..2b5b56514 100644 --- a/src/lib/activity/evm/fetch.ts +++ b/src/lib/activity/evm/fetch.ts @@ -1,10 +1,22 @@ +import { + Alchemy, + AssetTransfersCategory, + Network, + AssetTransfersWithMetadataParams, + SortingOrder, + AssetTransfersWithMetadataResult +} from 'alchemy-sdk'; +import { groupBy, uniqBy } from 'lodash'; + import { getEvmERC20Transfers, getEvmTransactions } from 'lib/apis/temple/endpoints/evm'; import { fromAssetSlug } from 'lib/assets'; import { EVM_TOKEN_SLUG } from 'lib/assets/defaults'; +import { TempleChainKind } from 'temple/types'; -import { EvmActivity } from '../types'; +import { ActivityStatus, EvmActivity } from '../types'; import { parseGoldRushTransaction, parseGoldRushERC20Transfer } from './parse'; +import { parseTransfer } from './parse/alchemy'; export async function getEvmAssetTransactions( walletAddress: string, @@ -57,3 +69,157 @@ export async function getEvmAssetTransactions( nextPage }; } + +export async function getEvmActivities( + chainId: number, + accAddress: string, + olderThanBlockHeight?: string, + signal?: AbortSignal +) { + const accAddressLowercased = accAddress.toLowerCase(); + + const allTransfers = await fetchTransfers(chainId, accAddress, olderThanBlockHeight, signal); + if (!allTransfers.length) return []; + + const groups = Object.entries(groupBy(allTransfers, 'hash')); + + const activities: EvmActivity[] = []; + + for (const [hash, transfers] of groups) { + const firstTransfer = transfers.at(0)!; + + const operations = transfers.map(transfer => parseTransfer(transfer, accAddressLowercased)); + + const activity: EvmActivity = { + chain: TempleChainKind.EVM, + chainId, + hash, + status: ActivityStatus.applied, + addedAt: firstTransfer.metadata.blockTimestamp, + operations, + operationsCount: operations.length, + blockHeight: `${Number(firstTransfer.blockNum)}` + }; + + activities.push(activity); + } + + return activities; +} + +async function fetchTransfers( + chainId: number, + accAddress: string, + olderThanBlockHeight?: string, + signal?: AbortSignal +): Promise { + const chainName = CHAINS_NAMES[chainId]; + if (!chainName) return []; + + const alchemy = new Alchemy({ + apiKey: process.env._ALCHEMY_API_KEY, // TODO: To EnvVars + network: chainName + }); + + const reqOptions: AssetTransfersWithMetadataParams = { + order: SortingOrder.DESCENDING, + category: Object.values(AssetTransfersCategory), + excludeZeroValue: true, + withMetadata: true, + toBlock: olderThanBlockHeight ? '0x' + (BigInt(olderThanBlockHeight) - BigInt(1)).toString(16) : undefined, + maxCount: 50 + }; + + const [transfersFrom, transfersTo] = await Promise.all([ + alchemy.core.getAssetTransfers({ ...reqOptions, fromAddress: accAddress }).then(r => r.transfers), + alchemy.core.getAssetTransfers({ ...reqOptions, toAddress: accAddress }).then(r => r.transfers) + ]); + + const allTransfers = transfersFrom + .concat(transfersTo) + .toSorted((a, b) => (a.metadata.blockTimestamp > b.metadata.blockTimestamp ? -1 : 1)); + + if (!allTransfers.length) return []; + + const sameTrailingHashes = calcSameTrailingHashes(allTransfers); + + /** Will need to filter those transfers, that r made from & to the same address */ + const uniqByKey: keyof (typeof allTransfers)[number] = 'uniqueId'; + + if (sameTrailingHashes === allTransfers.length) { + // const moreTransfers = []; // TODO: Fetch more transfers until reach another hash ? + // return uniqBy(allTransfers.concat(moreTransfers), uniqByKey); + } + + allTransfers.splice(allTransfers.length - sameTrailingHashes, sameTrailingHashes); + + return uniqBy(allTransfers, uniqByKey); +} + +function calcSameTrailingHashes(transfers: AssetTransfersWithMetadataResult[]) { + const trailingHash = transfers.at(transfers.length - 1)!.hash; + if (transfers.at(0)!.hash === trailingHash) return transfers.length; // All are same, saving runtime + + if (transfers.length === 2) return 1; // Preposition for further math + + const sameTrailingHashes = transfers.length - 1 - transfers.findLastIndex(tr => tr.hash !== trailingHash); + + return sameTrailingHashes; +} + +/** TODO: Verify this mapping */ +const CHAINS_NAMES: Record = { + 1: Network.ETH_MAINNET, + 5: Network.ETH_GOERLI, + 10: Network.OPT_MAINNET, + 30: Network.ROOTSTOCK_MAINNET, + 31: Network.ROOTSTOCK_TESTNET, + 56: Network.BNB_MAINNET, + 97: Network.BNB_TESTNET, + 100: Network.GNOSIS_MAINNET, + 137: Network.MATIC_MAINNET, + 204: Network.OPBNB_MAINNET, + 250: Network.FANTOM_MAINNET, + 300: Network.ZKSYNC_SEPOLIA, + 324: Network.ZKSYNC_MAINNET, + 360: Network.SHAPE_MAINNET, + 420: Network.OPT_GOERLI, + 480: Network.WORLDCHAIN_MAINNET, + 592: Network.ASTAR_MAINNET, + 1088: Network.METIS_MAINNET, + 1101: Network.POLYGONZKEVM_MAINNET, + 1442: Network.POLYGONZKEVM_TESTNET, + 1946: Network.SONEIUM_MINATO, + 2442: Network.POLYGONZKEVM_CARDONA, + 4002: Network.FANTOM_TESTNET, + 4801: Network.WORLDCHAIN_SEPOLIA, + 5000: Network.MANTLE_MAINNET, + 5003: Network.MANTLE_SEPOLIA, + 5611: Network.OPBNB_TESTNET, + 7000: Network.ZETACHAIN_MAINNET, + 7001: Network.ZETACHAIN_TESTNET, + 8453: Network.BASE_MAINNET, + 10200: Network.GNOSIS_CHIADO, + 11011: Network.SHAPE_SEPOLIA, + 42161: Network.ARB_MAINNET, + 42220: Network.CELO_MAINNET, + 43113: Network.AVAX_FUJI, + 43114: Network.AVAX_MAINNET, + 42170: Network.ARBNOVA_MAINNET, + 44787: Network.CELO_ALFAJORES, + 59141: Network.LINEA_SEPOLIA, + 59144: Network.LINEA_MAINNET, + 80001: Network.MATIC_MUMBAI, + 80002: Network.MATIC_AMOY, + 80084: Network.BERACHAIN_BARTIO, + 81457: Network.BLAST_MAINNET, + 84531: Network.BASE_GOERLI, + 84532: Network.BASE_SEPOLIA, + 421613: Network.ARB_GOERLI, + 421614: Network.ARB_SEPOLIA, + 534351: Network.SCROLL_SEPOLIA, + 534352: Network.SCROLL_MAINNET, + 11155111: Network.ETH_SEPOLIA, + 11155420: Network.OPT_SEPOLIA, + 168587773: Network.BLAST_SEPOLIA +}; diff --git a/src/lib/activity/evm/parse/alchemy.ts b/src/lib/activity/evm/parse/alchemy.ts new file mode 100644 index 000000000..2368aeb6f --- /dev/null +++ b/src/lib/activity/evm/parse/alchemy.ts @@ -0,0 +1,186 @@ +import { AssetTransfersCategory, AssetTransfersWithMetadataResult } from 'alchemy-sdk'; + +import { ActivityOperKindEnum, ActivityOperTransferType, EvmActivityAsset, EvmOperation } from 'lib/activity/types'; +import { EVM_TOKEN_SLUG } from 'lib/assets/defaults'; + +export function parseTransfer(transfer: AssetTransfersWithMetadataResult, accAddress: string): EvmOperation { + const fromAddress = transfer.from; + const toAddress = transfer.to; + + if (!fromAddress || !toAddress) return buildInteraction(transfer, accAddress); + + const contractAddress = transfer.rawContract.address; + + // TODO: to/from contract/account recognition + const type = fromAddress === accAddress ? ActivityOperTransferType.send : ActivityOperTransferType.receive; + + if (transfer.category === AssetTransfersCategory.EXTERNAL) { + // fromAddress is an account's address for 'external' transfers + const type = toAddress === accAddress ? ActivityOperTransferType.receiveFromAccount : ActivityOperTransferType.send; + + const { decimal, value } = transfer.rawContract; + + const amount = value ? hexToStringInteger(value) : undefined; + const amountSigned = amount ? (fromAddress === accAddress ? `-${amount}` : amount) : undefined; + + const asset: EvmActivityAsset = { + contract: EVM_TOKEN_SLUG, + amountSigned, + symbol: transfer.asset ?? undefined, + decimals: decimal ? Number(decimal) : undefined + }; + + return { + kind: ActivityOperKindEnum.transfer, + type, + fromAddress, + toAddress, + asset + }; + } + + if (transfer.category === AssetTransfersCategory.INTERNAL) { + // fromAddress is contract address for 'internal' transfers + const type = toAddress === accAddress ? ActivityOperTransferType.receive : ActivityOperTransferType.send; + + const { decimal, value } = transfer.rawContract; + + const amount = value ? hexToStringInteger(value) : undefined; + const amountSigned = amount ? (fromAddress === accAddress ? `-${amount}` : amount) : undefined; + + const asset: EvmActivityAsset = { + contract: EVM_TOKEN_SLUG, + amountSigned, + symbol: transfer.asset ?? undefined, + decimals: decimal ? Number(decimal) : undefined + }; + + return { + kind: ActivityOperKindEnum.transfer, + type, + fromAddress, + toAddress, + asset + }; + } + + if (transfer.category === AssetTransfersCategory.ERC721) { + if (!contractAddress) return buildInteraction(transfer, accAddress); + + const tokenId = transfer.erc721TokenId; + + const asset: EvmActivityAsset = { + contract: contractAddress, + tokenId: tokenId ? hexToStringInteger(tokenId) : undefined, + amountSigned: fromAddress === accAddress ? '-1' : '1', + symbol: transfer.asset ?? undefined, + decimals: 0, + nft: true + }; + + return { + kind: ActivityOperKindEnum.transfer, + type, + fromAddress, + toAddress, + asset + }; + } + + if (transfer.category === AssetTransfersCategory.ERC1155) { + const erc1155Metadata = transfer.erc1155Metadata?.at(0); + + if (transfer.hash === '0xf1ec44d7cbff7eca47ec34675eab1b1a36a2b0fa3e480acc441c71290fff2508') + console.log('FUCK:', 1, contractAddress, erc1155Metadata, transfer); + + if (!contractAddress || !erc1155Metadata) return buildInteraction(transfer, accAddress); + + const { tokenId, value } = erc1155Metadata; + + const amount = hexToStringInteger(value); + + const asset: EvmActivityAsset = { + contract: contractAddress, + tokenId: tokenId ? hexToStringInteger(tokenId) : undefined, + amountSigned: fromAddress === accAddress ? `-${amount}` : amount, + symbol: transfer.asset ?? undefined, + decimals: 0, + nft: true + }; + + if (transfer.hash === '0xf1ec44d7cbff7eca47ec34675eab1b1a36a2b0fa3e480acc441c71290fff2508') + console.log('FUCK:', 2, type, asset); + + return { + kind: ActivityOperKindEnum.transfer, + type, + fromAddress, + toAddress, + asset + }; + } + + if (transfer.category === AssetTransfersCategory.ERC20) { + if (!contractAddress) return buildInteraction(transfer, accAddress); + + const { decimal, value } = transfer.rawContract; + + const amount = value ? hexToStringInteger(value) : undefined; + const amountSigned = amount ? (fromAddress === accAddress ? `-${amount}` : amount) : undefined; + + const asset: EvmActivityAsset = { + contract: contractAddress, + amountSigned, + symbol: transfer.asset ?? undefined, + decimals: decimal ? Number(decimal) : undefined + }; + + return { + kind: ActivityOperKindEnum.transfer, + type, + fromAddress, + toAddress, + asset + }; + } + + if (transfer.category === AssetTransfersCategory.SPECIALNFT) { + if (!contractAddress) return buildInteraction(transfer, accAddress); + + const tokenId = transfer.tokenId; + + const { decimal, value } = transfer.rawContract; + + const amount = value ? hexToStringInteger(value) : undefined; + const amountSigned = amount ? (fromAddress === accAddress ? `-${amount}` : amount) : undefined; + + const asset: EvmActivityAsset = { + contract: contractAddress, + tokenId: tokenId ? hexToStringInteger(tokenId) : undefined, + amountSigned, + symbol: transfer.asset ?? undefined, + decimals: decimal ? Number(decimal) : undefined, + nft: true + }; + + return { + kind: ActivityOperKindEnum.transfer, + type, + fromAddress, + toAddress, + asset + }; + } + + return buildInteraction(transfer, accAddress); +} + +function buildInteraction(transfer: AssetTransfersWithMetadataResult, accAddress: string): EvmOperation { + const withAddress = transfer.from === accAddress ? transfer.to ?? undefined : transfer.from; + + return { kind: ActivityOperKindEnum.interaction, withAddress }; +} + +function hexToStringInteger(hex: string) { + return BigInt(hex).toString(); +} diff --git a/src/lib/activity/types.ts b/src/lib/activity/types.ts index 7e9200239..2390dad9e 100644 --- a/src/lib/activity/types.ts +++ b/src/lib/activity/types.ts @@ -73,6 +73,7 @@ export interface EvmActivity extends ChainActivityBase { chainId: number; blockExplorerUrl?: string; operations: EvmOperation[]; + blockHeight: `${number}`; } interface EvmOperationBase extends OperationBase { diff --git a/yarn.lock b/yarn.lock index 0bc8fd0e4..99f7ab929 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1669,6 +1669,336 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@ethersproject/abi@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" + integrity sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/abstract-provider@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz#b0a8550f88b6bf9d51f90e4795d48294630cb9ef" + integrity sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + +"@ethersproject/abstract-signer@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz#13f4f32117868452191a4649723cb086d2b596b2" + integrity sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/address@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.7.0.tgz#19b56c4d74a3b0a46bfdbb6cfcc0a153fc697f37" + integrity sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + +"@ethersproject/base64@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.7.0.tgz#ac4ee92aa36c1628173e221d0d01f53692059e1c" + integrity sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + +"@ethersproject/basex@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.7.0.tgz#97034dc7e8938a8ca943ab20f8a5e492ece4020b" + integrity sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + +"@ethersproject/bignumber@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.7.0.tgz#e2f03837f268ba655ffba03a57853e18a18dc9c2" + integrity sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + bn.js "^5.2.1" + +"@ethersproject/bytes@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.7.0.tgz#a00f6ea8d7e7534d6d87f47188af1148d71f155d" + integrity sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/constants@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.7.0.tgz#df80a9705a7e08984161f09014ea012d1c75295e" + integrity sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + +"@ethersproject/contracts@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + +"@ethersproject/hash@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" + integrity sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/hdnode@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.7.0.tgz#e627ddc6b466bc77aebf1a6b9e47405ca5aef9cf" + integrity sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/json-wallets@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz#5e3355287b548c32b368d91014919ebebddd5360" + integrity sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g== + dependencies: + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/pbkdf2" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + aes-js "3.0.0" + scrypt-js "3.0.1" + +"@ethersproject/keccak256@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.7.0.tgz#3186350c6e1cd6aba7940384ec7d6d9db01f335a" + integrity sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + js-sha3 "0.8.0" + +"@ethersproject/logger@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.7.0.tgz#6ce9ae168e74fecf287be17062b590852c311892" + integrity sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig== + +"@ethersproject/networks@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.7.1.tgz#118e1a981d757d45ccea6bb58d9fd3d9db14ead6" + integrity sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/pbkdf2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz#d2267d0a1f6e123f3771007338c47cccd83d3102" + integrity sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + +"@ethersproject/properties@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.7.0.tgz#a6e12cb0439b878aaf470f1902a176033067ed30" + integrity sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw== + dependencies: + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/providers@^5.7.0": + version "5.7.2" + resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" + integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/base64" "^5.7.0" + "@ethersproject/basex" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/sha2" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/web" "^5.7.0" + bech32 "1.1.4" + ws "7.4.6" + +"@ethersproject/random@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.7.0.tgz#af19dcbc2484aae078bb03656ec05df66253280c" + integrity sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/rlp@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.7.0.tgz#de39e4d5918b9d74d46de93af80b7685a9c21304" + integrity sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/sha2@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.7.0.tgz#9a5f7a7824ef784f7f7680984e593a800480c9fb" + integrity sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + hash.js "1.1.7" + +"@ethersproject/signing-key@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.7.0.tgz#06b2df39411b00bc57c7c09b01d1e41cf1b16ab3" + integrity sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + bn.js "^5.2.1" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/strings@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.7.0.tgz#54c9d2a7c57ae8f1205c88a9d3a56471e14d5ed2" + integrity sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/transactions@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.7.0.tgz#91318fc24063e057885a6af13fdb703e1f993d3b" + integrity sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ== + dependencies: + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/rlp" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + +"@ethersproject/units@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.7.0.tgz#637b563d7e14f42deeee39245275d477aae1d8b1" + integrity sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg== + dependencies: + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + +"@ethersproject/wallet@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.7.0.tgz#4e5d0790d96fe21d61d38fb40324e6c7ef350b2d" + integrity sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA== + dependencies: + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/hdnode" "^5.7.0" + "@ethersproject/json-wallets" "^5.7.0" + "@ethersproject/keccak256" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/random" "^5.7.0" + "@ethersproject/signing-key" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/wordlists" "^5.7.0" + +"@ethersproject/web@^5.7.0": + version "5.7.1" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.7.1.tgz#de1f285b373149bee5928f4eb7bcb87ee5fbb4ae" + integrity sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w== + dependencies: + "@ethersproject/base64" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + +"@ethersproject/wordlists@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.7.0.tgz#8fb2c07185d68c3e09eb3bfd6e779ba2774627f5" + integrity sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA== + dependencies: + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/strings" "^5.7.0" + "@fingerprintjs/fingerprintjs-pro@^3.8.6": version "3.9.0" resolved "https://registry.yarnpkg.com/@fingerprintjs/fingerprintjs-pro/-/fingerprintjs-pro-3.9.0.tgz#fb8b7619e19222fa756fa6c319368bfaf8d63465" @@ -4524,6 +4854,11 @@ address@^1.1.2: resolved "https://registry.yarnpkg.com/address/-/address-1.2.1.tgz#25bb61095b7522d65b357baa11bc05492d4c8acd" integrity sha512-B+6bi5D34+fDYENiH5qOlA0cV2rAGKuWZ9LeyUUehbXy8e0VS9e498yO0Jeeh+iM+6KbfudHTFjXw2MmJD4QRA== +aes-js@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" + integrity sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw== + ag-channel@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ag-channel/-/ag-channel-5.0.0.tgz#c2c00dfbe372ae43e0466ec89e29aca1bbb2fb3e" @@ -4594,6 +4929,26 @@ ajv@^8.9.0: require-from-string "^2.0.2" uri-js "^4.2.2" +alchemy-sdk@^3.4.8: + version "3.4.8" + resolved "https://registry.yarnpkg.com/alchemy-sdk/-/alchemy-sdk-3.4.8.tgz#ef84a3efe00b5e5924da79ef77c54cdb943e8772" + integrity sha512-JVyXuARxys8cI+Gu9KdvKr9TWDbbyvUY7cV2QkX7v4skCZUS01n5OqD5tA8iy5cLzU6XEN+MM6f9h4bDDFDmqA== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/contracts" "^5.7.0" + "@ethersproject/hash" "^5.7.0" + "@ethersproject/networks" "^5.7.0" + "@ethersproject/providers" "^5.7.0" + "@ethersproject/units" "^5.7.0" + "@ethersproject/wallet" "^5.7.0" + "@ethersproject/web" "^5.7.0" + axios "^1.7.4" + sturdy-websocket "^0.2.1" + websocket "^1.0.34" + ansi-escapes@^4.2.1: version "4.3.1" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" @@ -5091,6 +5446,11 @@ batch@0.6.1: resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== +bech32@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" + integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== + big.js@^6.2.1: version "6.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.2.2.tgz#be3bb9ac834558b53b099deef2a1d06ac6368e1a" @@ -5377,6 +5737,13 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +bufferutil@^4.0.1: + version "4.0.8" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" + integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== + dependencies: + node-gyp-build "^4.3.0" + bundle-name@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" @@ -6226,7 +6593,7 @@ debounce@^1.2.1: resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== -debug@2.6.9, debug@^2.6.0: +debug@2.6.9, debug@^2.2.0, debug@^2.6.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -6629,7 +6996,7 @@ electron-to-chromium@^1.4.668, electron-to-chromium@^1.5.4: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.12.tgz#ee31756eaa2e06f2aa606f170b7ad06dd402b4e4" integrity sha512-tIhPkdlEoCL1Y+PToq3zRNehUaKp3wBX/sr7aclAWdIWjvqAe/Im/H0SiCM4c1Q8BLPHCdoJTol+ZblflydehA== -elliptic@^6.5.3, elliptic@^6.5.4: +elliptic@6.5.4, elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== @@ -6839,7 +7206,7 @@ es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@ es6-symbol "^3.1.3" next-tick "^1.1.0" -es5-ext@^0.10.62, es5-ext@^0.10.64: +es5-ext@^0.10.62, es5-ext@^0.10.63, es5-ext@^0.10.64: version "0.10.64" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714" integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== @@ -7995,7 +8362,7 @@ hash-base@^3.0.0: readable-stream "^3.6.0" safe-buffer "^5.2.0" -hash.js@^1.0.0, hash.js@^1.0.3: +hash.js@1.1.7, hash.js@^1.0.0, hash.js@^1.0.3: version "1.1.7" resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== @@ -9196,6 +9563,11 @@ jiti@^1.20.0, jiti@^1.21.0: resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -10028,6 +10400,11 @@ node-forge@^1, node-forge@^1.3.1: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== +node-gyp-build@^4.3.0: + version "4.8.3" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.3.tgz#9187216d24dbee29e44eb20d2ebf62a296bbea1a" + integrity sha512-EMS95CMJzdoSKoIiXo8pxKoL8DYxwIZXYlLmgPb8KUv794abpnLK6ynsCAWNliOjREKruYKdzbh76HHYUHX7nw== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -12061,6 +12438,11 @@ schema-utils@^4.0.0, schema-utils@^4.2.0: ajv-formats "^2.1.1" ajv-keywords "^5.1.0" +scrypt-js@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" + integrity sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA== + scryptsy@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-2.1.0.tgz#8d1e8d0c025b58fdd25b6fa9a0dc905ee8faa790" @@ -12483,7 +12865,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-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: 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== @@ -12570,7 +12961,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== @@ -12609,6 +13007,11 @@ strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +sturdy-websocket@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/sturdy-websocket/-/sturdy-websocket-0.2.1.tgz#20a58fd53372ef96eaa08f3c61c91a10b07c7c05" + integrity sha512-NnzSOEKyv4I83qbuKw9ROtJrrT6Z/Xt7I0HiP/e6H6GnpeTDvzwGIGeJ8slai+VwODSHQDooW2CAilJwT9SpRg== + stylehacks@^6.1.1: version "6.1.1" resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-6.1.1.tgz#543f91c10d17d00a440430362d419f79c25545a6" @@ -13403,6 +13806,13 @@ useragent@^2.3.0: lru-cache "4.1.x" tmp "0.0.x" +utf-8-validate@^5.0.2: + version "5.0.10" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz#d7d10ea39318171ca982718b6b96a8d2442571a2" + integrity sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ== + dependencies: + node-gyp-build "^4.3.0" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -13755,6 +14165,18 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== +websocket@^1.0.34: + version "1.0.35" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.35.tgz#374197207d7d4cc4c36cbf8a1bb886ee52a07885" + integrity sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.63" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -13858,7 +14280,16 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -"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== @@ -13905,6 +14336,11 @@ write@^1.0.3: dependencies: mkdirp "^0.5.1" +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + ws@8.18.0, ws@^8.18.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" @@ -13940,6 +14376,11 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" From 247f7d02bd744beba990206e89c57e7d7a43a23f Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 17 Nov 2024 15:59:46 +0200 Subject: [PATCH 2/3] TW-1587: [research] Activity History on Alchemy. Multichain --- src/app/templates/activity/MultichainList.tsx | 30 ++++--------- src/lib/activity/evm/fetch.ts | 44 ++++++++++++++----- src/lib/activity/evm/parse/alchemy.ts | 6 --- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/src/app/templates/activity/MultichainList.tsx b/src/app/templates/activity/MultichainList.tsx index f5b7db17d..bde32faf4 100644 --- a/src/app/templates/activity/MultichainList.tsx +++ b/src/app/templates/activity/MultichainList.tsx @@ -1,10 +1,8 @@ import React, { memo, useMemo } from 'react'; -import { AxiosError } from 'axios'; - import { useLoadPartnersPromo } from 'app/hooks/use-load-partners-promo'; import { Activity, EvmActivity, TezosActivity } from 'lib/activity'; -import { getEvmAssetTransactions } from 'lib/activity/evm'; +import { getEvmActivities } from 'lib/activity/evm/fetch'; import { parseTezosOperationsGroup } from 'lib/activity/tezos'; import fetchTezosOperationsGroups from 'lib/activity/tezos/fetch'; import { TzktApiChainId } from 'lib/apis/tzkt'; @@ -163,15 +161,11 @@ function isTezosActivity(activity: Activity): activity is TezosActivity { class EvmActivityLoader { activities: EvmActivity[] = []; + reachedTheEnd = false; lastError: unknown; - private nextPage: number | nullish; constructor(readonly chainId: number, readonly accountAddress: string) {} - get reachedTheEnd() { - return this.nextPage === null; - } - async loadNext(edgeDate: string | undefined, signal: AbortSignal, assetSlug?: string) { if (edgeDate) { const lastAct = this.activities.at(-1); @@ -179,23 +173,18 @@ class EvmActivityLoader { } try { - const { accountAddress, chainId, nextPage } = this; + const { accountAddress, chainId } = this; - if (nextPage === null) return; + if (this.reachedTheEnd || this.lastError) return; - const { nextPage: newNextPage, activities: newActivities } = await getEvmAssetTransactions( - accountAddress, - chainId, - assetSlug, - nextPage, - signal - ); + const olderThanBlockHeight = this.activities.at(this.activities.length - 1)?.blockHeight; - if (signal.aborted) return; + const newActivities = await getEvmActivities(chainId, accountAddress, olderThanBlockHeight, signal); - this.nextPage = newNextPage; + if (signal.aborted) return; if (newActivities.length) this.activities = this.activities.concat(newActivities); + else this.reachedTheEnd = true; delete this.lastError; } catch (error) { @@ -203,8 +192,7 @@ class EvmActivityLoader { console.error(error); - if (error instanceof AxiosError && error.status === 501) this.nextPage = null; - else this.lastError = error; + this.lastError = error; } } } diff --git a/src/lib/activity/evm/fetch.ts b/src/lib/activity/evm/fetch.ts index 2b5b56514..11bedc765 100644 --- a/src/lib/activity/evm/fetch.ts +++ b/src/lib/activity/evm/fetch.ts @@ -1,3 +1,4 @@ +// TODO: Make requests via axios, not SDK import { Alchemy, AssetTransfersCategory, @@ -121,18 +122,9 @@ async function fetchTransfers( network: chainName }); - const reqOptions: AssetTransfersWithMetadataParams = { - order: SortingOrder.DESCENDING, - category: Object.values(AssetTransfersCategory), - excludeZeroValue: true, - withMetadata: true, - toBlock: olderThanBlockHeight ? '0x' + (BigInt(olderThanBlockHeight) - BigInt(1)).toString(16) : undefined, - maxCount: 50 - }; - const [transfersFrom, transfersTo] = await Promise.all([ - alchemy.core.getAssetTransfers({ ...reqOptions, fromAddress: accAddress }).then(r => r.transfers), - alchemy.core.getAssetTransfers({ ...reqOptions, toAddress: accAddress }).then(r => r.transfers) + _fetchTransfers(alchemy, accAddress, false, olderThanBlockHeight), + _fetchTransfers(alchemy, accAddress, true, olderThanBlockHeight) ]); const allTransfers = transfersFrom @@ -156,6 +148,29 @@ async function fetchTransfers( return uniqBy(allTransfers, uniqByKey); } +async function _fetchTransfers(alchemy: Alchemy, accAddress: string, toAcc = false, olderThanBlockHeight?: string) { + const toBlock = olderThanBlockHeight ? '0x' + (BigInt(olderThanBlockHeight) - BigInt(1)).toString(16) : undefined; + + const categories = new Set(Object.values(AssetTransfersCategory)); + const excludedCategory = EXCLUDED_CATEGORIES[alchemy.config.network]; + if (excludedCategory) categories.delete(excludedCategory); + + const reqOptions: AssetTransfersWithMetadataParams = { + order: SortingOrder.DESCENDING, + category: Array.from(categories), + excludeZeroValue: true, + withMetadata: true, + toBlock, + maxCount: 50 + }; + + if (toAcc) reqOptions.toAddress = accAddress; + else reqOptions.fromAddress = accAddress; + + // TODO: Seems like Alchemy SDK processes Error 429 itself + return alchemy.core.getAssetTransfers(reqOptions).then(r => r.transfers); +} + function calcSameTrailingHashes(transfers: AssetTransfersWithMetadataResult[]) { const trailingHash = transfers.at(transfers.length - 1)!.hash; if (transfers.at(0)!.hash === trailingHash) return transfers.length; // All are same, saving runtime @@ -167,6 +182,13 @@ function calcSameTrailingHashes(transfers: AssetTransfersWithMetadataResult[]) { return sameTrailingHashes; } +/** E.g. 'opt_mainnet' does not support 'internal' category */ +const EXCLUDED_CATEGORIES: Partial> = { + [Network.OPT_MAINNET]: AssetTransfersCategory.INTERNAL, + [Network.OPT_SEPOLIA]: AssetTransfersCategory.INTERNAL, + [Network.MATIC_AMOY]: AssetTransfersCategory.INTERNAL +}; + /** TODO: Verify this mapping */ const CHAINS_NAMES: Record = { 1: Network.ETH_MAINNET, diff --git a/src/lib/activity/evm/parse/alchemy.ts b/src/lib/activity/evm/parse/alchemy.ts index 2368aeb6f..55a7509ef 100644 --- a/src/lib/activity/evm/parse/alchemy.ts +++ b/src/lib/activity/evm/parse/alchemy.ts @@ -90,9 +90,6 @@ export function parseTransfer(transfer: AssetTransfersWithMetadataResult, accAdd if (transfer.category === AssetTransfersCategory.ERC1155) { const erc1155Metadata = transfer.erc1155Metadata?.at(0); - if (transfer.hash === '0xf1ec44d7cbff7eca47ec34675eab1b1a36a2b0fa3e480acc441c71290fff2508') - console.log('FUCK:', 1, contractAddress, erc1155Metadata, transfer); - if (!contractAddress || !erc1155Metadata) return buildInteraction(transfer, accAddress); const { tokenId, value } = erc1155Metadata; @@ -108,9 +105,6 @@ export function parseTransfer(transfer: AssetTransfersWithMetadataResult, accAdd nft: true }; - if (transfer.hash === '0xf1ec44d7cbff7eca47ec34675eab1b1a36a2b0fa3e480acc441c71290fff2508') - console.log('FUCK:', 2, type, asset); - return { kind: ActivityOperKindEnum.transfer, type, From 483de385ed4100b7c097551b26fa4bf676c4a05a Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 17 Nov 2024 19:10:13 +0200 Subject: [PATCH 3/3] TW-1587: [research] Activity History on Alchemy. + Approvals --- src/lib/activity/evm/fetch.ts | 85 +++++++++++++++++++-------- src/lib/activity/evm/parse/alchemy.ts | 30 +++++++++- 2 files changed, 90 insertions(+), 25 deletions(-) diff --git a/src/lib/activity/evm/fetch.ts b/src/lib/activity/evm/fetch.ts index 11bedc765..0d43031dd 100644 --- a/src/lib/activity/evm/fetch.ts +++ b/src/lib/activity/evm/fetch.ts @@ -17,7 +17,7 @@ import { TempleChainKind } from 'temple/types'; import { ActivityStatus, EvmActivity } from '../types'; import { parseGoldRushTransaction, parseGoldRushERC20Transfer } from './parse'; -import { parseTransfer } from './parse/alchemy'; +import { parseApprovalLog, parseTransfer } from './parse/alchemy'; export async function getEvmAssetTransactions( walletAddress: string, @@ -74,49 +74,54 @@ export async function getEvmAssetTransactions( export async function getEvmActivities( chainId: number, accAddress: string, - olderThanBlockHeight?: string, + olderThanBlockHeight?: `${number}`, signal?: AbortSignal ) { + const chainName = CHAINS_NAMES[chainId]; + if (!chainName) return []; + const accAddressLowercased = accAddress.toLowerCase(); - const allTransfers = await fetchTransfers(chainId, accAddress, olderThanBlockHeight, signal); + const allTransfers = await fetchTransfers(chainName, accAddress, olderThanBlockHeight, signal); if (!allTransfers.length) return []; - const groups = Object.entries(groupBy(allTransfers, 'hash')); + const allApprovals = await fetchApprovals( + chainName, + accAddress, + olderThanBlockHeight, + // Loading approvals withing the gap of received transfers. + // TODO: Mind the case of reaching response items number limit & not reaching block heights gap. + allTransfers.at(allTransfers.length - 1)?.blockNum + ); - const activities: EvmActivity[] = []; + const groups = Object.entries(groupBy(allTransfers, 'hash')); - for (const [hash, transfers] of groups) { + return groups.map(([hash, transfers]) => { const firstTransfer = transfers.at(0)!; - const operations = transfers.map(transfer => parseTransfer(transfer, accAddressLowercased)); + const approvals = allApprovals.filter(a => a.transactionHash === hash).map(approval => parseApprovalLog(approval)); + + const operations = transfers.map(transfer => parseTransfer(transfer, accAddressLowercased)).concat(approvals); - const activity: EvmActivity = { + return { chain: TempleChainKind.EVM, chainId, hash, - status: ActivityStatus.applied, + status: ActivityStatus.applied, // TODO: Differentiate - how? addedAt: firstTransfer.metadata.blockTimestamp, operations, operationsCount: operations.length, blockHeight: `${Number(firstTransfer.blockNum)}` }; - - activities.push(activity); - } - - return activities; + }); } async function fetchTransfers( - chainId: number, + chainName: Network, accAddress: string, - olderThanBlockHeight?: string, + olderThanBlockHeight?: `${number}`, signal?: AbortSignal ): Promise { - const chainName = CHAINS_NAMES[chainId]; - if (!chainName) return []; - const alchemy = new Alchemy({ apiKey: process.env._ALCHEMY_API_KEY, // TODO: To EnvVars network: chainName @@ -129,7 +134,7 @@ async function fetchTransfers( const allTransfers = transfersFrom .concat(transfersTo) - .toSorted((a, b) => (a.metadata.blockTimestamp > b.metadata.blockTimestamp ? -1 : 1)); + .toSorted((a, b) => (a.metadata.blockTimestamp < b.metadata.blockTimestamp ? 1 : -1)); if (!allTransfers.length) return []; @@ -148,9 +153,12 @@ async function fetchTransfers( return uniqBy(allTransfers, uniqByKey); } -async function _fetchTransfers(alchemy: Alchemy, accAddress: string, toAcc = false, olderThanBlockHeight?: string) { - const toBlock = olderThanBlockHeight ? '0x' + (BigInt(olderThanBlockHeight) - BigInt(1)).toString(16) : undefined; - +async function _fetchTransfers( + alchemy: Alchemy, + accAddress: string, + toAcc = false, + olderThanBlockHeight?: `${number}` +) { const categories = new Set(Object.values(AssetTransfersCategory)); const excludedCategory = EXCLUDED_CATEGORIES[alchemy.config.network]; if (excludedCategory) categories.delete(excludedCategory); @@ -160,7 +168,7 @@ async function _fetchTransfers(alchemy: Alchemy, accAddress: string, toAcc = fal category: Array.from(categories), excludeZeroValue: true, withMetadata: true, - toBlock, + toBlock: olderThanBlockToToBlockValue(olderThanBlockHeight), maxCount: 50 }; @@ -182,6 +190,35 @@ function calcSameTrailingHashes(transfers: AssetTransfersWithMetadataResult[]) { return sameTrailingHashes; } +function fetchApprovals( + chainName: Network, + accAddress: string, + olderThanBlockHeight?: `${number}`, + /** Hex string. Including said block. */ + fromBlock?: string +) { + const alchemy = new Alchemy({ + apiKey: process.env._ALCHEMY_API_KEY, // TODO: To EnvVars + network: chainName + }); + + return alchemy.core.getLogs({ + topics: [ + [ + '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', // Approval + '0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31' // ApprovalForAll + ], + `0x000000000000000000000000${accAddress.slice(2)}` + ], + toBlock: olderThanBlockToToBlockValue(olderThanBlockHeight), + fromBlock + }); +} + +function olderThanBlockToToBlockValue(olderThanBlockHeight: `${number}` | undefined) { + return olderThanBlockHeight ? '0x' + (BigInt(olderThanBlockHeight) - BigInt(1)).toString(16) : undefined; +} + /** E.g. 'opt_mainnet' does not support 'internal' category */ const EXCLUDED_CATEGORIES: Partial> = { [Network.OPT_MAINNET]: AssetTransfersCategory.INTERNAL, diff --git a/src/lib/activity/evm/parse/alchemy.ts b/src/lib/activity/evm/parse/alchemy.ts index 55a7509ef..7df4ef23e 100644 --- a/src/lib/activity/evm/parse/alchemy.ts +++ b/src/lib/activity/evm/parse/alchemy.ts @@ -1,4 +1,4 @@ -import { AssetTransfersCategory, AssetTransfersWithMetadataResult } from 'alchemy-sdk'; +import { AssetTransfersCategory, AssetTransfersWithMetadataResult, Log } from 'alchemy-sdk'; import { ActivityOperKindEnum, ActivityOperTransferType, EvmActivityAsset, EvmOperation } from 'lib/activity/types'; import { EVM_TOKEN_SLUG } from 'lib/assets/defaults'; @@ -169,6 +169,34 @@ export function parseTransfer(transfer: AssetTransfersWithMetadataResult, accAdd return buildInteraction(transfer, accAddress); } +export function parseApprovalLog(approval: Log): EvmOperation { + const spenderAddress = '0x' + approval.topics.at(2)!.slice(26); + + if (approval.topics[0] !== '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925') { + // Not Approval, but ApprovalForAll method + if (approval.data.endsWith('0')) return { kind: ActivityOperKindEnum.interaction, withAddress: approval.address }; + + const asset: EvmActivityAsset = { + contract: approval.address, + amountSigned: null, + nft: true + }; + + return { kind: ActivityOperKindEnum.approve, spenderAddress, asset }; + } + + const approvalOnERC721 = approval.topics.length === 4; + + const asset: EvmActivityAsset = { + contract: approval.address, + tokenId: approvalOnERC721 ? hexToStringInteger(approval.topics.at(3)!) : undefined, + amountSigned: approvalOnERC721 ? '1' : hexToStringInteger(approval.data), + nft: approvalOnERC721 ? true : undefined // Still exhaustive? + }; + + return { kind: ActivityOperKindEnum.approve, spenderAddress, asset }; +} + function buildInteraction(transfer: AssetTransfersWithMetadataResult, accAddress: string): EvmOperation { const withAddress = transfer.from === accAddress ? transfer.to ?? undefined : transfer.from;