@unicitylabs/sphere-sdk 0.14.1-dev.2 → 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
package/dist/core/index.d.cts
CHANGED
|
@@ -165,6 +165,7 @@ interface PendingTransfer {
|
|
|
165
165
|
deliveryPending: boolean;
|
|
166
166
|
createdAt: number;
|
|
167
167
|
}
|
|
168
|
+
type ConnectionStatus$1 = 'connected' | 'degraded' | 'offline';
|
|
168
169
|
interface PaymentsV2 {
|
|
169
170
|
assets(coinId?: string): Promise<Asset[]>;
|
|
170
171
|
tokens(filter?: {
|
|
@@ -181,6 +182,7 @@ interface PaymentsV2 {
|
|
|
181
182
|
}>;
|
|
182
183
|
pendingTransfers(): Promise<PendingTransfer[]>;
|
|
183
184
|
resumeNow(): Promise<void>;
|
|
185
|
+
connectionStatus(): ConnectionStatus$1;
|
|
184
186
|
readonly requests: PaymentsRequestsApi;
|
|
185
187
|
}
|
|
186
188
|
|
|
@@ -1138,6 +1140,19 @@ interface PeerInfo {
|
|
|
1138
1140
|
chainPubkey: string;
|
|
1139
1141
|
/** L3 DIRECT address (DIRECT://...) */
|
|
1140
1142
|
directAddress: string;
|
|
1143
|
+
/**
|
|
1144
|
+
* Network the peer's identity binding DECLARES ('testnet2', …) — absent when
|
|
1145
|
+
* the binding carries none, which is every binding published so far. Parsed
|
|
1146
|
+
* only, never invented: money refuses a PROVEN foreign network (§5.6).
|
|
1147
|
+
*
|
|
1148
|
+
* Absent is also the STRUCTURAL answer on the `@nametag` and `DIRECT://`
|
|
1149
|
+
* routes of the Nostr transport, whichever binding is out there: those two
|
|
1150
|
+
* resolve through nostr-js-sdk's `queryBindingBy*`, whose parser drops every
|
|
1151
|
+
* content field outside its whitelist and never hands back the signed event
|
|
1152
|
+
* (#734 tracks the upstream fix). Only routes that parse the raw event —
|
|
1153
|
+
* `resolveTransportPubkeyInfo`, `discoverAddresses` — can populate it today.
|
|
1154
|
+
*/
|
|
1155
|
+
network?: string;
|
|
1141
1156
|
/** Event timestamp */
|
|
1142
1157
|
timestamp: number;
|
|
1143
1158
|
}
|
|
@@ -2640,12 +2655,17 @@ interface FacadeSession {
|
|
|
2640
2655
|
* stream nudge resumes. The facade registers handleEpochChange here.
|
|
2641
2656
|
*/
|
|
2642
2657
|
subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
|
|
2658
|
+
/**
|
|
2659
|
+
* The session's CURRENT status — REQUIRED, and the ONE source `connectionStatus()`
|
|
2660
|
+
* and `connection:status` both come from ('offline' before first contact).
|
|
2661
|
+
*/
|
|
2662
|
+
status(): ConnectionStatus$1;
|
|
2643
2663
|
/**
|
|
2644
2664
|
* Optional connection-status feed (same wiring pattern as the streams; the
|
|
2645
2665
|
* emission point is the session's existing `connection:status` transition).
|
|
2646
2666
|
* The facade's heartbeat resets its backoff on a 'connected' recovery.
|
|
2647
2667
|
*/
|
|
2648
|
-
subscribeStatus?(handler: (status:
|
|
2668
|
+
subscribeStatus?(handler: (status: ConnectionStatus$1) => void): () => void;
|
|
2649
2669
|
}
|
|
2650
2670
|
interface IntentWireLike {
|
|
2651
2671
|
transferId: string;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -165,6 +165,7 @@ interface PendingTransfer {
|
|
|
165
165
|
deliveryPending: boolean;
|
|
166
166
|
createdAt: number;
|
|
167
167
|
}
|
|
168
|
+
type ConnectionStatus$1 = 'connected' | 'degraded' | 'offline';
|
|
168
169
|
interface PaymentsV2 {
|
|
169
170
|
assets(coinId?: string): Promise<Asset[]>;
|
|
170
171
|
tokens(filter?: {
|
|
@@ -181,6 +182,7 @@ interface PaymentsV2 {
|
|
|
181
182
|
}>;
|
|
182
183
|
pendingTransfers(): Promise<PendingTransfer[]>;
|
|
183
184
|
resumeNow(): Promise<void>;
|
|
185
|
+
connectionStatus(): ConnectionStatus$1;
|
|
184
186
|
readonly requests: PaymentsRequestsApi;
|
|
185
187
|
}
|
|
186
188
|
|
|
@@ -1138,6 +1140,19 @@ interface PeerInfo {
|
|
|
1138
1140
|
chainPubkey: string;
|
|
1139
1141
|
/** L3 DIRECT address (DIRECT://...) */
|
|
1140
1142
|
directAddress: string;
|
|
1143
|
+
/**
|
|
1144
|
+
* Network the peer's identity binding DECLARES ('testnet2', …) — absent when
|
|
1145
|
+
* the binding carries none, which is every binding published so far. Parsed
|
|
1146
|
+
* only, never invented: money refuses a PROVEN foreign network (§5.6).
|
|
1147
|
+
*
|
|
1148
|
+
* Absent is also the STRUCTURAL answer on the `@nametag` and `DIRECT://`
|
|
1149
|
+
* routes of the Nostr transport, whichever binding is out there: those two
|
|
1150
|
+
* resolve through nostr-js-sdk's `queryBindingBy*`, whose parser drops every
|
|
1151
|
+
* content field outside its whitelist and never hands back the signed event
|
|
1152
|
+
* (#734 tracks the upstream fix). Only routes that parse the raw event —
|
|
1153
|
+
* `resolveTransportPubkeyInfo`, `discoverAddresses` — can populate it today.
|
|
1154
|
+
*/
|
|
1155
|
+
network?: string;
|
|
1141
1156
|
/** Event timestamp */
|
|
1142
1157
|
timestamp: number;
|
|
1143
1158
|
}
|
|
@@ -2640,12 +2655,17 @@ interface FacadeSession {
|
|
|
2640
2655
|
* stream nudge resumes. The facade registers handleEpochChange here.
|
|
2641
2656
|
*/
|
|
2642
2657
|
subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
|
|
2658
|
+
/**
|
|
2659
|
+
* The session's CURRENT status — REQUIRED, and the ONE source `connectionStatus()`
|
|
2660
|
+
* and `connection:status` both come from ('offline' before first contact).
|
|
2661
|
+
*/
|
|
2662
|
+
status(): ConnectionStatus$1;
|
|
2643
2663
|
/**
|
|
2644
2664
|
* Optional connection-status feed (same wiring pattern as the streams; the
|
|
2645
2665
|
* emission point is the session's existing `connection:status` transition).
|
|
2646
2666
|
* The facade's heartbeat resets its backoff on a 'connected' recovery.
|
|
2647
2667
|
*/
|
|
2648
|
-
subscribeStatus?(handler: (status:
|
|
2668
|
+
subscribeStatus?(handler: (status: ConnectionStatus$1) => void): () => void;
|
|
2649
2669
|
}
|
|
2650
2670
|
interface IntentWireLike {
|
|
2651
2671
|
transferId: string;
|
package/dist/core/index.js
CHANGED
|
@@ -832,6 +832,15 @@ var TIMEOUTS = {
|
|
|
832
832
|
var COMPOSING_INDICATOR_KIND = 25050;
|
|
833
833
|
var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
|
|
834
834
|
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
835
|
+
function declaredNetwork(event) {
|
|
836
|
+
let value;
|
|
837
|
+
try {
|
|
838
|
+
value = JSON.parse(event.content)?.network;
|
|
839
|
+
} catch {
|
|
840
|
+
return void 0;
|
|
841
|
+
}
|
|
842
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
843
|
+
}
|
|
835
844
|
var NostrTransportProvider = class _NostrTransportProvider {
|
|
836
845
|
id = "nostr";
|
|
837
846
|
name = "Nostr Transport";
|
|
@@ -1328,6 +1337,26 @@ var NostrTransportProvider = class _NostrTransportProvider {
|
|
|
1328
1337
|
}
|
|
1329
1338
|
/**
|
|
1330
1339
|
* Convert a BindingInfo (from nostr-js-sdk) to PeerInfo (sphere-sdk type).
|
|
1340
|
+
*
|
|
1341
|
+
* `network` IS DELIBERATELY ABSENT HERE — a live limitation, not an oversight
|
|
1342
|
+
* (#734). `queryBindingByNametag`/`queryBindingByAddress` are the whole public
|
|
1343
|
+
* resolution API for `@nametag` and `DIRECT://`, and both hardcode
|
|
1344
|
+
* `parseBindingInfo` inside the SDK's PRIVATE `queryWithFirstSeenWins`: the
|
|
1345
|
+
* caller never sees the signed event that resolution selected, and the parse
|
|
1346
|
+
* has already dropped every content field outside its whitelist. Re-querying
|
|
1347
|
+
* raw would mean re-deriving UNIP-01 ownership ourselves (marker preference,
|
|
1348
|
+
* cross-author first-seen-wins, ambiguity→null, multi-relay settle — all
|
|
1349
|
+
* private), and a second resolver that can disagree with the SDK's is worse
|
|
1350
|
+
* than a missing field on a MONEY guard. Nothing can declare a network today
|
|
1351
|
+
* regardless: the publish builders whitelist the same fields.
|
|
1352
|
+
*
|
|
1353
|
+
* CONSEQUENCE, stated plainly: for `@nametag` and `DIRECT://` recipients the
|
|
1354
|
+
* §5.6 cross-network guard can only SIGNAL (`recipient:network-unverified`),
|
|
1355
|
+
* never refuse. Upstream #734 must add `network` to the binding builders AND
|
|
1356
|
+
* to `BindingInfo`; the tripwire in
|
|
1357
|
+
* tests/unit/payments-v2/recipient-network.transport.test.ts fails the day it
|
|
1358
|
+
* lands, so this wiring cannot be forgotten. Do not "fix" this by inventing a
|
|
1359
|
+
* value — a stamped network is what made the guard unfalsifiable (#733).
|
|
1331
1360
|
*/
|
|
1332
1361
|
async bindingInfoToPeerInfo(binding, nametag) {
|
|
1333
1362
|
return {
|
|
@@ -1354,11 +1383,13 @@ var NostrTransportProvider = class _NostrTransportProvider {
|
|
|
1354
1383
|
const bindingEvent = events[0];
|
|
1355
1384
|
try {
|
|
1356
1385
|
const content = JSON.parse(bindingEvent.content);
|
|
1386
|
+
const network = declaredNetwork(bindingEvent);
|
|
1357
1387
|
return {
|
|
1358
1388
|
nametag: content.nametag || void 0,
|
|
1359
1389
|
transportPubkey: bindingEvent.pubkey,
|
|
1360
1390
|
chainPubkey: content.public_key || "",
|
|
1361
1391
|
directAddress: content.direct_address || "",
|
|
1392
|
+
...network !== void 0 ? { network } : {},
|
|
1362
1393
|
timestamp: bindingEvent.created_at * 1e3
|
|
1363
1394
|
};
|
|
1364
1395
|
} catch {
|
|
@@ -1394,11 +1425,13 @@ var NostrTransportProvider = class _NostrTransportProvider {
|
|
|
1394
1425
|
for (const [pubkey, event] of byAuthor) {
|
|
1395
1426
|
try {
|
|
1396
1427
|
const content = JSON.parse(event.content);
|
|
1428
|
+
const network = declaredNetwork(event);
|
|
1397
1429
|
results.push({
|
|
1398
1430
|
nametag: content.nametag || void 0,
|
|
1399
1431
|
transportPubkey: pubkey,
|
|
1400
1432
|
chainPubkey: content.public_key || "",
|
|
1401
1433
|
directAddress: content.direct_address || "",
|
|
1434
|
+
...network !== void 0 ? { network } : {},
|
|
1402
1435
|
timestamp: event.created_at * 1e3
|
|
1403
1436
|
});
|
|
1404
1437
|
} catch {
|
|
@@ -11995,6 +12028,31 @@ function distinctOpCount(entries) {
|
|
|
11995
12028
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
11996
12029
|
}
|
|
11997
12030
|
|
|
12031
|
+
// modules/payments-v2/recipient.ts
|
|
12032
|
+
var ATTENTION_RECIPIENT_NETWORK_UNVERIFIED = "recipient:network-unverified";
|
|
12033
|
+
async function requireSameNetworkRecipient(deps, identifier) {
|
|
12034
|
+
const recipient = await deps.resolveRecipient(identifier);
|
|
12035
|
+
if (recipient === null || recipient.chainPubkey === "") {
|
|
12036
|
+
throw new SphereError(`Recipient ${identifier} has no published chain pubkey`, "INVALID_RECIPIENT");
|
|
12037
|
+
}
|
|
12038
|
+
if (recipient.network === null) {
|
|
12039
|
+
deps.emit("transfer:attention", {
|
|
12040
|
+
transferId: "",
|
|
12041
|
+
// the gate precedes plan allocation — no transferId exists yet
|
|
12042
|
+
code: ATTENTION_RECIPIENT_NETWORK_UNVERIFIED,
|
|
12043
|
+
detail: `could not verify the network of recipient ${identifier} \u2014 assuming this session's "${deps.network}"`
|
|
12044
|
+
});
|
|
12045
|
+
return recipient;
|
|
12046
|
+
}
|
|
12047
|
+
if (recipient.network !== deps.network) {
|
|
12048
|
+
throw new SphereError(
|
|
12049
|
+
`Recipient ${identifier} is on network "${recipient.network}" but this session is on "${deps.network}" \u2014 a cross-network deposit is unrecoverable`,
|
|
12050
|
+
"INVALID_RECIPIENT"
|
|
12051
|
+
);
|
|
12052
|
+
}
|
|
12053
|
+
return recipient;
|
|
12054
|
+
}
|
|
12055
|
+
|
|
11998
12056
|
// modules/payments-v2/restore.ts
|
|
11999
12057
|
var ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
|
|
12000
12058
|
var ALL_STREAMS = ["inventory", "mailbox", "payment_requests", "history"];
|
|
@@ -13587,6 +13645,10 @@ var PaymentsFacade = class {
|
|
|
13587
13645
|
history(page) {
|
|
13588
13646
|
return this.historyStore.page(page ?? {});
|
|
13589
13647
|
}
|
|
13648
|
+
/** §4: read through to the session — the same value `connection:status` carries (sphere#473). No session ⇒ 'offline'. */
|
|
13649
|
+
connectionStatus() {
|
|
13650
|
+
return this.started ? this.deps.session.status() : "offline";
|
|
13651
|
+
}
|
|
13590
13652
|
/** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
|
|
13591
13653
|
pendingTransfers() {
|
|
13592
13654
|
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
@@ -13661,7 +13723,7 @@ var PaymentsFacade = class {
|
|
|
13661
13723
|
this.deps.emit("transfer:updated", failed);
|
|
13662
13724
|
}
|
|
13663
13725
|
async sendWithPolicy(request, run) {
|
|
13664
|
-
const recipient = await this.
|
|
13726
|
+
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
13665
13727
|
for (let attempt = 0; ; attempt++) {
|
|
13666
13728
|
let ctx;
|
|
13667
13729
|
try {
|
|
@@ -13684,20 +13746,6 @@ var PaymentsFacade = class {
|
|
|
13684
13746
|
if (surfaced !== null) return surfaced;
|
|
13685
13747
|
}
|
|
13686
13748
|
}
|
|
13687
|
-
/** §5.6 cross-network deposit trap: refused BEFORE any reserve/certification. */
|
|
13688
|
-
async requireSameNetworkRecipient(identifier) {
|
|
13689
|
-
const recipient = await this.deps.resolveRecipient(identifier);
|
|
13690
|
-
if (recipient === null || recipient.chainPubkey === "") {
|
|
13691
|
-
throw new SphereError(`Recipient ${identifier} has no published chain pubkey`, "INVALID_RECIPIENT");
|
|
13692
|
-
}
|
|
13693
|
-
if (recipient.network !== this.deps.network) {
|
|
13694
|
-
throw new SphereError(
|
|
13695
|
-
`Recipient ${identifier} is on network "${recipient.network}" but this session is on "${this.deps.network}" \u2014 a cross-network deposit is unrecoverable`,
|
|
13696
|
-
"INVALID_RECIPIENT"
|
|
13697
|
-
);
|
|
13698
|
-
}
|
|
13699
|
-
return recipient;
|
|
13700
|
-
}
|
|
13701
13749
|
async runAttempt(ctx) {
|
|
13702
13750
|
this.activeMoneyOps.add(ctx.transferId);
|
|
13703
13751
|
try {
|
|
@@ -14074,19 +14122,12 @@ var PaymentsFacade = class {
|
|
|
14074
14122
|
}
|
|
14075
14123
|
toUiToken(tokenId, coinId, amount) {
|
|
14076
14124
|
const now = this.nowMs();
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
{ coinId, amount: amount.toString() },
|
|
14081
|
-
this.deps.registry,
|
|
14082
|
-
{ transferring: true, suspectedSpent: false }
|
|
14083
|
-
);
|
|
14125
|
+
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
14126
|
+
const flags = { transferring: true, suspectedSpent: false };
|
|
14127
|
+
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, this.deps.registry, flags);
|
|
14084
14128
|
}
|
|
14085
14129
|
track(op) {
|
|
14086
|
-
const settled = op.then(
|
|
14087
|
-
() => void 0,
|
|
14088
|
-
() => void 0
|
|
14089
|
-
);
|
|
14130
|
+
const settled = op.then(() => void 0, () => void 0);
|
|
14090
14131
|
this.pendingOps.add(settled);
|
|
14091
14132
|
void settled.finally(() => this.pendingOps.delete(settled));
|
|
14092
14133
|
return op;
|
|
@@ -15187,7 +15228,8 @@ async function resolveRecipientInfo(identifier, network, resolvePeer) {
|
|
|
15187
15228
|
if (!peer?.chainPubkey) return null;
|
|
15188
15229
|
return {
|
|
15189
15230
|
chainPubkey: peer.chainPubkey,
|
|
15190
|
-
|
|
15231
|
+
// Only what the binding declares (blank declares nothing) — never a local stamp.
|
|
15232
|
+
network: peer.network || null,
|
|
15191
15233
|
...peer.nametag !== void 0 ? { nametag: peer.nametag } : {}
|
|
15192
15234
|
};
|
|
15193
15235
|
}
|