@gvnrdao/dh-sdk 0.0.331 → 0.0.333
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 +37 -0
- package/dist/index.js +396 -187
- package/dist/index.mjs +420 -211
- package/dist/modules/loan/loan-query.module.d.ts +8 -0
- package/dist/modules/withdrawal-address/withdrawal-address.module.d.ts +23 -0
- package/dist/utils/borrower-authorization-712.d.ts +81 -0
- package/dist/utils/extend-authorization.utils.d.ts +16 -15
- package/dist/utils/mint-authorization.utils.d.ts +50 -43
- package/package.json +3 -3
|
@@ -17,6 +17,14 @@ import type { LoanData, LoanDataDetail, PaginatedLoansResponse } from "../../int
|
|
|
17
17
|
import type { LoanEvents, LoanEventsFilter } from "../../types/event-types";
|
|
18
18
|
import { DiamondHandsGraphClient } from "@graphs/diamond-hands";
|
|
19
19
|
import { type Provider } from "ethers";
|
|
20
|
+
/**
|
|
21
|
+
* After `normalizePkpId`, a Chipotle PKP id is a plain 20-byte ETH address
|
|
22
|
+
* (40 hex). Length is the whole discriminator: legacy token ids and still-padded
|
|
23
|
+
* bytes32 ids are 64 hex, compressed keys 66, uncompressed keys 130. A leading
|
|
24
|
+
* `04` on a 40-hex id is an address byte, not the uncompressed-pubkey prefix —
|
|
25
|
+
* excluding it silently dropped the vault for every PKP address starting `0x04`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isNormalizedChipotlePkpId(pkpId: string): boolean;
|
|
20
28
|
/**
|
|
21
29
|
* Loan query filters
|
|
22
30
|
*/
|
|
@@ -85,6 +85,29 @@ export declare class WithdrawalAddressModule {
|
|
|
85
85
|
* hard guarantee).
|
|
86
86
|
*/
|
|
87
87
|
assertApprovedForWithdrawal(borrower: string, btcAddress: string): Promise<Result<void, SDKError>>;
|
|
88
|
+
/**
|
|
89
|
+
* Ask the node for the reason a failed write did not carry.
|
|
90
|
+
*
|
|
91
|
+
* A failing `eth_estimateGas` frequently answers with NOTHING to decode — ethers renders that
|
|
92
|
+
* as `missing revert data ... data=null, reason=null, revert=null`, which names no cause and
|
|
93
|
+
* cannot distinguish "the contract rejected this" from "the RPC hiccuped". An `eth_call` of the
|
|
94
|
+
* same calldata usually does carry the revert reason or custom-error selector, so this runs one
|
|
95
|
+
* and folds the answer into the reported error.
|
|
96
|
+
*
|
|
97
|
+
* Three deliberate properties:
|
|
98
|
+
*
|
|
99
|
+
* 1. It runs ONLY after the write has already failed. Gating the write on a static call would
|
|
100
|
+
* let a flaky endpoint — precisely the condition this exists to diagnose — block an add that
|
|
101
|
+
* would otherwise have succeeded.
|
|
102
|
+
* 2. It costs NO extra signature. `staticCall` is `eth_call`: a node read, never a wallet prompt.
|
|
103
|
+
* 3. It can never throw. A diagnostic that escapes would replace the real cause with itself.
|
|
104
|
+
* (The `try/catch` here is the authorized exception to CLAUDE.md's forbidden-patterns rule;
|
|
105
|
+
* it swallows nothing — every branch returns text that is reported alongside the original.)
|
|
106
|
+
*
|
|
107
|
+
* A static call that SUCCEEDS is itself a finding: the contract accepts this call, so the
|
|
108
|
+
* failure lay outside contract logic (RPC, gas, or nonce).
|
|
109
|
+
*/
|
|
110
|
+
private staticCallDiagnostic;
|
|
88
111
|
private txFailure;
|
|
89
112
|
}
|
|
90
113
|
export declare function createWithdrawalAddressModule(config: WithdrawalAddressModuleConfig): WithdrawalAddressModule;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EIP-712 type definitions for borrower authorization messages — SDK copy.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this is a copy, and why that is safe
|
|
5
|
+
*
|
|
6
|
+
* The single source of truth lives in `@gvnrdao/dh-lit-actions`
|
|
7
|
+
* (`lit-actions/src/constants/borrower-authorization-types.ts`), which the LIT
|
|
8
|
+
* Actions' dual-accept verifier reads. The SDK cannot import it yet: the
|
|
9
|
+
* published `dh-lit-actions@0.0.321` predates those constants, and bumping this
|
|
10
|
+
* package's dependency to an unpublished version makes `npm ci` fail with E404
|
|
11
|
+
* (proven 2026-08-27). So this module re-declares the four field lists — with a
|
|
12
|
+
* **pinned-vector parity suite**
|
|
13
|
+
* (`sdk/tests/shared/unit/borrower-authorization-712.test.ts`) asserting the
|
|
14
|
+
* domain separator, every typehash and every digest are byte-identical to the
|
|
15
|
+
* pins in `lit-actions/tests/unit/borrower-authorization-types.unit.test.ts`.
|
|
16
|
+
* Drift breaks a test suite before it can break a production signature — the
|
|
17
|
+
* same cross-package discipline as `recovery-auth-hash-parity`.
|
|
18
|
+
*
|
|
19
|
+
* **Fold this back into a re-export when `dh-lit-actions@>=0.0.322` publishes**
|
|
20
|
+
* (the deferred publish follow-up); the parity suite then becomes redundant
|
|
21
|
+
* protection rather than the only protection.
|
|
22
|
+
*
|
|
23
|
+
* ## What these are
|
|
24
|
+
*
|
|
25
|
+
* The wallet prompt fired BEFORE every loan transaction. Encoding it as typed
|
|
26
|
+
* data (instead of `personal_sign` over a precomputed keccak digest) is what
|
|
27
|
+
* lets MetaMask render `RepayRequest { positionId, paymentAmount,
|
|
28
|
+
* quantumTimestamp }` instead of an opaque 32-byte hex string. The ERC-7730
|
|
29
|
+
* descriptor (`clear-signing/pending/eip712-LoanOperationsManager.json`) keys
|
|
30
|
+
* its formats by these `encodeType` strings verbatim.
|
|
31
|
+
*
|
|
32
|
+
* No Solidity contract recomputes these digests: the borrower signature is
|
|
33
|
+
* consumed entirely inside the LIT Actions' `AuthorizationModule`. The
|
|
34
|
+
* `verifyingContract` (the LoanOperationsManager proxy) is truthful as the
|
|
35
|
+
* module that owns the operations, not as a `_hashTypedDataV4` call site.
|
|
36
|
+
*/
|
|
37
|
+
import { type Signer, type TypedDataField } from 'ethers';
|
|
38
|
+
/** Shared with the LIT runtime — see `PROTOCOL_EIP712_DOMAIN_NAME` there. */
|
|
39
|
+
export declare const BORROWER_AUTHORIZATION_712_DOMAIN_NAME = "DiamondHands.Protocol";
|
|
40
|
+
export declare const BORROWER_AUTHORIZATION_712_DOMAIN_VERSION = "1";
|
|
41
|
+
export type BorrowerAuthorization712PrimaryType = 'MintRequest' | 'RepayRequest' | 'RenewRequest' | 'WithdrawRequest';
|
|
42
|
+
/**
|
|
43
|
+
* Field lists per primary type. **Order is part of the digest** — any change
|
|
44
|
+
* here must land in the same coordinated step as the lit-actions constants,
|
|
45
|
+
* the descriptor, and a CID rotation (EIP712_DOMAIN_SPEC.md §4.1).
|
|
46
|
+
*/
|
|
47
|
+
export declare const BORROWER_AUTHORIZATION_712_FIELDS: Readonly<Record<BorrowerAuthorization712PrimaryType, readonly TypedDataField[]>>;
|
|
48
|
+
export interface BorrowerAuthorization712Domain {
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly version: string;
|
|
51
|
+
readonly chainId: number;
|
|
52
|
+
readonly verifyingContract: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The `types` argument for `signer.signTypedData(domain, types, message)`.
|
|
56
|
+
* Returns ONLY the requested primary type — handing the signer all four would
|
|
57
|
+
* let a mis-shaped message encode as a different operation without complaint.
|
|
58
|
+
*/
|
|
59
|
+
export declare function borrowerAuthorization712Types(primaryType: BorrowerAuthorization712PrimaryType): Record<string, TypedDataField[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Build the EIP-712 domain for a chain.
|
|
62
|
+
*
|
|
63
|
+
* `verifyingContract` is the LoanOperationsManagerModule proxy from the SDK's
|
|
64
|
+
* own synced deployment constants (`scripts/sync-deployments.js` — the same
|
|
65
|
+
* generator that feeds the LIT Actions' pinned map, so the two sides of the
|
|
66
|
+
* digest cannot desync on a redeploy). Throws for a chain with no known LOM —
|
|
67
|
+
* there is no legitimate borrower authorization to build there.
|
|
68
|
+
*/
|
|
69
|
+
export declare function buildBorrowerAuthorization712Domain(chainId: number): BorrowerAuthorization712Domain;
|
|
70
|
+
/** Compute the EIP-712 digest — the precomputed-signature path's hash. */
|
|
71
|
+
export declare function borrowerAuthorization712Digest(primaryType: BorrowerAuthorization712PrimaryType, chainId: number, message: Record<string, unknown>): string;
|
|
72
|
+
/**
|
|
73
|
+
* Sign a borrower authorization as typed data.
|
|
74
|
+
*
|
|
75
|
+
* Fails fast on a signer without `signTypedData` — no capability fallback to
|
|
76
|
+
* `personal_sign`: a signer that cannot produce typed data must go through an
|
|
77
|
+
* `authorizationProvider` (the Safe/agent seam), where the signature is
|
|
78
|
+
* produced by a party that can. A silent downgrade here would quietly
|
|
79
|
+
* reintroduce the opaque-hash prompt this whole change removes.
|
|
80
|
+
*/
|
|
81
|
+
export declare function signBorrowerAuthorization712(signer: Signer, primaryType: BorrowerAuthorization712PrimaryType, chainId: number, message: Record<string, unknown>): Promise<string>;
|
|
@@ -18,14 +18,18 @@ export interface ExtendOwnerAuthorization {
|
|
|
18
18
|
callerAddress: string;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Build the extend authorization
|
|
21
|
+
* Build the extend authorization digest without signing.
|
|
22
22
|
*
|
|
23
23
|
* Use this when signing with a smart contract wallet (e.g. Safe) that
|
|
24
|
-
* needs to wrap the
|
|
24
|
+
* needs to wrap the digest in its own domain before signing.
|
|
25
|
+
*
|
|
26
|
+
* Since the EIP-712 cutover this is the TYPED-DATA digest
|
|
27
|
+
* (`RenewRequest(bytes32 positionId,uint256 selectedTerm,uint256
|
|
28
|
+
* quantumTimestamp)`) — same shape, different hash (plan step 18).
|
|
25
29
|
*
|
|
26
30
|
* @param positionId - Position identifier
|
|
27
31
|
* @param newTerm - Extension term in months (number)
|
|
28
|
-
* @param chainId - Chain ID
|
|
32
|
+
* @param chainId - Chain ID (selects the EIP-712 domain)
|
|
29
33
|
* @returns { hash, timestamp } — pass these to Safe for signing
|
|
30
34
|
*/
|
|
31
35
|
export declare function buildExtendAuthorizationHash(positionId: string, newTerm: number, chainId: number): {
|
|
@@ -35,19 +39,16 @@ export declare function buildExtendAuthorizationHash(positionId: string, newTerm
|
|
|
35
39
|
/**
|
|
36
40
|
* Generate extend position authorization signature
|
|
37
41
|
*
|
|
38
|
-
* Creates
|
|
39
|
-
* AuthorizationModule.verifyExtendAuthorization
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* ["bytes32", "uint256", "uint256", "uint256", "bytes32"],
|
|
44
|
-
* [positionId, timestamp, chainId, newTerm, actionHash]
|
|
45
|
-
* )
|
|
42
|
+
* Creates an EIP-712 signature the dual-accept
|
|
43
|
+
* AuthorizationModule.verifyExtendAuthorization verifies on its typed arm —
|
|
44
|
+
* rendered by the wallet as `RenewRequest { positionId, selectedTerm,
|
|
45
|
+
* quantumTimestamp }` under the DiamondHands.Protocol/LOM domain
|
|
46
|
+
* (see utils/borrower-authorization-712.ts).
|
|
46
47
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* The WIRE OBJECT IS UNCHANGED (plan step 19): the transported fields keep
|
|
49
|
+
* their legacy names (`timestamp`, `newTerm`, `action: "extend-position"`) so
|
|
50
|
+
* every server hop and the validator's legacy arm keep working — only the
|
|
51
|
+
* signature's encoding differs, and the verifier tries both.
|
|
51
52
|
*
|
|
52
53
|
* @param positionId - Position identifier
|
|
53
54
|
* @param newTerm - Extension term in months (number)
|
|
@@ -18,17 +18,23 @@ export interface MintOwnerAuthorization {
|
|
|
18
18
|
signature: string;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Build the mint authorization
|
|
21
|
+
* Build the mint authorization digest without signing.
|
|
22
22
|
*
|
|
23
23
|
* Use this when signing with a smart contract wallet (e.g. Safe) that
|
|
24
|
-
* needs to wrap the
|
|
24
|
+
* needs to wrap the digest in its own domain before signing.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
26
|
+
* Since the EIP-712 cutover this is the TYPED-DATA digest
|
|
27
|
+
* (`MintRequest(bytes32 positionId,uint256 mintAmount,uint256 quantumTimestamp)`
|
|
28
|
+
* under the DiamondHands.Protocol/LOM domain) — same shape, different hash
|
|
29
|
+
* (plan step 18). The LIT Action's dual-accept verifier resolves EIP-1271
|
|
30
|
+
* queries against this digest on the typed arm.
|
|
31
|
+
*
|
|
32
|
+
* Returns the digest AND the timestamp so both can be passed to
|
|
27
33
|
* generateMintAuthorization via the pre-computed signature overload.
|
|
28
34
|
*
|
|
29
35
|
* @param positionId - Position identifier
|
|
30
36
|
* @param amount - Amount to mint in wei (bigint)
|
|
31
|
-
* @param chainId - Chain ID
|
|
37
|
+
* @param chainId - Chain ID (selects the EIP-712 domain)
|
|
32
38
|
* @returns { hash, timestamp } — pass these to Safe for signing
|
|
33
39
|
*/
|
|
34
40
|
export declare function buildMintAuthorizationHash(positionId: string, amount: bigint, chainId: number): {
|
|
@@ -38,19 +44,22 @@ export declare function buildMintAuthorizationHash(positionId: string, amount: b
|
|
|
38
44
|
/**
|
|
39
45
|
* Generate mint authorization signature
|
|
40
46
|
*
|
|
41
|
-
* Creates
|
|
42
|
-
* AuthorizationModule.verifyMintAuthorization in lit-actions
|
|
47
|
+
* Creates an EIP-712 signature the dual-accept
|
|
48
|
+
* AuthorizationModule.verifyMintAuthorization in lit-actions verifies on its
|
|
49
|
+
* typed arm — and, unlike the legacy `personal_sign`-over-a-digest form, one
|
|
50
|
+
* the wallet can RENDER: MetaMask shows
|
|
51
|
+
* `MintRequest { positionId, mintAmount, quantumTimestamp }` instead of an
|
|
52
|
+
* opaque 32-byte hex string.
|
|
43
53
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* )
|
|
54
|
+
* Typed message (see utils/borrower-authorization-712.ts):
|
|
55
|
+
* MintRequest(bytes32 positionId,uint256 mintAmount,uint256 quantumTimestamp)
|
|
56
|
+
* under domain { name: "DiamondHands.Protocol", version: "1", chainId,
|
|
57
|
+
* verifyingContract: LoanOperationsManagerModule }.
|
|
49
58
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
59
|
+
* The WIRE OBJECT IS UNCHANGED (plan step 19): the transported fields
|
|
60
|
+
* (positionId, timestamp, chainId, amount, action) keep their names and
|
|
61
|
+
* values so the LIT Action's legacy arm and every server hop keep working —
|
|
62
|
+
* only the signature's encoding differs, and the verifier tries both.
|
|
54
63
|
*
|
|
55
64
|
* @param positionId - Position identifier
|
|
56
65
|
* @param amount - Amount to mint in wei (bigint)
|
|
@@ -99,19 +108,14 @@ export interface BalanceConfirmationAuthorization {
|
|
|
99
108
|
/**
|
|
100
109
|
* Generate payment authorization signature
|
|
101
110
|
*
|
|
102
|
-
* Creates
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* solidityKeccak256(
|
|
107
|
-
* ["bytes32", "uint256", "uint256", "uint256", "bytes32"],
|
|
108
|
-
* [positionId, timestamp, chainId, amount, actionHash]
|
|
109
|
-
* )
|
|
111
|
+
* Creates an EIP-712 signature the dual-accept process-payment-validator
|
|
112
|
+
* verifies on its typed arm — rendered by the wallet as
|
|
113
|
+
* `RepayRequest { positionId, paymentAmount, quantumTimestamp }` under the
|
|
114
|
+
* DiamondHands.Protocol/LOM domain (see utils/borrower-authorization-712.ts).
|
|
110
115
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* - LIT Action validates recovered address === position owner
|
|
116
|
+
* The WIRE OBJECT IS UNCHANGED (plan step 19): fields keep their legacy names
|
|
117
|
+
* (`timestamp`, `amount`, `action: "make-payment"`) so every server hop and the
|
|
118
|
+
* validator's legacy arm keep working; only the signature's encoding differs.
|
|
115
119
|
*/
|
|
116
120
|
export declare function generatePaymentAuthorization(positionId: string, amount: bigint, chainId: number, signerOrPrecomputed: Signer | {
|
|
117
121
|
timestamp: number;
|
|
@@ -173,14 +177,19 @@ export interface WithdrawOwnerAuthorization {
|
|
|
173
177
|
signature: string;
|
|
174
178
|
}
|
|
175
179
|
/**
|
|
176
|
-
* Build the withdraw authorization
|
|
180
|
+
* Build the withdraw authorization digest without signing.
|
|
177
181
|
*
|
|
178
182
|
* Use this when signing with a smart contract wallet (e.g. Safe) that
|
|
179
|
-
* needs to wrap the
|
|
183
|
+
* needs to wrap the digest in its own domain before signing.
|
|
184
|
+
*
|
|
185
|
+
* Since the EIP-712 cutover this is the TYPED-DATA digest
|
|
186
|
+
* (`WithdrawRequest(bytes32 positionId,uint256 totalDeduction,string
|
|
187
|
+
* withdrawalAddress,uint256 quantumTimestamp)`) — same shape, different hash
|
|
188
|
+
* (plan step 18).
|
|
180
189
|
*
|
|
181
190
|
* @param positionId - Position identifier
|
|
182
191
|
* @param amount - Amount to withdraw in satoshis (bigint)
|
|
183
|
-
* @param chainId - Chain ID
|
|
192
|
+
* @param chainId - Chain ID (selects the EIP-712 domain)
|
|
184
193
|
* @param destinationAddress - Bitcoin destination address
|
|
185
194
|
* @returns { hash, timestamp } — pass these to Safe for signing
|
|
186
195
|
*/
|
|
@@ -191,19 +200,17 @@ export declare function buildWithdrawAuthorizationHash(positionId: string, amoun
|
|
|
191
200
|
/**
|
|
192
201
|
* Generate withdrawal authorization signature
|
|
193
202
|
*
|
|
194
|
-
* Creates
|
|
195
|
-
* AuthorizationModule.verifyWithdrawAuthorization
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
* )
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
* - Signer address is recovered from signature by LIT Action
|
|
206
|
-
* - LIT Action validates recovered address === position owner
|
|
203
|
+
* Creates an EIP-712 signature the dual-accept
|
|
204
|
+
* AuthorizationModule.verifyWithdrawAuthorization verifies on its typed arm —
|
|
205
|
+
* rendered by the wallet as `WithdrawRequest { positionId, totalDeduction,
|
|
206
|
+
* withdrawalAddress, quantumTimestamp }`, which puts the actual Bitcoin
|
|
207
|
+
* destination on the confirmation screen. A frontend that silently substitutes
|
|
208
|
+
* a different destination is contradicted by the user's own wallet.
|
|
209
|
+
*
|
|
210
|
+
* The WIRE OBJECT IS UNCHANGED (plan step 19): the transported fields keep
|
|
211
|
+
* their legacy names (`amount`, `action: "withdraw-btc"`; `destinationAddress`
|
|
212
|
+
* is re-attached by the caller as `withdrawalAddress`) — only the signature's
|
|
213
|
+
* encoding differs, and the verifier tries both.
|
|
207
214
|
*
|
|
208
215
|
* @param positionId - Position identifier
|
|
209
216
|
* @param amount - Amount to withdraw in satoshis (bigint)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gvnrdao/dh-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.333",
|
|
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.322",
|
|
91
|
+
"@gvnrdao/dh-lit-ops": "^0.0.315",
|
|
92
92
|
"@noble/hashes": "^1.5.0",
|
|
93
93
|
"axios": "^1.17.0",
|
|
94
94
|
"bech32": "^2.0.0",
|