Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,10 @@ Squid tools assume a certain [project layout](https://docs.subsquid.io/basics/sq
- Run `sqd deploy .`
- Make branch for new version (eg v9) and push to origin
- Switch back to main branch

## Reset cloud dev version (v999)

Useful if you made a schema change or need to reload data.

- Check `squid.yaml` to make sure you're on v999
- `sqd deploy . --update --hard-reset`

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,19 @@ type OETHWithdrawalRequest @entity {
claimed: Boolean!
txHash: String! @index
}
type OGNDailyStat @entity {
id: ID!
blockNumber: Int! @index
timestamp: DateTime! @index

totalSupply: BigInt!
totalSupplyUSD: Float!
totalStaked: BigInt!
tradingVolumeUSD: Float!
marketCapUSD: Float!
priceUSD: Float!
holdersOverThreshold: Int!
}
type OGV @entity {
id: ID!
timestamp: DateTime! @index
Expand Down
13 changes: 13 additions & 0 deletions schema/ogn.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
type OGNDailyStat @entity {
id: ID!
blockNumber: Int! @index
timestamp: DateTime! @index

totalSupply: BigInt!
totalSupplyUSD: Float!
totalStaked: BigInt!
tradingVolumeUSD: Float!
marketCapUSD: Float!
priceUSD: Float!
holdersOverThreshold: Int!
}
5 changes: 3 additions & 2 deletions src/main-mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import {
XOGN_ADDRESS,
} from '@utils/addresses'

import * as dailyStats from './mainnet/post-processors/daily-stats'
import * as curve from './mainnet/processors/curve'
import * as legacyStaking from './mainnet/processors/legacy-staking'
import { erc20s } from './mainnet/processors/erc20s'
import * as legacyStaking from './mainnet/processors/legacy-staking'
import * as nativeStaking from './mainnet/processors/native-staking'
import * as validate from './mainnet/validators/validate-mainnet'

Expand All @@ -38,7 +39,7 @@ export const processor = {
}),
createFRRSProcessor({ from: 19917521, address: OGN_REWARDS_SOURCE_ADDRESS }),
],
postProcessors: [exchangeRates, processStatus('mainnet')],
postProcessors: [exchangeRates, dailyStats, processStatus('mainnet')],
validators: [validate],
}
export default processor
Expand Down
110 changes: 110 additions & 0 deletions src/mainnet/post-processors/daily-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import { FindOptionsOrderValue, LessThanOrEqual, MoreThanOrEqual } from 'typeorm'

import { ERC20Balance, ERC20Holder, ERC20State, OGNDailyStat } from '@model'
import { Context } from '@processor'
import { EvmBatchProcessor } from '@subsquid/evm-processor'
import { applyCoingeckoData } 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 ognDailyStats = [] as OGNDailyStat[]

for (const date of dates) {
const dailyOGNStatInserts = await updateOGNDailyStats(ctx, date)
if (dailyOGNStatInserts) {
ognDailyStats.push(dailyOGNStatInserts.dailyStat)
}
}

if (ctx.isHead) {
const updatedOGNStats = (await applyCoingeckoData(ctx, {
Entity: OGNDailyStat,
coinId: 'origin-protocol',
// startTimestamp: Date.UTC(2023, 4, 17),
})) as OGNDailyStat[]

const existingOGNIds = ognDailyStats.map((stat) => stat.id)
ognDailyStats.push(...updatedOGNStats.filter((stat) => existingOGNIds.indexOf(stat.id) < 0))
}

await ctx.store.upsert(ognDailyStats)
}

async function updateOGNDailyStats(ctx: Context, date: Date) {
const queryParams = {
where: { timestamp: LessThanOrEqual(date) },
order: { timestamp: 'desc' as FindOptionsOrderValue },
}

const [stakedBalance, holdersOverThreshold, totalSupply] = await Promise.all([
ctx.store.findOne(ERC20Balance, {
where: {
timestamp: LessThanOrEqual(date),
address: '0x8207c1ffc5b6804f6024322ccf34f29c3541ae26',
account: '0x63898b3b6ef3d39332082178656e9862bee45c57',
},
order: { timestamp: 'desc' as FindOptionsOrderValue },
}),
ctx.store.countBy(ERC20Holder, {
balance: MoreThanOrEqual(10n ** 20n),
address: '0x8207c1ffc5b6804f6024322ccf34f29c3541ae26',
}), // 100 OGN
ctx.store.findOne(ERC20State, {
where: {
timestamp: LessThanOrEqual(date),
address: '0x8207c1ffc5b6804f6024322ccf34f29c3541ae26',
},
order: { timestamp: 'desc' as FindOptionsOrderValue },
}),
])

const allEntities = [totalSupply].filter(Boolean)
if (!totalSupply) {
return null
}

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 OGNDailyStat({
id,
blockNumber: mostRecentEntity?.blockNumber,
timestamp: mostRecentEntity?.timestamp,

totalSupply: totalSupply?.totalSupply || 0n,
totalStaked: stakedBalance?.balance || 0n,
totalSupplyUSD: 0,
tradingVolumeUSD: 0,
marketCapUSD: 0,
priceUSD: 0,
holdersOverThreshold,
})

return { dailyStat }
}
1 change: 1 addition & 0 deletions src/model/generated/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export * from "./oethStrategyHoldingDailyStat.model"
export * from "./oethCollateralDailyStat.model"
export * from "./oethRewardTokenCollected.model"
export * from "./oethWithdrawalRequest.model"
export * from "./ognDailyStat.model"
export * from "./ogv.model"
export * from "./ogvAddress.model"
export * from "./ogvLockupTxLog.model"
Expand Down
40 changes: 40 additions & 0 deletions src/model/generated/ognDailyStat.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {Entity as Entity_, Column as Column_, PrimaryColumn as PrimaryColumn_, IntColumn as IntColumn_, Index as Index_, DateTimeColumn as DateTimeColumn_, BigIntColumn as BigIntColumn_, FloatColumn as FloatColumn_} from "@subsquid/typeorm-store"

@Entity_()
export class OGNDailyStat {
constructor(props?: Partial<OGNDailyStat>) {
Object.assign(this, props)
}

@PrimaryColumn_()
id!: string

@Index_()
@IntColumn_({nullable: false})
blockNumber!: number

@Index_()
@DateTimeColumn_({nullable: false})
timestamp!: Date

@BigIntColumn_({nullable: false})
totalSupply!: bigint

@FloatColumn_({nullable: false})
totalSupplyUSD!: number

@BigIntColumn_({nullable: false})
totalStaked!: bigint

@FloatColumn_({nullable: false})
tradingVolumeUSD!: number

@FloatColumn_({nullable: false})
marketCapUSD!: number

@FloatColumn_({nullable: false})
priceUSD!: number

@IntColumn_({nullable: false})
holdersOverThreshold!: number
}
22 changes: 16 additions & 6 deletions src/utils/coingecko.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Between, LessThanOrEqual } from 'typeorm'
import { parseEther } from 'viem'

import { OETHDailyStat, OGVDailyStat, OUSDDailyStat } from '@model'
import { OETHDailyStat, OGNDailyStat, OGVDailyStat, OUSDDailyStat } from '@model'
import { Context } from '@processor'
import { queryClient } from '@utils/queryClient'
import { EntityClassT } from '@utils/type'

type DailyStat = EntityClassT<OETHDailyStat> | EntityClassT<OGVDailyStat> | EntityClassT<OUSDDailyStat>
type DailyStat =
| EntityClassT<OETHDailyStat>
| EntityClassT<OGVDailyStat>
| EntityClassT<OGNDailyStat>
| EntityClassT<OUSDDailyStat>

export interface CoingeckoDataInput {
prices: [number, number][]
Expand Down Expand Up @@ -81,7 +85,7 @@ export async function applyCoingeckoData(
let whereClause = {
timestamp: LessThanOrEqual(getStartOfDayTimestamp()),
} as any
if (Entity === OGVDailyStat) {
if (Entity === OGVDailyStat || Entity === OGNDailyStat) {
whereClause.priceUSD = 0
} else {
whereClause.pegPrice = 0n
Expand All @@ -104,7 +108,9 @@ export async function applyCoingeckoData(
if (response.status === 429) {
throw new Error('Coingecko rate limited')
}
return await response.json()
const result = await response.json()
console.log(`Found ${result.prices.length} prices`)
return result
},

staleTime: 600_000, // 10 minutes
Expand All @@ -115,13 +121,17 @@ export async function applyCoingeckoData(
} else {
const coingeckData = processCoingeckoData(coingeckoJson)
for (const dayId in coingeckData) {
const stat = statsWithNoPrice.find((s) => s.id === dayId) as OETHDailyStat | OUSDDailyStat | OGVDailyStat
const stat = statsWithNoPrice.find((s) => s.id === dayId) as
| OETHDailyStat
| OUSDDailyStat
| OGVDailyStat
| OGNDailyStat
const day = coingeckData[dayId]

if (stat && day.prices) {
stat.tradingVolumeUSD = day.total_volumes || 0
stat.marketCapUSD = day.market_caps || 0
if (stat instanceof OGVDailyStat) {
if (stat instanceof OGVDailyStat || stat instanceof OGNDailyStat) {
stat.priceUSD = day.prices
} else {
stat.pegPrice = parseEther(String(day.prices))
Expand Down