-
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 31 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,100 @@ | ||
import { Logger, supportedChains } from "@ebo-agent/shared"; | ||
import { | ||
createPublicClient, | ||
fallback, | ||
FallbackTransport, | ||
http, | ||
HttpTransport, | ||
PublicClient, | ||
} from "viem"; | ||
|
||
import { ChainWithoutProvider, EmptyRpcUrls, UnsupportedChain } from "../exceptions/index.js"; | ||
import { BlockNumberProvider } from "../providers/blockNumberProvider.js"; | ||
import { EvmBlockNumberProvider } from "../providers/evmBlockNumberProvider.js"; | ||
import { Caip2ChainId } from "../types.js"; | ||
import { Caip2 } from "../utils/index.js"; | ||
|
||
type RpcUrl = NonNullable<Parameters<typeof http>[0]>; | ||
|
||
const DEFAULT_PROVIDER_CONFIG = { | ||
blocksLookback: 10_000n, | ||
deltaMultiplier: 2n, | ||
}; | ||
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.
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. I see where you are going, yes. Probably we could define a 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. Makes totally sense, i was thinking that maybe it is better having almost all the constants on the centralized Lets leave it as it is. |
||
|
||
export class BlockNumberService { | ||
private blockNumberProviders: Map<Caip2ChainId, BlockNumberProvider>; | ||
|
||
constructor(chainRpcUrls: Map<Caip2ChainId, RpcUrl[]>) { | ||
this.blockNumberProviders = this.buildBlockNumberProviders(chainRpcUrls); | ||
} | ||
|
||
public async getEpochBlockNumbers(timestamp: number, chains: Caip2ChainId[]) { | ||
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. also remember that will be 1 request -> 1 chain so lets also add a 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. Oh yes. Do you think leveraging the multiple chains method would be ok? ie: getEpochBlockNumber(timestamp, chain) {
getEpochBlockNumbers(timestamp, [chain])
} 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. I would do it the other way round, like: getEpochBlockNumbers(timestamp, [chain]) { |
||
const epochBlockNumbers = await Promise.all( | ||
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. probably here we will need a |
||
chains.map(async (chainId) => { | ||
const provider = this.blockNumberProviders.get(chainId); | ||
|
||
if (!provider) throw new ChainWithoutProvider(chainId); | ||
|
||
const blockNumber = await provider.getEpochBlockNumber(timestamp); | ||
|
||
return [chainId, blockNumber] as [Caip2ChainId, bigint]; | ||
}), | ||
); | ||
|
||
const e = epochBlockNumbers.filter( | ||
(entry): entry is [Caip2ChainId, bigint] => entry !== null, | ||
); | ||
|
||
return new Map(e); | ||
} | ||
|
||
private buildBlockNumberProviders(chainRpcUrls: Map<Caip2ChainId, RpcUrl[]>) { | ||
if (chainRpcUrls.size == 0) throw new EmptyRpcUrls(); | ||
|
||
const supportedChainIds = this.getSupportedChainIds(supportedChains); | ||
const providers = new Map<Caip2ChainId, BlockNumberProvider>(); | ||
|
||
for (const [chainId, urls] of chainRpcUrls) { | ||
if (!supportedChainIds.includes(chainId)) throw new UnsupportedChain(chainId); | ||
|
||
const client = createPublicClient({ | ||
transport: fallback(urls.map((url) => http(url))), | ||
}); | ||
|
||
const provider = BlockNumberService.buildProvider(chainId, client); | ||
|
||
if (!provider) throw new ChainWithoutProvider(chainId); | ||
|
||
providers.set(chainId, provider); | ||
} | ||
|
||
return providers; | ||
} | ||
|
||
private getSupportedChainIds(chainsConfig: typeof supportedChains) { | ||
const namespacesChains = Object.values(chainsConfig); | ||
|
||
return namespacesChains.reduce((acc, namespaceChains) => { | ||
return [...acc, ...Object.values(namespaceChains.chains)]; | ||
}, [] as string[]); | ||
} | ||
|
||
public static buildProvider( | ||
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. this smells like a Factory method pattern pattern 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. Whoops missed to answer this one; it is indeed a Factory method pattern! Want me to do some special tweaks on it? 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. I was wondering if we should remove this method from the Also the Probably creating a new class called 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. I see. We can extract this to a new class, absolutely, makes testing cleaner too. |
||
chainId: Caip2ChainId, | ||
client: PublicClient<FallbackTransport<HttpTransport[]>>, | ||
) { | ||
const chainNamespace = Caip2.getNamespace(chainId); | ||
|
||
switch (chainNamespace) { | ||
case supportedChains.evm.namespace: | ||
return new EvmBlockNumberProvider( | ||
client, | ||
DEFAULT_PROVIDER_CONFIG, | ||
Logger.getInstance(), // Should we drop this arg? | ||
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. dont think we should, maybe we can use 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. Aaah yesss let's add a |
||
); | ||
|
||
default: | ||
throw new UnsupportedChain(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 | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -1,41 +1,19 @@ | ||||||||||
// Based on https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md | ||||||||||
|
||||||||||
import { InvalidChainId } from "../exceptions/invalidChain.js"; | ||||||||||
|
||||||||||
type ChainNamespace = string; | ||||||||||
type ChainReference = string; | ||||||||||
|
||||||||||
interface ChainIdParams { | ||||||||||
namespace: ChainNamespace; | ||||||||||
reference: ChainReference; | ||||||||||
} | ||||||||||
import { InvalidChainId } from "../../exceptions/invalidChain.js"; | ||||||||||
import { Caip2ChainId } from "../../types.js"; | ||||||||||
|
||||||||||
const NAMESPACE_FORMAT = /^[-a-z0-9]{3,8}$/; | ||||||||||
const REFERENCE_FORMAT = /^[-_a-zA-Z0-9]{1,32}$/; | ||||||||||
|
||||||||||
export class ChainId { | ||||||||||
private namespace: string; | ||||||||||
private reference: string; | ||||||||||
|
||||||||||
/** | ||||||||||
* Creates a validated CAIP-2 compliant chain ID. | ||||||||||
* | ||||||||||
* @param chainId a CAIP-2 compliant string. | ||||||||||
*/ | ||||||||||
constructor(chainId: string) { | ||||||||||
const params = ChainId.parse(chainId); | ||||||||||
|
||||||||||
this.namespace = params.namespace; | ||||||||||
this.reference = params.reference; | ||||||||||
} | ||||||||||
|
||||||||||
export class Caip2 { | ||||||||||
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.
Suggested change
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.
Suggested change
?? |
||||||||||
/** | ||||||||||
* Parses a CAIP-2 compliant string. | ||||||||||
* | ||||||||||
* @param chainId {string} a CAIP-2 compliant string | ||||||||||
* @returns an object containing the namespace and the reference of the chain id | ||||||||||
* @returns the CAIP-2 validated chain id string | ||||||||||
*/ | ||||||||||
public static parse(chainId: string): ChainIdParams { | ||||||||||
public static validateChainId(chainId: string): chainId is Caip2ChainId { | ||||||||||
const elements = chainId.split(":"); | ||||||||||
|
||||||||||
if (elements.length !== 2) { | ||||||||||
|
@@ -54,13 +32,14 @@ export class ChainId { | |||||||||
const isValidReference = REFERENCE_FORMAT.test(reference); | ||||||||||
if (!isValidReference) throw new InvalidChainId("Chain ID reference is not valid."); | ||||||||||
|
||||||||||
return { | ||||||||||
namespace, | ||||||||||
reference, | ||||||||||
}; | ||||||||||
return true; | ||||||||||
} | ||||||||||
|
||||||||||
public toString() { | ||||||||||
return `${this.namespace}:${this.reference}`; | ||||||||||
public static getNamespace(chainId: string | Caip2ChainId) { | ||||||||||
this.validateChainId(chainId); | ||||||||||
|
||||||||||
const namespace = chainId.split(":")[0] as string; | ||||||||||
|
||||||||||
return namespace; | ||||||||||
} | ||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./caip2.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/caip2.js"; |
This file was deleted.
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.
missing natspec
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.
Ended up adding docs to the
BlockNumberProviderFactory
too 👌