toucan-sdk
Version:
A JavaScript SDK for Toucan Protocol. Works in the web browser and Node.js.
752 lines (744 loc) • 26.9 kB
JavaScript
"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;