@gvnrdao/dh-sdk 0.0.337 → 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.
Files changed (32) hide show
  1. package/browser/dist/browser.js +1 -1
  2. package/dist/deployments.js +8 -8
  3. package/dist/deployments.mjs +8 -8
  4. package/dist/graphs/diamond-hands.d.ts +14 -0
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.js +259 -44
  7. package/dist/index.mjs +260 -45
  8. package/dist/interfaces/chunks/config.i.d.ts +19 -0
  9. package/dist/interfaces/chunks/loan-operations.i.d.ts +34 -0
  10. package/dist/modules/diamond-hands-sdk.d.ts +10 -0
  11. package/dist/modules/loan/loan-query.module.d.ts +12 -1
  12. package/dist/safe-delegation.js +8 -8
  13. package/dist/safe-delegation.mjs +8 -8
  14. package/dist/sign-guard.d.ts +26 -0
  15. package/dist/sign-guard.js +1008 -0
  16. package/dist/sign-guard.mjs +941 -0
  17. package/dist/utils/borrower-authorization-712.d.ts +1 -1
  18. package/dist/utils/borrower-ucd-debt-summary.d.ts +44 -0
  19. package/dist/utils/eip712-login.d.ts +34 -1
  20. package/dist/utils/server-session-store.d.ts +15 -0
  21. package/dist/utils/server-session.d.ts +13 -0
  22. package/dist/utils/sign-guard/errors.d.ts +9 -0
  23. package/dist/utils/sign-guard/extend-fee-bound.d.ts +15 -0
  24. package/dist/utils/sign-guard/fee-ceiling.d.ts +40 -0
  25. package/dist/utils/sign-guard/index.d.ts +9 -0
  26. package/dist/utils/sign-guard/lit-ops-client.d.ts +107 -0
  27. package/dist/utils/sign-guard/op-envelope.d.ts +88 -0
  28. package/dist/utils/sign-guard/op-specs.d.ts +51 -0
  29. package/dist/utils/sign-guard/sign-and-broadcast.d.ts +56 -0
  30. package/dist/utils/sign-guard/signable-functions.d.ts +41 -0
  31. package/dist/utils/sign-guard/tx-validator.d.ts +148 -0
  32. package/package.json +26 -4
@@ -459,13 +459,13 @@ var MAINNET_DEPLOYMENT = {
459
459
  block: 25472956
460
460
  },
461
461
  BTCSpendAuthorizer: {
462
- previousImplementation: "0xb003ed89F695E8819a85513fE17260b805B3c478",
463
- newImplementation: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
464
- upgradedAt: "2026-07-06T10:40:59.000Z",
465
- upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0xe1b1884a7e76c7fd569522545a04e49d5128d83d27b3892cda59f143b53b5281 (block 25472957)",
466
- reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
467
- executeTx: "0xe1b1884a7e76c7fd569522545a04e49d5128d83d27b3892cda59f143b53b5281",
468
- block: 25472957
462
+ previousImplementation: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
463
+ newImplementation: "0x00D7a47798E8ff169104b748131703B78f23390C",
464
+ upgradedAt: "2026-09-09T12:05:35.000Z",
465
+ upgradedBy: "AdminModule generic op 0xc0214ff38bb454864a34f6482dcd03570b3fd04171c84be2c20184afb1a2e161 (bsa-1-7-0 ceremony: proposed by seat 0x3030C0414B6d95aA9d5A57269E51a347fCBaB472 2026-09-08T11:35:23Z, 3/3 seat signatures, 24h timelock); executeGenericAdminOperation sent by 0xF20986F02420EF443AE9BE5092FB8A4975cF3aA6 in tx 0x77cd231966c546177bd8f1a07bd4e705afd5c2dfbd110b096836401679c0c69c (block 25939703)",
466
+ reason: "BTCSpendAuthorizer 1.6.0 -> 1.7.0: allow a full-vault sweep (targetAmount == satoshis); TargetMustLeaveFeeRoom -> TargetExceedsInputValue (PR #849; mainnet incidents 2026-09-04 and 2026-09-07). Impl deployed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f at nonce 430 from source 0a7c535a1 (contracts/src identical to main 99b335f77). Cross-ref onchain-auth/.safe-batches/mainnet-bsa-1-7-0-{propose,sign,execute}.json and docs/mainnet-bsa-1-7-0-ceremony-2026-09-08.md",
467
+ executeTx: "0x77cd231966c546177bd8f1a07bd4e705afd5c2dfbd110b096836401679c0c69c",
468
+ block: 25939703
469
469
  },
470
470
  PositionManager: {
471
471
  previousImplementation: "0x600Dc0D1C2c0F94882B6Bc09172f8650047Ad5B1",
@@ -568,7 +568,7 @@ var MAINNET_DEPLOYMENT = {
568
568
  ADMIN_MODULE: "0x0023B71B586795eE5fa4E83E384345C1EC8Bb946",
569
569
  ADMIN_MODULE_IMPL: "0xb4C5C7E051A1e672F014B25CB5D55B225Aa0D512",
570
570
  BTC_SPEND_AUTHORIZER: "0xfBbF108486fa07F1B06a811C5b66ed68DD9c508a",
571
- BTC_SPEND_AUTHORIZER_IMPL: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
571
+ BTC_SPEND_AUTHORIZER_IMPL: "0x00D7a47798E8ff169104b748131703B78f23390C",
572
572
  POSITION_MANAGER_VIEWS: "0x8B57954eA338c6A6bfF1457f71080e25baFF3937",
573
573
  POSITION_MANAGER_VIEWS_IMPL: "0xE68DC096A65Ecddbc6A9b5c6e5f878e2CE143Bfe",
574
574
  POSITION_MANAGER: "0x12A1836e56139ccaD8722B89519d84233efc224B",
@@ -426,13 +426,13 @@ var MAINNET_DEPLOYMENT = {
426
426
  block: 25472956
427
427
  },
428
428
  BTCSpendAuthorizer: {
429
- previousImplementation: "0xb003ed89F695E8819a85513fE17260b805B3c478",
430
- newImplementation: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
431
- upgradedAt: "2026-07-06T10:40:59.000Z",
432
- upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0xe1b1884a7e76c7fd569522545a04e49d5128d83d27b3892cda59f143b53b5281 (block 25472957)",
433
- reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
434
- executeTx: "0xe1b1884a7e76c7fd569522545a04e49d5128d83d27b3892cda59f143b53b5281",
435
- block: 25472957
429
+ previousImplementation: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
430
+ newImplementation: "0x00D7a47798E8ff169104b748131703B78f23390C",
431
+ upgradedAt: "2026-09-09T12:05:35.000Z",
432
+ upgradedBy: "AdminModule generic op 0xc0214ff38bb454864a34f6482dcd03570b3fd04171c84be2c20184afb1a2e161 (bsa-1-7-0 ceremony: proposed by seat 0x3030C0414B6d95aA9d5A57269E51a347fCBaB472 2026-09-08T11:35:23Z, 3/3 seat signatures, 24h timelock); executeGenericAdminOperation sent by 0xF20986F02420EF443AE9BE5092FB8A4975cF3aA6 in tx 0x77cd231966c546177bd8f1a07bd4e705afd5c2dfbd110b096836401679c0c69c (block 25939703)",
433
+ reason: "BTCSpendAuthorizer 1.6.0 -> 1.7.0: allow a full-vault sweep (targetAmount == satoshis); TargetMustLeaveFeeRoom -> TargetExceedsInputValue (PR #849; mainnet incidents 2026-09-04 and 2026-09-07). Impl deployed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f at nonce 430 from source 0a7c535a1 (contracts/src identical to main 99b335f77). Cross-ref onchain-auth/.safe-batches/mainnet-bsa-1-7-0-{propose,sign,execute}.json and docs/mainnet-bsa-1-7-0-ceremony-2026-09-08.md",
434
+ executeTx: "0x77cd231966c546177bd8f1a07bd4e705afd5c2dfbd110b096836401679c0c69c",
435
+ block: 25939703
436
436
  },
437
437
  PositionManager: {
438
438
  previousImplementation: "0x600Dc0D1C2c0F94882B6Bc09172f8650047Ad5B1",
@@ -535,7 +535,7 @@ var MAINNET_DEPLOYMENT = {
535
535
  ADMIN_MODULE: "0x0023B71B586795eE5fa4E83E384345C1EC8Bb946",
536
536
  ADMIN_MODULE_IMPL: "0xb4C5C7E051A1e672F014B25CB5D55B225Aa0D512",
537
537
  BTC_SPEND_AUTHORIZER: "0xfBbF108486fa07F1B06a811C5b66ed68DD9c508a",
538
- BTC_SPEND_AUTHORIZER_IMPL: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
538
+ BTC_SPEND_AUTHORIZER_IMPL: "0x00D7a47798E8ff169104b748131703B78f23390C",
539
539
  POSITION_MANAGER_VIEWS: "0x8B57954eA338c6A6bfF1457f71080e25baFF3937",
540
540
  POSITION_MANAGER_VIEWS_IMPL: "0xE68DC096A65Ecddbc6A9b5c6e5f878e2CE143Bfe",
541
541
  POSITION_MANAGER: "0x12A1836e56139ccaD8722B89519d84233efc224B",
@@ -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
@@ -484,13 +484,13 @@ var init_deployment_addresses = __esm({
484
484
  block: 25472956
485
485
  },
486
486
  BTCSpendAuthorizer: {
487
- previousImplementation: "0xb003ed89F695E8819a85513fE17260b805B3c478",
488
- newImplementation: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
489
- upgradedAt: "2026-07-06T10:40:59.000Z",
490
- upgradedBy: "AdminModule/Safe audit-batch upgradeToAndCall; executed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f in tx 0xe1b1884a7e76c7fd569522545a04e49d5128d83d27b3892cda59f143b53b5281 (block 25472957)",
491
- reason: "Reconciled from chain \u2014 mainnet audit rollout batch (cross-ref onchain-auth/.safe-batches/mainnet-audit-execute.json)",
492
- executeTx: "0xe1b1884a7e76c7fd569522545a04e49d5128d83d27b3892cda59f143b53b5281",
493
- block: 25472957
487
+ previousImplementation: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
488
+ newImplementation: "0x00D7a47798E8ff169104b748131703B78f23390C",
489
+ upgradedAt: "2026-09-09T12:05:35.000Z",
490
+ upgradedBy: "AdminModule generic op 0xc0214ff38bb454864a34f6482dcd03570b3fd04171c84be2c20184afb1a2e161 (bsa-1-7-0 ceremony: proposed by seat 0x3030C0414B6d95aA9d5A57269E51a347fCBaB472 2026-09-08T11:35:23Z, 3/3 seat signatures, 24h timelock); executeGenericAdminOperation sent by 0xF20986F02420EF443AE9BE5092FB8A4975cF3aA6 in tx 0x77cd231966c546177bd8f1a07bd4e705afd5c2dfbd110b096836401679c0c69c (block 25939703)",
491
+ reason: "BTCSpendAuthorizer 1.6.0 -> 1.7.0: allow a full-vault sweep (targetAmount == satoshis); TargetMustLeaveFeeRoom -> TargetExceedsInputValue (PR #849; mainnet incidents 2026-09-04 and 2026-09-07). Impl deployed by 0xEba7BfAeB35ee18408b424Ee0BB45AdDf776589f at nonce 430 from source 0a7c535a1 (contracts/src identical to main 99b335f77). Cross-ref onchain-auth/.safe-batches/mainnet-bsa-1-7-0-{propose,sign,execute}.json and docs/mainnet-bsa-1-7-0-ceremony-2026-09-08.md",
492
+ executeTx: "0x77cd231966c546177bd8f1a07bd4e705afd5c2dfbd110b096836401679c0c69c",
493
+ block: 25939703
494
494
  },
495
495
  PositionManager: {
496
496
  previousImplementation: "0x600Dc0D1C2c0F94882B6Bc09172f8650047Ad5B1",
@@ -593,7 +593,7 @@ var init_deployment_addresses = __esm({
593
593
  ADMIN_MODULE: "0x0023B71B586795eE5fa4E83E384345C1EC8Bb946",
594
594
  ADMIN_MODULE_IMPL: "0xb4C5C7E051A1e672F014B25CB5D55B225Aa0D512",
595
595
  BTC_SPEND_AUTHORIZER: "0xfBbF108486fa07F1B06a811C5b66ed68DD9c508a",
596
- BTC_SPEND_AUTHORIZER_IMPL: "0x40c9e8C46C65E0A57BB63da5368868A9E8Df81A5",
596
+ BTC_SPEND_AUTHORIZER_IMPL: "0x00D7a47798E8ff169104b748131703B78f23390C",
597
597
  POSITION_MANAGER_VIEWS: "0x8B57954eA338c6A6bfF1457f71080e25baFF3937",
598
598
  POSITION_MANAGER_VIEWS_IMPL: "0xE68DC096A65Ecddbc6A9b5c6e5f878e2CE143Bfe",
599
599
  POSITION_MANAGER: "0x12A1836e56139ccaD8722B89519d84233efc224B",
@@ -6886,9 +6886,27 @@ var LOGIN_TYPES_WITH_AUDIENCE = {
6886
6886
  { name: "audience", type: "string" }
6887
6887
  ]
6888
6888
  };
6889
- async function buildSignedLoginPayload(signer, chainId, audience, reuse) {
6889
+ var LOGIN_TYPES_WITH_STATEMENT = {
6890
+ DiamondHands: [
6891
+ { name: "statement", type: "string" },
6892
+ { name: "address", type: "address" },
6893
+ { name: "issuedAt", type: "uint256" },
6894
+ { name: "nonce", type: "bytes32" },
6895
+ { name: "audience", type: "string" }
6896
+ ]
6897
+ };
6898
+ var LOGIN_STATEMENT_TEMPLATE = "{host} is verifying you own this wallet. Signing proves control of the address so we can show your loans and positions. It authorizes no transaction, moves no funds, and costs no gas.";
6899
+ function canonicalLoginStatement(audience) {
6900
+ const host = /^https?:\/\/([^/?#]+)/i.exec(audience)?.[1];
6901
+ if (!host)
6902
+ return null;
6903
+ return LOGIN_STATEMENT_TEMPLATE.replace("{host}", host);
6904
+ }
6905
+ async function buildSignedLoginPayload(signer, chainId, audience, reuse, withStatement = false) {
6890
6906
  const address = await signer.getAddress();
6907
+ const statement = withStatement && audience ? canonicalLoginStatement(audience) : null;
6891
6908
  const message = {
6909
+ ...statement ? { statement } : {},
6892
6910
  address,
6893
6911
  issuedAt: reuse?.issuedAt ?? Math.floor(Date.now() / 1e3),
6894
6912
  nonce: reuse?.nonce ?? (0, import_ethers4.hexlify)((0, import_ethers4.randomBytes)(32)),
@@ -6896,7 +6914,7 @@ async function buildSignedLoginPayload(signer, chainId, audience, reuse) {
6896
6914
  };
6897
6915
  const signature = await signer.signTypedData(
6898
6916
  buildLoginDomain(chainId),
6899
- audience ? LOGIN_TYPES_WITH_AUDIENCE : LOGIN_TYPES,
6917
+ statement ? LOGIN_TYPES_WITH_STATEMENT : audience ? LOGIN_TYPES_WITH_AUDIENCE : LOGIN_TYPES,
6900
6918
  message
6901
6919
  );
6902
6920
  return { chainId, message, signature };
@@ -7069,6 +7087,8 @@ var ServerSession = class {
7069
7087
  signer;
7070
7088
  serviceEndpoint;
7071
7089
  loginAudience;
7090
+ /** Whether this session's login carries the human-readable statement. */
7091
+ withStatement;
7072
7092
  chainId;
7073
7093
  store;
7074
7094
  onSignaturePrompt;
@@ -7110,6 +7130,13 @@ var ServerSession = class {
7110
7130
  /\/+$/,
7111
7131
  ""
7112
7132
  );
7133
+ const explicitAudience = typeof opts.loginAudience === "string" && opts.loginAudience.length > 0;
7134
+ this.withStatement = opts.withStatement === true && explicitAudience;
7135
+ if (opts.withStatement === true && !explicitAudience) {
7136
+ console.warn(
7137
+ "[dh-sdk] withStatement ignored: it requires an explicit loginAudience, since the servers rebuild the expected sentence from the audience host"
7138
+ );
7139
+ }
7113
7140
  this.chainId = opts.chainId;
7114
7141
  this.store = opts.persistSession === false ? null : opts.sessionStore ?? createDefaultSessionStore();
7115
7142
  this.onSignaturePrompt = opts.onSignaturePrompt;
@@ -7363,12 +7390,16 @@ var ServerSession = class {
7363
7390
  this.signer,
7364
7391
  this.chainId,
7365
7392
  this.loginAudience,
7366
- reuse
7393
+ reuse,
7394
+ reuse ? reuse.withStatement === true : this.withStatement
7367
7395
  );
7368
7396
  if (storeKey && !reuse) {
7369
7397
  store.savePending?.(storeKey, {
7370
7398
  issuedAt: payload.message.issuedAt,
7371
- nonce: payload.message.nonce
7399
+ nonce: payload.message.nonce,
7400
+ // Recorded so a resume reproduces this digest even if the
7401
+ // capability flag changes before the owners finish signing.
7402
+ withStatement: this.withStatement
7372
7403
  });
7373
7404
  }
7374
7405
  session = await this.login(payload);
@@ -10434,6 +10465,101 @@ var LoanStatus = /* @__PURE__ */ ((LoanStatus2) => {
10434
10465
 
10435
10466
  // src/modules/loan/loan-query.module.ts
10436
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
10437
10563
  var POSITION_CORE_ABI = [
10438
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))"
10439
10565
  ];
@@ -10826,6 +10952,43 @@ var LoanQuery = class {
10826
10952
  async getLoansByBorrower(borrower, pagination, orderBy, orderDirection) {
10827
10953
  return this.getLoans({ borrower, orderBy, orderDirection }, pagination);
10828
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
+ }
10829
10992
  /**
10830
10993
  * Get active loans (status = ACTIVE)
10831
10994
  *
@@ -12360,10 +12523,36 @@ var GraphClient = class {
12360
12523
  }
12361
12524
  };
12362
12525
 
12363
- // src/constants/chunks/sdk-limits.ts
12364
- var THE_GRAPH_MAX_BATCH_SIZE = 1e3;
12365
-
12366
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
+ `;
12367
12556
  function normalizePkpId2(pkpId) {
12368
12557
  const raw = pkpId.startsWith("0x") ? pkpId.slice(2) : pkpId;
12369
12558
  if (raw.length === 64 && raw.startsWith("000000000000000000000000") && raw !== "0".repeat(64)) {
@@ -12681,35 +12870,7 @@ var DiamondHandsGraph = class {
12681
12870
  * Get user positions from subgraph
12682
12871
  */
12683
12872
  async getUserPositionsOnly(userAddress, first, skip, orderBy, orderDirection) {
12684
- const query = `
12685
- query GetUserPositions($borrower: Bytes!, $first: Int, $skip: Int, $orderBy: Position_orderBy, $orderDirection: OrderDirection) {
12686
- user(id: $borrower) {
12687
- id
12688
- positions(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection) {
12689
- id
12690
- pkpId
12691
- borrower {
12692
- createdAt
12693
- id
12694
- }
12695
- # btcAmount removed - balance is now queried on-chain via lit-actions with signature validation
12696
- ucdMinted
12697
- ucdPaid
12698
- ucdDebt
12699
- collateralRatio
12700
- requestedCollateralRatio
12701
- selectedTerm
12702
- status
12703
- createdAt
12704
- createdAtBlock
12705
- lastUpdated
12706
- originalTerm
12707
- remainingDebt
12708
- expiryAt
12709
- }
12710
- }
12711
- }
12712
- `;
12873
+ const query = GET_USER_POSITIONS_DOCUMENT;
12713
12874
  const variables = {
12714
12875
  borrower: userAddress.toLowerCase(),
12715
12876
  first: first || void 0,
@@ -12727,6 +12888,45 @@ var DiamondHandsGraph = class {
12727
12888
  }
12728
12889
  return positions;
12729
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
+ }
12730
12930
  /**
12731
12931
  * Get user positions, plus an accurate total count.
12732
12932
  *
@@ -19709,6 +19909,9 @@ var DiamondHandsSDK = class _DiamondHandsSDK {
19709
19909
  signer: config.authSigner ?? config.contractSigner,
19710
19910
  serviceEndpoint: config.serviceEndpoint,
19711
19911
  loginAudience: config.loginAudience,
19912
+ // Only meaningful alongside an audience; the servers derive the expected
19913
+ // sentence from its host. Defaults off so Node callers are untouched.
19914
+ withStatement: config.loginStatement === true,
19712
19915
  chainId: config.chainId,
19713
19916
  persistSession: config.sessionPersistence?.enabled,
19714
19917
  sessionStore: config.sessionPersistence?.store,
@@ -25674,6 +25877,18 @@ Message: ${causeMessage}${quantumContext}${mintDebtDiagnostics}`
25674
25877
  async getLoansByBorrower(borrower, pagination, orderBy, orderDirection) {
25675
25878
  return this.loanQuery.getLoansByBorrower(borrower, pagination, orderBy, orderDirection);
25676
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
+ }
25677
25892
  /**
25678
25893
  * Get all active loans
25679
25894
  *