Skip to content
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
4 changes: 2 additions & 2 deletions packages/shared/src/db/tokenMap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { CollectionReference, Query } from "@google-cloud/firestore";
import { FIRESTORE_COLLECTIONS, FIRESTORE_MAX_BATCH_SIZE } from "../constants";
import { AppError, ErrorCode, logger } from "../lib";
import type { TokenMapData, TokenMapFilter } from "../types";
import type { TokenMapData, TokenMapFilter, TokenMapInput } from "../types";
import { db } from "./firestore";

export class TokenMap {
Expand All @@ -22,7 +22,7 @@ export class TokenMap {
return TokenMap.instance;
}

async saveTokenMapsBatch(inputs: TokenMapData[]) {
async saveTokenMapsBatch(inputs: TokenMapInput[]) {
const batches = [];
const now = new Date();

Expand Down
18 changes: 16 additions & 2 deletions packages/shared/src/types/tokenMap.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
export type TokenMapData = {
import { TokenType } from "./blockchain";

export interface TokenMapData {
tokenIndex: number;
tokenId: number;
tokenType: TokenType;
contractAddress: string;
symbol: string;
decimals: number;
createdAt: FirebaseFirestore.Timestamp;
}

export interface TokenMapInput {
tokenIndex: number;
tokenId: bigint;
tokenType: TokenType;
contractAddress: string;
symbol: string;
decimals: number;
};
}

export interface TokenMapFilter {
tokenIndexes?: string[];
Expand Down
4 changes: 2 additions & 2 deletions packages/token-map-register/src/service/map.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TokenMap, TokenMapData, TokenType } from "@intmax2-function/shared";
import { TokenMap, TokenMapInput, TokenType } from "@intmax2-function/shared";
import { erc20Abi, type PublicClient } from "viem";
import { MULTICALL_SIZE } from "../constants";
import type { TokenInfo } from "../types";
Expand Down Expand Up @@ -104,7 +104,7 @@ const enrichTokensWithMetadata = (
});
};

const saveTokenMaps = async (enrichedTokens: TokenMapData[]) => {
const saveTokenMaps = async (enrichedTokens: TokenMapInput[]) => {
const tokenMap = TokenMap.getInstance();
await tokenMap.saveTokenMapsBatch(enrichedTokens);
};
20 changes: 20 additions & 0 deletions packages/token/src/lib/tokenPrice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export class TokenPrice {
private readonly FETCH_INTERVAL = 1000 * 60 * 5; // 5 minutes
private readonly RETRY_INTERVAL = 1000 * 5; // 5 seconds
private tokenPriceList: Token[] = [];
private tokenPriceMap: Map<string, Token> = new Map();
private initialized: boolean = false;

public static getInstance() {
Expand Down Expand Up @@ -36,6 +37,7 @@ export class TokenPrice {
}

this.tokenPriceList = tokenList;
this.tokenPriceMap = new Map(tokenList.map((token) => [token.id, token]));
logger.info(`Successfully fetched ${tokenList.length} tokens`);

this.clearRetryTimeout();
Expand Down Expand Up @@ -77,9 +79,27 @@ export class TokenPrice {
return this.tokenPriceList;
}

async getTokenPriceMap() {
while (!this.initialized) {
logger.info("TokenPrice not initialized, waiting...");
await sleep(100);
}
if (!this.tokenPriceMap.size) {
logger.info("Token price map is empty, fetching data...");
await this.fetchAndCacheTokenList();
}
return this.tokenPriceMap;
}

async getTokenByAddress(contractAddress: string) {
const tokenMap = await this.getTokenPriceMap();
return tokenMap.get(contractAddress);
}

cleanup() {
this.stopScheduler();
this.tokenPriceList = [];
this.tokenPriceMap.clear();
}

private startScheduler() {
Expand Down
21 changes: 13 additions & 8 deletions packages/token/src/services/tokenMaps.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
DEFAULT_IMAGE_PATH,
type Token,
TokenMap,
type TokenMapData,
type TokenPaginationValidationType,
TokenType,
} from "@intmax2-function/shared";
import { DEFAULT_PAGE_SIZE } from "../constants";
import { TokenPrice } from "../lib/tokenPrice";
Expand All @@ -14,9 +16,12 @@ export const list = async (
) => {
const tokenMaps = await fetchTokenMaps(tokenIndexes);

const tokenPrice = TokenPrice.getInstance();
const priceMap = await tokenPrice.getTokenPriceMap();

if (Object.keys(paginationOptions).length === 0) {
return {
items: await formatTokenMaps(tokenMaps),
items: formatTokenMaps(tokenMaps, priceMap),
nextCursor: null,
total: tokenMaps.length,
};
Expand All @@ -32,7 +37,7 @@ export const list = async (
const nextCursor = getNextCursor(items, tokenMaps.length, startIndex, perPage);

return {
items: await formatTokenMaps(items),
items: formatTokenMaps(items, priceMap),
nextCursor,
total: tokenMaps.length,
};
Expand All @@ -47,14 +52,14 @@ const fetchTokenMaps = async (tokenIndexes: string[]) => {
return tokenMaps;
};

const formatTokenMaps = async (tokenMaps: TokenMapData[]) => {
const tokenPrice = TokenPrice.getInstance();
const tokenPriceList = await tokenPrice.getTokenPriceList();

return tokenMaps.map((map) => {
const priceData = tokenPriceList.find((item) => item.contractAddress === map.contractAddress);
const formatTokenMaps = (tokenMaps: TokenMapData[], priceMap: Map<string, Token>) => {
return tokenMaps.map(({ createdAt, tokenId, ...map }) => {
const priceData = priceMap.get(map.contractAddress);
return {
...map,
...(map.tokenType === TokenType.ERC721 || map.tokenType === TokenType.ERC1155
? { tokenId }
: {}),
price: priceData?.price ?? 0,
image: priceData?.image ?? DEFAULT_IMAGE_PATH,
};
Expand Down
20 changes: 15 additions & 5 deletions packages/token/src/services/tokenPrices.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ export const list = async (
paginationOptions: TokenPaginationValidationType = {},
) => {
const tokenPrice = TokenPrice.getInstance();
const tokenPriceList = await tokenPrice.getTokenPriceList();
const filteredList = filterTokensByAddresses(tokenPriceList, contractAddresses);
const filteredList = await getFilteredTokens(tokenPrice, contractAddresses);

if (Object.keys(paginationOptions).length === 0) {
return {
Expand All @@ -36,9 +35,20 @@ export const list = async (
};
};

const filterTokensByAddresses = (tokenList: Token[], contractAddresses: string[]) => {
const getFilteredTokens = async (tokenPrice: TokenPrice, contractAddresses: string[]) => {
if (contractAddresses.length === 0) {
return tokenList;
return await tokenPrice.getTokenPriceList();
}
return tokenList.filter((token) => contractAddresses.includes(token.contractAddress));

const tokenPriceMap = await tokenPrice.getTokenPriceMap();
const filteredTokens: Token[] = [];

for (const address of contractAddresses) {
const token = tokenPriceMap.get(address);
if (token) {
filteredTokens.push(token);
}
}

return filteredTokens;
};