Skip to content

Commit a8e97d3

Browse files
committed
added subgraph health store
1 parent b318453 commit a8e97d3

File tree

6 files changed

+120
-2
lines changed

6 files changed

+120
-2
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@quest-chains/sdk",
33
"description": "An SDK for building applications on top of Quest Chains",
4-
"version": "0.1.11",
4+
"version": "0.1.12",
55
"license": "GPL-3.0",
66
"main": "dist/index.js",
77
"typings": "dist/index.d.ts",

src/graphql/client.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,30 @@ import { Client, createClient, dedupExchange, fetchExchange } from 'urql';
33
export type NetworkInfo = {
44
[chainId: string]: {
55
chainId: string;
6+
subgraphName: string;
67
subgraphUrl: string;
78
};
89
};
910

1011
export const SUPPORTED_NETWORK_INFO: NetworkInfo = {
1112
'0x89': {
1213
chainId: '0x89',
14+
subgraphName: 'dan13ram/quest-chains-polygon',
1315
subgraphUrl: 'https://api.thegraph.com/subgraphs/name/dan13ram/quest-chains-polygon',
1416
},
1517
'0x64': {
1618
chainId: '0x64',
19+
subgraphName: 'dan13ram/quest-chains-xdai',
1720
subgraphUrl: 'https://api.thegraph.com/subgraphs/name/dan13ram/quest-chains-xdai',
1821
},
1922
'0x5': {
2023
chainId: '0x5',
24+
subgraphName: 'dan13ram/quest-chains-goerli',
2125
subgraphUrl: 'https://api.thegraph.com/subgraphs/name/dan13ram/quest-chains-goerli',
2226
},
2327
'0x13881': {
2428
chainId: '0x13881',
29+
subgraphName: 'dan13ram/quest-chains-mumbai',
2530
subgraphUrl: 'https://api.thegraph.com/subgraphs/name/dan13ram/quest-chains-mumbai',
2631
},
2732
};

src/graphql/health.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/* eslint-disable no-console, no-await-in-loop */
2+
import { gql, request } from 'graphql-request';
3+
4+
import { SUPPORTED_NETWORK_INFO } from './client';
5+
6+
const GRAPH_HEALTH_ENDPOINT = 'https://api.thegraph.com/index-node/graphql';
7+
8+
const statusQuery = gql`
9+
query getSubgraphStatus($subgraph: String!) {
10+
status: indexingStatusForCurrentVersion(subgraphName: $subgraph) {
11+
chains {
12+
latestBlock {
13+
number
14+
}
15+
}
16+
}
17+
}
18+
`;
19+
20+
const getLatestBlock = async (subgraph: string): Promise<number> => {
21+
const data = await request(GRAPH_HEALTH_ENDPOINT, statusQuery, {
22+
subgraph,
23+
});
24+
return data.status.chains[0].latestBlock.number;
25+
};
26+
27+
const UPDATE_INTERVAL = 10000;
28+
29+
class SubgraphHealthStore {
30+
graphHealth: Record<string, number> = {};
31+
32+
constructor() {
33+
// console.debug('@quest-chains/sdk: health store init');
34+
this.updateSubgraphHealth();
35+
}
36+
37+
public async updateSubgraphHealth() {
38+
await Promise.all(
39+
Object.values(SUPPORTED_NETWORK_INFO).map(async info => {
40+
this.graphHealth[info.chainId] = await getLatestBlock(info.subgraphName);
41+
}),
42+
);
43+
// console.debug('@quest-chains/sdk: updated graph health:', this.graphHealth);
44+
setTimeout(() => this.updateSubgraphHealth(), UPDATE_INTERVAL);
45+
}
46+
47+
status() {
48+
return this.graphHealth;
49+
}
50+
}
51+
52+
const HealthStoreSingleton = (function () {
53+
let instance: SubgraphHealthStore;
54+
55+
function createInstance() {
56+
return new SubgraphHealthStore();
57+
}
58+
59+
return {
60+
getInstance: function () {
61+
if (!instance) {
62+
instance = createInstance();
63+
}
64+
return instance;
65+
},
66+
};
67+
})();
68+
69+
const getSubgraphStatus = () => HealthStoreSingleton.getInstance().status();
70+
71+
const initSubgraphHealthStore = getSubgraphStatus;
72+
73+
initSubgraphHealthStore();
74+
75+
export const getSubgraphLatestBlock = (chainId: string): number => getSubgraphStatus()[chainId];

src/graphql/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export * from './statusForUser';
77
export * from './rolesForUser';
88
export * from './types';
99
export * from './client';
10+
export * from './health';

src/helpers.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* eslint-disable no-await-in-loop */
2+
3+
import { Log, TransactionReceipt } from '@ethersproject/abstract-provider';
4+
import { ethers } from 'ethers';
5+
6+
import { getSubgraphLatestBlock } from './graphql/health';
7+
8+
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
9+
10+
const UPDATE_INTERVAL = 10000;
11+
12+
const MAX_RETRIES = 6;
13+
14+
export const waitUntilSubgraphIndexed = async (chainId: string, block: number): Promise<boolean> => {
15+
let latestBlock = getSubgraphLatestBlock(chainId);
16+
let tries = 0;
17+
while (latestBlock < block && tries < MAX_RETRIES) {
18+
await sleep(UPDATE_INTERVAL);
19+
tries += 1;
20+
latestBlock = getSubgraphLatestBlock(chainId);
21+
}
22+
return latestBlock >= block;
23+
};
24+
25+
export const getQuestChainAddressFromTx = async (receipt: TransactionReceipt) => {
26+
if (!receipt || !receipt.logs) return '';
27+
const abi = new ethers.utils.Interface(['event QuestChainCreated(uint256 id, address questChain)']);
28+
const eventFragment = abi.events[Object.keys(abi.events)[0]];
29+
const eventTopic = abi.getEventTopic(eventFragment);
30+
const event = receipt.logs.find((e: Log) => e.topics[0] === eventTopic);
31+
if (event) {
32+
const decodedLog = abi.decodeEventLog(eventFragment, event.data, event.topics);
33+
return decodedLog.questChain;
34+
}
35+
return '';
36+
};

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as graphql from './graphql';
22
import * as contracts from './contracts';
33
import * as metadata from './metadata';
4+
import * as helpers from './helpers';
45

5-
export { graphql, contracts, metadata };
6+
export { graphql, contracts, metadata, helpers };

0 commit comments

Comments
 (0)