@proofchain/sdk
v2.21.0
Published
Official JavaScript/TypeScript SDK for ProofChain - blockchain-anchored document attestation
Maintainers
Readme
ProofChain JavaScript/TypeScript SDK
Official JavaScript/TypeScript SDK for ProofChain - blockchain-anchored document attestation.
Works in Node.js and browsers.
Installation
npm install @proofchain/sdk
# or
yarn add @proofchain/sdk
# or
pnpm add @proofchain/sdkQuick Start
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: '[email protected]',
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:
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):
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
// Attest a file (Node.js)
import { readFileSync } from 'fs';
const result = await client.documents.attest({
file: readFileSync('contract.pdf'),
filename: 'contract.pdf',
userId: '[email protected]',
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: '[email protected]',
});
// Get document by hash
const doc = await client.documents.get('Qm...');Events
// 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
// 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
// Issue a certificate
const cert = await client.certificates.issue({
recipientName: 'John Doe',
recipientEmail: '[email protected]',
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
// 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.
// 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.
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:
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):
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:
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
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
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:
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:
<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: '[email protected]',
});
console.log('Attested:', result.ipfsHash);
});
</script>License
MIT License - see LICENSE for details.
Support
- Documentation: https://proofchain.co.za/docs
- Email: [email protected]
- GitHub Issues: https://github.com/proofchain/proofchain-js/issues
