mirror of https://github.com/oxen-io/oxen-core.git
wallet3 cli
This introduces wallet3 cli to the codebase, build using python and wraps the c++ wallet3 core using pybind.
This commit is contained in:
parent
f60d722e4d
commit
172093fbbf
|
@ -43,3 +43,7 @@
|
|||
[submodule "external/oxen-mq"]
|
||||
path = external/oxen-mq
|
||||
url = https://github.com/oxen-io/oxen-mq.git
|
||||
[submodule "external/pybind11"]
|
||||
path = external/pybind11
|
||||
url = https://github.com/pybind/pybind11
|
||||
branch = stable
|
||||
|
|
|
@ -299,6 +299,8 @@ if(NOT MANUAL_SUBMODULES)
|
|||
endif()
|
||||
check_submodule(external/uWebSockets uSockets)
|
||||
check_submodule(external/ghc-filesystem)
|
||||
check_submodule(external/SQLiteCpp)
|
||||
check_submodule(external/pybind11)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -967,3 +969,5 @@ add_custom_target(create_zip
|
|||
DEPENDS ${oxen_exec_tgts})
|
||||
|
||||
add_custom_target(create_archive DEPENDS ${default_archive})
|
||||
|
||||
add_subdirectory(pybind)
|
||||
|
|
|
@ -67,5 +67,4 @@ target_link_libraries(epee
|
|||
PRIVATE
|
||||
filesystem
|
||||
Boost::thread
|
||||
date::date
|
||||
extra)
|
||||
|
|
|
@ -169,3 +169,6 @@ set(SQLITECPP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
|||
add_subdirectory(SQLiteCpp)
|
||||
|
||||
add_subdirectory(Catch2)
|
||||
|
||||
add_subdirectory(pybind11 EXCLUDE_FROM_ALL)
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 0ba639d6177659c5dc2955ac06ad7b5b0d22e05c
|
|
@ -0,0 +1,6 @@
|
|||
*~
|
||||
*\#*
|
||||
build/
|
||||
__pycache__
|
||||
dist/
|
||||
*.egg-info
|
|
@ -0,0 +1,16 @@
|
|||
pybind11_add_module(pywallet3 MODULE
|
||||
module.cpp
|
||||
wallet/daemon_comms_config.cpp
|
||||
wallet/rpc_config.cpp
|
||||
wallet/keyring.cpp
|
||||
wallet/keyring_manager.cpp
|
||||
wallet/wallet.cpp
|
||||
wallet/wallet_config.cpp
|
||||
)
|
||||
target_link_libraries(pywallet3 PUBLIC wallet3)
|
||||
target_include_directories(pywallet3 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
file(GENERATE
|
||||
OUTPUT setup.py
|
||||
INPUT setup.py.in)
|
||||
configure_file(pyproject.toml pyproject.toml COPYONLY)
|
|
@ -0,0 +1,16 @@
|
|||
# PyWallet3
|
||||
|
||||
Python interface to oxen wallet3
|
||||
|
||||
## building
|
||||
First build the static oxen core
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DBUILD_STATIC_DEPS=ON ..
|
||||
make wallet3_merged -j16
|
||||
```
|
||||
Then, still in the build directory, install via pip using:
|
||||
```
|
||||
pip3 install ./pybind
|
||||
```
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/stl.h>
|
||||
#include <pybind11/functional.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
void
|
||||
Wallet_Init(py::module& mod);
|
||||
|
||||
void
|
||||
Keyring_Init(py::module& mod);
|
||||
|
||||
void
|
||||
KeyringManager_Init(py::module& mod);
|
||||
|
||||
void
|
||||
DaemonCommsConfig_Init(py::module& mod);
|
||||
|
||||
void
|
||||
RPCConfig_Init(py::module& mod);
|
||||
|
||||
void
|
||||
WalletConfig_Init(py::module& mod);
|
||||
} // namespace wallet
|
|
@ -0,0 +1,11 @@
|
|||
#include "common.hpp"
|
||||
|
||||
PYBIND11_MODULE(pywallet3, m)
|
||||
{
|
||||
wallet::Wallet_Init(m);
|
||||
wallet::Keyring_Init(m);
|
||||
wallet::KeyringManager_Init(m);
|
||||
wallet::DaemonCommsConfig_Init(m);
|
||||
wallet::RPCConfig_Init(m);
|
||||
wallet::WalletConfig_Init(m);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"wheel",
|
||||
"pybind11>=2.6.0",
|
||||
]
|
||||
|
||||
build-backend = "setuptools.build_meta"
|
|
@ -0,0 +1,38 @@
|
|||
from setuptools import setup
|
||||
|
||||
# Available at setup time due to pyproject.toml
|
||||
from pybind11.setup_helpers import Pybind11Extension, build_ext
|
||||
|
||||
__version__ = "0.0.1.dev0"
|
||||
|
||||
pywallet3_sources = ['$<TARGET_PROPERTY:pywallet3,SOURCE_DIR>/' + src for src in (
|
||||
'$<JOIN:$<TARGET_PROPERTY:pywallet3,SOURCES>,',
|
||||
'>')]
|
||||
pywallet3_include_dirs = [
|
||||
'$<JOIN:$<TARGET_PROPERTY:wallet3,INCLUDE_DIRECTORIES>,',
|
||||
'>']
|
||||
|
||||
# Note:
|
||||
# Sort input source files if you glob sources to ensure bit-for-bit
|
||||
# reproducible builds (https://github.com/pybind/python_example/pull/53)
|
||||
|
||||
ext_modules = [Pybind11Extension(
|
||||
"pywallet3",
|
||||
pywallet3_sources,
|
||||
cxx_std=17,
|
||||
include_dirs=pywallet3_include_dirs,
|
||||
extra_objects=['$<TARGET_PROPERTY:wallet3,BINARY_DIR>/libwallet3_merged.a'],
|
||||
),
|
||||
]
|
||||
|
||||
setup(
|
||||
name="pywallet3",
|
||||
version=__version__,
|
||||
author="Sean Darcy",
|
||||
author_email="sean@oxen.io",
|
||||
url="https://github.com/oxen-io/oxen-core",
|
||||
description="Python wrapper for oxen wallet3 library",
|
||||
long_description="",
|
||||
ext_modules=ext_modules,
|
||||
zip_safe=False,
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
#include "../common.hpp"
|
||||
#include "wallet3/config/config.hpp"
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
void
|
||||
DaemonCommsConfig_Init(py::module& mod)
|
||||
{
|
||||
py::class_<DaemonCommsConfig, std::shared_ptr<DaemonCommsConfig>>(mod, "DaemonCommsConfig")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("address", &DaemonCommsConfig::address);
|
||||
}
|
||||
|
||||
} // namespace wallet
|
|
@ -0,0 +1,31 @@
|
|||
#include "../common.hpp"
|
||||
#include "wallet3/keyring.hpp"
|
||||
|
||||
#include <crypto/crypto.h>
|
||||
#include <common/hex.h>
|
||||
#include <cryptonote_basic/cryptonote_basic.h>
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
void
|
||||
Keyring_Init(py::module& mod)
|
||||
{
|
||||
py::class_<Keyring, std::shared_ptr<Keyring>>(mod, "Keyring")
|
||||
.def(py::init([]( std::string ssk, std::string spk, std::string vsk, std::string vpk, std::string nettype) {
|
||||
auto type = cryptonote::network_type::MAINNET;
|
||||
if (nettype == "testnet") type = cryptonote::network_type::TESTNET;
|
||||
else if (nettype == "devnet") type = cryptonote::network_type::DEVNET;
|
||||
crypto::secret_key spend_priv;
|
||||
crypto::public_key spend_pub;
|
||||
crypto::secret_key view_priv;
|
||||
crypto::public_key view_pub;
|
||||
tools::hex_to_type<crypto::secret_key>(ssk, spend_priv);
|
||||
tools::hex_to_type<crypto::public_key>(spk, spend_pub);
|
||||
tools::hex_to_type<crypto::secret_key>(vsk, view_priv);
|
||||
tools::hex_to_type<crypto::public_key>(vpk, view_pub);
|
||||
return Keyring(spend_priv, spend_pub, view_priv, view_pub, std::move(type)); }))
|
||||
|
||||
.def("get_main_address", &Keyring::get_main_address);
|
||||
}
|
||||
|
||||
} // namespace wallet
|
|
@ -0,0 +1,22 @@
|
|||
#include "../common.hpp"
|
||||
#include "wallet3/keyring_manager.hpp"
|
||||
|
||||
#include <crypto/crypto.h>
|
||||
#include <cryptonote_basic/cryptonote_basic.h>
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
void
|
||||
KeyringManager_Init(py::module& mod)
|
||||
{
|
||||
py::class_<KeyringManager, std::shared_ptr<KeyringManager>>(mod, "KeyringManager")
|
||||
.def(py::init([](std::string nettype) {
|
||||
auto type = cryptonote::network_type::MAINNET;
|
||||
if (nettype == "testnet") type = cryptonote::network_type::TESTNET;
|
||||
else if (nettype == "devnet") type = cryptonote::network_type::DEVNET;
|
||||
return KeyringManager(std::move(type)); }))
|
||||
|
||||
.def("generate_keyring_from_electrum_seed", &KeyringManager::generate_keyring_from_electrum_seed);
|
||||
}
|
||||
|
||||
} // namespace wallet
|
|
@ -0,0 +1,14 @@
|
|||
#include "../common.hpp"
|
||||
#include "wallet3/config/config.hpp"
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
void
|
||||
RPCConfig_Init(py::module& mod)
|
||||
{
|
||||
py::class_<rpc::Config, std::shared_ptr<rpc::Config>>(mod, "RPCConfig")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("sockname", &rpc::Config::sockname);
|
||||
}
|
||||
|
||||
} // namespace wallet
|
|
@ -0,0 +1,27 @@
|
|||
#include "../common.hpp"
|
||||
|
||||
#include <wallet3/wallet.hpp>
|
||||
#include <wallet3/default_daemon_comms.hpp>
|
||||
#include <wallet3/keyring.hpp>
|
||||
#include <wallet3/config/config.hpp>
|
||||
#include <oxenmq/oxenmq.h>
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
void
|
||||
Wallet_Init(py::module& mod)
|
||||
{
|
||||
py::class_<Wallet, std::shared_ptr<Wallet>>(mod, "Wallet")
|
||||
.def(py::init([](const std::string& wallet_name, std::shared_ptr<Keyring> keyring, Config config) {
|
||||
auto& comms_config = config.daemon;
|
||||
auto& omq_rpc_config = config.omq_rpc;
|
||||
auto oxenmq = std::make_shared<oxenmq::OxenMQ>();
|
||||
auto comms = std::make_shared<DefaultDaemonComms>(std::move(oxenmq), comms_config);
|
||||
return Wallet::create(oxenmq, std::move(keyring), nullptr, std::move(comms), wallet_name + ".sqlite", "", std::move(config));
|
||||
}))
|
||||
.def("get_balance", &Wallet::get_balance)
|
||||
.def("get_unlocked_balance", &Wallet::get_unlocked_balance)
|
||||
.def("deregister", &Wallet::deregister);
|
||||
}
|
||||
|
||||
} // namespace wallet
|
|
@ -0,0 +1,15 @@
|
|||
#include "../common.hpp"
|
||||
#include "wallet3/config/config.hpp"
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
void
|
||||
WalletConfig_Init(py::module& mod)
|
||||
{
|
||||
py::class_<Config, std::shared_ptr<Config>>(mod, "WalletConfig")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("daemon", &Config::daemon)
|
||||
.def_readwrite("omq_rpc", &Config::omq_rpc);
|
||||
}
|
||||
|
||||
} // namespace wallet
|
|
@ -125,3 +125,8 @@ else()
|
|||
endif()
|
||||
|
||||
add_library(version "${CMAKE_CURRENT_BINARY_DIR}/version.cpp")
|
||||
|
||||
install(
|
||||
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/"
|
||||
DESTINATION "include${OXEN_INSTALL_INCLUDEDIR_SUFFIX}"
|
||||
FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp")
|
||||
|
|
|
@ -66,7 +66,6 @@ target_link_libraries(common
|
|||
filesystem
|
||||
oxen::logging
|
||||
fmt::fmt
|
||||
date::date
|
||||
PRIVATE
|
||||
sodium
|
||||
logging
|
||||
|
|
|
@ -33,10 +33,9 @@
|
|||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <thread>
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/color.h>
|
||||
|
||||
#include <date/date.h>
|
||||
|
||||
#include "epee/string_tools.h"
|
||||
#include "epee/wipeable_string.h"
|
||||
#include "crypto/crypto.h"
|
||||
|
@ -197,7 +196,7 @@ namespace tools
|
|||
{
|
||||
if (t < 1234567890)
|
||||
return "<unknown>";
|
||||
return date::format("%Y-%m-%d %H:%M:%S UTC", std::chrono::system_clock::from_time_t(t));
|
||||
return "{:%Y-%m-%d %H:%M:%S} UTC"_format(fmt::gmtime(t));
|
||||
}
|
||||
|
||||
std::string get_human_readable_timespan(std::chrono::seconds seconds)
|
||||
|
|
|
@ -66,7 +66,6 @@ extern "C" {
|
|||
#include "service_node_rules.h"
|
||||
#include "service_node_swarm.h"
|
||||
#include "version.h"
|
||||
#include <date/date.h>
|
||||
|
||||
using cryptonote::hf;
|
||||
|
||||
|
|
|
@ -49,8 +49,7 @@
|
|||
#include <oxenc/base32z.h>
|
||||
#include <oxenc/variant.h>
|
||||
#include <fmt/core.h>
|
||||
#include <date/date.h>
|
||||
#include <fmt/core.h>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <ctime>
|
||||
|
@ -1544,7 +1543,7 @@ static void append_printable_service_node_list_entry(cryptonote::network_type ne
|
|||
stream << expiry_height << " (in " << delta_height << ") blocks\n";
|
||||
|
||||
stream << indent2 << "Expiry Date (estimated): " <<
|
||||
date::format("%Y-%m-%d %I:%M:%S %p UTC", std::chrono::system_clock::from_time_t(expiry_epoch_time)) <<
|
||||
"{:%Y-%m-%d %I:%M:%S %p} UTC"_format(fmt::gmtime(expiry_epoch_time)) <<
|
||||
" (" << get_human_time_ago(expiry_epoch_time, now) << ")\n";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,6 @@
|
|||
namespace cryptonote::rpc {
|
||||
|
||||
using nlohmann::json;
|
||||
using oxen::json_to_bt;
|
||||
|
||||
static auto logcat = log::Cat("daemon.rpc");
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ add_library(wallet3
|
|||
db_schema.cpp
|
||||
default_daemon_comms.cpp
|
||||
keyring.cpp
|
||||
keyring_manager.cpp
|
||||
transaction_constructor.cpp
|
||||
transaction_scanner.cpp
|
||||
pending_transaction.cpp
|
||||
|
@ -27,3 +28,90 @@ target_link_libraries(wallet3
|
|||
extra
|
||||
mnemonics
|
||||
SQLiteCpp)
|
||||
|
||||
function(combine_archives output_archive)
|
||||
set(FULL_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/lib${output_archive}.a)
|
||||
|
||||
if(NOT APPLE)
|
||||
set(mri_file ${CMAKE_CURRENT_BINARY_DIR}/${output_archive}.mri)
|
||||
set(mri_content "create ${FULL_OUTPUT_PATH}\n")
|
||||
foreach(in_archive ${ARGN})
|
||||
string(APPEND mri_content "addlib $<TARGET_FILE:${in_archive}>\n")
|
||||
endforeach()
|
||||
string(APPEND mri_content "save\nend\n")
|
||||
file(GENERATE OUTPUT ${mri_file} CONTENT "${mri_content}")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${FULL_OUTPUT_PATH}
|
||||
DEPENDS ${mri_file} ${ARGN}
|
||||
COMMAND ar -M < ${mri_file})
|
||||
else()
|
||||
set(merge_libs)
|
||||
foreach(in_archive ${ARGN})
|
||||
list(APPEND merge_libs $<TARGET_FILE:${in_archive}>)
|
||||
endforeach()
|
||||
add_custom_command(
|
||||
OUTPUT ${FULL_OUTPUT_PATH}
|
||||
DEPENDS ${mri_file} ${ARGN}
|
||||
COMMAND /usr/bin/libtool -static -o ${FULL_OUTPUT_PATH} ${merge_libs})
|
||||
endif()
|
||||
add_custom_target(wallet3_merged DEPENDS ${FULL_OUTPUT_PATH})
|
||||
endfunction(combine_archives)
|
||||
|
||||
if (STATIC AND BUILD_STATIC_DEPS)
|
||||
set(optional_targets)
|
||||
foreach(maybe_target IN ITEMS libusb_vendor hidapi_libusb)
|
||||
if(TARGET ${maybe_target})
|
||||
list(APPEND optional_targets ${maybe_target})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
combine_archives(wallet3_merged
|
||||
multisig
|
||||
cryptonote_core
|
||||
cryptonote_basic
|
||||
cryptonote_protocol
|
||||
sqlitedb
|
||||
logging
|
||||
wallet3
|
||||
mnemonics
|
||||
common
|
||||
cncrypto
|
||||
device
|
||||
ringct
|
||||
ringct_basic
|
||||
checkpoints
|
||||
version
|
||||
net
|
||||
epee
|
||||
blockchain_db
|
||||
rpc_common
|
||||
rpc_http_client
|
||||
rpc_commands
|
||||
|
||||
# Static deps:
|
||||
Boost::program_options Boost::serialization Boost::system Boost::thread
|
||||
zlib
|
||||
SQLite::SQLite3
|
||||
SQLiteCpp
|
||||
sodium
|
||||
libzmq
|
||||
CURL::libcurl
|
||||
oxenmq::oxenmq
|
||||
lmdb
|
||||
randomx
|
||||
uSockets
|
||||
cpr
|
||||
oxen::logging
|
||||
spdlog::spdlog
|
||||
fmt::fmt
|
||||
|
||||
${optional_targets}
|
||||
)
|
||||
|
||||
if(IOS)
|
||||
set(lib_folder lib-${ARCH})
|
||||
else()
|
||||
set(lib_folder lib)
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
__pycache__
|
||||
*.egg-info
|
||||
venv/
|
||||
build/
|
||||
release/
|
||||
dist/
|
|
@ -0,0 +1,23 @@
|
|||
PYTHON_MAJOR_VERSION=3
|
||||
PYTHON_MINOR_VERSION=8
|
||||
PYTHON_VERSION=$(PYTHON_MAJOR_VERSION).$(PYTHON_MINOR_VERSION)
|
||||
PYTHON_WITH_VERSION=python$(PYTHON_VERSION)
|
||||
PIP_WITH_VERSION=pip$(PYTHON_VERSION)
|
||||
|
||||
all: build
|
||||
|
||||
system_dependencies:
|
||||
$(PIP_WITH_VERSION) install --upgrade setuptools
|
||||
$(PIP_WITH_VERSION) install --upgrade build
|
||||
|
||||
build:
|
||||
$(PYTHON_WITH_VERSION) -m build
|
||||
$(PIP_WITH_VERSION) install --editable .
|
||||
|
||||
run:
|
||||
oxen_wallet_cli
|
||||
|
||||
clean:
|
||||
find . -type d -name "__pycache__" -exec rm -r {} +
|
||||
find . -type d -name "*.egg-info" -exec rm -r {} +
|
||||
rm -rf dist/ build/
|
|
@ -0,0 +1,13 @@
|
|||
# Oxen Wallet CLI
|
||||
|
||||
## Development Notes
|
||||
### Click stuff
|
||||
https://click.palletsprojects.com/en/8.1.x/
|
||||
|
||||
https://openbase.com/python/click-repl
|
||||
|
||||
### Example Python wallets
|
||||
|
||||
https://github.com/AndreMiras/PyWallet
|
||||
|
||||
https://github.com/Blockstream/green_cli
|
|
@ -0,0 +1 @@
|
|||
version = '0.0.1'
|
|
@ -0,0 +1,5 @@
|
|||
"""Entry point for oxen-wallet-cli."""
|
||||
from oxen_wallet_cli.walletcli import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,27 @@
|
|||
import atexit
|
||||
import sys
|
||||
|
||||
import pywallet3
|
||||
|
||||
from oxen_wallet_cli import version
|
||||
|
||||
class Context:
|
||||
"""Holds global context related to the invocation of the tool"""
|
||||
|
||||
def __init__(self):
|
||||
self.options = None
|
||||
self.logged_in = False
|
||||
self.configured = False
|
||||
self.wallet = None
|
||||
self.wallet_core_config = None
|
||||
self.keyring_manager = None
|
||||
|
||||
def configure(self, options):
|
||||
self.options = options
|
||||
self.__dict__.update(options)
|
||||
self.wallet_core_config = pywallet3.WalletConfig()
|
||||
self.wallet_core_config.daemon.address = self.options["oxend_url"]
|
||||
self.keyring_manager = pywallet3.KeyringManager(self.options["network"])
|
||||
self.configured = True
|
||||
|
||||
sys.modules[__name__] = Context()
|
|
@ -0,0 +1,109 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
import click
|
||||
from click_repl import repl
|
||||
|
||||
from oxen_wallet_cli import context
|
||||
|
||||
import pywallet3
|
||||
|
||||
def _get_config_dir(options):
|
||||
"""Return the default config dir for network"""
|
||||
return os.path.expanduser(os.path.join('~', '.oxen-wallet', options['network']))
|
||||
|
||||
@click.group(invoke_without_command=True)
|
||||
@click.option('--log-level', type=click.Choice(['error', 'warn', 'info', 'debug']))
|
||||
@click.option('--network', default='testnet', help='Network: mainnet|testnet|devnet.')
|
||||
@click.option('--config-dir', '-C', default=None, help='Override config directory.')
|
||||
@click.option('--oxend-url', default="ipc:///home/sean/.oxen/testnet/oxend.sock", type=str, help='Use the given daemon')
|
||||
@click.option('--datadir', help='A directory which the wallet will save data')
|
||||
@click.pass_context
|
||||
def walletcli(click_ctx, **options):
|
||||
"""Command line interface for Oxen Wallet CLI."""
|
||||
if context.configured:
|
||||
# In repl mode run configuration once only
|
||||
return
|
||||
|
||||
if options['log_level']:
|
||||
py_log_level = {
|
||||
'error': logging.ERROR,
|
||||
'warn': logging.WARNING,
|
||||
'info': logging.INFO,
|
||||
'debug': logging.DEBUG,
|
||||
}[options['log_level']]
|
||||
|
||||
logging.basicConfig(level=py_log_level)
|
||||
|
||||
if options['config_dir'] is None:
|
||||
options['config_dir'] = _get_config_dir(options)
|
||||
os.makedirs(options['config_dir'], exist_ok=True)
|
||||
|
||||
if options['datadir'] is None:
|
||||
options['datadir'] = os.path.join(options['config_dir'], 'oxen_datadir')
|
||||
os.makedirs(options['datadir'], exist_ok=True)
|
||||
|
||||
context.configure(options)
|
||||
|
||||
if click_ctx.invoked_subcommand is None:
|
||||
click.echo("Run ':help' for help information, or ':quit' to quit.")
|
||||
repl(click_ctx)
|
||||
|
||||
@walletcli.command()
|
||||
def load_test_wallet():
|
||||
click.echo("Loading test wallet")
|
||||
if context.wallet is not None:
|
||||
click.echo("Wallet already loaded")
|
||||
return
|
||||
|
||||
spend_priv = "e6c9165356c619a64a0d26fafd99891acccccf8717a8067859d972ecd8bcfc0a"
|
||||
spend_pub = "b76f2d7c8a036ff65c564dcb27081c04fe3f2157942e23b0496ca797ba728e4f"
|
||||
view_priv = "961d67bb5b3ed1af8678bbfcf621f9c15c2b7bff080892890020bdfd47fe4f0a"
|
||||
view_pub = "8a0ebacd613e0b03b8f27bc64bd961ea2ebf4c671c6e7f3268651acf0823fed5"
|
||||
|
||||
keyring = pywallet3.Keyring(spend_priv, spend_pub, view_priv, view_pub, context.options["network"])
|
||||
click.echo("Wallet address {} loaded".format(keyring.get_main_address()))
|
||||
name = click.prompt("Wallet Name", default="{}-oxen-wallet".format(context.options["network"])).strip()
|
||||
context.wallet_core_config.omq_rpc.sockname = name + ".sock";
|
||||
context.wallet = pywallet3.Wallet(name, keyring, context.wallet_core_config)
|
||||
|
||||
@walletcli.command()
|
||||
@click.argument('seed_phrase', nargs=25)
|
||||
@click.argument('seed_phrase_passphrase', default="")
|
||||
def load_from_seed(seed_phrase, seed_phrase_passphrase):
|
||||
click.echo("Loading wallet from seed")
|
||||
if context.wallet is not None:
|
||||
click.echo("Wallet already loaded")
|
||||
return
|
||||
|
||||
seed_phrase_str = ' '.join(seed_phrase)
|
||||
keyring = context.keyring_manager.generate_keyring_from_electrum_seed(seed_phrase_str, seed_phrase_passphrase)
|
||||
click.echo("Wallet address {} loaded".format(keyring.get_main_address()))
|
||||
name = click.prompt("Wallet Name", default="{}-oxen-wallet".format(context.options["network"])).strip()
|
||||
context.wallet_core_config.omq_rpc.sockname = name + ".sock";
|
||||
context.wallet = pywallet3.Wallet(name, keyring, context.wallet_core_config)
|
||||
|
||||
@walletcli.command()
|
||||
def register_service_node():
|
||||
click.echo("Registering Service Node")
|
||||
if click.confirm("Would you like to register a service node now"):
|
||||
click.echo("")
|
||||
name = click.prompt("Enter the wallet address of the operator", default="").strip()
|
||||
click.echo("The wallet address to be used is: {}".format(name))
|
||||
click.echo("TODO: This function is not yet implemented")
|
||||
|
||||
@walletcli.command()
|
||||
def address():
|
||||
# click.echo("Address: {}".format(context.keyring.get_main_address()))
|
||||
click.echo("Address: {}".format("TODO sean get the address here"))
|
||||
|
||||
@walletcli.command()
|
||||
def get_balance():
|
||||
click.echo("Balance: {}".format(context.wallet.get_balance()))
|
||||
|
||||
@walletcli.command()
|
||||
def get_unlocked_balance():
|
||||
click.echo("Unlocked Balance: {}".format(context.wallet.get_unlocked_balance()))
|
||||
|
||||
def main():
|
||||
walletcli()
|
|
@ -0,0 +1,30 @@
|
|||
[build-system]
|
||||
requires = ["setuptools", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = 'oxen_wallet_cli'
|
||||
authors = [
|
||||
{name = "Sean Darcy", email = "sean@oxen.io"},
|
||||
]
|
||||
description = "CLI wallet for Oxen"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"License :: OSI Approved :: GPL-3.0-or-later",
|
||||
"Natural Language :: English",
|
||||
"Operating System :: OS Independent",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
]
|
||||
dependencies = [
|
||||
"Click",
|
||||
"click-repl",
|
||||
"pywallet3",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
[project.scripts]
|
||||
oxen_wallet_cli = "oxen_wallet_cli.__main__:main"
|
|
@ -0,0 +1,32 @@
|
|||
#include "keyring_manager.hpp"
|
||||
#include "mnemonics/electrum-words.h"
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
std::shared_ptr<Keyring> KeyringManager::generate_keyring_from_electrum_seed(std::string& seed_phrase, std::string& seed_phrase_passphrase)
|
||||
{
|
||||
std::string old_language;
|
||||
crypto::secret_key recovery_key;
|
||||
if (!crypto::ElectrumWords::words_to_bytes(seed_phrase, recovery_key, old_language))
|
||||
throw std::runtime_error("Electrum-style word list failed verification");
|
||||
|
||||
if (!seed_phrase_passphrase.empty())
|
||||
recovery_key = cryptonote::decrypt_key(recovery_key, seed_phrase_passphrase);
|
||||
|
||||
cryptonote::account_base account;
|
||||
// Generate the account keys using the recovery key
|
||||
// param recovery_param If it is a restore, the recovery key
|
||||
// param recover Whether it is a restore
|
||||
// param two_random Whether it is a non-deterministic wallet
|
||||
account.generate(recovery_key, true, false);
|
||||
cryptonote::account_keys account_keys = account.get_keys();
|
||||
|
||||
return std::make_shared<wallet::Keyring>(
|
||||
account_keys.m_spend_secret_key,
|
||||
account_keys.m_account_address.m_spend_public_key,
|
||||
account_keys.m_view_secret_key,
|
||||
account_keys.m_account_address.m_view_public_key,
|
||||
nettype);
|
||||
}
|
||||
|
||||
} // namespace wallet
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "keyring.hpp"
|
||||
|
||||
namespace wallet
|
||||
{
|
||||
class KeyringManager
|
||||
{
|
||||
public:
|
||||
KeyringManager() = default;
|
||||
KeyringManager(const cryptonote::network_type& type): nettype(type) {};
|
||||
std::shared_ptr<Keyring> generate_keyring_from_electrum_seed(std::string& seed_phrase, std::string& seed_phrase_passphrase);
|
||||
private:
|
||||
cryptonote::network_type nettype = cryptonote::network_type::MAINNET;
|
||||
|
||||
};
|
||||
|
||||
} // namespace wallet
|
|
@ -58,7 +58,7 @@ namespace wallet
|
|||
{
|
||||
request_handler.set_wallet(weak_from_this());
|
||||
omq->start();
|
||||
daemon_comms->set_remote("ipc://./oxend.sock");
|
||||
daemon_comms->set_remote(config.daemon.address);
|
||||
daemon_comms->register_wallet(*this, last_scan_height + 1 /*next needed block*/,
|
||||
true /* update sync height */,
|
||||
true /* new wallet */);
|
||||
|
|
Loading…
Reference in New Issue