Deposit
A deposit moves BTC from a user's Bitcoin wallet into the Hashi-managed UTXO
(unspent transaction output) pool, minting a corresponding amount of hBTC
into the user's account on Sui. The process has 4 phases:
The split between Approve and Confirm introduces a configurable
time-delay window (see
bitcoin_deposit_time_delay_ms)
between the moment the committee certifies a deposit and the moment funds are
minted. This delay gives operators a chance to detect a faulty or fraudulent
approval and pause the service before the contract mints any hBTC.
Request
The user creates a Bitcoin transaction that sends BTC to a Hashi deposit
address. Each deposit address is a unique Taproot address derived from the
target destination address on Sui (see Bitcoin Address Scheme).
The deposit must meet the
bitcoin_deposit_minimum, which is never
below the dust minimum (546 sats), to avoid creating unspendable UTXOs on
Bitcoin.
After the Bitcoin transaction is broadcast, the user notifies Hashi by
constructing a Utxo and calling hashi::deposit::deposit on Sui.
You construct the Utxo from the Bitcoin transaction details:
public fun utxo(
utxo_id: UtxoId,
amount: u64,
derivation_path: Option<address>,
): Utxo
public fun utxo_id(
txid: address,
vout: u32,
): UtxoId
txid: the 32-byte transaction hash in Bitcoin's internal byte order, the reverse of the hex txid that wallets and explorers show.vout: the output index within that transaction.amount: the deposit amount in satoshis.derivation_path: the Sui address used to derive the deposit address.
The user then submits it:
entry fun deposit(
hashi: &mut Hashi,
utxo: Utxo,
clock: &Clock,
ctx: &mut TxContext,
)
The function checks that the service is not paused (the contract accepts deposits
during a reconfiguration), that the deposit meets the minimum amount, and that
the UTXO is not already in the pool, active or spent. It then creates a
DepositRequest and places it in the deposit queue. Committee members start
monitoring Bitcoin for confirmation.
Approve
Committee members monitor the Bitcoin network for the deposit transaction. The
transaction must reach a sufficient number of block confirmations (see
bitcoin_confirmation_threshold)
before the committee considers it final. This guards against chain
reorganizations that could reverse a confirmed transaction. If the transaction
never reaches the threshold, for example because a reorg drops it, the
committee never approves the request. A request that the committee does not
confirm expires 24 hours after its creation, and the contract then deletes it;
the user can submit the UTXO again.
After confirmation, on Bitcoin Mainnet, each committee member with a TRM Labs API key independently screens the deposit's Bitcoin transaction and Sui recipient (see Handling Sanctioned Addresses); other members approve without screening. A member that rejects the deposit does not vote to approve it.
After a node determines that a deposit request is both confirmed on Bitcoin and passes its own screening checks, it communicates with the other members of the Hashi committee and collects signatures from validators that agree to approve the deposit. If a quorum of validators cannot agree to approve a deposit, the node either retries the request later or ignores it if invalid.
After the signatures reach a quorum, one validator submits the certificate onchain by
calling hashi::deposit::approve_deposit:
entry fun approve_deposit(
hashi: &mut Hashi,
request_id: address,
cert: CommitteeSignature,
clock: &Clock,
ctx: &mut TxContext,
)
The function aborts while the service is paused or a reconfiguration is
pending, if the UTXO is already in the pool, or if the current committee has
already approved the request. Otherwise it verifies the committee certificate
against the current committee and records both the certificate and the
current clock timestamp on the request. The request remains in the deposit
queue, and the contract has not minted any hBTC yet.
Confirm
After approval, the deposit must wait through the configured time-delay
window (see
bitcoin_deposit_time_delay_ms)
before anyone can confirm it. The window gives operators a chance to detect a
faulty or fraudulent approval and pause the service before the contract mints
funds. While the service is paused, the contract rejects both approve_deposit
and confirm_deposit, so pending approvals stay parked in the queue until the system is
unpaused, unless the request expires first. The 24-hour expiry described in
Approve counts from request creation, so it keeps running through
the time-delay window.
If the committee rotates before anyone confirms the deposit, the existing
approval becomes invalid and the new committee must re-approve the deposit,
which restarts the delay. The onchain confirm_deposit re-verifies
the stored certificate against the current committee, not the committee that
originally approved it.
After the delay has elapsed, any caller can call hashi::deposit::confirm_deposit:
entry fun confirm_deposit(
hashi: &mut Hashi,
request_id: address,
clock: &Clock,
ctx: &mut TxContext,
)
The function:
- Aborts while the service is paused or a reconfiguration is pending, or if the UTXO is already in the pool (active or spent), before anything changes.
- Re-verifies the stored committee certificate against the current committee.
- Asserts that
approved_timestamp_ms + bitcoin_deposit_time_delay_ms <= now. - Aborts if the request was never approved, the certificate no longer verifies, or the delay has not elapsed.
Mint
After both checks in confirm_deposit pass, the function mints the
corresponding amount of hBTC and sends it to the Sui address in the UTXO's
derivation_path (a UTXO whose derivation_path is None mints nothing). The function adds the
deposited UTXO to the Hashi-managed UTXO pool, making it available
for future withdrawal coin selection.