@gvnrdao/dh-sdk 0.0.347 → 0.0.348

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.
@@ -38,16 +38,29 @@ type BitcoinAddressNetwork = 'mainnet' | 'testnet' | 'regtest';
38
38
  * @throws if the address is malformed, fails its checksum, or is not on `network`
39
39
  */
40
40
  export declare function safeValidateBitcoinAddress(address: string, network: BitcoinAddressNetwork): string;
41
+ /**
42
+ * Why `address` has no canonical spelling, or null when it has one. Pure — for callers that
43
+ * report failures as values (Result) rather than exceptions.
44
+ *
45
+ * BIP-173: a bech32/bech32m string is valid all-lowercase or all-uppercase, never mixed. A mixed
46
+ * string is not a typo we may repair by lowercasing — it is invalid, and folding it would let a
47
+ * corrupted address hash to a real allowlist entry. Base58 is case-sensitive by design and is
48
+ * never folded, so it is never a case error.
49
+ */
50
+ export declare function bitcoinAddressCaseError(address: string): string | null;
41
51
  /**
42
52
  * Canonicalize a Bitcoin address so that the SAME string is used on both the
43
53
  * approve path (registry.addAddress) and the withdraw path. The on-chain
44
54
  * allowlist is keyed by keccak256(bytes(address)), so the two strings MUST
45
- * byte-match. Bech32 (bc1/tb1/bcrt1) is lower-case canonical, so we lower-case
46
- * it; Base58 (1.../3.../m.../n.../2...) is case-sensitive and is returned
47
- * unchanged.
55
+ * byte-match, and the contract accepts lowercase-only bech32.
56
+ *
57
+ * - bech32 (bc1/tb1/bcrt1): lowercase is canonical; an ALL-uppercase string
58
+ * (BIP-173 allows it, e.g. QR codes) is lowercased; MIXED case throws.
59
+ * - base58 (1…/3…/m…/n…/2…): case-sensitive, returned unchanged.
48
60
  *
49
61
  * @param address Bitcoin address to canonicalize
50
62
  * @returns The canonical form to store/compare
63
+ * @throws on mixed-case bech32 — see `bitcoinAddressCaseError`
51
64
  */
52
65
  export declare function normalizeBitcoinAddress(address: string): string;
53
66
  /**
@@ -5,9 +5,21 @@
5
5
  * this file owns the feature-specific orchestration (the lit-ops-server mint-agent endpoint).
6
6
  */
7
7
  /**
8
- * Mint a fresh per-user AGENT PKP via lit-ops-server and return its Ethereum address, to be
9
- * registered as the renewal agent in `AgentDelegationRegistry`. Each call yields a unique
10
- * address (satisfies the registry's AR-1 non-reuse). Throws on failure.
8
+ * EIP-712 types of the agent's binding consent (`contracts/docs/EIP712_DOMAIN_SPEC.md` §3.9).
9
+ * Domain: "DiamondHands.Protocol" v1, verifyingContract = the AgentDelegationRegistry proxy.
10
+ */
11
+ export declare const AGENT_BINDING_CONSENT_TYPES: {
12
+ AgentBindingConsent: {
13
+ name: string;
14
+ type: string;
15
+ }[];
16
+ };
17
+ /**
18
+ * Mint a fresh per-user AGENT PKP via lit-ops-server and return its Ethereum address together
19
+ * with the agent's binding consent — the EIP-712 `AgentBindingConsent(user, agent)` signature
20
+ * AgentDelegationRegistry 3.0.0's `registerAgent` / `rotateAgent` require (issue #1197). The
21
+ * server signs it for the authenticated session address only. Each call yields a unique address
22
+ * (satisfies the registry's AR-1 non-reuse). Throws on failure.
11
23
  *
12
24
  * @param serviceEndpoint lit-ops-server base URL (the SDK's `serviceEndpoint`).
13
25
  * @param authHeader Optional async/sync provider of auth headers (the SDK's session token).
@@ -17,7 +29,10 @@ export declare function mintAgentPkp(params: {
17
29
  serviceEndpoint: string;
18
30
  authHeader?: () => Promise<Record<string, string>> | Record<string, string>;
19
31
  timeoutMs?: number;
20
- }): Promise<string>;
32
+ }): Promise<{
33
+ walletAddress: string;
34
+ agentConsent: string;
35
+ }>;
21
36
  /** `AgentStatus` in `IAgentDelegateRegistryBase.sol`. */
22
37
  export declare const AGENT_STATUS: {
23
38
  readonly None: 0;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * EIP-712 types for the BTC-custody owner authorizations — SDK copy.
3
+ *
4
+ * The withdrawal **execute** step and the stale-spend **invalidate** step
5
+ * (`contracts/docs/EIP712_DOMAIN_SPEC.md` §3.8). Before these types, both were
6
+ * `personal_sign` over a 32-byte packed hash that no wallet can decode
7
+ * (watchlist C-46); typed data lets the wallet show the destination, what the
8
+ * recipient receives, the fee and the total itself.
9
+ *
10
+ * ## Why this is a copy, and why that is safe
11
+ *
12
+ * The single source lives in `@gvnrdao/dh-lit-actions`
13
+ * (`lit-actions/src/constants/btc-spend-request-types.ts`), which the
14
+ * `btc-transaction-signer` / `btc-utxo-invalidator` verifiers read. The SDK's
15
+ * published `dh-lit-actions` dependency predates it, so this module re-declares
16
+ * the two field lists; `sdk/tests/shared/unit/btc-spend-request-712.test.ts`
17
+ * reads the golden vectors (`lit-actions/tests/vectors/btc-spend-request.v1.json`)
18
+ * and asserts every digest is byte-identical. Fold this into a re-export once
19
+ * the SDK depends on a `dh-lit-actions` that exports these names.
20
+ *
21
+ * ## The message comes from the chain, not the caller
22
+ *
23
+ * `readAuthorizedSpend` reads the `BTCSpendAuthorizer` record on the pinned
24
+ * proxy; the builders take destination, total deduction and outpoint from it.
25
+ * The Lit Action builds its message from the same record, so the wallet shows
26
+ * the borrower exactly what the transaction pays — never a server's claim.
27
+ */
28
+ import { type ContractRunner, type Signer, type TypedDataField } from 'ethers';
29
+ export declare const BTC_SPEND_REQUEST_712_DOMAIN_NAME = "DiamondHands.Protocol";
30
+ export declare const BTC_SPEND_REQUEST_712_DOMAIN_VERSION = "1";
31
+ export type BtcSpendRequest712PrimaryType = 'ExecuteBtcWithdrawalRequest' | 'InvalidateStaleSpendRequest';
32
+ /**
33
+ * Field lists per primary type. **Order is part of the digest** — a change
34
+ * lands with the lit-actions constants, the descriptor and a CID rotation.
35
+ */
36
+ export declare const BTC_SPEND_REQUEST_712_FIELDS: Readonly<Record<BtcSpendRequest712PrimaryType, readonly TypedDataField[]>>;
37
+ export interface BtcSpendRequest712Domain {
38
+ readonly name: string;
39
+ readonly version: string;
40
+ readonly chainId: number;
41
+ readonly verifyingContract: string;
42
+ }
43
+ /** A complete `eth_signTypedData_v4` payload, without the `EIP712Domain` entry. */
44
+ export interface BtcSpendRequest712Payload {
45
+ domain: BtcSpendRequest712Domain;
46
+ types: Record<string, TypedDataField[]>;
47
+ primaryType: BtcSpendRequest712PrimaryType;
48
+ message: Record<string, string | number>;
49
+ }
50
+ /** The `BTCSpendAuthorizer.getAuthorizedSpends` row a signature consumes. */
51
+ export interface AuthorizedSpendRecord {
52
+ txid: string;
53
+ vout: number;
54
+ satoshis: bigint;
55
+ targetAddress: string;
56
+ targetAmount: bigint;
57
+ authorizedAt: bigint;
58
+ }
59
+ /** Returns ONLY the requested primary type. @throws on an unknown type. */
60
+ export declare function btcSpendRequest712Types(primaryType: BtcSpendRequest712PrimaryType): Record<string, TypedDataField[]>;
61
+ /** The pinned BTCSpendAuthorizer proxy for a chain. @throws for an unknown chain. */
62
+ export declare function btcSpendAuthorizerAddress(chainId: number): string;
63
+ /** Build the EIP-712 domain: the pinned BTCSpendAuthorizer proxy, never caller-supplied. */
64
+ export declare function buildBtcSpendRequest712Domain(chainId: number): BtcSpendRequest712Domain;
65
+ /** Canonical txid: lowercase, no `0x`, exactly 64 hex. @throws otherwise. */
66
+ export declare function canonicalBtcTxid(txid: unknown): string;
67
+ /**
68
+ * Read the on-chain spend record for one outpoint from the pinned
69
+ * BTCSpendAuthorizer. The outpoint is matched in canonical txid form.
70
+ *
71
+ * @throws if no record exists for the outpoint under this position.
72
+ */
73
+ export declare function readAuthorizedSpend(params: {
74
+ runner: ContractRunner;
75
+ chainId: number;
76
+ positionId: string;
77
+ txid: string;
78
+ vout: number;
79
+ }): Promise<AuthorizedSpendRecord>;
80
+ /** @throws if `record` is not the record for `txid:vout` (canonical txid form). */
81
+ export declare function assertRecordIsOutpoint(record: Pick<AuthorizedSpendRecord, 'txid' | 'vout'>, txid: string, vout: number): void;
82
+ /**
83
+ * The typed execute payload, built from the on-chain record, the fee the
84
+ * transaction will pay and the input-set hash. Exported for Safe/precomputed
85
+ * signing and for tests.
86
+ *
87
+ * @throws on a non-integer or negative fee, or one not below the total.
88
+ */
89
+ export declare function buildExecuteBtcWithdrawalTypedData(params: {
90
+ chainId: number;
91
+ positionId: string;
92
+ record: Pick<AuthorizedSpendRecord, 'txid' | 'vout' | 'targetAddress' | 'targetAmount'>;
93
+ networkFee: number;
94
+ inputSetHash: string;
95
+ quantumTimestamp: number;
96
+ }): BtcSpendRequest712Payload;
97
+ /** The typed invalidate payload over the on-chain record's outpoint. */
98
+ export declare function buildInvalidateStaleSpendTypedData(params: {
99
+ chainId: number;
100
+ positionId: string;
101
+ record: Pick<AuthorizedSpendRecord, 'txid' | 'vout'>;
102
+ quantumTimestamp: number;
103
+ }): BtcSpendRequest712Payload;
104
+ /** EIP-712 digest of a payload — the hash a contract wallet's `isValidSignature` receives. */
105
+ export declare function btcSpendRequest712Digest(payload: BtcSpendRequest712Payload): string;
106
+ export declare const BTC_SPEND_QUANTUM_SECONDS = 60;
107
+ /**
108
+ * Optional signer capability: re-stamp at the moment the wallet is actually
109
+ * asked. The hosted connector implements it, because the borrower may open
110
+ * the request long after the SDK built it. The signer calls `build` with the
111
+ * quantum start it stamps (and may call it again to re-stamp) and returns the
112
+ * signature with the stamp it signed.
113
+ */
114
+ export interface RestampableTypedDataSigner {
115
+ dhRestampTypedData(build: (quantumTimestamp: number) => BtcSpendRequest712Payload): Promise<{
116
+ signature: string;
117
+ quantumTimestamp: number;
118
+ }>;
119
+ }
120
+ /**
121
+ * Latest moment, in seconds after the stamp, at which the SDK still relays a
122
+ * signature.
123
+ *
124
+ * The Lit Action accepts a stamp from the current or previous quantum, but
125
+ * its shared verifier first waits out the dead zone (seconds 52–59) and then
126
+ * re-checks the window, so a stamp from quantum S is only accepted when
127
+ * verification STARTS by second 51 of quantum S+1 — 111 s after the stamp.
128
+ * Verification starts after the server's chain reads and the signer's own
129
+ * RPC steps; 90 s leaves ~20 s for that trip. Past it, the refusal is
130
+ * certain, so nothing is relayed.
131
+ */
132
+ export declare const BTC_SPEND_RELAY_DEADLINE_SECONDS = 90;
133
+ /**
134
+ * Sign a BTC spend request as typed data, stamping the CURRENT quantum.
135
+ *
136
+ * The verifier accepts a typed execute stamp from the current or previous
137
+ * quantum (DR-6), so there is no pre-wait: the borrower has between 30 and
138
+ * 90 s from the prompt (depending on where in the quantum it opens), and a
139
+ * re-stamping signer that stamps at dispatch gets the full 90 s. After the
140
+ * wallet returns, a signature older than `BTC_SPEND_RELAY_DEADLINE_SECONDS`,
141
+ * or stamped in a quantum that has not started, is refused here ("signed too
142
+ * late") rather than relayed into a certain refusal.
143
+ *
144
+ * No `personal_sign` fallback: a signer without `signTypedData` throws.
145
+ */
146
+ export declare function signBtcSpendRequest712(signer: Signer, build: (quantumTimestamp: number) => BtcSpendRequest712Payload): Promise<{
147
+ signature: string;
148
+ quantumTimestamp: number;
149
+ payload: BtcSpendRequest712Payload;
150
+ }>;
@@ -1,11 +1,17 @@
1
1
  /**
2
- * Helpers for building the EIP-191 `execute-btc-withdrawal` envelope that the
2
+ * Helpers for building the `execute-btc-withdrawal` envelope that the
3
3
  * `btc-transaction-signer` LIT Action verifies.
4
4
  *
5
5
  * Pulled out of the CLI's executePendingWithdrawal util so the SDK and CLI
6
6
  * use the same logic and signing rules.
7
+ *
8
+ * Since A-2 (EIP712_DOMAIN_SPEC.md §3.8) the borrower signs an EIP-712
9
+ * `ExecuteBtcWithdrawalRequest` the wallet can display — destination, what
10
+ * the recipient receives, the fee and the total — instead of `personal_sign`
11
+ * over a 32-byte hash. The envelope shape sent to the server is unchanged.
7
12
  */
8
- import { type Signer } from "ethers";
13
+ import { type ContractRunner, type Signer } from "ethers";
14
+ import { type AuthorizedSpendRecord } from "./btc-spend-request-712";
9
15
  export interface BtcExecuteSignParams {
10
16
  positionId: string;
11
17
  txid: string;
@@ -37,17 +43,17 @@ export interface BtcExecuteSignedEnvelope {
37
43
  txid: string;
38
44
  vout: number;
39
45
  satoshis: number;
40
- /** Bitcoin destination address bound into the signature. */
46
+ /** Bitcoin destination — the on-chain record's `targetAddress`. */
41
47
  targetAddress: string;
42
- /** Authorized withdrawal amount (sats) bound into the signature. */
48
+ /** Total deduction in sats — the on-chain record's `targetAmount`. */
43
49
  targetAmount: number;
44
50
  networkFee: number;
45
51
  chainId: number;
46
- /** Quantum-aligned timestamp (start of a 60s quantum). */
52
+ /** Quantum-aligned timestamp (start of a 60s quantum) the signature binds. */
47
53
  timestamp: number;
48
- /** EIP-191 signature over the message hash. */
54
+ /** EIP-712 `ExecuteBtcWithdrawalRequest` signature. */
49
55
  userSignature: string;
50
- /** Address recovered from the signer (borrower). */
56
+ /** Address of the signer (borrower, or PKP owner for a liquidated position). */
51
57
  borrowerAddress: string;
52
58
  /**
53
59
  * The FULL input set bound into the signature (P6/#8). The server must
@@ -57,30 +63,37 @@ export interface BtcExecuteSignedEnvelope {
57
63
  utxos: BtcExecuteInput[];
58
64
  }
59
65
  /**
60
- * Build the quantum-aligned timestamp + EIP-191 signature the
61
- * `/api/lit/pending-withdrawals/execute` endpoint expects.
62
- *
63
- * Audit CRIT-1: the signed message now binds `targetAddress` and `targetAmount`
64
- * in addition to the UTXO identifier. The previous shape bound only the UTXO,
65
- * delegating destination/amount enforcement to the Lit Action's on-chain
66
- * `getAuthorizedSpends` lookup. That single defense was reliable in practice
67
- * but the user-signed message is now self-describing — the Lit Action
68
- * cross-checks the user-signed values against the on-chain authorizer record
69
- * and refuses any disagreement, so a bug or future loosening of one side does
70
- * not silently redirect funds.
66
+ * Where the on-chain spend record comes from: a chain reader (the builder
67
+ * reads `BTCSpendAuthorizer.getAuthorizedSpends` itself) or a record the
68
+ * caller already read from the chain. Exactly one.
69
+ */
70
+ export type AuthorizedSpendSource = {
71
+ provider: ContractRunner;
72
+ authorizedSpend?: never;
73
+ } | {
74
+ authorizedSpend: AuthorizedSpendRecord;
75
+ provider?: never;
76
+ };
77
+ /**
78
+ * Build and sign the envelope the `/api/lit/pending-withdrawals/execute`
79
+ * endpoint expects.
71
80
  *
72
- * Caller passes the UTXO satoshis (from Phase 1 result) AND the user-authorized
73
- * destination + amount; this helper returns everything the server needs to
74
- * delegate to the BTC signer LIT Action.
81
+ * The destination and total come from the ON-CHAIN `BTCSpendAuthorizer`
82
+ * record for the outpoint, never from the caller: the borrower's wallet
83
+ * displays them, and the Lit Action builds the identical typed message from
84
+ * the same record. Caller-supplied `targetAddress` / `targetAmount` (e.g. from
85
+ * a server's pending-withdrawals list) are ASSERTIONS — a disagreement with
86
+ * the record throws before anything is signed.
75
87
  *
76
- * Wire-compat: this is a coordinated breaking change — the new SDK signatures
77
- * are not accepted by an older btc-transaction-signer Lit Action, and the new
78
- * Lit Action does not accept older SDK signatures. Deploy together.
88
+ * The stamp is the current quantum start; the signer accepts the current or
89
+ * previous quantum, so a borrower has 60–120 s to read and approve.
79
90
  */
80
- export declare function buildBtcExecuteEnvelope(params: BtcExecuteSignParams & {
91
+ export declare function buildBtcExecuteEnvelope(params: BtcExecuteSignParams & AuthorizedSpendSource & {
81
92
  satoshis: number;
82
- targetAddress: string;
83
- targetAmount: number;
93
+ /** Expected destination; must equal the on-chain record. */
94
+ targetAddress?: string;
95
+ /** Expected total in sats; must equal the on-chain record. */
96
+ targetAmount?: number;
84
97
  /**
85
98
  * FULL input set the phase-2 signer may spend (P6/#8). Omit for the
86
99
  * common single-UTXO withdrawal — it defaults to exactly the authorized
@@ -8,3 +8,4 @@ export { REQUEST_TIMEOUT_MS, LitOpsHttpError, LitOpsTransportError, fetchWithTim
8
8
  export { signAndBroadcast, type SignGuardSigner } from "./sign-and-broadcast";
9
9
  export { computeExtendFeeUpperBound, type ExtendFeeBound } from "./extend-fee-bound";
10
10
  export { buildPsmApproveTx, buildPsmSwapTx, buildPsmRedeemTx, planPsmExchange, executePsmPlan, psmExchangeReadsFromProvider, type PsmUnsignedTx, type PsmLeg, type PsmExchangeAddresses, type PsmExchangeReads, type PsmExchangePlan, } from "./psm-exchange";
11
+ export { assertSignableTypedData, SIGNABLE_TYPED_DATA, TypedDataRefused, } from "./typed-data-guard";
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Typed-data allowlist — the check between an EIP-712 request and a
3
+ * borrower's wallet (A-2 plan, Step 8).
4
+ *
5
+ * A typed-data signature is only as safe as the struct it covers. Without a
6
+ * guard, any party that can put a request in front of the signer (a hostile
7
+ * server, a prompt-injected tool call) could ask for an EIP-2612 `Permit` or
8
+ * any other contract's typed data, and the borrower would see a well-formed
9
+ * prompt for something the protocol never asks for. This guard admits ONLY
10
+ * the typed requests Diamond Hands clients make, each pinned to its own
11
+ * domain, exact ordered field list and exact message keys, and returns the
12
+ * human label a signing card should show for it.
13
+ *
14
+ * Inventory (2026-10-19, every typed request a connector / CLI signer is
15
+ * asked for): the server login `DiamondHands` (three field-list variants:
16
+ * base, `audience`, `statement`+`audience`; `sdk/src/utils/eip712-login.ts`),
17
+ * the four loan `*Request` types under the LoanOperationsManager domain
18
+ * (`sdk/src/utils/borrower-authorization-712.ts`), and the two BTC-custody
19
+ * `*Request` types under the BTCSpendAuthorizer domain
20
+ * (`sdk/src/utils/btc-spend-request-712.ts`).
21
+ *
22
+ * PURE: imports only `ethers` and the address table
23
+ * (`scripts/check-sign-guard-imports.mjs`), so the field lists are declared
24
+ * here and `tests/shared/unit/sign-guard/typed-data-guard.test.ts` holds them
25
+ * in lockstep with the modules that build the requests.
26
+ */
27
+ type Fields = ReadonlyArray<{
28
+ readonly name: string;
29
+ readonly type: string;
30
+ }>;
31
+ type DomainKind = "login" | "loan-operations" | "btc-spend-authorizer";
32
+ interface AllowedType {
33
+ readonly label: string;
34
+ readonly domain: DomainKind;
35
+ /** Accepted field lists, each exact and ordered. */
36
+ readonly variants: ReadonlyArray<Fields>;
37
+ }
38
+ /**
39
+ * Every typed request a Diamond Hands client may put in front of a wallet.
40
+ * Deep-frozen: it is exported (tests and consumers read it), and a writable
41
+ * export would let any caller widen the guard's policy.
42
+ */
43
+ export declare const SIGNABLE_TYPED_DATA: Readonly<Record<string, AllowedType>>;
44
+ /** Refusal for a typed request outside the allowlist; the wallet is never asked. */
45
+ export declare class TypedDataRefused extends Error {
46
+ constructor(reason: string);
47
+ }
48
+ /**
49
+ * Admit a typed-data request or throw `TypedDataRefused`.
50
+ *
51
+ * Checks, all required: the primary type `TypedDataEncoder.getPrimaryType`
52
+ * derives equals the one sent; it is on the allowlist and is the ONLY struct
53
+ * (besides an `EIP712Domain` entry); its field list is one allowed variant,
54
+ * exact and ordered; the domain has exactly the keys its kind allows (login:
55
+ * name, version, chainId; protocol: + verifyingContract; never `salt`), the
56
+ * expected name and version, `chainId` equal to the session's as a number,
57
+ * and the pinned contract for that kind; the message has exactly the field
58
+ * names as keys.
59
+ *
60
+ * @returns the human action label for the signing card.
61
+ */
62
+ export declare function assertSignableTypedData(chainId: number, domain: Record<string, unknown>, types: Record<string, ReadonlyArray<{
63
+ name: string;
64
+ type: string;
65
+ }>>, primaryType: string, message: Record<string, unknown>): {
66
+ label: string;
67
+ };
68
+ export {};
@@ -1,15 +1,20 @@
1
1
  /**
2
- * Borrower authorization envelope for the btc-utxo-invalidator Lit Action —
3
- * the SDK-side mirror of `AuthorizationModule.verifyRecoveryAuthorization`
4
- * (lit-actions/src/modules/authorization.module.ts:539). Field order and
5
- * types MUST match byte-for-byte; the action recomputes this hash and
6
- * EIP-191-recovers the borrower/delegate from it.
2
+ * Borrower authorization envelope for the btc-utxo-invalidator Lit Action.
7
3
  *
8
- * NOTE: the lit-actions side hashes with ethers v5 `solidityKeccak256`, this
9
- * side with v6 `solidityPackedKeccak256` — identical packed encoding; the
10
- * unit test asserts byte-equality of the preimages so drift cannot sneak in.
4
+ * Since A-2 (EIP712_DOMAIN_SPEC.md §3.8) the borrower signs an EIP-712
5
+ * `InvalidateStaleSpendRequest` over the on-chain record's outpoint, which
6
+ * the wallet can display, instead of `personal_sign` over a 32-byte hash.
7
+ *
8
+ * `buildInvalidateMessageHash` is kept: it is the legacy packed hash the Lit
9
+ * Action still accepts until the legacy arm is switched off, and the
10
+ * cross-package parity suites pin it. Field order and types MUST match
11
+ * `AuthorizationModule.verifyRecoveryAuthorization`
12
+ * (lit-actions/src/modules/authorization.module.ts) byte-for-byte; the
13
+ * lit-actions side hashes with ethers v5 `solidityKeccak256`, this side with
14
+ * v6 `solidityPackedKeccak256` — identical packed encoding.
11
15
  */
12
- import { type Signer } from "ethers";
16
+ import { type ContractRunner, type Signer } from "ethers";
17
+ import { type AuthorizedSpendRecord } from "./btc-spend-request-712";
13
18
  /** Must equal RECOVERY_INVALIDATE_ACTION_STRING in lit-actions constants. */
14
19
  export declare const RECOVERY_INVALIDATE_ACTION = "btc-utxo-invalidator";
15
20
  export interface InvalidateStaleSpendEnvelope {
@@ -20,10 +25,11 @@ export interface InvalidateStaleSpendEnvelope {
20
25
  timestamp: number;
21
26
  chainId: number;
22
27
  action: typeof RECOVERY_INVALIDATE_ACTION;
28
+ /** EIP-712 `InvalidateStaleSpendRequest` signature. */
23
29
  signature: string;
24
30
  borrowerAddress: string;
25
31
  }
26
- /** The exact packed hash `verifyRecoveryAuthorization` recomputes in-TEE. */
32
+ /** The legacy packed hash `verifyRecoveryAuthorization` recomputes in-TEE. */
27
33
  export declare function buildInvalidateMessageHash(params: {
28
34
  positionId: string;
29
35
  timestamp: number;
@@ -32,10 +38,11 @@ export declare function buildInvalidateMessageHash(params: {
32
38
  utxoVout: number;
33
39
  }): string;
34
40
  /**
35
- * Build + sign the invalidate envelope with a quantum-aligned timestamp.
36
- * The signature is scoped to exactly one (positionId, utxoTxid, utxoVout)
37
- * recovery on one chain and cannot be replayed as any other authorization
38
- * (the action string is pinned into the hash).
41
+ * Build + sign the invalidate envelope. The outpoint the wallet displays is
42
+ * the on-chain record's: the builder reads it (or takes a record the caller
43
+ * already read from the chain) and refuses an outpoint with no record. The
44
+ * signature is scoped to exactly one (positionId, outpoint) recovery on one
45
+ * chain, and its primary type cannot be replayed as any other authorization.
39
46
  */
40
47
  export declare function buildInvalidateStaleSpendEnvelope(params: {
41
48
  positionId: string;
@@ -43,4 +50,10 @@ export declare function buildInvalidateStaleSpendEnvelope(params: {
43
50
  utxoVout: number;
44
51
  chainId: number;
45
52
  signer: Signer;
46
- }): Promise<InvalidateStaleSpendEnvelope>;
53
+ } & ({
54
+ provider: ContractRunner;
55
+ authorizedSpend?: never;
56
+ } | {
57
+ authorizedSpend: AuthorizedSpendRecord;
58
+ provider?: never;
59
+ })): Promise<InvalidateStaleSpendEnvelope>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gvnrdao/dh-sdk",
3
- "version": "0.0.347",
3
+ "version": "0.0.348",
4
4
  "description": "TypeScript SDK for Diamond Hands Protocol",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -77,9 +77,10 @@
77
77
  "test:watch": "jest --watch",
78
78
  "test:coverage": "jest --coverage",
79
79
  "authorize:pkp": "node scripts/authorize-pkp-actions.js",
80
- "lint": "eslint src --ext .ts && npm run lint:server-boundary && npm run lint:sign-guard",
80
+ "lint": "eslint src --ext .ts && npm run lint:server-boundary && npm run lint:sign-guard && npm run lint:no-blind-sign",
81
81
  "lint:server-boundary": "node scripts/check-pkp-mint-server-imports.mjs",
82
82
  "lint:sign-guard": "node scripts/check-sign-guard-imports.mjs",
83
+ "lint:no-blind-sign": "node scripts/check-no-blind-borrower-sign.mjs",
83
84
  "clean": "rm -rf dist && rm -rf browser/dist",
84
85
  "gen:contract-errors": "node scripts/gen-contract-errors.mjs"
85
86
  },
@@ -100,8 +101,8 @@
100
101
  },
101
102
  "sideEffects": false,
102
103
  "dependencies": {
103
- "@gvnrdao/dh-lit-actions": "^0.0.324",
104
- "@gvnrdao/dh-lit-ops": "^0.0.321",
104
+ "@gvnrdao/dh-lit-actions": "^0.0.325",
105
+ "@gvnrdao/dh-lit-ops": "^0.0.322",
105
106
  "@noble/hashes": "^1.5.0",
106
107
  "axios": "^1.17.0",
107
108
  "bech32": "^2.0.0",