Skip to content

Commit

Permalink
Add endpoints for Dexscreener support #422 (#446)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt <[email protected]>
  • Loading branch information
CapCap and xbtmatt authored Dec 17, 2024
1 parent a5b57f7 commit 91e9dc7
Show file tree
Hide file tree
Showing 14 changed files with 758 additions and 6 deletions.
6 changes: 5 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions src/typescript/frontend/src/app/dexscreener/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# DEX Screener Adapter Specs

<!-- markdownlint-disable -->

<b>Taken from https://dexscreener.notion.site/DEX-Screener-Adapter-Specs-cc1223cdf6e74a7799599106b65dcd0e </b>

<!-- markdownlint-enable -->

v1.1 / Dec 2023

The DEX Screener Adapter is a set of HTTP endpoints that allows DEX Screener to
track historical and real-time data for any Partner Decentralized Exchange.
Adapters are responsible for supplying accurate and up-to-date data, whereas DEX
Screener handles all data ingestion, processing and serving.

## Overview

- The DEX Screener Indexer queries Adapter endpoints to continuously index
events as they become available, in chunks of one or many blocks at a time.
Each block is only queried once, so caution must be taken to ensure that all
returned data is accurate at the time of indexing.
- Adapters are to be deployed, served and maintained by the Partner
- If adapter endpoints become unreachable indexing will halt and automatically
resume once they become available again
- The Indexer allows for customizable rate limits and block chunk size to ensure
Adapter endpoints are not overloaded

## Endpoints

- An in-depth explanation of the schemas expected for each endpoint is described
on the `Schemas` section below

- Numbers for amounts and price can be both `number` and `string`. Strings are
more suitable when dealing with extremely small or extremely large numbers
that can't be accurately serialized into JSON numbers.

- Indexing will halt if schemas are invalid or contain unexpected values
(i.e.: `swapEvent.priceNative=0` or `pair.name=""`)
90 changes: 90 additions & 0 deletions src/typescript/frontend/src/app/dexscreener/asset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/***
Request: GET /asset?id=:string
Response Schema:
// interface AssetResponse {
// asset: Asset;
// }
Example Response:
// {
// "asset": {
// "id": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
// "name": "Wrapped Ether",
// "symbol": "WETH",
// "totalSupply": 10000000,
// "circulatingSupply": 900000,
// "coinGeckoId": "ether",
// "coinMarketCapId": "ether"
// }
// }
**/

import { type NextRequest, NextResponse } from "next/server";
import { toMarketEmojiData } from "@sdk/emoji_data";
import { EMOJICOIN_SUPPLY } from "@sdk/const";
import { calculateCirculatingSupply } from "@sdk/markets";
import { symbolEmojiStringToArray } from "./util";
import { fetchMarketState } from "@/queries/market";

/**
* - In most cases, asset ids will correspond to contract addresses. Ids are case-sensitive.
* - All `Asset` props aside from `id` may be mutable. The Indexer will periodically query assets for their most
* up-to-date info
* - `totalSupply` is optional but DEX Screener cannot calculate FDV/Market Cap if not available
* - `circulatingSupply` is optional but DEX Screener may not be able to show accurate market cap if not available
* - `coinGeckoId` and `coinMarketCapId` are optional but may be used for displaying additional token information such
* as image, description and self-reported/off-chain circulating supply
* - `metadata` includes any optional auxiliary info not covered in the default schema and not required in most cases
*/
export interface Asset {
id: string;
name: string;
symbol: string;
totalSupply: number | string;
circulatingSupply?: number | string;
coinGeckoId?: string;
coinMarketCapId?: string;
metadata?: Record<string, string>;
}

export interface AssetResponse {
asset: Asset;
}

/**
* Fetches an asset by a string of the emojis that represent the asset
* @param assetId
*/
export async function getAsset(assetId: string): Promise<Asset> {
const marketEmojiData = toMarketEmojiData(assetId);
const symbolEmojis = symbolEmojiStringToArray(assetId);
const marketState = await fetchMarketState({ searchEmojis: symbolEmojis });

const circulatingSupply: { circulatingSupply?: number | string } = {};
if (marketState && marketState.state) {
circulatingSupply.circulatingSupply = calculateCirculatingSupply(marketState.state).toString();
}

return {
id: assetId,
name: marketEmojiData.symbolData.name,
symbol: marketEmojiData.symbolData.symbol,
totalSupply: Number(EMOJICOIN_SUPPLY),
...circulatingSupply,
// coinGeckoId: assetId,
// coinMarketCapId: assetId,
};
}

// NextJS JSON response handler
export async function GET(request: NextRequest): Promise<NextResponse<AssetResponse>> {
const searchParams = request.nextUrl.searchParams;
const assetId = searchParams.get("id");
if (!assetId) {
// This is a required field, and is an error otherwise
return new NextResponse("id is a parameter", { status: 400 });
}
const asset = await getAsset(assetId);
return NextResponse.json({ asset });
}
Loading

0 comments on commit 91e9dc7

Please sign in to comment.