diff --git a/prisma/migrations/20241122151152_init/migration.sql b/prisma/migrations/20241122151152_init/migration.sql new file mode 100644 index 0000000..eefd693 --- /dev/null +++ b/prisma/migrations/20241122151152_init/migration.sql @@ -0,0 +1,20 @@ +-- CreateTable +CREATE TABLE "Bundle" ( + "bundleNftId" BIGINT NOT NULL, + "poolNftId" BIGINT NOT NULL, + "lifetime" BIGINT NOT NULL, + "locked" BOOLEAN NOT NULL DEFAULT false, + "closed" BOOLEAN NOT NULL DEFAULT false, + "balance" BIGINT NOT NULL, + "lockedAmount" BIGINT NOT NULL, + "created_blockNumber" INTEGER NOT NULL, + "created_timestamp" BIGINT NOT NULL, + "created_txHash" TEXT NOT NULL, + "created_from" TEXT NOT NULL, + "modified_blockNumber" INTEGER NOT NULL, + "modified_timestamp" BIGINT NOT NULL, + "modified_txHash" TEXT NOT NULL, + "modified_from" TEXT NOT NULL, + + CONSTRAINT "Bundle_pkey" PRIMARY KEY ("bundleNftId") +); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b42884f..1152ee3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -142,4 +142,22 @@ model OracleRequest { modified_timestamp BigInt modified_txHash String modified_from String -} \ No newline at end of file +} + +model Bundle { + bundleNftId BigInt @id + poolNftId BigInt + lifetime BigInt + locked Boolean @default(false) + closed Boolean @default(false) + balance BigInt + lockedAmount BigInt + created_blockNumber Int + created_timestamp BigInt + created_txHash String + created_from String + modified_blockNumber Int + modified_timestamp BigInt + modified_txHash String + modified_from String +} diff --git a/src/bundle_processor.ts b/src/bundle_processor.ts new file mode 100644 index 0000000..9d95462 --- /dev/null +++ b/src/bundle_processor.ts @@ -0,0 +1,143 @@ +import { PrismaClient } from "@prisma/client"; +import { IBundleService__factory } from "./generated/contracts/gif"; +import { logger } from "./logger"; +import { Bundle } from "./types/bundle"; +import { DecodedLogEntry } from "./types/logdata"; + +export default class BundleProcessor { + private prisma: PrismaClient; + + constructor(prisma: PrismaClient) { + this.prisma = prisma; + } + + async persistBundles(bundles: Bundle[]): Promise { + for (const bundle of bundles) { + await this.prisma.bundle.upsert({ + where: { bundleNftId: bundle.bundleNftId }, + update: { + poolNftId: bundle.poolNftId, + lifetime: bundle.lifetime, + locked: bundle.locked, + closed: bundle.closed, + balance: bundle.balance, + lockedAmount: bundle.lockedAmount, + modified_blockNumber: bundle.modified.blockNumber, + modified_timestamp: bundle.modified.timestamp, + modified_txHash: bundle.modified.txHash, + modified_from: bundle.modified.from + }, + create: { + bundleNftId: bundle.bundleNftId, + poolNftId: bundle.poolNftId, + lifetime: bundle.lifetime, + locked: bundle.locked, + closed: bundle.closed, + balance: bundle.balance, + lockedAmount: bundle.lockedAmount, + created_blockNumber: bundle.created.blockNumber, + created_timestamp: bundle.created.timestamp, + created_txHash: bundle.created.txHash, + created_from: bundle.created.from, + modified_blockNumber: bundle.modified.blockNumber, + modified_timestamp: bundle.modified.timestamp, + modified_txHash: bundle.modified.txHash, + modified_from: bundle.modified.from + } + }); + } + } + + async processBundleCreatedEvent(event: DecodedLogEntry, bundles: Map): Promise> { + if (event.event_name !== 'LogBundleServiceBundleCreated') { + throw new Error(`Invalid event type ${event.event_name}`); + } + + logger.info(`Processing bundle created event ${event.tx_hash} - ${event.event_name} - ${event.data}`); + const data = this.decodeBundleServiceEvent(event); + if (data === null || data === undefined) { + logger.error(`Failed to decode event ${event.tx_hash} - ${event.event_name} - ${event.data}`); + return bundles; + } + if (data.name !== 'LogBundleServiceBundleCreated') { + throw new Error(`Invalid event name ${data.name}`); + } + + const bundleNftId = data.args[0] as BigInt; + const poolNftId = data.args[1] as BigInt; + const lifetime = data.args[2] as BigInt; + + // TODO: validate poolNftId + + const bundle = { + bundleNftId, + poolNftId, + lifetime, + locked: false, + closed: false, + balance: BigInt(0), + lockedAmount: BigInt(0), + created: { + blockNumber: event.block_number, + timestamp:BigInt(new Date(event.block_time).getTime()), + txHash: event.tx_hash, + from: event.tx_from + }, + modified: { + blockNumber: event.block_number, + timestamp:BigInt(new Date(event.block_time).getTime()), + txHash: event.tx_hash, + from: event.tx_from + } + } as Bundle; + bundles.set(bundleNftId, bundle); + return bundles; + } + + async processBundleClosedEvent(event: DecodedLogEntry, bundles: Map): Promise> { + if (event.event_name !== 'LogBundleServiceBundleClosed') { + throw new Error(`Invalid event type ${event.event_name}`); + } + + logger.info(`Processing bundle closed event ${event.tx_hash} - ${event.event_name} - ${event.data}`); + const data = this.decodeBundleServiceEvent(event); + if (data === null || data === undefined) { + logger.error(`Failed to decode event ${event.tx_hash} - ${event.event_name} - ${event.data}`); + return bundles; + } + if (data.name !== 'LogBundleServiceBundleClosed') { + throw new Error(`Invalid event name ${data.name}`); + } + + const bundleNftId = data.args[0] as BigInt; + const bundle = bundles.get(bundleNftId); + if (bundle === undefined) { + throw new Error(`Bundle not found ${bundleNftId}`); + } + bundle.closed = true; + bundle.modified = { + blockNumber: event.block_number, + timestamp:BigInt(new Date(event.block_time).getTime()), + txHash: event.tx_hash, + from: event.tx_from + }; + return bundles; + } + + decodeBundleServiceEvent(event: DecodedLogEntry) { + const topic0 = event.topic0; + let topic1 = event.topic1; + if (topic1 === null || topic1 === undefined || topic1 === '') { + topic1 = '0x'; + } + let topic2 = event.topic2; + if (topic2 === null || topic2 === undefined || topic2 === '') { + topic2 = '0x'; + } + let topic3 = event.topic3; + if (topic3 === null || topic3 === undefined || topic3 === '') { + topic3 = '0x'; + } + return IBundleService__factory.createInterface().parseLog({ topics: [topic0, topic1, topic2, topic3], data: event.data }); + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 72b99a0..a528aa3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -20,6 +20,8 @@ import { Claim } from './types/claim'; import { Payout } from './types/payout'; import { OracleRequest } from './types/oracle_request'; import OracleProcessor from './oracle_processor'; +import BundleProcessor from './bundle_processor'; +import { Bundle } from './types/bundle'; dotenv.config(); @@ -37,6 +39,7 @@ class Main { private componentProcessor: ComponentProcessor; private riskProcessor: RiskProcessor; private oracleProcessor: OracleProcessor; + private bundleProcessor: BundleProcessor; constructor(prisma: PrismaClient) { this.dune = new DuneApi(); @@ -46,11 +49,15 @@ class Main { this.policyProcessor = new PolicyProcessor(prisma); this.riskProcessor = new RiskProcessor(prisma); this.oracleProcessor = new OracleProcessor(prisma); + this.bundleProcessor = new BundleProcessor(prisma); } public async main(): Promise { const gifEvents = await this.dune.getLatestResult(DUNE_QUERY_ID_GIF_EVENTS, 0); - const { nfts, instances, policies, components, risks, claims, payouts, requests } = await this.parseGifEvents(gifEvents); + const { + nfts, instances, policies, components, + risks, claims, payouts, requests, bundles + } = await this.parseGifEvents(gifEvents); await this.nftProcessor.persistNfts(Array.from(nfts.values())); await this.instanceProcessor.persistInstances(Array.from(instances.values())); @@ -60,6 +67,7 @@ class Main { await this.policyProcessor.persistClaims(Array.from(claims.values())); await this.policyProcessor.persistPayouts(Array.from(payouts.values())); await this.oracleProcessor.persistOracleRequests(Array.from(requests.values())); + await this.bundleProcessor.persistBundles(Array.from(bundles.values())); for (const nft of nfts.values()) { logger.info(`NFT: ${nft.nftId} - ${ObjectType[nft.objectType]} - ${nft.objectAddress} - ${nft.owner}`); @@ -84,6 +92,10 @@ class Main { for (const payout of payouts.values()) { logger.info(`Payout: ${payout.policyNftId} ${payout.payoutId} - ${payout.payoutAmount}`); } + + for (const bundle of bundles.values()) { + logger.info(`Bundle: ${bundle.bundleNftId} - ${bundle.balance} - ${bundle.lockedAmount}`); + } } async parseGifEvents(gifEvents: Array) @@ -95,7 +107,8 @@ class Main { risks: Map, claims: Map, payouts: Map, - requests: Map + requests: Map, + bundles: Map }> { const nfts = new Map(); @@ -106,6 +119,7 @@ class Main { const claims = new Map(); const payouts = new Map(); const requests = new Map(); + const bundles = new Map(); for (const event of gifEvents) { // logger.debug(`Processing gif event ${event.tx_hash} - ${event.block_number} - ${event.event_name}`); @@ -200,12 +214,44 @@ class Main { await this.oracleProcessor.processOracleRequestCancelledEvent(event, requests); break; + case 'LogBundleServiceBundleCreated': + await this.bundleProcessor.processBundleCreatedEvent(event, bundles); + break; + case 'LogBundleServiceBundleClosed': + await this.bundleProcessor.processBundleClosedEvent(event, bundles); + break; + + // event LogBundleServiceBundleLocked(NftId bundleNftId); + // event LogBundleServiceBundleUnlocked(NftId bundleNftId); + // event LogBundleServiceBundleExtended(NftId bundleNftId, Seconds lifetimeExtension, Timestamp extendedExpiredAt); + // event LogBundleServiceBundleFeeUpdated(NftId bundleNftId, Amount fixedFee, UFixed fractionalFee); + // event LogBundleServiceCollateralLocked(NftId bundleNftId, NftId policyNftId, Amount collateralAmount); + // event LogBundleServiceCollateralReleased(NftId bundleNftId, NftId policyNftId, Amount collateralAmount); + // event LogBundleServiceBundleStaked(NftId bundleNftId, Amount amount); + // event LogBundleServiceBundleUnstaked(NftId bundleNftId, Amount amount); + + // event LogPoolServiceMaxBalanceAmountUpdated(NftId poolNftId, Amount previousMaxCapitalAmount, Amount currentMaxCapitalAmount); + // event LogPoolServiceWalletFunded(NftId poolNftId, address poolOwner, Amount amount); + // event LogPoolServiceWalletDefunded(NftId poolNftId, address poolOwner, Amount amount); + // event LogPoolServiceBundleCreated(NftId instanceNftId, NftId poolNftId, NftId bundleNftId); + // event LogPoolServiceBundleClosed(NftId instanceNftId, NftId poolNftId, NftId bundleNftId); + // event LogPoolServiceBundleStaked(NftId instanceNftId, NftId poolNftId, NftId bundleNftId, Amount amount, Amount netAmount); + // event LogPoolServiceBundleUnstaked(NftId instanceNftId, NftId poolNftId, NftId bundleNftId, Amount amount, Amount netAmount); + // event LogPoolServiceFeesWithdrawn(NftId bundleNftId, address recipient, address tokenAddress, Amount amount); + // event LogPoolServiceProcessFundedClaim(NftId policyNftId, ClaimId claimId, Amount availableAmount); + // event LogPoolServiceApplicationVerified(NftId poolNftId, NftId bundleNftId, NftId applicationNftId, Amount totalCollateralAmount); + // event LogPoolServiceCollateralLocked(NftId poolNftId, NftId bundleNftId, NftId applicationNftId, Amount totalCollateralAmount, Amount lockedCollateralAmount); + // event LogPoolServiceCollateralReleased(NftId bundleNftId, NftId policyNftId, Amount remainingCollateralAmount); + // event LogPoolServiceSaleProcessed(NftId poolNftId, NftId bundleNftId, Amount bundleNetAmount, Amount bundleFeeAmount, Amount poolFeeAmount); + // event LogPoolServicePayoutProcessed(NftId poolNftId, NftId bundleNftId, NftId policyNftId, PayoutId payoutId, Amount netPayoutAmount, Amount processingFeeAmount, address payoutBeneficiary); + + default: logger.info('Unhandeled event: ' + event.event_name); } } - return { nfts, instances, policies, components, risks, claims, payouts, requests }; + return { nfts, instances, policies, components, risks, claims, payouts, requests, bundles }; } } diff --git a/src/types/bundle.ts b/src/types/bundle.ts new file mode 100644 index 0000000..97687fc --- /dev/null +++ b/src/types/bundle.ts @@ -0,0 +1,23 @@ + +export interface Bundle { + bundleNftId: bigint; + poolNftId: bigint; + lifetime: bigint; + locked: boolean; + closed: boolean; + balance: bigint; + lockedAmount: bigint; + created: { + blockNumber: number; + timestamp: bigint; + txHash: string; + from: string; + } + modified: { + blockNumber: number; + timestamp: bigint; + txHash: string; + from: string; + } +} + diff --git a/src/types/oracle_request.ts b/src/types/oracle_request.ts index aca8ba2..110d12a 100644 --- a/src/types/oracle_request.ts +++ b/src/types/oracle_request.ts @@ -1,4 +1,3 @@ -import { ObjectType } from "./objecttype"; export interface OracleRequest { oracleNftId: bigint;