Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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