@mysten-incubation/hashi 0.3.1 → 0.4.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 +10 -0
- package/README.md +1 -1
- package/dist/client.d.mts +33 -7
- package/dist/client.mjs +86 -11
- package/dist/constants.mjs +2 -2
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @mysten-incubation/hashi
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 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
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
|
|
3
13
|
## 0.3.1
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -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
|
-
* `
|
|
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` (`
|
|
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 `
|
|
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 `
|
|
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 `
|
|
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
|
-
|
|
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::
|
|
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.
|
|
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::
|
|
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::
|
|
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
|
-
* `
|
|
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` (`
|
|
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.
|
|
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.
|
|
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.
|
|
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,
|
package/dist/constants.mjs
CHANGED
|
@@ -61,8 +61,8 @@ const GUARDIAN_PUBLIC_KEY_LEN = 32;
|
|
|
61
61
|
*/
|
|
62
62
|
const GUARDIAN_BTC_PUBLIC_KEY_LEN = 32;
|
|
63
63
|
const NETWORK_CONFIG = { devnet: {
|
|
64
|
-
hashiObjectId: "
|
|
65
|
-
packageId: "
|
|
64
|
+
hashiObjectId: "0x84081242ebb05eac5e09ab2a930a60b1357d3d8bc6f927380979f72de991ccca",
|
|
65
|
+
packageId: "0xa877d4d97b6a8bae1da982a84980c502c5ad2ead4b24e6c8e50c57cd6ddc3771",
|
|
66
66
|
bitcoinNetwork: "signet"
|
|
67
67
|
} };
|
|
68
68
|
|
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
|
19
|
-
name: `${$moduleName}::
|
|
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
|
|
31
|
-
name: `${$moduleName}::
|
|
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
|
|
40
|
-
name: `${$moduleName}::
|
|
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
|
|
47
|
-
name: `${$moduleName}::
|
|
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
|
-
|
|
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
|
-
|
|
35
|
+
approved_timestamp_ms: bcs.option(bcs.u64()),
|
|
36
|
+
confirmed_timestamp_ms: bcs.option(bcs.u64())
|
|
29
37
|
}
|
|
30
38
|
});
|
|
31
39
|
|
|
@@ -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,
|