From 5ea9ba2b93941d0982255adac3c1f8aa7431a900 Mon Sep 17 00:00:00 2001 From: defi-moses Date: Mon, 16 Sep 2024 19:01:00 +0100 Subject: [PATCH 1/6] fix swaptxinfo function --- packages/rest-api/src/controllers/swapTxInfoController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rest-api/src/controllers/swapTxInfoController.ts b/packages/rest-api/src/controllers/swapTxInfoController.ts index 9577d3ceea..618239eb5e 100644 --- a/packages/rest-api/src/controllers/swapTxInfoController.ts +++ b/packages/rest-api/src/controllers/swapTxInfoController.ts @@ -10,7 +10,7 @@ export const swapTxInfoController = async (req, res) => { } try { - const { chain, amount } = req.query + const { chain, amount, address } = req.query const fromTokenInfo = res.locals.tokenInfo.fromToken const toTokenInfo = res.locals.tokenInfo.toToken @@ -25,7 +25,7 @@ export const swapTxInfoController = async (req, res) => { const txInfo = await Synapse.swap( Number(chain), - fromTokenInfo.address, + address, toTokenInfo.address, amountInWei, quote.query From e2bab252f535a6f8dc5e1900d83152701395b1f3 Mon Sep 17 00:00:00 2001 From: abtestingalpha Date: Mon, 16 Sep 2024 14:48:45 -0400 Subject: [PATCH 2/6] Updates test coverage command --- packages/rest-api/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rest-api/package.json b/packages/rest-api/package.json index 930a0ce44b..a06c563e91 100644 --- a/packages/rest-api/package.json +++ b/packages/rest-api/package.json @@ -14,7 +14,7 @@ "lint:check": "eslint . --max-warnings=0", "ci:lint": "npm run lint:check", "test": "jest", - "test:coverage": "echo 'No tests defined.'", + "test:coverage": "jest --collect-coverage", "postinstall": "npm run build" }, "dependencies": { From d35d50a5e54d3c94b02cd520cc660e30b91321cf Mon Sep 17 00:00:00 2001 From: defi-moses Date: Mon, 16 Sep 2024 23:35:35 +0100 Subject: [PATCH 3/6] migrating to using token addresses instead of symbols --- .../src/controllers/bridgeController.ts | 23 +++++++-- .../src/controllers/bridgeTxInfoController.ts | 25 +++++++--- .../src/controllers/swapController.ts | 23 +++++++-- .../src/controllers/swapTxInfoController.ts | 25 +++++++--- packages/rest-api/src/routes/bridgeRoute.ts | 16 +++++-- .../rest-api/src/routes/bridgeTxInfoRoute.ts | 22 +++++++-- packages/rest-api/src/routes/swapRoute.ts | 14 ++++-- .../rest-api/src/routes/swapTxInfoRoute.ts | 21 +++++++-- .../rest-api/src/tests/bridgeRoute.test.ts | 28 ++++++----- .../src/tests/bridgeTxInfoRoute.test.ts | 47 +++++++++---------- packages/rest-api/src/tests/swapRoute.test.ts | 23 +++++---- .../src/tests/swapTxInfoRoute.test.ts | 39 +++++++++++---- .../rest-api/src/utils/tokenAddressToToken.ts | 17 +++++++ ...findTokenInfo.ts => tokenSymbolToToken.ts} | 2 +- .../src/validations/validateTokens.ts | 4 +- 15 files changed, 229 insertions(+), 100 deletions(-) create mode 100644 packages/rest-api/src/utils/tokenAddressToToken.ts rename packages/rest-api/src/utils/{findTokenInfo.ts => tokenSymbolToToken.ts} (82%) diff --git a/packages/rest-api/src/controllers/bridgeController.ts b/packages/rest-api/src/controllers/bridgeController.ts index aeead94c52..f5cffdb45c 100644 --- a/packages/rest-api/src/controllers/bridgeController.ts +++ b/packages/rest-api/src/controllers/bridgeController.ts @@ -1,8 +1,10 @@ import { validationResult } from 'express-validator' import { parseUnits } from '@ethersproject/units' +import { isAddress } from 'ethers/lib/utils' import { formatBNToString } from '../utils/formatBNToString' import { Synapse } from '../services/synapseService' +import { tokenAddressToToken } from '../utils/tokenAddressToToken' export const bridgeController = async (req, res) => { const errors = validationResult(req) @@ -10,17 +12,28 @@ export const bridgeController = async (req, res) => { return res.status(400).json({ errors: errors.array() }) } try { - const { fromChain, toChain, amount } = req.query - const fromTokenInfo = res.locals.tokenInfo.fromToken - const toTokenInfo = res.locals.tokenInfo.toToken + const { fromChain, toChain, amount, fromToken, toToken } = req.query + + if (!isAddress(fromToken) || !isAddress(toToken)) { + return res.status(400).json({ error: 'Invalid token address' }) + } + + const fromTokenInfo = tokenAddressToToken(fromChain.toString(), fromToken) + const toTokenInfo = tokenAddressToToken(toChain.toString(), toToken) + + if (!fromTokenInfo || !toTokenInfo) { + return res + .status(400) + .json({ error: 'Token not supported on specified chain' }) + } const amountInWei = parseUnits(amount.toString(), fromTokenInfo.decimals) const resp = await Synapse.allBridgeQuotes( Number(fromChain), Number(toChain), - fromTokenInfo.address, - toTokenInfo.address, + fromToken, + toToken, amountInWei ) const payload = resp.map((quote) => ({ diff --git a/packages/rest-api/src/controllers/bridgeTxInfoController.ts b/packages/rest-api/src/controllers/bridgeTxInfoController.ts index 3f75330dee..62e070d2f4 100644 --- a/packages/rest-api/src/controllers/bridgeTxInfoController.ts +++ b/packages/rest-api/src/controllers/bridgeTxInfoController.ts @@ -1,7 +1,9 @@ import { validationResult } from 'express-validator' import { parseUnits } from '@ethersproject/units' +import { isAddress } from 'ethers/lib/utils' import { Synapse } from '../services/synapseService' +import { tokenAddressToToken } from '../utils/tokenAddressToToken' export const bridgeTxInfoController = async (req, res) => { const errors = validationResult(req) @@ -10,17 +12,28 @@ export const bridgeTxInfoController = async (req, res) => { } try { - const { fromChain, toChain, amount, destAddress } = req.query - const fromTokenInfo = res.locals.tokenInfo.fromToken - const toTokenInfo = res.locals.tokenInfo.toToken + const { fromChain, toChain, amount, destAddress, fromToken, toToken } = req.query + + if (!isAddress(fromToken) || !isAddress(toToken)) { + return res.status(400).json({ error: 'Invalid token address' }) + } + + const fromTokenInfo = tokenAddressToToken(fromChain.toString(), fromToken) + const toTokenInfo = tokenAddressToToken(toChain.toString(), toToken) + + if (!fromTokenInfo || !toTokenInfo) { + return res + .status(400) + .json({ error: 'Token not supported on specified chain' }) + } const amountInWei = parseUnits(amount.toString(), fromTokenInfo.decimals) const quotes = await Synapse.allBridgeQuotes( Number(fromChain), Number(toChain), - fromTokenInfo.address, - toTokenInfo.address, + fromToken, + toToken, amountInWei ) @@ -31,7 +44,7 @@ export const bridgeTxInfoController = async (req, res) => { quote.routerAddress, Number(fromChain), Number(toChain), - fromTokenInfo.address, + fromToken, amountInWei, quote.originQuery, quote.destQuery diff --git a/packages/rest-api/src/controllers/swapController.ts b/packages/rest-api/src/controllers/swapController.ts index 53d28412bb..44716cef13 100644 --- a/packages/rest-api/src/controllers/swapController.ts +++ b/packages/rest-api/src/controllers/swapController.ts @@ -1,7 +1,9 @@ import { validationResult } from 'express-validator' import { formatUnits, parseUnits } from '@ethersproject/units' +import { isAddress } from 'ethers/lib/utils' import { Synapse } from '../services/synapseService' +import { tokenAddressToToken } from '../utils/tokenAddressToToken' export const swapController = async (req, res) => { const errors = validationResult(req) @@ -9,15 +11,26 @@ export const swapController = async (req, res) => { return res.status(400).json({ errors: errors.array() }) } try { - const { chain, amount } = req.query - const fromTokenInfo = res.locals.tokenInfo.fromToken - const toTokenInfo = res.locals.tokenInfo.toToken + const { chain, amount, fromToken, toToken } = req.query + + if (!isAddress(fromToken) || !isAddress(toToken)) { + return res.status(400).json({ error: 'Invalid token address' }) + } + + const fromTokenInfo = tokenAddressToToken(chain.toString(), fromToken) + const toTokenInfo = tokenAddressToToken(chain.toString(), toToken) + + if (!fromTokenInfo || !toTokenInfo) { + return res + .status(400) + .json({ error: 'Token not supported on specified chain' }) + } const amountInWei = parseUnits(amount.toString(), fromTokenInfo.decimals) const quote = await Synapse.swapQuote( Number(chain), - fromTokenInfo.address, - toTokenInfo.address, + fromToken, + toToken, amountInWei ) res.json({ diff --git a/packages/rest-api/src/controllers/swapTxInfoController.ts b/packages/rest-api/src/controllers/swapTxInfoController.ts index 618239eb5e..2aa2f824cd 100644 --- a/packages/rest-api/src/controllers/swapTxInfoController.ts +++ b/packages/rest-api/src/controllers/swapTxInfoController.ts @@ -1,7 +1,9 @@ import { validationResult } from 'express-validator' import { parseUnits } from '@ethersproject/units' +import { isAddress } from 'ethers/lib/utils' import { Synapse } from '../services/synapseService' +import { tokenAddressToToken } from '../utils/tokenAddressToToken' export const swapTxInfoController = async (req, res) => { const errors = validationResult(req) @@ -10,23 +12,34 @@ export const swapTxInfoController = async (req, res) => { } try { - const { chain, amount, address } = req.query - const fromTokenInfo = res.locals.tokenInfo.fromToken - const toTokenInfo = res.locals.tokenInfo.toToken + const { chain, amount, address, fromToken, toToken } = req.query + + if (!isAddress(fromToken) || !isAddress(toToken)) { + return res.status(400).json({ error: 'Invalid token address' }) + } + + const fromTokenInfo = tokenAddressToToken(chain.toString(), fromToken) + const toTokenInfo = tokenAddressToToken(chain.toString(), toToken) + + if (!fromTokenInfo || !toTokenInfo) { + return res + .status(400) + .json({ error: 'Token not supported on specified chain' }) + } const amountInWei = parseUnits(amount.toString(), fromTokenInfo.decimals) const quote = await Synapse.swapQuote( Number(chain), - fromTokenInfo.address, - toTokenInfo.address, + fromToken, + toToken, amountInWei ) const txInfo = await Synapse.swap( Number(chain), address, - toTokenInfo.address, + toToken, amountInWei, quote.query ) diff --git a/packages/rest-api/src/routes/bridgeRoute.ts b/packages/rest-api/src/routes/bridgeRoute.ts index cb00696b5b..c4e03ac540 100644 --- a/packages/rest-api/src/routes/bridgeRoute.ts +++ b/packages/rest-api/src/routes/bridgeRoute.ts @@ -1,8 +1,8 @@ import express from 'express' import { check } from 'express-validator' +import { isAddress } from 'ethers/lib/utils' import { CHAINS_ARRAY } from '../constants/chains' -import { validateTokens } from '../validations/validateTokens' import { showFirstValidationError } from '../middleware/showFirstValidationError' import { bridgeController } from '../controllers/bridgeController' @@ -23,9 +23,17 @@ router.get( .withMessage('Unsupported toChain') .exists() .withMessage('toChain is required'), - validateTokens('fromChain', 'fromToken', 'fromToken'), - validateTokens('toChain', 'toToken', 'toToken'), - check('amount').isNumeric(), + check('fromToken') + .exists() + .withMessage('fromToken is required') + .custom((value) => isAddress(value)) + .withMessage('Invalid fromToken address'), + check('toToken') + .exists() + .withMessage('toToken is required') + .custom((value) => isAddress(value)) + .withMessage('Invalid toToken address'), + check('amount').isNumeric().exists().withMessage('amount is required'), ], showFirstValidationError, bridgeController diff --git a/packages/rest-api/src/routes/bridgeTxInfoRoute.ts b/packages/rest-api/src/routes/bridgeTxInfoRoute.ts index 8929f66202..73f2f74d45 100644 --- a/packages/rest-api/src/routes/bridgeTxInfoRoute.ts +++ b/packages/rest-api/src/routes/bridgeTxInfoRoute.ts @@ -1,8 +1,8 @@ import express from 'express' import { check } from 'express-validator' +import { isAddress } from 'ethers/lib/utils' import { CHAINS_ARRAY } from '../constants/chains' -import { validateTokens } from '../validations/validateTokens' import { showFirstValidationError } from '../middleware/showFirstValidationError' import { bridgeTxInfoController } from '../controllers/bridgeTxInfoController' @@ -23,10 +23,22 @@ router.get( .withMessage('Unsupported toChain') .exists() .withMessage('toChain is required'), - validateTokens('fromChain', 'fromToken', 'fromToken'), - validateTokens('toChain', 'toToken', 'toToken'), - check('amount').isNumeric(), - check('destAddress').isString(), + check('fromToken') + .exists() + .withMessage('fromToken is required') + .custom((value) => isAddress(value)) + .withMessage('Invalid fromToken address'), + check('toToken') + .exists() + .withMessage('toToken is required') + .custom((value) => isAddress(value)) + .withMessage('Invalid toToken address'), + check('amount').isNumeric().exists().withMessage('amount is required'), + check('destAddress') + .exists() + .withMessage('destAddress is required') + .custom((value) => isAddress(value)) + .withMessage('Invalid destination address'), ], showFirstValidationError, bridgeTxInfoController diff --git a/packages/rest-api/src/routes/swapRoute.ts b/packages/rest-api/src/routes/swapRoute.ts index 8aaeac316f..77d279fbc0 100644 --- a/packages/rest-api/src/routes/swapRoute.ts +++ b/packages/rest-api/src/routes/swapRoute.ts @@ -1,7 +1,7 @@ import express from 'express' import { check } from 'express-validator' +import { isAddress } from 'ethers/lib/utils' -import { validateTokens } from '../validations/validateTokens' import { showFirstValidationError } from '../middleware/showFirstValidationError' import { swapController } from '../controllers/swapController' import { CHAINS_ARRAY } from '../constants/chains' @@ -17,8 +17,16 @@ router.get( .withMessage('Unsupported chain') .exists() .withMessage('chain is required'), - validateTokens('chain', 'fromToken', 'fromToken'), - validateTokens('chain', 'toToken', 'toToken'), + check('fromToken') + .exists() + .withMessage('fromToken is required') + .custom((value) => isAddress(value)) + .withMessage('Invalid fromToken address'), + check('toToken') + .exists() + .withMessage('toToken is required') + .custom((value) => isAddress(value)) + .withMessage('Invalid toToken address'), check('amount').isNumeric().exists().withMessage('amount is required'), ], showFirstValidationError, diff --git a/packages/rest-api/src/routes/swapTxInfoRoute.ts b/packages/rest-api/src/routes/swapTxInfoRoute.ts index 9cc9805b6d..956d287aff 100644 --- a/packages/rest-api/src/routes/swapTxInfoRoute.ts +++ b/packages/rest-api/src/routes/swapTxInfoRoute.ts @@ -1,8 +1,8 @@ import express from 'express' import { check } from 'express-validator' +import { isAddress } from 'ethers/lib/utils' import { CHAINS_ARRAY } from '../constants/chains' -import { validateTokens } from '../validations/validateTokens' import { showFirstValidationError } from '../middleware/showFirstValidationError' import { swapTxInfoController } from '../controllers/swapTxInfoController' @@ -17,9 +17,22 @@ router.get( .withMessage('Unsupported chain') .exists() .withMessage('chain is required'), - validateTokens('chain', 'fromToken', 'fromToken'), - validateTokens('chain', 'toToken', 'toToken'), - check('amount').isNumeric(), + check('fromToken') + .exists() + .withMessage('fromToken is required') + .custom((value) => isAddress(value)) + .withMessage('Invalid fromToken address'), + check('toToken') + .exists() + .withMessage('toToken is required') + .custom((value) => isAddress(value)) + .withMessage('Invalid toToken address'), + check('amount').isNumeric().exists().withMessage('amount is required'), + check('address') + .exists() + .withMessage('address is required') + .custom((value) => isAddress(value)) + .withMessage('Invalid Ethereum address'), ], showFirstValidationError, swapTxInfoController diff --git a/packages/rest-api/src/tests/bridgeRoute.test.ts b/packages/rest-api/src/tests/bridgeRoute.test.ts index 16addfc80f..e7795f69a4 100644 --- a/packages/rest-api/src/tests/bridgeRoute.test.ts +++ b/packages/rest-api/src/tests/bridgeRoute.test.ts @@ -11,8 +11,8 @@ describe('Bridge Route with Real Synapse Service', () => { const response = await request(app).get('/bridge').query({ fromChain: '1', toChain: '137', - fromToken: 'USDC', - toToken: 'USDC', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // USDC.e on Polygon amount: '1000', }) expect(response.status).toBe(200) @@ -26,8 +26,8 @@ describe('Bridge Route with Real Synapse Service', () => { const response = await request(app).get('/bridge').query({ fromChain: '999', toChain: '137', - fromToken: 'USDC', - toToken: 'USDC', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', amount: '1000', }) expect(response.status).toBe(400) @@ -41,34 +41,36 @@ describe('Bridge Route with Real Synapse Service', () => { const response = await request(app).get('/bridge').query({ fromChain: '1', toChain: '999', - fromToken: 'USDC', - toToken: 'USDC', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', amount: '1000', }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('message', 'Unsupported toChain') }, 10000) - it('should return 400 for missing fromToken, with error message', async () => { + it('should return 400 for invalid fromToken address, with error message', async () => { const response = await request(app).get('/bridge').query({ fromChain: '1', toChain: '137', - toToken: 'USDC', + fromToken: 'invalid_address', + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', amount: '1000', }) - expect(response.status).toBe(400) - expect(response.body.error).toHaveProperty('field', 'fromToken') + expect(response.body.error).toHaveProperty( + 'message', + 'Invalid fromToken address' + ) }, 10000) it('should return 400 for missing amount, with error message', async () => { const response = await request(app).get('/bridge').query({ fromChain: '1', toChain: '137', - fromToken: 'USDC', - toToken: 'USDC', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', }) - expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('field', 'amount') }, 10000) diff --git a/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts b/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts index 8e83702336..41b9c77b8e 100644 --- a/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts +++ b/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts @@ -11,8 +11,8 @@ describe('Bridge TX Info Route', () => { const response = await request(app).get('/bridgeTxInfo').query({ fromChain: '1', toChain: '137', - fromToken: 'USDC', - toToken: 'USDC', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // USDC on Polygon amount: '1000', destAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', }) @@ -31,8 +31,8 @@ describe('Bridge TX Info Route', () => { const response = await request(app).get('/bridgeTxInfo').query({ fromChain: '999', toChain: '137', - fromToken: 'USDC', - toToken: 'USDC', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', amount: '1000', destAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', }) @@ -43,52 +43,47 @@ describe('Bridge TX Info Route', () => { ) }, 10_000) - it('should return 400 for unsupported toChain', async () => { - const response = await request(app).get('/bridgeTxInfo').query({ - fromChain: '1', - toChain: '999', - fromToken: 'USDC', - toToken: 'USDC', - amount: '1000', - destAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', - }) - expect(response.status).toBe(400) - expect(response.body.error).toHaveProperty('message', 'Unsupported toChain') - }, 10_000) - - it('should return 400 for missing fromToken', async () => { + it('should return 400 for invalid fromToken address', async () => { const response = await request(app).get('/bridgeTxInfo').query({ fromChain: '1', toChain: '137', - toToken: 'USDC', + fromToken: 'invalid_address', + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', amount: '1000', destAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', }) expect(response.status).toBe(400) - expect(response.body.error).toHaveProperty('field', 'fromToken') + expect(response.body.error).toHaveProperty( + 'message', + 'Invalid fromToken address' + ) }, 10_000) it('should return 400 for missing amount', async () => { const response = await request(app).get('/bridgeTxInfo').query({ fromChain: '1', toChain: '137', - fromToken: 'USDC', - toToken: 'USDC', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', destAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('field', 'amount') }, 10_000) - it('should return 400 for missing destAddress', async () => { + it('should return 400 for invalid destAddress', async () => { const response = await request(app).get('/bridgeTxInfo').query({ fromChain: '1', toChain: '137', - fromToken: 'USDC', - toToken: 'USDC', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', amount: '1000', + destAddress: 'invalid_address', }) expect(response.status).toBe(400) - expect(response.body.error).toHaveProperty('field', 'destAddress') + expect(response.body.error).toHaveProperty( + 'message', + 'Invalid destination address' + ) }, 10_000) }) diff --git a/packages/rest-api/src/tests/swapRoute.test.ts b/packages/rest-api/src/tests/swapRoute.test.ts index 24d2e05861..4cf644e8d6 100644 --- a/packages/rest-api/src/tests/swapRoute.test.ts +++ b/packages/rest-api/src/tests/swapRoute.test.ts @@ -10,8 +10,8 @@ describe('Swap Route with Real Synapse Service', () => { it('should return a real swap quote for valid input, 1000 USDC', async () => { const response = await request(app).get('/swap').query({ chain: '1', - fromToken: 'USDC', - toToken: 'DAI', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum + toToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI on Ethereum amount: '1000', }) @@ -24,32 +24,35 @@ describe('Swap Route with Real Synapse Service', () => { it('should return 400 for unsupported chain, with error message', async () => { const response = await request(app).get('/swap').query({ chain: '111', - fromToken: 'USDC', - toToken: 'DAI', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', amount: '1000', }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('message', 'Unsupported chain') - expect(response.body.error).toHaveProperty('field', 'chain') }, 10_000) - it('should return 400 for missing toToken, with error message', async () => { + it('should return 400 for invalid toToken address, with error message', async () => { const response = await request(app).get('/swap').query({ chain: '1', - fromToken: 'USDC', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: 'invalid_address', amount: '1000', }) expect(response.status).toBe(400) - expect(response.body.error).toHaveProperty('field', 'toToken') + expect(response.body.error).toHaveProperty( + 'message', + 'Invalid toToken address' + ) }, 10_000) it('should return 400 for missing amount, with error message', async () => { const response = await request(app).get('/swap').query({ chain: '1', - fromToken: 'USDC', - toToken: 'DAI', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', }) expect(response.status).toBe(400) diff --git a/packages/rest-api/src/tests/swapTxInfoRoute.test.ts b/packages/rest-api/src/tests/swapTxInfoRoute.test.ts index 3c682006da..479c925ef5 100644 --- a/packages/rest-api/src/tests/swapTxInfoRoute.test.ts +++ b/packages/rest-api/src/tests/swapTxInfoRoute.test.ts @@ -10,9 +10,10 @@ describe('Swap TX Info Route with Real Synapse Service', () => { it('should return transaction info for valid input, 1000 USDC to DAI', async () => { const response = await request(app).get('/swapTxInfo').query({ chain: '1', - fromToken: 'USDC', - toToken: 'DAI', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum + toToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI on Ethereum amount: '1000', + address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', }) expect(response.status).toBe(200) expect(response.body).toHaveProperty('data') @@ -20,33 +21,51 @@ describe('Swap TX Info Route with Real Synapse Service', () => { expect(response.body).toHaveProperty('value') }, 10_000) + it('should return 400 for invalid address, with error message', async () => { + const response = await request(app).get('/swapTxInfo').query({ + chain: '1', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + amount: '1000', + address: 'invalid_address', + }) + expect(response.status).toBe(400) + expect(response.body.error).toHaveProperty('message', 'Invalid Ethereum address') + }, 10_000) + it('should return 400 for unsupported chain, with error message', async () => { const response = await request(app).get('/swapTxInfo').query({ chain: '111', - fromToken: 'USDC', - toToken: 'DAI', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', amount: '1000', + address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('message', 'Unsupported chain') - expect(response.body.error).toHaveProperty('field', 'chain') }, 10_000) - it('should return 400 for missing toToken, with error message', async () => { + it('should return 400 for invalid toToken address, with error message', async () => { const response = await request(app).get('/swapTxInfo').query({ chain: '1', - fromToken: 'USDC', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: 'invalid_address', amount: '1000', + address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', }) expect(response.status).toBe(400) - expect(response.body.error).toHaveProperty('field', 'toToken') + expect(response.body.error).toHaveProperty( + 'message', + 'Invalid toToken address' + ) }, 10_000) it('should return 400 for missing amount, with error message', async () => { const response = await request(app).get('/swapTxInfo').query({ chain: '1', - fromToken: 'USDC', - toToken: 'DAI', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('field', 'amount') diff --git a/packages/rest-api/src/utils/tokenAddressToToken.ts b/packages/rest-api/src/utils/tokenAddressToToken.ts new file mode 100644 index 0000000000..f28c9b86e1 --- /dev/null +++ b/packages/rest-api/src/utils/tokenAddressToToken.ts @@ -0,0 +1,17 @@ +import { BRIDGE_MAP } from '../constants/bridgeMap' + +export const tokenAddressToToken = (chain: string, tokenAddress: string) => { + const chainData = BRIDGE_MAP[chain] + if (!chainData) { + return null + } + const tokenInfo = chainData[tokenAddress] + if (!tokenInfo) { + return null + } + return { + address: tokenAddress, + symbol: tokenInfo.symbol, + decimals: tokenInfo.decimals, + } +} diff --git a/packages/rest-api/src/utils/findTokenInfo.ts b/packages/rest-api/src/utils/tokenSymbolToToken.ts similarity index 82% rename from packages/rest-api/src/utils/findTokenInfo.ts rename to packages/rest-api/src/utils/tokenSymbolToToken.ts index 7cd4e9d63d..bed196c851 100644 --- a/packages/rest-api/src/utils/findTokenInfo.ts +++ b/packages/rest-api/src/utils/tokenSymbolToToken.ts @@ -1,6 +1,6 @@ import { BRIDGE_MAP } from '../constants/bridgeMap' -export const findTokenInfo = (chain: string, tokenSymbol: string) => { +export const tokenSymbolToToken = (chain: string, tokenSymbol: string) => { const chainData = BRIDGE_MAP[chain] if (!chainData) { return null diff --git a/packages/rest-api/src/validations/validateTokens.ts b/packages/rest-api/src/validations/validateTokens.ts index c787115e1e..6e89a9c70c 100644 --- a/packages/rest-api/src/validations/validateTokens.ts +++ b/packages/rest-api/src/validations/validateTokens.ts @@ -1,6 +1,6 @@ import { check } from 'express-validator' -import { findTokenInfo } from '../utils/findTokenInfo' +import { tokenSymbolToToken } from '../utils/tokenSymbolToToken' export const validateTokens = (chainParam, tokenParam, paramName) => { return check(tokenParam) @@ -9,7 +9,7 @@ export const validateTokens = (chainParam, tokenParam, paramName) => { .withMessage(`${paramName} is required`) .custom((value, { req }) => { const chain = req.query[chainParam] - const tokenInfo = findTokenInfo(chain, value) + const tokenInfo = tokenSymbolToToken(chain, value) if (!tokenInfo) { throw new Error(`Invalid ${paramName} symbol`) } From b7ba450e469909f778b6c952b0d35280f71adb03 Mon Sep 17 00:00:00 2001 From: defi-moses Date: Tue, 17 Sep 2024 01:23:46 +0100 Subject: [PATCH 4/6] fix linting errors --- packages/rest-api/src/controllers/bridgeTxInfoController.ts | 3 ++- packages/rest-api/src/tests/swapTxInfoRoute.test.ts | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/rest-api/src/controllers/bridgeTxInfoController.ts b/packages/rest-api/src/controllers/bridgeTxInfoController.ts index 62e070d2f4..43ac7910da 100644 --- a/packages/rest-api/src/controllers/bridgeTxInfoController.ts +++ b/packages/rest-api/src/controllers/bridgeTxInfoController.ts @@ -12,7 +12,8 @@ export const bridgeTxInfoController = async (req, res) => { } try { - const { fromChain, toChain, amount, destAddress, fromToken, toToken } = req.query + const { fromChain, toChain, amount, destAddress, fromToken, toToken } = + req.query if (!isAddress(fromToken) || !isAddress(toToken)) { return res.status(400).json({ error: 'Invalid token address' }) diff --git a/packages/rest-api/src/tests/swapTxInfoRoute.test.ts b/packages/rest-api/src/tests/swapTxInfoRoute.test.ts index 479c925ef5..401857d376 100644 --- a/packages/rest-api/src/tests/swapTxInfoRoute.test.ts +++ b/packages/rest-api/src/tests/swapTxInfoRoute.test.ts @@ -30,7 +30,10 @@ describe('Swap TX Info Route with Real Synapse Service', () => { address: 'invalid_address', }) expect(response.status).toBe(400) - expect(response.body.error).toHaveProperty('message', 'Invalid Ethereum address') + expect(response.body.error).toHaveProperty( + 'message', + 'Invalid Ethereum address' + ) }, 10_000) it('should return 400 for unsupported chain, with error message', async () => { From 42890d99801b50e8af07e5f5da818dc55db7a6ed Mon Sep 17 00:00:00 2001 From: defi-moses Date: Tue, 17 Sep 2024 16:48:58 +0100 Subject: [PATCH 5/6] fixing swaptxinfocontroller --- packages/rest-api/src/controllers/swapTxInfoController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rest-api/src/controllers/swapTxInfoController.ts b/packages/rest-api/src/controllers/swapTxInfoController.ts index 2aa2f824cd..e720219497 100644 --- a/packages/rest-api/src/controllers/swapTxInfoController.ts +++ b/packages/rest-api/src/controllers/swapTxInfoController.ts @@ -39,7 +39,7 @@ export const swapTxInfoController = async (req, res) => { const txInfo = await Synapse.swap( Number(chain), address, - toToken, + fromToken, amountInWei, quote.query ) From f0a059b5884e417349eed0c51f68507ff5ba62c0 Mon Sep 17 00:00:00 2001 From: defi-moses Date: Tue, 17 Sep 2024 23:45:17 +0100 Subject: [PATCH 6/6] new tests and new functionality --- packages/rest-api/jest.config.js | 2 +- packages/rest-api/package.json | 1 + .../src/controllers/bridgeController.ts | 11 -------- .../src/controllers/bridgeTxInfoController.ts | 12 --------- .../src/controllers/swapController.ts | 20 ++++++-------- .../src/controllers/swapTxInfoController.ts | 12 --------- packages/rest-api/src/routes/bridgeRoute.ts | 19 +++++++++---- .../rest-api/src/routes/bridgeTxInfoRoute.ts | 18 ++++++++++--- packages/rest-api/src/routes/swapRoute.ts | 19 +++++++++---- .../rest-api/src/routes/swapTxInfoRoute.ts | 18 ++++++++++--- .../rest-api/src/tests/bridgeRoute.test.ts | 27 ++++++++++++++----- .../src/tests/bridgeTxInfoRoute.test.ts | 16 +++++++++++ packages/rest-api/src/tests/swapRoute.test.ts | 15 +++++++++++ .../src/tests/swapTxInfoRoute.test.ts | 15 +++++++++++ packages/rest-api/src/utils/isTokenAddress.ts | 12 +++++++++ .../src/utils/isTokenSupportedOnChain.ts | 16 +++++++++++ 16 files changed, 161 insertions(+), 72 deletions(-) create mode 100644 packages/rest-api/src/utils/isTokenAddress.ts create mode 100644 packages/rest-api/src/utils/isTokenSupportedOnChain.ts diff --git a/packages/rest-api/jest.config.js b/packages/rest-api/jest.config.js index 23f6f815f5..ba447263ea 100644 --- a/packages/rest-api/jest.config.js +++ b/packages/rest-api/jest.config.js @@ -6,5 +6,5 @@ module.exports = { '^.+\\.(ts|tsx)$': 'babel-jest', }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], - moduleDirectories: ['node_modules', 'src'], + moduleDirectories: ['node_modules', ''], } diff --git a/packages/rest-api/package.json b/packages/rest-api/package.json index a06c563e91..c249b07f5f 100644 --- a/packages/rest-api/package.json +++ b/packages/rest-api/package.json @@ -26,6 +26,7 @@ "ethers": "5.7.2", "express": "^4.18.2", "express-validator": "^7.2.0", + "jest": "^29.7.0", "lodash": "^4.17.21" }, "description": "A node.js project exposing a rest api for synapse sdk quotes", diff --git a/packages/rest-api/src/controllers/bridgeController.ts b/packages/rest-api/src/controllers/bridgeController.ts index f5cffdb45c..7bb703631b 100644 --- a/packages/rest-api/src/controllers/bridgeController.ts +++ b/packages/rest-api/src/controllers/bridgeController.ts @@ -1,6 +1,5 @@ import { validationResult } from 'express-validator' import { parseUnits } from '@ethersproject/units' -import { isAddress } from 'ethers/lib/utils' import { formatBNToString } from '../utils/formatBNToString' import { Synapse } from '../services/synapseService' @@ -14,19 +13,9 @@ export const bridgeController = async (req, res) => { try { const { fromChain, toChain, amount, fromToken, toToken } = req.query - if (!isAddress(fromToken) || !isAddress(toToken)) { - return res.status(400).json({ error: 'Invalid token address' }) - } - const fromTokenInfo = tokenAddressToToken(fromChain.toString(), fromToken) const toTokenInfo = tokenAddressToToken(toChain.toString(), toToken) - if (!fromTokenInfo || !toTokenInfo) { - return res - .status(400) - .json({ error: 'Token not supported on specified chain' }) - } - const amountInWei = parseUnits(amount.toString(), fromTokenInfo.decimals) const resp = await Synapse.allBridgeQuotes( diff --git a/packages/rest-api/src/controllers/bridgeTxInfoController.ts b/packages/rest-api/src/controllers/bridgeTxInfoController.ts index 43ac7910da..0a0b4bc7bc 100644 --- a/packages/rest-api/src/controllers/bridgeTxInfoController.ts +++ b/packages/rest-api/src/controllers/bridgeTxInfoController.ts @@ -1,6 +1,5 @@ import { validationResult } from 'express-validator' import { parseUnits } from '@ethersproject/units' -import { isAddress } from 'ethers/lib/utils' import { Synapse } from '../services/synapseService' import { tokenAddressToToken } from '../utils/tokenAddressToToken' @@ -15,18 +14,7 @@ export const bridgeTxInfoController = async (req, res) => { const { fromChain, toChain, amount, destAddress, fromToken, toToken } = req.query - if (!isAddress(fromToken) || !isAddress(toToken)) { - return res.status(400).json({ error: 'Invalid token address' }) - } - const fromTokenInfo = tokenAddressToToken(fromChain.toString(), fromToken) - const toTokenInfo = tokenAddressToToken(toChain.toString(), toToken) - - if (!fromTokenInfo || !toTokenInfo) { - return res - .status(400) - .json({ error: 'Token not supported on specified chain' }) - } const amountInWei = parseUnits(amount.toString(), fromTokenInfo.decimals) diff --git a/packages/rest-api/src/controllers/swapController.ts b/packages/rest-api/src/controllers/swapController.ts index 44716cef13..ac695af971 100644 --- a/packages/rest-api/src/controllers/swapController.ts +++ b/packages/rest-api/src/controllers/swapController.ts @@ -1,6 +1,6 @@ import { validationResult } from 'express-validator' import { formatUnits, parseUnits } from '@ethersproject/units' -import { isAddress } from 'ethers/lib/utils' +import { BigNumber } from '@ethersproject/bignumber' import { Synapse } from '../services/synapseService' import { tokenAddressToToken } from '../utils/tokenAddressToToken' @@ -13,19 +13,9 @@ export const swapController = async (req, res) => { try { const { chain, amount, fromToken, toToken } = req.query - if (!isAddress(fromToken) || !isAddress(toToken)) { - return res.status(400).json({ error: 'Invalid token address' }) - } - const fromTokenInfo = tokenAddressToToken(chain.toString(), fromToken) const toTokenInfo = tokenAddressToToken(chain.toString(), toToken) - if (!fromTokenInfo || !toTokenInfo) { - return res - .status(400) - .json({ error: 'Token not supported on specified chain' }) - } - const amountInWei = parseUnits(amount.toString(), fromTokenInfo.decimals) const quote = await Synapse.swapQuote( Number(chain), @@ -33,9 +23,15 @@ export const swapController = async (req, res) => { toToken, amountInWei ) + + const formattedMaxAmountOut = formatUnits( + BigNumber.from(quote.maxAmountOut), + toTokenInfo.decimals + ) + res.json({ - maxAmountOut: formatUnits(quote.maxAmountOut, toTokenInfo.decimals), ...quote, + maxAmountOut: formattedMaxAmountOut, }) } catch (err) { res.status(500).json({ diff --git a/packages/rest-api/src/controllers/swapTxInfoController.ts b/packages/rest-api/src/controllers/swapTxInfoController.ts index e720219497..49f63891db 100644 --- a/packages/rest-api/src/controllers/swapTxInfoController.ts +++ b/packages/rest-api/src/controllers/swapTxInfoController.ts @@ -1,6 +1,5 @@ import { validationResult } from 'express-validator' import { parseUnits } from '@ethersproject/units' -import { isAddress } from 'ethers/lib/utils' import { Synapse } from '../services/synapseService' import { tokenAddressToToken } from '../utils/tokenAddressToToken' @@ -14,18 +13,7 @@ export const swapTxInfoController = async (req, res) => { try { const { chain, amount, address, fromToken, toToken } = req.query - if (!isAddress(fromToken) || !isAddress(toToken)) { - return res.status(400).json({ error: 'Invalid token address' }) - } - const fromTokenInfo = tokenAddressToToken(chain.toString(), fromToken) - const toTokenInfo = tokenAddressToToken(chain.toString(), toToken) - - if (!fromTokenInfo || !toTokenInfo) { - return res - .status(400) - .json({ error: 'Token not supported on specified chain' }) - } const amountInWei = parseUnits(amount.toString(), fromTokenInfo.decimals) diff --git a/packages/rest-api/src/routes/bridgeRoute.ts b/packages/rest-api/src/routes/bridgeRoute.ts index c4e03ac540..b27e9c99d8 100644 --- a/packages/rest-api/src/routes/bridgeRoute.ts +++ b/packages/rest-api/src/routes/bridgeRoute.ts @@ -1,10 +1,11 @@ import express from 'express' import { check } from 'express-validator' -import { isAddress } from 'ethers/lib/utils' +import { isTokenAddress } from '../utils/isTokenAddress' import { CHAINS_ARRAY } from '../constants/chains' import { showFirstValidationError } from '../middleware/showFirstValidationError' import { bridgeController } from '../controllers/bridgeController' +import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain' const router = express.Router() @@ -26,13 +27,21 @@ router.get( check('fromToken') .exists() .withMessage('fromToken is required') - .custom((value) => isAddress(value)) - .withMessage('Invalid fromToken address'), + .custom((value) => isTokenAddress(value)) + .withMessage('Invalid fromToken address') + .custom((value, { req }) => + isTokenSupportedOnChain(value, req.query.fromChain as string) + ) + .withMessage('Token not supported on specified chain'), check('toToken') .exists() .withMessage('toToken is required') - .custom((value) => isAddress(value)) - .withMessage('Invalid toToken address'), + .custom((value) => isTokenAddress(value)) + .withMessage('Invalid toToken address') + .custom((value, { req }) => + isTokenSupportedOnChain(value, req.query.toChain as string) + ) + .withMessage('Token not supported on specified chain'), check('amount').isNumeric().exists().withMessage('amount is required'), ], showFirstValidationError, diff --git a/packages/rest-api/src/routes/bridgeTxInfoRoute.ts b/packages/rest-api/src/routes/bridgeTxInfoRoute.ts index 73f2f74d45..8287143cea 100644 --- a/packages/rest-api/src/routes/bridgeTxInfoRoute.ts +++ b/packages/rest-api/src/routes/bridgeTxInfoRoute.ts @@ -5,6 +5,8 @@ import { isAddress } from 'ethers/lib/utils' import { CHAINS_ARRAY } from '../constants/chains' import { showFirstValidationError } from '../middleware/showFirstValidationError' import { bridgeTxInfoController } from '../controllers/bridgeTxInfoController' +import { isTokenAddress } from '../utils/isTokenAddress' +import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain' const router = express.Router() @@ -26,13 +28,21 @@ router.get( check('fromToken') .exists() .withMessage('fromToken is required') - .custom((value) => isAddress(value)) - .withMessage('Invalid fromToken address'), + .custom((value) => isTokenAddress(value)) + .withMessage('Invalid fromToken address') + .custom((value, { req }) => + isTokenSupportedOnChain(value, req.query.fromChain as string) + ) + .withMessage('Token not supported on specified chain'), check('toToken') .exists() .withMessage('toToken is required') - .custom((value) => isAddress(value)) - .withMessage('Invalid toToken address'), + .custom((value) => isTokenAddress(value)) + .withMessage('Invalid toToken address') + .custom((value, { req }) => + isTokenSupportedOnChain(value, req.query.toChain as string) + ) + .withMessage('Token not supported on specified chain'), check('amount').isNumeric().exists().withMessage('amount is required'), check('destAddress') .exists() diff --git a/packages/rest-api/src/routes/swapRoute.ts b/packages/rest-api/src/routes/swapRoute.ts index 77d279fbc0..931efec5ef 100644 --- a/packages/rest-api/src/routes/swapRoute.ts +++ b/packages/rest-api/src/routes/swapRoute.ts @@ -1,10 +1,11 @@ import express from 'express' import { check } from 'express-validator' -import { isAddress } from 'ethers/lib/utils' import { showFirstValidationError } from '../middleware/showFirstValidationError' import { swapController } from '../controllers/swapController' import { CHAINS_ARRAY } from '../constants/chains' +import { isTokenAddress } from '../utils/isTokenAddress' +import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain' const router = express.Router() @@ -20,13 +21,21 @@ router.get( check('fromToken') .exists() .withMessage('fromToken is required') - .custom((value) => isAddress(value)) - .withMessage('Invalid fromToken address'), + .custom((value) => isTokenAddress(value)) + .withMessage('Invalid fromToken address') + .custom((value, { req }) => + isTokenSupportedOnChain(value, req.query.chain as string) + ) + .withMessage('Token not supported on specified chain'), check('toToken') .exists() .withMessage('toToken is required') - .custom((value) => isAddress(value)) - .withMessage('Invalid toToken address'), + .custom((value) => isTokenAddress(value)) + .withMessage('Invalid toToken address') + .custom((value, { req }) => + isTokenSupportedOnChain(value, req.query.chain as string) + ) + .withMessage('Token not supported on specified chain'), check('amount').isNumeric().exists().withMessage('amount is required'), ], showFirstValidationError, diff --git a/packages/rest-api/src/routes/swapTxInfoRoute.ts b/packages/rest-api/src/routes/swapTxInfoRoute.ts index 956d287aff..a54ce1cef9 100644 --- a/packages/rest-api/src/routes/swapTxInfoRoute.ts +++ b/packages/rest-api/src/routes/swapTxInfoRoute.ts @@ -5,6 +5,8 @@ import { isAddress } from 'ethers/lib/utils' import { CHAINS_ARRAY } from '../constants/chains' import { showFirstValidationError } from '../middleware/showFirstValidationError' import { swapTxInfoController } from '../controllers/swapTxInfoController' +import { isTokenAddress } from '../utils/isTokenAddress' +import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain' const router = express.Router() @@ -20,13 +22,21 @@ router.get( check('fromToken') .exists() .withMessage('fromToken is required') - .custom((value) => isAddress(value)) - .withMessage('Invalid fromToken address'), + .custom((value) => isTokenAddress(value)) + .withMessage('Invalid fromToken address') + .custom((value, { req }) => + isTokenSupportedOnChain(value, req.query.chain as string) + ) + .withMessage('Token not supported on specified chain'), check('toToken') .exists() .withMessage('toToken is required') - .custom((value) => isAddress(value)) - .withMessage('Invalid toToken address'), + .custom((value) => isTokenAddress(value)) + .withMessage('Invalid toToken address') + .custom((value, { req }) => + isTokenSupportedOnChain(value, req.query.chain as string) + ) + .withMessage('Token not supported on specified chain'), check('amount').isNumeric().exists().withMessage('amount is required'), check('address') .exists() diff --git a/packages/rest-api/src/tests/bridgeRoute.test.ts b/packages/rest-api/src/tests/bridgeRoute.test.ts index e7795f69a4..dcef8451ec 100644 --- a/packages/rest-api/src/tests/bridgeRoute.test.ts +++ b/packages/rest-api/src/tests/bridgeRoute.test.ts @@ -7,12 +7,12 @@ const app = express() app.use('/bridge', bridgeRoute) describe('Bridge Route with Real Synapse Service', () => { - it('should return bridge quotes for valid input, 1000 USDC from Ethereum to Polygon', async () => { + it('should return bridge quotes for valid input, 1000 USDC from Ethereum to Optimism', async () => { const response = await request(app).get('/bridge').query({ fromChain: '1', - toChain: '137', + toChain: '10', fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC on Ethereum - toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // USDC.e on Polygon + toToken: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', // USDC on Optimism amount: '1000', }) expect(response.status).toBe(200) @@ -42,7 +42,7 @@ describe('Bridge Route with Real Synapse Service', () => { fromChain: '1', toChain: '999', fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + toToken: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', amount: '1000', }) expect(response.status).toBe(400) @@ -64,12 +64,27 @@ describe('Bridge Route with Real Synapse Service', () => { ) }, 10000) - it('should return 400 for missing amount, with error message', async () => { + it('should return 400 for token not supported on specified chain, with error message', async () => { const response = await request(app).get('/bridge').query({ fromChain: '1', toChain: '137', - fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + fromToken: '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F', // SNX on Ethereum (Not supported) toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + amount: '1000', + }) + expect(response.status).toBe(400) + expect(response.body.error).toHaveProperty( + 'message', + 'Invalid fromToken address' + ) + }, 10000) + + it('should return 400 for missing amount, with error message', async () => { + const response = await request(app).get('/bridge').query({ + fromChain: '1', + toChain: '10', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('field', 'amount') diff --git a/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts b/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts index 41b9c77b8e..35ff81f19a 100644 --- a/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts +++ b/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts @@ -59,6 +59,22 @@ describe('Bridge TX Info Route', () => { ) }, 10_000) + it('should return 400 for token not supported on specified chain', async () => { + const response = await request(app).get('/bridgeTxInfo').query({ + fromChain: '1', + toChain: '137', + fromToken: '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F', // SNX on Ethereum (Not supported) + toToken: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + amount: '1000', + destAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', + }) + expect(response.status).toBe(400) + expect(response.body.error).toHaveProperty( + 'message', + 'Invalid fromToken address' + ) + }, 10_000) + it('should return 400 for missing amount', async () => { const response = await request(app).get('/bridgeTxInfo').query({ fromChain: '1', diff --git a/packages/rest-api/src/tests/swapRoute.test.ts b/packages/rest-api/src/tests/swapRoute.test.ts index 4cf644e8d6..6c6d7ac43b 100644 --- a/packages/rest-api/src/tests/swapRoute.test.ts +++ b/packages/rest-api/src/tests/swapRoute.test.ts @@ -48,6 +48,21 @@ describe('Swap Route with Real Synapse Service', () => { ) }, 10_000) + it('should return 400 for token not supported on specified chain', async () => { + const response = await request(app).get('/swap').query({ + chain: '1', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F', // SNX on Ethereum (Not supported) + amount: '1000', + }) + + expect(response.status).toBe(400) + expect(response.body.error).toHaveProperty( + 'message', + 'Invalid toToken address' + ) + }, 10_000) + it('should return 400 for missing amount, with error message', async () => { const response = await request(app).get('/swap').query({ chain: '1', diff --git a/packages/rest-api/src/tests/swapTxInfoRoute.test.ts b/packages/rest-api/src/tests/swapTxInfoRoute.test.ts index 401857d376..3faf9ca8ea 100644 --- a/packages/rest-api/src/tests/swapTxInfoRoute.test.ts +++ b/packages/rest-api/src/tests/swapTxInfoRoute.test.ts @@ -63,6 +63,21 @@ describe('Swap TX Info Route with Real Synapse Service', () => { ) }, 10_000) + it('should return 400 for token not supported on specified chain', async () => { + const response = await request(app).get('/swapTxInfo').query({ + chain: '1', + fromToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + toToken: '0xC011a73ee8576Fb46F5E1c5751cA3B9Fe0af2a6F', // SNX on Ethereum (Not supported) + amount: '1000', + address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', + }) + expect(response.status).toBe(400) + expect(response.body.error).toHaveProperty( + 'message', + 'Invalid toToken address' + ) + }, 10_000) + it('should return 400 for missing amount, with error message', async () => { const response = await request(app).get('/swapTxInfo').query({ chain: '1', diff --git a/packages/rest-api/src/utils/isTokenAddress.ts b/packages/rest-api/src/utils/isTokenAddress.ts new file mode 100644 index 0000000000..7945b1ba4c --- /dev/null +++ b/packages/rest-api/src/utils/isTokenAddress.ts @@ -0,0 +1,12 @@ +import { BridgeableToken } from '../types' +import * as bridgeableTokens from '../constants/bridgeable' + +export const isTokenAddress = (address: string): boolean => { + const normalizedAddress = address.toLowerCase() + + return Object.values(bridgeableTokens).some((token: BridgeableToken) => + Object.values(token.addresses).some( + (tokenAddress: string) => tokenAddress.toLowerCase() === normalizedAddress + ) + ) +} diff --git a/packages/rest-api/src/utils/isTokenSupportedOnChain.ts b/packages/rest-api/src/utils/isTokenSupportedOnChain.ts new file mode 100644 index 0000000000..161404a6f6 --- /dev/null +++ b/packages/rest-api/src/utils/isTokenSupportedOnChain.ts @@ -0,0 +1,16 @@ +import { BridgeableToken } from '../types' +import * as bridgeableTokens from '../constants/bridgeable' + +export const isTokenSupportedOnChain = ( + tokenAddress: string, + chainId: string +): boolean => { + const normalizedAddress = tokenAddress.toLowerCase() + const chainIdNumber = parseInt(chainId, 10) + + return Object.values(bridgeableTokens).some( + (token: BridgeableToken) => + token.addresses[chainIdNumber] !== undefined && + token.addresses[chainIdNumber].toLowerCase() === normalizedAddress + ) +}