-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: implements BlockNumberService #13
Changes from 36 commits
06ef8af
9802a89
546d76e
5b57a2d
f291b45
0e8fc5a
baa144a
9965c35
6215697
2748ca2
ab869a4
212960a
21f025b
ecd91e9
4a38c03
711216d
c7a9035
18d537a
5ad1369
6875aea
80da90d
9a60076
60a57e1
841dc96
cc0a6f0
0b44abe
32a49b7
557a073
163669b
776ba42
ea13674
0c01a4b
472dab2
0954b57
7f0fa3a
e97d367
927d237
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Caip2ChainId } from "../types.js"; | ||
|
||
export class ChainWithoutProvider extends Error { | ||
constructor(chainId: Caip2ChainId) { | ||
super(`Chain ${chainId} has no provider defined.`); | ||
|
||
this.name = "ChainWithoutProvider"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export class EmptyRpcUrls extends Error { | ||
constructor() { | ||
super(`At least one chain with its RPC endpoint must be defined.`); | ||
|
||
this.name = "EmptyRpcUrls"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
export * from "./chainWithoutProvider.js"; | ||
export * from "./emptyRpcUrls.js"; | ||
export * from "./invalidChain.js"; | ||
export * from "./invalidTimestamp.js"; | ||
export * from "./lastBlockEpoch.js"; | ||
export * from "./timestampNotFound.js"; | ||
export * from "./unexpectedSearchRange.js"; | ||
export * from "./unsupportedBlockNumber.js"; | ||
export * from "./unsupportedBlockTimestamps.js"; | ||
export * from "./unsupportedChain.js"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { Caip2ChainId } from "../types.js"; | ||
|
||
export class UnsupportedChain extends Error { | ||
constructor(chainId: Caip2ChainId) { | ||
super(`Chain ${chainId} is not supported.`); | ||
|
||
this.name = "UnsupportedChain"; | ||
} | ||
} |
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { EBO_SUPPORTED_CHAINS_CONFIG, ILogger } from "@ebo-agent/shared"; | ||
import { FallbackTransport, HttpTransport, PublicClient } from "viem"; | ||
|
||
import { UnsupportedChain } from "../exceptions/unsupportedChain.js"; | ||
import { Caip2ChainId } from "../types.js"; | ||
import { Caip2Utils } from "../utils/index.js"; | ||
import { EvmBlockNumberProvider } from "./evmBlockNumberProvider.js"; | ||
|
||
const DEFAULT_PROVIDER_CONFIG = { | ||
blocksLookback: 10_000n, | ||
deltaMultiplier: 2n, | ||
}; | ||
|
||
export class BlockNumberProviderFactory { | ||
public static buildProvider( | ||
chainId: Caip2ChainId, | ||
client: PublicClient<FallbackTransport<HttpTransport[]>>, | ||
logger: ILogger, | ||
) { | ||
const chainNamespace = Caip2Utils.getNamespace(chainId); | ||
|
||
switch (chainNamespace) { | ||
case EBO_SUPPORTED_CHAINS_CONFIG.evm.namespace: | ||
return new EvmBlockNumberProvider(client, DEFAULT_PROVIDER_CONFIG, logger); | ||
|
||
default: | ||
throw new UnsupportedChain(chainId); | ||
} | ||
Comment on lines
+30
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a switch for just two cases is too much i think but if this is expected to grow then feel free to ignore this and leave it as it is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will grow, yep |
||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing natspec There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ended up adding docs to the |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { EBO_SUPPORTED_CHAIN_IDS, ILogger } from "@ebo-agent/shared"; | ||
import { createPublicClient, fallback, http } from "viem"; | ||
|
||
import { ChainWithoutProvider, EmptyRpcUrls, UnsupportedChain } from "../exceptions/index.js"; | ||
import { BlockNumberProvider } from "../providers/blockNumberProvider.js"; | ||
import { BlockNumberProviderFactory } from "../providers/blockNumberProviderFactory.js"; | ||
import { Caip2ChainId } from "../types.js"; | ||
|
||
type RpcUrl = NonNullable<Parameters<typeof http>[0]>; | ||
|
||
export class BlockNumberService { | ||
private blockNumberProviders: Map<Caip2ChainId, BlockNumberProvider>; | ||
|
||
constructor( | ||
chainRpcUrls: Map<Caip2ChainId, RpcUrl[]>, | ||
private readonly logger: ILogger, | ||
) { | ||
this.blockNumberProviders = this.buildBlockNumberProviders(chainRpcUrls); | ||
} | ||
|
||
public async getEpochBlockNumber(timestamp: number, chainId: Caip2ChainId): Promise<bigint> { | ||
const provider = this.blockNumberProviders.get(chainId); | ||
|
||
if (!provider) throw new ChainWithoutProvider(chainId); | ||
|
||
const blockNumber = await provider.getEpochBlockNumber(timestamp); | ||
|
||
return blockNumber; | ||
} | ||
|
||
public async getEpochBlockNumbers(timestamp: number, chains: Caip2ChainId[]) { | ||
const epochBlockNumbers = await Promise.all( | ||
chains.map(async (chain) => ({ | ||
chainId: chain, | ||
blockNumber: await this.getEpochBlockNumber(timestamp, chain), | ||
})), | ||
); | ||
|
||
return epochBlockNumbers.reduce((epochBlockNumbersMap, epoch) => { | ||
return epochBlockNumbersMap.set(epoch.chainId, epoch.blockNumber); | ||
}, new Map<Caip2ChainId, bigint>()); | ||
} | ||
|
||
private buildBlockNumberProviders(chainRpcUrls: Map<Caip2ChainId, RpcUrl[]>) { | ||
if (chainRpcUrls.size == 0) throw new EmptyRpcUrls(); | ||
|
||
const providers = new Map<Caip2ChainId, BlockNumberProvider>(); | ||
|
||
for (const [chainId, urls] of chainRpcUrls) { | ||
if (!this.isChainSupported(chainId)) throw new UnsupportedChain(chainId); | ||
|
||
const client = createPublicClient({ | ||
transport: fallback(urls.map((url) => http(url))), | ||
}); | ||
|
||
const provider = BlockNumberProviderFactory.buildProvider(chainId, client, this.logger); | ||
|
||
if (!provider) throw new ChainWithoutProvider(chainId); | ||
|
||
providers.set(chainId, provider); | ||
} | ||
|
||
return providers; | ||
} | ||
|
||
private isChainSupported(chainId: Caip2ChainId) { | ||
return EBO_SUPPORTED_CHAIN_IDS.includes(chainId); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./blockNumberService.js"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export type Caip2ChainId = `${string}:${string}`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./caip2Utils.js"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1 @@ | ||
export * from "./chainId.js"; | ||
export * from "./logger.js"; | ||
export * from "./caip/caip2Utils.js"; |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Logger } from "@ebo-agent/shared"; | ||
import { createPublicClient, fallback, http } from "viem"; | ||
import { describe, expect, it } from "vitest"; | ||
|
||
import { UnsupportedChain } from "../../src/exceptions"; | ||
import { BlockNumberProviderFactory } from "../../src/providers/blockNumberProviderFactory"; | ||
import { EvmBlockNumberProvider } from "../../src/providers/evmBlockNumberProvider"; | ||
import { Caip2ChainId } from "../../src/types"; | ||
|
||
describe("BlockNumberProviderFactory", () => { | ||
const logger = Logger.getInstance(); | ||
|
||
describe("buildProvider", () => { | ||
const client = createPublicClient({ transport: fallback([http("http://localhost:8545")]) }); | ||
|
||
it("builds a provider", () => { | ||
const provider = BlockNumberProviderFactory.buildProvider("eip155:1", client, logger); | ||
|
||
expect(provider).toBeInstanceOf(EvmBlockNumberProvider); | ||
}); | ||
|
||
it("fails if chain is not supported", () => { | ||
const unsupportedChainId = "solana:80085" as Caip2ChainId; | ||
|
||
expect(() => { | ||
BlockNumberProviderFactory.buildProvider(unsupportedChainId, client, logger); | ||
}).toThrow(UnsupportedChain); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚀