@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.
- package/CHANGELOG.md +20 -0
- package/README.md +5 -5
- package/dist/client.d.mts +33 -7
- package/dist/client.mjs +86 -11
- package/dist/constants.mjs +12 -5
- package/dist/contracts/hashi/bitcoin_state.mjs +8 -0
- package/dist/contracts/hashi/committee.mjs +10 -3
- package/dist/contracts/hashi/committee_set.mjs +32 -3
- package/dist/contracts/hashi/config.mjs +10 -10
- package/dist/contracts/hashi/config_value.mjs +10 -0
- package/dist/contracts/hashi/deposit.mjs +18 -9
- package/dist/contracts/hashi/deposit_queue.mjs +11 -3
- package/dist/contracts/hashi/hashi.mjs +12 -2
- package/dist/contracts/hashi/mpc_signing.mjs +56 -0
- package/dist/contracts/hashi/proposals.mjs +6 -0
- package/dist/contracts/hashi/treasury.mjs +10 -4
- package/dist/contracts/hashi/utxo.mjs +7 -0
- package/dist/contracts/hashi/utxo_pool.mjs +10 -3
- package/dist/contracts/hashi/versioning.mjs +28 -0
- package/dist/contracts/hashi/withdraw.mjs +17 -1
- package/dist/contracts/hashi/withdrawal_queue.mjs +44 -24
- package/dist/errors.d.mts +28 -1
- package/dist/errors.mjs +17 -1
- package/dist/guardian.d.mts +26 -0
- package/dist/guardian.mjs +101 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.mjs +3 -2
- package/dist/types.d.mts +62 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
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
|
-
/**
|
|
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
|
|
19
|
-
name: `${$moduleName}::
|
|
24
|
+
const Minted = new MoveStruct({
|
|
25
|
+
name: `${$moduleName}::Minted<phantom T>`,
|
|
20
26
|
fields: { amount: bcs.u64() }
|
|
21
27
|
});
|
|
22
|
-
const
|
|
23
|
-
name: `${$moduleName}::
|
|
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
|
-
|
|
30
|
+
spent_by: bcs.option(bcs.Address),
|
|
24
31
|
spent_epoch: bcs.option(bcs.u64())
|
|
25
32
|
}
|
|
26
33
|
});
|
|
27
|
-
const
|
|
28
|
-
name: `${$moduleName}::
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
|
77
|
-
name: `${$moduleName}::
|
|
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
|
|
88
|
-
name: `${$moduleName}::
|
|
99
|
+
const WithdrawalApproved = new MoveStruct({
|
|
100
|
+
name: `${$moduleName}::WithdrawalApproved`,
|
|
89
101
|
fields: { request_id: bcs.Address }
|
|
90
102
|
});
|
|
91
|
-
const
|
|
92
|
-
name: `${$moduleName}::
|
|
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
|
-
|
|
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
|
|
105
|
-
name: `${$moduleName}::
|
|
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
|
|
114
|
-
name: `${$moduleName}::
|
|
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
|
|
122
|
-
name: `${$moduleName}::
|
|
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
|
-
|
|
146
|
+
change_utxo_ids: bcs.vector(UtxoId),
|
|
127
147
|
request_ids: bcs.vector(bcs.Address),
|
|
128
|
-
|
|
148
|
+
change_utxo_amounts: bcs.vector(bcs.u64())
|
|
129
149
|
}
|
|
130
150
|
});
|
|
131
|
-
const
|
|
132
|
-
name: `${$moduleName}::
|
|
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
|
-
|
|
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 };
|