npm package: @proofchain/sdk
Description: Official JavaScript/TypeScript SDK for ProofChain - blockchain-anchored document attestation
Author: ProofChain
Latest version: 2.21.1

# ProofChain JavaScript/TypeScript SDK

Official JavaScript/TypeScript SDK for [ProofChain](https://proofchain.co.za) - blockchain-anchored document attestation.

Works in Node.js and browsers.

## Installation

```bash
npm install @proofchain/sdk
# or
yarn add @proofchain/sdk
# or
pnpm add @proofchain/sdk
```

## Quick Start

```typescript
import { ProofChain } from '@proofchain/sdk';

// Create client
const client = new ProofChain({ apiKey: 'your-api-key' });

// Attest a document
const result = await client.documents.attest({
  file: myFile, // File, Blob, or Buffer
  userId: 'user@example.com',
  eventType: 'contract_signed',
});

console.log('IPFS Hash:', result.ipfsHash);
console.log('Verify URL:', result.verifyUrl);

// Verify
const verification = await client.verify(result.ipfsHash);
console.log('Valid:', verification.valid);
```

## High-Performance Ingestion

For maximum throughput, use the dedicated `IngestionClient` which connects directly to the Rust ingestion API:

```typescript
import { IngestionClient } from '@proofchain/sdk';

const ingestion = new IngestionClient({ apiKey: 'your-api-key' });

// Single event (immediate attestation)
const result = await ingestion.ingest({
  userId: 'user-123',
  eventType: 'purchase',
  data: { amount: 99.99, product: 'widget' },
});

console.log('Event ID:', result.eventId);
console.log('Certificate:', result.certificateId);

// Batch events (up to 1000 per request)
const batchResult = await ingestion.ingestBatch({
  events: [
    { userId: 'user-1', eventType: 'click', data: { page: '/home' } },
    { userId: 'user-2', eventType: 'click', data: { page: '/products' } },
    // ... up to 1000 events
  ],
});

console.log('Queued:', batchResult.queued);
console.log('Failed:', batchResult.failed);
```

## State Channels (High-Volume Streaming)

For high-throughput scenarios (100K+ events/sec):

```typescript
import { ProofChain } from '@proofchain/sdk';

const client = new ProofChain({ apiKey: 'your-api-key' });

// Create a state channel
const channel = await client.channels.create({ name: 'iot-sensors' });

// Stream events
for (const reading of sensorReadings) {
  await client.channels.stream(channel.channelId, {
    eventType: 'sensor_reading',
    userId: reading.deviceId,
    data: { temperature: reading.temp, humidity: reading.humidity },
  });
}

// Settle on-chain
const settlement = await client.channels.settle(channel.channelId);
console.log('TX Hash:', settlement.txHash);
```

## Features

### Documents

```typescript
// Attest a file (Node.js)
import { readFileSync } from 'fs';

const result = await client.documents.attest({
  file: readFileSync('contract.pdf'),
  filename: 'contract.pdf',
  userId: 'user@example.com',
  eventType: 'document_uploaded',
  metadata: { department: 'legal' },
});

// Attest a file (Browser)
const fileInput = document.querySelector('input[type="file"]');
const result = await client.documents.attest({
  file: fileInput.files[0],
  userId: 'user@example.com',
});

// Get document by hash
const doc = await client.documents.get('Qm...');
```

### Events

```typescript
// Create an event
const event = await client.events.create({
  eventType: 'user_action',
  userId: 'user123',
  data: { action: 'login', ip: '192.168.1.1' },
});

// List events
const events = await client.events.list({
  userId: 'user123',
  eventType: 'user_action',
  limit: 100,
});

// Search events
const results = await client.events.search({
  query: 'login',
  startDate: '2024-01-01',
  endDate: '2024-12-31',
});
```

### Verification

```typescript
// Verify by IPFS hash
const result = await client.verify('Qm...');

if (result.valid) {
  console.log('Timestamp:', result.timestamp);
  console.log('Blockchain TX:', result.blockchainTx);
  console.log('Certificate ID:', result.certificateId);
}
```

### Certificates

```typescript
// Issue a certificate
const cert = await client.certificates.issue({
  recipientName: 'John Doe',
  recipientEmail: 'john@example.com',
  title: 'Course Completion',
  description: 'Completed JavaScript Fundamentals',
  metadata: { courseId: 'JS101', score: 95 },
});

console.log('Certificate ID:', cert.certificateId);
console.log('QR Code:', cert.qrCodeUrl);

// Revoke
await client.certificates.revoke(cert.certificateId, 'Issued in error');
```

### Webhooks

```typescript
// Register a webhook
const webhook = await client.webhooks.create({
  url: 'https://your-app.com/webhook',
  events: ['document.attested', 'channel.settled'],
  secret: 'your-secret',
});

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

// Delete
await client.webhooks.delete(webhook.id);
```

### Quests

Quests are gamified user journeys with steps that can be completed via events.

```typescript
// List available quests for a user (with their progress)
const quests = await client.quests.listAvailable(userId);
// Returns: { quest: Quest, progress?: UserQuestProgress }[]

// Each quest has a type that determines behavior:
// - 'epic': Long-form quests - user must explicitly start
// - 'quick_hit': Short quests - auto-enrolls on matching event
// - 'challenge': Time-limited quests - auto-enrolls on matching event

for (const { quest, progress } of quests) {
  console.log(`${quest.name} (${quest.quest_type})`);
  if (progress) {
    console.log(`  Progress: ${progress.completion_percentage}%`);
  }
}

// Start an Epic quest (required for epic type only)
const progress = await client.quests.startQuest(questId, userId);

// Get user's progress on a specific quest
const questProgress = await client.quests.getUserProgress(questId, userId);

// Get all quest progress for a user
const allProgress = await client.quests.getAllUserProgress(userId);

// Complete a step manually (for manual step types)
const result = await client.quests.completeStep(questId, userId, stepIndex);
console.log('Step completed:', result.step_completed);
console.log('Quest completed:', result.quest_completed);
```

**Quest Types:**

| Type | Behavior |
|------|----------|
| `epic` | Long-form, multi-step quests. User must call `startQuest()` to begin. |
| `quick_hit` | Short, easy quests. Auto-enrolls when user triggers a matching event. |
| `challenge` | Time-limited or competitive. Auto-enrolls when user triggers a matching event. |

### Real-Time Notifications (SSE)

Push notifications to your frontend in real-time when quest/reward events happen. Uses Server-Sent Events (SSE) — no WebSocket setup needed.

```typescript
import { ProofChain } from '@proofchain/sdk';

const client = new ProofChain({ apiKey: 'your-api-key' });

// Subscribe to notifications for a user
const unsubscribe = client.notifications.subscribe('user-123', (event) => {
  console.log('Notification:', event.event, event.data);

  switch (event.event) {
    case 'quest_step_completed':
      showToast(`Step completed: ${event.data.step_name}`);
      break;

    case 'quest_completed':
      showConfetti();
      showToast(`Quest completed: ${event.data.quest_name}! 🎉`);
      break;

    case 'badge_earned':
      showBadgeModal({
        name: event.data.reward_name,
        image: event.data.image_url,
      });
      break;

    case 'reward_claimable':
      showClaimButton(event.data.quest_id);
      break;

    case 'reward_earned':
      showToast(`Reward earned: ${event.data.reward_name}`);
      break;
  }
}, {
  onConnect: () => console.log('Connected to notifications'),
  onDisconnect: () => console.log('Disconnected, will auto-reconnect...'),
  onError: (err) => console.error('Notification error:', err),
  autoReconnect: true,    // default: true
  reconnectDelay: 3000,   // default: 3000ms
});

// Cleanup when component unmounts or user logs out
unsubscribe();
```

**React example:**

```tsx
import { useEffect } from 'react';
import { ProofChain } from '@proofchain/sdk';

function useNotifications(client: ProofChain, userId: string) {
  useEffect(() => {
    const unsub = client.notifications.subscribe(userId, (event) => {
      // Handle notification (show toast, update UI, etc.)
      console.log(event.event, event.data);
    });
    return () => unsub(); // cleanup on unmount
  }, [client, userId]);
}
```

**End-user JWT auth (for PWA/frontend without API key):**

```typescript
const client = ProofChain.forEndUser({
  userToken: auth.getIdToken(),
  tenantId: 'my-tenant-slug',
});

// End-user can only subscribe to their own notifications
const unsub = client.notifications.subscribe(myUserId, (event) => {
  // ...
});
```

**Notification event types:**

| Event | When | Data fields |
|-------|------|-------------|
| `quest_step_completed` | A quest step is completed | `quest_id`, `quest_name`, `step_index`, `step_name`, `steps_completed`, `total_steps`, `points_earned` |
| `quest_completed` | A quest is fully completed (auto-award) | `quest_id`, `quest_name`, `points_earned`, `completion_count` |
| `reward_claimable` | A quest is completed (claimable mode) | `quest_id`, `quest_name`, `points_earned`, `reward_definition_id` |
| `badge_earned` | A badge reward is distributed | `quest_id`, `quest_name`, `reward_id`, `reward_name`, `reward_type`, `image_url` |
| `reward_earned` | A non-badge reward is distributed | `quest_id`, `quest_name`, `reward_id`, `reward_name`, `reward_type`, `value`, `image_url` |

**Notification payload shape:**

```typescript
interface NotificationEvent {
  id: string;           // unique event ID
  event: string;        // event type (see table above)
  data: Record<string, any>; // event-specific data
  tenant_id: string;
  user_id: string;
  timestamp: string;    // ISO 8601
}
```

## Error Handling

```typescript
import { ProofChain, ProofChainError, AuthenticationError, RateLimitError } from '@proofchain/sdk';

try {
  const result = await client.documents.attest(req);
} catch (error) {
  if (error instanceof AuthenticationError) {
    console.error('Invalid API key');
  } else if (error instanceof RateLimitError) {
    console.error(`Rate limited. Retry after ${error.retryAfter} seconds`);
  } else if (error instanceof ProofChainError) {
    console.error(`API error: ${error.message}`);
  }
}
```

## Configuration

```typescript
import { ProofChain } from '@proofchain/sdk';

// Full configuration
const client = new ProofChain({
  apiKey: 'your-api-key',
  baseUrl: 'https://ingest.proofchain.co.za', // Ingestion API (Rust)
  mgmtUrl: 'https://api.proofchain.co.za',    // Management API (Python)
  timeout: 30000, // 30 seconds
  maxRetries: 3,
});

// From environment variable (Node.js)
// Set PROOFCHAIN_API_KEY
const client = ProofChain.fromEnv();
```

## TypeScript Support

Full TypeScript support with exported types:

```typescript
import type {
  AttestationResult,
  Event,
  Channel,
  ChannelStatus,
  Settlement,
  Certificate,
  Webhook,
  VerificationResult,
  SearchResult,
} from '@proofchain/sdk';
```

## Browser Usage

The SDK works in browsers with no additional configuration:

```html
<script type="module">
  import { ProofChain } from 'https://esm.sh/@proofchain/sdk';

  const client = new ProofChain({ apiKey: 'your-api-key' });
  
  // Use with file input
  document.querySelector('#upload').addEventListener('change', async (e) => {
    const file = e.target.files[0];
    const result = await client.documents.attest({
      file,
      userId: 'user@example.com',
    });
    console.log('Attested:', result.ipfsHash);
  });
</script>
```

## License

MIT License - see [LICENSE](LICENSE) for details.

## Support

- Documentation: https://proofchain.co.za/docs
- Email: support@proofchain.co.za
- GitHub Issues: https://github.com/proofchain/proofchain-js/issues