From e93156844f6d245f73df5ac06fb3732f83c6c1f1 Mon Sep 17 00:00:00 2001 From: green-jay Date: Fri, 10 Nov 2023 16:15:25 +0100 Subject: [PATCH 1/3] add fees endpoint --- app/routes/hydradx-ui/v1/stats/fees.mjs | 72 ++++++++++++++++ queries/hydradx-ui/v1/stats/fees.sql | 108 ++++++++++++++++++++++++ variables.mjs | 4 + 3 files changed, 184 insertions(+) create mode 100644 app/routes/hydradx-ui/v1/stats/fees.mjs create mode 100644 queries/hydradx-ui/v1/stats/fees.sql diff --git a/app/routes/hydradx-ui/v1/stats/fees.mjs b/app/routes/hydradx-ui/v1/stats/fees.mjs new file mode 100644 index 0000000..df0a62c --- /dev/null +++ b/app/routes/hydradx-ui/v1/stats/fees.mjs @@ -0,0 +1,72 @@ +import yesql from "yesql"; +import path from "path"; +import { dirname } from "../../../../../variables.mjs"; +import { CACHE_SETTINGS } from "../../../../../variables.mjs"; +import { cachedFetch } from "../../../../../helpers/cache_helpers.mjs"; + +const sqlQueries = yesql(path.join(dirname(), "queries/hydradx-ui/v1/stats"), { + type: "pg", +}); + +export const VALID_TIMEFRAMES = ['day', 'week', 'month', 'year']; + +export default async (fastify, opts) => { + fastify.route({ + url: "/fees/:asset?", + method: ["GET"], + schema: { + description: "Omnipool trading fees for the HydraDX stats page.", + tags: ["hydradx-ui/v1"], + params: { + type: "object", + properties: { + asset: { + type: "integer", + description: "Asset (id). Leave empty for all assets.", + }, + }, + }, + querystring: { + type: "object", + properties: { + timeframe: { + type: "string", + enum: VALID_TIMEFRAMES, + default: "1mon", + }, + }, + }, + response: { + 200: { + description: "Success Response", + type: "array", + items: { + type: "object", + properties: { + accrued_fees_usd: { type: "number" }, + projected_apy_perc: { type: "number" }, + }, + }, + }, + }, + }, + handler: async (request, reply) => { + const asset = request.params.asset ? request.params.asset : null; + const timeframe = request.query.timeframe; + + const sqlQuery = sqlQueries.statsFees({ asset, timeframe }); + + let cacheSetting = { ...CACHE_SETTINGS["hydradxUiV1StatsFees"] }; + cacheSetting.key = cacheSetting.key + "_" + asset + "_" + timeframe; + + const result = await cachedFetch( + fastify.pg, + fastify.redis, + cacheSetting, + sqlQuery + ); + + reply.send(JSON.parse(result)); + }, + }); +}; diff --git a/queries/hydradx-ui/v1/stats/fees.sql b/queries/hydradx-ui/v1/stats/fees.sql new file mode 100644 index 0000000..0540a59 --- /dev/null +++ b/queries/hydradx-ui/v1/stats/fees.sql @@ -0,0 +1,108 @@ +-- statsFees + +/* Returns fees and projected LP APY +based on past :timeframe = 1d, 1w, 1mon, 1y +*/ + +WITH interval_prep AS ( + SELECT + CASE + WHEN :timeframe = 'day' THEN '1 day'::interval + WHEN :timeframe = 'week' THEN '1 week'::interval + WHEN :timeframe = 'month' THEN '1 month'::interval + WHEN :timeframe = 'year' THEN '1 year'::interval + ELSE '1 month'::interval -- Default case + END AS interval +), +fees AS ( + SELECT + COALESCE(q1.asset_id, q2.asset_id) AS asset_id, + COALESCE(q1.amount, 0) + COALESCE(q2.amount, 0) AS amount + FROM + (SELECT + CAST(args ->> 'assetOut' AS numeric) AS asset_id, + SUM(CAST(args ->> 'assetFeeAmount' AS numeric)) AS amount + FROM event e + JOIN block b ON e.block_id = b.id + WHERE timestamp > NOW() - (SELECT interval FROM interval_prep) + AND name = 'Omnipool.SellExecuted' + AND + CASE + WHEN :asset::integer IS NOT NULL + THEN CAST(args ->> 'assetOut' AS numeric) = :asset + ELSE + true + END + GROUP BY 1) AS q1 + FULL OUTER JOIN + (SELECT + CAST(args ->> 'assetIn' AS numeric) AS asset_id, + SUM(CAST(args ->> 'assetFeeAmount' AS numeric)) AS amount + FROM event e + JOIN block b ON e.block_id = b.id + WHERE timestamp > NOW() - (SELECT interval FROM interval_prep) + AND name = 'Omnipool.BuyExecuted' + AND + CASE + WHEN :asset::integer IS NOT NULL + THEN CAST(args ->> 'assetIn' AS numeric) = :asset + ELSE + true + END + GROUP BY 1) AS q2 + ON q1.asset_id = q2.asset_id +), +last_price AS ( + SELECT + asset_id, + price_usd + FROM ( + SELECT + asset_id, + price_usd, + ROW_NUMBER() OVER ( + PARTITION BY asset_id + ORDER BY timestamp DESC + ) AS rn + FROM + stats_historical + ) a + WHERE + rn = 1 + UNION + (SELECT 1 AS asset_id, last_lrna_price AS price_usd FROM lrna_every_block ORDER BY 1 DESC LIMIT 1) +), +tvl AS ( + SELECT + asset_id, + ROUND(SUM(oa.hub_reserve / 10^12 * leb.last_lrna_price)) AS asset_tvl + FROM + lrna_every_block leb + JOIN ( + SELECT + LEAST(max_leb.max_height, max_oa.max_block) AS joined_height + FROM + (SELECT MAX(height) AS max_height FROM lrna_every_block) max_leb, + (SELECT MAX(block) AS max_block FROM omnipool_asset) max_oa + ) subq ON leb.height = subq.joined_height + JOIN omnipool_asset oa ON leb.height = oa.block + JOIN token_metadata tm ON oa.asset_id = tm.id + GROUP BY 1 +) +SELECT + round(sum((amount / 10^decimals) * price_usd)::numeric, 2) AS accrued_fees_usd, + round(avg((POWER(1 + (COALESCE(ROUND((amount / 10^decimals) * price_usd), 0) * parts) / asset_tvl, parts) - 1)::numeric), 4) AS projected_apy_perc +FROM + fees + JOIN token_metadata tm ON asset_id = tm.id + JOIN last_price lp ON tm.id = lp.asset_id + JOIN tvl ON tm.id = tvl.asset_id + CROSS JOIN (SELECT + CASE + WHEN :timeframe = 'day' THEN 365 + WHEN :timeframe = 'week' THEN 52 + WHEN :timeframe = 'month' THEN 12 + WHEN :timeframe = 'year' THEN 1 + ELSE 12 -- default to monthly if timeframe not recognized + END AS parts + ) AS interval_calc \ No newline at end of file diff --git a/variables.mjs b/variables.mjs index 4b90d70..3c25a46 100644 --- a/variables.mjs +++ b/variables.mjs @@ -54,6 +54,10 @@ export const CACHE_SETTINGS = { key: "hydradx-ui_v1_stats_volume", expire_after: 10 * 60, }, + hydradxUiV1StatsFees: { + key: "hydradx-ui_v1_stats_fees", + expire_after: 10 * 60, + }, hydradxUiV1StatsLrna: { key: "hydradx-ui_v1_stats_lrna", expire_after: 10 * 60, From 8ca3fa5801bc4933dd9e5d77f5bbbc06c0baa967 Mon Sep 17 00:00:00 2001 From: green-jay Date: Fri, 10 Nov 2023 16:17:25 +0100 Subject: [PATCH 2/3] fmt --- app/routes/hydradx-ui/v1/stats/fees.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/hydradx-ui/v1/stats/fees.mjs b/app/routes/hydradx-ui/v1/stats/fees.mjs index df0a62c..0d54763 100644 --- a/app/routes/hydradx-ui/v1/stats/fees.mjs +++ b/app/routes/hydradx-ui/v1/stats/fees.mjs @@ -8,7 +8,7 @@ const sqlQueries = yesql(path.join(dirname(), "queries/hydradx-ui/v1/stats"), { type: "pg", }); -export const VALID_TIMEFRAMES = ['day', 'week', 'month', 'year']; +export const VALID_TIMEFRAMES = ["day", "week", "month", "year"]; export default async (fastify, opts) => { fastify.route({ From 71477682040e374dcec5256bd3003379605650b5 Mon Sep 17 00:00:00 2001 From: green-jay Date: Tue, 14 Nov 2023 15:53:53 +0100 Subject: [PATCH 3/3] fix timeframes --- app/routes/hydradx-ui/v1/stats/fees.mjs | 2 +- queries/hydradx-ui/v1/stats/fees.sql | 24 +++++++----------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/app/routes/hydradx-ui/v1/stats/fees.mjs b/app/routes/hydradx-ui/v1/stats/fees.mjs index 0d54763..bd251c5 100644 --- a/app/routes/hydradx-ui/v1/stats/fees.mjs +++ b/app/routes/hydradx-ui/v1/stats/fees.mjs @@ -8,7 +8,7 @@ const sqlQueries = yesql(path.join(dirname(), "queries/hydradx-ui/v1/stats"), { type: "pg", }); -export const VALID_TIMEFRAMES = ["day", "week", "month", "year"]; +export const VALID_TIMEFRAMES = ["1d", "1w", "1mon", "1y"]; export default async (fastify, opts) => { fastify.route({ diff --git a/queries/hydradx-ui/v1/stats/fees.sql b/queries/hydradx-ui/v1/stats/fees.sql index 0540a59..177b0bc 100644 --- a/queries/hydradx-ui/v1/stats/fees.sql +++ b/queries/hydradx-ui/v1/stats/fees.sql @@ -4,17 +4,7 @@ based on past :timeframe = 1d, 1w, 1mon, 1y */ -WITH interval_prep AS ( - SELECT - CASE - WHEN :timeframe = 'day' THEN '1 day'::interval - WHEN :timeframe = 'week' THEN '1 week'::interval - WHEN :timeframe = 'month' THEN '1 month'::interval - WHEN :timeframe = 'year' THEN '1 year'::interval - ELSE '1 month'::interval -- Default case - END AS interval -), -fees AS ( +WITH fees AS ( SELECT COALESCE(q1.asset_id, q2.asset_id) AS asset_id, COALESCE(q1.amount, 0) + COALESCE(q2.amount, 0) AS amount @@ -24,7 +14,7 @@ fees AS ( SUM(CAST(args ->> 'assetFeeAmount' AS numeric)) AS amount FROM event e JOIN block b ON e.block_id = b.id - WHERE timestamp > NOW() - (SELECT interval FROM interval_prep) + WHERE timestamp > NOW() - :timeframe::interval AND name = 'Omnipool.SellExecuted' AND CASE @@ -40,7 +30,7 @@ fees AS ( SUM(CAST(args ->> 'assetFeeAmount' AS numeric)) AS amount FROM event e JOIN block b ON e.block_id = b.id - WHERE timestamp > NOW() - (SELECT interval FROM interval_prep) + WHERE timestamp > NOW() - :timeframe::interval AND name = 'Omnipool.BuyExecuted' AND CASE @@ -99,10 +89,10 @@ FROM JOIN tvl ON tm.id = tvl.asset_id CROSS JOIN (SELECT CASE - WHEN :timeframe = 'day' THEN 365 - WHEN :timeframe = 'week' THEN 52 - WHEN :timeframe = 'month' THEN 12 - WHEN :timeframe = 'year' THEN 1 + WHEN :timeframe = '1d' THEN 365 + WHEN :timeframe = '1w' THEN 52 + WHEN :timeframe = '1mon' THEN 12 + WHEN :timeframe = '1y' THEN 1 ELSE 12 -- default to monthly if timeframe not recognized END AS parts ) AS interval_calc \ No newline at end of file