@gvnrdao/dh-sdk 0.0.320 → 0.0.323
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/397.browser.js +2 -0
- package/browser/dist/397.browser.js.LICENSE.txt +1 -0
- package/browser/dist/833.browser.js +2 -0
- package/browser/dist/833.browser.js.LICENSE.txt +1 -0
- package/browser/dist/browser.js +1 -1
- package/browser/dist/index.d.ts +8 -0
- package/browser/dist/index.d.ts.map +1 -0
- package/browser/dist/index.js +25 -0
- package/dist/constants/chunks/deployment-addresses.d.ts +2 -0
- package/dist/constants/chunks/network-configs.d.ts +2 -0
- package/dist/contracts/typechain-contracts/factories/src/agent/AgentDelegationRegistry__factory.d.ts +100 -1
- package/dist/contracts/typechain-contracts/src/agent/AgentDelegationRegistry.d.ts +101 -3
- package/dist/deployments.js +29 -3
- package/dist/deployments.mjs +29 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +765 -64
- package/dist/index.mjs +764 -64
- package/dist/interfaces/chunks/config.i.d.ts +2 -0
- package/dist/modules/diamond-hands-sdk.d.ts +207 -0
- package/dist/safe-delegation.d.ts +15 -0
- package/dist/safe-delegation.js +838 -0
- package/dist/safe-delegation.mjs +810 -0
- package/dist/utils/safe-agent-delegation.utils.d.ts +154 -0
- package/dist/utils/withdrawal-reconciliation.utils.d.ts +40 -14
- package/package.json +6 -1
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Is this Safe delegated, and to which agent?" — the single implementation.
|
|
3
|
+
*
|
|
4
|
+
* Every consumer that needs to know whether a multi-sig Safe can act through an
|
|
5
|
+
* AgentModule asks this: the frontend gate, the CLI, MCP. It was previously
|
|
6
|
+
* reimplemented per-consumer, and this session shows what that costs — the same
|
|
7
|
+
* invariant lived in two places twice, and both times only one copy was fixed
|
|
8
|
+
* when it turned out to be wrong.
|
|
9
|
+
*
|
|
10
|
+
* ── WHAT MAKES A DELEGATION REAL ───────────────────────────────────────────
|
|
11
|
+
* A module counts only when ALL of these hold:
|
|
12
|
+
* - it is enabled on the Safe (`getModulesPaginated`)
|
|
13
|
+
* - `safe()` is that Safe and `positionManager()` is this chain's
|
|
14
|
+
* - `AgentModuleFactory.isFromFactory(module)` claims it
|
|
15
|
+
*
|
|
16
|
+
* That last one is not decoration. The agent-module-executor Lit Action refuses
|
|
17
|
+
* at its second guard to sign through any module the factory disowns, so an
|
|
18
|
+
* unprovenanced module is one nothing can ever use. Measured on the Sepolia test
|
|
19
|
+
* Safe (2026-08-05): it carried a pre-factory module from an old e2e harness,
|
|
20
|
+
* correctly bound and pointing at an EOA agent nothing could sign with — and
|
|
21
|
+
* without the provenance check it read as a working delegation.
|
|
22
|
+
*
|
|
23
|
+
* ── WHY THE ANSWER IS TRI-STATE ────────────────────────────────────────────
|
|
24
|
+
* `unknown` is not a rounding error, it is the whole safety property. Callers
|
|
25
|
+
* act on "not-delegated" by BLOCKING loan creation, so a check that merely
|
|
26
|
+
* failed to run must never produce it. The distinctions below are load-bearing
|
|
27
|
+
* and were each learned from a live failure:
|
|
28
|
+
*
|
|
29
|
+
* - `safe()` unreadable → unknown. Every module here answers it, so a
|
|
30
|
+
* failure means we could not read, not that the
|
|
31
|
+
* module is wrong.
|
|
32
|
+
* - `positionManager()` absent → EVIDENCE. It is the discriminator (absent on
|
|
33
|
+
* VaultProvisionerModule), so a revert is a real
|
|
34
|
+
* answer: not an AgentModule.
|
|
35
|
+
* - provenance unreadable → unknown.
|
|
36
|
+
* - no factory on this chain → unknown, never "not-delegated". Mainnet has
|
|
37
|
+
* no AgentModuleFactory, and reporting that as
|
|
38
|
+
* not-delegated blocked vault creation behind a
|
|
39
|
+
* ceremony that cannot run there at all.
|
|
40
|
+
*
|
|
41
|
+
* ── NOT A SUBSTITUTE FOR THE LIT ACTION'S OWN CHECKS ───────────────────────
|
|
42
|
+
* The executor re-derives provenance and the registry binding itself. It must:
|
|
43
|
+
* it is the enforcement point and cannot trust anything a caller computed.
|
|
44
|
+
* Sharing this code with it would weaken it, so it stays independent.
|
|
45
|
+
*/
|
|
46
|
+
import { type Provider } from "ethers";
|
|
47
|
+
export type SafeDelegationStatus = "delegated" | "not-delegated" | "unknown";
|
|
48
|
+
export interface SafeAgentDelegation {
|
|
49
|
+
/** Lowercased Safe the answer describes. */
|
|
50
|
+
safeAddress: string;
|
|
51
|
+
/** Chain the answer describes — a module on one chain says nothing about another. */
|
|
52
|
+
chainId: number;
|
|
53
|
+
status: SafeDelegationStatus;
|
|
54
|
+
/** The AgentModule, or null when there isn't one (or we could not tell). */
|
|
55
|
+
moduleAddress: string | null;
|
|
56
|
+
/**
|
|
57
|
+
* The agent EOA that SIGNS AND PAYS for delegated operations — read from the
|
|
58
|
+
* module, not the registry. They should agree, but the module's `agent()` is
|
|
59
|
+
* who `onlyAgent` actually admits, so it is the truth about who needs gas.
|
|
60
|
+
*/
|
|
61
|
+
agentAddress: string | null;
|
|
62
|
+
/** Registry expiry for the Safe's agent record, when readable. */
|
|
63
|
+
validUntil: number | null;
|
|
64
|
+
/** True when the Safe's module list exceeded one page — the answer may be partial. */
|
|
65
|
+
truncated: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the Safe's agent delegation from chain state.
|
|
69
|
+
*
|
|
70
|
+
* `provider` is the CALLER'S, deliberately: it must read the chain the caller is
|
|
71
|
+
* actually on. Resolving it internally would let an SDK configured for one chain
|
|
72
|
+
* answer about another, which is the exact confusion that made a live Sepolia
|
|
73
|
+
* delegation look absent while the wallet had silently defaulted to mainnet.
|
|
74
|
+
*
|
|
75
|
+
* Never throws — every failure resolves to `unknown`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function getSafeAgentDelegation(params: {
|
|
78
|
+
safeAddress: string;
|
|
79
|
+
chainId: number;
|
|
80
|
+
provider: Provider;
|
|
81
|
+
}): Promise<SafeAgentDelegation>;
|
|
82
|
+
/** One entry in a Safe transaction batch. */
|
|
83
|
+
export interface SafeDelegationCall {
|
|
84
|
+
to: string;
|
|
85
|
+
data: string;
|
|
86
|
+
value: string;
|
|
87
|
+
}
|
|
88
|
+
export interface SafeAgentDelegationDisablePlan {
|
|
89
|
+
/**
|
|
90
|
+
* The calls, in execution order. They must land ATOMICALLY — see below for
|
|
91
|
+
* why a half-applied revocation is worse than none.
|
|
92
|
+
*/
|
|
93
|
+
calls: SafeDelegationCall[];
|
|
94
|
+
/** The module being removed. */
|
|
95
|
+
moduleAddress: string;
|
|
96
|
+
/** The agent being revoked, when the module could name it. */
|
|
97
|
+
agentAddress: string | null;
|
|
98
|
+
/** The predecessor `disableModule` needs — SENTINEL when the module heads the list. */
|
|
99
|
+
prevModule: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Build the calls that undo a Safe's agent delegation.
|
|
103
|
+
*
|
|
104
|
+
* THE COUNTERPART TO {@link getSafeAgentDelegation}, and here rather than in any
|
|
105
|
+
* one consumer for the reason that reader is here: it needs the same module
|
|
106
|
+
* enumeration, and a second copy of that walk is how the two drift. The
|
|
107
|
+
* frontend, the CLI and MCP all revoke the same way or none of them can be
|
|
108
|
+
* trusted to.
|
|
109
|
+
*
|
|
110
|
+
* ── WHY TWO CALLS ──────────────────────────────────────────────────────────
|
|
111
|
+
* Neither alone is a full revocation:
|
|
112
|
+
*
|
|
113
|
+
* - `revokeAgent` flips the registry record to Revoked, which is what the Lit
|
|
114
|
+
* Action's `can*` gates read. After it, no mint/repay/renew/withdraw can be
|
|
115
|
+
* authorized for this agent.
|
|
116
|
+
* - `disableModule` removes the on-chain execution path, and it is the one
|
|
117
|
+
* that matters for the calls needing NO validator attestation: with the
|
|
118
|
+
* module still enabled, a live agent key can forward `approve` and
|
|
119
|
+
* `setPositionDelegate` through the Safe whatever the registry says
|
|
120
|
+
* (`AgentModule.execute` checks only `msg.sender == agent` and its own
|
|
121
|
+
* selector whitelist).
|
|
122
|
+
*
|
|
123
|
+
* Submit them as ONE batch. Revoking the record while leaving the module
|
|
124
|
+
* enabled reads as "not delegated" everywhere in the UI while those unattested
|
|
125
|
+
* paths stay open — the worst of both states.
|
|
126
|
+
*
|
|
127
|
+
* ── WHAT IT DOES NOT UNDO ──────────────────────────────────────────────────
|
|
128
|
+
* A `setPositionDelegate` the agent already installed on PositionDelegateRegistry
|
|
129
|
+
* SURVIVES both calls (vigil C-43). Clearing that is per-position and is not
|
|
130
|
+
* part of this plan.
|
|
131
|
+
*
|
|
132
|
+
* THROWS, unlike `getSafeAgentDelegation` — the asymmetry is deliberate. That
|
|
133
|
+
* one answers a question and "unknown" is a usable answer. This one produces a
|
|
134
|
+
* transaction the Safe's owners must gather to sign, so a guess costs them an
|
|
135
|
+
* approval round on something that must revert. Every failure is loud:
|
|
136
|
+
*
|
|
137
|
+
* - the chain has no AgentDelegationRegistry;
|
|
138
|
+
* - the Safe's module list cannot be read (never guess a predecessor: a wrong
|
|
139
|
+
* `prevModule` is Safe's GS103, AFTER the owners have signed);
|
|
140
|
+
* - the module is not enabled on the Safe (the caller is acting on a stale
|
|
141
|
+
* verdict).
|
|
142
|
+
*
|
|
143
|
+
* @param params.moduleAddress Optional. Supply the module from a verdict already
|
|
144
|
+
* on screen so the transaction describes what the user was looking at;
|
|
145
|
+
* omit it and the delegation is resolved here.
|
|
146
|
+
*/
|
|
147
|
+
export declare function buildSafeAgentDelegationDisableCalls(params: {
|
|
148
|
+
safeAddress: string;
|
|
149
|
+
chainId: number;
|
|
150
|
+
provider: Provider;
|
|
151
|
+
moduleAddress?: string;
|
|
152
|
+
/** bytes32 recorded against the revocation. Defaults to `bytes32("user-disabled")`. */
|
|
153
|
+
reason?: string;
|
|
154
|
+
}): Promise<SafeAgentDelegationDisablePlan>;
|
|
@@ -3,23 +3,36 @@
|
|
|
3
3
|
* (`BTCSpendAuthorizer.getAuthorizedSpends`) against BITCOIN truth before any
|
|
4
4
|
* UI offers "Execute" or any server invokes the TEE signer.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
6
|
+
* `satoshis` semantics — AGGREGATE, by design: the authorized `satoshis` is
|
|
7
|
+
* the TOTAL input value the consolidation will consume across the vault's
|
|
8
|
+
* confirmed UTXO set (btc-withdrawal.ts signs `vaultSatoshis = newCollateral +
|
|
9
|
+
* totalDeduction`), while `(txid, vout)` pins ONE representative outpoint.
|
|
10
|
+
* `BTCSpendAuthorizer.sol` enforces `targetAmount < satoshis`
|
|
11
|
+
* (TargetMustLeaveFeeRoom) — a withdrawal near the vault's full balance could
|
|
12
|
+
* never be authorized against a single small outpoint's value, so the
|
|
13
|
+
* aggregate is structurally required. NEVER compare `satoshis` to the
|
|
14
|
+
* representative outpoint's own value: for any vault holding more than one
|
|
15
|
+
* UTXO they legitimately differ (incident 2026-08-07, position 0x992d5c…, a
|
|
16
|
+
* 4-UTXO vault whose valid authorization was misclassified as corrupt).
|
|
17
|
+
*
|
|
18
|
+
* What IS invariant, and what this module verifies:
|
|
19
|
+
* - the funding tx exists and is confirmed (else UNFUNDED);
|
|
20
|
+
* - the representative `vout` exists on that tx (else CORRUPT);
|
|
21
|
+
* - the output at `vout` pays the VAULT address, when the caller supplies it
|
|
22
|
+
* (else CORRUPT — this is the real 2026-07-20 (vout, satoshis)-decoupling
|
|
23
|
+
* bug class: a vout pointing at an output the vault does not own);
|
|
24
|
+
* - `targetAmount < satoshis`, mirroring the contract invariant (else CORRUPT);
|
|
25
|
+
* - the outpoint's spend state (EXECUTED / SPENT_MISMATCH / EXECUTABLE).
|
|
13
26
|
*
|
|
14
27
|
* Statuses:
|
|
15
|
-
* - EXECUTABLE — outpoint confirmed, unspent,
|
|
28
|
+
* - EXECUTABLE — outpoint confirmed, unspent, record coherent → offer Execute.
|
|
16
29
|
* - EXECUTED — outpoint spent by a tx that pays the authorized target →
|
|
17
30
|
* auto-clear, show `spendingTxid` as the completion proof.
|
|
18
31
|
* - SPENT_MISMATCH — outpoint spent but the spending tx pays the target
|
|
19
32
|
* nothing → unexecutable; recoverStaleSpend clears it.
|
|
20
|
-
* - CORRUPT — authorization contradicts the chain (
|
|
21
|
-
*
|
|
22
|
-
*
|
|
33
|
+
* - CORRUPT — authorization contradicts the chain (vout out of range,
|
|
34
|
+
* output not owned by the vault, or targetAmount ≥
|
|
35
|
+
* satoshis) → operator recovery (admin clearing path);
|
|
23
36
|
* never Execute.
|
|
24
37
|
* - UNFUNDED — funding tx unknown/unconfirmed → wait; no Execute yet.
|
|
25
38
|
*
|
|
@@ -43,7 +56,11 @@ export interface ReconciledWithdrawal<T extends AuthorizedSpendLike = Authorized
|
|
|
43
56
|
status: PendingWithdrawalStatus;
|
|
44
57
|
/** Human-readable, single-sentence explanation of the classification. */
|
|
45
58
|
reason: string;
|
|
46
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* Real value of the referenced outpoint, when the funding tx is known.
|
|
61
|
+
* Diagnostic only — for a multi-UTXO vault it is legitimately smaller than
|
|
62
|
+
* the aggregate `satoshis`.
|
|
63
|
+
*/
|
|
47
64
|
onChainOutputValue?: number;
|
|
48
65
|
/** The verified spending tx, for EXECUTED / SPENT_MISMATCH. */
|
|
49
66
|
spendingTxid?: string;
|
|
@@ -61,5 +78,14 @@ export type HttpGetJson = (url: string) => Promise<{
|
|
|
61
78
|
body: unknown;
|
|
62
79
|
}>;
|
|
63
80
|
export declare const defaultHttpGetJson: HttpGetJson;
|
|
64
|
-
/**
|
|
65
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Classify one authorized spend against the esplora at `esploraBaseUrl`.
|
|
83
|
+
*
|
|
84
|
+
* `opts.vaultAddress` enables the vault-ownership check (the strongest guard
|
|
85
|
+
* against the (vout, satoshis)-decoupling bug class). When absent the check is
|
|
86
|
+
* SKIPPED, not assumed — production callers (lit-ops-server, frontend) must
|
|
87
|
+
* supply it.
|
|
88
|
+
*/
|
|
89
|
+
export declare function reconcileAuthorizedSpend<T extends AuthorizedSpendLike>(spend: T, esploraBaseUrl: string, httpGetJson?: HttpGetJson, opts?: {
|
|
90
|
+
vaultAddress?: string;
|
|
91
|
+
}): Promise<ReconciledWithdrawal<T>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gvnrdao/dh-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.323",
|
|
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",
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
"types": "./dist/deployments.d.ts",
|
|
30
30
|
"import": "./dist/deployments.mjs",
|
|
31
31
|
"require": "./dist/deployments.js"
|
|
32
|
+
},
|
|
33
|
+
"./safe-delegation": {
|
|
34
|
+
"types": "./dist/safe-delegation.d.ts",
|
|
35
|
+
"import": "./dist/safe-delegation.mjs",
|
|
36
|
+
"require": "./dist/safe-delegation.js"
|
|
32
37
|
}
|
|
33
38
|
},
|
|
34
39
|
"files": [
|