From 898f70a6c4255ef79c1d0483b95803fe13cd6430 Mon Sep 17 00:00:00 2001 From: Taha Abbasi Date: Fri, 31 Mar 2023 15:20:05 -0600 Subject: [PATCH 1/3] Updating format --- src/cacheMiddleware.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cacheMiddleware.ts b/src/cacheMiddleware.ts index 4e9555d..8b00d4e 100644 --- a/src/cacheMiddleware.ts +++ b/src/cacheMiddleware.ts @@ -1,3 +1,4 @@ +// src/cacheMiddleware.ts import { Request, Response, NextFunction } from "express"; import NodeCache from "node-cache"; From 86be5443553b1a573bd9c9b51fb653eb8d1ffa96 Mon Sep 17 00:00:00 2001 From: Taha Abbasi Date: Fri, 31 Mar 2023 18:03:33 -0600 Subject: [PATCH 2/3] Improved security and made the API dynamic so any token configuration can be provided in the DB instead --- build/config.js | 36 +++- build/getSupplyAcrossNetworks.js | 63 +------ build/server.js | 17 +- .../nonCirculatingSupplyAddressesConfig.json | 47 ----- src/config.ts | 43 +++-- src/getSupplyAcrossNetworks.ts | 163 ++++++------------ src/server.ts | 49 ++++-- src/types.ts | 3 + 8 files changed, 164 insertions(+), 257 deletions(-) delete mode 100644 config/nonCirculatingSupplyAddressesConfig.json diff --git a/build/config.js b/build/config.js index 8c9ea09..fd2f430 100644 --- a/build/config.js +++ b/build/config.js @@ -12,17 +12,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getNonCirculatingSupplyAddressConfigurations = exports.getNetworkConfigurations = exports.nonCirculatingSupplyAddressesConfigInput = void 0; +exports.getNonCirculatingSupplyAddressConfigurations = exports.getNetworkConfigurations = exports.getNonCirculatingSupplyAddressesConfigInput = void 0; // src/config.ts const node_fetch_1 = __importDefault(require("node-fetch")); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); const mongodb_1 = require("mongodb"); const dotenv_1 = require("dotenv"); (0, dotenv_1.config)(); const API_URL = process.env.API_URL; -// const MONGODB_URI = "mongodb+srv://tokenSupply_app_dev_qa_uat:M4T9dEmF4hpDTt5f@ferrum-netwrok-dev-qa-u.kyjw1.mongodb.net/?retryWrites=true&w=majority"; -const DATABASE_NAME = "ferrum-network-dev"; +const DATABASE_NAME = process.env.DATABASE_NAME; function getChainIdToNetworkMap() { return __awaiter(this, void 0, void 0, function* () { const MONGODB_URI = process.env.MONGODB_URI; @@ -65,19 +62,40 @@ function getNetworkConfigurations(tokenContractAddress, chainId) { }; } } - return networks; + // Get the currencyId from the response data + const currencyId = data.body.currencyAddressesByNetworks[0].currency._id; + return { networks, currencyId }; }); } exports.getNetworkConfigurations = getNetworkConfigurations; -exports.nonCirculatingSupplyAddressesConfigInput = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, "../config/", "nonCirculatingSupplyAddressesConfig.json"), "utf-8")); -function getNonCirculatingSupplyAddressConfigurations(tokenContractAddress, chainId) { +function getNonCirculatingSupplyAddressesConfigInput(currencyId) { + return __awaiter(this, void 0, void 0, function* () { + const MONGODB_URI = process.env.MONGODB_URI; + if (!MONGODB_URI) { + throw new Error('MONGODB_URI is not defined in the environment variables'); + } + const client = new mongodb_1.MongoClient(MONGODB_URI); + yield client.connect(); + const database = client.db(DATABASE_NAME); + const collection = database.collection("nonCirculatingSupplyAddressesConfig"); + const result = yield collection.findOne({ currency: new mongodb_1.ObjectId(currencyId) }); + yield client.close(); + if (!result) { + throw new Error(`No non-circulating supply addresses configuration found for currency with ID: ${currencyId}`); + } + return result.nonCirculatingSupplyAddresses; + }); +} +exports.getNonCirculatingSupplyAddressesConfigInput = getNonCirculatingSupplyAddressesConfigInput; +function getNonCirculatingSupplyAddressConfigurations(tokenContractAddress, chainId, currencyId) { return __awaiter(this, void 0, void 0, function* () { const url = `${API_URL}?tokenContractAddress=${tokenContractAddress}&chainId=${chainId}&offset=0`; const response = yield (0, node_fetch_1.default)(url); const data = yield response.json(); const nonCirculatingSupplyAddresses = []; const chainIdToNetworkMap = yield getChainIdToNetworkMap(); - for (const item of exports.nonCirculatingSupplyAddressesConfigInput) { + const nonCirculatingSupplyAddressesConfigInput = yield getNonCirculatingSupplyAddressesConfigInput(currencyId); + for (const item of nonCirculatingSupplyAddressesConfigInput) { const network = chainIdToNetworkMap[item.chainId]; let networkItemFromGatewayConfig = data.body.currencyAddressesByNetworks.find(i => i.network.chainId === item.chainId); nonCirculatingSupplyAddresses.push({ diff --git a/build/getSupplyAcrossNetworks.js b/build/getSupplyAcrossNetworks.js index ccec91c..24b7658 100644 --- a/build/getSupplyAcrossNetworks.js +++ b/build/getSupplyAcrossNetworks.js @@ -15,13 +15,15 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.getTotalSupplyAcrossNetworks = exports.getNonCirculatingSupplyBalances = void 0; const bignumber_js_1 = __importDefault(require("bignumber.js")); const web3_1 = __importDefault(require("web3")); +const erc20Abi_json_1 = __importDefault(require("./erc20Abi.json")); const config_1 = require("./config"); const utils_1 = require("./utils"); -function getNonCirculatingSupplyBalances(tokenContractAddress, chainId) { +const erc20Abi = erc20Abi_json_1.default; +function getNonCirculatingSupplyBalances(tokenContractAddress, chainId, currencyId) { return __awaiter(this, void 0, void 0, function* () { const nonCirculatingSupplyBalances = []; let total = new bignumber_js_1.default(0); - for (const { chainId: currentChainId, address, jsonRpcUrl, tokenContractAddress: currentTokenContractAddress, name, } of yield (0, config_1.getNonCirculatingSupplyAddressConfigurations)(tokenContractAddress, chainId)) { + for (const { chainId: currentChainId, address, jsonRpcUrl, tokenContractAddress: currentTokenContractAddress, name, } of yield (0, config_1.getNonCirculatingSupplyAddressConfigurations)(tokenContractAddress, chainId, currencyId)) { let balance; if (currentChainId === "bnbBeaconChain") { balance = new bignumber_js_1.default(yield (0, utils_1.getBep2TokenBalance)(address, jsonRpcUrl, currentTokenContractAddress)); @@ -46,33 +48,12 @@ function getNonCirculatingSupplyBalances(tokenContractAddress, chainId) { } exports.getNonCirculatingSupplyBalances = getNonCirculatingSupplyBalances; const getTotalSupplyAcrossNetworks = (networks) => __awaiter(void 0, void 0, void 0, function* () { - const erc20ABI = [ - // Some parts of the ABI have been removed for brevity - { - constant: true, - inputs: [], - name: "totalSupply", - outputs: [{ name: "", type: "uint256" }], - payable: false, - stateMutability: "view", - type: "function", - }, - { - constant: true, - inputs: [], - name: "decimals", - outputs: [{ name: "", type: "uint8" }], - payable: false, - stateMutability: "view", - type: "function", - }, - ]; const supplyPerNetwork = {}; let totalSupply = new bignumber_js_1.default(0); for (const network in networks) { const config = networks[network]; const web3 = new web3_1.default(config.jsonRpcUrl); - const tokenContract = new web3.eth.Contract(erc20ABI, config.tokenContractAddress); + const tokenContract = new web3.eth.Contract(erc20Abi, config.tokenContractAddress); try { const [supply, decimals] = yield Promise.all([ tokenContract.methods.totalSupply().call(), @@ -82,42 +63,12 @@ const getTotalSupplyAcrossNetworks = (networks) => __awaiter(void 0, void 0, voi const decimalsBN = new bignumber_js_1.default(10).pow(decimals); const supplyInEther = supplyBN.div(decimalsBN); totalSupply = totalSupply.plus(supplyInEther); - supplyPerNetwork[network] = supplyInEther.toString(); + supplyPerNetwork[network] = supplyInEther.toFixed(18); } catch (error) { console.error(`Error getting total supply for ${network}:`, error.message); } } - return Object.assign(Object.assign({}, supplyPerNetwork), { total: totalSupply.toString() }); + return Object.assign(Object.assign({}, supplyPerNetwork), { total: totalSupply.toFixed(18) }); }); exports.getTotalSupplyAcrossNetworks = getTotalSupplyAcrossNetworks; -const erc20ABI = [ - // Add necessary parts of the ABI, such as "balanceOf" - { - constant: true, - inputs: [], - name: "totalSupply", - outputs: [{ name: "", type: "uint256" }], - payable: false, - stateMutability: "view", - type: "function", - }, - { - constant: true, - inputs: [], - name: "decimals", - outputs: [{ name: "", type: "uint8" }], - payable: false, - stateMutability: "view", - type: "function", - }, - { - constant: true, - inputs: [{ name: "_owner", type: "address" }], - name: "balanceOf", - outputs: [{ name: "balance", type: "uint256" }], - payable: false, - stateMutability: "view", - type: "function", - }, -]; diff --git a/build/server.js b/build/server.js index 013f1c4..1569852 100644 --- a/build/server.js +++ b/build/server.js @@ -32,7 +32,7 @@ app.get("/totalSupplyAcrossNetworks", (0, cacheMiddleware_1.default)(cacheDurati res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const networks = yield (0, config_1.getNetworkConfigurations)(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = yield (0, config_1.getNetworkConfigurations)(tokenContractAddress, Number(chainId)); const totalSupplyData = yield (0, getSupplyAcrossNetworks_1.getTotalSupplyAcrossNetworks)(networks); res.json(totalSupplyData); } @@ -47,7 +47,7 @@ app.get('/totalSupply', (0, cacheMiddleware_1.default)(cacheDuration), (req, res res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const networks = yield (0, config_1.getNetworkConfigurations)(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = yield (0, config_1.getNetworkConfigurations)(tokenContractAddress, Number(chainId)); const totalSupplyData = yield (0, getSupplyAcrossNetworks_1.getTotalSupplyAcrossNetworks)(networks); res.send(totalSupplyData.total); } @@ -62,7 +62,8 @@ app.get("/nonCirculatingSupplyAddresses", (0, cacheMiddleware_1.default)(cacheDu res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const nonCirculatingSupplyAddressConfigurations = yield (0, config_1.getNonCirculatingSupplyAddressConfigurations)(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = yield (0, config_1.getNetworkConfigurations)(tokenContractAddress, Number(chainId)); + const nonCirculatingSupplyAddressConfigurations = yield (0, config_1.getNonCirculatingSupplyAddressConfigurations)(tokenContractAddress, Number(chainId), currencyId); res.json(nonCirculatingSupplyAddressConfigurations); })); app.get('/nonCirculatingSupplyBalancesByAddress', (0, cacheMiddleware_1.default)(cacheDuration), (req, res) => __awaiter(void 0, void 0, void 0, function* () { @@ -72,7 +73,8 @@ app.get('/nonCirculatingSupplyBalancesByAddress', (0, cacheMiddleware_1.default) res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const nonCirculatingSupplyBalances = yield (0, getSupplyAcrossNetworks_1.getNonCirculatingSupplyBalances)(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = yield (0, config_1.getNetworkConfigurations)(tokenContractAddress, Number(chainId)); + const nonCirculatingSupplyBalances = yield (0, getSupplyAcrossNetworks_1.getNonCirculatingSupplyBalances)(tokenContractAddress, Number(chainId), currencyId); res.json(nonCirculatingSupplyBalances); } catch (error) { @@ -87,7 +89,8 @@ app.get('/nonCirculatingSupplyBalance', (0, cacheMiddleware_1.default)(cacheDura res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const { balances } = yield (0, getSupplyAcrossNetworks_1.getNonCirculatingSupplyBalances)(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = yield (0, config_1.getNetworkConfigurations)(tokenContractAddress, Number(chainId)); + const { balances } = yield (0, getSupplyAcrossNetworks_1.getNonCirculatingSupplyBalances)(tokenContractAddress, Number(chainId), currencyId); const totalBalance = balances.reduce((sum, balance) => sum.plus(balance.balance), new bignumber_js_1.default(0)); res.send(totalBalance.toString()); } @@ -103,10 +106,10 @@ app.get('/circulatingSupplyBalance', (0, cacheMiddleware_1.default)(cacheDuratio res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const networks = yield (0, config_1.getNetworkConfigurations)(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = yield (0, config_1.getNetworkConfigurations)(tokenContractAddress, Number(chainId)); const totalSupplyData = yield (0, getSupplyAcrossNetworks_1.getTotalSupplyAcrossNetworks)(networks); const totalSupply = new bignumber_js_1.default(totalSupplyData.total); - const { balances } = yield (0, getSupplyAcrossNetworks_1.getNonCirculatingSupplyBalances)(tokenContractAddress, Number(chainId)); + const { balances } = yield (0, getSupplyAcrossNetworks_1.getNonCirculatingSupplyBalances)(tokenContractAddress, Number(chainId), currencyId); const nonCirculatingSupply = balances.reduce((sum, balance) => sum.plus(balance.balance), new bignumber_js_1.default(0)); const circulatingSupply = totalSupply.minus(nonCirculatingSupply); res.send(circulatingSupply.toString()); diff --git a/config/nonCirculatingSupplyAddressesConfig.json b/config/nonCirculatingSupplyAddressesConfig.json deleted file mode 100644 index 821ee65..0000000 --- a/config/nonCirculatingSupplyAddressesConfig.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "name": "Deployer", - "address": "0xc2fdcb728170192c72ada2c08957f2e9390076b7", - "chainId": "1" - }, - { - "name": "Treasury", - "address": "0x517873ca1edaaa0f6403a0dab2cb0162433de9d1", - "chainId": "56" - }, - { - "name": "Deployer", - "address": "0xc2fdcb728170192c72ada2c08957f2e9390076b7", - "chainId": "137" - }, - { - "name": "Deployer", - "address": "0xc2fdcb728170192c72ada2c08957f2e9390076b7", - "chainId": "43114" - }, - { - "name": "Treasury", - "address": "0xe42b80dA58ccEAbe0A6ECe8e3311AE939Ef6b96c", - "chainId": "42161" - }, - { - "name": "Bridge Pool", - "address": "0x8e01cc26d6dd73581347c4370573ce9e59e74802", - "chainId": "1" - }, - { - "name": "Bridge Pool", - "address": "0x8e01cc26d6dd73581347c4370573ce9e59e74802", - "chainId": "56" - }, - { - "name": "Bridge Pool", - "address": "0x8e01cc26d6dd73581347c4370573ce9e59e74802", - "chainId": "137" - }, - { - "name": "Bridge Pool", - "address": "0x8e01cc26d6dd73581347c4370573ce9e59e74802", - "chainId": "43114" - } - ] \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index ffb84ab..7a5d40a 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,13 +10,8 @@ import { MongoClient, ObjectId } from "mongodb"; import { config } from 'dotenv'; config(); - - const API_URL = process.env.API_URL; - - -// const MONGODB_URI = "mongodb+srv://tokenSupply_app_dev_qa_uat:M4T9dEmF4hpDTt5f@ferrum-netwrok-dev-qa-u.kyjw1.mongodb.net/?retryWrites=true&w=majority"; -const DATABASE_NAME = "ferrum-network-dev"; +const DATABASE_NAME = process.env.DATABASE_NAME; async function getChainIdToNetworkMap(): Promise { const MONGODB_URI = process.env.MONGODB_URI; @@ -48,7 +43,7 @@ async function getChainIdToNetworkMap(): Promise { return chainIdToNetworkMap; } -async function getNetworkConfigurations(tokenContractAddress: string, chainId: number): Promise { +async function getNetworkConfigurations(tokenContractAddress: string, chainId: number): Promise<{ networks: NetworkConfigurations, currencyId: string }> { const url = `${API_URL}?tokenContractAddress=${tokenContractAddress}&chainId=${chainId}&offset=0`; const response = await fetch(url); const data: GatewayCabnApiResponse = await response.json(); @@ -66,20 +61,43 @@ async function getNetworkConfigurations(tokenContractAddress: string, chainId: n }; } } - return networks; + + // Get the currencyId from the response data + const currencyId = data.body.currencyAddressesByNetworks[0].currency._id; + + return { networks, currencyId }; } -export const nonCirculatingSupplyAddressesConfigInput: AddressConfigurationInput[] = JSON.parse( - fs.readFileSync(path.join(__dirname, "../config/", "nonCirculatingSupplyAddressesConfig.json"), "utf-8") -); -async function getNonCirculatingSupplyAddressConfigurations(tokenContractAddress: string, chainId: number): Promise { +export async function getNonCirculatingSupplyAddressesConfigInput(currencyId: string): Promise { + const MONGODB_URI = process.env.MONGODB_URI; + if (!MONGODB_URI) { + throw new Error('MONGODB_URI is not defined in the environment variables'); + } + const client = new MongoClient(MONGODB_URI); + await client.connect(); + const database = client.db(DATABASE_NAME); + const collection = database.collection("nonCirculatingSupplyAddressesConfig"); + + const result = await collection.findOne({ currency: new ObjectId(currencyId) }); + + await client.close(); + + if (!result) { + throw new Error(`No non-circulating supply addresses configuration found for currency with ID: ${currencyId}`); + } + + return result.nonCirculatingSupplyAddresses; +} + +async function getNonCirculatingSupplyAddressConfigurations(tokenContractAddress: string, chainId: number, currencyId: string): Promise { const url = `${API_URL}?tokenContractAddress=${tokenContractAddress}&chainId=${chainId}&offset=0`; const response = await fetch(url); const data: GatewayCabnApiResponse = await response.json(); const nonCirculatingSupplyAddresses: AddressConfiguration[] = []; const chainIdToNetworkMap = await getChainIdToNetworkMap(); + const nonCirculatingSupplyAddressesConfigInput = await getNonCirculatingSupplyAddressesConfigInput(currencyId); for (const item of nonCirculatingSupplyAddressesConfigInput) { const network = chainIdToNetworkMap[item.chainId] @@ -99,3 +117,4 @@ async function getNonCirculatingSupplyAddressConfigurations(tokenContractAddress } export { getNetworkConfigurations, getNonCirculatingSupplyAddressConfigurations }; + diff --git a/src/getSupplyAcrossNetworks.ts b/src/getSupplyAcrossNetworks.ts index a18a599..94429b3 100644 --- a/src/getSupplyAcrossNetworks.ts +++ b/src/getSupplyAcrossNetworks.ts @@ -2,126 +2,75 @@ import { AbiItem } from "web3-utils"; import BigNumber from "bignumber.js"; import Web3 from "web3"; -import erc20Abi from "./erc20Abi.json"; +import erc20AbiJson from "./erc20Abi.json"; import axios, { AxiosResponse } from "axios"; -import {getNetworkConfigurations, getNonCirculatingSupplyAddressConfigurations } from "./config"; +import { getNetworkConfigurations, getNonCirculatingSupplyAddressConfigurations } from "./config"; import { NonCirculatingSupplyBalance } from './types'; import { getBep2TokenBalance, getErc20TokenBalance } from './utils'; import { NetworkConfigurations } from "./types"; -export async function getNonCirculatingSupplyBalances(tokenContractAddress: string, chainId: number): Promise < { - balances: NonCirculatingSupplyBalance[];total: BigNumber -} > { +const erc20Abi: AbiItem[] = erc20AbiJson as any; + +export async function getNonCirculatingSupplyBalances(tokenContractAddress: string, chainId: number, currencyId: string): Promise<{ + balances: NonCirculatingSupplyBalance[]; + total: BigNumber; +}> { const nonCirculatingSupplyBalances: NonCirculatingSupplyBalance[] = []; let total = new BigNumber(0); for (const { - chainId: currentChainId, - address, - jsonRpcUrl, - tokenContractAddress: currentTokenContractAddress, - name, - } of await getNonCirculatingSupplyAddressConfigurations(tokenContractAddress, chainId)) { - let balance: BigNumber; - if (currentChainId === "bnbBeaconChain") { - balance = new BigNumber(await getBep2TokenBalance(address, jsonRpcUrl, currentTokenContractAddress)); - } else { - balance = new BigNumber(await getErc20TokenBalance(address, jsonRpcUrl, currentTokenContractAddress)); - } - total = total.plus(balance); - nonCirculatingSupplyBalances.push({ - chainId: currentChainId, - address, - tokenContractAddress: currentTokenContractAddress, - name, - balance: balance, - }); + chainId: currentChainId, + address, + jsonRpcUrl, + tokenContractAddress: currentTokenContractAddress, + name, + } of await getNonCirculatingSupplyAddressConfigurations(tokenContractAddress, chainId, currencyId)) { + let balance: BigNumber; + if (currentChainId === "bnbBeaconChain") { + balance = new BigNumber(await getBep2TokenBalance(address, jsonRpcUrl, currentTokenContractAddress)); + } else { + balance = new BigNumber(await getErc20TokenBalance(address, jsonRpcUrl, currentTokenContractAddress)); + } + total = total.plus(balance); + nonCirculatingSupplyBalances.push({ + chainId: currentChainId, + address, + tokenContractAddress: currentTokenContractAddress, + name, + balance: balance, + }); } return { - balances: nonCirculatingSupplyBalances, - total: total, + balances: nonCirculatingSupplyBalances, + total: total, }; } -export const getTotalSupplyAcrossNetworks = async ( - networks: NetworkConfigurations -): Promise<{ [network: string]: string; total: string }> => { - const erc20ABI: AbiItem[] = [ - // Some parts of the ABI have been removed for brevity - { - constant: true, - inputs: [], - name: "totalSupply", - outputs: [{ name: "", type: "uint256" }], - payable: false, - stateMutability: "view" as const, - type: "function", - }, - { - constant: true, - inputs: [], - name: "decimals", - outputs: [{ name: "", type: "uint8" }], - payable: false, - stateMutability: "view" as const, - type: "function", - }, - ]; - - const supplyPerNetwork: { [network: string]: string } = {}; +export const getTotalSupplyAcrossNetworks = async (networks: NetworkConfigurations): Promise < { + [network: string]: string;total: string +} > => { + const supplyPerNetwork: { + [network: string]: string + } = {}; let totalSupply = new BigNumber(0); - for (const network in networks) { - const config = networks[network]; - const web3 = new Web3(config.jsonRpcUrl); - const tokenContract = new web3.eth.Contract(erc20ABI, config.tokenContractAddress); - - try { - const [supply, decimals] = await Promise.all([ - tokenContract.methods.totalSupply().call(), - tokenContract.methods.decimals().call(), - ]); - - const supplyBN = new BigNumber(supply); - const decimalsBN = new BigNumber(10).pow(decimals); - const supplyInEther = supplyBN.div(decimalsBN); - - totalSupply = totalSupply.plus(supplyInEther); - supplyPerNetwork[network] = supplyInEther.toString(); - } catch (error) { - console.error(`Error getting total supply for ${network}:`, (error as Error).message); - } + const config = networks[network]; + const web3 = new Web3(config.jsonRpcUrl); + const tokenContract = new web3.eth.Contract(erc20Abi, config.tokenContractAddress); + try { + const [supply, decimals] = await Promise.all([ + tokenContract.methods.totalSupply().call(), + tokenContract.methods.decimals().call(), + ]); + const supplyBN = new BigNumber(supply); + const decimalsBN = new BigNumber(10).pow(decimals); + const supplyInEther = supplyBN.div(decimalsBN); + totalSupply = totalSupply.plus(supplyInEther); + supplyPerNetwork[network] = supplyInEther.toFixed(18); + } catch (error) { + console.error(`Error getting total supply for ${network}:`, (error as Error).message); + } } - - return { ...supplyPerNetwork, total: totalSupply.toString() }; -}; - -const erc20ABI: AbiItem[] = [ - // Add necessary parts of the ABI, such as "balanceOf" - { - constant: true, - inputs: [], - name: "totalSupply", - outputs: [{ name: "", type: "uint256" }], - payable: false, - stateMutability: "view" as const, - type: "function", - }, - { - constant: true, - inputs: [], - name: "decimals", - outputs: [{ name: "", type: "uint8" }], - payable: false, - stateMutability: "view" as const, - type: "function", - }, - { - constant: true, - inputs: [{ name: "_owner", type: "address" }], - name: "balanceOf", - outputs: [{ name: "balance", type: "uint256" }], - payable: false, - stateMutability: "view" as const, - type: "function", - }, -]; \ No newline at end of file + return { ...supplyPerNetwork, + total: totalSupply.toFixed(18) + }; +}; \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 3cee018..1421cdf 100644 --- a/src/server.ts +++ b/src/server.ts @@ -14,18 +14,19 @@ const app = express(); const port = process.env.PORT || 8080; const cacheDuration = Number(process.env.CACHE_DURATION || 300); + app.use(bodyParser.json()); app.get("/totalSupplyAcrossNetworks", cacheMiddleware(cacheDuration), async (req, res) => { try { const { tokenContractAddress, chainId } = req.query; - + if (typeof tokenContractAddress !== 'string' || typeof chainId !== 'string') { res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const networks = await getNetworkConfigurations(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = await getNetworkConfigurations(tokenContractAddress, Number(chainId)); const totalSupplyData = await getTotalSupplyAcrossNetworks(networks); res.json(totalSupplyData); } catch (error) { @@ -36,13 +37,13 @@ app.get("/totalSupplyAcrossNetworks", cacheMiddleware(cacheDuration), async (req app.get('/totalSupply', cacheMiddleware(cacheDuration), async (req, res) => { try { const { tokenContractAddress, chainId } = req.query; - + if (typeof tokenContractAddress !== 'string' || typeof chainId !== 'string') { res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const networks = await getNetworkConfigurations(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = await getNetworkConfigurations(tokenContractAddress, Number(chainId)); const totalSupplyData = await getTotalSupplyAcrossNetworks(networks); res.send(totalSupplyData.total); } catch (error) { @@ -53,24 +54,31 @@ app.get('/totalSupply', cacheMiddleware(cacheDuration), async (req, res) => { app.get("/nonCirculatingSupplyAddresses", cacheMiddleware(cacheDuration), async (req, res) => { const { tokenContractAddress, chainId } = req.query; - - if (typeof tokenContractAddress !== 'string' || typeof chainId !== 'string') { - res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); - return; - } - const nonCirculatingSupplyAddressConfigurations = await getNonCirculatingSupplyAddressConfigurations(tokenContractAddress, Number(chainId)); - res.json(nonCirculatingSupplyAddressConfigurations); + + if (typeof tokenContractAddress !== 'string' || typeof chainId !== 'string') { + res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); + return; + } + const { networks, currencyId } = await getNetworkConfigurations(tokenContractAddress, Number(chainId)); + const nonCirculatingSupplyAddressConfigurations = await getNonCirculatingSupplyAddressConfigurations(tokenContractAddress, Number(chainId), currencyId); + + // Remove jsonRpcUrl from the response + const filteredResponse = nonCirculatingSupplyAddressConfigurations.map(({ jsonRpcUrl, ...rest }) => rest); + + res.json(filteredResponse); }); + app.get('/nonCirculatingSupplyBalancesByAddress', cacheMiddleware(cacheDuration), async (req, res) => { try { const { tokenContractAddress, chainId } = req.query; - + if (typeof tokenContractAddress !== 'string' || typeof chainId !== 'string') { res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const nonCirculatingSupplyBalances = await getNonCirculatingSupplyBalances(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = await getNetworkConfigurations(tokenContractAddress, Number(chainId)); + const nonCirculatingSupplyBalances = await getNonCirculatingSupplyBalances(tokenContractAddress, Number(chainId), currencyId); res.json(nonCirculatingSupplyBalances); } catch (error) { console.error('Error fetching non-circulating supply balances:', error); @@ -78,15 +86,17 @@ app.get('/nonCirculatingSupplyBalancesByAddress', cacheMiddleware(cacheDuration) } }); + app.get('/nonCirculatingSupplyBalance', cacheMiddleware(cacheDuration), async (req, res) => { try { const { tokenContractAddress, chainId } = req.query; - + if (typeof tokenContractAddress !== 'string' || typeof chainId !== 'string') { res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const { balances }: { balances: NonCirculatingSupplyBalance[] } = await getNonCirculatingSupplyBalances(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = await getNetworkConfigurations(tokenContractAddress, Number(chainId)); + const { balances }: { balances: NonCirculatingSupplyBalance[] } = await getNonCirculatingSupplyBalances(tokenContractAddress, Number(chainId), currencyId); const totalBalance = balances.reduce((sum, balance) => sum.plus(balance.balance), new BigNumber(0)); res.send(totalBalance.toString()); } catch (error) { @@ -95,20 +105,20 @@ app.get('/nonCirculatingSupplyBalance', cacheMiddleware(cacheDuration), async (r } }); + app.get('/circulatingSupplyBalance', cacheMiddleware(cacheDuration), async (req, res) => { try { const { tokenContractAddress, chainId } = req.query; - + if (typeof tokenContractAddress !== 'string' || typeof chainId !== 'string') { res.status(400).json({ error: 'Both tokenContractAddress and chainId must be provided as query parameters.' }); return; } - const networks = await getNetworkConfigurations(tokenContractAddress, Number(chainId)); + const { networks, currencyId } = await getNetworkConfigurations(tokenContractAddress, Number(chainId)); const totalSupplyData = await getTotalSupplyAcrossNetworks(networks); const totalSupply = new BigNumber(totalSupplyData.total); - - const { balances }: { balances: NonCirculatingSupplyBalance[] } = await getNonCirculatingSupplyBalances(tokenContractAddress, Number(chainId)); + const { balances }: { balances: NonCirculatingSupplyBalance[] } = await getNonCirculatingSupplyBalances(tokenContractAddress, Number(chainId), currencyId); const nonCirculatingSupply = balances.reduce((sum, balance) => sum.plus(balance.balance), new BigNumber(0)); const circulatingSupply = totalSupply.minus(nonCirculatingSupply); @@ -119,6 +129,7 @@ app.get('/circulatingSupplyBalance', cacheMiddleware(cacheDuration), async (req, } }); + app.listen(port, () => { console.log(`Server is running on port ${port}`); }); diff --git a/src/types.ts b/src/types.ts index 941013d..cd929e2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -47,6 +47,9 @@ export interface NonCirculatingSupplyBalance { network: { chainId: string; }; + currency: { + _id: string; + } tokenContractAddress: string; }[]; }; From 64495d4a472c1baacb0a80af955a36da3ea7aede Mon Sep 17 00:00:00 2001 From: Taha Abbasi Date: Fri, 31 Mar 2023 18:28:55 -0600 Subject: [PATCH 3/3] Removed more config items to env variables and introduced dynamic token lookup --- build/config.js | 8 +++++--- build/server.js | 18 +++++++++++++++++- src/config.ts | 8 +++++--- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/build/config.js b/build/config.js index fd2f430..f15008b 100644 --- a/build/config.js +++ b/build/config.js @@ -20,6 +20,8 @@ const dotenv_1 = require("dotenv"); (0, dotenv_1.config)(); const API_URL = process.env.API_URL; const DATABASE_NAME = process.env.DATABASE_NAME; +const DB_COLLECTION_NAME_NON_CIRCULATING_SUPPLY_ADDRESS = process.env.DB_COLLECTION_NAME_NON_CIRCULATING_SUPPLY_ADDRESS; +const DB_COLLECTION_NAME_CHAIN_NETWORK_MAP = process.env.DB_COLLECTION_NAME_CHAIN_NETWORK_MAP; function getChainIdToNetworkMap() { return __awaiter(this, void 0, void 0, function* () { const MONGODB_URI = process.env.MONGODB_URI; @@ -29,11 +31,11 @@ function getChainIdToNetworkMap() { const client = new mongodb_1.MongoClient(MONGODB_URI); yield client.connect(); const database = client.db(DATABASE_NAME); - const chainIdToNetworkMapCollection = database.collection("chainIdToNetworkMap"); + const chainIdToNetworkMapCollection = database.collection(DB_COLLECTION_NAME_CHAIN_NETWORK_MAP); const result = yield chainIdToNetworkMapCollection.findOne({ appName: "tokenSupply" }); yield client.close(); if (!result) { - throw new Error("chainIdToNetworkMap not found in the database."); + throw new Error(`${DB_COLLECTION_NAME_CHAIN_NETWORK_MAP} not found in the database.`); } const chainIdToNetworkMap = {}; for (const item of result.chainIdToNetworkMap) { @@ -77,7 +79,7 @@ function getNonCirculatingSupplyAddressesConfigInput(currencyId) { const client = new mongodb_1.MongoClient(MONGODB_URI); yield client.connect(); const database = client.db(DATABASE_NAME); - const collection = database.collection("nonCirculatingSupplyAddressesConfig"); + const collection = database.collection(DB_COLLECTION_NAME_NON_CIRCULATING_SUPPLY_ADDRESS); const result = yield collection.findOne({ currency: new mongodb_1.ObjectId(currencyId) }); yield client.close(); if (!result) { diff --git a/build/server.js b/build/server.js index 1569852..1fcfab2 100644 --- a/build/server.js +++ b/build/server.js @@ -8,6 +8,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +var __rest = (this && this.__rest) || function (s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +}; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; @@ -64,7 +75,12 @@ app.get("/nonCirculatingSupplyAddresses", (0, cacheMiddleware_1.default)(cacheDu } const { networks, currencyId } = yield (0, config_1.getNetworkConfigurations)(tokenContractAddress, Number(chainId)); const nonCirculatingSupplyAddressConfigurations = yield (0, config_1.getNonCirculatingSupplyAddressConfigurations)(tokenContractAddress, Number(chainId), currencyId); - res.json(nonCirculatingSupplyAddressConfigurations); + // Remove jsonRpcUrl from the response + const filteredResponse = nonCirculatingSupplyAddressConfigurations.map((_a) => { + var { jsonRpcUrl } = _a, rest = __rest(_a, ["jsonRpcUrl"]); + return rest; + }); + res.json(filteredResponse); })); app.get('/nonCirculatingSupplyBalancesByAddress', (0, cacheMiddleware_1.default)(cacheDuration), (req, res) => __awaiter(void 0, void 0, void 0, function* () { try { diff --git a/src/config.ts b/src/config.ts index 7a5d40a..7c2407b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,6 +12,8 @@ config(); const API_URL = process.env.API_URL; const DATABASE_NAME = process.env.DATABASE_NAME; +const DB_COLLECTION_NAME_NON_CIRCULATING_SUPPLY_ADDRESS = process.env.DB_COLLECTION_NAME_NON_CIRCULATING_SUPPLY_ADDRESS as string; +const DB_COLLECTION_NAME_CHAIN_NETWORK_MAP = process.env.DB_COLLECTION_NAME_CHAIN_NETWORK_MAP as string; async function getChainIdToNetworkMap(): Promise { const MONGODB_URI = process.env.MONGODB_URI; @@ -21,14 +23,14 @@ async function getChainIdToNetworkMap(): Promise { const client = new MongoClient(MONGODB_URI); await client.connect(); const database = client.db(DATABASE_NAME); - const chainIdToNetworkMapCollection = database.collection("chainIdToNetworkMap"); + const chainIdToNetworkMapCollection = database.collection(DB_COLLECTION_NAME_CHAIN_NETWORK_MAP); const result = await chainIdToNetworkMapCollection.findOne({ appName: "tokenSupply" }); await client.close(); if (!result) { - throw new Error("chainIdToNetworkMap not found in the database."); + throw new Error(`${DB_COLLECTION_NAME_CHAIN_NETWORK_MAP} not found in the database.`); } const chainIdToNetworkMap: ChainIdToNetwork = {}; @@ -77,7 +79,7 @@ export async function getNonCirculatingSupplyAddressesConfigInput(currencyId: st const client = new MongoClient(MONGODB_URI); await client.connect(); const database = client.db(DATABASE_NAME); - const collection = database.collection("nonCirculatingSupplyAddressesConfig"); + const collection = database.collection(DB_COLLECTION_NAME_NON_CIRCULATING_SUPPLY_ADDRESS); const result = await collection.findOne({ currency: new ObjectId(currencyId) });