Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ECO-2631] Add coingecko routes #498

Merged
merged 7 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
};
Loading