@gvnrdao/dh-sdk 0.0.323 → 0.0.327
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/deployments.js +2 -26
- package/dist/deployments.mjs +2 -26
- package/dist/index.d.ts +1 -0
- package/dist/index.js +399 -84
- package/dist/index.mjs +399 -84
- package/dist/interfaces/chunks/loan-operations.i.d.ts +37 -0
- package/dist/modules/diamond-hands-sdk.d.ts +93 -2
- package/dist/safe-delegation.js +2 -26
- package/dist/safe-delegation.mjs +2 -26
- package/dist/utils/authorization-provider.utils.d.ts +65 -0
- package/dist/utils/mint-authorization.utils.d.ts +10 -16
- package/dist/utils/quantum-revert.utils.d.ts +21 -0
- package/dist/utils/quantum-submission.utils.d.ts +23 -3
- package/dist/utils/quantum-timing.d.ts +49 -2
- package/package.json +3 -3
- package/browser/dist/397.browser.js +0 -2
- package/browser/dist/397.browser.js.LICENSE.txt +0 -1
- package/browser/dist/833.browser.js +0 -2
- package/browser/dist/833.browser.js.LICENSE.txt +0 -1
- package/browser/dist/index.d.ts +0 -8
- package/browser/dist/index.d.ts.map +0 -1
- package/browser/dist/index.js +0 -25
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Loan Operations Interfaces
|
|
3
3
|
*/
|
|
4
4
|
import { LoanStatus } from "../../types/loanStatus";
|
|
5
|
+
import type { AuthorizationProvider } from "../../utils/authorization-provider.utils";
|
|
5
6
|
export type BitcoinAddresses = {
|
|
6
7
|
mainnet: string;
|
|
7
8
|
testnet: string;
|
|
@@ -176,6 +177,18 @@ export interface UCDMintRequest {
|
|
|
176
177
|
stubbedAvailableBtcSats?: string;
|
|
177
178
|
stubbedAuthorizedSpendsHash?: string;
|
|
178
179
|
};
|
|
180
|
+
/**
|
|
181
|
+
* Supplies the borrower authorization envelope instead of the connected
|
|
182
|
+
* signer — the seam a delegated Safe uses so its agent signs instantly
|
|
183
|
+
* rather than the SDK asking for an owner quorum inside a quantum window.
|
|
184
|
+
*
|
|
185
|
+
* Omit it and nothing changes: the SDK signs with the connected signer
|
|
186
|
+
* exactly as before, which is what every EOA borrower does.
|
|
187
|
+
*
|
|
188
|
+
* See `utils/authorization-provider.utils.ts` for why this is a callback and
|
|
189
|
+
* not a `{ timestamp, signature }` value.
|
|
190
|
+
*/
|
|
191
|
+
authorizationProvider?: AuthorizationProvider;
|
|
179
192
|
}
|
|
180
193
|
/**
|
|
181
194
|
* UCD Mint Result Interface
|
|
@@ -213,6 +226,18 @@ export interface RenewPositionResult {
|
|
|
213
226
|
export interface RenewPositionRequest {
|
|
214
227
|
positionId: string;
|
|
215
228
|
selectedTerm: number;
|
|
229
|
+
/**
|
|
230
|
+
* Supplies the borrower authorization envelope instead of the connected
|
|
231
|
+
* signer — the seam a delegated Safe uses so its agent signs instantly
|
|
232
|
+
* rather than the SDK asking for an owner quorum inside a quantum window.
|
|
233
|
+
*
|
|
234
|
+
* Omit it and nothing changes: the SDK signs with the connected signer
|
|
235
|
+
* exactly as before, which is what every EOA borrower does.
|
|
236
|
+
*
|
|
237
|
+
* See `utils/authorization-provider.utils.ts` for why this is a callback and
|
|
238
|
+
* not a `{ timestamp, signature }` value.
|
|
239
|
+
*/
|
|
240
|
+
authorizationProvider?: AuthorizationProvider;
|
|
216
241
|
}
|
|
217
242
|
/**
|
|
218
243
|
* BTC Withdrawal Result Interface
|
|
@@ -282,6 +307,18 @@ export interface PartialPaymentRequest {
|
|
|
282
307
|
positionId: string;
|
|
283
308
|
paymentAmount: number;
|
|
284
309
|
rpcUrl?: string;
|
|
310
|
+
/**
|
|
311
|
+
* Supplies the borrower authorization envelope instead of the connected
|
|
312
|
+
* signer — the seam a delegated Safe uses so its agent signs instantly
|
|
313
|
+
* rather than the SDK asking for an owner quorum inside a quantum window.
|
|
314
|
+
*
|
|
315
|
+
* Omit it and nothing changes: the SDK signs with the connected signer
|
|
316
|
+
* exactly as before, which is what every EOA borrower does.
|
|
317
|
+
*
|
|
318
|
+
* See `utils/authorization-provider.utils.ts` for why this is a callback and
|
|
319
|
+
* not a `{ timestamp, signature }` value.
|
|
320
|
+
*/
|
|
321
|
+
authorizationProvider?: AuthorizationProvider;
|
|
285
322
|
}
|
|
286
323
|
/**
|
|
287
324
|
* Confirm Balance Result Interface
|
|
@@ -18,6 +18,7 @@ import { SDKError } from "../utils/error-handler";
|
|
|
18
18
|
import type { CreateLoanRequest, CreateLoanResult, LoanDataDetail, UCDMintRequest, UCDMintResult, PartialPaymentRequest, PartialPaymentResult, BTCWithdrawalResult, RenewPositionRequest, RenewPositionResult, LiquidationRequest, LiquidationResult, ConfirmBalanceRequest, ConfirmBalanceResult, TermsWithFeesResult } from "../interfaces/chunks/loan-operations.i";
|
|
19
19
|
import type { DiamondHandsSDKConfig } from "../interfaces/chunks/config.i";
|
|
20
20
|
import type { PKPData } from "../interfaces/chunks/pkp-integration.i";
|
|
21
|
+
import { type AuthorizationProvider } from "../utils/authorization-provider.utils";
|
|
21
22
|
import type { DhServerLoginPayload } from "../utils/eip712-login";
|
|
22
23
|
import { type ReconciledWithdrawal } from "../utils/withdrawal-reconciliation.utils";
|
|
23
24
|
import { ContractManager } from "./contract/contract-manager.module";
|
|
@@ -540,6 +541,24 @@ export declare class DiamondHandsSDK {
|
|
|
540
541
|
* address strings, query the subgraph `WithdrawalAddressBook`.
|
|
541
542
|
*/
|
|
542
543
|
getApprovedWithdrawalAddresses(user?: string): Promise<Result<import("./withdrawal-address/withdrawal-address.module").WithdrawalAddressEntry[], SDKError>>;
|
|
544
|
+
/**
|
|
545
|
+
* Recover the revert reason of an already-mined, already-reverted transaction.
|
|
546
|
+
*
|
|
547
|
+
* A status-0 receipt carries no revert data, and ethers does not re-fetch it, so
|
|
548
|
+
* the reason has to be recovered by replaying the exact calldata as an `eth_call`
|
|
549
|
+
* pinned to the block that mined it — the only block whose `block.timestamp`
|
|
550
|
+
* reproduces a timing-dependent revert such as `DeadZoneViolation()`.
|
|
551
|
+
*
|
|
552
|
+
* Caveat: the replay runs against end-of-block state rather than the state at the
|
|
553
|
+
* transaction's own index, so for a state-dependent revert the recovered name is
|
|
554
|
+
* diagnostic, not proof. Timing reverts (the case this exists for) are unaffected —
|
|
555
|
+
* they depend only on the block timestamp.
|
|
556
|
+
*
|
|
557
|
+
* @returns the decoded error name, its raw selector if unknown, or `null` when the
|
|
558
|
+
* replay yields no revert data at all (never throws — a failed diagnosis
|
|
559
|
+
* must not mask the underlying failure).
|
|
560
|
+
*/
|
|
561
|
+
private decodeMinedRevert;
|
|
543
562
|
/**
|
|
544
563
|
* Withdraw Bitcoin from a position
|
|
545
564
|
*
|
|
@@ -563,7 +582,15 @@ export declare class DiamondHandsSDK {
|
|
|
563
582
|
* @param customBitcoinRpcUrl - Optional custom Bitcoin RPC URL (for local testing)
|
|
564
583
|
* @returns Withdrawal result with transaction details
|
|
565
584
|
*/
|
|
566
|
-
withdrawBTC(positionId: string, withdrawalAddress: string, withdrawalAmount: number
|
|
585
|
+
withdrawBTC(positionId: string, withdrawalAddress: string, withdrawalAmount: number,
|
|
586
|
+
/**
|
|
587
|
+
* An options bag rather than a fifth positional argument: this method is
|
|
588
|
+
* already three positional strings/numbers deep, and the next caller to
|
|
589
|
+
* add one would have to guess the order.
|
|
590
|
+
*/
|
|
591
|
+
options?: {
|
|
592
|
+
authorizationProvider?: AuthorizationProvider;
|
|
593
|
+
}): Promise<BTCWithdrawalResult>;
|
|
567
594
|
/**
|
|
568
595
|
* TEMPORARY (multi-UTXO withdrawal fix, Phase A): fetch the vault's confirmed
|
|
569
596
|
* UTXO set from lit-ops-server (`GET /api/lit/vault-utxos`) so the caller can
|
|
@@ -721,6 +748,11 @@ export declare class DiamondHandsSDK {
|
|
|
721
748
|
* (`MAX_ATTESTATION_AGE = 1 hour`) and replay-protects via
|
|
722
749
|
* `authorizedAt` binding.
|
|
723
750
|
*
|
|
751
|
+
* WHO PAYS: in **service mode** lit-ops-server performs BOTH steps and its
|
|
752
|
+
* relayer funds the transaction — the borrower is never prompted, because the
|
|
753
|
+
* Bitcoin withdrawal this cleans up after has already settled. In **direct
|
|
754
|
+
* mode** (CLI / tests / hardhat) the SDK's own signer submits step 2.
|
|
755
|
+
*
|
|
724
756
|
* `invalidatorTxid` is either:
|
|
725
757
|
* - the borrower's own authorized broadcast (classified "consumed-as-authorized"
|
|
726
758
|
* by the LIT validator), once it has ≥6 confirmations on the BTC
|
|
@@ -1053,6 +1085,16 @@ export declare class DiamondHandsSDK {
|
|
|
1053
1085
|
agentActive: boolean | null;
|
|
1054
1086
|
/** Agent expiry (unix seconds) — the ONLY time bound on every grant. Null without `user`. */
|
|
1055
1087
|
agentValidUntil: number | null;
|
|
1088
|
+
/** SCOPE_WITHDRAW granted. Always false where the registry predates v2.1.0. */
|
|
1089
|
+
withdrawEnabled: boolean;
|
|
1090
|
+
/** Does the DEPLOYED registry honour SCOPE_WITHDRAW at all (v2.1.0+)? */
|
|
1091
|
+
withdrawScopeSupported: boolean;
|
|
1092
|
+
/**
|
|
1093
|
+
* Grant's post-withdrawal collateral floor in bps; 0 when WITHDRAW is not granted.
|
|
1094
|
+
* INERT AT ZERO DEBT — a ratio cannot bound a withdrawal once debt is 0. Never present this
|
|
1095
|
+
* as a cap on how much an agent may withdraw.
|
|
1096
|
+
*/
|
|
1097
|
+
minWithdrawRatioBps: number;
|
|
1056
1098
|
}>;
|
|
1057
1099
|
/** Governance floor-of-floors for agent mints (bps). The UI's minimum for the floor editor. */
|
|
1058
1100
|
getAgentMintFloorMinBps(): Promise<number>;
|
|
@@ -1143,7 +1185,56 @@ export declare class DiamondHandsSDK {
|
|
|
1143
1185
|
hash: string;
|
|
1144
1186
|
blockNumber: number;
|
|
1145
1187
|
}>;
|
|
1146
|
-
/**
|
|
1188
|
+
/**
|
|
1189
|
+
* Grant the agent the WITHDRAW scope for a position (registry v2.1.0+). Borrower-signed.
|
|
1190
|
+
*
|
|
1191
|
+
* CONTRACT WALLETS ONLY, and this is enforced here rather than on-chain. The withdrawal
|
|
1192
|
+
* validator's only non-borrower arm is `checkSafeAgentWithdrawAuthorization`, which requires a
|
|
1193
|
+
* factory-provenanced `AgentModule` whose `safe()` is the borrower — something an EOA cannot
|
|
1194
|
+
* have. The registry deliberately does NOT check wallet type ("Callable by any borrower (this
|
|
1195
|
+
* function does not itself require a Safe)"), so an EOA can record a grant that no code path
|
|
1196
|
+
* will ever satisfy. Refusing here keeps the supported path from writing a dead record.
|
|
1197
|
+
*
|
|
1198
|
+
* That refusal is ergonomics, NOT enforcement — `enableAutoWithdraw` is `external` and reachable
|
|
1199
|
+
* from any wallet. The real reason delegated withdrawal is closed to EOAs is that they can sign
|
|
1200
|
+
* inside the ~44s window themselves, so delegating buys them nothing and only adds risk.
|
|
1201
|
+
*
|
|
1202
|
+
* @param minWithdrawRatioBps Post-withdrawal collateral-ratio floor for AGENT withdrawals.
|
|
1203
|
+
* Clamped by the same governance minimum as the mint floor. **Inert at zero debt** — it
|
|
1204
|
+
* is a ratio, and the post-withdrawal ratio is infinite for any amount once debt is 0.
|
|
1205
|
+
* The operative bound there is the quorum-gated destination allowlist. Do not surface
|
|
1206
|
+
* this to users as a cap on how much an agent may withdraw.
|
|
1207
|
+
* There is no in-place raise: changing it means disable-then-re-enable, deliberately —
|
|
1208
|
+
* on the one scope that moves custodied BTC, incident response can only disable outright.
|
|
1209
|
+
*/
|
|
1210
|
+
enableAutoWithdraw(positionId: string, minWithdrawRatioBps: number, options?: {
|
|
1211
|
+
agentValiditySeconds?: number;
|
|
1212
|
+
}): Promise<{
|
|
1213
|
+
hash: string;
|
|
1214
|
+
blockNumber: number;
|
|
1215
|
+
agentAddress?: string;
|
|
1216
|
+
}>;
|
|
1217
|
+
/**
|
|
1218
|
+
* Revoke the WITHDRAW scope for a position (also zeroes its withdraw floor). Borrower-signed.
|
|
1219
|
+
*
|
|
1220
|
+
* No wallet-type check: revoking is the safe direction, and an EOA that recorded a grant before
|
|
1221
|
+
* this guard existed must be able to clear it.
|
|
1222
|
+
*/
|
|
1223
|
+
disableAutoWithdraw(positionId: string): Promise<{
|
|
1224
|
+
hash: string;
|
|
1225
|
+
blockNumber: number;
|
|
1226
|
+
}>;
|
|
1227
|
+
/**
|
|
1228
|
+
* Layer 2 of the withdrawal-delegation gating: refuse to write a grant that could never be
|
|
1229
|
+
* satisfied. See `enableAutoWithdraw` for why this lives here and not in the registry.
|
|
1230
|
+
*/
|
|
1231
|
+
private assertBorrowerIsContractWallet;
|
|
1232
|
+
/**
|
|
1233
|
+
* Pre-flight the registry's `FloorBelowProtocolMin` guard with a message a user can act on.
|
|
1234
|
+
*
|
|
1235
|
+
* `agentMintFloorMinBps` is the governance floor-of-floors for BOTH the mint and withdraw
|
|
1236
|
+
* grants — one lever, one invariant — so `scope` only shapes the message.
|
|
1237
|
+
*/
|
|
1147
1238
|
private assertMintFloorAtOrAboveProtocolMin;
|
|
1148
1239
|
/**
|
|
1149
1240
|
* Get Bitcoin balance for an address
|
package/dist/safe-delegation.js
CHANGED
|
@@ -326,7 +326,7 @@ var SEPOLIA_DEPLOYMENT = {
|
|
|
326
326
|
CARRY_AGENT_REGISTRY: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
|
|
327
327
|
CARRY_AGENT_REGISTRY_IMPL: "0x1b11a3D5e46F3672E643c997f0d569DFf0b64a01",
|
|
328
328
|
AGENT_DELEGATION_REGISTRY: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
|
|
329
|
-
AGENT_DELEGATION_REGISTRY_IMPL: "
|
|
329
|
+
AGENT_DELEGATION_REGISTRY_IMPL: "0xE931E4b9ceF2A48Ac7D57192da4B523756cFC310",
|
|
330
330
|
DH_AGENT_DELEGATE: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF"
|
|
331
331
|
}
|
|
332
332
|
};
|
|
@@ -538,7 +538,7 @@ var MAINNET_DEPLOYMENT = {
|
|
|
538
538
|
var LOCALHOST_DEPLOYMENT = {
|
|
539
539
|
network: "localhost",
|
|
540
540
|
chainId: 1337,
|
|
541
|
-
timestamp: "2026-07-
|
|
541
|
+
timestamp: "2026-07-20T19:45:58.514Z",
|
|
542
542
|
deployer: "",
|
|
543
543
|
contracts: {
|
|
544
544
|
MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
|
@@ -558,30 +558,6 @@ var LOCALHOST_DEPLOYMENT = {
|
|
|
558
558
|
PositionManager: "0x851356ae760d987E095750cCeb3bC6014560891C",
|
|
559
559
|
OperationAuthorizationRegistry: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
|
|
560
560
|
PKPValidation: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A"
|
|
561
|
-
},
|
|
562
|
-
latestEnv: {
|
|
563
|
-
UCD_TOKEN: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
|
|
564
|
-
UCD_CONTROLLER: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
|
565
|
-
POSITION_MANAGER: "0x851356ae760d987E095750cCeb3bC6014560891C",
|
|
566
|
-
PKP_VALIDATION_REGISTRY: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A",
|
|
567
|
-
PKP_VALIDATION_CID_V1: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
|
|
568
|
-
PKP_VALIDATION_CID_V3: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
|
|
569
|
-
PKP_ETH_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
570
|
-
OPERATION_AUTHORIZATION_REGISTRY: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
|
|
571
|
-
UCD_MINT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
572
|
-
UCD_MINT_VALIDATOR_VERSION: 1,
|
|
573
|
-
BTC_WITHDRAWAL_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
574
|
-
BTC_WITHDRAWAL_VALIDATOR_VERSION: 3,
|
|
575
|
-
UPDATE_BALANCE_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
576
|
-
UPDATE_BALANCE_VALIDATOR_VERSION: 1,
|
|
577
|
-
PROCESS_PAYMENT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
578
|
-
PROCESS_PAYMENT_VALIDATOR_VERSION: 1,
|
|
579
|
-
EXTEND_POSITION_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
580
|
-
EXTEND_POSITION_VALIDATOR_VERSION: 1,
|
|
581
|
-
POSITION_MANAGER_CORE_MODULE: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
|
|
582
|
-
LOAN_OPERATIONS_MANAGER_MODULE: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
|
|
583
|
-
TERM_MANAGER_MODULE: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
|
|
584
|
-
COMMUNITY_MANAGER_MODULE: ""
|
|
585
561
|
}
|
|
586
562
|
};
|
|
587
563
|
var ALL_DEPLOYMENTS = {
|
package/dist/safe-delegation.mjs
CHANGED
|
@@ -299,7 +299,7 @@ var SEPOLIA_DEPLOYMENT = {
|
|
|
299
299
|
CARRY_AGENT_REGISTRY: "0x5595CB538D44E6Cf179D20364fD631D0477e69f7",
|
|
300
300
|
CARRY_AGENT_REGISTRY_IMPL: "0x1b11a3D5e46F3672E643c997f0d569DFf0b64a01",
|
|
301
301
|
AGENT_DELEGATION_REGISTRY: "0x6AE7fc6bE0925126b9D25b1B7Ce5AD5d127ec025",
|
|
302
|
-
AGENT_DELEGATION_REGISTRY_IMPL: "
|
|
302
|
+
AGENT_DELEGATION_REGISTRY_IMPL: "0xE931E4b9ceF2A48Ac7D57192da4B523756cFC310",
|
|
303
303
|
DH_AGENT_DELEGATE: "0xa5A14489ae883960df6E1463A41a47aBE9Ecc5AF"
|
|
304
304
|
}
|
|
305
305
|
};
|
|
@@ -511,7 +511,7 @@ var MAINNET_DEPLOYMENT = {
|
|
|
511
511
|
var LOCALHOST_DEPLOYMENT = {
|
|
512
512
|
network: "localhost",
|
|
513
513
|
chainId: 1337,
|
|
514
|
-
timestamp: "2026-07-
|
|
514
|
+
timestamp: "2026-07-20T19:45:58.514Z",
|
|
515
515
|
deployer: "",
|
|
516
516
|
contracts: {
|
|
517
517
|
MessageHashBuilder: "0xa82fF9aFd8f496c3d6ac40E2a0F282E47488CFc9",
|
|
@@ -531,30 +531,6 @@ var LOCALHOST_DEPLOYMENT = {
|
|
|
531
531
|
PositionManager: "0x851356ae760d987E095750cCeb3bC6014560891C",
|
|
532
532
|
OperationAuthorizationRegistry: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
|
|
533
533
|
PKPValidation: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A"
|
|
534
|
-
},
|
|
535
|
-
latestEnv: {
|
|
536
|
-
UCD_TOKEN: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
|
|
537
|
-
UCD_CONTROLLER: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9",
|
|
538
|
-
POSITION_MANAGER: "0x851356ae760d987E095750cCeb3bC6014560891C",
|
|
539
|
-
PKP_VALIDATION_REGISTRY: "0xD0141E899a65C95a556fE2B27e5982A6DE7fDD7A",
|
|
540
|
-
PKP_VALIDATION_CID_V1: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
|
|
541
|
-
PKP_VALIDATION_CID_V3: "0x12203af2054b0ab6e18d898ddfc7985593439fb82d22336133387c1169f98d3702ab",
|
|
542
|
-
PKP_ETH_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
543
|
-
OPERATION_AUTHORIZATION_REGISTRY: "0xc96304e3c037f81dA488ed9dEa1D8F2a48278a75",
|
|
544
|
-
UCD_MINT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
545
|
-
UCD_MINT_VALIDATOR_VERSION: 1,
|
|
546
|
-
BTC_WITHDRAWAL_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
547
|
-
BTC_WITHDRAWAL_VALIDATOR_VERSION: 3,
|
|
548
|
-
UPDATE_BALANCE_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
549
|
-
UPDATE_BALANCE_VALIDATOR_VERSION: 1,
|
|
550
|
-
PROCESS_PAYMENT_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
551
|
-
PROCESS_PAYMENT_VALIDATOR_VERSION: 1,
|
|
552
|
-
EXTEND_POSITION_VALIDATOR_ADDRESS: "0x406f6a9855de2f5577b1525f0600461f02859bd0",
|
|
553
|
-
EXTEND_POSITION_VALIDATOR_VERSION: 1,
|
|
554
|
-
POSITION_MANAGER_CORE_MODULE: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e",
|
|
555
|
-
LOAN_OPERATIONS_MANAGER_MODULE: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed",
|
|
556
|
-
TERM_MANAGER_MODULE: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82",
|
|
557
|
-
COMMUNITY_MANAGER_MODULE: ""
|
|
558
534
|
}
|
|
559
535
|
};
|
|
560
536
|
var ALL_DEPLOYMENTS = {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Borrower authorization, supplied by something other than the connected signer.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS. Every loan operation carries an envelope the validator
|
|
5
|
+
* recovers a signer from, and the SDK builds it by calling
|
|
6
|
+
* `signer.signMessage()`. For an EOA that is one instant prompt. For a k-of-n
|
|
7
|
+
* Safe it is an EIP-1271 signature — a full owner quorum — and the operation
|
|
8
|
+
* also needs a second quorum to send the transaction, both inside a quantum
|
|
9
|
+
* window of 180s (~44s for withdrawals). That cannot be gathered, so a genuine
|
|
10
|
+
* multi-owner Safe could not complete a loan operation at all.
|
|
11
|
+
*
|
|
12
|
+
* The fix is for something that CAN sign instantly to sign the envelope: the
|
|
13
|
+
* Safe's delegated agent. This is the seam that lets a caller supply it.
|
|
14
|
+
*
|
|
15
|
+
* A PROVIDER, NOT A VALUE. The obvious API — hand in `{ timestamp, signature }`
|
|
16
|
+
* — is wrong, and the reason is the retry loops. Every operation re-signs on
|
|
17
|
+
* retry precisely because a quantum window can lapse between building the
|
|
18
|
+
* envelope and landing the transaction. A fixed value would be replayed on
|
|
19
|
+
* attempt 2 carrying attempt 1's timestamp, so the envelope would already be
|
|
20
|
+
* dead: a recoverable timing miss turned into a hard failure. A provider is
|
|
21
|
+
* called again per attempt and returns a fresh signature over a fresh window.
|
|
22
|
+
*
|
|
23
|
+
* THE SDK OWNS THE TIMESTAMP. It computes the quantum-aligned value and hands
|
|
24
|
+
* it to the provider, rather than accepting one back unseen, because the same
|
|
25
|
+
* value must be embedded in the protocol call that follows. Likewise `amount`
|
|
26
|
+
* is the SDK's own computed wei — a caller re-deriving it and differing by one
|
|
27
|
+
* wei produces a signature that recovers to a different address, which surfaces
|
|
28
|
+
* as an opaque authorization failure with nothing to point at.
|
|
29
|
+
*/
|
|
30
|
+
/** Everything the authorization envelope binds, as the SDK computed it. */
|
|
31
|
+
export interface AuthorizationContext {
|
|
32
|
+
positionId: string;
|
|
33
|
+
/** Quantum-aligned, and the value the protocol call will carry. */
|
|
34
|
+
timestamp: number;
|
|
35
|
+
/** Wei, as a decimal string. The SDK's own value — never re-derive it. */
|
|
36
|
+
amount: string;
|
|
37
|
+
/** Renewals only: the term the envelope binds instead of an amount. */
|
|
38
|
+
selectedTerm?: number;
|
|
39
|
+
/** Withdrawals only: the destination the envelope binds. */
|
|
40
|
+
destinationAddress?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns a signature over the envelope described by `ctx`. The returned
|
|
44
|
+
* `timestamp` is echoed back rather than assumed so a provider that had to
|
|
45
|
+
* re-align it can say so — but it SHOULD be the one it was given.
|
|
46
|
+
*/
|
|
47
|
+
export type AuthorizationProvider = (ctx: AuthorizationContext) => Promise<{
|
|
48
|
+
timestamp: number;
|
|
49
|
+
signature: string;
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve what the `generate*Authorization` helpers should be handed: either a
|
|
53
|
+
* precomputed `{ timestamp, signature }` from the provider, or the live signer
|
|
54
|
+
* exactly as before.
|
|
55
|
+
*
|
|
56
|
+
* Call this INSIDE a retry loop, never above one — that is the whole point.
|
|
57
|
+
*
|
|
58
|
+
* @param nextQuantumTimestamp The SDK's quantum-aligned timestamp for THIS
|
|
59
|
+
* attempt. Passed in rather than computed here so the caller keeps a
|
|
60
|
+
* single source for the value it will also send on-chain.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveAuthorizationInput<TSigner>(provider: AuthorizationProvider | undefined, signer: TSigner, ctx: AuthorizationContext): Promise<TSigner | {
|
|
63
|
+
timestamp: number;
|
|
64
|
+
signature: string;
|
|
65
|
+
}>;
|
|
@@ -113,25 +113,19 @@ export interface BalanceConfirmationAuthorization {
|
|
|
113
113
|
* - Signer address is recovered from signature by LIT Action
|
|
114
114
|
* - LIT Action validates recovered address === position owner
|
|
115
115
|
*/
|
|
116
|
-
export declare function generatePaymentAuthorization(positionId: string, amount: bigint, chainId: number,
|
|
116
|
+
export declare function generatePaymentAuthorization(positionId: string, amount: bigint, chainId: number, signerOrPrecomputed: Signer | {
|
|
117
|
+
timestamp: number;
|
|
118
|
+
signature: string;
|
|
119
|
+
}): Promise<PaymentOwnerAuthorization>;
|
|
117
120
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* Creates a signature that matches the format expected by
|
|
121
|
-
* extend-position-validator LIT Action.
|
|
122
|
-
*
|
|
123
|
-
* Message structure:
|
|
124
|
-
* solidityKeccak256(
|
|
125
|
-
* ["bytes32", "uint256", "uint256", "uint256", "bytes32"],
|
|
126
|
-
* [positionId, timestamp, chainId, selectedTerm, actionHash]
|
|
127
|
-
* )
|
|
121
|
+
* NOTE: `generateExtendAuthorization` does NOT live here.
|
|
128
122
|
*
|
|
129
|
-
*
|
|
130
|
-
* -
|
|
131
|
-
* -
|
|
132
|
-
*
|
|
123
|
+
* It used to — a second copy, without the precomputed-signature union, whose
|
|
124
|
+
* import in `diamond-hands-sdk.ts` was commented out ("Not used yet"). The live
|
|
125
|
+
* one is `utils/extend-authorization.utils.ts`, and that is the only one. The
|
|
126
|
+
* duplicate is removed rather than left dormant because an edit landing in the
|
|
127
|
+
* wrong copy is a silent no-op: the code looks changed, the behaviour does not.
|
|
133
128
|
*/
|
|
134
|
-
export declare function generateExtendAuthorization(positionId: string, selectedTerm: number, chainId: number, signer: Signer): Promise<ExtendOwnerAuthorization>;
|
|
135
129
|
/**
|
|
136
130
|
* Optional Chipotle service fallback for `getPKPPublicKeyFromTokenId`.
|
|
137
131
|
*
|
|
@@ -39,3 +39,24 @@ export interface DecodedRevert {
|
|
|
39
39
|
export declare function decodeQuantumRevert(e: any): DecodedRevert;
|
|
40
40
|
/** True iff the error decodes to `DeadZoneViolation()` from any provider nesting. */
|
|
41
41
|
export declare function isDeadZoneViolation(e: any): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether a failed quantum-signed operation can be safely retried with a fresh
|
|
44
|
+
* signature. Shared by the payment and BTC-withdrawal retry loops so the two paths
|
|
45
|
+
* cannot drift apart again (they had: withdrawal recognised only
|
|
46
|
+
* `QuantumOutsideWindow`, and never decoded a mined revert at all, so the mainnet
|
|
47
|
+
* `DeadZoneViolation` of 2026-08-07 surfaced as a hard failure on a flow that
|
|
48
|
+
* payments recover from automatically).
|
|
49
|
+
*
|
|
50
|
+
* **Retryable** — the operation provably did not happen:
|
|
51
|
+
* - a pre-send simulation quantum error (nothing was ever broadcast), or
|
|
52
|
+
* - a mined revert (`status === 0`): atomic, so no funds moved, no authorization
|
|
53
|
+
* was recorded, and the quantum replay lane is untouched. Re-signing is safe.
|
|
54
|
+
*
|
|
55
|
+
* **NOT retryable** — the outcome is unknown, so a resubmit risks double-spending:
|
|
56
|
+
* - a confirmation timeout (the tx may still be pending), or
|
|
57
|
+
* - a confirmation failure with no receipt (transport died mid-wait).
|
|
58
|
+
*
|
|
59
|
+
* Callers MUST therefore only produce a "Transaction reverted" message after
|
|
60
|
+
* observing a receipt with `status === 0`.
|
|
61
|
+
*/
|
|
62
|
+
export declare function isRetryableQuantumFailure(message: string | null | undefined): boolean;
|
|
@@ -37,13 +37,21 @@ export declare class QuantumRevertError extends Error {
|
|
|
37
37
|
constructor(errorName: string | null, selector: string | null, data: string | null, cause: unknown);
|
|
38
38
|
}
|
|
39
39
|
export interface SafeQuantumSubmissionOpts {
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Provider used for the `eth_call` simulation and — when it exposes `getBlock`
|
|
42
|
+
* (every ethers provider does) — for the dead-zone gate's chain clock. Without
|
|
43
|
+
* `getBlock` the gate silently degrades to the client's local clock, which on the
|
|
44
|
+
* browser path is exactly the skew we are trying to design out.
|
|
45
|
+
*/
|
|
41
46
|
provider: {
|
|
42
47
|
call(tx: {
|
|
43
48
|
to: string;
|
|
44
49
|
from: string;
|
|
45
50
|
data: string;
|
|
46
51
|
}): Promise<string>;
|
|
52
|
+
getBlock?(blockTag: string): Promise<{
|
|
53
|
+
timestamp: number;
|
|
54
|
+
} | null | undefined>;
|
|
47
55
|
};
|
|
48
56
|
/** Target contract address. */
|
|
49
57
|
to: string;
|
|
@@ -59,6 +67,12 @@ export interface SafeQuantumSubmissionOpts {
|
|
|
59
67
|
now?: () => number;
|
|
60
68
|
/** Sleep seam, forwarded to the gate. */
|
|
61
69
|
sleep?: (ms: number) => Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Chain-clock seam (latest mined block timestamp, whole seconds). Defaults to
|
|
72
|
+
* `provider.getBlock("latest")` when the provider exposes it. Pass explicitly to
|
|
73
|
+
* override in tests, or `null`-resolving to force the local-clock fallback.
|
|
74
|
+
*/
|
|
75
|
+
chainNow?: () => Promise<number | null>;
|
|
62
76
|
/** Debug sink; called with human-readable progress lines. */
|
|
63
77
|
onDebug?: (msg: string) => void;
|
|
64
78
|
/**
|
|
@@ -79,7 +93,9 @@ export interface SafeQuantumSubmissionOpts {
|
|
|
79
93
|
* 1. **Gate** (`awaitSafeSubmissionWindow`): a NEXT-quantum signature mined in
|
|
80
94
|
* the last DEAD_ZONE_SECONDS of the current quantum reverts
|
|
81
95
|
* `DeadZoneViolation()` on-chain, so defer the send across the boundary
|
|
82
|
-
* when inclusion could land there.
|
|
96
|
+
* when inclusion could land there. Anchored on `provider.getBlock("latest")`
|
|
97
|
+
* — the same clock `block.timestamp` comes from — so a skewed client clock
|
|
98
|
+
* cannot wave a doomed send through.
|
|
83
99
|
* 2. **Simulate** (`eth_call`) and route the outcome:
|
|
84
100
|
* - success → safe to broadcast.
|
|
85
101
|
* - `DeadZoneViolation()` → `eth_call` runs against the LATEST block's
|
|
@@ -87,7 +103,11 @@ export interface SafeQuantumSubmissionOpts {
|
|
|
87
103
|
* the signature is already the CURRENT quantum in real time it can never
|
|
88
104
|
* be dead-zoned on-chain — stale-block artifact, proceed. Otherwise the
|
|
89
105
|
* signature is still NEXT near the boundary: re-gate across it and
|
|
90
|
-
* re-simulate (bounded by `maxResimulations`).
|
|
106
|
+
* re-simulate (bounded by `maxResimulations`). This artifact test must use
|
|
107
|
+
* the LOCAL clock, not the chain head: the chain head IS the stale value
|
|
108
|
+
* the simulation reverted against, so anchoring it there would classify
|
|
109
|
+
* every artifact as real. A fast client clock wrongly reaching "artifact"
|
|
110
|
+
* is caught by the chain-anchored final re-gate in step 3.
|
|
91
111
|
* - any other DECODABLE revert → throw `QuantumRevertError` (fail fast: no
|
|
92
112
|
* broadcast, no gas burned on a doomed tx).
|
|
93
113
|
* - UNDECODABLE failure (no revert data: RPC timeout, rate limit, provider
|
|
@@ -28,13 +28,38 @@
|
|
|
28
28
|
*/
|
|
29
29
|
export declare const QUANTUM_WINDOW_SECONDS = 60;
|
|
30
30
|
export declare const DEAD_ZONE_SECONDS = 8;
|
|
31
|
+
/** Ethereum slot cadence. Block timestamps are multiples of 12s from genesis. */
|
|
32
|
+
export declare const SLOT_SECONDS = 12;
|
|
33
|
+
/**
|
|
34
|
+
* Slots of inclusion latency we insure against. A broadcast is NOT guaranteed to
|
|
35
|
+
* land in the very next slot: builders skip low-tip transactions and slots are
|
|
36
|
+
* missed, so 2–3 slot inclusion is routine on mainnet.
|
|
37
|
+
*
|
|
38
|
+
* Sized from a real mainnet failure (tx 0x5933d64d…, 2026-08-07): a NEXT-quantum
|
|
39
|
+
* withdrawal was broadcast with ~25s to the boundary — outside the old 16s budget,
|
|
40
|
+
* so the gate let it through — then sat out one near-empty block (0.0007 gwei tip)
|
|
41
|
+
* and was mined 24s later at second 59, one second inside the trailing dead zone.
|
|
42
|
+
* `DeadZoneViolation()`. 3 slots (36s) covers that skip with a slot to spare.
|
|
43
|
+
*/
|
|
44
|
+
export declare const INCLUSION_SLOT_TOLERANCE = 3;
|
|
31
45
|
/**
|
|
32
46
|
* Seconds of mainnet inclusion latency we insure against BEFORE the quantum
|
|
33
|
-
* boundary.
|
|
47
|
+
* boundary. A NEXT/PAST-quantum send within
|
|
34
48
|
* `DEAD_ZONE_SECONDS + INCLUSION_LATENCY_BUDGET` of the boundary is deferred so it
|
|
35
49
|
* cannot be mined in the trailing dead zone `[boundary - 8, boundary)`.
|
|
50
|
+
*
|
|
51
|
+
* Used by the LOCAL-CLOCK fallback path only. When a chain clock is available the
|
|
52
|
+
* gate reasons in whole slots instead (see `awaitSafeSubmissionWindow`), which is
|
|
53
|
+
* both tighter and immune to client clock skew.
|
|
36
54
|
*/
|
|
37
|
-
export declare const INCLUSION_LATENCY_BUDGET
|
|
55
|
+
export declare const INCLUSION_LATENCY_BUDGET: number;
|
|
56
|
+
/**
|
|
57
|
+
* Cap on chain-clock polls while deferring across a boundary. Each poll sleeps at
|
|
58
|
+
* most one slot, so this bounds a deferral at ~`MAX_GATE_POLLS` slots — comfortably
|
|
59
|
+
* more than the ~44s worst-case wait, while refusing to spin forever on a stuck or
|
|
60
|
+
* lying RPC.
|
|
61
|
+
*/
|
|
62
|
+
export declare const MAX_GATE_POLLS = 12;
|
|
38
63
|
/**
|
|
39
64
|
* Seconds of client-clock-vs-chain skew guard we add AFTER the boundary when
|
|
40
65
|
* deferring. A CURRENT-quantum signature is never dead-zoned, so this only needs to
|
|
@@ -108,6 +133,16 @@ export interface SafeSubmissionOptions {
|
|
|
108
133
|
now?: () => number;
|
|
109
134
|
/** Sleeps for `ms` milliseconds. Defaults to `setTimeout`. */
|
|
110
135
|
sleep?: (ms: number) => Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Latest MINED block timestamp, in whole seconds — the only clock the on-chain
|
|
138
|
+
* dead-zone rule is expressed in. Supply this wherever a provider is available:
|
|
139
|
+
* the local-clock path below cannot see client clock skew, and the primary
|
|
140
|
+
* consumer of this gate is a browser wallet whose clock is not trustworthy.
|
|
141
|
+
*
|
|
142
|
+
* Resolve to `null` (or throw) to fall back to the local clock — a flaky RPC
|
|
143
|
+
* must never block a valid money-path operation.
|
|
144
|
+
*/
|
|
145
|
+
chainNow?: () => Promise<number | null>;
|
|
111
146
|
}
|
|
112
147
|
export interface SafeSubmissionResult {
|
|
113
148
|
/** Whether the send was deferred across the quantum boundary. */
|
|
@@ -136,6 +171,18 @@ export interface SafeSubmissionResult {
|
|
|
136
171
|
* acceptance window (`QuantumOutsideWindow`). They must be handled by re-signing
|
|
137
172
|
* upstream. In practice the SDK only ever signs CURRENT/NEXT.
|
|
138
173
|
*
|
|
174
|
+
* Two implementations, same predicate:
|
|
175
|
+
*
|
|
176
|
+
* - **Chain-anchored** (`opts.chainNow` supplied — always prefer this): reasons in
|
|
177
|
+
* whole slots off the latest mined block timestamp `H`, the same clock the
|
|
178
|
+
* contract's `block.timestamp` comes from. A broadcast issued now can only be
|
|
179
|
+
* mined at `H + 12k`, so it is safe iff EVERY plausible `k` lands clear of the
|
|
180
|
+
* dead zone. No client clock is consulted at all — only sleep *durations*, which
|
|
181
|
+
* are immune to an offset clock.
|
|
182
|
+
* - **Local-clock fallback** (no `chainNow`, or the chain read failed): the original
|
|
183
|
+
* `Date.now()` math with the widened {@link INCLUSION_LATENCY_BUDGET}. Retained so
|
|
184
|
+
* a flaky RPC degrades rather than blocks, but it cannot see client clock skew.
|
|
185
|
+
*
|
|
139
186
|
* @param quantumTimestamp The quantum timestamp embedded in the LIT-signed payload.
|
|
140
187
|
* @returns whether it waited, and for how long.
|
|
141
188
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gvnrdao/dh-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.327",
|
|
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",
|
|
@@ -87,8 +87,8 @@
|
|
|
87
87
|
},
|
|
88
88
|
"sideEffects": false,
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@gvnrdao/dh-lit-actions": "^0.0.
|
|
91
|
-
"@gvnrdao/dh-lit-ops": "^0.0.
|
|
90
|
+
"@gvnrdao/dh-lit-actions": "^0.0.320",
|
|
91
|
+
"@gvnrdao/dh-lit-ops": "^0.0.312",
|
|
92
92
|
"@noble/hashes": "^1.5.0",
|
|
93
93
|
"axios": "^1.17.0",
|
|
94
94
|
"bech32": "^2.0.0",
|