From 8bcf7800457e985334cc83934f14a8cb331f9fc3 Mon Sep 17 00:00:00 2001 From: Nick Poulden Date: Mon, 18 Dec 2023 15:30:02 -0700 Subject: [PATCH 1/3] Daily stats expansion --- ...67079012-Data.js => 1702938429756-Data.js} | 14 +- schema-oeth.graphql | 9 +- schema-ogv.graphql | 14 ++ schema-ousd.graphql | 6 + schema.graphql | 30 +++- src/main-ogv.ts | 3 +- src/model/generated/index.ts | 1 + src/model/generated/oethDailyStat.model.ts | 18 ++- src/model/generated/ogvDailyStat.model.ts | 41 +++++ src/model/generated/ousdDailyStat.model.ts | 15 ++ src/ogv/post-processors/daily-stats.ts | 145 ++++++++++++++++++ src/utils/coingecko.ts | 46 ++++++ 12 files changed, 327 insertions(+), 15 deletions(-) rename db/migrations/{1702667079012-Data.js => 1702938429756-Data.js} (95%) create mode 100644 src/model/generated/ogvDailyStat.model.ts create mode 100644 src/ogv/post-processors/daily-stats.ts create mode 100644 src/utils/coingecko.ts diff --git a/db/migrations/1702667079012-Data.js b/db/migrations/1702938429756-Data.js similarity index 95% rename from db/migrations/1702667079012-Data.js rename to db/migrations/1702938429756-Data.js index 74e97d25..774fca70 100644 --- a/db/migrations/1702667079012-Data.js +++ b/db/migrations/1702938429756-Data.js @@ -1,5 +1,5 @@ -module.exports = class Data1702667079012 { - name = 'Data1702667079012' +module.exports = class Data1702938429756 { + name = 'Data1702938429756' 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"))`) @@ -82,7 +82,7 @@ module.exports = class Data1702667079012 { 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, "dripper_weth" numeric NOT NULL, "wrapped_supply" numeric NOT NULL, "yield_eth" numeric NOT NULL, "yield_eth_all_time" numeric NOT NULL, "yield_usd" numeric NOT NULL, "yield_usd_all_time" numeric NOT NULL, "fees_eth" numeric NOT NULL, "fees_eth7_day" numeric NOT NULL, "fees_eth_all_time" numeric NOT NULL, "fees_usd" numeric NOT NULL, "fees_usd7_day" numeric NOT NULL, "fees_usd_all_time" numeric NOT NULL, "peg_price" numeric NOT NULL, CONSTRAINT "PK_9144a02ab13b1baa818a7d5eae5" PRIMARY KEY ("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, "dripper_weth" numeric NOT NULL, "wrapped_supply" numeric NOT NULL, "trading_volume_usd" numeric NOT NULL, "yield_eth" numeric NOT NULL, "yield_eth7_day" numeric NOT NULL, "yield_eth_all_time" numeric NOT NULL, "yield_usd" numeric NOT NULL, "yield_usd7day" numeric NOT NULL, "yield_usd_all_time" numeric NOT NULL, "fees_eth" numeric NOT NULL, "fees_eth7_day" numeric NOT NULL, "fees_eth_all_time" numeric NOT NULL, "fees_usd" numeric NOT NULL, "fees_usd7_day" numeric NOT NULL, "fees_usd_all_time" numeric NOT NULL, "peg_price" numeric NOT NULL, "market_cap_usd" numeric NOT NULL, "holders_over_threshold" integer 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 "oeth_reward_token_collected" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "strategy" text NOT NULL, "recipient" text NOT NULL, "reward_token" text NOT NULL, "amount" numeric NOT NULL, CONSTRAINT "PK_47098cc5fbc7cb95c2374fa33cd" PRIMARY KEY ("id"))`) @@ -109,6 +109,9 @@ module.exports = class Data1702667079012 { await db.query(`CREATE TABLE "ogv_proposal_vote" ("id" character varying NOT NULL, "weight" numeric NOT NULL, "type" character varying(7) NOT NULL, "tx_hash" text NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "proposal_id" character varying, "voter_id" character varying, CONSTRAINT "PK_93c03f35b95221586cb8b83f523" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_58d732bc6523c2609d2725cc0a" ON "ogv_proposal_vote" ("proposal_id") `) await db.query(`CREATE INDEX "IDX_2fd621aea353448fb3f17721bc" ON "ogv_proposal_vote" ("voter_id") `) + await db.query(`CREATE TABLE "ogv_daily_stat" ("id" character varying NOT NULL, "block_number" integer NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "total_supply" numeric NOT NULL, "total_supply_usd" numeric NOT NULL, "total_staked" numeric NOT NULL, "trading_volume_usd" numeric NOT NULL, "market_cap_usd" numeric NOT NULL, "price_usd" numeric NOT NULL, "holders_over_threshold" integer NOT NULL, CONSTRAINT "PK_69bc6d866711151e2712481794c" PRIMARY KEY ("id"))`) + await db.query(`CREATE INDEX "IDX_c974bde87107cacae6d3654972" ON "ogv_daily_stat" ("block_number") `) + await db.query(`CREATE INDEX "IDX_b62fa80951183bb0acf8a5e8b9" ON "ogv_daily_stat" ("timestamp") `) await db.query(`CREATE TABLE "ousd" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "total_supply" numeric NOT NULL, "rebasing_supply" numeric NOT NULL, "non_rebasing_supply" numeric NOT NULL, CONSTRAINT "PK_acecae4a20bc14b22d9f6738d8d" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_c8d1e285213b445b088805ac7c" ON "ousd" ("timestamp") `) await db.query(`CREATE INDEX "IDX_806949dd853b7e8acab5d03b81" ON "ousd" ("block_number") `) @@ -173,7 +176,7 @@ module.exports = class Data1702667079012 { await db.query(`CREATE INDEX "IDX_3edda581683682257159cb0863" ON "ousd_strategy_daily_stat" ("daily_stat_id_id") `) await db.query(`CREATE TABLE "ousd_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_85a804677f14a0784914e27b7b9" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_78be9d3f3dc1f31cf17ae810fe" ON "ousd_collateral_daily_stat" ("daily_stat_id_id") `) - await db.query(`CREATE TABLE "ousd_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, "dripper_weth" numeric NOT NULL, "wrapped_supply" numeric NOT NULL, "yield_eth" numeric NOT NULL, "yield_eth_all_time" numeric NOT NULL, "yield_usd" numeric NOT NULL, "yield_usd_all_time" numeric NOT NULL, "fees_eth" numeric NOT NULL, "fees_eth7_day" numeric NOT NULL, "fees_eth_all_time" numeric NOT NULL, "fees_usd" numeric NOT NULL, "fees_usd7_day" numeric NOT NULL, "fees_usd_all_time" numeric NOT NULL, "peg_price" numeric NOT NULL, CONSTRAINT "PK_f8adaf321a99f2b4b877c262880" PRIMARY KEY ("id"))`) + await db.query(`CREATE TABLE "ousd_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, "dripper_weth" numeric NOT NULL, "wrapped_supply" numeric NOT NULL, "trading_volume_usd" numeric NOT NULL, "yield_eth" numeric NOT NULL, "yield_eth7_day" numeric NOT NULL, "yield_eth_all_time" numeric NOT NULL, "yield_usd" numeric NOT NULL, "yield_usd7_day" numeric NOT NULL, "yield_usd_all_time" numeric NOT NULL, "fees_eth" numeric NOT NULL, "fees_eth7_day" numeric NOT NULL, "fees_eth_all_time" numeric NOT NULL, "fees_usd" numeric NOT NULL, "fees_usd7_day" numeric NOT NULL, "fees_usd_all_time" numeric NOT NULL, "peg_price" numeric NOT NULL, "market_cap_usd" numeric NOT NULL, "holders_over_threshold" integer NOT NULL, CONSTRAINT "PK_f8adaf321a99f2b4b877c262880" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_f9020d89932aad2d0de8923490" ON "ousd_daily_stat" ("block_number") `) await db.query(`CREATE INDEX "IDX_0bb5f72bf5fa59ce8c232caa4c" ON "ousd_daily_stat" ("timestamp") `) 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`) @@ -305,6 +308,9 @@ module.exports = class Data1702667079012 { await db.query(`DROP TABLE "ogv_proposal_vote"`) await db.query(`DROP INDEX "public"."IDX_58d732bc6523c2609d2725cc0a"`) await db.query(`DROP INDEX "public"."IDX_2fd621aea353448fb3f17721bc"`) + await db.query(`DROP TABLE "ogv_daily_stat"`) + await db.query(`DROP INDEX "public"."IDX_c974bde87107cacae6d3654972"`) + await db.query(`DROP INDEX "public"."IDX_b62fa80951183bb0acf8a5e8b9"`) await db.query(`DROP TABLE "ousd"`) await db.query(`DROP INDEX "public"."IDX_c8d1e285213b445b088805ac7c"`) await db.query(`DROP INDEX "public"."IDX_806949dd853b7e8acab5d03b81"`) diff --git a/schema-oeth.graphql b/schema-oeth.graphql index 5f3c40c8..ea4716f8 100644 --- a/schema-oeth.graphql +++ b/schema-oeth.graphql @@ -185,10 +185,14 @@ type OETHDailyStat @entity { amoSupply: BigInt! dripperWETH: BigInt! wrappedSupply: BigInt! + tradingVolumeUSD: Float! yieldETH: BigInt! + yieldETH7Day: BigInt! yieldETHAllTime: BigInt! + yieldUSD: BigInt! + yieldUSD7day: BigInt! yieldUSDAllTime: BigInt! feesETH: BigInt! @@ -200,9 +204,8 @@ type OETHDailyStat @entity { feesUSDAllTime: BigInt! pegPrice: BigInt! - """ - Price of OETH in ETH - """ + marketCapUSD: Float! + holdersOverThreshold: Int! strategies: [OETHStrategyDailyStat] @derivedFrom(field: "dailyStatId") collateral: [OETHCollateralDailyStat] @derivedFrom(field: "dailyStatId") } diff --git a/schema-ogv.graphql b/schema-ogv.graphql index 5ac65017..56a85d43 100644 --- a/schema-ogv.graphql +++ b/schema-ogv.graphql @@ -100,3 +100,17 @@ type OGVProposalVote @entity { txHash: String! timestamp: DateTime! } + +type OGVDailyStat @entity { + id: ID! + blockNumber: Int! @index + timestamp: DateTime! @index + + totalSupply: BigInt! + totalSupplyUSD: Float! + totalStaked: BigInt! + tradingVolumeUSD: Float! + marketCapUSD: Float! + priceUSD: Float! + holdersOverThreshold: Int! +} \ No newline at end of file diff --git a/schema-ousd.graphql b/schema-ousd.graphql index 2ae1e77e..c1a0e221 100644 --- a/schema-ousd.graphql +++ b/schema-ousd.graphql @@ -218,10 +218,14 @@ type OUSDDailyStat @entity { amoSupply: BigInt! dripperWETH: BigInt! wrappedSupply: BigInt! + tradingVolumeUSD: Float! yieldETH: BigInt! + yieldETH7Day: BigInt! yieldETHAllTime: BigInt! + yieldUSD: BigInt! + yieldUSD7Day: BigInt! yieldUSDAllTime: BigInt! feesETH: BigInt! @@ -233,6 +237,8 @@ type OUSDDailyStat @entity { feesUSDAllTime: BigInt! pegPrice: BigInt! + marketCapUSD: Float! + holdersOverThreshold: Int! strategies: [OUSDStrategyDailyStat] @derivedFrom(field: "dailyStatId") collateral: [OUSDCollateralDailyStat] @derivedFrom(field: "dailyStatId") } diff --git a/schema.graphql b/schema.graphql index 89c942c7..2aa81450 100644 --- a/schema.graphql +++ b/schema.graphql @@ -345,10 +345,14 @@ type OETHDailyStat @entity { amoSupply: BigInt! dripperWETH: BigInt! wrappedSupply: BigInt! + tradingVolumeUSD: Float! yieldETH: BigInt! + yieldETH7Day: BigInt! yieldETHAllTime: BigInt! + yieldUSD: BigInt! + yieldUSD7day: BigInt! yieldUSDAllTime: BigInt! feesETH: BigInt! @@ -360,9 +364,8 @@ type OETHDailyStat @entity { feesUSDAllTime: BigInt! pegPrice: BigInt! - """ - Price of OETH in ETH - """ + marketCapUSD: Float! + holdersOverThreshold: Int! strategies: [OETHStrategyDailyStat] @derivedFrom(field: "dailyStatId") collateral: [OETHCollateralDailyStat] @derivedFrom(field: "dailyStatId") } @@ -554,7 +557,20 @@ type OGVProposalVote @entity { txHash: String! timestamp: DateTime! } -""" + +type OGVDailyStat @entity { + id: ID! + blockNumber: Int! @index + timestamp: DateTime! @index + + totalSupply: BigInt! + totalSupplyUSD: Float! + totalStaked: BigInt! + tradingVolumeUSD: Float! + marketCapUSD: Float! + priceUSD: Float! + holdersOverThreshold: Int! +}""" The OUSD entity tracks the change in total supply of OUSD over time. """ type OUSD @entity { @@ -774,10 +790,14 @@ type OUSDDailyStat @entity { amoSupply: BigInt! dripperWETH: BigInt! wrappedSupply: BigInt! + tradingVolumeUSD: Float! yieldETH: BigInt! + yieldETH7Day: BigInt! yieldETHAllTime: BigInt! + yieldUSD: BigInt! + yieldUSD7Day: BigInt! yieldUSDAllTime: BigInt! feesETH: BigInt! @@ -789,6 +809,8 @@ type OUSDDailyStat @entity { feesUSDAllTime: BigInt! pegPrice: BigInt! + marketCapUSD: Float! + holdersOverThreshold: Int! strategies: [OUSDStrategyDailyStat] @derivedFrom(field: "dailyStatId") collateral: [OUSDCollateralDailyStat] @derivedFrom(field: "dailyStatId") } diff --git a/src/main-ogv.ts b/src/main-ogv.ts index a219f3d6..3cda535e 100644 --- a/src/main-ogv.ts +++ b/src/main-ogv.ts @@ -1,3 +1,4 @@ +import * as dailyStats from './ogv/post-processors/daily-stats' import * as governance from './ogv/post-processors/governance' import * as ogv from './ogv/processors/ogv' import * as ogvSupply from './ogv/processors/ogv-supply' @@ -6,7 +7,7 @@ import { run } from './processor' export const processor = { stateSchema: 'ogv-processor', processors: [ogvSupply, ogv], - postProcessors: [governance], + postProcessors: [governance, dailyStats], validators: [], } export default processor diff --git a/src/model/generated/index.ts b/src/model/generated/index.ts index 14fcdb2a..d3196aa6 100644 --- a/src/model/generated/index.ts +++ b/src/model/generated/index.ts @@ -42,6 +42,7 @@ export * from "./ogvProposal.model" export * from "./_ogvProposalState" export * from "./ogvProposalVote.model" export * from "./_ogvVoteType" +export * from "./ogvDailyStat.model" export * from "./ousd.model" export * from "./ousdAsset.model" export * from "./ousdAddress.model" diff --git a/src/model/generated/oethDailyStat.model.ts b/src/model/generated/oethDailyStat.model.ts index fb120cde..b262e260 100644 --- a/src/model/generated/oethDailyStat.model.ts +++ b/src/model/generated/oethDailyStat.model.ts @@ -65,15 +65,24 @@ export class OETHDailyStat { @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) wrappedSupply!: bigint + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + tradingVolumeUSD!: number + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) yieldETH!: bigint + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + yieldETH7Day!: bigint + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) yieldETHAllTime!: bigint @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) yieldUSD!: bigint + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + yieldUSD7day!: bigint + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) yieldUSDAllTime!: bigint @@ -98,9 +107,12 @@ export class OETHDailyStat { @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) pegPrice!: bigint - /** - * Price of OETH in ETH - */ + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + marketCapUSD!: number + + @Column_("int4", {nullable: false}) + holdersOverThreshold!: number + @OneToMany_(() => OETHStrategyDailyStat, e => e.dailyStatId) strategies!: OETHStrategyDailyStat[] diff --git a/src/model/generated/ogvDailyStat.model.ts b/src/model/generated/ogvDailyStat.model.ts new file mode 100644 index 00000000..a0923f4c --- /dev/null +++ b/src/model/generated/ogvDailyStat.model.ts @@ -0,0 +1,41 @@ +import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, Index as Index_} from "typeorm" +import * as marshal from "./marshal" + +@Entity_() +export class OGVDailyStat { + constructor(props?: Partial) { + Object.assign(this, props) + } + + @PrimaryColumn_() + id!: string + + @Index_() + @Column_("int4", {nullable: false}) + blockNumber!: number + + @Index_() + @Column_("timestamp with time zone", {nullable: false}) + timestamp!: Date + + @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}) + totalStaked!: bigint + + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + tradingVolumeUSD!: number + + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + marketCapUSD!: number + + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + priceUSD!: number + + @Column_("int4", {nullable: false}) + holdersOverThreshold!: number +} diff --git a/src/model/generated/ousdDailyStat.model.ts b/src/model/generated/ousdDailyStat.model.ts index 649f3160..b4591ba8 100644 --- a/src/model/generated/ousdDailyStat.model.ts +++ b/src/model/generated/ousdDailyStat.model.ts @@ -56,15 +56,24 @@ export class OUSDDailyStat { @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) wrappedSupply!: bigint + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + tradingVolumeUSD!: number + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) yieldETH!: bigint + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + yieldETH7Day!: bigint + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) yieldETHAllTime!: bigint @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) yieldUSD!: bigint + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) + yieldUSD7Day!: bigint + @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) yieldUSDAllTime!: bigint @@ -89,6 +98,12 @@ export class OUSDDailyStat { @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) pegPrice!: bigint + @Column_("numeric", {transformer: marshal.floatTransformer, nullable: false}) + marketCapUSD!: number + + @Column_("int4", {nullable: false}) + holdersOverThreshold!: number + @OneToMany_(() => OUSDStrategyDailyStat, e => e.dailyStatId) strategies!: OUSDStrategyDailyStat[] diff --git a/src/ogv/post-processors/daily-stats.ts b/src/ogv/post-processors/daily-stats.ts new file mode 100644 index 00000000..fd281fcc --- /dev/null +++ b/src/ogv/post-processors/daily-stats.ts @@ -0,0 +1,145 @@ +import { EvmBatchProcessor } from '@subsquid/evm-processor' +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +import { + EntityManager, + FindOptionsOrderValue, + LessThanOrEqual, + MoreThan, +} from 'typeorm' +import { parseEther } from 'viem' + +import { OGV, OGVAddress, OGVDailyStat } from '../../model' +import { Context } from '../../processor' +import { processCoingeckoData } from '../../utils/coingecko' + +dayjs.extend(utc) + +export const from = 15491391 + +export const setup = async (processor: EvmBatchProcessor) => { + processor.includeAllBlocks({ from }) +} + +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 startDate = dayjs.utc(firstBlock.header.timestamp).endOf('day') + const endDate = dayjs.utc(lastBlock.header.timestamp).endOf('day') + + let dates: Date[] = [] + for ( + let date = startDate; + !date.isAfter(endDate); + date = date.add(1, 'day').endOf('day') + ) { + dates.push(date.toDate()) + } + + const dailyStats = [] as OGVDailyStat[] + + for (const date of dates) { + const dailyStatInserts = await updateDailyStats(ctx, date) + if (dailyStatInserts) { + dailyStats.push(dailyStatInserts.dailyStat) + } + } + + if (ctx.isHead) { + const statsWithNoPrice = await ctx.store.findBy(OGVDailyStat, { + priceUSD: 0, + timestamp: LessThanOrEqual(getStartOfDayTimestamp()), + }) + if (statsWithNoPrice.length > 0) { + console.log(`Found ${statsWithNoPrice.length} stats with no price`) + const coingeckoURL = `https://api.coingecko.com/api/v3/coins/origin-dollar-governance/market_chart?vs_currency=usd&days=max&interval=daily&precision=18` + const coingeckoResponse = await fetch(coingeckoURL) + const coingeckoJson = await coingeckoResponse.json() + + if (!coingeckoJson) { + console.log('Could not fetch coingecko data') + } else { + const coingeckData = processCoingeckoData(coingeckoJson) + for (const dayId in coingeckData) { + const stat = statsWithNoPrice.find((s) => s.id === dayId) + const day = coingeckData[dayId] + + if (stat && day.prices) { + stat.tradingVolumeUSD = day.total_volumes || 0 + stat.marketCapUSD = day.market_caps || 0 + stat.priceUSD = day.prices + dailyStats.push(stat) + } + } + } + } + } + + await ctx.store.upsert(dailyStats) +} + +async function updateDailyStats(ctx: Context, date: Date) { + const queryParams = { + where: { timestamp: LessThanOrEqual(date) }, + order: { timestamp: 'desc' as FindOptionsOrderValue }, + } + + const [lastOgv] = await Promise.all([ctx.store.findOne(OGV, queryParams)]) + + // Do we have any useful data yet? + const allEntities = [lastOgv].filter(Boolean) + if (!lastOgv) { + return null + } + + const entityManager = ( + ctx.store as unknown as { + em: () => EntityManager + } + ).em() + + const holderStats = await entityManager.query< + { + holders_over_threshold: number + }[] + >(holderStatsQuery) + + 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 OGVDailyStat({ + id, + blockNumber: mostRecentEntity?.blockNumber, + timestamp: mostRecentEntity?.timestamp, + + totalSupply: lastOgv?.total || 0n, + totalStaked: lastOgv?.staked, + totalSupplyUSD: 0, + tradingVolumeUSD: 0, + marketCapUSD: 0, + priceUSD: 0, + holdersOverThreshold: holderStats[0]?.holders_over_threshold || 0, + }) + + return { dailyStat } +} + +const holderStatsQuery = ` +SELECT COUNT(*) as holders_over_threshold +FROM ogv_address +WHERE balance > 100000000000000000000 +` + +function getStartOfDayTimestamp(): Date { + const utcDate = new Date() + utcDate.setUTCHours(0, 0, 0, 0) + return utcDate +} diff --git a/src/utils/coingecko.ts b/src/utils/coingecko.ts new file mode 100644 index 00000000..fc760900 --- /dev/null +++ b/src/utils/coingecko.ts @@ -0,0 +1,46 @@ +export interface CoingeckoDataInput { + prices: [number, number][] + market_caps: [number, number][] + total_volumes: [number, number][] +} + +export interface CoingeckoDataOutput { + [date: string]: { + prices: number | null + market_caps: number | null + total_volumes: number | null + } +} + +export function processCoingeckoData(data: CoingeckoDataInput): CoingeckoDataOutput { + const result: CoingeckoDataOutput = {} + + // Helper function to convert timestamp to date string + const timestampToDate = (timestamp: number): string => { + const date = new Date(timestamp) + return date.toISOString().split('T')[0] + } + + const dataKeys = Object.keys(data) as (keyof CoingeckoDataInput)[] + + // Process each data category + dataKeys.forEach((category) => { + data[category].forEach(([timestamp, value]) => { + const dateStr = timestampToDate(timestamp) + + // Initialize the object for the date if it doesn't exist + if (!result[dateStr]) { + result[dateStr] = { + prices: null, + market_caps: null, + total_volumes: null, + } + } + + // Update the relevant category + result[dateStr][category] = value + }) + }) + + return result +} From d9616171b16641fbd6c321691aa3dec007f66d3f Mon Sep 17 00:00:00 2001 From: Nick Poulden Date: Tue, 19 Dec 2023 14:46:33 -0700 Subject: [PATCH 2/3] Daily stats --- ...38429756-Data.js => 1702941827074-Data.js} | 6 +- schema-oeth.graphql | 2 +- schema.graphql | 2 +- src/model/generated/oethDailyStat.model.ts | 2 +- .../daily-stats/daily-stats.ts | 41 ++++++++-- src/ogv/post-processors/daily-stats.ts | 48 +++--------- .../daily-stats/daily-stats.ts | 75 +++++++++++++----- src/utils/coingecko.ts | 77 ++++++++++++++++++- 8 files changed, 183 insertions(+), 70 deletions(-) rename db/migrations/{1702938429756-Data.js => 1702941827074-Data.js} (98%) diff --git a/db/migrations/1702938429756-Data.js b/db/migrations/1702941827074-Data.js similarity index 98% rename from db/migrations/1702938429756-Data.js rename to db/migrations/1702941827074-Data.js index 774fca70..e86f92c2 100644 --- a/db/migrations/1702938429756-Data.js +++ b/db/migrations/1702941827074-Data.js @@ -1,5 +1,5 @@ -module.exports = class Data1702938429756 { - name = 'Data1702938429756' +module.exports = class Data1702941827074 { + name = 'Data1702941827074' 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"))`) @@ -82,7 +82,7 @@ module.exports = class Data1702938429756 { 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, "dripper_weth" numeric NOT NULL, "wrapped_supply" numeric NOT NULL, "trading_volume_usd" numeric NOT NULL, "yield_eth" numeric NOT NULL, "yield_eth7_day" numeric NOT NULL, "yield_eth_all_time" numeric NOT NULL, "yield_usd" numeric NOT NULL, "yield_usd7day" numeric NOT NULL, "yield_usd_all_time" numeric NOT NULL, "fees_eth" numeric NOT NULL, "fees_eth7_day" numeric NOT NULL, "fees_eth_all_time" numeric NOT NULL, "fees_usd" numeric NOT NULL, "fees_usd7_day" numeric NOT NULL, "fees_usd_all_time" numeric NOT NULL, "peg_price" numeric NOT NULL, "market_cap_usd" numeric NOT NULL, "holders_over_threshold" integer NOT NULL, CONSTRAINT "PK_9144a02ab13b1baa818a7d5eae5" PRIMARY KEY ("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, "dripper_weth" numeric NOT NULL, "wrapped_supply" numeric NOT NULL, "trading_volume_usd" numeric NOT NULL, "yield_eth" numeric NOT NULL, "yield_eth7_day" numeric NOT NULL, "yield_eth_all_time" numeric NOT NULL, "yield_usd" numeric NOT NULL, "yield_usd7_day" numeric NOT NULL, "yield_usd_all_time" numeric NOT NULL, "fees_eth" numeric NOT NULL, "fees_eth7_day" numeric NOT NULL, "fees_eth_all_time" numeric NOT NULL, "fees_usd" numeric NOT NULL, "fees_usd7_day" numeric NOT NULL, "fees_usd_all_time" numeric NOT NULL, "peg_price" numeric NOT NULL, "market_cap_usd" numeric NOT NULL, "holders_over_threshold" integer 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 "oeth_reward_token_collected" ("id" character varying NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "strategy" text NOT NULL, "recipient" text NOT NULL, "reward_token" text NOT NULL, "amount" numeric NOT NULL, CONSTRAINT "PK_47098cc5fbc7cb95c2374fa33cd" PRIMARY KEY ("id"))`) diff --git a/schema-oeth.graphql b/schema-oeth.graphql index ea4716f8..30900392 100644 --- a/schema-oeth.graphql +++ b/schema-oeth.graphql @@ -192,7 +192,7 @@ type OETHDailyStat @entity { yieldETHAllTime: BigInt! yieldUSD: BigInt! - yieldUSD7day: BigInt! + yieldUSD7Day: BigInt! yieldUSDAllTime: BigInt! feesETH: BigInt! diff --git a/schema.graphql b/schema.graphql index 2aa81450..c8e33d81 100644 --- a/schema.graphql +++ b/schema.graphql @@ -352,7 +352,7 @@ type OETHDailyStat @entity { yieldETHAllTime: BigInt! yieldUSD: BigInt! - yieldUSD7day: BigInt! + yieldUSD7Day: BigInt! yieldUSDAllTime: BigInt! feesETH: BigInt! diff --git a/src/model/generated/oethDailyStat.model.ts b/src/model/generated/oethDailyStat.model.ts index b262e260..1dd57c89 100644 --- a/src/model/generated/oethDailyStat.model.ts +++ b/src/model/generated/oethDailyStat.model.ts @@ -81,7 +81,7 @@ export class OETHDailyStat { yieldUSD!: bigint @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) - yieldUSD7day!: bigint + yieldUSD7Day!: bigint @Column_("numeric", {transformer: marshal.bigintTransformer, nullable: false}) yieldUSDAllTime!: bigint diff --git a/src/oeth/post-processors/daily-stats/daily-stats.ts b/src/oeth/post-processors/daily-stats/daily-stats.ts index ca2dd311..9cb62063 100644 --- a/src/oeth/post-processors/daily-stats/daily-stats.ts +++ b/src/oeth/post-processors/daily-stats/daily-stats.ts @@ -1,12 +1,18 @@ import { EvmBatchProcessor } from '@subsquid/evm-processor' import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' -import { EntityManager, FindOptionsOrderValue, LessThanOrEqual } from 'typeorm' +import { + EntityManager, + FindOptionsOrderValue, + LessThanOrEqual, + MoreThanOrEqual, +} from 'typeorm' import { ExchangeRate, OETH, OETHAPY, + OETHAddress, OETHBalancerMetaPoolStrategy, OETHCollateralDailyStat, OETHCurveLP, @@ -20,6 +26,7 @@ import { OETHVault, } from '../../../model' import { Context } from '../../../processor' +import { applyCoingeckoData } from '../../../utils/coingecko' dayjs.extend(utc) @@ -64,6 +71,18 @@ export const process = async (ctx: Context) => { } } + if (ctx.isHead) { + const updatedStats = (await applyCoingeckoData(ctx, { + Entity: OETHDailyStat, + coinId: 'origin-ether', + startTimestamp: Date.UTC(2023, 4, 17), + })) as OETHDailyStat[] + const existingIds = dailyStats.map((stat) => stat.id) + dailyStats.push( + ...updatedStats.filter((stat) => existingIds.indexOf(stat.id) < 0), + ) + } + await ctx.store.upsert(dailyStats) await Promise.all([ ctx.store.upsert(dailyCollateralStats), @@ -90,6 +109,7 @@ async function updateDailyStats(ctx: Context, date: Date) { lastRethRate, lastSfrxEthRate, lastWrappedOETHHistory, + holdersOverThreshold, ] = await Promise.all([ ctx.store.findOne(OETHAPY, queryParams), ctx.store.findOne(OETH, queryParams), @@ -114,6 +134,7 @@ async function updateDailyStats(ctx: Context, date: Date) { }, order: { timestamp: 'desc' as FindOptionsOrderValue }, }), + ctx.store.countBy(OETHAddress, { balance: MoreThanOrEqual(10n ** 17n) }), ]) // Do we have any useful data yet? @@ -175,19 +196,27 @@ async function updateDailyStats(ctx: Context, date: Date) { amoSupply: lastCurve?.oethOwned || 0n, dripperWETH: lastDripper?.weth || 0n, wrappedSupply: lastWrappedOETHHistory?.balance || 0n, + tradingVolumeUSD: 0, - yieldUSD: yieldStats[0].total_yield_usd || 0n, - yieldUSDAllTime: yieldStats[2].total_yield_usd || 0n, yieldETH: yieldStats[0].total_yield_eth || 0n, + yieldETH7Day: yieldStats[1].total_yield_eth || 0n, yieldETHAllTime: yieldStats[2].total_yield_eth || 0n, - feesUSD: yieldStats[0].total_fees_usd || 0n, - feesUSD7Day: yieldStats[1].total_fees_usd || 0n, - feesUSDAllTime: yieldStats[2].total_fees_usd || 0n, + + yieldUSD: yieldStats[0].total_yield_usd || 0n, + yieldUSD7Day: yieldStats[1].total_yield_usd || 0n, + yieldUSDAllTime: yieldStats[2].total_yield_usd || 0n, + feesETH: yieldStats[0].total_fees_eth || 0n, feesETH7Day: yieldStats[1].total_fees_eth || 0n, feesETHAllTime: yieldStats[2].total_fees_eth || 0n, + feesUSD: yieldStats[0].total_fees_usd || 0n, + feesUSD7Day: yieldStats[1].total_fees_usd || 0n, + feesUSDAllTime: yieldStats[2].total_fees_usd || 0n, + pegPrice: 0n, + marketCapUSD: 0, + holdersOverThreshold, }) // Collateral totals diff --git a/src/ogv/post-processors/daily-stats.ts b/src/ogv/post-processors/daily-stats.ts index fd281fcc..fd588462 100644 --- a/src/ogv/post-processors/daily-stats.ts +++ b/src/ogv/post-processors/daily-stats.ts @@ -9,9 +9,9 @@ import { } from 'typeorm' import { parseEther } from 'viem' -import { OGV, OGVAddress, OGVDailyStat } from '../../model' +import { OGV, OGVDailyStat } from '../../model' import { Context } from '../../processor' -import { processCoingeckoData } from '../../utils/coingecko' +import { applyCoingeckoData } from '../../utils/coingecko' dayjs.extend(utc) @@ -50,33 +50,16 @@ export const process = async (ctx: Context) => { } if (ctx.isHead) { - const statsWithNoPrice = await ctx.store.findBy(OGVDailyStat, { - priceUSD: 0, - timestamp: LessThanOrEqual(getStartOfDayTimestamp()), - }) - if (statsWithNoPrice.length > 0) { - console.log(`Found ${statsWithNoPrice.length} stats with no price`) - const coingeckoURL = `https://api.coingecko.com/api/v3/coins/origin-dollar-governance/market_chart?vs_currency=usd&days=max&interval=daily&precision=18` - const coingeckoResponse = await fetch(coingeckoURL) - const coingeckoJson = await coingeckoResponse.json() - - if (!coingeckoJson) { - console.log('Could not fetch coingecko data') - } else { - const coingeckData = processCoingeckoData(coingeckoJson) - for (const dayId in coingeckData) { - const stat = statsWithNoPrice.find((s) => s.id === dayId) - const day = coingeckData[dayId] - - if (stat && day.prices) { - stat.tradingVolumeUSD = day.total_volumes || 0 - stat.marketCapUSD = day.market_caps || 0 - stat.priceUSD = day.prices - dailyStats.push(stat) - } - } - } - } + const updatedStats = (await applyCoingeckoData(ctx, { + Entity: OGVDailyStat, + coinId: 'origin-dollar-governance', + // startTimestamp: Date.UTC(2023, 4, 17), + })) as OGVDailyStat[] + + const existingIds = dailyStats.map((stat) => stat.id) + dailyStats.push( + ...updatedStats.filter((stat) => existingIds.indexOf(stat.id) < 0), + ) } await ctx.store.upsert(dailyStats) @@ -90,7 +73,6 @@ async function updateDailyStats(ctx: Context, date: Date) { const [lastOgv] = await Promise.all([ctx.store.findOne(OGV, queryParams)]) - // Do we have any useful data yet? const allEntities = [lastOgv].filter(Boolean) if (!lastOgv) { return null @@ -137,9 +119,3 @@ SELECT COUNT(*) as holders_over_threshold FROM ogv_address WHERE balance > 100000000000000000000 ` - -function getStartOfDayTimestamp(): Date { - const utcDate = new Date() - utcDate.setUTCHours(0, 0, 0, 0) - return utcDate -} diff --git a/src/ousd/post-processors/daily-stats/daily-stats.ts b/src/ousd/post-processors/daily-stats/daily-stats.ts index ffe9a066..c2413b3f 100644 --- a/src/ousd/post-processors/daily-stats/daily-stats.ts +++ b/src/ousd/post-processors/daily-stats/daily-stats.ts @@ -1,12 +1,17 @@ import { EvmBatchProcessor } from '@subsquid/evm-processor' import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' -import { EntityManager, FindOptionsOrderValue, LessThanOrEqual } from 'typeorm' +import { + EntityManager, + FindOptionsOrderValue, + LessThanOrEqual, + MoreThanOrEqual, +} from 'typeorm' import { - ExchangeRate, OUSD, OUSDAPY, + OUSDAddress, OUSDCollateralDailyStat, OUSDDailyStat, OUSDHistory, @@ -16,6 +21,7 @@ import { OUSDVault, } from '../../../model' import { Context } from '../../../processor' +import { applyCoingeckoData } from '../../../utils/coingecko' dayjs.extend(utc) @@ -59,6 +65,18 @@ export const process = async (ctx: Context) => { } } + if (ctx.isHead) { + const updatedStats = (await applyCoingeckoData(ctx, { + Entity: OUSDDailyStat, + coinId: 'origin-dollar', + // startTimestamp: Date.UTC(2023, 4, 17), + })) as OUSDDailyStat[] + const existingIds = dailyStats.map((stat) => stat.id) + dailyStats.push( + ...updatedStats.filter((stat) => existingIds.indexOf(stat.id) < 0), + ) + } + await ctx.store.upsert(dailyStats) await Promise.all([ ctx.store.upsert(dailyCollateralStats), @@ -73,20 +91,27 @@ async function updateDailyStats(ctx: Context, date: Date) { order: { timestamp: 'desc' as FindOptionsOrderValue }, } - const [lastApy, lastOusd, lastVault, lastMorpho, lastWrappedOUSDHistory] = - await Promise.all([ - ctx.store.findOne(OUSDAPY, queryParams), - ctx.store.findOne(OUSD, queryParams), - ctx.store.findOne(OUSDVault, queryParams), - ctx.store.findOne(OUSDMorphoAave, queryParams), - ctx.store.findOne(OUSDHistory, { - where: { - timestamp: LessThanOrEqual(date), - address: { id: '0xd2af830e8cbdfed6cc11bab697bb25496ed6fa62' }, - }, - order: { timestamp: 'desc' as FindOptionsOrderValue }, - }), - ]) + const [ + lastApy, + lastOusd, + lastVault, + lastMorpho, + lastWrappedOUSDHistory, + holdersOverThreshold, + ] = await Promise.all([ + ctx.store.findOne(OUSDAPY, queryParams), + ctx.store.findOne(OUSD, queryParams), + ctx.store.findOne(OUSDVault, queryParams), + ctx.store.findOne(OUSDMorphoAave, queryParams), + ctx.store.findOne(OUSDHistory, { + where: { + timestamp: LessThanOrEqual(date), + address: { id: '0xd2af830e8cbdfed6cc11bab697bb25496ed6fa62' }, + }, + order: { timestamp: 'desc' as FindOptionsOrderValue }, + }), + ctx.store.countBy(OUSDAddress, { balance: MoreThanOrEqual(10n ** 20n) }), // $100 + ]) // Do we have any useful data yet? const allEntities = [lastApy, lastOusd, lastVault, lastMorpho].filter(Boolean) @@ -140,19 +165,27 @@ async function updateDailyStats(ctx: Context, date: Date) { amoSupply: 0n, dripperWETH: 0n, wrappedSupply: lastWrappedOUSDHistory?.balance || 0n, + tradingVolumeUSD: 0, - yieldUSD: yieldStats[0].total_yield_usd || 0n, - yieldUSDAllTime: yieldStats[2].total_yield_usd || 0n, yieldETH: yieldStats[0].total_yield_eth || 0n, + yieldETH7Day: yieldStats[1].total_yield_eth || 0n, yieldETHAllTime: yieldStats[2].total_yield_eth || 0n, - feesUSD: yieldStats[0].total_fees_usd || 0n, - feesUSD7Day: yieldStats[1].total_fees_usd || 0n, - feesUSDAllTime: yieldStats[2].total_fees_usd || 0n, + + yieldUSD: yieldStats[0].total_yield_usd || 0n, + yieldUSD7Day: yieldStats[1].total_yield_usd || 0n, + yieldUSDAllTime: yieldStats[2].total_yield_usd || 0n, + feesETH: yieldStats[0].total_fees_eth || 0n, feesETH7Day: yieldStats[1].total_fees_eth || 0n, feesETHAllTime: yieldStats[2].total_fees_eth || 0n, + feesUSD: yieldStats[0].total_fees_usd || 0n, + feesUSD7Day: yieldStats[1].total_fees_usd || 0n, + feesUSDAllTime: yieldStats[2].total_fees_usd || 0n, + pegPrice: 0n, + marketCapUSD: 0, + holdersOverThreshold, }) const dailyStrategyStats: OUSDStrategyDailyStat[] = [ diff --git a/src/utils/coingecko.ts b/src/utils/coingecko.ts index fc760900..c9792703 100644 --- a/src/utils/coingecko.ts +++ b/src/utils/coingecko.ts @@ -1,3 +1,9 @@ +import { Between, LessThanOrEqual } from 'typeorm' +import { parseEther } from 'viem' + +import { OETHDailyStat, OGVDailyStat, OUSDDailyStat } from '../model' +import { Context } from '../processor' + export interface CoingeckoDataInput { prices: [number, number][] market_caps: [number, number][] @@ -12,7 +18,9 @@ export interface CoingeckoDataOutput { } } -export function processCoingeckoData(data: CoingeckoDataInput): CoingeckoDataOutput { +export function processCoingeckoData( + data: CoingeckoDataInput, +): CoingeckoDataOutput { const result: CoingeckoDataOutput = {} // Helper function to convert timestamp to date string @@ -44,3 +52,70 @@ export function processCoingeckoData(data: CoingeckoDataInput): CoingeckoDataOut return result } + +export function getStartOfDayTimestamp(): Date { + const utcDate = new Date() + utcDate.setUTCHours(0, 0, 0, 0) + return utcDate +} + +export async function applyCoingeckoData( + ctx: Context, + props: { + Entity: any + coinId: string + startTimestamp?: number + }, +) { + const { Entity } = props + + const updatedStats = [] + let whereClause = { + timestamp: LessThanOrEqual(getStartOfDayTimestamp()), + } as any + if (Entity === OGVDailyStat) { + whereClause.priceUSD = 0 + } else { + whereClause.pegPrice = 0n + } + if (props.startTimestamp) { + whereClause.timestamp = Between( + new Date(props.startTimestamp), + getStartOfDayTimestamp(), + ) + } + const statsWithNoPrice = await ctx.store.findBy(Entity as any, whereClause) + + if (statsWithNoPrice.length > 0) { + console.log(`Found ${statsWithNoPrice.length} stats with no price`) + console.log(JSON.stringify(statsWithNoPrice.map((s) => s.id))) + const coingeckoURL = `https://api.coingecko.com/api/v3/coins/${props.coinId}/market_chart?vs_currency=usd&days=max&interval=daily&precision=18` + const coingeckoResponse = await fetch(coingeckoURL) + const coingeckoJson = await coingeckoResponse.json() + + if (!coingeckoJson) { + console.log('Could not fetch coingecko data') + } else { + const coingeckData = processCoingeckoData(coingeckoJson) + for (const dayId in coingeckData) { + const stat = statsWithNoPrice.find((s) => s.id === dayId) as + | OETHDailyStat + | OUSDDailyStat + | OGVDailyStat + const day = coingeckData[dayId] + + if (stat && day.prices) { + stat.tradingVolumeUSD = day.total_volumes || 0 + stat.marketCapUSD = day.market_caps || 0 + if (stat instanceof OGVDailyStat) { + stat.priceUSD = day.prices + } else { + stat.pegPrice = parseEther(String(day.prices)) + } + updatedStats.push(stat) + } + } + } + } + return updatedStats +} From e72c0906ef248161f1eac6db0cb48fe12cd7a51c Mon Sep 17 00:00:00 2001 From: Nick Poulden Date: Wed, 20 Dec 2023 09:33:54 -0700 Subject: [PATCH 3/3] Address feedback --- src/ogv/post-processors/daily-stats.ts | 31 ++++++-------------------- src/utils/coingecko.ts | 10 +++++++-- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/ogv/post-processors/daily-stats.ts b/src/ogv/post-processors/daily-stats.ts index fd588462..57328330 100644 --- a/src/ogv/post-processors/daily-stats.ts +++ b/src/ogv/post-processors/daily-stats.ts @@ -2,14 +2,12 @@ import { EvmBatchProcessor } from '@subsquid/evm-processor' import dayjs from 'dayjs' import utc from 'dayjs/plugin/utc' import { - EntityManager, FindOptionsOrderValue, LessThanOrEqual, - MoreThan, + MoreThanOrEqual, } from 'typeorm' -import { parseEther } from 'viem' -import { OGV, OGVDailyStat } from '../../model' +import { OGV, OGVAddress, OGVDailyStat } from '../../model' import { Context } from '../../processor' import { applyCoingeckoData } from '../../utils/coingecko' @@ -71,25 +69,16 @@ async function updateDailyStats(ctx: Context, date: Date) { order: { timestamp: 'desc' as FindOptionsOrderValue }, } - const [lastOgv] = await Promise.all([ctx.store.findOne(OGV, queryParams)]) + const [lastOgv, holdersOverThreshold] = await Promise.all([ + ctx.store.findOne(OGV, queryParams), + ctx.store.countBy(OGVAddress, { balance: MoreThanOrEqual(10n ** 20n) }), // 100 OGV + ]) const allEntities = [lastOgv].filter(Boolean) if (!lastOgv) { return null } - const entityManager = ( - ctx.store as unknown as { - em: () => EntityManager - } - ).em() - - const holderStats = await entityManager.query< - { - holders_over_threshold: number - }[] - >(holderStatsQuery) - const mostRecentEntity = allEntities.reduce((highest, current) => { if (!highest || !current) return current return current.blockNumber > highest.blockNumber ? current : highest @@ -108,14 +97,8 @@ async function updateDailyStats(ctx: Context, date: Date) { tradingVolumeUSD: 0, marketCapUSD: 0, priceUSD: 0, - holdersOverThreshold: holderStats[0]?.holders_over_threshold || 0, + holdersOverThreshold, }) return { dailyStat } } - -const holderStatsQuery = ` -SELECT COUNT(*) as holders_over_threshold -FROM ogv_address -WHERE balance > 100000000000000000000 -` diff --git a/src/utils/coingecko.ts b/src/utils/coingecko.ts index c9792703..838c727e 100644 --- a/src/utils/coingecko.ts +++ b/src/utils/coingecko.ts @@ -3,6 +3,12 @@ import { parseEther } from 'viem' import { OETHDailyStat, OGVDailyStat, OUSDDailyStat } from '../model' import { Context } from '../processor' +import { EntityClassT } from '../utils/type' + +type DailyStat = + | EntityClassT + | EntityClassT + | EntityClassT export interface CoingeckoDataInput { prices: [number, number][] @@ -62,7 +68,7 @@ export function getStartOfDayTimestamp(): Date { export async function applyCoingeckoData( ctx: Context, props: { - Entity: any + Entity: DailyStat coinId: string startTimestamp?: number }, @@ -88,7 +94,7 @@ export async function applyCoingeckoData( if (statsWithNoPrice.length > 0) { console.log(`Found ${statsWithNoPrice.length} stats with no price`) - console.log(JSON.stringify(statsWithNoPrice.map((s) => s.id))) + // console.log(JSON.stringify(statsWithNoPrice.map((s) => s.id))) const coingeckoURL = `https://api.coingecko.com/api/v3/coins/${props.coinId}/market_chart?vs_currency=usd&days=max&interval=daily&precision=18` const coingeckoResponse = await fetch(coingeckoURL) const coingeckoJson = await coingeckoResponse.json()