@dvmkit/sdk 0.1.0-rc.1 → 0.1.0-rc.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.
@@ -1001,6 +1001,7 @@ var CreditLedger = class {
1001
1001
  */
1002
1002
  durable = true;
1003
1003
  x402Settlements;
1004
+ expiryReleaseOutbox;
1004
1005
  /**
1005
1006
  * Bind the durable x402 settlement state this ledger gates spending on
1006
1007
  * (internal-review).
@@ -1013,6 +1014,24 @@ var CreditLedger = class {
1013
1014
  useX402SettlementGate(gate) {
1014
1015
  this.x402Settlements = gate;
1015
1016
  }
1017
+ /**
1018
+ * Bind the durable outbox that reports credit-expiry releases and their
1019
+ * revivals to the platform (internal-review).
1020
+ *
1021
+ * Held on the ledger rather than threaded through {@link fund} for the same
1022
+ * reason as the gate above: the revival half fires from *every* funding path
1023
+ * — Cashu commit, x402 exact and channel, Tempo, the Lightning invoice
1024
+ * settle, the implicit N=1 per-call payment — and a seam each of those has to
1025
+ * remember to pass is a seam one of them will eventually forget.
1026
+ *
1027
+ * Unbound (a `dvmctl dev` server, a self-hosted builder, a test host) the
1028
+ * sweep and the reversal still run and still write the ledger's own rows;
1029
+ * only the platform report is skipped. Reporting is bookkeeping and must
1030
+ * never gate the ledger.
1031
+ */
1032
+ useCreditExpiryReleaseOutbox(outbox) {
1033
+ this.expiryReleaseOutbox = outbox;
1034
+ }
1016
1035
  /** Create the `credits` / `credit_draws` tables if absent. Call once at SDK boot. */
1017
1036
  async init() {
1018
1037
  await withSdkInitLock(this.pool, () => this.createTables());
@@ -1275,6 +1294,24 @@ var CreditLedger = class {
1275
1294
  `CREATE INDEX IF NOT EXISTS idx_credit_invoices_blocked
1276
1295
  ON credit_invoices(created_at, credit_id, fund_id) WHERE status = 'blocked';`
1277
1296
  );
1297
+ await this.pool.query(`
1298
+ CREATE TABLE IF NOT EXISTS credit_expiry_releases (
1299
+ credit_id TEXT NOT NULL,
1300
+ release_id TEXT NOT NULL,
1301
+ caller_pubkey TEXT NOT NULL,
1302
+ currency TEXT NOT NULL,
1303
+ rail TEXT,
1304
+ amount_micro BIGINT NOT NULL,
1305
+ expiry_ms BIGINT NOT NULL,
1306
+ released_at BIGINT NOT NULL,
1307
+ reversed_at BIGINT,
1308
+ PRIMARY KEY (credit_id, release_id)
1309
+ );
1310
+ `);
1311
+ await this.pool.query(
1312
+ `CREATE INDEX IF NOT EXISTS idx_credit_expiry_releases_open
1313
+ ON credit_expiry_releases(credit_id) WHERE reversed_at IS NULL;`
1314
+ );
1278
1315
  }
1279
1316
  /**
1280
1317
  * Give every open non-channel Bitcoin credit a funding lot (internal-review).
@@ -1341,9 +1378,15 @@ var CreditLedger = class {
1341
1378
  *
1342
1379
  * Pass `tx` (a client inside a caller-owned `BEGIN`) to commit the rail
1343
1380
  * receive and the ledger credit atomically (spec condition 3 — the internal-review
1344
- * verifier does this). The ledger issues **no** transaction control on `tx`;
1345
- * the upsert is a single statement, so without `tx` it is equally atomic on
1346
- * the pool.
1381
+ * verifier does this). The ledger issues **no** transaction control on `tx`.
1382
+ *
1383
+ * Without `tx` it opens one of its own, because a funding is no longer a
1384
+ * single statement: it upserts the credit, records its funding lot
1385
+ * (internal-review), and reverses any standing expiry release (internal-review) — and that
1386
+ * last leg restores balance and queues a report. A crash between the upsert
1387
+ * and the reversal would leave a revived credit whose release still stands,
1388
+ * which the sweep's own exclusion then makes permanent: `balance_micro > 0`
1389
+ * but a standing release means it is neither drainable nor re-releasable.
1347
1390
  *
1348
1391
  * `basis` records the rail value behind the fiat (internal-review) so each draw can
1349
1392
  * be allocated its share of the rail-native amount actually received. A
@@ -1389,9 +1432,29 @@ var CreditLedger = class {
1389
1432
  );
1390
1433
  }
1391
1434
  const b = assertFundingBasis(args.basis);
1392
- const q = args.tx ?? this.pool;
1393
1435
  const nowMs = args.nowMs ?? Date.now();
1394
1436
  const creditId = args.creditId ?? randomUUID();
1437
+ if (args.tx) return this.fundOn(args.tx, args, b, nowMs, creditId);
1438
+ const client = await this.pool.connect();
1439
+ try {
1440
+ await client.query("BEGIN");
1441
+ const snapshot = await this.fundOn(client, args, b, nowMs, creditId);
1442
+ await client.query("COMMIT");
1443
+ return snapshot;
1444
+ } catch (err) {
1445
+ await client.query("ROLLBACK").catch(() => {
1446
+ });
1447
+ throw err;
1448
+ } finally {
1449
+ client.release();
1450
+ }
1451
+ }
1452
+ /**
1453
+ * The funding itself, on whichever handle {@link fund} chose. Every refusal
1454
+ * it raises is the caller's to see unchanged; a self-opened transaction rolls
1455
+ * back around it.
1456
+ */
1457
+ async fundOn(q, args, b, nowMs, creditId) {
1395
1458
  const wedged = await this.blockingX402Refund(
1396
1459
  b.x402Channel?.channelId,
1397
1460
  X402_SPEND_BLOCKING_SETTLEMENT_STATUSES
@@ -1524,7 +1587,196 @@ var CreditLedger = class {
1524
1587
  );
1525
1588
  }
1526
1589
  await this.recordFundingLot(q, creditId, args.amountMicro, b, nowMs);
1527
- return this.snapshotFromRow(rows[0], q, nowMs);
1590
+ const revived = await this.reverseExpiryReleases(q, creditId, nowMs);
1591
+ return this.snapshotFromRow(revived ?? rows[0], q, nowMs);
1592
+ }
1593
+ /**
1594
+ * Undo any expiry release this credit still carries, because a funding just
1595
+ * landed on it (internal-review — the revival rule), restoring the balance the
1596
+ * release took.
1597
+ *
1598
+ * A release records the credit's **final** undrawn remainder and zeroes it
1599
+ * (see {@link releaseExpiredCreditLocked}). Funding adds to the balance and
1600
+ * overwrites `expiry_ms` (spec §5 carry-forward: the credit is a rolling
1601
+ * buffer), so the moment money arrives the recorded remainder is no longer
1602
+ * final: the release stops counting and its micro come back.
1603
+ *
1604
+ * Deliberately unconditional on the incoming expiry rather than gated on
1605
+ * `expiryMs > nowMs`. A release exists only for a credit that had already
1606
+ * lapsed, so any funding that reaches one is money the release did not
1607
+ * account for — and a funding that arrives with an already-past expiry leaves
1608
+ * a credit the very next sweep re-releases at its new, larger remainder. One
1609
+ * rule, self-healing in both directions, and it never needs to read the
1610
+ * pre-funding expiry the upsert has already overwritten.
1611
+ *
1612
+ * Runs on the funding's own handle — which {@link fund} now guarantees is a
1613
+ * transaction — so the reversal, the restored balance and the report commit
1614
+ * with the money or not at all. A reversal that committed without its report
1615
+ * would be unrecoverable: `WHERE reversed_at IS NULL` means no later pass
1616
+ * re-derives it.
1617
+ *
1618
+ * @returns the credit row as the restore left it, or undefined when there was
1619
+ * nothing to reverse. The caller reads its snapshot off this rather than off
1620
+ * the funding upsert's `RETURNING`, which predates the restore.
1621
+ */
1622
+ async reverseExpiryReleases(q, creditId, nowMs) {
1623
+ const { rows } = await q.query(
1624
+ `UPDATE credit_expiry_releases
1625
+ SET reversed_at = $2
1626
+ WHERE credit_id = $1 AND reversed_at IS NULL
1627
+ RETURNING *`,
1628
+ [creditId, nowMs]
1629
+ );
1630
+ if (rows.length === 0) return void 0;
1631
+ const restoredMicro = rows.reduce((sum, row) => sum + toSafeInt(row.amount_micro), 0);
1632
+ const { rows: restored } = await q.query(
1633
+ `UPDATE credits SET balance_micro = balance_micro + $2 WHERE credit_id = $1 RETURNING *`,
1634
+ [creditId, restoredMicro]
1635
+ );
1636
+ const outbox = this.expiryReleaseOutbox;
1637
+ if (outbox) {
1638
+ for (const row of rows) {
1639
+ await outbox.enqueue(q, expiryReleaseReport(outbox.dvmId, row));
1640
+ }
1641
+ }
1642
+ return restored[0];
1643
+ }
1644
+ /**
1645
+ * Release the undrawn remainder of every credit whose TTL has run out
1646
+ * (internal-review). Returns the releases this pass recorded.
1647
+ *
1648
+ * **What is released.** The credit's whole `balance_micro`. Expiry ends
1649
+ * spending but never ownership of the record, so the balance stays readable;
1650
+ * what ends is the caller's claim on it, and on the prepaid rails the value
1651
+ * behind it is already in builder custody.
1652
+ *
1653
+ * **Tempo is excluded**, at both of its markers (`tempo_channel_id` and a
1654
+ * `tempo` rail). A Tempo credit is channel-backed and its undrawn value
1655
+ * returns to the *caller* at channel exit, so releasing it would book the
1656
+ * builder money the chain is about to hand back.
1657
+ *
1658
+ * **A credit with a pending hold is skipped, not partially released.** A draw
1659
+ * placed before expiry stays settleable afterwards (`draw`'s replay lookup
1660
+ * runs above the expiry check, so a lost response is still recoverable), so
1661
+ * the remainder is not final while a hold is outstanding. Skipping costs one
1662
+ * sweep interval and keeps the released figure exactly "what nothing bought";
1663
+ * holds do resolve — the orphan-draw watchdog (internal-review) is what guarantees
1664
+ * a stranded one still reaches a terminal state.
1665
+ *
1666
+ * **Idempotent** two ways. `release_id` is derived from the expiry instant,
1667
+ * so a re-sweep after a crash between the insert and its report collides on
1668
+ * the same primary key instead of writing a second row; and the candidate
1669
+ * scan excludes any credit that already has an un-reversed release.
1670
+ *
1671
+ * Each candidate is re-checked under the credit row lock every
1672
+ * `credit_draws` write is taken under, so a draw racing the sweep either
1673
+ * loses the race (its credit is already released and it would have been
1674
+ * refused `credit_expired` anyway) or wins it and leaves a pending hold the
1675
+ * locked re-check sees.
1676
+ */
1677
+ async sweepExpiredCredits(args) {
1678
+ const nowMs = args?.nowMs ?? Date.now();
1679
+ const limit = args?.limit ?? DEFAULT_EXPIRY_SWEEP_LIMIT;
1680
+ const { rows: candidates } = await this.pool.query(
1681
+ `SELECT c.credit_id
1682
+ FROM credits c
1683
+ WHERE c.expiry_ms <= $1
1684
+ AND c.balance_micro > 0
1685
+ AND c.status = 'active'
1686
+ AND c.tempo_channel_id IS NULL
1687
+ AND COALESCE(c.rail, '') <> 'tempo'
1688
+ AND NOT EXISTS (
1689
+ SELECT 1 FROM credit_expiry_releases r
1690
+ WHERE r.credit_id = c.credit_id AND r.reversed_at IS NULL
1691
+ )
1692
+ AND NOT EXISTS (
1693
+ SELECT 1 FROM credit_draws d
1694
+ WHERE d.credit_id = c.credit_id AND d.status = 'pending'
1695
+ )
1696
+ ORDER BY c.expiry_ms
1697
+ LIMIT $2`,
1698
+ [nowMs, limit]
1699
+ );
1700
+ const released = [];
1701
+ for (const candidate of candidates) {
1702
+ const row = await this.withCreditLock(
1703
+ candidate.credit_id,
1704
+ (client, credit) => this.releaseExpiredCreditLocked(client, credit, nowMs)
1705
+ );
1706
+ if (row) released.push(expiryReleaseFromRow(row));
1707
+ }
1708
+ return { released };
1709
+ }
1710
+ /**
1711
+ * Write one credit's expiry release under its row lock, zero the balance it
1712
+ * released, and re-assert every condition the unlocked candidate scan tested.
1713
+ * Returns undefined when the credit no longer qualifies — funded, drawn
1714
+ * against, or already released between the scan and the lock.
1715
+ *
1716
+ * **Zeroing the balance is what makes the release real**, and it is the whole
1717
+ * reason this runs under the lock rather than as a bare INSERT. The row alone
1718
+ * records that the caller's claim ended; it does not *end* it. `requestDrain`
1719
+ * has no expiry check by design (expiry gates new draws, not reclaims), so a
1720
+ * credit whose remainder had been released and reported was still fully
1721
+ * drainable: the DVM would pay out money the platform had already booked as
1722
+ * the builder's, and `deposited - drawn - drained - released` would go
1723
+ * negative on the same micro. With the balance at zero, `requestDrain`'s
1724
+ * existing `availableMicro <= 0` guard refuses with `nothing_to_drain` and no
1725
+ * new check is needed anywhere. {@link reverseExpiryReleases} puts the
1726
+ * balance back when a funding revives the credit.
1727
+ *
1728
+ * `status` deliberately stays `active`: a released credit must remain
1729
+ * fundable, or the revival rule has nothing to revive.
1730
+ *
1731
+ * The zeroed balance also takes the credit out of `BITCOIN_CREDIT_PREDICATE`
1732
+ * (`balance_micro > 0`), so the in-kind sweep floor stops counting sats the
1733
+ * builder no longer owes back — and counts them again after a revival, with
1734
+ * the credit's funding lots untouched throughout.
1735
+ */
1736
+ async releaseExpiredCreditLocked(client, credit, nowMs) {
1737
+ const expiryMs = toSafeInt(credit.expiry_ms);
1738
+ const balanceMicro = toSafeInt(credit.balance_micro);
1739
+ if (expiryMs > nowMs || balanceMicro <= 0 || credit.status !== "active") return void 0;
1740
+ if (credit.tempo_channel_id !== null || credit.rail === "tempo") return void 0;
1741
+ const pending = await this.pendingSums(client, credit.credit_id);
1742
+ if (pending.micro > 0) return void 0;
1743
+ const { rows: open } = await client.query(
1744
+ `SELECT 1 AS one FROM credit_expiry_releases
1745
+ WHERE credit_id = $1 AND reversed_at IS NULL`,
1746
+ [credit.credit_id]
1747
+ );
1748
+ if (open.length > 0) return void 0;
1749
+ const { rows: episodes } = await client.query(
1750
+ `SELECT COUNT(*)::text AS n FROM credit_expiry_releases WHERE credit_id = $1`,
1751
+ [credit.credit_id]
1752
+ );
1753
+ const seq = toSafeInt(episodes[0].n) + 1;
1754
+ const { rows } = await client.query(
1755
+ `INSERT INTO credit_expiry_releases
1756
+ (credit_id, release_id, caller_pubkey, currency, rail, amount_micro, expiry_ms, released_at)
1757
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
1758
+ ON CONFLICT (credit_id, release_id) DO NOTHING
1759
+ RETURNING *`,
1760
+ [
1761
+ credit.credit_id,
1762
+ expiryReleaseId(expiryMs, seq),
1763
+ credit.caller_pubkey,
1764
+ credit.currency,
1765
+ credit.rail,
1766
+ balanceMicro,
1767
+ expiryMs,
1768
+ nowMs
1769
+ ]
1770
+ );
1771
+ if (rows.length === 0) return void 0;
1772
+ const row = rows[0];
1773
+ await client.query(
1774
+ `UPDATE credits SET balance_micro = balance_micro - $2 WHERE credit_id = $1`,
1775
+ [credit.credit_id, balanceMicro]
1776
+ );
1777
+ const outbox = this.expiryReleaseOutbox;
1778
+ if (outbox) await outbox.enqueue(client, expiryReleaseReport(outbox.dvmId, row));
1779
+ return row;
1528
1780
  }
1529
1781
  /**
1530
1782
  * Record this funding's in-kind basis as a lot (internal-review).
@@ -2654,7 +2906,15 @@ var CreditLedger = class {
2654
2906
  async listTempoCreditLosses(args) {
2655
2907
  return this.readTempoCreditLosses(this.pool, { limit: args?.limit ?? 50 });
2656
2908
  }
2657
- /** Retire the active credit bound to a chain-proven empty x402 channel. */
2909
+ /**
2910
+ * Reconcile the credit bound to a chain-proven empty x402 channel.
2911
+ *
2912
+ * A channel with no unclaimed backing makes every remaining customer
2913
+ * liability unsafe, so those credits become terminal. A fully settled credit
2914
+ * has neither a balance nor an unfinished drain and is safe to retain for a
2915
+ * later deposit. This also repairs a zero-liability row an older build
2916
+ * terminalized from the same observation.
2917
+ */
2658
2918
  async terminalizeX402Credit(evidence, tx) {
2659
2919
  const channelId = evidence.channelId.toLowerCase();
2660
2920
  const observedAt = evidence.observedAt ?? Date.now();
@@ -2682,36 +2942,57 @@ var CreditLedger = class {
2682
2942
  `SELECT * FROM credits WHERE x402_channel_id = $1 FOR UPDATE`,
2683
2943
  [channelId]
2684
2944
  );
2685
- if (credits.length > 0 && credits[0].status !== "unbacked") {
2945
+ if (credits.length > 0) {
2686
2946
  const credit = credits[0];
2687
- if (credit.status !== "active") throw terminalCreditError(credit.credit_id, credit.status);
2688
- const { rows: consumed } = await q.query(
2689
- `SELECT COALESCE(SUM(amount_micro), 0)::text AS amount
2690
- FROM credit_draws
2691
- WHERE credit_id = $1 AND status = 'settled'`,
2947
+ if (credit.status !== "active" && credit.status !== "unbacked") {
2948
+ throw terminalCreditError(credit.credit_id, credit.status);
2949
+ }
2950
+ const { rows: drains } = await q.query(
2951
+ `SELECT EXISTS(
2952
+ SELECT 1 FROM credit_drains
2953
+ WHERE credit_id = $1 AND status IN ('pending', 'parked')
2954
+ ) AS outstanding`,
2692
2955
  [credit.credit_id]
2693
2956
  );
2694
- await q.query(
2695
- `INSERT INTO x402_credit_losses
2696
- (credit_id, channel_id, caller_pubkey, currency, former_balance_micro,
2697
- channel_balance_native, total_claimed_native, consumed_service_micro, observed_at)
2698
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
2699
- ON CONFLICT (credit_id) DO NOTHING`,
2700
- [
2701
- credit.credit_id,
2702
- channelId,
2703
- credit.caller_pubkey,
2704
- credit.currency,
2705
- credit.balance_micro,
2706
- evidence.channelBalanceNative.toString(),
2707
- evidence.totalClaimedNative.toString(),
2708
- consumed[0]?.amount ?? "0",
2709
- observedAt
2710
- ]
2711
- );
2712
- await q.query(`UPDATE credits SET status = 'unbacked' WHERE credit_id = $1`, [
2713
- credit.credit_id
2714
- ]);
2957
+ const hasLiability = toSafeInt(credit.balance_micro) > 0 || drains[0]?.outstanding;
2958
+ if (!hasLiability) {
2959
+ if (credit.status === "unbacked") {
2960
+ await q.query(`UPDATE credits SET status = 'active' WHERE credit_id = $1`, [
2961
+ credit.credit_id
2962
+ ]);
2963
+ await q.query(`DELETE FROM x402_credit_losses WHERE credit_id = $1`, [credit.credit_id]);
2964
+ }
2965
+ return this.readX402CreditLosses(q, { channelId });
2966
+ }
2967
+ if (credit.status !== "unbacked") {
2968
+ const { rows: consumed } = await q.query(
2969
+ `SELECT COALESCE(SUM(amount_micro), 0)::text AS amount
2970
+ FROM credit_draws
2971
+ WHERE credit_id = $1 AND status = 'settled'`,
2972
+ [credit.credit_id]
2973
+ );
2974
+ await q.query(
2975
+ `INSERT INTO x402_credit_losses
2976
+ (credit_id, channel_id, caller_pubkey, currency, former_balance_micro,
2977
+ channel_balance_native, total_claimed_native, consumed_service_micro, observed_at)
2978
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
2979
+ ON CONFLICT (credit_id) DO NOTHING`,
2980
+ [
2981
+ credit.credit_id,
2982
+ channelId,
2983
+ credit.caller_pubkey,
2984
+ credit.currency,
2985
+ credit.balance_micro,
2986
+ evidence.channelBalanceNative.toString(),
2987
+ evidence.totalClaimedNative.toString(),
2988
+ consumed[0]?.amount ?? "0",
2989
+ observedAt
2990
+ ]
2991
+ );
2992
+ await q.query(`UPDATE credits SET status = 'unbacked' WHERE credit_id = $1`, [
2993
+ credit.credit_id
2994
+ ]);
2995
+ }
2715
2996
  }
2716
2997
  return this.readX402CreditLosses(q, { channelId });
2717
2998
  }
@@ -3778,6 +4059,38 @@ function invoiceRecordFromRow(row) {
3778
4059
  settledAt: row.settled_at === null ? null : toSafeInt(row.settled_at)
3779
4060
  };
3780
4061
  }
4062
+ var DEFAULT_EXPIRY_SWEEP_LIMIT = 200;
4063
+ function expiryReleaseId(expiryMs, seq) {
4064
+ return `xrel:${expiryMs}:${seq}`;
4065
+ }
4066
+ function expiryReleaseFromRow(row) {
4067
+ return {
4068
+ creditId: row.credit_id,
4069
+ releaseId: row.release_id,
4070
+ callerPubkey: row.caller_pubkey,
4071
+ currency: row.currency,
4072
+ rail: row.rail,
4073
+ amountMicro: toSafeInt(row.amount_micro),
4074
+ expiryMs: toSafeInt(row.expiry_ms),
4075
+ releasedAt: toSafeInt(row.released_at),
4076
+ reversedAt: row.reversed_at === null ? null : toSafeInt(row.reversed_at)
4077
+ };
4078
+ }
4079
+ function expiryReleaseReport(dvmId, row) {
4080
+ const release = expiryReleaseFromRow(row);
4081
+ return {
4082
+ dvmId,
4083
+ creditId: release.creditId,
4084
+ releaseId: release.releaseId,
4085
+ callerPubkey: release.callerPubkey,
4086
+ rail: release.rail,
4087
+ amountMicro: release.amountMicro,
4088
+ creditCurrency: release.currency,
4089
+ expiryMs: release.expiryMs,
4090
+ releasedAt: release.releasedAt,
4091
+ reversedAt: release.reversedAt
4092
+ };
4093
+ }
3781
4094
 
3782
4095
  export {
3783
4096
  isNonChannelBitcoinRail,
@@ -15,7 +15,7 @@ import {
15
15
  lotOwedSats,
16
16
  netOwedSats,
17
17
  x402SettlementPending
18
- } from "./chunk-RPXHKMYE.js";
18
+ } from "./chunk-365P52XQ.js";
19
19
 
20
20
  // src/sdk/server/memory-credit-ledger.ts
21
21
  import { randomUUID } from "crypto";
@@ -702,6 +702,16 @@ var MemoryCreditLedger = class {
702
702
  const observedAt = evidence.observedAt ?? Date.now();
703
703
  for (const credit of this.credits.values()) {
704
704
  if (credit.x402ChannelId !== channelId) continue;
705
+ const hasLiability = credit.balanceMicro > 0 || [...credit.drains.values()].some(
706
+ (drain) => drain.status === "pending" || drain.status === "parked"
707
+ );
708
+ if (!hasLiability) {
709
+ if (credit.status === "unbacked") {
710
+ credit.status = "active";
711
+ this.x402Losses.delete(credit.creditId);
712
+ }
713
+ continue;
714
+ }
705
715
  if (credit.status === "unbacked") continue;
706
716
  const consumedServiceMicro = [...credit.draws.values()].filter((draw) => draw.status === "settled").reduce((sum, draw) => sum + draw.amountMicro, 0);
707
717
  this.x402Losses.set(credit.creditId, {
@@ -10,7 +10,7 @@ import {
10
10
  isDrainMethod,
11
11
  isFundingRail,
12
12
  x402SettlementPending
13
- } from "./chunk-RPXHKMYE.js";
13
+ } from "./chunk-365P52XQ.js";
14
14
  import "./chunk-H25M54MI.js";
15
15
  import "./chunk-S3XAHZQY.js";
16
16
  export {
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { C as Currency, Z as ZodLike, D as DVMConfig, a as DVMDescriptor } from './job-store-C5n6bhap.js';
2
- export { A as ApprovalContent, b as ArtifactContent, c as CancelContent, d as CanonicalEnvelope, e as CreateSignedRequestVerifierOpts, f as CreditConfig, g as CreditView, h as DEFAULT_CREDIT_MAX, i as DEFAULT_CREDIT_MIN, j as DEFAULT_CREDIT_TTL_SECONDS, k as DVMRouteContext, I as IncomingMessage, l as InputType, m as InvalidCurrencyError, J as JobRecord, n as JobStatus, o as JobStore, K as KVStore, L as Logger, P as PaymentContent, p as PaymentMethod, q as PriceValue, r as ProgressContent, s as PromptOpts, Q as QuoteConfig, t as QuoteContext, u as QuoteResult, R as ResolvedCreditConfig, v as ResponseContent, S as SDKJobContext, w as SDKPaymentRequestOpts, x as SIGNED_REQUEST_AUTH_ID, y as SIGNED_REQUEST_STATEMENT_VERSION, z as SignedRequestAudience, B as SignedRequestDomain, E as SignedRequestError, F as SignedRequestFailure, G as SignedRequestReplayStore, H as SignedRequestSignOpts, M as SignedRequestStatementHeader, N as SignedRequestVerifier, U as UnsupportedCurrencyError, O as createSignedRequestVerifier, T as isZodSchema, V as signedRequestStatementHeader, W as validateCurrency } from './job-store-C5n6bhap.js';
3
- export { C as CreateFxFetcherOpts, D as DEFAULT_FX_CURRENCIES, a as DEFAULT_FX_RATE_SOURCE, F as FxFetcher, b as FxRateSnapshot, c as FxRateUnavailableError, P as PinnedFetch, d as PlatformFxSource, S as SSRFError, e as SSRFGuardOpts, f as SSRFReason, g as SSRFResolver, h as assertSafeUrl, i as createFxFetcher, j as createPinnedFetch, k as fxRateFor, r as resolveFxSourceFromEnv } from './ssrf-BdHsrrIb.js';
1
+ import { C as Currency, Z as ZodLike, D as DVMConfig, a as DVMDescriptor } from './job-store-6gR4pZRP.js';
2
+ export { A as ApprovalContent, b as ArtifactContent, c as CancelContent, d as CanonicalEnvelope, e as CreateSignedRequestVerifierOpts, f as CreditConfig, g as CreditView, h as DEFAULT_CREDIT_MAX, i as DEFAULT_CREDIT_MIN, j as DEFAULT_CREDIT_TTL_SECONDS, k as DVMRouteContext, I as IncomingMessage, l as InputType, m as InvalidCurrencyError, J as JobRecord, n as JobStatus, o as JobStore, K as KVStore, L as Logger, P as PaymentContent, p as PaymentMethod, q as PriceValue, r as ProgressContent, s as PromptOpts, Q as QuoteConfig, t as QuoteContext, u as QuoteResult, R as ResolvedCreditConfig, v as ResponseContent, S as SDKJobContext, w as SDKPaymentRequestOpts, x as SIGNED_REQUEST_AUTH_ID, y as SIGNED_REQUEST_STATEMENT_VERSION, z as SignedRequestAudience, B as SignedRequestDomain, E as SignedRequestError, F as SignedRequestFailure, G as SignedRequestReplayStore, H as SignedRequestSignOpts, M as SignedRequestStatementHeader, N as SignedRequestVerifier, U as UnsupportedCurrencyError, O as createSignedRequestVerifier, T as isZodSchema, V as signedRequestStatementHeader, W as validateCurrency } from './job-store-6gR4pZRP.js';
3
+ export { C as CreateFxFetcherOpts, D as DEFAULT_FX_CURRENCIES, a as DEFAULT_FX_RATE_SOURCE, F as FxFetcher, b as FxRateSnapshot, c as FxRateUnavailableError, P as PinnedFetch, d as PlatformFxSource, S as SSRFError, e as SSRFGuardOpts, f as SSRFReason, g as SSRFResolver, h as assertSafeUrl, i as createFxFetcher, j as createPinnedFetch, k as fxRateFor, r as resolveFxSourceFromEnv } from './ssrf-DZi-xJyn.js';
4
4
  export { z } from 'zod';
5
5
  import '@cashu/cashu-ts';
6
6
  import 'mppx';