app.ts


Overview

`app.ts` is the main entry point for the ShapeShift Polygon API server application. It sets up an Express.js HTTP server combined with a WebSocket server to provide real-time blockchain data indexing and transaction processing for the Polygon network. This file orchestrates middleware, API routes, WebSocket event handling, and integrates with blockchain data sources to deliver a scalable, observable, and documented API service.

Key functionalities include:

Overall, `app.ts` serves as the coordinator that wires together core components, middleware, and external services into a cohesive API server for Polygon blockchain data.


Detailed Explanations

Constants and Environment Variables

Name

Purpose

`PORT`

Port number for the Express HTTP server (default 3000)

`INDEXER_WS_URL`

WebSocket URL for the Polygon blockchain indexer

`INDEXER_API_KEY`

API key for authenticating with the blockchain indexer

`INDEXER_WS_URL` is mandatory and the app throws an error if not set.


Logger Instance

export const logger = new Logger({
  namespace: ['unchained', 'coinstacks', 'polygon', 'api'],
  level: process.env.LOG_LEVEL,
})

Express Application Setup

const app = express()

app.use(...middleware.common(prometheus))

HTTP Routes

Health Check Route

app.get('/health', async (_, res) => res.json({ status: 'up', asset: 'polygon', connections: wsServer.clients.size }))

Metrics Route

app.get('/metrics', async (_, res) => {
  res.setHeader('Content-Type', prometheus.register.contentType)
  res.send(await prometheus.register.metrics())
})

Swagger API Documentation

const options: swaggerUi.SwaggerUiOptions = {
  customCss: '.swagger-ui .topbar { display: none }',
  customSiteTitle: 'ShapeShift Polygon API Docs',
  customfavIcon: '/public/favi-blue.png',
  swaggerUrl: '/swagger.json',
}

app.use('/public', express.static(join(__dirname, '../../../../../../common/api/public')))
app.use('/swagger.json', express.static(join(__dirname, './swagger.json')))
app.use('/docs', swaggerUi.serve, swaggerUi.setup(undefined, options))

Register API Routes

RegisterRoutes(app)

Redirect Root to Docs

app.get('/', async (_, res) => {
  res.redirect('/docs')
})

Error Handling Middleware

app.use(middleware.errorHandler, middleware.notFoundHandler)

Blockchain Data Handlers

These handlers process blockchain data received from the Polygon indexer.

Block Handler

const blockHandler: BlockHandler<NewBlock, Array<{ addresses: Array<string>; tx: evm.Tx }>> = async (block) => {
  const [blockbookTxs, internalTxs] = await Promise.all([
    service.handleBlock(block.hash),
    service.fetchInternalTxsByBlockDebug(block.hash),
  ])

  const txs = blockbookTxs.map((t) => {
    const tx = service.handleTransaction(t)
    tx.internalTxs = internalTxs[t.txid]

    const internalAddresses = (tx.internalTxs ?? []).reduce<Array<string>>((prev, tx) => [...prev, tx.to, tx.from], [])
    const addresses = [...new Set([...getAddresses(t), ...internalAddresses])]

    return { addresses, tx }
  })

  return { txs }
}

Transaction Handler

const transactionHandler: TransactionHandler<BlockbookTx, evm.Tx> = async (blockbookTx) => {
  const tx = await service.handleTransactionWithInternalTrace(blockbookTx)
  const internalAddresses = (tx.internalTxs ?? []).reduce<Array<string>>((prev, tx) => [...prev, tx.to, tx.from], [])
  const addresses = [...new Set([...getAddresses(blockbookTx), ...internalAddresses])]

  return { addresses, tx }
}

Registry Instance

const registry = new Registry({ addressFormatter: evm.formatAddress, blockHandler, transactionHandler })

Blockbook WebSocket Client

const blockbook = new WebsocketClient(INDEXER_WS_URL, {
  apiKey: INDEXER_API_KEY,
  blockHandler: [registry.onBlock.bind(registry), gasOracle.onBlock.bind(gasOracle)],
  transactionHandler: registry.onTransaction.bind(registry),
})

Server and WebSocket Server Initialization

const server = app.listen(PORT, () => logger.info('Server started'))
const wsServer = new Server({ server })

wsServer.on('connection', (connection) => {
  ConnectionHandler.start(connection, registry, blockbook, prometheus, logger)
})

Important Implementation Details and Algorithms


Interaction with Other Parts of the System


Usage Example

The server is started simply by running the compiled `app.ts` (usually via `ts-node` or after build):

INDEXER_WS_URL=wss://polygon-indexer.example.com/ws \
INDEXER_API_KEY=yourapikey \
PORT=3000 \
npm start

Clients can:


Visual Diagram

classDiagram
    class App {
        -app: Express.Application
        -wsServer: WebSocket.Server
        -blockbook: WebsocketClient
        -registry: Registry
        -prometheus: Prometheus
        -logger: Logger
        +start()
    }

    class Registry {
        +onBlock()
        +onTransaction()
    }

    class WebsocketClient {
        +blockHandler
        +transactionHandler
        +connect()
    }

    class ConnectionHandler {
        +start(connection, registry, blockbook, prometheus, logger)
    }

    class Express {
        +use()
        +get()
        +listen()
    }

    App --> Express : uses
    App --> WebsocketClient : creates
    App --> Registry : creates
    App --> ConnectionHandler : uses
    WebsocketClient --> Registry : calls onBlock/onTransaction
    ConnectionHandler --> Registry : interacts with
    ConnectionHandler --> WebsocketClient : interacts with

Summary

The `app.ts` file is the backbone of the ShapeShift Polygon API, integrating HTTP and WebSocket servers, blockchain data streaming, and monitoring. It leverages modular components from the `@shapeshiftoss` ecosystem and external services to provide a robust, documented API for Polygon blockchain data. The architecture promotes separation of concerns, observability, and scalability.