From b54cdb48839eee14271923bc03514864eaf89906 Mon Sep 17 00:00:00 2001 From: rabi-siddique Date: Tue, 30 Apr 2024 15:40:47 +0500 Subject: [PATCH] feat: index transfer events --- project.ts | 7 +++ schema.graphql | 7 +++ src/mappings/constants.ts | 6 ++ src/mappings/events/balances.ts | 97 +++++++++++++++++++++++++++++++++ src/mappings/mappingHandlers.ts | 43 ++++++++++++++- 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 src/mappings/events/balances.ts diff --git a/project.ts b/project.ts index 39aaf9dce..14507895a 100644 --- a/project.ts +++ b/project.ts @@ -87,6 +87,13 @@ const project: CosmosProject = { type: "state_change", }, }, + { + handler: "handleTransferEvent", + kind: CosmosHandlerKind.Event, + filter: { + type: "transfer", + }, + }, ], }, }, diff --git a/schema.graphql b/schema.graphql index 069c413b4..1f7bdee95 100644 --- a/schema.graphql +++ b/schema.graphql @@ -262,3 +262,10 @@ type ReserveMetrics @entity { totalFeeMinted: BigInt! allocations: [ReserveAllocationMetrics] @derivedFrom(field: "reserveMetrics") } + +type Balances @entity { + id: ID! + address: String @index + balance: BigInt + denom: String @index +} \ No newline at end of file diff --git a/src/mappings/constants.ts b/src/mappings/constants.ts index 4362f60c4..8d308db58 100644 --- a/src/mappings/constants.ts +++ b/src/mappings/constants.ts @@ -24,6 +24,12 @@ export const VAULT_STATES = { LIQUIDATED: "liquidated" } +export const TRANSACTION_FIELDS = { + RECIPIENT: 'recipient', + SENDER: 'sender', + AMOUNT: 'amount' +} + export const VALUE_KEY = b64encode("value"); export const STORE_KEY = b64encode("store"); export const VSTORAGE_VALUE = b64encode("vstorage"); diff --git a/src/mappings/events/balances.ts b/src/mappings/events/balances.ts new file mode 100644 index 000000000..1327cb7a4 --- /dev/null +++ b/src/mappings/events/balances.ts @@ -0,0 +1,97 @@ +import { Balances } from '../../types'; +import { b64decode } from '../utils'; +import { CosmosEvent } from '@subql/types-cosmos'; + +interface Attribute { + key: string; + value: string; +} + +export interface DecodedEvent { + type: string; + attributes: Attribute[]; +} +export const balancesEventKit = () => { + function decodeEvent(cosmosEvent: CosmosEvent): DecodedEvent { + const { event } = cosmosEvent; + + const decodedData: DecodedEvent = { + type: event.type, + attributes: [], + }; + + event.attributes.forEach((attribute) => { + const decodedKey = b64decode(attribute.key); + const decodedValue = b64decode(attribute.value); + + decodedData.attributes.push({ + key: decodedKey, + value: decodedValue, + }); + }); + + return decodedData; + } + + async function addressExists(address: string): Promise { + const balance = await Balances.getByAddress(address); + if (!balance) { + return false; + } + return true; + } + + async function createBalancesEntry(address: string) { + const newBalance = new Balances(address); + newBalance.address = address; + newBalance.balance = BigInt(0); + newBalance.denom = ''; + + await newBalance.save(); + + logger.info(`Created new entry for address: ${address}`); + } + + async function updateBalances( + senderAddress: string, + recipientAddress: string, + amount: bigint + ): Promise { + const senderBalances = await Balances.getByAddress(senderAddress); + const senderBalance = senderBalances ? senderBalances[0] : undefined; + + const recipientBalances = await Balances.getByAddress(recipientAddress); + const recipientBalance = recipientBalances + ? recipientBalances[0] + : undefined; + + if (senderBalance && recipientBalance) { + senderBalance.balance = (senderBalance.balance || BigInt(0)) - amount; + recipientBalance.balance = + (recipientBalance.balance || BigInt(0)) + amount; + + await senderBalance.save(); + await recipientBalance.save(); + + logger.info( + `Updated balances: Sender (${senderAddress}) balance: ${senderBalance.balance}, Recipient (${recipientAddress}) balance: ${recipientBalance.balance}` + ); + } else { + if (!senderBalance) { + logger.error(`Sender balance not found for address: ${senderAddress}`); + } + if (!recipientBalance) { + logger.error( + `Recipient balance not found for address: ${recipientAddress}` + ); + } + } + } + + return { + decodeEvent, + addressExists, + createBalancesEntry, + updateBalances, + }; +}; diff --git a/src/mappings/mappingHandlers.ts b/src/mappings/mappingHandlers.ts index 6c87b46d7..66966fdc9 100644 --- a/src/mappings/mappingHandlers.ts +++ b/src/mappings/mappingHandlers.ts @@ -28,12 +28,13 @@ import { dateToDayKey, } from "./utils"; -import { EVENT_TYPES, STORE_KEY, VSTORAGE_VALUE, KEY_KEY, VALUE_KEY } from "./constants"; +import { EVENT_TYPES, STORE_KEY, VSTORAGE_VALUE, KEY_KEY, VALUE_KEY, TRANSACTION_FIELDS } from "./constants"; import { psmEventKit } from "./events/psm"; import { boardAuxEventKit } from "./events/boardAux"; import { priceFeedEventKit } from "./events/priceFeed"; import { vaultsEventKit } from "./events/vaults"; import { reservesEventKit } from "./events/reserves"; +import { DecodedEvent, balancesEventKit } from "./events/balances"; // @ts-ignore BigInt.prototype.toJSON = function () { @@ -148,3 +149,43 @@ export async function handleStateChangeEvent(cosmosEvent: CosmosEvent): Promise< await Promise.allSettled(recordSaves); } + +export async function handleTransferEvent( + cosmosEvent: CosmosEvent +): Promise { + const { event, block } = cosmosEvent; + + if (event.type != EVENT_TYPES.TRANSFER) { + logger.warn('Not valid transfer event.'); + return; + } + + const balancesKit = balancesEventKit(); + const decodedData: DecodedEvent = balancesKit.decodeEvent(cosmosEvent); + + const recipientAddress = + decodedData.attributes.find( + (attr) => attr.key === TRANSACTION_FIELDS.RECIPIENT + )?.value || ''; + + if (recipientAddress && (await balancesKit.addressExists(recipientAddress))) { + await balancesKit.createBalancesEntry(recipientAddress); + } + + const senderAddress = + decodedData.attributes.find( + (attr) => attr.key === TRANSACTION_FIELDS.SENDER + )?.value || ''; + + if (senderAddress && (await balancesKit.addressExists(senderAddress))) { + await balancesKit.createBalancesEntry(senderAddress); + } + + const amount = BigInt( + decodedData.attributes + .find((attr) => attr.key === TRANSACTION_FIELDS.AMOUNT) + ?.value.slice(0, -3) || 0 + ); + + balancesKit.updateBalances(senderAddress, recipientAddress, amount); +}