Skip to content

Commit 158a32c

Browse files
authored
Merge pull request #1228 from halseth/fee-estimation-rpc
Fee estimation RPC
2 parents eb30870 + 54f1f32 commit 158a32c

File tree

11 files changed

+1181
-567
lines changed

11 files changed

+1181
-567
lines changed

cmd/lncli/commands.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,52 @@ func newAddress(ctx *cli.Context) error {
144144
return nil
145145
}
146146

147+
var estimateFeeCommand = cli.Command{
148+
Name: "estimatefee",
149+
Category: "On-chain",
150+
Usage: "Get fee estimates for sending bitcoin on-chain to multiple addresses.",
151+
ArgsUsage: "send-json-string [--conf_target=N]",
152+
Description: `
153+
Get fee estimates for sending a transaction paying the specified amount(s) to the passed address(es).
154+
155+
The send-json-string' param decodes addresses and the amount to send respectively in the following format:
156+
157+
'{"ExampleAddr": NumCoinsInSatoshis, "SecondAddr": NumCoins}'
158+
`,
159+
Flags: []cli.Flag{
160+
cli.Int64Flag{
161+
Name: "conf_target",
162+
Usage: "(optional) the number of blocks that the transaction *should* " +
163+
"confirm in",
164+
},
165+
},
166+
Action: actionDecorator(estimateFees),
167+
}
168+
169+
func estimateFees(ctx *cli.Context) error {
170+
var amountToAddr map[string]int64
171+
172+
jsonMap := ctx.Args().First()
173+
if err := json.Unmarshal([]byte(jsonMap), &amountToAddr); err != nil {
174+
return err
175+
}
176+
177+
ctxb := context.Background()
178+
client, cleanUp := getClient(ctx)
179+
defer cleanUp()
180+
181+
resp, err := client.EstimateFee(ctxb, &lnrpc.EstimateFeeRequest{
182+
AddrToAmount: amountToAddr,
183+
TargetConf: int32(ctx.Int64("conf_target")),
184+
})
185+
if err != nil {
186+
return err
187+
}
188+
189+
printRespJSON(resp)
190+
return nil
191+
}
192+
147193
var sendCoinsCommand = cli.Command{
148194
Name: "sendcoins",
149195
Category: "On-chain",

cmd/lncli/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ func main() {
257257
unlockCommand,
258258
changePasswordCommand,
259259
newAddressCommand,
260+
estimateFeeCommand,
260261
sendManyCommand,
261262
sendCoinsCommand,
262263
listUnspentCommand,

lnrpc/rpc.pb.go

Lines changed: 710 additions & 567 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lnrpc/rpc.pb.gw.go

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lnrpc/rpc.proto

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,16 @@ service Lightning {
220220
};
221221
}
222222

223+
/** lncli: `estimatefee`
224+
EstimateFee asks the chain backend to estimate the fee rate and total fees
225+
for a transaction that pays to multiple specified outputs.
226+
*/
227+
rpc EstimateFee (EstimateFeeRequest) returns (EstimateFeeResponse) {
228+
option (google.api.http) = {
229+
get: "/v1/transactions/fee"
230+
};
231+
}
232+
223233
/** lncli: `sendcoins`
224234
SendCoins executes a request to send coins to a particular address. Unlike
225235
SendMany, this RPC call only allows creating a single output at a time. If
@@ -839,6 +849,22 @@ message LightningAddress {
839849
string host = 2 [json_name = "host"];
840850
}
841851

852+
message EstimateFeeRequest {
853+
/// The map from addresses to amounts for the transaction.
854+
map<string, int64> AddrToAmount = 1;
855+
856+
/// The target number of blocks that this transaction should be confirmed by.
857+
int32 target_conf = 2;
858+
}
859+
860+
message EstimateFeeResponse {
861+
/// The total fee in satoshis.
862+
int64 fee_sat = 1 [json_name = "fee_sat"];
863+
864+
/// The fee rate in satoshi/byte.
865+
int64 feerate_sat_per_byte = 2 [json_name = "feerate_sat_per_byte"];
866+
}
867+
842868
message SendManyRequest {
843869
/// The map from addresses to amounts
844870
map<string, int64> AddrToAmount = 1;

lnrpc/rpc.swagger.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,33 @@
10441044
]
10451045
}
10461046
},
1047+
"/v1/transactions/fee": {
1048+
"get": {
1049+
"summary": "* lncli: `estimatefee`\nEstimateFee asks the chain backend to estimate the fee rate and total fees\nfor a transaction that pays to multiple specified outputs.",
1050+
"operationId": "EstimateFee",
1051+
"responses": {
1052+
"200": {
1053+
"description": "",
1054+
"schema": {
1055+
"$ref": "#/definitions/lnrpcEstimateFeeResponse"
1056+
}
1057+
}
1058+
},
1059+
"parameters": [
1060+
{
1061+
"name": "target_conf",
1062+
"description": "/ The target number of blocks that this transaction should be confirmed by.",
1063+
"in": "query",
1064+
"required": false,
1065+
"type": "integer",
1066+
"format": "int32"
1067+
}
1068+
],
1069+
"tags": [
1070+
"Lightning"
1071+
]
1072+
}
1073+
},
10471074
"/v1/unlockwallet": {
10481075
"post": {
10491076
"summary": "* lncli: `unlock`\nUnlockWallet is used at startup of lnd to provide a password to unlock\nthe wallet database.",
@@ -1747,6 +1774,21 @@
17471774
"lnrpcDisconnectPeerResponse": {
17481775
"type": "object"
17491776
},
1777+
"lnrpcEstimateFeeResponse": {
1778+
"type": "object",
1779+
"properties": {
1780+
"fee_sat": {
1781+
"type": "string",
1782+
"format": "int64",
1783+
"description": "/ The total fee in satoshis."
1784+
},
1785+
"feerate_sat_per_byte": {
1786+
"type": "string",
1787+
"format": "int64",
1788+
"description": "/ The fee rate in satoshi/byte."
1789+
}
1790+
}
1791+
},
17501792
"lnrpcFeeLimit": {
17511793
"type": "object",
17521794
"properties": {

lnwallet/btcwallet/btcwallet.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import (
1717
"github.com/btcsuite/btcwallet/chain"
1818
"github.com/btcsuite/btcwallet/waddrmgr"
1919
base "github.com/btcsuite/btcwallet/wallet"
20+
"github.com/btcsuite/btcwallet/wallet/txauthor"
21+
"github.com/btcsuite/btcwallet/wallet/txrules"
2022
"github.com/btcsuite/btcwallet/walletdb"
2123
"github.com/lightningnetwork/lnd/keychain"
2224
"github.com/lightningnetwork/lnd/lnwallet"
@@ -297,9 +299,45 @@ func (b *BtcWallet) SendOutputs(outputs []*wire.TxOut,
297299
// SendOutputs.
298300
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
299301

302+
// Sanity check outputs.
303+
if len(outputs) < 1 {
304+
return nil, lnwallet.ErrNoOutputs
305+
}
300306
return b.wallet.SendOutputs(outputs, defaultAccount, 1, feeSatPerKB)
301307
}
302308

309+
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
310+
// outputs. The transaction is not broadcasted to the network, but a new change
311+
// address might be created in the wallet database. In the case the wallet has
312+
// insufficient funds, or the outputs are non-standard, an error should be
313+
// returned. This method also takes the target fee expressed in sat/kw that
314+
// should be used when crafting the transaction.
315+
//
316+
// NOTE: The dryRun argument can be set true to create a tx that doesn't alter
317+
// the database. A tx created with this set to true SHOULD NOT be broadcasted.
318+
//
319+
// This is a part of the WalletController interface.
320+
func (b *BtcWallet) CreateSimpleTx(outputs []*wire.TxOut,
321+
feeRate lnwallet.SatPerKWeight, dryRun bool) (*txauthor.AuthoredTx, error) {
322+
323+
// The fee rate is passed in using units of sat/kw, so we'll convert
324+
// this to sat/KB as the CreateSimpleTx method requires this unit.
325+
feeSatPerKB := btcutil.Amount(feeRate.FeePerKVByte())
326+
327+
// Sanity check outputs.
328+
if len(outputs) < 1 {
329+
return nil, lnwallet.ErrNoOutputs
330+
}
331+
for _, output := range outputs {
332+
err := txrules.CheckOutput(output, feeSatPerKB)
333+
if err != nil {
334+
return nil, err
335+
}
336+
}
337+
338+
return b.wallet.CreateSimpleTx(defaultAccount, outputs, 1, feeSatPerKB, dryRun)
339+
}
340+
303341
// LockOutpoint marks an outpoint as locked meaning it will no longer be deemed
304342
// as eligible for coin selection. Locking outputs are utilized in order to
305343
// avoid race conditions when selecting inputs for usage when funding a

lnwallet/interface.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/btcsuite/btcd/chaincfg/chainhash"
1010
"github.com/btcsuite/btcd/wire"
1111
"github.com/btcsuite/btcutil"
12+
"github.com/btcsuite/btcwallet/wallet/txauthor"
1213
"github.com/lightningnetwork/lnd/lntypes"
1314
)
1415

@@ -48,6 +49,10 @@ var (
4849
ErrNotMine = errors.New("the passed output doesn't belong to the wallet")
4950
)
5051

52+
// ErrNoOutputs is returned if we try to create a transaction with no outputs
53+
// or send coins to a set of outputs that is empty.
54+
var ErrNoOutputs = errors.New("no outputs")
55+
5156
// Utxo is an unspent output denoted by its outpoint, and output value of the
5257
// original output.
5358
type Utxo struct {
@@ -169,6 +174,19 @@ type WalletController interface {
169174
SendOutputs(outputs []*wire.TxOut,
170175
feeRate SatPerKWeight) (*wire.MsgTx, error)
171176

177+
// CreateSimpleTx creates a Bitcoin transaction paying to the specified
178+
// outputs. The transaction is not broadcasted to the network. In the
179+
// case the wallet has insufficient funds, or the outputs are
180+
// non-standard, an error should be returned. This method also takes
181+
// the target fee expressed in sat/kw that should be used when crafting
182+
// the transaction.
183+
//
184+
// NOTE: The dryRun argument can be set true to create a tx that
185+
// doesn't alter the database. A tx created with this set to true
186+
// SHOULD NOT be broadcasted.
187+
CreateSimpleTx(outputs []*wire.TxOut, feeRate SatPerKWeight,
188+
dryRun bool) (*txauthor.AuthoredTx, error)
189+
172190
// ListUnspentWitness returns all unspent outputs which are version 0
173191
// witness programs. The 'minconfirms' and 'maxconfirms' parameters
174192
// indicate the minimum and maximum number of confirmations an output

0 commit comments

Comments
 (0)