Skip to main content
This page documents a TypeScript client pattern for server-side bots and market makers. It is meant to make the API flow concrete: authenticate, fetch market data, build orders, sign orders, save orders, subscribe to streams, and cancel orders.
This pattern is for backend services and dedicated bot accounts. Do not ship private-key signing code in a browser app, and do not use a primary wallet key for automated trading.

Dependencies

npm install ethers@5 ws @hiero-ledger/sdk
npm install --save-dev @types/ws
The example client uses:
PackagePurpose
ethers@5EIP-712 signing and EVM account utilities
wsNode.js WebSocket support
@hiero-ledger/sdkHedera account key handling for ED25519 accounts
The example pins ethers@5 intentionally for CommonJS-friendly bot projects. If your service already runs as ESM, you can adapt the signing helpers to ethers@6+, but do not upgrade the sample dependency without also reviewing the module format and API changes.
If you build a small wrapper around the API, use a surface like this:
MethodPurpose
authenticate(accountId, privateKey)Run challenge and verify flow; store the JWT
getOrderbooks(query)Discover orderbook token IDs, EVM addresses, decimals, status, and routing flags
getDepth(orderbookId)Fetch a depth snapshot
getDomain()Fetch and cache the EIP-712 signing domain
buildOrders(requests)Request nonce-assigned serialized orders
signOrder(order, privateKey, domain)Produce a prefixed signature for a built order
saveOrders(items)Submit signed orders
cancel(orderIds)Request cancellation for specific order IDs
cancelAll(nonceFloor)Emergency cancel by nonce floor
connectDepthWs(books, handlers)Subscribe to live depth updates
connectUserEventsWs(books, handlers)Subscribe to authenticated order events
The examples below assume a wrapper with this shape. You can also call the REST and WebSocket endpoints directly.

Minimal Limit Order Flow

const client = new OrderbookApiClient({ environment: 'testnet' })

async function placeLimitOrder() {
  await client.authenticate('0.0.123456', process.env.PRIVATE_KEY!)

  const domain = await client.getDomain()

  const requests = [
    {
      orderbookId: '3',
      type: 'LIMIT' as const,
      deadline: String(Math.floor(Date.now() / 1000) + 3600),
      inputToken: '0xbase-token-evm-address',
      inputAmount: '1000000',
      outputToken: '0xquote-token-evm-address',
      outputAmount: '950000',
      isAMMEnabled: true,
    },
  ]

  const builtOrders = await client.buildOrders(requests)

  const items = []
  for (let i = 0; i < builtOrders.length; i++) {
    const signature = await client.signOrder(
      builtOrders[i],
      process.env.PRIVATE_KEY!,
      domain,
    )

    items.push({
      order: builtOrders[i],
      signature,
      orderbookId: requests[i].orderbookId,
      type: requests[i].type,
    })
  }

  const { orders: saved } = await client.saveOrders(items)
  for (const order of saved) {
    console.log(
      `order ${order.meta?.id}: status=${order.meta?.status} nonce=${order.info.nonce}`,
    )
  }
}

placeLimitOrder().catch(console.error)

Signing Behavior

The bot client signs the serialized order returned by buildOrders(). For ECDSA_SECP256K1 accounts:
  1. Build the EIP-712 order payload.
  2. Sign with wallet._signTypedData(domain, types, order).
  3. Prefix the signature with 0x00.
For ED25519 Hedera accounts:
  1. Build the same EIP-712 order payload.
  2. Hash it with ethers.utils._TypedDataEncoder.hash.
  3. Sign the raw hash bytes with the Hiero SDK private key.
  4. Prefix the signature bytes with 0x00.
Wallet integrations that use Hedera personal sign should instead use the 0x01 signing mode and submit a HIP-632 SignatureMap around the raw wallet signature.

Implementation Notes

  • Cache the value returned by getDomain() for the current session.
  • Reuse the same authenticated account for build, sign, save, and cancellation.
  • Sign the order returned by buildOrders(), not the original request body.
  • Preserve all integer fields as strings.
  • Use baseTokenDecimals and quoteTokenDecimals from getOrderbooks() when converting between display amounts and raw token units.
  • Keep the signature mode byte in the submitted signature.
  • Re-authenticate before reconnecting WebSocket streams.

WebSocket Example

await client.authenticate('0.0.123456', process.env.PRIVATE_KEY!)

const ws = client.connectDepthWs(['3'], {
  onOpen: () => console.log('depth stream connected'),
  onMessage: (message) => {
    const event = JSON.parse(message)
    console.log(event)
  },
  onClose: (code, reason) => {
    console.log(`depth stream closed: ${code} ${reason}`)
  },
  onError: (error) => {
    console.error(error)
  },
})
Production clients should re-authenticate before reconnecting and should not log full WebSocket URLs because the JWT is passed in the query string.

Cancellation Example

await client.cancel([1234, 1235])
Cancellation is asynchronous. Wait for the user-event stream or fetch order history before treating an order as cancelled.
await client.cancelAll('100000000000000000000')
Use nonce-floor cancellation only for emergency recovery or deliberate session shutdown flows.