diff --git a/dexs/nado-perp/index.ts b/dexs/nado-perp/index.ts new file mode 100644 index 0000000000..b2b85bef47 --- /dev/null +++ b/dexs/nado-perp/index.ts @@ -0,0 +1,110 @@ +import { FetchOptions, SimpleAdapter } from "../../adapters/types"; +import { CHAIN } from "../../helpers/chains"; +import { httpGet, httpPost } from "../../utils/fetchURL"; + +interface IProducts { + perp_products: number[]; +} + +// Nado (Private Alpha) +// Production API on Ink Mainnet +const gatewayInkUrl = "https://gateway.prod.nado.xyz/v1"; +const archiveInkUrl = "https://archive.prod.nado.xyz/v1"; + +type TURL = { + [s: string]: { + gateway: string; + archive: string; + }; +}; + +const url: TURL = { + [CHAIN.INK]: { + gateway: gatewayInkUrl, + archive: archiveInkUrl, + }, +}; + +const fetchValidSymbols = async ( + fetchOptions: FetchOptions +): Promise => { + const symbols = await httpGet(`${url[fetchOptions.chain].gateway}/symbols`); + return symbols.map((product: { product_id: number }) => product.product_id); +}; + +const fetchProducts = async ( + fetchOptions: FetchOptions +): Promise => { + const validSymbols = await fetchValidSymbols(fetchOptions); + const allProducts = ( + await httpGet(`${url[fetchOptions.chain].gateway}/query?type=all_products`) + ).data; + return { + perp_products: allProducts.perp_products + .map((product: { product_id: number }) => product.product_id) + .filter((id: number) => validSymbols.includes(id)) + }; +}; + +const computeVolume = async ( + timestamp: number, + productIds: number[], + fetchOptions: FetchOptions +) => { + if (!productIds.length) { + return { dailyVolume: undefined }; + } + + const response = await httpPost(url[fetchOptions.chain].archive, { + market_snapshots: { + interval: { + count: 2, + granularity: 86400, + max_time: timestamp, + }, + product_ids: productIds, + }, + }); + + const snapshots = response?.snapshots; + if (!Array.isArray(snapshots) || snapshots.length < 2) { + return { dailyVolume: undefined }; + } + + const lastCumulativeVolumes: Record = + snapshots[0].cumulative_volumes; + const prevCumulativeVolumes: Record = + snapshots[1].cumulative_volumes; + const totalVolume = Number( + Object.values(lastCumulativeVolumes).reduce( + (acc, current) => acc + BigInt(current), + BigInt(0) + ) / BigInt(10 ** 18) + ); + const totalVolumeOneDayAgo = Number( + Object.values(prevCumulativeVolumes).reduce( + (acc, current) => acc + BigInt(current), + BigInt(0) + ) / BigInt(10 ** 18) + ); + const dailyVolume = totalVolume - totalVolumeOneDayAgo; + + return { dailyVolume }; +}; + + +const fetch = async (timestamp: number, _: any, fetchOptions: FetchOptions) => { + const products = await fetchProducts(fetchOptions); + const perpVolumes = await computeVolume(timestamp, products.perp_products, fetchOptions); + return { dailyVolume: perpVolumes.dailyVolume }; +}; + + +const adapter: SimpleAdapter = { + version: 1, + fetch, + chains: [CHAIN.INK], + start: '2025-11-15', +}; + +export default adapter; diff --git a/dexs/nado-spot/index.ts b/dexs/nado-spot/index.ts new file mode 100644 index 0000000000..4d18636f10 --- /dev/null +++ b/dexs/nado-spot/index.ts @@ -0,0 +1,124 @@ +import { FetchOptions, SimpleAdapter } from "../../adapters/types"; +import { CHAIN } from "../../helpers/chains"; +import { httpGet, httpPost } from "../../utils/fetchURL"; + +interface IProducts { + spot_products: number[]; + margined_products: number[]; +} + +// Nado (Private Alpha) +// Production API on Ink Mainnet +const gatewayInkUrl = "https://gateway.prod.nado.xyz/v1"; +const archiveInkUrl = "https://archive.prod.nado.xyz/v1"; + +type TURL = { + [s: string]: { + gateway: string; + archive: string; + }; +}; + +const url: TURL = { + [CHAIN.INK]: { + gateway: gatewayInkUrl, + archive: archiveInkUrl, + }, +}; + +const fetchValidSymbols = async ( + fetchOptions: FetchOptions +): Promise => { + const symbols = await httpGet(`${url[fetchOptions.chain].gateway}/symbols`); + return symbols.map((product: { product_id: number }) => product.product_id); +}; + +const fetchProducts = async ( + fetchOptions: FetchOptions +): Promise => { + const validSymbols = await fetchValidSymbols(fetchOptions); + const allProducts = ( + await httpGet(`${url[fetchOptions.chain].gateway}/query?type=all_products`) + ).data; + return { + spot_products: allProducts.spot_products + .map((product: { product_id: number }) => product.product_id) + .filter((id: number) => validSymbols.includes(id) && id > 0), + margined_products: allProducts.spot_products + .map((product: { product_id: number }) => product.product_id) + .filter((id: number) => validSymbols.includes(id) && id > 0), + + }; +}; + +const computeVolume = async ( + timestamp: number, + productIds: number[], + fetchOptions: FetchOptions +) => { + if (!productIds.length) { + return { + dailyVolume: undefined, + }; + } + + const response = await httpPost(url[fetchOptions.chain].archive, { + market_snapshots: { + interval: { + count: 2, + granularity: 86400, + max_time: timestamp, + }, + product_ids: productIds, + }, + }); + + const snapshots = response?.snapshots; + if (!Array.isArray(snapshots) || snapshots.length < 2) { + return { + dailyVolume: undefined, + }; + } + + const lastCumulativeVolumes: Record = + snapshots[0].cumulative_volumes; + const prevCumulativeVolumes: Record = + snapshots[1].cumulative_volumes; + const totalVolume = Number( + Object.values(lastCumulativeVolumes).reduce( + (acc, current) => acc + BigInt(current), + BigInt(0) + ) / BigInt(10 ** 18) + ); + const totalVolumeOneDayAgo = Number( + Object.values(prevCumulativeVolumes).reduce( + (acc, current) => acc + BigInt(current), + BigInt(0) + ) / BigInt(10 ** 18) + ); + const dailyVolume = totalVolume - totalVolumeOneDayAgo; + + return { dailyVolume }; +}; + + +const fetch = async (timestamp: number, _: any, fetchOptions: FetchOptions) => { + const products = await fetchProducts(fetchOptions); + + const [spotVolumes, marginedVolumes] = await Promise.all([ + computeVolume(timestamp, products.spot_products, fetchOptions), + computeVolume(timestamp, products.margined_products, fetchOptions), + ]); + const dailyVolume = (spotVolumes.dailyVolume ?? 0) + (marginedVolumes.dailyVolume ?? 0); + return { dailyVolume }; +}; + + +const adapter: SimpleAdapter = { + version: 1, + fetch, + chains: [CHAIN.INK], + start: '2025-11-15', +}; + +export default adapter; diff --git a/fees/nado.ts b/fees/nado.ts new file mode 100644 index 0000000000..fd3d414174 --- /dev/null +++ b/fees/nado.ts @@ -0,0 +1,135 @@ +import { CHAIN } from "../helpers/chains"; +import { Adapter, FetchOptions, FetchResultFees } from "../adapters/types"; +import { httpPost } from "../utils/fetchURL"; + +interface MarketSnapshots { + interval: { + count: number; + granularity: number; + max_time: number; + }; +} + +interface QueryBody { + market_snapshots: MarketSnapshots; +} + +interface IData { + [s: string]: string; +} + +interface Snapshot { + [s: string]: IData; +} + +interface Response { + snapshots: Snapshot[]; +} + +// Nado (Private Alpha) +// Production API on Ink Mainnet +const archiveInkUrl = "https://archive.prod.nado.xyz/v1"; + +type TURL = { + [s: string]: string; +}; + +const url: TURL = { + [CHAIN.INK]: archiveInkUrl, +}; + +const query = async ( + max_time: number, + fetchOptions: FetchOptions +): Promise => { + const body: QueryBody = { + market_snapshots: { + interval: { + count: 2, + granularity: 86400, + max_time: max_time, + }, + }, + }; + + const response = await httpPost(url[fetchOptions.chain], body); + return response; +}; + +const sumAllProductStats = (stat_map: IData): number => { + let stat_sum = 0; + for (const v of Object.values(stat_map)) { + stat_sum += parseInt(v); + } + return stat_sum / 1e18; +}; + +const get24hrStat = async ( + field: string, + max_time: number, + fetchOptions: FetchOptions +): Promise => { + const response = await query(max_time, fetchOptions); + const cur_res: Snapshot = response.snapshots[0]; + const past_res: Snapshot = response.snapshots[1]; + return ( + sumAllProductStats(cur_res[field]) - sumAllProductStats(past_res[field]) + ); +}; + +const get24hrFees = async ( + max_time: number, + fetchOptions: FetchOptions +): Promise => { + const fees = await get24hrStat( + "cumulative_taker_fees", + max_time, + fetchOptions + ); + const sequencer_fees = await get24hrStat( + "cumulative_sequencer_fees", + max_time, + fetchOptions + ); + return fees - sequencer_fees; +}; + +const get24hrRevenue = async ( + max_time: number, + fetchOptions: FetchOptions +): Promise => { + const fees = await get24hrFees(max_time, fetchOptions); + const rebates = await get24hrStat( + "cumulative_maker_fees", + max_time, + fetchOptions + ); + return fees + rebates; +}; + +const fetch = async ( + timestamp: number, + _: any, + fetchOptions: FetchOptions +): Promise => { + const dailyFees = await get24hrFees(timestamp, fetchOptions); + const dailyRevenue = await get24hrRevenue(timestamp, fetchOptions); + + return { dailyFees, dailyRevenue, dailyProtocolRevenue: dailyRevenue }; +}; + +const methodology = { + Fees: 'spot and perp trading fees paid by users', + Revenue: 'trading fees - maker rebates goes to the protocol treasury', + ProtocolRevenue: 'net trading fees goes to the protocol treasury', +} + +const adapter: Adapter = { + version: 1, + fetch, + chains: [CHAIN.INK], + start: '2025-11-15', + methodology, +}; + +export default adapter;