Skip to content

Commit 18bd0e5

Browse files
authored
Add supply indexing (#28)
Adds the Supply, SupplyDenom and BlockSupply Join entities. Also updates the block entity.
1 parent 5ecfc6b commit 18bd0e5

File tree

6 files changed

+299
-115
lines changed

6 files changed

+299
-115
lines changed

project.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { CosmosDatasourceKind, CosmosHandlerKind, CosmosProject } from "@subql/types-cosmos";
1+
import {
2+
CosmosDatasourceKind,
3+
CosmosHandlerKind,
4+
CosmosProject,
5+
} from "@subql/types-cosmos";
26

37
import * as dotenv from "dotenv";
48
import path from "path";

schema.graphql

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ type EventAttribute @entity {
99
event: Event!
1010
}
1111

12+
type SupplyDenom @entity {
13+
# ID is the supply coin denomination.
14+
id: ID!
15+
}
16+
1217
type Block @entity {
1318
id: ID! # The block header hash
1419
chainId: String! @index
@@ -18,6 +23,7 @@ type Block @entity {
1823
messages: [Message] @derivedFrom(field: "block")
1924
events: [Event] @derivedFrom(field: "block")
2025
balancesOfAccountByDenom: [Balance] @derivedFrom(field: "lastUpdatedBlock")
26+
supplies: [BlockSupply]! @derivedFrom(field: "block")
2127
# PARAMS
2228
appParams: [AppParam] @derivedFrom(field: "block")
2329
authParams: [AuthParam] @derivedFrom(field: "block")
@@ -585,3 +591,17 @@ type AuthzMsgExec @entity {
585591
authzExec: AuthzExec!
586592
message: Message!
587593
}
594+
595+
type Supply @entity {
596+
id: ID!
597+
denom: String! @index
598+
amount: BigInt!
599+
blocks: [BlockSupply]! @derivedFrom(field: "supply")
600+
}
601+
602+
# This entity is a Join between Block and Supply
603+
type BlockSupply @entity {
604+
id: ID!
605+
block: Block!
606+
supply: Supply!
607+
}

src/mappings/bank/supply.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type {
2+
Coin,
3+
CosmosBlock,
4+
} from "@subql/types-cosmos";
5+
import type { QueryTotalSupplyResponse } from "cosmjs-types/cosmos/bank/v1beta1/query";
6+
import {
7+
BlockSupply,
8+
Supply,
9+
} from "../../types";
10+
import { stringify } from "../utils";
11+
12+
export const getSupplyId = function(denom: string, height: number): string {
13+
return `${denom}@${height}`;
14+
};
15+
16+
export const getSupplyRecord = function(supply: Coin, block: CosmosBlock): Supply {
17+
return Supply.create({
18+
id: getSupplyId(supply.denom, block.header.height),
19+
denom: supply.denom,
20+
amount: BigInt(supply.amount),
21+
});
22+
};
23+
24+
export async function queryTotalSupply(): Promise<Coin[]> {
25+
const finalSupply: Coin[] = [];
26+
let paginationKey: Uint8Array | undefined;
27+
28+
try {
29+
// Here we force the use of a private property, breaking typescript limitation, due to the need of call a total supply
30+
// rpc query of cosmosjs that is not exposed on the implemented client by SubQuery team.
31+
// To avoid this, we need to move to implement our own rpc client and also use `unsafe` parameter which I prefer to avoid.
32+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
33+
// @ts-ignore
34+
const queryClient = api.forceGetQueryClient();
35+
36+
// Initial call to get the first set of results
37+
const initialResponse: QueryTotalSupplyResponse = await queryClient.bank.totalSupply() as unknown as QueryTotalSupplyResponse;
38+
logger.debug(`[handleTotalSupply]: initialResponse=${stringify(initialResponse, undefined, 2)}`);
39+
finalSupply.push(...initialResponse.supply);
40+
paginationKey = initialResponse.pagination?.nextKey;
41+
42+
// Continue fetching if there is a nextKey
43+
while (paginationKey && paginationKey.length > 0) {
44+
logger.debug(`[handleTotalSupply]: loading more supply pages pagination.nextKey=${JSON.stringify(paginationKey, undefined, 2)}`);
45+
const response = await queryClient.bank.totalSupply(paginationKey);
46+
finalSupply.push(...response.supply);
47+
paginationKey = response.pagination?.nextKey;
48+
}
49+
logger.debug(`[handleTotalSupply]: all_total_supply=${JSON.stringify(finalSupply, undefined, 2)}`);
50+
} catch (error) {
51+
logger.error(`[handleTotalSupply] errored: ${error}`);
52+
}
53+
54+
return finalSupply;
55+
}
56+
57+
export async function _handleSupply(block: CosmosBlock): Promise<void> {
58+
const totalSupply = await queryTotalSupply();
59+
if (totalSupply.length === 0) {
60+
logger.warn(`[_handleSupply]: no total supply found`);
61+
return;
62+
}
63+
64+
for (const supply of totalSupply) {
65+
// get the current blockSupply create on block handler to been able to access the assigned previous supply id
66+
// that will allow us to compare the amount and create a new on if needed.
67+
const blockSupplyId = getSupplyId(supply.denom, block.header.height);
68+
const latestBlockSupply = await BlockSupply.get(blockSupplyId);
69+
if (!latestBlockSupply) {
70+
logger.warn(`[_handleSupply]: no BlockSupply found id=${blockSupplyId}`);
71+
continue;
72+
}
73+
74+
const latestDenomSupply = await Supply.get(latestBlockSupply.supplyId);
75+
if (!latestDenomSupply) {
76+
logger.warn(`[_handleSupply]: no total supply found id=${latestBlockSupply.supplyId}`);
77+
continue;
78+
}
79+
80+
if (latestDenomSupply.amount.toString() !== supply.amount) {
81+
const newSupply = getSupplyRecord(supply, block);
82+
await newSupply.save();
83+
latestBlockSupply.supplyId = newSupply.id;
84+
await latestBlockSupply.save();
85+
break;
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)