Skip to main content

Move Model Lifecycle

This document illustrates the lifecycle of key Move models in the deposit and withdrawal flows, showing how data structures transform between onchain (Sui) and offchain (Bitcoin) states.

What is the entry-function gating principle?​

Every entry function that takes the Hashi object asserts the package version is enabled. Beyond that, pause and reconfiguration gate only the deposit and withdrawal flows. The table shows which entry functions stop when governance pauses the system and which stop while a committee handoff is pending.

Entry functionsStops during a pauseStops while a committee handoff is pending
Deposit approval (approve_deposit()) and confirmation (confirm_deposit())YesYes
Withdrawal approval (approve_request()), commitment, signing, and finalization (finalize_withdrawal())YesYes
User requests (deposit(), request_withdrawal())YesNo
Withdrawal confirmation (confirm_withdrawal())YesNo
Withdrawal cancellationNoNo
Reconfiguration, MPC certificate submission, governanceNoNo
Cleanup entries: cleanup_spent_utxos, archive_confirmed_withdrawals, archive_withdrawal_requests, finish_archive_withdrawal_txns, destroy_key_gen_certs, destroy_nonce_certs, delete_expired_deposit, proposal::delete_expiredNoNo
Validator registration or key rotationNoNo

The last 4 rows never check pause, because an emergency pause must not block operators from maintaining nodes or the chain from shedding dead state. Nodes themselves stop all leader work while paused, garbage collection included, so during a pause only a manual call sheds that state. One asymmetry is intentional: users can request deposits and withdrawals during reconfiguration (a withdrawal request only escrows their hBTC); only committee approval, commitment, signing, and deposit confirmation wait for the new committee.

How do Move models change during a deposit?​

During a reconfiguration, users can still call deposit(), but approval and confirmation wait for the new committee; see What is the entry-function gating principle?.

Deposit flow summary​

StepActorActionModel Transformation
1UserUser sends BTC to bridge addressBitcoin UTXO created
2UserUser calls deposit()DepositRequest → DepositRequestQueue
3Committee membersCommittee members wait for Bitcoin confirmationsbitcoin_confirmation_threshold blocks on the deposit transaction
4Committee membersCommittee members screen and sign requestMembers with a TRM API key screen it (Mainnet only); BLS signatures aggregated → CommitteeSignature
5LeaderLeader calls approve_deposit() with certverify_certificate() → request stores approval_cert and approved_timestamp_ms
6NoneTime-delay window elapsesbitcoin_deposit_time_delay_ms (a window to catch a faulty approval and pause before the contract mints anything)
7AnyoneAnyone calls confirm_deposit()Re-verify_certificate() → CertifiedMessage<DepositConfirmationMessage>
8TreasuryTreasury mints tokensIf the UTXO has a derivation path, Utxo.amount → Balance<BTC> sent to that address
9NoneCertified request processedUtxo → UtxoPool; DepositRequest → DepositRequestQueue.processed with confirmed_timestamp_ms set

How do Move models change during a withdrawal?​

During a reconfiguration, users can still call request_withdrawal() and withdrawal confirmation still proceeds, but approval, commitment, signing, and finalization wait for the new committee; see What is the entry-function gating principle?.

info

The config key bitcoin_confirmation_threshold (default 6) stores the Bitcoin confirmation threshold onchain. Hashi stores witness signatures onchain so that any leader can reconstruct and re-broadcast the signed Bitcoin transaction without MPC re-signing (for example, after leader rotation, mempool eviction, or a Bitcoin reorganization).

Limiter coordination. The LocalLimiter on each committee node is a deterministic projection of the onchain stream. Outside a reconcile (below), its next_seq advances only when the node's object mirror sees a WithdrawalTransaction become fully signed (the finalize_withdrawal write). The finalize path uses validate_consume (read-only) to gate participation, never to mutate state: the leader checks before it calls the guardian, and each member checks again before it signs the finalize certificate. The leader's flow only reaches finalize_withdrawal() after finalize_withdrawal_through_guardian returns Ok, so seeing the transaction become fully signed onchain is a sufficient proxy for the guardian having acknowledged the request. The guardian advances its own next_seq when it signs, so the local cache trails it while a withdrawal is in flight. On startup, each node seeds its LocalLimiter through GetGuardianInfo, then periodically reconciles against it, snapping back a cache that stalled or drifted and adopting a new limiter policy after a guardian rotation.

Withdrawal flow summary​

StepActorActionModel Transformation
1UserUser requests withdrawalBalance<BTC> → WithdrawalRequest → WithdrawalRequestQueue.requests
2CommitteeCommittee screens and approves requestMembers with a TRM API key screen it (Mainnet only); approve_request() records approval_cert on the request
3LeaderLeader commits withdrawal txBalance<BTC> burned, WithdrawalTransaction created in .withdrawal_txns
4Committee (MPC)MPC protocol signs Bitcoin transactionCommittee signs each input through MPC; commit_input_signatures() stores the signatures onchain in chunks
5LeaderLeader checks its LocalLimiterLocalLimiter::validate_consume(seq, ts, amt) (read-only) before calling the guardian
6LeaderLeader BLS-certs StandardWithdrawalRequestAggregated cert over (wid, seq, ts, utxos), the input to the guardian RPC
7GuardianGuardian rate-limit check + enclave sigStandardWithdrawal RPC: verify cert → consume(seq, ts, amt) → enclave signatures
8LeaderLeader finalizes with guardian signaturesMembers re-run validate_consume and certify both signature sets; finalize_withdrawal() attaches the guardian signatures
9Each nodeEach node advances its LocalLimiterObject mirror sees the tx become fully signed → apply_consume(seq, ts, amt)
10LeaderBTC transaction broadcast (and re-broadcast)Signed tx reconstructed from onchain data, broadcast to Bitcoin
11CommitteeCommittee signs confirmation certificateCommitteeSignature created after BTC tx confirmed
12LeaderLeader confirms withdrawalconfirmed_timestamp_ms set, input UTXOs marked spent; the leader's GC later moves the tx to .confirmed_txns, its requests to .processed, and the spent inputs to spent_utxos

What are the key Move models?​

ModelLocationDescription
Balance<BTC>User walletWrapped BTC token on Sui
DepositRequestDepositRequestQueue.requests, then .processed once confirmedDeposit request with its approval cert and timestamps; deleted if it expires unconfirmed
UtxoUtxoPoolOnchain representation of a Bitcoin UTXO
WithdrawalRequestWithdrawalRequestQueue.requests, then .processed once archivedUser's withdrawal request with destination; approval_cert and withdrawal_txn_id mark approval and commitment
WithdrawalTransactionWithdrawalRequestQueue.withdrawal_txnsIn-flight withdrawal tx (kept after confirmation until archived), stores inputs, outputs, and witness signatures
WithdrawalTransactionWithdrawalRequestQueue.confirmed_txnsConfirmed withdrawal tx, moved here by the leader's archival GC (historical record)
Bitcoin UTXOBitcoin NetworkActual unspent transaction output on Bitcoin
CommitteeCommitteeSetBLS signing committee of Sui validators for an epoch
CommitteeMemberCommittee.membersValidator with public_key and voting weight
CommitteeSignatureBuilt in the programmable transaction block (PTB) by committee::new_committee_signature; stored as approval_cert on requestsAggregated BLS signature with signers bitmap
CertifiedMessage<T>Verified onchainMessage proven to have committee quorum support