diff --git a/src/sdk/builders/blocks-builder.ts b/src/sdk/builders/blocks-builder.ts index ef08ef00..60bd1721 100644 --- a/src/sdk/builders/blocks-builder.ts +++ b/src/sdk/builders/blocks-builder.ts @@ -5,14 +5,12 @@ import { BlocksService } from '@services/blocks/block-service'; import { IProviderService } from '@services/providers'; import { RPCBlockSource } from '@services/blocks/block-sources/rpc-block-source'; import { FallbackBlockSource } from '@services/blocks/block-sources/fallback-block-source'; -import { AlchemyBlockSource } from '@services/blocks/block-sources/alchemy-block-source'; export type BlocksSourceInput = | { type: 'custom'; instance: IBlocksSource } | { type: 'custom-with-underlying'; underlyingSource: BlocksSourceInput; build: (underlying: IBlocksSource) => IBlocksSource } | { type: 'defi-llama' } | { type: 'rpc' } - | { type: 'alchemy'; apiKey: string } | { type: 'fallback'; sources: BlocksSourceInput[] }; export type BuildBlocksParams = { source: BlocksSourceInput }; @@ -35,8 +33,6 @@ function buildSource( return new DefiLlamaBlockSource(fetchService, providerService); case 'rpc': return new RPCBlockSource(providerService); - case 'alchemy': - return new AlchemyBlockSource({ key: source.apiKey, fetchService, providerService }); case 'fallback': return new FallbackBlockSource(source.sources.map((source) => buildSource(source, { fetchService, providerService }))); case 'custom': diff --git a/src/services/blocks/block-sources/alchemy-block-source.ts b/src/services/blocks/block-sources/alchemy-block-source.ts deleted file mode 100644 index 0fdac251..00000000 --- a/src/services/blocks/block-sources/alchemy-block-source.ts +++ /dev/null @@ -1,90 +0,0 @@ -import qs from 'qs'; -import { ChainId, Timestamp, TimeString } from '@types'; -import { ALCHEMY_NETWORKS } from '@shared/alchemy'; -import { IFetchService } from '@services/fetch'; -import { BlockInput, BlockResult, IBlocksSource } from '../types'; -import { IProviderService } from '@services/providers'; -import { closestBlock } from './utils'; - -export class AlchemyBlockSource implements IBlocksSource { - private readonly providerService: IProviderService; - private readonly fetchService: IFetchService; - private readonly apiKey: string; - - constructor({ key, fetchService, providerService }: { key: string; fetchService: IFetchService; providerService: IProviderService }) { - this.fetchService = fetchService; - this.apiKey = key; - this.providerService = providerService; - } - - supportedChains(): ChainId[] { - return Object.entries(ALCHEMY_NETWORKS) - .filter(([, { blocks }]) => blocks.supported) - .map(([chainId]) => Number(chainId)); - } - - async getBlocksClosestToTimestamps({ - timestamps, - config, - }: { - timestamps: BlockInput[]; - config?: { timeout?: TimeString }; - }): Promise>> { - const grouped = groupByTimestamp(timestamps); - const promises = Object.entries(grouped).map(async ([timestamp, chains]) => { - const queryParams = { - // TODO: Alchemy added a limit of networks. Max is 3. We would need to split the request - networks: chains.map((chainId) => ALCHEMY_NETWORKS[chainId].key), - timestamp, - direction: 'AFTER', - }; - const queryString = qs.stringify(queryParams, { skipNulls: true, arrayFormat: 'repeat' }); - const response = await this.fetchService.fetch( - `https://api.g.alchemy.com/data/v1/${this.apiKey}/utility/blocks/by-timestamp?${queryString}`, - { timeout: config?.timeout } - ); - if (!response.ok) { - throw new Error(`Failed to fetch blocks for timestamp ${timestamp} in chains ${chains.join(',')}. Error was ${await response.text()}`); - } - const result: Response = await response.json(); - - const alchemyNetworks = Object.fromEntries(Object.entries(ALCHEMY_NETWORKS).map(([chainId, { key }]) => [key, Number(chainId)])); - return Promise.all( - result.data.map(async ({ network, block }) => { - const chainId = alchemyNetworks[network]; - const blockAfter = { timestamp: Math.floor(new Date(block.timestamp).getTime() / 1000), block: BigInt(block.number) }; - const previousBlock = await this.providerService - .getViemPublicClient({ chainId }) - .getBlock({ blockNumber: blockAfter.block - 1n }) - .then((block) => ({ block: block.number, timestamp: Number(block.timestamp) })); - return { chainId, timestamp: Number(timestamp), block: closestBlock(previousBlock, blockAfter, Number(timestamp)) }; - }) - ); - }); - const allResults = await Promise.all(promises); - const result: Record> = {}; - allResults.flat().forEach(({ chainId, timestamp, block }) => { - if (!(chainId in result)) { - result[chainId] = {}; - } - result[chainId][timestamp] = block; - }); - return result; - } -} - -function groupByTimestamp(timestamps: BlockInput[]) { - const result: Record = {}; - for (const { timestamp, chainId } of timestamps) { - if (!(timestamp in result)) { - result[timestamp] = [chainId]; - } else { - result[timestamp].push(chainId); - } - } - return result; -} - -type Response = { - data: { network: string; block: { number: number; timestamp: string } }[]; -}; diff --git a/src/shared/alchemy.ts b/src/shared/alchemy.ts index 7731f0f0..8bacbda5 100644 --- a/src/shared/alchemy.ts +++ b/src/shared/alchemy.ts @@ -1,34 +1,33 @@ import { Chains } from '@chains'; import { ChainId } from '@types'; -export const ALCHEMY_NETWORKS: Record = { - [Chains.ETHEREUM.chainId]: { key: 'eth-mainnet', price: { supported: true }, blocks: { supported: true } }, - [Chains.ETHEREUM_SEPOLIA.chainId]: { key: 'eth-sepolia', price: { supported: false }, blocks: { supported: false } }, - [Chains.OPTIMISM.chainId]: { key: 'opt-mainnet', price: { supported: true }, blocks: { supported: true } }, - [Chains.ARBITRUM.chainId]: { key: 'arb-mainnet', price: { supported: true }, blocks: { supported: true } }, - [Chains.POLYGON.chainId]: { key: 'polygon-mainnet', price: { supported: true }, blocks: { supported: true } }, - [Chains.POLYGON_MUMBAI.chainId]: { key: 'polygon-mumbai', price: { supported: false }, blocks: { supported: false } }, - [Chains.ASTAR.chainId]: { key: 'astar-mainnet', price: { supported: false }, blocks: { supported: false } }, - [Chains.BLAST.chainId]: { key: 'blast-mainnet', price: { supported: true }, blocks: { supported: true } }, - [Chains.BNB_CHAIN.chainId]: { key: 'bnb-mainnet', price: { supported: true }, blocks: { supported: true } }, - [Chains.AVALANCHE.chainId]: { key: 'avax-mainnet', price: { supported: true }, blocks: { supported: true } }, - [Chains.FANTOM.chainId]: { key: 'fantom-mainnet', price: { supported: true }, blocks: { supported: false } }, - [Chains.METIS_ANDROMEDA.chainId]: { key: 'metis-mainnet', price: { supported: true }, blocks: { supported: false } }, +export const ALCHEMY_NETWORKS: Record = { + [Chains.ETHEREUM.chainId]: { key: 'eth-mainnet', price: { supported: true } }, + [Chains.ETHEREUM_SEPOLIA.chainId]: { key: 'eth-sepolia', price: { supported: false } }, + [Chains.OPTIMISM.chainId]: { key: 'opt-mainnet', price: { supported: true } }, + [Chains.ARBITRUM.chainId]: { key: 'arb-mainnet', price: { supported: true } }, + [Chains.POLYGON.chainId]: { key: 'polygon-mainnet', price: { supported: true } }, + [Chains.POLYGON_MUMBAI.chainId]: { key: 'polygon-mumbai', price: { supported: false } }, + [Chains.ASTAR.chainId]: { key: 'astar-mainnet', price: { supported: false } }, + [Chains.BLAST.chainId]: { key: 'blast-mainnet', price: { supported: true } }, + [Chains.BNB_CHAIN.chainId]: { key: 'bnb-mainnet', price: { supported: true } }, + [Chains.AVALANCHE.chainId]: { key: 'avax-mainnet', price: { supported: true } }, + [Chains.FANTOM.chainId]: { key: 'fantom-mainnet', price: { supported: true } }, + [Chains.METIS_ANDROMEDA.chainId]: { key: 'metis-mainnet', price: { supported: true } }, [Chains.POLYGON_ZKEVM.chainId]: { key: 'polygonzkevm-mainnet', price: { supported: true }, - blocks: { supported: false }, }, - [Chains.BASE.chainId]: { key: 'base-mainnet', price: { supported: true }, blocks: { supported: true } }, - [Chains.GNOSIS.chainId]: { key: 'gnosis-mainnet', price: { supported: true }, blocks: { supported: false } }, - [Chains.SCROLL.chainId]: { key: 'scroll-mainnet', price: { supported: true }, blocks: { supported: false } }, - [Chains.opBNB.chainId]: { key: 'opbnb-mainnet', price: { supported: true }, blocks: { supported: false } }, - [Chains.MANTLE.chainId]: { key: 'mantle-mainnet', price: { supported: false }, blocks: { supported: false } }, - [Chains.ROOTSTOCK.chainId]: { key: 'rootstock-mainnet', price: { supported: false }, blocks: { supported: false } }, - [Chains.LINEA.chainId]: { key: 'linea-mainnet', price: { supported: true }, blocks: { supported: false } }, - [Chains.SONIC.chainId]: { key: 'sonic-mainnet', price: { supported: false }, blocks: { supported: false } }, - [Chains.ZK_SYNC_ERA.chainId]: { key: 'zksync-mainnet', price: { supported: true }, blocks: { supported: true } }, - [Chains.CELO.chainId]: { key: 'celo-mainnet', price: { supported: false }, blocks: { supported: true } }, - [Chains.PLASMA.chainId]: { key: 'plasma-mainnet', price: { supported: false }, blocks: { supported: false } }, - [Chains.UNICHAIN.chainId]: { key: 'unichain-mainnet', price: { supported: false }, blocks: { supported: false } }, + [Chains.BASE.chainId]: { key: 'base-mainnet', price: { supported: true } }, + [Chains.GNOSIS.chainId]: { key: 'gnosis-mainnet', price: { supported: true } }, + [Chains.SCROLL.chainId]: { key: 'scroll-mainnet', price: { supported: true } }, + [Chains.opBNB.chainId]: { key: 'opbnb-mainnet', price: { supported: true } }, + [Chains.MANTLE.chainId]: { key: 'mantle-mainnet', price: { supported: false } }, + [Chains.ROOTSTOCK.chainId]: { key: 'rootstock-mainnet', price: { supported: false } }, + [Chains.LINEA.chainId]: { key: 'linea-mainnet', price: { supported: true } }, + [Chains.SONIC.chainId]: { key: 'sonic-mainnet', price: { supported: false } }, + [Chains.ZK_SYNC_ERA.chainId]: { key: 'zksync-mainnet', price: { supported: true } }, + [Chains.CELO.chainId]: { key: 'celo-mainnet', price: { supported: false } }, + [Chains.PLASMA.chainId]: { key: 'plasma-mainnet', price: { supported: false } }, + [Chains.UNICHAIN.chainId]: { key: 'unichain-mainnet', price: { supported: false } }, }; diff --git a/src/shared/defi-llama.ts b/src/shared/defi-llama.ts index 9304a1f0..1b504d47 100644 --- a/src/shared/defi-llama.ts +++ b/src/shared/defi-llama.ts @@ -27,7 +27,6 @@ const CHAIN_ID_TO_KEY: Record> = { [Chains.VELAS.chainId]: 'velas', [Chains.ROOTSTOCK.chainId]: 'rsk', [Chains.EVMOS.chainId]: 'evmos', - [Chains.CANTO.chainId]: 'canto', [Chains.POLYGON_ZKEVM.chainId]: 'polygon_zkevm', [Chains.KAVA.chainId]: 'kava', [Chains.BASE.chainId]: 'base', diff --git a/test/integration/services/blocks/block-sources.spec.ts b/test/integration/services/blocks/block-sources.spec.ts index baba8f45..e5b3c9db 100644 --- a/test/integration/services/blocks/block-sources.spec.ts +++ b/test/integration/services/blocks/block-sources.spec.ts @@ -11,7 +11,6 @@ import { BlockResult, IBlocksSource } from '@services/blocks'; import { ProviderService } from '@services/providers/provider-service'; import { PublicRPCsProviderSource } from '@services/providers/provider-sources/public-rpcs-provider'; import { FallbackBlockSource } from '@services/blocks/block-sources/fallback-block-source'; -import { AlchemyBlockSource } from '@services/blocks/block-sources/alchemy-block-source'; import { AlchemyProviderSource } from '@services/providers/provider-sources/alchemy-provider'; dotenv.config(); chai.use(chaiAsPromised); @@ -37,20 +36,14 @@ const FETCH_SERVICE = new FetchService(); const DEFI_LLAMA_BLOCKS_SOURCE = new DefiLlamaBlockSource(FETCH_SERVICE, PROVIDER_SERVICE); const RPC_BLOCKS_SOURCE = new RPCBlockSource(PROVIDER_SERVICE); const FALLBACK_BLOCK_SOURCE = new FallbackBlockSource([RPC_BLOCKS_SOURCE, DEFI_LLAMA_BLOCKS_SOURCE]); -const ALCHEMY_BLOCK_SOURCE = new AlchemyBlockSource({ - key: process.env.ALCHEMY_API_KEY!, - fetchService: FETCH_SERVICE, - providerService: PROVIDER_SERVICE, -}); jest.retryTimes(2); jest.setTimeout(ms('3m')); describe('Blocks Sources', () => { - // blocksSourceTest({ title: 'Defi Llama Source', source: DEFI_LLAMA_BLOCKS_SOURCE }); DefiLlama is not passing tests since they sometimes return a block that is not exactly the closed, but the second closest + // blocksSourceTest({ title: 'Defi Llama Source', source: DEFI_LLAMA_BLOCKS_SOURCE }); DefiLlama is not passing tests since they sometimes return a block that is not exactly the closest, but the second closest blocksSourceTest({ title: 'RPC Source', source: RPC_BLOCKS_SOURCE }); blocksSourceTest({ title: 'Fallback Source', source: FALLBACK_BLOCK_SOURCE }); - blocksSourceTest({ title: 'Alchemy Source', source: ALCHEMY_BLOCK_SOURCE }); function blocksSourceTest({ title, source }: { title: string; source: IBlocksSource }) { describe(title, () => {