@gvnrdao/dh-sdk 0.0.341 → 0.0.343

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.
@@ -60,7 +60,6 @@ var SEPOLIA_DEPLOYMENT = {
60
60
  PositionDelegateRegistry: "0x482369De283622E7b05731875ec137d4E3D208F9",
61
61
  FeeRecipientRegistry: "0x95795F8403DDb629E8527B2934C4e71f5fC0C374",
62
62
  BitcoinWithdrawalAddressRegistry: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe",
63
- CarryAgentRegistry: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
64
63
  AgentDelegationRegistry: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
65
64
  DHAgentDelegate: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF",
66
65
  AgentModuleFactory: "0x1Fd3aCa2C030Cf77ce28Af9694D3Bb3034ACa2E5"
@@ -330,8 +329,6 @@ var SEPOLIA_DEPLOYMENT = {
330
329
  OPERATION_AUTHORIZATION_REGISTRY_IMPL: "0xe0D981B60351f0f8fFDA2b50C8180Fb37CeF3883",
331
330
  BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe",
332
331
  BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY_IMPL: "0x76E0a2c9d89562fD7c50b5f554d7fa25957e39Db",
333
- CARRY_AGENT_REGISTRY: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
334
- CARRY_AGENT_REGISTRY_IMPL: "0x1b11a3D5e46F3672E643c997f0d569DFf0b64a01",
335
332
  AGENT_DELEGATION_REGISTRY: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
336
333
  AGENT_DELEGATION_REGISTRY_IMPL: "0xE931E4b9ceF2A48Ac7D57192da4B523756cFC310",
337
334
  DH_AGENT_DELEGATE: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF"
@@ -33,7 +33,6 @@ var SEPOLIA_DEPLOYMENT = {
33
33
  PositionDelegateRegistry: "0x482369De283622E7b05731875ec137d4E3D208F9",
34
34
  FeeRecipientRegistry: "0x95795F8403DDb629E8527B2934C4e71f5fC0C374",
35
35
  BitcoinWithdrawalAddressRegistry: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe",
36
- CarryAgentRegistry: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
37
36
  AgentDelegationRegistry: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
38
37
  DHAgentDelegate: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF",
39
38
  AgentModuleFactory: "0x1Fd3aCa2C030Cf77ce28Af9694D3Bb3034ACa2E5"
@@ -303,8 +302,6 @@ var SEPOLIA_DEPLOYMENT = {
303
302
  OPERATION_AUTHORIZATION_REGISTRY_IMPL: "0xe0D981B60351f0f8fFDA2b50C8180Fb37CeF3883",
304
303
  BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe",
305
304
  BITCOIN_WITHDRAWAL_ADDRESS_REGISTRY_IMPL: "0x76E0a2c9d89562fD7c50b5f554d7fa25957e39Db",
306
- CARRY_AGENT_REGISTRY: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
307
- CARRY_AGENT_REGISTRY_IMPL: "0x1b11a3D5e46F3672E643c997f0d569DFf0b64a01",
308
305
  AGENT_DELEGATION_REGISTRY: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
309
306
  AGENT_DELEGATION_REGISTRY_IMPL: "0xE931E4b9ceF2A48Ac7D57192da4B523756cFC310",
310
307
  DH_AGENT_DELEGATE: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF"
@@ -227,7 +227,6 @@ var SEPOLIA_CONTRACTS = {
227
227
  PositionDelegateRegistry: "0x482369De283622E7b05731875ec137d4E3D208F9",
228
228
  FeeRecipientRegistry: "0x95795F8403DDb629E8527B2934C4e71f5fC0C374",
229
229
  BitcoinWithdrawalAddressRegistry: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe",
230
- CarryAgentRegistry: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
231
230
  AgentDelegationRegistry: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
232
231
  DHAgentDelegate: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF",
233
232
  AgentModuleFactory: "0x1Fd3aCa2C030Cf77ce28Af9694D3Bb3034ACa2E5"
@@ -670,7 +669,9 @@ function nextQuantumTimestamp(nowMs = Date.now()) {
670
669
  return currentQuantum + QUANTUM_SECONDS;
671
670
  }
672
671
  function isAuthFresh(issuedAtSec, nowSec) {
673
- return issuedAtSec <= nowSec && nowSec - issuedAtSec < authValiditySec();
672
+ const sq = Math.floor(issuedAtSec / QUANTUM_SECONDS) * QUANTUM_SECONDS;
673
+ const cq = Math.floor(nowSec / QUANTUM_SECONDS) * QUANTUM_SECONDS;
674
+ return sq === cq - QUANTUM_SECONDS || sq === cq || sq === cq + QUANTUM_SECONDS;
674
675
  }
675
676
  function isStrictCurrentQuantum(issuedAtSec, nowSec) {
676
677
  return issuedAtSec <= nowSec && Math.floor(issuedAtSec / QUANTUM_SECONDS) === Math.floor(nowSec / QUANTUM_SECONDS);
@@ -939,6 +940,9 @@ var POSITION_DETAILS_ABI = [
939
940
  var TERM_MANAGER_ABI = [
940
941
  "function getTermFees(uint256 termMonths) view returns (uint88 originationFee, uint88 extensionFee)"
941
942
  ];
943
+ function extendFeeUpperBoundWei(ucdDebt, extensionFeeRateBps) {
944
+ return ucdDebt * extensionFeeRateBps / 10000n;
945
+ }
942
946
  async function computeExtendFeeUpperBound(provider, contracts, positionId, selectedTerm, onDegraded) {
943
947
  const degrade = (reason) => {
944
948
  onDegraded?.(reason);
@@ -973,7 +977,7 @@ async function computeExtendFeeUpperBound(provider, contracts, positionId, selec
973
977
  } catch (e) {
974
978
  return degrade(`getTermFees(${selectedTerm}) failed on ${termMgr}: ${e.message.slice(0, 160)}`);
975
979
  }
976
- const upperBoundFeeWei = ucdDebt * extensionFeeRateBps / 10000n;
980
+ const upperBoundFeeWei = extendFeeUpperBoundWei(ucdDebt, extensionFeeRateBps);
977
981
  return { ucdDebt, extensionFeeRateBps, upperBoundFeeWei };
978
982
  }
979
983
 
@@ -153,7 +153,6 @@ var SEPOLIA_CONTRACTS = {
153
153
  PositionDelegateRegistry: "0x482369De283622E7b05731875ec137d4E3D208F9",
154
154
  FeeRecipientRegistry: "0x95795F8403DDb629E8527B2934C4e71f5fC0C374",
155
155
  BitcoinWithdrawalAddressRegistry: "0x6376D392097F0A81e51BD83960A97EA20eaf1dbe",
156
- CarryAgentRegistry: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
157
156
  AgentDelegationRegistry: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
158
157
  DHAgentDelegate: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF",
159
158
  AgentModuleFactory: "0x1Fd3aCa2C030Cf77ce28Af9694D3Bb3034ACa2E5"
@@ -596,7 +595,9 @@ function nextQuantumTimestamp(nowMs = Date.now()) {
596
595
  return currentQuantum + QUANTUM_SECONDS;
597
596
  }
598
597
  function isAuthFresh(issuedAtSec, nowSec) {
599
- return issuedAtSec <= nowSec && nowSec - issuedAtSec < authValiditySec();
598
+ const sq = Math.floor(issuedAtSec / QUANTUM_SECONDS) * QUANTUM_SECONDS;
599
+ const cq = Math.floor(nowSec / QUANTUM_SECONDS) * QUANTUM_SECONDS;
600
+ return sq === cq - QUANTUM_SECONDS || sq === cq || sq === cq + QUANTUM_SECONDS;
600
601
  }
601
602
  function isStrictCurrentQuantum(issuedAtSec, nowSec) {
602
603
  return issuedAtSec <= nowSec && Math.floor(issuedAtSec / QUANTUM_SECONDS) === Math.floor(nowSec / QUANTUM_SECONDS);
@@ -865,6 +866,9 @@ var POSITION_DETAILS_ABI = [
865
866
  var TERM_MANAGER_ABI = [
866
867
  "function getTermFees(uint256 termMonths) view returns (uint88 originationFee, uint88 extensionFee)"
867
868
  ];
869
+ function extendFeeUpperBoundWei(ucdDebt, extensionFeeRateBps) {
870
+ return ucdDebt * extensionFeeRateBps / 10000n;
871
+ }
868
872
  async function computeExtendFeeUpperBound(provider, contracts, positionId, selectedTerm, onDegraded) {
869
873
  const degrade = (reason) => {
870
874
  onDegraded?.(reason);
@@ -899,7 +903,7 @@ async function computeExtendFeeUpperBound(provider, contracts, positionId, selec
899
903
  } catch (e) {
900
904
  return degrade(`getTermFees(${selectedTerm}) failed on ${termMgr}: ${e.message.slice(0, 160)}`);
901
905
  }
902
- const upperBoundFeeWei = ucdDebt * extensionFeeRateBps / 10000n;
906
+ const upperBoundFeeWei = extendFeeUpperBoundWei(ucdDebt, extensionFeeRateBps);
903
907
  return { ucdDebt, extensionFeeRateBps, upperBoundFeeWei };
904
908
  }
905
909
 
@@ -42,14 +42,21 @@ export type AgentBindingPlan = {
42
42
  * needs a NEW agent address (AR-1 burns an address on first bind). Treating "not active" as
43
43
  * "register" minted a billed PKP and then reverted, on every attempt, once an agent expired.
44
44
  *
45
- * `isAgentActive` also folds in the registry's pause. An UNEXPIRED `Active` record that reads
46
- * inactive means paused: both `registerAgent` and `rotateAgent` are `whenNotPaused`, so this
47
- * throws rather than let the caller mint a PKP for a transaction that cannot land.
45
+ * PAUSE is read explicitly (`registry.paused()`) and refused BEFORE anything else except reuse:
46
+ * both `registerAgent` and `rotateAgent` are `whenNotPaused`, so any plan other than `reuse` would
47
+ * mint a billed PKP for a transaction that cannot land. Inferring the pause from "an unexpired
48
+ * `Active` record reads inactive" (the previous rule) missed a paused registry whenever the
49
+ * record was expired, `None` or `Revoked` (plan A4, `formal/lean/DH/AgentBinding.lean`).
48
50
  *
51
+ * Formal model and proofs: `formal/lean/DH/AgentBinding.lean` (replayed by
52
+ * `tests/shared/unit/lean-vectors.test.ts`).
53
+ *
54
+ * @param paused `registry.paused()`, read in the same batch as `isActive` and `record`
49
55
  * @param nowSeconds chain time (latest block timestamp), not the local clock
50
56
  */
51
57
  export declare function planAgentBinding(params: {
52
58
  isActive: boolean;
59
+ paused: boolean;
53
60
  record: {
54
61
  agent: string;
55
62
  validUntil: bigint | number;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Bitcoin dust limit for a DESTINATION address — the SDK side of the table the Lit Action applies
3
+ * by scriptPubKey (`lit-actions/src/modules/bitcoin/dust.ts`, `dustLimitForScript`):
4
+ *
5
+ * P2WPKH (witness v0, 20-byte program) → 294
6
+ * P2WSH (witness v0, 32-byte program) → 330
7
+ * P2TR (witness v1, 32-byte program) → 330
8
+ * anything else (legacy P2PKH / P2SH, unknown) → 546
9
+ *
10
+ * The SDK has addresses, not scripts, so the witness version and program length are read from the
11
+ * bech32/bech32m data part. Checksums are NOT verified here — call `safeValidateBitcoinAddress`
12
+ * first; this only classifies. Both sides replay `formal/lean/vectors/dust.json`
13
+ * (`formal/lean/DH/BtcFee.lean`), so the two tables cannot drift apart.
14
+ */
15
+ /** Witness version and program length (bytes) of a segwit address, or `null` if not bech32. */
16
+ export declare function segwitProgramShape(address: string): {
17
+ version: number;
18
+ programBytes: number;
19
+ } | null;
20
+ export declare function dustLimitForAddress(address: string): number;
21
+ /**
22
+ * Phase-2 guard (plan A5): the dust limit the net payout `targetAmount - networkFee` falls short
23
+ * of, or `null` when it relays. Mirrors the Lit signer's refusal.
24
+ */
25
+ export declare function destinationDustShortfall(targetAmount: number, networkFee: number, destination: string): number | null;
@@ -64,6 +64,8 @@ export declare function selectBitcoinAddress(addresses: BitcoinAddresses, bitcoi
64
64
  * This function is purely local (no RPC calls) and mirrors ProtocolConstants logic:
65
65
  * 12 months = 365 days; other terms = 30 days/month.
66
66
  *
67
+ * Formal model and proofs: `formal/lean/DH/TailFee.lean`.
68
+ *
67
69
  * @param mintAmount - Amount of UCD to mint (in wei, bigint)
68
70
  * @param mintTimestamp - Unix timestamp of the mint (seconds)
69
71
  * @param originationFeeBps - Origination fee in basis points (e.g. 50 = 0.5%)
@@ -104,12 +106,33 @@ export declare function debtAfterMintExceedsLoanCap(params: {
104
106
  maxLoanWei: bigint;
105
107
  }): boolean;
106
108
  /**
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.
109
+ * The LARGEST principal (wei) that still fits under the cap once its BASE fee is added:
110
+ * `floor(((room + 1) * 10000 - 1) / (10000 + bps))` with `room = maxLoan - currentDebt`;
111
+ * zero when the position is already at or above the cap. Proved exact in
112
+ * `formal/lean/DH/LoanCap.lean` (`maxPrincipal_exact`: it fits, one wei more does not).
113
+ * Base fee only — for a suggestion that includes the post-renewal tail fee, use
114
+ * `maxPrincipalForFee` with the validator's full fee (`calculateMintFee`).
110
115
  */
111
116
  export declare function maxPrincipalWithinLoanCap(params: {
112
117
  maxLoanWei: bigint;
113
118
  currentDebtWei: bigint;
114
119
  originationFeeBps: number;
115
120
  }): bigint;
121
+ /**
122
+ * Collateral ratio in basis points exactly as the contract computes it
123
+ * (`SafeCalculations.safeCollateralValue` then `safeCollateralRatio`):
124
+ * `floor(sats * price8 * 100 * 10000 / debtWei)`. `price8` is USD per BTC with 8 decimals;
125
+ * `debtWei` is UCD with 18 decimals. Proved against `formal/lean/DH/Collateral.lean`
126
+ * `contractRatio` (vectors: `collateral.json`).
127
+ */
128
+ export declare function collateralRatioBps(sats: bigint, price8: bigint, debtWei: bigint): bigint;
129
+ /** `collateralRatioBps` as a percentage string with two decimals, truncated like the contract. */
130
+ export declare function collateralRatioPercentString(sats: bigint, price8: bigint, debtWei: bigint): string;
131
+ /**
132
+ * The largest principal `p <= room` with `p + fee(p) <= room`, for any fee that is monotone in the
133
+ * principal with `fee(0) == 0` (the validator's full mint fee, base + tail, is). Binary search;
134
+ * proved exact in `formal/lean/DH/MintSuggestion.lean` (`maxPrincipalFor_exact`), and a suggestion
135
+ * computed with an earlier authorization stamp is proved accepted at any later stamp
136
+ * (`suggestion_accepted_later`).
137
+ */
138
+ export declare function maxPrincipalForFee(room: bigint, fee: (principal: bigint) => bigint): bigint;
@@ -1,5 +1,10 @@
1
1
  import { ethers } from "ethers";
2
2
  import type { ContractRegistry } from "./tx-validator";
3
+ /**
4
+ * The H-3 baseline `ucdDebt × extensionFeeRateBps / 10000`. Proved in
5
+ * `formal/lean/DH/RenewalFee.lean` to bound the validator's pro-rata fee for all inputs.
6
+ */
7
+ export declare function extendFeeUpperBoundWei(ucdDebt: bigint, extensionFeeRateBps: bigint): bigint;
3
8
  export interface ExtendFeeBound {
4
9
  ucdDebt: bigint;
5
10
  extensionFeeRateBps: bigint;
@@ -20,7 +20,13 @@ export declare function authValiditySec(): number;
20
20
  * advance, or the authorization is born at the edge of its window.
21
21
  */
22
22
  export declare function nextQuantumTimestamp(nowMs?: number): number;
23
- /** General ops (mint/repay/extend): issued within the last 3 windows and never in the future. */
23
+ /**
24
+ * General ops (mint/repay/extend): the Lit validator's rule — the signature's quantum is the
25
+ * PAST, CURRENT or NEXT quantum of `nowSec`. The next quantum is allowed because authorizations
26
+ * are STAMPED with `nextQuantumTimestamp`. A sliding "`nowSec - issuedAtSec < 180`, never in the
27
+ * future" check rejected every such stamp and accepted signatures three quanta back that the
28
+ * validator refuses (plan A10, `formal/lean/DH/Quantum.lean` `client_fresh_iff_lit`).
29
+ */
24
30
  export declare function isAuthFresh(issuedAtSec: number, nowSec: number): boolean;
25
31
  /** Withdrawal (`strictCurrentQuantum`): issued in the SAME quantum window as now. */
26
32
  export declare function isStrictCurrentQuantum(issuedAtSec: number, nowSec: number): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gvnrdao/dh-sdk",
3
- "version": "0.0.341",
3
+ "version": "0.0.343",
4
4
  "description": "TypeScript SDK for Diamond Hands Protocol",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -100,22 +100,17 @@
100
100
  },
101
101
  "sideEffects": false,
102
102
  "dependencies": {
103
+ "@gvnrdao/dh-lit-actions": "^0.0.323",
104
+ "@gvnrdao/dh-lit-ops": "^0.0.318",
103
105
  "@noble/hashes": "^1.5.0",
104
106
  "axios": "^1.17.0",
105
107
  "bech32": "^2.0.0",
106
108
  "bip66": "^2.0.0",
107
109
  "bitcoinjs-lib": "^6.1.0",
108
- "bn.js": "^5.2.3",
109
110
  "bs58check": "^3.0.1",
110
111
  "crypto-js": "^4.2.0",
111
112
  "dotenv": "^17.4.2",
112
- "elliptic": "^6.6.1",
113
- "ethers": "6.16.0",
114
- "uuid": "^9.0.1"
115
- },
116
- "optionalDependencies": {
117
- "@gvnrdao/dh-lit-actions": "^0.0.322",
118
- "@gvnrdao/dh-lit-ops": "^0.0.316"
113
+ "ethers": "6.16.0"
119
114
  },
120
115
  "devDependencies": {
121
116
  "@babel/preset-env": "7.29.7",