TypeScript SDK Guide

@0agent/0watch-sdk is a lightweight TypeScript client for the 0watch API. It wraps all public endpoints in typed methods, handles auth headers, and includes a verifyWebhookSignature utility for server-side verification.


Install

npm install @0agent/0watch-sdk
# or
pnpm add @0agent/0watch-sdk
# or
yarn add @0agent/0watch-sdk

Requires Node.js 20+. Works in any runtime with a global fetch (Node 18+ with --experimental-fetch, Bun, Deno, Cloudflare Workers).


Create a client

import { ZeroWatchClient } from '@0agent/0watch-sdk';

const client = new ZeroWatchClient({
  apiKey: process.env.ZEROWATCH_API_KEY,
});

All options:

const client = new ZeroWatchClient({
  apiKey: 'owk_...',           // X-API-Key header
  token: 'session_token',      // Bearer token (alternative to apiKey)
  baseUrl: 'https://watch.0agent.ai', // default
  fetch: customFetchImpl,      // custom fetch (e.g. for testing)
  headers: { 'X-Custom': '1' }, // default headers on every request
});

You can update auth at runtime:

client.setApiKey('owk_new...');
client.setToken(null);

Common patterns

Sign up and start monitoring

import { ZeroWatchClient } from '@0agent/0watch-sdk';

const client = new ZeroWatchClient();

// 1. Create account
const signup = await client.signup({
  email: 'dev@example.com',
  password: 'StrongPassword123',
});

// 2. Authenticate with the returned API key
client.setApiKey(signup.apiKey);
console.log('Account:', signup.accountId);

// 3. Add wallets
await client.createWallet({
  address: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  label: 'Treasury',
  chain: 'base', // or chain ID: 8453
});

await client.createWallet({
  address: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
  label: 'Ops',
});

// 4. List what you're watching
const { wallets } = await client.listWallets();
console.log(`Monitoring ${wallets.length} wallets`);

Query transactions

const { transactions, count } = await client.getWalletTransactions(
  '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  { limit: 50 }
);

for (const tx of transactions) {
  // valueWei is a string — use BigInt for arithmetic
  const eth = Number(BigInt(tx.valueWei)) / 1e18;
  console.log(`${tx.txType}: ${eth.toFixed(4)} ETH — ${tx.hash}`);
}

Query anomaly alerts

const { alerts } = await client.getWalletAlerts(
  '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  { limit: 20 }
);

const high = alerts.filter(a => a.severity === 'high' || a.severity === 'critical');
console.log(`${high.length} high/critical alerts`);

Alert severities: low, medium, high, critical. Anomaly types: large_transfer, high_velocity, failed_tx.

Get a wallet activity summary

const summary = await client.getWalletSummary(
  '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
);

console.log({
  totalTx: summary.totalTransactionsMonitored,
  alertsTriggered: summary.alertsTriggered,
  lastActivity: summary.lastActivityTimestamp
    ? new Date(summary.lastActivityTimestamp * 1000)
    : null,
});

Set up a webhook

const webhook = await client.createWebhook({
  url: 'https://your-server.example.com/hook',
  wallet_address: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  threshold_eth: 0.5,
});

// Store signingSecret securely — it won't be returned again
console.log({ webhookId: webhook.id, signingSecret: webhook.signingSecret });

Rotate a webhook signing secret

const updated = await client.updateWebhook(webhook.id, {
  rotate_signing_secret: true,
});
// The new signing secret is in updated.signingSecret

Check delivery history

const { deliveries } = await client.listDeliveries({
  wallet: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  limit: 25,
});

const failed = deliveries.filter(d => d.status === 'failed');
if (failed.length > 0) {
  console.warn(`${failed.length} failed deliveries`);
  for (const d of failed) {
    const attempts = await client.getDeliveryAttempts(d.id);
    console.error(`Delivery ${d.id}: ${attempts.attempts.at(-1)?.error}`);
  }
}

Dashboard stats

const stats = await client.getDashboardStats();
console.log({
  wallets: stats.walletsMonitored,
  alertsThisWeek: stats.alertsThisWeek,
  indexerStatus: stats.uptime.status,
  lastBlock: stats.uptime.lastSyncedBlock,
});

Webhook signature verification

When 0watch POSTs to your endpoint, it includes X-0Watch-Signature: sha256=<hex>. Verify it before processing the payload.

import { verifyWebhookSignature } from '@0agent/0watch-sdk';

// Works in any framework — example using the Fetch Request API (Bun, Cloudflare Workers, Deno)
async function handleWebhook(request: Request): Promise<Response> {
  const rawBody = await request.text();

  const valid = verifyWebhookSignature({
    payload: rawBody,
    signingSecret: process.env.ZEROWATCH_WEBHOOK_SECRET!,
    signatureHeader: request.headers.get('x-0watch-signature'),
  });

  if (!valid) {
    return new Response('Unauthorized', { status: 401 });
  }

  const payload = JSON.parse(rawBody);
  // { event, walletAddress, valueEth, thresholdEth, transaction }

  console.log(`Alert: ${payload.walletAddress} moved ${payload.valueEth} ETH`);
  return new Response('OK', { status: 200 });
}

For Node.js + Express, read the raw body before verification:

import express from 'express';
import { verifyWebhookSignature } from '@0agent/0watch-sdk';

const app = express();

app.post('/hook', express.raw({ type: 'application/json' }), (req, res) => {
  const valid = verifyWebhookSignature({
    payload: req.body,                              // Buffer
    signingSecret: process.env.ZEROWATCH_WEBHOOK_SECRET!,
    signatureHeader: req.headers['x-0watch-signature'] as string,
  });

  if (!valid) return res.status(401).send('Unauthorized');

  const payload = JSON.parse(req.body.toString());
  // handle payload
  res.sendStatus(200);
});

You can also generate a signature manually (useful for testing):

import { createWebhookSignature } from '@0agent/0watch-sdk';

const body = JSON.stringify({ event: 'test', walletAddress: '0x...' });
const signature = createWebhookSignature(body, 'your-signing-secret');
// sha256=<hex>

Error handling

All API errors throw ZeroWatchApiError:

import { ZeroWatchApiError } from '@0agent/0watch-sdk';

try {
  await client.createWallet({
    address: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
    label: 'Treasury',
  });
} catch (err) {
  if (err instanceof ZeroWatchApiError) {
    console.error(`API error ${err.status}: ${err.message}`);

    if (err.status === 402) {
      console.error(`Wallet limit reached. Upgrade required: ${err.data?.upgradeRequired}`);
    }

    if (err.status === 429) {
      const retryAfter = err.data?.retryAfterSeconds;
      console.error(`Rate limited. Retry after ${retryAfter}s`);
    }
  } else {
    throw err;
  }
}

ZeroWatchApiError properties:

Common error codes:


Billing and usage

// Check subscription and wallet usage
const { subscription, usage } = await client.getSubscription();
console.log({
  tier: subscription.tier,
  status: subscription.status,
  wallets: `${usage.watchedWallets} / ${usage.walletLimit ?? 'unlimited'}`,
});

// Detailed rate limit stats
const usageDetail = await client.getUsage({ limit: 10 });
console.log({
  remaining: usageDetail.rateLimit.remaining,
  resetAt: new Date(usageDetail.rateLimit.resetAt * 1000),
});

// Start a crypto checkout payment request (Developer or Team tier)
const checkout = await client.createCheckout({
  tier: 'developer',
  success_url: 'https://yourapp.com/billing/success',
  cancel_url: 'https://yourapp.com/billing',
});
// Redirect to checkout.url or use checkout.paymentRequest for custom confirmation UX

API key management

// List active keys
const { keys } = await client.listApiKeys();

// Create an additional key
client.setOwnerEmail('you@example.com');
const { apiKey } = await client.createApiKey({ name: 'ci-pipeline' });

// Rotate (creates a new key, old key enters grace period)
const rotated = await client.rotateApiKey();

// Revoke a key by ID
await client.revokeApiKey(keys[0].id);

ownerEmail is required when creating or rotating keys. Set it via the constructor (ownerEmail option) or client.setOwnerEmail().


TypeScript types

All request and response types are exported from the package root:

import type {
  ZeroWatchClientOptions,
  WalletRecord,
  TransactionRecord,
  AlertRecord,
  WebhookRecord,
  CreatedWebhookRecord,
  AlertSeverity,
  AnomalyType,
  BillingTier,
  ZeroWatchApiError,
} from '@0agent/0watch-sdk';

The Address and TransactionHash types are branded strings:

import type { Address, TransactionHash } from '@0agent/0watch-sdk';

function process(addr: Address) { /* ... */ }
process('0xabc...' as Address);

Next steps