#wallet #command-line-tool #serial #kit #change #bash-completion #usr-share #jq

bin+lib lwk_cli

Liquid Wallet Kit - Command line interface to interact with the RPC server

15 breaking releases

new 0.17.0 Apr 29, 2026
0.16.0 Mar 23, 2026
0.15.0 Feb 18, 2026
0.13.0 Dec 11, 2025
0.3.0 Mar 20, 2024

#4 in #usr-share

MIT OR BSD-2-Clause

56KB
1K SLoC

CLI

Building the needed executable requires rust:

$ git clone git@github.com:Blockstream/lwk.git
$ cd lwk
$ cargo install --path ./lwk_cli

If you want to enable Jade over serial build with

$ cargo install --path ./lwk_cli --features serial

Help shows available commands:

$ lwk_cli --help

Install bash completion with:

$ lwk_cli generate-completion bash | jq -r . | sudo tee /usr/share/bash-completion/completions/lwk_cli

Other shells are available: bash, elvish, fish, powershell, zsh. The destination file path /usr/share/bash-completion/completions/cli may change according to your distro.

Server

Start

$ lwk_cli server start

Or with more logs:

$ RUST_LOG=debug lwk_cli server start

Start the server in background and have logs on file

$ lwk_cli server start 2>debug.log &

Stop

If not in background hit ctrl-c in the terminal where it started or in another shell:

$ lwk_cli server stop

Another way to terminate a server started in background is to type fg to bring the background process in the foreground and then hit ctrl-c

Client

Every command requires the server running.

Generate a software signer ("stateless" request)

$ lwk_cli signer generate

is equivalent to:

$ curl --header "Content-Type: application/json" --request POST --data '{"method":"signer_generate","params":[],"id":1,"jsonrpc":"2.0"}' http://localhost:32111 -s

To see RPC data exchanged via the cli commands enable app log tracing eg:

$ RUST_LOG=app=trace cargo run -- wallet balance --wallet ciao
...
2023-11-28T09:36:18.696846Z TRACE app::client: ---> {"method":"balance","params":{"name":"ciao"},"id":2,"jsonrpc":"2.0"}
2023-11-28T09:36:18.697675Z TRACE app::client: <--- {"result":null,"error":{"code":-32008,"message":"Wallet 'ciao' does not exist","data":{"name":"ciao"}},"id":2,"jsonrpc":"2.0"}
{
  "code": -32008,
  "data": {
    "name": "ciao"
  },
  "message": "Wallet 'ciao' does not exist"
}

Load a wallet and request a balance ("stateful" request)

$ lwk_cli wallet load --wallet custody -d "ct(L3jXxwef3fpB7hcrFozcWgHeJCPSAFiZ1Ji2YJMPxceaGvy3PC1q,elwpkh(tpubD6NzVbkrYhZ4Was8nwnZi7eiWUNJq2LFpPSCMQLioUfUtT1e72GkRbmVeRAZc26j5MRUz2hRLsaVHJfs6L7ppNfLUrm9btQTuaEsLrT7D87/*))#lrwadl63"
$ lwk_cli wallet balance --wallet custody

is equivalent to:

$ curl --header "Content-Type: application/json" --request POST --data '{"method":"wallet_load","params":{"descriptor":"ct(L3jXxwef3fpB7hcrFozcWgHeJCPSAFiZ1Ji2YJMPxceaGvy3PC1q,elwpkh(tpubD6NzVbkrYhZ4Was8nwnZi7eiWUNJq2LFpPSCMQLioUfUtT1e72GkRbmVeRAZc26j5MRUz2hRLsaVHJfs6L7ppNfLUrm9btQTuaEsLrT7D87/*))#lrwadl63", "name": "custody"},"id":1,"jsonrpc":"2.0"}' http://localhost:32111 -s

$ curl --header "Content-Type: application/json" --request POST --data '{"method":"balance","params":{"name":"custody"},"id":1,"jsonrpc":"2.0"}' http://localhost:32111 -s | jq .result

Request an address:

$ lwk_cli wallet address --wallet custody
$ lwk_cli wallet address --wallet custody --index 4

An error test case:

$ curl --header "Content-Type: application/json" --request POST --data '{"method":"wallet_load","params":{"desc":"fake"},"id":1,"jsonrpc":"2.0"}' http://localhost:32111 -s | jq
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32603,
    "message": "Serde JSON Error: missing field `descriptor`",
    "data": null
  }
}

Create a singlesig wallet

First start the server

lwk_cli --network testnet server start
$ MNEMONIC=$(lwk_cli signer generate | jq -r .mnemonic)
$ lwk_cli signer load-software --persist true --mnemonic "$MNEMONIC" --signer s1
$ DESCRIPTOR=$(lwk_cli signer singlesig-desc --signer s1 --descriptor-blinding-key slip77 --kind wpkh | jq -r .descriptor)
$ lwk_cli wallet load --wallet w1 -d "$DESCRIPTOR"
$ lwk_cli wallet address --wallet w1

Send some lbtc to the address

$ lwk_cli wallet balance --wallet w1

Should show a balance

Creating a transaction, signing and broadcasting

You must have a loaded singlesig wallet w1, with the corresponding signer w1 as created in the previous step. Must also have funds in the wallet.

$ UNSIGNED_PSET=$(lwk_cli wallet send --wallet w1 --recipient tlq1qqwe0a3dp3hce866snkll5vq244n47ph5zy2xr330uc8wkrvc0whwsvw4w67xksmfyxwqdyrykp0tsxzsm24mqm994pfy4f6lg:1000:144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49 | jq -r .pset)

Creates an unsigned PSET sending 1000 satoshi of liquid btc (144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49 is the policy asset in testnet) to the address tlq1qqwe0a3dp3hce866snkll5vq244n47ph5zy2xr330uc8wkrvc0whwsvw4w67xksmfyxwqdyrykp0tsxzsm24mqm994pfy4f6lg

Sign the pset

$ SIGNED_PSET=$(lwk_cli signer sign --signer s1 --pset $UNSIGNED_PSET | jq -r .pset)

Broadcast it. Remove --dry-run to effectively broadcast live, otherwise only partial checks on the transactions finalization are made (for example it's not checked inputs are unspent)

$ lwk_cli wallet broadcast --dry-run --wallet w1 --pset $SIGNED_PSET)

Dependencies

~35–55MB
~794K SLoC