Skip to content

Commit

Permalink
Adds /destinationTokens route
Browse files Browse the repository at this point in the history
  • Loading branch information
abtestingalpha committed Sep 19, 2024
1 parent d4d1c5a commit 8adb913
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 0 deletions.
28 changes: 28 additions & 0 deletions packages/rest-api/src/controllers/destinationTokensController.ts
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

View workflow job for this annotation

GitHub Actions / lint

There should be at least one empty line between import groups

Check failure on line 1 in packages/rest-api/src/controllers/destinationTokensController.ts

View workflow job for this annotation

GitHub Actions / lint

There should be at least one empty line between import groups
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,
})
}
}
38 changes: 38 additions & 0 deletions packages/rest-api/src/routes/destinationTokensRoute.ts
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
2 changes: 2 additions & 0 deletions packages/rest-api/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import getSynapseTxIdRoute from './getSynapseTxIdRoute'
import getBridgeTxStatusRoute from './getBridgeTxStatusRoute'
import getDestinationTxRoute from './getDestinationTxRoute'
import tokenListRoute from './tokenListRoute'
import destinationTokensRoute from './destinationTokensRoute'

const router = express.Router()

Expand All @@ -21,5 +22,6 @@ router.use('/getSynapseTxId', getSynapseTxIdRoute)
router.use('/getBridgeTxStatus', getBridgeTxStatusRoute)
router.use('/getDestinationTx', getDestinationTxRoute)
router.use('/tokenList', tokenListRoute)
router.use('/destinationTokens', destinationTokensRoute)

export default router
98 changes: 98 additions & 0 deletions packages/rest-api/src/tests/destinationTokensRoute.test.ts
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

View workflow job for this annotation

GitHub Actions / lint

There should be at least one empty line between import groups

Check failure on line 2 in packages/rest-api/src/tests/destinationTokensRoute.test.ts

View workflow job for this annotation

GitHub Actions / lint

There should be at least one empty line between import groups
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'
)
})
})
62 changes: 62 additions & 0 deletions packages/rest-api/src/utils/bridgeRouteMapping.ts
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

View workflow job for this annotation

GitHub Actions / lint

The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype

Check failure on line 19 in packages/rest-api/src/utils/bridgeRouteMapping.ts

View workflow job for this annotation

GitHub Actions / lint

The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype
for (const addressA in swappableMap[chainA]) {

Check failure on line 20 in packages/rest-api/src/utils/bridgeRouteMapping.ts

View workflow job for this annotation

GitHub Actions / lint

The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype

Check failure on line 20 in packages/rest-api/src/utils/bridgeRouteMapping.ts

View workflow job for this annotation

GitHub Actions / lint

The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype
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

View workflow job for this annotation

GitHub Actions / lint

Expected { after 'if' condition

Check failure on line 24 in packages/rest-api/src/utils/bridgeRouteMapping.ts

View workflow job for this annotation

GitHub Actions / lint

Expected { after 'if' condition

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

View workflow job for this annotation

GitHub Actions / lint

The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype

Check failure on line 30 in packages/rest-api/src/utils/bridgeRouteMapping.ts

View workflow job for this annotation

GitHub Actions / lint

The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype
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

View workflow job for this annotation

GitHub Actions / lint

Expected { after 'if' condition

Check failure on line 34 in packages/rest-api/src/utils/bridgeRouteMapping.ts

View workflow job for this annotation

GitHub Actions / lint

Expected { after 'if' condition

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, [])

0 comments on commit 8adb913

Please sign in to comment.