@unicitylabs/sphere-sdk 0.13.2 → 0.13.3-dev.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.
@@ -464,9 +464,6 @@ interface TokenStorageProvider<TData = unknown> extends BaseProvider {
464
464
  interface TxfStorageDataBase {
465
465
  _meta: TxfMeta;
466
466
  _tombstones?: TxfTombstone[];
467
- _outbox?: TxfOutboxEntry[];
468
- _sent?: TxfSentEntry[];
469
- _invalid?: TxfInvalidEntry[];
470
467
  _history?: HistoryRecord[];
471
468
  [key: `_${string}`]: unknown;
472
469
  }
@@ -482,25 +479,6 @@ interface TxfTombstone {
482
479
  stateHash: string;
483
480
  timestamp: number;
484
481
  }
485
- interface TxfOutboxEntry {
486
- id: string;
487
- status: string;
488
- tokenId: string;
489
- recipient: string;
490
- createdAt: number;
491
- data: unknown;
492
- }
493
- interface TxfSentEntry {
494
- tokenId: string;
495
- recipient: string;
496
- txHash: string;
497
- sentAt: number;
498
- }
499
- interface TxfInvalidEntry {
500
- tokenId: string;
501
- reason: string;
502
- detectedAt: number;
503
- }
504
482
 
505
483
  /**
506
484
  * Oracle Provider Interface
@@ -949,6 +927,237 @@ interface TombstoneEntry {
949
927
  timestamp: number;
950
928
  }
951
929
 
930
+ /**
931
+ * transport/delivery-provider.ts — the `DeliveryProvider` port (sdk-changes S7,
932
+ * covenant §3.1-6).
933
+ *
934
+ * The seam that keeps the delivery rail swappable. In Unicity, a transfer —
935
+ * after certification — is just a file handoff, so the port is deliberately
936
+ * tiny: hand a finished token blob to a recipient, pull incoming deliveries,
937
+ * acknowledge them. `WalletApiMailboxProvider`
938
+ * (impl/shared/wallet-api/WalletApiMailboxProvider.ts) is the reference
939
+ * implementation; anything that can move a file can implement it (the port
940
+ * shape must not preclude the old Nostr transport or a future federated
941
+ * transport — neither is a deliverable here).
942
+ *
943
+ * Normative shapes (sdk-changes S7):
944
+ * - `DeliveryReceipt = { deliveryId }`
945
+ * - `IncomingDelivery = { deliveryId, transferId?, senderPubkey?, memo?,
946
+ * fetchBlob(), cursor }`
947
+ * - `deliveryId` is the **content-derived** entry id —
948
+ * `hex(SHA-256(tokenId bytes ‖ stateHash bytes))` — NEVER a server-assigned
949
+ * row id or seq (covenant §3.1-4; the contract suite asserts it). It is
950
+ * computed client-side ({@link computeDeliveryId}) and must equal the
951
+ * backend's `entry_id` (ARCHITECTURE §6).
952
+ * - **Custody is a composition-time property, not a per-call flag**:
953
+ * implementations take `custody: 'inventory' | 'external'` at construction
954
+ * and every ack sends the corresponding `intoInventory` — delivery-only
955
+ * safety must never depend on remembering an option at a call site.
956
+ * - Implementations MUST keep a **persistent `(tokenId, stateHash)` seen-set**
957
+ * for incoming deliveries: the recipient-side replay guard is part of the
958
+ * port contract, not a server promise (the recipient never trusts the
959
+ * backend — ARCHITECTURE §8.2). `deliveryId` is the canonical hash encoding
960
+ * of exactly that pair, so a persistent deliveryId set satisfies this.
961
+ */
962
+ /** Receipt for a delivered blob. `deliveryId` is content-derived — see module doc. */
963
+ interface DeliveryReceipt {
964
+ deliveryId: string;
965
+ }
966
+ /** Options for {@link DeliveryProvider.deliver}. */
967
+ interface DeliverOptions {
968
+ /**
969
+ * The send's transferId (the E.3 intent id / realization seed). Recorded
970
+ * with the delivery so the recipient can group multi-token payments and the
971
+ * backend can evidence-check the sender's removals (ARCHITECTURE §5.3/§6).
972
+ */
973
+ transferId: string;
974
+ /** Optional human memo. Implementations encrypt it client-side (S6). */
975
+ memo?: string;
976
+ /**
977
+ * The SENDER's own nametag (without a leading `@`), so the recipient can
978
+ * render the human identity instead of a raw pubkey ("Someone"). Bundled
979
+ * with the memo into ONE recipient-addressed (ECDH) `enc1.` envelope (S6) —
980
+ * the operator never sees it. Attached whenever the sender has a nametag OR
981
+ * a memo (so the nametag travels even on a memo-less transfer).
982
+ */
983
+ senderNametag?: string;
984
+ }
985
+ /** One incoming delivery pulled from the feed. */
986
+ interface IncomingDelivery {
987
+ /** Content-derived id — `hex(SHA-256(tokenId bytes ‖ stateHash bytes))`. */
988
+ deliveryId: string;
989
+ /** The sender's transferId, when the transport carries it. */
990
+ transferId?: string;
991
+ /** The sender's pubkey, when the transport carries it. */
992
+ senderPubkey?: string;
993
+ /** Decrypted memo (S6), when present and decryptable. */
994
+ memo?: string;
995
+ /**
996
+ * The sender's nametag (without a leading `@`), decrypted from the same
997
+ * recipient-addressed delivery envelope as {@link memo} (S6). Lets the
998
+ * receiver render the human identity instead of a raw pubkey, with no
999
+ * Nostr/transport lookup. Absent when the envelope carried none or could
1000
+ * not be decrypted.
1001
+ */
1002
+ senderNametag?: string;
1003
+ /** Fetch the finished token blob bytes (the encoded TokenBlob). */
1004
+ fetchBlob(): Promise<Uint8Array>;
1005
+ /** Transport-local resume cursor (opaque to callers). */
1006
+ cursor: string;
1007
+ }
1008
+ type DeliveryDisposition = 'claimed' | 'rejected';
1009
+ /**
1010
+ * The §9 wake streams a backend may nudge: `mailbox` (incoming deliveries),
1011
+ * `inventory` (owned-token set changed — e.g. a top-up or a claim on another
1012
+ * device), and `payment_requests` (a request created/answered). A wake on any
1013
+ * of these is a NUDGE — the consumer pulls that stream's cursor; correctness
1014
+ * never depends on the wake arriving (the poll backstop is the source of
1015
+ * truth).
1016
+ */
1017
+ type WakeStream = 'inventory' | 'mailbox' | 'payment_requests';
1018
+ /**
1019
+ * True liveness of the realtime wake channel (§9), decoupled from sign-in
1020
+ * session state: `connecting`/`connected` — a socket is (being) established;
1021
+ * `reconnecting` — it dropped and is backing off to re-establish (the poll
1022
+ * backstop carries correctness meanwhile); `closed` — torn down intentionally.
1023
+ * The wake is a nudge, so this is informational for the frontend (a "live"
1024
+ * indicator) — never a correctness gate.
1025
+ */
1026
+ type WakeChannelStatus = 'connecting' | 'connected' | 'reconnecting' | 'closed';
1027
+ /**
1028
+ * Custody mode (composition-time): `'inventory'` — acknowledged deliveries
1029
+ * enter the wallet-api inventory (the full wallet-api preset); `'external'` —
1030
+ * the app's own storage keeps custody and acks perform ZERO inventory writes
1031
+ * (the delivery-only preset, ARCHITECTURE §6 "delivery-only claim").
1032
+ */
1033
+ type DeliveryCustody = 'inventory' | 'external';
1034
+ interface DeliveryProvider {
1035
+ /** Composition-time custody property — never a per-call flag (S7). */
1036
+ readonly custody: DeliveryCustody;
1037
+ /**
1038
+ * Bind the wallet identity (optional — implementations that authenticate or
1039
+ * encrypt per-wallet need it; mirrors `TokenStorageProvider.setIdentity`).
1040
+ */
1041
+ setIdentity?(identity: {
1042
+ privateKey: string;
1043
+ chainPubkey: string;
1044
+ }): void;
1045
+ /**
1046
+ * #583 per-address client isolation: mint an INDEPENDENT delivery provider for
1047
+ * a different HD address, backed by its OWN authenticated client + wake socket
1048
+ * (mirrors `TokenStorageProvider.createForAddress`). Implementations that hold
1049
+ * a single mutable identity+session per instance (e.g. the wallet-api mailbox
1050
+ * over one `WalletApiClient`) provide this so `Sphere.switchToAddress` can give
1051
+ * each address its OWN delivery instance — an orphaned previous-address pump
1052
+ * then re-auths as ITS OWN owner (harmless) instead of driving a client that
1053
+ * was re-bound to the new owner. Stateless transports may omit it (the same
1054
+ * instance serves every address).
1055
+ */
1056
+ createForAddress?(): DeliveryProvider;
1057
+ /**
1058
+ * Hand a finished token blob to a recipient. `recipientPubkey` is the
1059
+ * recipient's CHAIN pubkey (33-byte compressed secp256k1, hex) — the
1060
+ * canonical Unicity identity (ARCHITECTURE §4); transports that address
1061
+ * recipients differently resolve it themselves.
1062
+ *
1063
+ * MUST be idempotent per (token, state): re-delivering the same finished
1064
+ * blob — including after the recipient claimed — succeeds and returns the
1065
+ * same content-derived `deliveryId` (ARCHITECTURE §6 deposit idempotency).
1066
+ */
1067
+ deliver(recipientPubkey: string, blob: Uint8Array, options: DeliverOptions): Promise<DeliveryReceipt>;
1068
+ /**
1069
+ * Pull-based feed of incoming deliveries since the given transport-local
1070
+ * cursor (or the provider's persisted cursor when omitted). Yields only
1071
+ * deliveries not yet in the persistent seen-set; completes when the feed is
1072
+ * drained — callers re-invoke on poll/wake. Feeds the existing
1073
+ * transport-agnostic `handleV2Transfer` (sdk-changes S3).
1074
+ */
1075
+ incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
1076
+ /**
1077
+ * Acknowledge a delivery: `'claimed'` accepts it (with the provider's
1078
+ * composition-time custody), `'rejected'` marks it locally-unverifiable —
1079
+ * terminal for discovery only (the entry stays claimable server-side and
1080
+ * its blob is retained — ARCHITECTURE §6). Both record the delivery in the
1081
+ * persistent seen-set.
1082
+ */
1083
+ ack(deliveryId: string, disposition: DeliveryDisposition): Promise<void>;
1084
+ /**
1085
+ * Optional batch acknowledge (#623): claim and reject whole pages of incoming deliveries in a
1086
+ * single request each, instead of one per entry — so draining a large inbox (a long-offline or
1087
+ * service wallet) doesn't fire thousands of writes and trip the per-owner rate limit. Same
1088
+ * semantics as {@link ack}: the seen-set records only entries that were acked successfully, so a
1089
+ * partial/failed batch is re-listed and re-processed (idempotent claim, §6). A provider that does
1090
+ * not implement it (e.g. the relay no-op) is driven via per-entry {@link ack}.
1091
+ */
1092
+ ackBatch?(claimed: string[], rejected: string[]): Promise<void>;
1093
+ /**
1094
+ * Optional batch deliver (#699): hand N finished blobs to ONE recipient with a single deposit
1095
+ * request (and one upload-urls request) instead of N — a multi-source send then costs O(1)
1096
+ * against the backend's deposit rate limit regardless of fragmentation. Optional like
1097
+ * {@link ackBatch}: the port must not preclude the relay transport or a future federated one,
1098
+ * and neither has a batch primitive — callers probe and fall back to per-blob {@link deliver}.
1099
+ *
1100
+ * Semantically equivalent to awaiting {@link deliver} once per blob, in order:
1101
+ * - receipts return in REQUEST order; each `deliveryId` is the content-derived entry id
1102
+ * (covenant §3.1-4 — NEVER the batch endpoint's server-assigned seq);
1103
+ * - idempotent per (token, state) exactly like {@link deliver};
1104
+ * - `options` apply to every blob (one send = one transferId/memo/senderNametag);
1105
+ * - throws when ANY blob could not be deposited — blobs that DID land are absorbed
1106
+ * idempotently when the caller retries, batched or per-blob.
1107
+ */
1108
+ deliverBatch?(recipientPubkey: string, blobs: Uint8Array[], options: DeliverOptions): Promise<DeliveryReceipt[]>;
1109
+ /**
1110
+ * Optional wake hook: `callback` fires with the {@link WakeStream} that was
1111
+ * nudged when new data may be available on it (e.g. a WS nudge — never a
1112
+ * correctness dependency, ARCHITECTURE §9). The wallet-api wake socket
1113
+ * multiplexes all three owner streams (`mailbox` | `inventory` |
1114
+ * `payment_requests`); the consumer routes each to that stream's pull.
1115
+ *
1116
+ * The underlying socket SELF-HEALS (§9): it reconnects with backoff on any
1117
+ * drop and a liveness watchdog force-reconnects a half-open socket. On every
1118
+ * (re)connect the consumer MUST run a full catch-up pull of every stream —
1119
+ * wakes missed while the socket was dead are not replayed — so `callback`
1120
+ * fires once for EACH stream on (re)connect (a synthetic catch-up nudge).
1121
+ * `onStatus` (optional) surfaces true socket liveness for the frontend,
1122
+ * decoupled from sign-in state. Returns an unsubscribe function.
1123
+ */
1124
+ onWake?(callback: (stream: WakeStream) => void, onStatus?: (status: WakeChannelStatus) => void): () => void;
1125
+ /**
1126
+ * Late-bind the backend-true (tokenId, stateHash) derivation —
1127
+ * `ITokenEngine.deliveryKeys`. Compositions are engine-less (the engine is
1128
+ * built later); the module that owns both (PaymentsModule) binds this at
1129
+ * init. Implementations that derive ids (S7) MUST use it and fail loudly if
1130
+ * unbound; transports that don't derive may omit the method.
1131
+ */
1132
+ bindDeliveryKeys?(derive: (blobBytes: Uint8Array) => Promise<{
1133
+ tokenId: string;
1134
+ stateHash: string;
1135
+ }>): void;
1136
+ }
1137
+
1138
+ /**
1139
+ * Receive + delivery: `receive()`, the `handleV2Transfer` receiver, the
1140
+ * PENDING_V2_DELIVERIES journal, the hoisted send delivery pass and the bounded
1141
+ * replay + poison budget. The token map and `save()` stay in PaymentsModule.
1142
+ */
1143
+
1144
+ /**
1145
+ * @deprecated v2 transfers arrive as finished tokens — there is no finalization
1146
+ * phase. The options are accepted for backwards compatibility and ignored.
1147
+ */
1148
+ interface ReceiveOptions {
1149
+ /** @deprecated Ignored — v2 tokens are stored confirmed on receipt. */
1150
+ finalize?: boolean;
1151
+ /** @deprecated Ignored. */
1152
+ timeout?: number;
1153
+ /** @deprecated Ignored. */
1154
+ pollInterval?: number;
1155
+ }
1156
+ interface ReceiveResult {
1157
+ /** Newly received incoming transfers. */
1158
+ transfers: IncomingTransfer[];
1159
+ }
1160
+
952
1161
  /**
953
1162
  * Transport Provider Interface
954
1163
  * Platform-independent P2P messaging abstraction
@@ -1247,274 +1456,66 @@ interface IncomingPaymentRequestResponse {
1247
1456
  response: {
1248
1457
  requestId: string;
1249
1458
  responseType: PaymentRequestResponseType$1;
1250
- message?: string;
1251
- transferId?: string;
1252
- };
1253
- /** Timestamp */
1254
- timestamp: number;
1255
- }
1256
- type PaymentRequestResponseHandler$1 = (response: IncomingPaymentRequestResponse) => void;
1257
- interface IncomingBroadcast {
1258
- id: string;
1259
- /** Transport-specific pubkey of author */
1260
- authorTransportPubkey: string;
1261
- content: string;
1262
- tags: string[];
1263
- timestamp: number;
1264
- }
1265
- type BroadcastHandler = (broadcast: IncomingBroadcast) => void;
1266
- type TransportEventType = 'transport:connected' | 'transport:disconnected' | 'transport:reconnecting' | 'transport:error' | 'transport:relay_added' | 'transport:relay_removed' | 'message:received' | 'message:sent' | 'transfer:received' | 'transfer:sent';
1267
- interface TransportEvent {
1268
- type: TransportEventType;
1269
- timestamp: number;
1270
- data?: unknown;
1271
- error?: string;
1272
- }
1273
- type TransportEventCallback = (event: TransportEvent) => void;
1274
- /**
1275
- * Resolved peer identity information.
1276
- * Returned by resolve methods — contains all public address formats for a peer.
1277
- * The nametag field is optional (only present if a nametag is registered).
1278
- */
1279
- interface PeerInfo {
1280
- /** Nametag name (without @), if registered */
1281
- nametag?: string;
1282
- /** Transport-specific pubkey (for messaging/encryption) */
1283
- transportPubkey: string;
1284
- /** 33-byte compressed secp256k1 public key (for L3 chain) */
1285
- chainPubkey: string;
1286
- /** L3 DIRECT address (DIRECT://...) */
1287
- directAddress: string;
1288
- /** Event timestamp */
1289
- timestamp: number;
1290
- }
1291
- interface IncomingReadReceipt {
1292
- /** Transport-specific pubkey of the sender who read the message */
1293
- senderTransportPubkey: string;
1294
- /** Event ID of the message that was read */
1295
- messageEventId: string;
1296
- /** Timestamp */
1297
- timestamp: number;
1298
- }
1299
- type ReadReceiptHandler = (receipt: IncomingReadReceipt) => void;
1300
- interface IncomingTypingIndicator {
1301
- /** Transport-specific pubkey of the sender who is typing */
1302
- senderTransportPubkey: string;
1303
- /** Sender's nametag (if known) */
1304
- senderNametag?: string;
1305
- /** Timestamp */
1306
- timestamp: number;
1307
- }
1308
- type TypingIndicatorHandler = (indicator: IncomingTypingIndicator) => void;
1309
- type ComposingHandler = (indicator: ComposingIndicator) => void;
1310
-
1311
- /**
1312
- * transport/delivery-provider.ts — the `DeliveryProvider` port (sdk-changes S7,
1313
- * covenant §3.1-6).
1314
- *
1315
- * The seam that keeps the delivery rail swappable. In Unicity, a transfer —
1316
- * after certification — is just a file handoff, so the port is deliberately
1317
- * tiny: hand a finished token blob to a recipient, pull incoming deliveries,
1318
- * acknowledge them. `WalletApiMailboxProvider`
1319
- * (impl/shared/wallet-api/WalletApiMailboxProvider.ts) is the reference
1320
- * implementation; anything that can move a file can implement it (the port
1321
- * shape must not preclude the old Nostr transport or a future federated
1322
- * transport — neither is a deliverable here).
1323
- *
1324
- * Normative shapes (sdk-changes S7):
1325
- * - `DeliveryReceipt = { deliveryId }`
1326
- * - `IncomingDelivery = { deliveryId, transferId?, senderPubkey?, memo?,
1327
- * fetchBlob(), cursor }`
1328
- * - `deliveryId` is the **content-derived** entry id —
1329
- * `hex(SHA-256(tokenId bytes ‖ stateHash bytes))` — NEVER a server-assigned
1330
- * row id or seq (covenant §3.1-4; the contract suite asserts it). It is
1331
- * computed client-side ({@link computeDeliveryId}) and must equal the
1332
- * backend's `entry_id` (ARCHITECTURE §6).
1333
- * - **Custody is a composition-time property, not a per-call flag**:
1334
- * implementations take `custody: 'inventory' | 'external'` at construction
1335
- * and every ack sends the corresponding `intoInventory` — delivery-only
1336
- * safety must never depend on remembering an option at a call site.
1337
- * - Implementations MUST keep a **persistent `(tokenId, stateHash)` seen-set**
1338
- * for incoming deliveries: the recipient-side replay guard is part of the
1339
- * port contract, not a server promise (the recipient never trusts the
1340
- * backend — ARCHITECTURE §8.2). `deliveryId` is the canonical hash encoding
1341
- * of exactly that pair, so a persistent deliveryId set satisfies this.
1342
- */
1343
- /** Receipt for a delivered blob. `deliveryId` is content-derived — see module doc. */
1344
- interface DeliveryReceipt {
1345
- deliveryId: string;
1346
- }
1347
- /** Options for {@link DeliveryProvider.deliver}. */
1348
- interface DeliverOptions {
1349
- /**
1350
- * The send's transferId (the E.3 intent id / realization seed). Recorded
1351
- * with the delivery so the recipient can group multi-token payments and the
1352
- * backend can evidence-check the sender's removals (ARCHITECTURE §5.3/§6).
1353
- */
1354
- transferId: string;
1355
- /** Optional human memo. Implementations encrypt it client-side (S6). */
1356
- memo?: string;
1357
- /**
1358
- * The SENDER's own nametag (without a leading `@`), so the recipient can
1359
- * render the human identity instead of a raw pubkey ("Someone"). Bundled
1360
- * with the memo into ONE recipient-addressed (ECDH) `enc1.` envelope (S6) —
1361
- * the operator never sees it. Attached whenever the sender has a nametag OR
1362
- * a memo (so the nametag travels even on a memo-less transfer).
1363
- */
1364
- senderNametag?: string;
1365
- }
1366
- /** One incoming delivery pulled from the feed. */
1367
- interface IncomingDelivery {
1368
- /** Content-derived id — `hex(SHA-256(tokenId bytes ‖ stateHash bytes))`. */
1369
- deliveryId: string;
1370
- /** The sender's transferId, when the transport carries it. */
1371
- transferId?: string;
1372
- /** The sender's pubkey, when the transport carries it. */
1373
- senderPubkey?: string;
1374
- /** Decrypted memo (S6), when present and decryptable. */
1375
- memo?: string;
1376
- /**
1377
- * The sender's nametag (without a leading `@`), decrypted from the same
1378
- * recipient-addressed delivery envelope as {@link memo} (S6). Lets the
1379
- * receiver render the human identity instead of a raw pubkey, with no
1380
- * Nostr/transport lookup. Absent when the envelope carried none or could
1381
- * not be decrypted.
1382
- */
1383
- senderNametag?: string;
1384
- /** Fetch the finished token blob bytes (the encoded TokenBlob). */
1385
- fetchBlob(): Promise<Uint8Array>;
1386
- /** Transport-local resume cursor (opaque to callers). */
1387
- cursor: string;
1388
- }
1389
- type DeliveryDisposition = 'claimed' | 'rejected';
1390
- /**
1391
- * The §9 wake streams a backend may nudge: `mailbox` (incoming deliveries),
1392
- * `inventory` (owned-token set changed — e.g. a top-up or a claim on another
1393
- * device), and `payment_requests` (a request created/answered). A wake on any
1394
- * of these is a NUDGE — the consumer pulls that stream's cursor; correctness
1395
- * never depends on the wake arriving (the poll backstop is the source of
1396
- * truth).
1397
- */
1398
- type WakeStream = 'inventory' | 'mailbox' | 'payment_requests';
1399
- /**
1400
- * True liveness of the realtime wake channel (§9), decoupled from sign-in
1401
- * session state: `connecting`/`connected` — a socket is (being) established;
1402
- * `reconnecting` — it dropped and is backing off to re-establish (the poll
1403
- * backstop carries correctness meanwhile); `closed` — torn down intentionally.
1404
- * The wake is a nudge, so this is informational for the frontend (a "live"
1405
- * indicator) — never a correctness gate.
1406
- */
1407
- type WakeChannelStatus = 'connecting' | 'connected' | 'reconnecting' | 'closed';
1408
- /**
1409
- * Custody mode (composition-time): `'inventory'` — acknowledged deliveries
1410
- * enter the wallet-api inventory (the full wallet-api preset); `'external'` —
1411
- * the app's own storage keeps custody and acks perform ZERO inventory writes
1412
- * (the delivery-only preset, ARCHITECTURE §6 "delivery-only claim").
1413
- */
1414
- type DeliveryCustody = 'inventory' | 'external';
1415
- interface DeliveryProvider {
1416
- /** Composition-time custody property — never a per-call flag (S7). */
1417
- readonly custody: DeliveryCustody;
1418
- /**
1419
- * Bind the wallet identity (optional — implementations that authenticate or
1420
- * encrypt per-wallet need it; mirrors `TokenStorageProvider.setIdentity`).
1421
- */
1422
- setIdentity?(identity: {
1423
- privateKey: string;
1424
- chainPubkey: string;
1425
- }): void;
1426
- /**
1427
- * #583 per-address client isolation: mint an INDEPENDENT delivery provider for
1428
- * a different HD address, backed by its OWN authenticated client + wake socket
1429
- * (mirrors `TokenStorageProvider.createForAddress`). Implementations that hold
1430
- * a single mutable identity+session per instance (e.g. the wallet-api mailbox
1431
- * over one `WalletApiClient`) provide this so `Sphere.switchToAddress` can give
1432
- * each address its OWN delivery instance — an orphaned previous-address pump
1433
- * then re-auths as ITS OWN owner (harmless) instead of driving a client that
1434
- * was re-bound to the new owner. Stateless transports may omit it (the same
1435
- * instance serves every address).
1436
- */
1437
- createForAddress?(): DeliveryProvider;
1438
- /**
1439
- * Hand a finished token blob to a recipient. `recipientPubkey` is the
1440
- * recipient's CHAIN pubkey (33-byte compressed secp256k1, hex) — the
1441
- * canonical Unicity identity (ARCHITECTURE §4); transports that address
1442
- * recipients differently resolve it themselves.
1443
- *
1444
- * MUST be idempotent per (token, state): re-delivering the same finished
1445
- * blob — including after the recipient claimed — succeeds and returns the
1446
- * same content-derived `deliveryId` (ARCHITECTURE §6 deposit idempotency).
1447
- */
1448
- deliver(recipientPubkey: string, blob: Uint8Array, options: DeliverOptions): Promise<DeliveryReceipt>;
1449
- /**
1450
- * Pull-based feed of incoming deliveries since the given transport-local
1451
- * cursor (or the provider's persisted cursor when omitted). Yields only
1452
- * deliveries not yet in the persistent seen-set; completes when the feed is
1453
- * drained — callers re-invoke on poll/wake. Feeds the existing
1454
- * transport-agnostic `handleV2Transfer` (sdk-changes S3).
1455
- */
1456
- incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
1457
- /**
1458
- * Acknowledge a delivery: `'claimed'` accepts it (with the provider's
1459
- * composition-time custody), `'rejected'` marks it locally-unverifiable —
1460
- * terminal for discovery only (the entry stays claimable server-side and
1461
- * its blob is retained — ARCHITECTURE §6). Both record the delivery in the
1462
- * persistent seen-set.
1463
- */
1464
- ack(deliveryId: string, disposition: DeliveryDisposition): Promise<void>;
1465
- /**
1466
- * Optional batch acknowledge (#623): claim and reject whole pages of incoming deliveries in a
1467
- * single request each, instead of one per entry — so draining a large inbox (a long-offline or
1468
- * service wallet) doesn't fire thousands of writes and trip the per-owner rate limit. Same
1469
- * semantics as {@link ack}: the seen-set records only entries that were acked successfully, so a
1470
- * partial/failed batch is re-listed and re-processed (idempotent claim, §6). A provider that does
1471
- * not implement it (e.g. the relay no-op) is driven via per-entry {@link ack}.
1472
- */
1473
- ackBatch?(claimed: string[], rejected: string[]): Promise<void>;
1474
- /**
1475
- * Optional batch deliver (#699): hand N finished blobs to ONE recipient with a single deposit
1476
- * request (and one upload-urls request) instead of N — a multi-source send then costs O(1)
1477
- * against the backend's deposit rate limit regardless of fragmentation. Optional like
1478
- * {@link ackBatch}: the port must not preclude the relay transport or a future federated one,
1479
- * and neither has a batch primitive — callers probe and fall back to per-blob {@link deliver}.
1480
- *
1481
- * Semantically equivalent to awaiting {@link deliver} once per blob, in order:
1482
- * - receipts return in REQUEST order; each `deliveryId` is the content-derived entry id
1483
- * (covenant §3.1-4 — NEVER the batch endpoint's server-assigned seq);
1484
- * - idempotent per (token, state) exactly like {@link deliver};
1485
- * - `options` apply to every blob (one send = one transferId/memo/senderNametag);
1486
- * - throws when ANY blob could not be deposited — blobs that DID land are absorbed
1487
- * idempotently when the caller retries, batched or per-blob.
1488
- */
1489
- deliverBatch?(recipientPubkey: string, blobs: Uint8Array[], options: DeliverOptions): Promise<DeliveryReceipt[]>;
1490
- /**
1491
- * Optional wake hook: `callback` fires with the {@link WakeStream} that was
1492
- * nudged when new data may be available on it (e.g. a WS nudge — never a
1493
- * correctness dependency, ARCHITECTURE §9). The wallet-api wake socket
1494
- * multiplexes all three owner streams (`mailbox` | `inventory` |
1495
- * `payment_requests`); the consumer routes each to that stream's pull.
1496
- *
1497
- * The underlying socket SELF-HEALS (§9): it reconnects with backoff on any
1498
- * drop and a liveness watchdog force-reconnects a half-open socket. On every
1499
- * (re)connect the consumer MUST run a full catch-up pull of every stream —
1500
- * wakes missed while the socket was dead are not replayed — so `callback`
1501
- * fires once for EACH stream on (re)connect (a synthetic catch-up nudge).
1502
- * `onStatus` (optional) surfaces true socket liveness for the frontend,
1503
- * decoupled from sign-in state. Returns an unsubscribe function.
1504
- */
1505
- onWake?(callback: (stream: WakeStream) => void, onStatus?: (status: WakeChannelStatus) => void): () => void;
1506
- /**
1507
- * Late-bind the backend-true (tokenId, stateHash) derivation —
1508
- * `ITokenEngine.deliveryKeys`. Compositions are engine-less (the engine is
1509
- * built later); the module that owns both (PaymentsModule) binds this at
1510
- * init. Implementations that derive ids (S7) MUST use it and fail loudly if
1511
- * unbound; transports that don't derive may omit the method.
1512
- */
1513
- bindDeliveryKeys?(derive: (blobBytes: Uint8Array) => Promise<{
1514
- tokenId: string;
1515
- stateHash: string;
1516
- }>): void;
1459
+ message?: string;
1460
+ transferId?: string;
1461
+ };
1462
+ /** Timestamp */
1463
+ timestamp: number;
1464
+ }
1465
+ type PaymentRequestResponseHandler$1 = (response: IncomingPaymentRequestResponse) => void;
1466
+ interface IncomingBroadcast {
1467
+ id: string;
1468
+ /** Transport-specific pubkey of author */
1469
+ authorTransportPubkey: string;
1470
+ content: string;
1471
+ tags: string[];
1472
+ timestamp: number;
1473
+ }
1474
+ type BroadcastHandler = (broadcast: IncomingBroadcast) => void;
1475
+ type TransportEventType = 'transport:connected' | 'transport:disconnected' | 'transport:reconnecting' | 'transport:error' | 'transport:relay_added' | 'transport:relay_removed' | 'message:received' | 'message:sent' | 'transfer:received' | 'transfer:sent';
1476
+ interface TransportEvent {
1477
+ type: TransportEventType;
1478
+ timestamp: number;
1479
+ data?: unknown;
1480
+ error?: string;
1481
+ }
1482
+ type TransportEventCallback = (event: TransportEvent) => void;
1483
+ /**
1484
+ * Resolved peer identity information.
1485
+ * Returned by resolve methods — contains all public address formats for a peer.
1486
+ * The nametag field is optional (only present if a nametag is registered).
1487
+ */
1488
+ interface PeerInfo {
1489
+ /** Nametag name (without @), if registered */
1490
+ nametag?: string;
1491
+ /** Transport-specific pubkey (for messaging/encryption) */
1492
+ transportPubkey: string;
1493
+ /** 33-byte compressed secp256k1 public key (for L3 chain) */
1494
+ chainPubkey: string;
1495
+ /** L3 DIRECT address (DIRECT://...) */
1496
+ directAddress: string;
1497
+ /** Event timestamp */
1498
+ timestamp: number;
1499
+ }
1500
+ interface IncomingReadReceipt {
1501
+ /** Transport-specific pubkey of the sender who read the message */
1502
+ senderTransportPubkey: string;
1503
+ /** Event ID of the message that was read */
1504
+ messageEventId: string;
1505
+ /** Timestamp */
1506
+ timestamp: number;
1517
1507
  }
1508
+ type ReadReceiptHandler = (receipt: IncomingReadReceipt) => void;
1509
+ interface IncomingTypingIndicator {
1510
+ /** Transport-specific pubkey of the sender who is typing */
1511
+ senderTransportPubkey: string;
1512
+ /** Sender's nametag (if known) */
1513
+ senderNametag?: string;
1514
+ /** Timestamp */
1515
+ timestamp: number;
1516
+ }
1517
+ type TypingIndicatorHandler = (indicator: IncomingTypingIndicator) => void;
1518
+ type ComposingHandler = (indicator: ComposingIndicator) => void;
1518
1519
 
1519
1520
  /**
1520
1521
  * WebSocket Abstraction
@@ -1962,22 +1963,6 @@ interface PriceProvider {
1962
1963
  * Single source of truth: {@link HistoryRecord} in `storage/storage-provider.ts`.
1963
1964
  */
1964
1965
  type TransactionHistoryEntry = HistoryRecord;
1965
- /**
1966
- * @deprecated v2 transfers arrive as finished tokens — there is no finalization
1967
- * phase. The options are accepted for backwards compatibility and ignored.
1968
- */
1969
- interface ReceiveOptions {
1970
- /** @deprecated Ignored — v2 tokens are stored confirmed on receipt. */
1971
- finalize?: boolean;
1972
- /** @deprecated Ignored. */
1973
- timeout?: number;
1974
- /** @deprecated Ignored. */
1975
- pollInterval?: number;
1976
- }
1977
- interface ReceiveResult {
1978
- /** Newly received incoming transfers. */
1979
- transfers: IncomingTransfer[];
1980
- }
1981
1966
  interface PaymentsModuleConfig {
1982
1967
  /** Auto-sync after operations */
1983
1968
  autoSync?: boolean;
@@ -2211,15 +2196,17 @@ declare class PaymentsModule {
2211
2196
  private readonly moduleConfig;
2212
2197
  private deps;
2213
2198
  private tokens;
2214
- private tombstones;
2215
- private tombstoneKeySet;
2216
- private _historyCache;
2217
2199
  private nametags;
2218
- private paymentRequests;
2219
- private paymentRequestHandlers;
2220
- private outgoingPaymentRequests;
2221
- private paymentRequestResponseHandlers;
2222
- private pendingResponseResolvers;
2200
+ /** Inventory reads: balances/assets, the token accessors, tombstones, validate(). */
2201
+ private readonly inventory;
2202
+ /** Payment requests (incoming + outgoing, the S4 pump, the #441 journal). */
2203
+ private readonly requests;
2204
+ /** Transaction history (the cache, the dedupKey rules, the §10 server log). */
2205
+ private readonly history;
2206
+ /** E.3/E.4 intent resume (the sign-in replay of OPEN send intents). */
2207
+ private readonly intents;
2208
+ /** Receive + delivery (receive(), the v2 receiver, the journal, the replay budget). */
2209
+ private readonly deliveries;
2223
2210
  /** The single delivery seam — an injected provider or the transport adapter. */
2224
2211
  private delivery;
2225
2212
  /** Set only when a provider was INJECTED — gates the incoming pump (S3). */
@@ -2236,35 +2223,6 @@ declare class PaymentsModule {
2236
2223
  /** S6 field-encryption key (intent payloads, history memos) — per identity. */
2237
2224
  private fieldEncryptionKey;
2238
2225
  private checkpointStore;
2239
- private prPollTimer;
2240
- /** Coalesces concurrent payment-request pump runs. */
2241
- private prPumpInFlight;
2242
- /**
2243
- * Set after the once-per-session full incoming hydration (#556): the surfaced
2244
- * incoming list is in-memory only, so on a fresh engine the CURRENT state of
2245
- * ALL incoming requests — open AND resolved (paid/declined/expired) — must be
2246
- * rebuilt from a `role=incoming&since=0` pull, not just the still-open ones.
2247
- * A status-filtered bootstrap (the pre-#556 `status=open` scan) dropped
2248
- * requests resolved in a PRIOR session, so the payer reopened and the
2249
- * 'Paid Successfully' request was gone (twin of #521/#549).
2250
- */
2251
- private prBootstrapped;
2252
- /**
2253
- * #441 deferred-paid journal (durable, per network+identity): links a payment
2254
- * request to the in-flight transfer of a possibly-committed pay so the request
2255
- * is held NON-payable ('settling') until that transfer completes (→ 'paid',
2256
- * server told) or aborts (→ payable, journal cleared). Keyed by request wire id
2257
- * (== requestId for wallet-api-surfaced requests). The in-memory Map is the
2258
- * synchronous source of truth used by the reload re-apply seam; it is
2259
- * single-flight loaded and every read-modify-write is serialized through a tail
2260
- * promise (the #679/#680 lesson: an unserialized RMW drops entries under
2261
- * concurrency = a re-payable request = the double-pay this fix prevents).
2262
- */
2263
- private settlingJournal;
2264
- private settlingJournalLoad;
2265
- private settlingJournalWrite;
2266
- /** #441: guard overlapping resume reconciles (resumeOpenIntents has 2 fire-and-forget call sites). */
2267
- private reconcileInFlight;
2268
2226
  private loadedPromise;
2269
2227
  private loaded;
2270
2228
  /**
@@ -2281,20 +2239,6 @@ declare class PaymentsModule {
2281
2239
  private loadInFlightOwner;
2282
2240
  private loadRerunRequested;
2283
2241
  private loadRerunTimer;
2284
- /**
2285
- * Owner (chainPubkey) whose server history hydration last completed —
2286
- * enables the #642 incremental fast path in {@link hydrateHistoryFromServer}.
2287
- * Reset on re-init (address switch). `serverSeenHistoryKeys` holds only
2288
- * dedupKeys actually PULLED from the server (never locally-POSTed ones), so
2289
- * the incremental stop condition can't be masked by our own fresh POSTs;
2290
- * `incrementalHistoryPulls` forces a periodic full re-pull to bound the
2291
- * staleness window if server keyset order ever diverges from arrival order.
2292
- * `hydrationEpoch` guards a pull racing a same-owner re-init.
2293
- */
2294
- private historyHydratedFor;
2295
- private serverSeenHistoryKeys;
2296
- private incrementalHistoryPulls;
2297
- private hydrationEpoch;
2298
2242
  private inventoryDebounceTimer;
2299
2243
  private static readonly SYNC_DEBOUNCE_MS;
2300
2244
  /** Quiet-then-escalate logging for the background wallet-api pumps (#630). */
@@ -2305,43 +2249,45 @@ declare class PaymentsModule {
2305
2249
  private tokenChangeCallbacks;
2306
2250
  private readonly reservationLedger;
2307
2251
  private readonly spendPlanner;
2308
- /**
2309
- * Per-request single-flight for {@link payPaymentRequest}. The pay flow flips the request to
2310
- * 'accepted' before it awaits send(), and the status guard re-admits 'accepted' (intended for a
2311
- * sequential retry-after-failure) — so a concurrent double-tap / second session would otherwise
2312
- * enter send() a second time and DOUBLE-PAY (each send picks a different token; the reservation
2313
- * ledger is coin-scoped, not request-scoped). Concurrent calls for the same requestId coalesce
2314
- * onto the first in-flight pay; the entry is cleared when it settles, so a later retry is unaffected.
2315
- */
2316
- private readonly payInFlight;
2317
2252
  private spendQueue;
2318
2253
  /** Cache of parsed SdkToken data for synchronous queue re-evaluation */
2319
2254
  private readonly parsedTokenCache;
2255
+ constructor(config?: PaymentsModuleConfig);
2320
2256
  /**
2321
- * Base delay (ms) for the journaled-delivery replay backoff (#517 item 1).
2322
- * A field, not a const, so tests can drive the bounded retry loop without
2323
- * waiting real time — never mutated in production.
2257
+ * The narrow seam {@link TokenView} reaches back through. Live getters for
2258
+ * `deps` and `priceProvider` (both swapped on {@link initialize}); the token
2259
+ * map is handed over as a READ-ONLY view, so the inventory view never becomes
2260
+ * a second writer of it.
2324
2261
  */
2325
- private replayBackoffBaseMs;
2326
- /** Deferral window for recipient-quota (429) deliveries (#621). Overridable in tests. */
2327
- private deliveryDeferralMs;
2262
+ private tokenViewHost;
2328
2263
  /**
2329
- * #517: serializes {@link replayPendingV2Deliveries}. `load()` kicks replay
2330
- * off fire-and-forget and `receive()` calls `load()`, so two passes can
2331
- * overlap — duplicating delivery attempts and clobbering each other's
2332
- * `attempts` increments (both read the same stale value, both write N+1,
2333
- * delaying poison surfacing). Only one pass runs per module instance at a time.
2264
+ * The narrow seam {@link Delivery} reaches back through. Live getters for
2265
+ * `deps` and `delivery` (both swapped on every {@link initialize}); the token
2266
+ * map is handed over as a READ-ONLY view and written only by the module's own
2267
+ * `storeEngineToken`, so delivery never becomes a second writer.
2334
2268
  */
2335
- private replayInFlight;
2269
+ private deliveryHost;
2336
2270
  /**
2337
- * #517: serializes every read-modify-write of the PENDING_V2_DELIVERIES journal.
2338
- * save/remove/update each load → mutate → store the WHOLE blob, so a replay
2339
- * (fire-and-forget from load()) overlapping a send()'s journal write could
2340
- * clobber a newly-saved undelivered entry and lose it — defeating the crash-
2341
- * safety guarantee. A promise-chain mutex makes each whole RMW atomic.
2271
+ * The narrow seam {@link PaymentRequests} reaches back through. Live getters,
2272
+ * not a snapshot: `deps` is swapped on every {@link initialize} (address
2273
+ * switch) and the feature must always observe the CURRENT one.
2342
2274
  */
2343
- private journalMutation;
2344
- constructor(config?: PaymentsModuleConfig);
2275
+ private paymentRequestsHost;
2276
+ /**
2277
+ * The narrow seam {@link TransferHistory} reaches back through. Live getter
2278
+ * for `deps` (swapped on every {@link initialize}); the rest are thunks onto
2279
+ * the module's own private helpers — history never touches the token map or
2280
+ * `save()`.
2281
+ */
2282
+ private transferHistoryHost;
2283
+ /**
2284
+ * The narrow seam {@link IntentResume} reaches back through. Live getters for
2285
+ * `deps` and `delivery` (both swapped on every {@link initialize}); the rest
2286
+ * are thunks onto the module's own helpers. The token map is READ through
2287
+ * `getHeldToken` and written only by the module's `removeToken` /
2288
+ * `storeEngineToken` — resume never becomes a second writer.
2289
+ */
2290
+ private intentResumeHost;
2345
2291
  /**
2346
2292
  * Get the current module configuration.
2347
2293
  *
@@ -2664,141 +2610,40 @@ declare class PaymentsModule {
2664
2610
  * fetched only when a token is selected to be spent.
2665
2611
  */
2666
2612
  private mergeLazyInventory;
2667
- /**
2668
- * Send a payment request to someone
2669
- * @param recipientPubkeyOrNametag - Recipient's pubkey or @nametag
2670
- * @param request - Payment request details
2671
- * @returns Result with event ID
2672
- */
2613
+ /** Send a payment request to someone. @see PaymentRequests.sendPaymentRequest */
2673
2614
  sendPaymentRequest(recipientPubkeyOrNametag: string, request: Omit<PaymentRequest, 'id' | 'createdAt'>): Promise<PaymentRequestResult>;
2674
- /**
2675
- * S4: create the request via wallet-api (§16). The payer is addressed by
2676
- * CHAIN pubkey (the canonical identity); the memo is S6-encrypted client-
2677
- * side BEFORE it leaves the device (§8.3) — the operator stores ciphertext.
2678
- * Mirrors the transport path's no-throw contract: failures (including the
2679
- * §5.5 per-payer cap → 429) come back as `{ success: false, error }`.
2680
- */
2681
- private sendWalletApiPaymentRequest;
2682
- /**
2683
- * Subscribe to incoming payment requests
2684
- * @param handler - Handler function for incoming requests
2685
- * @returns Unsubscribe function
2686
- */
2615
+ /** Subscribe to incoming payment requests. @see PaymentRequests.onPaymentRequest */
2687
2616
  onPaymentRequest(handler: PaymentRequestHandler): () => void;
2688
- /**
2689
- * Get all payment requests
2690
- * @param filter - Optional status filter
2691
- */
2617
+ /** Get all payment requests. @see PaymentRequests.getPaymentRequests */
2692
2618
  getPaymentRequests(filter?: {
2693
2619
  status?: PaymentRequestStatus;
2694
2620
  }): IncomingPaymentRequest[];
2695
- /**
2696
- * Get the count of payment requests with status `'pending'`.
2697
- *
2698
- * @returns Number of pending incoming payment requests.
2699
- */
2621
+ /** Count of incoming payment requests with status `'pending'`. @see PaymentRequests.getPendingPaymentRequestsCount */
2700
2622
  getPendingPaymentRequestsCount(): number;
2701
- /**
2702
- * Accept a payment request and notify the requester.
2703
- *
2704
- * Marks the request as `'accepted'` and sends a response via transport.
2705
- * The caller should subsequently call {@link send} to fulfill the payment.
2706
- *
2707
- * @param requestId - ID of the incoming payment request to accept.
2708
- */
2709
- acceptPaymentRequest(requestId: string): Promise<void>;
2710
- /**
2711
- * Reject a payment request and notify the requester.
2712
- *
2713
- * On the wallet-api path (S4) the respond IS the state change — it is
2714
- * confirmed server-side (`action: 'declined'`, §16) before the local status
2715
- * flips, and a server rejection (403/409) propagates to the caller. The
2716
- * transport path is best-effort and never throws.
2717
- *
2718
- * @param requestId - ID of the incoming payment request to reject.
2719
- */
2623
+ /** Reject a payment request and notify the requester. @see PaymentRequests.rejectPaymentRequest */
2720
2624
  rejectPaymentRequest(requestId: string): Promise<void>;
2721
- /**
2722
- * Mark a payment request as paid (local status update only).
2723
- *
2724
- * Typically called after a successful {@link send} to record that the
2725
- * request has been fulfilled.
2726
- *
2727
- * @param requestId - ID of the incoming payment request to mark as paid.
2728
- */
2729
- markPaymentRequestPaid(requestId: string): void;
2730
- /**
2731
- * Remove resolved incoming payment requests from memory.
2732
- *
2733
- * Keeps requests with status `'pending'` OR `'settling'` (#441). A `'settling'`
2734
- * request is UNRESOLVED — its linked transfer is still in-flight — so evicting
2735
- * it would drop the in-memory hold that keeps it non-payable until the journal
2736
- * re-surfaces it. Only terminal statuses (`'paid'`/`'rejected'`/`'expired'`)
2737
- * are removed.
2738
- */
2625
+ /** Remove resolved incoming payment requests from memory. @see PaymentRequests.clearProcessedPaymentRequests */
2739
2626
  clearProcessedPaymentRequests(): void;
2740
- /**
2741
- * Remove a specific incoming payment request by ID.
2742
- *
2743
- * @param requestId - ID of the payment request to remove.
2744
- */
2627
+ /** Remove a specific incoming payment request by ID. @see PaymentRequests.removePaymentRequest */
2745
2628
  removePaymentRequest(requestId: string): void;
2746
- /**
2747
- * Pay a payment request directly
2748
- * Convenience method that accepts, sends, and marks as paid
2749
- */
2629
+ /** Pay a payment request directly. @see PaymentRequests.payPaymentRequest */
2750
2630
  payPaymentRequest(requestId: string, memo?: string): Promise<TransferResult>;
2751
- private payPaymentRequestInner;
2752
- private updatePaymentRequestStatus;
2753
- /**
2754
- * Get outgoing payment requests
2755
- * @param filter - Optional status filter
2756
- */
2631
+ /** Get outgoing payment requests. @see PaymentRequests.getOutgoingPaymentRequests */
2757
2632
  getOutgoingPaymentRequests(filter?: {
2758
2633
  status?: PaymentRequestStatus;
2759
2634
  }): OutgoingPaymentRequest[];
2760
- /**
2761
- * Subscribe to payment request responses (for outgoing requests)
2762
- * @param handler - Handler function for incoming responses
2763
- * @returns Unsubscribe function
2764
- */
2635
+ /** Subscribe to payment request responses. @see PaymentRequests.onPaymentRequestResponse */
2765
2636
  onPaymentRequestResponse(handler: PaymentRequestResponseHandler): () => void;
2766
- /**
2767
- * Wait for a response to a payment request
2768
- * @param requestId - The outgoing request ID to wait for
2769
- * @param timeoutMs - Timeout in milliseconds (default: 60000)
2770
- * @returns Promise that resolves with the response or rejects on timeout
2771
- */
2637
+ /** Wait for a response to a payment request. @see PaymentRequests.waitForPaymentResponse */
2772
2638
  waitForPaymentResponse(requestId: string, timeoutMs?: number): Promise<PaymentRequestResponse>;
2773
- /**
2774
- * Cancel an active {@link waitForPaymentResponse} call.
2775
- *
2776
- * The pending promise is rejected with a `'Cancelled'` error.
2777
- *
2778
- * @param requestId - The outgoing request ID whose wait should be cancelled.
2779
- */
2639
+ /** Cancel an active {@link waitForPaymentResponse} call. @see PaymentRequests.cancelWaitForPaymentResponse */
2780
2640
  cancelWaitForPaymentResponse(requestId: string): void;
2781
- /**
2782
- * Remove an outgoing payment request and cancel any pending wait.
2783
- *
2784
- * @param requestId - ID of the outgoing request to remove.
2785
- */
2641
+ /** Remove an outgoing payment request and cancel any pending wait. @see PaymentRequests.removeOutgoingPaymentRequest */
2786
2642
  removeOutgoingPaymentRequest(requestId: string): void;
2787
- /**
2788
- * Remove all outgoing payment requests that are `'paid'`, `'rejected'`, or `'expired'`.
2789
- */
2643
+ /** Remove all `'paid'`/`'rejected'`/`'expired'` outgoing requests. @see PaymentRequests.clearCompletedOutgoingPaymentRequests */
2790
2644
  clearCompletedOutgoingPaymentRequests(): void;
2791
- /**
2792
- * Fold a payment-request response into the outgoing surface and notify —
2793
- * shared by the transport subscription and the wallet-api outgoing refresh
2794
- * (S4): update the matched outgoing request, resolve any
2795
- * {@link waitForPaymentResponse} waiter, emit the event, run the handlers.
2796
- */
2797
- private dispatchPaymentRequestResponse;
2798
- /**
2799
- * Send a response to a payment request (used internally by accept/reject/pay methods)
2800
- */
2801
- private sendPaymentRequestResponse;
2645
+ /** Pull the wallet-api payment-request streams now (S4). @see PaymentRequests.syncPaymentRequests */
2646
+ syncPaymentRequests(): Promise<void>;
2802
2647
  /**
2803
2648
  * Fetch and process pending incoming transfers from the transport layer.
2804
2649
  *
@@ -2812,6 +2657,7 @@ declare class PaymentsModule {
2812
2657
  * @param _options - Deprecated; the v1 finalization options are ignored.
2813
2658
  * @param callback - Optional callback invoked for each newly received transfer
2814
2659
  * @returns ReceiveResult with the newly received transfers
2660
+ * @see Delivery.receive
2815
2661
  */
2816
2662
  receive(_options?: ReceiveOptions, callback?: (transfer: IncomingTransfer) => void): Promise<ReceiveResult>;
2817
2663
  /**
@@ -2821,6 +2667,8 @@ declare class PaymentsModule {
2821
2667
  /**
2822
2668
  * Get total portfolio value in USD.
2823
2669
  * Returns null if PriceProvider is not configured.
2670
+ *
2671
+ * @see TokenView.getFiatBalance
2824
2672
  */
2825
2673
  getFiatBalance(): Promise<number | null>;
2826
2674
  /**
@@ -2838,6 +2686,7 @@ declare class PaymentsModule {
2838
2686
  *
2839
2687
  * @param coinId - Optional coin ID to filter by (e.g. hex string). When omitted, all coin types are returned.
2840
2688
  * @returns Array of balance summaries (synchronous — no await needed).
2689
+ * @see TokenView.getBalance
2841
2690
  */
2842
2691
  getBalance(coinId?: string): Asset[];
2843
2692
  /**
@@ -2846,22 +2695,10 @@ declare class PaymentsModule {
2846
2695
  * (`'transferring'`) tokens are reported only in the `transferring*` fields
2847
2696
  * and excluded from `totalAmount` (#517 item 3). Fiat value derives from
2848
2697
  * `totalAmount`, so it likewise excludes in-flight value.
2849
- */
2850
- getAssets(coinId?: string): Promise<Asset[]>;
2851
- /**
2852
- * Aggregate tokens by coinId with confirmed/unconfirmed/transferring breakdown.
2853
- * Excludes tokens with status 'spent' or 'invalid'.
2854
2698
  *
2855
- * In-flight (`'transferring'`) tokens are LEAVING the wallet during an active
2856
- * send, so they are NOT counted as spendable: they are tracked in their own
2857
- * `transferring*` fields and excluded from `totalAmount`/`unconfirmedAmount`.
2858
- * Counting them as balance would show the user value they cannot spend (the
2859
- * #517 incident follow-up).
2699
+ * @see TokenView.getAssets
2860
2700
  */
2861
- private aggregateTokens;
2862
- /** Fold one token into its coin's running totals (see {@link aggregateTokens}). */
2863
- private accumulateToken;
2864
- private newAssetAccumulator;
2701
+ getAssets(coinId?: string): Promise<Asset[]>;
2865
2702
  /**
2866
2703
  * Get all tokens, optionally filtered by coin type and/or status.
2867
2704
  *
@@ -2869,6 +2706,7 @@ declare class PaymentsModule {
2869
2706
  * @param filter.coinId - Return only tokens of this coin type.
2870
2707
  * @param filter.status - Return only tokens with this status (e.g. `'submitted'` for unconfirmed).
2871
2708
  * @returns Array of matching {@link Token} objects (synchronous).
2709
+ * @see TokenView.getTokens
2872
2710
  */
2873
2711
  getTokens(filter?: {
2874
2712
  coinId?: string;
@@ -2879,6 +2717,7 @@ declare class PaymentsModule {
2879
2717
  *
2880
2718
  * @param id - The local UUID assigned when the token was added.
2881
2719
  * @returns The token, or `undefined` if not found.
2720
+ * @see TokenView.getToken
2882
2721
  */
2883
2722
  getToken(id: string): Token | undefined;
2884
2723
  /**
@@ -2936,6 +2775,7 @@ declare class PaymentsModule {
2936
2775
  * token state from being re-added (e.g. via Nostr re-delivery).
2937
2776
  *
2938
2777
  * @returns A shallow copy of the tombstone array.
2778
+ * @see TokenView.getTombstones
2939
2779
  */
2940
2780
  getTombstones(): TombstoneEntry[];
2941
2781
  /**
@@ -2945,36 +2785,23 @@ declare class PaymentsModule {
2945
2785
  * @param tokenId - The genesis token ID.
2946
2786
  * @param stateHash - The state hash of the token version to check.
2947
2787
  * @returns `true` if the exact combination has been tombstoned.
2788
+ * @see TokenView.isStateTombstoned
2948
2789
  */
2949
2790
  isStateTombstoned(tokenId: string, stateHash: string): boolean;
2950
- private rebuildTombstoneKeySet;
2951
- /**
2952
- * Merge tombstones received from a remote sync source.
2953
- *
2954
- * Any local token whose `(tokenId, stateHash)` matches a remote tombstone is
2955
- * removed. The remote tombstones are then added to the local set (union merge).
2956
- *
2957
- * @param remoteTombstones - Tombstone entries from the remote source.
2958
- * @returns Number of local tokens that were removed.
2959
- */
2960
- mergeTombstones(remoteTombstones: TombstoneEntry[]): Promise<number>;
2961
2791
  /**
2962
2792
  * Remove tombstones older than `maxAge` and cap the list at 100 entries.
2963
2793
  *
2964
2794
  * @param maxAge - Maximum age in milliseconds (default: 30 days).
2795
+ * @see TokenView.pruneTombstones
2965
2796
  */
2966
2797
  pruneTombstones(maxAge?: number): Promise<void>;
2967
2798
  /**
2968
2799
  * Get the transaction history sorted newest-first.
2969
2800
  *
2970
2801
  * @returns Array of {@link TransactionHistoryEntry} objects in descending timestamp order.
2802
+ * @see TransferHistory.getHistory
2971
2803
  */
2972
2804
  getHistory(): TransactionHistoryEntry[];
2973
- /**
2974
- * Best-effort resolve sender's DIRECT address and nametag from their transport pubkey.
2975
- * Returns empty object if transport doesn't support resolution or lookup fails.
2976
- */
2977
- private resolveSenderInfo;
2978
2805
  /**
2979
2806
  * Append an entry to the transaction history.
2980
2807
  *
@@ -2983,56 +2810,15 @@ declare class PaymentsModule {
2983
2810
  * Duplicate entries with the same `dedupKey` are silently ignored (upsert).
2984
2811
  *
2985
2812
  * @param entry - History entry fields (without `id` and `dedupKey`).
2813
+ * @see TransferHistory.addToHistory
2986
2814
  */
2987
2815
  addToHistory(entry: Omit<TransactionHistoryEntry, 'id' | 'dedupKey'>): Promise<void>;
2988
2816
  /**
2989
2817
  * Load history into the in-memory cache.
2990
2818
  *
2991
- * In the wallet-api composition (the `walletApi` client is present) the
2992
- * durable §10 history log lives on the SERVER — the thin storage provider
2993
- * keeps none — so the cache is rebuilt from `walletApi.listHistory()`. The
2994
- * twin of the #521 inventory reload bug: `_historyCache` is process-lifetime,
2995
- * so a reload (tab refresh) must re-pull it or render an empty history.
2996
- * Compositions WITHOUT `walletApi` keep the legacy local path below.
2819
+ * @see TransferHistory.loadHistory
2997
2820
  */
2998
2821
  loadHistory(): Promise<void>;
2999
- /**
3000
- * Rebuild `_historyCache` from the server's §10 history log (the wallet-api
3001
- * composition). Pages newest-first via the keyset cursor until `more:false`
3002
- * or the page cap; dedups by `dedupKey` (a hydrate-then-receive in the same
3003
- * session must not double-list). The S6 `memo` / `counterpartyNametag`
3004
- * envelopes are decrypted with the owner's own field key on the way in.
3005
- *
3006
- * #642 incremental fast path: after one completed hydration for this owner,
3007
- * later pulls stop at the first non-empty page holding nothing new — the
3008
- * §10 log is append-only (newest-first keyset), so everything past a fully
3009
- * known page is already cached. The steady-state 30s inventory resync then
3010
- * costs ONE history page instead of a full re-pagination (which on a wallet
3011
- * whose history overflows the page cap was 100 pages, every tick, forever).
3012
- *
3013
- * Best-effort, like the §10 history POST: history is untrusted DISPLAY data,
3014
- * so a backend outage during hydration must NEVER fail `load()` (the money
3015
- * path) — the in-session cache is left intact and the pull retries next load.
3016
- */
3017
- private hydrateHistoryFromServer;
3018
- /**
3019
- * Map one §16 history wire record onto the display
3020
- * {@link TransactionHistoryEntry}. `counterpartyNametag` lands on the role-
3021
- * appropriate field (sender for RECEIVED, recipient otherwise); the S6 memo +
3022
- * nametag envelopes decrypt under THIS wallet's field key (self-scoped at
3023
- * rest — §8.3), surfaced as absent if they don't decrypt rather than as
3024
- * ciphertext (same rule as mailbox/payment-request memos).
3025
- */
3026
- private historyEntryFromWire;
3027
- /** S6 field decrypt that surfaces an undecryptable envelope as absent (§8.3). */
3028
- private tryDecryptField;
3029
- /**
3030
- * Import history entries from remote TXF data into local store.
3031
- * Delegates to the local TokenStorageProvider's importHistoryEntries() for
3032
- * persistent storage, with in-memory fallback.
3033
- * Reused by both load() (initial IPFS fetch) and _doSync() (merge result).
3034
- */
3035
- private importRemoteHistoryEntries;
3036
2822
  /**
3037
2823
  * Get the first local token storage provider (for history operations).
3038
2824
  */
@@ -3155,6 +2941,7 @@ declare class PaymentsModule {
3155
2941
  * Tokens that fail validation or are detected as spent are marked `'invalid'`.
3156
2942
  *
3157
2943
  * @returns Object with arrays of valid and invalid tokens.
2944
+ * @see TokenView.validate
3158
2945
  */
3159
2946
  validate(): Promise<{
3160
2947
  valid: Token[];
@@ -3169,17 +2956,6 @@ declare class PaymentsModule {
3169
2956
  * Uses pre-resolved PeerInfo if available, otherwise resolves via transport.
3170
2957
  */
3171
2958
  private resolveTransportPubkey;
3172
- /**
3173
- * v2 engine transfer (sender-driven): the sender handed us a FINISHED token.
3174
- * Decode the blob, dedup by the genesis-stable token id, store it as a
3175
- * confirmed token, and emit/record the receipt. No commitment / inclusion-proof
3176
- * / finalization round-trip (contrast the v1 sourceToken+transferTx path).
3177
- *
3178
- * Transport-agnostic (sdk-changes S3): fed by the relay push subscription
3179
- * AND by the delivery port's incoming pump — the returned verdict lets the
3180
- * pump map outcomes onto `ack('claimed' | 'rejected')`.
3181
- */
3182
- private handleV2Transfer;
3183
2959
  private teardownDeliveryPump;
3184
2960
  /**
3185
2961
  * Route a §9 wake nudge to the matching stream's pull. The wake is
@@ -3243,143 +3019,16 @@ declare class PaymentsModule {
3243
3019
  * ack for a provider without it (e.g. the relay no-op).
3244
3020
  */
3245
3021
  private flushIncomingAcks;
3246
- /** The S4 capability slice — null unless the composed wallet-api port carries it. */
3247
- private paymentRequestsApi;
3248
- private teardownPaymentRequestPump;
3249
- private prCursorKey;
3250
- private readPrCursorState;
3251
- private persistPrCursorState;
3252
- private prSettlingKey;
3253
- /**
3254
- * Single-flight load: concurrent callers (the pump's reload re-apply, resume's
3255
- * reconcile, a live pay catch) share ONE storage.get + ONE Map instance so a
3256
- * lazy read never overwrites another context's in-memory mutation.
3257
- */
3258
- private ensureSettlingJournalLoaded;
3259
- /**
3260
- * Serialize every read-modify-write through a tail-promise chain so two
3261
- * concurrent mutations can't lose an entry (the #679/#680 mutex lesson — a
3262
- * dropped entry is a re-payable request is a double-pay). `fn` returns true
3263
- * when the Map changed and must be persisted.
3264
- */
3265
- private mutateSettlingJournal;
3266
- /**
3267
- * #441: link a request to its in-flight transfer. `committed` marks a
3268
- * DEFINITE spend whose anchor intent is soft-aborted and will NOT resume-
3269
- * complete — a `PartialSendConflictError` (≥1 leg already delivered). The
3270
- * reconcile must resolve such a link 'paid' and NEVER revert it to payable
3271
- * (re-paying the full amount would double-pay the delivered leg). A
3272
- * non-`committed` link is a keep-open outcome that resume may still complete
3273
- * OR abort (e.g. CERTIFICATION_UNCONFIRMED losing to a foreign tx delivers
3274
- * nothing) — those DO revert to payable on abort.
3275
- */
3276
- private journalSettling;
3277
- private clearSettling;
3278
- /**
3279
- * Pull the wallet-api payment-request streams now (S4): drains the payer's
3280
- * incoming `?since=<seq>` stream (gap-free — §9/§16) into the existing
3281
- * handler/event surface and refreshes outgoing requests still awaiting a
3282
- * response. The poll interval and `load()` call this automatically; it is
3283
- * public for explicit fetch-now flows (mirrors {@link receive}). A no-op in
3284
- * compositions without the wallet-api payment-request capability — there
3285
- * the Nostr subscription is push-based.
3286
- */
3287
- syncPaymentRequests(): Promise<void>;
3288
- /** Coalesces concurrent pump runs (poll + wake + load can overlap). */
3289
- private pumpPaymentRequests;
3290
- private doPumpPaymentRequests;
3291
- /**
3292
- * Drain the payer's gap-free `?since=<seq>` stream (§9/§16), mirroring the
3293
- * mailbox-cursor pattern: the persisted `{cursor, syncEpoch}` is the resume
3294
- * point; a `syncEpoch` change (server restore — §5.4) voids cursor
3295
- * continuity, so the tail re-pulls from 0 and the id-dedup in
3296
- * {@link surfaceIncomingPaymentRequest} absorbs the replays.
3297
- *
3298
- * Because the surfaced list is in-memory only, each session FIRST runs one
3299
- * full incoming hydration from `since=0` with NO status filter (#556 —
3300
- * mirrors {@link hydrateHistoryFromServer}): a fresh engine rebuilds the
3301
- * CURRENT state of ALL incoming requests — open AND resolved — so a request
3302
- * paid/declined/expired in a PRIOR session is still present (with its
3303
- * resolved status) instead of vanishing once the cursor advanced past it.
3304
- * Hydration NEVER fires the new-incoming handlers/events for resolved
3305
- * requests — only `open` ones notify (the status-aware
3306
- * {@link surfaceIncomingPaymentRequest}) — so reopening can't spam stale
3307
- * 'new request' notifications. After hydration the `since`-cursor delta poll
3308
- * picks up live updates from the resume point.
3309
- */
3310
- private pumpIncomingPaymentRequests;
3311
- /**
3312
- * Decrypt a payment-request's recipient-addressed memo envelope into
3313
- * `{ memo, senderNametag }` (the requester's message + nametag). The key is
3314
- * the ECDH shared secret between THIS wallet (the payer) and the requester's
3315
- * chain pubkey (`wire.fromPubkey`) — symmetric with the requester's
3316
- * create-time derivation. Returns an empty bundle (and logs at debug) on any
3317
- * absence/failure so the incoming view never wedges on an unreadable memo
3318
- * (PR twin of #546/#547).
3319
- */
3320
- private decryptPaymentRequestMemo;
3321
- /** §16 wire status → the public {@link PaymentRequestStatus} display status. */
3322
- private static readonly PR_WIRE_STATUS;
3323
- /** Terminal local statuses: a re-surfaced wire may advance a request INTO one, never out of it. */
3324
- private static readonly PR_TERMINAL;
3325
- /**
3326
- * Map a §16 wire request onto the public {@link IncomingPaymentRequest}
3327
- * surface, deduped by id (the in-memory id-dedup doubles as the replay guard
3328
- * for cursor resets). Requests of EVERY status are surfaced so a reloaded
3329
- * thin wallet rebuilds the CURRENT state of its incoming view (#556) — open
3330
- * ones land as actionable `pending`, resolved ones carry their paid/declined
3331
- * (→ `rejected`)/expired status. Only `open` requests fire the new-incoming
3332
- * event + handlers; resolved requests are folded into the list silently, so a
3333
- * reload (or a `syncEpoch` re-pull) never re-notifies for already-resolved
3334
- * requests. Multi-asset requests surface their first asset (the module's
3335
- * request surface is single-asset; module-created requests always are).
3336
- */
3337
- private surfaceIncomingPaymentRequest;
3338
- /**
3339
- * Outgoing requests are a `?before=` backfill view (§16 — newest-first, no
3340
- * gap-free tail): refresh only while something local still awaits a
3341
- * response, paging until every pending id is resolved or the view drains.
3342
- */
3343
- private refreshOutgoingPaymentRequests;
3344
- /** Fold a server-side status change into the outgoing surface (responses + expiry). */
3345
- private applyOutgoingPaymentRequestState;
3346
- /**
3347
- * E.3 resume: list this wallet's OPEN intents (server-side — any device),
3348
- * decrypt each payload, and re-run the engine with the SAME transferId and
3349
- * inputs — deterministic realization yields byte-identical transactions, so
3350
- * an interrupted transfer completes instead of failing (proof fetch →
3351
- * match-verify → apply, Part E). Called at sign-in (S4).
3022
+ /**
3023
+ * E.3 resume of this wallet's OPEN send intents.
3024
+ *
3025
+ * @see IntentResume.resumeOpenIntents
3352
3026
  */
3353
3027
  resumeOpenIntents(): Promise<{
3354
3028
  resumed: string[];
3355
3029
  conflicted: string[];
3356
3030
  failed: string[];
3357
3031
  }>;
3358
- /**
3359
- * #441: resolve every journaled settling payment request against a resume
3360
- * outcome. Completed → send the deferred 'paid' response (server now told) and
3361
- * resolve 'paid'. Aborted (nothing delivered) → clear journal, return to
3362
- * payable. Still open → leave it. For an id accounted for by NONE of those
3363
- * (a crash between resume-complete and the local write), consult the server
3364
- * `listIntents('aborted')` authority: aborted → payable; else → paid (a
3365
- * completed row the server GC'd). Direction-of-error is deliberate: the only
3366
- * residual false-paid is a transfer that aborted server-side AND whose local
3367
- * 'aborted' write was lost AND whose server aborted-row was later GC'd — it is
3368
- * treated as paid, erring toward PAID-NEVER-RE-PAYABLE (no double-pay), the
3369
- * invariant this fix exists to protect.
3370
- */
3371
- private reconcileSettlingPaymentRequests;
3372
- /** #441: the linked transfer completed — tell the server 'paid' and resolve 'paid'. */
3373
- private resolveSettledPaid;
3374
- /** #441: the linked transfer aborted — nothing was paid; clear the link and return to payable. */
3375
- private revertSettlingToPayable;
3376
- /**
3377
- * Re-run one intent end-to-end: getToken (works for tombstoned rows — §5.3
3378
- * recovery surface), engine re-run under the original transferId, journaled
3379
- * delivery, the single §7 apply (inventory custody only), uniform close,
3380
- * and the SENT history record (dedupKey'd by transferId — idempotent).
3381
- */
3382
- private resumeIntent;
3383
3032
  /**
3384
3033
  * Persist the token state to every (non-disabled) token storage provider.
3385
3034
  *
@@ -3395,77 +3044,6 @@ declare class PaymentsModule {
3395
3044
  * behavior.
3396
3045
  */
3397
3046
  private save;
3398
- private loadPendingV2Deliveries;
3399
- /**
3400
- * #621: journaled finished blobs for an intent, keyed by op position. opIndex when present,
3401
- * else positional among the intent's entries (legacy entries journaled in op order). Resume
3402
- * uses this to re-deliver an already-certified op instead of re-running the engine on its spent source.
3403
- */
3404
- private journaledByOp;
3405
- /** #517: run a journal read-modify-write atomically against all other journal mutations. */
3406
- private withJournalLock;
3407
- private savePendingV2Delivery;
3408
- private removePendingV2Delivery;
3409
- /**
3410
- * The hoisted send delivery pass (#699): hand a send's journaled committed
3411
- * blobs to ONE recipient — a single deliverBatch call when the port offers
3412
- * it and there is more than one blob, else per-blob deliver at the
3413
- * certification fan-out width. Returns true iff every blob was delivered
3414
- * (deferred blobs stay journaled). Never throws (§3.1).
3415
- */
3416
- private deliverCommittedBlobs;
3417
- /**
3418
- * Batch sibling of {@link tryDeliver}: success clears every journal entry;
3419
- * ANY failure keeps them ALL journaled (the deposit is idempotent by
3420
- * content-derived entry_id, so a replay absorbs entries that already
3421
- * landed). Never throws.
3422
- */
3423
- private tryDeliverBatch;
3424
- /**
3425
- * Per-blob fallback (a batch-less port, or a single blob), chunked at
3426
- * MAX_SEND_OPERATION_CONCURRENCY so the pass keeps the certification
3427
- * fan-out era's delivery width. Never throws.
3428
- */
3429
- private deliverPerBlob;
3430
- /**
3431
- * Covenant §3.1 (#621): once the source is certified on-chain, a delivery failure must NEVER
3432
- * fail the sender. Recipient-side conditions (a full mailbox / 429 from a never-claiming recipient)
3433
- * and transient delivery-infra failures (5xx/network) both leave the finished blob JOURNALED for
3434
- * (re-)delivery — the send resolves as delivery-pending and the sender moves on. The blob is already
3435
- * journaled (savePendingV2Delivery ran before this); on success we clear the journal, on any failure
3436
- * we keep it. Returns true if delivered, false if deferred. Never throws.
3437
- */
3438
- private tryDeliver;
3439
- /**
3440
- * Replay journaled finished-but-undelivered v2 blobs (kicked from load())
3441
- * through the delivery port (sdk-changes S3). Idempotent end to end: the
3442
- * mailbox deposit is idempotent by the content-derived entry_id — it succeeds
3443
- * even after the recipient claimed (§6) — and the relay path's recipient
3444
- * dedups by the genesis-stable token id.
3445
- *
3446
- * #517 item 1: instead of a silent unbounded fire-and-forget, each entry is
3447
- * retried with bounded exponential backoff, its cumulative failed-attempt
3448
- * count is journaled across loads, and an entry that exhausts
3449
- * {@link MAX_DELIVERY_REPLAY_ATTEMPTS} is SURFACED as poison
3450
- * (`delivery:undeliverable`) and left journaled but no longer auto-retried —
3451
- * so a journaled blob never sits undelivered invisibly.
3452
- */
3453
- private replayPendingV2Deliveries;
3454
- /** Replay a single journaled entry: backoff retries → success/removal, or → bounded poison. */
3455
- private replayOneDelivery;
3456
- /**
3457
- * §3.1 (#621): defer a recipient-quota (429) delivery — keep it journaled, never poison it (it
3458
- * self-heals when the recipient claims), and retry no sooner than the deferral window. Surfaced
3459
- * distinctly (delivery:deferred) so a UI can show "recipient's mailbox is full — will retry".
3460
- */
3461
- private deferDelivery;
3462
- /** One delivery with in-pass exponential backoff; throws the last error if all attempts fail. */
3463
- private attemptDeliveryWithBackoff;
3464
- /** Mark a journal entry poison (keep it, stop retrying) and surface it (#517 item 1). */
3465
- private markDeliveryPoison;
3466
- private bumpDeliveryAttempts;
3467
- /** Mutate one journaled entry in place (keyed by tokenBlob) and persist. No-op if gone. */
3468
- private updatePendingV2Delivery;
3469
3047
  private createStorageData;
3470
3048
  private loadFromStorageData;
3471
3049
  /**
@@ -4879,7 +4457,7 @@ interface IncomingTransfer {
4879
4457
  readonly memo?: string;
4880
4458
  readonly receivedAt: number;
4881
4459
  }
4882
- type PaymentRequestStatus = 'pending' | 'accepted' | 'rejected' | 'paid' | 'expired' | 'settling';
4460
+ type PaymentRequestStatus = 'pending' | 'rejected' | 'paid' | 'expired' | 'settling';
4883
4461
  /**
4884
4462
  * Outgoing payment request (requesting payment from someone)
4885
4463
  */
@@ -4938,7 +4516,7 @@ type PaymentRequestHandler = (request: IncomingPaymentRequest) => void;
4938
4516
  /**
4939
4517
  * Response type for payment requests
4940
4518
  */
4941
- type PaymentRequestResponseType = 'accepted' | 'rejected' | 'paid';
4519
+ type PaymentRequestResponseType = 'rejected' | 'paid';
4942
4520
  /**
4943
4521
  * Outgoing payment request (we sent to someone)
4944
4522
  */
@@ -5040,7 +4618,7 @@ interface TrackedAddress extends TrackedAddressEntry {
5040
4618
  /** Primary nametag (from nametag cache, without @ prefix) */
5041
4619
  readonly nametag?: string;
5042
4620
  }
5043
- type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'transfer:delivery_pending' | 'transfer:invalid' | 'payment_request:incoming' | 'payment_request:accepted' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:expired' | 'payment_request:settling' | 'payment_request:response' | 'message:dm' | 'message:read' | 'message:typing' | 'composing:started' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:error' | 'storage:degraded' | 'inventory:conflict' | 'split:checkpoint-stuck' | 'send:partial-remainder' | 'delivery:undeliverable' | 'delivery:deferred' | 'walletapi:session' | 'realtime:status' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection' | 'groupchat:ready' | 'communications:ready' | 'history:updated' | 'invoice:created' | 'invoice:payment' | 'invoice:asset_covered' | 'invoice:target_covered' | 'invoice:covered' | 'invoice:closed' | 'invoice:cancelled' | 'invoice:expired' | 'invoice:unknown_reference' | 'invoice:overpayment' | 'invoice:irrelevant' | 'invoice:auto_returned' | 'invoice:auto_return_failed' | 'invoice:return_received' | 'invoice:over_refund_warning' | 'invoice:receipt_sent' | 'invoice:receipt_received' | 'invoice:cancellation_sent' | 'invoice:cancellation_received' | 'swap:proposal_received' | 'swap:proposed' | 'swap:accepted' | 'swap:rejected' | 'swap:announced' | 'swap:deposit_sent' | 'swap:deposit_confirmed' | 'swap:deposits_covered' | 'swap:concluding' | 'swap:payout_received' | 'swap:completed' | 'swap:cancelled' | 'swap:failed' | 'swap:deposit_returned' | 'swap:bounce_received';
4621
+ type SphereEventType = 'transfer:incoming' | 'transfer:confirmed' | 'transfer:failed' | 'transfer:delivery_pending' | 'transfer:invalid' | 'payment_request:incoming' | 'payment_request:rejected' | 'payment_request:paid' | 'payment_request:expired' | 'payment_request:settling' | 'payment_request:response' | 'message:dm' | 'message:read' | 'message:typing' | 'composing:started' | 'message:broadcast' | 'sync:started' | 'sync:completed' | 'sync:error' | 'storage:degraded' | 'inventory:conflict' | 'split:checkpoint-stuck' | 'send:partial-remainder' | 'delivery:undeliverable' | 'delivery:deferred' | 'walletapi:session' | 'realtime:status' | 'connection:changed' | 'nametag:registered' | 'nametag:recovered' | 'identity:changed' | 'address:activated' | 'address:hidden' | 'address:unhidden' | 'sync:remote-update' | 'groupchat:message' | 'groupchat:joined' | 'groupchat:left' | 'groupchat:kicked' | 'groupchat:group_deleted' | 'groupchat:updated' | 'groupchat:connection' | 'groupchat:ready' | 'communications:ready' | 'history:updated' | 'invoice:created' | 'invoice:payment' | 'invoice:asset_covered' | 'invoice:target_covered' | 'invoice:covered' | 'invoice:closed' | 'invoice:cancelled' | 'invoice:expired' | 'invoice:unknown_reference' | 'invoice:overpayment' | 'invoice:irrelevant' | 'invoice:auto_returned' | 'invoice:auto_return_failed' | 'invoice:return_received' | 'invoice:over_refund_warning' | 'invoice:receipt_sent' | 'invoice:receipt_received' | 'invoice:cancellation_sent' | 'invoice:cancellation_received' | 'swap:proposal_received' | 'swap:proposed' | 'swap:accepted' | 'swap:rejected' | 'swap:announced' | 'swap:deposit_sent' | 'swap:deposit_confirmed' | 'swap:deposits_covered' | 'swap:concluding' | 'swap:payout_received' | 'swap:completed' | 'swap:cancelled' | 'swap:failed' | 'swap:deposit_returned' | 'swap:bounce_received';
5044
4622
  interface SphereEventMap {
5045
4623
  'transfer:incoming': IncomingTransfer;
5046
4624
  'transfer:confirmed': TransferResult;
@@ -5063,7 +4641,6 @@ interface SphereEventMap {
5063
4641
  reason: string;
5064
4642
  };
5065
4643
  'payment_request:incoming': IncomingPaymentRequest;
5066
- 'payment_request:accepted': IncomingPaymentRequest;
5067
4644
  'payment_request:rejected': IncomingPaymentRequest;
5068
4645
  'payment_request:paid': IncomingPaymentRequest;
5069
4646
  'payment_request:expired': IncomingPaymentRequest;
@@ -7470,13 +7047,15 @@ interface DerivedAddressInfo {
7470
7047
  declare function discoverAddressesImpl(deriveTransportPubkey: (index: number) => DerivedAddressInfo, batchResolve: (transportPubkeys: string[]) => Promise<PeerInfo[]>, options?: DiscoverAddressesOptions): Promise<DiscoverAddressesResult>;
7471
7048
 
7472
7049
  /**
7473
- * Legacy File Serialization Types
7050
+ * Text Wallet Backup Serialization Types
7474
7051
  */
7475
7052
 
7476
- type LegacyFileType = 'dat' | 'txt' | 'json' | 'mnemonic' | 'unknown';
7477
7053
  /**
7478
- * Progress callback for decryption operations
7054
+ * Result of parsing a text wallet backup file
7479
7055
  */
7056
+ /** Backup file shapes `Sphere.importFromLegacyFile` accepts. Bitcoin Core `.dat` was removed (#604 — L3-only). */
7057
+ type LegacyFileType = 'txt' | 'json' | 'mnemonic' | 'unknown';
7058
+ /** Reports PBKDF2 progress while decrypting a password-protected backup. */
7480
7059
  type DecryptionProgressCallback = (iteration: number, total: number) => Promise<void> | void;
7481
7060
 
7482
7061
  /**
@@ -7520,7 +7099,6 @@ type DecryptionProgressCallback = (iteration: number, total: number) => Promise<
7520
7099
  */
7521
7100
 
7522
7101
  declare function isValidNametag(nametag: string): boolean;
7523
-
7524
7102
  /** Steps reported by the onProgress callback during wallet init/create/load/import */
7525
7103
  type InitProgressStep = 'clearing' | 'storing_keys' | 'initializing' | 'recovering_nametag' | 'registering_nametag' | 'syncing_identity' | 'syncing_tokens' | 'discovering_addresses' | 'finalizing' | 'complete';
7526
7104
  /** Progress info passed to onProgress callback */
@@ -8159,6 +7737,43 @@ declare class Sphere {
8159
7737
  * modules fall back to their legacy path, same as init).
8160
7738
  */
8161
7739
  setOracleApiKey(apiKey: string): Promise<void>;
7740
+ /**
7741
+ * Import a wallet from a backup file: a `UNICITY WALLET DETAILS` text backup
7742
+ * (the format `exportToTxt` writes, optionally password-encrypted), a legacy
7743
+ * flat-JSON webwallet export, or a bare mnemonic in a text file.
7744
+ *
7745
+ * @example
7746
+ * const result = await Sphere.importFromLegacyFile({
7747
+ * fileContent: await file.text(),
7748
+ * fileName: file.name,
7749
+ * password, // when the backup is encrypted
7750
+ * ...providers,
7751
+ * });
7752
+ */
7753
+ static importFromLegacyFile(options: Omit<SphereImportOptions, 'mnemonic' | 'masterKey' | 'chainCode' | 'derivationPath' | 'basePath' | 'derivationMode'> & {
7754
+ /** The backup file's text. */
7755
+ fileContent: string;
7756
+ /** File name (used for type detection) */
7757
+ fileName: string;
7758
+ /** Password for encrypted files */
7759
+ password?: string;
7760
+ /** Progress callback for long decryption operations */
7761
+ onDecryptProgress?: DecryptionProgressCallback;
7762
+ }): Promise<{
7763
+ success: boolean;
7764
+ sphere?: Sphere;
7765
+ mnemonic?: string;
7766
+ needsPassword?: boolean;
7767
+ error?: string;
7768
+ }>;
7769
+ /**
7770
+ * Detect legacy file type from filename and content
7771
+ */
7772
+ static detectLegacyFileType(fileName: string, content: string): LegacyFileType;
7773
+ /**
7774
+ * Check if a legacy file is encrypted
7775
+ */
7776
+ static isLegacyFileEncrypted(fileName: string, content: string): boolean;
8162
7777
  /**
8163
7778
  * Check if wallet has BIP32 master key for HD derivation
8164
7779
  */
@@ -8243,60 +7858,6 @@ declare class Sphere {
8243
7858
  mnemonic?: string;
8244
7859
  error?: string;
8245
7860
  }>;
8246
- /**
8247
- * Import wallet from legacy file (.dat, .txt, or mnemonic text)
8248
- *
8249
- * Supports:
8250
- * - Bitcoin Core wallet.dat files (SQLite format, encrypted or unencrypted)
8251
- * - Text backup files (UNICITY WALLET DETAILS format)
8252
- * - Plain mnemonic text (12 or 24 words)
8253
- *
8254
- * @returns Object with success status, created Sphere instance, and optionally recovered mnemonic
8255
- *
8256
- * @example
8257
- * ```ts
8258
- * // Import from .dat file
8259
- * const fileBuffer = await file.arrayBuffer();
8260
- * const result = await Sphere.importFromLegacyFile({
8261
- * fileContent: new Uint8Array(fileBuffer),
8262
- * fileName: 'wallet.dat',
8263
- * password: 'wallet-password', // if encrypted
8264
- * storage, transport, oracle,
8265
- * });
8266
- *
8267
- * // Import from .txt file
8268
- * const textContent = await file.text();
8269
- * const result = await Sphere.importFromLegacyFile({
8270
- * fileContent: textContent,
8271
- * fileName: 'backup.txt',
8272
- * storage, transport, oracle,
8273
- * });
8274
- * ```
8275
- */
8276
- static importFromLegacyFile(options: Omit<SphereImportOptions, 'mnemonic' | 'masterKey' | 'chainCode' | 'derivationPath' | 'basePath' | 'derivationMode'> & {
8277
- /** File content - Uint8Array for .dat, string for .txt */
8278
- fileContent: string | Uint8Array;
8279
- /** File name (used for type detection) */
8280
- fileName: string;
8281
- /** Password for encrypted files */
8282
- password?: string;
8283
- /** Progress callback for long decryption operations */
8284
- onDecryptProgress?: DecryptionProgressCallback;
8285
- }): Promise<{
8286
- success: boolean;
8287
- sphere?: Sphere;
8288
- mnemonic?: string;
8289
- needsPassword?: boolean;
8290
- error?: string;
8291
- }>;
8292
- /**
8293
- * Detect legacy file type from filename and content
8294
- */
8295
- static detectLegacyFileType(fileName: string, content: string | Uint8Array): LegacyFileType;
8296
- /**
8297
- * Check if a legacy file is encrypted
8298
- */
8299
- static isLegacyFileEncrypted(fileName: string, content: string | Uint8Array): boolean;
8300
7861
  /**
8301
7862
  * Get the current active address index
8302
7863
  *
@@ -9036,59 +8597,6 @@ declare const CurrencyUtils: {
9036
8597
  format: typeof formatAmount;
9037
8598
  };
9038
8599
 
9039
- /**
9040
- * Bech32 Encoding/Decoding
9041
- * BIP-173 implementation for address encoding
9042
- */
9043
- /** Bech32 character set from BIP-173 */
9044
- declare const CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
9045
- /**
9046
- * Convert between bit arrays (8→5 or 5→8)
9047
- */
9048
- declare function convertBits(data: number[], fromBits: number, toBits: number, pad: boolean): number[] | null;
9049
- /**
9050
- * Encode data to bech32 address
9051
- *
9052
- * @example
9053
- * ```ts
9054
- * const address = encodeBech32('bc', 1, pubkeyHash);
9055
- * // 'bc1qw...'
9056
- * ```
9057
- */
9058
- declare function encodeBech32(hrp: string, version: number, program: Uint8Array): string;
9059
- /**
9060
- * Decode bech32 address
9061
- *
9062
- * @example
9063
- * ```ts
9064
- * const result = decodeBech32('bc1qw...');
9065
- * // { hrp: 'bc', witnessVersion: 1, data: Uint8Array }
9066
- * ```
9067
- */
9068
- declare function decodeBech32(addr: string): {
9069
- hrp: string;
9070
- witnessVersion: number;
9071
- data: Uint8Array;
9072
- } | null;
9073
- /**
9074
- * Create address from public key hash
9075
- *
9076
- * @example
9077
- * ```ts
9078
- * const address = createAddress('bc', pubkeyHash);
9079
- * // 'bc1...'
9080
- * ```
9081
- */
9082
- declare function createAddress(hrp: string, pubkeyHash: Uint8Array | string): string;
9083
- /**
9084
- * Validate bech32 address
9085
- */
9086
- declare function isValidBech32(addr: string): boolean;
9087
- /**
9088
- * Get HRP from address
9089
- */
9090
- declare function getAddressHrp(addr: string): string | null;
9091
-
9092
8600
  /**
9093
8601
  * Browser-safe UUID v4 generation.
9094
8602
  *
@@ -9335,4 +8843,4 @@ interface CheckNetworkHealthOptions {
9335
8843
  */
9336
8844
  declare function checkNetworkHealth(network?: NetworkType, options?: CheckNetworkHealthOptions): Promise<NetworkHealthResult>;
9337
8845
 
9338
- export { type AddressInfo, type AddressModuleSet, CHARSET, type CheckNetworkHealthOptions, CurrencyUtils, DEFAULT_DERIVATION_PATH, DEFAULT_TOKEN_DECIMALS, DELIVERY_ENCRYPTION_HKDF_INFO, type DeliveryBundle, type DerivedKey, type DiscoverAddressProgress, type DiscoverAddressesOptions, type DiscoverAddressesResult, type DiscoveredAddress, type EncryptedData, type EncryptionOptions, FIELD_ENCRYPTION_HKDF_INFO, FIELD_ENVELOPE_MAX_BYTES, FIELD_ENVELOPE_NONCE_BYTES, FIELD_ENVELOPE_PREFIX, type InitProgress, type InitProgressCallback, type InitProgressStep, type KeyPair, type LogHandler, type LogLevel, type LoggerConfig, type MasterKey, PartialSendConflictError, SIGN_MESSAGE_PREFIX, Sphere, type SphereCreateOptions, SphereError, type SphereErrorCode, type SphereImportOptions, type SphereInitOptions, type SphereInitResult, type SphereLoadOptions, type SphereWalletApiSession, assertFieldEnvelopeShape, base58Decode, base58Encode, bytesToHex, checkNetworkHealth, convertBits, createAddress, createKeyPair, createSphere, decodeBech32, decrypt, decryptDeliveryBundle, decryptField, decryptFieldBytes, decryptJson, decryptMnemonic, decryptSimple, decryptWithSalt, deriveAddressInfo, deriveChildKey, deriveDeliveryEncryptionKey, deriveFieldEncryptionKey, deriveKeyAtPath, deserializeEncrypted, discoverAddressesImpl, doubleSha256, encodeBech32, encrypt, encryptDeliveryBundle, encryptField, encryptFieldBytes, encryptMnemonic, encryptSimple, entropyToMnemonic, extractFromText, findPattern, formatAmount, generateAddressFromMasterKey, generateAddressInfo, generateMasterKey, generateMnemonic, generateRandomKey, getAddressHrp, getPublicKey, getSphere, hash160, hashSignMessage, hexToBytes, identityFromMnemonic, identityFromMnemonicSync, importSphere, initSphere, isEncryptedData, isPossiblyCommittedSendOutcome, isSphereError, isValidBech32, isValidNametag, isValidPrivateKey, loadSphere, logger, mnemonicToEntropy, mnemonicToSeed, mnemonicToSeedSync, parseTokenAmount, randomBytes, randomHex, randomUUID, recoverPubkeyFromSignature, ripemd160, safeParseTokenAmount, serializeEncrypted, sha256, signMessage, sleep, sphereExists, toHumanReadable, validateMnemonic, verifySignedMessage };
8846
+ export { type AddressInfo, type AddressModuleSet, type CheckNetworkHealthOptions, CurrencyUtils, DEFAULT_DERIVATION_PATH, DEFAULT_TOKEN_DECIMALS, DELIVERY_ENCRYPTION_HKDF_INFO, type DeliveryBundle, type DerivedKey, type DiscoverAddressProgress, type DiscoverAddressesOptions, type DiscoverAddressesResult, type DiscoveredAddress, type EncryptedData, type EncryptionOptions, FIELD_ENCRYPTION_HKDF_INFO, FIELD_ENVELOPE_MAX_BYTES, FIELD_ENVELOPE_NONCE_BYTES, FIELD_ENVELOPE_PREFIX, type InitProgress, type InitProgressCallback, type InitProgressStep, type KeyPair, type LogHandler, type LogLevel, type LoggerConfig, type MasterKey, PartialSendConflictError, SIGN_MESSAGE_PREFIX, Sphere, type SphereCreateOptions, SphereError, type SphereErrorCode, type SphereImportOptions, type SphereInitOptions, type SphereInitResult, type SphereLoadOptions, type SphereWalletApiSession, assertFieldEnvelopeShape, base58Decode, base58Encode, bytesToHex, checkNetworkHealth, createKeyPair, createSphere, decrypt, decryptDeliveryBundle, decryptField, decryptFieldBytes, decryptJson, decryptMnemonic, decryptSimple, decryptWithSalt, deriveAddressInfo, deriveChildKey, deriveDeliveryEncryptionKey, deriveFieldEncryptionKey, deriveKeyAtPath, deserializeEncrypted, discoverAddressesImpl, doubleSha256, encrypt, encryptDeliveryBundle, encryptField, encryptFieldBytes, encryptMnemonic, encryptSimple, entropyToMnemonic, extractFromText, findPattern, formatAmount, generateAddressFromMasterKey, generateAddressInfo, generateMasterKey, generateMnemonic, generateRandomKey, getPublicKey, getSphere, hash160, hashSignMessage, hexToBytes, identityFromMnemonic, identityFromMnemonicSync, importSphere, initSphere, isEncryptedData, isPossiblyCommittedSendOutcome, isSphereError, isValidNametag, isValidPrivateKey, loadSphere, logger, mnemonicToEntropy, mnemonicToSeed, mnemonicToSeedSync, parseTokenAmount, randomBytes, randomHex, randomUUID, recoverPubkeyFromSignature, ripemd160, safeParseTokenAmount, serializeEncrypted, sha256, signMessage, sleep, sphereExists, toHumanReadable, validateMnemonic, verifySignedMessage };