synapsecns/sanguine

View on GitHub
packages/rest-api/src/routes/swapTxInfoRoute.ts

Summary

Maintainability
C
7 hrs
Test Coverage
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 { swapTxInfoController } from '../controllers/swapTxInfoController'
import { isTokenAddress } from '../utils/isTokenAddress'
import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain'
import { checksumAddresses } from '../middleware/checksumAddresses'
import { normalizeNativeTokenAddress } from '../middleware/normalizeNativeTokenAddress'
import { validateDecimals } from '../validations/validateDecimals'
import { tokenAddressToToken } from '../utils/tokenAddressToToken'

const router: express.Router = express.Router()

/**
 * @openapi
 * /swapTxInfo:
 *   get:
 *     summary: "[Deprecated] in favor of using the /swap endpoint, which now returns call data
 *     description: "[Deprecated] Originally used to get Swap transaction information
 *     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: true
 *         schema:
 *           type: string
 *         description: The Ethereum address of the user performing the swap
 *     responses:
 *       200:
 *         description: Successful response
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 data:
 *                   type: string
 *                   description: Encoded transaction data
 *                 to:
 *                   type: string
 *                   description: The address of the contract to interact with
 *                 value:
 *                   type: object
 *                   properties:
 *                     type:
 *                       type: string
 *                       enum: [BigNumber]
 *                     hex:
 *                       type: string
 *                   description: The amount of native currency to send with the transaction
 *             example:
 *               data: "0xb5d1cdd4000000000000000000000000abb4f79430002534df3f62e964d62659a010ef3c000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000000000800000000000000000000000007e7a0e201fd38d3adaa9523da6c109a07118c96a000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000003b96eaed0000000000000000000000000000000000000000000000000000000066ecbb7c00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001116898dda4015ed8ddefb84b6e8bc24528af2d800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"
 *               to: "0x7E7A0e201FD38d3ADAA9523Da6C109a07118C96a"
 *               value:
 *                 type: "BigNumber"
 *                 hex: "0x00"
 *       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
 */
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')
      .isNumeric()
      .exists()
      .withMessage('amount is required')
      .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('address')
      .exists()
      .withMessage('address is required')
      .custom((value) => isAddress(value))
      .withMessage('Invalid Ethereum address'),
  ],
  showFirstValidationError,
  swapTxInfoController
)

export default router