Skip to content

Commit

Permalink
added nft price api
Browse files Browse the repository at this point in the history
  • Loading branch information
nicarq committed Feb 8, 2023
1 parent 078b626 commit 0644e59
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 11 deletions.
5 changes: 4 additions & 1 deletion config/default.ts
Expand Up @@ -10,6 +10,9 @@ export default {
APIGenerated: {
refreshBackoffCap: process.env.REFRESH_BACKOFF_CAP ?? 60000 * 60 * 3,
refreshInterval: process.env.REFRESH_INTERVAL ?? 60000 * 5, // refreshes every 5 mins
port: process.env.PORT ?? 8090,
openCNFTRatePerRequest: process.env.OPEN_CNFT_RATE_REQUEST ?? 20,
openCNFTRefreshRate: process.env.OPEN_CNFT_REFRESH_RATE ?? (60000 * 1 + 5000), // refreshes every 1 min and 1 sec
collectionUpdateInterval: process.env.OPEN_CNFT_REFRESH_RATE ?? 60000 * 60 * 6, // refreshes every 6 hours
port: process.env.PORT ?? 8080,
},
} as ConfigType;
122 changes: 116 additions & 6 deletions src/index.ts
Expand Up @@ -20,11 +20,19 @@ import {
SupportedCurrencyFrom,
SupportedCurrencyTo,
PriceHistoryCryptoCompareEntry,
NFTRequest,
CachedCollection,
CNFT,
} from "./types/types";
import CONFIG from "../config/default";
import { calculateMissingHistoryEntry, extractPriceHistoryEntry, getExternalPrice, historyEntryToResult, safeNumberPrecision } from "./cryptocompare";
import {
calculateMissingHistoryEntry,
extractPriceHistoryEntry,
getExternalPrice,
historyEntryToResult,
safeNumberPrecision,
} from "./cryptocompare";
import { axios } from "./utils/index";
import { getCNFT, getCollections } from "./opencnft";

const dailyHistoryLimit = 2000; // defined by CryptoCompare
const hourlyHistoryLimit = moment.duration(1, "week").asHours();
Expand All @@ -41,6 +49,9 @@ const initEmptyHistory = (): PriceHistory =>

// Server cache
let currentPrice: CurrentPrice | undefined;
let currentCNFTsPrice: { [key: string]: CachedCollection } = {};
let CNFTPolicyNotFoundList: string[] = [];
let lastOpenCNFTRequested: number = 0; // used for pagination and avoid the rate limiter
const historyDailyAll: PriceHistory = initEmptyHistory();
const historyHourlyWeek: PriceHistory = initEmptyHistory();

Expand All @@ -67,6 +78,52 @@ const updatePrice = async (): Promise<void> => {
}
};

const updateCollections = async (): Promise<void> => {
try {
const collections = await getCollections();
collections.forEach((collection) => {
if (!collection.policies) return;
// if data already exist in the cache, we don't update it
const data =
currentCNFTsPrice[collection.policies] &&
currentCNFTsPrice[collection.policies].data != null
? currentCNFTsPrice[collection.policies].data
: null;

currentCNFTsPrice[collection.policies] = {
...collection,
data,
lastUpdatedTimestamp: Date.now(),
};
});
} catch (e) {
console.error("Error updating price: ", e);
}
};

const updateCollectionsUsingOpenCNFTInterval = async (
start: number,
limit = 20
): Promise<void> => {
// get the keys of the currentCNFTsPrice
const keys = Object.keys(currentCNFTsPrice);
// get the keys of the currentCNFTsPrice that are in the range of start and start + limit
const keysInRange = keys.slice(start, start + limit);
for (const keys of keysInRange) {
try {
const collection = await getCNFT(keys);
if (!collection.policy) return;
currentCNFTsPrice[collection.policy] = {
...currentCNFTsPrice[collection.policy],
data: collection,
lastUpdatedTimestamp: Date.now(),
};
} catch (e) {
console.error("Error updating price for openCNFT: ", e);
}
}
};

const updateHistory = async (
cache: PriceHistory,
endpoint: string,
Expand Down Expand Up @@ -99,7 +156,7 @@ const updateHistory = async (
for (const to of priceHistoryNotQueriedCurrenciesTo) {
newCache[from][to] = newCache[from][priceHistoryBaseCurrencyTo].map(
(from_base, i) => {
const to_base = newCache[to][priceHistoryBaseCurrencyTo][i];
const to_base: any = newCache[to][priceHistoryBaseCurrencyTo][i];
return calculateMissingHistoryEntry(from_base, to_base);
}
);
Expand Down Expand Up @@ -129,18 +186,49 @@ const updateHourly = () =>
CONFIG.APIGenerated.refreshInterval
);


const getNFTsPriceEndpoint = async (req: Request, res: Response) => {
if (req.body == null) {
res.status(400).send('Did not specify "body"!');
return;
}
const nfts = assertType<NFTRequest[]>(req.body);
const nfts = assertType<string[]>(req.body);
if (nfts.length === 0) {
res.status(400).send('Did not specify "nfts"!');
return;
}
console.log("Not implemented yet!");

if (nfts.length > 100) {
res.status(400).send({
status: "Too many nfts. Maximum 100.",
data: [],
});
return;
}

if (Object.keys(currentCNFTsPrice).length == 0) {
res.status(200).send({
status: "CNFT Price API not available",
data: [],
});
return;
}

const result = Object.keys(currentCNFTsPrice).map((key) => {
const cachedNft = currentCNFTsPrice[key];
if (cachedNft.policies != null && nfts.includes(cachedNft.policies)) {
return cachedNft;
} else {
return null;
}
});

// remove null values
const filteredResult = result.filter((nft) => nft != null);

res.status(200).send({
status: "ok",
data: filteredResult,
});
};

const getPriceEndpoint = async (req: Request, res: Response) => {
Expand Down Expand Up @@ -198,6 +286,10 @@ applyRoutes(routes, router);
router.use(middleware.logErrors);
router.use(middleware.errorHandler);

router.get("/", (req, res) => {
res.send("Hello World!");
});

const server = http.createServer(router);
const port: number = CONFIG.APIGenerated.port;
const refreshInterval = CONFIG.APIGenerated.refreshInterval;
Expand All @@ -217,11 +309,29 @@ console.log("Starting interval");

new Promise(async (resolve) => {
await updatePrice();
await updateCollections();

setInterval(async () => {
await updatePrice();
}, CONFIG.APIGenerated.refreshInterval);

setInterval(async () => {
await updateCollections();
}, CONFIG.APIGenerated.collectionUpdateInterval);

updateDaily();

// Update X OpenCNFTs every 1 minute
setInterval(async () => {
if (currentCNFTsPrice == null) return;
return updateCollectionsUsingOpenCNFTInterval(
lastOpenCNFTRequested,
CONFIG.APIGenerated.openCNFTRatePerRequest
).then(() => {
lastOpenCNFTRequested += CONFIG.APIGenerated.openCNFTRatePerRequest;
});
}, CONFIG.APIGenerated.openCNFTRefreshRate);

setTimeout(
() => {
setInterval(async () => {
Expand Down
9 changes: 5 additions & 4 deletions src/types/types.ts
Expand Up @@ -27,10 +27,6 @@ export interface NFTPrice {
}
}

export interface NFTRequest {
policy: string;
}

export type CachedNFTMapping = {
[key: string]: string;
};
Expand Down Expand Up @@ -77,6 +73,11 @@ export interface NFTCollection {
policies: string | null; // no idea how this can be null but it can
}

export interface CachedCollection extends NFTCollection {
data: CNFT | null;
lastUpdatedTimestamp: number;
}

// To reduce the number of queries, we query "from" all currencies "to" one base currency,
// and fill in the remaining values ourselves.
export const priceHistoryBaseCurrencyTo = 'USD' as const;
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Expand Up @@ -41,6 +41,7 @@ export const applyRoutes = (routes: Route[], router: Router) => {
// (router as any)[method](`/api${path}`, handler);
(router as any)[method](path, handler);
}
console.log("path: ", routes.map(r => r.path));
};

export function exponentialBackoff(query: () => Promise<any>, baseInterval: number) {
Expand Down
3 changes: 3 additions & 0 deletions types/globals/index.d.ts
Expand Up @@ -6,6 +6,9 @@ interface ConfigType {
APIGenerated: {
refreshBackoffCap: number,
refreshInterval: number,
openCNFTRatePerRequest: number, // 60
openCNFTRefreshRate: number,
collectionUpdateInterval: number,
port: number
},
}

0 comments on commit 0644e59

Please sign in to comment.