c5478b159d
Signed-off-by: AlienTornadosaurusHex <>
353 lines
10 KiB
TypeScript
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)
|
|
}
|
|
}
|