-
Notifications
You must be signed in to change notification settings - Fork 1
feat: restore api search #159
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
1acc06a
533975b
757d1bb
c828f27
b95857c
cddf736
5d24b4d
814204b
998c76d
49af8db
2824fe8
80aa1ae
0ced8ec
61723c6
94ee9a0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { initTokenList } from '@cowprotocol/repositories'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { FastifyPluginAsync } from 'fastify'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| errorSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ErrorSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| paramsSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| RouteSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| successSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| SuccessSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from './schemas'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const root: FastifyPluginAsync = async (fastify): Promise<void> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // example: http://localhost:3001/1/tokens/initTokenList | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| fastify.get<{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Params: RouteSchema; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Reply: SuccessSchema | ErrorSchema; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }>( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| '/', | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| schema: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| params: paramsSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| response: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| '2XX': successSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| '404': errorSchema, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Describe 500 (and 400) responses in the route schema Handler returns 500, but schema only defines 2XX and 404. Add 500; consider 400 for “unsupported chain” errors. Apply: schema: {
params: paramsSchema,
response: {
- '2XX': successSchema,
- '404': errorSchema,
+ 200: successSchema,
+ 400: errorSchema,
+ 500: errorSchema,
},
},
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function (request, reply) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { chainId } = request.params; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await initTokenList(chainId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| fastify.log.info(`Token list initalized for chain ${chainId}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| reply.send(true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| fastify.log.error('Error searching tokens:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| reply.code(500).send({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: 'Internal server error while searching tokens', | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Fix log/message text and map known errors to proper status codes Aligns copy with route intent and sends 400 for unsupported chains. Apply: - try {
- await initTokenList(chainId);
-
- fastify.log.info(`Token list initalized for chain ${chainId}`);
-
- reply.send(true);
- } catch (error) {
- fastify.log.error('Error searching tokens:', error);
- reply.code(500).send({
- message: 'Internal server error while searching tokens',
- });
- }
+ try {
+ await initTokenList(chainId);
+ fastify.log.info(`Token list initialized for chain ${chainId}`);
+ reply.send(true);
+ } catch (error) {
+ const msg = (error as Error)?.message ?? 'Unknown error';
+ fastify.log.error('Error initializing token list:', error);
+ if (/not supported by CoinGecko/i.test(msg)) {
+ return reply.code(400).send({ message: msg });
+ }
+ reply.code(500).send({
+ message: 'Internal server error while initializing token list',
+ });
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default root; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { FromSchema, JSONSchema } from 'json-schema-to-ts'; | ||
| import { SupportedChainIdSchema } from '../../../../schemas'; | ||
|
|
||
| export const paramsSchema = { | ||
| type: 'object', | ||
| required: ['chainId'], | ||
| additionalProperties: false, | ||
| properties: { | ||
| chainId: SupportedChainIdSchema, | ||
| }, | ||
| } as const satisfies JSONSchema; | ||
|
|
||
| export const successSchema = { | ||
| type: 'boolean', | ||
| } as const satisfies JSONSchema; | ||
|
|
||
| export const errorSchema = { | ||
| type: 'object', | ||
| required: ['message'], | ||
| additionalProperties: false, | ||
| properties: { | ||
| message: { | ||
| title: 'Message', | ||
| description: 'Message describing the error.', | ||
| type: 'string', | ||
| }, | ||
| }, | ||
| } as const satisfies JSONSchema; | ||
|
|
||
| export type RouteSchema = FromSchema<typeof paramsSchema>; | ||
| export type SuccessSchema = FromSchema<typeof successSchema>; | ||
| export type ErrorSchema = FromSchema<typeof errorSchema>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import { getTokenListBySearchParam } from '@cowprotocol/repositories'; | ||
| import { FastifyPluginAsync } from 'fastify'; | ||
| import { | ||
| errorSchema, | ||
| ErrorSchema, | ||
| paramsSchema, | ||
| RouteSchema, | ||
| successSchema, | ||
| SuccessSchema, | ||
| } from './schemas'; | ||
|
|
||
| const root: FastifyPluginAsync = async (fastify): Promise<void> => { | ||
| // example: http://localhost:3010/1/tokens/search/USDC | ||
| fastify.get<{ | ||
| Params: RouteSchema; | ||
| Reply: SuccessSchema | ErrorSchema; | ||
| }>( | ||
| '/', | ||
| { | ||
| schema: { | ||
| params: paramsSchema, | ||
| response: { | ||
| '2XX': successSchema, | ||
| '404': errorSchema, | ||
| }, | ||
| }, | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| }, | ||
| async function (request, reply) { | ||
| const { chainId, searchParam } = request.params; | ||
|
|
||
| try { | ||
| const tokens = await getTokenListBySearchParam(chainId, searchParam); | ||
|
|
||
| fastify.log.info( | ||
| `Token search for "${searchParam}" on chain ${chainId}: ${tokens.length} tokens found` | ||
| ); | ||
|
|
||
| reply.send(tokens); | ||
| } catch (error) { | ||
| fastify.log.error('Error searching tokens:', error); | ||
| reply.code(500).send({ | ||
| message: 'Internal server error while searching tokens', | ||
| }); | ||
| } | ||
| } | ||
| ); | ||
| }; | ||
|
|
||
| export default root; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,81 @@ | ||||||
| import { FromSchema, JSONSchema } from 'json-schema-to-ts'; | ||||||
| import { SupportedChainIdSchema } from '../../../../../schemas'; | ||||||
| import { AllChainIds } from '@cowprotocol/shared'; | ||||||
|
|
||||||
| export const paramsSchema = { | ||||||
| type: 'object', | ||||||
| required: ['chainId', 'searchParam'], | ||||||
| additionalProperties: false, | ||||||
| properties: { | ||||||
| chainId: SupportedChainIdSchema, | ||||||
| searchParam: { | ||||||
| title: 'Search Parameter', | ||||||
| description: 'Token search parameter (name, symbol, or address)', | ||||||
| type: 'string', | ||||||
| minLength: 3, | ||||||
| maxLength: 100, | ||||||
| }, | ||||||
| }, | ||||||
| } as const satisfies JSONSchema; | ||||||
|
|
||||||
| export const successSchema = { | ||||||
| type: 'array', | ||||||
| items: { | ||||||
| type: 'object', | ||||||
| required: ['chainId', 'address', 'name', 'symbol', 'decimals', 'logoURI'], | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say the logoURI is optional
Suggested change
|
||||||
| additionalProperties: false, | ||||||
| properties: { | ||||||
| chainId: { | ||||||
| title: 'Chain ID', | ||||||
| description: 'Blockchain network identifier.', | ||||||
| type: 'integer', | ||||||
| enum: AllChainIds, | ||||||
| }, | ||||||
| address: { | ||||||
| title: 'Token Address', | ||||||
| description: 'Contract address of the token.', | ||||||
| type: 'string', | ||||||
| pattern: '^0x[a-fA-F0-9]{40}$', | ||||||
| }, | ||||||
| name: { | ||||||
| title: 'Name', | ||||||
| description: 'Full name of the token.', | ||||||
| type: 'string', | ||||||
| }, | ||||||
| symbol: { | ||||||
| title: 'Symbol', | ||||||
| description: 'Token symbol/ticker.', | ||||||
| type: 'string', | ||||||
| }, | ||||||
| decimals: { | ||||||
| title: 'Decimals', | ||||||
| description: 'Number of decimal places for the token.', | ||||||
| type: 'integer', | ||||||
| minimum: 0, | ||||||
| maximum: 18, | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The standard is 18, but it's actually not the max AFAICT. https://ethereum.stackexchange.com/questions/118896/can-an-erc-20-have-more-than-18-decimals. I'd remove it:
Suggested change
|
||||||
| }, | ||||||
| logoURI: { | ||||||
| title: 'Logo URI', | ||||||
| description: 'URI to the token logo.', | ||||||
| type: 'string', | ||||||
| }, | ||||||
| }, | ||||||
| }, | ||||||
| } as const satisfies JSONSchema; | ||||||
|
|
||||||
| export const errorSchema = { | ||||||
| type: 'object', | ||||||
| required: ['message'], | ||||||
| additionalProperties: false, | ||||||
| properties: { | ||||||
| message: { | ||||||
| title: 'Message', | ||||||
| description: 'Message describing the error.', | ||||||
| type: 'string', | ||||||
| }, | ||||||
| }, | ||||||
| } as const satisfies JSONSchema; | ||||||
|
|
||||||
| export type RouteSchema = FromSchema<typeof paramsSchema>; | ||||||
| export type SuccessSchema = FromSchema<typeof successSchema>; | ||||||
| export type ErrorSchema = FromSchema<typeof errorSchema>; | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,8 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| import Fastify from 'fastify'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { app } from './app/app'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { logger } from '@cowprotocol/shared'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { getTokenCacheRepository } from '@cowprotocol/services'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { setTokenCacheRepository } from '@cowprotocol/repositories'; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const host = process.env.HOST ?? 'localhost'; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const port = process.env.PORT ? Number(process.env.PORT) : 3001; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -10,6 +12,15 @@ export const server = Fastify({ | |||||||||||||||||||||||||||||||||||||||||||||||
| logger, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| // Initialize token cache repository | ||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const tokenCacheRepository = getTokenCacheRepository(); | ||||||||||||||||||||||||||||||||||||||||||||||||
| setTokenCacheRepository(tokenCacheRepository); | ||||||||||||||||||||||||||||||||||||||||||||||||
| logger.info('Token cache repository initialized with Redis'); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warn('Token cache repository not initialized:', error); | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Startup handles missing Redis gracefully, but DI still hard-fails — align both paths Main catches getTokenCacheRepository() failures and proceeds, but inversify.config.ts unconditionally constructs/binds the same repo and will throw if Redis isn’t configured. Result: app still crashes during container build even though this block tries to degrade gracefully. Apply this change in apps/api/src/app/inversify.config.ts to make the binding optional and consistent with this file’s behavior: - const tokenCacheRepository = getTokenCacheRepository();
+ let tokenCacheRepository: TokenCacheRepository | undefined;
+ try {
+ tokenCacheRepository = getTokenCacheRepository();
+ } catch (err) {
+ logger.warn('Token cache repository not initialized; proceeding without Redis', err);
+ }
...
- apiContainer
- .bind<TokenCacheRepository>(tokenCacheRepositorySymbol)
- .toConstantValue(tokenCacheRepository);
+ if (tokenCacheRepository) {
+ apiContainer
+ .bind<TokenCacheRepository>(tokenCacheRepositorySymbol)
+ .toConstantValue(tokenCacheRepository);
+ }Additionally consider unifying on a single source of truth (either DI or the module-level setter) to avoid double instantiation. I can draft that follow-up if you want. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Unconditional Redis dependency here can crash API startup; remove wiring from main or guard it. Recommended (remove from main; let inversify.config.ts own lifecycle): -import { getTokenCacheRepository } from '@cowprotocol/services';
-import { setTokenCacheRepository } from '@cowprotocol/repositories';
...
-const tokenCacheRepository = getTokenCacheRepository();
-setTokenCacheRepository(tokenCacheRepository);If you must keep it in main, at least guard: +import { getTokenCacheRepository } from '@cowprotocol/services';
+import { setTokenCacheRepository } from '@cowprotocol/repositories';
...
-const tokenCacheRepository = getTokenCacheRepository();
-setTokenCacheRepository(tokenCacheRepository);
+try {
+ const tokenCacheRepository = getTokenCacheRepository();
+ setTokenCacheRepository(tokenCacheRepository);
+ server.log.info('Token cache repository initialized');
+} catch (err) {
+ server.log.warn({ err }, 'Token cache repository not initialized; proceeding without Redis');
+}Also applies to: 4-5 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| // Register your application as a normal plugin. | ||||||||||||||||||||||||||||||||||||||||||||||||
| server.register(app); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,9 @@ export default { | |
| transform: { | ||
| '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }], | ||
| }, | ||
| transformIgnorePatterns: [ | ||
| 'node_modules/(?!(node-fetch|data-uri-to-buffer|fetch-blob|formdata-polyfill|@cowprotocol|@uniswap)/)', | ||
| ], | ||
|
Comment on lines
+9
to
+11
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainCentralize the ESM allowlist and verify ts-jest can transpile JS.
Apply if centralizing in - transformIgnorePatterns: [
- 'node_modules/(?!(node-fetch|data-uri-to-buffer|fetch-blob|formdata-polyfill|@cowprotocol|@uniswap)/)',
- ],Add to jest.preset.js (outside this file): // jest.preset.js
module.exports = {
// ...
transformIgnorePatterns: [
'node_modules/(?!(node-fetch|data-uri-to-buffer|fetch-blob|formdata-polyfill|@cowprotocol|@uniswap)/)',
],
};Verify config consistency and 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== Check allowJs in tsconfig.spec.json =="
fd -a tsconfig.spec.json | while read -r f; do
echo "-> $f"
rg -n '"allowJs"\s*:\s*true' "$f" || echo "allowJs not set to true"
done
echo
echo "== transformIgnorePatterns occurrences (should be identical across projects) =="
rg -n --glob "**/jest.config.ts" -C2 "transformIgnorePatterns|node_modules/\(\?\!\("Length of output: 1511 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== transformIgnorePatterns occurrences in jest.config.ts across repo =="
rg -n --glob "**/jest.config.ts" "transformIgnorePatterns" -C2 || echo "No matches found"
echo
echo "== transformIgnorePatterns occurrences in jest.preset.js =="
rg -n --glob "**/jest.preset.js" "transformIgnorePatterns" -C2 || echo "None in jest.preset.js"Length of output: 1395 Enable JS transpilation and centralize transformIgnorePatterns
🤖 Prompt for AI Agents |
||
| moduleFileExtensions: ['ts', 'js', 'html'], | ||
| coverageDirectory: '../../coverage/libs/repositories', | ||
| setupFilesAfterEnv: ['../../jest.setup.ts'], | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,41 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import { SupportedChainId } from '@cowprotocol/cow-sdk'; | ||||||||||||||||||||||||||||||||||||||||||
| import { TokenFromAPI } from './types'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| async function fetchTokensFromCoinGecko( | ||||||||||||||||||||||||||||||||||||||||||
| chainName: string, | ||||||||||||||||||||||||||||||||||||||||||
| chainId: SupportedChainId | ||||||||||||||||||||||||||||||||||||||||||
| ): Promise<TokenFromAPI[]> { | ||||||||||||||||||||||||||||||||||||||||||
| const tokenSource = `https://tokens.coingecko.com/${chainName}/all.json`; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| console.log(`Fetching tokens for ${chainName}`); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(tokenSource); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+13
to
+14
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add timeout to external fetch to prevent hanging requests External calls must have timeouts. Abort the fetch after a sane limit. Apply: - const response = await fetch(tokenSource);
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
+ const response = await fetch(tokenSource, {
+ signal: controller.signal,
+ headers: { accept: 'application/json' },
+ });
+ clearTimeout(timeout);Add at file top (outside the function): const FETCH_TIMEOUT_MS = 15_000;🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| if (!response.ok) { | ||||||||||||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||||||||||||
| `Failed to fetch tokens from ${tokenSource}: ${response.status} ${response.statusText}` | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const data = await response.json(); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!data.tokens || !Array.isArray(data.tokens)) { | ||||||||||||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||||||||||||
| `Invalid token list format from ${tokenSource}: missing or invalid tokens array` | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Harden JSON parsing and shape validation Catch parse errors explicitly and validate presence of Apply: - const data = await response.json();
-
- if (!data.tokens || !Array.isArray(data.tokens)) {
+ let data: unknown;
+ try {
+ data = await response.json();
+ } catch (e) {
+ throw new Error(
+ `Failed to parse token list JSON from ${tokenSource}: ${(e as Error).message}`
+ );
+ }
+ if (!data || typeof data !== 'object' || !Array.isArray((data as any).tokens)) {
throw new Error(
`Invalid token list format from ${tokenSource}: missing or invalid tokens array`
);
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| console.log(`Fetched ${data.tokens.length} tokens for ${chainName}`); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return data.tokens.map((token: TokenFromAPI) => ({ | ||||||||||||||||||||||||||||||||||||||||||
| ...token, | ||||||||||||||||||||||||||||||||||||||||||
| chainId, | ||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+34
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Normalize mapped fields and provide safe defaults Do not trust upstream types blindly; map and coerce to our contract. Apply: - return data.tokens.map((token: TokenFromAPI) => ({
- ...token,
- chainId,
- }));
+ const { tokens } = data as { tokens: any[] };
+ return tokens.map((t: any): TokenFromAPI => ({
+ chainId,
+ address: String(t?.address ?? ''),
+ name: String(t?.name ?? ''),
+ symbol: String(t?.symbol ?? ''),
+ decimals: Number.isFinite(t?.decimals) ? Number(t.decimals) : 18,
+ logoURI: typeof t?.logoURI === 'string' ? t.logoURI : '',
+ }));📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export async function getTokensByChainName( | ||||||||||||||||||||||||||||||||||||||||||
| chainName: string, | ||||||||||||||||||||||||||||||||||||||||||
| chainId: SupportedChainId | ||||||||||||||||||||||||||||||||||||||||||
| ): Promise<TokenFromAPI[]> { | ||||||||||||||||||||||||||||||||||||||||||
| return fetchTokensFromCoinGecko(chainName, chainId); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './tokenList'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard token-cache DI binding; don’t crash when Redis is absent
getTokenCacheRepository() throws without Redis. Wrap creation, bind only if available, and keep startup consistent with main.ts’ graceful path.
Longer-term, consider sourcing the single instance from DI and calling setTokenCacheRepository() with that instance to avoid duplication and ensure consistent lifecycle.
Also applies to: 92-95
🤖 Prompt for AI Agents