@unicitylabs/sphere-sdk 0.14.1 → 0.14.2
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/connect/index.cjs +1 -1
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.js +1 -1
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +69 -27
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +21 -1
- package/dist/core/index.d.ts +21 -1
- package/dist/core/index.js +69 -27
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +1 -1
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.js +1 -1
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +33 -0
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +33 -0
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +33 -0
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +41 -1
- package/dist/impl/nodejs/index.d.ts +41 -1
- package/dist/impl/nodejs/index.js +33 -0
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/impl/shared/wallet-api/index.d.cts +21 -1
- package/dist/impl/shared/wallet-api/index.d.ts +21 -1
- package/dist/index.cjs +69 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +69 -27
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +36 -26
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +16 -6
- package/dist/modules/payments-v2/index.d.ts +16 -6
- package/dist/modules/payments-v2/index.js +35 -26
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
type ConnectionStatus$1 = 'connected' | 'degraded' | 'offline';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* SDK2 Core Types
|
|
3
5
|
* Platform-independent type definitions
|
|
@@ -308,6 +310,19 @@ interface PeerInfo {
|
|
|
308
310
|
chainPubkey: string;
|
|
309
311
|
/** L3 DIRECT address (DIRECT://...) */
|
|
310
312
|
directAddress: string;
|
|
313
|
+
/**
|
|
314
|
+
* Network the peer's identity binding DECLARES ('testnet2', …) — absent when
|
|
315
|
+
* the binding carries none, which is every binding published so far. Parsed
|
|
316
|
+
* only, never invented: money refuses a PROVEN foreign network (§5.6).
|
|
317
|
+
*
|
|
318
|
+
* Absent is also the STRUCTURAL answer on the `@nametag` and `DIRECT://`
|
|
319
|
+
* routes of the Nostr transport, whichever binding is out there: those two
|
|
320
|
+
* resolve through nostr-js-sdk's `queryBindingBy*`, whose parser drops every
|
|
321
|
+
* content field outside its whitelist and never hands back the signed event
|
|
322
|
+
* (#734 tracks the upstream fix). Only routes that parse the raw event —
|
|
323
|
+
* `resolveTransportPubkeyInfo`, `discoverAddresses` — can populate it today.
|
|
324
|
+
*/
|
|
325
|
+
network?: string;
|
|
311
326
|
/** Event timestamp */
|
|
312
327
|
timestamp: number;
|
|
313
328
|
}
|
|
@@ -459,12 +474,17 @@ interface FacadeSession {
|
|
|
459
474
|
* stream nudge resumes. The facade registers handleEpochChange here.
|
|
460
475
|
*/
|
|
461
476
|
subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
|
|
477
|
+
/**
|
|
478
|
+
* The session's CURRENT status — REQUIRED, and the ONE source `connectionStatus()`
|
|
479
|
+
* and `connection:status` both come from ('offline' before first contact).
|
|
480
|
+
*/
|
|
481
|
+
status(): ConnectionStatus$1;
|
|
462
482
|
/**
|
|
463
483
|
* Optional connection-status feed (same wiring pattern as the streams; the
|
|
464
484
|
* emission point is the session's existing `connection:status` transition).
|
|
465
485
|
* The facade's heartbeat resets its backoff on a 'connected' recovery.
|
|
466
486
|
*/
|
|
467
|
-
subscribeStatus?(handler: (status:
|
|
487
|
+
subscribeStatus?(handler: (status: ConnectionStatus$1) => void): () => void;
|
|
468
488
|
}
|
|
469
489
|
interface IntentWireLike {
|
|
470
490
|
transferId: string;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
type ConnectionStatus$1 = 'connected' | 'degraded' | 'offline';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* SDK2 Core Types
|
|
3
5
|
* Platform-independent type definitions
|
|
@@ -308,6 +310,19 @@ interface PeerInfo {
|
|
|
308
310
|
chainPubkey: string;
|
|
309
311
|
/** L3 DIRECT address (DIRECT://...) */
|
|
310
312
|
directAddress: string;
|
|
313
|
+
/**
|
|
314
|
+
* Network the peer's identity binding DECLARES ('testnet2', …) — absent when
|
|
315
|
+
* the binding carries none, which is every binding published so far. Parsed
|
|
316
|
+
* only, never invented: money refuses a PROVEN foreign network (§5.6).
|
|
317
|
+
*
|
|
318
|
+
* Absent is also the STRUCTURAL answer on the `@nametag` and `DIRECT://`
|
|
319
|
+
* routes of the Nostr transport, whichever binding is out there: those two
|
|
320
|
+
* resolve through nostr-js-sdk's `queryBindingBy*`, whose parser drops every
|
|
321
|
+
* content field outside its whitelist and never hands back the signed event
|
|
322
|
+
* (#734 tracks the upstream fix). Only routes that parse the raw event —
|
|
323
|
+
* `resolveTransportPubkeyInfo`, `discoverAddresses` — can populate it today.
|
|
324
|
+
*/
|
|
325
|
+
network?: string;
|
|
311
326
|
/** Event timestamp */
|
|
312
327
|
timestamp: number;
|
|
313
328
|
}
|
|
@@ -459,12 +474,17 @@ interface FacadeSession {
|
|
|
459
474
|
* stream nudge resumes. The facade registers handleEpochChange here.
|
|
460
475
|
*/
|
|
461
476
|
subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
|
|
477
|
+
/**
|
|
478
|
+
* The session's CURRENT status — REQUIRED, and the ONE source `connectionStatus()`
|
|
479
|
+
* and `connection:status` both come from ('offline' before first contact).
|
|
480
|
+
*/
|
|
481
|
+
status(): ConnectionStatus$1;
|
|
462
482
|
/**
|
|
463
483
|
* Optional connection-status feed (same wiring pattern as the streams; the
|
|
464
484
|
* emission point is the session's existing `connection:status` transition).
|
|
465
485
|
* The facade's heartbeat resets its backoff on a 'connected' recovery.
|
|
466
486
|
*/
|
|
467
|
-
subscribeStatus?(handler: (status:
|
|
487
|
+
subscribeStatus?(handler: (status: ConnectionStatus$1) => void): () => void;
|
|
468
488
|
}
|
|
469
489
|
interface IntentWireLike {
|
|
470
490
|
transferId: string;
|
package/dist/index.cjs
CHANGED
|
@@ -983,6 +983,15 @@ var LIMITS = {
|
|
|
983
983
|
var COMPOSING_INDICATOR_KIND = 25050;
|
|
984
984
|
var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
|
|
985
985
|
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
986
|
+
function declaredNetwork(event) {
|
|
987
|
+
let value;
|
|
988
|
+
try {
|
|
989
|
+
value = JSON.parse(event.content)?.network;
|
|
990
|
+
} catch {
|
|
991
|
+
return void 0;
|
|
992
|
+
}
|
|
993
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
994
|
+
}
|
|
986
995
|
var NostrTransportProvider = class _NostrTransportProvider {
|
|
987
996
|
id = "nostr";
|
|
988
997
|
name = "Nostr Transport";
|
|
@@ -1479,6 +1488,26 @@ var NostrTransportProvider = class _NostrTransportProvider {
|
|
|
1479
1488
|
}
|
|
1480
1489
|
/**
|
|
1481
1490
|
* Convert a BindingInfo (from nostr-js-sdk) to PeerInfo (sphere-sdk type).
|
|
1491
|
+
*
|
|
1492
|
+
* `network` IS DELIBERATELY ABSENT HERE — a live limitation, not an oversight
|
|
1493
|
+
* (#734). `queryBindingByNametag`/`queryBindingByAddress` are the whole public
|
|
1494
|
+
* resolution API for `@nametag` and `DIRECT://`, and both hardcode
|
|
1495
|
+
* `parseBindingInfo` inside the SDK's PRIVATE `queryWithFirstSeenWins`: the
|
|
1496
|
+
* caller never sees the signed event that resolution selected, and the parse
|
|
1497
|
+
* has already dropped every content field outside its whitelist. Re-querying
|
|
1498
|
+
* raw would mean re-deriving UNIP-01 ownership ourselves (marker preference,
|
|
1499
|
+
* cross-author first-seen-wins, ambiguity→null, multi-relay settle — all
|
|
1500
|
+
* private), and a second resolver that can disagree with the SDK's is worse
|
|
1501
|
+
* than a missing field on a MONEY guard. Nothing can declare a network today
|
|
1502
|
+
* regardless: the publish builders whitelist the same fields.
|
|
1503
|
+
*
|
|
1504
|
+
* CONSEQUENCE, stated plainly: for `@nametag` and `DIRECT://` recipients the
|
|
1505
|
+
* §5.6 cross-network guard can only SIGNAL (`recipient:network-unverified`),
|
|
1506
|
+
* never refuse. Upstream #734 must add `network` to the binding builders AND
|
|
1507
|
+
* to `BindingInfo`; the tripwire in
|
|
1508
|
+
* tests/unit/payments-v2/recipient-network.transport.test.ts fails the day it
|
|
1509
|
+
* lands, so this wiring cannot be forgotten. Do not "fix" this by inventing a
|
|
1510
|
+
* value — a stamped network is what made the guard unfalsifiable (#733).
|
|
1482
1511
|
*/
|
|
1483
1512
|
async bindingInfoToPeerInfo(binding, nametag) {
|
|
1484
1513
|
return {
|
|
@@ -1505,11 +1534,13 @@ var NostrTransportProvider = class _NostrTransportProvider {
|
|
|
1505
1534
|
const bindingEvent = events[0];
|
|
1506
1535
|
try {
|
|
1507
1536
|
const content = JSON.parse(bindingEvent.content);
|
|
1537
|
+
const network = declaredNetwork(bindingEvent);
|
|
1508
1538
|
return {
|
|
1509
1539
|
nametag: content.nametag || void 0,
|
|
1510
1540
|
transportPubkey: bindingEvent.pubkey,
|
|
1511
1541
|
chainPubkey: content.public_key || "",
|
|
1512
1542
|
directAddress: content.direct_address || "",
|
|
1543
|
+
...network !== void 0 ? { network } : {},
|
|
1513
1544
|
timestamp: bindingEvent.created_at * 1e3
|
|
1514
1545
|
};
|
|
1515
1546
|
} catch {
|
|
@@ -1545,11 +1576,13 @@ var NostrTransportProvider = class _NostrTransportProvider {
|
|
|
1545
1576
|
for (const [pubkey, event] of byAuthor) {
|
|
1546
1577
|
try {
|
|
1547
1578
|
const content = JSON.parse(event.content);
|
|
1579
|
+
const network = declaredNetwork(event);
|
|
1548
1580
|
results.push({
|
|
1549
1581
|
nametag: content.nametag || void 0,
|
|
1550
1582
|
transportPubkey: pubkey,
|
|
1551
1583
|
chainPubkey: content.public_key || "",
|
|
1552
1584
|
directAddress: content.direct_address || "",
|
|
1585
|
+
...network !== void 0 ? { network } : {},
|
|
1553
1586
|
timestamp: event.created_at * 1e3
|
|
1554
1587
|
});
|
|
1555
1588
|
} catch {
|
|
@@ -12139,6 +12172,31 @@ function distinctOpCount(entries) {
|
|
|
12139
12172
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
12140
12173
|
}
|
|
12141
12174
|
|
|
12175
|
+
// modules/payments-v2/recipient.ts
|
|
12176
|
+
var ATTENTION_RECIPIENT_NETWORK_UNVERIFIED = "recipient:network-unverified";
|
|
12177
|
+
async function requireSameNetworkRecipient(deps, identifier) {
|
|
12178
|
+
const recipient = await deps.resolveRecipient(identifier);
|
|
12179
|
+
if (recipient === null || recipient.chainPubkey === "") {
|
|
12180
|
+
throw new SphereError(`Recipient ${identifier} has no published chain pubkey`, "INVALID_RECIPIENT");
|
|
12181
|
+
}
|
|
12182
|
+
if (recipient.network === null) {
|
|
12183
|
+
deps.emit("transfer:attention", {
|
|
12184
|
+
transferId: "",
|
|
12185
|
+
// the gate precedes plan allocation — no transferId exists yet
|
|
12186
|
+
code: ATTENTION_RECIPIENT_NETWORK_UNVERIFIED,
|
|
12187
|
+
detail: `could not verify the network of recipient ${identifier} \u2014 assuming this session's "${deps.network}"`
|
|
12188
|
+
});
|
|
12189
|
+
return recipient;
|
|
12190
|
+
}
|
|
12191
|
+
if (recipient.network !== deps.network) {
|
|
12192
|
+
throw new SphereError(
|
|
12193
|
+
`Recipient ${identifier} is on network "${recipient.network}" but this session is on "${deps.network}" \u2014 a cross-network deposit is unrecoverable`,
|
|
12194
|
+
"INVALID_RECIPIENT"
|
|
12195
|
+
);
|
|
12196
|
+
}
|
|
12197
|
+
return recipient;
|
|
12198
|
+
}
|
|
12199
|
+
|
|
12142
12200
|
// modules/payments-v2/restore.ts
|
|
12143
12201
|
var ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
|
|
12144
12202
|
var ALL_STREAMS = ["inventory", "mailbox", "payment_requests", "history"];
|
|
@@ -13731,6 +13789,10 @@ var PaymentsFacade = class {
|
|
|
13731
13789
|
history(page) {
|
|
13732
13790
|
return this.historyStore.page(page ?? {});
|
|
13733
13791
|
}
|
|
13792
|
+
/** §4: read through to the session — the same value `connection:status` carries (sphere#473). No session ⇒ 'offline'. */
|
|
13793
|
+
connectionStatus() {
|
|
13794
|
+
return this.started ? this.deps.session.status() : "offline";
|
|
13795
|
+
}
|
|
13734
13796
|
/** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
|
|
13735
13797
|
pendingTransfers() {
|
|
13736
13798
|
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
@@ -13805,7 +13867,7 @@ var PaymentsFacade = class {
|
|
|
13805
13867
|
this.deps.emit("transfer:updated", failed);
|
|
13806
13868
|
}
|
|
13807
13869
|
async sendWithPolicy(request, run) {
|
|
13808
|
-
const recipient = await this.
|
|
13870
|
+
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
13809
13871
|
for (let attempt = 0; ; attempt++) {
|
|
13810
13872
|
let ctx;
|
|
13811
13873
|
try {
|
|
@@ -13828,20 +13890,6 @@ var PaymentsFacade = class {
|
|
|
13828
13890
|
if (surfaced !== null) return surfaced;
|
|
13829
13891
|
}
|
|
13830
13892
|
}
|
|
13831
|
-
/** §5.6 cross-network deposit trap: refused BEFORE any reserve/certification. */
|
|
13832
|
-
async requireSameNetworkRecipient(identifier) {
|
|
13833
|
-
const recipient = await this.deps.resolveRecipient(identifier);
|
|
13834
|
-
if (recipient === null || recipient.chainPubkey === "") {
|
|
13835
|
-
throw new SphereError(`Recipient ${identifier} has no published chain pubkey`, "INVALID_RECIPIENT");
|
|
13836
|
-
}
|
|
13837
|
-
if (recipient.network !== this.deps.network) {
|
|
13838
|
-
throw new SphereError(
|
|
13839
|
-
`Recipient ${identifier} is on network "${recipient.network}" but this session is on "${this.deps.network}" \u2014 a cross-network deposit is unrecoverable`,
|
|
13840
|
-
"INVALID_RECIPIENT"
|
|
13841
|
-
);
|
|
13842
|
-
}
|
|
13843
|
-
return recipient;
|
|
13844
|
-
}
|
|
13845
13893
|
async runAttempt(ctx) {
|
|
13846
13894
|
this.activeMoneyOps.add(ctx.transferId);
|
|
13847
13895
|
try {
|
|
@@ -14218,19 +14266,12 @@ var PaymentsFacade = class {
|
|
|
14218
14266
|
}
|
|
14219
14267
|
toUiToken(tokenId, coinId, amount) {
|
|
14220
14268
|
const now = this.nowMs();
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
{ coinId, amount: amount.toString() },
|
|
14225
|
-
this.deps.registry,
|
|
14226
|
-
{ transferring: true, suspectedSpent: false }
|
|
14227
|
-
);
|
|
14269
|
+
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
14270
|
+
const flags = { transferring: true, suspectedSpent: false };
|
|
14271
|
+
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, this.deps.registry, flags);
|
|
14228
14272
|
}
|
|
14229
14273
|
track(op) {
|
|
14230
|
-
const settled = op.then(
|
|
14231
|
-
() => void 0,
|
|
14232
|
-
() => void 0
|
|
14233
|
-
);
|
|
14274
|
+
const settled = op.then(() => void 0, () => void 0);
|
|
14234
14275
|
this.pendingOps.add(settled);
|
|
14235
14276
|
void settled.finally(() => this.pendingOps.delete(settled));
|
|
14236
14277
|
return op;
|
|
@@ -15331,7 +15372,8 @@ async function resolveRecipientInfo(identifier, network, resolvePeer) {
|
|
|
15331
15372
|
if (!peer?.chainPubkey) return null;
|
|
15332
15373
|
return {
|
|
15333
15374
|
chainPubkey: peer.chainPubkey,
|
|
15334
|
-
|
|
15375
|
+
// Only what the binding declares (blank declares nothing) — never a local stamp.
|
|
15376
|
+
network: peer.network || null,
|
|
15335
15377
|
...peer.nametag !== void 0 ? { nametag: peer.nametag } : {}
|
|
15336
15378
|
};
|
|
15337
15379
|
}
|