Skip to content

Commit

Permalink
[ECO-2631] Add coingecko routes (#498)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt <[email protected]>
  • Loading branch information
CRBl69 and xbtmatt authored Jan 22, 2025
1 parent 2074e19 commit c1ae6c1
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { postgrest } from "@sdk/indexer-v2/queries/client";
import { TableName } from "@sdk/indexer-v2/types";
import Big from "big.js";
import type { NextRequest } from "next/server";
import { stringifyJSON } from "utils";

/**
* @see {@link https://docs.google.com/document/d/1v27QFoQq1SKT3Priq3aqPgB70Xd_PnDzbOCiuoCyixw/edit?tab=t.0}
*/
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;

const { tickerId, type, startTime, endTime, limit } = {
tickerId: searchParams.get("ticker_id"),
type: searchParams.get("type"),
startTime: searchParams.get("start_time"),
endTime: searchParams.get("end_time"),
limit: Number(searchParams.get("limit") ?? 500),
};

if (type && type !== "buy" && type !== "sell")
return new Response("Invalid type.", { status: 400 });
if (limit > 500) return new Response("Max limit is 500.", { status: 400 });
if (limit < 1) return new Response("Min limit is 1.", { status: 400 });
if (startTime && (isNaN(Number(startTime)) || Number(startTime) < 1))
return new Response("Start time is not a valid unix timestamp.", { status: 400 });
if (endTime && (isNaN(Number(endTime)) || Number(endTime) < 1))
return new Response("End time is not a valid unix timestamp.", { status: 400 });

let query = postgrest
.from(TableName.SwapEvents)
.select("market_nonce, avg_execution_price_q64, base_volume, quote_volume, bump_time, is_sell");

if (tickerId) query = query.eq("market_address", tickerId.split("_")[0].split("::")[0]);
if (type) query = query.eq("is_sell", type === "sell");
if (startTime) query = query.gte("bump_time", new Date(Number(startTime) * 1000).toISOString());
if (endTime) query = query.lte("bump_time", new Date(Number(endTime) * 1000).toISOString());
query = query.limit(limit);
query = query.order("block_number, transaction_version, event_index");

const swaps = await query;

const data = swaps.data?.map((e) => ({
trade_id: e.market_nonce.toString(),
price: Big(e.avg_execution_price_q64).div(Big(2).pow(64)).toString(),
base_volume: Big(e.base_volume)
.div(10 ** 8)
.toString(),
target_volume: Big(e.quote_volume)
.div(10 ** 8)
.toString(),
trade_timestamp: Math.round(new Date(e.bump_time).getTime() / 1000).toString(),
type: e.is_sell ? "sell" : "buy",
}));

return new Response(stringifyJSON(data), { headers: { "Content-type": "application/json" } });
}
70 changes: 70 additions & 0 deletions src/typescript/frontend/src/app/coingecko/tickers/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { APTOS_COIN_TYPE_TAG } from "@sdk/const";
import { postgrest } from "@sdk/indexer-v2/queries/client";
import { TableName } from "@sdk/indexer-v2/types";
import { toNominalPrice } from "@sdk/utils";
import { toNominal } from "lib/utils/decimals";
import type { NextRequest } from "next/server";
import { stringifyJSON } from "utils";
import { estimateLiquidityInUSD } from "./utils";
import { getAptPrice } from "lib/queries/get-apt-price";

// Since this is a public endpoint, set revalidate to 1 to ensure it can't spam the indexer.
export const revalidate = 1;

/**
* Query params:
* - skip: number, the number of rows to skip at the beginning, default is 0
* - limit: number, the max number of rows per query, default is 100, max is 500
*
* @see {@link https://docs.google.com/document/d/1v27QFoQq1SKT3Priq3aqPgB70Xd_PnDzbOCiuoCyixw/edit?tab=t.0}
*
* @example
* // The example for a single row, given in the Google doc linked above:
* ```json
* "ticker_id": "0x1234::coin_factory::Emojicoin_0x1::aptos_coin::AptosCoin",
* "base_currency": "0x1234::coin_factory::Emojicoin",
* "target_currency": "0x1::aptos_coin::AptosCoin",
* "pool_id": "✨",
* "last_price":"50.0",
* "base_volume":"10",
* "target_volume":"500",
* "liquidity_in_usd": "100",
* ```
*/
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;

const { skip, limit } = {
skip: Number(searchParams.get("skip") ?? 0),
limit: Number(searchParams.get("limit") ?? 100),
};

if (limit > 500) return new Response("Max limit is 500.", { status: 400 });
if (limit < 1) return new Response("Min limit is 1.", { status: 400 });
if (skip < 0) return new Response("Min skip is 0.", { status: 400 });

const markets = await postgrest
.from(TableName.MarketState)
.select(
"market_address, symbol_emojis, lp_coin_supply, last_swap_avg_execution_price_q64, daily_volume, daily_base_volume, clamm_virtual_reserves_base, clamm_virtual_reserves_quote, cpamm_real_reserves_base, cpamm_real_reserves_quote"
)
.order("market_id")
.range(skip, skip + limit);

const APTOS_COIN = APTOS_COIN_TYPE_TAG.toString();

const aptPrice = await getAptPrice();

const data = markets.data?.map((e) => ({
ticker_id: `${e.market_address}::coin_factory::Emojicoin_${APTOS_COIN}`,
base_currency: `${e.market_address}::coin_factory::Emojicoin`,
target_currency: APTOS_COIN,
pool_id: e.symbol_emojis.join(""),
last_price: toNominalPrice(e.last_swap_avg_execution_price_q64).toString(),
base_volume: toNominal(BigInt(e.daily_base_volume)).toString(),
target_volume: toNominal(BigInt(e.daily_volume)).toString(),
liquidity_in_usd: aptPrice !== undefined ? estimateLiquidityInUSD(e, aptPrice) : undefined,
}));

return new Response(stringifyJSON(data), { headers: { "Content-type": "application/json" } });
}
44 changes: 44 additions & 0 deletions src/typescript/frontend/src/app/coingecko/tickers/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { toReserves } from "@sdk-types";
import { type Uint64String } from "@sdk/emojicoin_dot_fun";
import { calculateCurvePrice, calculateRealReserves } from "@sdk/markets";
import Big from "big.js";
import "server-only";

export const estimateLiquidityInUSD = (
e: {
clamm_virtual_reserves_base: Uint64String;
clamm_virtual_reserves_quote: Uint64String;
cpamm_real_reserves_base: Uint64String;
cpamm_real_reserves_quote: Uint64String;
lp_coin_supply: Uint64String;
},
aptPrice: number
) => {
const clammVirtualReserves = toReserves({
base: e.clamm_virtual_reserves_base,
quote: e.clamm_virtual_reserves_quote,
});
const cpammRealReserves = toReserves({
base: e.cpamm_real_reserves_base,
quote: e.cpamm_real_reserves_quote,
});

const lpCoinSupply = BigInt(e.lp_coin_supply);
const priceRatio = calculateCurvePrice({
clammVirtualReserves,
cpammRealReserves,
lpCoinSupply,
});
const reserves = calculateRealReserves({
clammVirtualReserves,
cpammRealReserves,
lpCoinSupply,
});
const bigReserves = {
base: Big(reserves.base.toString()),
quote: Big(reserves.quote.toString()),
};
const totalAPTInReserves = bigReserves.quote.plus(bigReserves.base.mul(priceRatio));
const totalUSDInReserves = totalAPTInReserves.mul(aptPrice).div(Big(10 ** 8));
return totalUSDInReserves.toFixed(4);
};

0 comments on commit c1ae6c1

Please sign in to comment.