@mysten-incubation/hashi 0.2.0 → 0.3.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 +6 -0
- package/dist/bitcoin.d.mts +68 -36
- package/dist/bitcoin.mjs +73 -53
- package/dist/client.d.mts +15 -2
- package/dist/client.mjs +83 -23
- package/dist/constants.mjs +11 -1
- package/dist/contracts/hashi/utxo_pool.mjs +2 -1
- package/dist/contracts/hashi/withdraw.mjs +2 -1
- package/dist/contracts/hashi/withdrawal_queue.mjs +11 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/types.d.mts +18 -0
- package/dist/util.mjs +11 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @mysten-incubation/hashi
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ced85d2: Derive deposit addresses as 2-of-2 (guardian, MPC-child) taproot to match the on-chain bridge (hashi#609). `generateDepositAddress` (pure helper) now takes a named-args object including `guardianBtcXOnly`; `HashiClient.generateDepositAddress` reads the guardian key from on-chain and fails fast with `HashiConfigError` when the deployment is not guardian-provisioned. `GovernanceConfig` gains `guardianUrl`, `guardianPublicKey`, `guardianBtcPublicKey`. Adds `twoOfTwoTaprootScriptPathAddress` as a public primitive and removes the single-key `taprootScriptPathAddress` helper, which the bridge no longer accepts.
|
|
8
|
+
|
|
3
9
|
## 0.2.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/dist/bitcoin.d.mts
CHANGED
|
@@ -56,61 +56,93 @@ declare function arkworksToSec1Compressed(ark: Uint8Array): Uint8Array;
|
|
|
56
56
|
*/
|
|
57
57
|
declare function deriveChildPubkey(mpcKeyCompressed: Uint8Array, suiAddress: Uint8Array): Uint8Array;
|
|
58
58
|
/**
|
|
59
|
-
* Builds a P2TR script-path-only address:
|
|
59
|
+
* Builds a 2-of-2 P2TR script-path-only address:
|
|
60
|
+
* `tr(NUMS, multi_a(2, guardian, derived_mpc))`.
|
|
60
61
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
62
|
+
* The leaf script is a BIP-342 `multi_a` 2-of-2 — both Schnorr signatures must
|
|
63
|
+
* be present to spend, ordered so that the witness stack is
|
|
64
|
+
* `[derived_mpc_sig, guardian_sig, leaf_script, control_block]` (LIFO). This
|
|
65
|
+
* is the exact script the bridge's withdrawal path constructs in
|
|
66
|
+
* `crates/hashi-types/src/guardian/bitcoin_utils.rs`'s `compute_taproot_descriptor`.
|
|
66
67
|
*
|
|
67
|
-
* The script
|
|
68
|
-
*
|
|
69
|
-
*
|
|
68
|
+
* The leaf script is exactly 70 bytes:
|
|
69
|
+
*
|
|
70
|
+
* ```text
|
|
71
|
+
* 0x20 ‖ guardian (32) // OP_PUSHBYTES_32 <pk1>
|
|
72
|
+
* 0xAC // OP_CHECKSIG
|
|
73
|
+
* 0x20 ‖ derived_mpc(32) // OP_PUSHBYTES_32 <pk2>
|
|
74
|
+
* 0xBA // OP_CHECKSIGADD
|
|
75
|
+
* 0x52 // OP_2 (push small int 2)
|
|
76
|
+
* 0x9C // OP_NUMEQUAL
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* Note `0x52` (OP_2) — not `0x01 0x02` — because `rust-miniscript`'s `multi_a`
|
|
80
|
+
* codegen routes through `Builder::push_int(2)` which emits the small-num
|
|
81
|
+
* opcode. A literal pushdata would change the leaf hash and the address.
|
|
70
82
|
*
|
|
71
83
|
* The taproot output key is computed per BIP-341:
|
|
84
|
+
*
|
|
72
85
|
* ```text
|
|
73
|
-
* leafHash
|
|
74
|
-
* tweak
|
|
75
|
-
* outputKey
|
|
86
|
+
* leafHash = tagged_hash("TapLeaf", 0xC0 ‖ compact_size(script) ‖ script)
|
|
87
|
+
* tweak = tagged_hash("TapTweak", NUMS ‖ leafHash)
|
|
88
|
+
* outputKey = NUMS + tweak × G
|
|
76
89
|
* ```
|
|
77
90
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
91
|
+
* **Argument ordering is load-bearing.** Guardian first, derived-MPC second.
|
|
92
|
+
* Swapping them produces a different (but real-looking) `tb1p…` whose
|
|
93
|
+
* withdrawals the bridge cannot spend.
|
|
80
94
|
*
|
|
81
|
-
* @param
|
|
95
|
+
* @param guardianBtcXOnly - 32-byte BIP-340 x-only guardian BTC public key
|
|
96
|
+
* (from the on-chain `guardian_btc_public_key` config)
|
|
97
|
+
* @param derivedMpcXOnly - 32-byte x-only public key for the MPC-derived child
|
|
98
|
+
* (output of {@link deriveChildPubkey})
|
|
82
99
|
* @param network - Bitcoin network for the bech32m human-readable prefix
|
|
83
100
|
* @returns bech32m-encoded P2TR address (e.g. `bc1p…`, `tb1p…`, `bcrt1p…`)
|
|
84
101
|
*/
|
|
85
|
-
declare function
|
|
102
|
+
declare function twoOfTwoTaprootScriptPathAddress(guardianBtcXOnly: Uint8Array, derivedMpcXOnly: Uint8Array, network: BitcoinNetwork): string;
|
|
103
|
+
/**
|
|
104
|
+
* Named-args input bundle for {@link generateDepositAddress}. Using named args
|
|
105
|
+
* avoids the foot-gun of two 32-byte `Uint8Array`s in adjacent positions —
|
|
106
|
+
* swapping `guardianBtcXOnly` and `suiAddress` would silently produce a valid
|
|
107
|
+
* but wrong address.
|
|
108
|
+
*/
|
|
109
|
+
interface DepositAddressInputs {
|
|
110
|
+
/**
|
|
111
|
+
* 33-byte SEC1-compressed secp256k1 MPC master key (post-arkworks
|
|
112
|
+
* conversion). See {@link arkworksToSec1Compressed}.
|
|
113
|
+
*/
|
|
114
|
+
readonly mpcMasterCompressed: Uint8Array;
|
|
115
|
+
/**
|
|
116
|
+
* 32-byte BIP-340 x-only guardian BTC public key (from the on-chain
|
|
117
|
+
* `guardian_btc_public_key` config).
|
|
118
|
+
*/
|
|
119
|
+
readonly guardianBtcXOnly: Uint8Array;
|
|
120
|
+
/** 32-byte Sui address used as the derivation path. */
|
|
121
|
+
readonly suiAddress: Uint8Array;
|
|
122
|
+
/** Bitcoin network (determines the bech32m address prefix). */
|
|
123
|
+
readonly network: BitcoinNetwork;
|
|
124
|
+
}
|
|
86
125
|
/**
|
|
87
126
|
* Generates a Bitcoin P2TR deposit address for a Sui address.
|
|
88
127
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* Combines {@link deriveChildPubkey} (child key derivation) and
|
|
95
|
-
* {@link taprootScriptPathAddress} (taproot address construction) into a
|
|
96
|
-
* single call.
|
|
128
|
+
* Main entry point for the address derivation pipeline. Combines
|
|
129
|
+
* {@link deriveChildPubkey} and {@link twoOfTwoTaprootScriptPathAddress} into
|
|
130
|
+
* a single call. The produced address matches the Rust node's
|
|
131
|
+
* `bitcoin_utils::two_of_two_taproot_script_path_address` byte-for-byte.
|
|
97
132
|
*
|
|
98
|
-
* The
|
|
133
|
+
* The address scheme is:
|
|
99
134
|
* ```text
|
|
100
|
-
* tr(NUMS,
|
|
135
|
+
* tr(NUMS, multi_a(2, guardian, derive(mpc_master, sui_address)))
|
|
101
136
|
* ```
|
|
102
|
-
* where `H` is the MPC master key and `d` is the depositor's Sui address.
|
|
103
137
|
*
|
|
104
|
-
* For mainnet the descriptor will include the guardian key:
|
|
105
|
-
* `tr(NUMS, multi_a(2, guardian, derive(H, d)))` — not yet implemented here.
|
|
106
|
-
*
|
|
107
|
-
* @param mpcKeyCompressed - 33-byte SEC1 compressed secp256k1 MPC public key
|
|
108
|
-
* (the MPC master key, after arkworks-to-SEC1 conversion)
|
|
109
|
-
* @param suiAddress - 32-byte Sui address
|
|
110
|
-
* @param network - Bitcoin network (determines the bech32m address prefix)
|
|
111
138
|
* @returns bech32m-encoded P2TR deposit address (e.g. `tb1p…` for signet)
|
|
112
139
|
*/
|
|
113
|
-
declare function generateDepositAddress(
|
|
140
|
+
declare function generateDepositAddress({
|
|
141
|
+
mpcMasterCompressed,
|
|
142
|
+
guardianBtcXOnly,
|
|
143
|
+
suiAddress,
|
|
144
|
+
network
|
|
145
|
+
}: DepositAddressInputs): string;
|
|
114
146
|
/**
|
|
115
147
|
* Decodes a bech32/bech32m SegWit Bitcoin address into a witness program.
|
|
116
148
|
*
|
|
@@ -149,4 +181,4 @@ declare function bitcoinAddressToWitnessProgram(address: string, network: Bitcoi
|
|
|
149
181
|
*/
|
|
150
182
|
declare function witnessProgramToAddress(program: Uint8Array, network: BitcoinNetwork): string;
|
|
151
183
|
//#endregion
|
|
152
|
-
export { arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress,
|
|
184
|
+
export { DepositAddressInputs, arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, twoOfTwoTaprootScriptPathAddress, witnessProgramToAddress };
|
package/dist/bitcoin.mjs
CHANGED
|
@@ -13,30 +13,39 @@ import { concatBytes } from "@noble/hashes/utils.js";
|
|
|
13
13
|
*
|
|
14
14
|
* Hashi bridges Bitcoin and Sui by assigning each Sui address a unique Bitcoin
|
|
15
15
|
* deposit address. When BTC is sent to that address, the Hashi MPC committee
|
|
16
|
-
*
|
|
16
|
+
* and the Hashi guardian co-sign the withdrawal that spends it, and the bridge
|
|
17
|
+
* mints equivalent tokens on Sui.
|
|
17
18
|
*
|
|
18
|
-
* The deposit address is a Pay-to-Taproot (P2TR / BIP-341) script-path
|
|
19
|
-
* whose spending condition is
|
|
20
|
-
*
|
|
19
|
+
* The deposit address is a 2-of-2 Pay-to-Taproot (P2TR / BIP-341) script-path
|
|
20
|
+
* address whose spending condition is `multi_a(2, guardian_btc_pubkey,
|
|
21
|
+
* derive(mpc_master, sui_address))` — both the guardian enclave's BIP-340 key
|
|
22
|
+
* and the MPC-derived child key must produce a Schnorr signature to spend.
|
|
21
23
|
*
|
|
22
24
|
* The full derivation pipeline is:
|
|
23
25
|
*
|
|
24
|
-
* 1. **Fetch** the MPC master key from on-chain (`CommitteeSet.mpc_public_key`)
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* client's
|
|
26
|
+
* 1. **Fetch** the MPC master key from on-chain (`CommitteeSet.mpc_public_key`)
|
|
27
|
+
* and the guardian's BTC public key from the on-chain config
|
|
28
|
+
* (`guardian_btc_public_key`). The MPC bytes use the arkworks compressed
|
|
29
|
+
* format and must first be converted to SEC1 via
|
|
30
|
+
* {@link arkworksToSec1Compressed} — done automatically by the client's
|
|
31
|
+
* `view.mpcPublicKey()`. The guardian key is already in BIP-340 x-only form.
|
|
29
32
|
*
|
|
30
|
-
* 2. **Derive** a child key: `child = masterKey + HKDF-SHA3-256(x ‖ suiAddr) × G`
|
|
33
|
+
* 2. **Derive** a child MPC key: `child = masterKey + HKDF-SHA3-256(x ‖ suiAddr) × G`
|
|
31
34
|
* (see {@link deriveChildPubkey}). This replicates the Rust function
|
|
32
35
|
* `fastcrypto_tbls::threshold_schnorr::key_derivation::derive_verifying_key`.
|
|
33
36
|
*
|
|
34
|
-
* 3. **Build** the taproot address: `tr(NUMS,
|
|
35
|
-
* Nothing-Up-My-Sleeve point with no known private key,
|
|
36
|
-
* through the script path (see
|
|
37
|
+
* 3. **Build** the taproot address: `tr(NUMS, multi_a(2, guardian, child))`
|
|
38
|
+
* where NUMS is a Nothing-Up-My-Sleeve point with no known private key,
|
|
39
|
+
* forcing all spends through the script path (see
|
|
40
|
+
* {@link twoOfTwoTaprootScriptPathAddress}).
|
|
37
41
|
*
|
|
38
42
|
* The end-to-end helper {@link generateDepositAddress} combines steps 2–3.
|
|
39
43
|
*
|
|
44
|
+
* Mirrors `two_of_two_taproot_script_path_address` in
|
|
45
|
+
* `crates/hashi-types/src/guardian/bitcoin_utils.rs`. Cross-language test
|
|
46
|
+
* vectors live in both this file's unit tests and the matching Rust unit test
|
|
47
|
+
* `cross_lang_2of2_test_vectors`.
|
|
48
|
+
*
|
|
40
49
|
* @see https://mystenlabs.github.io/hashi/design/address-scheme.html
|
|
41
50
|
*/
|
|
42
51
|
const Point = secp256k1.Point;
|
|
@@ -127,38 +136,61 @@ function deriveChildPubkey(mpcKeyCompressed, suiAddress) {
|
|
|
127
136
|
return parentPoint.add(Point.BASE.multiply(tweakScalar)).toBytes(true).slice(1);
|
|
128
137
|
}
|
|
129
138
|
/**
|
|
130
|
-
* Builds a P2TR script-path-only address:
|
|
139
|
+
* Builds a 2-of-2 P2TR script-path-only address:
|
|
140
|
+
* `tr(NUMS, multi_a(2, guardian, derived_mpc))`.
|
|
141
|
+
*
|
|
142
|
+
* The leaf script is a BIP-342 `multi_a` 2-of-2 — both Schnorr signatures must
|
|
143
|
+
* be present to spend, ordered so that the witness stack is
|
|
144
|
+
* `[derived_mpc_sig, guardian_sig, leaf_script, control_block]` (LIFO). This
|
|
145
|
+
* is the exact script the bridge's withdrawal path constructs in
|
|
146
|
+
* `crates/hashi-types/src/guardian/bitcoin_utils.rs`'s `compute_taproot_descriptor`.
|
|
147
|
+
*
|
|
148
|
+
* The leaf script is exactly 70 bytes:
|
|
131
149
|
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
150
|
+
* ```text
|
|
151
|
+
* 0x20 ‖ guardian (32) // OP_PUSHBYTES_32 <pk1>
|
|
152
|
+
* 0xAC // OP_CHECKSIG
|
|
153
|
+
* 0x20 ‖ derived_mpc(32) // OP_PUSHBYTES_32 <pk2>
|
|
154
|
+
* 0xBA // OP_CHECKSIGADD
|
|
155
|
+
* 0x52 // OP_2 (push small int 2)
|
|
156
|
+
* 0x9C // OP_NUMEQUAL
|
|
157
|
+
* ```
|
|
137
158
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
159
|
+
* Note `0x52` (OP_2) — not `0x01 0x02` — because `rust-miniscript`'s `multi_a`
|
|
160
|
+
* codegen routes through `Builder::push_int(2)` which emits the small-num
|
|
161
|
+
* opcode. A literal pushdata would change the leaf hash and the address.
|
|
141
162
|
*
|
|
142
163
|
* The taproot output key is computed per BIP-341:
|
|
164
|
+
*
|
|
143
165
|
* ```text
|
|
144
|
-
* leafHash
|
|
145
|
-
* tweak
|
|
146
|
-
* outputKey
|
|
166
|
+
* leafHash = tagged_hash("TapLeaf", 0xC0 ‖ compact_size(script) ‖ script)
|
|
167
|
+
* tweak = tagged_hash("TapTweak", NUMS ‖ leafHash)
|
|
168
|
+
* outputKey = NUMS + tweak × G
|
|
147
169
|
* ```
|
|
148
170
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
171
|
+
* **Argument ordering is load-bearing.** Guardian first, derived-MPC second.
|
|
172
|
+
* Swapping them produces a different (but real-looking) `tb1p…` whose
|
|
173
|
+
* withdrawals the bridge cannot spend.
|
|
151
174
|
*
|
|
152
|
-
* @param
|
|
175
|
+
* @param guardianBtcXOnly - 32-byte BIP-340 x-only guardian BTC public key
|
|
176
|
+
* (from the on-chain `guardian_btc_public_key` config)
|
|
177
|
+
* @param derivedMpcXOnly - 32-byte x-only public key for the MPC-derived child
|
|
178
|
+
* (output of {@link deriveChildPubkey})
|
|
153
179
|
* @param network - Bitcoin network for the bech32m human-readable prefix
|
|
154
180
|
* @returns bech32m-encoded P2TR address (e.g. `bc1p…`, `tb1p…`, `bcrt1p…`)
|
|
155
181
|
*/
|
|
156
|
-
function
|
|
157
|
-
if (
|
|
158
|
-
|
|
182
|
+
function twoOfTwoTaprootScriptPathAddress(guardianBtcXOnly, derivedMpcXOnly, network) {
|
|
183
|
+
if (guardianBtcXOnly.length !== 32) throw new Error(`Expected 32-byte x-only guardian pubkey, got ${guardianBtcXOnly.length}`);
|
|
184
|
+
if (derivedMpcXOnly.length !== 32) throw new Error(`Expected 32-byte x-only derived MPC pubkey, got ${derivedMpcXOnly.length}`);
|
|
185
|
+
const tapscript = new Uint8Array(70);
|
|
159
186
|
tapscript[0] = 32;
|
|
160
|
-
tapscript.set(
|
|
187
|
+
tapscript.set(guardianBtcXOnly, 1);
|
|
161
188
|
tapscript[33] = 172;
|
|
189
|
+
tapscript[34] = 32;
|
|
190
|
+
tapscript.set(derivedMpcXOnly, 35);
|
|
191
|
+
tapscript[67] = 186;
|
|
192
|
+
tapscript[68] = 82;
|
|
193
|
+
tapscript[69] = 156;
|
|
162
194
|
const tweakScalar = bytesToNumberBE(taggedHash("TapTweak", NUMS_KEY, taggedHash("TapLeaf", new Uint8Array([192, tapscript.length]), tapscript))) % CURVE_ORDER;
|
|
163
195
|
const outputKey = NUMS_POINT.add(Point.BASE.multiply(tweakScalar)).toBytes(true).slice(1);
|
|
164
196
|
const words = [1, ...bech32m.toWords(outputKey)];
|
|
@@ -167,32 +199,20 @@ function taprootScriptPathAddress(pubkey, network) {
|
|
|
167
199
|
/**
|
|
168
200
|
* Generates a Bitcoin P2TR deposit address for a Sui address.
|
|
169
201
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* Combines {@link deriveChildPubkey} (child key derivation) and
|
|
176
|
-
* {@link taprootScriptPathAddress} (taproot address construction) into a
|
|
177
|
-
* single call.
|
|
202
|
+
* Main entry point for the address derivation pipeline. Combines
|
|
203
|
+
* {@link deriveChildPubkey} and {@link twoOfTwoTaprootScriptPathAddress} into
|
|
204
|
+
* a single call. The produced address matches the Rust node's
|
|
205
|
+
* `bitcoin_utils::two_of_two_taproot_script_path_address` byte-for-byte.
|
|
178
206
|
*
|
|
179
|
-
* The
|
|
207
|
+
* The address scheme is:
|
|
180
208
|
* ```text
|
|
181
|
-
* tr(NUMS,
|
|
209
|
+
* tr(NUMS, multi_a(2, guardian, derive(mpc_master, sui_address)))
|
|
182
210
|
* ```
|
|
183
|
-
* where `H` is the MPC master key and `d` is the depositor's Sui address.
|
|
184
211
|
*
|
|
185
|
-
* For mainnet the descriptor will include the guardian key:
|
|
186
|
-
* `tr(NUMS, multi_a(2, guardian, derive(H, d)))` — not yet implemented here.
|
|
187
|
-
*
|
|
188
|
-
* @param mpcKeyCompressed - 33-byte SEC1 compressed secp256k1 MPC public key
|
|
189
|
-
* (the MPC master key, after arkworks-to-SEC1 conversion)
|
|
190
|
-
* @param suiAddress - 32-byte Sui address
|
|
191
|
-
* @param network - Bitcoin network (determines the bech32m address prefix)
|
|
192
212
|
* @returns bech32m-encoded P2TR deposit address (e.g. `tb1p…` for signet)
|
|
193
213
|
*/
|
|
194
|
-
function generateDepositAddress(
|
|
195
|
-
return
|
|
214
|
+
function generateDepositAddress({ mpcMasterCompressed, guardianBtcXOnly, suiAddress, network }) {
|
|
215
|
+
return twoOfTwoTaprootScriptPathAddress(guardianBtcXOnly, deriveChildPubkey(mpcMasterCompressed, suiAddress), network);
|
|
196
216
|
}
|
|
197
217
|
/**
|
|
198
218
|
* Decodes a bech32/bech32m SegWit Bitcoin address into a witness program.
|
|
@@ -292,4 +312,4 @@ function witnessProgramToAddress(program, network) {
|
|
|
292
312
|
}
|
|
293
313
|
|
|
294
314
|
//#endregion
|
|
295
|
-
export { arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress,
|
|
315
|
+
export { arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, twoOfTwoTaprootScriptPathAddress, witnessProgramToAddress };
|
package/dist/client.d.mts
CHANGED
|
@@ -50,8 +50,21 @@ declare class HashiClient {
|
|
|
50
50
|
/**
|
|
51
51
|
* Generates a unique Bitcoin P2TR deposit address for a Sui address.
|
|
52
52
|
*
|
|
53
|
-
* Fetches the MPC committee public key
|
|
54
|
-
*
|
|
53
|
+
* Fetches the MPC committee public key and the guardian's BTC public key
|
|
54
|
+
* from on-chain, derives an MPC child key against the Sui address, and
|
|
55
|
+
* builds a 2-of-2 taproot script-path address
|
|
56
|
+
* (`tr(NUMS, multi_a(2, guardian, derived_mpc))`). The address matches
|
|
57
|
+
* the bridge's on-chain `validate_deposit_request_derivation_path` check
|
|
58
|
+
* byte-for-byte.
|
|
59
|
+
*
|
|
60
|
+
* The MPC key (`committee_set.mpc_public_key`) and the guardian key
|
|
61
|
+
* (`guardian_btc_public_key` config) come from a single fetch of the
|
|
62
|
+
* Hashi object, and only those two fields are parsed — an unrelated
|
|
63
|
+
* malformed config entry can't block deposit-address generation.
|
|
64
|
+
*
|
|
65
|
+
* Throws `HashiConfigError` if the deployment isn't guardian-provisioned
|
|
66
|
+
* yet (no `guardian_btc_public_key` on chain). The SDK refuses to fall
|
|
67
|
+
* back to a single-key address because the bridge validator rejects it.
|
|
55
68
|
*
|
|
56
69
|
* @example
|
|
57
70
|
* ```ts
|
package/dist/client.mjs
CHANGED
|
@@ -6,13 +6,13 @@ import { WithdrawalRequest, WithdrawalTransaction } from "./contracts/hashi/with
|
|
|
6
6
|
import { BitcoinState, BitcoinStateKey } from "./contracts/hashi/bitcoin_state.mjs";
|
|
7
7
|
import { deposit } from "./contracts/hashi/deposit.mjs";
|
|
8
8
|
import { cancelWithdrawal, requestWithdrawal } from "./contracts/hashi/withdraw.mjs";
|
|
9
|
-
import { DUST_RELAY_MIN_VALUE, NETWORK_CONFIG } from "./constants.mjs";
|
|
9
|
+
import { DUST_RELAY_MIN_VALUE, GUARDIAN_BTC_PUBLIC_KEY_LEN, GUARDIAN_PUBLIC_KEY_LEN, NETWORK_CONFIG } from "./constants.mjs";
|
|
10
10
|
import { AmountBelowMinimumError, HashiConfigError, HashiFetchError, HashiPausedError, InvalidParamsError } from "./errors.mjs";
|
|
11
11
|
import { arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, generateDepositAddress } from "./bitcoin.mjs";
|
|
12
12
|
import { getTxConfirmations, lookupAllVouts, lookupVout } from "./btc-rpc.mjs";
|
|
13
|
-
import { assertHex32, entry, reverseTxidBytes } from "./util.mjs";
|
|
13
|
+
import { assertHex32, configBytes, entry, reverseTxidBytes } from "./util.mjs";
|
|
14
14
|
import { TypeTagSerializer, bcs } from "@mysten/sui/bcs";
|
|
15
|
-
import { deriveDynamicFieldID, fromHex } from "@mysten/sui/utils";
|
|
15
|
+
import { deriveDynamicFieldID, fromHex, normalizeSuiAddress } from "@mysten/sui/utils";
|
|
16
16
|
import { base58 } from "@scure/base";
|
|
17
17
|
import { Transaction, coinWithBalance } from "@mysten/sui/transactions";
|
|
18
18
|
|
|
@@ -176,26 +176,13 @@ var HashiClient = class {
|
|
|
176
176
|
};
|
|
177
177
|
this.view = {
|
|
178
178
|
mpcPublicKey: async () => {
|
|
179
|
-
|
|
179
|
+
return parseMpcPublicKey((await Hashi.get({
|
|
180
180
|
client: this.#client,
|
|
181
181
|
objectId: this.#hashiObjectId
|
|
182
|
-
});
|
|
183
|
-
const mpcKey = new Uint8Array(result.json.committee_set.mpc_public_key);
|
|
184
|
-
if (mpcKey.length === 0) throw new Error("MPC public key not available on-chain. Has the committee completed DKG?");
|
|
185
|
-
return arkworksToSec1Compressed(mpcKey);
|
|
182
|
+
})).json.committee_set.mpc_public_key);
|
|
186
183
|
},
|
|
187
184
|
all: async () => {
|
|
188
|
-
|
|
189
|
-
try {
|
|
190
|
-
result = await Hashi.get({
|
|
191
|
-
client: this.#client,
|
|
192
|
-
objectId: this.#hashiObjectId
|
|
193
|
-
});
|
|
194
|
-
} catch (cause) {
|
|
195
|
-
throw new HashiFetchError(`Failed to fetch Hashi shared object ${this.#hashiObjectId}.`, this.#hashiObjectId, { cause });
|
|
196
|
-
}
|
|
197
|
-
const contents = result.json?.config?.config?.contents;
|
|
198
|
-
if (!Array.isArray(contents)) throw new HashiFetchError(`Hashi object ${this.#hashiObjectId} returned an unexpected shape: config.config.contents is not an array.`, this.#hashiObjectId);
|
|
185
|
+
const { contents } = await this.#fetchHashiObject();
|
|
199
186
|
return this.#parseConfig(contents);
|
|
200
187
|
},
|
|
201
188
|
paused: async () => (await this.view.all()).paused,
|
|
@@ -455,8 +442,21 @@ var HashiClient = class {
|
|
|
455
442
|
/**
|
|
456
443
|
* Generates a unique Bitcoin P2TR deposit address for a Sui address.
|
|
457
444
|
*
|
|
458
|
-
* Fetches the MPC committee public key
|
|
459
|
-
*
|
|
445
|
+
* Fetches the MPC committee public key and the guardian's BTC public key
|
|
446
|
+
* from on-chain, derives an MPC child key against the Sui address, and
|
|
447
|
+
* builds a 2-of-2 taproot script-path address
|
|
448
|
+
* (`tr(NUMS, multi_a(2, guardian, derived_mpc))`). The address matches
|
|
449
|
+
* the bridge's on-chain `validate_deposit_request_derivation_path` check
|
|
450
|
+
* byte-for-byte.
|
|
451
|
+
*
|
|
452
|
+
* The MPC key (`committee_set.mpc_public_key`) and the guardian key
|
|
453
|
+
* (`guardian_btc_public_key` config) come from a single fetch of the
|
|
454
|
+
* Hashi object, and only those two fields are parsed — an unrelated
|
|
455
|
+
* malformed config entry can't block deposit-address generation.
|
|
456
|
+
*
|
|
457
|
+
* Throws `HashiConfigError` if the deployment isn't guardian-provisioned
|
|
458
|
+
* yet (no `guardian_btc_public_key` on chain). The SDK refuses to fall
|
|
459
|
+
* back to a single-key address because the bridge validator rejects it.
|
|
460
460
|
*
|
|
461
461
|
* @example
|
|
462
462
|
* ```ts
|
|
@@ -466,7 +466,13 @@ var HashiClient = class {
|
|
|
466
466
|
* ```
|
|
467
467
|
*/
|
|
468
468
|
async generateDepositAddress({ suiAddress, bitcoinNetwork = this.#bitcoinNetwork }) {
|
|
469
|
-
|
|
469
|
+
const { json, contents } = await this.#fetchHashiObject();
|
|
470
|
+
return generateDepositAddress({
|
|
471
|
+
mpcMasterCompressed: parseMpcPublicKey(json.committee_set.mpc_public_key),
|
|
472
|
+
guardianBtcXOnly: configBytes(contents, "guardian_btc_public_key", GUARDIAN_BTC_PUBLIC_KEY_LEN),
|
|
473
|
+
suiAddress: fromHex(normalizeSuiAddress(suiAddress)),
|
|
474
|
+
network: bitcoinNetwork
|
|
475
|
+
});
|
|
470
476
|
}
|
|
471
477
|
/**
|
|
472
478
|
* Submit one or more Bitcoin deposits for committee confirmation, batched
|
|
@@ -633,6 +639,22 @@ var HashiClient = class {
|
|
|
633
639
|
};
|
|
634
640
|
const bool = (key) => entry(contents, key, "Bool").Bool;
|
|
635
641
|
const addr = (key) => entry(contents, key, "Address").Address;
|
|
642
|
+
const optionalString = (key) => {
|
|
643
|
+
try {
|
|
644
|
+
return entry(contents, key, "String").String;
|
|
645
|
+
} catch (e) {
|
|
646
|
+
if (e instanceof HashiConfigError && e.actualVariant === void 0) return null;
|
|
647
|
+
throw e;
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
const optionalBytes = (key, expectedLen) => {
|
|
651
|
+
try {
|
|
652
|
+
return configBytes(contents, key, expectedLen);
|
|
653
|
+
} catch (e) {
|
|
654
|
+
if (e instanceof HashiConfigError && e.actualVariant === void 0) return null;
|
|
655
|
+
throw e;
|
|
656
|
+
}
|
|
657
|
+
};
|
|
636
658
|
const rawDepositMin = u64("bitcoin_deposit_minimum");
|
|
637
659
|
const rawWithdrawalMin = u64("bitcoin_withdrawal_minimum");
|
|
638
660
|
const bitcoinDepositMinimum = rawDepositMin < DUST_RELAY_MIN_VALUE ? DUST_RELAY_MIN_VALUE : rawDepositMin;
|
|
@@ -646,7 +668,10 @@ var HashiClient = class {
|
|
|
646
668
|
withdrawalCancellationCooldownMs: u64("withdrawal_cancellation_cooldown_ms"),
|
|
647
669
|
bitcoinDepositTimeDelayMs: u64("bitcoin_deposit_time_delay_ms"),
|
|
648
670
|
depositMinimum: bitcoinDepositMinimum,
|
|
649
|
-
worstCaseNetworkFee: bitcoinWithdrawalMinimum - DUST_RELAY_MIN_VALUE
|
|
671
|
+
worstCaseNetworkFee: bitcoinWithdrawalMinimum - DUST_RELAY_MIN_VALUE,
|
|
672
|
+
guardianUrl: optionalString("guardian_url"),
|
|
673
|
+
guardianPublicKey: optionalBytes("guardian_public_key", GUARDIAN_PUBLIC_KEY_LEN),
|
|
674
|
+
guardianBtcPublicKey: optionalBytes("guardian_btc_public_key", GUARDIAN_BTC_PUBLIC_KEY_LEN)
|
|
650
675
|
};
|
|
651
676
|
}
|
|
652
677
|
/**
|
|
@@ -696,6 +721,31 @@ var HashiClient = class {
|
|
|
696
721
|
return 0n;
|
|
697
722
|
}
|
|
698
723
|
/**
|
|
724
|
+
* Fetches the Hashi shared object once and returns its decoded `json`
|
|
725
|
+
* alongside the validated governance-config `contents` array. Wraps
|
|
726
|
+
* transport failures and unexpected shapes in `HashiFetchError`. Shared by
|
|
727
|
+
* `view.all()` and `generateDepositAddress` so a single round-trip serves
|
|
728
|
+
* both the committee key (from `json`) and the config reads (from
|
|
729
|
+
* `contents`).
|
|
730
|
+
*/
|
|
731
|
+
async #fetchHashiObject() {
|
|
732
|
+
let result;
|
|
733
|
+
try {
|
|
734
|
+
result = await Hashi.get({
|
|
735
|
+
client: this.#client,
|
|
736
|
+
objectId: this.#hashiObjectId
|
|
737
|
+
});
|
|
738
|
+
} catch (cause) {
|
|
739
|
+
throw new HashiFetchError(`Failed to fetch Hashi shared object ${this.#hashiObjectId}.`, this.#hashiObjectId, { cause });
|
|
740
|
+
}
|
|
741
|
+
const contents = result.json?.config?.config?.contents;
|
|
742
|
+
if (!Array.isArray(contents)) throw new HashiFetchError(`Hashi object ${this.#hashiObjectId} returned an unexpected shape: config.config.contents is not an array.`, this.#hashiObjectId);
|
|
743
|
+
return {
|
|
744
|
+
json: result.json,
|
|
745
|
+
contents
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
699
749
|
* Fetches the `BitcoinState` dynamic field from the Hashi shared object.
|
|
700
750
|
* Returns the BCS-parsed struct whose nested Bag/Table IDs are used by
|
|
701
751
|
* `findUsedUtxos` and `transactionHistory`.
|
|
@@ -851,6 +901,16 @@ var HashiClient = class {
|
|
|
851
901
|
}
|
|
852
902
|
}
|
|
853
903
|
};
|
|
904
|
+
/**
|
|
905
|
+
* Convert the on-chain `committee_set.mpc_public_key` (arkworks-compressed)
|
|
906
|
+
* into a 33-byte SEC1 key. Throws `HashiConfigError` if DKG hasn't populated
|
|
907
|
+
* it yet (empty vector).
|
|
908
|
+
*/
|
|
909
|
+
function parseMpcPublicKey(raw) {
|
|
910
|
+
const mpcKey = new Uint8Array(raw);
|
|
911
|
+
if (mpcKey.length === 0) throw HashiConfigError.missing("committee_set.mpc_public_key", "Bytes");
|
|
912
|
+
return arkworksToSec1Compressed(mpcKey);
|
|
913
|
+
}
|
|
854
914
|
function parseDepositHistoryItem(content, timeDelayMs) {
|
|
855
915
|
const parsed = DepositRequest.parse(content);
|
|
856
916
|
const approvalTimestampMs = parsed.approval_timestamp_ms === null ? null : BigInt(parsed.approval_timestamp_ms);
|
package/dist/constants.mjs
CHANGED
|
@@ -50,6 +50,16 @@ const NUMS_KEY = new Uint8Array([
|
|
|
50
50
|
* `hashi::btc_config`.
|
|
51
51
|
*/
|
|
52
52
|
const DUST_RELAY_MIN_VALUE = 546n;
|
|
53
|
+
/**
|
|
54
|
+
* Length of the Guardian's Ed25519 attestation public key, in bytes. Matches
|
|
55
|
+
* `GUARDIAN_PUBLIC_KEY_LEN` in `hashi::config`.
|
|
56
|
+
*/
|
|
57
|
+
const GUARDIAN_PUBLIC_KEY_LEN = 32;
|
|
58
|
+
/**
|
|
59
|
+
* Length of the Guardian's BIP-340 x-only BTC public key, in bytes. Matches
|
|
60
|
+
* `GUARDIAN_BTC_PUBLIC_KEY_LEN` in `hashi::config`.
|
|
61
|
+
*/
|
|
62
|
+
const GUARDIAN_BTC_PUBLIC_KEY_LEN = 32;
|
|
53
63
|
const NETWORK_CONFIG = { devnet: {
|
|
54
64
|
hashiObjectId: "0x61f1acbf8dd79d284cb312853389747a55a158b1b12a68554afcade05dec5f40",
|
|
55
65
|
packageId: "0xc377616c626d73556cae080706acf37e862d24d26c690cc7be5a957f53fadf17",
|
|
@@ -57,4 +67,4 @@ const NETWORK_CONFIG = { devnet: {
|
|
|
57
67
|
} };
|
|
58
68
|
|
|
59
69
|
//#endregion
|
|
60
|
-
export { DUST_RELAY_MIN_VALUE, NETWORK_CONFIG, NETWORK_HRP, NUMS_KEY };
|
|
70
|
+
export { DUST_RELAY_MIN_VALUE, GUARDIAN_BTC_PUBLIC_KEY_LEN, GUARDIAN_PUBLIC_KEY_LEN, NETWORK_CONFIG, NETWORK_HRP, NUMS_KEY };
|
|
@@ -20,7 +20,8 @@ const UtxoRecord = new MoveStruct({
|
|
|
20
20
|
fields: {
|
|
21
21
|
utxo: Utxo,
|
|
22
22
|
produced_by: bcs.option(bcs.Address),
|
|
23
|
-
locked_by: bcs.option(bcs.Address)
|
|
23
|
+
locked_by: bcs.option(bcs.Address),
|
|
24
|
+
spent_epoch: bcs.option(bcs.u64())
|
|
24
25
|
}
|
|
25
26
|
});
|
|
26
27
|
const UtxoSpentEvent = new MoveStruct({
|
|
@@ -27,7 +27,8 @@ const WithdrawalSignedMessage = new MoveStruct({
|
|
|
27
27
|
fields: {
|
|
28
28
|
withdrawal_id: bcs.Address,
|
|
29
29
|
request_ids: bcs.vector(bcs.Address),
|
|
30
|
-
signatures: bcs.vector(bcs.vector(bcs.u8()))
|
|
30
|
+
signatures: bcs.vector(bcs.vector(bcs.u8())),
|
|
31
|
+
guardian_signatures: bcs.vector(bcs.vector(bcs.u8()))
|
|
31
32
|
}
|
|
32
33
|
});
|
|
33
34
|
const WithdrawalConfirmationMessage = new MoveStruct({
|
|
@@ -61,6 +61,7 @@ const WithdrawalTransaction = new MoveStruct({
|
|
|
61
61
|
timestamp_ms: bcs.u64(),
|
|
62
62
|
randomness: bcs.vector(bcs.u8()),
|
|
63
63
|
signatures: bcs.option(bcs.vector(bcs.vector(bcs.u8()))),
|
|
64
|
+
guardian_signatures: bcs.option(bcs.vector(bcs.vector(bcs.u8()))),
|
|
64
65
|
presig_start_index: bcs.u64(),
|
|
65
66
|
epoch: bcs.u64()
|
|
66
67
|
}
|
|
@@ -105,7 +106,16 @@ const WithdrawalSignedEvent = new MoveStruct({
|
|
|
105
106
|
fields: {
|
|
106
107
|
withdrawal_txn_id: bcs.Address,
|
|
107
108
|
request_ids: bcs.vector(bcs.Address),
|
|
108
|
-
signatures: bcs.vector(bcs.vector(bcs.u8()))
|
|
109
|
+
signatures: bcs.vector(bcs.vector(bcs.u8())),
|
|
110
|
+
guardian_signatures: bcs.vector(bcs.vector(bcs.u8()))
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
const WithdrawalPresigsReassignedEvent = new MoveStruct({
|
|
114
|
+
name: `${$moduleName}::WithdrawalPresigsReassignedEvent`,
|
|
115
|
+
fields: {
|
|
116
|
+
withdrawal_txn_id: bcs.Address,
|
|
117
|
+
epoch: bcs.u64(),
|
|
118
|
+
presig_start_index: bcs.u64()
|
|
109
119
|
}
|
|
110
120
|
});
|
|
111
121
|
const WithdrawalConfirmedEvent = new MoveStruct({
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
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";
|
|
2
2
|
import { HashiClient, hashi } from "./client.mjs";
|
|
3
3
|
import { AmountBelowMinimumError, AmountViolation, HashiConfigError, HashiFetchError, HashiPausedError, InvalidBitcoinAddressCode, InvalidBitcoinAddressError, InvalidParamsError } from "./errors.mjs";
|
|
4
|
-
import { arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress,
|
|
5
|
-
export { AmountBelowMinimumError, type AmountViolation, type BitcoinNetwork, type CancelWithdrawalParams, 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,
|
|
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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AmountBelowMinimumError, HashiConfigError, HashiFetchError, HashiPausedError, InvalidBitcoinAddressError, InvalidParamsError } from "./errors.mjs";
|
|
2
|
-
import { arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress,
|
|
2
|
+
import { arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, twoOfTwoTaprootScriptPathAddress, witnessProgramToAddress } from "./bitcoin.mjs";
|
|
3
3
|
import { HashiClient, hashi } from "./client.mjs";
|
|
4
4
|
|
|
5
|
-
export { AmountBelowMinimumError, HashiClient, HashiConfigError, HashiFetchError, HashiPausedError, InvalidBitcoinAddressError, InvalidParamsError, arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, hashi,
|
|
5
|
+
export { AmountBelowMinimumError, HashiClient, HashiConfigError, HashiFetchError, HashiPausedError, InvalidBitcoinAddressError, InvalidParamsError, arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, hashi, twoOfTwoTaprootScriptPathAddress, witnessProgramToAddress };
|
package/dist/types.d.mts
CHANGED
|
@@ -28,6 +28,11 @@ interface HashiClientOptions<Name = "HashiClient"> {
|
|
|
28
28
|
* state. `depositMinimum` is a Move-side alias of `bitcoinDepositMinimum`;
|
|
29
29
|
* `worstCaseNetworkFee` is derived as `bitcoinWithdrawalMinimum - 546` (the
|
|
30
30
|
* dust relay floor) and is always ≥ 1.
|
|
31
|
+
*
|
|
32
|
+
* The three `guardian*` fields are `null` on deployments where the guardian
|
|
33
|
+
* config hasn't been written yet (pre-feature chains or in-flight rollouts).
|
|
34
|
+
* `guardianBtcPublicKey` is required for {@link HashiClient.generateDepositAddress}
|
|
35
|
+
* to succeed.
|
|
31
36
|
*/
|
|
32
37
|
interface GovernanceConfig {
|
|
33
38
|
readonly paused: boolean;
|
|
@@ -39,6 +44,19 @@ interface GovernanceConfig {
|
|
|
39
44
|
readonly bitcoinDepositTimeDelayMs: bigint;
|
|
40
45
|
readonly depositMinimum: bigint;
|
|
41
46
|
readonly worstCaseNetworkFee: bigint;
|
|
47
|
+
/** Guardian gRPC/HTTP endpoint from on-chain config. `null` if unset. */
|
|
48
|
+
readonly guardianUrl: string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Guardian's Ed25519 attestation key (32 bytes), used to verify signed
|
|
51
|
+
* `GetGuardianInfo` responses. `null` if unset.
|
|
52
|
+
*/
|
|
53
|
+
readonly guardianPublicKey: Uint8Array | null;
|
|
54
|
+
/**
|
|
55
|
+
* Guardian's BIP-340 x-only secp256k1 BTC public key (32 bytes), the
|
|
56
|
+
* `pk1` slot of the on-chain 2-of-2 deposit-address descriptor. `null`
|
|
57
|
+
* if unset (deposit-address derivation will fail).
|
|
58
|
+
*/
|
|
59
|
+
readonly guardianBtcPublicKey: Uint8Array | null;
|
|
42
60
|
}
|
|
43
61
|
/**
|
|
44
62
|
* A single UTXO output within a Bitcoin transaction used to fund a deposit.
|
package/dist/util.mjs
CHANGED
|
@@ -45,6 +45,16 @@ function entry(contents, key, expectedVariant) {
|
|
|
45
45
|
if (e.value.$kind !== expectedVariant) throw HashiConfigError.wrongVariant(key, expectedVariant, e.value.$kind);
|
|
46
46
|
return e.value;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Read a `Bytes` config entry and assert its byte length. Throws
|
|
50
|
+
* `HashiConfigError` if the key is missing, holds a non-`Bytes` variant, or
|
|
51
|
+
* has the wrong length.
|
|
52
|
+
*/
|
|
53
|
+
function configBytes(contents, key, expectedLen) {
|
|
54
|
+
const v = entry(contents, key, "Bytes");
|
|
55
|
+
if (v.Bytes.length !== expectedLen) throw HashiConfigError.malformedPayload(key, "Bytes", `expected ${expectedLen} bytes, got ${v.Bytes.length}`);
|
|
56
|
+
return new Uint8Array(v.Bytes);
|
|
57
|
+
}
|
|
48
58
|
|
|
49
59
|
//#endregion
|
|
50
|
-
export { assertHex32, entry, reverseTxidBytes };
|
|
60
|
+
export { assertHex32, configBytes, entry, reverseTxidBytes };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mysten-incubation/hashi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "TypeScript SDK for the Hashi Sui Move smart contracts",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Mysten Labs <build@mystenlabs.com>",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"test": "vitest --project=unit",
|
|
60
60
|
"test:integration": "vitest run --project=integration",
|
|
61
61
|
"coverage": "vitest run --project=unit --coverage",
|
|
62
|
-
"codegen": "sui-ts-codegen generate",
|
|
62
|
+
"codegen": "sui-ts-codegen generate && prettier --ignore-path /dev/null --tab-width 4 --print-width 100 --write 'src/contracts/**/*.ts'",
|
|
63
63
|
"format": "prettier --write \"**/*.{ts,json,md}\""
|
|
64
64
|
}
|
|
65
65
|
}
|