@otskit/client 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +531 -0
- package/dist/index.cjs +1012 -0
- package/dist/index.d.cts +361 -0
- package/dist/index.d.ts +361 -0
- package/dist/index.js +965 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 alexalves87
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src=".github/otskit-client-header.png" alt="OTSkit Client" width="480" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @otskit/client
|
|
6
|
+
|
|
7
|
+
> TypeScript/JavaScript client for OpenTimestamps with enterprise-grade resilience patterns
|
|
8
|
+
|
|
9
|
+
[](https://github.com/OTSkit/OTSkit-client/actions/workflows/ci.yml)
|
|
10
|
+
[](https://www.npmjs.com/package/@otskit/client)
|
|
11
|
+
[](https://opensource.org/licenses/MIT)
|
|
12
|
+
[](https://www.typescriptlang.org/)
|
|
13
|
+
[](https://nodejs.org/)
|
|
14
|
+
|
|
15
|
+
`@otskit/client` is the official client SDK for submitting, upgrading, and verifying [OpenTimestamps](https://opentimestamps.org) proofs. It sits on top of [@otskit/core](https://github.com/OTSkit/OTSkit-core) — the low-level protocol engine — and wraps it in a high-level API with production-ready resilience patterns built in.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
### Complete OpenTimestamps Workflow
|
|
20
|
+
- **`stamp()`** — Hash your data, build a Merkle tree with a secure nonce, and submit to multiple calendar servers simultaneously
|
|
21
|
+
- **`upgrade()`** — Query calendars for Bitcoin confirmations and merge them into the pending proof
|
|
22
|
+
- **`verify()`** — Verify a completed proof against the Bitcoin blockchain via Esplora
|
|
23
|
+
|
|
24
|
+
### Enterprise-Grade Resilience
|
|
25
|
+
- **Circuit Breaker** — Per-calendar isolation; one failing calendar never affects the others
|
|
26
|
+
- **Exponential Backoff** — Three strategies (`exponential`, `linear`, `constant`) with three jitter modes (`full`, `equal`, `none`)
|
|
27
|
+
- **Dual Timeouts** — Independent `totalTimeoutMs` (whole operation) and `connectTimeoutMs` (per attempt)
|
|
28
|
+
- **Threshold Submissions** — `stamp()` requires N-of-M successful submissions (default 2-of-4); configurable
|
|
29
|
+
- **Fail-Fast on 4xx** — Client errors are never retried; only 5xx and network failures trigger retries
|
|
30
|
+
|
|
31
|
+
### Developer Experience
|
|
32
|
+
- **TypeScript-first** — Strict types throughout; full IntelliSense for every option and error
|
|
33
|
+
- **Multi-runtime** — Node.js 18+, browsers, and edge runtimes (uses the standard `fetch` API)
|
|
34
|
+
- **Tree-shakeable** — Dual ESM/CJS build, zero runtime dependencies
|
|
35
|
+
- **`AbortController` support** — Cancel any in-flight operation at any level
|
|
36
|
+
- **Observable** — Drop-in `Logger` interface compatible with `console`, `pino`, `winston`, etc.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install @otskit/client
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`@otskit/core` is a peer dependency bundled as a `file:` reference in monorepo setups; no separate install is needed.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { OpenTimestampsClient } from '@otskit/client'
|
|
54
|
+
import { createHash } from 'crypto'
|
|
55
|
+
import { readFileSync, writeFileSync } from 'fs'
|
|
56
|
+
|
|
57
|
+
const client = new OpenTimestampsClient()
|
|
58
|
+
|
|
59
|
+
// 1. Hash the file you want to timestamp
|
|
60
|
+
const fileBytes = readFileSync('contract.pdf')
|
|
61
|
+
const hash = createHash('sha256').update(fileBytes).digest()
|
|
62
|
+
|
|
63
|
+
// 2. Submit to calendars → get a pending .ots proof
|
|
64
|
+
const pendingProof = await client.stamp(hash)
|
|
65
|
+
writeFileSync('contract.pdf.ots', pendingProof)
|
|
66
|
+
console.log('Proof saved — Bitcoin confirmation usually arrives in 10–60 minutes.')
|
|
67
|
+
|
|
68
|
+
// 3. Later: query calendars for a Bitcoin confirmation
|
|
69
|
+
const upgradedProof = await client.upgrade(pendingProof)
|
|
70
|
+
writeFileSync('contract.pdf.ots', upgradedProof)
|
|
71
|
+
|
|
72
|
+
// 4. Verify the completed proof
|
|
73
|
+
const result = await client.verify(upgradedProof, hash)
|
|
74
|
+
if (result.valid) {
|
|
75
|
+
console.log(`Timestamp confirmed in Bitcoin block ${result.blockHeight}`)
|
|
76
|
+
console.log(`Block time: ${new Date(result.timestamp! * 1000).toISOString()}`)
|
|
77
|
+
} else {
|
|
78
|
+
console.error(`Verification failed: ${result.error}`)
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Usage
|
|
85
|
+
|
|
86
|
+
### Stamping data
|
|
87
|
+
|
|
88
|
+
`stamp()` accepts either a 32-byte `Buffer` or a 64-character hex string:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { createHash } from 'crypto'
|
|
92
|
+
|
|
93
|
+
// From a Buffer
|
|
94
|
+
const hashBuffer = createHash('sha256').update(fileBytes).digest()
|
|
95
|
+
const proof = await client.stamp(hashBuffer)
|
|
96
|
+
|
|
97
|
+
// From a hex string
|
|
98
|
+
const hashHex = createHash('sha256').update(fileBytes).digest('hex')
|
|
99
|
+
const proof = await client.stamp(hashHex)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Internally, `stamp()` prepends a 16-byte cryptographic nonce to each submission, builds a Merkle tree over all concurrent submissions, and serializes the result as a standard `.ots` file.
|
|
103
|
+
|
|
104
|
+
### Upgrading a pending proof
|
|
105
|
+
|
|
106
|
+
Call `upgrade()` periodically until Bitcoin confirms the timestamp. It queries only the calendars embedded in the proof (validated against a whitelist), so your `calendars` option does not affect this step.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { UpgradeError } from '@otskit/client'
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const upgradedProof = await client.upgrade(pendingProof)
|
|
113
|
+
// Save and stop polling
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (err instanceof UpgradeError) {
|
|
116
|
+
// No calendar has a Bitcoin confirmation yet — try again later
|
|
117
|
+
console.log('Not confirmed yet, retry in 5 minutes')
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Verifying a proof
|
|
123
|
+
|
|
124
|
+
`verify()` queries the Blockstream Esplora API to check the Bitcoin merkle root. Passing `originalDataHash` adds an extra integrity check that the proof was created for that specific hash.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const result = await client.verify(proof, originalHash)
|
|
128
|
+
|
|
129
|
+
if (result.valid) {
|
|
130
|
+
console.log(result.blockHeight) // Bitcoin block number
|
|
131
|
+
console.log(result.blockHash) // Block hash (hex)
|
|
132
|
+
console.log(result.timestamp) // Unix timestamp of the block
|
|
133
|
+
} else {
|
|
134
|
+
console.log(result.error) // Human-readable reason
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`verify()` always returns `VerificationResult` — it never throws for invalid proofs, only for unexpected network failures.
|
|
139
|
+
|
|
140
|
+
### Error handling
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import {
|
|
144
|
+
StampError,
|
|
145
|
+
UpgradeError,
|
|
146
|
+
ValidationError,
|
|
147
|
+
NetworkError,
|
|
148
|
+
CircuitBreakerError,
|
|
149
|
+
} from '@otskit/client'
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
await client.stamp(hash)
|
|
153
|
+
} catch (err) {
|
|
154
|
+
if (err instanceof ValidationError) {
|
|
155
|
+
// Invalid hash format
|
|
156
|
+
} else if (err instanceof StampError) {
|
|
157
|
+
// Not enough calendars accepted the submission
|
|
158
|
+
console.log(`Succeeded: ${err.successfulSubmissions.map(s => s.calendar)}`)
|
|
159
|
+
console.log(`Failed: ${err.failedSubmissions.map(s => s.calendar)}`)
|
|
160
|
+
} else if (err instanceof CircuitBreakerError) {
|
|
161
|
+
// A calendar is isolated due to repeated failures
|
|
162
|
+
} else if (err instanceof NetworkError) {
|
|
163
|
+
console.log(`HTTP status: ${err.status}`) // undefined for non-HTTP errors
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Cancellation with AbortController
|
|
169
|
+
|
|
170
|
+
You can cancel individual operations or set a client-wide signal:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
// Per-operation cancellation
|
|
174
|
+
const controller = new AbortController()
|
|
175
|
+
setTimeout(() => controller.abort(), 10_000)
|
|
176
|
+
|
|
177
|
+
const proof = await client.stamp(hash, { signal: controller.signal })
|
|
178
|
+
|
|
179
|
+
// Client-wide cancellation (applies to all operations)
|
|
180
|
+
const clientController = new AbortController()
|
|
181
|
+
const client = new OpenTimestampsClient({ signal: clientController.signal })
|
|
182
|
+
|
|
183
|
+
clientController.abort() // cancels any in-flight request
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Observability with a logger
|
|
187
|
+
|
|
188
|
+
Any object with `debug`, `info`, `warn`, and `error` methods works:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import pino from 'pino'
|
|
192
|
+
|
|
193
|
+
const client = new OpenTimestampsClient({
|
|
194
|
+
logger: pino({ level: 'debug' }),
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Using `console` directly:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const client = new OpenTimestampsClient({ logger: console })
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Monitoring circuit breakers
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const state = client.getCircuitState('https://alice.btc.calendar.opentimestamps.org')
|
|
208
|
+
// 'CLOSED' | 'OPEN' | 'HALF_OPEN' | undefined
|
|
209
|
+
|
|
210
|
+
// Manually recover a calendar after a known incident
|
|
211
|
+
client.resetCircuit('https://alice.btc.calendar.opentimestamps.org')
|
|
212
|
+
|
|
213
|
+
// Reset all calendars at once
|
|
214
|
+
client.resetAllCircuits()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Configuration
|
|
220
|
+
|
|
221
|
+
### `ClientOptions`
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const client = new OpenTimestampsClient({
|
|
225
|
+
// Calendar servers to submit to (default: the four public OTS calendars)
|
|
226
|
+
calendars: [
|
|
227
|
+
'https://alice.btc.calendar.opentimestamps.org',
|
|
228
|
+
'https://bob.btc.calendar.opentimestamps.org',
|
|
229
|
+
'https://finney.calendar.eternitywall.com',
|
|
230
|
+
'https://btc.calendar.catallaxy.com',
|
|
231
|
+
],
|
|
232
|
+
|
|
233
|
+
// How many calendars must succeed for stamp() to resolve (default: 2)
|
|
234
|
+
minimumSuccessfulSubmissions: 2,
|
|
235
|
+
|
|
236
|
+
// Resilience configuration (see below)
|
|
237
|
+
resilience: { ... },
|
|
238
|
+
|
|
239
|
+
// Logger implementing { debug, info, warn, error }
|
|
240
|
+
logger: console,
|
|
241
|
+
|
|
242
|
+
// AbortSignal applied to all operations on this client
|
|
243
|
+
signal: controller.signal,
|
|
244
|
+
})
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### `ResilienceOptions`
|
|
248
|
+
|
|
249
|
+
All fields are optional — unspecified fields fall back to the defaults shown.
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
resilience: {
|
|
253
|
+
// Maximum total time for a single operation across all retries (ms)
|
|
254
|
+
totalTimeoutMs: 30_000, // default
|
|
255
|
+
|
|
256
|
+
// Maximum time for a single HTTP attempt (ms)
|
|
257
|
+
connectTimeoutMs: 5_000, // default
|
|
258
|
+
|
|
259
|
+
retries: {
|
|
260
|
+
enabled: true, // default
|
|
261
|
+
maxAttempts: 3, // default
|
|
262
|
+
|
|
263
|
+
backoff: {
|
|
264
|
+
strategy: 'exponential', // 'exponential' | 'linear' | 'constant'
|
|
265
|
+
initialDelayMs: 200, // default
|
|
266
|
+
maxDelayMs: 5_000, // default; caps the computed delay
|
|
267
|
+
jitter: 'full', // 'full' | 'equal' | 'none'
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
circuitBreaker: {
|
|
272
|
+
enabled: true, // default
|
|
273
|
+
failureThreshold: 5, // consecutive failures before OPEN (default)
|
|
274
|
+
recoveryTimeoutMs: 15_000,// time in OPEN before trying HALF_OPEN (default)
|
|
275
|
+
halfOpenMaxAttempts: 1, // probing requests in HALF_OPEN state (default)
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Backoff strategies:**
|
|
281
|
+
|
|
282
|
+
| Strategy | Delay formula |
|
|
283
|
+
|---|---|
|
|
284
|
+
| `exponential` | `initialDelayMs × 2^(attempt - 1)` |
|
|
285
|
+
| `linear` | `initialDelayMs × attempt` |
|
|
286
|
+
| `constant` | `initialDelayMs` |
|
|
287
|
+
|
|
288
|
+
**Jitter modes:**
|
|
289
|
+
|
|
290
|
+
| Mode | Effect |
|
|
291
|
+
|---|---|
|
|
292
|
+
| `full` | Random value in `[0, delay]` — best for thundering-herd prevention |
|
|
293
|
+
| `equal` | Random value in `[delay/2, delay]` |
|
|
294
|
+
| `none` | Deterministic delay |
|
|
295
|
+
|
|
296
|
+
**Circuit breaker states:**
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
CLOSED ──(failureThreshold consecutive failures)──► OPEN
|
|
300
|
+
OPEN ──(recoveryTimeoutMs elapsed) ──► HALF_OPEN
|
|
301
|
+
HALF_OPEN ──(success) ──► CLOSED
|
|
302
|
+
HALF_OPEN ──(failure) ──► OPEN
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## API Reference
|
|
308
|
+
|
|
309
|
+
### `OpenTimestampsClient`
|
|
310
|
+
|
|
311
|
+
#### Constructor
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
new OpenTimestampsClient(options?: ClientOptions)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
#### `stamp(hash, options?): Promise<Buffer>`
|
|
318
|
+
|
|
319
|
+
Submits the hash to configured calendars and returns a serialized `.ots` proof.
|
|
320
|
+
|
|
321
|
+
| Parameter | Type | Description |
|
|
322
|
+
|---|---|---|
|
|
323
|
+
| `hash` | `Buffer \| string` | SHA-256 hash (32-byte Buffer or 64-char hex string) |
|
|
324
|
+
| `options.signal` | `AbortSignal` | Override the client-level signal for this call |
|
|
325
|
+
|
|
326
|
+
Throws `ValidationError` if the hash format is invalid.
|
|
327
|
+
Throws `StampError` if fewer than `minimumSuccessfulSubmissions` calendars accepted.
|
|
328
|
+
|
|
329
|
+
#### `upgrade(proof, options?): Promise<Buffer>`
|
|
330
|
+
|
|
331
|
+
Queries the calendars referenced in the proof for Bitcoin confirmations. Returns the updated proof if at least one calendar confirmed; otherwise throws `UpgradeError`.
|
|
332
|
+
|
|
333
|
+
| Parameter | Type | Description |
|
|
334
|
+
|---|---|---|
|
|
335
|
+
| `proof` | `Buffer` | Serialized `.ots` proof as returned by `stamp()` |
|
|
336
|
+
| `options.signal` | `AbortSignal` | Override the client-level signal for this call |
|
|
337
|
+
|
|
338
|
+
Throws `ValidationError` if the proof is malformed.
|
|
339
|
+
Throws `UpgradeError` if no calendar has confirmed the timestamp yet.
|
|
340
|
+
|
|
341
|
+
#### `verify(proof, originalDataHash?): Promise<VerificationResult>`
|
|
342
|
+
|
|
343
|
+
Verifies a completed proof against the Bitcoin blockchain via Esplora. Never throws for invalid or incomplete proofs — failures are returned as `{ valid: false, error: '...' }`.
|
|
344
|
+
|
|
345
|
+
| Parameter | Type | Description |
|
|
346
|
+
|---|---|---|
|
|
347
|
+
| `proof` | `Buffer` | Completed `.ots` proof with a Bitcoin attestation |
|
|
348
|
+
| `originalDataHash` | `Buffer \| string \| undefined` | If provided, also checks that the proof was created for this hash |
|
|
349
|
+
|
|
350
|
+
Returns `VerificationResult`:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
{
|
|
354
|
+
valid: boolean
|
|
355
|
+
blockHeight?: number // Bitcoin block number
|
|
356
|
+
blockHash?: string // Block hash (hex)
|
|
357
|
+
timestamp?: number // Unix epoch of the block
|
|
358
|
+
error?: string // Set when valid is false
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### `getCircuitState(calendarUrl): CircuitState | undefined`
|
|
363
|
+
|
|
364
|
+
Returns the current state of the circuit breaker for a calendar URL (`'CLOSED'`, `'OPEN'`, `'HALF_OPEN'`, or `undefined` if not yet initialized).
|
|
365
|
+
|
|
366
|
+
#### `resetCircuit(calendarUrl): void`
|
|
367
|
+
|
|
368
|
+
Manually resets the circuit breaker for a calendar. Use this after a known outage is resolved.
|
|
369
|
+
|
|
370
|
+
#### `resetAllCircuits(): void`
|
|
371
|
+
|
|
372
|
+
Resets all circuit breakers across all calendars.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
### Errors
|
|
377
|
+
|
|
378
|
+
All errors extend `OpenTimestampsClientError extends Error`.
|
|
379
|
+
|
|
380
|
+
| Class | When |
|
|
381
|
+
|---|---|
|
|
382
|
+
| `ValidationError` | Invalid input (bad hash format, malformed proof, invalid URL) |
|
|
383
|
+
| `StampError` | `stamp()` did not reach `minimumSuccessfulSubmissions`. Has `.successfulSubmissions` and `.failedSubmissions` arrays |
|
|
384
|
+
| `UpgradeError` | No calendar confirmed the timestamp yet |
|
|
385
|
+
| `NetworkError` | Network failure (timeout, all retries exhausted). Has `.status?: number` |
|
|
386
|
+
| `CircuitBreakerError extends NetworkError` | Request rejected because the circuit is OPEN |
|
|
387
|
+
| `CommitmentNotFoundError extends NetworkError` | Calendar returned 404 for a commitment |
|
|
388
|
+
| `CalendarResponseTooLargeError extends NetworkError` | Calendar response exceeded the 10 KB size limit |
|
|
389
|
+
| `EsploraResponseError extends NetworkError` | Esplora returned an invalid, malformed, or oversized response |
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
### Advanced exports
|
|
394
|
+
|
|
395
|
+
These are available for custom integrations and advanced use cases.
|
|
396
|
+
|
|
397
|
+
#### `CalendarClient`
|
|
398
|
+
|
|
399
|
+
Low-level client for a single OTS calendar server.
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
import { CalendarClient, ResilientNetworkLayer, DEFAULT_RESILIENCE } from '@otskit/client'
|
|
403
|
+
|
|
404
|
+
const network = new ResilientNetworkLayer(DEFAULT_RESILIENCE)
|
|
405
|
+
const calendar = new CalendarClient('https://alice.btc.calendar.opentimestamps.org', network)
|
|
406
|
+
|
|
407
|
+
const timestamp = await calendar.submit(digest) // POST /digest
|
|
408
|
+
const upgraded = await calendar.getTimestamp(digest) // GET /timestamp/:hex
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### `EsploraClient`
|
|
412
|
+
|
|
413
|
+
Client for querying a Bitcoin block explorer compatible with the [Esplora API](https://github.com/Blockstream/esplora/blob/master/API.md).
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
import { EsploraClient, ResilientNetworkLayer, DEFAULT_RESILIENCE, PUBLIC_ESPLORA_URL } from '@otskit/client'
|
|
417
|
+
|
|
418
|
+
const network = new ResilientNetworkLayer(DEFAULT_RESILIENCE)
|
|
419
|
+
const esplora = new EsploraClient(network, { url: PUBLIC_ESPLORA_URL })
|
|
420
|
+
|
|
421
|
+
const blockHash = await esplora.blockHash(850_000) // → hex string
|
|
422
|
+
const blockHeader = await esplora.block(blockHash) // → { merkleroot, time }
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### `verifyTimestampAttestation`
|
|
426
|
+
|
|
427
|
+
Verifies a single `Attestation` (Bitcoin or Litecoin) against a block explorer.
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import { verifyTimestampAttestation } from '@otskit/client'
|
|
431
|
+
|
|
432
|
+
const blockTime = await verifyTimestampAttestation(digest, attestation, esploraClient)
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### `UrlWhitelist`
|
|
436
|
+
|
|
437
|
+
Wildcard URL allowlist used internally to validate calendar URLs in upgrade proofs.
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
import { UrlWhitelist } from '@otskit/client'
|
|
441
|
+
|
|
442
|
+
const wl = new UrlWhitelist([
|
|
443
|
+
'https://*.calendar.opentimestamps.org',
|
|
444
|
+
'https://my-calendar.example.com',
|
|
445
|
+
])
|
|
446
|
+
|
|
447
|
+
wl.contains('https://alice.btc.calendar.opentimestamps.org') // true
|
|
448
|
+
wl.contains('https://evil.example.com') // false
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
#### `ResilientNetworkLayer`
|
|
452
|
+
|
|
453
|
+
The full timeout + retry + circuit-breaker stack as a standalone class.
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { ResilientNetworkLayer, DEFAULT_RESILIENCE } from '@otskit/client'
|
|
457
|
+
|
|
458
|
+
const network = new ResilientNetworkLayer(DEFAULT_RESILIENCE, logger)
|
|
459
|
+
const response = await network.request(calendarUrl, {
|
|
460
|
+
url: 'https://...',
|
|
461
|
+
method: 'POST',
|
|
462
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
463
|
+
body: new Uint8Array([...]),
|
|
464
|
+
})
|
|
465
|
+
// response.data: Uint8Array, response.ok: boolean, response.status: number
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
#### Constants
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import {
|
|
472
|
+
DEFAULT_CALENDARS, // string[] — the four public OTS calendars
|
|
473
|
+
DEFAULT_RESILIENCE, // ResilienceOptions — default timeout/retry/cb config
|
|
474
|
+
DEFAULT_CALENDAR_WHITELIST, // UrlWhitelist — trusted calendar domains for upgrade
|
|
475
|
+
DEFAULT_AGGREGATORS, // string[] — OTS aggregator pool URLs
|
|
476
|
+
PUBLIC_ESPLORA_URL, // 'https://blockstream.info/api'
|
|
477
|
+
MAX_CALENDAR_RESPONSE_SIZE, // 10_000 (bytes)
|
|
478
|
+
MAX_ESPLORA_RESPONSE_SIZE, // 100_000 (bytes)
|
|
479
|
+
} from '@otskit/client'
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## Contributing
|
|
485
|
+
|
|
486
|
+
Contributions are welcome. Please open an issue before starting significant work so we can align on approach.
|
|
487
|
+
|
|
488
|
+
### Setup
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
git clone https://github.com/OTSkit/OTSkit-client.git
|
|
492
|
+
cd OTSkit-client
|
|
493
|
+
npm install
|
|
494
|
+
npm test # 160 unit + integration tests
|
|
495
|
+
npm run lint # ESLint
|
|
496
|
+
npm run build # tsup → dist/
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Testing
|
|
500
|
+
|
|
501
|
+
The test suite uses [Vitest](https://vitest.dev), [MSW](https://mswjs.io) for HTTP mocking, and [fast-check](https://fast-check.dev) for property-based testing. All tests run in Node.js (no browser required).
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
npm test # run all tests once
|
|
505
|
+
npm run test:watch # watch mode
|
|
506
|
+
npm test -- --coverage # with coverage report (100% threshold enforced)
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Commit convention
|
|
510
|
+
|
|
511
|
+
This repository uses [Conventional Commits](https://www.conventionalcommits.org). Releases are automated via [semantic-release](https://semantic-release.gitbook.io).
|
|
512
|
+
|
|
513
|
+
### Code style
|
|
514
|
+
|
|
515
|
+
- TypeScript strict mode
|
|
516
|
+
- ESLint + Prettier (run `npm run format` before pushing)
|
|
517
|
+
- Fail-closed: all external input is validated at the boundary
|
|
518
|
+
- No runtime dependencies
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## Links
|
|
523
|
+
|
|
524
|
+
- [OpenTimestamps Protocol](https://opentimestamps.org)
|
|
525
|
+
- [@otskit/core](https://github.com/OTSkit/OTSkit-core) — Protocol engine used by this SDK
|
|
526
|
+
- [npm Package](https://www.npmjs.com/package/@otskit/client)
|
|
527
|
+
- [Issue Tracker](https://github.com/OTSkit/OTSkit-client/issues)
|
|
528
|
+
|
|
529
|
+
## License
|
|
530
|
+
|
|
531
|
+
MIT
|