@gvnrdao/dh-sdk 0.0.299 → 0.0.301

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.
@@ -4,27 +4,24 @@
4
4
  */
5
5
  import { type Provider, type Signer, type TransactionResponse } from "ethers";
6
6
  /**
7
- * Cap on mintUCD's gasLimit. mintUCD dynamically sizes gasLimit via
8
- * estimateContractCallGasWithMargin() (real eth_estimateGas + 25% margin) on
9
- * mainnet, so this value is NOT normally what gets submitted -- it only
10
- * becomes the literal gasLimit if (a) the estimate comes back anomalously
11
- * high, or (b) eth_estimateGas itself fails (some RPCs return bare 0x even
12
- * for valid txs), in which case after a static call proves the tx valid —
13
- * this raw value is used as the gasLimit on any chain.
14
- * Previously 10,000,000 with a comment claiming "mintUCD + LIT validation
15
- * can exceed 7M gas" -- no evidence of that was found across 16 real mint
16
- * transactions checked (1 mainnet, 15 recent Sepolia, 2026-07-02): the
17
- * dynamically-computed gasLimit actually submitted ranged 388,620-886,988.
18
- * Lowered to 2,000,000 (~2.25x headroom over the highest real value seen)
19
- * for consistency with MAKE_PAYMENT_GAS_CEILING/EXTEND_POSITION_GAS_CEILING's
20
- * ~1.5-2x-headroom convention -- a smaller margin than those because this
21
- * ceiling is a rarely-hit fallback/cap, not the everyday operating value, so
22
- * a little extra buffer for cold-storage variance is cheap insurance.
7
+ * Fixed-gas ceiling for mintUCD's broadcast. Since the gold-standard refactor
8
+ * (2026-07-08) this IS the everyday submitted gasLimit: mintUCD no longer runs
9
+ * eth_estimateGas (its round-trip latency re-introduced the quantum-boundary
10
+ * race the submission gate exists to prevent), matching makePayment /
11
+ * withdrawBTC / extendPosition. Grounded in 16 real mint transactions
12
+ * (1 mainnet, 15 recent Sepolia, 2026-07-02): the estimate-derived gasLimit
13
+ * actually submitted ranged 388,620-886,988, and those limits all succeeded
14
+ * so 886,988 is a KNOWN-SUFFICIENT limit, not a bare gasUsed figure.
15
+ * 1,300,000 gives ~1.47x headroom over it, matching MAKE_PAYMENT_GAS_CEILING's
16
+ * ~1.5x convention. Deliberately NOT higher: wallets (MetaMask) reject a tx
17
+ * when balance < gasLimit × maxFeePerGas, so an oversized fixed ceiling
18
+ * (previously 2M as a rarely-hit cap) would block low-ETH users from an
19
+ * affordable mint.
23
20
  */
24
21
  export declare const MINT_UCD_GAS_CEILING: bigint;
25
22
  /**
26
- * Fixed-gas ceiling for the withdrawBTC raw-tx fallback (used when estimateGas
27
- * is unavailable on the RPC). The on-chain flow now spans PositionManager →
23
+ * Fixed-gas ceiling for withdrawBTC's broadcast (the everyday submitted
24
+ * gasLimit no estimateGas runs on this path). The on-chain flow now spans PositionManager →
28
25
  * CollateralManager → LoanOperationsManager → BTCSpendAuthorizer plus the audit
29
26
  * #3 Chainlink feed-staleness gate and the BitcoinWithdrawalAddressRegistry
30
27
  * allowlist STATICCALL; observed end-to-end cost is ~1.0–1.3M gas, so the prior
@@ -89,12 +86,6 @@ export declare const LIQUIDATION_COMMIT_GAS_CEILING: bigint;
89
86
  * provisional until re-measured against a real liquidation.
90
87
  */
91
88
  export declare const LIQUIDATION_REVEAL_GAS_CEILING: bigint;
92
- /** Headroom on eth_estimateGas (25%). */
93
- export declare const MINT_UCD_ESTIMATE_MARGIN_BPS = 2500;
94
- /**
95
- * Returns gas limit with margin, capped, or null if estimateGas reverts/fails.
96
- */
97
- export declare function estimateContractCallGasWithMargin(provider: Provider, from: string, to: string, data: string, marginBps: number, ceiling: bigint): Promise<bigint | null>;
98
89
  export declare function resolveEip1559FeeFields(provider: Provider): Promise<{
99
90
  maxFeePerGas: bigint;
100
91
  maxPriorityFeePerGas: bigint;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared decoder for custom-error reverts surfaced by quantum-signed operations
3
+ * (mintUCD / makePayment / withdrawBTC / extendPosition / liquidation).
4
+ *
5
+ * Consolidates what used to be three hand-rolled selector maps + ad-hoc error-data
6
+ * extraction scattered across diamond-hands-sdk.ts. The extraction probes EVERY
7
+ * nesting ethers v6 uses across provider types — notably `info.error.data`, which
8
+ * `BrowserProvider` (MetaMask) uses and the older per-site extractors missed, so a
9
+ * browser-provider DeadZoneViolation could slip through undetected and wrongly throw.
10
+ */
11
+ /** `DeadZoneViolation()` selector — the stale-latest-block artifact we tolerate. */
12
+ export declare const DEAD_ZONE_VIOLATION_SELECTOR = "0xbe4b82c1";
13
+ /** Solidity `Error(string)` (require/revert with a message). */
14
+ export declare const ERROR_STRING_SELECTOR = "0x08c379a0";
15
+ /** Solidity `Panic(uint256)` (assert failures, overflow, div-by-zero, …). */
16
+ export declare const PANIC_SELECTOR = "0x4e487b71";
17
+ /**
18
+ * Union of the custom-error selectors the mint/payment/quantum paths decode. Keep in
19
+ * sync with OperationAuthorizationRegistry / PositionManager / LoanOperationsManager.
20
+ */
21
+ export declare const QUANTUM_REVERT_NAMES: Record<string, string>;
22
+ export interface DecodedRevert {
23
+ /** Raw revert data (`0x…`) if any provider nesting carried it, else null. */
24
+ data: string | null;
25
+ /** 4-byte custom-error selector (`0x…`) if the data is a valid custom error. */
26
+ selector: string | null;
27
+ /** Human-readable error name if the selector is known, else null. */
28
+ name: string | null;
29
+ }
30
+ /**
31
+ * Extract the revert data / custom-error selector / known name from an ethers error,
32
+ * probing every provider nesting (JsonRpc, Browser/MetaMask, wrapped `error.error`).
33
+ *
34
+ * Besides the custom-error selector map, decodes the two standard Solidity revert
35
+ * shapes so require-message reverts stay actionable when call sites rely solely on
36
+ * this decoder: `Error(string)` yields `name = 'Error("<message>")'` and
37
+ * `Panic(uint256)` yields `name = "Panic(0x<code>)"`.
38
+ */
39
+ export declare function decodeQuantumRevert(e: any): DecodedRevert;
40
+ /** True iff the error decodes to `DeadZoneViolation()` from any provider nesting. */
41
+ export declare function isDeadZoneViolation(e: any): boolean;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Shared submission-time safety for quantum-signed broadcasts
3
+ * (makePayment / mintUCD / withdrawBTC / extendPosition).
4
+ *
5
+ * One helper owns the dead-zone gate AND the pre-send simulate/re-simulate loop
6
+ * that used to exist as three subtly different inline copies in
7
+ * diamond-hands-sdk.ts. Callers encode calldata, call
8
+ * `assertSafeQuantumSubmission`, then broadcast ONCE with a fixed gas ceiling
9
+ * (`sendEip1559Transaction`) — never `estimateGas`, whose latency re-introduces
10
+ * the boundary race the gate exists to prevent, and never a re-broadcast.
11
+ *
12
+ * See docs/sdk-quantum-goldstandard-refactor-plan-2026-07-08.md.
13
+ */
14
+ /** Default cap on dead-zone-triggered re-simulations (mirrors old makePayment). */
15
+ export declare const MAX_DEADZONE_RESIMULATIONS = 3;
16
+ /**
17
+ * Thrown when the pre-send simulation proves the transaction would revert.
18
+ * Carries everything a caller needs to enrich the failure without re-probing
19
+ * the provider error itself:
20
+ *
21
+ * - `errorName` — decoded name, e.g. `"QuantumExpired()"` or `Error("msg")`
22
+ * (named to avoid shadowing `Error.name`).
23
+ * - `selector` — 4-byte selector, e.g. `"0x131d9a21"`.
24
+ * - `data` — FULL revert data (mintUCD decodes
25
+ * `DebtUpdateVerificationFailedDetailed` args from it).
26
+ * - `cause` — the original provider error (`reason` / `shortMessage`
27
+ * fallbacks, empty-data detection → `diagnoseMintUCD`).
28
+ *
29
+ * Only thrown for DECODABLE reverts (revert data present). Transport-level
30
+ * `eth_call` failures never raise this — see `assertSafeQuantumSubmission`.
31
+ */
32
+ export declare class QuantumRevertError extends Error {
33
+ readonly errorName: string | null;
34
+ readonly selector: string | null;
35
+ readonly data: string | null;
36
+ readonly cause: unknown;
37
+ constructor(errorName: string | null, selector: string | null, data: string | null, cause: unknown);
38
+ }
39
+ export interface SafeQuantumSubmissionOpts {
40
+ /** Provider used for the `eth_call` simulation (only `.call` is needed). */
41
+ provider: {
42
+ call(tx: {
43
+ to: string;
44
+ from: string;
45
+ data: string;
46
+ }): Promise<string>;
47
+ };
48
+ /** Target contract address. */
49
+ to: string;
50
+ /** Sender address — `eth_call` must run as the real sender (auth checks). */
51
+ from: string;
52
+ /** Encoded calldata; broadcast MUST send these exact bytes. */
53
+ data: string;
54
+ /** The quantum timestamp embedded in the LIT-signed payload. */
55
+ quantumTimestamp: number;
56
+ /** Cap on dead-zone re-simulations (default {@link MAX_DEADZONE_RESIMULATIONS}). */
57
+ maxResimulations?: number;
58
+ /** Clock seam (whole seconds). Drives BOTH the gate and the artifact test. */
59
+ now?: () => number;
60
+ /** Sleep seam, forwarded to the gate. */
61
+ sleep?: (ms: number) => Promise<void>;
62
+ /** Debug sink; called with human-readable progress lines. */
63
+ onDebug?: (msg: string) => void;
64
+ }
65
+ /**
66
+ * Gate + pre-send simulation loop. Resolves when it is safe to broadcast;
67
+ * throws {@link QuantumRevertError} on a real (non-artifact) decodable revert.
68
+ * Never broadcasts anything itself.
69
+ *
70
+ * Sequence:
71
+ * 1. **Gate** (`awaitSafeSubmissionWindow`): a NEXT-quantum signature mined in
72
+ * the last DEAD_ZONE_SECONDS of the current quantum reverts
73
+ * `DeadZoneViolation()` on-chain, so defer the send across the boundary
74
+ * when inclusion could land there.
75
+ * 2. **Simulate** (`eth_call`) and route the outcome:
76
+ * - success → safe to broadcast.
77
+ * - `DeadZoneViolation()` → `eth_call` runs against the LATEST block's
78
+ * (slightly stale) timestamp while the on-chain rule uses MINE time. If
79
+ * the signature is already the CURRENT quantum in real time it can never
80
+ * be dead-zoned on-chain — stale-block artifact, proceed. Otherwise the
81
+ * signature is still NEXT near the boundary: re-gate across it and
82
+ * re-simulate (bounded by `maxResimulations`).
83
+ * - any other DECODABLE revert → throw `QuantumRevertError` (fail fast: no
84
+ * broadcast, no gas burned on a doomed tx).
85
+ * - UNDECODABLE failure (no revert data: RPC timeout, rate limit, provider
86
+ * quirk) → log and proceed. The fixed-ceiling broadcast degrades to the
87
+ * pre-refactor blind-broadcast behavior instead of letting a flaky RPC
88
+ * block a valid money-path operation.
89
+ * 3. **Final re-gate**: the sim loop adds RPC round-trips after step 1; this
90
+ * closes the drift with pure clock math (no RPC). If it waits, the
91
+ * signature is CURRENT afterward and needs no further simulation.
92
+ */
93
+ export declare function assertSafeQuantumSubmission(opts: SafeQuantumSubmissionOpts): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gvnrdao/dh-sdk",
3
- "version": "0.0.299",
3
+ "version": "0.0.301",
4
4
  "description": "TypeScript SDK for Diamond Hands Protocol - Bitcoin-backed lending with LIT Protocol PKPs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -82,8 +82,8 @@
82
82
  },
83
83
  "sideEffects": false,
84
84
  "dependencies": {
85
- "@gvnrdao/dh-lit-actions": "^0.0.312",
86
- "@gvnrdao/dh-lit-ops": "^0.0.302",
85
+ "@gvnrdao/dh-lit-actions": "^0.0.313",
86
+ "@gvnrdao/dh-lit-ops": "^0.0.303",
87
87
  "@noble/hashes": "^1.5.0",
88
88
  "axios": "^1.17.0",
89
89
  "bech32": "^2.0.0",