diff --git a/db/migrations/1736192405171-Data.js b/db/migrations/1736203023690-Data.js similarity index 99% rename from db/migrations/1736192405171-Data.js rename to db/migrations/1736203023690-Data.js index 1e3653c2..0c5fa375 100644 --- a/db/migrations/1736192405171-Data.js +++ b/db/migrations/1736203023690-Data.js @@ -1,5 +1,5 @@ -module.exports = class Data1736192405171 { - name = 'Data1736192405171' +module.exports = class Data1736203023690 { + name = 'Data1736203023690' async up(db) { await db.query(`CREATE TABLE "exchange_rate" ("id" character varying NOT NULL, "chain_id" integer 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"))`) @@ -99,7 +99,7 @@ module.exports = class Data1736192405171 { await db.query(`CREATE INDEX "IDX_1ada02a88b8355495e0917b49b" ON "transaction_details" ("block_number") `) await db.query(`CREATE INDEX "IDX_81a859fb3a14a34f194e1d8606" ON "transaction_details" ("from") `) await db.query(`CREATE INDEX "IDX_ae3e0f0414a1deb6e4840e2e44" ON "transaction_details" ("to") `) - await db.query(`CREATE TABLE "o_token" ("id" character varying NOT NULL, "chain_id" integer NOT NULL, "otoken" text 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, "holder_count" integer NOT NULL, CONSTRAINT "PK_4450ef96d5e51ef55bc8ea0b53e" PRIMARY KEY ("id"))`) + await db.query(`CREATE TABLE "o_token" ("id" character varying NOT NULL, "chain_id" integer NOT NULL, "otoken" text NOT NULL, "timestamp" TIMESTAMP WITH TIME ZONE NOT NULL, "block_number" integer NOT NULL, "unallocated_supply" numeric NOT NULL, "total_supply" numeric NOT NULL, "rebasing_supply" numeric NOT NULL, "non_rebasing_supply" numeric NOT NULL, "holder_count" integer NOT NULL, CONSTRAINT "PK_4450ef96d5e51ef55bc8ea0b53e" PRIMARY KEY ("id"))`) await db.query(`CREATE INDEX "IDX_1da06ba7d2c0a02d01d1b7a6c0" ON "o_token" ("chain_id") `) await db.query(`CREATE INDEX "IDX_0851d95d178f682d279a04ebf9" ON "o_token" ("otoken") `) await db.query(`CREATE INDEX "IDX_7889d6ce061da066df89309e51" ON "o_token" ("timestamp") `) diff --git a/schema.graphql b/schema.graphql index 2e69eedb..f1f7ef55 100644 --- a/schema.graphql +++ b/schema.graphql @@ -345,6 +345,7 @@ type OToken @entity { otoken: String! @index timestamp: DateTime! @index blockNumber: Int! @index + unallocatedSupply: BigInt! totalSupply: BigInt! rebasingSupply: BigInt! nonRebasingSupply: BigInt! diff --git a/src/base/super-oeth-b.ts b/src/base/super-oeth-b.ts index 8347b560..5099aea6 100644 --- a/src/base/super-oeth-b.ts +++ b/src/base/super-oeth-b.ts @@ -26,6 +26,7 @@ const otokenProcessor = createOTokenProcessor({ yieldSent: true, }, otokenVaultAddress: baseAddresses.superOETHb.vault, + redemptionAsset: { asset: baseAddresses.tokens.WETH, symbol: 'WETH' }, oTokenAssets: [ { asset: baseAddresses.tokens.superOETHb, symbol: 'superOETHb' }, { diff --git a/src/mainnet/processors/erc20s.ts b/src/mainnet/processors/erc20s.ts index 5eb97d81..64b720e6 100644 --- a/src/mainnet/processors/erc20s.ts +++ b/src/mainnet/processors/erc20s.ts @@ -1,6 +1,13 @@ import { createERC20EventTracker } from '@templates/erc20/erc20-event' import { createERC20PollingTracker } from '@templates/erc20/erc20-polling' -import { OETH_DRIPPER_ADDRESS, OETH_VAULT_ADDRESS, WOETH_ADDRESS, oethStrategyArray, tokens } from '@utils/addresses' +import { + OETH_DRIPPER_ADDRESS, + OETH_NATIVE_STRATEGIES, + OETH_VAULT_ADDRESS, + WOETH_ADDRESS, + oethStrategyArray, + tokens, +} from '@utils/addresses' import { TokenSymbol } from '@utils/symbols' // TODO: Would be nice if interested parties could register their desires here from other parts of the code, @@ -45,6 +52,7 @@ const tracks: Record[0]> = ...oethStrategyArray, OETH_VAULT_ADDRESS, OETH_DRIPPER_ADDRESS, + ...OETH_NATIVE_STRATEGIES.map((s) => s.address), // '0xa4e0faA58465A2D369aa21B3e42d43374c6F9613', // Uniswap wstETH/WETH // '0x109830a1aaad605bbf02a9dfa7b0b92ec2fb7daa', // Uniswap rETH/WETH ], diff --git a/src/mainnet/processors/native-staking.ts b/src/mainnet/processors/native-staking.ts index 98c1990f..c41e0635 100644 --- a/src/mainnet/processors/native-staking.ts +++ b/src/mainnet/processors/native-staking.ts @@ -93,6 +93,7 @@ export const process = async (ctx: Context) => { } } } + await ctx.store.upsert([...result.pubkeys.values()]) await ctx.store.insert([...result.deposits.values()]) } diff --git a/src/model/generated/oToken.model.ts b/src/model/generated/oToken.model.ts index ef6c3d9f..ab316063 100644 --- a/src/model/generated/oToken.model.ts +++ b/src/model/generated/oToken.model.ts @@ -25,6 +25,9 @@ export class OToken { @IntColumn_({nullable: false}) blockNumber!: number + @BigIntColumn_({nullable: false}) + unallocatedSupply!: bigint + @BigIntColumn_({nullable: false}) totalSupply!: bigint diff --git a/src/oeth/processors/oeth.ts b/src/oeth/processors/oeth.ts index 47a5bb49..e218f5e9 100644 --- a/src/oeth/processors/oeth.ts +++ b/src/oeth/processors/oeth.ts @@ -42,6 +42,7 @@ const otokenProcessor = createOTokenProcessor({ from: 17067707, }, otokenVaultAddress: OETH_VAULT_ADDRESS, + redemptionAsset: { asset: WETH_ADDRESS, symbol: 'WETH' }, oTokenAssets: [ { asset: ETH_ADDRESS, symbol: 'ETH' }, { asset: WETH_ADDRESS, symbol: 'WETH' }, diff --git a/src/ousd/processors/strategies/strategies.ts b/src/ousd/processors/strategies/strategies.ts index e2e9f257..02b219c7 100644 --- a/src/ousd/processors/strategies/strategies.ts +++ b/src/ousd/processors/strategies/strategies.ts @@ -1,9 +1,12 @@ import { Context } from '@processor' +import { mainnetCurrencies } from '@shared/post-processors/exchange-rates/mainnetCurrencies' import { EvmBatchProcessor } from '@subsquid/evm-processor' import { IStrategyData, createStrategyProcessor, createStrategySetup } from '@templates/strategy' import { createStrategyRewardProcessor, createStrategyRewardSetup } from '@templates/strategy-rewards' +import * as mainnetAddresses from '@utils/addresses' import { aaveStrategy } from './aave-strategy' +import { USDC, USDT } from './const' import { convexMetaStrategy } from './convex-meta-strategy' import { fluxStrategy } from './flux-strategy' import { makerDsrStrategy } from './maker-dsr-strategy' @@ -19,7 +22,30 @@ export const ousdStrategies: readonly IStrategyData[] = [ fluxStrategy, makerDsrStrategy, metamorphoStrategy, - + { + chainId: 1, + from: 21425796, + oTokenAddress: mainnetAddresses.OUSD_ADDRESS, + kind: 'Generic', + name: 'OUSD Gauntlet Prime USDC', + contractName: 'Generalized4626Strategy', + address: mainnetAddresses.strategies.ousd.GauntletPrimeUSDCStrategy, + base: { address: mainnetCurrencies.USD, decimals: 18 }, + assets: [USDC], + earnings: { passiveByDepositWithdrawal: true, rewardTokenCollected: true }, + }, + { + chainId: 1, + from: 21425837, + oTokenAddress: mainnetAddresses.OUSD_ADDRESS, + kind: 'Generic', + name: 'OUSD Gauntlet Prime USDT', + contractName: 'Generalized4626Strategy', + address: mainnetAddresses.strategies.ousd.GauntletPrimeUSDTStrategy, + base: { address: mainnetCurrencies.USD, decimals: 18 }, + assets: [USDT], + earnings: { passiveByDepositWithdrawal: true, rewardTokenCollected: true }, + }, // Deprecated // { // from: 13369299, diff --git a/src/templates/otoken/otoken.graphql b/src/templates/otoken/otoken.graphql index 6f543c0f..37b61cb0 100644 --- a/src/templates/otoken/otoken.graphql +++ b/src/templates/otoken/otoken.graphql @@ -4,6 +4,7 @@ type OToken @entity { otoken: String! @index timestamp: DateTime! @index blockNumber: Int! @index + unallocatedSupply: BigInt! totalSupply: BigInt! rebasingSupply: BigInt! nonRebasingSupply: BigInt! diff --git a/src/templates/otoken/otoken.ts b/src/templates/otoken/otoken.ts index 90b6a15c..d5cd98d1 100644 --- a/src/templates/otoken/otoken.ts +++ b/src/templates/otoken/otoken.ts @@ -4,10 +4,10 @@ import { Between, LessThanOrEqual } from 'typeorm' import { formatUnits } from 'viem' import * as erc20 from '@abi/erc20' +import * as otokenVault from '@abi/oeth-vault' import * as otoken from '@abi/otoken' import * as otokenHarvester from '@abi/otoken-base-harvester' import * as otokenDripper from '@abi/otoken-dripper' -import * as otokenVault from '@abi/otoken-vault' import * as wotoken from '@abi/woeth' import { ERC20, @@ -69,6 +69,7 @@ export const createOTokenProcessor = (params: { yieldSent: boolean } otokenVaultAddress: string + redemptionAsset?: { asset: CurrencyAddress; symbol: CurrencySymbol } oTokenAssets: { asset: CurrencyAddress; symbol: CurrencySymbol }[] getAmoSupply: (ctx: Context, height: number) => Promise upgrades?: { @@ -93,6 +94,15 @@ export const createOTokenProcessor = (params: { topic0: [otoken.events.YieldUndelegated.topic], range: { from: params.from }, }) + const withdrawalRelatedFilter = logFilter({ + address: [params.otokenVaultAddress], + topic0: [ + otokenVault.events.WithdrawalRequested.topic, + otokenVault.events.WithdrawalClaimable.topic, + otokenVault.events.WithdrawalClaimed.topic, + ], + range: { from: params.from }, + }) const rebaseEventTopics = { [otoken.events.AccountRebasingEnabled.topic]: otoken.events.AccountRebasingEnabled, @@ -137,6 +147,7 @@ export const createOTokenProcessor = (params: { if (harvesterYieldSentFilter) { processor.addLog(harvesterYieldSentFilter.value) } + processor.addLog(withdrawalRelatedFilter.value) } const initialize = async (ctx: Context) => { @@ -738,6 +749,20 @@ export const createOTokenProcessor = (params: { time('processHarvesterYieldSent') } + // Update the unallocatedSupply of OToken. + const processWithdrawalRelated = async (block: Block, log: Log) => { + if (!params.redemptionAsset) return + const vault = new otokenVault.Contract(ctx, block.header, params.otokenVaultAddress) + const redeemingAsset = new erc20.Contract(ctx, block.header, params.redemptionAsset.asset) + const [otokenObject, withdrawalQueueMetadata, redeemingAssetBalance] = await Promise.all([ + getOTokenObject(block), + vault.withdrawalQueueMetadata(), + redeemingAsset.balanceOf(params.otokenVaultAddress), + ]) + const claimableSupply = withdrawalQueueMetadata.queued - withdrawalQueueMetadata.claimed + otokenObject.unallocatedSupply = redeemingAssetBalance - claimableSupply + } + const getOTokenObject = async (block: Block) => { const timestamp = new Date(block.header.timestamp) const otokenId = `${ctx.chain.id}-${params.otokenAddress}-${timestamp.toISOString()}` @@ -761,6 +786,7 @@ export const createOTokenProcessor = (params: { otoken: params.otokenAddress, timestamp: new Date(block.header.timestamp), blockNumber: block.header.height, + unallocatedSupply: latest?.unallocatedSupply ?? 0n, totalSupply: latest?.totalSupply ?? 0n, rebasingSupply: latest?.rebasingSupply ?? 0n, nonRebasingSupply: latest?.nonRebasingSupply ?? 0n, @@ -900,6 +926,9 @@ export const createOTokenProcessor = (params: { if (yieldUndelegatedFilter.matches(log)) { await processYieldUndelegated(block, log) } + if (withdrawalRelatedFilter.matches(log)) { + await processWithdrawalRelated(block, log) + } } } diff --git a/src/utils/addresses.ts b/src/utils/addresses.ts index 1a9dc870..b81ce241 100644 --- a/src/utils/addresses.ts +++ b/src/utils/addresses.ts @@ -139,12 +139,14 @@ export const strategies = { MorphoCompoundStrategy: '0x5a4eee58744d1430876d5ca93cab5ccb763c037d', MorphoAaveStrategy: '0x79f2188ef9350a1dc11a062cca0abe90684b0197', FluxStrategy: '0x76bf500b6305dc4ea851384d3d5502f1c7a0ed44', - Generalized4626Strategy: '0x6b69b755c629590ed59618a2712d8a2957ca98fc', + MakerDsrStrategy: '0x6b69b755c629590ed59618a2712d8a2957ca98fc', CompoundStrategy: '0x9c459eeb3fa179a40329b81c1635525e9a0ef094', // Deprecated ConvexStrategy: '0xea2ef2e2e5a749d4a66b41db9ad85a38aa264cb3', // Deprecated LUSDMetaStrategy: '0x7A192DD9Cc4Ea9bdEdeC9992df74F1DA55e60a19', // Deprecated MetaMorphoStrategy: '0x603cdeaec82a60e3c4a10da6ab546459e5f64fa0', MakerStrategy: '0x6b69b755c629590ed59618a2712d8a2957ca98fc', + GauntletPrimeUSDCStrategy: '0x2b8f37893ee713a4e9ff0ceb79f27539f20a32a1', + GauntletPrimeUSDTStrategy: '0xe3ae7c80a1b02ccd3fb0227773553aeb14e32f26', }, } as const