@majikah/sdk 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.
Files changed (71) hide show
  1. package/LICENSE +67 -0
  2. package/README.md +1112 -0
  3. package/dist/client/MajikahSDKClient.d.ts +308 -0
  4. package/dist/client/MajikahSDKClient.js +318 -0
  5. package/dist/errors/APIError.d.ts +7 -0
  6. package/dist/errors/APIError.js +14 -0
  7. package/dist/errors/AuthenticationError.d.ts +3 -0
  8. package/dist/errors/AuthenticationError.js +4 -0
  9. package/dist/errors/MajikahError.d.ts +4 -0
  10. package/dist/errors/MajikahError.js +9 -0
  11. package/dist/errors/QuotaExhaustedError.d.ts +3 -0
  12. package/dist/errors/QuotaExhaustedError.js +4 -0
  13. package/dist/errors/RateLimitError.d.ts +5 -0
  14. package/dist/errors/RateLimitError.js +10 -0
  15. package/dist/errors/ServiceUnavailableError.d.ts +3 -0
  16. package/dist/errors/ServiceUnavailableError.js +4 -0
  17. package/dist/errors/ValidationError.d.ts +5 -0
  18. package/dist/errors/ValidationError.js +10 -0
  19. package/dist/errors/index.d.ts +8 -0
  20. package/dist/errors/index.js +8 -0
  21. package/dist/errors/mapError.d.ts +3 -0
  22. package/dist/errors/mapError.js +22 -0
  23. package/dist/index.d.ts +10 -0
  24. package/dist/index.js +10 -0
  25. package/dist/services/index.d.ts +4 -0
  26. package/dist/services/index.js +4 -0
  27. package/dist/services/muid/MUIDClient.d.ts +95 -0
  28. package/dist/services/muid/MUIDClient.js +138 -0
  29. package/dist/services/muid/key-resolver.d.ts +24 -0
  30. package/dist/services/muid/key-resolver.js +32 -0
  31. package/dist/services/notary/NotaryClient.d.ts +188 -0
  32. package/dist/services/notary/NotaryClient.js +291 -0
  33. package/dist/services/notary/validation.d.ts +7 -0
  34. package/dist/services/notary/validation.js +19 -0
  35. package/dist/services/shared/encoding.d.ts +21 -0
  36. package/dist/services/shared/encoding.js +43 -0
  37. package/dist/services/shared/resolve-signature.d.ts +23 -0
  38. package/dist/services/shared/resolve-signature.js +31 -0
  39. package/dist/services/shared/sleep.d.ts +1 -0
  40. package/dist/services/shared/sleep.js +3 -0
  41. package/dist/services/shared/validation.d.ts +9 -0
  42. package/dist/services/shared/validation.js +16 -0
  43. package/dist/services/slink/SLinkClient.d.ts +174 -0
  44. package/dist/services/slink/SLinkClient.js +231 -0
  45. package/dist/services/slink/validation.d.ts +24 -0
  46. package/dist/services/slink/validation.js +31 -0
  47. package/dist/services/tsa/TSAClient.d.ts +101 -0
  48. package/dist/services/tsa/TSAClient.js +178 -0
  49. package/dist/services/tsa/validation.d.ts +2 -0
  50. package/dist/services/tsa/validation.js +12 -0
  51. package/dist/transport/HttpClient.d.ts +85 -0
  52. package/dist/transport/HttpClient.js +135 -0
  53. package/dist/transport/RouteResolver.d.ts +54 -0
  54. package/dist/transport/RouteResolver.js +67 -0
  55. package/dist/transport/retry-after.d.ts +17 -0
  56. package/dist/transport/retry-after.js +39 -0
  57. package/dist/transport/retry.d.ts +8 -0
  58. package/dist/transport/retry.js +61 -0
  59. package/dist/types/common.d.ts +133 -0
  60. package/dist/types/common.js +42 -0
  61. package/dist/types/index.d.ts +4 -0
  62. package/dist/types/index.js +1 -0
  63. package/dist/types/muid.d.ts +80 -0
  64. package/dist/types/muid.js +1 -0
  65. package/dist/types/notary.d.ts +243 -0
  66. package/dist/types/notary.js +1 -0
  67. package/dist/types/slink.d.ts +60 -0
  68. package/dist/types/slink.js +1 -0
  69. package/dist/types/tsa.d.ts +144 -0
  70. package/dist/types/tsa.js +1 -0
  71. package/package.json +67 -0
package/README.md ADDED
@@ -0,0 +1,1112 @@
1
+ # Majikah SDK
2
+
3
+ [![Developed by Majikah Solutions OPC](https://img.shields.io/badge/Developed%20by-Majikah%20Solutions%20OPC-red?logo=github\&logoColor=white)](https://majikah.solutions)
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@majikah/sdk)](https://www.npmjs.com/package/@majikah/sdk) [![npm downloads](https://img.shields.io/npm/dm/@majikah/sdk)](https://www.npmjs.com/package/@majikah/sdk)
6
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)](https://www.typescriptlang.org/) [![IANA Media Type](https://img.shields.io/badge/IANA-vnd.majikah.mjksig-green)](https://www.iana.org/assignments/media-types/application/vnd.majikah.mjksig)
7
+
8
+
9
+ The official TypeScript SDK for the **Majikah ecosystem**.
10
+
11
+ `@majikah/sdk` provides a unified developer interface for Majikah's public services:
12
+
13
+ * **Majik Universal ID (MUID)** — public identity lookup and signature verification
14
+ * **Time Stamping Authority (TSA)** — trusted timestamps for digital signatures
15
+ * **Signed Links (SLink)** — signed claims associated with URLs and content hashes
16
+ * **File Notarization** — payment-aware on-chain anchoring of sealed files
17
+
18
+ The SDK is designed to work with Majikah's local cryptographic libraries, including `@majikah/majik-key`, `@majikah/majik-signature`, and `@majikah/majik-slink`.
19
+
20
+ Cryptographic operations that require private keys are performed locally. Private signing keys are not uploaded to the Majikah API by the SDK.
21
+
22
+ ---
23
+
24
+ ## Why the Majikah SDK?
25
+
26
+ The SDK combines Majikah's public services with local cryptographic operations so applications can build complete signing, verification, timestamping, SLink, and notarization workflows without implementing the API protocol themselves.
27
+
28
+ For example, a single application can:
29
+
30
+ ```text
31
+ Create or receive a signed file
32
+
33
+ ├── Verify the signature against a MUID
34
+
35
+ ├── Request a trusted timestamp
36
+
37
+ ├── Publish or verify a signed URL claim
38
+
39
+ └── Seal and notarize the file on-chain
40
+ ```
41
+
42
+ The SDK also keeps service-specific functionality separated:
43
+
44
+ ```ts
45
+ majikah.muid
46
+ majikah.tsa
47
+ majikah.slink
48
+ majikah.notary
49
+ ```
50
+
51
+ This allows you to use only the part of the ecosystem your application actually needs.
52
+
53
+ ---
54
+
55
+ ## Features
56
+
57
+ ### MUID
58
+
59
+ Verify signatures against public Majik Universal ID identities.
60
+
61
+ * Look up public MUID profiles
62
+ * Retrieve the authenticated MUID
63
+ * Verify existing embedded signatures
64
+ * Verify detached signature envelopes
65
+ * Select a specific signer in multi-signature files
66
+ * Verify against a specific MUID by ID or username
67
+
68
+ ### TSA
69
+
70
+ Request trusted timestamps for digital signatures.
71
+
72
+ * Issue a TSA timestamp directly from a signature
73
+ * Timestamp an already-signed embedded file
74
+ * Timestamp a detached signature envelope
75
+ * Sign and timestamp a file in one operation
76
+ * Check TSA quota
77
+ * Automatically reuse the signature's TSA request payload
78
+
79
+ ### SLink
80
+
81
+ Create, register, search, and cryptographically verify signed link claims.
82
+
83
+ * Register existing SLinks
84
+ * Create, sign, and register an SLink in one operation
85
+ * Search claims by URL
86
+ * Search claims by content hash
87
+ * Retrieve SLinks by ID
88
+ * Delete SLinks
89
+ * Verify SLink signatures locally
90
+ * Resolve public signing keys through MUID or a custom key registry
91
+ * Cursor-based pagination for owned SLinks
92
+
93
+ ### File Notarization
94
+
95
+ Anchor sealed file hashes on-chain through a payment-aware workflow.
96
+
97
+ * Check or initiate payment for a sealed file
98
+ * Return the sealed artifact while payment is pending
99
+ * Register a paid seal hash
100
+ * Monitor on-chain confirmation
101
+ * Poll until a terminal state
102
+ * Embed the resulting chain anchor back into the file
103
+ * Seal an already-signed file and begin notarization
104
+ * Run the complete single-signer `sign → seal → initiate` workflow
105
+
106
+ ---
107
+
108
+ ## Installation
109
+
110
+ Install the SDK with npm:
111
+
112
+ ```bash
113
+ npm install @majikah/sdk
114
+ ```
115
+
116
+ The SDK is designed to work with the Majikah cryptographic packages used by the specific workflows in your application.
117
+
118
+ For example:
119
+
120
+ ```bash
121
+ npm install @majikah/sdk @majikah/majik-key @majikah/majik-signature @majikah/majik-slink
122
+ ```
123
+
124
+ Use the packages required by your application rather than installing the entire Majikah ecosystem unnecessarily.
125
+
126
+ ---
127
+
128
+ ## Get an API Key
129
+
130
+ To use the Majikah public API, you first need to create and activate your developer account.
131
+
132
+ 1. Go to [developers.majikah.solutions](https://developers.majikah.solutions/early-access).
133
+ 2. Complete the developer onboarding process:
134
+ - Create your Majikah account.
135
+ - Create your **Majik Universal ID (MUID)**.
136
+ - Verify your MUID by completing the required **KYC verification**.
137
+ - Submit the **Early Access application** through the developer portal.
138
+ 3. Wait for your application to be reviewed and approved by the Majikah team.
139
+ 4. Once approved, you will receive an **email confirmation** and gain access to API key generation in the developer portal.
140
+ 5. Generate your API key and keep it secure. You will use this key when initializing `MajikahSDKClient`.
141
+
142
+ > **Early Access:** Public API access is currently subject to developer approval. An API key cannot be generated until your Early Access application has been approved.
143
+
144
+
145
+ ---
146
+
147
+ ## Requirements
148
+
149
+ The SDK expects a modern JavaScript runtime with:
150
+
151
+ * `fetch`
152
+ * `URL`
153
+ * `AbortController`
154
+ * `atob` / `btoa`
155
+
156
+ These APIs are available natively in modern browsers and current Node.js, Deno, Bun, and edge runtimes.
157
+
158
+ A custom `fetch` implementation can also be supplied through the client options when integrating with a specialized runtime or transport layer.
159
+
160
+ ---
161
+
162
+ ## Authentication
163
+
164
+ Create a `MajikahSDKClient` with your public API key:
165
+
166
+ ```ts
167
+ import { MajikahSDKClient } from "@majikah/sdk";
168
+
169
+ const majikah = new MajikahSDKClient({
170
+ apiKey: process.env.MAJIKAH_API_KEY!,
171
+ });
172
+ ```
173
+
174
+ The SDK automatically sends the API key using the expected authentication header.
175
+
176
+ Do not hard-code API keys into browser source code or commit them to your repository.
177
+
178
+ For server-side applications, use environment variables or another secure secret-management mechanism.
179
+
180
+ ---
181
+
182
+ ## Client Configuration
183
+
184
+ The client supports a number of optional transport and API settings.
185
+
186
+ ```ts
187
+ const majikah = new MajikahSDKClient({
188
+ apiKey: process.env.MAJIKAH_API_KEY!,
189
+
190
+ baseUrl: "https://api-public.majikah.solutions",
191
+
192
+ version: 1,
193
+
194
+ timeoutMs: 15_000,
195
+
196
+ headers: {
197
+ "X-Custom-Header": "example",
198
+ },
199
+
200
+ retry: {
201
+ maxAttempts: 3,
202
+ initialDelayMs: 300,
203
+ jitterFactor: 0.4,
204
+ capDelayMs: 5_000,
205
+ },
206
+ });
207
+ ```
208
+
209
+ ### Configuration options
210
+
211
+ | Option | Description | Default |
212
+ | ----------- | ------------------------------------------- | -------------------------------------- |
213
+ | `apiKey` | API key used to authenticate requests | Required |
214
+ | `baseUrl` | Public Majikah API base URL | `https://api-public.majikah.solutions` |
215
+ | `version` | Global API version or per-service versions | `1` |
216
+ | `timeoutMs` | Request timeout in milliseconds | `15000` |
217
+ | `headers` | Additional request headers | `{}` |
218
+ | `fetch` | Custom `fetch` implementation | Runtime `fetch` |
219
+ | `retry` | Retry configuration for retry-safe requests | See below |
220
+
221
+ ### Per-service API versions
222
+
223
+ A single version can be applied to every service:
224
+
225
+ ```ts
226
+ const majikah = new MajikahSDKClient({
227
+ apiKey,
228
+ version: 1,
229
+ });
230
+ ```
231
+
232
+ Individual services can also be versioned independently:
233
+
234
+ ```ts
235
+ const majikah = new MajikahSDKClient({
236
+ apiKey,
237
+ version: {
238
+ tsa: 1,
239
+ notary: 1,
240
+ slink: 1,
241
+ muid: 2,
242
+ },
243
+ });
244
+ ```
245
+
246
+ Services not explicitly overridden inherit the fallback version.
247
+
248
+ ---
249
+
250
+ ## Retry behavior
251
+
252
+ The SDK automatically retries retry-safe requests using exponential backoff with jitter.
253
+
254
+ `GET` requests are retryable by default.
255
+
256
+ `POST` requests are only retried when explicitly marked as idempotent internally.
257
+
258
+ This distinction prevents the SDK from accidentally repeating operations that could create duplicate side effects.
259
+
260
+ Default retry settings:
261
+
262
+ ```ts
263
+ {
264
+ maxAttempts: 3,
265
+ initialDelayMs: 300,
266
+ jitterFactor: 0.4,
267
+ capDelayMs: 5_000,
268
+ }
269
+ ```
270
+
271
+ Rate-limit responses can also provide retry timing information that the SDK uses when mapping the API response.
272
+
273
+ ---
274
+
275
+ # Service Modules
276
+
277
+ ## 1. MUID — Identity and Signature Verification
278
+
279
+ MUID provides public identity information and lets applications verify digital signatures against a MUID.
280
+
281
+ ### Look up a MUID
282
+
283
+ ```ts
284
+ const profile = await majikah.muid.lookup("alice");
285
+
286
+ console.log(profile);
287
+ ```
288
+
289
+ The lookup accepts either a MUID identifier or username.
290
+
291
+ ### Get the current MUID
292
+
293
+ ```ts
294
+ const me = await majikah.muid.me();
295
+
296
+ console.log(me);
297
+ ```
298
+
299
+ ### Verify an existing signed file
300
+
301
+ ```ts
302
+ const result = await majikah.muid.verifyFile(signedFile, {
303
+ muid: "alice",
304
+ });
305
+
306
+ if (result.valid) {
307
+ console.log("Signature is valid.");
308
+ }
309
+ ```
310
+
311
+ When the file contains multiple signatures, identify which signer should be verified:
312
+
313
+ ```ts
314
+ const result = await majikah.muid.verifyFile(signedFile, {
315
+ expectedSignerId: signerFingerprint,
316
+ muid: "alice",
317
+ });
318
+ ```
319
+
320
+ When `muid` is omitted, verification uses the MUID associated with the caller's API credentials where supported by the API.
321
+
322
+ ### Verify a detached envelope
323
+
324
+ ```ts
325
+ const result = await majikah.muid.verifyFileDetached(envelope, {
326
+ muid: "alice",
327
+ });
328
+ ```
329
+
330
+ Detached verification operates on the signature envelope rather than an embedded signature inside the original file.
331
+
332
+ ---
333
+
334
+ ## 2. TSA — Trusted Timestamping
335
+
336
+ The TSA service provides trusted timestamps for Majik Signatures.
337
+
338
+ ### Timestamp an existing signature
339
+
340
+ For a file that has already been signed:
341
+
342
+ ```ts
343
+ const { blob, signature } = await majikah.tsa.timestampFile(
344
+ signedFile,
345
+ );
346
+ ```
347
+
348
+ No `MajikKey` is required for this operation because the method does not create a new signature.
349
+
350
+ For a multi-signature file:
351
+
352
+ ```ts
353
+ const { blob, signature } = await majikah.tsa.timestampFile(
354
+ signedFile,
355
+ {
356
+ expectedSignerId: signerFingerprint,
357
+ },
358
+ );
359
+ ```
360
+
361
+ ### Timestamp a detached envelope
362
+
363
+ ```ts
364
+ const { envelope, mjksig } =
365
+ await majikah.tsa.timestampDetached(envelope);
366
+ ```
367
+
368
+ The returned `mjksig` is the serialized detached signature envelope.
369
+
370
+ ### Sign and timestamp in one operation
371
+
372
+ ```ts
373
+ const result = await majikah.tsa.stampFile(
374
+ file,
375
+ myUnlockedMajikKey,
376
+ {
377
+ contentType: "application/pdf",
378
+ },
379
+ );
380
+
381
+ console.log(result.blob);
382
+ ```
383
+
384
+ This performs:
385
+
386
+ ```text
387
+ sign
388
+
389
+ request TSA timestamp
390
+
391
+ attach TSA
392
+
393
+ embed signature
394
+ ```
395
+
396
+ ### Sign and return a detached timestamped envelope
397
+
398
+ ```ts
399
+ const result = await majikah.tsa.stampFileDetached(
400
+ file,
401
+ myUnlockedMajikKey,
402
+ {
403
+ contentType: "application/pdf",
404
+ },
405
+ );
406
+
407
+ const { envelope, mjksig } = result;
408
+ ```
409
+
410
+ This is useful when the signature needs to be distributed separately from the original file.
411
+
412
+ ### TSA quota
413
+
414
+ ```ts
415
+ const quota = await majikah.tsa.quota();
416
+
417
+ console.log(quota);
418
+ ```
419
+
420
+ ---
421
+
422
+ ## 3. SLink — Signed Link Claims
423
+
424
+ SLink associates cryptographically signed claims with URLs or content hashes.
425
+
426
+ A useful distinction is:
427
+
428
+ > **SLink lookup establishes that a claim exists. Cryptographic verification establishes that the claim's signature is valid.**
429
+
430
+ The API does not need to store the signer's public keys for local verification.
431
+
432
+ ### Register an existing SLink
433
+
434
+ ```ts
435
+ const stored = await majikah.slink.create(slink);
436
+ ```
437
+
438
+ A `MajikSLink` instance or serialized JSON can be supplied.
439
+
440
+ ### Create, sign, and register an SLink
441
+
442
+ ```ts
443
+ const stored = await majikah.slink.registerUrl(
444
+ "https://example.com",
445
+ aliceKey,
446
+ userId,
447
+ muid,
448
+ );
449
+ ```
450
+
451
+ A bare domain is also supported by the underlying SLink workflow:
452
+
453
+ ```ts
454
+ const stored = await majikah.slink.registerUrl(
455
+ "example.com",
456
+ aliceKey,
457
+ userId,
458
+ muid,
459
+ );
460
+ ```
461
+
462
+ ### Search by URL
463
+
464
+ ```ts
465
+ const results = await majikah.slink.verifyUrl(
466
+ "example.com",
467
+ );
468
+
469
+ console.log(results.matches);
470
+ ```
471
+
472
+ The SDK automatically normalizes a bare domain to HTTPS before sending the request.
473
+
474
+ ### Search by content hash
475
+
476
+ ```ts
477
+ const results = await majikah.slink.verifyByHash(hash);
478
+
479
+ console.log(results.matches);
480
+ ```
481
+
482
+ ### Local cryptographic verification
483
+
484
+ To verify every search result cryptographically, use `verifyUrlWithProof()`.
485
+
486
+ The simplest MUID-backed approach is:
487
+
488
+ ```ts
489
+ import { createMuidPublicKeyResolver } from "@majikah/sdk";
490
+
491
+ const results = await majikah.slink.verifyUrlWithProof(
492
+ "thezelijah.world",
493
+ createMuidPublicKeyResolver(majikah.muid),
494
+ );
495
+
496
+ const verified = results.filter(({ result }) => result.valid);
497
+ ```
498
+
499
+ `createMuidPublicKeyResolver()`:
500
+
501
+ 1. Resolves each MUID through `MUIDClient.lookup()`
502
+ 2. Reads the public signing keys
503
+ 3. Converts their Base64 representation to the SDK's binary key format
504
+ 4. Supplies the keys to the local signature verifier
505
+
506
+ ### Custom public key sources
507
+
508
+ Applications can supply their own trusted key registry:
509
+
510
+ ```ts
511
+ const results = await majikah.slink.verifyUrlWithProof(
512
+ "example.com",
513
+ async (muid, signerId) => {
514
+ const keys = await myKeyRegistry.get(muid);
515
+
516
+ return {
517
+ signerId,
518
+ edPublicKey: keys.edPublicKey,
519
+ mlDsaPublicKey: keys.mlDsaPublicKey,
520
+ };
521
+ },
522
+ );
523
+ ```
524
+
525
+ This makes SLink verification independent of a specific public-key storage backend.
526
+
527
+ ### Verify already-fetched matches
528
+
529
+ If you already called `verifyUrl()` or `verifyByHash()`, you can verify the returned matches without performing another lookup:
530
+
531
+ ```ts
532
+ const search = await majikah.slink.verifyUrl("example.com");
533
+
534
+ const verified = await majikah.slink.verifyMatches(
535
+ search.matches,
536
+ createMuidPublicKeyResolver(majikah.muid),
537
+ );
538
+ ```
539
+
540
+ ### List your SLinks
541
+
542
+ SLink ownership listings use cursor-based pagination:
543
+
544
+ ```ts
545
+ const page = await majikah.slink.me({
546
+ limit: 50,
547
+ });
548
+
549
+ console.log(page.items);
550
+ ```
551
+
552
+ Fetch the next page using the returned opaque cursor:
553
+
554
+ ```ts
555
+ if (page.has_more && page.next_cursor) {
556
+ const nextPage = await majikah.slink.me({
557
+ cursor: page.next_cursor,
558
+ limit: 50,
559
+ });
560
+ }
561
+ ```
562
+
563
+ The cursor should be treated as opaque data. Do not decode or modify it.
564
+
565
+ ---
566
+
567
+ # 4. Notary — File Notarization
568
+
569
+ Notary provides payment-aware on-chain anchoring for sealed files.
570
+
571
+ A notarization is based on the file's **seal hash**, rather than uploading the entire file to the notarization service.
572
+
573
+ The sealed artifact remains with the application. The Majikah notarization service uses the seal hash to associate payment and on-chain anchoring with that artifact.
574
+
575
+ Do not re-seal or otherwise modify the artifact before finalization, as the `sealHash` must continue to correspond to the sealed content.
576
+
577
+ The high-level lifecycle is:
578
+
579
+ ```mermaid
580
+ flowchart TD
581
+ A[Signed file] --> B[Create seal]
582
+ B --> C[sealedBlob + sealHash]
583
+
584
+ C --> D[Initiate notarization]
585
+
586
+ D --> E{Already anchored?}
587
+
588
+ E -->|Yes| F[Return notarized blob]
589
+ E -->|No| G[Create payment checkout]
590
+
591
+ G --> H[Return sealedBlob + sealHash + checkout]
592
+ H --> I[User completes payment]
593
+
594
+ I --> J[Register sealHash on-chain]
595
+ J --> K[Wait for chain confirmation]
596
+ K --> L[Embed chain anchor into sealed blob]
597
+ L --> M[Return notarized blob]
598
+ ```
599
+
600
+
601
+ ### Start notarization for an already-sealed file
602
+
603
+ ```ts
604
+ const result =
605
+ await majikah.notary.initiateNotarization(sealedFile);
606
+ ```
607
+
608
+ The result tells you what happens next.
609
+
610
+
611
+ ### Payment required
612
+
613
+ ```ts
614
+ const result =
615
+ await majikah.notary.initiateNotarization(sealedFile);
616
+
617
+ if (result.status === "payment_required") {
618
+ console.log(result.sealHash);
619
+ console.log(result.checkout.checkout_url);
620
+ console.log(result.sealedBlob);
621
+ }
622
+ ```
623
+
624
+ The checkout URL can be presented as a QR code or another payment interface.
625
+
626
+ The `sealedBlob` returned with the payment-required result is the exact sealed artifact associated with the returned `sealHash`. Retain both values while payment is being completed.
627
+
628
+ After payment is completed, finalize the notarization using the returned `sealedBlob` and `sealHash`:
629
+
630
+ ```ts
631
+ if (result.status === "payment_required") {
632
+ const finalized =
633
+ await majikah.notary.finalizeNotarization(
634
+ result.sealedBlob,
635
+ result.sealHash,
636
+ {
637
+ poll: {
638
+ intervalMs: 2000,
639
+ timeoutMs: 130_000,
640
+ },
641
+ },
642
+ );
643
+
644
+ console.log(finalized.anchor.id);
645
+ console.log(finalized.blob);
646
+ }
647
+ ```
648
+
649
+ The sealed file is not uploaded to the notarization service as part of this flow. The service uses the seal hash to associate the payment and on-chain notarization with the sealed artifact.
650
+
651
+
652
+
653
+ ### Already anchored
654
+
655
+ If the file was already notarized:
656
+
657
+ ```ts
658
+ if (result.status === "anchored") {
659
+ console.log("file is already notarized.");
660
+ console.log(result.anchor);
661
+ console.log(result.blob);
662
+ }
663
+ ```
664
+
665
+ The existing anchor is returned and the anchor information can be embedded into the file.
666
+
667
+ ### Already paid
668
+
669
+ If payment was previously completed but notarization has not yet been finalized:
670
+
671
+ ```ts
672
+ const result =
673
+ await majikah.notary.initiateNotarization(sealedFile);
674
+
675
+ if (result.status === "ready_to_finalize") {
676
+ const finalized =
677
+ await majikah.notary.finalizeNotarization(
678
+ result.sealedBlob,
679
+ result.sealHash,
680
+ );
681
+
682
+ console.log(finalized.anchor);
683
+ console.log(finalized.blob);
684
+ }
685
+ ```
686
+
687
+ The result contains both the `sealHash` and the exact `sealedBlob` associated with it, so the caller does not need to reconstruct or re-seal the document.
688
+
689
+
690
+ ### Register manually
691
+
692
+ For applications that want lower-level lifecycle control:
693
+
694
+ ```ts
695
+ const anchor = await majikah.notary.register(sealHash);
696
+ ```
697
+
698
+ Registration may initially return a pending anchor.
699
+
700
+ ### Poll for confirmation
701
+
702
+ ```ts
703
+ const anchor = await majikah.notary.pollUntilTerminal(
704
+ anchorId,
705
+ {
706
+ intervalMs: 2000,
707
+ timeoutMs: 130_000,
708
+ },
709
+ );
710
+
711
+ switch (anchor.status) {
712
+ case "confirmed":
713
+ console.log("Anchor confirmed.");
714
+ break;
715
+
716
+ case "finalized":
717
+ console.log("Anchor finalized.");
718
+ break;
719
+
720
+ case "failed":
721
+ console.error("Anchor failed.");
722
+ break;
723
+ }
724
+ ```
725
+
726
+ A terminal `failed` status is returned as a normal result. It is not automatically converted into an exception because the failure state itself contains useful application-level information.
727
+
728
+ ---
729
+
730
+ ## Complete single-signer flow
731
+
732
+ For a document that needs to be signed, sealed, paid, and notarized:
733
+
734
+ ```ts
735
+ const result =
736
+ await majikah.notary.signSealAndInitiateNotarization(
737
+ file,
738
+ issuerKey,
739
+ {
740
+ contentType: "application/pdf",
741
+ },
742
+ );
743
+
744
+ if (result.status === "payment_required") {
745
+ // Present the checkout to the user.
746
+ showQrCode(result.checkout.checkout_url);
747
+
748
+ // After payment has been completed:
749
+ const finalized =
750
+ await majikah.notary.finalizeNotarization(
751
+ result.sealedBlob,
752
+ result.sealHash,
753
+ );
754
+
755
+ console.log(finalized.anchor);
756
+ console.log(finalized.blob);
757
+ }
758
+
759
+ if (result.status === "ready_to_finalize") {
760
+ const finalized =
761
+ await majikah.notary.finalizeNotarization(
762
+ result.sealedBlob,
763
+ result.sealHash,
764
+ );
765
+
766
+ console.log(finalized.anchor);
767
+ console.log(finalized.blob);
768
+ }
769
+
770
+ if (result.status === "anchored") {
771
+ console.log(result.blob);
772
+ console.log(result.anchor);
773
+ }
774
+ ```
775
+
776
+ This demonstrates the complete discriminated-union workflow without requiring developers to understand the lower-level `payment()` and `register()` methods first.
777
+
778
+
779
+ For multi-signature files, complete the signing process first, then seal and initiate notarization:
780
+
781
+ ```ts
782
+ const result =
783
+ await majikah.notary.sealAndInitiateNotarization(
784
+ fullySignedFile,
785
+ issuerKey,
786
+ );
787
+ ```
788
+
789
+ This keeps the seal operation as the explicit transition from a mutable signing envelope to the notarization-ready file state.
790
+
791
+ ---
792
+
793
+ # Detached Signatures
794
+
795
+ Majik Signature supports detached signatures through the `.mjksig` format.
796
+
797
+ The SDK can operate directly on detached envelopes without requiring signatures to be embedded into the original file.
798
+
799
+ For example:
800
+
801
+ ```ts
802
+ const result =
803
+ await majikah.tsa.stampFileDetached(
804
+ file,
805
+ key,
806
+ );
807
+
808
+ await saveFile(
809
+ result.mjksig,
810
+ "file.mjksig",
811
+ );
812
+ ```
813
+
814
+ Detached envelopes are useful for:
815
+
816
+ * Out-of-band signature distribution
817
+ * Independent verification
818
+ * Signature storage separate from the original file
819
+ * Workflows where modifying the original file is undesirable
820
+
821
+ The `.mjksig` media type is registered with IANA as:
822
+
823
+ ```text
824
+ application/vnd.majikah.mjksig
825
+ ```
826
+
827
+ ---
828
+
829
+ # Local Cryptography and Key Handling
830
+
831
+ The SDK intentionally separates API operations from local cryptographic operations.
832
+
833
+ For workflows involving private keys:
834
+
835
+ ```text
836
+ Your application
837
+
838
+ ├── MajikKey
839
+ │ │
840
+ │ └── private key operations stay local
841
+
842
+ └── Majikah API
843
+
844
+ ├── identity lookup
845
+ ├── TSA
846
+ ├── SLink registry
847
+ └── notarization services
848
+ ```
849
+
850
+ A `MajikKey` is supplied to operations that require signing or sealing.
851
+
852
+ The SDK does not need to upload the private signing key to the API to perform those operations.
853
+
854
+ Applications should still follow normal key-management practices and protect unlocked keys within their own execution environment.
855
+
856
+ ---
857
+
858
+ # Verification Model
859
+
860
+ Majikah verification is intentionally layered.
861
+
862
+ For example, SLink verification consists of two different questions:
863
+
864
+ ### Does a claim exist?
865
+
866
+ ```ts
867
+ const result = await majikah.slink.verifyUrl(
868
+ "example.com",
869
+ );
870
+ ```
871
+
872
+ This performs a service-side lookup.
873
+
874
+ ### Is the signature cryptographically valid?
875
+
876
+ ```ts
877
+ const result =
878
+ await majikah.slink.verifyUrlWithProof(
879
+ "example.com",
880
+ createMuidPublicKeyResolver(majikah.muid),
881
+ );
882
+ ```
883
+
884
+ This performs local cryptographic verification using trusted public keys.
885
+
886
+ This distinction is important when building security-sensitive applications: **a registry match should not automatically be treated as cryptographic proof.**
887
+
888
+ ---
889
+
890
+ # File and Signature Compatibility
891
+
892
+ The SDK is built around the Majik Signature ecosystem and accepts the `FileLike` abstractions supported by `@majikah/majik-signature`.
893
+
894
+ Depending on the workflow, the SDK can operate on:
895
+
896
+ * Regular file or blob-like data
897
+ * Embedded Majik Signatures
898
+ * Detached `.mjksig` envelopes
899
+ * Sealed signature envelopes
900
+ * files that receive a notarization chain anchor
901
+
902
+ The exact file-handling capabilities are provided by the corresponding Majikah cryptographic libraries rather than duplicated inside this SDK.
903
+
904
+ ---
905
+
906
+ # Error Handling
907
+
908
+ The SDK maps API and transport failures into SDK-specific error types.
909
+
910
+ For example:
911
+
912
+ ```ts
913
+ try {
914
+ await majikah.tsa.issueForSignature(signature);
915
+ } catch (error) {
916
+ console.error(error);
917
+ }
918
+ ```
919
+
920
+ Applications should distinguish between:
921
+
922
+ * Validation errors caused by invalid local input
923
+ * API errors returned by the Majikah service
924
+ * Rate-limit responses
925
+ * Request timeouts
926
+ * Terminal workflow states such as a failed notarization
927
+
928
+ Not every negative application outcome is represented as an exception. For example, a notarization anchor with `status === "failed"` is a valid terminal result and can be handled through normal control flow.
929
+
930
+ ---
931
+
932
+ # TypeScript
933
+
934
+ The SDK is written in TypeScript and provides typed service methods, request options, response models, and workflow results.
935
+
936
+ For example:
937
+
938
+ ```ts
939
+ const page = await majikah.slink.me({
940
+ limit: 25,
941
+ });
942
+
943
+ page.items;
944
+ // MajikSLinkJSON[]
945
+
946
+ page.next_cursor;
947
+ // string | null
948
+ ```
949
+
950
+ The SDK's public types are designed to make service behavior discoverable directly through IDE autocomplete and TypeScript type information.
951
+
952
+ ---
953
+
954
+ # Runtime Support
955
+
956
+ The SDK is designed for modern full-stack and edge environments, including:
957
+
958
+ * Browser applications
959
+ * Node.js
960
+ * Deno
961
+ * Bun
962
+ * Cloudflare Workers
963
+ * Other runtimes implementing the standard web APIs used by the SDK
964
+
965
+ A custom `fetch` implementation may be supplied when required:
966
+
967
+ ```ts
968
+ const majikah = new MajikahSDKClient({
969
+ apiKey,
970
+ fetch: customFetch,
971
+ });
972
+ ```
973
+
974
+ This allows applications to integrate the SDK into environments with specialized HTTP transports or execution constraints.
975
+
976
+ ---
977
+
978
+ # API Design
979
+
980
+ The SDK deliberately separates lower-level service operations from higher-level convenience workflows.
981
+
982
+ For example:
983
+
984
+ ```ts
985
+ majikah.tsa.issue(...)
986
+ majikah.tsa.timestampFile(...)
987
+ majikah.tsa.stampFile(...)
988
+ ```
989
+
990
+ and:
991
+
992
+ ```ts
993
+ majikah.notary.payment(...)
994
+ majikah.notary.register(...)
995
+ majikah.notary.status(...)
996
+ majikah.notary.initiateNotarization(...)
997
+ majikah.notary.finalizeNotarization(...)
998
+ ```
999
+
1000
+ This allows developers to choose between:
1001
+
1002
+ * **Fine-grained control** for backend workflows and infrastructure integrations
1003
+ * **Convenience methods** for common application flows
1004
+
1005
+ The same principle applies to SLink verification and MUID identity operations.
1006
+
1007
+ ---
1008
+
1009
+ # Security Considerations
1010
+
1011
+ Applications integrating the SDK should:
1012
+
1013
+ * Keep API keys private on trusted server-side systems where appropriate
1014
+ * Never expose privileged API credentials in client-side source code
1015
+ * Protect unlocked `MajikKey` instances
1016
+ * Use trusted sources when resolving public signing keys
1017
+ * Treat API lookup results and cryptographic verification results as distinct security signals
1018
+ * Validate application-level authorization before allowing destructive operations
1019
+ * Handle rate limits and timeouts appropriately
1020
+ * Preserve detached signature and seal data exactly as generated
1021
+
1022
+ The SDK provides cryptographic primitives and service integration, but your application's authorization, key custody, access control, and trust policies remain the responsibility of the application.
1023
+
1024
+ ---
1025
+
1026
+ # Contributing
1027
+
1028
+ Contributions, bug reports, documentation improvements, and ecosystem integrations are welcome.
1029
+
1030
+ Before submitting a change:
1031
+
1032
+ 1. Keep the public API backward-compatible unless a breaking change is intentional.
1033
+ 2. Preserve strong TypeScript typing.
1034
+ 3. Add tests for both successful and failure paths.
1035
+ 4. Document new public APIs with JSDoc.
1036
+ 5. Avoid silently changing cryptographic behavior or serialization formats.
1037
+
1038
+ Please open an issue or pull request in the project repository for proposed changes.
1039
+
1040
+ ---
1041
+
1042
+ # License
1043
+
1044
+ Apache-2.0
1045
+
1046
+ See [LICENSE](LICENSE) for the full license text.
1047
+
1048
+ ---
1049
+
1050
+ # Maintainer
1051
+
1052
+ Developed by **Josef Elijah Fabian (Zelijah)** and **Majikah Solutions OPC**.
1053
+
1054
+ * Website: https://majikah.solutions
1055
+ * Developer: https://github.com/jedlsf
1056
+ * GitHub: https://github.com/Majikah
1057
+ * Organization: Majikah Solutions OPC
1058
+
1059
+ ---
1060
+
1061
+ # Links
1062
+
1063
+ * **Majikah:** https://majikah.solutions
1064
+ * **SDK package:** https://www.npmjs.com/package/@majikah/sdk
1065
+ * **GitHub organization:** https://github.com/Majikah
1066
+ * **Majik Signature:** https://github.com/Majikah/majik-signature
1067
+ * **Business contact:** [business@majikah.solutions](mailto:business@majikah.solutions)
1068
+
1069
+ ---
1070
+
1071
+ ## Quick Reference
1072
+
1073
+ ```ts
1074
+ import {
1075
+ MajikahSDKClient,
1076
+ createMuidPublicKeyResolver,
1077
+ } from "@majikah/sdk";
1078
+
1079
+ const majikah = new MajikahSDKClient({
1080
+ apiKey: process.env.MAJIKAH_API_KEY!,
1081
+ });
1082
+
1083
+ // MUID
1084
+ const profile = await majikah.muid.lookup("alice");
1085
+
1086
+ // TSA
1087
+ const stamped = await majikah.tsa.stampFile(
1088
+ file,
1089
+ signingKey,
1090
+ );
1091
+
1092
+ // SLink
1093
+ const slinks = await majikah.slink.verifyUrl(
1094
+ "example.com",
1095
+ );
1096
+
1097
+ // SLink + local cryptographic proof
1098
+ const verified = await majikah.slink.verifyUrlWithProof(
1099
+ "example.com",
1100
+ createMuidPublicKeyResolver(majikah.muid),
1101
+ );
1102
+
1103
+ // Notary
1104
+ const notarization =
1105
+ await majikah.notary.initiateNotarization(
1106
+ sealedFile,
1107
+ );
1108
+ ```
1109
+
1110
+ **Majikah SDK — connect your applications to identity, signatures, timestamps, signed claims, and file notarization.**
1111
+
1112
+