From 0eae09701a46722fc2bbffcbcf2b300e6389fcb2 Mon Sep 17 00:00:00 2001 From: Moses <103143573+Defi-Moses@users.noreply.github.com> Date: Mon, 18 Nov 2024 19:49:35 +0700 Subject: [PATCH] bridge endpoint and swap endpoint consolidation for swap data (#3371) --- .../src/controllers/bridgeController.ts | 62 +++++++++++++------ .../src/controllers/swapController.ts | 13 +++- packages/rest-api/src/routes/bridgeRoute.ts | 20 ++++++ packages/rest-api/src/routes/swapRoute.ts | 21 +++++++ .../rest-api/src/tests/bridgeRoute.test.ts | 44 +++++++++++-- packages/rest-api/src/tests/swapRoute.test.ts | 46 +++++++++++--- packages/rest-api/swagger.json | 18 ++++++ 7 files changed, 192 insertions(+), 32 deletions(-) diff --git a/packages/rest-api/src/controllers/bridgeController.ts b/packages/rest-api/src/controllers/bridgeController.ts index 96847bccc2..44aabb5ea4 100644 --- a/packages/rest-api/src/controllers/bridgeController.ts +++ b/packages/rest-api/src/controllers/bridgeController.ts @@ -19,7 +19,16 @@ export const bridgeController = async (req, res) => { fromToken, toToken, originUserAddress, - } = req.query + destAddress, // Optional parameter + } = req.query as { + fromChain: string + toChain: string + amount: string + fromToken: string + toToken: string + originUserAddress?: string + destAddress?: string + } const fromTokenInfo = tokenAddressToToken(fromChain.toString(), fromToken) const toTokenInfo = tokenAddressToToken(toChain.toString(), toToken) @@ -37,23 +46,40 @@ export const bridgeController = async (req, res) => { : {} ) - const payload = resp.map((quote) => { - const originQueryTokenOutInfo = tokenAddressToToken( - fromChain.toString(), - quote.originQuery.tokenOut - ) - return { - ...quote, - maxAmountOutStr: formatBNToString( - quote.maxAmountOut, - toTokenInfo.decimals - ), - bridgeFeeFormatted: formatBNToString( - quote.feeAmount, - originQueryTokenOutInfo.decimals - ), - } - }) + const payload = await Promise.all( + resp.map(async (quote) => { + const originQueryTokenOutInfo = tokenAddressToToken( + fromChain.toString(), + quote.originQuery.tokenOut + ) + + const callData = destAddress + ? await Synapse.bridge( + destAddress, + quote.routerAddress, + Number(fromChain), + Number(toChain), + fromToken, + amountInWei, + quote.originQuery, + quote.destQuery + ) + : null + + return { + ...quote, + maxAmountOutStr: formatBNToString( + quote.maxAmountOut, + toTokenInfo.decimals + ), + bridgeFeeFormatted: formatBNToString( + quote.feeAmount, + originQueryTokenOutInfo.decimals + ), + callData, + } + }) + ) logger.info(`Successful bridgeController response`, { payload, diff --git a/packages/rest-api/src/controllers/swapController.ts b/packages/rest-api/src/controllers/swapController.ts index fbaec5f754..cace5e3a22 100644 --- a/packages/rest-api/src/controllers/swapController.ts +++ b/packages/rest-api/src/controllers/swapController.ts @@ -12,7 +12,7 @@ export const swapController = async (req, res) => { return res.status(400).json({ errors: errors.array() }) } try { - const { chain, amount, fromToken, toToken } = req.query + const { chain, amount, fromToken, toToken, address } = req.query const fromTokenInfo = tokenAddressToToken(chain.toString(), fromToken) const toTokenInfo = tokenAddressToToken(chain.toString(), toToken) @@ -30,9 +30,20 @@ export const swapController = async (req, res) => { toTokenInfo.decimals ) + const callData = address + ? await Synapse.swap( + Number(chain), + address, + fromToken, + amountInWei, + quote.query + ) + : null + const payload = { ...quote, maxAmountOut: formattedMaxAmountOut, + callData, } logger.info(`Successful swapController response`, { diff --git a/packages/rest-api/src/routes/bridgeRoute.ts b/packages/rest-api/src/routes/bridgeRoute.ts index 3c6e34c1ed..4d534306fc 100644 --- a/packages/rest-api/src/routes/bridgeRoute.ts +++ b/packages/rest-api/src/routes/bridgeRoute.ts @@ -57,6 +57,12 @@ const router: express.Router = express.Router() * schema: * type: string * description: The address of the user on the origin chain + * - in: query + * name: destAddress + * required: false + * schema: + * type: string + * description: The destination address for the bridge transaction * responses: * 200: * description: Successful response @@ -102,6 +108,16 @@ const router: express.Router = express.Router() * type: string * bridgeFeeFormatted: * type: string + * callData: + * type: object + * nullable: true + * properties: + * to: + * type: string + * data: + * type: string + * value: + * type: string * example: * - id: "01920c87-7f14-7cdf-90e1-e13b2d4af55f" * feeAmount: @@ -253,6 +269,10 @@ router.get( .optional() .custom((value) => isAddress(value)) .withMessage('Invalid originUserAddress address'), + check('destAddress') + .optional() + .custom((value) => isAddress(value)) + .withMessage('Invalid destAddress'), ], showFirstValidationError, bridgeController diff --git a/packages/rest-api/src/routes/swapRoute.ts b/packages/rest-api/src/routes/swapRoute.ts index 2fb224062d..4018fe2b90 100644 --- a/packages/rest-api/src/routes/swapRoute.ts +++ b/packages/rest-api/src/routes/swapRoute.ts @@ -1,5 +1,6 @@ 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' @@ -46,6 +47,12 @@ const router: express.Router = express.Router() * schema: * type: number * description: The amount of tokens to swap + * - in: query + * name: address + * required: false + * schema: + * type: string + * description: Optional. The address that will perform the swap. If provided, returns transaction data. * responses: * 200: * description: Successful response @@ -76,6 +83,16 @@ const router: express.Router = express.Router() * rawParams: * type: string * description: Raw parameters for the swap + * callData: + * type: object + * nullable: true + * properties: + * to: + * type: string + * data: + * type: string + * value: + * type: string * example: * routerAddress: "0x7E7A0e201FD38d3ADAA9523Da6C109a07118C96a" * maxAmountOut: "999.746386" @@ -191,6 +208,10 @@ router.get( return validSwapTokens(chain, fromToken, toToken) }) .withMessage('Swap not supported for given tokens'), + check('address') + .optional() + .custom((value) => isAddress(value)) + .withMessage('Invalid address'), ], showFirstValidationError, swapController diff --git a/packages/rest-api/src/tests/bridgeRoute.test.ts b/packages/rest-api/src/tests/bridgeRoute.test.ts index 811c267d0c..ce6fc247d4 100644 --- a/packages/rest-api/src/tests/bridgeRoute.test.ts +++ b/packages/rest-api/src/tests/bridgeRoute.test.ts @@ -174,19 +174,51 @@ describe('Bridge Route with Real Synapse Service', () => { expect(response.body.error).toHaveProperty('field', 'amount') }) - it('should return 400 for amount with too many decimals', async () => { + it('should return bridge quotes with callData when destAddress is provided', async () => { const response = await request(app).get('/bridge').query({ fromChain: '1', toChain: '10', fromToken: USDC.addresses[1], toToken: USDC.addresses[10], - amount: '1000.123456789', // Assuming USDC has 6 decimals + amount: '1000', + destAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', + }) + + expect(response.status).toBe(200) + expect(Array.isArray(response.body)).toBe(true) + expect(response.body.length).toBeGreaterThan(0) + expect(response.body[0]).toHaveProperty('callData') + expect(response.body[0].callData).toHaveProperty('to') + expect(response.body[0].callData).toHaveProperty('data') + expect(response.body[0].callData).toHaveProperty('value') + }, 15000) + + it('should return bridge quotes without callData when destAddress is not provided', async () => { + const response = await request(app).get('/bridge').query({ + fromChain: '1', + toChain: '10', + fromToken: USDC.addresses[1], + toToken: USDC.addresses[10], + amount: '1000', + }) + + expect(response.status).toBe(200) + expect(Array.isArray(response.body)).toBe(true) + expect(response.body.length).toBeGreaterThan(0) + expect(response.body[0].callData).toBeNull() + }, 15000) + + it('should return 400 for invalid destAddress', async () => { + const response = await request(app).get('/bridge').query({ + fromChain: '1', + toChain: '10', + fromToken: USDC.addresses[1], + toToken: USDC.addresses[10], + amount: '1000', + destAddress: 'invalid_address', }) expect(response.status).toBe(400) - expect(response.body.error).toHaveProperty( - 'message', - expect.stringContaining('Amount has too many decimals') - ) + expect(response.body.error).toHaveProperty('message', 'Invalid destAddress') }, 15000) }) diff --git a/packages/rest-api/src/tests/swapRoute.test.ts b/packages/rest-api/src/tests/swapRoute.test.ts index b4408d9bb4..23a5871207 100644 --- a/packages/rest-api/src/tests/swapRoute.test.ts +++ b/packages/rest-api/src/tests/swapRoute.test.ts @@ -134,18 +134,50 @@ describe('Swap Route with Real Synapse Service', () => { expect(response.body.error).toHaveProperty('field', 'amount') }, 10_000) - it('should return 400 for amount with too many decimals', async () => { + it('should return swap quote with callData when address is provided', async () => { const response = await request(app).get('/swap').query({ chain: '1', fromToken: USDC.addresses[1], toToken: DAI.addresses[1], - amount: '1000.123456789', // Assuming USDC has 6 decimals + amount: '1000', + address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', + }) + + expect(response.status).toBe(200) + expect(response.body).toHaveProperty('maxAmountOut') + expect(response.body).toHaveProperty('routerAddress') + expect(response.body).toHaveProperty('query') + expect(response.body).toHaveProperty('callData') + expect(response.body.callData).toHaveProperty('to') + expect(response.body.callData).toHaveProperty('data') + expect(response.body.callData).toHaveProperty('value') + }, 10_000) + + it('should return swap quote without callData when address is not provided', async () => { + const response = await request(app).get('/swap').query({ + chain: '1', + fromToken: USDC.addresses[1], + toToken: DAI.addresses[1], + amount: '1000', + }) + + expect(response.status).toBe(200) + expect(response.body).toHaveProperty('maxAmountOut') + expect(response.body).toHaveProperty('routerAddress') + expect(response.body).toHaveProperty('query') + expect(response.body.callData).toBeNull() + }, 10_000) + + it('should return 400 for invalid address', async () => { + const response = await request(app).get('/swap').query({ + chain: '1', + fromToken: USDC.addresses[1], + toToken: DAI.addresses[1], + amount: '1000', + address: 'invalid_address', }) expect(response.status).toBe(400) - expect(response.body.error).toHaveProperty( - 'message', - expect.stringContaining('Amount has too many decimals') - ) - }, 10000) + expect(response.body.error).toHaveProperty('message', 'Invalid address') + }, 10_000) }) diff --git a/packages/rest-api/swagger.json b/packages/rest-api/swagger.json index 40bae70d6b..7190b10524 100644 --- a/packages/rest-api/swagger.json +++ b/packages/rest-api/swagger.json @@ -197,6 +197,15 @@ "type": "string" }, "description": "The address of the user on the origin chain" + }, + { + "in": "query", + "name": "destAddress", + "required": true, + "schema": { + "type": "string" + }, + "description": "The destination address of the user on the destination chain" } ], "responses": { @@ -1206,6 +1215,15 @@ "type": "number" }, "description": "The amount of tokens to swap" + }, + { + "in": "query", + "name": "address", + "required": true, + "schema": { + "type": "string" + }, + "description": "The address of the user" } ], "responses": {