@gvnrdao/dh-sdk 0.0.303 → 0.0.305

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.
@@ -413,9 +413,13 @@ var MAINNET_DEPLOYMENT = {
413
413
  reason: "Validator gas fix (#420): BitcoinAddressValidator packed-table lookups; VERSION 1.0.0 -> 1.0.1"
414
414
  },
415
415
  UCDController: {
416
+ previousImplementation: "0x16f94AcC78cE87bEeBCfF90552091B3EfC071E48",
416
417
  newImplementation: "0x28B8cAB89D89B3F83581FD2290de2Ce84535bd0A",
417
- upgradedBy: "Reconciled from chain \u2014 Upgraded event log unavailable; provenance omitted (never fabricated)",
418
- reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)"
418
+ upgradedAt: "2026-07-06T10:41:59.000Z",
419
+ upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0xf6609d3fa43b460d953c85d8d7265b59bf177723042db79a6a700571bad777b7 (block 25472962)",
420
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
421
+ executeTx: "0xf6609d3fa43b460d953c85d8d7265b59bf177723042db79a6a700571bad777b7",
422
+ block: 25472962
419
423
  },
420
424
  PositionManagerCoreModule: {
421
425
  previousImplementation: "0x6C8b0FB93F8d0239254aefaC24d4264cA2F8AAeB",
@@ -380,9 +380,13 @@ var MAINNET_DEPLOYMENT = {
380
380
  reason: "Validator gas fix (#420): BitcoinAddressValidator packed-table lookups; VERSION 1.0.0 -> 1.0.1"
381
381
  },
382
382
  UCDController: {
383
+ previousImplementation: "0x16f94AcC78cE87bEeBCfF90552091B3EfC071E48",
383
384
  newImplementation: "0x28B8cAB89D89B3F83581FD2290de2Ce84535bd0A",
384
- upgradedBy: "Reconciled from chain \u2014 Upgraded event log unavailable; provenance omitted (never fabricated)",
385
- reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)"
385
+ upgradedAt: "2026-07-06T10:41:59.000Z",
386
+ upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0xf6609d3fa43b460d953c85d8d7265b59bf177723042db79a6a700571bad777b7 (block 25472962)",
387
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
388
+ executeTx: "0xf6609d3fa43b460d953c85d8d7265b59bf177723042db79a6a700571bad777b7",
389
+ block: 25472962
386
390
  },
387
391
  PositionManagerCoreModule: {
388
392
  previousImplementation: "0x6C8b0FB93F8d0239254aefaC24d4264cA2F8AAeB",
package/dist/index.js CHANGED
@@ -438,9 +438,13 @@ var init_deployment_addresses = __esm({
438
438
  reason: "Validator gas fix (#420): BitcoinAddressValidator packed-table lookups; VERSION 1.0.0 -> 1.0.1"
439
439
  },
440
440
  UCDController: {
441
+ previousImplementation: "0x16f94AcC78cE87bEeBCfF90552091B3EfC071E48",
441
442
  newImplementation: "0x28B8cAB89D89B3F83581FD2290de2Ce84535bd0A",
442
- upgradedBy: "Reconciled from chain \u2014 Upgraded event log unavailable; provenance omitted (never fabricated)",
443
- reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)"
443
+ upgradedAt: "2026-07-06T10:41:59.000Z",
444
+ upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0xf6609d3fa43b460d953c85d8d7265b59bf177723042db79a6a700571bad777b7 (block 25472962)",
445
+ reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
446
+ executeTx: "0xf6609d3fa43b460d953c85d8d7265b59bf177723042db79a6a700571bad777b7",
447
+ block: 25472962
444
448
  },
445
449
  PositionManagerCoreModule: {
446
450
  previousImplementation: "0x6C8b0FB93F8d0239254aefaC24d4264cA2F8AAeB",
@@ -1287,7 +1291,7 @@ var require_pkg_src = __commonJS({
1287
1291
  description: "Production Pkp Validator",
1288
1292
  version: "1.0.0",
1289
1293
  deployed: true,
1290
- deployedAt: 1783106284785,
1294
+ deployedAt: 1784569687492,
1291
1295
  size: 4140,
1292
1296
  hash: "4c0d65bf1f33cdac22a78fd8c4764319cc3fe00650e0fe7fa8a6090d9a318a7b",
1293
1297
  validatorWalletAddress: "0x82db9f515957aa380b596c6ea2bd30cef181f236",
@@ -1364,17 +1368,17 @@ var require_pkg_src = __commonJS({
1364
1368
  }
1365
1369
  },
1366
1370
  btcWithdrawal: {
1367
- cid: "QmYG1o9qMBZsEMDE9DNsJMzQmvxfj6jTu9V37WwuJZAtC2",
1371
+ cid: "QmexvpfHPacjsVcqNHHLyJhSnnAPLgbWmWd9N3NDLDvYds",
1368
1372
  authorizedCidHex: cidToHex(
1369
- "QmYG1o9qMBZsEMDE9DNsJMzQmvxfj6jTu9V37WwuJZAtC2"
1373
+ "QmexvpfHPacjsVcqNHHLyJhSnnAPLgbWmWd9N3NDLDvYds"
1370
1374
  ),
1371
1375
  name: "Btc Withdrawal",
1372
1376
  description: "Production Btc Withdrawal",
1373
- version: "1.2.1",
1377
+ version: "1.2.3",
1374
1378
  deployed: true,
1375
- deployedAt: 1784316549409,
1376
- size: 90692,
1377
- hash: "ca7276763142fe1fc812b70f6319973000faf338a91c531f898f8f6406035c64",
1379
+ deployedAt: 1784585746901,
1380
+ size: 92027,
1381
+ hash: "7b4f4eab1f0c08b8b5b1c9cdb1064dd403ec13f57765919f62dec67fb8ba3c69",
1378
1382
  validatorWalletAddress: "0x82DB9F515957aA380B596C6eA2Bd30CEF181f236",
1379
1383
  pkp: {
1380
1384
  publicKey: "0x043616787c5432415c24378c4ef48de2bcd6bb7b575b837e3cff09171802662da7105d79586c7659677a0ecbaddac4cce06cb2a11f69a16fa0c4d7002ac7d51a4d",
@@ -3746,12 +3750,23 @@ ${auth.clientId}`;
3746
3750
  if (!data.vout || !Array.isArray(data.vout)) {
3747
3751
  return null;
3748
3752
  }
3749
- return data.vout.map((output, index) => ({
3750
- index,
3751
- scriptpubkey: output.scriptpubkey,
3752
- scriptpubkey_address: output.scriptpubkey_address,
3753
- value: output.value ?? 0
3754
- }));
3753
+ return data.vout.map((output, index) => {
3754
+ if (output.value === void 0) {
3755
+ throw new Error(
3756
+ `getTransactionOutputs(${txid}): output ${index} is missing its value \u2014 malformed provider response, refusing to return outputs that cannot be value-verified`
3757
+ );
3758
+ }
3759
+ assertSaneSatoshis(
3760
+ output.value,
3761
+ `getTransactionOutputs(${txid}) output ${index}`
3762
+ );
3763
+ return {
3764
+ index,
3765
+ scriptpubkey: output.scriptpubkey,
3766
+ scriptpubkey_address: output.scriptpubkey_address,
3767
+ value: output.value
3768
+ };
3769
+ });
3755
3770
  } catch (error) {
3756
3771
  clearTimeout(timeoutId);
3757
3772
  if (isAbortError(error)) {
@@ -6602,6 +6617,14 @@ var ServerSession = class {
6602
6617
  fetchImpl;
6603
6618
  cached = null;
6604
6619
  inFlight = null;
6620
+ /**
6621
+ * Bumped by `clearPersisted()` (logout / disconnect). An in-flight
6622
+ * `getOrRefresh()` captures this at start and refuses to write its freshly
6623
+ * minted token back to `this.cached`/the store if the value changed
6624
+ * meanwhile — so a refresh racing a sign-out can't silently "un-clear" the
6625
+ * credential it was told to drop.
6626
+ */
6627
+ generation = 0;
6605
6628
  /**
6606
6629
  * Store key for the current signer, memoized on first use so the sync
6607
6630
  * `clear()` can drop the persisted token without an async address lookup.
@@ -6617,8 +6640,22 @@ var ServerSession = class {
6617
6640
  this.now = opts.now ?? (() => Math.floor(Date.now() / 1e3));
6618
6641
  this.fetchImpl = opts.fetchImpl ?? ((...a) => fetch(...a));
6619
6642
  }
6620
- /** Returns a JWT good for at least `REFRESH_LEEWAY_SECONDS` more seconds. */
6621
- async getValidToken() {
6643
+ /**
6644
+ * Returns a JWT good for at least `REFRESH_LEEWAY_SECONDS` more seconds.
6645
+ * Pass `{ silent: true }` to forbid a fresh wallet signature: it resolves ONLY
6646
+ * from a live cached token or the persisted re-mint envelope, never prompting
6647
+ * and never joining a prompt-capable in-flight refresh (used by `logout()`,
6648
+ * which must never pop the wallet during sign-out). Rejects when only a fresh
6649
+ * signature could produce a token.
6650
+ */
6651
+ async getValidToken(opts) {
6652
+ if (opts?.silent) {
6653
+ const session2 = await this.tryResolveSilently(this.generation);
6654
+ if (!session2) {
6655
+ throw new Error("ServerSession: no existing session to refresh");
6656
+ }
6657
+ return session2.token;
6658
+ }
6622
6659
  const session = await this.getOrRefresh();
6623
6660
  return session.token;
6624
6661
  }
@@ -6651,20 +6688,24 @@ var ServerSession = class {
6651
6688
  * from storage).
6652
6689
  */
6653
6690
  async clearPersisted() {
6691
+ this.generation += 1;
6654
6692
  this.cached = null;
6655
6693
  this.inFlight = null;
6656
6694
  if (!this.store)
6657
6695
  return;
6658
- if (this.lastStoreKey) {
6659
- this.store.clear(this.lastStoreKey);
6660
- return;
6661
- }
6662
6696
  try {
6663
- const address = (await this.signer.getAddress()).toLowerCase();
6664
- this.store.clear(
6665
- sessionStoreKey(address, this.chainId, this.serviceEndpoint)
6666
- );
6697
+ if (this.lastStoreKey) {
6698
+ this.store.clear(this.lastStoreKey);
6699
+ } else {
6700
+ const address = (await this.signer.getAddress()).toLowerCase();
6701
+ this.store.clear(
6702
+ sessionStoreKey(address, this.chainId, this.serviceEndpoint)
6703
+ );
6704
+ }
6667
6705
  } catch {
6706
+ } finally {
6707
+ this.generation += 1;
6708
+ this.cached = null;
6668
6709
  }
6669
6710
  }
6670
6711
  /**
@@ -6674,7 +6715,8 @@ var ServerSession = class {
6674
6715
  * of server response so the client stops presenting the token.
6675
6716
  */
6676
6717
  async logout() {
6677
- const token = this.cached?.token ?? await this.getValidToken().catch(() => null);
6718
+ const cached = this.cached;
6719
+ const token = cached && cached.expiresAt > this.now() ? cached.token : await this.getValidToken({ silent: true }).catch(() => null);
6678
6720
  await this.clearPersisted();
6679
6721
  if (!token)
6680
6722
  return;
@@ -6689,6 +6731,50 @@ var ServerSession = class {
6689
6731
  );
6690
6732
  }
6691
6733
  }
6734
+ /**
6735
+ * Resolve a session WITHOUT ever prompting for a wallet signature: a live
6736
+ * cached token, else the persisted JWT (adopted if still fresh, otherwise
6737
+ * silently re-minted from the envelope). Returns `null` only when a fresh
6738
+ * wallet signature would be required (dead/absent envelope). Never touches
6739
+ * `this.inFlight`, so a silent caller (logout) can resolve independently
6740
+ * instead of being dragged into a prompt-capable refresh already in flight.
6741
+ *
6742
+ * `gen` is the caller's clear-generation snapshot: writes to `this.cached`
6743
+ * and the store are skipped if a `clearPersisted()` landed meanwhile.
6744
+ */
6745
+ async tryResolveSilently(gen) {
6746
+ const nowSec = this.now();
6747
+ if (this.cached && this.cached.expiresAt - REFRESH_LEEWAY_SECONDS > nowSec) {
6748
+ return this.cached;
6749
+ }
6750
+ const persisted = await this.loadPersisted();
6751
+ if (!persisted)
6752
+ return null;
6753
+ if (persisted.token && typeof persisted.tokenExpiresAt === "number" && persisted.tokenExpiresAt - REFRESH_LEEWAY_SECONDS > this.now()) {
6754
+ const adopted = {
6755
+ token: persisted.token,
6756
+ expiresAt: persisted.tokenExpiresAt
6757
+ };
6758
+ if (gen === this.generation)
6759
+ this.cached = adopted;
6760
+ return adopted;
6761
+ }
6762
+ try {
6763
+ const session = await this.login(persisted.payload);
6764
+ if (gen === this.generation) {
6765
+ this.persist(persisted.payload, session);
6766
+ this.cached = session;
6767
+ }
6768
+ return session;
6769
+ } catch (error) {
6770
+ if (!isLoginRejection(error))
6771
+ throw error;
6772
+ if (gen === this.generation && this.store && this.lastStoreKey) {
6773
+ this.store.clear(this.lastStoreKey);
6774
+ }
6775
+ return null;
6776
+ }
6777
+ }
6692
6778
  async getOrRefresh() {
6693
6779
  const nowSec = this.now();
6694
6780
  if (this.cached && this.cached.expiresAt - REFRESH_LEEWAY_SECONDS > nowSec) {
@@ -6696,30 +6782,13 @@ var ServerSession = class {
6696
6782
  }
6697
6783
  if (this.inFlight)
6698
6784
  return this.inFlight;
6785
+ const gen = this.generation;
6786
+ const notCleared = () => gen === this.generation;
6699
6787
  this.inFlight = (async () => {
6700
6788
  try {
6701
- const persisted = await this.loadPersisted();
6702
- if (persisted) {
6703
- if (persisted.token && typeof persisted.tokenExpiresAt === "number" && persisted.tokenExpiresAt - REFRESH_LEEWAY_SECONDS > this.now()) {
6704
- this.cached = {
6705
- token: persisted.token,
6706
- expiresAt: persisted.tokenExpiresAt
6707
- };
6708
- return this.cached;
6709
- }
6710
- try {
6711
- const session2 = await this.login(persisted.payload);
6712
- this.persist(persisted.payload, session2);
6713
- this.cached = session2;
6714
- return session2;
6715
- } catch (error) {
6716
- if (!isLoginRejection(error))
6717
- throw error;
6718
- if (this.store && this.lastStoreKey) {
6719
- this.store.clear(this.lastStoreKey);
6720
- }
6721
- }
6722
- }
6789
+ const existing = await this.tryResolveSilently(gen);
6790
+ if (existing)
6791
+ return existing;
6723
6792
  this.onSignaturePrompt?.();
6724
6793
  const payload = await buildSignedLoginPayload(
6725
6794
  this.signer,
@@ -6727,11 +6796,14 @@ var ServerSession = class {
6727
6796
  this.serviceEndpoint
6728
6797
  );
6729
6798
  const session = await this.login(payload);
6730
- this.persist(payload, session);
6731
- this.cached = session;
6799
+ if (notCleared()) {
6800
+ this.persist(payload, session);
6801
+ this.cached = session;
6802
+ }
6732
6803
  return session;
6733
6804
  } finally {
6734
- this.inFlight = null;
6805
+ if (notCleared())
6806
+ this.inFlight = null;
6735
6807
  }
6736
6808
  })();
6737
6809
  return this.inFlight;
@@ -16104,13 +16176,25 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
16104
16176
  return this.serverSession.getAuthHeader();
16105
16177
  }
16106
16178
  /**
16107
- * Remove the persisted lit-ops-server session (the 24h re-mint envelope and
16108
- * its JWT) for the current auth signer. Call on wallet disconnect or
16109
- * explicit sign-out so the credential does not outlive the wallet
16110
- * connection. No-op in standalone mode.
16179
+ * Sign out of the lit-ops-server session for the current auth signer. Call on
16180
+ * wallet disconnect or explicit sign-out so the credential does not outlive
16181
+ * the wallet connection. No-op in standalone mode.
16182
+ *
16183
+ * Attempts a server-side revocation first (`ServerSession.logout()` — which
16184
+ * tombstones the envelope's 24h re-mint window and invalidates the JWT), then
16185
+ * always removes the local credential. If the revocation call fails (offline,
16186
+ * server down, wallet already disconnected) we still clear locally so the
16187
+ * session cannot silently re-mint after sign-out; `logout()` is best-effort
16188
+ * and never prompts the wallet.
16111
16189
  */
16112
16190
  async clearServerSession() {
16113
- await this.serverSession?.clearPersisted();
16191
+ if (!this.serverSession)
16192
+ return;
16193
+ try {
16194
+ await this.serverSession.logout();
16195
+ } catch {
16196
+ await this.serverSession.clearPersisted();
16197
+ }
16114
16198
  }
16115
16199
  /**
16116
16200
  * Audit H-9: invalidate the LoanQuery cache so subsequent reads return
@@ -20685,7 +20769,9 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
20685
20769
  gasUsed: receipt.gasUsed?.toString() || "unknown",
20686
20770
  utxoTxid: validationResponse.utxoTxid,
20687
20771
  utxoVout: validationResponse.utxoVout,
20688
- utxoSatoshis: validationResponse.utxoSatoshis
20772
+ utxoSatoshis: validationResponse.utxoSatoshis,
20773
+ // Phase B: full consensus-validated spend set (absent on older actions).
20774
+ confirmedUtxos: validationResponse.confirmedUtxos
20689
20775
  };
20690
20776
  } catch (error) {
20691
20777
  const errorMsg = error instanceof Error ? error.message : String(error);
@@ -20715,6 +20801,42 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
20715
20801
  };
20716
20802
  }
20717
20803
  }
20804
+ /**
20805
+ * TEMPORARY (multi-UTXO withdrawal fix, Phase A): fetch the vault's confirmed
20806
+ * UTXO set from lit-ops-server (`GET /api/lit/vault-utxos`) so the caller can
20807
+ * assemble and sign the FULL input set for a multi-UTXO consolidation
20808
+ * withdrawal. Phase 1 only returns ONE representative UTXO, so a vault whose
20809
+ * balance is spread across several UTXOs otherwise fails the Phase-2 signer
20810
+ * with "Insufficient UTXO value ... across 1 input(s)".
20811
+ *
20812
+ * Returns `[]` on any failure (non-service mode, network error, bad shape) so
20813
+ * callers transparently fall back to the single authorized UTXO. Superseded
20814
+ * by the btc-withdrawal Lit Action returning its own confirmedUTXOs (Phase B).
20815
+ */
20816
+ async fetchConfirmedVaultUtxos(positionId) {
20817
+ if (this.config.mode !== "service" || !this.config.serviceEndpoint)
20818
+ return [];
20819
+ try {
20820
+ const pid = positionId.startsWith("0x") ? positionId : `0x${positionId}`;
20821
+ const resp = await fetch(
20822
+ `${this.config.serviceEndpoint}/api/lit/vault-utxos?positionId=${encodeURIComponent(pid)}`,
20823
+ { method: "GET", headers: { ...await this.getAuthHeader() } }
20824
+ );
20825
+ if (!resp.ok)
20826
+ return [];
20827
+ const json = await resp.json();
20828
+ if (!json.success || !Array.isArray(json.data?.utxos))
20829
+ return [];
20830
+ return json.data.utxos.map((u) => ({
20831
+ txid: String(u.txid),
20832
+ vout: Number(u.vout),
20833
+ value: Number(u.value),
20834
+ confirmations: Number(u.confirmations ?? 0)
20835
+ }));
20836
+ } catch {
20837
+ return [];
20838
+ }
20839
+ }
20718
20840
  /**
20719
20841
  * Execute Bitcoin withdrawal (Phase 2)
20720
20842
  *
@@ -21331,13 +21453,49 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21331
21453
  if (this.config.debug) {
21332
21454
  log.info("\u{1F50D} Found vault UTXO for execution", { utxoIdentifier });
21333
21455
  }
21456
+ let executionUtxos;
21457
+ const gatheredUtxos = authResult.confirmedUtxos && authResult.confirmedUtxos.length > 0 ? authResult.confirmedUtxos.map((u) => ({
21458
+ txid: u.txid,
21459
+ vout: Number(u.vout),
21460
+ value: Number(u.value),
21461
+ confirmations: 0
21462
+ })) : await this.fetchConfirmedVaultUtxos(positionId);
21463
+ if (gatheredUtxos.length > 0) {
21464
+ const normTxid = (t) => String(t).toLowerCase().replace(/^0x/, "");
21465
+ const byOutpoint = /* @__PURE__ */ new Map();
21466
+ for (const u of gatheredUtxos) {
21467
+ const txid = normTxid(u.txid);
21468
+ byOutpoint.set(`${txid}:${u.vout}`, {
21469
+ txid,
21470
+ vout: Number(u.vout),
21471
+ value: Number(u.value)
21472
+ });
21473
+ }
21474
+ const repKey = `${normTxid(authResult.utxoTxid)}:${authResult.utxoVout}`;
21475
+ if (!byOutpoint.has(repKey)) {
21476
+ byOutpoint.set(repKey, {
21477
+ txid: normTxid(authResult.utxoTxid),
21478
+ vout: Number(authResult.utxoVout),
21479
+ value: Number(authResult.utxoSatoshis)
21480
+ });
21481
+ }
21482
+ if (byOutpoint.size > 1) {
21483
+ executionUtxos = Array.from(byOutpoint.values());
21484
+ if (this.config.debug) {
21485
+ log.info("\u{1F9E9} Multi-UTXO withdrawal: signing full input set", {
21486
+ inputs: executionUtxos.length
21487
+ });
21488
+ }
21489
+ }
21490
+ }
21334
21491
  const executionResult = await this.executeBTCWithdrawal({
21335
21492
  positionId,
21336
21493
  utxoIdentifier,
21337
21494
  utxoSatoshis: Number(authResult.utxoSatoshis),
21338
21495
  networkFee,
21339
21496
  destination: withdrawalAddress,
21340
- targetAmount: withdrawalAmount
21497
+ targetAmount: withdrawalAmount,
21498
+ ...executionUtxos ? { utxos: executionUtxos } : {}
21341
21499
  });
21342
21500
  if (this.config.debug) {
21343
21501
  log.info("\u2705 Phase 2 complete: Bitcoin transaction executed", {