Skip to content

Commit e4a5f71

Browse files
committed
feat: added bumpfee rpc
1 parent 657eebd commit e4a5f71

File tree

3 files changed

+113
-0
lines changed

3 files changed

+113
-0
lines changed

client/src/client.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,19 @@ pub trait RpcApi: Sized {
260260
self.call("addmultisigaddress", handle_defaults(&mut args, &[into_json("")?, null()]))
261261
}
262262

263+
fn bump_fee(
264+
&self,
265+
txid: &bitcoin::Txid,
266+
options: Option<&json::BumpFeeOptions>,
267+
) -> Result<json::BumpFeeResult> {
268+
let opts = match options {
269+
Some(options) => Some(options.to_serializable(self.version()?)),
270+
None => None,
271+
};
272+
let mut args = [into_json(txid)?, opt_into_json(opts)?];
273+
self.call("bumpfee", handle_defaults(&mut args, &[null()]))
274+
}
275+
263276
fn load_wallet(&self, wallet: &str) -> Result<json::LoadWalletResult> {
264277
self.call("loadwallet", &[wallet.into()])
265278
}

integration_test/src/main.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ fn main() {
172172
test_key_pool_refill(&cl);
173173
test_create_raw_transaction(&cl);
174174
test_fund_raw_transaction(&cl);
175+
test_bump_fee(&cl);
175176
test_test_mempool_accept(&cl);
176177
test_wallet_create_funded_psbt(&cl);
177178
test_wallet_process_psbt(&cl);
@@ -676,6 +677,27 @@ fn test_fund_raw_transaction(cl: &Client) {
676677
let _ = funded.transaction().unwrap();
677678
}
678679

680+
fn test_bump_fee(cl: &Client) {
681+
let addr = cl.get_new_address(None, None).unwrap();
682+
let txid = cl.send_to_address(&addr, btc(1), None, None, None, Some(true), None, None).unwrap();
683+
684+
// bump without explicit fee rate
685+
let bump_fee_result_1 = cl.bump_fee(&txid, None).unwrap();
686+
assert!(bump_fee_result_1.origfee < bump_fee_result_1.fee);
687+
688+
// bump with explicit fee rate
689+
let amount_per_vbyte = Amount::from_sat(500);
690+
let new_fee_rate = json::FeeRate::new(amount_per_vbyte);
691+
let options = json::BumpFeeOptions {
692+
fee_rate: Some(new_fee_rate),
693+
replaceable: Some(true),
694+
..Default::default()
695+
};
696+
let bump_fee_result_2 = cl.bump_fee(&bump_fee_result_1.txid.unwrap(), Some(&options)).unwrap();
697+
let vbytes = cl.get_mempool_entry(&bump_fee_result_2.txid.unwrap()).unwrap().vsize;
698+
assert_eq!(bump_fee_result_2.fee, amount_per_vbyte * vbytes);
699+
}
700+
679701
fn test_test_mempool_accept(cl: &Client) {
680702
let options = json::ListUnspentQueryOptions {
681703
minimum_amount: Some(btc(2)),

json/src/lib.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1812,6 +1812,84 @@ pub struct FundRawTransactionResult {
18121812
pub change_position: i32,
18131813
}
18141814

1815+
#[derive(Clone, PartialEq, Eq, Debug, Default)]
1816+
pub struct BumpFeeOptions {
1817+
pub conf_target: Option<u16>,
1818+
/// Specify a fee rate instead of relying on the built-in fee estimator.
1819+
pub fee_rate: Option<FeeRate>,
1820+
/// Whether this transaction could be replaced due to BIP125 (replace-by-fee)
1821+
pub replaceable: Option<bool>,
1822+
/// The fee estimate mode
1823+
pub estimate_mode: Option<EstimateMode>,
1824+
}
1825+
1826+
impl BumpFeeOptions {
1827+
pub fn to_serializable(&self, version: usize) -> SerializableBumpFeeOptions {
1828+
let fee_rate = self.fee_rate.map(|x| {
1829+
if version < 210000 {
1830+
x.btc_per_kvbyte()
1831+
} else {
1832+
x.sat_per_vbyte()
1833+
}
1834+
});
1835+
1836+
SerializableBumpFeeOptions {
1837+
fee_rate,
1838+
conf_target: self.conf_target,
1839+
replaceable: self.replaceable,
1840+
estimate_mode: self.estimate_mode,
1841+
}
1842+
}
1843+
}
1844+
1845+
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
1846+
pub struct FeeRate(Amount);
1847+
1848+
impl FeeRate {
1849+
pub fn new(amount_per_vbyte: Amount) -> Self {
1850+
Self(amount_per_vbyte)
1851+
}
1852+
pub fn sat_per_vbyte(&self) -> f64 {
1853+
// multiply by the number of decimals to get sat
1854+
self.0.as_sat() as f64
1855+
}
1856+
pub fn btc_per_kvbyte(&self) -> f64 {
1857+
// divide by 10^8 to get btc/vbyte, then multiply by 10^3 to get btc/kbyte
1858+
self.0.as_sat() as f64 / 100_000.0
1859+
}
1860+
}
1861+
1862+
#[derive(Serialize, Clone, PartialEq, Debug, Default)]
1863+
#[serde(rename_all = "camelCase")]
1864+
pub struct SerializableBumpFeeOptions {
1865+
#[serde(rename = "conf_target", skip_serializing_if = "Option::is_none")]
1866+
pub conf_target: Option<u16>,
1867+
/// Specify a fee rate instead of relying on the built-in fee estimator.
1868+
#[serde(rename = "fee_rate")]
1869+
pub fee_rate: Option<f64>,
1870+
/// Whether this transaction could be replaced due to BIP125 (replace-by-fee)
1871+
#[serde(skip_serializing_if = "Option::is_none")]
1872+
pub replaceable: Option<bool>,
1873+
/// The fee estimate mode
1874+
#[serde(rename = "estimate_mode", skip_serializing_if = "Option::is_none")]
1875+
pub estimate_mode: Option<EstimateMode>,
1876+
}
1877+
1878+
#[derive(Deserialize, Clone, PartialEq, Eq, Debug)]
1879+
#[serde(rename_all = "camelCase")]
1880+
pub struct BumpFeeResult {
1881+
/// The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled.
1882+
pub psbt: Option<String>,
1883+
/// The id of the new transaction. Only returned when wallet private keys are enabled.
1884+
pub txid: Option<bitcoin::Txid>,
1885+
#[serde(with = "bitcoin::util::amount::serde::as_btc")]
1886+
pub origfee: Amount,
1887+
#[serde(with = "bitcoin::util::amount::serde::as_btc")]
1888+
pub fee: Amount,
1889+
/// Errors encountered during processing.
1890+
pub errors: Vec<String>,
1891+
}
1892+
18151893
#[derive(Deserialize, Clone, PartialEq, Eq, Debug)]
18161894
pub struct GetBalancesResultEntry {
18171895
#[serde(with = "bitcoin::util::amount::serde::as_btc")]

0 commit comments

Comments
 (0)