From 5e0841039d73efdb1658cfd9a90f74e06be4d8b5 Mon Sep 17 00:00:00 2001 From: Chris Jacobs Date: Fri, 26 Jul 2024 12:38:25 -0700 Subject: [PATCH] create erc20 custom resolver with `erc20StateByDay` --- src/server-extension/erc20.ts | 116 ++++++++++++++++++++++++ src/server-extension/resolvers/index.ts | 1 + 2 files changed, 117 insertions(+) create mode 100644 src/server-extension/erc20.ts diff --git a/src/server-extension/erc20.ts b/src/server-extension/erc20.ts new file mode 100644 index 0000000..6fab9c4 --- /dev/null +++ b/src/server-extension/erc20.ts @@ -0,0 +1,116 @@ +import { GraphQLResolveInfo } from 'graphql' +import { Arg, Field, Info, Int, ObjectType, Query, Resolver } from 'type-graphql' +import { EntityManager } from 'typeorm' + +@ObjectType() +export class ERC20StateByDay { + @Field(() => String, { nullable: false }) + chainId!: number + @Field(() => String, { nullable: false }) + address!: string + @Field(() => Date, { nullable: false }) + day!: Date + @Field(() => BigInt, { nullable: false }) + totalSupply!: bigint + @Field(() => Int, { nullable: false }) + holderCount!: number + + constructor(props: Partial) { + Object.assign(this, props) + } +} + +@Resolver() +export class ERC20Resolver { + constructor(private tx: () => Promise) {} + + @Query(() => [ERC20StateByDay]) + async erc20StateByDay( + @Arg('address', () => String, { nullable: false }) address: string, + @Arg('from', () => String, { nullable: false }) from: string, + @Arg('to', () => String, { nullable: true }) to: string | null, + @Info() info: GraphQLResolveInfo, + ): Promise { + const manager = await this.tx() + const results = await manager.query( + ` + WITH RECURSIVE date_series AS ( + SELECT + DATE_TRUNC('day', $2::timestamp) AS day + UNION + SELECT + day + INTERVAL '1 day' + FROM + date_series + WHERE + day + INTERVAL '1 day' <= COALESCE($3::timestamp, NOW()) + ), + latest_daily_data AS ( + SELECT DISTINCT ON (DATE_TRUNC('day', timestamp)) + DATE_TRUNC('day', timestamp) AS day, + chain_id, + address, + total_supply, + holder_count + FROM + erc20_state + WHERE + address = $1 + AND timestamp >= $2 + AND ($3::timestamp IS NULL OR timestamp <= $3) + ORDER BY + DATE_TRUNC('day', timestamp), timestamp DESC + ), + data_with_gaps AS ( + SELECT + ds.day, + ldd.chain_id, + ldd.address, + ldd.total_supply, + ldd.holder_count + FROM + date_series ds + LEFT JOIN + latest_daily_data ldd ON ds.day = ldd.day + ), + filled_data AS ( + SELECT + day, + chain_id, + address, + total_supply, + holder_count, + COALESCE(total_supply, LAG(total_supply) OVER (PARTITION BY address ORDER BY day)) AS filled_total_supply, + COALESCE(holder_count, LAG(holder_count) OVER (PARTITION BY address ORDER BY day)) AS filled_holder_count + FROM + data_with_gaps + ) + SELECT + chain_id, + address, + day, + filled_total_supply AS total_supply, + filled_holder_count AS holder_count + FROM + filled_data + WHERE + chain_id is not null + ORDER BY + day; + + `, + [address, from, to], + ) + + return results.map( + (row: any) => + new ERC20StateByDay({ + chainId: row.chain_id, + address: row.address, + day: row.day, + totalSupply: BigInt(row.total_supply), + holderCount: row.holder_count, + }), + ) + } +} diff --git a/src/server-extension/resolvers/index.ts b/src/server-extension/resolvers/index.ts index fa6f4ba..3981fde 100644 --- a/src/server-extension/resolvers/index.ts +++ b/src/server-extension/resolvers/index.ts @@ -2,3 +2,4 @@ import 'tsconfig-paths/register' export { OGNStatsResolver } from '../ogn-stats' export { StrategyResolver } from '../strategies' +export { ERC20Resolver } from '../erc20'