@unicitylabs/sphere-sdk 0.2.1 → 0.2.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/README.md +22 -69
- package/dist/core/index.cjs +944 -465
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +127 -6
- package/dist/core/index.d.ts +127 -6
- package/dist/core/index.js +833 -351
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +115 -19
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +115 -19
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/browser/ipfs.cjs +3 -1
- package/dist/impl/browser/ipfs.cjs.map +1 -1
- package/dist/impl/browser/ipfs.js +3 -1
- package/dist/impl/browser/ipfs.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +85 -17
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +22 -0
- package/dist/impl/nodejs/index.d.ts +22 -0
- package/dist/impl/nodejs/index.js +85 -17
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/index.cjs +1083 -644
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -6
- package/dist/index.d.ts +118 -6
- package/dist/index.js +993 -554
- package/dist/index.js.map +1 -1
- package/dist/l1/index.cjs +18 -0
- package/dist/l1/index.cjs.map +1 -1
- package/dist/l1/index.d.cts +4 -0
- package/dist/l1/index.d.ts +4 -0
- package/dist/l1/index.js +18 -0
- package/dist/l1/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -82,7 +82,9 @@ var STORAGE_KEYS_GLOBAL = {
|
|
|
82
82
|
/** Nametag cache per address (separate from tracked addresses registry) */
|
|
83
83
|
ADDRESS_NAMETAGS: "address_nametags",
|
|
84
84
|
/** Active addresses registry (JSON: TrackedAddressesStorage) */
|
|
85
|
-
TRACKED_ADDRESSES: "tracked_addresses"
|
|
85
|
+
TRACKED_ADDRESSES: "tracked_addresses",
|
|
86
|
+
/** Last processed Nostr wallet event timestamp (unix seconds), keyed per pubkey */
|
|
87
|
+
LAST_WALLET_EVENT_TS: "last_wallet_event_ts"
|
|
86
88
|
};
|
|
87
89
|
var STORAGE_KEYS_ADDRESS = {
|
|
88
90
|
/** Pending transfers for this address */
|
|
@@ -550,12 +552,40 @@ var IndexedDBTokenStorageProvider = class {
|
|
|
550
552
|
return meta !== null;
|
|
551
553
|
}
|
|
552
554
|
async clear() {
|
|
553
|
-
if (
|
|
555
|
+
if (this.db) {
|
|
556
|
+
this.db.close();
|
|
557
|
+
this.db = null;
|
|
558
|
+
}
|
|
559
|
+
this.status = "disconnected";
|
|
560
|
+
const CLEAR_TIMEOUT = 1500;
|
|
561
|
+
const withTimeout = (promise, ms, label) => Promise.race([
|
|
562
|
+
promise,
|
|
563
|
+
new Promise(
|
|
564
|
+
(_, reject) => setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms)
|
|
565
|
+
)
|
|
566
|
+
]);
|
|
567
|
+
const deleteDb = (name) => new Promise((resolve) => {
|
|
568
|
+
const req = indexedDB.deleteDatabase(name);
|
|
569
|
+
req.onsuccess = () => resolve();
|
|
570
|
+
req.onerror = () => resolve();
|
|
571
|
+
req.onblocked = () => resolve();
|
|
572
|
+
});
|
|
554
573
|
try {
|
|
555
|
-
|
|
556
|
-
|
|
574
|
+
if (typeof indexedDB.databases === "function") {
|
|
575
|
+
const dbs = await withTimeout(
|
|
576
|
+
indexedDB.databases(),
|
|
577
|
+
CLEAR_TIMEOUT,
|
|
578
|
+
"indexedDB.databases()"
|
|
579
|
+
);
|
|
580
|
+
await Promise.all(
|
|
581
|
+
dbs.filter((db) => db.name?.startsWith(this.dbNamePrefix)).map((db) => deleteDb(db.name))
|
|
582
|
+
);
|
|
583
|
+
} else {
|
|
584
|
+
await deleteDb(this.dbName);
|
|
585
|
+
}
|
|
557
586
|
return true;
|
|
558
|
-
} catch {
|
|
587
|
+
} catch (err) {
|
|
588
|
+
console.warn("[IndexedDBTokenStorage] clear() failed:", err);
|
|
559
589
|
return false;
|
|
560
590
|
}
|
|
561
591
|
}
|
|
@@ -1309,6 +1339,9 @@ var NostrTransportProvider = class {
|
|
|
1309
1339
|
type = "p2p";
|
|
1310
1340
|
description = "P2P messaging via Nostr protocol";
|
|
1311
1341
|
config;
|
|
1342
|
+
storage = null;
|
|
1343
|
+
/** In-memory max event timestamp to avoid read-before-write races in updateLastEventTimestamp. */
|
|
1344
|
+
lastEventTs = 0;
|
|
1312
1345
|
identity = null;
|
|
1313
1346
|
keyManager = null;
|
|
1314
1347
|
status = "disconnected";
|
|
@@ -1334,6 +1367,7 @@ var NostrTransportProvider = class {
|
|
|
1334
1367
|
createWebSocket: config.createWebSocket,
|
|
1335
1368
|
generateUUID: config.generateUUID ?? defaultUUIDGenerator
|
|
1336
1369
|
};
|
|
1370
|
+
this.storage = config.storage ?? null;
|
|
1337
1371
|
}
|
|
1338
1372
|
// ===========================================================================
|
|
1339
1373
|
// BaseProvider Implementation
|
|
@@ -1372,7 +1406,14 @@ var NostrTransportProvider = class {
|
|
|
1372
1406
|
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1373
1407
|
}
|
|
1374
1408
|
});
|
|
1375
|
-
await
|
|
1409
|
+
await Promise.race([
|
|
1410
|
+
this.nostrClient.connect(...this.config.relays),
|
|
1411
|
+
new Promise(
|
|
1412
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
1413
|
+
`Transport connection timed out after ${this.config.timeout}ms`
|
|
1414
|
+
)), this.config.timeout)
|
|
1415
|
+
)
|
|
1416
|
+
]);
|
|
1376
1417
|
if (!this.nostrClient.isConnected()) {
|
|
1377
1418
|
throw new Error("Failed to connect to any relay");
|
|
1378
1419
|
}
|
|
@@ -1380,7 +1421,7 @@ var NostrTransportProvider = class {
|
|
|
1380
1421
|
this.emitEvent({ type: "transport:connected", timestamp: Date.now() });
|
|
1381
1422
|
this.log("Connected to", this.nostrClient.getConnectedRelays().size, "relays");
|
|
1382
1423
|
if (this.identity) {
|
|
1383
|
-
this.subscribeToEvents();
|
|
1424
|
+
await this.subscribeToEvents();
|
|
1384
1425
|
}
|
|
1385
1426
|
} catch (error) {
|
|
1386
1427
|
this.status = "error";
|
|
@@ -1533,11 +1574,18 @@ var NostrTransportProvider = class {
|
|
|
1533
1574
|
this.log("NostrClient reconnected to relay:", url);
|
|
1534
1575
|
}
|
|
1535
1576
|
});
|
|
1536
|
-
await
|
|
1537
|
-
|
|
1577
|
+
await Promise.race([
|
|
1578
|
+
this.nostrClient.connect(...this.config.relays),
|
|
1579
|
+
new Promise(
|
|
1580
|
+
(_, reject) => setTimeout(() => reject(new Error(
|
|
1581
|
+
`Transport reconnection timed out after ${this.config.timeout}ms`
|
|
1582
|
+
)), this.config.timeout)
|
|
1583
|
+
)
|
|
1584
|
+
]);
|
|
1585
|
+
await this.subscribeToEvents();
|
|
1538
1586
|
oldClient.disconnect();
|
|
1539
1587
|
} else if (this.isConnected()) {
|
|
1540
|
-
this.subscribeToEvents();
|
|
1588
|
+
await this.subscribeToEvents();
|
|
1541
1589
|
}
|
|
1542
1590
|
}
|
|
1543
1591
|
/**
|
|
@@ -1678,7 +1726,7 @@ var NostrTransportProvider = class {
|
|
|
1678
1726
|
return this.resolveNametagInfo(identifier);
|
|
1679
1727
|
}
|
|
1680
1728
|
async resolveNametag(nametag) {
|
|
1681
|
-
this.
|
|
1729
|
+
this.ensureConnected();
|
|
1682
1730
|
const hashedNametag = (0, import_nostr_js_sdk.hashNametag)(nametag);
|
|
1683
1731
|
let events = await this.queryEvents({
|
|
1684
1732
|
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
@@ -1702,7 +1750,7 @@ var NostrTransportProvider = class {
|
|
|
1702
1750
|
return null;
|
|
1703
1751
|
}
|
|
1704
1752
|
async resolveNametagInfo(nametag) {
|
|
1705
|
-
this.
|
|
1753
|
+
this.ensureConnected();
|
|
1706
1754
|
const hashedNametag = (0, import_nostr_js_sdk.hashNametag)(nametag);
|
|
1707
1755
|
let events = await this.queryEvents({
|
|
1708
1756
|
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
@@ -1779,7 +1827,7 @@ var NostrTransportProvider = class {
|
|
|
1779
1827
|
* Works with both new identity binding events and legacy nametag binding events.
|
|
1780
1828
|
*/
|
|
1781
1829
|
async resolveAddressInfo(address) {
|
|
1782
|
-
this.
|
|
1830
|
+
this.ensureConnected();
|
|
1783
1831
|
const addressHash = hashAddressForTag(address);
|
|
1784
1832
|
const events = await this.queryEvents({
|
|
1785
1833
|
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
@@ -1814,7 +1862,7 @@ var NostrTransportProvider = class {
|
|
|
1814
1862
|
* Queries binding events authored by the given pubkey.
|
|
1815
1863
|
*/
|
|
1816
1864
|
async resolveTransportPubkeyInfo(transportPubkey) {
|
|
1817
|
-
this.
|
|
1865
|
+
this.ensureConnected();
|
|
1818
1866
|
const events = await this.queryEvents({
|
|
1819
1867
|
kinds: [EVENT_KINDS.NAMETAG_BINDING],
|
|
1820
1868
|
authors: [transportPubkey],
|
|
@@ -2065,10 +2113,31 @@ var NostrTransportProvider = class {
|
|
|
2065
2113
|
this.handleBroadcast(event);
|
|
2066
2114
|
break;
|
|
2067
2115
|
}
|
|
2116
|
+
if (event.created_at && this.storage && this.keyManager) {
|
|
2117
|
+
const kind = event.kind;
|
|
2118
|
+
if (kind === EVENT_KINDS.DIRECT_MESSAGE || kind === EVENT_KINDS.TOKEN_TRANSFER || kind === EVENT_KINDS.PAYMENT_REQUEST || kind === EVENT_KINDS.PAYMENT_REQUEST_RESPONSE) {
|
|
2119
|
+
this.updateLastEventTimestamp(event.created_at);
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2068
2122
|
} catch (error) {
|
|
2069
2123
|
this.log("Failed to handle event:", error);
|
|
2070
2124
|
}
|
|
2071
2125
|
}
|
|
2126
|
+
/**
|
|
2127
|
+
* Save the max event timestamp to storage (fire-and-forget, no await needed by caller).
|
|
2128
|
+
* Uses in-memory `lastEventTs` to avoid read-before-write race conditions
|
|
2129
|
+
* when multiple events arrive in quick succession.
|
|
2130
|
+
*/
|
|
2131
|
+
updateLastEventTimestamp(createdAt) {
|
|
2132
|
+
if (!this.storage || !this.keyManager) return;
|
|
2133
|
+
if (createdAt <= this.lastEventTs) return;
|
|
2134
|
+
this.lastEventTs = createdAt;
|
|
2135
|
+
const pubkey = this.keyManager.getPublicKeyHex();
|
|
2136
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${pubkey.slice(0, 16)}`;
|
|
2137
|
+
this.storage.set(storageKey, createdAt.toString()).catch((err) => {
|
|
2138
|
+
this.log("Failed to save last event timestamp:", err);
|
|
2139
|
+
});
|
|
2140
|
+
}
|
|
2072
2141
|
async handleDirectMessage(event) {
|
|
2073
2142
|
this.log("Ignoring NIP-04 kind 4 event (DMs use NIP-17):", event.id?.slice(0, 12));
|
|
2074
2143
|
}
|
|
@@ -2147,6 +2216,7 @@ var NostrTransportProvider = class {
|
|
|
2147
2216
|
const request = {
|
|
2148
2217
|
id: event.id,
|
|
2149
2218
|
senderTransportPubkey: event.pubkey,
|
|
2219
|
+
senderNametag: requestData.recipientNametag,
|
|
2150
2220
|
request: {
|
|
2151
2221
|
requestId: requestData.requestId,
|
|
2152
2222
|
amount: requestData.amount,
|
|
@@ -2301,7 +2371,7 @@ var NostrTransportProvider = class {
|
|
|
2301
2371
|
// Track subscription IDs for cleanup
|
|
2302
2372
|
walletSubscriptionId = null;
|
|
2303
2373
|
chatSubscriptionId = null;
|
|
2304
|
-
subscribeToEvents() {
|
|
2374
|
+
async subscribeToEvents() {
|
|
2305
2375
|
this.log("subscribeToEvents called, identity:", !!this.identity, "keyManager:", !!this.keyManager, "nostrClient:", !!this.nostrClient);
|
|
2306
2376
|
if (!this.identity || !this.keyManager || !this.nostrClient) {
|
|
2307
2377
|
this.log("subscribeToEvents: skipped - no identity, keyManager, or nostrClient");
|
|
@@ -2321,6 +2391,27 @@ var NostrTransportProvider = class {
|
|
|
2321
2391
|
}
|
|
2322
2392
|
const nostrPubkey = this.keyManager.getPublicKeyHex();
|
|
2323
2393
|
this.log("Subscribing with Nostr pubkey:", nostrPubkey);
|
|
2394
|
+
let since;
|
|
2395
|
+
if (this.storage) {
|
|
2396
|
+
const storageKey = `${STORAGE_KEYS_GLOBAL.LAST_WALLET_EVENT_TS}_${nostrPubkey.slice(0, 16)}`;
|
|
2397
|
+
try {
|
|
2398
|
+
const stored = await this.storage.get(storageKey);
|
|
2399
|
+
if (stored) {
|
|
2400
|
+
since = parseInt(stored, 10);
|
|
2401
|
+
this.lastEventTs = since;
|
|
2402
|
+
this.log("Resuming from stored event timestamp:", since);
|
|
2403
|
+
} else {
|
|
2404
|
+
since = Math.floor(Date.now() / 1e3);
|
|
2405
|
+
this.log("No stored timestamp, starting from now:", since);
|
|
2406
|
+
}
|
|
2407
|
+
} catch (err) {
|
|
2408
|
+
this.log("Failed to read last event timestamp, falling back to now:", err);
|
|
2409
|
+
since = Math.floor(Date.now() / 1e3);
|
|
2410
|
+
}
|
|
2411
|
+
} else {
|
|
2412
|
+
since = Math.floor(Date.now() / 1e3) - 86400;
|
|
2413
|
+
this.log("No storage adapter, using 24h fallback");
|
|
2414
|
+
}
|
|
2324
2415
|
const walletFilter = new import_nostr_js_sdk.Filter();
|
|
2325
2416
|
walletFilter.kinds = [
|
|
2326
2417
|
EVENT_KINDS.DIRECT_MESSAGE,
|
|
@@ -2329,7 +2420,7 @@ var NostrTransportProvider = class {
|
|
|
2329
2420
|
EVENT_KINDS.PAYMENT_REQUEST_RESPONSE
|
|
2330
2421
|
];
|
|
2331
2422
|
walletFilter["#p"] = [nostrPubkey];
|
|
2332
|
-
walletFilter.since =
|
|
2423
|
+
walletFilter.since = since;
|
|
2333
2424
|
this.walletSubscriptionId = this.nostrClient.subscribe(walletFilter, {
|
|
2334
2425
|
onEvent: (event) => {
|
|
2335
2426
|
this.log("Received wallet event kind:", event.kind, "id:", event.id?.slice(0, 12));
|
|
@@ -2432,10 +2523,13 @@ var NostrTransportProvider = class {
|
|
|
2432
2523
|
// ===========================================================================
|
|
2433
2524
|
// Private: Helpers
|
|
2434
2525
|
// ===========================================================================
|
|
2435
|
-
|
|
2526
|
+
ensureConnected() {
|
|
2436
2527
|
if (!this.isConnected()) {
|
|
2437
2528
|
throw new Error("NostrTransportProvider not connected");
|
|
2438
2529
|
}
|
|
2530
|
+
}
|
|
2531
|
+
ensureReady() {
|
|
2532
|
+
this.ensureConnected();
|
|
2439
2533
|
if (!this.identity) {
|
|
2440
2534
|
throw new Error("Identity not set");
|
|
2441
2535
|
}
|
|
@@ -3236,15 +3330,17 @@ function createBrowserProviders(config) {
|
|
|
3236
3330
|
const l1Config = resolveL1Config(network, config?.l1);
|
|
3237
3331
|
const tokenSyncConfig = resolveTokenSyncConfig(network, config?.tokenSync);
|
|
3238
3332
|
const priceConfig = resolvePriceConfig(config?.price);
|
|
3333
|
+
const storage = createLocalStorageProvider(config?.storage);
|
|
3239
3334
|
return {
|
|
3240
|
-
storage
|
|
3335
|
+
storage,
|
|
3241
3336
|
transport: createNostrTransportProvider({
|
|
3242
3337
|
relays: transportConfig.relays,
|
|
3243
3338
|
timeout: transportConfig.timeout,
|
|
3244
3339
|
autoReconnect: transportConfig.autoReconnect,
|
|
3245
3340
|
reconnectDelay: transportConfig.reconnectDelay,
|
|
3246
3341
|
maxReconnectAttempts: transportConfig.maxReconnectAttempts,
|
|
3247
|
-
debug: transportConfig.debug
|
|
3342
|
+
debug: transportConfig.debug,
|
|
3343
|
+
storage
|
|
3248
3344
|
}),
|
|
3249
3345
|
oracle: createUnicityAggregatorProvider({
|
|
3250
3346
|
url: oracleConfig.url,
|