@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 CHANGED
@@ -1,5 +1,25 @@
1
1
  # @mysten-incubation/hashi
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - ff9a398: Wire up Sui testnet: add the testnet Hashi object and package ids to `NETWORK_CONFIG` (BTC signet), so `hashi({ network: "testnet" })` works out of the box
8
+
9
+ ### Patch Changes
10
+
11
+ - ff9a398: Bump hashi submodule to cd2b81f (no contract or binding changes)
12
+
13
+ ## 0.4.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 874ec08: feat: add a `client.hashi.guardian.*` namespace (`info`, `limiterStatus`, `canWithdraw`) that reads the guardian's rate-limiter headroom from its read-only `/info` endpoint, resolving the guardian URL from `guardianUrl`, a `guardianInfoProvider`, or the on-chain `guardian_url` config
18
+
19
+ ### Patch Changes
20
+
21
+ - 8f7606f: Track the redeployed devnet contracts: regenerate bindings against hashi's `testnet` tip (`0e67b619`) (`config_value::Value` gained `U128`/`U256`, shifting the BCS tags the SDK decodes the on-chain config with), follow the `DepositRequested`/`WithdrawalRequested` event renames and request-object field renames, and point `NETWORK_CONFIG.devnet` at the new package and Hashi object.
22
+
3
23
  ## 0.3.1
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -6,7 +6,7 @@ TypeScript SDK for the [Hashi](https://github.com/MystenLabs/hashi) protocol. Ha
6
6
  > **Pre-1.0:** This package is under active development. Minor versions may contain breaking changes until the API stabilizes at 1.0.
7
7
 
8
8
  > [!WARNING]
9
- > Only Sui **devnet** is currently wired up (Bitcoin **signet** by default). Testnet and mainnet are not yet deployed.
9
+ > Sui **testnet** and **devnet** are wired up (Bitcoin **signet** by default). Prefer testnet devnet support is temporary. Mainnet is not yet deployed.
10
10
 
11
11
  ## Install
12
12
 
@@ -26,9 +26,9 @@ import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
26
26
  import { hashi } from "@mysten-incubation/hashi";
27
27
 
28
28
  const client = new SuiGrpcClient({
29
- network: "devnet",
30
- baseUrl: "https://fullnode.devnet.sui.io:443",
31
- }).$extend(hashi({ network: "devnet" }));
29
+ network: "testnet",
30
+ baseUrl: "https://fullnode.testnet.sui.io:443",
31
+ }).$extend(hashi({ network: "testnet" }));
32
32
 
33
33
  const signer = Ed25519Keypair.fromSecretKey(/* … */);
34
34
  const recipient = signer.toSuiAddress();
@@ -45,7 +45,7 @@ await client.hashi.deposit({
45
45
  });
46
46
  ```
47
47
 
48
- End-user actions only: **deposit**, **request withdrawal**, **cancel withdrawal**.
48
+ End-user actions only: **deposit**, **request withdrawal**, **cancel withdrawal**. An optional `client.hashi.guardian.*` namespace reads the guardian's rate-limiter headroom (`limiterStatus`, `canWithdraw`) — see the root README.
49
49
 
50
50
  ## Documentation
51
51
 
package/dist/client.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { RawTransactionArgument } from "./contracts/utils/index.mjs";
2
- import { BitcoinNetwork, CancelWithdrawalParams, DepositFees, DepositInfo, DepositParams, GovernanceConfig, HashiClientOptions, HbtcBalance, SuiNetwork, TransactionHistoryItem, UtxoId, UtxoLookupResult, UtxoUsageResult, WaitOptions, WithdrawalFees, WithdrawalInfo, WithdrawalParams } from "./types.mjs";
2
+ import { BitcoinNetwork, CancelWithdrawalParams, DepositFees, DepositInfo, DepositParams, GovernanceConfig, GuardianInfoProvider, GuardianLimiterSnapshot, GuardianWithdrawCheck, HashiClientOptions, HbtcBalance, RawGuardianInfo, SuiNetwork, TransactionHistoryItem, UtxoId, UtxoLookupResult, UtxoUsageResult, WaitOptions, WithdrawalFees, WithdrawalInfo, WithdrawalParams } from "./types.mjs";
3
3
  import * as _mysten_sui_transactions0 from "@mysten/sui/transactions";
4
4
  import { Transaction } from "@mysten/sui/transactions";
5
5
  import { ClientWithCoreApi, SuiClientTypes } from "@mysten/sui/client";
@@ -37,7 +37,9 @@ declare class HashiClient {
37
37
  packageId,
38
38
  bitcoinNetwork,
39
39
  btcRpcUrl,
40
- graphqlUrl
40
+ graphqlUrl,
41
+ guardianUrl,
42
+ guardianInfoProvider
41
43
  }: {
42
44
  client: ClientWithCoreApi;
43
45
  network: SuiNetwork;
@@ -46,6 +48,8 @@ declare class HashiClient {
46
48
  bitcoinNetwork?: BitcoinNetwork;
47
49
  btcRpcUrl?: string;
48
50
  graphqlUrl?: string;
51
+ guardianUrl?: string;
52
+ guardianInfoProvider?: GuardianInfoProvider;
49
53
  });
50
54
  /**
51
55
  * Generates a unique Bitcoin P2TR deposit address for a Sui address.
@@ -85,7 +89,7 @@ declare class HashiClient {
85
89
  * into a single Sui PTB. Signs with `signer` and submits, returning the
86
90
  * execution result (`$kind: "Transaction" | "FailedTransaction"`). The
87
91
  * result includes `effects` and `events` so callers can confirm
88
- * `DepositRequestedEvent` without an extra round-trip.
92
+ * `DepositRequested` without an extra round-trip.
89
93
  *
90
94
  * The method runs three preflight stages before signing:
91
95
  *
@@ -124,7 +128,7 @@ declare class HashiClient {
124
128
  * from the signer's balance and enqueues a request for the committee to
125
129
  * send `amountSats` to `bitcoinAddress` on the Bitcoin network. Signs
126
130
  * with `signer` and submits, returning the execution result including
127
- * `effects` and `events` (`WithdrawalRequestedEvent`).
131
+ * `effects` and `events` (`WithdrawalRequested`).
128
132
  *
129
133
  * The method runs three preflight stages before signing:
130
134
  *
@@ -331,7 +335,7 @@ declare class HashiClient {
331
335
  /**
332
336
  * Get the status and details of a deposit by its Sui transaction digest.
333
337
  *
334
- * Fetches the `DepositRequestedEvent` from the transaction, extracts the
338
+ * Fetches the `DepositRequested` event from the transaction, extracts the
335
339
  * request ID, then probes on-chain state to determine whether the deposit
336
340
  * is pending (still in `requests` ObjectBag), confirmed (object exists
337
341
  * but not in requests), or expired (object destroyed).
@@ -340,7 +344,7 @@ declare class HashiClient {
340
344
  /**
341
345
  * Get the status and details of a withdrawal by its Sui transaction digest.
342
346
  *
343
- * Fetches the `WithdrawalRequestedEvent` from the transaction, extracts the
347
+ * Fetches the `WithdrawalRequested` event from the transaction, extracts the
344
348
  * request ID, then reads the `WithdrawalRequest` object to determine the
345
349
  * current lifecycle state. If a `WithdrawalTransaction` is linked, its
346
350
  * Bitcoin txid is populated.
@@ -361,7 +365,7 @@ declare class HashiClient {
361
365
  * Fetch the unified transaction history (deposits + withdrawals) for
362
366
  * a Sui address. Confirmed requests come from the on-chain
363
367
  * `user_requests` index; in-flight deposits are discovered via
364
- * GraphQL `DepositRequestedEvent` queries (indexed by sender).
368
+ * GraphQL `DepositRequested` event queries (indexed by sender).
365
369
  */
366
370
  transactionHistory: (suiAddress: string) => Promise<TransactionHistoryItem[]>;
367
371
  };
@@ -396,6 +400,28 @@ declare class HashiClient {
396
400
  */
397
401
  confirmations: (txid: string) => Promise<number>;
398
402
  };
403
+ guardian: {
404
+ /**
405
+ * Fetch the guardian's curated `/info` (identity + limiter). `limiter`
406
+ * is `null` when the guardian is not yet provisioned/activated; this
407
+ * method never throws for that state, so it can detect an uninitialized
408
+ * guardian without a try/catch.
409
+ */
410
+ info: () => Promise<RawGuardianInfo>;
411
+ /**
412
+ * Fetch the limiter and compute derived fields: capacity projected to
413
+ * now, bucket fill percentage, and the refill-to-full ETA. Throws
414
+ * `HashiGuardianError` (`code: "not-initialized"`) if the guardian has
415
+ * no limiter yet.
416
+ */
417
+ limiterStatus: () => Promise<GuardianLimiterSnapshot>;
418
+ /**
419
+ * Check whether the guardian can sign a withdrawal of `amountSats` right
420
+ * now, with an estimated wait if not. Throws `HashiGuardianError`
421
+ * (`code: "not-initialized"`) if the guardian has no limiter yet.
422
+ */
423
+ canWithdraw: (amountSats: bigint) => Promise<GuardianWithdrawCheck>;
424
+ };
399
425
  }
400
426
  //#endregion
401
427
  export { HashiClient, hashi };
package/dist/client.mjs CHANGED
@@ -7,9 +7,10 @@ import { BitcoinState, BitcoinStateKey } from "./contracts/hashi/bitcoin_state.m
7
7
  import { deposit } from "./contracts/hashi/deposit.mjs";
8
8
  import { cancelWithdrawal, requestWithdrawal } from "./contracts/hashi/withdraw.mjs";
9
9
  import { DUST_RELAY_MIN_VALUE, GUARDIAN_BTC_PUBLIC_KEY_LEN, GUARDIAN_PUBLIC_KEY_LEN, NETWORK_CONFIG } from "./constants.mjs";
10
- import { AmountBelowMinimumError, HashiConfigError, HashiFetchError, HashiPausedError, InvalidParamsError } from "./errors.mjs";
10
+ import { AmountBelowMinimumError, HashiConfigError, HashiFetchError, HashiGuardianError, 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 { estimateWaitSecs, fetchGuardianInfo, projectCapacity } from "./guardian.mjs";
13
14
  import { assertHex32, configBytes, entry, reverseTxidBytes } from "./util.mjs";
14
15
  import { TypeTagSerializer, bcs } from "@mysten/sui/bcs";
15
16
  import { deriveDynamicFieldID, fromHex, normalizeSuiAddress } from "@mysten/sui/utils";
@@ -90,7 +91,10 @@ var HashiClient = class {
90
91
  #bitcoinNetwork;
91
92
  #btcRpcUrl;
92
93
  #graphqlUrl;
93
- constructor({ client, network, hashiObjectId, packageId, bitcoinNetwork, btcRpcUrl, graphqlUrl }) {
94
+ #guardianUrl;
95
+ #guardianInfoProvider;
96
+ #resolvedGuardianUrl;
97
+ constructor({ client, network, hashiObjectId, packageId, bitcoinNetwork, btcRpcUrl, graphqlUrl, guardianUrl, guardianInfoProvider }) {
94
98
  this.tx = {
95
99
  deposit: (params) => {
96
100
  const tx = new Transaction();
@@ -252,7 +256,7 @@ var HashiClient = class {
252
256
  });
253
257
  const txData = txResult.Transaction ?? txResult.FailedTransaction;
254
258
  if (!txData?.events) return null;
255
- const depositEvent = txData.events.find((e) => e.eventType.includes("::deposit::DepositRequestedEvent"));
259
+ const depositEvent = txData.events.find((e) => e.eventType.includes("::deposit::DepositRequested"));
256
260
  if (!depositEvent?.json) return null;
257
261
  const parsed = depositEvent.json;
258
262
  let status = "unknown";
@@ -263,7 +267,7 @@ var HashiClient = class {
263
267
  client: this.#client,
264
268
  objectId: parsed.request_id
265
269
  });
266
- if (reqObj.json.approval_timestamp_ms != null) approvalTimestampMs = BigInt(reqObj.json.approval_timestamp_ms);
270
+ if (reqObj.json.approved_timestamp_ms != null) approvalTimestampMs = BigInt(reqObj.json.approved_timestamp_ms);
267
271
  const [btcState, config$1] = await Promise.all([this.#fetchBitcoinState(), this.view.all().catch(() => null)]);
268
272
  if (approvalTimestampMs !== null && config$1) confirmableAtMs = approvalTimestampMs + config$1.bitcoinDepositTimeDelayMs;
269
273
  const requestsBagId = btcState.deposit_queue.requests.id;
@@ -298,7 +302,7 @@ var HashiClient = class {
298
302
  });
299
303
  const txData = txResult.Transaction ?? txResult.FailedTransaction;
300
304
  if (!txData?.events) return null;
301
- const withdrawEvent = txData.events.find((e) => e.eventType.includes("::withdrawal_queue::WithdrawalRequestedEvent"));
305
+ const withdrawEvent = txData.events.find((e) => e.eventType.includes("::withdrawal_queue::WithdrawalRequested"));
302
306
  if (!withdrawEvent?.json) return null;
303
307
  const parsed = withdrawEvent.json;
304
308
  let status = "Requested";
@@ -402,7 +406,7 @@ var HashiClient = class {
402
406
  }
403
407
  }
404
408
  try {
405
- const depositEventType = `${this.#packageId}::deposit::DepositRequestedEvent`;
409
+ const depositEventType = `${this.#packageId}::deposit::DepositRequested`;
406
410
  const pendingIds = (await this.#queryEventRequestIds(suiAddress, depositEventType)).filter((id) => !confirmedIds.has(id));
407
411
  if (pendingIds.length > 0) {
408
412
  const objects = await this.#batchGetObjects(pendingIds, { content: true });
@@ -428,6 +432,38 @@ var HashiClient = class {
428
432
  return getTxConfirmations(this.#btcRpcUrl, txid);
429
433
  }
430
434
  };
435
+ this.guardian = {
436
+ info: async () => {
437
+ return (await this.#resolveGuardianProvider())();
438
+ },
439
+ limiterStatus: async () => {
440
+ const { state, config: config$1 } = this.#requireLimiter(await this.guardian.info());
441
+ const nowSecs = BigInt(Math.floor(Date.now() / 1e3));
442
+ const availableNowSats = projectCapacity(config$1, state, nowSecs);
443
+ const max = config$1.maxBucketCapacitySats;
444
+ const bucketFillPercent = max > 0n ? Number(availableNowSats) / Number(max) * 100 : 0;
445
+ let fullAtSecs = null;
446
+ if (availableNowSats < max && config$1.refillRateSatsPerSec > 0n) fullAtSecs = nowSecs + (max - availableNowSats + config$1.refillRateSatsPerSec - 1n) / config$1.refillRateSatsPerSec;
447
+ return {
448
+ state,
449
+ config: config$1,
450
+ availableNowSats,
451
+ bucketFillPercent,
452
+ fullAtSecs
453
+ };
454
+ },
455
+ canWithdraw: async (amountSats) => {
456
+ const { state, config: config$1 } = this.#requireLimiter(await this.guardian.info());
457
+ const nowSecs = BigInt(Math.floor(Date.now() / 1e3));
458
+ const availableNowSats = projectCapacity(config$1, state, nowSecs);
459
+ const estimatedWaitSecs = estimateWaitSecs(config$1, state, amountSats, nowSecs);
460
+ return {
461
+ allowed: availableNowSats >= amountSats,
462
+ availableNowSats,
463
+ estimatedWaitSecs
464
+ };
465
+ }
466
+ };
431
467
  const config = NETWORK_CONFIG[network];
432
468
  const resolvedObjectId = hashiObjectId ?? config?.hashiObjectId;
433
469
  const resolvedPackageId = packageId ?? config?.packageId;
@@ -438,6 +474,8 @@ var HashiClient = class {
438
474
  this.#bitcoinNetwork = bitcoinNetwork ?? config?.bitcoinNetwork ?? "testnet";
439
475
  this.#btcRpcUrl = btcRpcUrl;
440
476
  this.#graphqlUrl = graphqlUrl ?? defaultGraphqlUrl(network);
477
+ this.#guardianUrl = guardianUrl;
478
+ this.#guardianInfoProvider = guardianInfoProvider;
441
479
  }
442
480
  /**
443
481
  * Generates a unique Bitcoin P2TR deposit address for a Sui address.
@@ -479,7 +517,7 @@ var HashiClient = class {
479
517
  * into a single Sui PTB. Signs with `signer` and submits, returning the
480
518
  * execution result (`$kind: "Transaction" | "FailedTransaction"`). The
481
519
  * result includes `effects` and `events` so callers can confirm
482
- * `DepositRequestedEvent` without an extra round-trip.
520
+ * `DepositRequested` without an extra round-trip.
483
521
  *
484
522
  * The method runs three preflight stages before signing:
485
523
  *
@@ -530,7 +568,7 @@ var HashiClient = class {
530
568
  * from the signer's balance and enqueues a request for the committee to
531
569
  * send `amountSats` to `bitcoinAddress` on the Bitcoin network. Signs
532
570
  * with `signer` and submits, returning the execution result including
533
- * `effects` and `events` (`WithdrawalRequestedEvent`).
571
+ * `effects` and `events` (`WithdrawalRequested`).
534
572
  *
535
573
  * The method runs three preflight stages before signing:
536
574
  *
@@ -705,6 +743,43 @@ var HashiClient = class {
705
743
  #requireBtcRpc() {
706
744
  if (!this.#btcRpcUrl) throw new Error("btcRpcUrl is required for Bitcoin RPC operations. Pass it in HashiClientOptions.");
707
745
  }
746
+ #requireLimiter(info) {
747
+ if (info.limiter === null) throw new HashiGuardianError({
748
+ message: "Guardian rate limiter is not initialized (limiter is null).",
749
+ code: "not-initialized"
750
+ });
751
+ return info.limiter;
752
+ }
753
+ async #resolveGuardianProvider() {
754
+ if (this.#guardianInfoProvider) return this.#guardianInfoProvider;
755
+ const url = this.#guardianUrl || await this.#resolveOnChainGuardianUrl();
756
+ if (!url) throw new HashiGuardianError({
757
+ message: "Guardian URL is not configured. Pass `guardianUrl` or `guardianInfoProvider` to hashi({...}), or set `guardian_url` in the on-chain config.",
758
+ code: "not-configured"
759
+ });
760
+ return () => fetchGuardianInfo(url);
761
+ }
762
+ /**
763
+ * Resolve the guardian origin from the on-chain `guardian_url`, caching only
764
+ * a found URL. `guardian_url` is absent until launch (`finish_publish`
765
+ * publishes it), so a missing value is left uncached and each call re-reads
766
+ * the chain — a client created before launch starts working once the URL is
767
+ * published, rather than caching the absence forever (mirrors the node's
768
+ * lazy `guardian_client()` resolution). Returns `undefined` while unresolved.
769
+ */
770
+ async #resolveOnChainGuardianUrl() {
771
+ if (this.#resolvedGuardianUrl) return this.#resolvedGuardianUrl;
772
+ let onChain;
773
+ try {
774
+ onChain = (await this.view.all()).guardianUrl;
775
+ } catch (cause) {
776
+ throw new HashiGuardianError({
777
+ message: "Could not read the on-chain guardian_url config. Pass `guardianUrl` or `guardianInfoProvider` to bypass the on-chain read.",
778
+ code: "not-configured"
779
+ }, { cause });
780
+ }
781
+ return this.#resolvedGuardianUrl = onChain || void 0;
782
+ }
708
783
  async #estimateGas(tx) {
709
784
  try {
710
785
  const result = await this.#client.core.simulateTransaction({
@@ -913,12 +988,12 @@ function parseMpcPublicKey(raw) {
913
988
  }
914
989
  function parseDepositHistoryItem(content, timeDelayMs) {
915
990
  const parsed = DepositRequest.parse(content);
916
- const approvalTimestampMs = parsed.approval_timestamp_ms === null ? null : BigInt(parsed.approval_timestamp_ms);
991
+ const approvalTimestampMs = parsed.approved_timestamp_ms === null ? null : BigInt(parsed.approved_timestamp_ms);
917
992
  return {
918
993
  kind: "deposit",
919
994
  requestId: parsed.id,
920
995
  sender: parsed.sender,
921
- timestampMs: BigInt(parsed.timestamp_ms),
996
+ timestampMs: BigInt(parsed.created_timestamp_ms),
922
997
  suiTxDigest: base58.encode(new Uint8Array(parsed.sui_tx_digest)),
923
998
  amountSats: BigInt(parsed.utxo.amount),
924
999
  btcTxid: reverseTxidBytes(parsed.utxo.id.txid),
@@ -936,7 +1011,7 @@ function parseWithdrawalHistoryItem(content) {
936
1011
  sender: parsed.sender,
937
1012
  btcAmountSats: BigInt(parsed.btc_amount),
938
1013
  bitcoinAddress: new Uint8Array(parsed.bitcoin_address),
939
- timestampMs: BigInt(parsed.timestamp_ms),
1014
+ timestampMs: BigInt(parsed.created_timestamp_ms),
940
1015
  suiTxDigest: base58.encode(new Uint8Array(parsed.sui_tx_digest)),
941
1016
  status: parsed.status.$kind,
942
1017
  withdrawalTxnId: parsed.withdrawal_txn_id ?? null,
@@ -60,11 +60,18 @@ const GUARDIAN_PUBLIC_KEY_LEN = 32;
60
60
  * `GUARDIAN_BTC_PUBLIC_KEY_LEN` in `hashi::config`.
61
61
  */
62
62
  const GUARDIAN_BTC_PUBLIC_KEY_LEN = 32;
63
- const NETWORK_CONFIG = { devnet: {
64
- hashiObjectId: "0x61f1acbf8dd79d284cb312853389747a55a158b1b12a68554afcade05dec5f40",
65
- packageId: "0xc377616c626d73556cae080706acf37e862d24d26c690cc7be5a957f53fadf17",
66
- bitcoinNetwork: "signet"
67
- } };
63
+ const NETWORK_CONFIG = {
64
+ devnet: {
65
+ hashiObjectId: "0x84081242ebb05eac5e09ab2a930a60b1357d3d8bc6f927380979f72de991ccca",
66
+ packageId: "0xa877d4d97b6a8bae1da982a84980c502c5ad2ead4b24e6c8e50c57cd6ddc3771",
67
+ bitcoinNetwork: "signet"
68
+ },
69
+ testnet: {
70
+ hashiObjectId: "0x22c0ce66ce09df2dc88a31bd320d4177b766518b9b88010368cfbdcd724528f8",
71
+ packageId: "0xfcea10cadbb553c4874201584abf68771592678952efd957b2e82c010c7f4360",
72
+ bitcoinNetwork: "signet"
73
+ }
74
+ };
68
75
 
69
76
  //#endregion
70
77
  export { DUST_RELAY_MIN_VALUE, GUARDIAN_BTC_PUBLIC_KEY_LEN, GUARDIAN_PUBLIC_KEY_LEN, NETWORK_CONFIG, NETWORK_HRP, NUMS_KEY };
@@ -9,6 +9,13 @@ import { bcs } from "@mysten/sui/bcs";
9
9
  /**************************************************************
10
10
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
11
11
  **************************************************************/
12
+ /**
13
+ * Per-chain Bitcoin state, attached to the `Hashi` shared object as a dynamic
14
+ * field keyed by `BitcoinStateKey`. Bundles the deposit queue, withdrawal queue,
15
+ * and UTXO pool behind package-only accessors, and maintains a per-user index of
16
+ * request IDs so clients can discover all deposits and withdrawals belonging to an
17
+ * address.
18
+ */
12
19
  const $moduleName = "@local-pkg/hashi::bitcoin_state";
13
20
  const BitcoinStateKey = new MoveStruct({
14
21
  name: `${$moduleName}::BitcoinStateKey`,
@@ -17,6 +24,7 @@ const BitcoinStateKey = new MoveStruct({
17
24
  const BitcoinState = new MoveStruct({
18
25
  name: `${$moduleName}::BitcoinState`,
19
26
  fields: {
27
+ id: bcs.Address,
20
28
  deposit_queue: DepositRequestQueue,
21
29
  withdrawal_queue: WithdrawalRequestQueue,
22
30
  utxo_pool: UtxoPool,
@@ -1,11 +1,20 @@
1
1
  import { MoveStruct } from "../utils/index.mjs";
2
2
  import { Element } from "./deps/sui/group_ops.mjs";
3
+ import { Config } from "./config.mjs";
3
4
  import { bcs } from "@mysten/sui/bcs";
4
5
 
5
6
  //#region src/contracts/hashi/committee.ts
6
7
  /**************************************************************
7
8
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
8
9
  **************************************************************/
10
+ /**
11
+ * BLS signing committees and certificate verification. A `Committee` pins an
12
+ * epoch's members (validator addresses, BLS public keys, encryption keys, voting
13
+ * weights) together with the MPC parameters snapshotted at reconfig time.
14
+ * `verify_certificate` checks an aggregate BLS12-381 min-pk signature against a
15
+ * signers bitmap, enforces the stake threshold, and wraps the payload in a
16
+ * `CertifiedMessage` as proof of committee approval.
17
+ */
9
18
  const $moduleName = "@local-pkg/hashi::committee";
10
19
  const CommitteeMember = new MoveStruct({
11
20
  name: `${$moduleName}::CommitteeMember`,
@@ -22,9 +31,7 @@ const Committee = new MoveStruct({
22
31
  epoch: bcs.u64(),
23
32
  members: bcs.vector(CommitteeMember),
24
33
  total_weight: bcs.u64(),
25
- mpc_threshold_in_basis_points: bcs.u64(),
26
- mpc_weight_reduction_allowed_delta: bcs.u64(),
27
- mpc_max_faulty_in_basis_points: bcs.u64()
34
+ config: Config
28
35
  }
29
36
  });
30
37
  const CommitteeSignature = new MoveStruct({
@@ -1,23 +1,51 @@
1
1
  import { MoveStruct } from "../utils/index.mjs";
2
- import { Bag } from "./deps/sui/bag.mjs";
3
2
  import { Element } from "./deps/sui/group_ops.mjs";
3
+ import { Config } from "./config.mjs";
4
+ import { CommitteeSignature } from "./committee.mjs";
5
+ import { Bag } from "./deps/sui/bag.mjs";
4
6
  import { bcs } from "@mysten/sui/bcs";
5
7
 
6
8
  //#region src/contracts/hashi/committee_set.ts
7
9
  /**************************************************************
8
10
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
9
11
  **************************************************************/
12
+ /**
13
+ * Registry of Hashi committee state: registered member metadata (`MemberInfo`),
14
+ * per-epoch `Committee`s, the current epoch, the MPC threshold public key, and the
15
+ * pending epoch change while a reconfiguration is in flight. Members register (and
16
+ * rotate keys/metadata) here between epochs; `start_reconfig` builds the next
17
+ * committee from Sui's active validator set, and `end_reconfig` activates it —
18
+ * storing the outgoing committee's handoff certificate for non-initial reconfigs.
19
+ */
10
20
  const $moduleName = "@local-pkg/hashi::committee_set";
21
+ const PendingEpochChange = new MoveStruct({
22
+ name: `${$moduleName}::PendingEpochChange`,
23
+ fields: {
24
+ epoch: bcs.u64(),
25
+ committee_handoff_cert: bcs.option(CommitteeSignature)
26
+ }
27
+ });
11
28
  const CommitteeSet = new MoveStruct({
12
29
  name: `${$moduleName}::CommitteeSet`,
13
30
  fields: {
14
31
  members: Bag,
15
32
  epoch: bcs.u64(),
16
33
  committees: Bag,
17
- pending_epoch_change: bcs.option(bcs.u64()),
34
+ pending_epoch_change: bcs.option(PendingEpochChange),
18
35
  mpc_public_key: bcs.vector(bcs.u8())
19
36
  }
20
37
  });
38
+ const CommitteeHandoffKey = new MoveStruct({
39
+ name: `${$moduleName}::CommitteeHandoffKey`,
40
+ fields: { epoch: bcs.u64() }
41
+ });
42
+ const CommitteeHandoff = new MoveStruct({
43
+ name: `${$moduleName}::CommitteeHandoff`,
44
+ fields: {
45
+ next_epoch: bcs.u64(),
46
+ cert: CommitteeSignature
47
+ }
48
+ });
21
49
  const MemberInfo = new MoveStruct({
22
50
  name: `${$moduleName}::MemberInfo`,
23
51
  fields: {
@@ -26,7 +54,8 @@ const MemberInfo = new MoveStruct({
26
54
  next_epoch_public_key: Element,
27
55
  endpoint_url: bcs.string(),
28
56
  tls_public_key: bcs.vector(bcs.u8()),
29
- next_epoch_encryption_public_key: bcs.vector(bcs.u8())
57
+ next_epoch_encryption_public_key: bcs.vector(bcs.u8()),
58
+ extra_fields: Config
30
59
  }
31
60
  });
32
61
 
@@ -1,8 +1,6 @@
1
1
  import { MoveStruct } from "../utils/index.mjs";
2
2
  import { VecMap } from "./deps/sui/vec_map.mjs";
3
3
  import { Value } from "./config_value.mjs";
4
- import { VecSet } from "./deps/sui/vec_set.mjs";
5
- import { UpgradeCap } from "./deps/sui/package.mjs";
6
4
  import { bcs } from "@mysten/sui/bcs";
7
5
 
8
6
  //#region src/contracts/hashi/config.ts
@@ -10,18 +8,20 @@ import { bcs } from "@mysten/sui/bcs";
10
8
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
11
9
  **************************************************************/
12
10
  /**
13
- * Core configuration: versioning, pause state, upgrade management, and generic
14
- * key-value config store. Chain-specific configuration (e.g. BTC fee parameters)
15
- * lives in separate modules that use get/upsert.
11
+ * A general-purpose, typed key-value configuration store: a map from string keys
12
+ * to `config_value::Value`s, with domain-specific accessors layered on top (pause
13
+ * state, guardian, emergency thresholds). Chain-specific configuration (e.g. BTC
14
+ * fee parameters) lives in separate modules that use get/upsert.
15
+ *
16
+ * `Config` has `copy, drop, store` and carries no policy of its own (no
17
+ * versioning, no upgrade authority — those live in `versioning`), so it can be
18
+ * embedded by value wherever a bag of settings is needed: it backs the package's
19
+ * global config and is also pinned per-epoch onto a `Committee`.
16
20
  */
17
21
  const $moduleName = "@local-pkg/hashi::config";
18
22
  const Config = new MoveStruct({
19
23
  name: `${$moduleName}::Config`,
20
- fields: {
21
- config: VecMap(bcs.string(), Value),
22
- enabled_versions: VecSet(bcs.u64()),
23
- upgrade_cap: bcs.option(UpgradeCap)
24
- }
24
+ fields: { config: VecMap(bcs.string(), Value) }
25
25
  });
26
26
 
27
27
  //#endregion
@@ -5,11 +5,21 @@ import { bcs } from "@mysten/sui/bcs";
5
5
  /**************************************************************
6
6
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
7
7
  **************************************************************/
8
+ /**
9
+ * Typed value wrapper for config entries: the `Value` enum carries one of the
10
+ * primitive types a configuration entry can hold, with a public constructor per
11
+ * variant and package-level helpers for variant checks (`is_*`) and extraction
12
+ * (`as_*`, aborting on a type mismatch). Storing typed values — rather than raw
13
+ * bytes — lets `config::is_valid_config_update` reject governance updates that
14
+ * would change an entry's type.
15
+ */
8
16
  const $moduleName = "@local-pkg/hashi::config_value";
9
17
  const Value = new MoveEnum({
10
18
  name: `${$moduleName}::Value`,
11
19
  fields: {
12
20
  U64: bcs.u64(),
21
+ U128: bcs.u128(),
22
+ U256: bcs.u256(),
13
23
  Address: bcs.Address,
14
24
  String: bcs.string(),
15
25
  Bool: bcs.bool(),
@@ -1,12 +1,21 @@
1
1
  import { MoveStruct, normalizeMoveArguments } from "../utils/index.mjs";
2
- import { Utxo, UtxoId } from "./utxo.mjs";
3
2
  import { CommitteeSignature } from "./committee.mjs";
3
+ import { Utxo, UtxoId } from "./utxo.mjs";
4
4
  import { bcs } from "@mysten/sui/bcs";
5
5
 
6
6
  //#region src/contracts/hashi/deposit.ts
7
7
  /**************************************************************
8
8
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
9
9
  **************************************************************/
10
+ /**
11
+ * User-facing Bitcoin deposit flow. A depositor registers the UTXO they sent to
12
+ * the bridge's address, the committee approves it with a certificate over
13
+ * `(request_id, utxo)`, and — after a configurable time delay in which a faulty
14
+ * approval can be caught and the service paused — the deposit is confirmed: hBTC
15
+ * is minted to the recipient encoded in the UTXO's derivation path and the UTXO
16
+ * joins the active pool. Requests that are never confirmed can be
17
+ * garbage-collected once they expire.
18
+ */
10
19
  const $moduleName = "@local-pkg/hashi::deposit";
11
20
  const DepositConfirmationMessage = new MoveStruct({
12
21
  name: `${$moduleName}::DepositConfirmationMessage`,
@@ -15,8 +24,8 @@ const DepositConfirmationMessage = new MoveStruct({
15
24
  utxo: Utxo
16
25
  }
17
26
  });
18
- const DepositRequestedEvent = new MoveStruct({
19
- name: `${$moduleName}::DepositRequestedEvent`,
27
+ const DepositRequested = new MoveStruct({
28
+ name: `${$moduleName}::DepositRequested`,
20
29
  fields: {
21
30
  request_id: bcs.Address,
22
31
  utxo_id: UtxoId,
@@ -27,8 +36,8 @@ const DepositRequestedEvent = new MoveStruct({
27
36
  sui_tx_digest: bcs.vector(bcs.u8())
28
37
  }
29
38
  });
30
- const DepositApprovedEvent = new MoveStruct({
31
- name: `${$moduleName}::DepositApprovedEvent`,
39
+ const DepositApproved = new MoveStruct({
40
+ name: `${$moduleName}::DepositApproved`,
32
41
  fields: {
33
42
  request_id: bcs.Address,
34
43
  utxo: Utxo,
@@ -36,15 +45,15 @@ const DepositApprovedEvent = new MoveStruct({
36
45
  approval_timestamp_ms: bcs.u64()
37
46
  }
38
47
  });
39
- const DepositConfirmedEvent = new MoveStruct({
40
- name: `${$moduleName}::DepositConfirmedEvent`,
48
+ const DepositConfirmed = new MoveStruct({
49
+ name: `${$moduleName}::DepositConfirmed`,
41
50
  fields: {
42
51
  request_id: bcs.Address,
43
52
  utxo: Utxo
44
53
  }
45
54
  });
46
- const ExpiredDepositDeletedEvent = new MoveStruct({
47
- name: `${$moduleName}::ExpiredDepositDeletedEvent`,
55
+ const ExpiredDepositDeleted = new MoveStruct({
56
+ name: `${$moduleName}::ExpiredDepositDeleted`,
48
57
  fields: { request_id: bcs.Address }
49
58
  });
50
59
  function deposit(options) {
@@ -1,13 +1,20 @@
1
1
  import { 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 } from "./utxo.mjs";
4
- import { CommitteeSignature } from "./committee.mjs";
5
5
  import { bcs } from "@mysten/sui/bcs";
6
6
 
7
7
  //#region src/contracts/hashi/deposit_queue.ts
8
8
  /**************************************************************
9
9
  * THIS FILE IS GENERATED AND SHOULD NOT BE MANUALLY MODIFIED *
10
10
  **************************************************************/
11
+ /**
12
+ * Storage and bookkeeping for Bitcoin deposit requests. Active requests sit in an
13
+ * ObjectBag awaiting committee approval and confirmation; confirmed requests move
14
+ * to a processed bag, and requests that were never confirmed can be deleted once
15
+ * they pass the maximum age. The state transitions themselves (certificate
16
+ * verification, minting, time-delay enforcement) are driven by `hashi::deposit`.
17
+ */
11
18
  const $moduleName = "@local-pkg/hashi::deposit_queue";
12
19
  const DepositRequestQueue = new MoveStruct({
13
20
  name: `${$moduleName}::DepositRequestQueue`,
@@ -21,11 +28,12 @@ const DepositRequest = new MoveStruct({
21
28
  fields: {
22
29
  id: bcs.Address,
23
30
  sender: bcs.Address,
24
- timestamp_ms: bcs.u64(),
31
+ created_timestamp_ms: bcs.u64(),
25
32
  sui_tx_digest: bcs.vector(bcs.u8()),
26
33
  utxo: Utxo,
27
34
  approval_cert: bcs.option(CommitteeSignature),
28
- approval_timestamp_ms: bcs.option(bcs.u64())
35
+ approved_timestamp_ms: bcs.option(bcs.u64()),
36
+ confirmed_timestamp_ms: bcs.option(bcs.u64())
29
37
  }
30
38
  });
31
39