@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.
- package/browser/dist/browser.js +1 -1
- package/dist/graphs/diamond-hands.d.ts +14 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +213 -32
- package/dist/index.mjs +214 -33
- package/dist/interfaces/chunks/loan-operations.i.d.ts +34 -0
- package/dist/modules/diamond-hands-sdk.d.ts +10 -0
- package/dist/modules/loan/loan-query.module.d.ts +12 -1
- package/dist/utils/borrower-ucd-debt-summary.d.ts +44 -0
- package/package.json +2 -2
|
@@ -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 > 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
|
*
|