Skip to content

Commit

Permalink
Adds /destinationTokens route [SLT-204] (#3151)
Browse files Browse the repository at this point in the history
* Adds /destinationTokens route
* ZeroAddress & NativeGasAddress
* Adds test for native gas tokens
* Checksums incoming token address params
  • Loading branch information
abtestingalpha authored Sep 19, 2024
1 parent 5052439 commit a03677e
Show file tree
Hide file tree
Showing 14 changed files with 336 additions and 3 deletions.
6 changes: 6 additions & 0 deletions packages/rest-api/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,11 @@ module.exports = {
'prettier/prettier': 'off',
},
},
{
files: ['**/*.ts'],
rules: {
'guard-for-in': 'off',
},
},
],
}
3 changes: 1 addition & 2 deletions packages/rest-api/src/constants/bridgeable.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { BridgeableToken } from '../types'
import { CHAINS } from './chains'

const ZeroAddress = '0x0000000000000000000000000000000000000000'
import { ZeroAddress } from '.'

export const GOHM: BridgeableToken = {
addresses: {
Expand Down
3 changes: 3 additions & 0 deletions packages/rest-api/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ export const VALID_BRIDGE_MODULES = [
'SynapseCCTP',
'SynapseRFQ',
]

export const ZeroAddress = '0x0000000000000000000000000000000000000000'
export const NativeGasAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
29 changes: 29 additions & 0 deletions packages/rest-api/src/controllers/destinationTokensController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { validationResult } from 'express-validator'

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,
})
}
}
14 changes: 14 additions & 0 deletions packages/rest-api/src/middleware/checksumAddresses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Request, Response, NextFunction } from 'express'
import { getAddress, isAddress } from 'ethers/lib/utils'

export const checksumAddresses = (addressFields: string[]) => {
return (req: Request, _res: Response, next: NextFunction) => {
for (const field of addressFields) {
const address = req.query[field]
if (typeof address === 'string' && isAddress(address)) {
req.query[field] = getAddress(address)
}
}
next()
}
}
2 changes: 2 additions & 0 deletions packages/rest-api/src/routes/bridgeRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { CHAINS_ARRAY } from '../constants/chains'
import { showFirstValidationError } from '../middleware/showFirstValidationError'
import { bridgeController } from '../controllers/bridgeController'
import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain'
import { checksumAddresses } from '../middleware/checksumAddresses'

const router = express.Router()

router.get(
'/',
checksumAddresses(['fromToken', 'toToken']),
[
check('fromChain')
.isNumeric()
Expand Down
2 changes: 2 additions & 0 deletions packages/rest-api/src/routes/bridgeTxInfoRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import { showFirstValidationError } from '../middleware/showFirstValidationError
import { bridgeTxInfoController } from '../controllers/bridgeTxInfoController'
import { isTokenAddress } from '../utils/isTokenAddress'
import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain'
import { checksumAddresses } from '../middleware/checksumAddresses'

const router = express.Router()

router.get(
'/',
checksumAddresses(['fromToken', 'toToken']),
[
check('fromChain')
.isNumeric()
Expand Down
40 changes: 40 additions & 0 deletions packages/rest-api/src/routes/destinationTokensRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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'
import { checksumAddresses } from '../middleware/checksumAddresses'

const router = express.Router()

router.get(
'/',
checksumAddresses(['fromToken']),
[
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
2 changes: 2 additions & 0 deletions packages/rest-api/src/routes/swapRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { swapController } from '../controllers/swapController'
import { CHAINS_ARRAY } from '../constants/chains'
import { isTokenAddress } from '../utils/isTokenAddress'
import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain'
import { checksumAddresses } from '../middleware/checksumAddresses'

const router = express.Router()

router.get(
'/',
checksumAddresses(['fromToken', 'toToken']),
[
check('chain')
.isNumeric()
Expand Down
2 changes: 2 additions & 0 deletions packages/rest-api/src/routes/swapTxInfoRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import { showFirstValidationError } from '../middleware/showFirstValidationError
import { swapTxInfoController } from '../controllers/swapTxInfoController'
import { isTokenAddress } from '../utils/isTokenAddress'
import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain'
import { checksumAddresses } from '../middleware/checksumAddresses'

const router = express.Router()

router.get(
'/',
checksumAddresses(['fromToken', 'toToken']),
[
check('chain')
.isNumeric()
Expand Down
143 changes: 143 additions & 0 deletions packages/rest-api/src/tests/destinationTokensRoute.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import request from 'supertest'
import express from 'express'

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 destination tokens for valid gas Tokens', async () => {
const response = await request(app).get('/destinationTokens').query({
fromChain: '1',
fromToken: '0x0000000000000000000000000000000000000000',
})

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 precisely the number of destination tokens', async () => {
// 'USDC-534352': [ 'USDC-1', 'USDC-10', 'USDC-8453', 'USDC-42161', 'USDC-59144' ]

const response = await request(app).get('/destinationTokens').query({
fromChain: '534352',
fromToken: '0x06eFdBFf2a14a7c8E15944D1F4A48F9F95F663A4',
})

expect(response.status).toBe(200)
expect(Array.isArray(response.body)).toBe(true)
expect(response.body.length).toBe(5)
expect(response.body[0]).toHaveProperty('symbol')
expect(response.body[0]).toHaveProperty('address')
expect(response.body[0]).toHaveProperty('chainId')
})

it('should return destination tokens for non-checksummed address', async () => {
const response = await request(app).get('/destinationTokens').query({
fromChain: '43114',
fromToken: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7',
})

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'
)
})
})
Loading

0 comments on commit a03677e

Please sign in to comment.