This repository has been archived by the owner on Apr 26, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
271 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export interface PriceConfig { | ||
tokenSymbol: string | ||
apiId: string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { Logger } from '@l2beat/backend-tools' | ||
import { | ||
Configuration, | ||
Indexer, | ||
IndexerOptions, | ||
MultiIndexer, | ||
RemovalConfiguration, | ||
SavedConfiguration, | ||
UpdateConfiguration, | ||
} from '@l2beat/uif' | ||
|
||
import { PriceConfig } from './PriceConfig' | ||
import { PriceIndexerRepository } from './PriceIndexerRepository' | ||
import { PriceRepository } from './PriceRepository' | ||
import { PriceService } from './PriceService' | ||
|
||
const ONE_HOUR = 60 * 60 * 1000 | ||
|
||
export class PriceIndexer extends MultiIndexer<PriceConfig> { | ||
private readonly apiId: string | ||
|
||
constructor( | ||
private readonly indexerId: string, | ||
private readonly priceService: PriceService, | ||
private readonly priceRepository: PriceRepository, | ||
private readonly priceIndexerRepository: PriceIndexerRepository, | ||
logger: Logger, | ||
parents: Indexer[], | ||
configurations: Configuration<PriceConfig>[], | ||
options?: IndexerOptions, | ||
) { | ||
super(logger, parents, configurations, options) | ||
const apiId = configurations[0]?.properties.apiId | ||
if (!apiId) { | ||
throw new Error('At least one configuration is required') | ||
} | ||
if (configurations.some((c) => c.properties.apiId !== apiId)) { | ||
throw new Error('All configurations must have the same apiId') | ||
} | ||
this.apiId = apiId | ||
} | ||
|
||
override async multiInitialize(): Promise<SavedConfiguration<PriceConfig>[]> { | ||
return this.priceIndexerRepository.load(this.indexerId) | ||
} | ||
|
||
override async multiUpdate( | ||
currentHeight: number, | ||
targetHeight: number, | ||
configurations: UpdateConfiguration<PriceConfig>[], | ||
): Promise<number> { | ||
const startHour = currentHeight - (currentHeight % ONE_HOUR) + ONE_HOUR | ||
const endHour = Math.min( | ||
targetHeight - (targetHeight % ONE_HOUR), | ||
// for example the api costs us more money for larger ranges | ||
startHour + 23 * ONE_HOUR, | ||
) | ||
|
||
if (startHour >= endHour) { | ||
return startHour | ||
} | ||
|
||
const prices = await this.priceService.getHourlyPrices( | ||
this.apiId, | ||
startHour, | ||
endHour, | ||
) | ||
|
||
const dataToSave = configurations.flatMap((configuration) => { | ||
return prices.map(({ timestamp, price }) => ({ | ||
tokenSymbol: configuration.properties.tokenSymbol, | ||
timestamp, | ||
price, | ||
})) | ||
}) | ||
await this.priceRepository.save(dataToSave) | ||
|
||
// TODO: Maybe if targetHeight is not exactly an hour we can return it? | ||
return endHour | ||
} | ||
|
||
override async removeData( | ||
configurations: RemovalConfiguration<PriceConfig>[], | ||
): Promise<void> { | ||
for (const c of configurations) { | ||
await this.priceRepository.deletePrices( | ||
c.properties.tokenSymbol, | ||
c.fromHeightInclusive, | ||
c.toHeightInclusive, | ||
) | ||
} | ||
} | ||
|
||
override async saveConfigurations( | ||
configurations: SavedConfiguration<PriceConfig>[], | ||
): Promise<void> { | ||
return this.priceIndexerRepository.save(this.indexerId, configurations) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { SavedConfiguration } from '@l2beat/uif' | ||
|
||
import { PriceConfig } from './PriceConfig' | ||
|
||
export class PriceIndexerRepository { | ||
private data: Record<string, SavedConfiguration<PriceConfig>[]> = {} | ||
|
||
async save( | ||
indexerId: string, | ||
configurations: SavedConfiguration<PriceConfig>[], | ||
): Promise<void> { | ||
this.data[indexerId] = configurations | ||
return Promise.resolve() | ||
} | ||
|
||
async load(indexerId: string): Promise<SavedConfiguration<PriceConfig>[]> { | ||
return Promise.resolve(this.data[indexerId] ?? []) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export class PriceRepository { | ||
async save( | ||
prices: { tokenSymbol: string; timestamp: number; price: number }[], | ||
): Promise<void> { | ||
prices // use it so that eslint doesn't complain | ||
return Promise.resolve() | ||
} | ||
|
||
async deletePrices( | ||
tokenSymbol: string, | ||
fromTimestampInclusive: number, | ||
toTimestampInclusive: number, | ||
): Promise<void> { | ||
tokenSymbol // use it so that eslint doesn't complain | ||
fromTimestampInclusive // use it so that eslint doesn't complain | ||
toTimestampInclusive // use it so that eslint doesn't complain | ||
return Promise.resolve() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const ONE_HOUR = 60 * 60 * 1000 | ||
|
||
export class PriceService { | ||
async getHourlyPrices( | ||
apiId: string, | ||
startHourInclusive: number, | ||
endHourInclusive: number, | ||
): Promise<{ timestamp: number; price: number }[]> { | ||
apiId // use it so that eslint doesn't complain | ||
|
||
const prices: { timestamp: number; price: number }[] = [] | ||
for (let t = startHourInclusive; t <= endHourInclusive; t += ONE_HOUR) { | ||
prices.push({ timestamp: t, price: Math.random() * 1000 }) | ||
} | ||
return Promise.resolve(prices) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
export * from './BaseIndexer' | ||
export * from './height' | ||
export * from './Indexer' | ||
export * from './indexers/ChildIndexer' | ||
export * from './indexers/multi/MultiIndexer' | ||
export type { Configuration } from './indexers/multi/types' | ||
export * from './indexers/multi/types' | ||
export * from './indexers/RootIndexer' | ||
export * from './Retries' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.