@alexalves87/opentimestamps-client
v1.0.5
Published
Official client SDK for OpenTimestamps calendars with resilience patterns
Maintainers
Readme
OpenTimestamps Client SDK
Official TypeScript/JavaScript client for OpenTimestamps with enterprise-grade resilience patterns
Features
( Complete OpenTimestamps Operations
stamp()- Create timestamp proofs by submitting to calendar serversupgrade()- Query calendars for Bitcoin confirmationsverify()- Verify proofs against the Bitcoin blockchain
=� Enterprise-Grade Resilience
- Circuit Breaker - Isolate failures per calendar (prevents cascading failures)
- Exponential Backoff - Configurable retry strategies with jitter
- Timeout Management - Per-attempt and total operation timeouts
- Threshold-based Submissions - Require minimum successful submissions (default: 2/4 calendars)
=' Developer Experience
- TypeScript First - Full type safety with strict mode enabled
- Multi-Runtime - Works in Node.js 18+, browsers, and edge runtimes
- Tree-Shakeable - Dual ESM/CJS build with zero dependencies (except core OTS library)
- Abort Support - Native AbortController integration for all operations
- Observable - Optional logger interface for monitoring and debugging
Installation
npm install @alexalves87/opentimestamps-clientOr with other package managers:
yarn add @alexalves87/opentimestamps-client
pnpm add @alexalves87/opentimestamps-clientQuick Start
Create a Timestamp
import { OpenTimestampsClient } from '@alexalves87/opentimestamps-client'
import { createHash } from 'crypto'
import { readFileSync } from 'fs'
// Initialize client (uses default OpenTimestamps calendars)
const client = new OpenTimestampsClient()
// Hash your file
const fileContent = readFileSync('document.pdf')
const hash = createHash('sha256').update(fileContent).digest()
// Create timestamp proof
const otsProof = await client.stamp(hash)
// Save the .ots file
writeFileSync('document.pdf.ots', otsProof)Upgrade to Bitcoin Confirmation
// Later, query calendars for Bitcoin confirmation
const upgradedProof = await client.upgrade(otsProof)
if (upgradedProof !== otsProof) {
console.log('Proof upgraded with Bitcoin confirmation!')
writeFileSync('document.pdf.ots', upgradedProof)
}Verify a Timestamp
// Verify the timestamp proof
const result = await client.verify(upgradedProof, hash)
if (result.valid) {
console.log(` Timestamp verified!`)
console.log(` Block: ${result.blockHeight}`)
console.log(` Time: ${result.timestamp}`)
} else {
console.error(`L Verification failed: ${result.error}`)
}Advanced Usage
Custom Calendars
const client = new OpenTimestampsClient({
calendars: [
'https://alice.btc.calendar.opentimestamps.org',
'https://bob.btc.calendar.opentimestamps.org',
'https://finney.calendar.eternitywall.com',
],
minimumSuccessfulSubmissions: 2, // Require 2/3 calendars to succeed
})Resilience Configuration
const client = new OpenTimestampsClient({
resilience: {
// Timeout settings
timeout: {
totalMs: 30000, // 30s total operation timeout
perAttemptMs: 5000, // 5s per attempt timeout
},
// Retry configuration
retries: {
enabled: true,
maxAttempts: 3,
backoff: {
strategy: 'exponential', // 'exponential' | 'linear' | 'constant'
initialDelayMs: 1000,
maxDelayMs: 10000,
jitter: 'full', // 'full' | 'equal' | 'none'
},
},
// Circuit breaker settings
circuitBreaker: {
enabled: true,
failureThreshold: 5, // Open circuit after 5 failures
recoveryTimeoutMs: 15000, // Try recovery after 15s
halfOpenMaxAttempts: 1, // Test with 1 request when half-open
},
},
})Cancellation Support
const controller = new AbortController()
// Cancel operation after 5 seconds
setTimeout(() => controller.abort(), 5000)
try {
const proof = await client.stamp(hash, { signal: controller.signal })
} catch (error) {
if (error.name === 'AbortError') {
console.log('Operation cancelled')
}
}Monitoring with Logger
const logger = {
debug: (msg) => console.log(`[DEBUG] ${msg}`),
info: (msg) => console.log(`[INFO] ${msg}`),
warn: (msg) => console.warn(`[WARN] ${msg}`),
error: (msg) => console.error(`[ERROR] ${msg}`),
}
const client = new OpenTimestampsClient({ logger })
// Monitor circuit breaker state
const state = client.getCircuitState('https://alice.btc.calendar.opentimestamps.org')
console.log(`Circuit state: ${state}`) // 'CLOSED' | 'OPEN' | 'HALF_OPEN'
// Manually reset circuit if needed
client.resetCircuit('https://alice.btc.calendar.opentimestamps.org')API Reference
OpenTimestampsClient
Constructor Options
interface ClientOptions {
// Calendar servers to use (defaults to official OpenTimestamps calendars)
calendars?: string[]
// Minimum number of successful submissions required for stamp()
minimumSuccessfulSubmissions?: number // default: 2
// Resilience configuration (timeout, retry, circuit breaker)
resilience?: ResilienceOptions
// Optional logger for observability
logger?: Logger
// Global AbortSignal for all operations
signal?: AbortSignal
}Methods
stamp(hash, options?)
Create a timestamp by submitting to calendar servers.
async stamp(
hash: Buffer | string, // SHA-256 hash (32 bytes or 64 hex chars)
options?: OperationOptions
): Promise<Buffer> // .ots proof with pending attestationsThrows:
ValidationError- Invalid hash formatStampError- Insufficient successful submissionsNetworkError- Network/timeout errors
upgrade(proof, options?)
Query calendars for Bitcoin confirmation.
async upgrade(
proof: Buffer, // Existing .ots proof
options?: OperationOptions
): Promise<Buffer> // Upgraded .ots proof (or original if no upgrade available)Throws:
ValidationError- Invalid proof formatUpgradeError- Upgrade operation failed
verify(proof, hash, options?)
Verify timestamp proof against Bitcoin blockchain.
async verify(
proof: Buffer, // .ots proof file
hash: Buffer | string, // Original file hash
options?: OperationOptions
): Promise<VerificationResult>
interface VerificationResult {
valid: boolean
blockHeight?: number
timestamp?: Date
error?: string
}getCircuitState(calendar)
Get current circuit breaker state for a calendar.
getCircuitState(calendar: string): 'CLOSED' | 'OPEN' | 'HALF_OPEN' | undefinedresetCircuit(calendar)
Manually reset circuit breaker for a calendar.
resetCircuit(calendar: string): voidArchitecture
The SDK implements a layered resilience architecture:
�������������������������������������
Public API (stamp/upgrade/verify)
����������������,��������������������
�������������������������������������
Orchestration Layer
- Validation
- Parallel/Sequential coordination
����������������,��������������������
�������������������������������������
Resilient Network Layer
- Timeout (total + per-attempt)
- Retry (exponential backoff)
- Circuit Breaker (per-calendar)
����������������,��������������������
�������������������������������������
Fetch Adapter (multi-runtime)
�������������������������������������Key Design Decisions
- Per-Calendar Circuit Breakers: Each calendar has independent failure tracking, so one failing calendar doesn't affect others
- Threshold-based Submissions:
stamp()requires N/M calendars to succeed (default: 2/4), providing resilience through redundancy - Sequential Upgrade Queries:
upgrade()stops at the first confirmed calendar, minimizing unnecessary API calls - 4xx = Fail Fast, 5xx = Retry: HTTP semantics are respected for efficient error handling
Error Handling
The SDK provides detailed error context:
try {
await client.stamp(hash)
} catch (error) {
if (error instanceof StampError) {
console.log(`${error.successfulSubmissions.length} calendars succeeded`)
console.log(`${error.failedSubmissions.length} calendars failed`)
error.failedSubmissions.forEach(({ calendar, error }) => {
console.error(`${calendar}: ${error.message}`)
})
}
}Browser Usage
The SDK works in browsers with native fetch support:
<script type="module">
import { OpenTimestampsClient } from '@alexalves87/opentimestamps-client'
const client = new OpenTimestampsClient()
// Hash from file input
const file = document.querySelector('input[type="file"]').files[0]
const buffer = await file.arrayBuffer()
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer)
const hash = Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
// Create timestamp
const proof = await client.stamp(hash)
console.log('Timestamp created!', proof)
</script>Testing
The SDK has comprehensive test coverage:
# Run tests
npm test
# Run tests with coverage
npm test -- --coverage
# Run tests in watch mode
npm run test:watchTest Stats:
- 83+ tests (unit + integration)
- 80%+ code coverage
- MSW-based integration tests with realistic scenarios
- Property-based testing with fast-check
Development
# Install dependencies
npm install
# Build (ESM + CJS)
npm run build
# Development watch mode
npm run dev
# Lint
npm run lint
# Format
npm run formatContributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Security
For security issues, please see SECURITY.md.
License
MIT � alexalves87
FAQ
How does this differ from the core opentimestamps library?
The core @alexalves87/opentimestamps library handles OTS file format serialization/deserialization and cryptographic operations. This client SDK adds:
- High-level API for calendar operations
- Enterprise resilience patterns (circuit breaker, retry, timeout)
- Multi-calendar coordination
- TypeScript-first developer experience
Why require 2/4 calendars by default?
Requiring multiple successful submissions provides resilience:
- Protects against single calendar outages
- Ensures timestamp validity even if some calendars are compromised
- Default threshold (2/4) balances reliability vs. speed
Can I use my own calendar servers?
Yes! Just pass custom calendars to the constructor:
const client = new OpenTimestampsClient({
calendars: ['https://my-calendar.example.com'],
minimumSuccessfulSubmissions: 1,
})How do I handle offline scenarios?
Use timeout configuration and catch network errors:
const client = new OpenTimestampsClient({
resilience: {
timeout: { totalMs: 5000 }, // Fail fast
retries: { enabled: false }, // Don't retry
},
})
try {
await client.stamp(hash)
} catch (error) {
// Store hash locally for retry later
await queueForLater(hash)
}Is this production-ready?
Yes! The SDK is designed for production use with:
- Comprehensive test coverage (83+ tests)
- Battle-tested resilience patterns
- Semantic versioning
- Clear error messages and debugging support
Links
Acknowledgments
Built with the OpenTimestamps protocol created by Peter Todd.
