@@ -3,9 +3,11 @@ package btcrpc
3
3
import (
4
4
"fmt"
5
5
"strconv"
6
+ "strings"
6
7
7
8
"github.com/btcsuite/btcd/btcutil"
8
9
"github.com/btcsuite/btcd/chaincfg"
10
+ "github.com/btcsuite/btcd/wire"
9
11
10
12
"github.com/dwarvesf/icy-backend/internal/btcrpc/blockstream"
11
13
"github.com/dwarvesf/icy-backend/internal/model"
@@ -86,8 +88,8 @@ func (b *BtcRpc) Send(receiverAddressStr string, amount *model.Web3BigInt) (stri
86
88
return "" , err
87
89
}
88
90
89
- // Serialize & broadcast tx
90
- txID , err := b .broadcast (tx )
91
+ // Serialize & broadcast tx with potential fee adjustment
92
+ txID , err := b .broadcastWithFeeAdjustment (tx , selectedUTXOs , receiverAddress , senderAddress , amountToSend , changeAmount )
91
93
if err != nil {
92
94
b .logger .Error ("[btcrpc.Send][broadcast]" , map [string ]string {
93
95
"error" : err .Error (),
@@ -98,6 +100,104 @@ func (b *BtcRpc) Send(receiverAddressStr string, amount *model.Web3BigInt) (stri
98
100
return txID , nil
99
101
}
100
102
103
+ // broadcastWithFeeAdjustment attempts to broadcast the transaction,
104
+ // and if it fails due to minimum relay fee, attempts to increase the fee by 5%
105
+ func (b * BtcRpc ) broadcastWithFeeAdjustment (
106
+ tx * wire.MsgTx ,
107
+ selectedUTXOs []blockstream.UTXO ,
108
+ receiverAddress btcutil.Address ,
109
+ senderAddress * btcutil.AddressWitnessPubKeyHash ,
110
+ amountToSend , changeAmount int64 ,
111
+ ) (string , error ) {
112
+ // First attempt to broadcast
113
+ txID , err := b .broadcast (tx )
114
+ if err == nil {
115
+ return txID , nil
116
+ }
117
+
118
+ // Check if the error is specifically about minimum relay fee
119
+ broadcastErr , ok := err .(* blockstream.BroadcastTxError )
120
+ if ok && strings .Contains (broadcastErr .Error (), "min relay fee not met" ) {
121
+ b .logger .Info ("[btcrpc.Send][FeeAdjustment]" , map [string ]string {
122
+ "message" : "Attempting to adjust transaction fee" ,
123
+ })
124
+
125
+ // Use the minimum fee from the error if available
126
+ var adjustedFee , currentFee int64
127
+ if broadcastErr .MinFee > 0 {
128
+ // Use the minimum fee from the error
129
+ adjustedFee = broadcastErr .MinFee
130
+
131
+ // Fallback to calculating current fee if no minimum fee in error
132
+ feeRates , err := b .blockstream .EstimateFees ()
133
+ if err != nil {
134
+ return "" , fmt .Errorf ("failed to get fee rates for adjustment: %v" , err )
135
+ }
136
+
137
+ currentFee , err = b .calculateTxFee (feeRates , len (selectedUTXOs ), 2 , 6 )
138
+ if err != nil {
139
+ return "" , fmt .Errorf ("failed to calculate current fee: %v" , err )
140
+ }
141
+
142
+ if adjustedFee > int64 (float64 (currentFee )* 1.05 ) {
143
+ return "" , fmt .Errorf ("fee too high to adjust, adjusted fee: %d, current fee: %d" , adjustedFee , currentFee )
144
+ }
145
+ } else {
146
+ // Fallback to calculating fee if no minimum fee in error
147
+ feeRates , err := b .blockstream .EstimateFees ()
148
+ if err != nil {
149
+ return "" , fmt .Errorf ("failed to get fee rates for adjustment: %v" , err )
150
+ }
151
+
152
+ currentFee , err = b .calculateTxFee (feeRates , len (selectedUTXOs ), 2 , 6 )
153
+ if err != nil {
154
+ return "" , fmt .Errorf ("failed to calculate current fee: %v" , err )
155
+ }
156
+
157
+ // Adjust fee to be 5% higher
158
+ adjustedFee = int64 (float64 (currentFee ) * 1.05 )
159
+ }
160
+
161
+ b .logger .Info ("[btcrpc.Send][FeeAdjustment]" , map [string ]string {
162
+ "currentFee" : strconv .FormatInt (currentFee , 10 ),
163
+ "adjustedFee" : strconv .FormatInt (adjustedFee , 10 ),
164
+ "changeAmount" : strconv .FormatInt (changeAmount , 10 ),
165
+ "amountToSend" : strconv .FormatInt (amountToSend , 10 ),
166
+ })
167
+
168
+ // Calculate adjusted change amount
169
+ adjustedChangeAmount := changeAmount - (adjustedFee - currentFee )
170
+
171
+ // If adjusted change amount becomes negative, we can't proceed
172
+ if adjustedChangeAmount < 0 {
173
+ return "" , fmt .Errorf ("insufficient funds to adjust transaction fee" )
174
+ }
175
+
176
+ // Recreate transaction with adjusted fee
177
+ adjustedTx , err := b .prepareTx (selectedUTXOs , receiverAddress , senderAddress , amountToSend , adjustedChangeAmount )
178
+ if err != nil {
179
+ return "" , fmt .Errorf ("failed to prepare adjusted transaction: %v" , err )
180
+ }
181
+
182
+ // Re-sign the transaction
183
+ privKey , _ , err := b .getSelfPrivKeyAndAddress (b .appConfig .Bitcoin .WalletWIF )
184
+ if err != nil {
185
+ return "" , fmt .Errorf ("failed to get private key for re-signing: %v" , err )
186
+ }
187
+
188
+ err = b .sign (adjustedTx , privKey , senderAddress , selectedUTXOs )
189
+ if err != nil {
190
+ return "" , fmt .Errorf ("failed to sign adjusted transaction: %v" , err )
191
+ }
192
+
193
+ // Attempt to broadcast adjusted transaction
194
+ return b .broadcast (adjustedTx )
195
+ }
196
+
197
+ // If it's a different error, return the original error
198
+ return "" , err
199
+ }
200
+
101
201
func (b * BtcRpc ) CurrentBalance () (* model.Web3BigInt , error ) {
102
202
balance , err := b .blockstream .GetBTCBalance (b .appConfig .Blockchain .BTCTreasuryAddress )
103
203
if err != nil {
0 commit comments