sdk-monorepo/@tornado/sdk-registry/src/index.ts
AlienTornadosaurusHex c5478b159d 0.0.12-alpha: Check HISTORY.md
Signed-off-by: AlienTornadosaurusHex <>
2023-05-31 20:53:28 +00:00

353 lines
10 KiB
TypeScript

// ts-essentials
import { DeepRequired, Merge } from 'ts-essentials'
// Ethers
import { Provider } from '@ethersproject/providers'
import { BigNumber, EventFilter } from 'ethers'
// Contract types
import { RelayerRegistry } from './deth/RelayerRegistry.js'
// Local imports
import { AsyncUtils, ErrorUtils } from '@tornado/sdk-utils'
import { Docs, Cache, Onchain, Options as DataOptions } from '@tornado/sdk-data'
import { Chain, Synchronizer, Contracts, Options as ChainOptions, syncErrorHandler } from '@tornado/sdk-chain'
// @ts-ignore
import { toIndexableString } from 'pouchdb-collate'
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ DECLARATIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
export namespace Options {
export type Cache = DataOptions.Cache
export type Sync = ChainOptions.Sync
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPLEMENTATIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
export class Registration extends Docs.Base {
ensName: string
address: string
relayer: string
stakedAmount: string
constructor(obj: any) {
const blockNumber = obj['blockNumber']
const ensName = obj['args']['ensName']
super(toIndexableString([blockNumber, ensName]))
this.ensName = ensName
this.relayer = obj['args']['relayer']
this.address = obj['args']['address']
this.stakedAmount = (obj['args']['stakedAmount'] as BigNumber).toString()
}
}
export class Stake extends Docs.Base {
relayer: string
amountStakeAdded: string
constructor(obj: any) {
const blockNumber = obj['blockNumber']
super(toIndexableString([blockNumber]))
this.relayer = obj['args']['relayer']
this.amountStakeAdded = (obj['args']['amountStakeAdded'] as BigNumber).toString()
}
}
export class Burn extends Docs.Base {
relayer: string
amountBurned: string
constructor(obj: any) {
const blockNumber = obj['blockNumber']
super(toIndexableString([blockNumber]))
this.relayer = obj['args']['relayer']
this.amountBurned = (obj['args']['amountBurned'] as BigNumber).toString()
}
}
export class RegistrationsCache extends Cache.Syncable<Registration> {
buildDoc(response: any): Registration {
return new Registration(response)
}
getErrorHandlers(): Array<AsyncUtils.ErrorHandler> {
return [syncErrorHandler]
}
getCallbacks(registry: RelayerRegistry): Array<AsyncUtils.Callback> {
return [
(fromBlock: number, toBlock: number) => {
return registry.queryFilter(
registry.filters['RelayerRegistered(bytes32,string,address,uint256)'](null, null, null, null),
fromBlock,
toBlock
)
}
]
}
}
export class StakesCache extends Cache.Syncable<Stake> {
buildDoc(response: any): Stake {
return new Stake(response)
}
getErrorHandlers(): Array<AsyncUtils.ErrorHandler> {
return [syncErrorHandler]
}
getCallbacks(registry: RelayerRegistry): Array<AsyncUtils.Callback> {
return [
(fromBlock: number, toBlock: number) => {
return registry.queryFilter(
registry.filters['StakeAddedToRelayer(address,uint256)'](null, null),
fromBlock,
toBlock
)
}
]
}
}
export class BurnsCache extends Cache.Syncable<Burn> {
buildDoc(response: any): Burn {
return new Burn(response)
}
getErrorHandlers(): Array<AsyncUtils.ErrorHandler> {
return [syncErrorHandler]
}
getCallbacks(registry: RelayerRegistry): Array<AsyncUtils.Callback> {
return [
(fromBlock: number, toBlock: number) => {
return registry.queryFilter(
registry.filters['StakeBurned(address,uint256)'](null, null),
fromBlock,
toBlock
)
}
]
}
}
export class Registry extends Synchronizer {
private _chain?: Chain
get chain(): Chain {
this._checkProvider('chain')
return this._chain!
}
constructor() {
super()
this.caches = new Map<string, Cache.Syncable<Docs.Base>>()
}
private _checkProvider(parentCallName: string): void {
if (!this._chain)
throw ErrorUtils.getError('Core.' + parentCallName + ': you must first connect a provider!')
}
async connect(provider: Provider): Promise<void> {
if (!this._chain) this._chain = new Chain(provider)
else this._chain.provider = provider
await this._chain.fetchChainData()
}
getContract(): RelayerRegistry {
return Contracts.getRegistry(this.chain.provider)
}
async syncRegistrations(
networkId?: string | number,
options?: Merge<Options.Sync, Options.Cache>
): Promise<void> {
await this._sync('RelayerRegistered', networkId, options)
}
async syncStakes(networkId?: string | number, options?: Merge<Options.Sync, Options.Cache>): Promise<void> {
await this._sync('StakeAddedToRelayer', networkId, options)
}
async syncBurns(networkId?: string | number, options?: Merge<Options.Sync, Options.Cache>): Promise<void> {
await this._sync('StakeBurned', networkId, options)
}
protected async _sync(
event: string | EventFilter,
networkId?: string | number,
options?: Merge<Options.Sync, Options.Cache>
): Promise<void> {
networkId = networkId ?? '1'
if (this.chain.id !== +networkId)
throw ErrorUtils.getError('Registry._sync: provider bound to wrong chain!')
let registry = this.getContract(),
needsFilter = typeof event !== 'string',
filter: EventFilter = event as EventFilter,
emitterName,
cache
options = options ?? {}
options.startBlock = options.startBlock ?? (await Onchain.getRegistryDeployBlockNum('' + networkId))
if (event === 'RelayerRegistered') {
cache = this.loadRegistrations(networkId, options)
emitterName = 'registration'
if (needsFilter) filter = registry.filters.RelayerRegistered(null, null, null, null)
} else if (event === 'StakeAddedToRelayer') {
cache = this.loadStakes(networkId, options)
emitterName = 'stake'
if (needsFilter) filter = registry.filters.StakeAddedToRelayer(null, null)
} else if (event === 'StakeBurned') {
cache = this.loadBurns(networkId, options)
emitterName = 'burn'
if (needsFilter) filter = registry.filters['StakeBurned(address,uint256)'](null, null)
} else throw ErrorUtils.getError(`Registry._sync: such an event ${event} is not supported`)
await this.sync(emitterName, filter, registry, cache, options)
}
loadRegistrations(networkId?: number | string, options?: Options.Cache): RegistrationsCache {
networkId = networkId ?? '1'
return this.loadCache<Registration, RegistrationsCache>('Registrations' + networkId, options)
}
loadStakes(networkId?: number | string, options?: Options.Cache): StakesCache {
networkId = networkId ?? '1'
return this.loadCache<Stake, StakesCache>('Stakes' + networkId, options)
}
loadBurns(networkId?: number | string, options?: Options.Cache): BurnsCache {
networkId = networkId ?? '1'
return this.loadCache<Burn, BurnsCache>('Burns' + networkId, options)
}
loadCache<D extends Docs.Base, C extends Cache.Syncable<D> = Cache.Syncable<D>>(
name: string,
options?: Options.Cache
): C {
let constructor,
cache = super.loadCache(name)
if (!cache) {
const regexp = /([A-Za-z]+)([0-9]+)/
const matches = name.match(regexp)
if (!matches) throw ErrorUtils.getError(`Registry.loadCache: name ${name} has wrong format for cache`)
if (matches.length === 2) name += '1'
if (matches[1] === 'Registrations') {
constructor = (name: string, options?: Options.Cache) => new RegistrationsCache(name, options)
} else if (matches[1] === 'Burns') {
constructor = (name: string, options?: Options.Cache) => new BurnsCache(name, options)
} else if (matches[1] === 'Stakes') {
constructor = (name: string, options?: Options.Cache) => new StakesCache(name, options)
} else throw ErrorUtils.getError(`Registry.loadCache: there exists no such event ${matches[1]}`)
cache = constructor(name, options)
this.caches.set(name, cache)
}
return cache as C
}
async exportRegistrationsZip(
networkId?: number | string,
outDirPath?: string,
close?: boolean,
options?: Options.Cache
): Promise<void> {
networkId = networkId ?? '1'
await this._exportCacheZip('Registrations' + networkId, outDirPath, close, options)
}
async exportStakesZip(
networkId?: number | string,
outDirPath?: string,
close?: boolean,
options?: Options.Cache
): Promise<void> {
networkId = networkId ?? '1'
await this._exportCacheZip('Stakes' + networkId, outDirPath, close, options)
}
async exportBurnsZip(
networkId?: number | string,
outDirPath?: string,
close?: boolean,
options?: Options.Cache
): Promise<void> {
networkId = networkId ?? '1'
await this._exportCacheZip('Burns' + networkId, outDirPath, close, options)
}
async exportRegistrationsJson(
networkId?: number | string,
outDirPath?: string,
close?: boolean,
options?: Options.Cache
): Promise<void> {
networkId = networkId ?? '1'
await this._exportCacheJson('Registrations' + networkId, outDirPath, close, options)
}
async exportStakesJson(
networkId?: number | string,
outDirPath?: string,
close?: boolean,
options?: Options.Cache
): Promise<void> {
networkId = networkId ?? '1'
await this._exportCacheJson('Stakes' + networkId, outDirPath, close, options)
}
async exportBurnsJson(
networkId?: number | string,
outDirPath?: string,
close?: boolean,
options?: Options.Cache
): Promise<void> {
networkId = networkId ?? '1'
await this._exportCacheJson('Burns' + networkId, outDirPath, close, options)
}
protected async _exportCacheZip(
cacheName: string,
outDirPath?: string,
close?: boolean,
options?: Options.Cache
): Promise<void> {
const cache = new Cache.Base<Docs.Base>(cacheName, options)
await cache.zip(outDirPath, close)
if (close === true) this.caches.delete(cacheName)
}
protected async _exportCacheJson(
cacheName: string,
outDirPath?: string,
close?: boolean,
options?: Options.Cache
): Promise<void> {
const cache = new Cache.Base<Docs.Base>(cacheName, options)
await cache.jsonify(outDirPath)
if (close === true) this.caches.delete(cacheName)
}
protected async _populateSyncOptions(options: Options.Sync): Promise<DeepRequired<Options.Sync>> {
options.targetBlock = options.targetBlock ?? (await this.chain.latestBlockNum())
return super._populateSyncOptions(options)
}
}