@morseai/sdk 0.1.0-beta.3

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/dist/index.mjs ADDED
@@ -0,0 +1,1606 @@
1
+ import _sodium from 'libsodium-wrappers';
2
+ import { ethers } from 'ethers';
3
+
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
+ }) : x)(function(x) {
9
+ if (typeof require !== "undefined") return require.apply(this, arguments);
10
+ throw Error('Dynamic require of "' + x + '" is not supported');
11
+ });
12
+ var __esm = (fn, res) => function __init() {
13
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
14
+ };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
19
+
20
+ // src/crypto-x25519.ts
21
+ var crypto_x25519_exports = {};
22
+ __export(crypto_x25519_exports, {
23
+ X25519_CIPHER_VERSION: () => X25519_CIPHER_VERSION,
24
+ createKeyCertificate: () => createKeyCertificate,
25
+ createSharedSignal: () => createSharedSignal,
26
+ deriveKeyPairFromWalletSignature: () => deriveKeyPairFromWalletSignature,
27
+ generateSignalId: () => generateSignalId,
28
+ openSharedSignal: () => openSharedSignal,
29
+ verifyKeyCertificate: () => verifyKeyCertificate
30
+ });
31
+ async function getSodium() {
32
+ if (!sodium) {
33
+ await _sodium.ready;
34
+ sodium = _sodium;
35
+ }
36
+ return sodium;
37
+ }
38
+ async function hkdfSha256(ikm, salt, info, length = 32) {
39
+ const saltBytes = typeof salt === "string" ? new TextEncoder().encode(salt) : new Uint8Array(salt);
40
+ const infoBytes = typeof info === "string" ? new TextEncoder().encode(info) : new Uint8Array(info);
41
+ const ikmCopy = new Uint8Array(ikm.length);
42
+ ikmCopy.set(ikm);
43
+ const saltCopy = new Uint8Array(saltBytes.length);
44
+ saltCopy.set(saltBytes);
45
+ const infoCopy = new Uint8Array(infoBytes.length);
46
+ infoCopy.set(infoBytes);
47
+ const crypto = globalThis.crypto;
48
+ const keyMaterial = await crypto.subtle.importKey(
49
+ "raw",
50
+ ikmCopy.buffer,
51
+ "HKDF",
52
+ false,
53
+ ["deriveBits"]
54
+ );
55
+ const derivedBits = await crypto.subtle.deriveBits(
56
+ {
57
+ name: "HKDF",
58
+ hash: "SHA-256",
59
+ salt: saltCopy.buffer,
60
+ info: infoCopy.buffer
61
+ },
62
+ keyMaterial,
63
+ length * 8
64
+ );
65
+ return new Uint8Array(derivedBits);
66
+ }
67
+ async function deriveKeyPairFromWalletSignature(walletAddress, domain, chainId, signMessage) {
68
+ await getSodium();
69
+ const message = `MORSE: derive encryption seed v1 | ${domain} | ${chainId} | ${walletAddress.toLowerCase()}`;
70
+ const signature = await signMessage(message);
71
+ const sigBytes = ethers.getBytes(signature);
72
+ const seed = await hkdfSha256(
73
+ sigBytes,
74
+ "MORSE_SEED_v1",
75
+ walletAddress.toLowerCase(),
76
+ 32
77
+ );
78
+ const keypair = sodium.crypto_box_seed_keypair(seed);
79
+ return {
80
+ publicKey: keypair.publicKey,
81
+ privateKey: keypair.privateKey
82
+ };
83
+ }
84
+ function createKeyCertTypedData(walletAddress, x25519PublicKey, keyId, issuedAt, expiresAt, domain, chainId) {
85
+ return {
86
+ domain: {
87
+ name: "MORSE",
88
+ version: "1",
89
+ chainId
90
+ },
91
+ types: {
92
+ MorseKeyCert: [
93
+ { name: "walletAddress", type: "address" },
94
+ { name: "x25519PublicKey", type: "bytes32" },
95
+ { name: "keyId", type: "bytes32" },
96
+ { name: "issuedAt", type: "uint64" },
97
+ { name: "expiresAt", type: "uint64" },
98
+ { name: "domain", type: "string" }
99
+ ]
100
+ },
101
+ primaryType: "MorseKeyCert",
102
+ message: {
103
+ walletAddress: walletAddress.toLowerCase(),
104
+ x25519PublicKey: ethers.zeroPadValue(
105
+ ethers.hexlify(Buffer.from(x25519PublicKey, "base64")),
106
+ 32
107
+ ),
108
+ keyId: `0x${keyId}`,
109
+ issuedAt,
110
+ expiresAt,
111
+ domain
112
+ }
113
+ };
114
+ }
115
+ async function createKeyCertificate(walletAddress, x25519PublicKey, domain, chainId, expiresAt, signTypedData) {
116
+ const issuedAt = Date.now();
117
+ const abiCoder = ethers.AbiCoder.defaultAbiCoder();
118
+ const keyIdInput = abiCoder.encode(
119
+ ["address", "bytes32", "uint64", "string", "uint256"],
120
+ [
121
+ walletAddress.toLowerCase(),
122
+ ethers.zeroPadValue(
123
+ ethers.hexlify(Buffer.from(x25519PublicKey, "base64")),
124
+ 32
125
+ ),
126
+ expiresAt,
127
+ domain,
128
+ chainId
129
+ ]
130
+ );
131
+ const keyId = ethers.keccak256(keyIdInput).slice(2);
132
+ const typedData = createKeyCertTypedData(
133
+ walletAddress,
134
+ x25519PublicKey,
135
+ keyId,
136
+ issuedAt,
137
+ expiresAt,
138
+ domain,
139
+ chainId
140
+ );
141
+ const signature = await signTypedData(
142
+ typedData.domain,
143
+ typedData.types,
144
+ typedData.message
145
+ );
146
+ return {
147
+ walletAddress: walletAddress.toLowerCase(),
148
+ x25519PublicKey,
149
+ keyId,
150
+ expiresAt,
151
+ issuedAt,
152
+ domain,
153
+ chainId,
154
+ signature
155
+ };
156
+ }
157
+ function verifyKeyCertificate(cert) {
158
+ try {
159
+ const typedData = createKeyCertTypedData(
160
+ cert.walletAddress,
161
+ cert.x25519PublicKey,
162
+ cert.keyId,
163
+ cert.issuedAt,
164
+ cert.expiresAt,
165
+ cert.domain,
166
+ cert.chainId
167
+ );
168
+ const recovered = ethers.verifyTypedData(
169
+ typedData.domain,
170
+ typedData.types,
171
+ typedData.message,
172
+ cert.signature
173
+ );
174
+ return recovered.toLowerCase() === cert.walletAddress.toLowerCase();
175
+ } catch {
176
+ return false;
177
+ }
178
+ }
179
+ async function createSharedSignal(payloadBytes, recipientPubKey, walletTarget, walletCreator, expiresAt, signalId) {
180
+ await getSodium();
181
+ const dataKey = sodium.randombytes_buf(32);
182
+ const payloadNonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
183
+ const encryptedPayload = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
184
+ payloadBytes,
185
+ null,
186
+ null,
187
+ payloadNonce,
188
+ dataKey
189
+ );
190
+ const senderKeypair = sodium.crypto_box_keypair();
191
+ const sharedSecret = sodium.crypto_scalarmult(
192
+ senderKeypair.privateKey,
193
+ recipientPubKey
194
+ );
195
+ const salt = `MORSE_SEAL_${signalId}_v1`;
196
+ const info = "wrap_datakey";
197
+ const wrappingKey = await hkdfSha256(sharedSecret, salt, info, 32);
198
+ const sealedNonce = sodium.randombytes_buf(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
199
+ const expiresAtInt = Math.floor(Number(expiresAt));
200
+ const abiCoder = ethers.AbiCoder.defaultAbiCoder();
201
+ const senderEphemeralPubKeyHex = ethers.zeroPadValue(ethers.hexlify(senderKeypair.publicKey), 32);
202
+ const aad = abiCoder.encode(
203
+ ["string", "address", "address", "uint64", "string", "bytes32"],
204
+ [
205
+ signalId,
206
+ walletTarget.toLowerCase(),
207
+ walletCreator.toLowerCase(),
208
+ expiresAtInt,
209
+ X25519_CIPHER_VERSION,
210
+ senderEphemeralPubKeyHex
211
+ ]
212
+ );
213
+ const aadBytes = ethers.getBytes(aad);
214
+ const aadHash = ethers.keccak256(aad).slice(2);
215
+ const sealedDataKey = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
216
+ dataKey,
217
+ aadBytes,
218
+ null,
219
+ sealedNonce,
220
+ wrappingKey
221
+ );
222
+ return {
223
+ cipherVersion: X25519_CIPHER_VERSION,
224
+ encryptedPayload: Buffer.from(encryptedPayload).toString("base64"),
225
+ payloadNonce: Buffer.from(payloadNonce).toString("base64"),
226
+ sealedDataKey: Buffer.from(sealedDataKey).toString("base64"),
227
+ sealedNonce: Buffer.from(sealedNonce).toString("base64"),
228
+ senderEphemeralPublicKey: Buffer.from(senderKeypair.publicKey).toString("base64"),
229
+ aadHash
230
+ };
231
+ }
232
+ async function openSharedSignal(encryptedPayloadBase64, payloadNonceBase64, sealedDataKeyBase64, sealedNonceBase64, senderEphemeralPublicKeyBase64, signalId, walletTarget, walletCreator, expiresAt, aadHash, walletAddress, domain, chainId, signMessage) {
233
+ await getSodium();
234
+ const recipientKeypair = await deriveKeyPairFromWalletSignature(
235
+ walletAddress,
236
+ domain,
237
+ chainId,
238
+ signMessage
239
+ );
240
+ const senderEphemeralPub = ethers.getBytes(
241
+ `0x${Buffer.from(senderEphemeralPublicKeyBase64, "base64").toString("hex")}`
242
+ );
243
+ const sharedSecret = sodium.crypto_scalarmult(
244
+ recipientKeypair.privateKey,
245
+ senderEphemeralPub
246
+ );
247
+ const salt = `MORSE_SEAL_${signalId}_v1`;
248
+ const info = "wrap_datakey";
249
+ const wrappingKey = await hkdfSha256(sharedSecret, salt, info, 32);
250
+ const sealedDataKey = ethers.getBytes(`0x${Buffer.from(sealedDataKeyBase64, "base64").toString("hex")}`);
251
+ const sealedNonce = ethers.getBytes(`0x${Buffer.from(sealedNonceBase64, "base64").toString("hex")}`);
252
+ const expiresAtInt = Math.floor(Number(expiresAt));
253
+ const abiCoder = ethers.AbiCoder.defaultAbiCoder();
254
+ const aad = abiCoder.encode(
255
+ ["string", "address", "address", "uint64", "string", "bytes32"],
256
+ [
257
+ signalId,
258
+ walletTarget.toLowerCase(),
259
+ walletCreator.toLowerCase(),
260
+ expiresAtInt,
261
+ X25519_CIPHER_VERSION,
262
+ ethers.zeroPadValue(ethers.hexlify(senderEphemeralPub), 32)
263
+ ]
264
+ );
265
+ const aadBytes = ethers.getBytes(aad);
266
+ const computedAadHash = ethers.keccak256(aad).slice(2);
267
+ if (computedAadHash !== aadHash) {
268
+ throw new Error("AAD hash mismatch - possible tampering");
269
+ }
270
+ const dataKey = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
271
+ null,
272
+ sealedDataKey,
273
+ aadBytes,
274
+ sealedNonce,
275
+ wrappingKey
276
+ );
277
+ const encryptedPayload = new Uint8Array(Buffer.from(encryptedPayloadBase64, "base64"));
278
+ const payloadNonce = new Uint8Array(Buffer.from(payloadNonceBase64, "base64"));
279
+ const payload = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
280
+ null,
281
+ encryptedPayload,
282
+ null,
283
+ payloadNonce,
284
+ dataKey
285
+ );
286
+ return payload;
287
+ }
288
+ function generateSignalId() {
289
+ const bytes = new Uint8Array(16);
290
+ globalThis.crypto.getRandomValues(bytes);
291
+ const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
292
+ return hex.substring(0, 20);
293
+ }
294
+ var sodium, X25519_CIPHER_VERSION;
295
+ var init_crypto_x25519 = __esm({
296
+ "src/crypto-x25519.ts"() {
297
+ X25519_CIPHER_VERSION = "morse-x25519-xchacha20poly1305-v2";
298
+ }
299
+ });
300
+
301
+ // src/rate-limiter.ts
302
+ var RateLimiter = class {
303
+ constructor(config) {
304
+ this.requests = /* @__PURE__ */ new Map();
305
+ this.maxRequests = config.maxRequests;
306
+ this.windowMs = config.windowMs;
307
+ }
308
+ async checkLimit(identifier = "default") {
309
+ const now = Date.now();
310
+ const requests = this.requests.get(identifier) || [];
311
+ const recentRequests = requests.filter((timestamp) => now - timestamp < this.windowMs);
312
+ if (recentRequests.length >= this.maxRequests) {
313
+ const oldestRequest = Math.min(...recentRequests);
314
+ const waitTime = this.windowMs - (now - oldestRequest);
315
+ throw new RateLimitError(
316
+ `Rate limit exceeded. Maximum ${this.maxRequests} requests per ${this.windowMs}ms. Retry after ${Math.ceil(waitTime / 1e3)}s.`,
317
+ waitTime
318
+ );
319
+ }
320
+ recentRequests.push(now);
321
+ this.requests.set(identifier, recentRequests);
322
+ this.cleanup(identifier, now);
323
+ }
324
+ cleanup(identifier, now) {
325
+ const requests = this.requests.get(identifier);
326
+ if (requests) {
327
+ const validRequests = requests.filter((timestamp) => now - timestamp < this.windowMs);
328
+ if (validRequests.length === 0) {
329
+ this.requests.delete(identifier);
330
+ } else {
331
+ this.requests.set(identifier, validRequests);
332
+ }
333
+ }
334
+ }
335
+ reset(identifier = "default") {
336
+ this.requests.delete(identifier);
337
+ }
338
+ resetAll() {
339
+ this.requests.clear();
340
+ }
341
+ getRemainingRequests(identifier = "default") {
342
+ const now = Date.now();
343
+ const requests = this.requests.get(identifier) || [];
344
+ const recentRequests = requests.filter((timestamp) => now - timestamp < this.windowMs);
345
+ return Math.max(0, this.maxRequests - recentRequests.length);
346
+ }
347
+ };
348
+ var RateLimitError = class extends Error {
349
+ constructor(message, retryAfterMs) {
350
+ super(message);
351
+ this.retryAfterMs = retryAfterMs;
352
+ this.name = "RateLimitError";
353
+ }
354
+ };
355
+
356
+ // src/errors.ts
357
+ var MorseSDKError = class _MorseSDKError extends Error {
358
+ constructor(message, code, statusCode) {
359
+ super(message);
360
+ this.code = code;
361
+ this.statusCode = statusCode;
362
+ this.name = "MorseSDKError";
363
+ Object.setPrototypeOf(this, _MorseSDKError.prototype);
364
+ }
365
+ };
366
+ var SignalNotFoundError = class _SignalNotFoundError extends MorseSDKError {
367
+ constructor(message = "Signal not found") {
368
+ super(message, "SIGNAL_NOT_FOUND", 404);
369
+ this.name = "SignalNotFoundError";
370
+ Object.setPrototypeOf(this, _SignalNotFoundError.prototype);
371
+ }
372
+ };
373
+ var SignalExpiredError = class _SignalExpiredError extends MorseSDKError {
374
+ constructor(message = "Signal has expired") {
375
+ super(message, "SIGNAL_EXPIRED", 400);
376
+ this.name = "SignalExpiredError";
377
+ Object.setPrototypeOf(this, _SignalExpiredError.prototype);
378
+ }
379
+ };
380
+ var SignalAlreadyUsedError = class _SignalAlreadyUsedError extends MorseSDKError {
381
+ constructor(message = "Signal has already been used") {
382
+ super(message, "SIGNAL_ALREADY_USED", 400);
383
+ this.name = "SignalAlreadyUsedError";
384
+ Object.setPrototypeOf(this, _SignalAlreadyUsedError.prototype);
385
+ }
386
+ };
387
+ var WalletNotAllowedError = class _WalletNotAllowedError extends MorseSDKError {
388
+ constructor(message = "Wallet not allowed to access this signal") {
389
+ super(message, "WALLET_NOT_ALLOWED", 401);
390
+ this.name = "WalletNotAllowedError";
391
+ Object.setPrototypeOf(this, _WalletNotAllowedError.prototype);
392
+ }
393
+ };
394
+ var ValidationError = class _ValidationError extends MorseSDKError {
395
+ constructor(message = "Validation error") {
396
+ super(message, "VALIDATION_ERROR", 400);
397
+ this.name = "ValidationError";
398
+ Object.setPrototypeOf(this, _ValidationError.prototype);
399
+ }
400
+ };
401
+ var NetworkError = class _NetworkError extends MorseSDKError {
402
+ constructor(message = "Network error", originalError) {
403
+ super(message, "NETWORK_ERROR", 0);
404
+ this.originalError = originalError;
405
+ this.name = "NetworkError";
406
+ Object.setPrototypeOf(this, _NetworkError.prototype);
407
+ }
408
+ };
409
+ function mapErrorResponse(error) {
410
+ if (error.code) {
411
+ switch (error.code) {
412
+ case "SIGNAL_NOT_FOUND":
413
+ return new SignalNotFoundError(error.message);
414
+ case "SIGNAL_EXPIRED":
415
+ return new SignalExpiredError(error.message);
416
+ case "SIGNAL_ALREADY_USED":
417
+ return new SignalAlreadyUsedError(error.message);
418
+ case "WALLET_NOT_ALLOWED":
419
+ return new WalletNotAllowedError(error.message);
420
+ case "VALIDATION_ERROR":
421
+ return new ValidationError(error.message);
422
+ default:
423
+ return new MorseSDKError(error.message || "Unknown error", error.code);
424
+ }
425
+ }
426
+ return new MorseSDKError(error.message || "Unknown error");
427
+ }
428
+
429
+ // src/logger.ts
430
+ var Logger = class {
431
+ constructor() {
432
+ this.level = "warn";
433
+ this.levels = {
434
+ none: 0,
435
+ error: 1,
436
+ warn: 2,
437
+ info: 3,
438
+ debug: 4
439
+ };
440
+ }
441
+ setLevel(level) {
442
+ this.level = level;
443
+ }
444
+ getLevel() {
445
+ return this.level;
446
+ }
447
+ shouldLog(level) {
448
+ return this.levels[level] <= this.levels[this.level];
449
+ }
450
+ error(message, ...args) {
451
+ if (this.shouldLog("error")) {
452
+ console.error(`[MorseSDK] ${message}`, ...args);
453
+ }
454
+ }
455
+ warn(message, ...args) {
456
+ if (this.shouldLog("warn")) {
457
+ console.warn(`[MorseSDK] ${message}`, ...args);
458
+ }
459
+ }
460
+ info(message, ...args) {
461
+ if (this.shouldLog("info")) {
462
+ console.info(`[MorseSDK] ${message}`, ...args);
463
+ }
464
+ }
465
+ debug(message, ...args) {
466
+ if (this.shouldLog("debug")) {
467
+ console.debug(`[MorseSDK] ${message}`, ...args);
468
+ }
469
+ }
470
+ };
471
+ var logger = new Logger();
472
+
473
+ // src/crypto.ts
474
+ var CIPHER_VERSION = "aes-gcm-256-v1";
475
+ function getCrypto() {
476
+ if (typeof globalThis !== "undefined" && globalThis.crypto) {
477
+ return globalThis.crypto;
478
+ }
479
+ if (typeof window !== "undefined" && window.crypto) {
480
+ return window.crypto;
481
+ }
482
+ throw new Error("Web Crypto API is not available. Requires Node.js 15+ or a browser with Web Crypto support.");
483
+ }
484
+ function toBase64(data) {
485
+ const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
486
+ if (typeof Buffer !== "undefined") {
487
+ return Buffer.from(bytes).toString("base64");
488
+ }
489
+ if (typeof btoa !== "undefined") {
490
+ return btoa(String.fromCharCode(...bytes));
491
+ }
492
+ throw new Error("Base64 encoding not available. Requires Node.js Buffer or browser btoa.");
493
+ }
494
+ function fromBase64(base64) {
495
+ if (typeof Buffer !== "undefined") {
496
+ return Uint8Array.from(Buffer.from(base64, "base64"));
497
+ }
498
+ if (typeof atob !== "undefined") {
499
+ return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
500
+ }
501
+ throw new Error("Base64 decoding not available. Requires Node.js Buffer or browser atob.");
502
+ }
503
+ function getCipherVersion() {
504
+ return CIPHER_VERSION;
505
+ }
506
+ async function generateKey() {
507
+ const crypto = getCrypto();
508
+ if (!crypto.subtle) {
509
+ throw new Error("Web Crypto API (crypto.subtle) is not available.");
510
+ }
511
+ return crypto.subtle.generateKey(
512
+ {
513
+ name: "AES-GCM",
514
+ length: 256
515
+ },
516
+ true,
517
+ ["encrypt", "decrypt"]
518
+ );
519
+ }
520
+ async function exportKey(key) {
521
+ const crypto = getCrypto();
522
+ const exported = await crypto.subtle.exportKey("raw", key);
523
+ const keyArray = new Uint8Array(exported);
524
+ return toBase64(keyArray);
525
+ }
526
+ async function importKey(keyBase64) {
527
+ const crypto = getCrypto();
528
+ const keyArray = fromBase64(keyBase64);
529
+ const keyBuffer = keyArray.buffer.slice(keyArray.byteOffset, keyArray.byteOffset + keyArray.byteLength);
530
+ return crypto.subtle.importKey(
531
+ "raw",
532
+ keyBuffer,
533
+ {
534
+ name: "AES-GCM",
535
+ length: 256
536
+ },
537
+ true,
538
+ ["encrypt", "decrypt"]
539
+ );
540
+ }
541
+ function generateIV() {
542
+ const crypto = getCrypto();
543
+ return crypto.getRandomValues(new Uint8Array(12));
544
+ }
545
+ async function encryptText(text, key) {
546
+ const crypto = getCrypto();
547
+ const encoder = new TextEncoder();
548
+ const data = encoder.encode(text);
549
+ const iv = generateIV();
550
+ const ivArray = new Uint8Array(iv);
551
+ const encrypted = await crypto.subtle.encrypt(
552
+ {
553
+ name: "AES-GCM",
554
+ iv: ivArray
555
+ },
556
+ key,
557
+ data
558
+ );
559
+ return {
560
+ encrypted: toBase64(new Uint8Array(encrypted)),
561
+ iv: toBase64(iv)
562
+ };
563
+ }
564
+ async function decryptText(encryptedBase64, ivBase64, key) {
565
+ const crypto = getCrypto();
566
+ const encrypted = fromBase64(encryptedBase64);
567
+ const iv = fromBase64(ivBase64);
568
+ const ivBuffer = iv.buffer.slice(iv.byteOffset, iv.byteOffset + iv.byteLength);
569
+ const encryptedBuffer = encrypted.buffer.slice(encrypted.byteOffset, encrypted.byteOffset + encrypted.byteLength);
570
+ const decrypted = await crypto.subtle.decrypt(
571
+ {
572
+ name: "AES-GCM",
573
+ iv: ivBuffer
574
+ },
575
+ key,
576
+ encryptedBuffer
577
+ );
578
+ const decoder = new TextDecoder();
579
+ return decoder.decode(decrypted);
580
+ }
581
+ async function encryptFile(fileData, key) {
582
+ const crypto = getCrypto();
583
+ let data;
584
+ if (fileData instanceof Buffer) {
585
+ data = fileData.buffer.slice(fileData.byteOffset, fileData.byteOffset + fileData.byteLength);
586
+ } else if (fileData instanceof Uint8Array) {
587
+ data = fileData.buffer.slice(fileData.byteOffset, fileData.byteOffset + fileData.byteLength);
588
+ } else {
589
+ data = fileData;
590
+ }
591
+ const iv = generateIV();
592
+ const ivBuffer = iv.buffer.slice(iv.byteOffset, iv.byteOffset + iv.byteLength);
593
+ const encrypted = await crypto.subtle.encrypt(
594
+ {
595
+ name: "AES-GCM",
596
+ iv: ivBuffer
597
+ },
598
+ key,
599
+ data
600
+ );
601
+ return {
602
+ encrypted,
603
+ iv: toBase64(iv)
604
+ };
605
+ }
606
+ async function decryptFile(encryptedData, ivBase64, key) {
607
+ const crypto = getCrypto();
608
+ const iv = fromBase64(ivBase64);
609
+ if (iv.length !== 12) {
610
+ throw new Error(`Invalid IV length: ${iv.length} bytes. Expected 12 bytes for AES-GCM.`);
611
+ }
612
+ const ivBuffer = iv.buffer.slice(iv.byteOffset, iv.byteOffset + iv.byteLength);
613
+ const dataBuffer = encryptedData;
614
+ const decrypted = await crypto.subtle.decrypt(
615
+ {
616
+ name: "AES-GCM",
617
+ iv: ivBuffer
618
+ },
619
+ key,
620
+ dataBuffer
621
+ );
622
+ return decrypted;
623
+ }
624
+ function generateShareableLink(baseUrl, signalId, keyBase64) {
625
+ return `${baseUrl}/view/${signalId}#k=${encodeURIComponent(keyBase64)}`;
626
+ }
627
+
628
+ // src/implementations/v1/MorseSDKV1.ts
629
+ init_crypto_x25519();
630
+
631
+ // src/wallet-key-service.ts
632
+ var WalletKeyService = class {
633
+ constructor(baseUrl, apiKey, apiVersion = "v1") {
634
+ this.baseUrl = baseUrl;
635
+ this.apiKey = apiKey;
636
+ this.apiVersion = apiVersion;
637
+ }
638
+ getApiUrl(path) {
639
+ return `${this.baseUrl}/${this.apiVersion}${path}`;
640
+ }
641
+ async getPublicKey(walletAddress) {
642
+ const url = this.getApiUrl(`/wallet-key-registry/${walletAddress.toLowerCase()}`);
643
+ const response = await fetch(url, {
644
+ method: "GET",
645
+ headers: {
646
+ "Content-Type": "application/json",
647
+ "X-API-Key": this.apiKey
648
+ }
649
+ });
650
+ if (!response.ok) {
651
+ if (response.status === 404) {
652
+ return {
653
+ walletAddress: walletAddress.toLowerCase(),
654
+ certificate: null,
655
+ exists: false
656
+ };
657
+ }
658
+ throw new Error(`Failed to fetch public key: ${response.status} ${response.statusText}`);
659
+ }
660
+ const data = await response.json();
661
+ return {
662
+ walletAddress: data.walletAddress,
663
+ certificate: data.certificate || null,
664
+ exists: !!data.certificate
665
+ };
666
+ }
667
+ async publishPublicKey(certificate) {
668
+ const url = this.getApiUrl("/wallet-key-registry");
669
+ const response = await fetch(url, {
670
+ method: "POST",
671
+ headers: {
672
+ "Content-Type": "application/json",
673
+ "X-API-Key": this.apiKey
674
+ },
675
+ body: JSON.stringify({ certificate })
676
+ });
677
+ if (!response.ok) {
678
+ const error = await response.json().catch(() => ({ message: "Unknown error" }));
679
+ throw new Error(`Failed to publish public key: ${error.message || response.statusText}`);
680
+ }
681
+ }
682
+ };
683
+
684
+ // src/utils.ts
685
+ function isValidSignalId(signalId) {
686
+ if (!signalId || typeof signalId !== "string") {
687
+ return false;
688
+ }
689
+ if (signalId.length < 8 || signalId.length > 64) {
690
+ return false;
691
+ }
692
+ return /^[a-zA-Z0-9_-]+$/.test(signalId);
693
+ }
694
+ function isValidWalletAddress(address) {
695
+ if (!address || typeof address !== "string") {
696
+ return false;
697
+ }
698
+ return /^0x[a-fA-F0-9]{40}$/.test(address);
699
+ }
700
+ function validateSignalId(signalId) {
701
+ if (!isValidSignalId(signalId)) {
702
+ throw new Error(`Invalid signal ID: ${signalId}. Must be 8-64 alphanumeric characters.`);
703
+ }
704
+ }
705
+ function validateWalletAddress(address) {
706
+ if (!isValidWalletAddress(address)) {
707
+ throw new Error(`Invalid wallet address: ${address}. Must be a valid Ethereum address.`);
708
+ }
709
+ }
710
+ function formatExpiration(expiresAt) {
711
+ const date = new Date(expiresAt);
712
+ return date.toLocaleString();
713
+ }
714
+ function isSignalExpired(expiresAt) {
715
+ return new Date(expiresAt) < /* @__PURE__ */ new Date();
716
+ }
717
+ function getTimeUntilExpiration(expiresAt) {
718
+ return Math.max(0, new Date(expiresAt).getTime() - Date.now());
719
+ }
720
+ function parseExpiresIn(expiresIn) {
721
+ const match = expiresIn.match(/^(\d+)([smhd])$/);
722
+ if (!match) {
723
+ throw new Error(`Invalid expiresIn format: ${expiresIn}. Use format like "24h", "7d", "30m"`);
724
+ }
725
+ const value = parseInt(match[1], 10);
726
+ const unit = match[2];
727
+ const multipliers = {
728
+ s: 1e3,
729
+ m: 60 * 1e3,
730
+ h: 60 * 60 * 1e3,
731
+ d: 24 * 60 * 60 * 1e3
732
+ };
733
+ return value * multipliers[unit];
734
+ }
735
+ function filterActiveSignals(signals) {
736
+ const now = /* @__PURE__ */ new Date();
737
+ return signals.filter(
738
+ (signal) => signal.status === "active" && new Date(signal.expiresAt) > now
739
+ );
740
+ }
741
+ function sortSignalsByDate(signals, order = "desc") {
742
+ return [...signals].sort((a, b) => {
743
+ const dateA = new Date(a.createdAt).getTime();
744
+ const dateB = new Date(b.createdAt).getTime();
745
+ return order === "desc" ? dateB - dateA : dateA - dateB;
746
+ });
747
+ }
748
+ function getSignalUrl(baseUrl, signalId, keyBase64) {
749
+ const url = `${baseUrl}/#/signal/${signalId}`;
750
+ if (keyBase64) {
751
+ return `${url}#k=${encodeURIComponent(keyBase64)}`;
752
+ }
753
+ return url;
754
+ }
755
+ function extractSignalIdFromUrl(url) {
756
+ const match = url.match(/\/signal\/([a-zA-Z0-9_-]+)/);
757
+ return match ? match[1] : null;
758
+ }
759
+
760
+ // src/implementations/v1/MorseSDKV1.ts
761
+ var API_BASE_URL = "https://api.morseai.tech";
762
+ var FRONTEND_BASE_URL = "https://morseai.tech";
763
+ var MorseSDKV1 = class {
764
+ constructor(config) {
765
+ this.version = "v1";
766
+ this.rateLimiter = null;
767
+ if (!config.apiKey) {
768
+ throw new Error("apiKey is required. Please provide an API key in the SDK configuration.");
769
+ }
770
+ if (!config.apiKey.startsWith("sk_")) {
771
+ throw new Error("Invalid API key format. API key must start with 'sk_'");
772
+ }
773
+ this.config = config;
774
+ this.timeout = config.timeout || 3e4;
775
+ this.retries = config.retries || 0;
776
+ this.retryDelay = config.retryDelay || 1e3;
777
+ if (config.rateLimit?.enabled !== false) {
778
+ const maxRequests = config.rateLimit?.maxRequests || 100;
779
+ const windowMs = config.rateLimit?.windowMs || 6e4;
780
+ this.rateLimiter = new RateLimiter({ maxRequests, windowMs });
781
+ }
782
+ logger.debug("MorseSDKV1 initialized", {
783
+ apiVersion: config.apiVersion || "v1",
784
+ timeout: this.timeout,
785
+ retries: this.retries,
786
+ rateLimitEnabled: this.rateLimiter !== null,
787
+ hasApiKey: !!config.apiKey
788
+ });
789
+ }
790
+ base64ToUint8Array(base64) {
791
+ if (typeof Buffer !== "undefined") {
792
+ return Uint8Array.from(Buffer.from(base64, "base64"));
793
+ }
794
+ if (typeof atob !== "undefined") {
795
+ return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
796
+ }
797
+ throw new Error("Base64 decoding not available. Requires Node.js Buffer or browser atob.");
798
+ }
799
+ arrayBufferToBase64(buffer) {
800
+ const bytes = new Uint8Array(buffer);
801
+ if (typeof Buffer !== "undefined") {
802
+ return Buffer.from(bytes).toString("base64");
803
+ }
804
+ if (typeof btoa !== "undefined") {
805
+ return btoa(String.fromCharCode(...bytes));
806
+ }
807
+ throw new Error("Base64 encoding not available. Requires Node.js Buffer or browser btoa.");
808
+ }
809
+ sanitizeRequestOptions(options) {
810
+ const sanitized = { ...options };
811
+ if (sanitized.body) {
812
+ try {
813
+ const body = JSON.parse(sanitized.body);
814
+ const sanitizedBody = {};
815
+ if (body.wallet) {
816
+ sanitizedBody.wallet = {
817
+ address: body.wallet.address,
818
+ signature: "[REDACTED]",
819
+ message: body.wallet.message
820
+ };
821
+ }
822
+ if (body.encryptedText) {
823
+ sanitizedBody.encryptedText = "[REDACTED]";
824
+ }
825
+ if (body.payloadNonce) {
826
+ sanitizedBody.payloadNonce = "[REDACTED]";
827
+ }
828
+ if (body.sealedDataKey) {
829
+ sanitizedBody.sealedDataKey = "[REDACTED]";
830
+ }
831
+ if (body.file) {
832
+ sanitizedBody.file = {
833
+ ...body.file,
834
+ payloadNonce: body.file.payloadNonce ? "[REDACTED]" : null,
835
+ sealedDataKey: body.file.sealedDataKey ? "[REDACTED]" : null
836
+ };
837
+ }
838
+ if (body.signalId) {
839
+ sanitizedBody.signalId = body.signalId;
840
+ }
841
+ sanitized.body = JSON.stringify(sanitizedBody);
842
+ } catch {
843
+ sanitized.body = "[REDACTED - non-JSON body]";
844
+ }
845
+ }
846
+ return sanitized;
847
+ }
848
+ getApiUrl(path) {
849
+ const version = this.config.apiVersion || "v1";
850
+ return `${API_BASE_URL}/${version}${path}`;
851
+ }
852
+ async createAuthMessage(action, context = "") {
853
+ const timestamp = Date.now();
854
+ if (context) {
855
+ return `MORSE: ${action} ${context} at ${timestamp}`;
856
+ }
857
+ return `MORSE: ${action} at ${timestamp}`;
858
+ }
859
+ async signMessage(wallet, message) {
860
+ return await wallet.signMessage(message);
861
+ }
862
+ async makeRequest(url, options = {}) {
863
+ if (this.rateLimiter) {
864
+ try {
865
+ await this.rateLimiter.checkLimit();
866
+ } catch (error) {
867
+ if (error instanceof RateLimitError) {
868
+ logger.warn("Rate limit exceeded", {
869
+ url,
870
+ retryAfterMs: error.retryAfterMs
871
+ });
872
+ if (this.config.onError) {
873
+ this.config.onError(error);
874
+ }
875
+ throw error;
876
+ }
877
+ throw error;
878
+ }
879
+ }
880
+ logger.debug("Making request", {
881
+ url,
882
+ method: options.method || "GET",
883
+ hasBody: !!options.body
884
+ });
885
+ const requestOptions = {
886
+ ...options,
887
+ headers: {
888
+ "Content-Type": "application/json",
889
+ "X-API-Key": this.config.apiKey,
890
+ ...options.headers
891
+ }
892
+ };
893
+ if (this.config.onRequest) {
894
+ const sanitizedOptions = this.sanitizeRequestOptions(requestOptions);
895
+ this.config.onRequest(url, sanitizedOptions);
896
+ }
897
+ let lastError = null;
898
+ for (let attempt = 0; attempt <= this.retries; attempt++) {
899
+ try {
900
+ const controller = new AbortController();
901
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
902
+ const response = await fetch(url, {
903
+ ...requestOptions,
904
+ signal: controller.signal
905
+ });
906
+ clearTimeout(timeoutId);
907
+ if (this.config.onResponse) {
908
+ this.config.onResponse(url, response);
909
+ }
910
+ if (!response.ok) {
911
+ const error = await response.json().catch(() => ({
912
+ status: "error",
913
+ code: "VALIDATION_ERROR",
914
+ message: `HTTP ${response.status}: ${response.statusText}`
915
+ }));
916
+ logger.error("Request failed", {
917
+ status: response.status,
918
+ code: error.code,
919
+ message: error.message,
920
+ attempt: attempt + 1
921
+ });
922
+ const mappedError = mapErrorResponse(error);
923
+ if (this.config.onError) {
924
+ this.config.onError(mappedError);
925
+ }
926
+ throw mappedError;
927
+ }
928
+ const data = await response.json();
929
+ logger.debug("Request successful", { url, attempt: attempt + 1 });
930
+ return data;
931
+ } catch (error) {
932
+ lastError = error;
933
+ if (error.name === "AbortError") {
934
+ const timeoutError = new NetworkError(`Request timeout after ${this.timeout}ms`, error);
935
+ if (this.config.onError) {
936
+ this.config.onError(timeoutError);
937
+ }
938
+ throw timeoutError;
939
+ }
940
+ if (error instanceof NetworkError || error.name?.includes("Error")) {
941
+ if (attempt < this.retries) {
942
+ logger.warn(`Request failed, retrying... (${attempt + 1}/${this.retries})`, { url });
943
+ await new Promise((resolve) => setTimeout(resolve, this.retryDelay * (attempt + 1)));
944
+ continue;
945
+ }
946
+ if (this.config.onError) {
947
+ this.config.onError(error);
948
+ }
949
+ throw error;
950
+ }
951
+ if (error.message?.includes("fetch") || error.message?.includes("network")) {
952
+ if (attempt < this.retries) {
953
+ logger.warn(`Network error, retrying... (${attempt + 1}/${this.retries})`, { url });
954
+ await new Promise((resolve) => setTimeout(resolve, this.retryDelay * (attempt + 1)));
955
+ continue;
956
+ }
957
+ const networkError = new NetworkError("Network request failed", error);
958
+ if (this.config.onError) {
959
+ this.config.onError(networkError);
960
+ }
961
+ throw networkError;
962
+ }
963
+ if (this.config.onError) {
964
+ this.config.onError(error);
965
+ }
966
+ throw error;
967
+ }
968
+ }
969
+ if (lastError) {
970
+ if (this.config.onError) {
971
+ this.config.onError(lastError);
972
+ }
973
+ throw lastError;
974
+ }
975
+ throw new NetworkError("Request failed after all retries");
976
+ }
977
+ async createSignal(wallet, options) {
978
+ validateWalletAddress(wallet.address);
979
+ if (options.walletTarget) {
980
+ validateWalletAddress(options.walletTarget);
981
+ }
982
+ if (options.shareWithRecipient && !options.walletTarget) {
983
+ throw new Error("walletTarget is required when shareWithRecipient is true");
984
+ }
985
+ if (!options.hasFile && !options.hasMessage) {
986
+ throw new Error("Either hasFile or hasMessage must be true");
987
+ }
988
+ logger.info("Creating signal", {
989
+ hasFile: options.hasFile,
990
+ hasMessage: options.hasMessage,
991
+ mode: options.mode
992
+ });
993
+ const tempSignalId = "temp-" + Date.now();
994
+ const authMessage = await this.createAuthMessage("create", `signal ${tempSignalId}`);
995
+ const signature = await this.signMessage(wallet, authMessage);
996
+ const requestBody = {
997
+ ...options,
998
+ wallet: {
999
+ address: wallet.address,
1000
+ signature,
1001
+ message: authMessage
1002
+ }
1003
+ };
1004
+ const result = await this.makeRequest(
1005
+ this.getApiUrl("/signals"),
1006
+ {
1007
+ method: "POST",
1008
+ body: JSON.stringify(requestBody)
1009
+ }
1010
+ );
1011
+ logger.info("Signal created", { signalId: result.signalId });
1012
+ return result;
1013
+ }
1014
+ async openSignal(wallet, signalId) {
1015
+ validateSignalId(signalId);
1016
+ validateWalletAddress(wallet.address);
1017
+ logger.info("Opening signal", { signalId });
1018
+ const authMessage = await this.createAuthMessage("open", `signal ${signalId}`);
1019
+ const signature = await this.signMessage(wallet, authMessage);
1020
+ const requestBody = {
1021
+ signalId,
1022
+ wallet: {
1023
+ address: wallet.address,
1024
+ signature,
1025
+ message: authMessage
1026
+ }
1027
+ };
1028
+ const result = await this.makeRequest(
1029
+ this.getApiUrl("/signals/open"),
1030
+ {
1031
+ method: "POST",
1032
+ body: JSON.stringify(requestBody)
1033
+ }
1034
+ );
1035
+ logger.info("Signal opened", { signalId, hasFile: !!result.file });
1036
+ return result;
1037
+ }
1038
+ async openSignalDecrypted(wallet, signalId, keyBase64) {
1039
+ validateSignalId(signalId);
1040
+ validateWalletAddress(wallet.address);
1041
+ const encryptedResponse = await this.openSignal(wallet, signalId);
1042
+ const isX25519Signal = Boolean(
1043
+ encryptedResponse.sealedDataKey && encryptedResponse.sealedNonce && encryptedResponse.senderEphemeralPublicKey
1044
+ );
1045
+ let message = null;
1046
+ let fileData = null;
1047
+ let keySource = "derived";
1048
+ if (isX25519Signal) {
1049
+ const { openSharedSignal: openSharedSignal2 } = await Promise.resolve().then(() => (init_crypto_x25519(), crypto_x25519_exports));
1050
+ const walletTarget = encryptedResponse.walletTarget || wallet.address;
1051
+ const walletCreator = encryptedResponse.walletCreator || wallet.address;
1052
+ const expiresAtMs = new Date(encryptedResponse.expiresAt).getTime();
1053
+ const keyService = new WalletKeyService(API_BASE_URL, this.config.apiKey, this.config.apiVersion || "v1");
1054
+ const certResponse = await keyService.getPublicKey(wallet.address);
1055
+ const domain = certResponse.certificate?.domain || "morseai.tech";
1056
+ const chainId = certResponse.certificate?.chainId || 1;
1057
+ if (encryptedResponse.encryptedText && encryptedResponse.payloadNonce) {
1058
+ const decryptedBytes = await openSharedSignal2(
1059
+ encryptedResponse.encryptedText,
1060
+ encryptedResponse.payloadNonce,
1061
+ encryptedResponse.sealedDataKey,
1062
+ encryptedResponse.sealedNonce,
1063
+ encryptedResponse.senderEphemeralPublicKey,
1064
+ signalId,
1065
+ walletTarget,
1066
+ walletCreator,
1067
+ expiresAtMs,
1068
+ encryptedResponse.aadHash || "",
1069
+ wallet.address,
1070
+ domain,
1071
+ chainId,
1072
+ wallet.signMessage
1073
+ );
1074
+ message = new TextDecoder().decode(decryptedBytes);
1075
+ }
1076
+ if (encryptedResponse.file) {
1077
+ const fileResponse = await this.downloadFile(wallet, signalId);
1078
+ const encryptedFileBase64 = fileResponse.file;
1079
+ const fileSealedDataKey = encryptedResponse.file.sealedDataKey || encryptedResponse.sealedDataKey;
1080
+ const fileSealedNonce = encryptedResponse.file.sealedNonce || encryptedResponse.sealedNonce;
1081
+ const fileSenderEphemeralPublicKey = encryptedResponse.file.senderEphemeralPublicKey || encryptedResponse.senderEphemeralPublicKey;
1082
+ const fileAadHash = encryptedResponse.file.aadHash || encryptedResponse.aadHash || "";
1083
+ if (fileSealedDataKey && fileSealedNonce && fileSenderEphemeralPublicKey && encryptedResponse.file.payloadNonce) {
1084
+ const decryptedFileBytes = await openSharedSignal2(
1085
+ encryptedFileBase64,
1086
+ encryptedResponse.file.payloadNonce,
1087
+ fileSealedDataKey,
1088
+ fileSealedNonce,
1089
+ fileSenderEphemeralPublicKey,
1090
+ `${signalId}-file`,
1091
+ // Use hyphen like frontend
1092
+ walletTarget.toLowerCase(),
1093
+ walletCreator.toLowerCase(),
1094
+ expiresAtMs,
1095
+ fileAadHash,
1096
+ wallet.address,
1097
+ domain,
1098
+ chainId,
1099
+ wallet.signMessage
1100
+ );
1101
+ fileData = decryptedFileBytes.buffer.slice(
1102
+ decryptedFileBytes.byteOffset,
1103
+ decryptedFileBytes.byteOffset + decryptedFileBytes.byteLength
1104
+ );
1105
+ } else {
1106
+ throw new Error("File encryption format not supported for this signal type");
1107
+ }
1108
+ }
1109
+ keySource = "derived";
1110
+ } else {
1111
+ if (!keyBase64 || typeof keyBase64 !== "string" || keyBase64.length < 32) {
1112
+ throw new Error("This signal requires an encryption key. Please provide the key from the URL fragment (#k=...).");
1113
+ }
1114
+ const key = await importKey(keyBase64);
1115
+ if (encryptedResponse.encryptedText && encryptedResponse.payloadNonce) {
1116
+ message = await decryptText(encryptedResponse.encryptedText, encryptedResponse.payloadNonce, key);
1117
+ }
1118
+ if (encryptedResponse.file) {
1119
+ const fileResponse = await this.downloadFile(wallet, signalId);
1120
+ const encryptedFileData = this.base64ToUint8Array(fileResponse.file);
1121
+ const buffer = encryptedFileData.buffer.slice(
1122
+ encryptedFileData.byteOffset,
1123
+ encryptedFileData.byteOffset + encryptedFileData.byteLength
1124
+ );
1125
+ fileData = await decryptFile(buffer, encryptedResponse.file.payloadNonce, key);
1126
+ }
1127
+ keySource = "provided";
1128
+ }
1129
+ return {
1130
+ message,
1131
+ file: encryptedResponse.file && fileData ? {
1132
+ data: fileData,
1133
+ originalName: encryptedResponse.file.originalName,
1134
+ mimeType: encryptedResponse.file.mimeType,
1135
+ sizeBytes: encryptedResponse.file.sizeBytes
1136
+ } : null,
1137
+ expiresAt: encryptedResponse.expiresAt,
1138
+ onChainNotification: encryptedResponse.onChainNotification,
1139
+ keySource
1140
+ };
1141
+ }
1142
+ async listMySignals(wallet) {
1143
+ const authMessage = await this.createAuthMessage("view", "vault");
1144
+ const signature = await this.signMessage(wallet, authMessage);
1145
+ const params = new URLSearchParams({
1146
+ walletAddress: wallet.address,
1147
+ walletSignature: signature,
1148
+ walletMessage: authMessage
1149
+ });
1150
+ return this.makeRequest(
1151
+ `${this.getApiUrl("/signals/my-signals")}?${params.toString()}`,
1152
+ {
1153
+ method: "GET"
1154
+ }
1155
+ );
1156
+ }
1157
+ async uploadFile(wallet, options) {
1158
+ const authMessage = await this.createAuthMessage("upload", "file");
1159
+ const signature = await this.signMessage(wallet, authMessage);
1160
+ const requestBody = {
1161
+ ...options,
1162
+ wallet: {
1163
+ address: wallet.address,
1164
+ signature,
1165
+ message: authMessage
1166
+ }
1167
+ };
1168
+ return this.makeRequest(
1169
+ this.getApiUrl("/files/upload"),
1170
+ {
1171
+ method: "POST",
1172
+ body: JSON.stringify(requestBody)
1173
+ }
1174
+ );
1175
+ }
1176
+ async downloadFile(wallet, signalId) {
1177
+ const authMessage = await this.createAuthMessage("download", `file ${signalId}`);
1178
+ const signature = await this.signMessage(wallet, authMessage);
1179
+ const requestBody = {
1180
+ signalId,
1181
+ wallet: {
1182
+ address: wallet.address,
1183
+ signature,
1184
+ message: authMessage
1185
+ }
1186
+ };
1187
+ return this.makeRequest(
1188
+ this.getApiUrl("/files/download"),
1189
+ {
1190
+ method: "POST",
1191
+ body: JSON.stringify(requestBody)
1192
+ }
1193
+ );
1194
+ }
1195
+ async downloadFileDecrypted(wallet, signalId, keyBase64) {
1196
+ const encryptedResponse = await this.openSignal(wallet, signalId);
1197
+ if (!encryptedResponse.file) {
1198
+ throw new Error("Signal does not have a file");
1199
+ }
1200
+ const fileResponse = await this.downloadFile(wallet, signalId);
1201
+ const key = await importKey(keyBase64);
1202
+ const encryptedFileData = this.base64ToUint8Array(fileResponse.file);
1203
+ const buffer = encryptedFileData.buffer.slice(
1204
+ encryptedFileData.byteOffset,
1205
+ encryptedFileData.byteOffset + encryptedFileData.byteLength
1206
+ );
1207
+ const decryptedData = await decryptFile(buffer, encryptedResponse.file.payloadNonce, key);
1208
+ return {
1209
+ data: decryptedData,
1210
+ originalName: fileResponse.originalName,
1211
+ mimeType: fileResponse.mimeType,
1212
+ sizeBytes: fileResponse.sizeBytes
1213
+ };
1214
+ }
1215
+ async createSignalEncrypted(wallet, options) {
1216
+ validateWalletAddress(wallet.address);
1217
+ const shareWithRecipient = options.mode === "shared_wallet";
1218
+ if (options.mode === "shared_wallet" && !options.walletTarget) {
1219
+ throw new Error("walletTarget is required when mode is 'shared_wallet'");
1220
+ }
1221
+ if (options.walletTarget) {
1222
+ validateWalletAddress(options.walletTarget);
1223
+ }
1224
+ if (!options.message && !options.file) {
1225
+ throw new Error("Either message or file must be provided");
1226
+ }
1227
+ if (!options.expiresIn && !options.expiresAt) {
1228
+ throw new Error(
1229
+ "Either expiresIn or expiresAt must be provided.\n - expiresIn: relative time (e.g., '24h', '7d', '1h')\n - expiresAt: specific date (ISO 8601, e.g., '2026-12-31T23:59:59.000Z')"
1230
+ );
1231
+ }
1232
+ if (options.expiresIn && options.expiresAt) {
1233
+ throw new Error("Cannot provide both expiresIn and expiresAt. Use only one.");
1234
+ }
1235
+ const mergedOptions = {
1236
+ ...options,
1237
+ shareWithRecipient
1238
+ };
1239
+ if (shareWithRecipient && options.walletTarget) {
1240
+ return this.createSharedSignalEncrypted(wallet, mergedOptions);
1241
+ }
1242
+ return this.createPrivateSignalEncrypted(wallet, mergedOptions);
1243
+ }
1244
+ async createPrivateSignalEncrypted(wallet, options) {
1245
+ const key = await generateKey();
1246
+ const keyBase64 = await exportKey(key);
1247
+ let encryptedText;
1248
+ let payloadNonce;
1249
+ let fileOptions;
1250
+ if (options.message) {
1251
+ const encrypted = await encryptText(options.message, key);
1252
+ encryptedText = encrypted.encrypted;
1253
+ payloadNonce = encrypted.iv;
1254
+ }
1255
+ if (options.file) {
1256
+ const uploadResult = await this.uploadFileEncrypted(
1257
+ wallet,
1258
+ options.file.data,
1259
+ options.file.originalName,
1260
+ options.file.mimeType,
1261
+ key
1262
+ );
1263
+ const encrypted = await encryptFile(options.file.data, key);
1264
+ fileOptions = {
1265
+ storagePath: uploadResult.storagePath,
1266
+ payloadNonce: encrypted.iv,
1267
+ originalName: options.file.originalName,
1268
+ mimeType: options.file.mimeType,
1269
+ sizeBytes: uploadResult.sizeBytes
1270
+ };
1271
+ }
1272
+ const signalOptions = {
1273
+ walletTarget: options.walletTarget,
1274
+ shareWithRecipient: options.shareWithRecipient,
1275
+ mode: options.mode,
1276
+ hasFile: !!options.file,
1277
+ hasMessage: !!options.message,
1278
+ cipherVersion: getCipherVersion(),
1279
+ encryptedText,
1280
+ payloadNonce,
1281
+ file: fileOptions,
1282
+ onChainNotification: options.onChainNotification,
1283
+ expiresAt: options.expiresAt,
1284
+ expiresIn: options.expiresIn
1285
+ };
1286
+ const result = await this.createSignal(wallet, signalOptions);
1287
+ const shareableLink = generateShareableLink(FRONTEND_BASE_URL, result.signalId, keyBase64);
1288
+ return {
1289
+ ...result,
1290
+ keyBase64,
1291
+ shareableLink
1292
+ };
1293
+ }
1294
+ async createSharedSignalEncrypted(wallet, options) {
1295
+ const walletTarget = options.walletTarget;
1296
+ const walletCreator = wallet.address;
1297
+ const signalId = options.signalId || generateSignalId();
1298
+ let expiresAtMs;
1299
+ if (options.expiresAt) {
1300
+ expiresAtMs = new Date(options.expiresAt).getTime();
1301
+ } else if (options.expiresIn) {
1302
+ const match = options.expiresIn.match(/^(\d+)([smhd])$/);
1303
+ if (!match) throw new Error("Invalid expiresIn format");
1304
+ const value = parseInt(match[1], 10);
1305
+ const unit = match[2];
1306
+ const multipliers = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
1307
+ expiresAtMs = Date.now() + value * multipliers[unit];
1308
+ } else {
1309
+ expiresAtMs = Date.now() + 24 * 60 * 60 * 1e3;
1310
+ }
1311
+ const keyService = new WalletKeyService(API_BASE_URL, this.config.apiKey, this.config.apiVersion || "v1");
1312
+ const recipientKeyResponse = await keyService.getPublicKey(walletTarget);
1313
+ if (!recipientKeyResponse.exists || !recipientKeyResponse.certificate) {
1314
+ throw new Error(
1315
+ `Public key not found for recipient ${walletTarget}. The recipient needs to publish their public key first. They can do this by opening any shared signal sent to them, or visiting the app.`
1316
+ );
1317
+ }
1318
+ if (!verifyKeyCertificate(recipientKeyResponse.certificate)) {
1319
+ throw new Error("Recipient's key certificate signature verification failed");
1320
+ }
1321
+ if (Date.now() > recipientKeyResponse.certificate.expiresAt) {
1322
+ throw new Error("Recipient's key certificate has expired");
1323
+ }
1324
+ const recipientPubKey = Buffer.from(recipientKeyResponse.certificate.x25519PublicKey, "base64");
1325
+ let payloadText = "";
1326
+ if (options.message) {
1327
+ payloadText = options.message;
1328
+ }
1329
+ const payloadBytes = new TextEncoder().encode(payloadText);
1330
+ const sealedResult = await createSharedSignal(
1331
+ payloadBytes,
1332
+ new Uint8Array(recipientPubKey),
1333
+ walletTarget,
1334
+ walletCreator,
1335
+ expiresAtMs,
1336
+ signalId
1337
+ );
1338
+ let fileOptions;
1339
+ if (options.file) {
1340
+ const fileBytes = options.file.data instanceof ArrayBuffer ? new Uint8Array(options.file.data) : new Uint8Array(options.file.data);
1341
+ const fileSealedResult = await createSharedSignal(
1342
+ fileBytes,
1343
+ new Uint8Array(recipientPubKey),
1344
+ walletTarget,
1345
+ walletCreator,
1346
+ expiresAtMs,
1347
+ signalId + "_file"
1348
+ );
1349
+ const uploadResult = await this.uploadFile(wallet, {
1350
+ file: fileSealedResult.encryptedPayload,
1351
+ originalName: options.file.originalName,
1352
+ mimeType: options.file.mimeType
1353
+ });
1354
+ fileOptions = {
1355
+ storagePath: uploadResult.storagePath,
1356
+ payloadNonce: fileSealedResult.payloadNonce,
1357
+ originalName: options.file.originalName,
1358
+ mimeType: options.file.mimeType,
1359
+ sizeBytes: uploadResult.sizeBytes,
1360
+ sealedDataKey: fileSealedResult.sealedDataKey,
1361
+ sealedNonce: fileSealedResult.sealedNonce,
1362
+ senderEphemeralPublicKey: fileSealedResult.senderEphemeralPublicKey,
1363
+ aadHash: fileSealedResult.aadHash
1364
+ };
1365
+ }
1366
+ const signalOptions = {
1367
+ signalId,
1368
+ walletTarget,
1369
+ shareWithRecipient: true,
1370
+ mode: "shared_wallet",
1371
+ hasFile: !!options.file,
1372
+ hasMessage: !!options.message,
1373
+ cipherVersion: sealedResult.cipherVersion,
1374
+ encryptedText: options.message ? sealedResult.encryptedPayload : void 0,
1375
+ payloadNonce: options.message ? sealedResult.payloadNonce : void 0,
1376
+ sealedDataKey: sealedResult.sealedDataKey,
1377
+ sealedNonce: sealedResult.sealedNonce,
1378
+ senderEphemeralPublicKey: sealedResult.senderEphemeralPublicKey,
1379
+ aadHash: sealedResult.aadHash,
1380
+ file: fileOptions,
1381
+ onChainNotification: options.onChainNotification,
1382
+ expiresAt: new Date(expiresAtMs).toISOString()
1383
+ };
1384
+ const result = await this.createSignal(wallet, signalOptions);
1385
+ const shareableLink = `${FRONTEND_BASE_URL}/view/${result.signalId}`;
1386
+ return {
1387
+ ...result,
1388
+ keyBase64: "",
1389
+ // No key needed for X25519 signals
1390
+ shareableLink
1391
+ };
1392
+ }
1393
+ async uploadFileEncrypted(wallet, fileData, originalName, mimeType, key) {
1394
+ const encrypted = await encryptFile(fileData, key);
1395
+ const encryptedBase64 = this.arrayBufferToBase64(encrypted.encrypted);
1396
+ return this.uploadFile(wallet, {
1397
+ file: encryptedBase64,
1398
+ originalName,
1399
+ mimeType
1400
+ });
1401
+ }
1402
+ async burnSignal(wallet, signalId) {
1403
+ validateSignalId(signalId);
1404
+ validateWalletAddress(wallet.address);
1405
+ logger.info("Burning signal", { signalId });
1406
+ const authMessage = await this.createAuthMessage("burn", `signal ${signalId}`);
1407
+ const signature = await this.signMessage(wallet, authMessage);
1408
+ const requestBody = {
1409
+ signalId,
1410
+ wallet: {
1411
+ address: wallet.address,
1412
+ signature,
1413
+ message: authMessage
1414
+ }
1415
+ };
1416
+ const result = await this.makeRequest(
1417
+ this.getApiUrl("/signals/burn"),
1418
+ {
1419
+ method: "POST",
1420
+ body: JSON.stringify(requestBody)
1421
+ }
1422
+ );
1423
+ logger.info("Signal burned successfully", { signalId });
1424
+ return result;
1425
+ }
1426
+ };
1427
+
1428
+ // src/morse-sdk.ts
1429
+ var MorseSDK = class {
1430
+ constructor(config) {
1431
+ if (!config.apiKey) {
1432
+ throw new Error("apiKey is required. Please provide an API key in the SDK configuration.");
1433
+ }
1434
+ this.config = config;
1435
+ this.apiVersion = config.apiVersion || "v1";
1436
+ switch (this.apiVersion) {
1437
+ case "v1":
1438
+ this.contract = new MorseSDKV1(config);
1439
+ break;
1440
+ default:
1441
+ logger.warn(`Unknown API version: ${this.apiVersion}, defaulting to v1`);
1442
+ this.contract = new MorseSDKV1(config);
1443
+ this.apiVersion = "v1";
1444
+ }
1445
+ logger.debug("MorseSDK initialized", {
1446
+ apiVersion: this.apiVersion,
1447
+ contractVersion: this.contract.version
1448
+ });
1449
+ }
1450
+ getConfig() {
1451
+ return { ...this.config };
1452
+ }
1453
+ getApiVersion() {
1454
+ return this.apiVersion;
1455
+ }
1456
+ getContract() {
1457
+ return this.contract;
1458
+ }
1459
+ async createSignal(wallet, options) {
1460
+ return this.contract.createSignal(wallet, options);
1461
+ }
1462
+ async openSignal(wallet, signalId) {
1463
+ return this.contract.openSignal(wallet, signalId);
1464
+ }
1465
+ async listMySignals(wallet) {
1466
+ return this.contract.listMySignals(wallet);
1467
+ }
1468
+ async uploadFile(wallet, options) {
1469
+ return this.contract.uploadFile(wallet, options);
1470
+ }
1471
+ async downloadFile(wallet, signalId) {
1472
+ return this.contract.downloadFile(wallet, signalId);
1473
+ }
1474
+ async burnSignal(wallet, signalId) {
1475
+ return this.contract.burnSignal(wallet, signalId);
1476
+ }
1477
+ async createSignalEncrypted(wallet, options) {
1478
+ const v1Contract = this.contract;
1479
+ if (v1Contract.createSignalEncrypted) {
1480
+ return v1Contract.createSignalEncrypted(wallet, options);
1481
+ }
1482
+ throw new Error("createSignalEncrypted is not available in this API version");
1483
+ }
1484
+ async openSignalDecrypted(wallet, signalId, keyBase64) {
1485
+ const v1Contract = this.contract;
1486
+ if (v1Contract.openSignalDecrypted) {
1487
+ return v1Contract.openSignalDecrypted(wallet, signalId, keyBase64);
1488
+ }
1489
+ throw new Error("openSignalDecrypted is not available in this API version");
1490
+ }
1491
+ async uploadFileEncrypted(wallet, fileData, originalName, mimeType, key) {
1492
+ const v1Contract = this.contract;
1493
+ if (v1Contract.uploadFileEncrypted) {
1494
+ return v1Contract.uploadFileEncrypted(wallet, fileData, originalName, mimeType, key);
1495
+ }
1496
+ throw new Error("uploadFileEncrypted is not available in this API version");
1497
+ }
1498
+ async downloadFileDecrypted(wallet, signalId, keyBase64) {
1499
+ const v1Contract = this.contract;
1500
+ if (v1Contract.downloadFileDecrypted) {
1501
+ return v1Contract.downloadFileDecrypted(wallet, signalId, keyBase64);
1502
+ }
1503
+ throw new Error("downloadFileDecrypted is not available in this API version");
1504
+ }
1505
+ };
1506
+
1507
+ // src/helpers.ts
1508
+ function createWalletFromPrivateKey(config) {
1509
+ let ethersModule;
1510
+ try {
1511
+ ethersModule = __require("ethers");
1512
+ } catch {
1513
+ try {
1514
+ throw new Error("ethers must be installed");
1515
+ } catch (error) {
1516
+ throw new Error(
1517
+ "ethers is required for private key wallets. Install it: npm install ethers"
1518
+ );
1519
+ }
1520
+ }
1521
+ const { Wallet } = ethersModule;
1522
+ const tempWallet = new Wallet(config.privateKey);
1523
+ const address = tempWallet.address;
1524
+ const privateKey = config.privateKey;
1525
+ return {
1526
+ address,
1527
+ signMessage: async (message) => {
1528
+ const wallet = new Wallet(privateKey);
1529
+ const signature = await wallet.signMessage(message);
1530
+ return signature;
1531
+ }
1532
+ };
1533
+ }
1534
+ function createWalletFromPreSigned(config) {
1535
+ return {
1536
+ address: config.address,
1537
+ signMessage: async (message) => {
1538
+ if (message === config.message) {
1539
+ return config.signature;
1540
+ }
1541
+ throw new Error(
1542
+ "Pre-signed wallet can only sign the original message. Expected: " + config.message + ", Got: " + message
1543
+ );
1544
+ }
1545
+ };
1546
+ }
1547
+ async function createBrowserWallet(ethereum, address) {
1548
+ let currentAddress = address || "";
1549
+ if (!currentAddress) {
1550
+ const accounts = await ethereum.request({ method: "eth_accounts" });
1551
+ if (accounts.length === 0) {
1552
+ throw new Error("No wallet connected. Please connect your wallet first.");
1553
+ }
1554
+ currentAddress = accounts[0];
1555
+ }
1556
+ return {
1557
+ address: currentAddress,
1558
+ signMessage: async (message) => {
1559
+ try {
1560
+ const signature = await ethereum.request({
1561
+ method: "personal_sign",
1562
+ params: [message, currentAddress]
1563
+ });
1564
+ return signature;
1565
+ } catch (error) {
1566
+ if (error.code === 4001) {
1567
+ throw new Error("User rejected the signing request");
1568
+ }
1569
+ throw error;
1570
+ }
1571
+ }
1572
+ };
1573
+ }
1574
+
1575
+ // src/index.ts
1576
+ init_crypto_x25519();
1577
+
1578
+ // src/expiration.ts
1579
+ var Expiration = {
1580
+ /** 5 seconds */
1581
+ FIVE_SECONDS: "5s",
1582
+ /** 30 seconds */
1583
+ THIRTY_SECONDS: "30s",
1584
+ /** 1 minute */
1585
+ ONE_MINUTE: "1m",
1586
+ /** 5 minutes */
1587
+ FIVE_MINUTES: "5m",
1588
+ /** 30 minutes */
1589
+ THIRTY_MINUTES: "30m",
1590
+ /** 1 hour */
1591
+ ONE_HOUR: "1h",
1592
+ /** 6 hours */
1593
+ SIX_HOURS: "6h",
1594
+ /** 12 hours */
1595
+ TWELVE_HOURS: "12h",
1596
+ /** 24 hours (1 day) */
1597
+ ONE_DAY: "24h",
1598
+ /** 7 days (1 week) */
1599
+ ONE_WEEK: "7d",
1600
+ /** 30 days (1 month) */
1601
+ ONE_MONTH: "30d"
1602
+ };
1603
+
1604
+ export { Expiration, MorseSDK, MorseSDKError, MorseSDKV1, NetworkError, RateLimitError, SignalAlreadyUsedError, SignalExpiredError, SignalNotFoundError, ValidationError, WalletKeyService, WalletNotAllowedError, X25519_CIPHER_VERSION, createBrowserWallet, createKeyCertificate, createSharedSignal, createWalletFromPreSigned, createWalletFromPrivateKey, deriveKeyPairFromWalletSignature, extractSignalIdFromUrl, filterActiveSignals, formatExpiration, generateShareableLink, generateSignalId, getCipherVersion, getSignalUrl, getTimeUntilExpiration, isSignalExpired, isValidSignalId, isValidWalletAddress, logger, openSharedSignal, parseExpiresIn, sortSignalsByDate, validateSignalId, validateWalletAddress, verifyKeyCertificate };
1605
+ //# sourceMappingURL=index.mjs.map
1606
+ //# sourceMappingURL=index.mjs.map