diff --git a/.gitignore b/.gitignore index eb63b2cc55..f831bc960b 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,4 @@ dist .vscode /.env.development.local.example /.env +.vercel diff --git a/api/health.py b/api/health.py deleted file mode 100644 index ae977b2b53..0000000000 --- a/api/health.py +++ /dev/null @@ -1,166 +0,0 @@ -import requests -import dateutil.parser -from datetime import datetime -from dateutil.tz import tzutc -from flask import Flask, jsonify, abort - - -class Oracle: - def __init__(self, baseUrl, token) -> None: - self.baseUrl = baseUrl - self.token = token - - def _composableExchangeRateSubquery(self): - return """{ oracleUpdates: oracleUpdates( - where: {type_eq: ExchangeRate, typeKey: { token_eq: %s }}, - orderBy: timestamp_DESC, - limit: 1 - ) { - oracleId - timestamp - updateValue - height { - absolute - active - } - } - } - """ % ( - self.token - ) - - def latestUpdateSeconds(self): - payload = {"query": self._composableExchangeRateSubquery(), "variables": None} - resp = requests.post(self.baseUrl, json=payload).json() - oracles = resp["data"]["oracleUpdates"] - - f = lambda x: ( - datetime.now(tz=tzutc()) - dateutil.parser.isoparse(x["timestamp"]) - ).total_seconds() - - return list(map(f, oracles)) - - def isHealthy(self): - return self.latestUpdateSeconds()[0] < 1800 - - -class Relayer: - def __init__(self, baseUrl) -> None: - self.baseUrl = baseUrl - - def _lastRelayedBlock(self): - q = """ - query MyQuery { - relayedBlocks(limit: 1, orderBy: id_DESC) { - id - timestamp - } - } - """ - payload = {"query": q, "variables": None} - resp = requests.post(self.baseUrl, json=payload) - return resp.json()["data"]["relayedBlocks"][0] - - def _lastChainBlock(self): - resp = requests.get("https://btc-mainnet.interlay.io/blocks/tip/height") - return resp.json() - - def latestUpdate(self): - chainHeight = self._lastChainBlock() - paraHeight = self._lastRelayedBlock() - - chainDiff = chainHeight - int(paraHeight["id"]) - secDiff = ( - datetime.now(tz=tzutc()) - dateutil.parser.isoparse(paraHeight["timestamp"]) - ).total_seconds() - return {"chainHeightDiff": chainDiff, "secondsDiff": secDiff} - - def isHealthy(self): - status = self.latestUpdate() - return status["chainHeightDiff"] < 3 and status["secondsDiff"] < 7200 # 2hrs - - -class Vault: - def __init__(self, baseUrl) -> None: - self.baseUrl = baseUrl - - def _latestVaults(self): - q = """ - query MyQuery { - vaults(limit: 10, orderBy: registrationBlock_active_DESC) { - id - registrationTimestamp - } - } - """ - payload = {"query": q, "variables": None} - resp = requests.post(self.baseUrl, json=payload) - return resp.json()["data"]["vaults"] - - def isHealthy(self): - vaults = self._latestVaults() - return len(self._latestVaults()) > 0 - - -KSM_URL = "https://api-kusama.interlay.io/graphql/graphql" -INTR_URL = "https://api.interlay.io/graphql/graphql" -TESTNET_INTR = "https://api-testnet.interlay.io/graphql/graphql" -TESTNET_KINT = "https://api-dev-kintsugi.interlay.io/graphql/graphql" - -app = Flask(__name__) - - -@app.route("/_health//oracle", methods=["GET"]) -def get_oracle_health(chain): - def oracle(): - if chain == "kint": - return Oracle(KSM_URL, "KSM") - elif chain == "intr": - return Oracle(INTR_URL, "DOT") - elif chain == "testnet_kint": - return Oracle(TESTNET_KINT, "KSM") - elif chain == "testnet_intr": - return Oracle(TESTNET_INTR, "DOT") - else: - abort(404) - - return jsonify(oracle().isHealthy()) - - -@app.route("/_health//relay", methods=["GET"]) -def get_relay_health(chain): - def relay(): - if chain == "kint": - return Relayer(KSM_URL) - elif chain == "intr": - return Relayer(INTR_URL) - elif chain == "testnet_kint": - return Relayer(TESTNET_KINT) - elif chain == "testnet_intr": - return Relayer(TESTNET_INTR) - else: - abort(404) - - return jsonify(relay().isHealthy()) - - -@app.route("/_health//vault", methods=["GET"]) -def get_vault_health(chain): - def vault(): - if chain == "kint": - return Vault(KSM_URL) - elif chain == "intr": - return Vault(INTR_URL) - elif chain == "testnet_kint": - return Vault(TESTNET_KINT) - elif chain == "testnet_intr": - return Vault(TESTNET_INTR) - else: - abort(404) - - return jsonify(vault().isHealthy()) - - -if __name__ == "__main__": - o = Relayer(INTR_URL) - print(o.isHealthy()) diff --git a/api/requirements.txt b/api/requirements.txt deleted file mode 100644 index 62fb9584d9..0000000000 --- a/api/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -Flask -flask-cors -requests -python-dateutil diff --git a/api/supply_info.js b/api/supply_info.js new file mode 100644 index 0000000000..3826589d2c --- /dev/null +++ b/api/supply_info.js @@ -0,0 +1,81 @@ +const KINT_SUBSCAN_URL = 'https://kintsugi.api.subscan.io/' +const INTR_SUBSCAN_URL = 'https://interlay.api.subscan.io/' + +const KINT_API_KEY = process.env.SUBSCAN_API_KEY +const INTR_API_KEY = process.env.INTR_SUBSCAN_API_KEY + +function fromDecimals (val, decimals) { + return val / Math.pow(10, decimals) +} + +async function subscanRequest (url, method = 'GET', json = null) { + const headers = { + 'Content-Type': 'application/json', + 'X-API-KEY': url.startsWith(INTR_SUBSCAN_URL) ? INTR_API_KEY : KINT_API_KEY + } + + const options = { method, headers } + if (json) { + options.body = JSON.stringify(json) + } + + const response = await fetch(url, options) + return await response.json() +} + +export default async function (request, response) { + if (request.method !== 'GET') { + return response.status(400).send('Bad Request') + } + const path = request.url.split('?')[0] + switch (path) { + case '/supply/intr-total-supply': { + const INTR_SUPPLY = 1_000_000_000 + return response + .status(200) + .send(INTR_SUPPLY.toString()) + } + case '/supply/kint-total-supply': { + const KINT_SUPPLY = 10_000_000 + return response + .status(200) + .send(KINT_SUPPLY.toString()) + } + case '/supply/intr-circ-supply': { + const unvestedSupply = fromDecimals(parseInt((await subscanRequest(INTR_SUBSCAN_URL + 'api/scan/token')).data.detail.INTR.available_balance), 10) + const systemAccountsSupply = (await subscanRequest(INTR_SUBSCAN_URL + 'api/scan/accounts', 'POST', + { filter: 'system', row: 25, page: 0 })).data.list + .reduce((acc, curr) => acc + parseFloat(curr.balance), 0) + const circulatingSupply = unvestedSupply - systemAccountsSupply + + return response + .status(200) + .send(circulatingSupply.toString()) + } + case '/supply/kint-circ-supply': { + const kintUnvestedSupply = fromDecimals(parseInt((await subscanRequest(KINT_SUBSCAN_URL + 'api/scan/token')).data.detail.KINT.available_balance), 12) + const kintSystemAccountsSupply = (await subscanRequest(KINT_SUBSCAN_URL + 'api/scan/accounts', 'POST', + { filter: 'system', row: 25, page: 0 })).data.list + .reduce((acc, curr) => acc + parseFloat(curr.balance), 0) + const kintCirculatingSupply = kintUnvestedSupply - kintSystemAccountsSupply + + return response + .status(200) + .send(kintCirculatingSupply.toString()) + } + case '/supply/ibtc-supply': { + const ibtcSupply = fromDecimals(parseInt((await subscanRequest(INTR_SUBSCAN_URL + 'api/scan/token')).data.detail.IBTC.available_balance), 10) + return response + .status(200) + .send(ibtcSupply.toString()) + } + case '/supply/kbtc-supply': { + const kbtcSupply = fromDecimals(parseInt((await subscanRequest(KINT_SUBSCAN_URL + 'api/scan/token')).data.detail.KBTC.available_balance), 8) + return response + .status(200) + .send(kbtcSupply.toString()) + } + default: + response.status(404).send('Not Found') + } +}; diff --git a/api/supply_info.py b/api/supply_info.py deleted file mode 100644 index 301be45842..0000000000 --- a/api/supply_info.py +++ /dev/null @@ -1,125 +0,0 @@ -from flask import Flask -import requests -import os - -KINT_SUBSCAN_URL = "https://kintsugi.api.subscan.io/" -INTR_SUBSCAN_URL = "https://interlay.api.subscan.io/" - -KINT_API_KEY = os.environ.get("SUBSCAN_API_KEY") -INTR_API_KEY = os.environ.get("INTR_SUBSCAN_API_KEY") - -CROWDLOAN_VESTING_END = 4838400 - - -def from_12_decimals(val): - return val / 1_000_000_000_000 - - -def from_10_decimals(val): - return val / 10_000_000_000 - - -def from_8_decimals(val): - return val / 100_000_000 - - -def subscan_get_request(url): - api_key = KINT_API_KEY - if url.startswith(INTR_SUBSCAN_URL): - api_key = INTR_API_KEY - - headers_dict = {"content-type": "application/json", "X-API-KEY": api_key} - return requests.get(url, headers=headers_dict) - - -def subscan_post_request(url, json): - api_key = KINT_API_KEY - if url.startswith(INTR_SUBSCAN_URL): - api_key = INTR_API_KEY - - headers_dict = {"content-type": "application/json", "X-API-KEY": api_key} - return requests.post(url, json=json, headers=headers_dict) - - -def max_crowdloan_vested(): - current_block = int( - subscan_get_request(INTR_SUBSCAN_URL + "api/scan/metadata").json()["data"][ - "blockNum" - ] - ) - return 0.3 + (current_block / CROWDLOAN_VESTING_END) - - -app = Flask(__name__) - - -@app.after_request -def add_header(response): - response.cache_control.max_age = 3600 - return response - - -@app.route("/supply/kint-circ-supply", methods=["GET"]) -def get_kint_circ_supply(): - token_info_subscan = subscan_get_request( - KINT_SUBSCAN_URL + "api/scan/token" - ).json()["data"]["detail"] - unvested_supply = from_12_decimals( - int(token_info_subscan["KINT"]["available_balance"]) - ) - system_accounts_subscan = subscan_post_request( - KINT_SUBSCAN_URL + "api/scan/accounts", - json={"filter": "system", "row": 25, "page": 0}, - ).json()["data"]["list"] - system_accounts_supply = sum([float(a["balance"]) for a in system_accounts_subscan]) - circulating_supply = unvested_supply - system_accounts_supply - return str(circulating_supply) - - -@app.route("/supply/kint-total-supply", methods=["GET"]) -def get_kint_total_supply(): - return str(10_000_000) - - -@app.route("/supply/kbtc-supply", methods=["GET"]) -def get_kbtc_supply(): - token_info_subscan = subscan_get_request( - KINT_SUBSCAN_URL + "api/scan/token" - ).json()["data"]["detail"] - kBTC_supply = from_8_decimals(int(token_info_subscan["KBTC"]["available_balance"])) - return str(kBTC_supply) - - -@app.route("/supply/intr-circ-supply", methods=["GET"]) -def get_intr_circ_supply(): - token_info_subscan = subscan_get_request( - INTR_SUBSCAN_URL + "api/scan/token" - ).json()["data"]["detail"] - unvested_supply = from_10_decimals( - int(token_info_subscan["INTR"]["available_balance"]) - ) - system_accounts_subscan = subscan_post_request( - INTR_SUBSCAN_URL + "api/scan/accounts", - json={"filter": "system", "row": 25, "page": 0}, - ).json()["data"]["list"] - system_accounts_supply = sum([float(a["balance"]) for a in system_accounts_subscan]) - circulating_supply = unvested_supply - system_accounts_supply - return str(circulating_supply) - - -@app.route("/supply/intr-total-supply", methods=["GET"]) -def get_intr_total_supply(): - return str(1_000_000_000) - - -@app.route("/supply/ibtc-supply", methods=["GET"]) -def get_ibtc_supply(): - token_info_subscan = subscan_get_request( - INTR_SUBSCAN_URL + "api/scan/token" - ).json()["data"]["detail"] - kBTC_supply = from_10_decimals(int(token_info_subscan["IBTC"]["available_balance"])) - return str(kBTC_supply) - - -if __name__ == "__main__": - app.run() diff --git a/package.json b/package.json index 113cf973c5..48834f4d4a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "interbtc-ui", - "version": "2.41.2", + "version": "2.41.4", "private": true, "dependencies": { "@craco/craco": "^6.1.1", diff --git a/src/hooks/api/xcm/xcm-endpoints.ts b/src/hooks/api/xcm/xcm-endpoints.ts index ba6ebbd19f..5ba9fb9ba6 100644 --- a/src/hooks/api/xcm/xcm-endpoints.ts +++ b/src/hooks/api/xcm/xcm-endpoints.ts @@ -5,7 +5,7 @@ type XCMEndpointsRecord = Record; const XCMEndpoints: XCMEndpointsRecord = { acala: ['wss://acala-rpc-1.aca-api.network', 'wss://acala-rpc-3.aca-api.network/ws', 'wss://acala-rpc.dwellir.com'], astar: ['wss://rpc.astar.network', 'wss://astar-rpc.dwellir.com'], - bifrost: ['wss://bifrost-rpc.dwellir.com'], + bifrost: ['wss://bifrost-rpc.dwellir.com', 'wss://us.bifrost-rpc.liebi.com/ws', 'wss://bifrost-rpc.liebi.com/ws'], bifrost_polkadot: ['wss://hk.p.bifrost-rpc.liebi.com/ws'], heiko: ['wss://heiko-rpc.parallel.fi'], hydra: ['wss://rpc.hydradx.cloud', 'wss://hydradx-rpc.dwellir.com'], diff --git a/vercel.json b/vercel.json index 38fc915762..9413e0f124 100644 --- a/vercel.json +++ b/vercel.json @@ -1,8 +1,8 @@ { "functions": { - "api/*.py": { + "api/supply_info.js": { "memory": 128, - "maxDuration": 5 + "maxDuration": 10 }, "api/terms.js": { "memory": 256, @@ -24,11 +24,7 @@ "rewrites": [ { "source": "/supply/(.*)", - "destination": "/api/supply_info.py" - }, - { - "source": "/_health/(.*)", - "destination": "/api/health.py" + "destination": "/api/supply_info.js" }, { "source": "/marketdata/(.*)",