@nexus-escrow/sdk
v1.2.0
Published
TypeScript SDK for NEXUS Escrow-as-a-Service (EaaS) API - Monero non-custodial escrow with FROST threshold signing
Maintainers
Readme
@nexus-escrow/sdk
TypeScript SDK for the NEXUS Escrow-as-a-Service (EaaS) API. Non-custodial Monero escrow with FROST 2-of-3 threshold signing.
Features
- Full TypeScript support with comprehensive type definitions
- Zero external dependencies (uses native
fetch) - Works in Browser and Node.js (18+)
- Tree-shakeable ESM exports
- Automatic retry handling for rate limits
- Webhook signature verification (HMAC-SHA256)
- Complete FROST DKG + signing flow
- E2EE escrow chat (X25519 + ChaCha20Poly1305)
- Fee estimation and daemon health monitoring
Installation
npm install @nexus-escrow/sdk
# or
yarn add @nexus-escrow/sdk
# or
pnpm add @nexus-escrow/sdkQuick Start
import { NexusClient, xmrToPiconero } from '@nexus-escrow/sdk';
const client = new NexusClient({
apiKey: process.env.NEXUS_API_KEY!,
baseUrl: 'https://your-nexus-instance.com',
});
// Create an escrow
const escrow = await client.escrow.create({
buyer_address: '4BuyerMoneroAddress...',
seller_address: '4SellerMoneroAddress...',
amount: xmrToPiconero(1.5), // 1.5 XMR
description: 'Purchase of item #123',
});
console.log(`Escrow ID: ${escrow.id}`);
console.log(`Deposit to: ${escrow.deposit_address}`);API Reference
NexusClient
Main client with all resource accessors.
const client = new NexusClient({
apiKey: 'nxs_live_...', // Required
baseUrl: 'https://api.nexus-escrow.com', // Optional
timeout: 30000, // Optional, ms
});
// Resource accessors:
client.escrow // Escrow lifecycle
client.frost // FROST DKG + signing
client.webhooks // Webhook management
client.fees // Fee estimation
client.analytics // Usage analytics
client.apiKeys // API key management
client.chat // E2EE escrow chatEscrow Operations
// Create
const escrow = await client.escrow.create({
buyer_address: '4...',
seller_address: '4...',
amount: 1_000_000_000_000n,
});
// Get
const escrow = await client.escrow.get('escrow-id');
// List with filters
const { escrows } = await client.escrow.list({ status: 'funded', limit: 20 });
// Join (counterparty)
await client.escrow.join('escrow-id');
// Lifecycle
await client.escrow.notifyFunding('escrow-id', { tx_hash: '...' });
await client.escrow.markDelivered('escrow-id');
await client.escrow.confirmDelivery('escrow-id');
await client.escrow.release({ escrow_id: '...', recipient_address: '4...' });
await client.escrow.refund('escrow-id');
await client.escrow.dispute({ escrow_id: '...', reason: '...', raised_by: 'buyer' });
await client.escrow.resolve({ escrow_id: '...', winner: 'buyer' });
// Addresses
await client.escrow.setPayoutAddress('escrow-id', { payout_address: '4...' });
await client.escrow.setRefundAddress('escrow-id', { refund_address: '4...' });
await client.escrow.getMultisigAddress('escrow-id');FROST DKG + Signing
Complete threshold signature flow for 2-of-3 multisig.
// Initialize DKG
const status = await client.frost.initDkg('escrow-id');
// DKG Round 1
await client.frost.submitRound1('escrow-id', {
role: 'buyer',
package: 'hex-encoded-round1-package',
});
const round1 = await client.frost.getRound1Packages('escrow-id');
// DKG Round 2
await client.frost.submitRound2('escrow-id', {
role: 'buyer',
packages: { '1': 'hex-for-vendor', '2': 'hex-for-arbiter' },
});
const round2 = await client.frost.getRound2Packages('escrow-id', 'buyer');
// Complete DKG
await client.frost.completeDkg('escrow-id', {
group_pubkey: '64-hex-chars',
multisig_address: '95-char-monero-address',
multisig_view_key: '64-hex-chars',
});
// Get Lagrange coefficients
const lagrange = await client.frost.getLagrangeCoefficients('buyer', 'vendor');
// Signing
await client.frost.initSigning('escrow-id', {
action: 'release',
destination_address: '4...',
});
await client.frost.submitNonceCommitment('escrow-id', {
role: 'buyer',
hiding_nonce_commitment: '...',
binding_nonce_commitment: '...',
});
await client.frost.submitPartialSignature('escrow-id', {
role: 'buyer',
partial_signature: '...',
});
const { tx_hash } = await client.frost.completeAndBroadcast('escrow-id');
// Shield backup
await client.frost.registerShield('escrow-id', { backup_id: '...', role: 'buyer' });
await client.frost.verifyShield('escrow-id', { backup_id: '...' });
// Shipping flow
await client.frost.confirmShipped('escrow-id', {
tracking_info: 'Shipped via DHL',
estimated_delivery_days: 7,
});
await client.frost.confirmReceipt('escrow-id', {
consent_confirmed: true,
});Webhooks
// Create (returns HMAC secret once)
const { webhook, secret } = await client.webhooks.create({
url: 'https://example.com/webhooks/nexus',
events: ['escrow.funded', 'escrow.released'],
});
// CRUD
const { webhooks } = await client.webhooks.list();
const wh = await client.webhooks.get('webhook-id');
await client.webhooks.update('webhook-id', { events: ['*'] });
await client.webhooks.delete('webhook-id');
// Control
await client.webhooks.activate('webhook-id');
await client.webhooks.deactivate('webhook-id');
const { secret: newSecret } = await client.webhooks.rotateSecret('webhook-id');
// Deliveries
const { deliveries } = await client.webhooks.getDeliveries('webhook-id');
await client.webhooks.retryDelivery('delivery-id');
const stats = await client.webhooks.getStats('webhook-id');Webhook Signature Verification
import { verifyWebhookSignature, parseWebhookPayload } from '@nexus-escrow/sdk';
app.post('/webhooks/nexus', async (req, res) => {
const isValid = await verifyWebhookSignature(
req.body,
req.headers['x-nexus-signature'],
req.headers['x-nexus-timestamp'],
process.env.WEBHOOK_SECRET!
);
if (!isValid) return res.status(401).send('Invalid signature');
const event = parseWebhookPayload(req.body);
switch (event.event_type) {
case 'escrow.funded':
// Handle funding...
break;
case 'escrow.released':
// Handle release...
break;
}
res.status(200).send('OK');
});Fee Estimation
// Single priority estimate
const fee = await client.fees.estimate('normal');
console.log(`Fee: ${fee.fee_xmr} (${fee.live ? 'live' : 'cached'})`);
// All priorities
const all = await client.fees.allEstimates();
for (const est of all.estimates) {
console.log(`${est.priority}: ${est.fee_xmr}`);
}
// Daemon health
const health = await client.fees.daemonHealth();
console.log(`${health.healthy}/${health.total} endpoints healthy`);
// Client fee config (B2B)
const config = await client.fees.getClientFeeConfig();
const estimate = await client.fees.estimateClientFees(1_000_000_000_000);
console.log(`Fee: ${estimate.fee_atomic} piconero (${estimate.fee_percent}%)`);Analytics
const usage = await client.analytics.getUsage('30d');
console.log(`Escrows: ${usage.total_escrows}`);
console.log(`Active: ${usage.active_escrows}`);
console.log(`Volume: ${usage.total_volume_atomic} piconero`);
console.log(`API requests: ${usage.total_api_requests}`);API Keys
// Create (raw key only shown once)
const { key } = await client.apiKeys.create({
name: 'Production Key',
csrf_token: csrfToken,
});
console.log(`Save this: ${key.raw_key}`);
// List
const { keys, total } = await client.apiKeys.list();
// Revoke / Delete
await client.apiKeys.revoke('key-id');
await client.apiKeys.delete('key-id');E2EE Escrow Chat
// Register keypair
await client.chat.registerKeypair('escrow-id', {
public_key: 'x25519-pubkey-hex',
});
// Get all participants' keys
const keypairs = await client.chat.getKeypairs('escrow-id');
if (keypairs.all_registered) {
// Send encrypted message (encrypted separately for each participant)
await client.chat.sendMessage('escrow-id', {
encrypted_content_buyer: '...',
encrypted_content_vendor: '...',
encrypted_content_arbiter: '...',
sender_ephemeral_pubkey: '...',
nonce: '...',
});
}
// Get messages
const { messages, has_more } = await client.chat.getMessages('escrow-id', {
limit: 50,
});
// Mark as read
await client.chat.markRead('escrow-id', 'message-id');
// Export for dispute evidence
const evidence = await client.chat.exportForDispute('escrow-id');Utility Functions
import { xmrToPiconero, piconeroToXmr, formatXmr, isValidMainnetAddress } from '@nexus-escrow/sdk';
xmrToPiconero(1.5); // 1_500_000_000_000n
piconeroToXmr(1_500_000_000_000n); // 1.5
formatXmr(1_500_000_000_000n, 4); // "1.5000 XMR"
isValidMainnetAddress('4...'); // true/falseError Handling
import {
NexusApiError,
AuthenticationError,
NotFoundError,
ValidationError,
RateLimitError,
NetworkError,
TimeoutError,
} from '@nexus-escrow/sdk';
try {
await client.escrow.get('nonexistent');
} catch (error) {
if (error instanceof NotFoundError) {
console.log('Escrow not found');
} else if (error instanceof RateLimitError) {
console.log(`Retry after ${error.retryAfter}s`);
} else if (error instanceof NexusApiError) {
console.log(`${error.code}: ${error.message} (req: ${error.requestId})`);
}
}Tree-Shaking
Import only what you need:
import { EscrowResource, xmrToPiconero } from '@nexus-escrow/sdk/escrow';
import { verifyWebhookSignature } from '@nexus-escrow/sdk/webhooks';
import { FrostResource } from '@nexus-escrow/sdk/frost';
import { FeeResource } from '@nexus-escrow/sdk/fees';
import { AnalyticsResource } from '@nexus-escrow/sdk/analytics';
import { ChatResource } from '@nexus-escrow/sdk/chat';Environment Support
- Node.js: 18.0.0+
- Browser: All modern browsers with
fetchsupport - Edge Runtime: Cloudflare Workers, Vercel Edge Functions, Deno
License
MIT
