@qntx/spark 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,6 +68,13 @@ those inputs once. Retrying without it would select different outputs and pay tw
68
68
  Offline receive: once a transfer commits, the sender cannot cancel it and its expiry does not
69
69
  return it — `transfers.claim(transferId)`/`claimPending` can pick it up at any later time.
70
70
 
71
+ Transaction history: `transfers.history({ limit?, offset? })` pages transfers newest-first
72
+ (`TRANSFER`, `PREIMAGE_SWAP`, `COOPERATIVE_EXIT`, `UTXO_SWAP`), folding each entry with its SSP
73
+ `user_request` when the counterparty is the SSP — lightning invoices, payment hashes and
74
+ preimages, coop-exit fees and payout txids, static-deposit credits. Entries surface as
75
+ `SparkPayment` (`send`/`receive`, `pending`/`completed`/`failed`, `spark`/`lightning`/`deposit`/
76
+ `withdraw`); `nextOffset` is `undefined` at the end.
77
+
71
78
  Unilateral exit: each pre-signed transaction carries a hard deadline — once its parent confirms
72
79
  and its relative timelock passes, it must confirm within 50 blocks; otherwise the operators'
73
80
  watchtower may publish a self-fee variant instead. For intermediate nodes this strands
@@ -332,6 +332,43 @@ type SparkTransfer = {
332
332
  readonly leaves: ReadonlyArray<SparkTransferLeaf>;
333
333
  };
334
334
  //#endregion
335
+ //#region src/transfer/payment.d.ts
336
+ /**
337
+ * One entry of `client.transfers.history`: a transfer folded with its SSP `user_request` into the
338
+ * wallet-facing payment shape. Mapping follows the Breez adaptor semantics; every field a consumer
339
+ * displays comes from authenticated SO data or the SSP record — never from host input.
340
+ */
341
+ type SparkPayment = {
342
+ readonly id: string;
343
+ readonly direction: "send" | "receive";
344
+ readonly status: "pending" | "completed" | "failed";
345
+ readonly method: "spark" | "lightning" | "deposit" | "withdraw";
346
+ /** Sats excluding fees — the total minus the fee for outbound sends and exits. */
347
+ readonly amountSats: number;
348
+ readonly feeSats: number;
349
+ /** Milliseconds since the epoch; absent when the SO reports none. */
350
+ readonly createdAt: number | undefined;
351
+ readonly details: {
352
+ readonly kind: "spark";
353
+ readonly invoice?: string;
354
+ } | {
355
+ readonly kind: "lightning";
356
+ readonly invoice: string;
357
+ /** Payment hash, hex. */
358
+ readonly paymentHash: string;
359
+ /** Payment preimage, hex. */
360
+ readonly preimage?: string;
361
+ readonly description?: string;
362
+ } | {
363
+ readonly kind: "withdraw";
364
+ readonly txid?: string;
365
+ } | {
366
+ readonly kind: "deposit";
367
+ readonly txid: string;
368
+ readonly vout: number;
369
+ } | undefined;
370
+ };
371
+ //#endregion
335
372
  //#region src/core/bitcoin/cpfp.d.ts
336
373
  type FundingInput = {
337
374
  /** Display-order hex txid of the confirmed funding output. */
@@ -430,6 +467,19 @@ type SparkClient = {
430
467
  readonly limit?: number;
431
468
  readonly offset?: number;
432
469
  }) => Promise<ReadonlyArray<SparkTransfer>>;
470
+ /**
471
+ * Transfer history as wallet-facing payments: TRANSFER, PREIMAGE_SWAP, COOPERATIVE_EXIT and
472
+ * UTXO_SWAP records (newest first) folded with their SSP user requests when the counterparty is
473
+ * the SSP — one batched SSP call per page. `limit` is 1–100 (default 20); `nextOffset` is the
474
+ * offset of the next page, `undefined` at the end.
475
+ */
476
+ readonly history: (options?: {
477
+ readonly limit?: number;
478
+ readonly offset?: number;
479
+ }) => Promise<{
480
+ readonly payments: ReadonlyArray<SparkPayment>;
481
+ readonly nextOffset: number | undefined;
482
+ }>;
433
483
  /**
434
484
  * Sends exactly `amountSats` to `receiver` (a `spark1…`/`sparkrt1…` address string or a 33-byte
435
485
  * identity public key) via `start_transfer_v2` — selecting exact leaves, or swapping with the
@@ -823,4 +873,4 @@ type SparkClientOptions = {
823
873
  */
824
874
  declare function createSparkClient(options: SparkClientOptions): SparkClient;
825
875
  //#endregion
826
- export { CooperativeExitStatus as A, exitTransactions as C, CooperativeExitQuote as D, createMemorySparkStore as E, UnusedDepositAddress as F, SparkLeaf as I, SparkEvent as L, StaticDepositClaim as M, StaticDepositRefund as N, CooperativeExitResult as O, StaticDepositUtxo as P, ExitTransaction as S, SparkStore as T, LightningSendStatus as _, createSparkClient as a, LightningInvoice as b, buildCpfpChild as c, SparkTokenBalance as d, SparkTokenOutput as f, LightningSendOutcome as g, LightningReceiveStatus as h, SparkInvoiceStatus as i, MIN_STATIC_REFUND_FEE_SATS as j, CooperativeExitSpeed as k, SparkTransfer as l, SparkWalletSettings as m, SparkClient as n, StaticDepositClaimResult as o, SparkTokenOutputStatus as p, SparkClientOptions as r, FundingInput as s, ClaimPendingResult as t, SparkTransferLeaf as u, LightningSendStatusRequest as v, SparkExitChain as w, WatchtowerExitedLeaf as x, LightningPayment as y };
876
+ export { CooperativeExitSpeed as A, ExitTransaction as C, createMemorySparkStore as D, SparkStore as E, StaticDepositUtxo as F, UnusedDepositAddress as I, SparkLeaf as L, MIN_STATIC_REFUND_FEE_SATS as M, StaticDepositClaim as N, CooperativeExitQuote as O, StaticDepositRefund as P, SparkEvent as R, WatchtowerExitedLeaf as S, SparkExitChain as T, LightningSendOutcome as _, createSparkClient as a, LightningPayment as b, buildCpfpChild as c, SparkTransferLeaf as d, SparkTokenBalance as f, LightningReceiveStatus as g, SparkWalletSettings as h, SparkInvoiceStatus as i, CooperativeExitStatus as j, CooperativeExitResult as k, SparkPayment as l, SparkTokenOutputStatus as m, SparkClient as n, StaticDepositClaimResult as o, SparkTokenOutput as p, SparkClientOptions as r, FundingInput as s, ClaimPendingResult as t, SparkTransfer as u, LightningSendStatus as v, exitTransactions as w, LightningInvoice as x, LightningSendStatusRequest as y };
package/dist/index.d.mts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { a as SparkTokenBroadcastError, i as SparkRequestError, n as SparkError, r as SparkInputError, t as SparkCryptoError } from "./index-D1fBaHTa.mjs";
2
2
  import { a as sparkNetworkConfig, i as SparkOperatorConfig, n as SparkNetwork, r as SparkNetworkConfig } from "./lightning-address-CRULmi9v.mjs";
3
3
  import { i as isExpired, n as Bolt11Network, r as decodeBolt11, t as Bolt11Invoice } from "./bolt11-DsNTTS_O.mjs";
4
- import { A as CooperativeExitStatus, C as exitTransactions, D as CooperativeExitQuote, E as createMemorySparkStore, F as UnusedDepositAddress, I as SparkLeaf, L as SparkEvent, M as StaticDepositClaim, N as StaticDepositRefund, O as CooperativeExitResult, P as StaticDepositUtxo, S as ExitTransaction, T as SparkStore, _ as LightningSendStatus, a as createSparkClient, b as LightningInvoice, c as buildCpfpChild, d as SparkTokenBalance, f as SparkTokenOutput, g as LightningSendOutcome, h as LightningReceiveStatus, i as SparkInvoiceStatus, j as MIN_STATIC_REFUND_FEE_SATS, k as CooperativeExitSpeed, l as SparkTransfer, m as SparkWalletSettings, n as SparkClient, o as StaticDepositClaimResult, p as SparkTokenOutputStatus, r as SparkClientOptions, s as FundingInput, t as ClaimPendingResult, u as SparkTransferLeaf, v as LightningSendStatusRequest, w as SparkExitChain, x as WatchtowerExitedLeaf, y as LightningPayment } from "./index-rHuqmVV3.mjs";
4
+ import { A as CooperativeExitSpeed, C as ExitTransaction, D as createMemorySparkStore, E as SparkStore, F as StaticDepositUtxo, I as UnusedDepositAddress, L as SparkLeaf, M as MIN_STATIC_REFUND_FEE_SATS, N as StaticDepositClaim, O as CooperativeExitQuote, P as StaticDepositRefund, R as SparkEvent, S as WatchtowerExitedLeaf, T as SparkExitChain, _ as LightningSendOutcome, a as createSparkClient, b as LightningPayment, c as buildCpfpChild, d as SparkTransferLeaf, f as SparkTokenBalance, g as LightningReceiveStatus, h as SparkWalletSettings, i as SparkInvoiceStatus, j as CooperativeExitStatus, k as CooperativeExitResult, l as SparkPayment, m as SparkTokenOutputStatus, n as SparkClient, o as StaticDepositClaimResult, p as SparkTokenOutput, r as SparkClientOptions, s as FundingInput, t as ClaimPendingResult, u as SparkTransfer, v as LightningSendStatus, w as exitTransactions, x as LightningInvoice, y as LightningSendStatusRequest } from "./index-Bq6pNclo.mjs";
5
5
  import { n as EPHEMERAL_ANCHOR_SCRIPT, t as addressToScript, v as SparkSigner } from "./transactions-DjTZU8ev.mjs";
6
6
  //#region src/core/invoice.d.ts
7
7
  /** Payment carried by a Spark invoice: satoshis or BTKN tokens. */
8
- type SparkPayment = {
8
+ type SparkInvoicePayment = {
9
9
  readonly type: "sats";
10
10
  readonly amount?: number | undefined;
11
11
  } | {
@@ -20,7 +20,7 @@ type SparkInvoiceFields = {
20
20
  readonly version: number;
21
21
  /** UUID identifying the invoice (canonical lowercase form). */
22
22
  readonly id: string;
23
- readonly payment?: SparkPayment | undefined;
23
+ readonly payment?: SparkInvoicePayment | undefined;
24
24
  /** At most 120 UTF-8 bytes. */
25
25
  readonly memo?: string | undefined;
26
26
  /** 33-byte compressed secp256k1 public key. */
@@ -120,4 +120,4 @@ export declare function parseSparkInvoice(text: string, options: {
120
120
  readonly now: number;
121
121
  }): ParsedSparkInvoice;
122
122
  //#endregion
123
- export { type Bolt11Invoice, type Bolt11Network, type ClaimPendingResult, type CooperativeExitQuote, type CooperativeExitResult, type CooperativeExitSpeed, type CooperativeExitStatus, EPHEMERAL_ANCHOR_SCRIPT, type ExitTransaction, type FundingInput, type LightningInvoice, type LightningPayment, type LightningReceiveStatus, type LightningSendOutcome, type LightningSendStatus, type LightningSendStatusRequest, MIN_STATIC_REFUND_FEE_SATS, type ParsedSparkInvoice, type SparkAddress, type SparkClient, type SparkClientOptions, SparkCryptoError, SparkError, type SparkEvent, type SparkExitChain, SparkInputError, type SparkInvoiceFields, type SparkInvoiceStatus, type SparkLeaf, type SparkNetwork, type SparkNetworkConfig, type SparkOperatorConfig, type SparkPayment, SparkRequestError, type SparkStore, type SparkTokenBalance, SparkTokenBroadcastError, type SparkTokenIdentifierData, type SparkTokenOutput, type SparkTokenOutputStatus, type SparkTransfer, type SparkTransferLeaf, type SparkWalletSettings, type StaticDepositClaim, type StaticDepositClaimResult, type StaticDepositRefund, type StaticDepositUtxo, type UnusedDepositAddress, type WatchtowerExitedLeaf, addressToScript, buildCpfpChild, createMemorySparkStore, createSparkClient, decodeBolt11, exitTransactions, isExpired, sparkNetworkConfig };
123
+ export { type Bolt11Invoice, type Bolt11Network, type ClaimPendingResult, type CooperativeExitQuote, type CooperativeExitResult, type CooperativeExitSpeed, type CooperativeExitStatus, EPHEMERAL_ANCHOR_SCRIPT, type ExitTransaction, type FundingInput, type LightningInvoice, type LightningPayment, type LightningReceiveStatus, type LightningSendOutcome, type LightningSendStatus, type LightningSendStatusRequest, MIN_STATIC_REFUND_FEE_SATS, type ParsedSparkInvoice, type SparkAddress, type SparkClient, type SparkClientOptions, SparkCryptoError, SparkError, type SparkEvent, type SparkExitChain, SparkInputError, type SparkInvoiceFields, type SparkInvoicePayment, type SparkInvoiceStatus, type SparkLeaf, type SparkNetwork, type SparkNetworkConfig, type SparkOperatorConfig, type SparkPayment, SparkRequestError, type SparkStore, type SparkTokenBalance, SparkTokenBroadcastError, type SparkTokenIdentifierData, type SparkTokenOutput, type SparkTokenOutputStatus, type SparkTransfer, type SparkTransferLeaf, type SparkWalletSettings, type StaticDepositClaim, type StaticDepositClaimResult, type StaticDepositRefund, type StaticDepositUtxo, type UnusedDepositAddress, type WatchtowerExitedLeaf, addressToScript, buildCpfpChild, createMemorySparkStore, createSparkClient, decodeBolt11, exitTransactions, isExpired, sparkNetworkConfig };
package/dist/index.mjs CHANGED
@@ -11,7 +11,7 @@ import { base64, bech32m } from "@scure/base";
11
11
  import { sha256 } from "@noble/hashes/sha2.js";
12
12
  import { Code, ConnectError, createClient } from "@connectrpc/connect";
13
13
  import { createGrpcWebTransport } from "@connectrpc/connect-web";
14
- import { bytesToNumberBE, equalBytes, hexToBytes as hexToBytes$1, numberToBytesBE, numberToVarBytesBE } from "@noble/curves/utils.js";
14
+ import { bytesToHex as bytesToHex$1, bytesToNumberBE, equalBytes, hexToBytes as hexToBytes$1, numberToBytesBE, numberToVarBytesBE } from "@noble/curves/utils.js";
15
15
  import { Address, OutScript, Transaction } from "@scure/btc-signer";
16
16
  //#region src/core/address.ts
17
17
  const BECH32M_LIMIT$1 = 1024;
@@ -1229,6 +1229,94 @@ fragment UserRequestFragment on UserRequest {
1229
1229
  lightning_send_request_payment_preimage: payment_preimage
1230
1230
  }
1231
1231
  }`;
1232
+ const TRANSFERS = `
1233
+ query Transfers($transfer_spark_ids: [UUID!]!) {
1234
+ transfers(transfer_spark_ids: $transfer_spark_ids) {
1235
+ ...TransferFragment
1236
+ transfer_user_request: user_request {
1237
+ ...UserRequestFragment
1238
+ }
1239
+ }
1240
+ }
1241
+
1242
+ fragment TransferFragment on Transfer {
1243
+ __typename
1244
+ transfer_spark_id: spark_id
1245
+ }
1246
+
1247
+ fragment CurrencyAmountFragment on CurrencyAmount {
1248
+ __typename
1249
+ currency_amount_original_value: original_value
1250
+ currency_amount_original_unit: original_unit
1251
+ }
1252
+
1253
+ fragment TransferSparkIdFragment on Transfer {
1254
+ __typename
1255
+ transfer_spark_id: spark_id
1256
+ }
1257
+
1258
+ fragment UserRequestFragment on UserRequest {
1259
+ __typename
1260
+ ... on ClaimStaticDeposit {
1261
+ claim_static_deposit_id: id
1262
+ claim_static_deposit_deposit_amount: deposit_amount {
1263
+ ...CurrencyAmountFragment
1264
+ }
1265
+ claim_static_deposit_credit_amount: credit_amount {
1266
+ ...CurrencyAmountFragment
1267
+ }
1268
+ claim_static_deposit_max_fee: max_fee {
1269
+ ...CurrencyAmountFragment
1270
+ }
1271
+ claim_static_deposit_status: status
1272
+ claim_static_deposit_transaction_id: transaction_id
1273
+ claim_static_deposit_output_index: output_index
1274
+ claim_static_deposit_transfer_spark_id: transfer_spark_id
1275
+ }
1276
+ ... on CoopExitRequest {
1277
+ coop_exit_request_id: id
1278
+ coop_exit_request_fee: fee {
1279
+ ...CurrencyAmountFragment
1280
+ }
1281
+ coop_exit_request_l1_broadcast_fee: l1_broadcast_fee {
1282
+ ...CurrencyAmountFragment
1283
+ }
1284
+ coop_exit_request_status: status
1285
+ coop_exit_request_coop_exit_txid: coop_exit_txid
1286
+ coop_exit_request_transfer: transfer {
1287
+ ...TransferSparkIdFragment
1288
+ }
1289
+ }
1290
+ ... on LeavesSwapRequest {
1291
+ leaves_swap_request_id: id
1292
+ leaves_swap_request_status: status
1293
+ }
1294
+ ... on LightningReceiveRequest {
1295
+ lightning_receive_request_id: id
1296
+ lightning_receive_request_invoice: invoice {
1297
+ __typename
1298
+ invoice_encoded_invoice: encoded_invoice
1299
+ invoice_payment_hash: payment_hash
1300
+ }
1301
+ lightning_receive_request_status: status
1302
+ lightning_receive_request_transfer: transfer {
1303
+ ...TransferSparkIdFragment
1304
+ }
1305
+ lightning_receive_request_payment_preimage: payment_preimage
1306
+ }
1307
+ ... on LightningSendRequest {
1308
+ lightning_send_request_id: id
1309
+ lightning_send_request_encoded_invoice: encoded_invoice
1310
+ lightning_send_request_fee: fee {
1311
+ ...CurrencyAmountFragment
1312
+ }
1313
+ lightning_send_request_status: status
1314
+ lightning_send_request_transfer: transfer {
1315
+ ...TransferSparkIdFragment
1316
+ }
1317
+ lightning_send_request_payment_preimage: payment_preimage
1318
+ }
1319
+ }`;
1232
1320
  //#endregion
1233
1321
  //#region src/ssp/parse.ts
1234
1322
  /** A malformed SSP response is a server fault, not caller input. */
@@ -1251,6 +1339,11 @@ function asSats(value, field) {
1251
1339
  if (n === void 0 || !Number.isSafeInteger(n) || n < 0) throw unexpectedResponse(field, "is not a non-negative integer");
1252
1340
  return n;
1253
1341
  }
1342
+ /** A GraphQL Int field used as an array/output index — a number, not a decimal string. */
1343
+ function asIndex(value, field) {
1344
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw unexpectedResponse(field, "is not a non-negative integer");
1345
+ return value;
1346
+ }
1254
1347
  /** CurrencyAmount in SATOSHI → sats; any other unit is an unexpected response. */
1255
1348
  function currencyAmountSats(value, field) {
1256
1349
  const amount = asObject(value, field);
@@ -1371,6 +1464,75 @@ function parseCoopExitResult(request, field) {
1371
1464
  rawConnectorTransaction: asHexBytes(request["coop_exit_request_raw_connector_transaction"], `${field}.coop_exit_request_raw_connector_transaction`)
1372
1465
  };
1373
1466
  }
1467
+ function optionalTxidHex(value, field) {
1468
+ if (value === void 0 || value === null) return;
1469
+ return asTxidHex(value, field);
1470
+ }
1471
+ /** The `transfers` query's per-item parse: id plus its user_request union member (or none). */
1472
+ function parseTransfersResponse(data) {
1473
+ const items = data["transfers"];
1474
+ if (!Array.isArray(items)) throw unexpectedResponse("transfers", "is not an array");
1475
+ return items.map((item, index) => {
1476
+ const field = `transfers[${index}]`;
1477
+ const entry = asObject(item, field);
1478
+ const request = entry["transfer_user_request"];
1479
+ return {
1480
+ transferId: asString(entry["transfer_spark_id"], `${field}.transfer_spark_id`),
1481
+ request: request === void 0 || request === null ? void 0 : parseUserRequest(asObject(request, `${field}.transfer_user_request`), `${field}.transfer_user_request`)
1482
+ };
1483
+ });
1484
+ }
1485
+ /**
1486
+ * Discriminated `user_request` parse. An unknown `__typename` is a variant we do not model, not a
1487
+ * malformed response.
1488
+ */
1489
+ function parseUserRequest(request, field) {
1490
+ switch (request["__typename"]) {
1491
+ case "LightningSendRequest": {
1492
+ const send = parseLightningSend(request, field);
1493
+ return {
1494
+ kind: "lightning-send",
1495
+ status: send.status,
1496
+ invoice: send.encodedInvoice,
1497
+ feeSats: send.feeSats,
1498
+ ...send.paymentPreimage === void 0 ? {} : { preimage: send.paymentPreimage }
1499
+ };
1500
+ }
1501
+ case "LightningReceiveRequest": {
1502
+ const receive = parseLightningReceive(request, field);
1503
+ return {
1504
+ kind: "lightning-receive",
1505
+ status: receive.status,
1506
+ invoice: receive.invoice,
1507
+ ...receive.paymentPreimage === void 0 ? {} : { preimage: receive.paymentPreimage }
1508
+ };
1509
+ }
1510
+ case "CoopExitRequest": return parseCoopExitRequest(request, field);
1511
+ case "ClaimStaticDeposit": return parseStaticDepositRequest(request, field);
1512
+ case "LeavesSwapRequest": return { kind: "leaves-swap" };
1513
+ default: return;
1514
+ }
1515
+ }
1516
+ /** `coop_exit` for history: fee = `fee` + `l1_broadcast_fee`, status and payout txid. */
1517
+ function parseCoopExitRequest(request, field) {
1518
+ const txid = optionalTxidHex(request["coop_exit_request_coop_exit_txid"], `${field}.coop_exit_request_coop_exit_txid`);
1519
+ return {
1520
+ kind: "coop-exit",
1521
+ status: asString(request["coop_exit_request_status"], `${field}.coop_exit_request_status`),
1522
+ feeSats: currencyAmountSats(request["coop_exit_request_fee"], `${field}.coop_exit_request_fee`) + currencyAmountSats(request["coop_exit_request_l1_broadcast_fee"], `${field}.coop_exit_request_l1_broadcast_fee`),
1523
+ ...txid === void 0 ? {} : { txid }
1524
+ };
1525
+ }
1526
+ /** `claim_static_deposit` for history: the claim's txid, vout and credited amount. */
1527
+ function parseStaticDepositRequest(request, field) {
1528
+ return {
1529
+ kind: "static-deposit",
1530
+ txid: asTxidHex(request["claim_static_deposit_transaction_id"], `${field}.claim_static_deposit_transaction_id`),
1531
+ vout: asIndex(request["claim_static_deposit_output_index"], `${field}.claim_static_deposit_output_index`),
1532
+ depositAmountSats: currencyAmountSats(request["claim_static_deposit_deposit_amount"], `${field}.claim_static_deposit_deposit_amount`),
1533
+ creditAmountSats: currencyAmountSats(request["claim_static_deposit_credit_amount"], `${field}.claim_static_deposit_credit_amount`)
1534
+ };
1535
+ }
1374
1536
  //#endregion
1375
1537
  //#region src/ssp/index.ts
1376
1538
  /**
@@ -1501,6 +1663,11 @@ function createSspClient(options) {
1501
1663
  }
1502
1664
  }
1503
1665
  return {
1666
+ async transfers(transferIds) {
1667
+ if (transferIds.length === 0) return [];
1668
+ if (transferIds.length > 100) throw new SparkInputError("transfers: at most 100 ids per call");
1669
+ return parseTransfersResponse(asObject(await readPost("Transfers", TRANSFERS, { transfer_spark_ids: [...transferIds] }), "transfers"));
1670
+ },
1504
1671
  async swapFeeEstimate(amountSats) {
1505
1672
  if (!Number.isSafeInteger(amountSats) || amountSats <= 0) throw new SparkInputError("amount sats must be a positive integer");
1506
1673
  return currencyAmountSats(asObject((await readPost("LeavesSwapFeeEstimate", LEAVES_SWAP_FEE_ESTIMATE, { total_amount_sats: amountSats }))["leaves_swap_fee_estimate"], "leaves_swap_fee_estimate")["leaves_swap_fee_estimate_output_fee_estimate"], "leaves_swap_fee_estimate_output_fee_estimate");
@@ -3706,17 +3873,27 @@ async function queryTransfersByIds(options) {
3706
3873
  network: sparkNetworkProto(options.network)
3707
3874
  }, options.call)).transfers;
3708
3875
  }
3709
- /** `query_all_transfers` — transfers where the identity is sender or receiver. */
3876
+ /**
3877
+ * `query_all_transfers` — transfers where the identity is sender or receiver. `types` filters on
3878
+ * the server side (empty means all). `nextOffset` is the offset of the following page — `undefined`
3879
+ * when the SO reports `-1` (a short page ends the listing).
3880
+ */
3710
3881
  async function queryAllTransfers(options) {
3711
- return (await options.spark.query_all_transfers({
3882
+ const response = await options.spark.query_all_transfers({
3712
3883
  participant: {
3713
3884
  case: "senderOrReceiverIdentityPublicKey",
3714
3885
  value: options.identityPublicKey
3715
3886
  },
3716
3887
  network: sparkNetworkProto(options.network),
3717
3888
  limit: BigInt(options.limit ?? 100),
3718
- offset: BigInt(options.offset ?? 0)
3719
- }, options.call)).transfers.map(mapTransfer);
3889
+ offset: BigInt(options.offset ?? 0),
3890
+ types: [...options.types ?? []]
3891
+ }, options.call);
3892
+ const nextOffset = Number(response.offset);
3893
+ return {
3894
+ transfers: response.transfers.map(mapTransfer),
3895
+ nextOffset: nextOffset < 0 ? void 0 : nextOffset
3896
+ };
3720
3897
  }
3721
3898
  /**
3722
3899
  * Whether `transfer` is exactly the one we signed for: our id, same type, our sender key, and the
@@ -4262,6 +4439,111 @@ async function claimByTransferId(options) {
4262
4439
  }
4263
4440
  }
4264
4441
  //#endregion
4442
+ //#region src/transfer/payment.ts
4443
+ /**
4444
+ * CoopExitRequest statuses, bucketed per Breez's `SparkCoopExitRequestStatus::acceptance`: accepted
4445
+ * means the SSP took the exit over — the SO-side transfer status keeps telling completion; terminal
4446
+ * failures are failed; anything else (incl. unknown values) is pending.
4447
+ */
4448
+ const COOP_EXIT_ACCEPTED$1 = /* @__PURE__ */ new Set([
4449
+ "INBOUND_TRANSFER_CHECKED",
4450
+ "TX_SIGNED",
4451
+ "TX_BROADCASTED",
4452
+ "WAITING_ON_TX_CONFIRMATIONS",
4453
+ "COMPLETE_REQUEST_RECEIVED",
4454
+ "ON_CHAIN_TX_CONFIRMED",
4455
+ "SUCCEEDED"
4456
+ ]);
4457
+ const COOP_EXIT_FAILED$1 = /* @__PURE__ */ new Set([
4458
+ "EXPIRED",
4459
+ "FAILED",
4460
+ "INBOUND_TRANSFER_CLAIMING_FAILED",
4461
+ "EXPIRING_FAILED",
4462
+ "FAILING_FAILED"
4463
+ ]);
4464
+ /**
4465
+ * Whether the transfer's counterparty is the SSP: the receiver on sends (any edge of a
4466
+ * multi-receiver), the sender on receives. Shared by the history batch-fetch and the mapper.
4467
+ */
4468
+ function sspCounterparty(transfer, identityPublicKey, sspIdentityPublicKey) {
4469
+ return equalBytes(transfer.senderIdentityPublicKey, identityPublicKey) ? equalBytes(transfer.receiverIdentityPublicKey, sspIdentityPublicKey) || transfer.receivers.some((receiver) => equalBytes(receiver.identityPublicKey, sspIdentityPublicKey)) : equalBytes(transfer.senderIdentityPublicKey, sspIdentityPublicKey);
4470
+ }
4471
+ function toSparkPayment(options) {
4472
+ const { transfer, request } = options;
4473
+ const direction = equalBytes(transfer.senderIdentityPublicKey, options.identityPublicKey) ? "send" : "receive";
4474
+ const isSsp = sspCounterparty(transfer, options.identityPublicKey, options.sspIdentityPublicKey);
4475
+ const method = transfer.type === "COOPERATIVE_EXIT" ? "withdraw" : transfer.type === "UTXO_SWAP" ? "deposit" : transfer.type === "PREIMAGE_SWAP" ? isSsp ? "lightning" : "spark" : "spark";
4476
+ let status;
4477
+ if (transfer.status === "COMPLETED") status = "completed";
4478
+ else if (direction === "send" && transfer.status === "SENDER_KEY_TWEAKED") status = "completed";
4479
+ else if (transfer.status === "EXPIRED" || transfer.status === "RETURNED") status = "failed";
4480
+ else status = "pending";
4481
+ let feeSats = 0;
4482
+ let amountSats = transfer.totalValueSats;
4483
+ let details;
4484
+ switch (method) {
4485
+ case "lightning":
4486
+ if (request?.kind === "lightning-send") {
4487
+ if (request.preimage !== void 0) status = "completed";
4488
+ ({feeSats} = request);
4489
+ amountSats = Math.max(0, transfer.totalValueSats - feeSats);
4490
+ details = lightningDetails(request.invoice, request.preimage);
4491
+ } else if (request?.kind === "lightning-receive") details = lightningDetails(request.invoice, request.preimage);
4492
+ break;
4493
+ case "withdraw":
4494
+ if (request?.kind === "coop-exit") {
4495
+ ({feeSats} = request);
4496
+ amountSats = Math.max(0, transfer.totalValueSats - feeSats);
4497
+ if (COOP_EXIT_FAILED$1.has(request.status)) status = "failed";
4498
+ else if (!COOP_EXIT_ACCEPTED$1.has(request.status)) status = "pending";
4499
+ details = {
4500
+ kind: "withdraw",
4501
+ ...request.txid === void 0 ? {} : { txid: request.txid }
4502
+ };
4503
+ }
4504
+ break;
4505
+ case "deposit":
4506
+ if (request?.kind === "static-deposit") {
4507
+ feeSats = Math.max(0, request.depositAmountSats - request.creditAmountSats);
4508
+ details = {
4509
+ kind: "deposit",
4510
+ txid: request.txid,
4511
+ vout: request.vout
4512
+ };
4513
+ }
4514
+ break;
4515
+ case "spark": details = {
4516
+ kind: "spark",
4517
+ ...transfer.sparkInvoice === void 0 ? {} : { invoice: transfer.sparkInvoice }
4518
+ };
4519
+ }
4520
+ if (method !== "spark" && details === void 0) status = "pending";
4521
+ return {
4522
+ id: transfer.id,
4523
+ direction,
4524
+ status,
4525
+ method,
4526
+ amountSats,
4527
+ feeSats,
4528
+ createdAt: transfer.createdTimeMs,
4529
+ details
4530
+ };
4531
+ }
4532
+ function lightningDetails(invoice, preimage) {
4533
+ try {
4534
+ const decoded = decodeBolt11(invoice);
4535
+ return {
4536
+ kind: "lightning",
4537
+ invoice,
4538
+ paymentHash: bytesToHex$1(decoded.paymentHash),
4539
+ ...decoded.description === void 0 ? {} : { description: decoded.description },
4540
+ ...preimage === void 0 ? {} : { preimage: bytesToHex$1(preimage) }
4541
+ };
4542
+ } catch {
4543
+ return;
4544
+ }
4545
+ }
4546
+ //#endregion
4265
4547
  //#region src/transfer/send.ts
4266
4548
  /**
4267
4549
  * The signed `TransferPackage` for a send-style transfer: every leaf gets a fresh recipient key
@@ -5892,13 +6174,49 @@ function createTransfers(context) {
5892
6174
  network
5893
6175
  })).transfers;
5894
6176
  },
5895
- list: async (listOptions) => queryAllTransfers({
5896
- spark,
5897
- identityPublicKey: await signer.identityPublicKey(),
5898
- network,
5899
- ...listOptions?.limit === void 0 ? {} : { limit: listOptions.limit },
5900
- ...listOptions?.offset === void 0 ? {} : { offset: listOptions.offset }
5901
- }),
6177
+ history: async (historyOptions) => {
6178
+ const limit = historyOptions?.limit ?? 20;
6179
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw new SparkInputError("history: limit must be an integer in 1–100");
6180
+ const offset = historyOptions?.offset ?? 0;
6181
+ if (!Number.isSafeInteger(offset) || offset < 0) throw new SparkInputError("history: offset must be a non-negative integer");
6182
+ const identityPublicKey = await signer.identityPublicKey();
6183
+ const sspIdentityPublicKey = context.config.ssp.identityPublicKey;
6184
+ const { transfers, nextOffset } = await queryAllTransfers({
6185
+ spark,
6186
+ identityPublicKey,
6187
+ network,
6188
+ limit,
6189
+ offset,
6190
+ types: [
6191
+ TransferType.TRANSFER,
6192
+ TransferType.PREIMAGE_SWAP,
6193
+ TransferType.COOPERATIVE_EXIT,
6194
+ TransferType.UTXO_SWAP
6195
+ ]
6196
+ });
6197
+ const sspIds = transfers.filter((transfer) => sspCounterparty(transfer, identityPublicKey, sspIdentityPublicKey)).map((transfer) => transfer.id);
6198
+ const requests = /* @__PURE__ */ new Map();
6199
+ for (const result of await context.sspClient.transfers(sspIds)) if (result.request !== void 0) requests.set(result.transferId, result.request);
6200
+ return {
6201
+ payments: transfers.map((transfer) => toSparkPayment({
6202
+ transfer,
6203
+ request: requests.get(transfer.id),
6204
+ identityPublicKey,
6205
+ sspIdentityPublicKey
6206
+ })),
6207
+ nextOffset
6208
+ };
6209
+ },
6210
+ list: async (listOptions) => {
6211
+ const { transfers } = await queryAllTransfers({
6212
+ spark,
6213
+ identityPublicKey: await signer.identityPublicKey(),
6214
+ network,
6215
+ ...listOptions?.limit === void 0 ? {} : { limit: listOptions.limit },
6216
+ ...listOptions?.offset === void 0 ? {} : { offset: listOptions.offset }
6217
+ });
6218
+ return transfers;
6219
+ },
5902
6220
  send: async (sendOptions) => sendSats(context, {
5903
6221
  receivers: [{
5904
6222
  receiverIdentityPublicKey: context.receiverIdentityKey(sendOptions.receiver),
package/dist/server.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as SparkClient } from "./index-rHuqmVV3.mjs";
1
+ import { n as SparkClient } from "./index-Bq6pNclo.mjs";
2
2
  //#region src/server/store.d.ts
3
3
  type LightningAddressUser = {
4
4
  /** Lowercase LUD-16 local-part. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qntx/spark",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Spark Bitcoin L2 protocol for TypeScript: platform-neutral client, signer and crypto-backend interfaces.",
5
5
  "keywords": [
6
6
  "bitcoin",