@mysten-incubation/hashi 0.0.1

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/bitcoin.d.mts +140 -0
  3. package/dist/bitcoin.mjs +272 -0
  4. package/dist/client.d.mts +318 -0
  5. package/dist/client.mjs +522 -0
  6. package/dist/constants.mjs +60 -0
  7. package/dist/contracts/hashi/bitcoin_state.mjs +28 -0
  8. package/dist/contracts/hashi/committee.mjs +40 -0
  9. package/dist/contracts/hashi/committee_set.mjs +34 -0
  10. package/dist/contracts/hashi/config.mjs +28 -0
  11. package/dist/contracts/hashi/config_value.mjs +21 -0
  12. package/dist/contracts/hashi/deposit.mjs +67 -0
  13. package/dist/contracts/hashi/deposit_queue.mjs +33 -0
  14. package/dist/contracts/hashi/deps/sui/bag.mjs +42 -0
  15. package/dist/contracts/hashi/deps/sui/balance.mjs +20 -0
  16. package/dist/contracts/hashi/deps/sui/group_ops.mjs +16 -0
  17. package/dist/contracts/hashi/deps/sui/object_bag.mjs +25 -0
  18. package/dist/contracts/hashi/deps/sui/package.mjs +26 -0
  19. package/dist/contracts/hashi/deps/sui/table.mjs +37 -0
  20. package/dist/contracts/hashi/deps/sui/vec_map.mjs +36 -0
  21. package/dist/contracts/hashi/deps/sui/vec_set.mjs +25 -0
  22. package/dist/contracts/hashi/hashi.mjs +29 -0
  23. package/dist/contracts/hashi/proposals.mjs +18 -0
  24. package/dist/contracts/hashi/treasury.mjs +28 -0
  25. package/dist/contracts/hashi/utxo.mjs +56 -0
  26. package/dist/contracts/hashi/utxo_pool.mjs +35 -0
  27. package/dist/contracts/hashi/withdraw.mjs +91 -0
  28. package/dist/contracts/hashi/withdrawal_queue.mjs +131 -0
  29. package/dist/contracts/utils/index.d.mts +8 -0
  30. package/dist/contracts/utils/index.mjs +118 -0
  31. package/dist/errors.d.mts +118 -0
  32. package/dist/errors.mjs +118 -0
  33. package/dist/index.d.mts +5 -0
  34. package/dist/index.mjs +5 -0
  35. package/dist/types.d.mts +137 -0
  36. package/dist/util.mjs +50 -0
  37. package/package.json +64 -0
@@ -0,0 +1,318 @@
1
+ import { RawTransactionArgument } from "./contracts/utils/index.mjs";
2
+ import { BitcoinNetwork, CancelWithdrawalParams, DepositParams, GovernanceConfig, HashiClientOptions, SuiNetwork, TransactionHistoryItem, UtxoId, UtxoUsageResult, WithdrawalParams } from "./types.mjs";
3
+ import * as _mysten_sui_transactions0 from "@mysten/sui/transactions";
4
+ import { Transaction } from "@mysten/sui/transactions";
5
+ import { ClientWithCoreApi, SuiClientTypes } from "@mysten/sui/client";
6
+ import { Signer } from "@mysten/sui/cryptography";
7
+
8
+ //#region src/client.d.ts
9
+ declare function hashi<const Name = "hashi">({
10
+ name,
11
+ ...options
12
+ }: HashiClientOptions<Name>): {
13
+ name: Name;
14
+ register: (client: ClientWithCoreApi) => HashiClient;
15
+ };
16
+ /**
17
+ * User-facing SDK client for the Hashi protocol. Constructed via the
18
+ * `hashi({...})` factory and attached to any Sui client via `$extend`:
19
+ *
20
+ * ```ts
21
+ * const client = new SuiGrpcClient({ ... }).$extend(hashi({ network: "devnet" }));
22
+ * const result = await client.hashi.deposit({ signer, ... });
23
+ * ```
24
+ *
25
+ * **Direct methods (`deposit`, `withdraw`) sign and execute transactions** on
26
+ * behalf of the caller — pass a `Signer` and receive the execution result.
27
+ * For composable flows (bundling into a larger PTB, sponsored transactions,
28
+ * dry-run/simulation), use the `tx.*` builders instead; they return unsigned
29
+ * `Transaction` objects and leave signing to the caller.
30
+ */
31
+ declare class HashiClient {
32
+ #private;
33
+ constructor({
34
+ client,
35
+ network,
36
+ hashiObjectId,
37
+ packageId,
38
+ bitcoinNetwork
39
+ }: {
40
+ client: ClientWithCoreApi;
41
+ network: SuiNetwork;
42
+ hashiObjectId?: string;
43
+ packageId?: string;
44
+ bitcoinNetwork?: BitcoinNetwork;
45
+ });
46
+ /**
47
+ * Generates a unique Bitcoin P2TR deposit address for a Sui address.
48
+ *
49
+ * Fetches the MPC committee public key from on-chain, derives a child key
50
+ * using the Sui address, and builds a taproot script-path address.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const btcAddress = await client.hashi.generateDepositAddress({
55
+ * suiAddress: signer.toSuiAddress(),
56
+ * });
57
+ * ```
58
+ */
59
+ generateDepositAddress({
60
+ suiAddress,
61
+ bitcoinNetwork
62
+ }: {
63
+ /** The Sui address to generate a deposit address for (hex string with 0x prefix). */suiAddress: string; /** Override the default Bitcoin network for this call. */
64
+ bitcoinNetwork?: BitcoinNetwork;
65
+ }): Promise<string>;
66
+ /**
67
+ * Submit one or more Bitcoin deposits for committee confirmation, batched
68
+ * into a single Sui PTB. Signs with `signer` and submits, returning the
69
+ * execution result (`$kind: "Transaction" | "FailedTransaction"`). The
70
+ * result includes `effects` and `events` so callers can confirm
71
+ * `DepositRequestedEvent` without an extra round-trip.
72
+ *
73
+ * The method runs three preflight stages before signing:
74
+ *
75
+ * 1. **Structural validation** — `txid` and `recipient` must be
76
+ * 0x-prefixed 32-byte hex; `utxos` must be non-empty; every `vout`
77
+ * must be a non-negative u32 and unique within the call. Violations
78
+ * throw `InvalidParamsError` without any chain read.
79
+ * 2. **Pause check** — reads the governance snapshot via `view.all()`
80
+ * and throws `HashiPausedError` if `paused` is `true`. Mirrors the
81
+ * Move-side `hashi::assert_unpaused`.
82
+ * 3. **Minimum check** — every UTXO must have `amountSats ≥
83
+ * snap.bitcoinDepositMinimum`. All offenders are collected into one
84
+ * `AmountBelowMinimumError`, so callers can fix the batch in one
85
+ * round-trip. Mirrors `EBelowMinimumDeposit` in `deposit::deposit`.
86
+ *
87
+ * Both chain-reading checks (2, 3) read from the same `view.all()`
88
+ * snapshot, so validation is internally consistent. Chain state can still
89
+ * drift between the snapshot and execution — the Move side re-asserts
90
+ * both invariants, so a genuine race simply aborts the tx.
91
+ *
92
+ * For composable flows (sponsored tx, dry-run, or bundling into a larger
93
+ * PTB), use `tx.deposit(params)` instead — it returns the unsigned
94
+ * `Transaction` and leaves signing to the caller.
95
+ */
96
+ deposit({
97
+ signer,
98
+ ...params
99
+ }: DepositParams & {
100
+ /** Signs and pays for the resulting transaction. The signer's address becomes the tx sender. */signer: Signer;
101
+ }): Promise<SuiClientTypes.TransactionResult<{
102
+ effects: true;
103
+ events: true;
104
+ }>>;
105
+ /**
106
+ * Submit a BTC withdrawal request for committee processing. Burns `hBTC`
107
+ * from the signer's balance and enqueues a request for the committee to
108
+ * send `amountSats` to `bitcoinAddress` on the Bitcoin network. Signs
109
+ * with `signer` and submits, returning the execution result including
110
+ * `effects` and `events` (`WithdrawalRequestedEvent`).
111
+ *
112
+ * The method runs three preflight stages before signing:
113
+ *
114
+ * 1. **Address decoding** — `bitcoinAddress` is decoded as bech32 (v0
115
+ * P2WPKH, 20 bytes) or bech32m (v1 P2TR, 32 bytes) via
116
+ * `bitcoinAddressToWitnessProgram`. The HRP must match the client's
117
+ * configured Bitcoin network. Violations throw
118
+ * `InvalidBitcoinAddressError` with a structured `code` so callers
119
+ * can distinguish a typo from a wrong-network mistake.
120
+ * 2. **Pause check** — reads the governance snapshot via `view.all()`
121
+ * and throws `HashiPausedError` if `paused` is `true`. Mirrors the
122
+ * Move-side `hashi::assert_unpaused` in `request_withdrawal`.
123
+ * 3. **Minimum check** — `amountSats` must be ≥
124
+ * `snap.bitcoinWithdrawalMinimum`. Below-minimum throws
125
+ * `AmountBelowMinimumError` with a single violation. Mirrors
126
+ * `EBelowMinimumWithdrawal` in `withdraw::request_withdrawal`.
127
+ *
128
+ * For composable flows (sponsored tx, dry-run, or bundling into a
129
+ * larger PTB), use `tx.requestWithdrawal(options)` instead — it returns
130
+ * the unsigned `Transaction` and leaves signing to the caller.
131
+ */
132
+ requestWithdrawal({
133
+ signer,
134
+ ...params
135
+ }: WithdrawalParams & {
136
+ /** Signs and pays for the resulting transaction. The signer's address becomes the tx sender. */signer: Signer;
137
+ }): Promise<SuiClientTypes.TransactionResult<{
138
+ effects: true;
139
+ events: true;
140
+ }>>;
141
+ /**
142
+ * Cancel a pending withdrawal request and return the locked BTC to the
143
+ * signer. Signs with `signer` and submits, returning the execution result.
144
+ *
145
+ * The only client-side precondition is that `requestId` is 0x-prefixed
146
+ * 32-byte hex. All other constraints — ownership (only the original
147
+ * requester can cancel), state window (cancellable only while `Requested`
148
+ * or `Approved`, not after committee commitment), and the on-chain
149
+ * `withdrawal_cancellation_cooldown_ms` — are enforced by the Move side
150
+ * and left to abort at execution. Pre-fetching them would cost an extra
151
+ * round-trip and still race chain state.
152
+ *
153
+ * Unlike `requestWithdrawal`, no pause check is performed: the Move
154
+ * `cancel_withdrawal` function has no `assert_unpaused`, so users can
155
+ * always unwind a pending request even when the system is paused.
156
+ *
157
+ * For composable flows, use `tx.cancelWithdrawal({ requestId, recipient })`.
158
+ */
159
+ cancelWithdrawal({
160
+ signer,
161
+ requestId
162
+ }: CancelWithdrawalParams & {
163
+ /** Signs and pays for the resulting transaction. The signer's address becomes the tx sender and the recipient of the returned BTC. */signer: Signer;
164
+ }): Promise<SuiClientTypes.TransactionResult<{
165
+ effects: true;
166
+ events: true;
167
+ }>>;
168
+ tx: {
169
+ /**
170
+ * Build a transaction that submits one or more Bitcoin deposits for
171
+ * committee confirmation, batched into a single Sui PTB.
172
+ *
173
+ * A single Bitcoin funding tx can pay the same deposit address on
174
+ * multiple outputs (e.g. change + donation, or a coinjoin). Rather
175
+ * than forcing the user to submit one Sui tx per output, this method
176
+ * accepts every qualifying output and emits a dedicated Move-call
177
+ * triple per UTXO:
178
+ *
179
+ * utxo::utxo_id(txid, vout_i) → UtxoId
180
+ * utxo::utxo(utxoId, amount_i, derivationPath = recipient) → Utxo
181
+ * deposit::deposit(hashi, utxo)
182
+ *
183
+ * The triples are emitted in `params.utxos` order, so N UTXOs yield
184
+ * exactly `3 * N` PTB commands.
185
+ *
186
+ * Because all triples live in one PTB, execution is atomic: either
187
+ * every deposit is recorded, or none are (any abort — wrong minimum,
188
+ * replayed UTXO, paused system — reverts the whole transaction).
189
+ *
190
+ * All UTXOs share the `txid` because `DepositParams` has a single
191
+ * top-level `txid` field, and are credited to the same `recipient`.
192
+ *
193
+ * The `txid` is provided in **display byte order** (the form
194
+ * mempool.space and `bitcoin-cli` show); this method reverses it
195
+ * to internal byte order before recording on-chain via
196
+ * `reverseTxidBytes`. The committee verifier reads `Utxo.txid`
197
+ * as a `bitcoin::Txid`, which expects internal byte order — so
198
+ * recording display-order bytes leaves the committee searching
199
+ * for a phantom (byte-reversed) tx and the deposit never confirms.
200
+ *
201
+ * @example
202
+ * ```ts
203
+ * const tx = client.hashi.tx.deposit({
204
+ * txid: `0x${btcTxid}`,
205
+ * utxos: [
206
+ * { vout: 0, amountSats: 100_000n },
207
+ * { vout: 2, amountSats: 50_000n },
208
+ * ],
209
+ * recipient: signer.toSuiAddress(),
210
+ * });
211
+ * await client.signAndExecuteTransaction({ signer, transaction: tx });
212
+ * ```
213
+ */
214
+ deposit: (params: DepositParams) => Transaction;
215
+ /**
216
+ * Build a transaction that submits a BTC withdrawal request. Sources the
217
+ * BTC via `coinWithBalance`, unwraps it into a `Balance<BTC>`, and passes
218
+ * it to `withdraw::request_withdrawal` along with the target Bitcoin
219
+ * output address.
220
+ */
221
+ requestWithdrawal: (options: {
222
+ /** Amount in sats to withdraw. Must be ≥ the on-chain withdrawal minimum. */amount: bigint;
223
+ /**
224
+ * Target Bitcoin address as raw witness program bytes — 20 bytes for
225
+ * P2WPKH, 32 bytes for P2TR. Callers decode their own bech32(m)
226
+ * strings for now; a string-input overload may land in a follow-up.
227
+ */
228
+ bitcoinAddress: Uint8Array;
229
+ }) => Transaction;
230
+ /**
231
+ * Build a transaction that cancels a pending withdrawal request and
232
+ * returns the locked BTC to the user. Consumes the `Balance<BTC>`
233
+ * hot-potato returned by `withdraw::cancel_withdrawal` by wrapping it
234
+ * into a `Coin<BTC>` and transferring to `recipient`.
235
+ */
236
+ cancelWithdrawal: (options: {
237
+ /** The withdrawal request ID to cancel. */requestId: string;
238
+ /**
239
+ * Sui address that will receive the returned `Coin<BTC>`. Required
240
+ * because the unsigned `Transaction` does not know its sender at
241
+ * build time — the caller must pass their own address explicitly.
242
+ */
243
+ recipient: string;
244
+ }) => Transaction;
245
+ };
246
+ call: {
247
+ deposit: (options: {
248
+ utxo: RawTransactionArgument<string>;
249
+ }) => (tx: Transaction) => _mysten_sui_transactions0.TransactionResult;
250
+ requestWithdrawal: (options: {
251
+ btc: RawTransactionArgument<string>;
252
+ bitcoinAddress: RawTransactionArgument<number[]>;
253
+ }) => (tx: Transaction) => _mysten_sui_transactions0.TransactionResult;
254
+ /**
255
+ * Cancel a pending withdrawal request. Returns a `Balance<BTC>` hot potato
256
+ * that must be consumed in the same PTB (e.g. wrapped into a Coin and
257
+ * transferred back to the sender).
258
+ */
259
+ cancelWithdrawal: (options: {
260
+ requestId: RawTransactionArgument<string>;
261
+ }) => (tx: Transaction) => _mysten_sui_transactions0.TransactionResult;
262
+ };
263
+ view: {
264
+ /**
265
+ * Fetches the MPC committee's threshold public key from on-chain.
266
+ *
267
+ * This is the 33-byte compressed secp256k1 key stored in `CommitteeSet.mpc_public_key`.
268
+ * It is set after the committee completes DKG and is updated at epoch boundaries.
269
+ *
270
+ * @returns 33-byte compressed secp256k1 public key
271
+ * @throws If the MPC key is not yet available (DKG not completed)
272
+ */
273
+ mpcPublicKey: () => Promise<Uint8Array>;
274
+ /**
275
+ * Fetches all governance values in a single round-trip and returns a
276
+ * consistent snapshot. Prefer this over individual methods when you
277
+ * need 2+ values — it avoids redundant `Hashi.get` calls and gives
278
+ * you all fields from the same on-chain state.
279
+ */
280
+ all: () => Promise<GovernanceConfig>;
281
+ paused: () => Promise<boolean>; /** Floored to `DUST_RELAY_MIN_VALUE` if the on-chain value is lower. */
282
+ bitcoinDepositMinimum: () => Promise<bigint>; /** Floored to `DUST_RELAY_MIN_VALUE + 1` so `worstCaseNetworkFee` is always ≥ 1. */
283
+ bitcoinWithdrawalMinimum: () => Promise<bigint>;
284
+ bitcoinConfirmationThreshold: () => Promise<bigint>;
285
+ withdrawalCancellationCooldownMs: () => Promise<bigint>;
286
+ bitcoinChainId: () => Promise<string>; /** Alias of `bitcoinDepositMinimum`. */
287
+ depositMinimum: () => Promise<bigint>;
288
+ /**
289
+ * Worst-case Bitcoin miner fee (sats) deducted from a withdrawal.
290
+ * Derived as `bitcoinWithdrawalMinimum - DUST_RELAY_MIN_VALUE`; always ≥ 1.
291
+ */
292
+ worstCaseNetworkFee: () => Promise<bigint>;
293
+ /**
294
+ * Check whether one or more Bitcoin UTXOs already exist in the
295
+ * on-chain `UtxoPool` — either as active (confirmed deposit, not yet
296
+ * consumed) or spent (consumed by a withdrawal). Callers use this to
297
+ * detect already-used outputs before submitting a deposit.
298
+ *
299
+ * `txid` in each `UtxoId` is **display byte order**; the method
300
+ * reverses to internal byte order before encoding the on-chain key.
301
+ */
302
+ findUsedUtxos: (utxos: readonly UtxoId[]) => Promise<UtxoUsageResult[]>;
303
+ /**
304
+ * Fetch the unified transaction history (deposits + withdrawals) for
305
+ * a Sui address. Reads from `BitcoinState.user_requests` — a Table
306
+ * that maps each user address to a Bag of request IDs — then batch-
307
+ * fetches the request objects and dispatches by Move type.
308
+ *
309
+ * Deposit items include `btcTxid` / `btcVout` extracted from the
310
+ * UTXO stored on the `DepositRequest`. Withdrawal items include the
311
+ * BTC txid from the linked `WithdrawalTransaction` once the committee
312
+ * has committed one.
313
+ */
314
+ transactionHistory: (suiAddress: string) => Promise<TransactionHistoryItem[]>;
315
+ };
316
+ }
317
+ //#endregion
318
+ export { HashiClient, hashi };