diff --git a/db/migrations/1697906643694-Data.js b/db/migrations/1698085120905-Data.js similarity index 88% rename from db/migrations/1697906643694-Data.js rename to db/migrations/1698085120905-Data.js index 6eebf67b..d366e328 100644 --- a/db/migrations/1697906643694-Data.js +++ b/db/migrations/1698085120905-Data.js @@ -1,5 +1,5 @@ -module.exports = class Data1697906643694 { - name = 'Data1697906643694' +module.exports = class Data1698085120905 { + name = 'Data1698085120905' async up(db) { await db.query(`CREATE TABLE "exchange_rate" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "pair" text NOT NULL, "base" text NOT NULL, "quote" text NOT NULL, "rate" numeric NOT NULL, CONSTRAINT "PK_5c5d27d2b900ef6cdeef0398472" PRIMARY KEY ("id"))`) @@ -54,6 +54,15 @@ module.exports = class Data1697906643694 { await db.query(`CREATE TABLE "oeth_balancer_meta_pool_strategy" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "r_eth" numeric NOT NULL, "weth" numeric NOT NULL, CONSTRAINT "PK_6ddf5b8ba878e6d706e59bd6de0" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_5e7ef383756fa18cb602f50089" ON "oeth_balancer_meta_pool_strategy" ("timestamp") `) await db.query(`CREATE INDEX "IDX_11d344b3e0e03cdb6697dd61f7" ON "oeth_balancer_meta_pool_strategy" ("block_number") `) + await db.query(`CREATE TABLE "oeth_strategy_holding_daily_stat" ("id" character varying NOT NULL, "symbol" text NOT NULL, "amount" numeric NOT NULL, "value" numeric NOT NULL, "strategy_daily_stat_id_id" character varying, CONSTRAINT "PK_7f1a62da5e53cf264c2f39b4acf" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_4e867f220975e615e6077d860c" ON "oeth_strategy_holding_daily_stat" ("strategy_daily_stat_id_id") `) + await db.query(`CREATE TABLE "oeth_strategy_daily_stat" ("id" character varying NOT NULL, "total" numeric NOT NULL, "tvl" numeric NOT NULL, "daily_stat_id_id" character varying, CONSTRAINT "PK_8af1a0c60e67b05baf928787a8e" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_6c7096c96a000d8471256ca8fc" ON "oeth_strategy_daily_stat" ("daily_stat_id_id") `) + await db.query(`CREATE TABLE "oeth_collateral_daily_stat" ("id" character varying NOT NULL, "symbol" text NOT NULL, "amount" numeric NOT NULL, "price" numeric NOT NULL, "value" numeric NOT NULL, "daily_stat_id_id" character varying, CONSTRAINT "PK_5fb23d7bae30dffe4543e7aa069" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_a90045de50406be7bd56efd3ea" ON "oeth_collateral_daily_stat" ("daily_stat_id_id") `) + await db.query(`CREATE TABLE "oeth_daily_stat" ("id" character varying NOT NULL, "block_number" integer NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "apr" numeric NOT NULL, "apy" numeric NOT NULL, "apy7_day_avg" numeric NOT NULL, "apy14_day_avg" numeric NOT NULL, "apy30_day_avg" numeric NOT NULL, "total_supply" numeric NOT NULL, "total_supply_usd" numeric NOT NULL, "rebasing_supply" numeric NOT NULL, "non_rebasing_supply" numeric NOT NULL, "amo_supply" numeric NOT NULL, "yield" numeric NOT NULL, "fees" numeric NOT NULL, "revenue" numeric NOT NULL, "revenue7_day_avg" numeric NOT NULL, "revenue7_day_total" numeric NOT NULL, "revenue_all_time" numeric NOT NULL, "peg_price" numeric NOT NULL, CONSTRAINT "PK_9144a02ab13b1baa818a7d5eae5" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_98d9001013aa37425ca47b7126" ON "oeth_daily_stat" ("block_number") `) + await db.query(`CREATE INDEX "IDX_c3e66051c7df4efd6a8fa8f9c1" ON "oeth_daily_stat" ("timestamp") `) await db.query(`CREATE TABLE "ogv" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "circulating" numeric NOT NULL, "total" numeric NOT NULL, CONSTRAINT "PK_f16038abf451ce82bd03ea54ee7" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_2418a8b8b92b2f5977be761cf9" ON "ogv" ("timestamp") `) await db.query(`CREATE INDEX "IDX_b8f20bcf48e4aa77e0f48d77db" ON "ogv" ("block_number") `) @@ -118,6 +127,9 @@ module.exports = class Data1697906643694 { await db.query(`ALTER TABLE "oeth_history" ADD CONSTRAINT "FK_94e47c4c49128c78f60b185b46b" FOREIGN KEY ("address_id") REFERENCES "oeth_address"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) await db.query(`ALTER TABLE "oeth_rebase" ADD CONSTRAINT "FK_3331819842173de7c27c046547a" FOREIGN KEY ("apy_id") REFERENCES "oethapy"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) await db.query(`ALTER TABLE "oeth_rebase_option" ADD CONSTRAINT "FK_034428879698039839b4ba6ffe8" FOREIGN KEY ("address_id") REFERENCES "oeth_address"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "oeth_strategy_holding_daily_stat" ADD CONSTRAINT "FK_4e867f220975e615e6077d860c1" FOREIGN KEY ("strategy_daily_stat_id_id") REFERENCES "oeth_strategy_daily_stat"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "oeth_strategy_daily_stat" ADD CONSTRAINT "FK_6c7096c96a000d8471256ca8fc3" FOREIGN KEY ("daily_stat_id_id") REFERENCES "oeth_daily_stat"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) + await db.query(`ALTER TABLE "oeth_collateral_daily_stat" ADD CONSTRAINT "FK_a90045de50406be7bd56efd3ea4" FOREIGN KEY ("daily_stat_id_id") REFERENCES "oeth_daily_stat"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) await db.query(`ALTER TABLE "ousd_history" ADD CONSTRAINT "FK_70291ea600c0c4d67d9bfe6a6bf" FOREIGN KEY ("address_id") REFERENCES "ousd_address"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) await db.query(`ALTER TABLE "ousd_rebase" ADD CONSTRAINT "FK_427468c97f9838b804efd6c8e55" FOREIGN KEY ("apy_id") REFERENCES "ousdapy"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) await db.query(`ALTER TABLE "ousd_rebase_option" ADD CONSTRAINT "FK_b04173f9349ddd991a3b60e914a" FOREIGN KEY ("address_id") REFERENCES "ousd_address"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`) @@ -176,6 +188,15 @@ module.exports = class Data1697906643694 { await db.query(`DROP TABLE "oeth_balancer_meta_pool_strategy"`) await db.query(`DROP INDEX "public"."IDX_5e7ef383756fa18cb602f50089"`) await db.query(`DROP INDEX "public"."IDX_11d344b3e0e03cdb6697dd61f7"`) + await db.query(`DROP TABLE "oeth_strategy_holding_daily_stat"`) + await db.query(`DROP INDEX "public"."IDX_4e867f220975e615e6077d860c"`) + await db.query(`DROP TABLE "oeth_strategy_daily_stat"`) + await db.query(`DROP INDEX "public"."IDX_6c7096c96a000d8471256ca8fc"`) + await db.query(`DROP TABLE "oeth_collateral_daily_stat"`) + await db.query(`DROP INDEX "public"."IDX_a90045de50406be7bd56efd3ea"`) + await db.query(`DROP TABLE "oeth_daily_stat"`) + await db.query(`DROP INDEX "public"."IDX_98d9001013aa37425ca47b7126"`) + await db.query(`DROP INDEX "public"."IDX_c3e66051c7df4efd6a8fa8f9c1"`) await db.query(`DROP TABLE "ogv"`) await db.query(`DROP INDEX "public"."IDX_2418a8b8b92b2f5977be761cf9"`) await db.query(`DROP INDEX "public"."IDX_b8f20bcf48e4aa77e0f48d77db"`) @@ -240,6 +261,9 @@ module.exports = class Data1697906643694 { await db.query(`ALTER TABLE "oeth_history" DROP CONSTRAINT "FK_94e47c4c49128c78f60b185b46b"`) await db.query(`ALTER TABLE "oeth_rebase" DROP CONSTRAINT "FK_3331819842173de7c27c046547a"`) await db.query(`ALTER TABLE "oeth_rebase_option" DROP CONSTRAINT "FK_034428879698039839b4ba6ffe8"`) + await db.query(`ALTER TABLE "oeth_strategy_holding_daily_stat" DROP CONSTRAINT "FK_4e867f220975e615e6077d860c1"`) + await db.query(`ALTER TABLE "oeth_strategy_daily_stat" DROP CONSTRAINT "FK_6c7096c96a000d8471256ca8fc3"`) + await db.query(`ALTER TABLE "oeth_collateral_daily_stat" DROP CONSTRAINT "FK_a90045de50406be7bd56efd3ea4"`) await db.query(`ALTER TABLE "ousd_history" DROP CONSTRAINT "FK_70291ea600c0c4d67d9bfe6a6bf"`) await db.query(`ALTER TABLE "ousd_rebase" DROP CONSTRAINT "FK_427468c97f9838b804efd6c8e55"`) await db.query(`ALTER TABLE "ousd_rebase_option" DROP CONSTRAINT "FK_b04173f9349ddd991a3b60e914a"`) diff --git a/schema-oeth.graphql b/schema-oeth.graphql index 722e33d5..fc35455f 100644 --- a/schema-oeth.graphql +++ b/schema-oeth.graphql @@ -147,3 +147,101 @@ type OETHBalancerMetaPoolStrategy @entity { rETH: BigInt! weth: BigInt! } + +type OETHDailyStat @entity { + id: ID! + """ + Timestamp, eg 2023-10-17 + """ + blockNumber: Int! @index + """ + Last block number stats were updated + """ + timestamp: DateTime! @index + """ + Timestamp of block number stats were updated + """ + apr: Float! + apy: Float! + apy7DayAvg: Float! + apy14DayAvg: Float! + apy30DayAvg: Float! + + totalSupply: BigInt! + totalSupplyUSD: Float! + rebasingSupply: BigInt! + nonRebasingSupply: BigInt! + amoSupply: BigInt! + + yield: BigInt! + fees: BigInt! + revenue: BigInt! + revenue7DayAvg: BigInt! + revenue7DayTotal: BigInt! + revenueAllTime: BigInt! + + pegPrice: BigInt! + """ + Price of OETH in ETH + """ + strategies: [OETHStrategyDailyStat] @derivedFrom(field: "dailyStatId") + collateral: [OETHCollateralDailyStat] @derivedFrom(field: "dailyStatId") +} + +type OETHStrategyDailyStat @entity { + id: ID! + dailyStatId: OETHDailyStat! + total: BigInt! + """ + Sum of tokens in strategy + """ + tvl: BigInt! + """ + Total ETH value + """ + holdings: [OETHStrategyHoldingDailyStat] + @derivedFrom(field: "strategyDailyStatId") +} + +type OETHStrategyHoldingDailyStat @entity { + id: ID! + strategyDailyStatId: OETHStrategyDailyStat! + """ + Token symbol + """ + symbol: String! + + """ + Amount held + """ + amount: BigInt! + + """ + Total ETH value + """ + value: BigInt! +} + +type OETHCollateralDailyStat @entity { + id: ID! + dailyStatId: OETHDailyStat! @index + """ + Token symbol + """ + symbol: String! + + """ + Amount held + """ + amount: BigInt! + + """ + Price in ETH + """ + price: BigInt! + + """ + Total ETH value + """ + value: BigInt! +} diff --git a/schema.graphql b/schema.graphql index 0136b539..a66887e1 100644 --- a/schema.graphql +++ b/schema.graphql @@ -210,6 +210,104 @@ type OETHBalancerMetaPoolStrategy @entity { rETH: BigInt! weth: BigInt! } + +type OETHDailyStat @entity { + id: ID! + """ + Timestamp, eg 2023-10-17 + """ + blockNumber: Int! @index + """ + Last block number stats were updated + """ + timestamp: DateTime! @index + """ + Timestamp of block number stats were updated + """ + apr: Float! + apy: Float! + apy7DayAvg: Float! + apy14DayAvg: Float! + apy30DayAvg: Float! + + totalSupply: BigInt! + totalSupplyUSD: Float! + rebasingSupply: BigInt! + nonRebasingSupply: BigInt! + amoSupply: BigInt! + + yield: BigInt! + fees: BigInt! + revenue: BigInt! + revenue7DayAvg: BigInt! + revenue7DayTotal: BigInt! + revenueAllTime: BigInt! + + pegPrice: BigInt! + """ + Price of OETH in ETH + """ + strategies: [OETHStrategyDailyStat] @derivedFrom(field: "dailyStatId") + collateral: [OETHCollateralDailyStat] @derivedFrom(field: "dailyStatId") +} + +type OETHStrategyDailyStat @entity { + id: ID! + dailyStatId: OETHDailyStat! + total: BigInt! + """ + Sum of tokens in strategy + """ + tvl: BigInt! + """ + Total ETH value + """ + holdings: [OETHStrategyHoldingDailyStat] + @derivedFrom(field: "strategyDailyStatId") +} + +type OETHStrategyHoldingDailyStat @entity { + id: ID! + strategyDailyStatId: OETHStrategyDailyStat! + """ + Token symbol + """ + symbol: String! + + """ + Amount held + """ + amount: BigInt! + + """ + Total ETH value + """ + value: BigInt! +} + +type OETHCollateralDailyStat @entity { + id: ID! + dailyStatId: OETHDailyStat! @index + """ + Token symbol + """ + symbol: String! + + """ + Amount held + """ + amount: BigInt! + + """ + Price in ETH + """ + price: BigInt! + + """ + Total ETH value + """ + value: BigInt! +} # OGV Price (5m?) # OGV Market Cap (5m?) # OGV Circulating Supply diff --git a/src/main.ts b/src/main.ts index dc71c3d5..e29c5521 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,4 @@ +import * as dailyStats from './post-processors/daily-stats' import * as exchangeRates from './post-processors/exchange-rates' import * as validateOeth from './post-processors/validate-oeth' import { run } from './processor' @@ -30,6 +31,6 @@ run({ // OUSD Related // ousd, ], - postProcessors: [exchangeRates], + postProcessors: [exchangeRates, dailyStats], validators: [validateOeth], }) diff --git a/src/model/generated/index.ts b/src/model/generated/index.ts index ad6af785..d6c96de3 100644 --- a/src/model/generated/index.ts +++ b/src/model/generated/index.ts @@ -16,6 +16,10 @@ export * from "./oethFraxStaking.model" export * from "./oethMorphoAave.model" export * from "./dripper.model" export * from "./oethBalancerMetaPoolStrategy.model" +export * from "./oethDailyStat.model" +export * from "./oethStrategyDailyStat.model" +export * from "./oethStrategyHoldingDailyStat.model" +export * from "./oethCollateralDailyStat.model" export * from "./ogv.model" export * from "./stakedOgv.model" export * from "./ogvGovernance.model" diff --git a/src/model/generated/oethCollateralDailyStat.model.ts b/src/model/generated/oethCollateralDailyStat.model.ts new file mode 100644 index 00000000..cd3922ed --- /dev/null +++ b/src/model/generated/oethCollateralDailyStat.model.ts @@ -0,0 +1,41 @@ +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, ManyToOne as ManyToOne_, Index as Index_} from "typeorm" +import * as marshal from "./marshal" +import {OETHDailyStat} from "./oethDailyStat.model" + +@Entity_() +export class OETHCollateralDailyStat { + constructor(props?: Partial) { + Object.assign(this, props) + } + + @PrimaryColumn_() + id!: string + + @Index_() + @ManyToOne_(() => OETHDailyStat, {nullable: true}) + dailyStatId!: OETHDailyStat + + /** + * Token symbol + */ + @Column_("text", {nullable: false}) + symbol!: string + + /** + * Amount held + */ + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + amount!: bigint + + /** + * Price in ETH + */ + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + price!: bigint + + /** + * Total ETH value + */ + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + value!: bigint +} diff --git a/src/model/generated/oethDailyStat.model.ts b/src/model/generated/oethDailyStat.model.ts new file mode 100644 index 00000000..85dbc3b8 --- /dev/null +++ b/src/model/generated/oethDailyStat.model.ts @@ -0,0 +1,91 @@ +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_, OneToMany as OneToMany_} from "typeorm" +import * as marshal from "./marshal" +import {OETHStrategyDailyStat} from "./oethStrategyDailyStat.model" +import {OETHCollateralDailyStat} from "./oethCollateralDailyStat.model" + +@Entity_() +export class OETHDailyStat { + constructor(props?: Partial) { + Object.assign(this, props) + } + + @PrimaryColumn_() + id!: string + + /** + * Timestamp, eg 2023-10-17 + */ + @Index_() + @Column_("int4", {nullable: false}) + blockNumber!: number + + /** + * Last block number stats were updated + */ + @Index_() + @Column_("timestamp with time zone", {nullable: false}) + timestamp!: Date + + /** + * Timestamp of block number stats were updated + */ + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + apr!: number + + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + apy!: number + + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + apy7DayAvg!: number + + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + apy14DayAvg!: number + + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + apy30DayAvg!: number + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + totalSupply!: bigint + + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + totalSupplyUSD!: number + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + rebasingSupply!: bigint + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + nonRebasingSupply!: bigint + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + amoSupply!: bigint + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + yield!: bigint + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + fees!: bigint + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + revenue!: bigint + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + revenue7DayAvg!: bigint + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + revenue7DayTotal!: bigint + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + revenueAllTime!: bigint + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + pegPrice!: bigint + + /** + * Price of OETH in ETH + */ + @OneToMany_(() => OETHStrategyDailyStat, e => e.dailyStatId) + strategies!: OETHStrategyDailyStat[] + + @OneToMany_(() => OETHCollateralDailyStat, e => e.dailyStatId) + collateral!: OETHCollateralDailyStat[] +} diff --git a/src/model/generated/oethStrategyDailyStat.model.ts b/src/model/generated/oethStrategyDailyStat.model.ts new file mode 100644 index 00000000..53372934 --- /dev/null +++ b/src/model/generated/oethStrategyDailyStat.model.ts @@ -0,0 +1,33 @@ +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, ManyToOne as ManyToOne_, Index as Index_, OneToMany as OneToMany_} from "typeorm" +import * as marshal from "./marshal" +import {OETHDailyStat} from "./oethDailyStat.model" +import {OETHStrategyHoldingDailyStat} from "./oethStrategyHoldingDailyStat.model" + +@Entity_() +export class OETHStrategyDailyStat { + constructor(props?: Partial) { + Object.assign(this, props) + } + + @PrimaryColumn_() + id!: string + + @Index_() + @ManyToOne_(() => OETHDailyStat, {nullable: true}) + dailyStatId!: OETHDailyStat + + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + total!: bigint + + /** + * Sum of tokens in strategy + */ + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + tvl!: bigint + + /** + * Total ETH value + */ + @OneToMany_(() => OETHStrategyHoldingDailyStat, e => e.strategyDailyStatId) + holdings!: OETHStrategyHoldingDailyStat[] +} diff --git a/src/model/generated/oethStrategyHoldingDailyStat.model.ts b/src/model/generated/oethStrategyHoldingDailyStat.model.ts new file mode 100644 index 00000000..fa1423c1 --- /dev/null +++ b/src/model/generated/oethStrategyHoldingDailyStat.model.ts @@ -0,0 +1,35 @@ +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, ManyToOne as ManyToOne_, Index as Index_} from "typeorm" +import * as marshal from "./marshal" +import {OETHStrategyDailyStat} from "./oethStrategyDailyStat.model" + +@Entity_() +export class OETHStrategyHoldingDailyStat { + constructor(props?: Partial) { + Object.assign(this, props) + } + + @PrimaryColumn_() + id!: string + + @Index_() + @ManyToOne_(() => OETHStrategyDailyStat, {nullable: true}) + strategyDailyStatId!: OETHStrategyDailyStat + + /** + * Token symbol + */ + @Column_("text", {nullable: false}) + symbol!: string + + /** + * Amount held + */ + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + amount!: bigint + + /** + * Total ETH value + */ + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + value!: bigint +} diff --git a/src/post-processors/daily-stats/daily-stats.ts b/src/post-processors/daily-stats/daily-stats.ts new file mode 100644 index 00000000..a09a1ad0 --- /dev/null +++ b/src/post-processors/daily-stats/daily-stats.ts @@ -0,0 +1,418 @@ +import dayjs from 'dayjs' +import { EntityManager, FindOptionsOrderValue, LessThanOrEqual } from 'typeorm' +import { formatEther } from 'viem' + +import { + ExchangeRate, + OETH, + OETHAPY, + OETHBalancerMetaPoolStrategy, + OETHCollateralDailyStat, + OETHCurveLP, + OETHDailyStat, + OETHFraxStaking, + OETHMorphoAave, + OETHStrategyDailyStat, + OETHStrategyHoldingDailyStat, + OETHVault, +} from '../../model' +import { Context } from '../../processor' + +export const from = 16933090 // https://etherscan.io/tx/0x3b4ece4f5fef04bf7ceaec4f6c6edf700540d7597589f8da0e3a8c94264a3b50 + +export const process = async (ctx: Context) => { + const firstBlockTimestamp = ctx.blocks.find((b) => b.header.height >= from) + ?.header.timestamp + if (!firstBlockTimestamp) return + + const firstBlock = ctx.blocks[0] + const lastBlock = ctx.blocks[ctx.blocks.length - 1] + const lastDailyStat = await ctx.store.findOne(OETHDailyStat, { + where: { timestamp: LessThanOrEqual(new Date(firstBlockTimestamp)) }, + order: { id: 'desc' }, + }) + + const startTimestamp = Math.min( + lastDailyStat?.timestamp.getTime() || Date.now(), + firstBlock.header.timestamp, + ) + const endTimestamp = lastBlock?.header.timestamp || Date.now() + + const days = getStartOfDays(startTimestamp, endTimestamp) + + const dailyStats = [] as OETHDailyStat[] + const dailyCollateralStats = [] as OETHCollateralDailyStat[] + const dailyStrategyStats = [] as OETHStrategyDailyStat[] + const dailyHoldingsStats = [] as OETHStrategyHoldingDailyStat[] + + for (const day of days) { + const dailyStatInserts = await updateDailyStats(ctx, day) + if (dailyStatInserts) { + dailyStats.push(dailyStatInserts.dailyStat) + dailyCollateralStats.push(...dailyStatInserts.dailyCollateralStats) + dailyStrategyStats.push(...dailyStatInserts.dailyStrategyStats) + dailyHoldingsStats.push(...dailyStatInserts.dailyStrategyHoldingsStats) + } + } + + await ctx.store.upsert(dailyStats) + await Promise.all([ + ctx.store.upsert(dailyCollateralStats), + ctx.store.upsert(dailyStrategyStats), + ctx.store.upsert(dailyHoldingsStats), + ]) +} + +async function updateDailyStats(ctx: Context, date: Date) { + const queryParams = { + where: { timestamp: LessThanOrEqual(date) }, + order: { timestamp: 'desc' as FindOptionsOrderValue }, + } + + const [ + lastApy, + lastOeth, + lastCurve, + lastVault, + lastBalancer, + lastFrax, + lastMorpho, + lastRethRate, + lastSfrxEthRate, + ] = await Promise.all([ + ctx.store.findOne(OETHAPY, queryParams), + ctx.store.findOne(OETH, queryParams), + ctx.store.findOne(OETHCurveLP, queryParams), + ctx.store.findOne(OETHVault, queryParams), + ctx.store.findOne(OETHBalancerMetaPoolStrategy, queryParams), + ctx.store.findOne(OETHFraxStaking, queryParams), + ctx.store.findOne(OETHMorphoAave, queryParams), + ctx.store.findOne(ExchangeRate, { + where: { timestamp: LessThanOrEqual(date), pair: 'ETH_rETH' }, + order: { timestamp: 'desc' as FindOptionsOrderValue }, + }), + ctx.store.findOne(ExchangeRate, { + where: { timestamp: LessThanOrEqual(date), pair: 'ETH_sfrxETH' }, + order: { timestamp: 'desc' as FindOptionsOrderValue }, + }), + ]) + + // Do we have any useful data yet? + const allEntities = [lastApy, lastOeth] + if (!allEntities.every((entity) => !!entity)) { + return null + } + + // console.log({ + // lastApy, + // lastOeth, + // lastCurve, + // lastVault, + // lastBalancer, + // lastFrax, + // lastMorpho, + // lastRethExchangeRate, + // lastSfrxEthExchangeRate, + // }) + + const entityManager = ( + ctx.store as unknown as { em: () => EntityManager } + ).em() + + const end = dayjs(date).endOf('day').toDate() + const yieldStats = await entityManager.query< + { + period: string + total_yield: bigint + total_fees: bigint + total_revenue: bigint + }[] + >(yieldStatsQuery, [end]) + + const mostRecentEntity = allEntities.reduce((highest, current) => { + if (!highest || !current) return current + return current.blockNumber > highest.blockNumber ? current : highest + }) + + const id = date.toISOString().substring(0, 10) + + const dailyStat = new OETHDailyStat({ + id, + blockNumber: mostRecentEntity?.blockNumber, + timestamp: mostRecentEntity?.timestamp, + + apr: lastApy?.apr, + apy: lastApy?.apy, + apy7DayAvg: lastApy?.apy7DayAvg, + apy14DayAvg: lastApy?.apy14DayAvg, + apy30DayAvg: lastApy?.apy30DayAvg, + + totalSupply: lastOeth?.totalSupply || 0n, + totalSupplyUSD: 0, + rebasingSupply: lastOeth?.rebasingSupply || 0n, + nonRebasingSupply: lastOeth?.nonRebasingSupply || 0n, + amoSupply: lastCurve?.oethOwned || 0n, + + yield: yieldStats[0].total_yield || 0n, + fees: yieldStats[0].total_fees || 0n, + revenue: yieldStats[0].total_revenue || 0n, + revenue7DayAvg: BigInt(yieldStats[1].total_revenue || 0n) / 7n, + revenue7DayTotal: yieldStats[1].total_revenue || 0n, + revenueAllTime: yieldStats[2].total_revenue || 0n, + + pegPrice: 0n, + }) + + // Collateral totals + const ETH = lastCurve?.ethOwned || 0n + const OETHOwned = lastCurve?.oethOwned || 0n + const WETH = + (lastVault?.weth || 0n) + + (lastMorpho?.weth || 0n) + + (lastBalancer?.weth || 0n) + const stETH = lastVault?.stETH || 0n + + const rETHRaw = (lastVault?.rETH || 0n) + (lastBalancer?.rETH || 0n) + const rethRate = lastRethRate?.rate || 1000000000000000000n + const rETH = (rETHRaw * rethRate) / 1000000000000000000n + + const sfrxEthExchangeRate = lastSfrxEthRate?.rate || 1000000000000000000n + const sfrxETH = lastFrax?.sfrxETH || 0n + const convertedSfrxEth = + (sfrxETH * sfrxEthExchangeRate) / 1000000000000000000n + const frxETH = (lastVault?.frxETH || 0n) + convertedSfrxEth + + const totalCollateral = ETH + WETH + frxETH + stETH + rETH + + // console.log(`Day: ${date}`) + // log([ + // ['Total Supply', dailyStat.totalSupply], + // ['Circulating Supply', dailyStat.totalSupply - OETHOwned], + // ['Total Collateral', totalCollateral], + // ['Difference', dailyStat.totalSupply - OETHOwned - totalCollateral], + // ['Total ETH', ETH], + // ['Total WETH', WETH], + // ['Total stETH', stETH], + // ['Total rETH', rETH], + // ['Total frxETH', frxETH], + // ['', null], + // ['Vault frxETH', lastVault?.frxETH || 0n], + // ['Total sfrxETH', sfrxETH], + // ]) + + // Strategy totals + const vaultTotal = + (lastVault?.frxETH || 0n) + + (lastVault?.weth || 0n) + + (lastVault?.stETH || 0n) + + (lastVault?.rETH || 0n) + + const balancerTotal = (lastBalancer?.weth || 0n) + (lastBalancer?.rETH || 0n) + + const dailyStrategyStats = [ + new OETHStrategyDailyStat({ + id: `${id}-CURVE`, + dailyStatId: id as unknown as OETHDailyStat, + tvl: lastCurve?.ethOwned || 0n, + total: lastCurve?.ethOwned || 0n, + }), + new OETHStrategyDailyStat({ + id: `${id}-VAULT`, + dailyStatId: id as unknown as OETHDailyStat, + tvl: vaultTotal, + total: vaultTotal, + }), + new OETHStrategyDailyStat({ + id: `${id}-BALANCER`, + dailyStatId: id as unknown as OETHDailyStat, + tvl: balancerTotal, + total: balancerTotal, + }), + new OETHStrategyDailyStat({ + id: `${id}-FRAX`, + dailyStatId: id as unknown as OETHDailyStat, + tvl: lastFrax?.sfrxETH || 0n, + total: frxETH, + }), + new OETHStrategyDailyStat({ + id: `${id}-MORPHO`, + dailyStatId: id as unknown as OETHDailyStat, + tvl: lastMorpho?.weth || 0n, + total: lastMorpho?.weth || 0n, + }), + ] + + const dailyStrategyHoldingsStats = [ + new OETHStrategyHoldingDailyStat({ + id: `${id}-CURVE-ETH`, + strategyDailyStatId: `${id}-CURVE` as unknown as OETHStrategyDailyStat, + symbol: 'ETH', + amount: lastCurve?.eth || 0n, + value: lastCurve?.eth || 0n, + }), + new OETHStrategyHoldingDailyStat({ + id: `${id}-VAULT-WETH`, + strategyDailyStatId: `${id}-VAULT` as unknown as OETHStrategyDailyStat, + symbol: 'WETH', + amount: lastVault?.weth || 0n, + value: lastVault?.weth || 0n, + }), + new OETHStrategyHoldingDailyStat({ + id: `${id}-VAULT-FRXETH`, + strategyDailyStatId: `${id}-VAULT` as unknown as OETHStrategyDailyStat, + symbol: 'FRXETH', + amount: lastVault?.frxETH || 0n, + value: lastVault?.frxETH || 0n, + }), + new OETHStrategyHoldingDailyStat({ + id: `${id}-VAULT-STETH`, + strategyDailyStatId: `${id}-VAULT` as unknown as OETHStrategyDailyStat, + symbol: 'STETH', + amount: lastVault?.stETH || 0n, + value: lastVault?.stETH || 0n, + }), + new OETHStrategyHoldingDailyStat({ + id: `${id}-VAULT-RETH`, + strategyDailyStatId: `${id}-VAULT` as unknown as OETHStrategyDailyStat, + symbol: 'RETH', + amount: lastVault?.rETH || 0n, + value: ((lastVault?.rETH || 0n) * rethRate) / 1000000000000000000n, + }), + new OETHStrategyHoldingDailyStat({ + id: `${id}-BALANCER-RETH`, + strategyDailyStatId: `${id}-BALANCER` as unknown as OETHStrategyDailyStat, + symbol: 'RETH', + amount: lastBalancer?.rETH || 0n, + value: ((lastBalancer?.rETH || 0n) * rethRate) / 1000000000000000000n, + }), + new OETHStrategyHoldingDailyStat({ + id: `${id}-BALANCER-WETH`, + strategyDailyStatId: `${id}-BALANCER` as unknown as OETHStrategyDailyStat, + symbol: 'WETH', + amount: lastBalancer?.weth || 0n, + value: lastBalancer?.weth || 0n, + }), + new OETHStrategyHoldingDailyStat({ + id: `${id}-FRAX-SFRXETH`, + strategyDailyStatId: `${id}-FRAX` as unknown as OETHStrategyDailyStat, + symbol: 'SFRXETH', + amount: lastFrax?.sfrxETH || 0n, + value: (sfrxETH * sfrxEthExchangeRate) / 1000000000000000000n, + }), + new OETHStrategyHoldingDailyStat({ + id: `${id}-MORPHO-WETH`, + strategyDailyStatId: `${id}-MORPHO` as unknown as OETHStrategyDailyStat, + symbol: 'WETH', + amount: lastMorpho?.weth || 0n, + value: lastMorpho?.weth || 0n, + }), + ] + + const dailyCollateralStats = [ + new OETHCollateralDailyStat({ + id: `${id}-ETH`, + dailyStatId: id as unknown as OETHDailyStat, + symbol: 'ETH', + amount: ETH, + price: 1n, + value: ETH, + }), + new OETHCollateralDailyStat({ + id: `${id}-WETH`, + dailyStatId: id as unknown as OETHDailyStat, + symbol: 'WETH', + amount: WETH, + price: 1n, + value: WETH, + }), + new OETHCollateralDailyStat({ + id: `${id}-STETH`, + dailyStatId: id as unknown as OETHDailyStat, + symbol: 'STETH', + amount: stETH, + price: 1n, + value: stETH, + }), + new OETHCollateralDailyStat({ + id: `${id}-RETH`, + dailyStatId: id as unknown as OETHDailyStat, + symbol: 'RETH', + amount: rETH, + price: rethRate, + value: (rETH * rethRate) / 1000000000000000000n, + }), + new OETHCollateralDailyStat({ + id: `${id}-FRXETH`, + dailyStatId: id as unknown as OETHDailyStat, + symbol: 'FRXETH', + amount: sfrxETH, + price: 1n, + value: sfrxETH, + }), + ] + + return { + dailyStat, + dailyCollateralStats, + dailyStrategyStats, + dailyStrategyHoldingsStats, + } +} + +function getStartOfDays(startTimestamp: number, endTimestamp: number): Date[] { + const dayMilliseconds = 24 * 60 * 60 * 1000 // Number of milliseconds in a day + let startTimestampStart = new Date(startTimestamp) + startTimestampStart.setUTCHours(0, 0, 0, 0) + + let currentTimestamp = startTimestampStart.getTime() + let dates: Date[] = [] + + while (currentTimestamp <= endTimestamp) { + let date = new Date(currentTimestamp) + date.setUTCHours(0, 0, 0, 0) // Set to start of the day + dates.push(date) + + currentTimestamp += dayMilliseconds // Move to the next day + } + + return dates +} + +const yieldStatsQuery = ` +-- Results for 1 day +SELECT '1 day' as period, SUM(fee) as total_fees, SUM(yield) as total_yield, SUM(yield - fee) as total_revenue +FROM oeth_rebase +WHERE timestamp BETWEEN ($1::timestamp - interval '1 day') AND $1::timestamp + +UNION ALL + +-- Results for 7 days +SELECT '7 days' as period, SUM(fee) as total_fees, SUM(yield) as total_yield, SUM(yield - fee) as total_revenue +FROM oeth_rebase +WHERE timestamp BETWEEN ($1::timestamp - interval '7 days') AND $1::timestamp + +UNION ALL + +-- Results for all time up to the end date +SELECT 'all time' as period, SUM(fee) as total_fees, SUM(yield) as total_yield, SUM(yield - fee) as total_revenue +FROM oeth_rebase +WHERE timestamp <= $1::timestamp +` + +function log(entries: [string, bigint | null][]): void { + if (!entries) { + console.log('') + return + } + // Find the longest label for alignment + const maxLength = Math.max(...entries.map((entry) => entry[0].length)) + + const lines = entries.map(([label, value]) => { + // Format the value + const formattedValue = value ? Number(formatEther(value)).toFixed(3) : '' + // Right-align the label and value + return `${label.padEnd(maxLength)} ${formattedValue.padStart(10)}` + }) + + console.log(`${lines.join('\n')}\n`) +} diff --git a/src/post-processors/daily-stats/index.ts b/src/post-processors/daily-stats/index.ts new file mode 100644 index 00000000..966fdd9a --- /dev/null +++ b/src/post-processors/daily-stats/index.ts @@ -0,0 +1 @@ +export * from './daily-stats' diff --git a/src/processor.ts b/src/processor.ts index f34d7f1a..583ca3c8 100644 --- a/src/processor.ts +++ b/src/processor.ts @@ -51,7 +51,6 @@ export const createSquidProcessor = () => callValue: true, callInput: true, createResultAddress: true, - // action: true, }, })