From d20d7c62e21b811bdf545cc21d3c9886acb46fd6 Mon Sep 17 00:00:00 2001 From: Marc Doerflinger Date: Fri, 15 Nov 2024 13:49:52 +0000 Subject: [PATCH] refactor to events query --- src/constants.ts | 1 + src/dune.ts | 9 +++- src/main.ts | 66 +++++++++++++++++------ src/nft_processor.ts | 117 ++++++++++++++++++++++++---------------- src/types/objecttype.ts | 49 ++--------------- 5 files changed, 133 insertions(+), 109 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index cdac2ce..213250c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -9,3 +9,4 @@ export const DUNE_QUERY_ID_BASE_LATEST_BLOCK = process.env.DUNE_QUERY_ID_BASE_LA export const DUNE_QUERY_ID_NFT_REGISTRATION_EVENTS = process.env.DUNE_QUERY_ID_NFT_REGISTRATION_EVENTS || "4283531"; export const DUNE_QUERY_ID_NFT_TRANSFER_EVENTS = process.env.DUNE_QUERY_ID_NFT_TRANSFER_EVENTS || "4283862"; export const DUNE_QUERY_ID_INSTANCE_SERVICE_EVENTS = process.env.DUNE_QUERY_ID_INSTANCE_SERVICE_EVENTS || "4284748"; +export const DUNE_QUERY_ID_GIF_EVENTS = process.env.DUNE_QUERY_ID_GIF_EVENTS || "4287183"; diff --git a/src/dune.ts b/src/dune.ts index dcfb7e4..534d19b 100644 --- a/src/dune.ts +++ b/src/dune.ts @@ -92,9 +92,16 @@ export default class DuneApi { if (totalRowCount === 0) { totalRowCount = response.data.result.metadata.total_row_count; } + const rowCount = response.data.result.metadata.row_count; + if (rowCount === 0) { + break; + } + if (rowCount !== response.data.result.rows.length) { + throw new Error(`Row count mismatch expected: ${rowCount} effective: ${response.data.result.rows.length}`); + } rows.push(...response.data.result.rows); offset += limit; - } while (totalRowCount > 0 && offset * limit < totalRowCount) + } while (totalRowCount > 0 && offset < totalRowCount) logger.info(`Fetched ${rows.length} rows`); diff --git a/src/main.ts b/src/main.ts index b1225d2..3ad8535 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,15 @@ import { PrismaClient } from '@prisma/client'; import axios from 'axios'; import * as dotenv from 'dotenv'; -import { DUNE_API_BASE_URL, DUNE_API_KEY, DUNE_QUERY_ID_INSTANCE_SERVICE_EVENTS, DUNE_QUERY_ID_NFT_REGISTRATION_EVENTS, DUNE_QUERY_ID_NFT_TRANSFER_EVENTS } from './constants'; +import { DUNE_API_BASE_URL, DUNE_API_KEY, DUNE_QUERY_ID_GIF_EVENTS, DUNE_QUERY_ID_INSTANCE_SERVICE_EVENTS, DUNE_QUERY_ID_NFT_REGISTRATION_EVENTS, DUNE_QUERY_ID_NFT_TRANSFER_EVENTS } from './constants'; import InstanceProcessor from './instance_processor'; import { logger } from './logger'; import NftProcessor from './nft_processor'; import { ObjectType } from './types/objecttype'; import DuneApi from './dune'; +import { DecodedLogEntry } from './types/logdata'; +import { Nft } from './types/nft'; +import { Instance } from './types/instance'; dotenv.config(); @@ -29,28 +32,59 @@ class Main { } public async main(): Promise { - const nftRegistrationEvents = await this.dune.getLatestResult(DUNE_QUERY_ID_NFT_REGISTRATION_EVENTS, 0); - const nftTransferEvents = await this.dune.getLatestResult(DUNE_QUERY_ID_NFT_TRANSFER_EVENTS, 0); + const gifEvents = await this.dune.getLatestResult(DUNE_QUERY_ID_GIF_EVENTS, 0); + const { nfts, instances } = await this.parseGifEvents(gifEvents); + // const nftTransferEvents = await this.dune.getLatestResult(DUNE_QUERY_ID_NFT_TRANSFER_EVENTS, 0); - let nfts = await this.nftProcessor.processNftRegistrationEvents(nftRegistrationEvents); - nfts = await this.nftProcessor.processNftTransferEvents(nftTransferEvents, nfts); - await this.nftProcessor.persistNfts(nfts); + // let nfts = await this.nftProcessor.processNftRegistrationEvents(nftRegistrationEvents); + // nfts = await this.nftProcessor.processNftTransferEvents(nftTransferEvents, nfts); + // await this.nftProcessor.persistNfts(nfts); - // print one log per event - nfts.forEach(event => { - logger.info(`NFT: ${event.nftId} - ${ObjectType[event.objectType]} - ${event.objectAddress} - ${event.owner}`); - }); + // // print one log per event + const nftIterator = nfts.values(); + + for (const nft of nftIterator) { + logger.info(`NFT: ${nft.nftId} - ${ObjectType[nft.objectType]} - ${nft.objectAddress} - ${nft.owner}`); + }; - const instanceEvents = await this.dune.getLatestResult(DUNE_QUERY_ID_INSTANCE_SERVICE_EVENTS, 0); - const instances = await this.instanceProcessor.processInstanceServiceEvents(instanceEvents); - await this.instanceProcessor.persistInstances(instances); + // const instanceEvents = await this.dune.getLatestResult(DUNE_QUERY_ID_INSTANCE_SERVICE_EVENTS, 0); + // const instances = await this.instanceProcessor.processInstanceServiceEvents(instanceEvents); + // await this.instanceProcessor.persistInstances(instances); // print one log per event - instances.forEach(event => { - logger.info(`Instance: ${event.nftId} - ${event.instanceAddress}`); - }); + // instances.forEach(event => { + // logger.info(`Instance: ${event.nftId} - ${event.instanceAddress}`); + // }); } + async parseGifEvents(gifEvents: Array) + : Promise<{ nfts: Map, instances: Map }> + { + const nfts = new Map(); + const instances = new Map(); + // TODO const policies = new Map(); + + for (const event of gifEvents) { + logger.debug(`Processing gif event ${event.tx_hash} - ${event.block_number} - ${event.event_name}`); + + switch (event.event_name) { + case 'Transfer': + await this.nftProcessor.processNftTransferEvent(event, nfts); + break; + case 'LogRegistryObjectRegistered': + await this.nftProcessor.processNftRegistrationEvent(event, nfts); + break; + // // Transfer + // // LogRegistryObjectRegistered + // // LogInstanceServiceInstanceCreated + // // LogApplicationServiceApplicationCreated + // // LogPolicyServicePolicyCreated + // // LogPolicyServicePolicyPremiumCollected + } + } + + return { nfts, instances }; + } } const prisma = new PrismaClient() diff --git a/src/nft_processor.ts b/src/nft_processor.ts index 6c7186e..c53189d 100644 --- a/src/nft_processor.ts +++ b/src/nft_processor.ts @@ -4,6 +4,7 @@ import { logger } from "./logger"; import { IRegistry__factory } from "./generated/contracts/gif"; import { DecodedLogEntry } from "./types/logdata"; import { Nft } from "./types/nft"; +import { log } from "console"; export default class NftProcessor { private prisma: PrismaClient; @@ -42,25 +43,76 @@ export default class NftProcessor { } } - async processNftRegistrationEvents(nftRegistrationEvents: Array): Promise> { - return nftRegistrationEvents.map(event => { - logger.info(`Processing nft registration event ${event.tx_hash} - ${event.event_name} - ${event.data}`); - const data = this.decodeLogRegistryObjectRegisteredEvent(event); - if (data === null || data === undefined) { - logger.error(`Failed to decode event ${event.tx_hash} - ${event.event_name} - ${event.data}`); - return null as unknown as Nft; + async processNftRegistrationEvent(event: DecodedLogEntry, nfts: Map): Promise> { + if (event.event_name !== 'LogRegistryObjectRegistered') { + throw new Error(`Invalid event type ${event.event_name}`); + } + + logger.info(`Processing nft registration event ${event.tx_hash} - ${event.event_name} - ${event.data}`); + const data = this.decodeLogRegistryObjectRegisteredEvent(event); + if (data === null || data === undefined) { + logger.error(`Failed to decode event ${event.tx_hash} - ${event.event_name} - ${event.data}`); + return nfts; + } + + const nftId = data.args[0] as BigInt; + const parentNftId = data.args[1] as BigInt; + const objectType = getObjectType(BigInt(data.args[2])); + const objectAddress = data.args[4] as string; + const owner = data.args[5] as string; + const nft = { + nftId, + parentNftId, + objectType: objectType, + objectAddress: objectAddress, + owner: owner, + created: { + blockNumber: event.block_number, + txHash: event.tx_hash, + from: event.tx_from + }, + modified: { + blockNumber: event.block_number, + txHash: event.tx_hash, + from: event.tx_from } - const nftId = data.args[0] as BigInt; - const parentNftId = data.args[1] as BigInt; - const objectType = getObjectType(BigInt(data.args[2])); - const objectAddress = data.args[4] as string; - const owner = data.args[5] as string; - return { + } as Nft; + nfts.set(nftId, nft); + return nfts; + } + + async processNftTransferEvent(event: DecodedLogEntry, nfts: Map): Promise> { + if (event.event_name !== 'Transfer') { + throw new Error(`Invalid event type ${event.event_name}`); + } + + const from = `0x${event.topic1.substring(26)}`; + const to = `0x${event.topic2.substring(26)}`; + const nftId = BigInt(event.topic3); + + if (nfts.has(nftId) && from === '0x0000000000000000000000000000000000000000') { + logger.debug(`Initial nft event transfer ${nftId}`); + } else if (nfts.has(nftId)) { + logger.debug(`Transfer event from known nft ${nftId} from ${from} to ${to}`); + const nft = nfts.get(nftId); + if (nft === undefined) { + logger.error(`NFT ${nftId} not found for update`); + return nfts; + } + nft.owner = to; + nft.modified = { + blockNumber: event.block_number, + txHash: event.tx_hash, + from: event.tx_from + }; + } else { + logger.debug(`Transfer event from unknown nft ${nftId} from ${from} to ${to}`); + const nft = { nftId, - parentNftId, - objectType: objectType, - objectAddress: objectAddress, - owner: owner, + parentNftId: BigInt(0), + objectType: ObjectType.UNKNOWN, + objectAddress: '', + owner: to, created: { blockNumber: event.block_number, txHash: event.tx_hash, @@ -71,36 +123,9 @@ export default class NftProcessor { txHash: event.tx_hash, from: event.tx_from } - } as Nft; - }).filter(event => event !== null); - } - - async processNftTransferEvents(nftTransferEvents: Array, nfts: Array): Promise> { - nftTransferEvents.forEach(event => { - logger.debug(`Processing nft transfer event ${event.tx_hash} - ${event.event_name} - ${event.topic0} - ${event.topic1} - ${event.topic2} - ${event.topic3} - ${event.data}`); - // extract addresses - const from = `0x${event.topic1.substring(26)}`; - const to = `0x${event.topic2.substring(26)}`; - const nftId = BigInt(event.topic3); - // logger.debug(`Transfer from ${from} to ${to} for NFT ${nftId}`); - - if (from === '0x0000000000000000000000000000000000000000') { - return; - } - - const nft = nfts.find(nft => nft.nftId === nftId); - if (nft === undefined) { - logger.error(`NFT ${nftId} not found`); - return; - } - nft.owner = to; - nft.modified = { - blockNumber: event.block_number, - txHash: event.tx_hash, - from: event.tx_from }; - logger.debug(`Transfer NFT ${nftId} from ${from} to ${to}`); - }); + nfts.set(nftId, nft); + } return nfts; } diff --git a/src/types/objecttype.ts b/src/types/objecttype.ts index 5207da8..82509aa 100644 --- a/src/types/objecttype.ts +++ b/src/types/objecttype.ts @@ -1,6 +1,7 @@ import { logger } from "../logger"; export enum ObjectType { + UNKNOWN, PROTOCOL, REGISTRY, STAKING, @@ -37,68 +38,24 @@ export enum ObjectType { export function getObjectType(type: BigInt): ObjectType { logger.debug(`getObjectType(${type})`); switch (type) { + case BigInt(0): + return ObjectType.UNKNOWN; case BigInt(1): return ObjectType.PROTOCOL; - // case 2: - // return ObjectType.REGISTRY; - // case 3: - // return ObjectType.STAKING; - // case 6: - // return ObjectType.RELEASE; - // case 7: - // return ObjectType.ROLE; case BigInt(8): return ObjectType.SERVICE; case BigInt(10): return ObjectType.INSTANCE; - // case 11: - // return ObjectType.COMPONENT; case BigInt(12): return ObjectType.PRODUCT; case BigInt(13): return ObjectType.ORACLE; - // case 14: - // return ObjectType.DISTRIBUTION; case BigInt(15): return ObjectType.POOL; - // case 20: - // return ObjectType.APPLICATION; case BigInt(21): return ObjectType.POLICY; case BigInt(22): return ObjectType.BUNDLE; - // case 23: - // return ObjectType.DISTRIBUTOR; - // case 30: - // return ObjectType.STAKE; - // case 31: - // return ObjectType.TARGET; - // case 40: - // return ObjectType.ACCOUNTING; - // case 41: - // return ObjectType.FEE; - // case 42: - // return ObjectType.PRICE; - // case 43: - // return ObjectType.PREMIUM; - // case 44: - // return ObjectType.RISK; - // case 45: - // return ObjectType.CLAIM; - // case 46: - // return ObjectType.PAYOUT; - // case 47: - // return ObjectType.REQUEST; - // case 48: - // return ObjectType.DISTRIBUTOR_TYPE; - // case 49: - // return ObjectType.REFERRAL; - // case 97: - // return ObjectType.CORE; - // case 98: - // return ObjectType.CUSTOM; - // case 99: - // return ObjectType.ALL; default: throw new Error("Invalid ObjectType"); }