packages/rest-api/src/routes/swapRoute.ts
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'
import { checksumAddresses } from '../middleware/checksumAddresses'
import { normalizeNativeTokenAddress } from '../middleware/normalizeNativeTokenAddress'
import { validSwapTokens } from '../validations/validSwapTokens'
import { validSwapChain } from '../validations/validSwapChain'
import { validateDecimals } from '../validations/validateDecimals'
import { tokenAddressToToken } from '../utils/tokenAddressToToken'
const router: express.Router = express.Router()
/**
* @openapi
* /swap:
* get:
* summary: Get swap quote for tokens on a specific chain
* description: Retrieve detailed swap quote for exchanging one token for another on a specified chain
* parameters:
* - in: query
* name: chain
* required: true
* schema:
* type: integer
* description: The chain ID where the swap will occur
* - in: query
* name: fromToken
* required: true
* schema:
* type: string
* description: The address of the token to swap from
* - in: query
* name: toToken
* required: true
* schema:
* type: string
* description: The address of the token to swap to
* - in: query
* name: amount
* required: true
* 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
* content:
* application/json:
* schema:
* type: object
* properties:
* routerAddress:
* type: string
* description: The address of the router contract
* maxAmountOut:
* type: string
* description: The maximum amount of tokens that will be received
* query:
* type: object
* properties:
* swapAdapter:
* type: string
* description: The address of the swap adapter
* tokenOut:
* type: string
* description: The address of the token being received
* minAmountOut:
* $ref: '#/components/schemas/BigNumber'
* deadline:
* $ref: '#/components/schemas/BigNumber'
* 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"
* query:
* swapAdapter: "0x7E7A0e201FD38d3ADAA9523Da6C109a07118C96a"
* tokenOut: "0xdAC17F958D2ee523a2206206994597C13D831ec7"
* minAmountOut:
* type: "BigNumber"
* hex: "0x3b96eb52"
* deadline:
* type: "BigNumber"
* hex: "0x66ecb470"
* rawParams: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001116898dda4015ed8ddefb84b6e8bc24528af2d800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"
* 400:
* description: Invalid input
* content:
* application/json:
* schema:
* type: object
* properties:
* error:
* type: object
* properties:
* value:
* type: string
* message:
* type: string
* field:
* type: string
* location:
* type: string
* example:
* error:
* value: "999"
* message: "Unsupported chain"
* field: "chain"
* location: "query"
* 500:
* description: Server error
* content:
* application/json:
* schema:
* type: object
* properties:
* error:
* type: string
*
* components:
* schemas:
* BigNumber:
* type: object
* properties:
* type:
* type: string
* enum: [BigNumber]
* hex:
* type: string
*/
router.get(
'/',
normalizeNativeTokenAddress(['fromToken', 'toToken']),
checksumAddresses(['fromToken', 'toToken']),
[
check('chain')
.exists()
.withMessage('chain is required')
.isNumeric()
.custom((value) => CHAINS_ARRAY.some((c) => c.id === Number(value)))
.withMessage('Unsupported chain'),
check('fromToken')
.exists()
.withMessage('fromToken is required')
.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) => 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')
.exists()
.withMessage('amount is required')
.isNumeric()
.custom((value, { req }) => {
const fromTokenInfo = tokenAddressToToken(
req.query.chain,
req.query.fromToken
)
return validateDecimals(value, fromTokenInfo.decimals)
})
.withMessage(
'Amount has too many decimals, beyond the maximum allowed for this token'
),
check()
.custom((_value, { req }) => {
const { chain } = req.query
return validSwapChain(chain)
})
.withMessage('Swap not supported for given chain'),
check()
.custom((_value, { req }) => {
const { chain, fromToken, toToken } = req.query
return validSwapTokens(chain, fromToken, toToken)
})
.withMessage('Swap not supported for given tokens'),
check('address')
.optional()
.custom((value) => isAddress(value))
.withMessage('Invalid address'),
],
showFirstValidationError,
swapController
)
export default router