diff --git a/packages/rest-api/package.json b/packages/rest-api/package.json index ce7806fee9..7914c61fe0 100644 --- a/packages/rest-api/package.json +++ b/packages/rest-api/package.json @@ -23,7 +23,9 @@ "@ethersproject/providers": "^5.7.2", "@ethersproject/units": "5.7.0", "@synapsecns/sdk-router": "^0.11.7", + "@synapsecns/synapse-constants": "^1.8.3", "bignumber": "^1.1.0", + "cross-fetch": "^4.0.0", "dotenv": "^16.4.5", "ethers": "5.7.2", "express": "^4.18.2", diff --git a/packages/rest-api/src/routes/addressIconRoute.ts b/packages/rest-api/src/routes/addressIconRoute.ts new file mode 100644 index 0000000000..3891f6b791 --- /dev/null +++ b/packages/rest-api/src/routes/addressIconRoute.ts @@ -0,0 +1,53 @@ +import express from 'express' +import { BRIDGABLE_TOKENS, Token } from '@synapsecns/synapse-constants' +import fetch from 'cross-fetch' + +const router: express.Router = express.Router() + +router.get('/:chainId/:address.svg', async (req, res) => { + const chainId = parseInt(req.params.chainId, 10) + const address = req.params.address.toLowerCase() + + // Find the token with matching address on the specified chain + const token = Object.values(BRIDGABLE_TOKENS[chainId] || []).find( + (t): t is Token => + typeof t === 'object' && + t !== null && + 'addresses' in t && + Object.entries(t.addresses).some(([chain, addr]) => { + const matches = + parseInt(chain, 10) === chainId && addr.toLowerCase() === address + return matches + }) + ) + + if (!token || !token.icon) { + console.log('Token not found or no icon:', { token }) + res.status(404).json({ error: 'Token icon not found' }) + return + } + + try { + // Fetch the image from the URL + const response = await fetch(token.icon) + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.statusText}`) + } + + const buffer = await response.arrayBuffer() + + // Set cache headers (cache for 1 week) + res.set({ + 'Cache-Control': 'public, max-age=604800', + 'Content-Type': response.headers.get('content-type') || 'image/svg+xml', + }) + + res.send(Buffer.from(buffer)) + } catch (error) { + console.error('Error fetching token icon:', error) + res.status(500).json({ error: 'Failed to fetch token icon' }) + } + return +}) + +export default router diff --git a/packages/rest-api/src/routes/chainIconRoute.ts b/packages/rest-api/src/routes/chainIconRoute.ts new file mode 100644 index 0000000000..708a4bad6c --- /dev/null +++ b/packages/rest-api/src/routes/chainIconRoute.ts @@ -0,0 +1,44 @@ +import express from 'express' +import { CHAINS, Chain } from '@synapsecns/synapse-constants' +import fetch from 'cross-fetch' + +const router: express.Router = express.Router() + +router.get('/:chainId.svg', async (req, res) => { + const chainId = parseInt(req.params.chainId, 10) + + // Find the chain with matching ID + const chain = Object.values(CHAINS).find( + (c): c is Chain => + typeof c === 'object' && c !== null && 'id' in c && c.id === chainId + ) + + if (!chain || !chain.chainImg) { + res.status(404).json({ error: 'Chain icon not found' }) + return + } + + try { + // Fetch the image from the URL + const response = await fetch(chain.chainImg) + if (!response.ok) { + throw new Error(`Failed to fetch image: ${response.statusText}`) + } + + const buffer = await response.arrayBuffer() + + // Set cache headers (cache for 1 week) + res.set({ + 'Cache-Control': 'public, max-age=604800', + 'Content-Type': response.headers.get('content-type') || 'image/svg+xml', + }) + + res.send(Buffer.from(buffer)) + } catch (error) { + console.error('Error fetching chain icon:', error) + res.status(500).json({ error: 'Failed to fetch chain icon' }) + } + return +}) + +export default router diff --git a/packages/rest-api/src/routes/index.ts b/packages/rest-api/src/routes/index.ts index c723d8d21b..0065901805 100644 --- a/packages/rest-api/src/routes/index.ts +++ b/packages/rest-api/src/routes/index.ts @@ -11,6 +11,8 @@ import destinationTxRoute from './destinationTxRoute' import tokenListRoute from './tokenListRoute' import destinationTokensRoute from './destinationTokensRoute' import bridgeLimitsRoute from './bridgeLimitsRoute' +import chainIconRoute from './chainIconRoute' +import addressIconRoute from './addressIconRoute' const router: express.Router = express.Router() @@ -25,5 +27,7 @@ router.use('/bridgeTxStatus', bridgeTxStatusRoute) router.use('/destinationTx', destinationTxRoute) router.use('/tokenList', tokenListRoute) router.use('/destinationTokens', destinationTokensRoute) +router.use('/chainIcon', chainIconRoute) +router.use('/tokenIcon', addressIconRoute) export default router diff --git a/yarn.lock b/yarn.lock index 684939f348..0beee2f97d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18757,12 +18757,7 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" -fdir@^6.2.0: - version "6.4.2" - resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689" - integrity sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ== - -fdir@^6.4.2: +fdir@^6.2.0, fdir@^6.4.2: version "6.4.2" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689" integrity sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ== @@ -37610,12 +37605,7 @@ yaml@2.0.0-1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18" integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== -yaml@^2.3.1, yaml@^2.3.4: - version "2.6.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.0.tgz#14059ad9d0b1680d0f04d3a60fe00f3a857303c3" - integrity sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ== - -yaml@^2.6.0: +yaml@^2.3.1, yaml@^2.3.4, yaml@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.6.0.tgz#14059ad9d0b1680d0f04d3a60fe00f3a857303c3" integrity sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==