From 2782388e4d79010f5f3338b516bdbb439145408f Mon Sep 17 00:00:00 2001 From: defi-moses Date: Tue, 5 Nov 2024 14:03:19 +0000 Subject: [PATCH] bridge endpoint and swap endpoint consolidation for swap data --- .../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 | 48 ++++++++++++++ packages/rest-api/src/tests/swapRoute.test.ts | 47 ++++++++++++++ packages/rest-api/swagger.json | 18 ++++++ 7 files changed, 210 insertions(+), 19 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 6911f77473..4c16448ddb 100644 --- a/packages/rest-api/src/routes/bridgeRoute.ts +++ b/packages/rest-api/src/routes/bridgeRoute.ts @@ -56,6 +56,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 @@ -101,6 +107,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: @@ -239,6 +255,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 d55937006c..93b711c5e7 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' @@ -44,6 +45,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 @@ -74,6 +81,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" @@ -176,6 +193,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 542feab63a..ce6fc247d4 100644 --- a/packages/rest-api/src/tests/bridgeRoute.test.ts +++ b/packages/rest-api/src/tests/bridgeRoute.test.ts @@ -173,4 +173,52 @@ describe('Bridge Route with Real Synapse Service', () => { expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('field', 'amount') }) + + 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', + 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', 'Invalid destAddress') + }, 15000) }) diff --git a/packages/rest-api/src/tests/swapRoute.test.ts b/packages/rest-api/src/tests/swapRoute.test.ts index 9c09f89a22..23a5871207 100644 --- a/packages/rest-api/src/tests/swapRoute.test.ts +++ b/packages/rest-api/src/tests/swapRoute.test.ts @@ -133,4 +133,51 @@ describe('Swap Route with Real Synapse Service', () => { expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('field', 'amount') }, 10_000) + + 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', + 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', '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": {