diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f57c84231627..217a5973afff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest steps: - id: file_changes - uses: trilom/file-changes-action@v1.2.3 + uses: trilom/file-changes-action@v1.2.4 with: output: 'json' fileOutput: 'json' diff --git a/.gitignore b/.gitignore index e92356faed0f..c2d649fddf00 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ historical-data.js yarn.lock .DS_Store projects/pooltogether/index.js +.vscode \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0ebfdfcfd18a..000000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "workbench.colorCustomizations": { - "activityBar.background": "#591417", - "titleBar.activeBackground": "#7C1C20", - "titleBar.activeForeground": "#FEFBFB" - } -} \ No newline at end of file diff --git a/dexVolumes/README.md b/dexVolumes/README.md new file mode 100644 index 000000000000..943784cf2701 --- /dev/null +++ b/dexVolumes/README.md @@ -0,0 +1,7 @@ +# DEX volumes + +> **_NOTE:_** Under developement. + +## Test an adapter + +`npm run test-dex 1inch` \ No newline at end of file diff --git a/dexVolumes/cli/testAdapter.js b/dexVolumes/cli/testAdapter.js deleted file mode 100644 index fa5caf65db3e..000000000000 --- a/dexVolumes/cli/testAdapter.js +++ /dev/null @@ -1,12 +0,0 @@ -const { getCurrentBlocks } = require("@defillama/sdk/build/computeTVL/blocks"); -const { volume } = require("../pancakeswap"); - -const test = async () => { - const { timestamp, chainBlocks } = await getCurrentBlocks(); - console.log(chainBlocks, "chainBlocks"); - volume.bsc.fetch(timestamp).then((res) => { - console.log(res); - }); -}; - -test(); diff --git a/dexVolumes/cli/testAdapter.ts b/dexVolumes/cli/testAdapter.ts new file mode 100644 index 000000000000..515c3d0676c3 --- /dev/null +++ b/dexVolumes/cli/testAdapter.ts @@ -0,0 +1,63 @@ +import * as path from 'path' +import type { ChainBlocks, DexAdapter, VolumeAdapter } from '../dexVolume.type'; +import { chainsForBlocks } from "@defillama/sdk/build/computeTVL/blocks"; +import { Chain } from '@defillama/sdk/build/general'; +import handleError from '../../utils/handleError'; +import { checkArguments, getLatestBlockRetry, printVolumes } from './utils'; + +// Add handler to rejections/exceptions +process.on('unhandledRejection', handleError) +process.on('uncaughtException', handleError) + +// Check if all arguments are present +checkArguments(process.argv) + +// Get path of module import +const passedFile = path.resolve(process.cwd(), `dexVolumes/${process.argv[2]}`); +(async () => { + console.info(`Running ${process.argv[2].toUpperCase()} adapter`) + // Import module to test + let module: DexAdapter = (await import(passedFile)).default + + if ("volume" in module) { + // Get adapter + const volumeAdapter = module.volume + const volumes = await runAdapter(volumeAdapter) + printVolumes(volumes) + } else if ("breakdown" in module) { + const breakdownAdapter = module.breakdown + const allVolumes = await Promise.all(Object.entries(breakdownAdapter).map(async ([version, adapter]) => + await runAdapter(adapter).then(res => ({ version, res })) + )) + allVolumes.forEach((promise) => { + console.info(promise.version) + printVolumes(promise.res) + }) + } else console.info("No compatible adapter found") +})() + +async function runAdapter(volumeAdapter: VolumeAdapter) { + // Get chains to check + const chains = Object.keys(volumeAdapter).filter(item => typeof volumeAdapter[item] === 'object'); + // Get lastest block + const chainBlocks: ChainBlocks = {}; + await Promise.all( + chains.map(async (chainRaw) => { + const chain: Chain = chainRaw === "ava" ? "avax" : chainRaw as Chain + if (chainsForBlocks.includes(chain as Chain) || chain === "ethereum") { + const latestBlock = await getLatestBlockRetry(chain) + if (!latestBlock) throw new Error("latestBlock") + chainBlocks[chain] = latestBlock.number - 10 + } + }) + ); + // Get volumes + const unixTimestamp = Math.round(Date.now() / 1000) - 60; + const volumes = await Promise.all(Object.keys(chainBlocks).map( + async chain => volumeAdapter[chain].fetch(unixTimestamp, chainBlocks) + .then(res => { + return { timestamp: unixTimestamp, totalVolume: res.totalVolume, dailyVolume: res.dailyVolume } + }) + )) + return volumes +} \ No newline at end of file diff --git a/dexVolumes/cli/utils.ts b/dexVolumes/cli/utils.ts new file mode 100644 index 000000000000..a707818e0ffa --- /dev/null +++ b/dexVolumes/cli/utils.ts @@ -0,0 +1,29 @@ +import { getLatestBlock } from "@defillama/sdk/build/util"; +import { FetchResult } from "../dexVolume.type"; + +export function checkArguments(argv: string[]) { + if (argv.length < 3) { + console.error(`Missing argument, you need to provide the filename of the adapter to test. + Eg: ts-node dexVolumes/cli/testAdapter.js dexVolumes/myadapter.js`); + process.exit(1); + } +} + +export async function getLatestBlockRetry(chain: string) { + for (let i = 0; i < 5; i++) { + try { + return await getLatestBlock(chain); + } catch (e) { + throw new Error(`Couln't get block heights for chain "${chain}"\n${e}`); + } + } +} + +export function printVolumes(volumes: FetchResult[]) { + volumes.forEach(element => { + console.info("----------") + console.info(`Daily: ${element.dailyVolume}`) + console.info(`Total: ${element.totalVolume}`) + console.info("----------") + }); +} \ No newline at end of file diff --git a/dexVolumes/dexVolume.type.ts b/dexVolumes/dexVolume.type.ts index 8acc54c01c52..0fb48e5ad82c 100644 --- a/dexVolumes/dexVolume.type.ts +++ b/dexVolumes/dexVolume.type.ts @@ -1,5 +1,5 @@ export type ChainBlocks = { - [x: string]: number; + [x: string]: number }; export type FetchResult = { diff --git a/dexVolumes/helper/getBlock.js b/dexVolumes/helper/getBlock.js deleted file mode 100644 index 011378153bf3..000000000000 --- a/dexVolumes/helper/getBlock.js +++ /dev/null @@ -1,34 +0,0 @@ -const sdk = require("@defillama/sdk"); -const retry = require("async-retry"); -const axios = require("axios"); - -async function getBlock(timestamp, chain, chainBlocks, undefinedOk = false) { - if ( - chainBlocks?.[chain] !== undefined || - (process.env.HISTORICAL === undefined && undefinedOk) - ) { - return chainBlocks[chain]; - } else { - if (chain === "celo") { - return Number( - ( - await retry( - async (bail) => - await axios.get( - "https://explorer.celo.org/api?module=block&action=getblocknobytime×tamp=" + - timestamp + - "&closest=before" - ) - ) - ).data.result.blockNumber - ); - } - return sdk.api.util - .lookupBlock(timestamp, { chain }) - .then((blockData) => blockData.block); - } -} - -module.exports = { - getBlock, -}; diff --git a/dexVolumes/helper/getStartTimestamp.js b/dexVolumes/helper/getStartTimestamp.js deleted file mode 100644 index 4fce0429cbab..000000000000 --- a/dexVolumes/helper/getStartTimestamp.js +++ /dev/null @@ -1,38 +0,0 @@ -const { request, gql } = require("graphql-request"); - -const { - DEFAULT_DAILY_VOLUME_FACTORY, - DEFAULT_DAILY_VOLUME_FIELD, -} = require("./getUniSubgraphVolume"); - -const getStartTimestamp = - ({ - endpoints, - chain, - dailyDataField = `${DEFAULT_DAILY_VOLUME_FACTORY}s`, - volumeField = DEFAULT_DAILY_VOLUME_FIELD, - dateField = "date", - first = 1000, - }) => - async () => { - const query = gql` - { - ${dailyDataField}(first: ${first}) { - ${dateField} - ${volumeField} - } - } - `; - - const result = await request(endpoints[chain], query); - - const days = result?.[dailyDataField]; - - const firstValidDay = days.find((data) => data[volumeField] !== "0"); - - return firstValidDay[dateField]; - }; - -module.exports = { - getStartTimestamp, -}; diff --git a/dexVolumes/helper/getStartTimestamp.ts b/dexVolumes/helper/getStartTimestamp.ts new file mode 100644 index 000000000000..bc1c532dc88f --- /dev/null +++ b/dexVolumes/helper/getStartTimestamp.ts @@ -0,0 +1,46 @@ +import { request, gql } from "graphql-request"; + +import { DEFAULT_DAILY_VOLUME_FACTORY, DEFAULT_DAILY_VOLUME_FIELD } from "./getUniSubgraphVolume"; + +interface IGetStartTimestamp { + endpoints: { + [chain: string]: string; + } + chain: string + dailyDataField: string + volumeField: string + dateField?: string + first?: number +} + +const getStartTimestamp = + ({ + endpoints, + chain, + dailyDataField = `${DEFAULT_DAILY_VOLUME_FACTORY}s`, + volumeField = DEFAULT_DAILY_VOLUME_FIELD, + dateField = "date", + first = 1000, + }: IGetStartTimestamp) => + async () => { + const query = gql` + { + ${dailyDataField}(first: ${first}) { + ${dateField} + ${volumeField} + } + } + `; + + const result = await request(endpoints[chain], query); + + const days = result?.[dailyDataField]; + + const firstValidDay = days.find((data: any) => data[volumeField] !== "0"); + + return firstValidDay[dateField]; + }; + +export { + getStartTimestamp, +}; diff --git a/dexVolumes/helper/getUniSubgraphVolume.js b/dexVolumes/helper/getUniSubgraphVolume.ts similarity index 65% rename from dexVolumes/helper/getUniSubgraphVolume.js rename to dexVolumes/helper/getUniSubgraphVolume.ts index 3fa2828ad095..584d094531d4 100644 --- a/dexVolumes/helper/getUniSubgraphVolume.js +++ b/dexVolumes/helper/getUniSubgraphVolume.ts @@ -1,5 +1,7 @@ -const { request, gql } = require("graphql-request"); -const { getBlock } = require("./getBlock"); +import { Chain } from "@defillama/sdk/build/general"; +import { request, gql } from "graphql-request"; +import { getBlock } from "../../projects/helper/getBlock"; +import { ChainBlocks } from "../dexVolume.type"; const getUniqStartOfTodayTimestamp = (date = new Date()) => { var date_utc = Date.UTC( @@ -11,7 +13,7 @@ const getUniqStartOfTodayTimestamp = (date = new Date()) => { date.getUTCSeconds() ); var startOfDay = new Date(date_utc); - var timestamp = startOfDay / 1000; + var timestamp = startOfDay.getTime() / 1000; return Math.floor(timestamp / 86400) * 86400; }; @@ -24,6 +26,24 @@ const DEFAULT_TOTAL_VOLUME_FIELD = "totalVolumeUSD"; const DEFAULT_DAILY_VOLUME_FACTORY = "uniswapDayData"; const DEFAULT_DAILY_VOLUME_FIELD = "dailyVolumeUSD"; +interface IGetChainVolumeParams { + graphUrls: { + [chains: string]: string + }, + totalVolume: { + factory: string, + field: string + }, + dailyVolume?: { + factory: string, + field: string + }, + customDailyVolume?: string, + hasDailyVolume?: boolean + hasTotalVolume?: boolean + getCustomBlock?: (timestamp: number) => Promise +} + function getChainVolume({ graphUrls, totalVolume = { @@ -38,7 +58,7 @@ function getChainVolume({ hasDailyVolume = true, hasTotalVolume = true, getCustomBlock = undefined, -}) { +}: IGetChainVolumeParams) { const totalVolumeQuery = gql` ${totalVolume.factory}( block: { number: $block } @@ -56,18 +76,18 @@ function getChainVolume({ ${dailyVolume.field} } `; - const graphQuery = gql` -query get_volume($block: Int, $id: Int) { - ${hasTotalVolume ? totalVolumeQuery : ""} - ${hasDailyVolume ? dailyVolumeQuery : ""} -} -`; - return (chain) => { - return async (timestamp, chainBlocks) => { + query get_volume($block: Int, $id: Int) { + ${hasTotalVolume ? totalVolumeQuery : ""} + ${hasDailyVolume ? dailyVolumeQuery : ""} + } + `; + return (chain: Chain) => { + return async (timestamp: number, chainBlocks: ChainBlocks) => { const block = - (getCustomBlock && (await getCustomBlock(timestamp))) || - (await getBlock(timestamp, chain, chainBlocks)); + getCustomBlock ? + await getCustomBlock(timestamp) : + await getBlock(timestamp, chain, chainBlocks); const id = getUniswapDateId(); const graphRes = await request(graphUrls[chain], graphQuery, { block, @@ -80,14 +100,14 @@ query get_volume($block: Int, $id: Int) { totalVolume: graphRes[totalVolume.factory][0][totalVolume.field], dailyVolume: hasDailyVolume ? (graphRes?.[dailyVolume.factory]?.[dailyVolume.field] || "0") ?? - undefined + undefined : undefined, }; }; }; } -module.exports = { +export { getUniqStartOfTodayTimestamp, getChainVolume, DEFAULT_TOTAL_VOLUME_FACTORY, diff --git a/dexVolumes/soulswap/index.ts b/dexVolumes/soulswap/index.ts index 14405dac48cc..709e30ee3594 100644 --- a/dexVolumes/soulswap/index.ts +++ b/dexVolumes/soulswap/index.ts @@ -2,6 +2,7 @@ import { getChainVolume } from "../helper/getUniSubgraphVolume"; import { getStartTimestamp } from "../helper/getStartTimestamp"; import { FANTOM } from "../helper/chains"; import { DexVolumeAdapter } from "../dexVolume.type"; +import { Chain } from "@defillama/sdk/build/general"; const endpoints = { // [AVAX]: "https://api.thegraph.com/subgraphs/name/soulswapfinance/avalanche-exchange @@ -35,7 +36,7 @@ const volume = Object.keys(endpoints).reduce( (acc, chain) => ({ ...acc, [chain]: { - fetch: graphs(chain), + fetch: graphs(chain as Chain), start: getStartTimestamp({ ...startTimeQuery, chain }), }, }), diff --git a/dexVolumes/sushiswap/index.ts b/dexVolumes/sushiswap/index.ts index 5ad221603f0d..a63b0979ab98 100644 --- a/dexVolumes/sushiswap/index.ts +++ b/dexVolumes/sushiswap/index.ts @@ -13,6 +13,7 @@ import { XDAI, } from "../helper/chains"; import { DexVolumeAdapter } from "../dexVolume.type"; +import { Chain } from "@defillama/sdk/build/general"; const endpoints = { [ARBITRUM]: @@ -67,7 +68,7 @@ const volume = Object.keys(endpoints).reduce( (acc, chain) => ({ ...acc, [chain]: { - fetch: graphs(chain), + fetch: graphs(chain as Chain), start: getStartTimestamp({ ...startTimeQuery, chain }), }, }), diff --git a/liquidations/test.ts b/liquidations/test.ts index 22d8e3f49bcf..c3ced5acde30 100644 --- a/liquidations/test.ts +++ b/liquidations/test.ts @@ -1,4 +1,4 @@ -import path from "path"; +import * as path from "path"; import axios from "axios"; import { ethers } from "ethers"; import { providers } from "./utils/ethers" diff --git a/package.json b/package.json index 9a3f2087cac6..523b2e281d58 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "test": "echo \"Error: no test specified\" && exit 1", "weeklyChanges": "git pull && git diff $(git log -1 --before=@{7.days.ago} --format=%H) --stat | grep -E \"projects/\" | cut -d / -f 2 | cut -d \" \" -f 1 | uniq | wc -l", "dev": "babel-watch curve.js", - "test-interactive": "node utils/testInteractive" + "test-interactive": "node utils/testInteractive", + "test-dex": "ts-node dexVolumes/cli/testAdapter.ts" }, "author": "", "license": "ISC", diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000000..a0dbe5815db3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + // This is an alias to @tsconfig/node16: https://github.com/tsconfig/bases + "extends": "ts-node/node16/tsconfig.json", + // Most ts-node options can be specified here using their programmatic names. + "ts-node": { + // It is faster to skip typechecking. + // Remove if you want ts-node to do typechecking. + "transpileOnly": true, + "files": true, + "compilerOptions": { + // compilerOptions specified here will override those declared below, + // but *only* in ts-node. Useful if you want ts-node and tsc to use + // different options with a single tsconfig.json. + } + }, + "compilerOptions": { + // typescript options here + "typeRoots": ["./typings/", "./node_modules/@types/"] + } +} diff --git a/typings/getBlock.d.ts b/typings/getBlock.d.ts new file mode 100644 index 000000000000..2cebb73471d0 --- /dev/null +++ b/typings/getBlock.d.ts @@ -0,0 +1,8 @@ +type ChainBlocks = { + [chain: string]: number +} //tmp fix +type Chain = string + +declare module '*/getBlock' { + function getBlock(timestamp: number, chain: Chain, chainBlocks: ChainBlocks, undefinedOk?: boolean): Promise +} \ No newline at end of file diff --git a/typings/handleError.d.ts b/typings/handleError.d.ts new file mode 100644 index 000000000000..4bcdfcff3f4d --- /dev/null +++ b/typings/handleError.d.ts @@ -0,0 +1 @@ +declare module '*/handleError'; \ No newline at end of file diff --git a/typings/index.d.ts b/typings/index.d.ts new file mode 100644 index 000000000000..b367fb26691e --- /dev/null +++ b/typings/index.d.ts @@ -0,0 +1 @@ +declare module '*/helper/chains'; \ No newline at end of file