Skip to main content

SDK

Official SDKs for the Chainara Blockchain Threat Intelligence API. Both SDKs cover the full API v2 surface with typed models, automatic retry with exponential backoff, and rate-limit-aware error handling.

Source: github.com/exfil-chris/chainara-sdk


Installation

npm install @chainara/sdk

Requires Node.js 18+. Also works in browser environments.


Initialization

Pass your API key and tenant base URL. The API key can also be read from the CHAINARA_API_KEY environment variable.

import { ChainaraClient } from "@chainara/sdk";

const client = new ChainaraClient({
apiKey: "ek_your_api_key",
baseUrl: "https://ripple-platform.chainara.io/api/v2",
timeout: 30000, // ms, default 30s
maxRetries: 3, // default 3
});

Replace ripple-platform with your tenant subdomain. The default Chainara instance uses platform.chainara.io.


Resources

Wallets

Fetch full threat intelligence or a fast risk score for any blockchain address.

// Full threat intelligence (risk score, signals, associated domains, fund flow)
const intel = await client.wallets.getIntelligence(1, "rAddress...");
console.log(intel.riskScore, intel.riskLevel);

// Risk score only (faster, lower latency)
const score = await client.wallets.getRiskScore(1, "rAddress...");

The first argument is the blockchain ID. Use 1 for XRPL, 2 for Stellar. See Blockchain IDs for the full list.


Fraud Reports

Submit reports and query the fraud intelligence database.

// List reports (paginated)
const reports = await client.fraudReports.list({ page: 1, limit: 20 });

// Submit a new report
const report = await client.fraudReports.create({
walletAddress: "rAddress...",
reportType: "scam_website",
description: "Fake XRP giveaway site impersonating Ripple",
});

Blacklist

Query confirmed-malicious addresses.

const entries = await client.blacklist.list({ page: 1, limit: 50 });

Threat Feed

Pull a full threat intelligence snapshot or an incremental update. Enterprise plan required.

// Full snapshot
const snapshot = await client.feed.getSnapshot();

// Incremental: only indicators added or updated since last sync
const delta = await client.feed.getSnapshot({
since: "2026-03-01T00:00:00Z",
});

// Filtered: blacklisted wallets on XRPL only, high confidence
const filtered = await client.feed.getSnapshot({
types: "wallet",
severityTier: "blacklisted",
minConfidence: 80,
blockchain: "xrpl",
limit: 1000,
});

// Paginate with cursor
let cursor: string | undefined;
do {
const page = await client.feed.getSnapshot({ cursor, limit: 1000 });
for (const indicator of page.indicators) {
process(indicator);
}
cursor = page.nextCursor;
} while (cursor);

Store generated_at from each response and pass it as since on the next call to receive only new or updated indicators.


Webhooks

Register endpoints to receive real-time threat indicator events. Enterprise plan required.

// Create a subscription
const sub = await client.webhooks.create({
url: "https://myapp.example.com/hooks/chainara",
eventTypes: ["indicator_added", "indicator_updated", "indicator_removed"],
description: "Production SIEM hook",
});
// Save sub.signingSecret. It is returned once and cannot be retrieved again.

// List subscriptions
const subs = await client.webhooks.list();

// Send a test delivery
await client.webhooks.test(sub.id);

// Rotate signing secret
const rotated = await client.webhooks.rotateSecret(sub.id);

// View delivery history
const history = await client.webhooks.listDeliveries(sub.id, 50);

// Update or delete
await client.webhooks.update(sub.id, { url: "https://new-url.example.com/hook" });
await client.webhooks.delete(sub.id);

Ingest

Bulk-submit threat intelligence from your own sources. All ingest submissions are tagged pending and capped at risk score 65 until an analyst verifies them.

// Bulk fraud reports
await client.ingest.reports([
{ walletAddress: "rAddr1...", reportType: "scam_website", description: "..." },
{ walletAddress: "rAddr2...", reportType: "phishing", description: "..." },
]);

// Bulk suspicious wallets
await client.ingest.wallets([
{ address: "rAddr1...", blockchainId: 1, reason: "Giveaway scam", confidence: 0.9 },
]);

// Bulk malicious domains
await client.ingest.domains([
{ domain: "evil-xrp.com", reason: "XRP phishing site" },
]);

// Async batch job (up to 10,000 items)
const job = await client.ingest.batch("wallets", items, "my-feed");
const status = await client.ingest.getBatchJob(job.jobId);

Health

const status = await client.health.check();

Webhook Signature Verification

Verify every incoming delivery before processing it. Both SDKs use constant-time comparison to prevent timing attacks.

import express from "express";
import { ChainaraClient } from "@chainara/sdk";

const app = express();
app.use("/hooks/chainara", express.raw({ type: "application/json" }));

app.post("/hooks/chainara", (req, res) => {
const isValid = ChainaraClient.verifyWebhookSignature(
req.body, // raw Buffer
req.headers["x-chainara-signature"] as string,
process.env.WEBHOOK_SECRET!,
);

if (!isValid) return res.status(401).send("Invalid signature");

const event = JSON.parse(req.body.toString());
console.log("Event:", event.event, event.indicators.length, "indicators");
res.sendStatus(200);
});

Note: use express.raw() (not express.json()) to preserve the raw bytes needed for HMAC verification.


Error Handling

All SDK errors extend ChainaraError. Catch specific subclasses for targeted handling.

import {
ChainaraError,
ChainaraConnectionError,
AuthenticationError,
PermissionDeniedError,
NotFoundError,
RateLimitError,
} from "@chainara/sdk";

try {
const intel = await client.wallets.getIntelligence(1, "rAddress...");
} catch (error) {
if (error instanceof AuthenticationError) {
// Invalid or missing API key
console.error("Check your API key");
} else if (error instanceof PermissionDeniedError) {
// Valid key but endpoint requires a higher plan
console.error("Endpoint requires Enterprise plan");
} else if (error instanceof NotFoundError) {
// Address not in database. Treat as unknown, not safe.
console.warn("Address not found");
} else if (error instanceof RateLimitError) {
// Respect Retry-After
const wait = error.retryAfter ?? 60;
console.warn(`Rate limited. Retry in ${wait}s`);
await new Promise(r => setTimeout(r, wait * 1000));
} else if (error instanceof ChainaraConnectionError) {
console.error("Could not reach the API");
} else if (error instanceof ChainaraError) {
console.error(`SDK error: ${error.message}`);
}
}

Exception hierarchy

ChainaraError              (base, catches everything)
├── ChainaraConnectionError (server unreachable)
├── ChainaraValidationError (client-side validation failed)
├── ChainaraTimeoutError (request timed out)
└── ChainaraAPIError (HTTP error response)
├── AuthenticationError 401
├── PermissionDeniedError 403
├── NotFoundError 404
├── BadRequestError 400
├── ConflictError 409
├── RateLimitError 429 (exposes retry_after / retryAfter)
└── InternalServerError 5xx

Environment Variables

export CHAINARA_API_KEY="ek_..."

When set, the SDK reads the key automatically and you can omit apiKey from the constructor.


SIEM and Webhook Integration Examples

The SDK repository includes production-ready integration examples under integrations/:

ExampleDescription
webhooks/webhook_server.pyFlask receiver: verifies signatures, dispatches events by type
webhooks/webhook-server.tsExpress receiver: same flow in TypeScript
webhooks/webhook_client.pyFull webhook lifecycle: create, test, rotate secret, delete
siem/elastic_push.pyBulk-index threat feed into Elasticsearch with incremental sync
siem/splunk_push.pyPush threat feed to Splunk HEC in batches
siem/generic_siem.pyReusable Fetch → Transform → Push scaffold for any SIEM

All SIEM scripts support incremental sync: the first run fetches the full snapshot; subsequent runs fetch only indicators updated since the last successful run using a state file.