@mysten-incubation/hashi 0.3.1 → 0.5.0

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.
@@ -1,7 +1,8 @@
1
1
  import { MoveStruct } from "../utils/index.mjs";
2
+ import { Config } from "./config.mjs";
2
3
  import { Bag } from "./deps/sui/bag.mjs";
3
4
  import { CommitteeSet } from "./committee_set.mjs";
4
- import { Config } from "./config.mjs";
5
+ import { Versioning } from "./versioning.mjs";
5
6
  import { Treasury } from "./treasury.mjs";
6
7
  import { Proposals } from "./proposals.mjs";
7
8
  import { bcs } from "@mysten/sui/bcs";
@@ -10,7 +11,15 @@ import { bcs } from "@mysten/sui/bcs";
10
11
  /**************************************************************
11
12
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
12
13
  **************************************************************/
13
- /** Module: hashi */
14
+ /**
15
+ * The root shared object of the bridge. `Hashi` aggregates every subsystem —
16
+ * committee set, config, versioning, treasury, governance proposals, and TOB
17
+ * certificate storage — and hangs per-chain state (e.g. `BitcoinState`) off its
18
+ * `UID` as dynamic fields. It also provides the package-wide guards (pause,
19
+ * reconfig, committee-signature verification) that entry functions in other
20
+ * modules call through, and the one-time `finish_publish` launch switch that hands
21
+ * the package `UpgradeCap` into on-chain custody.
22
+ */
14
23
  const $moduleName = "@local-pkg/hashi::hashi";
15
24
  const Hashi = new MoveStruct({
16
25
  name: `${$moduleName}::Hashi`,
@@ -18,6 +27,7 @@ const Hashi = new MoveStruct({
18
27
  id: bcs.Address,
19
28
  committee_set: CommitteeSet,
20
29
  config: Config,
30
+ versioning: Versioning,
21
31
  treasury: Treasury,
22
32
  proposals: Proposals,
23
33
  tob: Bag,
@@ -0,0 +1,56 @@
1
+ import { MoveEnum, MoveStruct } from "../utils/index.mjs";
2
+ import { bcs } from "@mysten/sui/bcs";
3
+
4
+ //#region src/contracts/hashi/mpc_signing.ts
5
+ /**************************************************************
6
+ * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
7
+ **************************************************************/
8
+ /**
9
+ * Durable, out-of-order accumulator for a withdrawal's per-input threshold Schnorr
10
+ * signatures. This is the MPC-protocol side of incremental signing: the dangerous
11
+ * presignature / nonce bookkeeping lives here, behind a module boundary, and is
12
+ * embedded (field-private) inside the BTC `WithdrawalTransaction` rather than
13
+ * stored as a separate object.
14
+ *
15
+ * Each input occupies one slot that is either:
16
+ *
17
+ * - `Pending(presig_index)` — awaiting its signature; carries the presignature
18
+ * index it will consume (valid within `epoch`), or
19
+ * - `Signed(bytes)` — the completed per-input MPC signature.
20
+ *
21
+ * Signatures are filled in any order (`record`), survive leader timeouts /
22
+ * rotation / restart because they live on chain, and survive committee
23
+ * reconfiguration: on an epoch change only the still-`Pending` slots are
24
+ * reassigned fresh presignatures (`reallocate`); `Signed` slots are final and
25
+ * epoch-independent (the committee group key is stable across rotation).
26
+ *
27
+ * NONCE SAFETY (a violation leaks the group secret share):
28
+ *
29
+ * - every `Pending` index is unique within (batch, epoch) — `new` / `reallocate`
30
+ * assign distinct offsets from a freshly allocated block;
31
+ * - indices are globally disjoint within an epoch — the allocator is monotonic
32
+ * (see `hashi::allocate_presigs`);
33
+ * - a stale-epoch index is never used after a reconfig — `reallocate` overwrites
34
+ * EVERY `Pending` slot before any signing happens in the new epoch, and the
35
+ * caller must `reallocate` whenever `epoch` is stale;
36
+ * - a `Signed` slot holds no index, so there is nothing stale to reuse.
37
+ */
38
+ const $moduleName = "@local-pkg/hashi::mpc_signing";
39
+ /** Per-input signing slot. */
40
+ const MpcSig = new MoveEnum({
41
+ name: `${$moduleName}::MpcSig`,
42
+ fields: {
43
+ Pending: bcs.u64(),
44
+ Signed: bcs.vector(bcs.u8())
45
+ }
46
+ });
47
+ const SigningBatch = new MoveStruct({
48
+ name: `${$moduleName}::SigningBatch`,
49
+ fields: {
50
+ signatures: bcs.vector(MpcSig),
51
+ epoch: bcs.u64()
52
+ }
53
+ });
54
+
55
+ //#endregion
56
+ export { SigningBatch };
@@ -5,6 +5,12 @@ import { ObjectBag } from "./deps/sui/object_bag.mjs";
5
5
  /**************************************************************
6
6
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
7
7
  **************************************************************/
8
+ /**
9
+ * Container for the package's governance proposals, hung off `Hashi`. Active and
10
+ * executed proposals live in two separate object bags so that each set can be
11
+ * enumerated directly, and executed proposals are archived indefinitely to keep
12
+ * historical governance actions inspectable.
13
+ */
8
14
  const $moduleName = "@local-pkg/hashi::proposals";
9
15
  const Proposals = new MoveStruct({
10
16
  name: `${$moduleName}::Proposals`,
@@ -6,6 +6,12 @@ import { bcs } from "@mysten/sui/bcs";
6
6
  /**************************************************************
7
7
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
8
8
  **************************************************************/
9
+ /**
10
+ * Custody of the coin capabilities for bridge-issued assets. `Treasury` holds the
11
+ * `TreasuryCap` and `MetadataCap` of each registered coin type in an `ObjectBag`
12
+ * keyed by cap type, and exposes package-only mint/burn that emit
13
+ * `Minted`/`Burned` events for off-chain watchers.
14
+ */
9
15
  const $moduleName = "@local-pkg/hashi::treasury";
10
16
  const Treasury = new MoveStruct({
11
17
  name: `${$moduleName}::Treasury`,
@@ -15,12 +21,12 @@ const Key = new MoveStruct({
15
21
  name: `${$moduleName}::Key<phantom T>`,
16
22
  fields: { dummy_field: bcs.bool() }
17
23
  });
18
- const MintEvent = new MoveStruct({
19
- name: `${$moduleName}::MintEvent<phantom T>`,
24
+ const Minted = new MoveStruct({
25
+ name: `${$moduleName}::Minted<phantom T>`,
20
26
  fields: { amount: bcs.u64() }
21
27
  });
22
- const BurnEvent = new MoveStruct({
23
- name: `${$moduleName}::BurnEvent<phantom T>`,
28
+ const Burned = new MoveStruct({
29
+ name: `${$moduleName}::Burned<phantom T>`,
24
30
  fields: { amount: bcs.u64() }
25
31
  });
26
32
 
@@ -5,6 +5,13 @@ import { bcs } from "@mysten/sui/bcs";
5
5
  /**************************************************************
6
6
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
7
7
  **************************************************************/
8
+ /**
9
+ * Bitcoin UTXO value types shared by the deposit and withdrawal flows. A `UtxoId`
10
+ * identifies an outpoint (txid:vout) and a `Utxo` pairs it with its satoshi amount
11
+ * and an optional derivation path (the Sui address a deposit mints to). The
12
+ * constructors are `public` so PTBs can assemble UTXOs when calling into the
13
+ * bridge; everything else is package-only.
14
+ */
8
15
  const $moduleName = "@local-pkg/hashi::utxo";
9
16
  const UtxoId = new MoveStruct({
10
17
  name: `${$moduleName}::UtxoId`,
@@ -7,6 +7,13 @@ import { bcs } from "@mysten/sui/bcs";
7
7
  /**************************************************************
8
8
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
9
9
  **************************************************************/
10
+ /**
11
+ * On-chain bookkeeping for the bridge's Bitcoin UTXO set. Confirmed deposit
12
+ * outputs and unconfirmed withdrawal change outputs live in `utxo_records` from
13
+ * insertion until the withdrawal that spends them confirms on Bitcoin, after which
14
+ * their IDs move to `spent_utxos` — kept permanently as replay protection so an
15
+ * already-spent outpoint can never be re-inserted.
16
+ */
10
17
  const $moduleName = "@local-pkg/hashi::utxo_pool";
11
18
  const UtxoPool = new MoveStruct({
12
19
  name: `${$moduleName}::UtxoPool`,
@@ -20,12 +27,12 @@ const UtxoRecord = new MoveStruct({
20
27
  fields: {
21
28
  utxo: Utxo,
22
29
  produced_by: bcs.option(bcs.Address),
23
- locked_by: bcs.option(bcs.Address),
30
+ spent_by: bcs.option(bcs.Address),
24
31
  spent_epoch: bcs.option(bcs.u64())
25
32
  }
26
33
  });
27
- const UtxoSpentEvent = new MoveStruct({
28
- name: `${$moduleName}::UtxoSpentEvent`,
34
+ const UtxoSpent = new MoveStruct({
35
+ name: `${$moduleName}::UtxoSpent`,
29
36
  fields: {
30
37
  utxo_id: UtxoId,
31
38
  spent_epoch: bcs.u64()
@@ -0,0 +1,28 @@
1
+ import { MoveStruct } from "../utils/index.mjs";
2
+ import { VecSet } from "./deps/sui/vec_set.mjs";
3
+ import { UpgradeCap } from "./deps/sui/package.mjs";
4
+ import { bcs } from "@mysten/sui/bcs";
5
+
6
+ //#region src/contracts/hashi/versioning.ts
7
+ /**************************************************************
8
+ * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
9
+ **************************************************************/
10
+ /**
11
+ * Package version gating and upgrade authority.
12
+ *
13
+ * Holds the set of package versions allowed to run and custodies the package
14
+ * `UpgradeCap`. The two live together because they are one lifecycle: committing
15
+ * an upgrade through the cap auto-enables the new version. Every entry function
16
+ * gates on `assert_version_enabled` so a disabled version cannot be executed.
17
+ */
18
+ const $moduleName = "@local-pkg/hashi::versioning";
19
+ const Versioning = new MoveStruct({
20
+ name: `${$moduleName}::Versioning`,
21
+ fields: {
22
+ enabled_versions: VecSet(bcs.u64()),
23
+ upgrade_cap: bcs.option(UpgradeCap)
24
+ }
25
+ });
26
+
27
+ //#endregion
28
+ export { Versioning };
@@ -7,7 +7,15 @@ import { bcs } from "@mysten/sui/bcs";
7
7
  /**************************************************************
8
8
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
9
9
  **************************************************************/
10
- /** Module: withdraw */
10
+ /**
11
+ * User-facing Bitcoin withdrawal flow. A user escrows hBTC against a target
12
+ * Bitcoin address; the committee approves the request, batches one or more
13
+ * requests into a withdrawal transaction (burning the hBTC and locking the input
14
+ * UTXOs), accumulates per-input MPC signatures incrementally, finalizes with the
15
+ * one-shot guardian signatures, and confirms once the transaction lands on
16
+ * Bitcoin. A not-yet-committed request can be cancelled by its requester after a
17
+ * cooldown, refunding the hBTC.
18
+ */
11
19
  const $moduleName = "@local-pkg/hashi::withdraw";
12
20
  const RequestApprovalMessage = new MoveStruct({
13
21
  name: `${$moduleName}::RequestApprovalMessage`,
@@ -31,6 +39,14 @@ const WithdrawalSignedMessage = new MoveStruct({
31
39
  guardian_signatures: bcs.vector(bcs.vector(bcs.u8()))
32
40
  }
33
41
  });
42
+ const MpcInputSignaturesMessage = new MoveStruct({
43
+ name: `${$moduleName}::MpcInputSignaturesMessage`,
44
+ fields: {
45
+ withdrawal_id: bcs.Address,
46
+ indices: bcs.vector(bcs.u64()),
47
+ signatures: bcs.vector(bcs.vector(bcs.u8()))
48
+ }
49
+ });
34
50
  const WithdrawalConfirmationMessage = new MoveStruct({
35
51
  name: `${$moduleName}::WithdrawalConfirmationMessage`,
36
52
  fields: { withdrawal_id: bcs.Address }
@@ -1,13 +1,23 @@
1
1
  import { MoveEnum, MoveStruct } from "../utils/index.mjs";
2
+ import { CommitteeSignature } from "./committee.mjs";
2
3
  import { ObjectBag } from "./deps/sui/object_bag.mjs";
3
4
  import { Utxo, UtxoId } from "./utxo.mjs";
4
5
  import { Balance } from "./deps/sui/balance.mjs";
6
+ import { SigningBatch } from "./mpc_signing.mjs";
5
7
  import { bcs } from "@mysten/sui/bcs";
6
8
 
7
9
  //#region src/contracts/hashi/withdrawal_queue.ts
8
10
  /**************************************************************
9
11
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
10
12
  **************************************************************/
13
+ /**
14
+ * Storage and state machine for Bitcoin withdrawals. Holds `WithdrawalRequest`
15
+ * objects as they move from Requested through Approved, Processing, Signed, and
16
+ * Confirmed, plus the `WithdrawalTransaction` objects that batch them: each
17
+ * transaction tracks its input UTXOs, withdrawal and change outputs, and the
18
+ * incrementally collected MPC and guardian signatures. Certificate verification
19
+ * and funds movement are driven by `hashi::withdraw`.
20
+ */
11
21
  const $moduleName = "@local-pkg/hashi::withdrawal_queue";
12
22
  const WithdrawalRequestQueue = new MoveStruct({
13
23
  name: `${$moduleName}::WithdrawalRequestQueue`,
@@ -42,8 +52,10 @@ const WithdrawalRequest = new MoveStruct({
42
52
  sender: bcs.Address,
43
53
  btc_amount: bcs.u64(),
44
54
  bitcoin_address: bcs.vector(bcs.u8()),
45
- timestamp_ms: bcs.u64(),
55
+ created_timestamp_ms: bcs.u64(),
46
56
  status: WithdrawalStatus,
57
+ approval_cert: bcs.option(CommitteeSignature),
58
+ approved_timestamp_ms: bcs.option(bcs.u64()),
47
59
  withdrawal_txn_id: bcs.option(bcs.Address),
48
60
  sui_tx_digest: bcs.vector(bcs.u8()),
49
61
  btc: Balance
@@ -57,13 +69,13 @@ const WithdrawalTransaction = new MoveStruct({
57
69
  request_ids: bcs.vector(bcs.Address),
58
70
  inputs: bcs.vector(Utxo),
59
71
  withdrawal_outputs: bcs.vector(OutputUtxo),
60
- change_output: bcs.option(OutputUtxo),
61
- timestamp_ms: bcs.u64(),
72
+ change_outputs: bcs.vector(OutputUtxo),
73
+ created_timestamp_ms: bcs.u64(),
74
+ signed_timestamp_ms: bcs.option(bcs.u64()),
75
+ confirmed_timestamp_ms: bcs.option(bcs.u64()),
62
76
  randomness: bcs.vector(bcs.u8()),
63
- signatures: bcs.option(bcs.vector(bcs.vector(bcs.u8()))),
64
- guardian_signatures: bcs.option(bcs.vector(bcs.vector(bcs.u8()))),
65
- presig_start_index: bcs.u64(),
66
- epoch: bcs.u64()
77
+ signing: SigningBatch,
78
+ guardian_signatures: bcs.option(bcs.vector(bcs.vector(bcs.u8())))
67
79
  }
68
80
  });
69
81
  const CommittedRequestInfo = new MoveStruct({
@@ -73,8 +85,8 @@ const CommittedRequestInfo = new MoveStruct({
73
85
  bitcoin_address: bcs.vector(bcs.u8())
74
86
  }
75
87
  });
76
- const WithdrawalRequestedEvent = new MoveStruct({
77
- name: `${$moduleName}::WithdrawalRequestedEvent`,
88
+ const WithdrawalRequested = new MoveStruct({
89
+ name: `${$moduleName}::WithdrawalRequested`,
78
90
  fields: {
79
91
  request_id: bcs.Address,
80
92
  btc_amount: bcs.u64(),
@@ -84,25 +96,33 @@ const WithdrawalRequestedEvent = new MoveStruct({
84
96
  sui_tx_digest: bcs.vector(bcs.u8())
85
97
  }
86
98
  });
87
- const WithdrawalApprovedEvent = new MoveStruct({
88
- name: `${$moduleName}::WithdrawalApprovedEvent`,
99
+ const WithdrawalApproved = new MoveStruct({
100
+ name: `${$moduleName}::WithdrawalApproved`,
89
101
  fields: { request_id: bcs.Address }
90
102
  });
91
- const WithdrawalPickedForProcessingEvent = new MoveStruct({
92
- name: `${$moduleName}::WithdrawalPickedForProcessingEvent`,
103
+ const WithdrawalPickedForProcessing = new MoveStruct({
104
+ name: `${$moduleName}::WithdrawalPickedForProcessing`,
93
105
  fields: {
94
106
  withdrawal_txn_id: bcs.Address,
95
107
  txid: bcs.Address,
96
108
  request_ids: bcs.vector(bcs.Address),
97
109
  inputs: bcs.vector(Utxo),
98
110
  withdrawal_outputs: bcs.vector(OutputUtxo),
99
- change_output: bcs.option(OutputUtxo),
111
+ change_outputs: bcs.vector(OutputUtxo),
100
112
  timestamp_ms: bcs.u64(),
101
113
  randomness: bcs.vector(bcs.u8())
102
114
  }
103
115
  });
104
- const WithdrawalSignedEvent = new MoveStruct({
105
- name: `${$moduleName}::WithdrawalSignedEvent`,
116
+ const WithdrawalInputsSigned = new MoveStruct({
117
+ name: `${$moduleName}::WithdrawalInputsSigned`,
118
+ fields: {
119
+ withdrawal_txn_id: bcs.Address,
120
+ signed_count: bcs.u64(),
121
+ num_inputs: bcs.u64()
122
+ }
123
+ });
124
+ const WithdrawalSigned = new MoveStruct({
125
+ name: `${$moduleName}::WithdrawalSigned`,
106
126
  fields: {
107
127
  withdrawal_txn_id: bcs.Address,
108
128
  request_ids: bcs.vector(bcs.Address),
@@ -110,26 +130,26 @@ const WithdrawalSignedEvent = new MoveStruct({
110
130
  guardian_signatures: bcs.vector(bcs.vector(bcs.u8()))
111
131
  }
112
132
  });
113
- const WithdrawalPresigsReassignedEvent = new MoveStruct({
114
- name: `${$moduleName}::WithdrawalPresigsReassignedEvent`,
133
+ const WithdrawalPresigsReassigned = new MoveStruct({
134
+ name: `${$moduleName}::WithdrawalPresigsReassigned`,
115
135
  fields: {
116
136
  withdrawal_txn_id: bcs.Address,
117
137
  epoch: bcs.u64(),
118
138
  presig_start_index: bcs.u64()
119
139
  }
120
140
  });
121
- const WithdrawalConfirmedEvent = new MoveStruct({
122
- name: `${$moduleName}::WithdrawalConfirmedEvent`,
141
+ const WithdrawalConfirmed = new MoveStruct({
142
+ name: `${$moduleName}::WithdrawalConfirmed`,
123
143
  fields: {
124
144
  withdrawal_txn_id: bcs.Address,
125
145
  txid: bcs.Address,
126
- change_utxo_id: bcs.option(UtxoId),
146
+ change_utxo_ids: bcs.vector(UtxoId),
127
147
  request_ids: bcs.vector(bcs.Address),
128
- change_utxo_amount: bcs.option(bcs.u64())
148
+ change_utxo_amounts: bcs.vector(bcs.u64())
129
149
  }
130
150
  });
131
- const WithdrawalCancelledEvent = new MoveStruct({
132
- name: `${$moduleName}::WithdrawalCancelledEvent`,
151
+ const WithdrawalCancelled = new MoveStruct({
152
+ name: `${$moduleName}::WithdrawalCancelled`,
133
153
  fields: {
134
154
  request_id: bcs.Address,
135
155
  requester_address: bcs.Address,
package/dist/errors.d.mts CHANGED
@@ -36,6 +36,33 @@ declare class HashiFetchError extends Error {
36
36
  cause?: unknown;
37
37
  });
38
38
  }
39
+ /**
40
+ * Stable discriminator for `HashiGuardianError`, so callers can `switch` on
41
+ * `code` rather than string-parse the message: `not-configured` (no guardian URL
42
+ * resolvable), `unreachable` (`fetch` threw), `http-error` (non-2xx),
43
+ * `malformed-response` (bad JSON or limiter field), `not-initialized` (no limiter yet).
44
+ */
45
+ type GuardianErrorCode = "not-configured" | "unreachable" | "http-error" | "malformed-response" | "not-initialized";
46
+ /**
47
+ * Thrown by the `client.hashi.guardian.*` methods when the guardian `/info`
48
+ * endpoint can't be resolved, reached, parsed, or is not yet initialized.
49
+ * `code` is a stable discriminator for programmatic handling; `url` is the
50
+ * `/info` endpoint that failed (`null` for `not-configured`); `status` carries
51
+ * the HTTP status for `http-error`.
52
+ */
53
+ declare class HashiGuardianError extends Error {
54
+ readonly code: GuardianErrorCode;
55
+ readonly url: string | null;
56
+ readonly status?: number;
57
+ constructor(details: {
58
+ message: string;
59
+ code: GuardianErrorCode;
60
+ url?: string | null;
61
+ status?: number;
62
+ }, options?: {
63
+ cause?: unknown;
64
+ });
65
+ }
39
66
  /**
40
67
  * One amount that failed a client-side minimum check. `vout` is present for
41
68
  * deposit-UTXO violations (so callers can map each offender back to a Bitcoin
@@ -115,4 +142,4 @@ declare class InvalidBitcoinAddressError extends Error {
115
142
  });
116
143
  }
117
144
  //#endregion
118
- export { AmountBelowMinimumError, AmountViolation, HashiConfigError, HashiFetchError, HashiPausedError, InvalidBitcoinAddressCode, InvalidBitcoinAddressError, InvalidParamsError };
145
+ export { AmountBelowMinimumError, AmountViolation, GuardianErrorCode, HashiConfigError, HashiFetchError, HashiGuardianError, HashiPausedError, InvalidBitcoinAddressCode, InvalidBitcoinAddressError, InvalidParamsError };
package/dist/errors.mjs CHANGED
@@ -52,6 +52,22 @@ var HashiFetchError = class extends Error {
52
52
  }
53
53
  };
54
54
  /**
55
+ * Thrown by the `client.hashi.guardian.*` methods when the guardian `/info`
56
+ * endpoint can't be resolved, reached, parsed, or is not yet initialized.
57
+ * `code` is a stable discriminator for programmatic handling; `url` is the
58
+ * `/info` endpoint that failed (`null` for `not-configured`); `status` carries
59
+ * the HTTP status for `http-error`.
60
+ */
61
+ var HashiGuardianError = class extends Error {
62
+ constructor(details, options) {
63
+ super(details.message, options);
64
+ this.name = "HashiGuardianError";
65
+ this.code = details.code;
66
+ this.url = details.url ?? null;
67
+ this.status = details.status;
68
+ }
69
+ };
70
+ /**
55
71
  * Thrown by `HashiClient.deposit()` and `HashiClient.requestWithdrawal()` when
56
72
  * one or more amounts are below the live on-chain minimum. Deposits may carry
57
73
  * multiple violations (one per offending UTXO) so callers can fix the whole
@@ -115,4 +131,4 @@ var InvalidBitcoinAddressError = class extends Error {
115
131
  };
116
132
 
117
133
  //#endregion
118
- export { AmountBelowMinimumError, HashiConfigError, HashiFetchError, HashiPausedError, InvalidBitcoinAddressError, InvalidParamsError };
134
+ export { AmountBelowMinimumError, HashiConfigError, HashiFetchError, HashiGuardianError, HashiPausedError, InvalidBitcoinAddressError, InvalidParamsError };
@@ -0,0 +1,26 @@
1
+ import { GuardianLimiterConfig, GuardianLimiterState, RawGuardianInfo } from "./types.mjs";
2
+
3
+ //#region src/guardian.d.ts
4
+ /**
5
+ * Project the token-bucket capacity (sats) forward to `timestampSecs`, mirroring
6
+ * the guardian's Rust limiter: `min(available + elapsed * refillRate, max)`.
7
+ * Timestamps are unix seconds; one at or before `lastUpdatedAtSecs` yields the
8
+ * stored balance.
9
+ */
10
+ declare function projectCapacity(config: GuardianLimiterConfig, state: GuardianLimiterState, timestampSecs: bigint): bigint;
11
+ /**
12
+ * Estimate the seconds until `amountSats` of capacity is available given the
13
+ * current bucket. Returns `0n` if it is available now, or `null` if it can
14
+ * never be satisfied in a single withdrawal — either the amount exceeds the
15
+ * bucket's maximum capacity, or the refill rate is `0` and a deficit remains.
16
+ */
17
+ declare function estimateWaitSecs(config: GuardianLimiterConfig, state: GuardianLimiterState, amountSats: bigint, nowSecs: bigint): bigint | null;
18
+ /**
19
+ * Fetch and parse the guardian's read-only `/info` JSON. `origin` is the base
20
+ * URL (no path — e.g. the on-chain `guardian_url`); `/info` is appended here.
21
+ * `u64` strings are parsed to `bigint`, and `limiter` is `null` for an
22
+ * unprovisioned guardian. Failures are wrapped in {@link HashiGuardianError}.
23
+ */
24
+ declare function fetchGuardianInfo(origin: string): Promise<RawGuardianInfo>;
25
+ //#endregion
26
+ export { estimateWaitSecs, fetchGuardianInfo, projectCapacity };
@@ -0,0 +1,101 @@
1
+ import { HashiGuardianError } from "./errors.mjs";
2
+
3
+ //#region src/guardian.ts
4
+ /**
5
+ * Project the token-bucket capacity (sats) forward to `timestampSecs`, mirroring
6
+ * the guardian's Rust limiter: `min(available + elapsed * refillRate, max)`.
7
+ * Timestamps are unix seconds; one at or before `lastUpdatedAtSecs` yields the
8
+ * stored balance.
9
+ */
10
+ function projectCapacity(config, state, timestampSecs) {
11
+ const refilled = (timestampSecs > state.lastUpdatedAtSecs ? timestampSecs - state.lastUpdatedAtSecs : 0n) * config.refillRateSatsPerSec;
12
+ const projected = state.numTokensAvailableSats + refilled;
13
+ return projected < config.maxBucketCapacitySats ? projected : config.maxBucketCapacitySats;
14
+ }
15
+ /**
16
+ * Estimate the seconds until `amountSats` of capacity is available given the
17
+ * current bucket. Returns `0n` if it is available now, or `null` if it can
18
+ * never be satisfied in a single withdrawal — either the amount exceeds the
19
+ * bucket's maximum capacity, or the refill rate is `0` and a deficit remains.
20
+ */
21
+ function estimateWaitSecs(config, state, amountSats, nowSecs) {
22
+ if (amountSats > config.maxBucketCapacitySats) return null;
23
+ const available = projectCapacity(config, state, nowSecs);
24
+ if (available >= amountSats) return 0n;
25
+ const deficit = amountSats - available;
26
+ if (config.refillRateSatsPerSec === 0n) return null;
27
+ return (deficit + config.refillRateSatsPerSec - 1n) / config.refillRateSatsPerSec;
28
+ }
29
+ /**
30
+ * Fetch and parse the guardian's read-only `/info` JSON. `origin` is the base
31
+ * URL (no path — e.g. the on-chain `guardian_url`); `/info` is appended here.
32
+ * `u64` strings are parsed to `bigint`, and `limiter` is `null` for an
33
+ * unprovisioned guardian. Failures are wrapped in {@link HashiGuardianError}.
34
+ */
35
+ async function fetchGuardianInfo(origin) {
36
+ const endpoint = `${origin.replace(/\/+$/, "")}/info`;
37
+ let res;
38
+ try {
39
+ res = await fetch(endpoint, { headers: { Accept: "application/json" } });
40
+ } catch (cause) {
41
+ throw new HashiGuardianError({
42
+ message: `Guardian endpoint unreachable: ${endpoint}`,
43
+ code: "unreachable",
44
+ url: endpoint
45
+ }, { cause });
46
+ }
47
+ if (!res.ok) throw new HashiGuardianError({
48
+ message: `Guardian /info returned HTTP ${res.status}`,
49
+ code: "http-error",
50
+ url: endpoint,
51
+ status: res.status
52
+ });
53
+ let body;
54
+ try {
55
+ body = await res.json();
56
+ } catch (cause) {
57
+ throw new HashiGuardianError({
58
+ message: "Guardian /info returned a non-JSON body",
59
+ code: "malformed-response",
60
+ url: endpoint
61
+ }, { cause });
62
+ }
63
+ const u64 = (value, field) => {
64
+ if (typeof value !== "string") throw new HashiGuardianError({
65
+ message: `Guardian /info field \`${field}\` must be a string, got ${typeof value}`,
66
+ code: "malformed-response",
67
+ url: endpoint
68
+ });
69
+ try {
70
+ return BigInt(value);
71
+ } catch (cause) {
72
+ throw new HashiGuardianError({
73
+ message: `Guardian /info field \`${field}\` is not an integer: ${JSON.stringify(value)}`,
74
+ code: "malformed-response",
75
+ url: endpoint
76
+ }, { cause });
77
+ }
78
+ };
79
+ const rawLimiter = body.limiter;
80
+ return {
81
+ limiter: rawLimiter == null ? null : {
82
+ state: {
83
+ numTokensAvailableSats: u64(rawLimiter.state?.numTokensAvailableSats, "limiter.state.numTokensAvailableSats"),
84
+ lastUpdatedAtSecs: u64(rawLimiter.state?.lastUpdatedAtSecs, "limiter.state.lastUpdatedAtSecs"),
85
+ nextSeq: u64(rawLimiter.state?.nextSeq, "limiter.state.nextSeq")
86
+ },
87
+ config: {
88
+ refillRateSatsPerSec: u64(rawLimiter.config?.refillRateSatsPerSec, "limiter.config.refillRateSatsPerSec"),
89
+ maxBucketCapacitySats: u64(rawLimiter.config?.maxBucketCapacitySats, "limiter.config.maxBucketCapacitySats")
90
+ }
91
+ },
92
+ gitRevision: body.gitRevision ?? "",
93
+ committeeEpoch: body.committeeEpoch == null ? null : u64(body.committeeEpoch, "committeeEpoch"),
94
+ btcPubkey: body.btcPubkey ?? null,
95
+ signingPubKey: body.signingPubKey ?? "",
96
+ signedAtMs: body.signedAtMs == null ? null : u64(body.signedAtMs, "signedAtMs")
97
+ };
98
+ }
99
+
100
+ //#endregion
101
+ export { estimateWaitSecs, fetchGuardianInfo, projectCapacity };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,6 @@
1
- import { BitcoinNetwork, CancelWithdrawalParams, DepositFees, DepositHistoryItem, DepositInfo, DepositParams, DepositStatus, GovernanceConfig, HashiClientOptions, HbtcBalance, NetworkConfig, SuiNetwork, TransactionHistoryItem, UtxoId, UtxoLookupResult, UtxoOutput, UtxoUsageResult, WaitOptions, WithdrawalFees, WithdrawalHistoryItem, WithdrawalInfo, WithdrawalParams, WithdrawalStatus } from "./types.mjs";
1
+ import { BitcoinNetwork, CancelWithdrawalParams, DepositFees, DepositHistoryItem, DepositInfo, DepositParams, DepositStatus, GovernanceConfig, GuardianInfoProvider, GuardianLimiterConfig, GuardianLimiterRaw, GuardianLimiterSnapshot, GuardianLimiterState, GuardianWithdrawCheck, HashiClientOptions, HbtcBalance, NetworkConfig, RawGuardianInfo, SuiNetwork, TransactionHistoryItem, UtxoId, UtxoLookupResult, UtxoOutput, UtxoUsageResult, WaitOptions, WithdrawalFees, WithdrawalHistoryItem, WithdrawalInfo, WithdrawalParams, WithdrawalStatus } from "./types.mjs";
2
2
  import { HashiClient, hashi } from "./client.mjs";
3
- import { AmountBelowMinimumError, AmountViolation, HashiConfigError, HashiFetchError, HashiPausedError, InvalidBitcoinAddressCode, InvalidBitcoinAddressError, InvalidParamsError } from "./errors.mjs";
3
+ import { AmountBelowMinimumError, AmountViolation, GuardianErrorCode, HashiConfigError, HashiFetchError, HashiGuardianError, HashiPausedError, InvalidBitcoinAddressCode, InvalidBitcoinAddressError, InvalidParamsError } from "./errors.mjs";
4
4
  import { DepositAddressInputs, arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, twoOfTwoTaprootScriptPathAddress, witnessProgramToAddress } from "./bitcoin.mjs";
5
- export { AmountBelowMinimumError, type AmountViolation, type BitcoinNetwork, type CancelWithdrawalParams, type DepositAddressInputs, type DepositFees, type DepositHistoryItem, type DepositInfo, type DepositParams, type DepositStatus, type GovernanceConfig, HashiClient, type HashiClientOptions, HashiConfigError, HashiFetchError, HashiPausedError, type HbtcBalance, type InvalidBitcoinAddressCode, InvalidBitcoinAddressError, InvalidParamsError, type NetworkConfig, type SuiNetwork, type TransactionHistoryItem, type UtxoId, type UtxoLookupResult, type UtxoOutput, type UtxoUsageResult, type WaitOptions, type WithdrawalFees, type WithdrawalHistoryItem, type WithdrawalInfo, type WithdrawalParams, type WithdrawalStatus, arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, hashi, twoOfTwoTaprootScriptPathAddress, witnessProgramToAddress };
5
+ import { estimateWaitSecs, fetchGuardianInfo, projectCapacity } from "./guardian.mjs";
6
+ export { AmountBelowMinimumError, type AmountViolation, type BitcoinNetwork, type CancelWithdrawalParams, type DepositAddressInputs, type DepositFees, type DepositHistoryItem, type DepositInfo, type DepositParams, type DepositStatus, type GovernanceConfig, type GuardianErrorCode, type GuardianInfoProvider, type GuardianLimiterConfig, type GuardianLimiterRaw, type GuardianLimiterSnapshot, type GuardianLimiterState, type GuardianWithdrawCheck, HashiClient, type HashiClientOptions, HashiConfigError, HashiFetchError, HashiGuardianError, HashiPausedError, type HbtcBalance, type InvalidBitcoinAddressCode, InvalidBitcoinAddressError, InvalidParamsError, type NetworkConfig, type RawGuardianInfo, type SuiNetwork, type TransactionHistoryItem, type UtxoId, type UtxoLookupResult, type UtxoOutput, type UtxoUsageResult, type WaitOptions, type WithdrawalFees, type WithdrawalHistoryItem, type WithdrawalInfo, type WithdrawalParams, type WithdrawalStatus, arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, estimateWaitSecs, fetchGuardianInfo, generateDepositAddress, hashi, projectCapacity, twoOfTwoTaprootScriptPathAddress, witnessProgramToAddress };
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import { AmountBelowMinimumError, HashiConfigError, HashiFetchError, HashiPausedError, InvalidBitcoinAddressError, InvalidParamsError } from "./errors.mjs";
1
+ import { AmountBelowMinimumError, HashiConfigError, HashiFetchError, HashiGuardianError, HashiPausedError, InvalidBitcoinAddressError, InvalidParamsError } from "./errors.mjs";
2
2
  import { arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, twoOfTwoTaprootScriptPathAddress, witnessProgramToAddress } from "./bitcoin.mjs";
3
+ import { estimateWaitSecs, fetchGuardianInfo, projectCapacity } from "./guardian.mjs";
3
4
  import { HashiClient, hashi } from "./client.mjs";
4
5
 
5
- export { AmountBelowMinimumError, HashiClient, HashiConfigError, HashiFetchError, HashiPausedError, InvalidBitcoinAddressError, InvalidParamsError, arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, hashi, twoOfTwoTaprootScriptPathAddress, witnessProgramToAddress };
6
+ export { AmountBelowMinimumError, HashiClient, HashiConfigError, HashiFetchError, HashiGuardianError, HashiPausedError, InvalidBitcoinAddressError, InvalidParamsError, arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, estimateWaitSecs, fetchGuardianInfo, generateDepositAddress, hashi, projectCapacity, twoOfTwoTaprootScriptPathAddress, witnessProgramToAddress };