@gvnrdao/dh-sdk 0.0.338 → 0.0.340

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.
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Concurrency Limiter Utility
3
+ * Limits the number of concurrent operations to prevent overwhelming external services
4
+ */
5
+ /**
6
+ * Concurrency limiter configuration
7
+ */
8
+ export interface ConcurrencyLimiterConfig {
9
+ concurrency?: number;
10
+ timeout?: number;
11
+ debug?: boolean;
12
+ }
13
+ /**
14
+ * Concurrency limiter for controlling parallel operations
15
+ */
16
+ export declare class ConcurrencyLimiter {
17
+ private concurrency;
18
+ private timeout;
19
+ private debug;
20
+ private running;
21
+ private queue;
22
+ constructor(config?: ConcurrencyLimiterConfig);
23
+ /**
24
+ * Execute a function with concurrency limiting
25
+ */
26
+ execute<T>(fn: () => Promise<T>): Promise<T>;
27
+ /**
28
+ * Process the queue of pending operations
29
+ */
30
+ private processQueue;
31
+ /**
32
+ * Run function with timeout
33
+ */
34
+ private runWithTimeout;
35
+ /**
36
+ * Get current status
37
+ */
38
+ getStatus(): {
39
+ running: number;
40
+ queued: number;
41
+ concurrency: number;
42
+ };
43
+ /**
44
+ * Wait for all operations to complete
45
+ */
46
+ waitForAll(): Promise<void>;
47
+ /**
48
+ * Clear queue and reject pending operations
49
+ */
50
+ clear(): void;
51
+ }
52
+ /**
53
+ * Create a concurrency limiter with default settings
54
+ */
55
+ export declare function createConcurrencyLimiter(config?: ConcurrencyLimiterConfig): ConcurrencyLimiter;
56
+ /**
57
+ * Execute multiple functions with concurrency limiting
58
+ */
59
+ export declare function executeWithConcurrencyLimit<T>(functions: Array<() => Promise<T>>, config?: ConcurrencyLimiterConfig): Promise<T[]>;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Chain names the Lit Actions understand, by EVM chainId.
3
+ *
4
+ * The name selects the action's policy — address pinning, allowed RPC hosts, whether a dev
5
+ * Bitcoin provider is honoured — so it is looked up, never guessed. Sepolia uses regtest Bitcoin
6
+ * (the private dh-btc-faucet), the same convention as lit-ops-server.
7
+ */
8
+ export declare const LIT_ACTION_CHAIN_NAMES: Readonly<Record<number, string>>;
9
+ /**
10
+ * The Lit Action chain name for `chainId`. Throws on an unsupported chain rather than falling
11
+ * back: defaulting to "sepolia" ran Hardhat under Sepolia policy, and signed for any unknown
12
+ * chain under Sepolia's name.
13
+ */
14
+ export declare function litActionChainNameForChainId(chainId: number | bigint): string;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Loan Helper Utilities
3
+ * Extracted common patterns for loan data processing and enrichment
4
+ */
5
+ import type { LoanData, LoanDataDetail, BitcoinAddresses } from '../interfaces';
6
+ import type { BitcoinProvider } from './bitcoin-provider.utils';
7
+ import type { ConcurrencyLimiter } from './concurrency-limiter.utils';
8
+ /**
9
+ * Derive Bitcoin addresses for a loan if needed
10
+ * @param loan Loan data that may need address derivation
11
+ * @param bitcoinNetwork Target Bitcoin network ("mainnet" | "testnet" | "regtest")
12
+ * @param getBitcoinAddressesFromPkp Function to derive addresses from PKP
13
+ * @returns Updated loan with derived addresses
14
+ */
15
+ export declare function deriveAddressesIfNeeded(loan: LoanData | LoanDataDetail, bitcoinNetwork: "mainnet" | "testnet" | "regtest" | undefined, getBitcoinAddressesFromPkp: (pkpPublicKey: string) => Promise<BitcoinAddresses>): Promise<LoanData | LoanDataDetail>;
16
+ /**
17
+ * Enrich loans with Bitcoin balance information
18
+ * Handles the common pattern of fetching and attaching balance data
19
+ * @param loans Array of loans to enrich
20
+ * @param bitcoinProvider Bitcoin provider instance
21
+ * @param concurrencyLimiter Optional concurrency limiter for controlled parallel execution
22
+ * @param getBalanceWithCache Function to get balance with caching
23
+ * @returns Enriched loans with balance information
24
+ */
25
+ export declare function enrichWithBalance(loans: (LoanData | LoanDataDetail)[], bitcoinProvider: BitcoinProvider | null, concurrencyLimiter: ConcurrencyLimiter | null, getBalanceWithCache: (vaultAddress: string) => Promise<any>): Promise<(LoanData | LoanDataDetail)[]>;
26
+ /**
27
+ * Normalize loan data transformation
28
+ * Single mapper to avoid subtle divergence across methods
29
+ * @param rawLoan Raw loan data from contract or graph
30
+ * @param transformType Type of transformation to apply
31
+ * @returns Normalized loan data
32
+ */
33
+ export declare function normalizeLoanTransform(rawLoan: any, transformType?: 'contract' | 'graph'): LoanData | LoanDataDetail;
34
+ /**
35
+ * Extract vault address from loan collateral
36
+ * Handles both string and BitcoinAddresses object types
37
+ * @param loan Loan data
38
+ * @param preferredNetwork Preferred network to use if multiple addresses available
39
+ * @returns Vault address string
40
+ */
41
+ export declare function extractVaultAddress(loan: LoanData | LoanDataDetail, preferredNetwork?: 'mainnet' | 'testnet' | 'regtest'): string;
42
+ /**
43
+ * Select Bitcoin address for the given network.
44
+ *
45
+ * @param addresses Bitcoin addresses object (mainnet/testnet/regtest variants)
46
+ * @param bitcoinNetwork Target network ("mainnet" | "testnet" | "regtest" | undefined → mainnet)
47
+ */
48
+ export declare function selectBitcoinAddress(addresses: BitcoinAddresses, bitcoinNetwork: "mainnet" | "testnet" | "regtest" | undefined): string;
49
+ /**
50
+ * Calculate the expected mint fee for a UCD mint, including the Scenario B tail fee.
51
+ *
52
+ * Scenario B applies when:
53
+ * - The loan has been renewed (previousExpiryAt > 0), AND
54
+ * - The mint occurs before the original term expiry (mintTimestamp < previousExpiryAt)
55
+ *
56
+ * In that case, an additional pro-rata tail fee is charged for the remaining time
57
+ * in the original term, on top of the standard origination fee for the renewal term.
58
+ *
59
+ * Formula:
60
+ * standardFee = originationFeeBps * mintAmount / 10000
61
+ * tailFee = (tailSeconds / originalTermSeconds) * originationFeeBps * mintAmount / 10000
62
+ * totalFee = standardFee + tailFee (Scenario B only)
63
+ *
64
+ * This function is purely local (no RPC calls) and mirrors ProtocolConstants logic:
65
+ * 12 months = 365 days; other terms = 30 days/month.
66
+ *
67
+ * @param mintAmount - Amount of UCD to mint (in wei, bigint)
68
+ * @param mintTimestamp - Unix timestamp of the mint (seconds)
69
+ * @param originationFeeBps - Origination fee in basis points (e.g. 50 = 0.5%)
70
+ * @param expiryAt - Current loan expiry timestamp after renewal (seconds)
71
+ * @param previousExpiryAt - Expiry before last renewal; 0 if never renewed (seconds)
72
+ * @param selectedTerm - Current term segment in months (e.g. 12)
73
+ * @param totalTerm - Total accumulated loan months (e.g. 24 after one renewal); 0 before first renewal
74
+ */
75
+ export declare function calculateMintFee(params: {
76
+ mintAmount: bigint;
77
+ mintTimestamp: number;
78
+ originationFeeBps: number;
79
+ expiryAt: number;
80
+ previousExpiryAt: number;
81
+ selectedTerm: number;
82
+ totalTerm: number;
83
+ }): bigint;
84
+ /**
85
+ * Base origination fee on a principal (wei), `floor(amount * bps / 10000)` —
86
+ * the same integer arithmetic the Lit validator and TermManager use. Excludes
87
+ * the post-renewal tail fee, so it is a LOWER bound on the fee the validator
88
+ * will charge; a pre-check built on it can only be more permissive, never
89
+ * stricter, than the validator's own fee-inclusive check.
90
+ */
91
+ export declare function baseMintFeeWei(mintAmountWei: bigint, originationFeeBps: number): bigint;
92
+ /**
93
+ * The loan cap is fee-INCLUSIVE on-chain: `UCDController._enforceLoanPathPerMintCap`
94
+ * rejects when `currentDebt + mintAmount + mintFee > maximumLoanValueUcd`
95
+ * (audit V12 #62230). Mainnet 2026-09-15: a 98 UCD mint + 2.94 fee against a
96
+ * 100 UCD cap passed every principal-only pre-check, paid for the Lit round-trip
97
+ * and reverted `MintGuardFailed(1)`. Mirrors the contract exactly — equal to the
98
+ * cap is allowed.
99
+ */
100
+ export declare function debtAfterMintExceedsLoanCap(params: {
101
+ currentDebtWei: bigint;
102
+ mintAmountWei: bigint;
103
+ mintFeeWei: bigint;
104
+ maxLoanWei: bigint;
105
+ }): boolean;
106
+ /**
107
+ * Largest principal (wei) that still fits under the cap once its base fee is
108
+ * added: `floor((maxLoan - currentDebt) * 10000 / (10000 + bps))`; zero when
109
+ * the position is already at or above the cap.
110
+ */
111
+ export declare function maxPrincipalWithinLoanCap(params: {
112
+ maxLoanWei: bigint;
113
+ currentDebtWei: bigint;
114
+ originationFeeBps: number;
115
+ }): bigint;
@@ -5,5 +5,9 @@
5
5
  * `instanceof` checks in consumers cannot split across two module realms.
6
6
  */
7
7
  export declare class TxValidationError extends Error {
8
- constructor(msg: string);
8
+ /**
9
+ * @param subject What is being refused. Defaults to the server-returned transaction the
10
+ * validator exists for; client-built flows (the PSM exchange) name themselves instead.
11
+ */
12
+ constructor(msg: string, subject?: string);
9
13
  }
@@ -7,3 +7,4 @@ export { DELEGATED_OPS, BROADCAST_PATH, type DelegatedOp, type DelegatedOpSpec }
7
7
  export { REQUEST_TIMEOUT_MS, LitOpsHttpError, LitOpsTransportError, fetchWithTimeout, postJson, asEnvelope, authorizeWithServer, broadcastSignedTx, type FetchOptions, type ExecuteEnvelope, type BroadcastEnvelope, type ExecuteOutcome, type BroadcastOutcome, } from "./lit-ops-client";
8
8
  export { signAndBroadcast, type SignGuardSigner } from "./sign-and-broadcast";
9
9
  export { computeExtendFeeUpperBound, type ExtendFeeBound } from "./extend-fee-bound";
10
+ export { buildPsmApproveTx, buildPsmSwapTx, buildPsmRedeemTx, planPsmExchange, executePsmPlan, psmExchangeReadsFromProvider, type PsmUnsignedTx, type PsmLeg, type PsmExchangeAddresses, type PsmExchangeReads, type PsmExchangePlan, } from "./psm-exchange";
@@ -0,0 +1,122 @@
1
+ import { ethers } from "ethers";
2
+ import { type ExpectedAction, type ValidationContext } from "./tx-validator";
3
+ /**
4
+ * The PSM exchange pair — stablecoin → UCD (`swap`) and UCD → stablecoin (`redeem`) — built and
5
+ * validated in ONE place, for every client.
6
+ *
7
+ * Exact-amount approvals only. The SDK's old `psmSwap` / `psmRedeem` approved `MaxUint256`, so a
8
+ * single swap left an unlimited allowance behind; the MCP refused to use them and built its own
9
+ * exact-amount flow. This module is that flow, lifted into the SDK.
10
+ *
11
+ * Deliberately split in two:
12
+ * - `planPsmExchange` builds every leg and runs `validateUnsignedTx` on each before returning;
13
+ * - `executePsmPlan` sends the legs through an ethers `Signer` — the path a browser wallet can
14
+ * take (`sendTransaction`). Clients with their own signing pipeline (the MCP's keystore and
15
+ * approval cards) take the validated legs from the plan and submit them their own way.
16
+ */
17
+ /** An unsigned tx exactly as `validateUnsignedTx` consumes it. */
18
+ export interface PsmUnsignedTx {
19
+ to: string;
20
+ data: string;
21
+ value: "0x0";
22
+ chainId: number;
23
+ }
24
+ export interface PsmLeg {
25
+ /** What this leg is — for progress reporting, cards and error messages. */
26
+ step: "reset-approve" | "approve" | "swap" | "redeem";
27
+ tx: PsmUnsignedTx;
28
+ /** The sign-guard expectation this leg was validated against. Re-check any populated form with it. */
29
+ expected: ExpectedAction;
30
+ }
31
+ export interface PsmExchangeAddresses {
32
+ /** SimplePSMV2 proxy. */
33
+ psm: string;
34
+ /** The stablecoin swapped in (swap) or received (redeem). */
35
+ stablecoin: string;
36
+ /** Required for redeem: UCD is the token approved. */
37
+ ucdToken?: string;
38
+ /** Required for redeem: UCDToken.burn spends the allowance as the UCDController (audit M-4). */
39
+ ucdController?: string;
40
+ }
41
+ /** The two on-chain facts the plan depends on. Injected so the planner stays pure. */
42
+ export interface PsmExchangeReads {
43
+ isStablecoinSupported(stablecoin: string): Promise<boolean>;
44
+ allowance(token: string, owner: string, spender: string): Promise<bigint>;
45
+ }
46
+ export interface PsmExchangePlan {
47
+ direction: "swap" | "redeem";
48
+ owner: string;
49
+ /** The token approved: the stablecoin for swap, UCD for redeem. */
50
+ token: string;
51
+ /** Its spender: the PSM for swap, the UCDController for redeem. */
52
+ spender: string;
53
+ amountIn: bigint;
54
+ minOut: bigint;
55
+ /** The allowance read when the plan was made. */
56
+ allowanceBefore: bigint;
57
+ /** Zero, one (approve) or two (reset to zero, then approve) legs — in send order. */
58
+ approvals: PsmLeg[];
59
+ exec: PsmLeg;
60
+ }
61
+ export declare function buildPsmApproveTx(p: {
62
+ token: string;
63
+ spender: string;
64
+ amount: bigint;
65
+ chainId: number;
66
+ }): PsmUnsignedTx;
67
+ export declare function buildPsmSwapTx(p: {
68
+ psm: string;
69
+ stablecoin: string;
70
+ amountIn: bigint;
71
+ minOut: bigint;
72
+ chainId: number;
73
+ }): PsmUnsignedTx;
74
+ export declare function buildPsmRedeemTx(p: {
75
+ psm: string;
76
+ stablecoin: string;
77
+ ucdAmount: bigint;
78
+ minOut: bigint;
79
+ chainId: number;
80
+ }): PsmUnsignedTx;
81
+ /** Provider-backed reads for the planner. */
82
+ export declare function psmExchangeReadsFromProvider(provider: ethers.Provider, psmAddress: string): PsmExchangeReads;
83
+ /**
84
+ * Build and validate every leg of one PSM exchange. Throws before anything is signed when:
85
+ * the amount or the minimum-out floor is zero (the contract refuses a zero floor); an address
86
+ * is missing — a redeem refuses rather than guess its spender; or the stablecoin is not
87
+ * supported by the PSM ON-CHAIN (an index or a config can lag a governance removal).
88
+ *
89
+ * Approvals, against the allowance read now:
90
+ * - `allowance >= amountIn` → no approve leg;
91
+ * - `allowance == 0` → approve exactly `amountIn`;
92
+ * - otherwise → approve 0, then approve exactly `amountIn`. Some tokens (USDT)
93
+ * revert a non-zero → non-zero approve, and a failed exchange can leave exactly this partial
94
+ * residue. Resetting first is correct for every ERC-20, so it is not token-sniffed.
95
+ */
96
+ export declare function planPsmExchange(params: {
97
+ direction: "swap" | "redeem";
98
+ owner: string;
99
+ addresses: PsmExchangeAddresses;
100
+ amountIn: bigint;
101
+ minOut: bigint;
102
+ vctx: ValidationContext;
103
+ reads: PsmExchangeReads;
104
+ }): Promise<PsmExchangePlan>;
105
+ /**
106
+ * Send a plan's legs through an ethers `Signer`, in order, each confirmed before the next.
107
+ *
108
+ * Each leg's request — exactly `{to, data, value: 0, chainId}` — is validated again immediately
109
+ * before it is handed over. Nonce, gas and fees are NOT populated here: the signer populates them
110
+ * when the leg's turn comes (a swap cannot be gas-estimated until its approve is mined), so a
111
+ * browser wallet shows the user its own fee and a Safe signer applies its own nonce, exactly as
112
+ * before. Blind signers that need the fee ceiling on the signed form (CLI, MCP) take the
113
+ * validated legs from the plan into their own pipeline instead.
114
+ *
115
+ * A leg that reverts or fails throws, naming the step; when an approve already landed, the error
116
+ * says so, because an exact allowance then stands.
117
+ */
118
+ export declare function executePsmPlan(plan: PsmExchangePlan, signer: ethers.Signer, vctx: ValidationContext): Promise<{
119
+ hash: string;
120
+ blockNumber: number;
121
+ approvalHashes: string[];
122
+ }>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gvnrdao/dh-sdk",
3
- "version": "0.0.338",
3
+ "version": "0.0.340",
4
4
  "description": "TypeScript SDK for Diamond Hands Protocol",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -94,7 +94,7 @@
94
94
  "sideEffects": false,
95
95
  "dependencies": {
96
96
  "@gvnrdao/dh-lit-actions": "^0.0.322",
97
- "@gvnrdao/dh-lit-ops": "^0.0.315",
97
+ "@gvnrdao/dh-lit-ops": "^0.0.316",
98
98
  "@noble/hashes": "^1.5.0",
99
99
  "axios": "^1.17.0",
100
100
  "bech32": "^2.0.0",
@@ -103,8 +103,7 @@
103
103
  "bs58check": "^3.0.1",
104
104
  "crypto-js": "^4.2.0",
105
105
  "dotenv": "^17.4.2",
106
- "ethers": "6.16.0",
107
- "valibot": "^1.1.0"
106
+ "ethers": "6.16.0"
108
107
  },
109
108
  "devDependencies": {
110
109
  "@babel/preset-env": "7.29.7",