UNPKG

toucan-sdk

Version:

A JavaScript SDK for Toucan Protocol. Works in the web browser and Node.js.

752 lines (744 loc) 26.9 kB
"use strict"; /** The OffsetHelper's purpose is to simplify the carbon offsetting process. Copyright (C) 2022 Toucan Labs This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const core_1 = require("@urql/core"); const addresses_1 = __importDefault(require("../utils/addresses")); const graphClients_1 = require("../utils/graphClients"); /** * @class ContractInteractions * @description This class helps query Toucan or Toucan-related subgraphs */ class SubgraphInteractions { /** * * @param network network that you want to work on */ constructor(network) { /** * * Note: It's very important that whenever you change the gql query of any existent * methods, you also change the return type of the method (in types/methods.ts) to * match it. * */ // -------------------------------------------------------------------------------- // Batches Subgraph Methods // -------------------------------------------------------------------------------- /** * * @description fetches the batches of a user * @param walletAddress address of user to query for * @returns an array of BatchTokens (they contain different properties of the Batch) */ this.fetchUserBatches = async (walletAddress, first = 100, skip = 0) => { var _a, _b; const query = (0, core_1.gql) ` query ($walletAddress: String) { users(id: $walletAddress) { batchesOwned(orderBy: id, orderDirection: desc) { id tx serialNumber quantity confirmationStatus comments { id comment sender { id } } creator { id } } } } `; const result = await this.graphClient .query(query, { walletAddress, first, skip }) .toPromise(); if (result.error) throw result.error; if ((_b = (_a = result.data) === null || _a === void 0 ? void 0 : _a.users[0]) === null || _b === void 0 ? void 0 : _b.batchesOwned) return result.data.users[0].batchesOwned; return []; }; // -------------------------------------------------------------------------------- // TCO2Tokens Subgraph Methods // -------------------------------------------------------------------------------- /** * * @description fetches properties of a TCO2 * @param id id of the TCO2 to query for; the id happens to be the same as the address e.g.: "0x004090eef602e024b2a6cb7f0c1edda992382994" * @returns a TCO2Detail object with properties of the TCO2 (name, address, etc) */ this.fetchTCO2TokenById = async (id) => { var _a; const query = (0, core_1.gql) ` query ($id: String) { tco2Token(id: $id) { id name symbol address projectVintage { name project { projectId } } } } `; const result = await this.graphClient.query(query, { id }).toPromise(); if (result.error) throw result.error; if ((_a = result.data) === null || _a === void 0 ? void 0 : _a.tco2Tokens) return result.data.tco2Tokens; return; }; /** * * @description fetches properties of a TCO2 * @param symbol full symbol of the TCO2 to query for e.g.: "TCO2-VCS-1718-2013" * @returns a TCO2Detail object with properties of the TCO2 (name, address, etc) */ this.fetchTCO2TokenByFullSymbol = async (symbol) => { var _a; const query = (0, core_1.gql) ` query ($symbol: String) { tco2Tokens(where: { symbol: $symbol }) { id name symbol address projectVintage { name project { projectId } } } } `; const result = await this.graphClient .query(query, { symbol: symbol }) .toPromise(); if (result.error) throw result.error; if ((_a = result.data) === null || _a === void 0 ? void 0 : _a.tco2Tokens[0]) return result.data.tco2Tokens[0]; return; }; /** * * @description fetches TCO2Details of all TCO2s * @returns an array of TCO2Detail objects with properties of the TCO2s (name, address, etc) */ this.fetchAllTCO2Tokens = async (first = 1000, skip = 0) => { var _a; let TCO2Tokens = []; for (;;) { const query = (0, core_1.gql) ` query ($first: Int, $skip: Int) { tco2Tokens(first: $first, skip: $skip) { name symbol address projectVintage { name project { projectId } } } } `; const result = await this.graphClient .query(query, { first, skip }) .toPromise(); if (result.error) throw result.error; if ((_a = result.data) === null || _a === void 0 ? void 0 : _a.tco2Tokens) { TCO2Tokens = TCO2Tokens.concat(result.data.tco2Tokens); if (result.data.tco2Tokens.length === first) { skip += first; continue; } } break; } return TCO2Tokens; }; // -------------------------------------------------------------------------------- // BatchTokens Subgraph Methods // -------------------------------------------------------------------------------- /** * * @description fetches data about BatchTokens that have been bridged * @returns an array of BatchTokens containing different properties like id, serialNumber or quantity */ this.fetchBridgedBatchTokens = async (first = 1000, skip = 0) => { var _a; let BridgedBatchTokens = []; for (;;) { const query = (0, core_1.gql) ` query ($retirementStatus: Int, $first: Int, $skip: Int) { batchTokens( where: { confirmationStatus: $retirementStatus } orderBy: timestamp first: $first skip: $skip ) { id serialNumber quantity creator { id } timestamp tx } } `; const result = await this.graphClient .query(query, { retirementStatus: 2, first, skip, }) .toPromise(); if (result.error) throw result.error; if ((_a = result.data) === null || _a === void 0 ? void 0 : _a.batchTokens) { BridgedBatchTokens = BridgedBatchTokens.concat(result.data.batchTokens); if (result.data.batchTokens.length === first) { skip += first; continue; } } break; } return BridgedBatchTokens; }; // -------------------------------------------------------------------------------- // Retirements Subgraph Methods // -------------------------------------------------------------------------------- /** * * @description fetches retirements made by a user * @param walletAddress address of the user/wallet to query for * @param first how many retirements you want fetched; defaults to 100 * @param skip how many (if any) retirements you want skipped; defaults to 0 * @returns an array of objects containing properties of the retirements like id, creationTx, amount and more */ this.fetchUserRetirements = async (walletAddress, first = 100, skip = 0) => { var _a, _b; const query = (0, core_1.gql) ` query ($walletAddress: String, $first: Int, $skip: Int) { user(id: $walletAddress) { retirementsCreated( first: $first skip: $skip orderBy: timestamp orderDirection: desc ) { id creationTx amount timestamp token { symbol name address projectVintage { name project { projectId } } } certificate { id retiringEntity { id } beneficiary { id } retiringEntityString beneficiaryString retirementMessage createdAt } } } } `; const result = await this.graphClient .query(query, { walletAddress: walletAddress, first, skip }) .toPromise(); if (result.error) throw result.error; if ((_b = (_a = result.data) === null || _a === void 0 ? void 0 : _a.user) === null || _b === void 0 ? void 0 : _b.retirementsCreated) return result.data.user.retirementsCreated; return []; }; // -------------------------------------------------------------------------------- // Redeems Subgraph Methods // -------------------------------------------------------------------------------- /** * * @description fetches redeems of a given pool * @param pool symbol of pool to fetch for * @param first how many redeems you want fetched; defaults to 100 * @param skip how many (if any) redeems you want skipped; defaults to 0 * @returns an array of objects with properties of the redeems like id, amount, timestamp and more */ this.fetchRedeems = async (pool, first = 100, skip = 0) => { var _a; const poolAddress = this.getPoolAddress(pool); const query = (0, core_1.gql) ` query ($poolAddress: String, $first: Int, $skip: Int) { redeems( where: { pool: $poolAddress } first: $first skip: $skip orderBy: timestamp orderDirection: desc ) { id amount timestamp creator { id } token { symbol name address projectVintage { name project { projectId } } } } } `; const result = await this.graphClient .query(query, { poolAddress, first, skip }) .toPromise(); if (result.error) throw result.error; if ((_a = result.data) === null || _a === void 0 ? void 0 : _a.redeems) return result.data.redeems; return []; }; /** * * @description fetches redeems of a given pool and user * @param walletAddress address of the user/wallet to query for * @param pool symbol of pool to fetch for * @param first how many redeems you want fetched; defaults to 100 * @param skip how many (if any) redeems you want skipped; defaults to 0 * @returns an array of objects with properties of the redeems like id, amount, timestamp and more */ this.fetchUserRedeems = async (walletAddress, pool, first = 100, skip = 0) => { var _a, _b; const poolAddress = this.getPoolAddress(pool); const query = (0, core_1.gql) ` query ( $walletAddress: String $poolAddress: String $first: Int $skip: Int ) { user(id: $walletAddress) { redeemsCreated( where: { pool: $poolAddress } first: $first skip: $skip orderBy: timestamp orderDirection: desc ) { id amount timestamp creator { id } token { symbol name address projectVintage { name project { projectId } } } } } } `; const result = await this.graphClient .query(query, { walletAddress, poolAddress, first, skip, }) .toPromise(); if (result.error) throw result.error; if ((_b = (_a = result.data) === null || _a === void 0 ? void 0 : _a.user) === null || _b === void 0 ? void 0 : _b.redeemsCreated) return result.data.user.redeemsCreated; return []; }; // -------------------------------------------------------------------------------- // PooledTCO2Tokens Subgraph Methods // -------------------------------------------------------------------------------- /** * * @description fetches TCO2 tokens that are part of the given pool * @param pool symbol of the pool to fetch for * @param first how many TCO2 tokens you want fetched; defaults to 1000 * @param skip how many (if any) retirements you want skipped; defaults to 0 * @returns an array of objects representing TCO2 tokens and containing properties like name, amount, methodology and more */ this.fetchPoolContents = async (pool, first = 1000, skip = 0) => { var _a; const poolAddress = this.getPoolAddress(pool); const query = (0, core_1.gql) ` query ($poolAddress: String, $first: Int, $skip: Int) { pooledTCO2Tokens( where: { poolAddress: $poolAddress } first: $first skip: $skip orderBy: amount orderDirection: desc ) { token { name projectVintage { id project { methodology standard } } } amount } } `; const result = await this.graphClient .query(query, { poolAddress, first, skip, }) .toPromise(); if (result.error) throw result.error; if ((_a = result.data) === null || _a === void 0 ? void 0 : _a.pooledTCO2Tokens) return result.data.pooledTCO2Tokens; return []; }; // -------------------------------------------------------------------------------- // Projects Subgraph Methods // -------------------------------------------------------------------------------- /** * * @description fetches a project by its id * @param id id of the project to fetch; e.g.: "10" * @returns an object with properties of the Project like projectId, region, standard and more */ this.fetchProjectById = async (id) => { var _a; const query = (0, core_1.gql) ` query ($id: String) { project(id: $id) { projectId region standard methodology vintages { id } } } `; const result = await this.graphClient.query(query, { id }).toPromise(); if (result.error) throw result.error; if ((_a = result.data) === null || _a === void 0 ? void 0 : _a.project) return result.data.project; return; }; // -------------------------------------------------------------------------------- // Aggregations Subgraph Methods // -------------------------------------------------------------------------------- /** * * @description fetch all aggregations (including, for example, tco2TotalRetired or totalCarbonBridged) * @returns an array of Aggregation objects containing properties like id, key, value */ this.fetchAggregations = async () => { var _a; const query = (0, core_1.gql) ` { aggregations { id key value } } `; const result = await this.graphClient.query(query).toPromise(); if (result.error) throw result.error; if ((_a = result.data) === null || _a === void 0 ? void 0 : _a.aggregations) return result.data.aggregations; return []; }; // -------------------------------------------------------------------------------- // Other Subgraph Methods // -------------------------------------------------------------------------------- /** * * @description if pre-made queries to Toucan's Subgraph don't fit all your needs; use this for custom queries * @param query a gql formated GraphQL query * @param params any parameters you may want to pass to the query * @returns all data fetched from query; you can use generics to declare what type to expect (if you're a fan of TS) */ this.fetchCustomQuery = async (query, params) => { const result = await this.graphClient .query(query, { ...(params !== null && params !== void 0 ? params : {}), }) .toPromise(); if (result.error) throw result.error; if (result.data) return result.data; return; }; // -------------------------------------------------------------------------------- // -------------------------------------------------------------------------------- // Price / Sushiswap related methods // -------------------------------------------------------------------------------- // -------------------------------------------------------------------------------- this.extractPriceInUSD = (pairs0, pairs1) => { if (pairs0 && pairs0.length > 0) { for (const pair of pairs0) { if (pair.token1.symbol === "USDC") { return parseFloat(pair.token1Price); } } } if (pairs1 && pairs1.length > 0) { for (const pair of pairs1) { if (pair.token0.symbol === "USDC") { return parseFloat(pair.token0Price); } } } return 0; }; this.fetchTokenPrice = async (tokenAddress) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; const DexGraphClient = (0, graphClients_1.getDexGraphClient)(this.network); const senderQuery = this.network === "celo" || this.network === "alfajores" ? (0, core_1.gql) ` query tokenPairsQuery( $id: String! $skip: Int $block: Block_height ) { pairs0: pairs( first: 1000 skip: $skip orderBy: reserveUSD orderDirection: desc token0: $id block: $block orderBy: reserveUSD orderDirection: desc ) { ...pairFields } pairs1: pairs( first: 1000 skip: $skip orderBy: reserveUSD orderDirection: desc token1: $id block: $block orderBy: reserveUSD orderDirection: desc ) { ...pairFields } } fragment pairFields on Pair { id reserveUSD reserveCELO volumeUSD untrackedVolumeUSD trackedReserveUSD token0 { ...PairToken } token1 { ...PairToken } reserve0 reserve1 token0Price token1Price totalSupply txCount } fragment PairToken on Token { id name symbol totalSupply } ` : (0, core_1.gql) ` query tokenPairsQuery( $id: String! $skip: Int $block: Block_height ) { pairs0: pairs( first: 1000 skip: $skip orderBy: reserveUSD orderDirection: desc token0: $id block: $block orderBy: reserveUSD orderDirection: desc ) { ...pairFields } pairs1: pairs( first: 1000 skip: $skip orderBy: reserveUSD orderDirection: desc token1: $id block: $block orderBy: reserveUSD orderDirection: desc ) { ...pairFields } } fragment pairFields on Pair { id reserveUSD reserveETH volumeUSD untrackedVolumeUSD trackedReserveETH token0 { ...PairToken } token1 { ...PairToken } reserve0 reserve1 token0Price token1Price totalSupply txCount timestamp } fragment PairToken on Token { id name symbol totalSupply derivedETH } `; const result = await DexGraphClient.query(senderQuery, { id: tokenAddress, }).toPromise(); if (result.error) throw result.error; const priceInUSD = this.extractPriceInUSD((_a = result.data) === null || _a === void 0 ? void 0 : _a.pairs0, (_b = result.data) === null || _b === void 0 ? void 0 : _b.pairs1); const liquidityUSD = ((_d = (_c = result.data) === null || _c === void 0 ? void 0 : _c.pairs0) === null || _d === void 0 ? void 0 : _d.reduce((acc, item) => { acc += parseFloat(item.reserveUSD); return acc; }, 0)) + ((_f = (_e = result.data) === null || _e === void 0 ? void 0 : _e.pairs1) === null || _f === void 0 ? void 0 : _f.reduce((acc, item) => { acc += parseFloat(item.reserveUSD); return acc; }, 0)); const volumeUSD = ((_h = (_g = result.data) === null || _g === void 0 ? void 0 : _g.pairs0) === null || _h === void 0 ? void 0 : _h.reduce((acc, item) => { acc += parseFloat(item.volumeUSD); return acc; }, 0)) + ((_k = (_j = result.data) === null || _j === void 0 ? void 0 : _j.pairs1) === null || _k === void 0 ? void 0 : _k.reduce((acc, item) => { acc += parseFloat(item.volumeUSD); return acc; }, 0)); return [priceInUSD, liquidityUSD, volumeUSD]; }; this.fetchTokenPriceOnDex = async (pool) => { const tokenAddress = this.getPoolAddress(pool); let url = null; const [price, liquidityUSD, volumeUSD] = await this.fetchTokenPrice(tokenAddress); if (!price) throw new Error(`No price found for ${pool}`); url = this.network === "celo" || this.network === "alfajores" ? `https://info.ubeswap.org/token/${tokenAddress}` : `https://app.sushi.com/analytics/tokens/${tokenAddress}`; return { price, url, liquidityUSD, volumeUSD }; }; // -------------------------------------------------------------------------------- // Internal methods // -------------------------------------------------------------------------------- /** * * @description gets the contract of a pool token based on the symbol * @param pool symbol of the pool (token) to use * @returns a ethers.contract to interact with the pool */ this.getPoolAddress = (pool) => { return pool == "BCT" ? this.addresses.bct : this.addresses.nct; }; this.network = network; this.addresses = addresses_1.default[this.network]; this.graphClient = (0, graphClients_1.getToucanGraphClient)(this.network); } } exports.default = SubgraphInteractions;