app.ts


Overview

`app.ts` is the main entry point for the **ShapeShift Arbitrum Nova API** server application. It sets up an Express-based HTTP server with integrated WebSocket support to handle blockchain data streaming and API requests. This file orchestrates the initialization and configuration of:

The application is specifically tailored for the Arbitrum Nova coinstack and leverages ShapeShift’s common APIs and utilities for blockchain data processing.


Detailed Components

Imports and Constants


Logger

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

Prometheus Metrics

const prometheus = new Prometheus({ coinstack: 'arbitrum-nova' })

Express App Setup

const app = express()
app.use(...middleware.common(prometheus))

HTTP Routes

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

Returns service status including number of active WebSocket connections.

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

Exposes Prometheus metrics.

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))

Serves API documentation at `/docs` with custom branding and favicon.

RegisterRoutes(app)

Dynamically adds all API routes from the `routes` module.

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

Redirects root path to API docs.

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

Handles 404 and other errors gracefully.


Blockchain Handlers

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 and WebSocket Client

const registry = new Registry({ addressFormatter: evm.formatAddress, blockHandler, transactionHandler })
const blockbook = new WebsocketClient(INDEXER_WS_URL, {
  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)
})

Usage Examples

Starting the Server

Set environment variables:

export INDEXER_WS_URL=wss://indexer.arbitrum-nova.example/ws
export PORT=3000
export LOG_LEVEL=info

Run:

node dist/app.js

Accessing API

curl http://localhost:3000/health
curl http://localhost:3000/metrics

Open browser at `http://localhost:3000/docs`

WebSocket Client Connection

Clients can connect to the WebSocket server at the same port and receive real-time blockchain updates managed by `ConnectionHandler`.


Important Implementation Details


Interaction with Other Parts of the System


Visual Diagram

classDiagram
    class App {
        +express app
        +wsServer: WebSocket.Server
        +logger: Logger
        +prometheus: Prometheus
        +registry: Registry
        +blockbook: WebsocketClient
        +blockHandler(block: NewBlock): Promise
        +transactionHandler(tx: BlockbookTx): Promise
        +start()
    }

    class Registry {
        +addressFormatter
        +blockHandler
        +transactionHandler
        +onBlock()
        +onTransaction()
    }

    class WebsocketClient {
        +blockHandler: Function[]
        +transactionHandler: Function
        +connect()
    }

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

    App --> Registry : uses
    App --> WebsocketClient : creates
    App --> ConnectionHandler : calls on ws connection
    WebsocketClient --> Registry : calls onBlock, onTransaction
    Registry --> App.blockHandler : delegates block handling
    Registry --> App.transactionHandler : delegates transaction handling

Summary

`app.ts` effectively combines Express REST API, Prometheus metrics, Swagger docs, and WebSocket servers to deliver a robust blockchain event streaming and querying API service for Arbitrum Nova. It leverages modular design principles, concurrency for efficient data fetching, and integrates several ShapeShift OSS libraries to handle blockchain-specific logic, making it the backbone of the Arbitrum Nova indexing API.