@gvnrdao/dh-sdk 0.0.306 → 0.0.307

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -52,3 +52,5 @@ export { SDKError, ErrorCategory, ErrorSeverity } from './utils/error-handler';
52
52
  export { setPositionDelegate, getPositionDelegate } from './utils/position-delegate.utils';
53
53
  export { buildBtcExecuteEnvelope, } from './utils/btc-withdrawal-message';
54
54
  export type { BtcExecuteSignParams, BtcExecuteSignedEnvelope, } from './utils/btc-withdrawal-message';
55
+ export { reconcileAuthorizedSpend, defaultHttpGetJson, } from './utils/withdrawal-reconciliation.utils';
56
+ export type { PendingWithdrawalStatus, AuthorizedSpendLike, ReconciledWithdrawal, HttpGetJson, } from './utils/withdrawal-reconciliation.utils';
package/dist/index.js CHANGED
@@ -5591,6 +5591,7 @@ __export(src_exports, {
5591
5591
  createPKPManager: () => createPKPManager,
5592
5592
  createWithdrawalAddressModule: () => createWithdrawalAddressModule,
5593
5593
  default: () => DiamondHandsSDK,
5594
+ defaultHttpGetJson: () => defaultHttpGetJson,
5594
5595
  failure: () => failure,
5595
5596
  fetchProtocolPauseStatus: () => fetchProtocolPauseStatus,
5596
5597
  firstSuccess: () => firstSuccess,
@@ -5614,6 +5615,7 @@ __export(src_exports, {
5614
5615
  mapError: () => mapError,
5615
5616
  match: () => match,
5616
5617
  numericToPositionStatus: () => numericToPositionStatus,
5618
+ reconcileAuthorizedSpend: () => reconcileAuthorizedSpend,
5617
5619
  setPositionDelegate: () => setPositionDelegate,
5618
5620
  success: () => success,
5619
5621
  toPromise: () => toPromise,
@@ -7028,6 +7030,122 @@ async function isLaneGuardActiveOnProvider(provider) {
7028
7030
  }
7029
7031
  }
7030
7032
 
7033
+ // src/utils/withdrawal-reconciliation.utils.ts
7034
+ var defaultHttpGetJson = async (url) => {
7035
+ const res = await fetch(url, { headers: { accept: "application/json" } });
7036
+ let body = null;
7037
+ try {
7038
+ body = await res.json();
7039
+ } catch {
7040
+ body = null;
7041
+ }
7042
+ return { status: res.status, body };
7043
+ };
7044
+ async function reconcileAuthorizedSpend(spend, esploraBaseUrl, httpGetJson = defaultHttpGetJson) {
7045
+ const base = esploraBaseUrl.replace(/\/+$/, "");
7046
+ const fundingRes = await httpGetJson(`${base}/tx/${spend.txid}`);
7047
+ if (fundingRes.status === 404) {
7048
+ return {
7049
+ spend,
7050
+ status: "UNFUNDED",
7051
+ reason: `Funding tx ${spend.txid} not found on the network`
7052
+ };
7053
+ }
7054
+ if (fundingRes.status !== 200) {
7055
+ throw new Error(
7056
+ `Withdrawal reconciliation: esplora /tx/${spend.txid} returned HTTP ${fundingRes.status}`
7057
+ );
7058
+ }
7059
+ const funding = fundingRes.body;
7060
+ if (!funding?.status?.confirmed) {
7061
+ return {
7062
+ spend,
7063
+ status: "UNFUNDED",
7064
+ reason: `Funding tx ${spend.txid} is not confirmed yet`
7065
+ };
7066
+ }
7067
+ const output = funding.vout?.[spend.vout];
7068
+ if (!output || typeof output.value !== "number") {
7069
+ return {
7070
+ spend,
7071
+ status: "CORRUPT",
7072
+ reason: `Authorized vout ${spend.vout} does not exist on tx ${spend.txid} (${funding.vout?.length ?? 0} outputs)`
7073
+ };
7074
+ }
7075
+ const onChainOutputValue = output.value;
7076
+ if (onChainOutputValue !== spend.satoshis) {
7077
+ return {
7078
+ spend,
7079
+ status: "CORRUPT",
7080
+ onChainOutputValue,
7081
+ reason: `Authorization declares ${spend.satoshis} sats for ${spend.txid}:${spend.vout} but the on-chain output is ${onChainOutputValue} sats`
7082
+ };
7083
+ }
7084
+ if (spend.targetAmount > onChainOutputValue) {
7085
+ return {
7086
+ spend,
7087
+ status: "CORRUPT",
7088
+ onChainOutputValue,
7089
+ reason: `Authorized targetAmount ${spend.targetAmount} sats exceeds the ${onChainOutputValue}-sat output it is authorized against`
7090
+ };
7091
+ }
7092
+ const outspendRes = await httpGetJson(
7093
+ `${base}/tx/${spend.txid}/outspend/${spend.vout}`
7094
+ );
7095
+ if (outspendRes.status !== 200) {
7096
+ throw new Error(
7097
+ `Withdrawal reconciliation: esplora outspend for ${spend.txid}:${spend.vout} returned HTTP ${outspendRes.status}`
7098
+ );
7099
+ }
7100
+ const outspend = outspendRes.body;
7101
+ if (outspend?.spent) {
7102
+ const spendingTxid = typeof outspend.txid === "string" && outspend.txid ? outspend.txid : null;
7103
+ if (spendingTxid) {
7104
+ const spendingRes = await httpGetJson(`${base}/tx/${spendingTxid}`);
7105
+ if (spendingRes.status === 200) {
7106
+ const spendingTx = spendingRes.body;
7107
+ const includesOutpoint = (spendingTx.vin ?? []).some(
7108
+ (v) => v.txid === spend.txid && Number(v.vout) === spend.vout
7109
+ );
7110
+ if (includesOutpoint) {
7111
+ const paidToTargetSats = (spendingTx.vout ?? []).filter(
7112
+ (o) => (o.scriptpubkey_address ?? "").toLowerCase() === spend.targetAddress.toLowerCase()
7113
+ ).reduce((sum, o) => sum + (o.value ?? 0), 0);
7114
+ if (paidToTargetSats > 0) {
7115
+ return {
7116
+ spend,
7117
+ status: "EXECUTED",
7118
+ spendingTxid,
7119
+ paidToTargetSats,
7120
+ onChainOutputValue,
7121
+ reason: `Outpoint spent by ${spendingTxid}, paying ${paidToTargetSats} sats to the authorized target \u2014 withdrawal complete`
7122
+ };
7123
+ }
7124
+ return {
7125
+ spend,
7126
+ status: "SPENT_MISMATCH",
7127
+ spendingTxid,
7128
+ onChainOutputValue,
7129
+ reason: `Outpoint spent by ${spendingTxid} which pays the authorized target nothing \u2014 authorization is unexecutable`
7130
+ };
7131
+ }
7132
+ }
7133
+ }
7134
+ return {
7135
+ spend,
7136
+ status: "EXECUTABLE",
7137
+ onChainOutputValue,
7138
+ reason: "Esplora claims the outpoint is spent but no spending tx provably includes it (known regtest-faucet artifact) \u2014 treated as unspent"
7139
+ };
7140
+ }
7141
+ return {
7142
+ spend,
7143
+ status: "EXECUTABLE",
7144
+ onChainOutputValue,
7145
+ reason: "Outpoint confirmed, unspent, and coherent with the authorization"
7146
+ };
7147
+ }
7148
+
7031
7149
  // src/utils/btc-withdrawal-message.ts
7032
7150
  var import_ethers6 = require("ethers");
7033
7151
  var QUANTUM_SECONDS = 60;
@@ -21400,6 +21518,41 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21400
21518
  )
21401
21519
  }));
21402
21520
  }
21521
+ /**
21522
+ * Reconcile every pending withdrawal against BITCOIN truth (incident
21523
+ * 2026-07-22): `getPendingWithdrawals` only reflects on-chain
21524
+ * authorizations, and the contract can never know whether the Phase-2 BTC
21525
+ * broadcast happened. Callers MUST use the returned `status` to decide what
21526
+ * to offer:
21527
+ * - EXECUTABLE → offer Execute (the only status that may).
21528
+ * - EXECUTED → auto-clear; `spendingTxid` is the completion proof.
21529
+ * - SPENT_MISMATCH → unexecutable; offer cancelPendingWithdrawal.
21530
+ * - CORRUPT → authorization contradicts the chain (e.g. declared
21531
+ * satoshis ≠ real output value); offer Cancel &
21532
+ * re-request — Execute can only die at the signer guard.
21533
+ * - UNFUNDED → funding tx unknown/unconfirmed; wait.
21534
+ *
21535
+ * @param opts.esploraBaseUrl Esplora API base (e.g. the api proxy
21536
+ * `/v1/proxy/esplora/<network>`). Falls back to
21537
+ * `config.bitcoinProviders[0].url`; throws when neither is configured.
21538
+ */
21539
+ async reconcilePendingWithdrawals(positionId, opts) {
21540
+ this.ensureInitialized();
21541
+ const esploraBaseUrl = opts?.esploraBaseUrl ?? this.config.bitcoinProviders?.[0]?.url;
21542
+ if (!esploraBaseUrl) {
21543
+ throw new SDKError({
21544
+ code: "SDK_ESPLORA_NOT_CONFIGURED",
21545
+ message: "reconcilePendingWithdrawals requires opts.esploraBaseUrl or config.bitcoinProviders[0].url",
21546
+ category: "CONFIGURATION" /* CONFIGURATION */,
21547
+ severity: "HIGH" /* HIGH */,
21548
+ originalError: new Error("esplora base URL not configured")
21549
+ });
21550
+ }
21551
+ const spends = await this.getPendingWithdrawals(positionId);
21552
+ return Promise.all(
21553
+ spends.map((spend) => reconcileAuthorizedSpend(spend, esploraBaseUrl))
21554
+ );
21555
+ }
21403
21556
  /**
21404
21557
  * Cancel a pending BTC withdrawal
21405
21558
  *
@@ -23298,6 +23451,7 @@ async function getPositionDelegate(positionId, provider, registryAddress) {
23298
23451
  createLoanQuery,
23299
23452
  createPKPManager,
23300
23453
  createWithdrawalAddressModule,
23454
+ defaultHttpGetJson,
23301
23455
  failure,
23302
23456
  fetchProtocolPauseStatus,
23303
23457
  firstSuccess,
@@ -23321,6 +23475,7 @@ async function getPositionDelegate(positionId, provider, registryAddress) {
23321
23475
  mapError,
23322
23476
  match,
23323
23477
  numericToPositionStatus,
23478
+ reconcileAuthorizedSpend,
23324
23479
  setPositionDelegate,
23325
23480
  success,
23326
23481
  toPromise,
package/dist/index.mjs CHANGED
@@ -6952,6 +6952,122 @@ async function isLaneGuardActiveOnProvider(provider) {
6952
6952
  }
6953
6953
  }
6954
6954
 
6955
+ // src/utils/withdrawal-reconciliation.utils.ts
6956
+ var defaultHttpGetJson = async (url) => {
6957
+ const res = await fetch(url, { headers: { accept: "application/json" } });
6958
+ let body = null;
6959
+ try {
6960
+ body = await res.json();
6961
+ } catch {
6962
+ body = null;
6963
+ }
6964
+ return { status: res.status, body };
6965
+ };
6966
+ async function reconcileAuthorizedSpend(spend, esploraBaseUrl, httpGetJson = defaultHttpGetJson) {
6967
+ const base = esploraBaseUrl.replace(/\/+$/, "");
6968
+ const fundingRes = await httpGetJson(`${base}/tx/${spend.txid}`);
6969
+ if (fundingRes.status === 404) {
6970
+ return {
6971
+ spend,
6972
+ status: "UNFUNDED",
6973
+ reason: `Funding tx ${spend.txid} not found on the network`
6974
+ };
6975
+ }
6976
+ if (fundingRes.status !== 200) {
6977
+ throw new Error(
6978
+ `Withdrawal reconciliation: esplora /tx/${spend.txid} returned HTTP ${fundingRes.status}`
6979
+ );
6980
+ }
6981
+ const funding = fundingRes.body;
6982
+ if (!funding?.status?.confirmed) {
6983
+ return {
6984
+ spend,
6985
+ status: "UNFUNDED",
6986
+ reason: `Funding tx ${spend.txid} is not confirmed yet`
6987
+ };
6988
+ }
6989
+ const output = funding.vout?.[spend.vout];
6990
+ if (!output || typeof output.value !== "number") {
6991
+ return {
6992
+ spend,
6993
+ status: "CORRUPT",
6994
+ reason: `Authorized vout ${spend.vout} does not exist on tx ${spend.txid} (${funding.vout?.length ?? 0} outputs)`
6995
+ };
6996
+ }
6997
+ const onChainOutputValue = output.value;
6998
+ if (onChainOutputValue !== spend.satoshis) {
6999
+ return {
7000
+ spend,
7001
+ status: "CORRUPT",
7002
+ onChainOutputValue,
7003
+ reason: `Authorization declares ${spend.satoshis} sats for ${spend.txid}:${spend.vout} but the on-chain output is ${onChainOutputValue} sats`
7004
+ };
7005
+ }
7006
+ if (spend.targetAmount > onChainOutputValue) {
7007
+ return {
7008
+ spend,
7009
+ status: "CORRUPT",
7010
+ onChainOutputValue,
7011
+ reason: `Authorized targetAmount ${spend.targetAmount} sats exceeds the ${onChainOutputValue}-sat output it is authorized against`
7012
+ };
7013
+ }
7014
+ const outspendRes = await httpGetJson(
7015
+ `${base}/tx/${spend.txid}/outspend/${spend.vout}`
7016
+ );
7017
+ if (outspendRes.status !== 200) {
7018
+ throw new Error(
7019
+ `Withdrawal reconciliation: esplora outspend for ${spend.txid}:${spend.vout} returned HTTP ${outspendRes.status}`
7020
+ );
7021
+ }
7022
+ const outspend = outspendRes.body;
7023
+ if (outspend?.spent) {
7024
+ const spendingTxid = typeof outspend.txid === "string" && outspend.txid ? outspend.txid : null;
7025
+ if (spendingTxid) {
7026
+ const spendingRes = await httpGetJson(`${base}/tx/${spendingTxid}`);
7027
+ if (spendingRes.status === 200) {
7028
+ const spendingTx = spendingRes.body;
7029
+ const includesOutpoint = (spendingTx.vin ?? []).some(
7030
+ (v) => v.txid === spend.txid && Number(v.vout) === spend.vout
7031
+ );
7032
+ if (includesOutpoint) {
7033
+ const paidToTargetSats = (spendingTx.vout ?? []).filter(
7034
+ (o) => (o.scriptpubkey_address ?? "").toLowerCase() === spend.targetAddress.toLowerCase()
7035
+ ).reduce((sum, o) => sum + (o.value ?? 0), 0);
7036
+ if (paidToTargetSats > 0) {
7037
+ return {
7038
+ spend,
7039
+ status: "EXECUTED",
7040
+ spendingTxid,
7041
+ paidToTargetSats,
7042
+ onChainOutputValue,
7043
+ reason: `Outpoint spent by ${spendingTxid}, paying ${paidToTargetSats} sats to the authorized target \u2014 withdrawal complete`
7044
+ };
7045
+ }
7046
+ return {
7047
+ spend,
7048
+ status: "SPENT_MISMATCH",
7049
+ spendingTxid,
7050
+ onChainOutputValue,
7051
+ reason: `Outpoint spent by ${spendingTxid} which pays the authorized target nothing \u2014 authorization is unexecutable`
7052
+ };
7053
+ }
7054
+ }
7055
+ }
7056
+ return {
7057
+ spend,
7058
+ status: "EXECUTABLE",
7059
+ onChainOutputValue,
7060
+ reason: "Esplora claims the outpoint is spent but no spending tx provably includes it (known regtest-faucet artifact) \u2014 treated as unspent"
7061
+ };
7062
+ }
7063
+ return {
7064
+ spend,
7065
+ status: "EXECUTABLE",
7066
+ onChainOutputValue,
7067
+ reason: "Outpoint confirmed, unspent, and coherent with the authorization"
7068
+ };
7069
+ }
7070
+
6955
7071
  // src/utils/btc-withdrawal-message.ts
6956
7072
  import { keccak256 as keccak2563, toUtf8Bytes as toUtf8Bytes3, solidityPackedKeccak256 as solidityPackedKeccak2563, getBytes as getBytes3, zeroPadValue } from "ethers";
6957
7073
  var QUANTUM_SECONDS = 60;
@@ -21328,6 +21444,41 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
21328
21444
  )
21329
21445
  }));
21330
21446
  }
21447
+ /**
21448
+ * Reconcile every pending withdrawal against BITCOIN truth (incident
21449
+ * 2026-07-22): `getPendingWithdrawals` only reflects on-chain
21450
+ * authorizations, and the contract can never know whether the Phase-2 BTC
21451
+ * broadcast happened. Callers MUST use the returned `status` to decide what
21452
+ * to offer:
21453
+ * - EXECUTABLE → offer Execute (the only status that may).
21454
+ * - EXECUTED → auto-clear; `spendingTxid` is the completion proof.
21455
+ * - SPENT_MISMATCH → unexecutable; offer cancelPendingWithdrawal.
21456
+ * - CORRUPT → authorization contradicts the chain (e.g. declared
21457
+ * satoshis ≠ real output value); offer Cancel &
21458
+ * re-request — Execute can only die at the signer guard.
21459
+ * - UNFUNDED → funding tx unknown/unconfirmed; wait.
21460
+ *
21461
+ * @param opts.esploraBaseUrl Esplora API base (e.g. the api proxy
21462
+ * `/v1/proxy/esplora/<network>`). Falls back to
21463
+ * `config.bitcoinProviders[0].url`; throws when neither is configured.
21464
+ */
21465
+ async reconcilePendingWithdrawals(positionId, opts) {
21466
+ this.ensureInitialized();
21467
+ const esploraBaseUrl = opts?.esploraBaseUrl ?? this.config.bitcoinProviders?.[0]?.url;
21468
+ if (!esploraBaseUrl) {
21469
+ throw new SDKError({
21470
+ code: "SDK_ESPLORA_NOT_CONFIGURED",
21471
+ message: "reconcilePendingWithdrawals requires opts.esploraBaseUrl or config.bitcoinProviders[0].url",
21472
+ category: "CONFIGURATION" /* CONFIGURATION */,
21473
+ severity: "HIGH" /* HIGH */,
21474
+ originalError: new Error("esplora base URL not configured")
21475
+ });
21476
+ }
21477
+ const spends = await this.getPendingWithdrawals(positionId);
21478
+ return Promise.all(
21479
+ spends.map((spend) => reconcileAuthorizedSpend(spend, esploraBaseUrl))
21480
+ );
21481
+ }
21331
21482
  /**
21332
21483
  * Cancel a pending BTC withdrawal
21333
21484
  *
@@ -23226,6 +23377,7 @@ export {
23226
23377
  createPKPManager,
23227
23378
  createWithdrawalAddressModule,
23228
23379
  DiamondHandsSDK as default,
23380
+ defaultHttpGetJson,
23229
23381
  failure,
23230
23382
  fetchProtocolPauseStatus,
23231
23383
  firstSuccess,
@@ -23249,6 +23401,7 @@ export {
23249
23401
  mapError,
23250
23402
  match,
23251
23403
  numericToPositionStatus,
23404
+ reconcileAuthorizedSpend,
23252
23405
  setPositionDelegate,
23253
23406
  success,
23254
23407
  toPromise,
@@ -18,6 +18,7 @@ import { SDKError } from "../utils/error-handler";
18
18
  import type { CreateLoanRequest, CreateLoanResult, LoanDataDetail, UCDMintRequest, UCDMintResult, PartialPaymentRequest, PartialPaymentResult, BTCWithdrawalResult, RenewPositionRequest, RenewPositionResult, LiquidationRequest, LiquidationResult, ConfirmBalanceRequest, ConfirmBalanceResult, TermsWithFeesResult } from "../interfaces/chunks/loan-operations.i";
19
19
  import type { DiamondHandsSDKConfig } from "../interfaces/chunks/config.i";
20
20
  import type { PKPData } from "../interfaces/chunks/pkp-integration.i";
21
+ import { type ReconciledWithdrawal } from "../utils/withdrawal-reconciliation.utils";
21
22
  import { ContractManager } from "./contract/contract-manager.module";
22
23
  import { WithdrawalAddressModule } from "./withdrawal-address/withdrawal-address.module";
23
24
  import { BitcoinOperations } from "./bitcoin/bitcoin-operations.module";
@@ -556,6 +557,27 @@ export declare class DiamondHandsSDK {
556
557
  authorizedAt: number;
557
558
  utxoKey: string;
558
559
  }>>;
560
+ /**
561
+ * Reconcile every pending withdrawal against BITCOIN truth (incident
562
+ * 2026-07-22): `getPendingWithdrawals` only reflects on-chain
563
+ * authorizations, and the contract can never know whether the Phase-2 BTC
564
+ * broadcast happened. Callers MUST use the returned `status` to decide what
565
+ * to offer:
566
+ * - EXECUTABLE → offer Execute (the only status that may).
567
+ * - EXECUTED → auto-clear; `spendingTxid` is the completion proof.
568
+ * - SPENT_MISMATCH → unexecutable; offer cancelPendingWithdrawal.
569
+ * - CORRUPT → authorization contradicts the chain (e.g. declared
570
+ * satoshis ≠ real output value); offer Cancel &
571
+ * re-request — Execute can only die at the signer guard.
572
+ * - UNFUNDED → funding tx unknown/unconfirmed; wait.
573
+ *
574
+ * @param opts.esploraBaseUrl Esplora API base (e.g. the api proxy
575
+ * `/v1/proxy/esplora/<network>`). Falls back to
576
+ * `config.bitcoinProviders[0].url`; throws when neither is configured.
577
+ */
578
+ reconcilePendingWithdrawals(positionId: string, opts?: {
579
+ esploraBaseUrl?: string;
580
+ }): Promise<Array<ReconciledWithdrawal<Awaited<ReturnType<DiamondHandsSDK["getPendingWithdrawals"]>>[number]>>>;
559
581
  /**
560
582
  * Cancel a pending BTC withdrawal
561
583
  *
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Pending-withdrawal reconciliation — classify each on-chain authorized spend
3
+ * (`BTCSpendAuthorizer.getAuthorizedSpends`) against BITCOIN truth before any
4
+ * UI offers "Execute" or any server invokes the TEE signer.
5
+ *
6
+ * Why this exists (incident 2026-07-22, position 0x992d5c…): the contract can
7
+ * never know whether a Phase-2 BTC broadcast happened, and a pre-P6/#8
8
+ * authorization could record a `(vout, satoshis)` pair that never matched the
9
+ * chain (declared 96,049 vs on-chain 33,000). Executing such an entry can only
10
+ * die at the signer's parent-fetch guard; and an entry whose outpoint was
11
+ * already spent paying the target is DONE and must auto-clear — while an
12
+ * entry whose funding tx merely confirmed must NOT be cleared as "complete".
13
+ *
14
+ * Statuses:
15
+ * - EXECUTABLE — outpoint confirmed, unspent, values coherent → offer Execute.
16
+ * - EXECUTED — outpoint spent by a tx that pays the authorized target →
17
+ * auto-clear, show `spendingTxid` as the completion proof.
18
+ * - SPENT_MISMATCH — outpoint spent but the spending tx pays the target
19
+ * nothing → unexecutable; surface Cancel.
20
+ * - CORRUPT — authorization contradicts the chain (declared value ≠
21
+ * real output value, targetAmount > real value, or vout
22
+ * out of range) → surface Cancel & re-request; never Execute.
23
+ * - UNFUNDED — funding tx unknown/unconfirmed → wait; no Execute yet.
24
+ *
25
+ * Esplora `spent: true` claims are only trusted when the spending tx can be
26
+ * fetched AND provably includes this outpoint among its inputs — the regtest
27
+ * faucet's esplora answers `spent: true` for arbitrary txids, and a false
28
+ * "executed" here would silently dismiss a withdrawal the user was never paid
29
+ * for. Unverifiable claims classify as EXECUTABLE (chain truth: no proven
30
+ * spend); a genuinely-spent outpoint then simply fails downstream, safely.
31
+ */
32
+ export type PendingWithdrawalStatus = "EXECUTABLE" | "EXECUTED" | "SPENT_MISMATCH" | "CORRUPT" | "UNFUNDED";
33
+ export interface AuthorizedSpendLike {
34
+ txid: string;
35
+ vout: number;
36
+ satoshis: number;
37
+ targetAddress: string;
38
+ targetAmount: number;
39
+ }
40
+ export interface ReconciledWithdrawal<T extends AuthorizedSpendLike = AuthorizedSpendLike> {
41
+ spend: T;
42
+ status: PendingWithdrawalStatus;
43
+ /** Human-readable, single-sentence explanation of the classification. */
44
+ reason: string;
45
+ /** Real value of the referenced outpoint, when the funding tx is known. */
46
+ onChainOutputValue?: number;
47
+ /** The verified spending tx, for EXECUTED / SPENT_MISMATCH. */
48
+ spendingTxid?: string;
49
+ /** Sats the verified spending tx pays to `targetAddress` (EXECUTED only). */
50
+ paidToTargetSats?: number;
51
+ }
52
+ /**
53
+ * HTTP seam: GET `url`, resolve `{ status, body }` (body null on non-JSON).
54
+ * Injectable for tests; the default uses global `fetch` (node ≥18 + browsers).
55
+ * Transport failures throw — reconciliation must fail LOUD, never classify on
56
+ * missing data.
57
+ */
58
+ export type HttpGetJson = (url: string) => Promise<{
59
+ status: number;
60
+ body: unknown;
61
+ }>;
62
+ export declare const defaultHttpGetJson: HttpGetJson;
63
+ /** Classify one authorized spend against the esplora at `esploraBaseUrl`. */
64
+ export declare function reconcileAuthorizedSpend<T extends AuthorizedSpendLike>(spend: T, esploraBaseUrl: string, httpGetJson?: HttpGetJson): Promise<ReconciledWithdrawal<T>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gvnrdao/dh-sdk",
3
- "version": "0.0.306",
3
+ "version": "0.0.307",
4
4
  "description": "TypeScript SDK for Diamond Hands Protocol - Bitcoin-backed lending with LIT Protocol PKPs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",