-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { validationResult } from 'express-validator' | ||
Check failure on line 1 in packages/rest-api/src/controllers/destinationTokensController.ts GitHub Actions / lint
|
||
import { tokenAddressToToken } from '../utils/tokenAddressToToken' | ||
import { BRIDGE_ROUTE_MAPPING } from '../utils/bridgeRouteMapping' | ||
|
||
export const destinationTokensController = async (req, res) => { | ||
const errors = validationResult(req) | ||
if (!errors.isEmpty()) { | ||
return res.status(400).json({ errors: errors.array() }) | ||
} | ||
|
||
try { | ||
const { fromChain, fromToken } = req.query | ||
|
||
const fromTokenInfo = tokenAddressToToken(fromChain.toString(), fromToken) | ||
|
||
const constructedKey = `${fromTokenInfo.symbol}-${fromChain}` | ||
|
||
const options = BRIDGE_ROUTE_MAPPING[constructedKey] | ||
|
||
res.json(options) | ||
} catch (err) { | ||
res.status(500).json({ | ||
error: | ||
'An unexpected error occurred in /destinationTokens. Please try again later.', | ||
details: err.message, | ||
}) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import express from 'express' | ||
import { check } from 'express-validator' | ||
import { isAddress } from 'ethers/lib/utils' | ||
|
||
import { CHAINS_ARRAY } from '../constants/chains' | ||
import { showFirstValidationError } from '../middleware/showFirstValidationError' | ||
import { destinationTokensController } from '../controllers/destinationTokensController' | ||
import { isTokenAddress } from '../utils/isTokenAddress' | ||
import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain' | ||
|
||
const router = express.Router() | ||
|
||
router.get( | ||
'/', | ||
[ | ||
check('fromChain') | ||
.exists() | ||
.withMessage('fromChain is required') | ||
.isNumeric() | ||
.custom((value) => CHAINS_ARRAY.some((c) => c.id === Number(value))) | ||
.withMessage('Unsupported fromChain'), | ||
check('fromToken') | ||
.exists() | ||
.withMessage('fromToken is required') | ||
.custom((value) => isAddress(value)) | ||
.withMessage('Invalid fromToken address') | ||
.custom((value) => isTokenAddress(value)) | ||
.withMessage('Unsupported fromToken address') | ||
.custom((value, { req }) => | ||
isTokenSupportedOnChain(value, req.query.fromChain as string) | ||
) | ||
.withMessage('Token not supported on specified chain'), | ||
], | ||
showFirstValidationError, | ||
destinationTokensController | ||
) | ||
|
||
export default router |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import request from 'supertest' | ||
import express from 'express' | ||
Check failure on line 2 in packages/rest-api/src/tests/destinationTokensRoute.test.ts GitHub Actions / lint
|
||
import destinationTokensRoute from '../routes/destinationTokensRoute' | ||
|
||
const app = express() | ||
app.use('/destinationTokens', destinationTokensRoute) | ||
|
||
describe('destinatonTokens Route', () => { | ||
it('should return destination tokens for valid input', async () => { | ||
const response = await request(app).get('/destinationTokens').query({ | ||
fromChain: '1', | ||
fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', | ||
}) | ||
|
||
expect(response.status).toBe(200) | ||
expect(Array.isArray(response.body)).toBe(true) | ||
expect(response.body.length).toBeGreaterThan(0) | ||
expect(response.body[0]).toHaveProperty('symbol') | ||
expect(response.body[0]).toHaveProperty('address') | ||
expect(response.body[0]).toHaveProperty('chainId') | ||
}) | ||
|
||
it('should return 400 for unsupported fromChain', async () => { | ||
const response = await request(app).get('/destinationTokens').query({ | ||
fromChain: '999', | ||
fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', | ||
}) | ||
|
||
expect(response.status).toBe(400) | ||
expect(response.body.error).toHaveProperty( | ||
'message', | ||
'Unsupported fromChain' | ||
) | ||
}) | ||
|
||
it('should return 400 for invalid fromToken address', async () => { | ||
const response = await request(app).get('/destinationTokens').query({ | ||
fromChain: '1', | ||
fromToken: 'invalid_address', | ||
}) | ||
|
||
expect(response.status).toBe(400) | ||
expect(response.body.error).toHaveProperty( | ||
'message', | ||
'Invalid fromToken address' | ||
) | ||
}) | ||
|
||
it('should return 400 for token not supported by Synapse', async () => { | ||
const response = await request(app).get('/destinationTokens').query({ | ||
fromChain: '1', | ||
fromToken: '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F', | ||
}) | ||
|
||
expect(response.status).toBe(400) | ||
expect(response.body.error).toHaveProperty( | ||
'message', | ||
'Unsupported fromToken address' | ||
) | ||
}) | ||
|
||
it('should return 400 for token not supported on specified chain', async () => { | ||
const response = await request(app).get('/destinationTokens').query({ | ||
fromChain: '10', | ||
fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', | ||
}) | ||
|
||
expect(response.status).toBe(400) | ||
expect(response.body.error).toHaveProperty( | ||
'message', | ||
'Token not supported on specified chain' | ||
) | ||
}) | ||
|
||
it('should return 400 for missing fromChain', async () => { | ||
const response = await request(app).get('/destinationTokens').query({ | ||
fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', | ||
}) | ||
|
||
expect(response.status).toBe(400) | ||
expect(response.body.error).toHaveProperty( | ||
'message', | ||
'fromChain is required' | ||
) | ||
}) | ||
|
||
it('should return 400 for missing fromToken', async () => { | ||
const response = await request(app).get('/destinationTokens').query({ | ||
fromChain: '1', | ||
}) | ||
|
||
expect(response.status).toBe(400) | ||
expect(response.body.error).toHaveProperty( | ||
'message', | ||
'fromToken is required' | ||
) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { BRIDGE_MAP } from '../constants/bridgeMap' | ||
|
||
interface TokenInfo { | ||
symbol: string | ||
address: string | ||
chainId: string | ||
} | ||
|
||
interface BridgeRoutes { | ||
[key: string]: TokenInfo[] | ||
} | ||
|
||
const constructJSON = ( | ||
swappableMap: typeof BRIDGE_MAP, | ||
exclusionList: string[] | ||
): BridgeRoutes => { | ||
const result: BridgeRoutes = {} | ||
|
||
for (const chainA in swappableMap) { | ||
Check failure on line 19 in packages/rest-api/src/utils/bridgeRouteMapping.ts GitHub Actions / lint
|
||
for (const addressA in swappableMap[chainA]) { | ||
Check failure on line 20 in packages/rest-api/src/utils/bridgeRouteMapping.ts GitHub Actions / lint
|
||
const tokenA = swappableMap[chainA][addressA] | ||
const keyA = `${tokenA.symbol}-${chainA}` | ||
|
||
if (exclusionList.includes(keyA)) continue | ||
Check failure on line 24 in packages/rest-api/src/utils/bridgeRouteMapping.ts GitHub Actions / lint
|
||
|
||
result[keyA] = [] | ||
|
||
for (const chainB in swappableMap) { | ||
if (chainA !== chainB) { | ||
for (const addressB in swappableMap[chainB]) { | ||
Check failure on line 30 in packages/rest-api/src/utils/bridgeRouteMapping.ts GitHub Actions / lint
|
||
const tokenB = swappableMap[chainB][addressB] | ||
const keyB = `${tokenB.symbol}-${chainB}` | ||
|
||
if (exclusionList.includes(keyB)) continue | ||
Check failure on line 34 in packages/rest-api/src/utils/bridgeRouteMapping.ts GitHub Actions / lint
|
||
|
||
const canBridge = tokenA.origin.some( | ||
(bridgeSymbol) => | ||
tokenB.destination.includes(bridgeSymbol) || | ||
tokenB.origin.includes(bridgeSymbol) | ||
) | ||
|
||
if (canBridge) { | ||
result[keyA].push({ | ||
symbol: tokenB.symbol, | ||
address: addressB, | ||
chainId: chainB, | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (result[keyA].length === 0) { | ||
delete result[keyA] | ||
} | ||
} | ||
} | ||
|
||
return result | ||
} | ||
|
||
export const BRIDGE_ROUTE_MAPPING: BridgeRoutes = constructJSON(BRIDGE_MAP, []) |