@gvnrdao/dh-sdk 0.0.338 → 0.0.339

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.
@@ -3,6 +3,7 @@ import type { LoanData } from "../interfaces";
3
3
  import type { GraphPositionWithBorrowerDTO } from "../types/graph-dtos";
4
4
  import type { LoanEvents, LoanEventsFilter } from "../types/event-types";
5
5
  import type { BitcoinOperations, EnrichedBitcoinBalance } from "../modules/bitcoin/bitcoin-operations.module";
6
+ import { type BorrowerDebtRow } from "../utils/borrower-ucd-debt-summary";
6
7
  export interface DiamondHandsGraphConfig {
7
8
  endpoint: string;
8
9
  requestTimeoutMs?: number;
@@ -321,6 +322,19 @@ export declare class DiamondHandsGraph {
321
322
  * Get user positions from subgraph
322
323
  */
323
324
  private getUserPositionsOnly;
325
+ /**
326
+ * Raw `{ id, status, ucdDebt }` for EVERY position of a borrower: one round trip per
327
+ * 1000 positions, no count walk, no vault RPCs, and no `LoanData` transform (which
328
+ * coerces wei to a float and leaves `status` a string cast to the numeric enum).
329
+ *
330
+ * Deliberately issues the `GetUserPositions` document rather than a leaner one: the
331
+ * lit-ops graph proxy only passes allowlisted documents (graph-document-policy.ts),
332
+ * and that one is already listed, so this needs no server change. The extra fields
333
+ * cost bytes, not round trips.
334
+ *
335
+ * Throws past `BORROWER_DEBT_ROWS_MAX_PAGES` — never a partial set.
336
+ */
337
+ getBorrowerDebtRows(borrower: string): Promise<BorrowerDebtRow[]>;
324
338
  /**
325
339
  * Get user positions, plus an accurate total count.
326
340
  *
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@ export type { DhServerLoginMessage, DhServerLoginPayload, } from './utils/eip712
41
41
  export { DEFAULT_LIT_NETWORK, VALID_LIT_NETWORKS, SDK_DEFAULTS, } from './constants/chunks/sdk-config';
42
42
  export { ALL_CONTRACTS, ALL_DEPLOYMENTS, getContractsByNetwork, getDeploymentByNetwork, LOCALHOST_CONTRACTS, SEPOLIA_CONTRACTS, } from './constants/chunks/deployment-addresses';
43
43
  export type { DeploymentContracts, DeploymentData, DeploymentLatestEnv, } from './constants/chunks/deployment-addresses';
44
- export type { CreateLoanRequest, CreateLoanResult, LoanData, LoanDataDetail, PaginatedLoansResponse, PKPValidationData, BitcoinAddresses, } from './interfaces/chunks/loan-operations.i';
44
+ export type { CreateLoanRequest, CreateLoanResult, LoanData, LoanDataDetail, PaginatedLoansResponse, BorrowerUcdDebtSummary, BorrowerUcdDebtStatusCount, PKPValidationData, BitcoinAddresses, } from './interfaces/chunks/loan-operations.i';
45
45
  export type { DiamondHandsSDKConfig, SDKMode, ContractAddresses, BitcoinProviderConfig, } from './interfaces/chunks/config.i';
46
46
  export type { PKPData, PKPCreationRequest, PKPCreationResult, PKPValidationResult, } from './interfaces/chunks/pkp-integration.i';
47
47
  export type { AuthorizationRequest, AuthorizationResult, BTCDepositRequest, BTCDepositResult, } from './interfaces/chunks/requests.i';
package/dist/index.js CHANGED
@@ -10465,6 +10465,101 @@ var LoanStatus = /* @__PURE__ */ ((LoanStatus2) => {
10465
10465
 
10466
10466
  // src/modules/loan/loan-query.module.ts
10467
10467
  var import_ethers12 = require("ethers");
10468
+
10469
+ // src/constants/chunks/sdk-limits.ts
10470
+ var THE_GRAPH_MAX_BATCH_SIZE = 1e3;
10471
+
10472
+ // src/utils/borrower-ucd-debt-summary.ts
10473
+ var BORROWER_DEBT_ROWS_PAGE_SIZE = THE_GRAPH_MAX_BATCH_SIZE;
10474
+ var BORROWER_DEBT_ROWS_MAX_PAGES = 10;
10475
+ async function collectBorrowerDebtRows(fetchPage) {
10476
+ const pageSize = BORROWER_DEBT_ROWS_PAGE_SIZE;
10477
+ const rows = [];
10478
+ for (let page = 0; page < BORROWER_DEBT_ROWS_MAX_PAGES; page++) {
10479
+ const batch = await fetchPage(page * pageSize, pageSize);
10480
+ if (batch.length > pageSize) {
10481
+ throw new Error(
10482
+ `Borrower debt page ${page} returned ${batch.length} rows for first: ${pageSize}`
10483
+ );
10484
+ }
10485
+ rows.push(...batch);
10486
+ if (batch.length < pageSize) {
10487
+ return rows;
10488
+ }
10489
+ }
10490
+ throw new Error(
10491
+ `Borrower debt rows exceeded ${BORROWER_DEBT_ROWS_MAX_PAGES} pages of ${pageSize} (${rows.length} rows so far); refusing to return a partial total`
10492
+ );
10493
+ }
10494
+ function parseSubgraphLoanStatus(label) {
10495
+ if (typeof label !== "string") {
10496
+ throw new Error(`Subgraph loan status is not a string: ${String(label)}`);
10497
+ }
10498
+ const value = LoanStatus[label];
10499
+ if (typeof value !== "number") {
10500
+ throw new Error(`Unknown subgraph loan status label: "${label}"`);
10501
+ }
10502
+ return value;
10503
+ }
10504
+ function parseSubgraphWei(row) {
10505
+ const raw = row.ucdDebt || "0";
10506
+ if (typeof raw !== "string") {
10507
+ throw new Error(
10508
+ `ucdDebt on position ${row.id} is not a string: ${String(raw)}`
10509
+ );
10510
+ }
10511
+ let wei;
10512
+ try {
10513
+ wei = BigInt(raw);
10514
+ } catch (error) {
10515
+ throw new Error(
10516
+ `ucdDebt on position ${row.id} is not an integer wei string: "${raw}"`,
10517
+ { cause: error }
10518
+ );
10519
+ }
10520
+ if (wei < 0n) {
10521
+ throw new Error(`Negative ucdDebt on position ${row.id}: ${raw}`);
10522
+ }
10523
+ return wei;
10524
+ }
10525
+ var WEI_PER_UCD = 10n ** 18n;
10526
+ function weiToHumanUcdString(wei) {
10527
+ if (wei < 0n) {
10528
+ throw new Error(`Cannot render negative wei: ${wei}`);
10529
+ }
10530
+ const whole = wei / WEI_PER_UCD;
10531
+ const frac = (wei % WEI_PER_UCD).toString().padStart(18, "0").replace(/0+$/, "");
10532
+ return frac.length > 0 ? `${whole}.${frac}` : `${whole}`;
10533
+ }
10534
+ var STATUS_ORDINALS = Object.values(LoanStatus).filter((v) => typeof v === "number").sort((a, b) => a - b);
10535
+ function buildBorrowerUcdDebtSummary(borrower, rows, fetchedAt = Date.now()) {
10536
+ let totalWei = 0n;
10537
+ const counts = /* @__PURE__ */ new Map();
10538
+ for (const row of rows) {
10539
+ totalWei += parseSubgraphWei(row);
10540
+ const status = parseSubgraphLoanStatus(row.status);
10541
+ counts.set(status, (counts.get(status) ?? 0) + 1);
10542
+ }
10543
+ const byStatus = [];
10544
+ for (const status of STATUS_ORDINALS) {
10545
+ const count = counts.get(status);
10546
+ if (count === void 0) {
10547
+ continue;
10548
+ }
10549
+ byStatus.push({ status, statusLabel: LoanStatus[status], count });
10550
+ }
10551
+ return {
10552
+ borrower: borrower.toLowerCase(),
10553
+ loanCount: rows.length,
10554
+ totalUcdDebt: totalWei.toString(),
10555
+ totalUcdDebtHuman: weiToHumanUcdString(totalWei),
10556
+ byStatus,
10557
+ source: "subgraph",
10558
+ fetchedAt
10559
+ };
10560
+ }
10561
+
10562
+ // src/modules/loan/loan-query.module.ts
10468
10563
  var POSITION_CORE_ABI = [
10469
10564
  "function getPositionDetails(bytes32) view returns (tuple(bytes32 positionId, bytes32 pkpId, uint256 ucdDebt, string vaultAddress, address borrower, uint40 createdAt, uint40 lastUpdated, uint16 selectedTerm, uint40 expiryAt, uint8 status, uint40 previousExpiryAt, uint16 totalTerm))"
10470
10565
  ];
@@ -10857,6 +10952,43 @@ var LoanQuery = class {
10857
10952
  async getLoansByBorrower(borrower, pagination, orderBy, orderDirection) {
10858
10953
  return this.getLoans({ borrower, orderBy, orderDirection }, pagination);
10859
10954
  }
10955
+ /**
10956
+ * Combined UCD debt + by-status counts for a borrower from ONE pass over the
10957
+ * subgraph's raw rows (`graphClient.getBorrowerDebtRows`): BigInt wei sum, strict
10958
+ * status labels, `source: "subgraph"`.
10959
+ *
10960
+ * INDEXED figure, not a transaction input — the subgraph lags writes. Anything that
10961
+ * sizes a repayment reads the chain (`getPositionDetailsView`). Fails loud: a page
10962
+ * failure or the page cap is a SUBGRAPH-category failure carrying the cause; there
10963
+ * is no partial total.
10964
+ */
10965
+ async getBorrowerUcdDebtSummary(borrower) {
10966
+ const trimmed = typeof borrower === "string" ? borrower.trim() : "";
10967
+ if (!(0, import_ethers12.isAddress)(trimmed)) {
10968
+ return failure(
10969
+ new SDKError({
10970
+ message: `Borrower must be an EVM address for a UCD debt summary, got "${trimmed}"`,
10971
+ category: "VALIDATION" /* VALIDATION */,
10972
+ severity: "LOW" /* LOW */,
10973
+ context: { borrower: trimmed }
10974
+ })
10975
+ );
10976
+ }
10977
+ return tryCatchAsync(
10978
+ async () => {
10979
+ const rows = await this.config.graphClient.getBorrowerDebtRows(trimmed);
10980
+ return buildBorrowerUcdDebtSummary(trimmed, rows);
10981
+ },
10982
+ (error) => error instanceof SDKError ? error : new SDKError({
10983
+ message: `Borrower UCD debt summary failed for ${trimmed}: ${error instanceof Error ? error.message : String(error)}`,
10984
+ category: "SUBGRAPH" /* SUBGRAPH */,
10985
+ severity: "MEDIUM" /* MEDIUM */,
10986
+ originalError: error instanceof Error ? error : void 0,
10987
+ cause: error,
10988
+ context: { borrower: trimmed }
10989
+ })
10990
+ );
10991
+ }
10860
10992
  /**
10861
10993
  * Get active loans (status = ACTIVE)
10862
10994
  *
@@ -12391,10 +12523,36 @@ var GraphClient = class {
12391
12523
  }
12392
12524
  };
12393
12525
 
12394
- // src/constants/chunks/sdk-limits.ts
12395
- var THE_GRAPH_MAX_BATCH_SIZE = 1e3;
12396
-
12397
12526
  // src/graphs/diamond-hands.ts
12527
+ var GET_USER_POSITIONS_DOCUMENT = `
12528
+ query GetUserPositions($borrower: Bytes!, $first: Int, $skip: Int, $orderBy: Position_orderBy, $orderDirection: OrderDirection) {
12529
+ user(id: $borrower) {
12530
+ id
12531
+ positions(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection) {
12532
+ id
12533
+ pkpId
12534
+ borrower {
12535
+ createdAt
12536
+ id
12537
+ }
12538
+ # btcAmount removed - balance is now queried on-chain via lit-actions with signature validation
12539
+ ucdMinted
12540
+ ucdPaid
12541
+ ucdDebt
12542
+ collateralRatio
12543
+ requestedCollateralRatio
12544
+ selectedTerm
12545
+ status
12546
+ createdAt
12547
+ createdAtBlock
12548
+ lastUpdated
12549
+ originalTerm
12550
+ remainingDebt
12551
+ expiryAt
12552
+ }
12553
+ }
12554
+ }
12555
+ `;
12398
12556
  function normalizePkpId2(pkpId) {
12399
12557
  const raw = pkpId.startsWith("0x") ? pkpId.slice(2) : pkpId;
12400
12558
  if (raw.length === 64 && raw.startsWith("000000000000000000000000") && raw !== "0".repeat(64)) {
@@ -12712,35 +12870,7 @@ var DiamondHandsGraph = class {
12712
12870
  * Get user positions from subgraph
12713
12871
  */
12714
12872
  async getUserPositionsOnly(userAddress, first, skip, orderBy, orderDirection) {
12715
- const query = `
12716
- query GetUserPositions($borrower: Bytes!, $first: Int, $skip: Int, $orderBy: Position_orderBy, $orderDirection: OrderDirection) {
12717
- user(id: $borrower) {
12718
- id
12719
- positions(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection) {
12720
- id
12721
- pkpId
12722
- borrower {
12723
- createdAt
12724
- id
12725
- }
12726
- # btcAmount removed - balance is now queried on-chain via lit-actions with signature validation
12727
- ucdMinted
12728
- ucdPaid
12729
- ucdDebt
12730
- collateralRatio
12731
- requestedCollateralRatio
12732
- selectedTerm
12733
- status
12734
- createdAt
12735
- createdAtBlock
12736
- lastUpdated
12737
- originalTerm
12738
- remainingDebt
12739
- expiryAt
12740
- }
12741
- }
12742
- }
12743
- `;
12873
+ const query = GET_USER_POSITIONS_DOCUMENT;
12744
12874
  const variables = {
12745
12875
  borrower: userAddress.toLowerCase(),
12746
12876
  first: first || void 0,
@@ -12758,6 +12888,45 @@ var DiamondHandsGraph = class {
12758
12888
  }
12759
12889
  return positions;
12760
12890
  }
12891
+ /**
12892
+ * Raw `{ id, status, ucdDebt }` for EVERY position of a borrower: one round trip per
12893
+ * 1000 positions, no count walk, no vault RPCs, and no `LoanData` transform (which
12894
+ * coerces wei to a float and leaves `status` a string cast to the numeric enum).
12895
+ *
12896
+ * Deliberately issues the `GetUserPositions` document rather than a leaner one: the
12897
+ * lit-ops graph proxy only passes allowlisted documents (graph-document-policy.ts),
12898
+ * and that one is already listed, so this needs no server change. The extra fields
12899
+ * cost bytes, not round trips.
12900
+ *
12901
+ * Throws past `BORROWER_DEBT_ROWS_MAX_PAGES` — never a partial set.
12902
+ */
12903
+ async getBorrowerDebtRows(borrower) {
12904
+ const borrowerId = borrower.toLowerCase();
12905
+ return collectBorrowerDebtRows(async (skip, first) => {
12906
+ const result = await this.client.execute(GET_USER_POSITIONS_DOCUMENT, {
12907
+ borrower: borrowerId,
12908
+ first,
12909
+ skip,
12910
+ orderBy: "id",
12911
+ orderDirection: "asc"
12912
+ });
12913
+ if (result?.user == null) {
12914
+ if (skip > 0) {
12915
+ throw new Error(
12916
+ `getBorrowerDebtRows: user ${borrowerId} vanished from the subgraph at skip ${skip}`
12917
+ );
12918
+ }
12919
+ return [];
12920
+ }
12921
+ const positions = result.user.positions;
12922
+ if (!Array.isArray(positions)) {
12923
+ throw new Error(
12924
+ `getBorrowerDebtRows: subgraph returned no positions array for ${borrowerId} at skip ${skip}`
12925
+ );
12926
+ }
12927
+ return positions.filter((p) => p != null);
12928
+ });
12929
+ }
12761
12930
  /**
12762
12931
  * Get user positions, plus an accurate total count.
12763
12932
  *
@@ -25708,6 +25877,18 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
25708
25877
  async getLoansByBorrower(borrower, pagination, orderBy, orderDirection) {
25709
25878
  return this.loanQuery.getLoansByBorrower(borrower, pagination, orderBy, orderDirection);
25710
25879
  }
25880
+ /**
25881
+ * Combined UCD debt (canonical wei string + display string) and by-status loan
25882
+ * counts for a borrower, from one pass over the subgraph. `source: "subgraph"` —
25883
+ * an INDEXED, informational figure that lags writes; never size a transaction
25884
+ * from it (repayments read the chain via `getPositionDetailsView`).
25885
+ *
25886
+ * Fails loud with a SUBGRAPH-category `SDKError` (cause attached) on any page
25887
+ * failure or past 10,000 positions; never returns a partial total.
25888
+ */
25889
+ async getBorrowerUcdDebtSummary(borrower) {
25890
+ return this.loanQuery.getBorrowerUcdDebtSummary(borrower);
25891
+ }
25711
25892
  /**
25712
25893
  * Get all active loans
25713
25894
  *
package/dist/index.mjs CHANGED
@@ -10382,7 +10382,102 @@ var LoanStatus = /* @__PURE__ */ ((LoanStatus2) => {
10382
10382
  })(LoanStatus || {});
10383
10383
 
10384
10384
  // src/modules/loan/loan-query.module.ts
10385
- import { Contract as Contract4 } from "ethers";
10385
+ import { Contract as Contract4, isAddress as isAddress2 } from "ethers";
10386
+
10387
+ // src/constants/chunks/sdk-limits.ts
10388
+ var THE_GRAPH_MAX_BATCH_SIZE = 1e3;
10389
+
10390
+ // src/utils/borrower-ucd-debt-summary.ts
10391
+ var BORROWER_DEBT_ROWS_PAGE_SIZE = THE_GRAPH_MAX_BATCH_SIZE;
10392
+ var BORROWER_DEBT_ROWS_MAX_PAGES = 10;
10393
+ async function collectBorrowerDebtRows(fetchPage) {
10394
+ const pageSize = BORROWER_DEBT_ROWS_PAGE_SIZE;
10395
+ const rows = [];
10396
+ for (let page = 0; page < BORROWER_DEBT_ROWS_MAX_PAGES; page++) {
10397
+ const batch = await fetchPage(page * pageSize, pageSize);
10398
+ if (batch.length > pageSize) {
10399
+ throw new Error(
10400
+ `Borrower debt page ${page} returned ${batch.length} rows for first: ${pageSize}`
10401
+ );
10402
+ }
10403
+ rows.push(...batch);
10404
+ if (batch.length < pageSize) {
10405
+ return rows;
10406
+ }
10407
+ }
10408
+ throw new Error(
10409
+ `Borrower debt rows exceeded ${BORROWER_DEBT_ROWS_MAX_PAGES} pages of ${pageSize} (${rows.length} rows so far); refusing to return a partial total`
10410
+ );
10411
+ }
10412
+ function parseSubgraphLoanStatus(label) {
10413
+ if (typeof label !== "string") {
10414
+ throw new Error(`Subgraph loan status is not a string: ${String(label)}`);
10415
+ }
10416
+ const value = LoanStatus[label];
10417
+ if (typeof value !== "number") {
10418
+ throw new Error(`Unknown subgraph loan status label: "${label}"`);
10419
+ }
10420
+ return value;
10421
+ }
10422
+ function parseSubgraphWei(row) {
10423
+ const raw = row.ucdDebt || "0";
10424
+ if (typeof raw !== "string") {
10425
+ throw new Error(
10426
+ `ucdDebt on position ${row.id} is not a string: ${String(raw)}`
10427
+ );
10428
+ }
10429
+ let wei;
10430
+ try {
10431
+ wei = BigInt(raw);
10432
+ } catch (error) {
10433
+ throw new Error(
10434
+ `ucdDebt on position ${row.id} is not an integer wei string: "${raw}"`,
10435
+ { cause: error }
10436
+ );
10437
+ }
10438
+ if (wei < 0n) {
10439
+ throw new Error(`Negative ucdDebt on position ${row.id}: ${raw}`);
10440
+ }
10441
+ return wei;
10442
+ }
10443
+ var WEI_PER_UCD = 10n ** 18n;
10444
+ function weiToHumanUcdString(wei) {
10445
+ if (wei < 0n) {
10446
+ throw new Error(`Cannot render negative wei: ${wei}`);
10447
+ }
10448
+ const whole = wei / WEI_PER_UCD;
10449
+ const frac = (wei % WEI_PER_UCD).toString().padStart(18, "0").replace(/0+$/, "");
10450
+ return frac.length > 0 ? `${whole}.${frac}` : `${whole}`;
10451
+ }
10452
+ var STATUS_ORDINALS = Object.values(LoanStatus).filter((v) => typeof v === "number").sort((a, b) => a - b);
10453
+ function buildBorrowerUcdDebtSummary(borrower, rows, fetchedAt = Date.now()) {
10454
+ let totalWei = 0n;
10455
+ const counts = /* @__PURE__ */ new Map();
10456
+ for (const row of rows) {
10457
+ totalWei += parseSubgraphWei(row);
10458
+ const status = parseSubgraphLoanStatus(row.status);
10459
+ counts.set(status, (counts.get(status) ?? 0) + 1);
10460
+ }
10461
+ const byStatus = [];
10462
+ for (const status of STATUS_ORDINALS) {
10463
+ const count = counts.get(status);
10464
+ if (count === void 0) {
10465
+ continue;
10466
+ }
10467
+ byStatus.push({ status, statusLabel: LoanStatus[status], count });
10468
+ }
10469
+ return {
10470
+ borrower: borrower.toLowerCase(),
10471
+ loanCount: rows.length,
10472
+ totalUcdDebt: totalWei.toString(),
10473
+ totalUcdDebtHuman: weiToHumanUcdString(totalWei),
10474
+ byStatus,
10475
+ source: "subgraph",
10476
+ fetchedAt
10477
+ };
10478
+ }
10479
+
10480
+ // src/modules/loan/loan-query.module.ts
10386
10481
  var POSITION_CORE_ABI = [
10387
10482
  "function getPositionDetails(bytes32) view returns (tuple(bytes32 positionId, bytes32 pkpId, uint256 ucdDebt, string vaultAddress, address borrower, uint40 createdAt, uint40 lastUpdated, uint16 selectedTerm, uint40 expiryAt, uint8 status, uint40 previousExpiryAt, uint16 totalTerm))"
10388
10483
  ];
@@ -10775,6 +10870,43 @@ var LoanQuery = class {
10775
10870
  async getLoansByBorrower(borrower, pagination, orderBy, orderDirection) {
10776
10871
  return this.getLoans({ borrower, orderBy, orderDirection }, pagination);
10777
10872
  }
10873
+ /**
10874
+ * Combined UCD debt + by-status counts for a borrower from ONE pass over the
10875
+ * subgraph's raw rows (`graphClient.getBorrowerDebtRows`): BigInt wei sum, strict
10876
+ * status labels, `source: "subgraph"`.
10877
+ *
10878
+ * INDEXED figure, not a transaction input — the subgraph lags writes. Anything that
10879
+ * sizes a repayment reads the chain (`getPositionDetailsView`). Fails loud: a page
10880
+ * failure or the page cap is a SUBGRAPH-category failure carrying the cause; there
10881
+ * is no partial total.
10882
+ */
10883
+ async getBorrowerUcdDebtSummary(borrower) {
10884
+ const trimmed = typeof borrower === "string" ? borrower.trim() : "";
10885
+ if (!isAddress2(trimmed)) {
10886
+ return failure(
10887
+ new SDKError({
10888
+ message: `Borrower must be an EVM address for a UCD debt summary, got "${trimmed}"`,
10889
+ category: "VALIDATION" /* VALIDATION */,
10890
+ severity: "LOW" /* LOW */,
10891
+ context: { borrower: trimmed }
10892
+ })
10893
+ );
10894
+ }
10895
+ return tryCatchAsync(
10896
+ async () => {
10897
+ const rows = await this.config.graphClient.getBorrowerDebtRows(trimmed);
10898
+ return buildBorrowerUcdDebtSummary(trimmed, rows);
10899
+ },
10900
+ (error) => error instanceof SDKError ? error : new SDKError({
10901
+ message: `Borrower UCD debt summary failed for ${trimmed}: ${error instanceof Error ? error.message : String(error)}`,
10902
+ category: "SUBGRAPH" /* SUBGRAPH */,
10903
+ severity: "MEDIUM" /* MEDIUM */,
10904
+ originalError: error instanceof Error ? error : void 0,
10905
+ cause: error,
10906
+ context: { borrower: trimmed }
10907
+ })
10908
+ );
10909
+ }
10778
10910
  /**
10779
10911
  * Get active loans (status = ACTIVE)
10780
10912
  *
@@ -12309,10 +12441,36 @@ var GraphClient = class {
12309
12441
  }
12310
12442
  };
12311
12443
 
12312
- // src/constants/chunks/sdk-limits.ts
12313
- var THE_GRAPH_MAX_BATCH_SIZE = 1e3;
12314
-
12315
12444
  // src/graphs/diamond-hands.ts
12445
+ var GET_USER_POSITIONS_DOCUMENT = `
12446
+ query GetUserPositions($borrower: Bytes!, $first: Int, $skip: Int, $orderBy: Position_orderBy, $orderDirection: OrderDirection) {
12447
+ user(id: $borrower) {
12448
+ id
12449
+ positions(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection) {
12450
+ id
12451
+ pkpId
12452
+ borrower {
12453
+ createdAt
12454
+ id
12455
+ }
12456
+ # btcAmount removed - balance is now queried on-chain via lit-actions with signature validation
12457
+ ucdMinted
12458
+ ucdPaid
12459
+ ucdDebt
12460
+ collateralRatio
12461
+ requestedCollateralRatio
12462
+ selectedTerm
12463
+ status
12464
+ createdAt
12465
+ createdAtBlock
12466
+ lastUpdated
12467
+ originalTerm
12468
+ remainingDebt
12469
+ expiryAt
12470
+ }
12471
+ }
12472
+ }
12473
+ `;
12316
12474
  function normalizePkpId2(pkpId) {
12317
12475
  const raw = pkpId.startsWith("0x") ? pkpId.slice(2) : pkpId;
12318
12476
  if (raw.length === 64 && raw.startsWith("000000000000000000000000") && raw !== "0".repeat(64)) {
@@ -12630,35 +12788,7 @@ var DiamondHandsGraph = class {
12630
12788
  * Get user positions from subgraph
12631
12789
  */
12632
12790
  async getUserPositionsOnly(userAddress, first, skip, orderBy, orderDirection) {
12633
- const query = `
12634
- query GetUserPositions($borrower: Bytes!, $first: Int, $skip: Int, $orderBy: Position_orderBy, $orderDirection: OrderDirection) {
12635
- user(id: $borrower) {
12636
- id
12637
- positions(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection) {
12638
- id
12639
- pkpId
12640
- borrower {
12641
- createdAt
12642
- id
12643
- }
12644
- # btcAmount removed - balance is now queried on-chain via lit-actions with signature validation
12645
- ucdMinted
12646
- ucdPaid
12647
- ucdDebt
12648
- collateralRatio
12649
- requestedCollateralRatio
12650
- selectedTerm
12651
- status
12652
- createdAt
12653
- createdAtBlock
12654
- lastUpdated
12655
- originalTerm
12656
- remainingDebt
12657
- expiryAt
12658
- }
12659
- }
12660
- }
12661
- `;
12791
+ const query = GET_USER_POSITIONS_DOCUMENT;
12662
12792
  const variables = {
12663
12793
  borrower: userAddress.toLowerCase(),
12664
12794
  first: first || void 0,
@@ -12676,6 +12806,45 @@ var DiamondHandsGraph = class {
12676
12806
  }
12677
12807
  return positions;
12678
12808
  }
12809
+ /**
12810
+ * Raw `{ id, status, ucdDebt }` for EVERY position of a borrower: one round trip per
12811
+ * 1000 positions, no count walk, no vault RPCs, and no `LoanData` transform (which
12812
+ * coerces wei to a float and leaves `status` a string cast to the numeric enum).
12813
+ *
12814
+ * Deliberately issues the `GetUserPositions` document rather than a leaner one: the
12815
+ * lit-ops graph proxy only passes allowlisted documents (graph-document-policy.ts),
12816
+ * and that one is already listed, so this needs no server change. The extra fields
12817
+ * cost bytes, not round trips.
12818
+ *
12819
+ * Throws past `BORROWER_DEBT_ROWS_MAX_PAGES` — never a partial set.
12820
+ */
12821
+ async getBorrowerDebtRows(borrower) {
12822
+ const borrowerId = borrower.toLowerCase();
12823
+ return collectBorrowerDebtRows(async (skip, first) => {
12824
+ const result = await this.client.execute(GET_USER_POSITIONS_DOCUMENT, {
12825
+ borrower: borrowerId,
12826
+ first,
12827
+ skip,
12828
+ orderBy: "id",
12829
+ orderDirection: "asc"
12830
+ });
12831
+ if (result?.user == null) {
12832
+ if (skip > 0) {
12833
+ throw new Error(
12834
+ `getBorrowerDebtRows: user ${borrowerId} vanished from the subgraph at skip ${skip}`
12835
+ );
12836
+ }
12837
+ return [];
12838
+ }
12839
+ const positions = result.user.positions;
12840
+ if (!Array.isArray(positions)) {
12841
+ throw new Error(
12842
+ `getBorrowerDebtRows: subgraph returned no positions array for ${borrowerId} at skip ${skip}`
12843
+ );
12844
+ }
12845
+ return positions.filter((p) => p != null);
12846
+ });
12847
+ }
12679
12848
  /**
12680
12849
  * Get user positions, plus an accurate total count.
12681
12850
  *
@@ -25634,6 +25803,18 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
25634
25803
  async getLoansByBorrower(borrower, pagination, orderBy, orderDirection) {
25635
25804
  return this.loanQuery.getLoansByBorrower(borrower, pagination, orderBy, orderDirection);
25636
25805
  }
25806
+ /**
25807
+ * Combined UCD debt (canonical wei string + display string) and by-status loan
25808
+ * counts for a borrower, from one pass over the subgraph. `source: "subgraph"` —
25809
+ * an INDEXED, informational figure that lags writes; never size a transaction
25810
+ * from it (repayments read the chain via `getPositionDetailsView`).
25811
+ *
25812
+ * Fails loud with a SUBGRAPH-category `SDKError` (cause attached) on any page
25813
+ * failure or past 10,000 positions; never returns a partial total.
25814
+ */
25815
+ async getBorrowerUcdDebtSummary(borrower) {
25816
+ return this.loanQuery.getBorrowerUcdDebtSummary(borrower);
25817
+ }
25637
25818
  /**
25638
25819
  * Get all active loans
25639
25820
  *
@@ -78,6 +78,40 @@ export interface PaginatedLoansResponse {
78
78
  maxRows: number;
79
79
  totalLoans: number;
80
80
  }
81
+ /**
82
+ * Per-status row in {@link BorrowerUcdDebtSummary.byStatus}.
83
+ * Only statuses with count &gt; 0 appear; order follows {@link LoanStatus} ordinals.
84
+ */
85
+ export interface BorrowerUcdDebtStatusCount {
86
+ status: LoanStatus;
87
+ /** Enum key, e.g. "ACTIVE", "PENDING_DEPOSIT". */
88
+ statusLabel: string;
89
+ count: number;
90
+ }
91
+ /**
92
+ * Aggregated UCD debt + status mix for one borrower, from ONE pass over the
93
+ * subgraph's raw position rows — shared by Butler, MCP, CLI.
94
+ *
95
+ * This is the INDEXED view (the subgraph lags writes). It is informational and
96
+ * must never size a transaction; repayments read the chain
97
+ * (`getPositionDetailsView`).
98
+ */
99
+ export interface BorrowerUcdDebtSummary {
100
+ /** Lower-cased. */
101
+ borrower: string;
102
+ /** Positions included in the sum — every status, all pages. */
103
+ loanCount: number;
104
+ /** Canonical total debt in wei (18 decimals), as a decimal string (BigInt sum). */
105
+ totalUcdDebt: string;
106
+ /** Display only: 18-decimal rendering with trailing zeros trimmed (e.g. "21.93"). */
107
+ totalUcdDebtHuman: string;
108
+ /** Only statuses with count > 0, ordered by {@link LoanStatus} ordinal. */
109
+ byStatus: BorrowerUcdDebtStatusCount[];
110
+ /** Where the figures came from. Reserved for a future on-chain cross-check ("chain"). */
111
+ source: "subgraph";
112
+ /** ms epoch, set by the SDK when the last page landed. */
113
+ fetchedAt: number;
114
+ }
81
115
  /**
82
116
  * Detailed Loan Data Interface (used by getLoanById / PKP)
83
117
  */
@@ -893,6 +893,16 @@ export declare class DiamondHandsSDK {
893
893
  page: number;
894
894
  pageSize: number;
895
895
  }, orderBy?: "createdAt" | "lastUpdatedAt" | "ucdDebt", orderDirection?: "asc" | "desc"): Promise<Result<import("../interfaces/chunks/loan-operations.i").PaginatedLoansResponse, SDKError>>;
896
+ /**
897
+ * Combined UCD debt (canonical wei string + display string) and by-status loan
898
+ * counts for a borrower, from one pass over the subgraph. `source: "subgraph"` —
899
+ * an INDEXED, informational figure that lags writes; never size a transaction
900
+ * from it (repayments read the chain via `getPositionDetailsView`).
901
+ *
902
+ * Fails loud with a SUBGRAPH-category `SDKError` (cause attached) on any page
903
+ * failure or past 10,000 positions; never returns a partial total.
904
+ */
905
+ getBorrowerUcdDebtSummary(borrower: string): Promise<Result<import("../interfaces/chunks/loan-operations.i").BorrowerUcdDebtSummary, SDKError>>;
896
906
  /**
897
907
  * Get all active loans
898
908
  *