Skip to content

Conversation

@biryukovmaxim
Copy link
Collaborator

@biryukovmaxim biryukovmaxim commented Dec 19, 2025

Implement Rust wallet daemon inspired by kaspawalletd

Summary

This PR introduces a Rust-based gRPC wallet daemon as a replacement for the existing Go (kaspawalletd) implementation.

The primary goal is to provide a fully Rust-native wallet daemon that integrates cleanly with the existing Rust wallet framework and build system.

At this stage, the implementation is feature-complete enough for testing and review.


What’s included

  • New kaspa-wallet-daemon binary implemented in Rust
  • gRPC service definitions and server implementation (wallet/grpc/core, wallet/grpc/server)
  • Integration with kaspa-wallet-core
  • CI and release pipeline updates:
  • Build and package kaspa-wallet-daemon
  • Compatibility with existing Go-based gRPC wallet clients
  • Integration tests validated against an external Go test suite

Current limitations

Due to limitations in the current Rust wallet framework, the following are not yet supported:

  • ECDSA signing
  • Multisig
  • Fee bumping (RBF)

These are explicitly left out for now and will be addressed in follow-up work once the wallet framework supports them properly.


How to test

1. Run a Kaspa node (testnet)

cargo run --release --bin kaspad -- \
  --testnet \
  --utxoindex \
  --rpclisten-borsh=default \
  --rpclisten-json=default \
  --loglevel=kaspa_grpc_server=warn

2. Run the Rust wallet daemon

cargo run --package kaspa-wallet-daemon --bin kaspa-wallet-daemon -- \
--password "123" \
--name kaspa \
--network-id "testnet-10"

3. Integration tests (Go)

We validated the gRPC handlers using an external Go test suite:

https://github.com/childhoodisend/rusty-kaspa-wallet-test

aspect and others added 30 commits September 2, 2024 16:02
@biryukovmaxim biryukovmaxim marked this pull request as ready for review December 19, 2025 11:49
@biryukovmaxim biryukovmaxim requested review from IzioDev and aspect and removed request for IzioDev December 19, 2025 11:49
source musl-toolchain/build.sh
# Build for musl
cargo --verbose build --bin kaspad --bin rothschild --bin kaspa-wallet --release --target x86_64-unknown-linux-musl
cargo --verbose build --bin kaspad --bin rothschild --bin kaspa-wallet-daemon --release --target x86_64-unknown-linux-musl
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the introduced deamon needs to also be built, it should be a new binary, probably not replacing the kaspa-wallet (cli)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wallet/native binary just reruns kaspa-cli.. there's no point to have such wrapper

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let result = kaspa_cli(TerminalOptions::new().with_prompt("$ "), None).await;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should Kaspa users (ex: me as a user) run the CLI without the codebase providing an entry point? (and potentially a built binary: cli.exe)

I guess it would require me to compile locally after having re-added such an entry-point file, correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kaspa-cli is a binary. You can either run or build it. I don't see any difference

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, got it now, thanks.

So I think we should introduce kaspa-cli now, so that its build is being tested in ci, and its builds still are published as release assets, what do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense. I thought it was already there

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment: kaspa-wallet-daemon should be consider an addition, not a replacement to the already existing kaspa-wallet (cli)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let result = kaspa_cli(TerminalOptions::new().with_prompt("$ "), None).await;

the crate was deleteed by @KaffinPX since its existence doesnt make sense

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 788 to 799
// update address manager with the last used index
if update_address_indexes {
receive_address_manager.set_index(last_receive_address_index)?;
change_address_manager.set_index(last_change_address_index)?;

let metadata = self.metadata()?.expect("derivation accounts must provide metadata");
let store = self.wallet().store().as_account_store()?;
store.update_metadata(vec![metadata]).await?;
self.clone().scan(None, None).await?;
self.wallet().notify(Events::AccountUpdate { account_descriptor: self.descriptor()? }).await?;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to be a conflict handling artifact, this block is duplicated with the one previously in the code (line 777)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command::new("kaspawalletd")
.about(format!("{} (kaspawalletd) v{}", env!("CARGO_PKG_DESCRIPTION"), version()))
.version(env!("CARGO_PKG_VERSION"))
.arg(Arg::new("password").long("password").short('p').value_name("password").help("Path of password file"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add .required(true), otherwise the program panic if no password provided (non informative output)

replace "Path of password file" with "Password" or "Wallet secret", in implementation it's not used to provide a password file

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.long("name")
.short('n')
.value_name("name")
.value_parser(clap::value_parser!(String))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is value parser on String useful here?
According to clap documentation, String is the default value parser

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are other cases where the same pattern is used. I don't see any issues with explicitly passed parser

Arg::new("name")
.long("name")
.short('n')
.value_name("name")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value_name is useful to display a more informative help panel (it has no other use).

example before:
Image

example after:

Image

default value is arg name, so no need to re-define without a different value

kaspa_core::log::init_logger(None, "");
let args = Args::parse();

let wallet = Arc::new(Wallet::try_new(Wallet::local_store()?, Some(Resolver::default()), None)?);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Golang daemon allowed user to select a specific wallet file (by explicit path)

@coderofstuff
Copy link
Collaborator

@biryukovmaxim Please add a description to this PR

@biryukovmaxim
Copy link
Collaborator Author

@biryukovmaxim Please add a description to this PR

Done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.