-
Notifications
You must be signed in to change notification settings - Fork 3
Support BTC refund flow #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: omni-main
Are you sure you want to change the base?
Changes from all commits
fa6f5ec
eb63441
b5d9ed1
edf58ef
b2b65ee
bc73080
3113f71
c2a2cc9
211cfa0
c0ca652
71ae63b
f8324d4
106268e
8f4f045
bc3ae0b
e1fd99e
1753f3c
8d23beb
99206e4
710d7d3
181b003
afaa15c
fcb4f46
252f996
20a4767
0620f00
a1122b4
c294518
1c9c272
5339354
724da78
33a97aa
fea99fd
865019b
2b6d4b0
f1880ee
403ff11
28397dd
80027f9
532eb0f
22d199e
e764521
f1f3ed5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -426,6 +426,134 @@ impl Contract { | |
| } | ||
| } | ||
|
|
||
| #[cfg(not(feature = "zcash"))] | ||
| #[near] | ||
| impl Contract { | ||
| // ── Refund API (Bitcoin only) ── | ||
|
|
||
| /// Submit a refund request for a deposit that was never finalized via `verify_deposit` or `safe_verify_deposit`. | ||
| /// The BTC transaction is verified through the Light Client to prove the deposit exists. | ||
| /// After the timelock period, anyone can call `execute_refund` to initiate the return. | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// * `deposit_msg` - The original deposit message. If `deposit_msg.refund_address` is set, | ||
| /// it must match the provided `refund_address`. | ||
| /// * `refund_address` - BTC address to send the refund to. If `deposit_msg.refund_address` | ||
| /// is `None`, this value is used directly. | ||
| /// * `tx_bytes` - BTC transaction bytes proving the deposit. | ||
| /// * `vout` - Output index of the deposit in the transaction. | ||
| /// * `tx_block_blockhash` - Block hash containing the transaction. | ||
| /// * `tx_index` - Transaction index within the block. | ||
| /// * `merkle_proof` - Merkle proof for Light Client verification. | ||
| /// * `gas_fee` - Optional custom gas fee. Only DAO or Operator can set this. | ||
| /// If `None`, the default `config.max_btc_gas_fee` is used during `execute_refund`. | ||
| #[allow(clippy::too_many_arguments)] | ||
| #[pause(except(roles(Role::DAO)))] | ||
| pub fn request_refund( | ||
| &mut self, | ||
| deposit_msg: DepositMsg, | ||
| refund_address: String, | ||
| tx_bytes: Vec<u8>, | ||
| vout: usize, | ||
| tx_block_blockhash: String, | ||
| tx_index: u64, | ||
| merkle_proof: Vec<String>, | ||
olga24912 marked this conversation as resolved.
Show resolved
Hide resolved
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are we going to add coinbase proof here? |
||
| gas_fee: Option<U128>, | ||
| ) -> Promise { | ||
| if gas_fee.is_some() { | ||
| let caller = env::predecessor_account_id(); | ||
| require!( | ||
| self.acl_has_role(Role::DAO.into(), caller.clone()) | ||
| || self.acl_has_role(Role::Operator.into(), caller), | ||
| "Only DAO or Operator can specify custom gas_fee" | ||
| ); | ||
| } | ||
| self.internal_request_refund( | ||
| deposit_msg, | ||
| refund_address, | ||
| tx_bytes, | ||
| vout, | ||
| tx_block_blockhash, | ||
| tx_index, | ||
| merkle_proof, | ||
| gas_fee.map(|v| v.0), | ||
| ) | ||
| } | ||
|
|
||
| /// Reject a pending refund request. | ||
| /// - DAO or Operator can reject any request. | ||
| /// - Anyone can reject a request if the UTXO has already been verified via `verify_deposit`. | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// * `utxo_storage_key` - The UTXO key identifying the refund request (`{tx_id}@{vout}`). | ||
| pub fn reject_refund(&mut self, utxo_storage_key: String) { | ||
| let caller = env::predecessor_account_id(); | ||
| let is_privileged = self.acl_has_role(Role::DAO.into(), caller.clone()) | ||
| || self.acl_has_role(Role::Operator.into(), caller); | ||
| let is_already_deposited = self | ||
| .data() | ||
| .verified_deposit_utxo | ||
| .contains(&utxo_storage_key); | ||
| require!( | ||
| is_privileged || is_already_deposited, | ||
| "Only DAO/Operator can reject, or UTXO must be already verified via deposit" | ||
| ); | ||
| self.internal_reject_refund(utxo_storage_key); | ||
| } | ||
|
|
||
| /// Execute a refund after the timelock has passed. Builds a BTC transaction | ||
| /// that sends the deposit UTXO back to the `refund_address` specified in the original | ||
| /// `DepositMsg`. Creates a `BTCPendingInfo` entry for the MPC sign pipeline. | ||
| /// Marks the UTXO in `verified_deposit_utxo` to prevent future `verify_deposit`. | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// * `utxo_storage_key` - The UTXO key identifying the refund request (`{tx_id}@{vout}`). | ||
| #[payable] | ||
| #[pause(except(roles(Role::DAO)))] | ||
| pub fn execute_refund(&mut self, utxo_storage_key: String) { | ||
| require!( | ||
| env::attached_deposit() >= self.required_balance_for_execute_refund(), | ||
| "Insufficient deposit for storage" | ||
| ); | ||
| self.internal_execute_refund(utxo_storage_key); | ||
| } | ||
|
|
||
| /// Verify that the refund BTC transaction has been confirmed on the Bitcoin network. | ||
| /// Cleans up the `BTCPendingInfo` after successful verification. | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// * `tx_id` - Transaction ID of the confirmed refund transaction. | ||
| /// * `tx_block_blockhash` - Block hash containing the transaction. | ||
| /// * `tx_index` - Transaction index within the block. | ||
| /// * `merkle_proof` - Merkle proof for Light Client verification. | ||
| #[pause(except(roles(Role::DAO)))] | ||
| pub fn verify_refund_finalize( | ||
| &mut self, | ||
| tx_id: String, | ||
| tx_block_blockhash: String, | ||
| tx_index: u64, | ||
| merkle_proof: Vec<String>, | ||
| ) -> Promise { | ||
| let btc_pending_info = self.internal_unwrap_btc_pending_info(&tx_id); | ||
| btc_pending_info.assert_refund_pending_verify_tx(); | ||
| require!( | ||
| btc_pending_info.tx_bytes_with_sign.is_some(), | ||
| "Missing tx_bytes_with_sign" | ||
| ); | ||
| self.internal_verify_refund_finalize( | ||
| tx_id, | ||
| tx_block_blockhash, | ||
| tx_index, | ||
| merkle_proof, | ||
| btc_pending_info, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| impl Contract { | ||
| pub fn create_active_utxo_management_pending_info( | ||
| &mut self, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -459,4 +459,12 @@ impl Contract { | |
| ); | ||
| self.internal_mut_config().unhealthy_utxo_amount = unhealthy_utxo_amount.0; | ||
| } | ||
|
|
||
| #[cfg(not(feature = "zcash"))] | ||
| #[payable] | ||
| #[access_control_any(roles(Role::DAO))] | ||
| pub fn set_refund_timelock_sec(&mut self, refund_timelock_sec: u64) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The number of setters has become huge, can we combine some of them under one setter, eg set_config? |
||
| assert_one_yocto(); | ||
| self.internal_mut_config().refund_timelock_sec = refund_timelock_sec; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| #[allow(clippy::too_many_arguments)] | ||
| mod bridge; | ||
| mod chain_signatures; | ||
| mod management; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.