From b7248b0267c6f7818f5c6d5b72be05f7eb6b43ff Mon Sep 17 00:00:00 2001 From: Olga Kunyavskaya Date: Thu, 2 Oct 2025 16:18:15 +0100 Subject: [PATCH 1/9] subsidy RBF --- .../satoshi-bridge/src/api/token_receiver.rs | 8 ++ .../src/bitcoin_utils/contract_methods.rs | 104 +++++++++++++++++- contracts/satoshi-bridge/src/event.rs | 6 + contracts/satoshi-bridge/src/rbf/withdraw.rs | 6 +- .../src/zcash_utils/contract_methods.rs | 18 ++- 5 files changed, 136 insertions(+), 6 deletions(-) diff --git a/contracts/satoshi-bridge/src/api/token_receiver.rs b/contracts/satoshi-bridge/src/api/token_receiver.rs index f0a261c..36fe138 100644 --- a/contracts/satoshi-bridge/src/api/token_receiver.rs +++ b/contracts/satoshi-bridge/src/api/token_receiver.rs @@ -14,6 +14,10 @@ pub enum TokenReceiverMessage { output: Vec, max_gas_fee: Option, }, + Rbf { + pending_tx_id: String, + output: Vec, + }, } #[near] @@ -60,6 +64,10 @@ impl FungibleTokenReceiver for Contract { output, max_gas_fee ), + TokenReceiverMessage::Rbf { + pending_tx_id, + output + } => self.rbf_subsidize_chain_specific(amount, sender_id, pending_tx_id, output) } } } diff --git a/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs b/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs index 55d6f07..4c63233 100644 --- a/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs +++ b/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs @@ -1,5 +1,8 @@ use crate::psbt_wrapper::PsbtWrapper; -use crate::{BTCPendingInfo, Contract, Event}; +use crate::{ + init_rbf_btc_pending_info, BTCPendingInfo, Contract, Event, PendingInfoStage, PendingInfoState, + RbfState, +}; use bitcoin::{OutPoint, TxOut}; use near_sdk::json_types::U128; use near_sdk::{require, AccountId, PromiseOrValue}; @@ -58,7 +61,13 @@ impl Contract { max_gas_fee: Option, ) -> PromiseOrValue { let mut psbt = PsbtWrapper::new(input, output); - self.create_btc_pending_info(sender_id, amount, target_btc_address, &mut psbt, max_gas_fee); + self.create_btc_pending_info( + sender_id, + amount, + target_btc_address, + &mut psbt, + max_gas_fee, + ); PromiseOrValue::Value(U128(0)) } @@ -91,4 +100,95 @@ impl Contract { let original_psbt = original_tx_btc_pending_info.get_psbt(); PsbtWrapper::from_original_psbt(original_psbt, output) } + + pub(crate) fn rbf_subsidize_chain_specific( + &mut self, + amount: u128, + sender_id: AccountId, + pending_tx_id: String, + output: Vec, + ) -> PromiseOrValue { + let user_account_id = self + .internal_unwrap_btc_pending_info(&pending_tx_id) + .account_id + .clone(); + require!( + self.internal_unwrap_account(&user_account_id) + .btc_pending_sign_id + .is_none(), + "Assisted user previous btc tx has not been signed" + ); + + let original_tx_btc_pending_info = self.internal_unwrap_btc_pending_info(&pending_tx_id); + + let new_psbt = self + .generate_psbt_from_original_psbt_and_new_output(original_tx_btc_pending_info, output); + + let btc_pending_id = self.internal_rbf_subsidize(&user_account_id, pending_tx_id, new_psbt, amount); + + self.internal_unwrap_mut_account(&user_account_id) + .btc_pending_sign_id = Some(btc_pending_id.clone()); + + Event::GenerateBtcPendingInfo { + account_id: &user_account_id, + btc_pending_id: &btc_pending_id, + } + .emit(); + + Event::SubsidizeRBF { + btc_pending_id: &btc_pending_id, + subsidy_amount: U128(amount), + subsidizer: &sender_id, + beneficiary: &user_account_id, + }.emit(); + + PromiseOrValue::Value(U128(0)) + } + + pub fn internal_rbf_subsidize( + &mut self, + account_id: &AccountId, + original_btc_pending_verify_id: String, + withdraw_rbf_psbt: PsbtWrapper, + subsidy_amount: u128, + ) -> String { + let original_tx_btc_pending_info = + self.internal_unwrap_btc_pending_info(&original_btc_pending_verify_id); + require!( + &original_tx_btc_pending_info.account_id == account_id, + "Not allow" + ); + original_tx_btc_pending_info.assert_not_canceled(); + original_tx_btc_pending_info.assert_withdraw_original_pending_verify_tx(); + + let mut btc_pending_info = init_rbf_btc_pending_info( + original_tx_btc_pending_info, + PendingInfoState::WithdrawUserRbf(RbfState { + stage: PendingInfoStage::PendingSign, + original_tx_id: original_btc_pending_verify_id.clone(), + }), + ); + btc_pending_info.transfer_amount += subsidy_amount; + + let (actual_received_amount, gas_fee) = + self.check_withdraw_rbf_psbt_valid(original_tx_btc_pending_info, &withdraw_rbf_psbt, subsidy_amount); + + require!(actual_received_amount == original_tx_btc_pending_info.actual_received_amount, "Actual received amount has been changed."); + let gas_fee_diff = gas_fee.saturating_sub(original_tx_btc_pending_info.gas_fee); + require!(gas_fee_diff == subsidy_amount, "Gas fee diff is not equal to subsidy amount."); + + btc_pending_info.gas_fee = gas_fee; + btc_pending_info.burn_amount = actual_received_amount + gas_fee; + Self::check_withdraw_chain_specific(original_tx_btc_pending_info, gas_fee); + + self.internal_unwrap_mut_btc_pending_info(&original_btc_pending_verify_id) + .update_max_gas_fee(gas_fee); + + self.set_rbf_pending_info( + &original_btc_pending_verify_id, + btc_pending_info, + withdraw_rbf_psbt, + false, + ) + } } diff --git a/contracts/satoshi-bridge/src/event.rs b/contracts/satoshi-bridge/src/event.rs index 6dbd4c7..958dc1a 100644 --- a/contracts/satoshi-bridge/src/event.rs +++ b/contracts/satoshi-bridge/src/event.rs @@ -44,6 +44,12 @@ pub enum Event<'a> { account_id: &'a AccountId, btc_pending_id: &'a String, }, + SubsidizeRBF { + btc_pending_id: &'a String, + subsidy_amount: U128, + subsidizer: &'a AccountId, + beneficiary: &'a AccountId, + }, BtcInputSignature { account_id: &'a AccountId, btc_pending_id: &'a String, diff --git a/contracts/satoshi-bridge/src/rbf/withdraw.rs b/contracts/satoshi-bridge/src/rbf/withdraw.rs index 90a1207..6079198 100644 --- a/contracts/satoshi-bridge/src/rbf/withdraw.rs +++ b/contracts/satoshi-bridge/src/rbf/withdraw.rs @@ -6,6 +6,7 @@ impl Contract { &self, original_tx_btc_pending_info: &BTCPendingInfo, withdraw_rbf_psbt: &PsbtWrapper, + subsidy_amount: u128, ) -> (u128, u128) { let withdraw_change_address_script_pubkey = self.internal_config().get_change_script_pubkey(); @@ -27,7 +28,7 @@ impl Contract { &target_address_script_pubkey, &withdraw_change_address_script_pubkey, &original_tx_btc_pending_info.vutxos, - original_tx_btc_pending_info.transfer_amount, + original_tx_btc_pending_info.transfer_amount + subsidy_amount, original_tx_btc_pending_info.withdraw_fee, ); (actual_received_amount, gas_fee) @@ -56,11 +57,10 @@ impl Contract { }), ); let (actual_received_amount, gas_fee) = - self.check_withdraw_rbf_psbt_valid(original_tx_btc_pending_info, &withdraw_rbf_psbt); + self.check_withdraw_rbf_psbt_valid(original_tx_btc_pending_info, &withdraw_rbf_psbt, 0); btc_pending_info.gas_fee = gas_fee; btc_pending_info.actual_received_amount = actual_received_amount; btc_pending_info.burn_amount = actual_received_amount + gas_fee; - Self::check_withdraw_chain_specific(original_tx_btc_pending_info, gas_fee); self.internal_unwrap_mut_btc_pending_info(&original_btc_pending_verify_id) .update_max_gas_fee(gas_fee); diff --git a/contracts/satoshi-bridge/src/zcash_utils/contract_methods.rs b/contracts/satoshi-bridge/src/zcash_utils/contract_methods.rs index 79137d9..6507dac 100644 --- a/contracts/satoshi-bridge/src/zcash_utils/contract_methods.rs +++ b/contracts/satoshi-bridge/src/zcash_utils/contract_methods.rs @@ -97,7 +97,13 @@ impl Contract { ) -> U128 { let expiry_height = last_block_height + self.get_config().expiry_height_gap; let mut psbt = PsbtWrapper::new(input, output, expiry_height, self.internal_config()); - self.create_btc_pending_info(sender_id, amount.0, target_btc_address, &mut psbt, max_gas_fee); + self.create_btc_pending_info( + sender_id, + amount.0, + target_btc_address, + &mut psbt, + max_gas_fee, + ); U128(0) } @@ -189,4 +195,14 @@ impl Contract { self.internal_config(), ) } + + pub(crate) fn rbf_subsidize_chain_specific( + &mut self, + amount: u128, + sender_id: AccountId, + pending_tx_id: String, + output: Vec, + ) -> PromiseOrValue { + unimplemented!("This function is not supported yet"); + } } From a6514fba956a817daf753fb6e209a42d2649f8e1 Mon Sep 17 00:00:00 2001 From: Olga Kunyavskaya Date: Mon, 6 Oct 2025 12:03:12 +0100 Subject: [PATCH 2/9] fix event name --- .../src/bitcoin_utils/contract_methods.rs | 35 ++++++++++++------- contracts/satoshi-bridge/src/event.rs | 2 +- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs b/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs index 4c63233..88e61d3 100644 --- a/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs +++ b/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs @@ -48,7 +48,10 @@ impl Contract { // Ensure that the RBF transaction pays more gas than the previous transaction. let max_gas_fee = original_tx_btc_pending_info.get_max_gas_fee(); let additional_gas_amount = gas_fee.saturating_sub(max_gas_fee); - require!(additional_gas_amount > 0, "No gas increase."); + require!( + additional_gas_amount > 0, + format!("No gas increase. Old gas fee = {max_gas_fee}, new gas fee = {gas_fee}") + ); } pub(crate) fn ft_on_transfer_withdraw_chain_specific( @@ -124,7 +127,8 @@ impl Contract { let new_psbt = self .generate_psbt_from_original_psbt_and_new_output(original_tx_btc_pending_info, output); - let btc_pending_id = self.internal_rbf_subsidize(&user_account_id, pending_tx_id, new_psbt, amount); + let btc_pending_id = + self.internal_rbf_subsidize(&user_account_id, pending_tx_id, new_psbt, amount); self.internal_unwrap_mut_account(&user_account_id) .btc_pending_sign_id = Some(btc_pending_id.clone()); @@ -134,13 +138,14 @@ impl Contract { btc_pending_id: &btc_pending_id, } .emit(); - - Event::SubsidizeRBF { + + Event::SubsidizeRbf { btc_pending_id: &btc_pending_id, subsidy_amount: U128(amount), subsidizer: &sender_id, beneficiary: &user_account_id, - }.emit(); + } + .emit(); PromiseOrValue::Value(U128(0)) } @@ -169,21 +174,27 @@ impl Contract { }), ); btc_pending_info.transfer_amount += subsidy_amount; - - let (actual_received_amount, gas_fee) = - self.check_withdraw_rbf_psbt_valid(original_tx_btc_pending_info, &withdraw_rbf_psbt, subsidy_amount); - - require!(actual_received_amount == original_tx_btc_pending_info.actual_received_amount, "Actual received amount has been changed."); + + let (actual_received_amount, gas_fee) = self.check_withdraw_rbf_psbt_valid( + original_tx_btc_pending_info, + &withdraw_rbf_psbt, + subsidy_amount, + ); + + require!( + actual_received_amount == original_tx_btc_pending_info.actual_received_amount, + "Actual received amount has been changed." + ); let gas_fee_diff = gas_fee.saturating_sub(original_tx_btc_pending_info.gas_fee); require!(gas_fee_diff == subsidy_amount, "Gas fee diff is not equal to subsidy amount."); - + btc_pending_info.gas_fee = gas_fee; btc_pending_info.burn_amount = actual_received_amount + gas_fee; Self::check_withdraw_chain_specific(original_tx_btc_pending_info, gas_fee); self.internal_unwrap_mut_btc_pending_info(&original_btc_pending_verify_id) .update_max_gas_fee(gas_fee); - + self.set_rbf_pending_info( &original_btc_pending_verify_id, btc_pending_info, diff --git a/contracts/satoshi-bridge/src/event.rs b/contracts/satoshi-bridge/src/event.rs index 958dc1a..b89c365 100644 --- a/contracts/satoshi-bridge/src/event.rs +++ b/contracts/satoshi-bridge/src/event.rs @@ -44,7 +44,7 @@ pub enum Event<'a> { account_id: &'a AccountId, btc_pending_id: &'a String, }, - SubsidizeRBF { + SubsidizeRbf { btc_pending_id: &'a String, subsidy_amount: U128, subsidizer: &'a AccountId, From e9407455b0a69565ec87abd22d736656e9089c84 Mon Sep 17 00:00:00 2001 From: Olga Kunyavskaya Date: Tue, 14 Oct 2025 22:03:27 +0100 Subject: [PATCH 3/9] migrate --- contracts/satoshi-bridge/src/api/bridge.rs | 1 + .../satoshi-bridge/src/api/token_receiver.rs | 1 + .../satoshi-bridge/src/btc_pending_info.rs | 120 +++++++++++++++++- 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/contracts/satoshi-bridge/src/api/bridge.rs b/contracts/satoshi-bridge/src/api/bridge.rs index f96fa0a..c512187 100644 --- a/contracts/satoshi-bridge/src/api/bridge.rs +++ b/contracts/satoshi-bridge/src/api/bridge.rs @@ -368,6 +368,7 @@ impl Contract { max_gas_fee: gas_fee, last_rbf_time_sec: None, cancel_rbf_reserved: None, + subsidize_amount: 0, }), }; require!( diff --git a/contracts/satoshi-bridge/src/api/token_receiver.rs b/contracts/satoshi-bridge/src/api/token_receiver.rs index 36fe138..3f6b2d4 100644 --- a/contracts/satoshi-bridge/src/api/token_receiver.rs +++ b/contracts/satoshi-bridge/src/api/token_receiver.rs @@ -127,6 +127,7 @@ impl Contract { max_gas_fee: gas_fee, last_rbf_time_sec: None, cancel_rbf_reserved: None, + subsidize_amount: 0, }), }; require!( diff --git a/contracts/satoshi-bridge/src/btc_pending_info.rs b/contracts/satoshi-bridge/src/btc_pending_info.rs index c519c03..8380699 100644 --- a/contracts/satoshi-bridge/src/btc_pending_info.rs +++ b/contracts/satoshi-bridge/src/btc_pending_info.rs @@ -12,6 +12,8 @@ pub struct OriginalState { pub max_gas_fee: u128, pub last_rbf_time_sec: Option, pub cancel_rbf_reserved: Option, + #[serde(with = "u128_dec_format")] + pub subsidize_amount: u128, } impl OriginalState { @@ -291,14 +293,116 @@ impl BTCPendingInfo { } } +#[near(serializers = [borsh, json])] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub struct OriginalStateV0 { + pub stage: PendingInfoStage, + #[serde(with = "u128_dec_format")] + pub max_gas_fee: u128, + pub last_rbf_time_sec: Option, + pub cancel_rbf_reserved: Option, +} + +#[near(serializers = [borsh, json])] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum PendingInfoStateV0 { + WithdrawOriginal(OriginalStateV0), + WithdrawUserRbf(RbfState), + WithdrawCancelRbf(RbfState), + ActiveUtxoManagementOriginal(OriginalStateV0), + ActiveUtxoManagementRbf(RbfState), + ActiveUtxoManagementCancelRbf(RbfState), +} + +#[near(serializers = [borsh, json])] +#[derive(Clone)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub struct BTCPendingInfoV0 { + pub account_id: AccountId, + pub btc_pending_id: String, + #[serde(with = "u128_dec_format")] + pub transfer_amount: u128, + #[serde(with = "u128_dec_format")] + pub actual_received_amount: u128, + #[serde(with = "u128_dec_format")] + pub withdraw_fee: u128, + #[serde(with = "u128_dec_format")] + pub gas_fee: u128, + #[serde(with = "u128_dec_format")] + pub burn_amount: u128, + pub psbt_hex: String, + pub vutxos: Vec, + pub signatures: Vec>, + pub tx_bytes_with_sign: Option>, + pub create_time_sec: u32, + pub last_sign_time_sec: u32, + pub state: PendingInfoStateV0, +} + +impl From for OriginalState { + fn from(c: OriginalStateV0) -> Self { + Self { + stage: c.stage, + max_gas_fee: c.max_gas_fee, + last_rbf_time_sec: c.last_rbf_time_sec, + cancel_rbf_reserved: c.cancel_rbf_reserved, + subsidize_amount: 0, + } + } +} + +impl From for PendingInfoState { + fn from(c: PendingInfoStateV0) -> Self { + match c { + PendingInfoStateV0::WithdrawOriginal(x) => PendingInfoState::WithdrawOriginal(x.into()), + PendingInfoStateV0::WithdrawUserRbf(x) => PendingInfoState::WithdrawUserRbf(x), + PendingInfoStateV0::WithdrawCancelRbf(x) => PendingInfoState::WithdrawCancelRbf(x), + PendingInfoStateV0::ActiveUtxoManagementOriginal(x) => { + PendingInfoState::ActiveUtxoManagementOriginal(x.into()) + } + PendingInfoStateV0::ActiveUtxoManagementRbf(x) => { + PendingInfoState::ActiveUtxoManagementRbf(x) + } + PendingInfoStateV0::ActiveUtxoManagementCancelRbf(x) => { + PendingInfoState::ActiveUtxoManagementCancelRbf(x) + } + } + } +} + +impl From for BTCPendingInfo { + fn from(c: BTCPendingInfoV0) -> Self { + Self { + account_id: c.account_id, + btc_pending_id: c.btc_pending_id, + transfer_amount: c.transfer_amount, + actual_received_amount: c.actual_received_amount, + withdraw_fee: c.withdraw_fee, + gas_fee: c.gas_fee, + burn_amount: c.burn_amount, + psbt_hex: c.psbt_hex, + vutxos: c.vutxos, + signatures: c.signatures, + tx_bytes_with_sign: c.tx_bytes_with_sign, + create_time_sec: c.create_time_sec, + last_sign_time_sec: c.last_sign_time_sec, + state: c.state.into(), + } + } +} + #[near(serializers = [borsh])] pub enum VBTCPendingInfo { + V0(BTCPendingInfoV0), Current(BTCPendingInfo), } impl From for BTCPendingInfo { fn from(v: VBTCPendingInfo) -> Self { match v { + VBTCPendingInfo::V0(c) => c.into(), VBTCPendingInfo::Current(c) => c, } } @@ -307,6 +411,7 @@ impl From for BTCPendingInfo { impl From<&VBTCPendingInfo> for BTCPendingInfo { fn from(v: &VBTCPendingInfo) -> Self { match v { + VBTCPendingInfo::V0(c) => c.clone().into(), VBTCPendingInfo::Current(c) => c.clone(), } } @@ -315,6 +420,7 @@ impl From<&VBTCPendingInfo> for BTCPendingInfo { impl<'a> From<&'a VBTCPendingInfo> for &'a BTCPendingInfo { fn from(v: &'a VBTCPendingInfo) -> Self { match v { + VBTCPendingInfo::V0(_) => unreachable!(), VBTCPendingInfo::Current(c) => c, } } @@ -323,6 +429,7 @@ impl<'a> From<&'a VBTCPendingInfo> for &'a BTCPendingInfo { impl<'a> From<&'a mut VBTCPendingInfo> for &'a mut BTCPendingInfo { fn from(v: &'a mut VBTCPendingInfo) -> Self { match v { + VBTCPendingInfo::V0(_) => unreachable!(), VBTCPendingInfo::Current(c) => c, } } @@ -361,11 +468,18 @@ impl Contract { &mut self, btc_pending_id: &String, ) -> &mut BTCPendingInfo { - self.data_mut() + let btc_pending_info = self + .data_mut() .btc_pending_infos .get_mut(btc_pending_id) - .map(|o| o.into()) - .expect("BTC pending info not exist") + .expect("BTC pending info not exist"); + + if let VBTCPendingInfo::V0(old) = &btc_pending_info { + let new_current = BTCPendingInfo::from(old.clone()); + *btc_pending_info = VBTCPendingInfo::Current(new_current); + } + + btc_pending_info.into() } pub fn internal_remove_btc_pending_info(&mut self, btc_pending_id: &String) -> BTCPendingInfo { From 13d0618f69c6fc4fb5dc9b900a9b31d6123fcdfd Mon Sep 17 00:00:00 2001 From: Olga Kunyavskaya Date: Thu, 16 Oct 2025 18:15:26 +0100 Subject: [PATCH 4/9] migrate --- Cargo.lock | 2 +- contracts/satoshi-bridge/Cargo.toml | 2 +- .../satoshi-bridge/src/btc_pending_info.rs | 100 ---------- contracts/satoshi-bridge/src/legacy.rs | 174 ++++++++++++++++++ contracts/satoshi-bridge/src/lib.rs | 1 + contracts/satoshi-bridge/src/upgrade.rs | 1 + 6 files changed, 178 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1a72fa..34ad884 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3715,7 +3715,7 @@ dependencies = [ [[package]] name = "satoshi-bridge" -version = "0.7.0" +version = "0.8.0" dependencies = [ "bitcoin", "core2", diff --git a/contracts/satoshi-bridge/Cargo.toml b/contracts/satoshi-bridge/Cargo.toml index 93a11c8..8dbd7c8 100644 --- a/contracts/satoshi-bridge/Cargo.toml +++ b/contracts/satoshi-bridge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "satoshi-bridge" -version = "0.7.0" +version = "0.8.0" edition.workspace = true publish.workspace = true repository.workspace = true diff --git a/contracts/satoshi-bridge/src/btc_pending_info.rs b/contracts/satoshi-bridge/src/btc_pending_info.rs index 8380699..a481202 100644 --- a/contracts/satoshi-bridge/src/btc_pending_info.rs +++ b/contracts/satoshi-bridge/src/btc_pending_info.rs @@ -293,106 +293,6 @@ impl BTCPendingInfo { } } -#[near(serializers = [borsh, json])] -#[derive(Clone, PartialEq, Eq)] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] -pub struct OriginalStateV0 { - pub stage: PendingInfoStage, - #[serde(with = "u128_dec_format")] - pub max_gas_fee: u128, - pub last_rbf_time_sec: Option, - pub cancel_rbf_reserved: Option, -} - -#[near(serializers = [borsh, json])] -#[derive(Clone, PartialEq, Eq)] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] -pub enum PendingInfoStateV0 { - WithdrawOriginal(OriginalStateV0), - WithdrawUserRbf(RbfState), - WithdrawCancelRbf(RbfState), - ActiveUtxoManagementOriginal(OriginalStateV0), - ActiveUtxoManagementRbf(RbfState), - ActiveUtxoManagementCancelRbf(RbfState), -} - -#[near(serializers = [borsh, json])] -#[derive(Clone)] -#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] -pub struct BTCPendingInfoV0 { - pub account_id: AccountId, - pub btc_pending_id: String, - #[serde(with = "u128_dec_format")] - pub transfer_amount: u128, - #[serde(with = "u128_dec_format")] - pub actual_received_amount: u128, - #[serde(with = "u128_dec_format")] - pub withdraw_fee: u128, - #[serde(with = "u128_dec_format")] - pub gas_fee: u128, - #[serde(with = "u128_dec_format")] - pub burn_amount: u128, - pub psbt_hex: String, - pub vutxos: Vec, - pub signatures: Vec>, - pub tx_bytes_with_sign: Option>, - pub create_time_sec: u32, - pub last_sign_time_sec: u32, - pub state: PendingInfoStateV0, -} - -impl From for OriginalState { - fn from(c: OriginalStateV0) -> Self { - Self { - stage: c.stage, - max_gas_fee: c.max_gas_fee, - last_rbf_time_sec: c.last_rbf_time_sec, - cancel_rbf_reserved: c.cancel_rbf_reserved, - subsidize_amount: 0, - } - } -} - -impl From for PendingInfoState { - fn from(c: PendingInfoStateV0) -> Self { - match c { - PendingInfoStateV0::WithdrawOriginal(x) => PendingInfoState::WithdrawOriginal(x.into()), - PendingInfoStateV0::WithdrawUserRbf(x) => PendingInfoState::WithdrawUserRbf(x), - PendingInfoStateV0::WithdrawCancelRbf(x) => PendingInfoState::WithdrawCancelRbf(x), - PendingInfoStateV0::ActiveUtxoManagementOriginal(x) => { - PendingInfoState::ActiveUtxoManagementOriginal(x.into()) - } - PendingInfoStateV0::ActiveUtxoManagementRbf(x) => { - PendingInfoState::ActiveUtxoManagementRbf(x) - } - PendingInfoStateV0::ActiveUtxoManagementCancelRbf(x) => { - PendingInfoState::ActiveUtxoManagementCancelRbf(x) - } - } - } -} - -impl From for BTCPendingInfo { - fn from(c: BTCPendingInfoV0) -> Self { - Self { - account_id: c.account_id, - btc_pending_id: c.btc_pending_id, - transfer_amount: c.transfer_amount, - actual_received_amount: c.actual_received_amount, - withdraw_fee: c.withdraw_fee, - gas_fee: c.gas_fee, - burn_amount: c.burn_amount, - psbt_hex: c.psbt_hex, - vutxos: c.vutxos, - signatures: c.signatures, - tx_bytes_with_sign: c.tx_bytes_with_sign, - create_time_sec: c.create_time_sec, - last_sign_time_sec: c.last_sign_time_sec, - state: c.state.into(), - } - } -} - #[near(serializers = [borsh])] pub enum VBTCPendingInfo { V0(BTCPendingInfoV0), diff --git a/contracts/satoshi-bridge/src/legacy.rs b/contracts/satoshi-bridge/src/legacy.rs index c766f19..1c5c3d8 100644 --- a/contracts/satoshi-bridge/src/legacy.rs +++ b/contracts/satoshi-bridge/src/legacy.rs @@ -467,3 +467,177 @@ impl From for ContractData { } } } + +#[near(serializers = [borsh, json])] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub struct OriginalStateV0 { + pub stage: PendingInfoStage, + #[serde(with = "u128_dec_format")] + pub max_gas_fee: u128, + pub last_rbf_time_sec: Option, + pub cancel_rbf_reserved: Option, +} + +#[near(serializers = [borsh, json])] +#[derive(Clone, PartialEq, Eq)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub enum PendingInfoStateV0 { + WithdrawOriginal(OriginalStateV0), + WithdrawUserRbf(RbfState), + WithdrawCancelRbf(RbfState), + ActiveUtxoManagementOriginal(OriginalStateV0), + ActiveUtxoManagementRbf(RbfState), + ActiveUtxoManagementCancelRbf(RbfState), +} + +#[near(serializers = [borsh, json])] +#[derive(Clone)] +#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))] +pub struct BTCPendingInfoV0 { + pub account_id: AccountId, + pub btc_pending_id: String, + #[serde(with = "u128_dec_format")] + pub transfer_amount: u128, + #[serde(with = "u128_dec_format")] + pub actual_received_amount: u128, + #[serde(with = "u128_dec_format")] + pub withdraw_fee: u128, + #[serde(with = "u128_dec_format")] + pub gas_fee: u128, + #[serde(with = "u128_dec_format")] + pub burn_amount: u128, + pub psbt_hex: String, + pub vutxos: Vec, + pub signatures: Vec>, + pub tx_bytes_with_sign: Option>, + pub create_time_sec: u32, + pub last_sign_time_sec: u32, + pub state: PendingInfoStateV0, +} + +impl From for OriginalState { + fn from(c: OriginalStateV0) -> Self { + Self { + stage: c.stage, + max_gas_fee: c.max_gas_fee, + last_rbf_time_sec: c.last_rbf_time_sec, + cancel_rbf_reserved: c.cancel_rbf_reserved, + subsidize_amount: 0, + } + } +} + +impl From for PendingInfoState { + fn from(c: PendingInfoStateV0) -> Self { + match c { + PendingInfoStateV0::WithdrawOriginal(x) => PendingInfoState::WithdrawOriginal(x.into()), + PendingInfoStateV0::WithdrawUserRbf(x) => PendingInfoState::WithdrawUserRbf(x), + PendingInfoStateV0::WithdrawCancelRbf(x) => PendingInfoState::WithdrawCancelRbf(x), + PendingInfoStateV0::ActiveUtxoManagementOriginal(x) => { + PendingInfoState::ActiveUtxoManagementOriginal(x.into()) + } + PendingInfoStateV0::ActiveUtxoManagementRbf(x) => { + PendingInfoState::ActiveUtxoManagementRbf(x) + } + PendingInfoStateV0::ActiveUtxoManagementCancelRbf(x) => { + PendingInfoState::ActiveUtxoManagementCancelRbf(x) + } + } + } +} + +impl From for BTCPendingInfo { + fn from(c: BTCPendingInfoV0) -> Self { + Self { + account_id: c.account_id, + btc_pending_id: c.btc_pending_id, + transfer_amount: c.transfer_amount, + actual_received_amount: c.actual_received_amount, + withdraw_fee: c.withdraw_fee, + gas_fee: c.gas_fee, + burn_amount: c.burn_amount, + psbt_hex: c.psbt_hex, + vutxos: c.vutxos, + signatures: c.signatures, + tx_bytes_with_sign: c.tx_bytes_with_sign, + create_time_sec: c.create_time_sec, + last_sign_time_sec: c.last_sign_time_sec, + state: c.state.into(), + } + } +} + +#[near(serializers = [borsh])] +pub struct ContractDataV3 { + pub config: LazyOption, + pub accounts: IterableMap, + pub utxos: IterableMap, + pub unavailable_utxos: IterableMap, + pub verified_deposit_utxo: LookupSet, + pub btc_pending_infos: IterableMap, + pub rbf_txs: IterableMap>, + pub relayer_white_list: IterableSet, + pub extra_msg_relayer_white_list: IterableSet, + pub post_action_receiver_id_white_list: IterableSet, + pub post_action_msg_templates: IterableMap>, + pub lost_found: IterableMap, + pub acc_collected_protocol_fee: u128, + pub cur_available_protocol_fee: u128, + pub acc_claimed_protocol_fee: u128, + pub cur_reserved_protocol_fee: u128, + pub acc_protocol_fee_for_gas: u128, +} + +impl From for ContractData { + fn from(c: ContractDataV3) -> Self { + let ContractDataV3 { + config, + accounts, + utxos, + unavailable_utxos, + verified_deposit_utxo, + mut btc_pending_infos, + rbf_txs, + relayer_white_list, + extra_msg_relayer_white_list, + post_action_receiver_id_white_list, + post_action_msg_templates, + lost_found, + acc_collected_protocol_fee, + cur_available_protocol_fee, + acc_claimed_protocol_fee, + cur_reserved_protocol_fee, + acc_protocol_fee_for_gas, + } = c; + + let keys: Vec = btc_pending_infos.keys().map(|k| k.clone()).collect(); + + for key in keys { + if let Some(value) = btc_pending_infos.get(&key) { + let current: BTCPendingInfo = value.into(); + btc_pending_infos.insert(key, VBTCPendingInfo::Current(current)); + } + } + + Self { + config, + accounts, + utxos, + unavailable_utxos, + verified_deposit_utxo, + btc_pending_infos, + rbf_txs, + relayer_white_list, + extra_msg_relayer_white_list, + post_action_receiver_id_white_list, + post_action_msg_templates, + lost_found, + acc_collected_protocol_fee, + cur_available_protocol_fee, + acc_claimed_protocol_fee, + cur_reserved_protocol_fee, + acc_protocol_fee_for_gas, + } + } +} diff --git a/contracts/satoshi-bridge/src/lib.rs b/contracts/satoshi-bridge/src/lib.rs index 149eb1b..98e70dd 100644 --- a/contracts/satoshi-bridge/src/lib.rs +++ b/contracts/satoshi-bridge/src/lib.rs @@ -125,6 +125,7 @@ pub enum VersionedContractData { V0(ContractDataV0), V1(ContractDataV1), V2(ContractDataV2), + V3(ContractDataV3), Current(ContractData), } diff --git a/contracts/satoshi-bridge/src/upgrade.rs b/contracts/satoshi-bridge/src/upgrade.rs index 755649c..72db212 100644 --- a/contracts/satoshi-bridge/src/upgrade.rs +++ b/contracts/satoshi-bridge/src/upgrade.rs @@ -12,6 +12,7 @@ impl Contract { VersionedContractData::V0(data) => VersionedContractData::Current(data.into()), VersionedContractData::V1(data) => VersionedContractData::Current(data.into()), VersionedContractData::V2(data) => VersionedContractData::Current(data.into()), + VersionedContractData::V3(data) => VersionedContractData::Current(data.into()), VersionedContractData::Current(data) => VersionedContractData::Current(data), }; contract From 429fc4b68078e0bae52f0eea784945870f964348 Mon Sep 17 00:00:00 2001 From: Olga Kunyavskaya Date: Thu, 16 Oct 2025 19:35:56 +0100 Subject: [PATCH 5/9] increment subsidy amount --- .../src/bitcoin_utils/contract_methods.rs | 10 ++++++--- .../satoshi-bridge/src/btc_pending_info.rs | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs b/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs index 88e61d3..df7e629 100644 --- a/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs +++ b/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs @@ -173,12 +173,13 @@ impl Contract { original_tx_id: original_btc_pending_verify_id.clone(), }), ); - btc_pending_info.transfer_amount += subsidy_amount; + let full_subsidy_amount = self.internal_unwrap_btc_pending_info(&original_btc_pending_verify_id).get_subsidize_amount() + subsidy_amount; + btc_pending_info.transfer_amount += full_subsidy_amount; let (actual_received_amount, gas_fee) = self.check_withdraw_rbf_psbt_valid( original_tx_btc_pending_info, &withdraw_rbf_psbt, - subsidy_amount, + full_subsidy_amount, ); require!( @@ -186,7 +187,7 @@ impl Contract { "Actual received amount has been changed." ); let gas_fee_diff = gas_fee.saturating_sub(original_tx_btc_pending_info.gas_fee); - require!(gas_fee_diff == subsidy_amount, "Gas fee diff is not equal to subsidy amount."); + require!(gas_fee_diff == full_subsidy_amount, "Gas fee diff is not equal to subsidy amount."); btc_pending_info.gas_fee = gas_fee; btc_pending_info.burn_amount = actual_received_amount + gas_fee; @@ -194,6 +195,9 @@ impl Contract { self.internal_unwrap_mut_btc_pending_info(&original_btc_pending_verify_id) .update_max_gas_fee(gas_fee); + + self.internal_unwrap_mut_btc_pending_info(&original_btc_pending_verify_id) + .update_subsidize_amount(full_subsidy_amount); self.set_rbf_pending_info( &original_btc_pending_verify_id, diff --git a/contracts/satoshi-bridge/src/btc_pending_info.rs b/contracts/satoshi-bridge/src/btc_pending_info.rs index a481202..0adfe41 100644 --- a/contracts/satoshi-bridge/src/btc_pending_info.rs +++ b/contracts/satoshi-bridge/src/btc_pending_info.rs @@ -201,6 +201,28 @@ impl BTCPendingInfo { _ => env::panic_str("Not original tx"), } } + + pub fn get_subsidize_amount(&self) -> u128 { + match self.state.borrow() { + PendingInfoState::WithdrawOriginal(state) => state.subsidize_amount, + PendingInfoState::ActiveUtxoManagementOriginal(state) => state.subsidize_amount, + _ => env::panic_str("Not original tx"), + } + } + + pub fn update_subsidize_amount(&mut self, subsidize_amount: u128) { + match self.state.borrow_mut() { + PendingInfoState::WithdrawOriginal(state) => { + state.subsidize_amount = subsidize_amount; + state.last_rbf_time_sec = Some(nano_to_sec(env::block_timestamp())); + } + PendingInfoState::ActiveUtxoManagementOriginal(state) => { + state.subsidize_amount = subsidize_amount; + state.last_rbf_time_sec = Some(nano_to_sec(env::block_timestamp())); + } + _ => env::panic_str("Not original tx"), + } + } pub fn to_pending_verify_stage(&mut self) { match self.state.borrow_mut() { From 01f772a4e494db1d814831646c9f75f7b80e7162 Mon Sep 17 00:00:00 2001 From: Olga Kunyavskaya Date: Fri, 17 Oct 2025 13:35:30 +0100 Subject: [PATCH 6/9] check amount only on ft_on_transfer --- .../satoshi-bridge/src/api/token_receiver.rs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/contracts/satoshi-bridge/src/api/token_receiver.rs b/contracts/satoshi-bridge/src/api/token_receiver.rs index a7528dc..538ad7a 100644 --- a/contracts/satoshi-bridge/src/api/token_receiver.rs +++ b/contracts/satoshi-bridge/src/api/token_receiver.rs @@ -30,10 +30,6 @@ impl FungibleTokenReceiver for Contract { msg: String, ) -> PromiseOrValue { let amount = amount.into(); - require!( - amount >= self.internal_config().min_withdraw_amount, - "Invalid amount" - ); let message = serde_json::from_str::(&msg).expect("INVALID MSG"); let token_id = env::predecessor_account_id(); require!( @@ -56,18 +52,24 @@ impl FungibleTokenReceiver for Contract { input, output, max_gas_fee, - } => self.ft_on_transfer_withdraw_chain_specific( - sender_id, - amount, - target_btc_address, - input, - output, - max_gas_fee, - ), + } => { + require!( + amount >= self.internal_config().min_withdraw_amount, + "Invalid amount" + ); + self.ft_on_transfer_withdraw_chain_specific( + sender_id, + amount, + target_btc_address, + input, + output, + max_gas_fee, + ) + } TokenReceiverMessage::Rbf { - pending_tx_id, - output - } => self.rbf_subsidize_chain_specific(amount, sender_id, pending_tx_id, output) + pending_tx_id, + output, + } => self.rbf_subsidize_chain_specific(amount, sender_id, pending_tx_id, output), } } } From 9419f37c6a9171c67f4f5e89acf569a895b330ad Mon Sep 17 00:00:00 2001 From: Olga Kunyavskaya Date: Mon, 20 Oct 2025 11:27:48 +0100 Subject: [PATCH 7/9] refactor --- .../src/bitcoin_utils/contract_methods.rs | 44 +++++++++---------- contracts/satoshi-bridge/src/event.rs | 3 +- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs b/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs index df7e629..01f60d5 100644 --- a/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs +++ b/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs @@ -84,6 +84,10 @@ impl Contract { active_utxo_management_rbf_chain_specific, internal_active_utxo_management_rbf ); + define_rbf_method!( + rbf_subsidize_chain_specific_inner, + internal_rbf_subsidize + ); pub(crate) fn active_utxo_management_chain_specific( &mut self, @@ -121,27 +125,19 @@ impl Contract { .is_none(), "Assisted user previous btc tx has not been signed" ); - - let original_tx_btc_pending_info = self.internal_unwrap_btc_pending_info(&pending_tx_id); - - let new_psbt = self - .generate_psbt_from_original_psbt_and_new_output(original_tx_btc_pending_info, output); - - let btc_pending_id = - self.internal_rbf_subsidize(&user_account_id, pending_tx_id, new_psbt, amount); - - self.internal_unwrap_mut_account(&user_account_id) - .btc_pending_sign_id = Some(btc_pending_id.clone()); - - Event::GenerateBtcPendingInfo { - account_id: &user_account_id, - btc_pending_id: &btc_pending_id, - } - .emit(); + let full_subsidy_amount = self + .internal_unwrap_btc_pending_info(&pending_tx_id) + .get_subsidize_amount() + + amount; + self.internal_unwrap_mut_btc_pending_info(&pending_tx_id) + .update_subsidize_amount(full_subsidy_amount); + + self.rbf_subsidize_chain_specific_inner(user_account_id.clone(), pending_tx_id.clone(), output); Event::SubsidizeRbf { - btc_pending_id: &btc_pending_id, + origin_btc_pending_id: &pending_tx_id, subsidy_amount: U128(amount), + full_subsidy_amount: U128(full_subsidy_amount), subsidizer: &sender_id, beneficiary: &user_account_id, } @@ -155,7 +151,6 @@ impl Contract { account_id: &AccountId, original_btc_pending_verify_id: String, withdraw_rbf_psbt: PsbtWrapper, - subsidy_amount: u128, ) -> String { let original_tx_btc_pending_info = self.internal_unwrap_btc_pending_info(&original_btc_pending_verify_id); @@ -173,7 +168,9 @@ impl Contract { original_tx_id: original_btc_pending_verify_id.clone(), }), ); - let full_subsidy_amount = self.internal_unwrap_btc_pending_info(&original_btc_pending_verify_id).get_subsidize_amount() + subsidy_amount; + let full_subsidy_amount = self + .internal_unwrap_btc_pending_info(&original_btc_pending_verify_id) + .get_subsidize_amount(); btc_pending_info.transfer_amount += full_subsidy_amount; let (actual_received_amount, gas_fee) = self.check_withdraw_rbf_psbt_valid( @@ -187,7 +184,10 @@ impl Contract { "Actual received amount has been changed." ); let gas_fee_diff = gas_fee.saturating_sub(original_tx_btc_pending_info.gas_fee); - require!(gas_fee_diff == full_subsidy_amount, "Gas fee diff is not equal to subsidy amount."); + require!( + gas_fee_diff == full_subsidy_amount, + "Gas fee diff is not equal to subsidy amount." + ); btc_pending_info.gas_fee = gas_fee; btc_pending_info.burn_amount = actual_received_amount + gas_fee; @@ -195,7 +195,7 @@ impl Contract { self.internal_unwrap_mut_btc_pending_info(&original_btc_pending_verify_id) .update_max_gas_fee(gas_fee); - + self.internal_unwrap_mut_btc_pending_info(&original_btc_pending_verify_id) .update_subsidize_amount(full_subsidy_amount); diff --git a/contracts/satoshi-bridge/src/event.rs b/contracts/satoshi-bridge/src/event.rs index b89c365..37d343a 100644 --- a/contracts/satoshi-bridge/src/event.rs +++ b/contracts/satoshi-bridge/src/event.rs @@ -45,8 +45,9 @@ pub enum Event<'a> { btc_pending_id: &'a String, }, SubsidizeRbf { - btc_pending_id: &'a String, + origin_btc_pending_id: &'a String, subsidy_amount: U128, + full_subsidy_amount: U128, subsidizer: &'a AccountId, beneficiary: &'a AccountId, }, From 22940c1350574d161a40041e6227d867db5c4397 Mon Sep 17 00:00:00 2001 From: Olga Kunyavskaya Date: Mon, 20 Oct 2025 12:46:25 +0100 Subject: [PATCH 8/9] refactor --- .../src/bitcoin_utils/contract_methods.rs | 102 +++++------------- contracts/satoshi-bridge/src/rbf/withdraw.rs | 17 ++- 2 files changed, 41 insertions(+), 78 deletions(-) diff --git a/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs b/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs index 01f60d5..e70a984 100644 --- a/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs +++ b/contracts/satoshi-bridge/src/bitcoin_utils/contract_methods.rs @@ -1,8 +1,5 @@ use crate::psbt_wrapper::PsbtWrapper; -use crate::{ - init_rbf_btc_pending_info, BTCPendingInfo, Contract, Event, PendingInfoStage, PendingInfoState, - RbfState, -}; +use crate::{BTCPendingInfo, Contract, Event}; use bitcoin::{OutPoint, TxOut}; use near_sdk::json_types::U128; use near_sdk::{require, AccountId, PromiseOrValue}; @@ -14,7 +11,7 @@ macro_rules! define_rbf_method { account_id: AccountId, original_btc_pending_verify_id: String, output: Vec, - ) { + ) -> String { let original_tx_btc_pending_info = self.internal_unwrap_btc_pending_info(&original_btc_pending_verify_id); @@ -34,6 +31,8 @@ macro_rules! define_rbf_method { btc_pending_id: &btc_pending_id, } .emit(); + + btc_pending_id } }; } @@ -84,10 +83,6 @@ impl Contract { active_utxo_management_rbf_chain_specific, internal_active_utxo_management_rbf ); - define_rbf_method!( - rbf_subsidize_chain_specific_inner, - internal_rbf_subsidize - ); pub(crate) fn active_utxo_management_chain_specific( &mut self, @@ -115,10 +110,8 @@ impl Contract { pending_tx_id: String, output: Vec, ) -> PromiseOrValue { - let user_account_id = self - .internal_unwrap_btc_pending_info(&pending_tx_id) - .account_id - .clone(); + let origin_tx_btc_pending_info = self.internal_unwrap_btc_pending_info(&pending_tx_id); + let user_account_id = origin_tx_btc_pending_info.account_id.clone(); require!( self.internal_unwrap_account(&user_account_id) .btc_pending_sign_id @@ -131,79 +124,38 @@ impl Contract { + amount; self.internal_unwrap_mut_btc_pending_info(&pending_tx_id) .update_subsidize_amount(full_subsidy_amount); - - self.rbf_subsidize_chain_specific_inner(user_account_id.clone(), pending_tx_id.clone(), output); - Event::SubsidizeRbf { - origin_btc_pending_id: &pending_tx_id, - subsidy_amount: U128(amount), - full_subsidy_amount: U128(full_subsidy_amount), - subsidizer: &sender_id, - beneficiary: &user_account_id, - } - .emit(); - - PromiseOrValue::Value(U128(0)) - } - - pub fn internal_rbf_subsidize( - &mut self, - account_id: &AccountId, - original_btc_pending_verify_id: String, - withdraw_rbf_psbt: PsbtWrapper, - ) -> String { - let original_tx_btc_pending_info = - self.internal_unwrap_btc_pending_info(&original_btc_pending_verify_id); - require!( - &original_tx_btc_pending_info.account_id == account_id, - "Not allow" - ); - original_tx_btc_pending_info.assert_not_canceled(); - original_tx_btc_pending_info.assert_withdraw_original_pending_verify_tx(); - - let mut btc_pending_info = init_rbf_btc_pending_info( - original_tx_btc_pending_info, - PendingInfoState::WithdrawUserRbf(RbfState { - stage: PendingInfoStage::PendingSign, - original_tx_id: original_btc_pending_verify_id.clone(), - }), - ); - let full_subsidy_amount = self - .internal_unwrap_btc_pending_info(&original_btc_pending_verify_id) - .get_subsidize_amount(); - btc_pending_info.transfer_amount += full_subsidy_amount; - - let (actual_received_amount, gas_fee) = self.check_withdraw_rbf_psbt_valid( - original_tx_btc_pending_info, - &withdraw_rbf_psbt, - full_subsidy_amount, + let new_pending_info_id = self.withdraw_rbf_chain_specific( + user_account_id.clone(), + pending_tx_id.clone(), + output, ); + let origin_tx_btc_pending_info = self.internal_unwrap_btc_pending_info(&pending_tx_id); + let new_tx_btc_pending_info = self.internal_unwrap_btc_pending_info(&new_pending_info_id); + require!( - actual_received_amount == original_tx_btc_pending_info.actual_received_amount, + new_tx_btc_pending_info.actual_received_amount + == origin_tx_btc_pending_info.actual_received_amount, "Actual received amount has been changed." ); - let gas_fee_diff = gas_fee.saturating_sub(original_tx_btc_pending_info.gas_fee); + let gas_fee_diff = new_tx_btc_pending_info + .gas_fee + .saturating_sub(origin_tx_btc_pending_info.gas_fee); require!( gas_fee_diff == full_subsidy_amount, "Gas fee diff is not equal to subsidy amount." ); - btc_pending_info.gas_fee = gas_fee; - btc_pending_info.burn_amount = actual_received_amount + gas_fee; - Self::check_withdraw_chain_specific(original_tx_btc_pending_info, gas_fee); - - self.internal_unwrap_mut_btc_pending_info(&original_btc_pending_verify_id) - .update_max_gas_fee(gas_fee); - - self.internal_unwrap_mut_btc_pending_info(&original_btc_pending_verify_id) - .update_subsidize_amount(full_subsidy_amount); + Event::SubsidizeRbf { + origin_btc_pending_id: &pending_tx_id, + subsidy_amount: U128(amount), + full_subsidy_amount: U128(full_subsidy_amount), + subsidizer: &sender_id, + beneficiary: &user_account_id, + } + .emit(); - self.set_rbf_pending_info( - &original_btc_pending_verify_id, - btc_pending_info, - withdraw_rbf_psbt, - false, - ) + PromiseOrValue::Value(U128(0)) } } diff --git a/contracts/satoshi-bridge/src/rbf/withdraw.rs b/contracts/satoshi-bridge/src/rbf/withdraw.rs index 6079198..90097e6 100644 --- a/contracts/satoshi-bridge/src/rbf/withdraw.rs +++ b/contracts/satoshi-bridge/src/rbf/withdraw.rs @@ -56,12 +56,23 @@ impl Contract { original_tx_id: original_btc_pending_verify_id.clone(), }), ); - let (actual_received_amount, gas_fee) = - self.check_withdraw_rbf_psbt_valid(original_tx_btc_pending_info, &withdraw_rbf_psbt, 0); + + let full_subsidy_amount = self + .internal_unwrap_btc_pending_info(&original_btc_pending_verify_id) + .get_subsidize_amount(); + btc_pending_info.transfer_amount += full_subsidy_amount; + + let (actual_received_amount, gas_fee) = self.check_withdraw_rbf_psbt_valid( + original_tx_btc_pending_info, + &withdraw_rbf_psbt, + full_subsidy_amount, + ); + btc_pending_info.gas_fee = gas_fee; btc_pending_info.actual_received_amount = actual_received_amount; btc_pending_info.burn_amount = actual_received_amount + gas_fee; - + Self::check_withdraw_chain_specific(original_tx_btc_pending_info, gas_fee); + self.internal_unwrap_mut_btc_pending_info(&original_btc_pending_verify_id) .update_max_gas_fee(gas_fee); self.set_rbf_pending_info( From e8dad6a7a4dd1466d0ca20403fa1f909da4c822c Mon Sep 17 00:00:00 2001 From: Olga Kunyavskaya Date: Mon, 20 Oct 2025 16:16:51 +0100 Subject: [PATCH 9/9] fix: enable rbf in rbf --- contracts/satoshi-bridge/src/bitcoin_utils/psbt_wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/satoshi-bridge/src/bitcoin_utils/psbt_wrapper.rs b/contracts/satoshi-bridge/src/bitcoin_utils/psbt_wrapper.rs index cea9346..436c004 100644 --- a/contracts/satoshi-bridge/src/bitcoin_utils/psbt_wrapper.rs +++ b/contracts/satoshi-bridge/src/bitcoin_utils/psbt_wrapper.rs @@ -42,7 +42,7 @@ impl PsbtWrapper { original_psbt: crate::psbt_wrapper::PsbtWrapper, output: Vec, ) -> Self { - let sequence = bitcoin::Sequence::MAX; + let sequence = bitcoin::Sequence::ENABLE_RBF_NO_LOCKTIME; let transaction = BtcTransaction { version: Version::TWO,