-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds /destinationTokens route [SLT-204] (#3151)
* Adds /destinationTokens route * ZeroAddress & NativeGasAddress * Adds test for native gas tokens * Checksums incoming token address params
- Loading branch information
1 parent
5052439
commit a03677e
Showing
14 changed files
with
336 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
packages/rest-api/src/controllers/destinationTokensController.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
packages/rest-api/src/tests/destinationTokensRoute.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
) | ||
}) | ||
}) |
Oops, something went wrong.