#include "daemon/seed_wallet_creator.h" #include #include #include #include #include "daemon/embedded_daemon.h" #include "rpc/rpc_client.h" #include "util/platform.h" namespace fs = std::filesystem; namespace dragonx { namespace daemon { namespace { // Random alphanumeric token for the isolated node's throwaway RPC credentials (libsodium CSPRNG; // sodium_init() has already run at app startup for the chat crypto). std::string randomToken(int n) { static const char cs[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; std::string s; s.reserve(n); for (int i = 0; i < n; ++i) s.push_back(cs[randombytes_uniform(sizeof(cs) - 1)]); return s; } // A free localhost port for the isolated node — just above the default so it never collides with // the main daemon (which keeps running on the default port throughout). int pickFreePort() { for (int p = 21770; p < 21900; ++p) if (!EmbeddedDaemon::tcpPortInUse(p)) return p; return 0; } } // namespace SeedWalletResult SeedWalletCreator::create(bool keepDatadir, const std::function& progress) { auto report = [&](const std::string& m) { if (progress) progress(m); }; SeedWalletResult r; std::error_code ec; // 1. Isolated throwaway datadir. The Komodo/Hush daemon requires the datadir's basename to be // the assetchain name (DRAGONX) — mirroring ~/.hush/DRAGONX — or it mis-resolves its conf and // RPC port. So the wallet lives in /DRAGONX; `base` is the migration root we clean up. const std::string base = util::Platform::getConfigDir() + "/seed-migrate"; const std::string dataDir = base + "/DRAGONX"; fs::remove_all(base, ec); fs::create_directories(dataDir, ec); if (ec) { r.error = "Could not create the temporary wallet directory."; return r; } // 2. Free port + fresh throwaway RPC credentials for the isolated node. const int port = pickFreePort(); if (port <= 0) { r.error = "No free local port for the isolated node."; return r; } const std::string user = randomToken(16); const std::string pass = randomToken(32); // 3. Minimal conf for the isolated node (own creds/port). The DRAGONX RPC is plaintext HTTP on // localhost — `-tls=only` applies to P2P, not the RPC — so the client below connects without // TLS, exactly as the main GUI does (its conf has no rpctls key either). const std::string conf = "rpcuser=" + user + "\n" "rpcpassword=" + pass + "\n" "rpcport=" + std::to_string(port) + "\n" "server=1\n"; if (!util::Platform::writeFileAtomically(dataDir + "/DRAGONX.conf", conf, /*restrictPermissions=*/true)) { r.error = "Could not write the isolated node config."; fs::remove_all(base, ec); return r; } // 4. Start the isolated daemon: fresh mnemonic wallet (-usemnemonic=1), no network/sync. report("Starting an isolated node…"); EmbeddedDaemon temp; temp.setSkipPortCheck(true); // runs on `port`, beside the main daemon on the default port temp.setNextStartOverride(dataDir, {"-usemnemonic=1", "-connect=0", "-listen=0", "-maxconnections=0"}); if (!temp.start("")) { r.error = "Could not start the isolated node: " + temp.getLastError(); fs::remove_all(base, ec); return r; } // 5. Connect to it, retrying until the RPC is responsive and past warmup. report("Creating your new seed wallet…"); rpc::RPCClient cli; bool ready = false; for (int i = 0; i < 90 && !ready; ++i) { if (cli.connect("127.0.0.1", std::to_string(port), user, pass, /*useTls=*/false)) { try { cli.call("getinfo"); ready = true; } // succeeds only once past warmup (-28) catch (...) { cli.disconnect(); } } if (!ready) std::this_thread::sleep_for(std::chrono::seconds(1)); } if (!ready) { r.error = "The isolated node did not become ready in time."; temp.stop(20000); fs::remove_all(base, ec); return r; } // 6. Export the new seed phrase + a fresh shielded receive address (the future sweep target). try { auto m = cli.callSecret("z_exportmnemonic"); // zero the raw body too (B7) if (m.contains("mnemonic") && m["mnemonic"].is_string()) { // Take our copy, then scrub the json node's own copy so it isn't freed in the clear (B7). auto& mn = m["mnemonic"].get_ref(); r.seedPhrase = mn; if (!mn.empty()) sodium_memzero(&mn[0], mn.size()); } r.destAddress = cli.call("z_getnewaddress").get(); r.ok = !r.seedPhrase.empty() && !r.destAddress.empty(); if (!r.ok) r.error = "The isolated node returned an empty seed or address."; } catch (const std::exception& e) { const std::string what = e.what(); // "Method not found" (JSON-RPC -32601) means this dragonxd predates mnemonic support — // it has no z_exportmnemonic RPC (the older bundled binary). Migrate-to-seed can't work // until the daemon is updated, so give an actionable message, not the raw RPC error. if (what.find("Method not found") != std::string::npos || what.find("-32601") != std::string::npos) { r.error = "This DragonX daemon is too old to create a seed wallet — it lacks mnemonic " "support (the z_exportmnemonic RPC). Update to the latest DragonX daemon " "(Settings -> NODE & SECURITY -> Check for updates, or Install bundled), then " "try again."; } else { r.error = std::string("Seed export failed: ") + what; } } // 7. Stop the isolated node (graceful; it flushes its tiny empty chain quickly). cli.disconnect(); temp.stop(20000); // 8. Keep the temp wallet for a later sweep/adopt step, or scrub it. tempDatadir is the // migration root `base`; the new wallet.dat lives in /DRAGONX. r.tempDatadir = base; if (!keepDatadir || !r.ok) { fs::remove_all(base, ec); r.tempDatadir.clear(); } return r; } } // namespace daemon } // namespace dragonx