Skip to content

Commit

Permalink
Add supply indexing (#28)
Browse files Browse the repository at this point in the history
Adds the Supply, SupplyDenom and BlockSupply Join entities. Also updates the block entity.
  • Loading branch information
jorgecuesta authored Nov 20, 2024
1 parent 5ecfc6b commit 18bd0e5
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 115 deletions.
6 changes: 5 additions & 1 deletion project.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { CosmosDatasourceKind, CosmosHandlerKind, CosmosProject } from "@subql/types-cosmos";
import {
CosmosDatasourceKind,
CosmosHandlerKind,
CosmosProject,
} from "@subql/types-cosmos";

import * as dotenv from "dotenv";
import path from "path";
Expand Down
20 changes: 20 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ type EventAttribute @entity {
event: Event!
}

type SupplyDenom @entity {
# ID is the supply coin denomination.
id: ID!
}

type Block @entity {
id: ID! # The block header hash
chainId: String! @index
Expand All @@ -18,6 +23,7 @@ type Block @entity {
messages: [Message] @derivedFrom(field: "block")
events: [Event] @derivedFrom(field: "block")
balancesOfAccountByDenom: [Balance] @derivedFrom(field: "lastUpdatedBlock")
supplies: [BlockSupply]! @derivedFrom(field: "block")
# PARAMS
appParams: [AppParam] @derivedFrom(field: "block")
authParams: [AuthParam] @derivedFrom(field: "block")
Expand Down Expand Up @@ -585,3 +591,17 @@ type AuthzMsgExec @entity {
authzExec: AuthzExec!
message: Message!
}

type Supply @entity {
id: ID!
denom: String! @index
amount: BigInt!
blocks: [BlockSupply]! @derivedFrom(field: "supply")
}

# This entity is a Join between Block and Supply
type BlockSupply @entity {
id: ID!
block: Block!
supply: Supply!
}
88 changes: 88 additions & 0 deletions src/mappings/bank/supply.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import type {
Coin,
CosmosBlock,
} from "@subql/types-cosmos";
import type { QueryTotalSupplyResponse } from "cosmjs-types/cosmos/bank/v1beta1/query";
import {
BlockSupply,
Supply,
} from "../../types";
import { stringify } from "../utils";

export const getSupplyId = function(denom: string, height: number): string {
return `${denom}@${height}`;
};

export const getSupplyRecord = function(supply: Coin, block: CosmosBlock): Supply {
return Supply.create({
id: getSupplyId(supply.denom, block.header.height),
denom: supply.denom,
amount: BigInt(supply.amount),
});
};

export async function queryTotalSupply(): Promise<Coin[]> {
const finalSupply: Coin[] = [];
let paginationKey: Uint8Array | undefined;

try {
// Here we force the use of a private property, breaking typescript limitation, due to the need of call a total supply
// rpc query of cosmosjs that is not exposed on the implemented client by SubQuery team.
// To avoid this, we need to move to implement our own rpc client and also use `unsafe` parameter which I prefer to avoid.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const queryClient = api.forceGetQueryClient();

// Initial call to get the first set of results
const initialResponse: QueryTotalSupplyResponse = await queryClient.bank.totalSupply() as unknown as QueryTotalSupplyResponse;
logger.debug(`[handleTotalSupply]: initialResponse=${stringify(initialResponse, undefined, 2)}`);
finalSupply.push(...initialResponse.supply);
paginationKey = initialResponse.pagination?.nextKey;

// Continue fetching if there is a nextKey
while (paginationKey && paginationKey.length > 0) {
logger.debug(`[handleTotalSupply]: loading more supply pages pagination.nextKey=${JSON.stringify(paginationKey, undefined, 2)}`);
const response = await queryClient.bank.totalSupply(paginationKey);
finalSupply.push(...response.supply);
paginationKey = response.pagination?.nextKey;
}
logger.debug(`[handleTotalSupply]: all_total_supply=${JSON.stringify(finalSupply, undefined, 2)}`);
} catch (error) {
logger.error(`[handleTotalSupply] errored: ${error}`);
}

return finalSupply;
}

export async function _handleSupply(block: CosmosBlock): Promise<void> {
const totalSupply = await queryTotalSupply();
if (totalSupply.length === 0) {
logger.warn(`[_handleSupply]: no total supply found`);
return;
}

for (const supply of totalSupply) {
// get the current blockSupply create on block handler to been able to access the assigned previous supply id
// that will allow us to compare the amount and create a new on if needed.
const blockSupplyId = getSupplyId(supply.denom, block.header.height);
const latestBlockSupply = await BlockSupply.get(blockSupplyId);
if (!latestBlockSupply) {
logger.warn(`[_handleSupply]: no BlockSupply found id=${blockSupplyId}`);
continue;
}

const latestDenomSupply = await Supply.get(latestBlockSupply.supplyId);
if (!latestDenomSupply) {
logger.warn(`[_handleSupply]: no total supply found id=${latestBlockSupply.supplyId}`);
continue;
}

if (latestDenomSupply.amount.toString() !== supply.amount) {
const newSupply = getSupplyRecord(supply, block);
await newSupply.save();
latestBlockSupply.supplyId = newSupply.id;
await latestBlockSupply.save();
break;
}
}
}
Loading

0 comments on commit 18bd0e5

Please sign in to comment.