npm package: @keystoneos/sdk
Description: TypeScript SDK for the KeyStone OS settlement orchestration API
Author: KeyStone OS
Latest version: 0.1.0

# @keystoneos/sdk

TypeScript SDK for the [KeyStone OS](https://keystoneos.xyz) settlement orchestration API.

## Installation

```bash
npm install @keystoneos/sdk
```

## Quick Start

```typescript
import { KeystoneClient } from '@keystoneos/sdk';

const keystone = new KeystoneClient({
  clientId: 'your-client-id',
  clientSecret: 'your-client-secret',
  environment: 'production',
});

// Create a settlement
const settlement = await keystone.settlements.create({
  idempotency_key: keystone.generateIdempotencyKey(),
  template_slug: 'dvp-immediate',
  timeout_at: new Date(Date.now() + 86400000).toISOString(),
  parties: [
    { role: 'seller', external_reference: 'SELLER-001', name: 'Acme Corp', wallet_address: '0x...' },
    { role: 'buyer', external_reference: 'BUYER-001', name: 'Beta Inc', wallet_address: '0x...' },
  ],
  legs: [
    { leg_type: 'asset_delivery', instrument_id: 'BOND-001', quantity: '1000', direction: 'deliver', party_role: 'seller' },
    { leg_type: 'payment', instrument_id: 'USDC', quantity: '50000', direction: 'deliver', party_role: 'buyer' },
  ],
});
```

## Features

- **Automatic auth** - M2M tokens via Auth0 client credentials, cached and refreshed automatically
- **Retry with backoff** - Automatic retry on 429 (respects Retry-After) and 5xx with exponential backoff
- **Idempotency** - Built-in idempotency key generation for safe retries
- **Webhook verification** - HMAC-SHA256 signature verification for incoming webhooks
- **Full type coverage** - Complete TypeScript types matching the API schema
- **Zero runtime dependencies** - Uses Node.js built-in `fetch` and `crypto`

## Authentication

The SDK uses Auth0 Client Credentials (M2M) flow. Your `client_id` is linked to a specific platform environment on the KeyStone side.

```typescript
const keystone = new KeystoneClient({
  clientId: process.env.KEYSTONE_CLIENT_ID!,
  clientSecret: process.env.KEYSTONE_CLIENT_SECRET!,
  environment: 'production',
});
```

Tokens are cached and refreshed automatically before expiry. No manual token management needed.

### Environment presets

| Environment   | API Base URL                          |
|---------------|---------------------------------------|
| `development` | `https://api.dev.keystoneos.xyz`      |
| `staging`     | `https://api.staging.keystoneos.xyz`  |
| `production`  | `https://api.keystoneos.xyz`          |

Override with `baseUrl` if needed:

```typescript
const keystone = new KeystoneClient({
  clientId: '...',
  clientSecret: '...',
  baseUrl: 'http://localhost:8000',
});
```

## Resources

### Settlements

```typescript
// Create
const settlement = await keystone.settlements.create({ ... });

// Get by ID
const settlement = await keystone.settlements.get('settlement-uuid');

// List with filters
const page = await keystone.settlements.list({ state: 'SETTLED', limit: 20 });

// List state events
const events = await keystone.settlements.listEvents('settlement-uuid');

// Get related repo settlements
const related = await keystone.settlements.getRelated('settlement-uuid');
```

### Instructions (bilateral flow)

```typescript
// Submit instruction (first party - generates trade_reference)
const instruction = await keystone.instructions.submit({
  template_slug: 'dvp-immediate',
  role: 'seller',
  party: { external_reference: 'SELLER-001', name: 'Acme Corp', wallet_address: '0x...' },
  legs: [{ instrument_id: 'BOND-001', quantity: '1000', direction: 'deliver' }],
  timeout_at: new Date(Date.now() + 86400000).toISOString(),
  idempotency_key: keystone.generateIdempotencyKey(),
});

// Submit counterparty instruction (uses same trade_reference)
const counter = await keystone.instructions.submit({
  trade_reference: instruction.trade_reference,
  template_slug: 'dvp-immediate',
  role: 'buyer',
  party: { external_reference: 'BUYER-001', name: 'Beta Inc', wallet_address: '0x...' },
  legs: [{ instrument_id: 'USDC', quantity: '50000', direction: 'deliver' }],
  timeout_at: new Date(Date.now() + 86400000).toISOString(),
  idempotency_key: keystone.generateIdempotencyKey(),
});

// Check if matched
if (counter.settlement_id) {
  console.log('Settlement created:', counter.settlement_id);
}

// List and cancel
const pending = await keystone.instructions.list({ status: 'pending_match' });
await keystone.instructions.cancel('instruction-uuid');
```

### Compliance

```typescript
await keystone.compliance.submitDecision('settlement-uuid', { decision: 'approve' });
await keystone.compliance.submitDecision('settlement-uuid', { decision: 'reject' });
```

### Templates

```typescript
const templates = await keystone.templates.list();
const template = await keystone.templates.get('template-uuid');
```

### Webhooks

```typescript
// Create endpoint (secret returned only in this response)
const endpoint = await keystone.webhooks.create({
  url: 'https://your-app.com/webhooks/keystone',
  events: ['settlement.*'],
});
console.log('Store this secret:', endpoint.secret);

// List, update, delete
const endpoints = await keystone.webhooks.list();
await keystone.webhooks.update('endpoint-uuid', { is_active: false });
await keystone.webhooks.delete('endpoint-uuid');

// Test and rotate
const test = await keystone.webhooks.test('endpoint-uuid');
const rotation = await keystone.webhooks.rotateSecret('endpoint-uuid');

// View delivery logs
const deliveries = await keystone.webhooks.listDeliveries('endpoint-uuid');
```

### Environments

```typescript
const envs = await keystone.environments.list();
const env = await keystone.environments.get('env-uuid');
await keystone.environments.update('env-uuid', { rate_limit_rpm: 100 });
await keystone.environments.deactivate('env-uuid');
```

## Webhook Signature Verification

Verify incoming webhook payloads using HMAC-SHA256:

```typescript
import { verifyWebhookSignature } from '@keystoneos/sdk';

// In your Express/Fastify/etc handler:
const isValid = verifyWebhookSignature(
  rawBody,                                    // raw request body (Buffer or string)
  process.env.WEBHOOK_SECRET!,                // your endpoint secret
  request.headers['x-keystone-signature']!,   // signature header
);

if (!isValid) {
  return response.status(401).send('Invalid signature');
}
```

Or use the client instance method:

```typescript
const isValid = keystone.webhooks.verifySignature(rawBody, signature, secret);
```

## Idempotency

All create/submit operations accept an `idempotency_key`. Use the built-in generator:

```typescript
const key = keystone.generateIdempotencyKey(); // UUIDv4
```

Sending the same idempotency key twice returns the existing resource without re-processing.

## Error Handling

```typescript
import { ApiError, RetryExhaustedError, AuthenticationError } from '@keystoneos/sdk';

try {
  await keystone.settlements.get('nonexistent');
} catch (err) {
  if (err instanceof ApiError) {
    console.log(err.status);  // 404
    console.log(err.body);    // { detail: "Settlement not found" }
  }
  if (err instanceof RetryExhaustedError) {
    console.log(`Failed after ${err.attempts} attempts`);
  }
  if (err instanceof AuthenticationError) {
    console.log('Check your credentials');
  }
}
```

## Retry Behavior

The SDK automatically retries on:
- **429 Too Many Requests** - respects the `Retry-After` header
- **500, 502, 503, 504** - server errors with exponential backoff

Configure retry behavior:

```typescript
const keystone = new KeystoneClient({
  clientId: '...',
  clientSecret: '...',
  maxRetries: 5,        // default: 3
  retryBaseDelay: 1000, // default: 500ms
  timeout: 60000,       // default: 30000ms
});
```

## Requirements

- Node.js >= 18.0.0 (uses native `fetch` and `crypto`)
- TypeScript >= 5.0 (for type definitions)

## License

MIT