@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
package/dist/index.d.cts
CHANGED
|
@@ -164,6 +164,7 @@ interface PendingTransfer {
|
|
|
164
164
|
deliveryPending: boolean;
|
|
165
165
|
createdAt: number;
|
|
166
166
|
}
|
|
167
|
+
type ConnectionStatus$1 = 'connected' | 'degraded' | 'offline';
|
|
167
168
|
interface PaymentsV2 {
|
|
168
169
|
assets(coinId?: string): Promise<Asset[]>;
|
|
169
170
|
tokens(filter?: {
|
|
@@ -180,6 +181,7 @@ interface PaymentsV2 {
|
|
|
180
181
|
}>;
|
|
181
182
|
pendingTransfers(): Promise<PendingTransfer[]>;
|
|
182
183
|
resumeNow(): Promise<void>;
|
|
184
|
+
connectionStatus(): ConnectionStatus$1;
|
|
183
185
|
readonly requests: PaymentsRequestsApi;
|
|
184
186
|
}
|
|
185
187
|
|
|
@@ -1148,6 +1150,19 @@ interface PeerInfo {
|
|
|
1148
1150
|
chainPubkey: string;
|
|
1149
1151
|
/** L3 DIRECT address (DIRECT://...) */
|
|
1150
1152
|
directAddress: string;
|
|
1153
|
+
/**
|
|
1154
|
+
* Network the peer's identity binding DECLARES ('testnet2', …) — absent when
|
|
1155
|
+
* the binding carries none, which is every binding published so far. Parsed
|
|
1156
|
+
* only, never invented: money refuses a PROVEN foreign network (§5.6).
|
|
1157
|
+
*
|
|
1158
|
+
* Absent is also the STRUCTURAL answer on the `@nametag` and `DIRECT://`
|
|
1159
|
+
* routes of the Nostr transport, whichever binding is out there: those two
|
|
1160
|
+
* resolve through nostr-js-sdk's `queryBindingBy*`, whose parser drops every
|
|
1161
|
+
* content field outside its whitelist and never hands back the signed event
|
|
1162
|
+
* (#734 tracks the upstream fix). Only routes that parse the raw event —
|
|
1163
|
+
* `resolveTransportPubkeyInfo`, `discoverAddresses` — can populate it today.
|
|
1164
|
+
*/
|
|
1165
|
+
network?: string;
|
|
1151
1166
|
/** Event timestamp */
|
|
1152
1167
|
timestamp: number;
|
|
1153
1168
|
}
|
|
@@ -2298,12 +2313,17 @@ interface FacadeSession {
|
|
|
2298
2313
|
* stream nudge resumes. The facade registers handleEpochChange here.
|
|
2299
2314
|
*/
|
|
2300
2315
|
subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
|
|
2316
|
+
/**
|
|
2317
|
+
* The session's CURRENT status — REQUIRED, and the ONE source `connectionStatus()`
|
|
2318
|
+
* and `connection:status` both come from ('offline' before first contact).
|
|
2319
|
+
*/
|
|
2320
|
+
status(): ConnectionStatus$1;
|
|
2301
2321
|
/**
|
|
2302
2322
|
* Optional connection-status feed (same wiring pattern as the streams; the
|
|
2303
2323
|
* emission point is the session's existing `connection:status` transition).
|
|
2304
2324
|
* The facade's heartbeat resets its backoff on a 'connected' recovery.
|
|
2305
2325
|
*/
|
|
2306
|
-
subscribeStatus?(handler: (status:
|
|
2326
|
+
subscribeStatus?(handler: (status: ConnectionStatus$1) => void): () => void;
|
|
2307
2327
|
}
|
|
2308
2328
|
interface IntentWireLike {
|
|
2309
2329
|
transferId: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -164,6 +164,7 @@ interface PendingTransfer {
|
|
|
164
164
|
deliveryPending: boolean;
|
|
165
165
|
createdAt: number;
|
|
166
166
|
}
|
|
167
|
+
type ConnectionStatus$1 = 'connected' | 'degraded' | 'offline';
|
|
167
168
|
interface PaymentsV2 {
|
|
168
169
|
assets(coinId?: string): Promise<Asset[]>;
|
|
169
170
|
tokens(filter?: {
|
|
@@ -180,6 +181,7 @@ interface PaymentsV2 {
|
|
|
180
181
|
}>;
|
|
181
182
|
pendingTransfers(): Promise<PendingTransfer[]>;
|
|
182
183
|
resumeNow(): Promise<void>;
|
|
184
|
+
connectionStatus(): ConnectionStatus$1;
|
|
183
185
|
readonly requests: PaymentsRequestsApi;
|
|
184
186
|
}
|
|
185
187
|
|
|
@@ -1148,6 +1150,19 @@ interface PeerInfo {
|
|
|
1148
1150
|
chainPubkey: string;
|
|
1149
1151
|
/** L3 DIRECT address (DIRECT://...) */
|
|
1150
1152
|
directAddress: string;
|
|
1153
|
+
/**
|
|
1154
|
+
* Network the peer's identity binding DECLARES ('testnet2', …) — absent when
|
|
1155
|
+
* the binding carries none, which is every binding published so far. Parsed
|
|
1156
|
+
* only, never invented: money refuses a PROVEN foreign network (§5.6).
|
|
1157
|
+
*
|
|
1158
|
+
* Absent is also the STRUCTURAL answer on the `@nametag` and `DIRECT://`
|
|
1159
|
+
* routes of the Nostr transport, whichever binding is out there: those two
|
|
1160
|
+
* resolve through nostr-js-sdk's `queryBindingBy*`, whose parser drops every
|
|
1161
|
+
* content field outside its whitelist and never hands back the signed event
|
|
1162
|
+
* (#734 tracks the upstream fix). Only routes that parse the raw event —
|
|
1163
|
+
* `resolveTransportPubkeyInfo`, `discoverAddresses` — can populate it today.
|
|
1164
|
+
*/
|
|
1165
|
+
network?: string;
|
|
1151
1166
|
/** Event timestamp */
|
|
1152
1167
|
timestamp: number;
|
|
1153
1168
|
}
|
|
@@ -2298,12 +2313,17 @@ interface FacadeSession {
|
|
|
2298
2313
|
* stream nudge resumes. The facade registers handleEpochChange here.
|
|
2299
2314
|
*/
|
|
2300
2315
|
subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
|
|
2316
|
+
/**
|
|
2317
|
+
* The session's CURRENT status — REQUIRED, and the ONE source `connectionStatus()`
|
|
2318
|
+
* and `connection:status` both come from ('offline' before first contact).
|
|
2319
|
+
*/
|
|
2320
|
+
status(): ConnectionStatus$1;
|
|
2301
2321
|
/**
|
|
2302
2322
|
* Optional connection-status feed (same wiring pattern as the streams; the
|
|
2303
2323
|
* emission point is the session's existing `connection:status` transition).
|
|
2304
2324
|
* The facade's heartbeat resets its backoff on a 'connected' recovery.
|
|
2305
2325
|
*/
|
|
2306
|
-
subscribeStatus?(handler: (status:
|
|
2326
|
+
subscribeStatus?(handler: (status: ConnectionStatus$1) => void): () => void;
|
|
2307
2327
|
}
|
|
2308
2328
|
interface IntentWireLike {
|
|
2309
2329
|
transferId: string;
|
package/dist/index.js
CHANGED
|
@@ -851,6 +851,15 @@ var LIMITS = {
|
|
|
851
851
|
var COMPOSING_INDICATOR_KIND = 25050;
|
|
852
852
|
var TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
|
|
853
853
|
var EVENT_KINDS = NOSTR_EVENT_KINDS;
|
|
854
|
+
function declaredNetwork(event) {
|
|
855
|
+
let value;
|
|
856
|
+
try {
|
|
857
|
+
value = JSON.parse(event.content)?.network;
|
|
858
|
+
} catch {
|
|
859
|
+
return void 0;
|
|
860
|
+
}
|
|
861
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
862
|
+
}
|
|
854
863
|
var NostrTransportProvider = class _NostrTransportProvider {
|
|
855
864
|
id = "nostr";
|
|
856
865
|
name = "Nostr Transport";
|
|
@@ -1347,6 +1356,26 @@ var NostrTransportProvider = class _NostrTransportProvider {
|
|
|
1347
1356
|
}
|
|
1348
1357
|
/**
|
|
1349
1358
|
* Convert a BindingInfo (from nostr-js-sdk) to PeerInfo (sphere-sdk type).
|
|
1359
|
+
*
|
|
1360
|
+
* `network` IS DELIBERATELY ABSENT HERE — a live limitation, not an oversight
|
|
1361
|
+
* (#734). `queryBindingByNametag`/`queryBindingByAddress` are the whole public
|
|
1362
|
+
* resolution API for `@nametag` and `DIRECT://`, and both hardcode
|
|
1363
|
+
* `parseBindingInfo` inside the SDK's PRIVATE `queryWithFirstSeenWins`: the
|
|
1364
|
+
* caller never sees the signed event that resolution selected, and the parse
|
|
1365
|
+
* has already dropped every content field outside its whitelist. Re-querying
|
|
1366
|
+
* raw would mean re-deriving UNIP-01 ownership ourselves (marker preference,
|
|
1367
|
+
* cross-author first-seen-wins, ambiguity→null, multi-relay settle — all
|
|
1368
|
+
* private), and a second resolver that can disagree with the SDK's is worse
|
|
1369
|
+
* than a missing field on a MONEY guard. Nothing can declare a network today
|
|
1370
|
+
* regardless: the publish builders whitelist the same fields.
|
|
1371
|
+
*
|
|
1372
|
+
* CONSEQUENCE, stated plainly: for `@nametag` and `DIRECT://` recipients the
|
|
1373
|
+
* §5.6 cross-network guard can only SIGNAL (`recipient:network-unverified`),
|
|
1374
|
+
* never refuse. Upstream #734 must add `network` to the binding builders AND
|
|
1375
|
+
* to `BindingInfo`; the tripwire in
|
|
1376
|
+
* tests/unit/payments-v2/recipient-network.transport.test.ts fails the day it
|
|
1377
|
+
* lands, so this wiring cannot be forgotten. Do not "fix" this by inventing a
|
|
1378
|
+
* value — a stamped network is what made the guard unfalsifiable (#733).
|
|
1350
1379
|
*/
|
|
1351
1380
|
async bindingInfoToPeerInfo(binding, nametag) {
|
|
1352
1381
|
return {
|
|
@@ -1373,11 +1402,13 @@ var NostrTransportProvider = class _NostrTransportProvider {
|
|
|
1373
1402
|
const bindingEvent = events[0];
|
|
1374
1403
|
try {
|
|
1375
1404
|
const content = JSON.parse(bindingEvent.content);
|
|
1405
|
+
const network = declaredNetwork(bindingEvent);
|
|
1376
1406
|
return {
|
|
1377
1407
|
nametag: content.nametag || void 0,
|
|
1378
1408
|
transportPubkey: bindingEvent.pubkey,
|
|
1379
1409
|
chainPubkey: content.public_key || "",
|
|
1380
1410
|
directAddress: content.direct_address || "",
|
|
1411
|
+
...network !== void 0 ? { network } : {},
|
|
1381
1412
|
timestamp: bindingEvent.created_at * 1e3
|
|
1382
1413
|
};
|
|
1383
1414
|
} catch {
|
|
@@ -1413,11 +1444,13 @@ var NostrTransportProvider = class _NostrTransportProvider {
|
|
|
1413
1444
|
for (const [pubkey, event] of byAuthor) {
|
|
1414
1445
|
try {
|
|
1415
1446
|
const content = JSON.parse(event.content);
|
|
1447
|
+
const network = declaredNetwork(event);
|
|
1416
1448
|
results.push({
|
|
1417
1449
|
nametag: content.nametag || void 0,
|
|
1418
1450
|
transportPubkey: pubkey,
|
|
1419
1451
|
chainPubkey: content.public_key || "",
|
|
1420
1452
|
directAddress: content.direct_address || "",
|
|
1453
|
+
...network !== void 0 ? { network } : {},
|
|
1421
1454
|
timestamp: event.created_at * 1e3
|
|
1422
1455
|
});
|
|
1423
1456
|
} catch {
|
|
@@ -12011,6 +12044,31 @@ function distinctOpCount(entries) {
|
|
|
12011
12044
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
12012
12045
|
}
|
|
12013
12046
|
|
|
12047
|
+
// modules/payments-v2/recipient.ts
|
|
12048
|
+
var ATTENTION_RECIPIENT_NETWORK_UNVERIFIED = "recipient:network-unverified";
|
|
12049
|
+
async function requireSameNetworkRecipient(deps, identifier) {
|
|
12050
|
+
const recipient = await deps.resolveRecipient(identifier);
|
|
12051
|
+
if (recipient === null || recipient.chainPubkey === "") {
|
|
12052
|
+
throw new SphereError(`Recipient ${identifier} has no published chain pubkey`, "INVALID_RECIPIENT");
|
|
12053
|
+
}
|
|
12054
|
+
if (recipient.network === null) {
|
|
12055
|
+
deps.emit("transfer:attention", {
|
|
12056
|
+
transferId: "",
|
|
12057
|
+
// the gate precedes plan allocation — no transferId exists yet
|
|
12058
|
+
code: ATTENTION_RECIPIENT_NETWORK_UNVERIFIED,
|
|
12059
|
+
detail: `could not verify the network of recipient ${identifier} \u2014 assuming this session's "${deps.network}"`
|
|
12060
|
+
});
|
|
12061
|
+
return recipient;
|
|
12062
|
+
}
|
|
12063
|
+
if (recipient.network !== deps.network) {
|
|
12064
|
+
throw new SphereError(
|
|
12065
|
+
`Recipient ${identifier} is on network "${recipient.network}" but this session is on "${deps.network}" \u2014 a cross-network deposit is unrecoverable`,
|
|
12066
|
+
"INVALID_RECIPIENT"
|
|
12067
|
+
);
|
|
12068
|
+
}
|
|
12069
|
+
return recipient;
|
|
12070
|
+
}
|
|
12071
|
+
|
|
12014
12072
|
// modules/payments-v2/restore.ts
|
|
12015
12073
|
var ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
|
|
12016
12074
|
var ALL_STREAMS = ["inventory", "mailbox", "payment_requests", "history"];
|
|
@@ -13603,6 +13661,10 @@ var PaymentsFacade = class {
|
|
|
13603
13661
|
history(page) {
|
|
13604
13662
|
return this.historyStore.page(page ?? {});
|
|
13605
13663
|
}
|
|
13664
|
+
/** §4: read through to the session — the same value `connection:status` carries (sphere#473). No session ⇒ 'offline'. */
|
|
13665
|
+
connectionStatus() {
|
|
13666
|
+
return this.started ? this.deps.session.status() : "offline";
|
|
13667
|
+
}
|
|
13606
13668
|
/** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
|
|
13607
13669
|
pendingTransfers() {
|
|
13608
13670
|
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
@@ -13677,7 +13739,7 @@ var PaymentsFacade = class {
|
|
|
13677
13739
|
this.deps.emit("transfer:updated", failed);
|
|
13678
13740
|
}
|
|
13679
13741
|
async sendWithPolicy(request, run) {
|
|
13680
|
-
const recipient = await this.
|
|
13742
|
+
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
13681
13743
|
for (let attempt = 0; ; attempt++) {
|
|
13682
13744
|
let ctx;
|
|
13683
13745
|
try {
|
|
@@ -13700,20 +13762,6 @@ var PaymentsFacade = class {
|
|
|
13700
13762
|
if (surfaced !== null) return surfaced;
|
|
13701
13763
|
}
|
|
13702
13764
|
}
|
|
13703
|
-
/** §5.6 cross-network deposit trap: refused BEFORE any reserve/certification. */
|
|
13704
|
-
async requireSameNetworkRecipient(identifier) {
|
|
13705
|
-
const recipient = await this.deps.resolveRecipient(identifier);
|
|
13706
|
-
if (recipient === null || recipient.chainPubkey === "") {
|
|
13707
|
-
throw new SphereError(`Recipient ${identifier} has no published chain pubkey`, "INVALID_RECIPIENT");
|
|
13708
|
-
}
|
|
13709
|
-
if (recipient.network !== this.deps.network) {
|
|
13710
|
-
throw new SphereError(
|
|
13711
|
-
`Recipient ${identifier} is on network "${recipient.network}" but this session is on "${this.deps.network}" \u2014 a cross-network deposit is unrecoverable`,
|
|
13712
|
-
"INVALID_RECIPIENT"
|
|
13713
|
-
);
|
|
13714
|
-
}
|
|
13715
|
-
return recipient;
|
|
13716
|
-
}
|
|
13717
13765
|
async runAttempt(ctx) {
|
|
13718
13766
|
this.activeMoneyOps.add(ctx.transferId);
|
|
13719
13767
|
try {
|
|
@@ -14090,19 +14138,12 @@ var PaymentsFacade = class {
|
|
|
14090
14138
|
}
|
|
14091
14139
|
toUiToken(tokenId, coinId, amount) {
|
|
14092
14140
|
const now = this.nowMs();
|
|
14093
|
-
|
|
14094
|
-
|
|
14095
|
-
|
|
14096
|
-
{ coinId, amount: amount.toString() },
|
|
14097
|
-
this.deps.registry,
|
|
14098
|
-
{ transferring: true, suspectedSpent: false }
|
|
14099
|
-
);
|
|
14141
|
+
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
14142
|
+
const flags = { transferring: true, suspectedSpent: false };
|
|
14143
|
+
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, this.deps.registry, flags);
|
|
14100
14144
|
}
|
|
14101
14145
|
track(op) {
|
|
14102
|
-
const settled = op.then(
|
|
14103
|
-
() => void 0,
|
|
14104
|
-
() => void 0
|
|
14105
|
-
);
|
|
14146
|
+
const settled = op.then(() => void 0, () => void 0);
|
|
14106
14147
|
this.pendingOps.add(settled);
|
|
14107
14148
|
void settled.finally(() => this.pendingOps.delete(settled));
|
|
14108
14149
|
return op;
|
|
@@ -15203,7 +15244,8 @@ async function resolveRecipientInfo(identifier, network, resolvePeer) {
|
|
|
15203
15244
|
if (!peer?.chainPubkey) return null;
|
|
15204
15245
|
return {
|
|
15205
15246
|
chainPubkey: peer.chainPubkey,
|
|
15206
|
-
|
|
15247
|
+
// Only what the binding declares (blank declares nothing) — never a local stamp.
|
|
15248
|
+
network: peer.network || null,
|
|
15207
15249
|
...peer.nametag !== void 0 ? { nametag: peer.nametag } : {}
|
|
15208
15250
|
};
|
|
15209
15251
|
}
|