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
51 changes: 15 additions & 36 deletions src/chains/ethereum/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ export type NewDebugMsgHandler = (msg: any) => void;
export class Ethereum {
private static _instances: { [name: string]: Ethereum };
public provider: providers.StaticJsonRpcProvider;
public tokenList: TokenInfo[] = [];
public tokenMap: Record<string, TokenInfo> = {};
public network: string;
public nativeTokenSymbol: string;
public chainId: number;
Expand Down Expand Up @@ -447,7 +445,6 @@ export class Ethereum {
*/
public async init(): Promise<void> {
try {
await this.loadTokens();
this._initialized = true;
} catch (e) {
logger.error(`Failed to initialize Ethereum chain: ${e}`);
Expand All @@ -456,36 +453,15 @@ export class Ethereum {
}

/**
* Load tokens from the token list source
* Get all tokens from the token list (reads from disk each time)
*/
public async loadTokens(): Promise<void> {
logger.info(`Loading tokens for ethereum/${this.network} using TokenService`);
try {
// Use TokenService to load tokens
const tokens = await TokenService.getInstance().loadTokenList('ethereum', this.network);

// Convert to TokenInfo format with chainId and normalize addresses
this.tokenList = tokens.map((token) => ({
...token,
address: getAddress(token.address), // Normalize to checksummed address
chainId: this.chainId,
}));

if (this.tokenList) {
// Build token map for faster lookups
this.tokenList.forEach((token: TokenInfo) => (this.tokenMap[token.symbol] = token));
}
} catch (error) {
logger.error(`Failed to load token list: ${error.message}`);
throw error;
}
}

/**
* Get all tokens loaded from the token list
*/
public get storedTokenList(): TokenInfo[] {
return Object.values(this.tokenMap);
public async getTokenList(): Promise<TokenInfo[]> {
const tokens = await TokenService.getInstance().loadTokenList('ethereum', this.network);
return tokens.map((token) => ({
...token,
address: getAddress(token.address), // Normalize to checksummed address
chainId: this.chainId,
}));
}

/**
Expand All @@ -494,8 +470,10 @@ export class Ethereum {
* @returns TokenInfo object or undefined if token not found in local list
*/
public async getToken(tokenSymbol: string): Promise<TokenInfo | undefined> {
const tokenList = await this.getTokenList();

// First try to find token by symbol
const tokenBySymbol = this.tokenList.find(
const tokenBySymbol = tokenList.find(
(token: TokenInfo) => token.symbol.toUpperCase() === tokenSymbol.toUpperCase() && token.chainId === this.chainId,
);

Expand All @@ -507,7 +485,7 @@ export class Ethereum {
try {
const normalizedAddress = utils.getAddress(tokenSymbol);
// Try to find token by normalized address
return this.tokenList.find(
return tokenList.find(
(token: TokenInfo) =>
token.address.toLowerCase() === normalizedAddress.toLowerCase() && token.chainId === this.chainId,
);
Expand Down Expand Up @@ -1119,10 +1097,11 @@ export class Ethereum {
isHardware: boolean,
balances: Record<string, number>,
): Promise<void> {
logger.info(`Checking balances for all ${this.storedTokenList.length} tokens in the token list`);
const tokenList = await this.getTokenList();
logger.info(`Checking balances for all ${tokenList.length} tokens in the token list`);

await Promise.all(
this.storedTokenList.map(async (token) => {
tokenList.map(async (token) => {
try {
const contract = this.getContract(token.address, this.provider);
const balance = isHardware
Expand Down
2 changes: 1 addition & 1 deletion src/chains/ethereum/routes/allowances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export async function getEthereumAllowances(
let tokenInfoMap: Record<string, TokenInfo>;
if (!tokens || tokens.length === 0) {
// Get all tokens from the token list
const allTokens = ethereum.storedTokenList;
const allTokens = await ethereum.getTokenList();
tokenInfoMap = {};
for (const token of allTokens) {
tokenInfoMap[token.symbol] = token;
Expand Down
10 changes: 6 additions & 4 deletions src/chains/ethereum/routes/unwrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ const WETH9ABI = [
* @param ethereum Ethereum instance
* @returns Wrapped token info (address, symbol, nativeSymbol)
*/
function getWrappedTokenInfo(ethereum: Ethereum): { address: string; symbol: string; nativeSymbol: string } {
async function getWrappedTokenInfo(
ethereum: Ethereum,
): Promise<{ address: string; symbol: string; nativeSymbol: string }> {
const nativeSymbol = ethereum.nativeTokenSymbol;
const wrappedSymbol = `W${nativeSymbol}`;

// Look up wrapped token in token map
const wrappedToken = ethereum.tokenMap[wrappedSymbol];
// Look up wrapped token in token list
const wrappedToken = await ethereum.getToken(wrappedSymbol);

if (!wrappedToken) {
throw new Error(
Expand All @@ -58,7 +60,7 @@ export async function unwrapEthereum(fastify: FastifyInstance, network: string,
// Get wrapped token info from token list
let wrappedInfo;
try {
wrappedInfo = getWrappedTokenInfo(ethereum);
wrappedInfo = await getWrappedTokenInfo(ethereum);
} catch (error: any) {
throw fastify.httpErrors.badRequest(error.message);
}
Expand Down
10 changes: 6 additions & 4 deletions src/chains/ethereum/routes/wrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ const WETH9ABI = [
* @param ethereum Ethereum instance
* @returns Wrapped token info (address, symbol, nativeSymbol)
*/
function getWrappedTokenInfo(ethereum: Ethereum): { address: string; symbol: string; nativeSymbol: string } {
async function getWrappedTokenInfo(
ethereum: Ethereum,
): Promise<{ address: string; symbol: string; nativeSymbol: string }> {
const nativeSymbol = ethereum.nativeTokenSymbol;
const wrappedSymbol = `W${nativeSymbol}`;

// Look up wrapped token in token map
const wrappedToken = ethereum.tokenMap[wrappedSymbol];
// Look up wrapped token in token list
const wrappedToken = await ethereum.getToken(wrappedSymbol);

if (!wrappedToken) {
throw new Error(
Expand All @@ -58,7 +60,7 @@ export async function wrapEthereum(fastify: FastifyInstance, network: string, ad
// Get wrapped token info from token list
let wrappedInfo;
try {
wrappedInfo = getWrappedTokenInfo(ethereum);
wrappedInfo = await getWrappedTokenInfo(ethereum);
} catch (error: any) {
throw fastify.httpErrors.badRequest(error.message);
}
Expand Down
71 changes: 30 additions & 41 deletions src/chains/solana/solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ export class Solana {
public network: string;
public nativeTokenSymbol: string;

public tokenList: TokenInfo[] = [];
public config: SolanaNetworkConfig;
private _tokenMap: Record<string, TokenInfo> = {};
private rpcProviderService?: RPCProvider;

private static _instances: { [name: string]: Solana };
Expand Down Expand Up @@ -162,8 +160,6 @@ export class Solana {

private async init(): Promise<void> {
try {
await this.loadTokens();

// Initialize RPC provider service if configured
if (this.rpcProviderService) {
await this.rpcProviderService.initialize();
Expand All @@ -174,43 +170,30 @@ export class Solana {
}
}

/**
* Get the token list from TokenService (reads from disk each time)
*/
async getTokenList(): Promise<TokenInfo[]> {
// Always return the stored list loaded via TokenService
return this.tokenList;
}

async loadTokens(): Promise<void> {
try {
// Use TokenService to load tokens
const tokens = await TokenService.getInstance().loadTokenList('solana', this.network);

// Convert to TokenInfo format (SPL token registry format)
this.tokenList = tokens.map((token) => ({
address: token.address,
symbol: token.symbol,
name: token.name,
decimals: token.decimals,
chainId: 101, // Solana mainnet chainId
}));

// Create symbol -> token mapping
this.tokenList.forEach((token: TokenInfo) => {
this._tokenMap[token.symbol] = token;
});
} catch (error) {
logger.error(`Failed to load token list for ${this.network}: ${error.message}`);
throw error;
}
const tokens = await TokenService.getInstance().loadTokenList('solana', this.network);
return tokens.map((token) => ({
address: token.address,
symbol: token.symbol,
name: token.name,
decimals: token.decimals,
chainId: 101, // Solana mainnet chainId
}));
}

async getToken(addressOrSymbol: string): Promise<TokenInfo | null> {
const tokenList = await this.getTokenList();

// First try to find by symbol (case-insensitive)
const normalizedSearch = addressOrSymbol.toUpperCase().trim();
let token = this.tokenList.find((token: TokenInfo) => token.symbol.toUpperCase().trim() === normalizedSearch);
let token = tokenList.find((token: TokenInfo) => token.symbol.toUpperCase().trim() === normalizedSearch);

// If not found by symbol, try to find by address
if (!token) {
token = this.tokenList.find((token: TokenInfo) => token.address.toLowerCase() === addressOrSymbol.toLowerCase());
token = tokenList.find((token: TokenInfo) => token.address.toLowerCase() === addressOrSymbol.toLowerCase());
}

// If still not found, try to create a new token assuming addressOrSymbol is an address
Expand Down Expand Up @@ -376,6 +359,7 @@ export class Solana {
async getBalance(wallet: Keypair, symbols?: string[]): Promise<Record<string, number>> {
const publicKey = wallet.publicKey;
const balances: Record<string, number> = {};
const tokenList = await this.getTokenList();

// Treat empty array as if no tokens were specified
const effectiveSymbols = symbols && symbols.length === 0 ? undefined : symbols;
Expand Down Expand Up @@ -456,7 +440,7 @@ export class Solana {
}

// Check if it's a token symbol in our list
const tokenBySymbol = this.tokenList.find((t) => t.symbol.toUpperCase() === s.toUpperCase());
const tokenBySymbol = tokenList.find((t) => t.symbol.toUpperCase() === s.toUpperCase());

if (tokenBySymbol) {
foundTokens.add(tokenBySymbol.symbol);
Expand Down Expand Up @@ -487,7 +471,7 @@ export class Solana {
const { parsedAccount } = mintToAccount.get(mintAddress);

// Try to get token from our token list
const token = this.tokenList.find((t) => t.address === mintAddress);
const token = tokenList.find((t) => t.address === mintAddress);

if (token) {
// Token is in our list
Expand All @@ -514,10 +498,10 @@ export class Solana {
} else {
// No symbols provided or empty array - check all tokens in the token list
// Note: When symbols is an empty array, we check all tokens in the token list
logger.info(`Checking balances for all ${this.tokenList.length} tokens in the token list`);
logger.info(`Checking balances for all ${tokenList.length} tokens in the token list`);

// Process all tokens from the token list
for (const token of this.tokenList) {
for (const token of tokenList) {
// Skip if already processed
if (token.symbol === 'SOL' || foundTokens.has(token.symbol)) {
continue;
Expand Down Expand Up @@ -768,6 +752,7 @@ export class Solana {
*/
private async fetchTokenAccounts(publicKey: PublicKey): Promise<Map<string, TokenAccount>> {
const tokenAccountsMap = new Map<string, TokenAccount>();
const tokenList = await this.getTokenList();

try {
// Fetch all accounts with base64 encoding - works reliably for all providers
Expand All @@ -791,7 +776,7 @@ export class Solana {
const mintAddress = accountInfo.mint.toString();

// Get decimals from token list (only process tokens in our list)
const tokenInList = this.tokenList.find((t) => t.address === mintAddress);
const tokenInList = tokenList.find((t) => t.address === mintAddress);
if (!tokenInList) {
// Skip tokens not in our token list
logger.debug(`Skipping token ${mintAddress} - not in token list`);
Expand Down Expand Up @@ -931,11 +916,13 @@ export class Solana {
balances: Record<string, number>,
): Promise<void> {
const SOL_NATIVE_MINT = 'So11111111111111111111111111111111111111112';
const tokenList = await this.getTokenList();

for (const symbol of symbols) {
if (symbol.toUpperCase() === 'SOL' || symbol === SOL_NATIVE_MINT) continue;

// Try to find token by symbol
const tokenInfo = this.tokenList.find((t) => t.symbol.toUpperCase() === symbol.toUpperCase());
const tokenInfo = tokenList.find((t) => t.symbol.toUpperCase() === symbol.toUpperCase());

if (tokenInfo) {
// Token found in list
Expand Down Expand Up @@ -965,11 +952,12 @@ export class Solana {
balances: Record<string, number>,
): Promise<void> {
logger.info('Processing all token accounts (fetchAll=true)');
const tokenList = await this.getTokenList();

for (const [mintAddress, tokenAccount] of tokenAccounts) {
try {
// Check if token is in our list
const tokenInfo = this.tokenList.find((t) => t.address === mintAddress);
const tokenInfo = tokenList.find((t) => t.address === mintAddress);

if (tokenInfo) {
const balance = this.getTokenBalance(tokenAccount, tokenInfo.decimals);
Expand All @@ -994,9 +982,10 @@ export class Solana {
tokenAccounts: Map<string, TokenAccount>,
balances: Record<string, number>,
): Promise<void> {
logger.info(`Checking balances for ${this.tokenList.length} tokens in token list`);
const tokenList = await this.getTokenList();
logger.info(`Checking balances for ${tokenList.length} tokens in token list`);

for (const tokenInfo of this.tokenList) {
for (const tokenInfo of tokenList) {
if (tokenInfo.symbol === 'SOL') continue;

const tokenAccount = tokenAccounts.get(tokenInfo.address);
Expand Down
3 changes: 1 addition & 2 deletions src/tokens/routes/addToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ export const addTokenRoute: FastifyPluginAsync = async (fastify) => {
await tokenService.addToken(chain, network, token);

return {
message: `Token ${token.symbol} added/updated successfully in ${chain}/${network}. Gateway restart required.`,
requiresRestart: true,
message: `Token ${token.symbol} added/updated successfully in ${chain}/${network}.`,
};
} catch (error) {
handleTokenError(fastify, error, 'Failed to add token');
Expand Down
3 changes: 1 addition & 2 deletions src/tokens/routes/removeToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ export const removeTokenRoute: FastifyPluginAsync = async (fastify) => {
await tokenService.removeToken(chain, network, address);

return {
message: `Token with address ${address} removed successfully from ${chain}/${network}. Gateway restart required.`,
requiresRestart: true,
message: `Token with address ${address} removed successfully from ${chain}/${network}.`,
};
} catch (error) {
handleTokenError(fastify, error, 'Failed to remove token');
Expand Down
12 changes: 0 additions & 12 deletions src/tokens/routes/save.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,6 @@ export const saveTokenRoute: FastifyPluginAsync = async (fastify) => {

logger.info(`Successfully saved token ${token.symbol} (${token.address}) to ${chain}/${network}`);

// Refresh token list and trigger balance cache refresh for Solana chains (non-blocking)
if (chain === 'solana') {
const { Solana } = await import('../../chains/solana/solana');
Solana.getInstance(network)
.then(async (solana) => {
// Reload token list to include new token
await solana.loadTokens();
logger.info(`Reloaded token list for solana/${network} with new token ${token.symbol}`);
})
.catch((err) => logger.warn(`Failed to refresh balances after adding token: ${err.message}`));
}

return {
message: `Token ${token.symbol} has been added to the token list for ${chain}/${network}`,
token,
Expand Down
4 changes: 0 additions & 4 deletions src/tokens/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,6 @@ export const TokenOperationResponseSchema = Type.Object({
message: Type.String({
description: 'Success message',
}),
requiresRestart: Type.Boolean({
description: 'Whether gateway restart is required',
default: true,
}),
});

export type TokenOperationResponse = typeof TokenOperationResponseSchema.static;
Expand Down
Loading
Loading