# WrkzCoin Documentation - full text Every documentation page concatenated. Source: https://github.com/wrkzcoin/wrkzcoin # WrkzCoin Documentation Reference and operator documentation for the WrkzCoin daemon, wallets and RPC surfaces. The source of truth is always the code in [wrkzcoin/wrkzcoin](https://github.com/wrkzcoin/wrkzcoin); these pages track the `development` branch. ## Start here | I want to … | Go to | | --- | --- | | Get a binary | [Building from Source](guides/building.md), or [latest.wrkz.work](https://latest.wrkz.work) | | Run a node | [Running a Node](guides/running-daemon.md) | | Look up a daemon flag | [Configuration Reference](guides/daemon-configuration.md) | | Run a node on a small disk | [Lite Nodes](guides/lite-node.md) and [Snapshots](guides/lite-snapshots.md) | | Mine | [Solo Mining](guides/solo-mining.md) | | Build on the daemon RPC | [Daemon RPC Overview](daemon-rpc/overview.md) | | Build a payment integration | [Wallet API Overview](wallet-api/overview.md) | | Use a wallet | [Wallet CLI](guides/wallet-cli.md) or the [wallet apps](guides/wallet-apps.md) | | Look up an error code | [Wallet Error Codes](guides/error-codes.md) | | Check a fork height or ring size | [Network Parameters](guides/network-parameters.md) | | See what changed | [Changelog](changelog/daemon.md) | | Build a wallet app | [Building the Wallet Apps](guides/building-wallet-apps.md) | | Find a script or a small binary | [Other Tools](guides/other-tools.md) | | Talk to somebody | [Community](community.md) | ## Binaries | Binary | What it is | | --- | --- | | `Wrkzd` | The daemon: P2P node, RPC server, optional stratum server | | `wrkz-wallet` | Interactive wallet CLI | | `wrkz-wallet-api` | HTTP wallet API, for services and the wallet apps | | `wrkz-service` | Legacy JSON-RPC wallet service (payment service) | | `wrkz-txpow-server` | Optional transaction proof-of-work helper | | `miner` | Reference CPU miner | | `cryptotest` | Hash function self-test | | `wallet-upgrader` | Converts old wallet container files | The Flutter desktop, mobile and web wallets live under `extras/` and are built separately; see [Desktop, Mobile and Web Wallets](guides/wallet-apps.md). ## Default ports | Port | Service | | --- | --- | | `17855` | P2P | | `17856` | Daemon RPC | | `17857` | ZMQ publisher (`--zmq-pub`) | | `17858` | Stratum, by convention — off unless `--stratum-bind-port` is given | | `17870` | `wrkz-txpow-server`, if you run one | | `7856` | `wrkz-wallet-api` **and** `wrkz-service` | `wrkz-wallet-api` and `wrkz-service` share the same default port (`SERVICE_DEFAULT_PORT` in `src/config/CryptoNoteConfig.h`). Give one of them a different `--port` / `--bind-port` if you run both on one machine. ## For AI agents and scripts Every page is published as plain markdown as well as HTML, so a model or a script can read the docs without parsing the rendered site. | URL | What it is | | --- | --- | | [`/llms.txt`](https://docs.wrkz.work/llms.txt) | An index in the [llmstxt.org](https://llmstxt.org) format: every page, grouped by section, one line of description each | | [`/llms-full.txt`](https://docs.wrkz.work/llms-full.txt) | The entire documentation set concatenated as one markdown file | | ` + .md` | The markdown source of that page — e.g. [`/guides/lite-node.md`](https://docs.wrkz.work/guides/lite-node.md) | Point a coding agent at `/llms.txt` and let it fetch the pages it needs; paste `/llms-full.txt` when you want the whole thing in context at once. All three are regenerated on every docs build, so they never lag the site. **They describe the documented behaviour, not your node.** Version-specific answers still need `Wrkzd --version` and the [changelog](changelog/daemon.md). ## Building the documentation These pages are MkDocs Material sources under `docs/` in the repository. ```bash pip install -r docs/requirements.txt mkdocs serve -f docs/mkdocs.yml mkdocs build -f docs/mkdocs.yml ``` Every page names the file it documents. When a route, a method handler or a command-line flag changes, update the page that names it in the same change. --- # Running a Node The daemon binary is `Wrkzd`. Run it with no arguments and it syncs the full chain, serves RPC on `127.0.0.1:17856` and P2P on `0.0.0.0:17855`, with no authentication and no stratum server. Every option is listed with its default in the [Configuration Reference](daemon-configuration.md). ## A reasonable public node ```bash ./Wrkzd \ --rpc-bind-ip 0.0.0.0 \ --rpc-bind-port 17856 \ --rpc-access-token "strong-token" \ --rpc-max-body-bytes 2097152 \ --rpc-max-rpm 240 \ --rpc-max-global-index-range 5000 \ --rpc-max-block-count 1000 \ --log-level 3 \ --no-console ``` - Bind RPC to localhost unless you intend to expose it. - Set `--rpc-access-token` for any non-local use. - Keep the request and rate limits on. - Use `--no-console` under a process manager, and reach the console with [`attach`](ipc-and-console.md#attaching-a-console-to-a-running-daemon) when you need it. Fuller advice is in [Security Hardening](security-hardening.md) and [Rate Limits and Performance](rate-limits-and-performance.md). ## Choosing a storage mode | | When | | --- | --- | | **Full** (default) | Public nodes, seed nodes, explorers, anything other people sync against | | **Pruned** (`--prune`) | Disk is tight but the node still serves others. Old block bodies are dropped; everything a wallet rescan needs is kept. Reversible | | **Lite** (`--lite --lite-height H`) | A node for your own wallets. Full block data only from `H` upward. Much smaller, **permanent for the database**, and a poor public node | See [Lite Nodes](lite-node.md) before choosing lite, and [Lite Node Snapshots](lite-snapshots.md) to skip the index-only sync. `--prune` and `--lite` cannot be combined, and `--lite` cannot be combined with `--daemon-mode explorer`. ## Explorer mode ```bash ./Wrkzd --daemon-mode explorer ``` Explorer mode additionally serves `f_blocks_list_json`, `f_block_json`, `f_transaction_json`, `f_on_transactions_pool_json`, `f_transactions_by_payment_id_json` and `/queryblocksdetailed`. These walk database indexes and are not something a plain node should answer for anyone who asks — put a token or a proxy in front of it. ## Mining The daemon can serve stratum directly, so a stock miner needs no pool: ```bash ./Wrkzd --stratum-bind-port 17858 ``` It is a separate TCP listener with no authentication of its own, so it stays on loopback unless you change it. Full notes in [Solo Mining](solo-mining.md). ## Local socket and console ```bash ./Wrkzd --no-console --rpc-ipc-path /run/wrkz/wrkzd.sock ./Wrkzd attach /run/wrkz/wrkzd.sock ``` The socket serves the whole RPC surface, plus `/console`, which is registered **only** there. See [Local IPC and Console](ipc-and-console.md). ## Push notifications ```bash ./Wrkzd --block-notify https://example.com/hooks/block \ --tx-notify "/usr/local/bin/on-tx.sh %s" ``` See [Notification Hooks](notify-hooks.md), or use the ZMQ publisher described in [Networking](networking.md#zmq-publisher). ## Smoke tests ```bash curl -s -H "X-API-Key: strong-token" http://127.0.0.1:17856/info curl -s -H "X-API-Key: strong-token" -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"1","method":"getblockcount","params":{}}' \ http://127.0.0.1:17856/json_rpc ``` `/info` answers `{"status":"BUSY"}` for the whole of a heavy initial sync — the handler tries to lock the chain and gives up rather than blocking it. That is expected; poll again once sync settles. Check that the build has zlib, without which every syncing wallet pulls several times as many bytes: ```bash curl -s http://127.0.0.1:17856/info | grep -o '"compression":"[^"]*"' ``` `"none"` means the build found no zlib. See [Rate Limits and Performance](rate-limits-and-performance.md#response-compression). --- # Daemon Configuration Reference Every `Wrkzd` command-line option, with the default the binary compiles in. Defined in `src/daemon/DaemonConfiguration.h` and parsed in `src/daemon/DaemonConfiguration.cpp`; `Wrkzd --help` prints the same list. Every option here is also accepted as a key in a config file (INI or JSON) via `--config-file`, and appears in `--dump-config` output. ## Configuration files | Option | Default | Meaning | | --- | --- | --- | | `--config-file ` | none | Load settings from an INI or JSON file. Command-line options win over file values | | `--save-config ` | none | Write the resolved configuration to a file and exit | | `--dump-config` | `false` | Print the resolved configuration to stdout | | `--data-dir ` | platform default | Blockchain data directory | ## Logging and console | Option | Default | Meaning | | --- | --- | --- | | `--log-file ` | `WRKZCoind.log` | Log file path | | `--log-level <0-5>` | `2` (WARNING) | `0` FATAL, `1` ERROR, `2` WARNING, `3` INFO, `4` DEBUG, `5` TRACE | | `--no-console` | `false` | Do not start the interactive console. Use this under a process manager | | `--attach ` | none | Attach an interactive console to a daemon already running, over its IPC socket, instead of starting a node. `Wrkzd attach ` is the same thing. See [Local IPC and Console](ipc-and-console.md) | A daemon whose stdin is at end of file — what a process manager hands a daemon started without `--no-console` — prints one notice and runs headless. ## RPC server | Option | Default | Meaning | | --- | --- | --- | | `--rpc-bind-ip ` | `127.0.0.1` | IPv4 interface for the RPC server | | `--rpc-bind-port ` | `17856` | RPC port | | `--rpc-bind-ipv6-address ` | empty | IPv6 address for a second RPC listener | | `--rpc-use-ipv6` | `false` | Start the IPv6 listener. Needs `--rpc-bind-ipv6-address` too | | `--daemon-mode ` | `standard` | `standard` or `explorer`. Explorer additionally serves the block-explorer routes | | `--rpc-access-token ` | empty | Require this token in `X-API-Key` or `Authorization: Bearer`. Empty disables auth | | `--enable-cors ` | empty | `Access-Control-Allow-Origin` value | | `--rpc-trust-proxy` | `false` | Take the client IP from `X-Forwarded-For`. **Only behind a trusted reverse proxy** | | `--rpc-read-timeout ` | `15` | RPC read timeout, seconds | | `--rpc-write-timeout ` | `30` | RPC write timeout, seconds | | `--rpc-max-body-bytes ` | `2097152` | Maximum request body, bytes (2 MB). Over it returns `413` | | `--rpc-max-rpm ` | `240` | Requests per minute per client IP. `0` disables. Over it returns `429` | | `--rpc-max-global-index-range ` | `5000` | Cap for `/get_global_indexes_for_range` | | `--rpc-max-block-count ` | `1000` | Cap on `blockCount` for wallet sync and raw-block methods | | `--rpc-sync-cache-size ` | `64` | Megabytes of finished wallet-sync responses to keep. `0` disables | An IPv6 bind failure is non-fatal: the daemon warns and keeps serving IPv4. ## Local IPC socket POSIX only; on Windows these are accepted and ignored with a warning. See [Local IPC and Console](ipc-and-console.md). | Option | Default | Meaning | | --- | --- | --- | | `--rpc-ipc-path ` | empty | Also serve RPC on this AF_UNIX socket, alongside the TCP listeners | | `--rpc-ipc-mode ` | `0600` | Permissions for the socket file | | `--rpc-ipc-group ` | empty | Group to own the socket file, for a `0660` shared setup | | `--rpc-ipc-require-token` | `false` | Also demand `--rpc-access-token` from IPC callers | ## P2P network | Option | Default | Meaning | | --- | --- | --- | | `--p2p-bind-ip ` | `0.0.0.0` | IPv4 interface for the P2P listener | | `--p2p-bind-port ` | `17855` | P2P port | | `--p2p-external-port ` | `0` | External port to announce, for NAT/UPnP | | `--p2p-bind-ipv6-address ` | empty | IPv6 bind address. `::` for all interfaces. Empty disables IPv6 | | `--p2p-bind-port-ipv6 ` | `0` | IPv6 P2P port. `0` reuses `--p2p-bind-port` | | `--out-peers ` | `15` | Maximum outgoing connections | | `--in-peers ` | `15` | Maximum incoming connections | | `--allow-local-ip` | `false` | Allow this machine's own IP into the peer list | | `--hide-my-port` | `false` | Do not announce this node as a peer candidate | | `--p2p-reset-peerstate` | `false` | New peer ID, discard saved peer state | | `--add-peer ` | none | Add a peer to the local list. Repeatable | | `--add-priority-node ` | none | Add a peer and keep a connection to it. Repeatable | | `--add-exclusive-node ` | none | Connect **only** to these, bypassing discovery. Repeatable | | `--seed-node ` | none | Bootstrap from this node. Repeatable | `--add-peer`, `--add-priority-node` and `--add-exclusive-node` accept IPv4 addresses only. `--seed-node` hostnames resolve to both A and AAAA records. ## Syncing | Option | Default | Meaning | | --- | --- | --- | | `--transaction-validation-threads ` | hardware concurrency | Threads for parallel input validation | | `--sync-max-peers ` | `3` | Peers to sync blocks from at once | | `--sync-peer-failure-threshold ` | `2` | Failures before a sync peer is demoted | | `--sync-batch-min ` | `120` | Minimum adaptive batch size | | `--sync-batch-max ` | `600` | Maximum adaptive batch size | | `--block-sync-size ` | `600` | Maximum blocks per sync chunk | | `--block-sync-bytes ` | `16777216` | Approximate maximum bytes per sync chunk (16 MB) | ## Database | Option | Default | Meaning | | --- | --- | --- | | `--db-enable-compression` | `true` | RocksDB block compression. This is a cxxopts implicit-value bool: write `--db-enable-compression=false`, not `--db-enable-compression false` | | `--db-max-open-files ` | `4096` | Files RocksDB may hold open | | `--db-read-buffer-size ` | `256` | Read cache size | | `--db-write-buffer-size ` | `64` | Write buffer size | | `--db-threads ` | `8` | Background compaction and flush threads | | `--db-compression-level ` | `0` | ZSTD level for the bottommost level. `0` uses RocksDB's default of 3 | | `--db-compression-dict-bytes ` | `0` | ZSTD dictionary size. `0` disables the dictionary | | `--db-block-size ` | `4` | SST block size | | `--db-row-cache-percent ` | `0` | Share of the read buffer given to the row cache. `0` uses the built-in eighth | | `--db-bottom-filters` | `false` | Keep bloom filters on the bottommost level. Costs space; makes spent key-image checks a lookup rather than a block read | | `--skip-boot-compaction` | `false` | Skip the startup compaction check | ## Auto-prune and auto-compaction | Option | Default | Meaning | | --- | --- | --- | | `--auto-prune-min-gap-blocks ` | `120` | Minimum blocks between automatic prune passes. `0` disables | | `--auto-compaction-min-gap-blocks ` | `720` | Minimum blocks between automatic compactions. `0` disables | | `--auto-prune-min-free-bytes ` | `4294967296` | Free space (4 GB) before the regular auto-prune schedule runs. Low-space mode can still force one earlier | | `--auto-compaction-min-free-bytes ` | `8589934592` | Free space (8 GB) required to start an automatic compaction | ## Storage mode `--prune` and `--lite` are different features and cannot be combined. See [Lite Nodes](lite-node.md). | Option | Default | Meaning | | --- | --- | --- | | `--prune` | `false` | Pruned node: keep only the most recent `--prune-depth` block bodies. Reversible by resyncing | | `--prune-depth ` | `10080` | Blocks to retain when pruning. Minimum is the same 7 days (`10080` blocks at 60 s) | | `--lite` | `false` | Lite node: store full block data only from `--lite-height` upward. **Permanent for the database** | | `--lite-height ` | `0` | Height at and above which a lite node stores full blocks. Required with `--lite` | | `--import-lite-snapshot ` | none | Load a lite node snapshot into an empty database, then exit. Needs `--lite` and the matching `--lite-height` | | `--snapshot-info ` | none | Print what a snapshot file contains, as JSON, and exit. Reads the header only | | `--snapshot-stats` | `false` | Report per-table storage, for sizing a snapshot | ## One-shot blockchain operations Each of these does its work and exits. | Option | Meaning | | --- | --- | | `--resync` | Delete the chain and peer state, then sync from scratch. The only option that deletes a chain | | `--rewind-to-height ` | Roll the local chain back to this height. Must be above `0`; refused below a lite node's height | | `--import-blockchain` | Import the database from a dump file | | `--export-blockchain` | Export the database to a dump file | | `--max-export-blocks ` | Cap the blocks included in an export | | `--load-checkpoints ` | CSV checkpoint file, or `default` for the compiled-in checkpoints | | `--print-genesis-tx` | Print the genesis transaction and exit | | `--version`, `--os-version`, `--help` | Print and exit | ## Stratum Off unless `--stratum-bind-port` is given. See [Solo Mining](solo-mining.md). | Option | Default | Meaning | | --- | --- | --- | | `--stratum-bind-port ` | `0` | Port to listen on. `0` leaves the server off | | `--stratum-bind-ip ` | `127.0.0.1` | Interface to listen on | | `--stratum-share-difficulty ` | `0` | Difficulty given to miners. `0` uses the network difficulty | | `--stratum-max-connections ` | `32` | Miners allowed on at once | The stratum listener has no account and no password. Anyone who reaches the port can mine to their own address using this node. ## ZMQ publisher | Option | Default | Meaning | | --- | --- | --- | | `--zmq-pub ` | `tcp://127.0.0.1:17857` | ZMQ PUB endpoint for block and transaction events. An empty string disables it. Accepts `ipc://` endpoints | | `--no-zmq` | `false` | Disable the publisher even if `--zmq-pub` is set | ## Notification hooks See [Notification Hooks](notify-hooks.md). | Option | Default | Meaning | | --- | --- | --- | | `--block-notify ` | empty | Fires on every new main-chain block | | `--reorg-notify ` | empty | Fires on every chain reorganisation | | `--tx-notify ` | empty | Fires on every transaction entering the mempool | | `--notify-during-sync` | `false` | Also fire while the node is still synchronizing | ## Options that do nothing | Option | Why | | --- | --- | | `--fee-address`, `--fee-amount` | Parsed and stored, but no RPC route serves them and the wallet backend hard-codes the node fee to zero. Node fees are not implemented on this network | --- # Daemon Console Commands Commands available at the daemon's interactive console, and — identically — in a session opened with [`Wrkzd attach`](ipc-and-console.md#attaching-a-console-to-a-running-daemon) against a node already running. Registered in `src/daemon/DaemonCommandsHandler.cpp`. `help` or `?` prints the same list from the binary itself. !!! note "Console commands are never served over TCP" They change log levels, ban peers, start compactions and stop the node, so they live on the local IPC socket alone (`POST /console`), where the mode on the socket file decides who may connect. There is no way to enable them on an RPC port, with or without a token. ## Session | Command | What it does | | --- | --- | | `help`, `?` | Show the command list | | `exit` | Local console: shut the daemon down. **Attached console: leave the session**, daemon carries on | | `quit` | Leave an attached session | | `stop` | Shut the daemon down. The same handler as the local `exit`, under a second name so that leaving an attached session is never confused with stopping the node | ## Status | Command | What it does | | --- | --- | | `status` | Daemon status: height, network height, peers, difficulty, hash rate, uptime. On a lite node it also shows `Lite Node`, `Full Block Data From`, and `Lite Sync Stage: Index only` while the tip is still below the lite height | | `sync_info` | Compact synchronization information | | `sync_tune` | Current sync tuning and adaptive sync statistics | | `sync_peers` | Per-peer sync diagnostics | | `prune_status` | Prune mode and prune capability status | | `db_status` | On-disk database status for the active engine | ## Inspecting the chain | Command | What it does | | --- | --- | | `print_pl` | Print the peer list | | `print_cn` | Print current connections. Shows IPv6 peers as `[2001:db8::1]:17855`, and which peers are pruned | | `print_block ` | Print a block | | `print_tx ` | Print a transaction | | `print_pool` | Print the transaction pool, long format | | `print_pool_sh` | Print the transaction pool, short format | On a [lite node](lite-node.md), `print_block` and `print_tx` cannot answer for heights below the lite height: the block bodies and transaction records were never written. ## Maintenance | Command | What it does | | --- | --- | | `save` | Force-save blockchain state to disk | | `set_log ` | Change the current log level. `0` FATAL, `1` ERROR, `2` WARNING, `3` INFO, `4` DEBUG, `5` TRACE | | `compact_db [start\|status\|wait\|force]` | Manage database compaction. `wait` holds the connection until it finishes | | `ban list` | List every banned address, IPv4 and IPv6 | | `ban add [seconds]` | Ban an address. Default 900 seconds | | `ban delete ` | Lift a ban | `compact_db` and `snapshot_export` both run in the background and report through their `status` subcommand, so the console stays usable while they work. Commands run one at a time across all consoles, local and attached; one that blocks holds its connection until it finishes. ## Snapshots | Command | What it does | | --- | --- | | `snapshot_export [start [height] [path]]` | Start a [lite node snapshot](lite-snapshots.md) export. With no arguments, `start` is assumed | | `snapshot_export status` | While running: elapsed time, output path, current table, records kept of records scanned. When idle: the last result, including the payload digest | | `snapshot_export cancel` | Cancel a running export. The partial file removes itself | Shutting the daemon down during an export cancels it rather than waiting tens of minutes for it to finish. ## Calling one from a script ```bash curl -s --unix-socket /run/wrkz/wrkzd.sock \ -H "Content-Type: application/json" \ -d '{"command":"status"}' \ http://localhost/console ``` Or pipe into `attach`, which exits when its input ends: ```bash printf 'status\nprint_cn\n' | Wrkzd attach /run/wrkz/wrkzd.sock ``` --- # Networking How `Wrkzd` binds, who it talks to, and how it finds peers. Every flag named here is listed with its default in the [Configuration Reference](daemon-configuration.md). The in-repo long form is `NETWORKING.md`. ## P2P The daemon always binds an IPv4 P2P listener on `--p2p-bind-ip` (default `0.0.0.0`) and `--p2p-bind-port` (default `17855`). IPv6 is opt-in. Setting `--p2p-bind-ipv6-address` binds a **separate** IPv6 listener in addition to the IPv4 one, using `IPV6_V6ONLY=0`, so it also accepts IPv4-mapped connections (`::ffff:1.2.3.4`) transparently. ```bash # All IPv6 interfaces, same port as IPv4 Wrkzd --p2p-bind-ipv6-address :: # A specific address on its own port Wrkzd --p2p-bind-ipv6-address 2001:db8::1 --p2p-bind-port-ipv6 17856 ``` ### Protocol version Nodes advertise **P2P version 19** as of 0.4.8. The minimum accepted version is 16, so older peers still connect; a node below 16 is refused. IPv6 peer exchange (the `local_peerlist6` field in handshakes and timed syncs) is gated on version 19 — peers below it never see it and ignore it if they do. ### What IPv6 does and does not cover Working: IPv6 inbound and outbound connections, IPv6 peer-list exchange, IPv6 bans, A+AAAA DNS seed resolution, IPv6 white-list promotion after a successful outbound handshake, and `print_cn` showing `[2001:db8::1]:17855` for both directions. Not covered: - A peer connecting **inbound** over pure IPv6 is tracked for display but is not promoted to the white list — the back-ping uses the IPv4 `NetworkAddress` struct. - IPv6 duplicate-connection detection deduplicates by peer ID, not by address. Duplicates fail cleanly at handshake instead. - `--add-peer`, `--add-priority-node` and `--add-exclusive-node` take IPv4 literals only. ## RPC binding `--rpc-bind-ip` (default `127.0.0.1`) and `--rpc-bind-port` (default `17856`). Setting both `--rpc-use-ipv6` and `--rpc-bind-ipv6-address` starts a **second** RPC server on the IPv6 address, on the same port. An IPv6 bind failure is non-fatal: the daemon warns and carries on with IPv4 only. ```bash Wrkzd --rpc-bind-ipv6-address :: --rpc-use-ipv6 ``` RPC can also be served over a local socket — see [Local IPC and Console](ipc-and-console.md). ## Seed nodes and peer discovery Static seed nodes and DNS seed hostnames are compiled in (`SEED_NODES` / `DNS_SEED_NODES` in `src/config/CryptoNoteConfig.h`). Hostnames resolve to **both A and AAAA records**; IPv4 and IPv6 results go into separate seed lists and the daemon bootstraps from whichever answers. Publishing an IPv6 seed needs only an AAAA record on the seed hostname — no daemon change. `--seed-node` entries join the same list. A seed is only ever asked for its peer list, and the connection is closed again afterwards. The daemon asks in two situations: - **Nothing known yet** — the white lists are empty (first start, or after `--p2p-reset-peerstate`). - **Stuck** — a connection-maker round could not dial anybody new *and* fewer than 3 outgoing connections are up. This is what recovers a node whose saved peer list has gone stale after a long time offline. Both share one 5-minute rate limit, so seeds that are down get one walk per interval rather than one per round. Seed hostnames are re-resolved on a helper thread every hour, or at the next seed round while the lookup has produced nothing (DNS not up yet at boot). A failed lookup keeps the addresses already resolved. ### Keeping the peer lists honest - An address that refused or timed out is left alone for 10 minutes rather than retried every round. - Once a minute one random gray-list peer is dialled for its peer list: if it answers it moves to the white list, if not it is dropped. - A peer list received in a handshake or timed sync is cut to 250 entries, the same number the daemon sends. - After 2 minutes with no connection at all the daemon logs a WARNING naming the known-peer and seed counts. `/info` reports `seed_nodes_count` and `last_seed_bootstrap` (unix time, `0` = never). ## Bans The in-memory ban list holds IPv4 and IPv6 addresses in separate lists. ``` ban list # everything currently banned ban add [seconds] # default 900 ban delete ``` IPv6 addresses go in standard notation; brackets are optional on the console. ``` ban add 2001:db8::1 3600 ban add ::ffff:1.2.3.4 900 ``` An IPv4 ban covers both the IPv4 listener and IPv4-mapped clients arriving on the IPv6 listener. ## ZMQ publisher `--zmq-pub` (default `tcp://127.0.0.1:17857`) publishes new block and transaction events for downstream services — explorers, wallets, anything that would otherwise poll. An empty string, or `--no-zmq`, turns it off. The endpoint goes straight to libzmq, so `ipc://` endpoints work: ```bash Wrkzd --zmq-pub ipc:///run/wrkz/wrkzd.zmq ``` Messages are sent as **two ZMQ frames**: the topic, then a JSON payload. Subscribe to a topic by its exact name, or to an empty prefix for everything. | Topic | Fires on | Payload | | --- | --- | --- | | `hashblock` | Every new main-chain block | `{"height":N,"hash":"..."}` | | `chain_main` | The same block, with its transaction list, for prefetching | `{"height":N,"hash":"...","transaction_hashes":["..."]}` | | `hashblock_alt` | A block added to an alternative chain | `{"height":N,"hash":"..."}` | | `chainswitch` | A reorganisation | `{"common_root_height":N,"hashes":["..."]}` | | `txpool_add` | Transactions entering the pool | `{"hashes":["..."]}` | | `txpool_del` | Transactions leaving the pool | `{"hashes":["..."],"reason":"InBlock\|Outdated\|NotActual"}` | `transaction_hashes` in `chain_main` lists the coinbase first, then the block's other transactions. !!! note "Two things to know before you build on it" - **A lite node publishes no `chain_main` for blocks below its lite height.** The transaction list needs the block body, and there is none down there. `hashblock` still fires for every block. - **Delivery is lossy by design.** The publisher sends non-blocking; when a subscriber cannot keep up, messages are dropped and counted, and the total is logged on shutdown (`dropped=`). Treat every message as a hint and reconcile through RPC. `scripts/zmq_sub_test.py` in the repository is a working subscriber to test against. The publisher is a build-time option: a daemon compiled without ZeroMQ ignores `--zmq-pub`. See `COMPILE.md`. For push notifications to a command or a webhook instead, see [Notification Hooks](notify-hooks.md). --- # Local IPC and Console Every listener that speaks HTTP can also be served over an **AF_UNIX socket** instead of, or alongside, a TCP port. Nothing is bound unless a path is given: IPC is off by default in all binaries. The point is not to avoid loopback but to change who decides. A TCP listener is reachable by every process on the machine and defends itself with a shared secret; a socket file is reachable only by whoever the mode on that file admits, and the kernel enforces it. !!! warning "POSIX only" Windows has had AF_UNIX since Windows 10 1803, but the socket file carries no permissions the OS enforces and there is no `SO_PEERCRED`, so the access control this rests on would not exist. The flags are accepted and ignored with a warning rather than opening an unrestricted endpoint. ## Serving | Binary | Option | Default | Meaning | | --- | --- | --- | --- | | `Wrkzd` | `--rpc-ipc-path` | empty | Also serve RPC on this socket, **alongside** the TCP listeners | | `Wrkzd` | `--rpc-ipc-mode` | `0600` | Octal permissions for the socket file | | `Wrkzd` | `--rpc-ipc-group` | empty | Group to own the socket file, for a `0660` shared setup | | `Wrkzd` | `--rpc-ipc-require-token` | `false` | Also demand `--rpc-access-token` from IPC callers | | `wrkz-wallet-api` | `--rpc-ipc-path` | empty | Also serve the API on this socket | | `wrkz-wallet-api` | `--rpc-ipc-mode` | `0600` | Octal permissions | | `wrkz-wallet-api` | `--rpc-ipc-group` | empty | Owning group | | `wrkz-service` | `--bind-ipc-path` | empty | Serve JSON-RPC on this socket **instead of** the TCP port | | `wrkz-service` | `--bind-ipc-mode` | `0600` | Octal permissions | | `wrkz-service` | `--bind-ipc-group` | empty | Owning group | A path must be absolute. Prefixing it with `@` uses the Linux **abstract namespace**, which has no filesystem entry and therefore no permissions at all — every process in the network namespace can connect, and the daemon warns when you ask for one. ```bash # Owner-only socket, the default Wrkzd --rpc-ipc-path /run/wrkz/wrkzd.sock # Shared with a group, so an unprivileged service account can reach the node Wrkzd --rpc-ipc-path /run/wrkz/wrkzd.sock --rpc-ipc-mode 0660 --rpc-ipc-group wrkz ``` ## Connecting Anywhere a daemon address is accepted, an absolute path, an `@name`, or an `ipc://path` means a local socket rather than a host. One spelling works across every client: ```bash Wrkzd --rpc-ipc-path /run/wrkz/wrkzd.sock miner --daemon-address /run/wrkz/wrkzd.sock --address WRKZ... wrkz-wallet --remote-daemon /run/wrkz/wrkzd.sock wrkz-service --daemon-address /run/wrkz/wrkzd.sock \ --container-file wallet --container-password hunter2 ``` A relative path has to be written as `ipc://./wrkzd.sock` and resolves against the client's working directory. Without the prefix it is indistinguishable from a hostname and is dialled as one. The daemon's own console uses the IPC socket automatically whenever one is bound, so `status`, `print_cn` and friends stop making loopback TCP connections to their own process. ## Authentication On the **daemon's** IPC socket `--rpc-access-token` is *not* required. The mode on the socket file already decided who may connect and the kernel enforced it, so the token adds nothing except an obligation to hand the secret to every local integration. Set `--rpc-ipc-require-token` to demand both. Rate limiting is skipped on IPC for the same reason it is skipped on loopback: the caller is neither anonymous nor remote. `wrkz-wallet-api` and `wrkz-service` **still require their password on IPC**. Those endpoints move money, and silently dropping an authentication step that was previously unconditional is not a change worth making by default. ## Attaching a console to a running daemon A daemon under a process manager — pm2, systemd, a container — has no terminal, so it runs with `--no-console` and its commands are out of reach. `attach` puts them back: it opens an interactive console against a daemon that is already running, over its IPC socket. Every line typed runs inside that daemon through the same command handler the local console uses, and whatever it prints comes back. ```bash pm2 start Wrkzd -- --no-console --rpc-ipc-path /run/wrkz/wrkzd.sock Wrkzd attach /run/wrkz/wrkzd.sock ``` `Wrkzd --attach /run/wrkz/wrkzd.sock` is the same thing. **The socket is the only endpoint accepted.** Console commands change log levels, ban peers, start compactions and stop the node, so they are served on the IPC socket alone and never on a TCP listener, with or without a token. Attach therefore runs as a user the socket admits, which by default means the daemon's own. | In an attached session | Effect | | --- | --- | | `exit`, `quit` | Leave the session. The daemon carries on | | `stop` | Shut the daemon down. Same command as the local console's `exit` | `stop` exists under a second name precisely so that leaving an attached session is never confused with stopping the node. Commands run one at a time across all consoles, local and attached, and a command that blocks — `compact_db wait` — holds its connection until it finishes. ### The route underneath One route: `POST /console` with `{"command": "..."}`, answering `{"output": "...", "status": "OK"}`. It is registered on the IPC listener only. Scripts can call it directly, or pipe commands into `attach`, which exits when its input ends. ```bash echo "status" | Wrkzd attach /run/wrkz/wrkzd.sock ``` ## Behaviour and guarantees - **Permissions are in place before the first client can connect.** The socket is created under a umask derived from the requested mode rather than `chmod`'ed afterwards, so there is no window in which it is reachable more widely than asked for. - **A stale socket from a crashed run is cleared automatically.** A path that exists but is not a socket is never removed, and a path another process is still listening on is refused rather than stolen. - **The socket file is unlinked on shutdown.** - **A failed IPC bind is not fatal** for `Wrkzd` or `wrkz-wallet-api` — they warn and keep serving on TCP, matching how an IPv6 bind failure is handled. For `wrkz-service`, where IPC *replaces* the TCP port, a failed bind stops startup. - Socket paths are limited to 107 bytes by the kernel, not by us. - P2P is not offered over IPC: a local socket cannot carry a peer-to-peer network. --- # Notification Hooks Monero-style push hooks, so a service learns about a block or a transaction without polling. Each flag takes **either** an `http://` / `https://` URL — the event is POSTed as a JSON object — **or** a command template, run directly *without a shell*. Empty means disabled. In a command template, quotes group arguments, `%`-placeholders are substituted per argument, and `%%` is a literal percent. ## Daemon (`Wrkzd`) | Option | Fires on | Placeholders | JSON body | | --- | --- | --- | --- | | `--block-notify ` | Every new main-chain block, and each replacement block after a reorg | `%s` block hash, `%h` height | `{"event":"block","height":N,"hash":"..."}` | | `--reorg-notify ` | Every chain reorganisation | `%s` split height, `%h` new height, `%n` new blocks, `%d` discarded blocks | `{"event":"reorg","split_height":N,"new_height":N,"new_blocks":N,"discarded_blocks":N}` | | `--tx-notify ` | Every transaction entering the mempool | `%s` tx hash | `{"event":"tx","hash":"..."}` | | `--notify-during-sync` | — | — | Also fire while the node is still synchronizing. Suppressed until synced by default | All four are also accepted in the daemon config file under the same names. ## Wallet service (`wrkz-service`) | Option | Fires on | Placeholders | | --- | --- | --- | | `--tx-notify ` | Every new wallet transaction when first seen: incoming (pool or block) and outgoing once sent | `%s` hash, `%h` height (`0` = unconfirmed), `%a` amount (atomic, signed), `%f` fee, `%p` payment id, `%c` confirmed `0`/`1` | | `--tx-confirmed-notify ` | Once per transaction, when it gets into a block | Same as above | | `--notify-during-sync` | — | Also fire for transactions more than 1440 blocks behind the daemon — i.e. during a rescan. Suppressed by default | JSON body: ```json { "event": "tx", "hash": "...", "height": 0, "amount": -1000000, "fee": 1000, "paymentId": "", "confirmed": false, "timestamp": 0, "unlockTime": 0 } ``` `"event"` is `"tx"` or `"tx_confirmed"`. ## Wallet API (`wrkz-wallet-api`) | Option | Fires on | Placeholders | | --- | --- | --- | | `--tx-notify ` | Every transaction **confirmed in a block** for the open wallet | `%s` hash, `%h` height, `%a` amount, `%f` fee, `%p` payment id | | `--notify-during-sync` | — | Same rescan suppression rule as the wallet service | wallet-api does not scan the pool for incoming transfers, so it has no unconfirmed event. The JSON body is the wallet service's plus `"isCoinbase"`, and `"confirmed"` is always `true`. ## Examples ```bash # Built-in webhook, no external tools needed Wrkzd --block-notify https://example.com/hooks/wrkz-block wrkz-service ... --tx-notify http://127.0.0.1:9000/tx \ --tx-confirmed-notify http://127.0.0.1:9000/tx-confirmed # Monero-compatible command form Wrkzd --block-notify "/usr/local/bin/on-block.sh %s" Wrkzd --block-notify "curl -s -X POST https://example.com/hook -d hash=%s -d height=%h" ``` ## Behaviour and guarantees - **Never blocks the node or wallet.** Events are queued in memory and delivered from a dedicated worker thread; the daemon's dispatcher and the wallet's sync loop only enqueue. - **The queue is bounded** at 1024 entries. When a receiver is too slow, excess notifications are dropped and counted — `dropped=` is logged on shutdown. - **Timeouts.** Each delivery has a 10-second budget: hung commands are killed, slow HTTP calls aborted. Transport failures on webhooks get one retry; HTTP 4xx and 5xx responses are final. - **Fire and forget.** There is no persistent retry queue. Treat notifications as hints and reconcile through RPC — the same model as Monero and ZMQ. - **HTTPS webhooks need a build with OpenSSL.** Without it the hook is disabled with a warning at startup. - Commands run with the privileges of the daemon or wallet process, and inherit its stdout and stderr. --- # Lite Nodes A lite node stores **full block data only from a chosen height upward**. Below that height it keeps just the indexes later blocks actually need: key output info, spent key images, per-amount output counts and block headers. The block bodies, transaction records, payment ID index and timestamp indexes for those heights are never written. This is a different feature from `--prune`, and the two cannot be combined. | | `--prune` | `--lite` | | --- | --- | --- | | What it drops | Raw block bodies older than `--prune-depth` | Bodies, transaction records, payment IDs and timestamp indexes below `--lite-height` | | When | A pass at every startup, after a full sync | At write time, from the very first block | | Reversible | Yes, by resyncing | **No** — permanent for the database | | Wallet rescan below the cut | Works | Does not work | The in-repo long form, with storage measurements and design notes, is `LITENODE.md`. ## Usage ``` --lite Enable lite mode. Permanent for this database. --lite-height Height at and above which full block data is kept. Required. ``` A user who created a wallet at height 4,000,000 and wants a node just for it: ```bash Wrkzd --lite --lite-height 4000000 ``` That node syncs, mines, relays and validates exactly like a full node, and the wallet syncs against it normally. ## Who should run one | | | | --- | --- | | **A user**, own node for own wallet | **Yes.** This is what lite mode is for | | **An operator**, public node others connect to | **No — run a full node**, with `--prune` if disk is tight | | **An operator**, private or service node, wallets you control | **Yes**, good fit | Never make a lite node a seed node. ### Choosing H is the whole decision **Set `H` at or below your wallet's creation height.** If `H` is above it, the wallet's scan is clamped up to `H` and it will never see transactions between its creation height and `H`. The balance simply comes out too low. The wallet logs a warning and `/info` reports `lite_start_height`, but neither gets read when the number looks plausible. ``` H = min(earliest wallet scan height, current network top - 20160) ``` then round **down** by another 10-20k for margin. Err low: too low costs some disk, too high costs transactions you cannot see. Two things to accept before you start: - **`H` cannot be changed without deleting the data directory.** The daemon refuses to start on a mismatch rather than wiping anything, but the choice is one way. - **A lite node cannot serve a wallet older than its `H`.** Restoring an older seed later needs a full node, or a fresh lite node at a lower `H`. ### Why an operator usually should not A public lite node is a worse network citizen and a support liability: - It cannot serve blocks below `H`, so it cannot help new nodes bootstrap. Peers below `H` skip it as a sync source automatically. If everyone ran lite, nothing could sync. - You do not know how old your users' wallets are. Someone restoring a two-year old seed against your node is clamped to `H` and sees a wrong balance. That reads as lost funds, and it reaches you as a support ticket rather than a bug report. Lite mode suits an operator when **you control which wallets connect**: a service node behind a recently created hot wallet, a mining node (mining reads only `alreadyGeneratedCoins` and the last 100 block sizes, so history is irrelevant to it), or a validation and relay node on cheap disk. ### Lite mode and explorer mode are mutually exclusive `--daemon-mode explorer --lite` is refused at startup. Every explorer endpoint below `H` — `f_blocks_list_json`, `f_block_json`, `f_transaction_json`, `/queryblocksdetailed` — reads the transaction records lite mode drops, so it would return nothing for those heights rather than report an error. ## It saves disk, not bandwidth The node still downloads every block from 0 to `H` over P2P: the key output and key image tables have to be built from real block data. What changes is that most of what it downloads below `H` is used and discarded rather than written. That pass is cheaper than a normal sync — below the last compiled-in checkpoint the daemon already skips proof-of-work and ring-signature verification, and lite mode also skips the majority of the database writes — but it is still a full download measured in hours. Roughly 4,000 blocks/min on a modest box at low heights, slower through denser later blocks. To skip that download entirely, import a snapshot instead: see [Lite Node Snapshots](lite-snapshots.md). ## Running one **Startup refuses rather than repairs.** Every mismatch between the flags and what the database was built as exits without touching it. Deleting the data directory is always left to you. A daemon that will not start is telling you something, and the fix is never to add a force flag. | Combination | Result | | --- | --- | | `--lite` without `--lite-height` | Refused — there is no safe default | | `--lite --prune` | Refused — mutually exclusive | | `--lite --daemon-mode explorer` | Refused — explorer needs records lite drops | | `--lite` on a database synced in full | Refused — delete the data directory yourself | | No `--lite` on a lite database | Refused — it has no block bodies to serve | | `--lite-height H'` on a database built at `H` | Refused — `H` cannot be changed | | `--rewind-to-height` below `H` | Refused — those blocks cannot be undone | | A database from an older storage format | Refused — restart with `--resync`, the only thing that deletes a chain | **The 14-day rule is checked at handshake time**, not at startup, because the network's height is not knowable before then. If `H` is too close to the tip the daemon exits and names the highest value the network currently allows. The check is deliberately hard to trigger: once the node's own chain reaches `H + 20160` the question is settled from local data and no peer is consulted at all, and before that what is weighed is the *tallest* chain claimed across several peers, so no single peer can produce the verdict. ### Monitoring - `/info` reports `lite` and `lite_start_height`. - `status` shows `Lite Node`, and on a lite node `Full Block Data From`. While the node's own tip is still below `H` it also shows `Lite Sync Stage: Index only`, which is why `Block Version` reads `v0` for that part of the sync. - `print_cn` shows which peers are pruned. ## Changing the lite height `H` cannot be changed in place, but a database can be rebuilt at a different one: ```bash Wrkzd --resync --lite --lite-height ``` `--resync` deletes the chain and the peer state, and the rebuild starts at the new height. This is the supported way to move `H`, and the reason changing `--lite-height` on its own is refused: the destructive step has to be asked for rather than inferred. Raising `H` on an existing database — turning blocks between the old and new height from full into index-only — is a deletion pass rather than a download, and is not implemented. ## RPC behaviour - `/info` gains `lite` (bool) and `lite_start_height`. - `/info` reports `major_version` and `minor_version` as `0` while the node's own top block is still below `H`, which is most of a first sync. Reading them needs the block body, and there is none down there. Everything else in the response works normally. - `/getwalletsyncdata` and `/getrawblocks` **clamp** a start height below `lite_start_height` up to it, and drop any start timestamp. A wallet that asks from 0 is served from the lite height rather than fed an empty response forever. - `/get_global_indexes_for_range` **refuses** below `lite_start_height`. There is nothing sensible to clamp to — a silently narrowed range would read as "these heights hold no transactions", which is a different and wrong answer. - Peers asking for a block below the lite height get it reported in `missed_ids`, so they ask elsewhere instead of losing the connection. ## Wallet behaviour Every wallet built on `walletbackend` — `wrkz-wallet`, `wrkz-wallet-api`, the C API the desktop and mobile wallets use, and the WASM web wallet — reads `lite_start_height` from `/info` and acts on it. | The wallet | What happens | | --- | --- | | Has scanned nothing yet | Its scan height is floored to `lite_start_height`. A warning is logged; `status` names the floor | | Has scanned to a height at or above `lite_start_height` | Nothing changes. It carries on | | Has scanned to a height **below** `lite_start_height` | **Sync stops** | The third case is the one worth understanding. The daemon would answer from its lite height whatever the wallet asked for, so continuing would move the wallet's recorded position over blocks nobody ever looked at: the wallet would report itself synced while every transaction in the skipped stretch was missing. Stopping is the honest answer. Point that wallet at a full node, or rebuild the lite node at a lower `H`. Rescanning a wallet below `H` against a lite node does not work, for the same reason. --- # Lite Node Snapshots A [lite node](lite-node.md) still downloads every block from 0 to its lite height `H` to build the indexes it keeps below it — hours of work for data that is then mostly discarded. A snapshot is that index-only region as a portable file: export it once, import it into an empty database, and the node starts life already holding it. The in-repo long form, with the file format, sizing measurements and the test record, is `LITESNAPSHOT.md`. ## Trust A snapshot is a chain state somebody else computed. What stands between an importer and a fabricated one is a **digest compiled into the binary**: the header names a height and a digest, the whole payload is streamed and hashed during import, and it must match a digest in `LITE_SNAPSHOT_DIGESTS` for that height. A file whose digest is not in the binary is refused, whoever produced it. That is also why importing is restricted to **blessed heights**. The first is **4,000,000**. New ones are published roughly annually — otherwise the tail every importer has to sync afterwards grows without bound. An operator may still run a lite node at any `H` they like by syncing it themselves. The restriction is on importing, not on lite mode. ## Producing one A console command, following `compact_db`'s shape: it starts, reports, and can be cancelled, and the console stays usable while it runs. ``` snapshot_export [start [height] [path] | status | cancel] ``` With no arguments, `start` is assumed. See [Console Commands](console-commands.md#snapshots). The default output is `/wrkz-lite-base-h-v1.litesnap`. There is deliberately no timestamp in the name: identical content must produce an identical name, or nobody can tell two copies of the same snapshot apart from two different ones. It refuses to start when: - the chain tip is below `H + 20160`, the same margin lite mode enforces at handshake. The region has to be settled beyond any reorg; - the output file already exists; - free space is short — checked before the write, not discovered 4 GB in. **A full node can produce one too**, and should — otherwise making a snapshot would first require the days-long lite sync it exists to avoid. The export zeroes `transactionHash` on every key output below `H` and skips the tables lite mode drops, so a full node and a lite node at the same `H` emit the same bytes. !!! note "Export from a node at the tip" The walk holds a RocksDB snapshot for its duration, which pins SST files. On a node still syncing, the data directory grows for as long as the export runs. ## Importing one Two runs. The first imports and exits; the second is an ordinary lite node. ```bash Wrkzd --lite --lite-height 4000000 --import-lite-snapshot Wrkzd --lite --lite-height 4000000 ``` Check a copy before spending an import on it — this reads the header only, with no database, chain or core, and prints JSON: ```bash Wrkzd --snapshot-info ``` **Why it exits rather than carrying straight on.** The import runs after the core has loaded, because that load writes the genesis block in full — raw block, base transaction and all — and a snapshot carries none of it. But by then the core has cached what it believes the chain's shape to be, and the import has just changed it underneath. Restarting is cheap and settles the question; it is also what `--import-blockchain` already does. ### What happens, in order **Refusals first, before the file is read twice:** 1. The database must hold nothing but genesis. 2. The genesis hash in the header must be this network's. 3. The header's height must equal `--lite-height`. 4. The header's digest must appear in `LITE_SNAPSHOT_DIGESTS`. **Then a verifying pass that writes nothing:** 5. The payload is streamed and hashed, and must hash to the digest the header claims. 6. Every record must belong to a table a snapshot may carry. 7. Block info must be complete — exactly `H` records, none twice, none at or above `H` — monotonic in cumulative difficulty, generated coins and transaction count, and must match every compiled-in checkpoint below `H`. 8. The record counts and the final transaction count must match the header. Nothing reaches the database until all of that passes. A digest can only be confirmed once the whole payload has been read, so writing as it went would mean a failed check leaves a half-imported chain that looks whole — which is the failure this design exists to prevent. The second read costs about a minute of decompression. **Then a writing pass:** 9. Key images and key output info go through `SstFileWriter` and `IngestExternalFile` — one write of about the file's size, rather than the 25 to 30 GB the normal write path amplifies to. This is the difference between half an hour and several hours on a laptop. 10. Block info goes through the ordinary write path, so the result matches a synced database rather than merely working. Block 0 is skipped: genesis is already there, in full. 11. The per-amount output tables are derived from the key outputs and cross-checked against the header's amount count, because an off-by-one there silently corrupts the global index of every output written afterwards. 12. The transaction count is taken from the header. After the restart the node is an ordinary lite node that happens to already hold its index region. It validates, relays and mines exactly as one that synced the region itself, and every wallet behaviour in [Lite Nodes](lite-node.md) applies unchanged. ## Sizing Key outputs are the overwhelming majority of a snapshot. `--snapshot-stats` reports per-table storage on a synced node, which is how to size one before producing it. ## The desktop wallet The desktop wallet can import a snapshot from its setup dialog when it manages a local node for you, so a user never types either command. See [Desktop, Mobile and Web Wallets](wallet-apps.md). --- # Solo Mining `Wrkzd` can hand work straight to a mining program, so solo mining needs no pool and no bridge. Start the node with a stratum port and point a miner at it. ```bash Wrkzd --stratum-bind-port 17858 ``` ```bash xmrig -o 127.0.0.1:17858 -a cn/upx2 -u ``` The address you give the miner is the address the block reward is paid to. The node validates it at login and refuses anything it cannot pay. The current proof-of-work algorithm is `cn_upx` (block major version 7), which is xmrig's `cn/upx2`. The algorithm for each block version is listed in `HASHING_ALGORITHMS_BY_BLOCK_VERSION` in `src/config/CryptoNoteConfig.h`. ## Why not `xmrig --daemon` xmrig's own solo mode reads the block template itself, and it expects Monero's layout: major version, minor version, timestamp, previous hash, nonce. Ours is Forknote lineage, where the outer header is major, minor and previous hash, and the real timestamp and nonce live inside a merge-mining parent block after it. xmrig misreads the first bytes of the previous hash as a timestamp and gives up with `Invalid block template received from daemon.` No daemon setting changes that; the blob shape is consensus. Stratum sidesteps it entirely. What a miner hashes is the parent block's hashing serialization, which is an ordinary 76-byte CryptoNote blob with a four-byte nonce at offset 39 — exactly where every stratum miner already writes one. The node does the block assembly, so the miner never sees a block at all. ## Options | Option | Default | Meaning | | --- | --- | --- | | `--stratum-bind-port` | `0` | Port to listen on. `0` leaves the server off | | `--stratum-bind-ip` | `127.0.0.1` | Interface to listen on. Set `0.0.0.0` to accept rigs from the network | | `--stratum-share-difficulty` | `0` | Difficulty given to miners. `0` uses the network difficulty | | `--stratum-max-connections` | `32` | Miners allowed on at once | !!! danger "There is no password and no account" Anyone who can reach the port can mine to their own address using this node. That is why it listens on loopback unless you say otherwise. Opening it to the internet lets strangers spend your node's CPU building templates, so put it behind a firewall or a VPN if it has to leave the machine, and cap it with `--stratum-max-connections`. ### Share difficulty By default a miner is given the network difficulty, so it reports only when it has actually found a block. That is what solo mining means, but it also means a slow machine can run for days showing nothing, with no way to tell working from broken. Setting `--stratum-share-difficulty` lower makes the miner report shares it can actually find. Those are counted and logged; a block still needs the real network difficulty, which the node checks itself before submitting. Use it to confirm a rig is hashing correctly, then take it back off. ## What the node logs ``` Stratum server listening on 127.0.0.1:17858 Stratum miner connected from 127.0.0.1 (XMRig/6.26.0) mining to Wrkz... Stratum miner 127.0.0.1 found block 7e6a9e92... at height 1402731 ``` A miner running the wrong algorithm is told so rather than left to look unlucky: the node compares the hash the miner reports against its own and answers `Invalid result`, naming the algorithm the current block version wants. While the node is still synchronizing it refuses to hand out work, saying how far behind it is. Mining on a chain the network has moved past would only produce orphans. ## Driving getblocktemplate yourself If you are writing a pool rather than using the stratum server, note that **the template is not ready to hash as returned**: the node leaves the parent block's merge-mining tag as a placeholder and expects the miner to seal it. Skip that and every block you submit is rejected as "Proof of work is too weak", however good the work was. The full procedure is in [JSON-RPC Methods → Before you mine the template](../daemon-rpc/json-rpc.md#before-you-mine-the-template), and `adjustMergeMiningTag()` in `src/miner/MinerManager.cpp` is the reference implementation. `getblocktemplate` takes the reserved space either way round — `reserve_size` for a pool that fills the bytes itself, or `extra_nonce` for a Monero-lineage solo miner that sends the bytes to embed. ## Solo-mining RPC aliases `/getinfo` and `/getheight` are served as aliases of `/info` and `/height`, under the spelling Monero-lineage tooling expects. A solo miner polls `/getheight` first and falls back to `/getinfo` when the answer carries no `hash` member, which is how it tells a CryptoNote daemon from a Monero one — so `/getheight` deliberately answers without one. `/json_rpc` echoes the request `id` back, as those clients require. ## The bundled miner `miner` still works and needs no stratum port, but it is a plain reference implementation with no assembly path. Its full option list is in [The Miner App](miner.md): ```bash miner --daemon-address 127.0.0.1:17856 --address --threads 8 ``` It also accepts a local IPC socket as its `--daemon-address`; see [Local IPC and Console](ipc-and-console.md). --- # Security Hardening What to lock down before a node, wallet API or wallet service is reachable by anybody but you, and why each control matters. ## Network exposure - Bind daemon RPC to localhost unless remote access is required. - Bind wallet-api to localhost unless behind a trusted reverse proxy. - Restrict inbound access using host firewall/security groups. - The stratum server (`--stratum-bind-port`) is off by default and binds to loopback when enabled. It has no account and no password: anyone who reaches the port can mine to their own address using this node's CPU to build templates. Put it behind a firewall or VPN before moving it off loopback, and cap it with `--stratum-max-connections`. - The transaction PoW server (`wrkz-txpow-server`) also binds loopback by default. It never sees a key or a signature, but it does see the transaction prefix and the client's address shortly before broadcast, so treat it as something you run for your own wallets. See [Transaction PoW Server](txpow-server.md). ## Prefer a local socket over loopback where you can On POSIX, `--rpc-ipc-path` serves the same RPC over an AF_UNIX socket whose file mode decides who may connect — kernel-enforced, rather than a shared secret every process on the box could read out of a config file. It runs alongside the TCP listener, so an integration on the same machine can move to it without changing anything else. Console commands are served **only** there. See [Local IPC and Console](ipc-and-console.md). ## Authentication baseline Daemon RPC: - Set `--rpc-access-token`. - Require either `X-API-Key` or `Authorization: Bearer`. - On the daemon's IPC socket the token is not required by default, because the socket's mode already decided who may connect. Add `--rpc-ipc-require-token` if you want both. Wallet API: - Always set `--rpc-password`. - Require `X-API-KEY` on every call. Wallet Service JSON-RPC: - Keep legacy security disabled. - Require JSON request `password` field. `wrkz-wallet-api` and `wrkz-service` require their password on an IPC socket too. Those endpoints move money, so the authentication step is never dropped. ## Transport security - If exposing externally, terminate TLS at reverse proxy (Nginx/Caddy/HAProxy). - Restrict methods/paths at proxy where possible. - Enable request size limits at proxy and app layers. ## Secrets handling - Do not commit API tokens/passwords in scripts or repositories. - Use environment variables or secret manager. - Rotate RPC tokens/passwords regularly. ## Operational controls - Keep rate limits enabled (`rpc-max-rpm`). - Keep body size limits enabled (`rpc-max-body-bytes`). - Monitor unauthorized attempts (`401`) and throttling (`429`). - Disable CORS or scope it to trusted origin(s), avoid `*` in production. ## A note on lite nodes Do not run a [lite node](lite-node.md) as a public node. It cannot serve blocks below its lite height, so it cannot help new nodes bootstrap, and a user restoring an older seed against it is clamped to that height and sees a balance that reads too low. That arrives as a support ticket about missing funds, not as a bug report. Use `--prune` if disk is the problem. ## Minimal production checklist 1. RPC bound to private interface, or to a local socket. 2. Auth enabled for every RPC surface reachable over TCP. 3. TLS at ingress. 4. Firewall allowlist in place, including the stratum port if it is on. 5. Logs monitored for auth failures and abuse. 6. Backup and restore procedure tested. --- # Rate Limits and Performance The knobs that decide how much work one client can ask a node for, and how much of that work the node has to redo. Most matter only on a node serving wallets it does not own. ## Daemon RPC controls Primary knobs (daemon config/CLI): - `rpc-max-rpm`: per-IP request cap (0 disables). - `rpc-max-body-bytes`: max request body bytes. - `rpc-max-global-index-range`: cap for `/get_global_indexes_for_range`. - `rpc-max-block-count`: cap for block sync/raw block methods. - `rpc-sync-cache-size`: megabytes of finished wallet sync responses to keep (0 disables). - `rpc-read-timeout` and `rpc-write-timeout`. - `db-read-buffer-size`: the database read cache. An eighth of it is used as a row cache, which answers repeated point lookups without touching a data block. ## Response compression Whether the daemon compresses its responses is decided when it is **built**, not configured: `cmake` looks for zlib and only enables it if it is there. Without it, every syncing wallet pulls several times as many bytes. Check it with: ```bash curl -s "$DAEMON_RPC_URL/info" | grep -o '"compression":"[^"]*"' ``` `"none"` means the build found no zlib. Install the zlib development package (`zlib1g-dev` on Debian/Ubuntu, `zlib-devel` on Fedora/RHEL) and rebuild. The daemon also prints a warning on startup when it is missing. ## Serving many wallets `rpc-sync-cache-size` (default 64 MB) keeps finished `/getwalletsyncdata` bodies around. Wallets syncing past the same height ask for the same range, so without it a node rebuilds the identical answer once per wallet: thousands of database reads, a full reassembly, and the encoding of the result, every time. Only ranges well below the chain tip are cached, so a reorganisation cannot make a cached body wrong; the cache is dropped outright if the tip ever moves backwards. Raise it on a public node serving many wallets, or set it to `0` on a node that only serves one. ## Recommended starting points - `rpc-max-rpm`: `240` for public-but-controlled access, higher for private LAN. - `rpc-max-body-bytes`: keep low, raise only for real payload needs. - `rpc-max-global-index-range`: keep default unless client genuinely needs larger windows. - `rpc-max-block-count`: keep conservative; paginate client requests instead. ## Client-side performance patterns - Prefer incremental sync calls over very large batch requests. - Use checkpoint-based sync (`blockHashCheckpoints`) for wallet sync. - Implement retry with backoff on `429`. - Read `sync_features` from `/info` once and use the optional sync fields the daemon advertises — `skipEmptyBlocks`, `encoding: "base64"` and `endHeight` — rather than sending them blind. See [HTTP Endpoints](../daemon-rpc/http-endpoints.md#optional-sync-fields). ## Example: pagination instead of huge blockCount ```bash for h in 0 100 200 300; do curl -s -H "Content-Type: application/json" \ -d "{\"startHeight\":$h,\"startTimestamp\":0,\"blockCount\":100,\"blockHashCheckpoints\":[],\"skipCoinbaseTransactions\":false}" \ "$DAEMON_RPC_URL/getwalletsyncdata" > /dev/null done ``` ## Example: observe throttling quickly ```bash for i in $(seq 1 500); do curl -s -o /dev/null -w "%{http_code}\n" "$DAEMON_RPC_URL/info" done | sort | uniq -c ``` ## Wallet API tuning notes - `--threads` affects wallet sync worker count. - Higher thread counts can improve sync but increase CPU and memory pressure. - Keep daemon and wallet API near each other (low RTT) for best sync behavior. --- # Daemon RPC Overview What the daemon serves, on which listeners, and which options control them. Implementation: `src/rpc/RpcServer.cpp`, `src/rpc/RpcServer.h` The daemon exposes: - JSON-RPC at `/json_rpc` (GET and POST) - HTTP-style endpoints (`/info`, `/height`, and others) - The same surface over a local AF_UNIX socket when `--rpc-ipc-path` is given, plus `/console`, which is served **only** there. See [Local IPC and Console](../guides/ipc-and-console.md) - A stratum server for miners, off unless `--stratum-bind-port` is given. It is a separate TCP listener, not part of the RPC surface. See [Solo Mining](../guides/solo-mining.md) RPC availability depends on the daemon RPC mode, chosen with `--daemon-mode ` (`RpcMode` in `src/rpc/RpcServer.h`): - `Standard` — the default. Everything except the block explorer methods. - `Explorer` — additionally serves the explorer routes, which walk database indexes and are not something a plain node should answer for anyone who asks. Permissions are enforced in middleware before handlers run. ## Main runtime options Defined in `src/daemon/DaemonConfiguration.h` and parsed in `src/daemon/DaemonConfiguration.cpp`: - `rpc-bind-ip` - `rpc-bind-port` - `rpc-access-token` - `rpc-read-timeout` - `rpc-write-timeout` - `rpc-max-body-bytes` - `rpc-max-rpm` - `rpc-max-global-index-range` - `rpc-max-block-count` - `rpc-sync-cache-size` - `rpc-trust-proxy` - `rpc-ipc-path` - `daemon-mode` The full option list, with every default, is in the [Configuration Reference](../guides/daemon-configuration.md). See the other daemon RPC pages for auth, limits, and method lists. ## On a lite node A [lite node](../guides/lite-node.md) answers `/info` with `lite` and `lite_start_height`, clamps `/getwalletsyncdata` and `/getrawblocks` requests up to that height, and **refuses** `/get_global_indexes_for_range` below it. Lite mode and explorer mode cannot be combined. ## Quick Examples Set shared environment variables: ```bash export DAEMON_RPC_URL="http://127.0.0.1:17856" export DAEMON_RPC_TOKEN="replace-me" ``` Get daemon info: ```bash curl -s "$DAEMON_RPC_URL/info" ``` Get peers with token auth: ```bash curl -s \ -H "X-API-Key: $DAEMON_RPC_TOKEN" \ "$DAEMON_RPC_URL/peers" ``` Call JSON-RPC (`getblockcount`): ```bash curl -s \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"1","method":"getblockcount","params":{}}' \ "$DAEMON_RPC_URL/json_rpc" ``` --- # Daemon RPC Auth and Security How the daemon authenticates an RPC caller and what it does to one that asks for too much. Implementation: `src/rpc/RpcServer.cpp`, `src/daemon/DaemonConfiguration.cpp` ## Authentication If `rpc-access-token` is set, every request must provide: - `X-API-Key: ` or - `Authorization: Bearer ` Otherwise daemon returns `401 Unauthorized`. If `rpc-access-token` is empty, token auth is disabled. ### On the local IPC socket When RPC is also served on an AF_UNIX socket (`--rpc-ipc-path`), the token is **not** required there and rate limiting is skipped. The mode on the socket file already decided who may connect and the kernel enforced it. Set `--rpc-ipc-require-token` to demand both. The `/console` route is registered on that socket only, never on a TCP listener, token or no token. See [Local IPC and Console](../guides/ipc-and-console.md). ## Request limits and protection Current protections in middleware: - Max request body size: `rpc-max-body-bytes` (returns `413` on overflow) - Rate limit per remote IP: `rpc-max-rpm` (returns `429` when exceeded) - Optional proxy trust behavior: `rpc-trust-proxy` - Method permission checks by RPC mode (returns `403` when blocked) Domain-specific bounds: - `rpc-max-block-count` for block sync/raw block queries - `rpc-max-global-index-range` for global index range queries ## CORS When CORS is configured, daemon sets `Access-Control-Allow-Origin`. ## Auth Header Examples Using `X-API-Key`: ```bash curl -s \ -H "X-API-Key: $DAEMON_RPC_TOKEN" \ "$DAEMON_RPC_URL/info" ``` Using Bearer token: ```bash curl -s \ -H "Authorization: Bearer $DAEMON_RPC_TOKEN" \ "$DAEMON_RPC_URL/info" ``` Unauthorized request example (expected `401`): ```bash curl -i -s "$DAEMON_RPC_URL/info" ``` Body too large example (expected `413`): ```bash python - <<'PY' import requests url = "http://127.0.0.1:17856/sendrawtransaction" big = {"tx_as_hex": "aa" * 10_000_000} resp = requests.post(url, json=big) print(resp.status_code) print(resp.text[:200]) PY ``` Rate-limit behavior example (expected some `429` when exceeded): ```bash for i in $(seq 1 400); do curl -s -o /dev/null -w "%{http_code}\n" "$DAEMON_RPC_URL/info" done | sort | uniq -c ``` --- # Daemon JSON-RPC Methods Every method the daemon answers on `/json_rpc`, with the RPC mode each needs and a worked example. Implementation mapping: `src/rpc/RpcServer.cpp` method switch in `/json_rpc` dispatcher. `/json_rpc` accepts JSON with a `method` field. Current methods: | Method | Handler | Permission Mode | | --- | --- | --- | | `getblocktemplate` | `getBlockTemplate` | `Standard` | | `submitblock` | `submitBlock` | `Standard` | | `getblockcount` | `getBlockCount` | `Standard` | | `getlastblockheader` | `getLastBlockHeader` | `Standard` | | `getblockheaderbyhash` | `getBlockHeaderByHash` | `Standard` | | `getblockheaderbyheight` | `getBlockHeaderByHeight` | `Standard` | | `f_blocks_list_json` | `getBlocksByHeight` | `Explorer` | | `f_block_json` | `getBlockDetailsByHash` | `Explorer` | | `f_transaction_json` | `getTransactionDetailsByHash` | `Explorer` | | `f_on_transactions_pool_json` | `getTransactionsInPool` | `Explorer` | | `f_transactions_by_payment_id_json` | `getTransactionHashesByPaymentId` | `Explorer` | Unknown methods return `404`. ## Request shape Example: ```json { "jsonrpc": "2.0", "id": "1", "method": "getblockcount", "params": {} } ``` Use `X-API-Key` or `Authorization: Bearer` header when `rpc-access-token` is enabled. ## Response shape The response carries back the `id` of the request it answers, as JSON-RPC 2.0 requires, in whatever type it was sent: ```json { "id": "1", "jsonrpc": "2.0", "result": { "count": 4207131, "status": "OK" } } ``` A request that sends no `id` gets an answer without one. Errors carry the same `id` alongside an `error` object, and still return HTTP `200`: ```json { "id": "1", "jsonrpc": "2.0", "error": { "code": -4, "message": "..." } } ``` ## Method Examples All examples call `POST /json_rpc`. `getblockcount`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"1","method":"getblockcount","params":{}}' \ "$DAEMON_RPC_URL/json_rpc" ``` `getlastblockheader`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"2","method":"getlastblockheader","params":{}}' \ "$DAEMON_RPC_URL/json_rpc" ``` `getblockheaderbyhash`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"3","method":"getblockheaderbyhash","params":{"hash":""}}' \ "$DAEMON_RPC_URL/json_rpc" ``` `getblockheaderbyheight`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"4","method":"getblockheaderbyheight","params":{"height":12345}}' \ "$DAEMON_RPC_URL/json_rpc" ``` `getblocktemplate`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"5","method":"getblocktemplate","params":{"reserve_size":8,"wallet_address":""}}' \ "$DAEMON_RPC_URL/json_rpc" ``` `params` takes the reserved space either way round: - `reserve_size` — how many bytes to leave blank in the miner transaction's extra. The reply's `reserved_offset` says where they are, and the caller fills them in. This is what a pool wants. - `extra_nonce` — hex bytes to embed directly, in which case the node reserves exactly that much and writes them for you. This is what Monero-lineage solo miners send, and they send no `reserve_size` at all. Sending neither gets a template with no reserved bytes. Sending both uses `extra_nonce`. Maximum 255 bytes either way. The reply carries `blocktemplate_blob`, `difficulty`, `height`, `reserved_offset` and `status`. ### Before you mine the template **The template is not ready to hash as returned.** For every block version above 1 the node appends a *placeholder* merge-mining tag to the parent block's coinbase and does not fill it in — whoever mines the block is expected to. Set `mmTag.depth = 0` and `mmTag.merkleRoot` to the block's auxiliary header hash (`CachedBlock::getAuxiliaryBlockHeaderHash()`, equivalently `getMerkleRoot()` in `src/miner/BlockUtilities.cpp`), clear the parent coinbase's extra, and write the tag back. `adjustMergeMiningTag()` in `src/miner/MinerManager.cpp` is the reference implementation. Skip this and `submitblock` rejects the finished block with **"Proof of work is too weak"**, no matter how good the proof of work was. That error covers four distinct failures in `Currency::checkProofOfWorkV2` — genuinely weak work, a missing merge-mining tag, an over-long blockchain branch, and this mismatch — and only the first is about difficulty. The daemon log tells them apart: the tag failures print `Aux block hash wasn't found in merkle tree` or `merge mining tag wasn't found ...` just before the rejection, and genuinely weak work prints nothing extra. Do this before computing the hashing blob: the tag lives in the parent coinbase, and the parent's merkle root is part of what gets hashed. Rolling the nonce afterwards is safe — the nonce is not part of what the tag commits to. `submitblock`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"6","method":"submitblock","params":[""]}' \ "$DAEMON_RPC_URL/json_rpc" ``` `f_blocks_list_json`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"7","method":"f_blocks_list_json","params":{"height":100000}}' \ "$DAEMON_RPC_URL/json_rpc" ``` `f_block_json`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"8","method":"f_block_json","params":{"hash":""}}' \ "$DAEMON_RPC_URL/json_rpc" ``` `f_transaction_json`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"9","method":"f_transaction_json","params":{"hash":""}}' \ "$DAEMON_RPC_URL/json_rpc" ``` `f_on_transactions_pool_json`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"10","method":"f_on_transactions_pool_json","params":{}}' \ "$DAEMON_RPC_URL/json_rpc" ``` Unknown method example (`404`): ```bash curl -i -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"404","method":"does_not_exist","params":{}}' \ "$DAEMON_RPC_URL/json_rpc" ``` --- # Daemon HTTP Endpoints Every non-JSON-RPC route the daemon serves, including the wallet sync endpoints and their optional fields. Implementation mapping: route registrations in `src/rpc/RpcServer.cpp`. ## GET | Path | Handler | Permission Mode | | --- | --- | --- | | `/json_rpc` | JSON-RPC dispatcher | Depends on method | | `/info` | `info` | `Standard` | | `/height` | `height` | `Standard` | | `/peers` | `peers` | `Standard` | | `/getinfo` | `info` | `Standard` | | `/getheight` | `height` | `Standard` | `/getinfo` and `/getheight` are aliases of `/info` and `/height`, serving the same handlers under the spelling Monero-lineage tooling expects. A solo miner polls `/getheight` first and falls back to `/getinfo` when the answer carries no `hash` member, which is how it tells a CryptoNote daemon from a Monero one — so `/getheight` deliberately answers without one. ## POST | Path | Handler | Permission Mode | | --- | --- | --- | | `/json_rpc` | JSON-RPC dispatcher | Depends on method | | `/sendrawtransaction` | `sendTransaction` | `Standard` | | `/getrandom_outs` | `getRandomOuts` | `Standard` | | `/getwalletsyncdata` | `getWalletSyncData` | `Standard` | | `/get_global_indexes_for_range` | `getGlobalIndexes` | `Standard` | | `/queryblockslite` | `queryBlocksLite` | `Standard` | | `/get_transactions_status` | `getTransactionsStatus` | `Standard` | | `/get_pool_changes_lite` | `getPoolChanges` | `Standard` | | `/queryblocksdetailed` | `queryBlocksDetailed` | `Explorer` | | `/get_o_indexes` | `getGlobalIndexesDeprecated` | `Standard` | | `/getrawblocks` | `getRawBlocks` | `Standard` | | `/console` | `console` | `Standard`, local socket only | `/console` runs daemon console commands and is registered **only** on the IPC socket (`--rpc-ipc-path`), never on a TCP listener, token or no token. The mode on the socket file decides who may connect. ## OPTIONS - `.*` handled for CORS preflight support. ## GET Examples ```bash curl -s "$DAEMON_RPC_URL/info" curl -s "$DAEMON_RPC_URL/height" curl -s "$DAEMON_RPC_URL/peers" ``` `/info` reports the node's own state: `height`, `top_block_hash`, `network_height`, `difficulty`, `hashrate`, peer counts, `synced`, the lite node fields (`lite`, `lite_start_height`), `compression` and `sync_features`. ## POST Examples `/sendrawtransaction`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"tx_as_hex":""}' \ "$DAEMON_RPC_URL/sendrawtransaction" ``` `/getrandom_outs`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"amounts":[10000,50000],"outs_count":5}' \ "$DAEMON_RPC_URL/getrandom_outs" ``` `/getwalletsyncdata`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"blockHashCheckpoints":[],"startHeight":0,"startTimestamp":0,"blockCount":100,"skipCoinbaseTransactions":false}' \ "$DAEMON_RPC_URL/getwalletsyncdata" ``` ### Optional sync fields All of these are opt in, and a request that omits them behaves exactly as it always has. A client should only send one after seeing the daemon name it in `sync_features` from `/info` — an older daemon ignores what it does not know, which for these would mean silently getting a different answer than expected. | Request field | Feature name | Effect | | --- | --- | --- | | `skipEmptyBlocks` | `skipEmptyBlocks` | Leaves out blocks holding nothing but a coinbase. Only takes effect together with `skipCoinbaseTransactions`, since otherwise the coinbase is wanted. One response can then carry a wallet across far more heights than its `blockCount`. | | `encoding` | `base64` | `"hex"` (default) or `"base64"`. Hashes and keys are 44 characters as base64 against 64 as hex. | | `endHeight` | `heightRange` | Exclusive upper bound on the heights the response may cover. Lets a client name several windows before it has seen any of the answers, and fetch them at once. `0` or absent means unbounded. | | Response field | Meaning | | --- | --- | | `scannedToHeight` | The highest height the daemon actually looked at, which is not the same as the highest block it sent — the ones in between held nothing worth sending. A client may resume from `scannedToHeight + 1`. Absent when the daemon cannot vouch for a whole window, in which case only advance as far as the last block received. | Because blocks are left out rather than the range being cut short, the heights in a response are not contiguous when `skipEmptyBlocks` is on. A client that treats a gap as a chain fork must not ask for it. `/get_global_indexes_for_range`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"startHeight":1000,"endHeight":1100}' \ "$DAEMON_RPC_URL/get_global_indexes_for_range" ``` `/queryblockslite`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"blockIds":[""],"timestamp":0}' \ "$DAEMON_RPC_URL/queryblockslite" ``` `/get_transactions_status`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"transactionHashes":["",""]}' \ "$DAEMON_RPC_URL/get_transactions_status" ``` `/get_pool_changes_lite`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"tailBlockId":"","knownTxsIds":[]}' \ "$DAEMON_RPC_URL/get_pool_changes_lite" ``` `/queryblocksdetailed`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"blockIds":[""],"timestamp":0,"blockCount":20}' \ "$DAEMON_RPC_URL/queryblocksdetailed" ``` `/get_o_indexes`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"txid":""}' \ "$DAEMON_RPC_URL/get_o_indexes" ``` `/getrawblocks`: ```bash curl -s -H "Content-Type: application/json" \ -d '{"startHeight":0,"startTimestamp":0,"blockHashCheckpoints":[],"blockCount":20,"skipCoinbaseTransactions":false}' \ "$DAEMON_RPC_URL/getrawblocks" ``` --- # Daemon RPC Cookbook Working curl recipes for the daemon RPC, in the order an integration usually needs them. Every call is documented in full under Daemon RPC. Set shared vars: ```bash export DAEMON_RPC_URL="http://127.0.0.1:17856" export DAEMON_RPC_TOKEN="strong-token" ``` Optional auth header: ```bash AUTH_HEADER=(-H "X-API-Key: $DAEMON_RPC_TOKEN") ``` ## 1) Health and network checks ```bash curl -s "${AUTH_HEADER[@]}" "$DAEMON_RPC_URL/info" curl -s "${AUTH_HEADER[@]}" "$DAEMON_RPC_URL/height" curl -s "${AUTH_HEADER[@]}" "$DAEMON_RPC_URL/peers" ``` There is **no `/fee` route** on the daemon. Node fees are not implemented on this network: the wallet backend hard-codes the node fee address to empty and the amount to zero, so `getFeeInfo` and `getNodeFeeInfo` on [wallet-service JSON-RPC](../wallet-service-json-rpc/methods.md) always answer with an empty address and `0`. `/info` carries the node's own state — see [HTTP Endpoints](../daemon-rpc/http-endpoints.md). ## 2) Basic JSON-RPC calls ```bash curl -s "${AUTH_HEADER[@]}" -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"1","method":"getblockcount","params":{}}' \ "$DAEMON_RPC_URL/json_rpc" curl -s "${AUTH_HEADER[@]}" -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"2","method":"getlastblockheader","params":{}}' \ "$DAEMON_RPC_URL/json_rpc" ``` ## 3) Wallet sync pipeline (common client flow) Pull compact sync data: ```bash curl -s "${AUTH_HEADER[@]}" -H "Content-Type: application/json" \ -d '{"blockHashCheckpoints":[],"startHeight":0,"startTimestamp":0,"blockCount":100,"skipCoinbaseTransactions":false}' \ "$DAEMON_RPC_URL/getwalletsyncdata" ``` Alternative lightweight chain query: ```bash curl -s "${AUTH_HEADER[@]}" -H "Content-Type: application/json" \ -d '{"blockIds":[""],"timestamp":0}' \ "$DAEMON_RPC_URL/queryblockslite" ``` Detailed chain query (requires `--daemon-mode explorer`): ```bash curl -s "${AUTH_HEADER[@]}" -H "Content-Type: application/json" \ -d '{"blockIds":[""],"timestamp":0,"blockCount":20}' \ "$DAEMON_RPC_URL/queryblocksdetailed" ``` ## 4) Pool and tx status checks ```bash curl -s "${AUTH_HEADER[@]}" -H "Content-Type: application/json" \ -d '{"transactionHashes":["",""]}' \ "$DAEMON_RPC_URL/get_transactions_status" curl -s "${AUTH_HEADER[@]}" -H "Content-Type: application/json" \ -d '{"tailBlockId":"","knownTxsIds":[]}' \ "$DAEMON_RPC_URL/get_pool_changes_lite" ``` ## 5) Broadcast signed transaction ```bash curl -s "${AUTH_HEADER[@]}" -H "Content-Type: application/json" \ -d '{"tx_as_hex":""}' \ "$DAEMON_RPC_URL/sendrawtransaction" ``` ## 6) Mining-oriented methods ```bash curl -s "${AUTH_HEADER[@]}" -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"10","method":"getblocktemplate","params":{"reserve_size":8,"wallet_address":""}}' \ "$DAEMON_RPC_URL/json_rpc" curl -s "${AUTH_HEADER[@]}" -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"11","method":"submitblock","params":[""]}' \ "$DAEMON_RPC_URL/json_rpc" ``` --- # Wallet API RPC Overview What `wrkz-wallet-api` exposes and how a client is expected to drive it. Implementation: `src/walletapi/ApiDispatcher.cpp`, `src/walletapi/ApiDispatcher.h` Wallet API is an HTTP API (not JSON-RPC) with routes for: - Wallet lifecycle (`/wallet/*`, `/save`, `/reset`) - Address management (`/addresses/*`) - Transaction creation, sending, sweeping and query - Key operations (`/keys/*`) - Balance and node status (`/balance`, `/balances`, `/node`, `/status`) Fusion / optimize endpoints were **removed in 0.4.7**; consolidate inputs with `/transactions/send/sweep` and `/transactions/send/sweep/all` instead. See the [changelog](../changelog/wallet-api.md). Request handling goes through middleware with: - API key authentication - Wallet state checks (must be open/closed depending on endpoint) - View-wallet restrictions for sensitive operations Regex constants used in routes: - Address regex: `ApiConstants::addressRegex` - Hash regex: `ApiConstants::hashRegex` - Payment ID regex: `ApiConstants::paymentIDRegex` Defined in `src/walletapi/Constants.h`. ## Quick Flow Examples Set environment variables: ```bash export WALLET_API_URL="http://127.0.0.1:7856" export WALLET_API_KEY="replace-me" ``` Open wallet: ```bash curl -s -X POST "$WALLET_API_URL/wallet/open" \ -H "X-API-KEY: $WALLET_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filename":"wallet.wallet","password":"wallet-pass","daemonHost":"127.0.0.1","daemonPort":17856,"daemonSSL":false}' ``` Check status: ```bash curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/status" ``` Get total balance: ```bash curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/balance" ``` Close wallet: ```bash curl -s -X DELETE -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/wallet" ``` --- # Running Wallet API `wrkz-wallet-api` serves the HTTP wallet API described in [Endpoints](../wallet-api/endpoints.md). It holds one wallet open at a time and talks to a daemon over RPC. ## Required `--rpc-password` must be set (`src/walletapi/ParseArguments.cpp`). There is no default and no way to disable authentication. ## Options | Option | Default | Meaning | | --- | --- | --- | | `--rpc-password ` | none, **required** | Value clients must send as `X-API-KEY` | | `--rpc-bind-ip ` | `127.0.0.1` | IPv4 interface to listen on | | `-p, --port ` | `7856` | TCP port | | `--rpc-bind-ipv6-address ` | empty | IPv6 address for a second listener | | `--rpc-use-ipv6` | `false` | Start the IPv6 listener | | `--enable-cors ` | empty | `Access-Control-Allow-Origin` value | | `--threads ` | hardware concurrency | Wallet sync worker threads | | `--scan-coinbase-transactions` | `false` | Scan coinbase transactions during sync | | `--log-level <0-5>` | | Log verbosity | | `--log-file ` | | Log file | | `--no-console` | `false` | Do not start the interactive console | | `--rpc-ipc-path ` | empty | Also serve the API on this local socket. POSIX only | | `--rpc-ipc-mode ` | `0600` | Permissions for the socket file | | `--rpc-ipc-group ` | empty | Group to own the socket file | | `--tx-notify ` | empty | Fire on each transaction confirmed for the open wallet | | `--notify-during-sync` | `false` | Also fire during a rescan | !!! note "Default port collides with `wrkz-service`" `wrkz-wallet-api` and `wrkz-service` both default to `7856`. Give one of them a different port if you run both on one machine. ## Request authentication Send `X-API-KEY: ` on every request. The server compares a PBKDF2-HMAC-SHA256 hash of the supplied value. This applies on the IPC socket too — unlike the daemon, wallet-api never drops its password, because these endpoints move money. See [Auth and Security](../wallet-api/auth-and-security.md). ## Example ```bash ./wrkz-wallet-api \ --rpc-bind-ip 127.0.0.1 \ --port 7856 \ --rpc-password "strong-password" \ --threads 4 ``` Prefer binding to `127.0.0.1` unless you are behind a trusted reverse proxy, and scope `--enable-cors` to a real origin rather than `*`. ## Smoke tests ```bash curl -s -H "X-API-KEY: strong-password" http://127.0.0.1:7856/status curl -s -X POST http://127.0.0.1:7856/wallet/open \ -H "X-API-KEY: strong-password" \ -H "Content-Type: application/json" \ -d '{"filename":"wallet.wallet","password":"wallet-pass","daemonHost":"127.0.0.1","daemonPort":17856,"daemonSSL":false}' ``` `daemonHost` also accepts an IPv6 literal and, on POSIX, a local IPC socket path or an `ipc://` URL — see [Local IPC and Console](ipc-and-console.md). ## Against a lite node The wallet backend reads `lite_start_height` from the daemon's `/info`. A wallet that has already scanned *below* that height **stops syncing** rather than skipping blocks it can never see. See [Lite Nodes → Wallet behaviour](lite-node.md#wallet-behaviour). ## Notes - Route behaviour and wallet state checks live in `src/walletapi/ApiDispatcher.cpp`. - Fusion / optimize endpoints were removed in 0.4.7; use the sweep endpoints instead. See the [changelog](../changelog/wallet-api.md). - `wrkz-wallet-api` always computes transaction proof of work on its own CPU. The [Tx PoW server](txpow-server.md) setting exists only in the desktop, mobile and web wallets. --- # Wallet API Auth and Security How `wrkz-wallet-api` authenticates callers, and the wallet-state and view-only rules the middleware enforces before a handler runs. Implementation: `src/walletapi/ApiDispatcher.cpp`, `src/walletapi/ParseArguments.cpp` ## Authentication Wallet API requires `--rpc-password` at startup. Clients must send: - `X-API-KEY: ` The server derives and compares a PBKDF2-HMAC-SHA256 hash of the supplied value. Missing or invalid `X-API-KEY` returns `401`. ## CORS When `--enable-cors` is set, response includes: - `Access-Control-Allow-Origin: ` - `Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, X-API-KEY` ## Wallet state and permission checks Middleware enforces: - Wallet must be open for most operational endpoints - Wallet must be closed for open/create/import operations - Some endpoints are blocked for view-only wallets Common status codes: - `401` unauthorized - `403` wrong wallet state - `400` invalid request or restricted view-wallet operation ## Auth Examples Missing API key (`401`): ```bash curl -i -s "$WALLET_API_URL/status" ``` Valid API key: ```bash curl -s \ -H "X-API-KEY: $WALLET_API_KEY" \ "$WALLET_API_URL/status" ``` Invalid API key (`401`): ```bash curl -i -s \ -H "X-API-KEY: wrong-password" \ "$WALLET_API_URL/status" ``` CORS preflight: ```bash curl -i -s -X OPTIONS \ -H "Origin: https://example.com" \ -H "Access-Control-Request-Method: POST" \ -H "Access-Control-Request-Headers: X-API-KEY, Content-Type" \ "$WALLET_API_URL/wallet/open" ``` --- # Wallet API Endpoints Every route `wrkz-wallet-api` serves, by method, with a worked request for each. Implementation mapping: route registrations in `src/walletapi/ApiDispatcher.cpp`. Route placeholders: - `{address}` -> `ApiConstants::addressRegex` - `{hash}` -> `ApiConstants::hashRegex` - `{paymentId}` -> `ApiConstants::paymentIDRegex` ## POST - `/wallet/open` - `/wallet/import/key` - `/wallet/import/seed` - `/wallet/import/view` - `/wallet/create` - `/addresses/create` - `/addresses/import` - `/addresses/import/deterministic` - `/addresses/import/view` - `/addresses/validate` - `/transactions/send/prepared` - `/transactions/prepare/basic` - `/transactions/send/basic` - `/transactions/prepare/advanced` - `/transactions/send/advanced` - `/transactions/send/sweep` - `/transactions/send/sweep/all` - `/export/json` ## DELETE - `/wallet` - `/addresses/{address}` - `/transactions/prepared/{hash}` ## PUT - `/save` - `/reset` - `/node` - `/sync/refresh` ## GET - `/node` - `/keys` - `/keys/{address}` - `/keys/mnemonic/{address}` - `/status` - `/addresses` - `/addresses/primary` - `/addresses/{address}/{paymentId}` - `/transactions` - `/transactions/unconfirmed` - `/transactions/unconfirmed/{address}` - `/transactions/{startHeight}` - `/transactions/{startHeight}/{endHeight}` - `/transactions/address/{address}/{startHeight}` - `/transactions/address/{address}/{startHeight}/{endHeight}` - `/transactions/privatekey/{hash}` - `/transactions/hash/{hash}` - `/transactions/paymentid/{hash}` - `/transactions/paymentid` - `/balance` - `/balance/{address}` - `/balances` ## OPTIONS - `.*` wildcard handler for CORS preflight. ## Notes - `/transactions/paymentid/{hash}` currently uses `hashRegex` in route registration. - Some endpoints require a full wallet (not view-only); enforced in middleware. - Failures carry a numeric `errorCode`; see [Wallet Error Codes](../guides/error-codes.md). - **Fusion endpoints were removed in 0.4.7.** `/transactions/send/fusion/basic` and `/transactions/send/fusion/advanced` no longer exist and return `404`. Use the sweep endpoints instead — see the [changelog](../changelog/wallet-api.md). ## Example Requests All examples include: ```bash -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" ``` ### Wallet lifecycle Create wallet: ```bash curl -s -X POST "$WALLET_API_URL/wallet/create" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"filename":"new.wallet","password":"wallet-pass","daemonHost":"127.0.0.1","daemonPort":17856,"daemonSSL":false}' ``` Open wallet: ```bash curl -s -X POST "$WALLET_API_URL/wallet/open" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"filename":"new.wallet","password":"wallet-pass","daemonHost":"127.0.0.1","daemonPort":17856,"daemonSSL":false}' ``` Import from keys: ```bash curl -s -X POST "$WALLET_API_URL/wallet/import/key" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"filename":"import.wallet","password":"wallet-pass","privateViewKey":"","privateSpendKey":"","scanHeight":0,"daemonHost":"127.0.0.1","daemonPort":17856,"daemonSSL":false}' ``` Import from seed: ```bash curl -s -X POST "$WALLET_API_URL/wallet/import/seed" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"filename":"seed.wallet","password":"wallet-pass","mnemonicSeed":"<25-word-seed>","scanHeight":0,"daemonHost":"127.0.0.1","daemonPort":17856,"daemonSSL":false}' ``` Import view wallet: ```bash curl -s -X POST "$WALLET_API_URL/wallet/import/view" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"filename":"view.wallet","password":"wallet-pass","address":"
","privateViewKey":"","scanHeight":0,"daemonHost":"127.0.0.1","daemonPort":17856,"daemonSSL":false}' ``` Save, reset, close: ```bash curl -s -X PUT "$WALLET_API_URL/save" -H "X-API-KEY: $WALLET_API_KEY" curl -s -X PUT "$WALLET_API_URL/reset" -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" -d '{"scanHeight":0}' curl -s -X DELETE "$WALLET_API_URL/wallet" -H "X-API-KEY: $WALLET_API_KEY" ``` ### Address operations Create random address: ```bash curl -s -X POST "$WALLET_API_URL/addresses/create" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{}' ``` Import spend-key address: ```bash curl -s -X POST "$WALLET_API_URL/addresses/import" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"privateSpendKey":"","scanHeight":0}' ``` Import deterministic address: ```bash curl -s -X POST "$WALLET_API_URL/addresses/import/deterministic" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"walletIndex":1,"scanHeight":0}' ``` Import view address: ```bash curl -s -X POST "$WALLET_API_URL/addresses/import/view" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"publicSpendKey":"","scanHeight":0}' ``` Validate address: ```bash curl -s -X POST "$WALLET_API_URL/addresses/validate" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"address":"
"}' ``` List/get/delete addresses: ```bash curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/addresses" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/addresses/primary" curl -s -X DELETE -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/addresses/
" ``` Create integrated address: ```bash curl -s -H "X-API-KEY: $WALLET_API_KEY" \ "$WALLET_API_URL/addresses/
/" ``` ### Transaction send/prepare Prepare basic: ```bash curl -s -X POST "$WALLET_API_URL/transactions/prepare/basic" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"destination":"
","amount":1000000,"paymentID":""}' ``` Send prepared: ```bash curl -s -X POST "$WALLET_API_URL/transactions/send/prepared" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"transactionHash":""}' ``` Delete prepared: ```bash curl -s -X DELETE -H "X-API-KEY: $WALLET_API_KEY" \ "$WALLET_API_URL/transactions/prepared/" ``` Send basic: ```bash curl -s -X POST "$WALLET_API_URL/transactions/send/basic" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"destination":"
","amount":1000000}' ``` Prepare/send advanced: ```bash curl -s -X POST "$WALLET_API_URL/transactions/prepare/advanced" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"destinations":[{"address":"
","amount":1000000}],"mixin":7,"fee":1000,"sourceAddresses":["
"],"paymentID":"","changeAddress":"","unlockTime":0,"extra":""}' curl -s -X POST "$WALLET_API_URL/transactions/send/advanced" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"destinations":[{"address":"
","amount":1000000}],"mixin":7,"feePerByte":1.25,"sourceAddresses":["
"]}' ``` ### Sweeping A sweep sends across **as many transactions as it takes**, so it is the way to move a balance made of many small inputs, and it replaced the fusion/optimize transactions removed in 0.4.7. Sweep a specific amount (omit `amount`, or send `0`, to sweep everything): ```bash curl -s -X POST "$WALLET_API_URL/transactions/send/sweep" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"destination":"
","amount":1000000,"paymentID":""}' ``` Sweep the whole balance: ```bash curl -s -X POST "$WALLET_API_URL/transactions/send/sweep/all" \ -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" \ -d '{"destination":"
","paymentID":""}' ``` Both answer `200` with one entry per transaction attempted: ```json { "transactions": [ { "success": true, "transactionHash": "" }, { "success": false, "errorCode": 12, "errorMessage": "..." } ] } ``` A sweep that partly fails still returns `200` — check every entry, not just the status code. ### Transaction queries ```bash curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/unconfirmed" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/unconfirmed/
" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/100000" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/100000/101000" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/address/
/100000" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/address/
/100000/101000" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/hash/" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/paymentid/" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/paymentid" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/privatekey/" ``` ### Key, node, and balances ```bash curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/keys" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/keys/
" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/keys/mnemonic/
" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/node" curl -s -X PUT "$WALLET_API_URL/node" -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" -d '{"daemonHost":"127.0.0.1","daemonPort":17856,"daemonSSL":false}' curl -s -X PUT "$WALLET_API_URL/sync/refresh" -H "X-API-KEY: $WALLET_API_KEY" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/balance" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/balance/
" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/balances" curl -s -X POST "$WALLET_API_URL/export/json" -H "X-API-KEY: $WALLET_API_KEY" -H "Content-Type: application/json" -d '{"filename":"wallet-export.json"}' ``` --- # Wallet API Cookbook A complete working sequence against the wallet API, from creating a wallet to sending from it and closing it again. Set shared vars: ```bash export WALLET_API_URL="http://127.0.0.1:7856" export WALLET_API_KEY="strong-password" ``` ## 1) Create wallet ```bash curl -s -X POST "$WALLET_API_URL/wallet/create" \ -H "X-API-KEY: $WALLET_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filename":"demo.wallet","password":"wallet-pass","daemonHost":"127.0.0.1","daemonPort":17856,"daemonSSL":false}' ``` ## 2) Open wallet ```bash curl -s -X POST "$WALLET_API_URL/wallet/open" \ -H "X-API-KEY: $WALLET_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filename":"demo.wallet","password":"wallet-pass","daemonHost":"127.0.0.1","daemonPort":17856,"daemonSSL":false}' ``` ## 3) Check status and balance ```bash curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/status" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/balance" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/addresses" ``` ## 4) Send transaction (basic) ```bash curl -s -X POST "$WALLET_API_URL/transactions/send/basic" \ -H "X-API-KEY: $WALLET_API_KEY" \ -H "Content-Type: application/json" \ -d '{"destination":"
","amount":1000000}' ``` ## 5) Prepare then send transaction ```bash curl -s -X POST "$WALLET_API_URL/transactions/prepare/basic" \ -H "X-API-KEY: $WALLET_API_KEY" \ -H "Content-Type: application/json" \ -d '{"destination":"
","amount":1000000}' ``` Use returned hash: ```bash curl -s -X POST "$WALLET_API_URL/transactions/send/prepared" \ -H "X-API-KEY: $WALLET_API_KEY" \ -H "Content-Type: application/json" \ -d '{"transactionHash":""}' ``` ## 6) Query transactions ```bash curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/unconfirmed" curl -s -H "X-API-KEY: $WALLET_API_KEY" "$WALLET_API_URL/transactions/hash/" ``` ## 7) Export and save ```bash curl -s -X POST "$WALLET_API_URL/export/json" \ -H "X-API-KEY: $WALLET_API_KEY" \ -H "Content-Type: application/json" \ -d '{"filename":"wallet-export.json"}' curl -s -X PUT "$WALLET_API_URL/save" -H "X-API-KEY: $WALLET_API_KEY" ``` ## 8) Reset and refresh ```bash curl -s -X PUT "$WALLET_API_URL/reset" \ -H "X-API-KEY: $WALLET_API_KEY" \ -H "Content-Type: application/json" \ -d '{"scanHeight":0}' curl -s -X PUT "$WALLET_API_URL/sync/refresh" -H "X-API-KEY: $WALLET_API_KEY" ``` ## 9) Close wallet ```bash curl -s -X DELETE "$WALLET_API_URL/wallet" -H "X-API-KEY: $WALLET_API_KEY" ``` --- # Wallet Service JSON-RPC Overview The legacy payment-service RPC served by `wrkz-service`: its envelope, its options, and how it differs from the wallet API. Implementation: `src/walletservice/PaymentServiceJsonRpcServer.cpp` This is a JSON-RPC server used by the legacy wallet service binary (`wrkz-service`), separate from the HTTP wallet-api routes. Request format is JSON-RPC-like with: - `method` - optional `params` - optional `password` when legacy security is disabled Password checking behavior is in `processJsonRpcRequest`. Use this section when maintaining integrations built on payment-service RPC method names. ## Notable options | Option | Default | Meaning | | --- | --- | --- | | `--rpc-password` | none | Value clients must send as the request's `password` field | | `--rpc-legacy-security` | `false` | Drop the password check. Do not use | | `--bind-address` | `127.0.0.1` | Interface to listen on. Accepts an IPv6 address | | `--bind-port` | `7856` | TCP port. **Shares its default with `wrkz-wallet-api`** — change one if you run both | | `--daemon-address` | `127.0.0.1` | Daemon host, an IPv6 literal, or a local IPC socket path | | `--bind-ipc-path` | empty | Serve JSON-RPC on this local socket **instead of** the TCP port. POSIX only | | `--bind-ipc-mode` | `0600` | Permissions for the socket file | | `--bind-ipc-group` | empty | Group to own the socket file | | `--tx-notify` | empty | Fire on every new wallet transaction, incoming or outgoing | | `--tx-confirmed-notify` | empty | Fire once per transaction, when it gets into a block | | `--notify-during-sync` | `false` | Also fire during a rescan | Unlike the daemon, `wrkz-service` still requires its password on the IPC socket — these endpoints move money. See [Local IPC and Console](../guides/ipc-and-console.md) and [Notification Hooks](../guides/notify-hooks.md). ## Endpoint and Envelope HTTP endpoint: - `POST /json_rpc` Example envelope: ```json { "jsonrpc": "2.0", "id": "1", "method": "getStatus", "password": "rpc-password", "params": {} } ``` Curl template: ```bash export WALLET_SERVICE_URL="http://127.0.0.1:7856/json_rpc" export WALLET_SERVICE_PASSWORD="replace-me" curl -s -H "Content-Type: application/json" \ -d "{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"method\":\"getStatus\",\"password\":\"$WALLET_SERVICE_PASSWORD\",\"params\":{}}" \ "$WALLET_SERVICE_URL" ``` --- # Wallet Service JSON-RPC Methods Every method `wrkz-service` answers, with an example payload for each. Method registrations are in `src/walletservice/PaymentServiceJsonRpcServer.cpp`. Current methods: - `save` - `export` - `reset` - `createAddress` - `createAddressList` - `deleteAddress` - `getSpendKeys` - `getBalance` - `getBlockHashes` - `getTransactionHashes` - `getTransactions` - `getUnconfirmedTransactionHashes` - `getTransaction` - `sendTransaction` - `createDelayedTransaction` - `getDelayedTransactionHashes` - `deleteDelayedTransaction` - `sendDelayedTransaction` - `getViewKey` - `getMnemonicSeed` - `getStatus` - `getAddresses` - `createIntegratedAddress` - `getFeeInfo` - `getNodeFeeInfo` ## Node fees are not implemented `getFeeInfo` and `getNodeFeeInfo` succeed but always answer with an empty address and an amount of `0`. The wallet backend does not read a node fee from the daemon (`NodeRpcProxy::getFeeInfo` clears both fields), and the daemon's `--fee-address` / `--fee-amount` options are not served over RPC. Do not build a fee-splitting integration on these two methods. ## Error codes Failures carry a numeric code from the shared wallet error table; see [Wallet Error Codes](../guides/error-codes.md). ## Authentication behavior When `legacySecurity` is disabled, requests must include a `password` field in request JSON. Invalid or missing password is rejected. ## Example Payloads Base command pattern: ```bash curl -s -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":"1","method":"","password":"","params":{}}' \ "$WALLET_SERVICE_URL" ``` `save`, `reset`, `export`: ```json {"jsonrpc":"2.0","id":"1","method":"save","password":"","params":{}} {"jsonrpc":"2.0","id":"2","method":"reset","password":"","params":{"scanHeight":0}} {"jsonrpc":"2.0","id":"3","method":"export","password":"","params":{"fileName":"wallet-export.json"}} ``` `createAddress`, `createAddressList`, `deleteAddress`: ```json {"jsonrpc":"2.0","id":"4","method":"createAddress","password":"","params":{}} {"jsonrpc":"2.0","id":"5","method":"createAddress","password":"","params":{"spendSecretKey":"","scanHeight":0}} {"jsonrpc":"2.0","id":"6","method":"createAddressList","password":"","params":{"spendSecretKeys":["",""],"scanHeight":0}} {"jsonrpc":"2.0","id":"7","method":"deleteAddress","password":"","params":{"address":"
"}} ``` `getSpendKeys`, `getBalance`, `getAddresses`, `getViewKey`, `getMnemonicSeed`: ```json {"jsonrpc":"2.0","id":"8","method":"getSpendKeys","password":"","params":{"address":"
"}} {"jsonrpc":"2.0","id":"9","method":"getBalance","password":"","params":{}} {"jsonrpc":"2.0","id":"10","method":"getBalance","password":"","params":{"address":"
"}} {"jsonrpc":"2.0","id":"11","method":"getAddresses","password":"","params":{}} {"jsonrpc":"2.0","id":"12","method":"getViewKey","password":"","params":{}} {"jsonrpc":"2.0","id":"13","method":"getMnemonicSeed","password":"","params":{"address":"
"}} ``` `getStatus`, `getFeeInfo`, `getNodeFeeInfo`: ```json {"jsonrpc":"2.0","id":"14","method":"getStatus","password":"","params":{}} {"jsonrpc":"2.0","id":"15","method":"getFeeInfo","password":"","params":{}} {"jsonrpc":"2.0","id":"16","method":"getNodeFeeInfo","password":"","params":{}} ``` `getBlockHashes`: ```json {"jsonrpc":"2.0","id":"17","method":"getBlockHashes","password":"","params":{"firstBlockIndex":0,"blockCount":100}} ``` `getTransactionHashes`, `getTransactions`, `getTransaction`, `getUnconfirmedTransactionHashes`: ```json {"jsonrpc":"2.0","id":"18","method":"getTransactionHashes","password":"","params":{"firstBlockIndex":0,"blockCount":100}} {"jsonrpc":"2.0","id":"19","method":"getTransactionHashes","password":"","params":{"blockHash":"","blockCount":100,"paymentId":"","addresses":["
"]}} {"jsonrpc":"2.0","id":"20","method":"getTransactions","password":"","params":{"firstBlockIndex":0,"blockCount":100}} {"jsonrpc":"2.0","id":"21","method":"getTransaction","password":"","params":{"transactionHash":""}} {"jsonrpc":"2.0","id":"22","method":"getUnconfirmedTransactionHashes","password":"","params":{"addresses":["
"]}} ``` `sendTransaction`: ```json {"jsonrpc":"2.0","id":"23","method":"sendTransaction","password":"","params":{"addresses":[""],"transfers":[{"address":"","amount":1000000}],"fee":1000,"anonymity":7,"unlockTime":0}} ``` `createDelayedTransaction`, `getDelayedTransactionHashes`, `sendDelayedTransaction`, `deleteDelayedTransaction`: ```json {"jsonrpc":"2.0","id":"24","method":"createDelayedTransaction","password":"","params":{"addresses":[""],"transfers":[{"address":"","amount":1000000}],"feePerByte":1.25,"anonymity":7}} {"jsonrpc":"2.0","id":"25","method":"getDelayedTransactionHashes","password":"","params":{}} {"jsonrpc":"2.0","id":"26","method":"sendDelayedTransaction","password":"","params":{"transactionHash":""}} {"jsonrpc":"2.0","id":"27","method":"deleteDelayedTransaction","password":"","params":{"transactionHash":""}} ``` `createIntegratedAddress`: ```json {"jsonrpc":"2.0","id":"28","method":"createIntegratedAddress","password":"","params":{"address":"
","paymentId":""}} ``` --- # Using Wallet CLI Implementation references: - CLI args: `src/zedwallet++/ParseArguments.cpp` - Command catalog: `src/zedwallet++/Commands.cpp` This project's interactive wallet CLI binary is `wrkz-wallet`. ## Start the CLI Show help: ```bash ./wrkz-wallet --help ``` Open an existing wallet from command line: ```bash ./wrkz-wallet \ --wallet-file wallet.wallet \ --password "wallet-pass" \ --remote-daemon 127.0.0.1:17856 \ --threads 4 ``` Useful startup options: - `--wallet-file ` - `--password ` - `--remote-daemon ` - `--threads ` - `--scan-coinbase-transactions` - `--log-level ` - `--log-file ` - `--ssl` (when SSL support is compiled) `--remote-daemon` also accepts an IPv6 address in brackets (`[2001:db8::1]:17856`) and, on POSIX, a local IPC socket — an absolute path, an `@name` in the abstract namespace, or an `ipc://path`. See [Local IPC and Console](ipc-and-console.md). ## Startup menu actions When no wallet is preselected, common startup choices include: - `open` - `create` - `seed_restore` - `key_restore` - `view_wallet` - `exit` ## Common interactive commands General/help: - `help` - `advanced` - `exit` Status and sync: - `status` - `refresh` - `swap_node` Wallet info: - `address` - `balance` Transactions: - `transfer` - `send_all` - `sweep` - `sweep_all` - `incoming_transfers` - `outgoing_transfers` - `list_transfers` - `txs` - `txs_full` - `get_tx_private_key` - `check_tx ` - `decode_integrated` Address and payment tools: - `make_integrated_address` Address book: - `ab_add` - `ab_delete` - `ab_list` - `ab_send` Security and maintenance: - `backup` - `change_password` - `save` - `save_csv` - `reset` - `set_log_level` ## Example session flow 1. Start: ```bash ./wrkz-wallet --remote-daemon 127.0.0.1:17856 ``` 2. Create or open wallet in startup menu: - `create` or `open` 3. Check readiness: - `status` - `balance` - `address` 4. Send funds: - `transfer` 5. Confirm activity: - `txs` - `incoming_transfers` - `outgoing_transfers` 6. Save and exit: - `save` - `exit` --- # Wallet CLI Command Reference Every command `wrkz-wallet` accepts, grouped by the menu it appears in. For a walkthrough rather than a list, see Using Wallet CLI. Source: `src/zedwallet++/Commands.cpp` ## Startup menu commands - `open`: Open a wallet already on your system - `create`: Create a new wallet - `seed_restore`: Restore a wallet using seed phrase - `key_restore`: Restore a wallet using view/spend keys - `view_wallet`: Import a view-only wallet - `exit`: Exit the program ## Node-down menu commands - `try_again` - `continue` - `swap_node` - `exit` ## General - `help` - `advanced` - `exit` ## Wallet and network info - `status` - `refresh` - `swap_node` - `address` - `addr` (alias) - `balance` - `bal` (alias) ## Transactions - `incoming_transfers` - `in` (alias) - `outgoing_transfers` - `out` (alias) - `list_transfers` - `txs` - `txs_full` - `transfer` - `ab_send` - `send_all` - `sweep` - `sweep_all` - `get_tx_private_key` - `check_tx ` - `decode_integrated` `sweep` and `sweep_all` send across as many transactions as it takes, and print the estimated transaction count and total fee before asking to confirm. They replaced the `optimize` command, removed in 0.4.7 along with fusion transactions. ## Address book - `ab_add` - `ab_delete` - `ab_list` ## Address and payment tools - `make_integrated_address` ## Security and maintenance - `backup` - `change_password` - `save` - `save_csv` - `reset` - `set_log_level` ## View-wallet support notes Some commands are disabled in view-only wallets. If unavailable, use: - `help` to list currently allowed commands for your wallet mode. --- # Desktop, Mobile and Web Wallets Three graphical wallets live under `extras/` in the repository. All three are Flutter apps over `wallet_capi` — the same wallet backend `wrkz-wallet` and `wrkz-wallet-api` use, built as a shared library and reached through FFI (or WebAssembly in the browser). There is no wallet daemon behind them and no sidecar process. | | Source | Platforms | Storage | | --- | --- | --- | --- | | **Desktop** | `extras/desktop-wallet` | Windows, Linux, macOS | Wallet files on disk; settings in the platform secure store | | **Mobile** | `extras/mobile-wallet` | Android, iOS | `/wallets/`, one registry plus `.wallet` / `.wallet.keys` per wallet | | **Web** | `extras/web-wallet` | Any browser meeting the requirements below | IndexedDB, in the browser | Each folder has its own `README.md` with build and packaging instructions; this page covers what an operator or a user needs to know about how they behave. ## Common ground - Multiple wallets, identified by a caption and switched from Settings or a picker. - Create, import from seed, import from keys, view-only import. - Prepare-then-send with a fee preview, and a **Sweep All** mode for consolidating many small inputs. - Integrated address generation with short or long payment IDs — see [Encrypted Payment IDs](encrypted-payment-ids.md). - History with search and direction filters. - A daemon node setting with presets, and a **Test** button that checks a node before switching to it. - A **Transaction PoW Server** setting, off by default — see [Transaction PoW Server](txpow-server.md). - Lite-node awareness: all three read `lite_start_height` from the connected node and say what it can serve. The desktop default node is `nodes.wrkz.work:17856`. ## Lite-node behaviour Every app reads `daemonLiteStartHeight` and `isSyncStalledByLiteNode` from the wallet status and reacts to both: - A standing notice names the lowest block the connected node holds. It stays up once the wallet is synced — which is exactly when a balance missing its older half looks most trustworthy. - The node setting shows *Serves blocks from*: a height for a lite node, *Full chain* otherwise. - **Rescan is floored at the node's start height.** Asking for less is refused in the app and refused again by the wallet backend (error 62), because a lite node answers a lower request from its own start anyway, which would move the recorded scan position over blocks nobody looked at. If the node starts above the wallet's own start height, the notice becomes a warning: funds received in between cannot be found through that node and the balance reads low. Connect a node holding the whole chain to see them. Full background is in [Lite Nodes](lite-node.md). ## Desktop: running a node for you Settings has a **Local Lite Node** card that supervises a `Wrkzd` child process on the same machine, so the wallet can sync against a node the user owns. The daemon is a **separate binary**, not bundled by the Flutter build. It is looked for in `$WRKZ_DAEMON_PATH`, next to the wallet executable, in a `sidecar/` folder beside it, in the macOS bundle's `Contents/Resources`, and in the working directory. Build the normal `Wrkzd` target and drop it next to the app — about 9 MB on a release. If it is absent the card says so and nothing else changes. | | | | --- | --- | | Data directory | `/node`, with its own config, pid file and log | | Ports | An ephemeral RPC and P2P port picked once and stored, so it never collides with a daemon the user runs themselves | | RPC binding | `127.0.0.1` only, **unauthenticated** | | Console | `--no-console`; the process is stopped by signal | | Restarts | A node that was running comes back on the next launch. One stopped on purpose stays stopped | | Quitting the app | **The node keeps running.** A first sync takes hours and is not worth throwing away every time the window closes. Stop it from Settings | | Crash recovery | A node left behind by a force-quit app is found by its port and adopted rather than started twice on the same database | !!! warning "The managed node's RPC has no token" `--rpc-access-token` cannot be used here: the wallet backend builds its own request headers and has no way to send one. Anything running as any user on the machine can reach this node. It binds loopback only. **The start height is the whole decision.** The setup wizard defaults to the wallet's own start height. Choosing higher is allowed but needs an explicit acknowledgement, because a node starting above the wallet can never show that wallet's older transactions. The height cannot be changed afterwards — the daemon refuses to start against a database built at a different one — so changing it means deleting the node and syncing again. The setup dialog can also **import a lite node snapshot** instead of syncing the index region, which turns hours into minutes. See [Lite Node Snapshots](lite-snapshots.md). **Switching between nodes.** The node's life is independent of the wallet's connection: it keeps syncing whether or not the wallet points at it, so the usual flow is to stay on a remote node for the hours the first sync takes and switch over when it is ready. *Use this node* stays disabled until the node reports itself synced. Stopping or deleting the node moves the wallet back to the remote one automatically rather than leaving it addressing a dead port. ## Mobile: no node on the phone yet There is no embedded node on Android or iOS, and it is a longer way off than the desktop version was: - The Android build profile forces `WRKZ_BUILD_EXECUTABLES=OFF`, and RocksDB is only built when executables are on, so the daemon has never been compiled for Android in this tree. - Size does not follow the start height. Key output info is written for every block from genesis, so a lite node is around 6 GB whatever height it starts at, and it downloads the whole chain to build that index. - A first sync is hours of continuous work, against Android's doze and background execution limits. Until then, point the wallet at a node you run yourself — a desktop machine on the same network works well, and the desktop app can host one for you. Mobile also has biometric unlock, auto-lock on backgrounding, incoming transaction notifications, and QR scanning for send and receive. ## Web: what the browser needs The wallet library is compiled to WebAssembly with Emscripten and runs entirely in the page; wallet data is persisted in IndexedDB and never leaves the browser. Because the WASM build uses pthreads, **the page must be served cross-origin-isolated** — `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` — or `SharedArrayBuffer` is unavailable and the wallet cannot start. `extras/web-wallet/README.md` has working nginx, Caddy, Docker and static-host configurations. The web wallet is also the one platform where transaction proof of work is genuinely expensive. Without a [Tx PoW server](txpow-server.md) configured it pays the 100 WRKZ bypass fee instead of computing the proof; with one, it pays the normal minimum fee. ## Building Each app's `README.md` carries the full recipe. In outline: 1. Build `wallet_capi` from this tree (`-DWRKZ_BUILD_WALLET_CAPI=ON`), or `wallet_wasm` with Emscripten for the web wallet. 2. Place the resulting library where the Flutter build expects it. 3. `flutter build `. The step-by-step version, per platform, is in [Building the Wallet Apps](building-wallet-apps.md). `scripts/docker` builds release packages for the CLI binaries and for the web, desktop and mobile wallet targets — see `COMPILE.md`. --- # Encrypted Payment IDs Short payment IDs are encrypted so that only the sender and the receiver can read them. Long payment IDs remain plaintext and are readable by anyone with a copy of the chain. This page covers what changed, what it means if you run an exchange, a pool or a tip bot, and how to migrate. ## The two kinds of payment ID | | Length | On chain | Who can read it | Can be looked up | |---|---|---|---|---| | Long | 64 hex chars (32 bytes) | plaintext | anyone | yes, by anything that indexes the chain | | Short | 16 hex chars (8 bytes) | encrypted | sender and receiver only | no | A daemon in explorer mode can look up a long payment ID directly — see [Looking up a payment ID](#looking-up-a-payment-id) below. A short one cannot be looked up by anything, including the wallets that can read it. Nothing about long payment IDs has changed. If you use them today, you can keep using them and no action is required. ## Why short payment IDs are encrypted A payment ID is normally an account number. Publishing it in the clear on a public ledger means anyone can see which deposits belong to the same customer, tie separate payments together, and correlate that with anything else they know. Encrypting it removes that: the value on chain is meaningless to an observer, and the receiver recovers the real one using a key only they hold. This is the same construction Monero uses, and it is byte for byte compatible with theirs. ## How it works Both parties independently arrive at the same shared secret. ``` Sender D = 8 * r * A r = transaction private key A = receiver's public view key Receiver D = 8 * a * R a = receiver's private view key R = transaction public key k = keccak256(D || 0x8d) ct = payment_id XOR k[0..8] ``` Because `8*r*A` and `8*a*R` are the same curve point, both sides derive the same keystream. Nobody else can: computing `D` requires one of the two private keys. The operation is a XOR, so encrypting and decrypting are the same call. The ciphertext is written into `tx_extra` under extra nonce sub-tag `0x03`. ### Wire format Inside the `tx_extra` nonce field (top level tag `0x02`): | Sub-tag | Meaning | Payload | |---|---|---| | `0x00` | Long payment ID, plaintext | 32 bytes | | `0x01` | Short payment ID, plaintext | 8 bytes — **legacy, see below** | | `0x02` | Short payment ID, plaintext | 8 bytes — **legacy, see below** | | `0x03` | Short payment ID, encrypted | 8 bytes | | `0x7f` | Arbitrary data | varint length, then bytes | ### About sub-tags `0x01` and `0x02` An earlier release shipped sub-tag `0x01` under the name "encrypted payment ID". It was never encrypted — the bytes were written in the clear. A pre-release build wrote the same thing under `0x02`. Both exist on mainnet. A scan of 700,000 blocks found eleven transactions using `0x01` and four using `0x02`, all in first position and all from testing. That is why encrypted payment IDs use `0x03`: it was the lowest sub-tag the same scan found no occurrences of. Do not pick a sub-tag without re-running that scan. `0x02` was chosen once on the assumption it was free, and it was not. Rather than change the meaning of `0x01` or `0x02` (which would make old software read a wrong payment ID and credit the wrong account), encrypted payment IDs use a new sub-tag `0x03`. An **old daemon** does not recognise `0x03` and reports no payment ID at all, which is the safe failure — an operator investigates a missing payment ID, but silently accepts a wrong one. An **old wallet talking to an upgraded daemon** is the one case that is not self-announcing: wallets do not parse `tx_extra` themselves, they take whatever the daemon reports, so such a wallet will display the ciphertext as though it were a payment ID. This cannot be prevented for software already deployed. In practice the window is harmless, because nothing can create a `0x03` field until upgraded wallets exist — but it is the reason to finish the wallet rollout rather than leaving it half done. Both `0x01` and `0x02` are still consumed correctly during parsing so surrounding fields decode, but neither is reported as a payment ID and neither is created any more. Before deploying, confirm you know who created every one of them, and check which sub-tags are free: ``` python scripts/scan-payment-id-tags.py --daemon http://127.0.0.1:17856 ``` Exit status is 0 if no legacy short payment IDs exist. If it finds some, they are only acceptable if you know they are your own; somebody else's mean a third party is relying on a payment ID that will stop being reported. The output also lists every nonce sub-tag seen on chain and the lowest values with no occurrences, which is where a new sub-tag must come from. ## Consequences you need to know about **A short payment ID means one destination.** The ciphertext is encrypted to one receiver's view key, so only that receiver can decrypt it. A transaction with a short payment ID and more than one destination is refused with `SHORT_PAYMENT_ID_NEEDS_SINGLE_DESTINATION`. Change outputs do not count. Use a long payment ID if you need to pay several addresses at once. **Short payment IDs cannot be looked up.** Nothing that reads the chain can answer "show me the transaction with payment ID X" — the value on chain is ciphertext, and the same payment ID encrypts differently in every transaction. Attribution has to happen in the wallet that holds the view key. Long payment IDs are unaffected; see below. ### Looking up a payment ID A daemon in explorer mode (`--daemon-mode explorer`) answers `f_transactions_by_payment_id_json`: ```json {"jsonrpc":"2.0","id":"0","method":"f_transactions_by_payment_id_json", "params":{"paymentId":"<64 hex characters>"}} ``` It returns `transactionHashes`, `totalCount` and `truncated`. Results come from both the chain and the mempool, so a pending deposit shows up. Only long payment IDs are indexed — the index is built from tag `0x00` alone, so encrypted ones never enter it. Passing a short payment ID is refused with an explanation rather than an empty result, because an empty result would wrongly read as "this payment ID was never used". A payment ID reused enough to exceed the node's `--rpc-max-block-count` gets a capped list with `truncated` set, rather than an unbounded response. It is gated behind explorer mode because it reads a database index, which is not something a plain node should do for anyone who asks. **A wallet restored from seed loses payment IDs on transactions it sent.** The payment ID was encrypted to the *receiver*, so the sender cannot recover it from chain data alone. Wallets keep a local copy at send time, so this only shows up after restoring from seed into a fresh wallet file. Note the wallet reports *nothing* in that case rather than guessing. Decrypting a payment ID meant for someone else does not fail — it quietly yields eight bytes of noise that look just like a real payment ID — so the wallet suppresses it instead. The cost is that a payment you sent to yourself also loses its payment ID after a seed restore. Received payments are unaffected; those are exactly the ones your view key can read. ### Where that guarantee does not hold Suppressing depends on the wallet recognising that it spent the inputs, which it does by matching key images. Two setups cannot produce key images at all, and will show eight bytes of noise where a transaction they sent carried a short payment ID: - **A view-only wallet**, including `wrkz-service` opened in tracking mode. It has no spend keys, so it can never derive a key image. It still sees its own outgoing transactions, because the change output comes back to an address it watches, and it reads them as ordinary incoming payments. - **A wallet restored with a scan height** later than the block its inputs arrived in. It never learns those key images, so its own sends look the same way. The fabricated value lands on a transaction that looks *incoming*, so this surfaces as a deposit carrying an unrecognised payment ID rather than a credit to the wrong account — the odds of eight random bytes matching a payment ID you actually issued are one in 2⁶⁴. Automation that matches on payment IDs should still check the direction of the transaction rather than relying on that. Closing this properly needs the key images exported from the wallet that holds the spend keys and imported into the view wallet, the way Monero does it. That does not exist here yet. Until it does: attribute short payment IDs in a wallet that holds the spend keys, and treat a view wallet's *outgoing* history as unreliable for them. Incoming payments — the reason to run a view wallet — are correct in every case, because your view key is genuinely the right one. **The wallet service (`wrkz-service`) supports both.** A short payment ID given to `sendTransaction` or `createDelayedTransaction` is carried down to the wallet in the clear and encrypted once the transaction — and so its private key — exists. Because it is encrypted to one receiver's view key, such a request must have exactly one entry in `transfers`; more than one is rejected with `BAD_PAYMENT_ID`. A configured node fee does not count against that, and neither does change. On the receiving side `getTransaction`, `getTransactions` and the `--tx-notify` / `--tx-confirmed-notify` hooks decrypt a short payment ID and report the plaintext, and `getTransactionsByPaymentId` accepts one as a filter — it compares decrypted values, since the same payment ID is different bytes in every transaction on chain. A transaction the service *sent* reports no short payment ID: it was encrypted to the receiver, not to us. **Block explorers show ciphertext.** The daemon reports `txDetails.paymentIdEncrypted` alongside `txDetails.paymentId` so explorers can label the value instead of presenting it as readable. ## Rolling it out The order matters. The daemon parses `tx_extra`, not the wallet, so a wallet talking to an old daemon receives no payment ID at all. 1. **Upgrade daemons first**, including every public node your users connect to. Wait for coverage before step 2. 2. **Then upgrade wallets.** A wallet on an old daemon will not see encrypted payment IDs. 3. **Then hand out new integrated addresses**, if you want to use short payment IDs. There is no consensus change and no fork. `tx_extra` content is not validated by consensus — only its total size — so old nodes relay, validate and mine these transactions exactly as before. No `FORK_HEIGHTS` entry, no block version bump, no risk of a chain split. What is required is a coordinated software rollout in the order above. ## Which should you use? **If you are an exchange or a pool**, you have three options. *Keep long payment IDs.* Nothing changes, everything keeps working. Your customers' deposit identifiers stay publicly linkable. *Move to short encrypted payment IDs.* Same operational model you have now — one deposit address plus a per-customer identifier, the pattern you already run for XRP, XLM and BNB. Requires a wallet upgrade and reissuing deposit addresses. You lose the ability to look a deposit up by payment ID on an explorer, though your own `wrkz-service` can still find one with `getTransactionsByPaymentId`. Note that a send carrying a short payment ID can only have one destination. *Move to one address per customer.* `POST /addresses/create` on `wrkz-wallet-api` generates deterministic sub-wallet addresses that share a single view key, are recoverable from the seed, and scan in constant time no matter how many you have. Each customer gets an ordinary address, so nothing links your depositors to each other — a stronger property than any payment ID scheme gives you, because it removes the shared deposit address as a correlation point. This last one needs no payment ID at all and works with every wallet on the network today. It is the option we recommend if you can manage a set of addresses rather than a single one. ## For wallet implementers The scheme is small enough to reimplement. Test vectors, verified against an independent from-scratch implementation: ``` receiver private view key 8d31b6a2b0e2d1c6f4e0b4d6b0b7d0a0e6b1b6e2e0b6d1a0c6b4e0d1b6a2b00d receiver public view key 86f65b62d4e7443f150d4955100e159eca8b18a59fc8aa9667d6b905236e684a transaction private key 0c1b6a2b0e2d1c6f4e0b4d6b0b7d0a0e6b1b6e2e0b6d1a0c6b4e0d1b6a2b0d0f transaction public key ec2dcf9ba1572f2d211ad676b5820ac09b35029ca8d51ccd704b8398d86e8a33 payment ID (plaintext) 1122334455667788 payment ID (encrypted) 5a3c5490ba23c021 ``` Encrypting from either side must produce the same ciphertext, and encrypting twice must return the plaintext. Note the hash is **original** Keccak-256, with `0x01` padding — not the NIST SHA3-256 variant, which pads with `0x06` and will give you a different answer. `src/cryptotest` checks all of this; run `cryptotest` to verify an implementation against the same vectors. --- # Transaction PoW Server Every WrkzCoin transaction carries a small proof of work: the wallet appends a nonce to the transaction extra and searches until the `cn_upx` hash of the unsigned transaction prefix meets a difficulty derived from the number of inputs and outputs — `40,000 + (inputs + 4 × outputs) × 1,000`, so a transaction with two inputs and six outputs needs 66,000. The daemon refuses transactions that do not carry it unless they pay at least 10,000 atomic units (100 WRKZ) in fees. On a desktop that search takes seconds. On a phone it takes longer, and in the single-threaded browser wallet it is impractical, which is why the web wallet has been paying the 100 WRKZ bypass fee. `wrkz-txpow-server` moves the search to a machine that is good at it. The in-repo long form is `TXPOWSERVER.md`. ## What the server does and does not see The proof of work is computed over the transaction prefix *before* the ring signatures are made, so the server receives exactly the bytes the daemon will see at broadcast a few seconds later: key images, ring member indexes, output amounts and one-time keys, and the extra field. It never sees a key, a seed or a signature. It cannot alter the transaction, because the wallet only accepts 8 nonce bytes back and re-verifies them with one hash, and it cannot spend anything. **The worst a bad server can do is waste the wallet's time**, after which the wallet computes the proof itself. Run the server where the wallet's remote node already runs and nothing new is learned by anyone. A third-party server is one more party that learns the sender's IP address and transaction shortly before broadcast. ## Running it ```bash wrkz-txpow-server --bind-ip 0.0.0.0 --bind-port 17870 --threads 8 ``` | Option | Default | Meaning | | --- | --- | --- | | `--bind-ip` | `127.0.0.1` | Interface to listen on. Use `0.0.0.0` or `::` for remote wallets | | `--bind-port` | `17870` | TCP port | | `--bind-ipv6-address` | off | Second listener on this IPv6 address, same port | | `--trusted-proxy` | none | Address of a reverse proxy in front of the server. Repeat or comma-separate for several | | `--threads` | all hardware threads | Hashing threads. All of them work on one job at a time | | `--rate-limit` | `60` | Requests per minute from one client address. `0` disables | | `--max-jobs-per-minute` | `120` | Jobs accepted per minute across all clients. `0` disables | | `--max-queue` | `64` | Jobs allowed to wait for the workers before new ones get `503` | | `--max-difficulty` | `1000000` | Refuse anything harder than this | | `--max-wait-ms` | `30000` | Longest a request may be held open waiting for its result | | `--job-timeout` | `600` | Seconds after which an uncollected queued job is dropped | | `--result-ttl` | `300` | Seconds a finished result stays available for polling | | `--api-key` | none | Require this value in the `X-API-KEY` header | | `--enable-cors` | none | `Access-Control-Allow-Origin` value. The web wallet needs this | | `--log-level` | `info` | `trace`, `debug`, `info`, `warning`, `fatal` or `disabled` | | `--log-file` | none | Also append log lines to this file | There is no service wrapper. On Linux a systemd unit or pm2 does the job; on Windows use Task Scheduler or NSSM. ### Behind a reverse proxy The server speaks plain HTTP. For HTTPS, put it behind the reverse proxy that already terminates TLS for the node. Two things matter: - **Tell the server who the proxy is.** Every request then arrives from the proxy's address, so without `--trusted-proxy` the per-address rate limit would treat all wallets as one client. With it, requests from that address are attributed to the client named in `X-Real-IP` (preferred) or the last entry of `X-Forwarded-For`. Requests from any other address keep their real address, so the headers cannot be forged by a direct client. - **Let the proxy hold a request open** for at least the long-poll length. The wallets ask for 20 seconds; the server caps it at `--max-wait-ms`. ```bash wrkz-txpow-server --bind-ip 127.0.0.1 --bind-port 17870 --trusted-proxy 127.0.0.1 ``` ```nginx server { listen 443 ssl; server_name node.example.com; # ssl_certificate / ssl_certificate_key as for the node # The trailing slash on proxy_pass strips the prefix, so the server # keeps seeing /pow, /stats and /health. location /txpow/ { proxy_pass http://127.0.0.1:17870/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 90s; proxy_buffering off; } } ``` In the wallet, enter `node.example.com/txpow` as the host, `443` as the port and switch SSL on. The host field also accepts a full URL such as `https://node.example.com/txpow`, in which case the scheme decides SSL. Serving from the root works the same with a plain host name. !!! warning "Do not set CORS twice" CORS for the web wallet can come either from `--enable-cors` on the server or from `add_header` directives in nginx — not both, or the browser sees the header twice and rejects it. ### Throughput The server uses the same reference hash implementation as the wallets. On a 16-thread laptop that is roughly 4,000 to 5,000 hashes per second, so a typical transaction takes 10 to 20 seconds of the whole machine. `--max-queue` and `--max-jobs-per-minute` exist so a burst turns into refusals — which make the wallets compute locally — rather than into a queue nobody lives to see the end of. ## Protocol All bodies are JSON. Every job reply has a `status` of `done`, `pending`, `cancelled` or `error`. ### `POST /pow` ```json { "prefix": "", "wait_ms": 20000, "height": 4300000 } ``` The prefix must already end with the PoW tag byte `0x04` followed by 8 nonce bytes (the wallet zero-fills them). `wait_ms` is optional and capped at `--max-wait-ms`; the request is held open that long waiting for a result. `height` is optional and only matters for historical difficulty rules. While the job is queued or running (HTTP `200`): ```json { "status": "pending", "job_id": "…32 hex…", "state": "running", "difficulty": 66000, "hashes": 12800, "elapsed_ms": 3100 } ``` Once it is solved (HTTP `200`): ```json { "status": "done", "job_id": "…", "nonce": "3a9f1c0000000000", "difficulty": 66000, "hashes": 71424, "elapsed_ms": 14020 } ``` `nonce` is the 8 bytes to copy over the trailing 8 bytes of the prefix, in that byte order. The wallet verifies `cn_upx(prefix)` against the difficulty before it signs. Refusals: | Code | When | | --- | --- | | `400` | Prefix does not parse, is not canonically serialized, has no fee, carries too many outputs, lacks the nonce tag, or is above `--max-difficulty` | | `401` | API key missing or wrong | | `429` | The client, or the server as a whole, is over its per-minute limit | | `503` | The queue is full | ### Other routes | Route | What it does | | --- | --- | | `GET /pow/?wait_ms=20000` | Same job reply. `404` for an unknown or expired job | | `DELETE /pow/` | Cancels a queued or running job | | `GET /stats` | Counters since start-up | | `GET /health` | Queue depth and capacity, for load balancers. Outside the API key and the rate limit | `/stats` reports jobs received, accepted, completed, failed, cancelled, expired and rejected by reason; total hashes, busy time and average hash rate; average and worst solve time; queue depth and the shape of the job currently running (never its id, which would let a reader cancel it); HTTP request counts; the two limits a client can act on (`max_difficulty`, `max_wait_ms`); and the version. It deliberately does **not** describe the deployment — bind addresses, trusted proxies, rate limits, the CORS origin and whether an API key is required stay in the start-up banner and the operator's command line. ## Wallet side The desktop, mobile and web wallets have a *Transaction PoW Server* section in Settings: a switch, host, port, SSL, a *Test* button and *Apply*. - **The switch is off by default**, so every wallet keeps computing the proof of work on its own CPU until the user turns it on. - The fields come prefilled with the project's public server, `txpow.wrkz.work` on port `443` with SSL, so enabling it is one switch. Any other server can be entered instead, and the host may include a path or be a full URL when the server sits behind a proxy. - *Test* calls `/health` over the same client path a transaction would use, including the native SSL support check, and reports latency, thread count and queue occupancy without saving anything. - When the setting is on, the wallet asks the server first and waits up to two minutes. If the server is unreachable, refuses the job, times out or returns a nonce that does not verify, the wallet computes the proof on its own CPU as before. - The setting is stored on the device and applied every time a wallet is opened. The web wallet keeps paying the 100 WRKZ bypass fee while no server is configured, because its local fallback is far slower than on any other platform. With a server configured it pays the normal minimum fee. **`wrkz-wallet`, `wrkz-wallet-api` and `wrkz-service` always compute the proof of work on their own CPU** and have no setting for this. For embedders, the C API exposes `wallet_set_tx_pow_server(host, port, ssl)`; an empty host turns the feature off. The WASM bridge exposes the same as `setTxPowServer`. --- # Building from Source Builds the daemon and the CLI binaries: `Wrkzd`, `wrkz-wallet`, `wrkz-wallet-api`, `wrkz-service`, `wrkz-txpow-server`, `miner`, `cryptotest` and `wallet-upgrader`. For the graphical wallets see [Building the Wallet Apps](building-wallet-apps.md). The in-repo long form is `COMPILE.md`, which this page tracks. ## Quick start (Linux / macOS) ```bash git clone https://github.com/wrkzcoin/wrkzcoin cd wrkzcoin cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j ``` Binaries land in `build/src`. ## Windows (Visual Studio) From an **x64 Native Tools Command Prompt for VS 2022** (VS 2019 16.11 or later also works, with `-G "Visual Studio 16 2019"`): ```bat cmake -S . -B build -G "Visual Studio 17 2022" -A x64 cmake --build build --config Release -- /m ``` Binaries land in `build/src/Release`. ## Requirements | | | | --- | --- | | CMake | 3.16 or later | | Compiler | **GCC 10+**, **Clang 10+** (15+ on Linux), or **MSVC 19.29+** (VS 2019 16.11 / VS 2022) | | Also | Git, and Make or Ninja | The tree is C++20 and CMake refuses older toolchains outright. !!! warning "clang 14 on Linux will not build this" `clang-14` against GCC 13's `libstdc++` headers fails inside ``. Use GCC, `clang >= 15`, clang with `libc++`, or point clang at an older GCC toolchain (GCC 11 headers and libraries work). ### Optional dependencies Both are detected at configure time and skipped with a status line when absent — but you almost certainly want them on a node. | Package | What you lose without it | | --- | --- | | OpenSSL (`libssl-dev`) | HTTPS in cpp-httplib: reaching `https://` daemons, HTTPS notify-hook URLs, and TLS to a remote [Tx PoW server](txpow-server.md) | | zlib (`zlib1g-dev`) | Compressed HTTP bodies. **Every syncing wallet then pulls several times as many bytes.** Also needed to link a static OpenSSL | Check a running daemon with `curl -s $DAEMON_RPC_URL/info | grep compression` — `"none"` means the build found no zlib. Everything else is bundled under `external/` and built with the tree: RocksDB and zstd (daemon only), libzmq, miniupnpc, argon2, and the header-only cpp-httplib, cxxopts, nlohmann-json and linenoise. `-DWRKZ_SYSTEM_ROCKSDB=ON` links a system RocksDB and zstd instead. ### Ubuntu ```bash sudo apt update sudo apt install -y build-essential git cmake pkg-config libssl-dev zlib1g-dev cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j$(nproc) ``` ### macOS (Homebrew) ```bash brew install cmake openssl cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j ``` If OpenSSL is not found, add `-DOPENSSL_ROOT_DIR=$(brew --prefix openssl)`. ## Build modes ### Default — use this for a node `ARCH=default` (portable, no `-march=native`) and `CONSENSUS_SAFE_BUILD=ON`, which uses safer and reproducible release flags (`-O2` rather than `-Ofast`). ### Performance — local benchmarking only ```bash cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DCONSENSUS_SAFE_BUILD=OFF \ -DARCH=native ``` Host-specific optimization. Do not ship the result. ### Static — portable binaries ```bash cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DPORTABLE_BINARY=ON \ -DENABLE_X86_AESNI=OFF \ -DFULLY_STATIC=ON ``` A static glibc link prints warnings of the form *"Using `getaddrinfo` in statically linked applications requires at runtime the shared libraries from the glibc version used for linking"* (also for `dlopen`, `gethostbyname`, `getgrnam`). **These are expected and harmless.** Name and group lookups go through NSS; with glibc 2.34 or newer the `files` and `dns` backends are inside libc, so DNS seed resolution and `--rpc-ipc-group` work as long as `/etc/nsswitch.conf` uses only those two. Other backends (`sss`, `ldap`, `mdns`, `resolve`) need a runtime glibc matching the build machine. !!! note "Switching target architecture" Going from x86_64 to aarch64 or back needs a **fresh build directory**, or at least a cleared CMake cache — otherwise stale `-march` and ISA flags survive. ## Build speed | Option | Effect | | --- | --- | | `-G Ninja` | Faster incremental builds. `ccache` is auto-enabled when installed | | `-DWRKZ_ENABLE_PCH=ON` | Precompiled headers for the larger targets. **Measure first** — on MSVC it made a clean build *slower* (per-target PCH files run 150-280 MB); GCC and Clang load them much more cheaply. With ccache set `CCACHE_SLOPPINESS=pch_defines,time_macros` | | `-DWRKZ_LINKER=mold` | Or `lld` / `gold`, when the toolchain supports `-fuse-ld=`. Falls back to the default linker | | `-DWRKZ_BUILD_EXECUTABLES=OFF` | Wallet-library-only builds skip zstd and RocksDB entirely | RocksDB is a nested CMake sub-build. To keep the job count aligned across both: ```bash export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) cmake --build build -j ``` Or set RocksDB's alone at configure time with `-DROCKSDB_BUILD_PARALLEL=4`. The sub-build is configured once per build directory, so re-running `cmake` is cheap; its auto-detected job count is capped by `ROCKSDB_BUILD_PARALLEL_CAP` (default 8). ## ZeroMQ On by default (`ENABLE_ZMQ=ON`). It builds bundled `external/libzmq` as a static library with CURVE off, so no system libzmq or libsodium is needed. Pass `-DENABLE_ZMQ=OFF` to leave it out — the daemon then ignores `--zmq-pub`. See [Networking](networking.md#zmq-publisher). ## Release packages with Docker One command builds and packs the portable CLI set for Linux (static), Windows (MinGW) and Android inside a single image, needing nothing on the host but Docker: ```bash bash scripts/docker/build.sh ``` | Target | Package | | --- | --- | | `linux` | `wrkzcoin-cli-linux-x86_64-.tar.gz` — native GCC, fully static glibc | | `windows` | `wrkzcoin-cli-windows-x86_64-.zip` — MinGW-w64 with static OpenSSL | | `android` | `wrkzcoin-cli-android--.tar.gz` — NDK, one per ABI, API 24+ | | `macos` | Not yet | The same image also builds the wallet apps (`web`, `desktop`, `mobile`, or `apps` for all three) from toolchain stages that are off by default. Windows and macOS *desktop* builds and iOS are **not** possible from it — `flutter build windows` needs MSVC on Windows and the Apple targets need Xcode on macOS. Only the Windows `wallet_capi.dll` cross-builds, with `scripts/cross-build-windows-wallet-lib.sh`. `` is `MAJOR.MINOR.REV.BUILD` from `src/config/version.h.in`; the apps carry their own version from their `pubspec.yaml`. Every package in a run is listed in that run's `SHA256SUMS-.txt`. Details in `scripts/docker/README.md`. ## Cross-building on an Ubuntu host `scripts/cross-platform/README.md` documents Linux-hosted cross-build flows for aarch64, Windows, Android and macOS (via osxcross), split into two tracks: wallet library only (`wallet_capi`), and full node/CLI binaries. Use the Docker image above unless you specifically need a prepared host. ## Known build traps - **MSVC and ZSTD.** RocksDB's CMake keeps its compression block in the `else()` arm of `if(MSVC)`, so `ZSTD` was never defined and RocksDB compiled the support out — every MSVC-built `Wrkzd` from this tree failed to create a database. Fixed in `external/CMakeLists.txt` as of 0.4.8. If you hit it on an older checkout, rebuild rather than working around it with `--db-enable-compression=false`. - **A stale `build/` tree** silently references sources deleted on another branch and fails with `C1083`. Re-run `cmake -S . -B build` after switching branches. - **A running `Wrkzd` or `wrkz-txpow-server` locks its own executable** on Windows, so the next link fails with `LNK1104`. Stop them first. --- # Building the Wallet Apps The desktop, mobile and web wallets are Flutter apps that call `wallet_capi` — the wallet backend compiled as a shared library — through FFI, or through WebAssembly in the browser. **Building one is always two builds**: the native library from this C++ tree, then the Flutter app that loads it. For what the apps do once built, see [Desktop, Mobile and Web Wallets](wallet-apps.md). For the daemon and CLI binaries see [Building from Source](building.md). Each app keeps the authoritative recipe in its own `README.md` under `extras/`. ## Step 1 — build `wallet_capi` From the repository root. `-DWRKZ_BUILD_WALLET_CAPI=ON` is what adds the target. === "Windows" ```bash cmake -S . -B build -G "Visual Studio 17 2022" -DWRKZ_BUILD_WALLET_CAPI=ON cmake --build build --target wallet_capi --config Release ``` Output: `build\src\Release\wallet_capi.dll` === "Linux" ```bash cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DWRKZ_BUILD_WALLET_CAPI=ON cmake --build build --target wallet_capi ``` Output: `build/libwallet_capi.so` === "macOS" ```bash cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DWRKZ_BUILD_WALLET_CAPI=ON cmake --build build --target wallet_capi ``` Output: `build/libwallet_capi.dylib` A wallet-library-only build can also set `-DWRKZ_BUILD_EXECUTABLES=OFF`, which skips zstd and RocksDB entirely and is much faster. That is what the Android flow does. ## Desktop (Windows, Linux, macOS) Source: `extras/desktop-wallet`. Requires Flutter 3.38.7+ / Dart 3.10.7+, CMake 3.14+, and Visual Studio 2022 with "Desktop development with C++" on Windows or GCC/Clang 11+ elsewhere. Enable desktop support once: ```bash flutter config --enable-windows-desktop flutter config --enable-linux-desktop flutter config --enable-macos-desktop ``` ### Place the library next to the Flutter executable | Platform | Library | Destination | | --- | --- | --- | | Windows | `wallet_capi.dll` | `build\windows\x64\runner\Debug\` or `Release\` | | Linux | `libwallet_capi.so` | `build/linux/x64/debug/bundle/` or `release/bundle/` | | macOS | `libwallet_capi.dylib` | Next to `pluton_wallet.app` | ### Run and package ```bash cd extras/desktop-wallet flutter pub get flutter run -d windows # or linux / macos ``` ```bash # Windows flutter build windows --release copy build\Release\wallet_capi.dll build\windows\x64\runner\Release\ # -> build/windows/x64/runner/Release/pluton_wallet.exe # Linux flutter build linux --release cp build/libwallet_capi.so build/linux/x64/release/bundle/ # -> build/linux/x64/release/bundle/pluton_wallet # macOS flutter build macos --release cp build/libwallet_capi.dylib "build/macos/Build/Products/Release/" # -> build/macos/Build/Products/Release/pluton_wallet.app ``` ### Shipping a daemon with it The desktop app can supervise a local node, but **`Wrkzd` is not bundled by the Flutter build**. Build the normal daemon target and drop the binary beside the app — about 9 MB on a release: ```bash cmake --build build --target Wrkzd --config Release copy build\src\Release\Wrkzd.exe build\windows\x64\runner\Release\ ``` It is searched for in `$WRKZ_DAEMON_PATH`, next to the wallet executable, in a `sidecar/` folder beside it, in the macOS bundle's `Contents/Resources`, and in the working directory. If it is absent the Local Lite Node card says so and nothing else changes. See [the desktop node section](wallet-apps.md#desktop-running-a-node-for-you). ## Mobile (Android, iOS) Source: `extras/mobile-wallet`. ```bash # Android — needs the NDK cmake -S ../.. -B build-android \ -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \ -DANDROID_ABI=arm64-v8a \ -DWRKZ_BUILD_WALLET_CAPI=ON cmake --build build-android --target wallet_capi # iOS cmake -S ../.. -B build-ios \ -DCMAKE_SYSTEM_NAME=iOS \ -DCMAKE_OSX_ARCHITECTURES=arm64 \ -DWRKZ_BUILD_WALLET_CAPI=ON cmake --build build-ios --target wallet_capi ``` Then place the result: - **Android** — `libwallet_capi.so` into `android/app/src/main/jniLibs//`. Build once per ABI you ship. - **iOS** — link `libwallet_capi.a` as a static framework, or embed the `.dylib`. ```bash cd extras/mobile-wallet flutter pub get flutter run ``` !!! note "The daemon does not build for Android" The Android profile forces `WRKZ_BUILD_EXECUTABLES=OFF`, and RocksDB is only built when executables are on, so `Wrkzd` has never been compiled for Android in this tree. There is no node on the phone — see [Mobile: no node on the phone yet](wallet-apps.md#mobile-no-node-on-the-phone-yet). ## Web Source: `extras/web-wallet`, with the WASM bridge in `extras/web-wallet-wasm`. Requires Flutter 3.29+, Emscripten 3.1.50+ and CMake 3.16+. ```bash # Emscripten, if not already active git clone https://github.com/emscripten-core/emsdk.git cd emsdk && ./emsdk install latest && ./emsdk activate latest source ./emsdk_env.sh # From the repo root mkdir build-wasm && cd build-wasm emcmake cmake .. \ -DCMAKE_BUILD_TYPE=Release \ -DWRKZ_BUILD_WALLET_CAPI=ON \ -DWRKZ_BUILD_WALLET_WASM=ON # -DWRKZ_WASM_PTHREADS=ON for multi-threaded sync cmake --build . -j$(nproc) ``` Copy the artefacts and the bridge JS into the Flutter web directory: ```bash cp build-wasm/wasm/wallet_wasm.js extras/web-wallet/web/ cp build-wasm/wasm/wallet_wasm.wasm extras/web-wallet/web/ cp build-wasm/wasm/wallet_wasm.worker.js extras/web-wallet/web/ # pthreads only cp extras/web-wallet-wasm/wasm/js/wallet_bridge.js extras/web-wallet/web/ cp extras/web-wallet-wasm/wasm/js/wallet_storage.js extras/web-wallet/web/ ``` ```bash cd extras/web-wallet flutter pub get # also generates l10n files flutter build web # -> build/web/ ``` !!! danger "The pthreads build will not run without cross-origin isolation" With `WRKZ_WASM_PTHREADS=ON` the browser needs `SharedArrayBuffer`, which requires the server to send **both**: ``` Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp ``` Without them the browser refuses to allocate shared memory and the module fails to initialise. `extras/web-wallet/README.md` has working nginx, Caddy, Docker and static-host configurations. Browser floor: Chrome 89+, Firefox 89+, Safari 15.2+, Edge 89+. ## Building them all with Docker `bash scripts/docker/build.sh web|desktop|mobile|apps` builds and packs the web bundle, the Linux desktop bundle and the Android APK/AAB from one image, with every toolchain preinstalled. Windows/macOS desktop and iOS cannot be built there — they need MSVC and Xcode on their own hosts. See [Building from Source](building.md#release-packages-with-docker). --- # The Miner App `miner` is the bundled reference CPU miner. It drives `getblocktemplate` and `submitblock` on a daemon directly and needs no stratum port. It is a **plain reference implementation with no assembly path**. For real hashing throughput, run a stock miner against the daemon's stratum server instead — see [Solo Mining](solo-mining.md). ```bash miner --daemon-address 127.0.0.1:17856 --address --threads 8 ``` The address is where the block reward is paid. ## Options Defined in `src/miner/MiningConfig.cpp`. ### Daemon | Option | Default | Meaning | | --- | --- | --- | | `--daemon-address ` | | Daemon to mine against. **Overrides `--daemon-host` and `--daemon-rpc-port`.** An absolute path, an `@name` or an `ipc://path` connects over the daemon's [local IPC socket](ipc-and-console.md) instead | | `--daemon-host ` | `127.0.0.1` | Daemon host | | `--daemon-rpc-port ` | `17856` | Daemon RPC port | | `--scan-time ` | `1` | How often to poll the chain for a new block to mine on | | `--daemon-timeout ` | `10` | How long to wait on a daemon request before giving up | | `--retry-interval ` | `1` | How long to wait before asking again after a failed request | ### Mining | Option | Default | Meaning | | --- | --- | --- | | `--address
` | | The address the reward is paid to. Required unless benchmarking | | `--threads ` | hardware concurrency | Mining threads. Going above what the hardware reports is allowed but warned about | | `--limit ` | `0` | Mine this exact number of blocks, then stop. `0` means no limit | | `--benchmark ` | `0` | Hash for this many seconds, report the rate, and exit. **Needs no daemon and no address** — it measures the proof of work alone | | `--hash-rate-interval ` | `60` | How often to report the hash rate. `0` turns the report off | | `--first-block-timestamp ` | `0` | Set the timestamp of the first mined block. `0` leaves it unchanged | | `--block-timestamp-interval ` | `0` | Timestamp step for each subsequent block. Only valid together with `--first-block-timestamp` | `--threads 0` is rejected. `--first-block-timestamp` and `--block-timestamp-interval` exist for test networks, not for mining the real chain. !!! tip "The best thread count is set by cache, not by cores" CryptoNight's scratchpad is what limits throughput. More threads than your L3 cache can hold scratchpads for makes the hash rate go *down*. ## Benchmarking without a daemon ```bash miner --benchmark 30 --threads 8 ``` This measures the proof-of-work function in isolation, which is the right way to compare a build (see the `-DARCH=native` performance mode in [Building from Source](building.md#performance-local-benchmarking-only)) without a node in the way. ## What changed in 0.4.8 - `--limit` no longer terminates the process or relaunches it; it stops cleanly after the requested blocks. - Ctrl+C is handled. - The reported hash rate is real — it was previously wrong. - The submit reply is trusted rather than second-guessed. - CryptoNight keeps its scratchpad for the life of the thread, which roughly doubled hash rate across every binary that hashes. ## `cryptotest` `cryptotest` is a self-test, not a miner. Run with no arguments it hashes known input with every algorithm in the tree and **aborts if any digest does not match the expected value**, then runs the container, encoding, payment ID and wallet crypto test suites. Worth doing after changing `ARCH`, the linker or the toolchain — a build that mines wrong is otherwise silent until it produces rejected blocks. ```bash cryptotest # correctness cryptotest --benchmark # hash rates cryptotest --benchmark -i 50000 # minimum 1,000 iterations ``` --- # Other Tools Smaller binaries, scripts and components that ship with the repository. Each one is small enough not to need a page of its own, but none of them are documented anywhere else. ## `wallet-upgrader` Converts an old wallet container file to the current format. Only needed when `wrkz-wallet` refuses a file with [`6 UNSUPPORTED_WALLET_FILE_FORMAT_VERSION`](error-codes.md). ```bash wallet-upgrader --wallet-file old.wallet --password "wallet-pass" ``` | Option | Meaning | | --- | --- | | `-w, --wallet-file ` | Container to upgrade | | `-p, --password ` | Its password | | `-h, --help`, `-v, --version` | Print and exit | Run with **neither** flag it prompts for the filename interactively, and offers to add the `.wallet` extension when the file lacks it. Give both flags for a non-interactive upgrade — the extension prompt is skipped in that case, so the file is upgraded under whatever name you passed. **Back the file up first.** The upgrade rewrites the container in place, and a wallet written in the newer format cannot be opened by an older binary. ## `cryptotest` Hash-function and crypto self-test. See [The Miner App](miner.md#cryptotest). ## Block explorer `extras/explorer` is a **static** block explorer UI — HTML, CSS and JavaScript, no build step and no server-side component. It is served by an ordinary web server while the daemon runs separately. The daemon must run in explorer mode, which is what registers the `f_*` JSON-RPC methods and `/queryblocksdetailed`: ```bash Wrkzd --daemon-mode explorer --enable-cors ``` By default the page expects the daemon RPC at `/api`, so the usual production layout is nginx serving the static files and reverse-proxying `/api` to the daemon — which also avoids CORS entirely, making `--enable-cors` unnecessary. For local work, any static file server will do: ```bash cd extras/explorer python -m http.server 8080 ``` | File | What it is | | --- | --- | | `index.html`, `app.js`, `style.css` | The page | | `vendor/wrkz-crypto.js` | Self-contained address and key primitives used by the wallet tools | | `vendor/TurtleCoinUtils.js` | Vendored client-side crypto helper used by Check Transaction | | `test/` | Node test suites for the crypto module and the page wiring | !!! warning "Explorer mode is not for a plain public node" Explorer routes walk database indexes and are not something a node should answer for anyone who asks. Put a token or a proxy in front of it, and note that explorer mode **cannot** be combined with [lite mode](lite-node.md). ## Checkpoints Checkpoints let a syncing node skip proof-of-work and ring-signature verification below the last one, which is most of what makes a first sync take hours rather than days. The daemon ships with compiled-in checkpoints and also accepts a CSV file: ```bash Wrkzd --load-checkpoints checkpoints.csv ``` The format is one `height,hash` line per checkpoint: ``` 0,877e55b4e902b9bf4c9e0a7c16440f449339d56679c49d62261ae5c92596a6ce 1,93bb1fd850d9e904ca810cdb57935b6df45cd75fc3a86358a421e126c1ae7b51 ``` Published checkpoints are at [checkpoints.wrkz.work](https://checkpoints.wrkz.work/) ([direct CSV](https://checkpoints.wrkz.work/checkpoints.csv)). ### Generating your own `scripts/checkpoints/gen_checkpoints.sh` walks a **synced** daemon over RPC and writes the file: ```bash ./gen_checkpoints.sh -H 127.0.0.1 -p 17856 -o checkpoints.csv ``` | Option | Default | Meaning | | --- | --- | --- | | `-H, --host` | `127.0.0.1` | Daemon RPC host | | `-p, --port` | `17856` | Daemon RPC port | | `--token` | | `X-API-Key` value, if the daemon runs with an access token | | `-s, --start` | `0`, or the resume point | First checkpoint height | | `-e, --end` | top height minus confirmations | Last checkpoint height | | `--step` | `1` for csv, `1000` otherwise | Interval between checkpoints | | `--confirmations` | `180` | Stay this many blocks behind the tip. `0` goes to the top | | `-c, --checkpoint-file` | | Existing file to resume from | | `-o, --output` | `checkpoints.csv` | Output file, or `-` for stdout | | `-m, --mode` | `csv` | `csv`, `append` (C++ entries to paste into `CHECKPOINTS`), `full` (a complete `CryptoNoteCheckpoints.h`), or `raw` | The `--confirmations` default exists so a checkpoint is never written for a block that could still be reorganised away. ## `wallet_capi` — the embedder C API The desktop, mobile and web wallets do not talk to `wrkz-wallet-api` over HTTP; they link `wallet_capi`, the wallet backend built as a shared library, and call it through FFI (or WebAssembly). It exposes around 70 `wallet_*` functions covering the whole wallet lifecycle, and returns the same [error codes](error-codes.md) as every other surface, with `wallet_error_code_to_string` to render them. Two entry points worth knowing: - `wallet_set_tx_pow_server(host, port, ssl)` — point the backend at a [transaction PoW server](txpow-server.md). An empty host turns it off. The WASM bridge exposes the same call as `setTxPowServer`. - `wallet_capi_api_version` / `wallet_capi_version_string` — check what a loaded library actually is before calling into it. There is no generated reference for this API yet; `src/walletcapi/wallet_capi.cpp` is the source of truth, and [Building the Wallet Apps](building-wallet-apps.md) covers producing the library. ## ZMQ subscriber test script `scripts/zmq_sub_test.py` is a working subscriber for the daemon's [ZMQ publisher](networking.md#zmq-publisher) — the quickest way to confirm a build has ZeroMQ compiled in and that events are actually flowing. ## Build and packaging scripts | Path | What it does | | --- | --- | | `scripts/docker/` | One-command release packaging for Linux, Windows, Android and the wallet apps | | `scripts/cross-platform/` | Linux-hosted cross-build flows for aarch64, Windows, Android and macOS | | `scripts/build-linux-wallet-lib.sh` | Static Linux `wallet_capi` | | `scripts/cross-build-windows-wallet-lib.sh` | Windows `wallet_capi.dll` from Linux | | `scripts/cross-build-android-wallet.sh`, `scripts/build-libucontext-android.sh` | Android `wallet_capi` and its dependency | | `scripts/package-macos.sh`, `scripts/package-windows-x86_64.sh` | Packaging steps for those targets | | `scripts/scan-payment-id-tags.py` | Audits extra sub-tag usage on chain — see [Encrypted Payment IDs](encrypted-payment-ids.md) | See [Building from Source](building.md) for how these fit together. --- # Network Parameters Consensus constants and the heights they change at. All of these live in `src/config/CryptoNoteConfig.h`; this page is a readable copy of that file, not a second source of truth. When one disagrees with the other, the header wins. ## Chain basics | | | | --- | --- | | Name | `WRKZCoin` | | Ticker | `WRKZ` | | Decimal places | `2` | | Atomic unit | `0.01 WRKZ` | | Target block time | `60` seconds | | Blocks per day | `1440` | | Total supply | `500,000,000,000.00 WRKZ` (`MONEY_SUPPLY` = 50,000,000,000,000 atomic) | | Emission speed factor | `22` | | Genesis block reward | 3% of the money supply | | Coinbase unlock window | `40` blocks | | Address base58 prefix | `999730` | | Initial max block size | `100,000` bytes | | P2P port / RPC port / ZMQ port | `17855` / `17856` / `17857` | ## Block versions and proof of work The hashing algorithm is selected by the block's major version (`HASHING_ALGORITHMS_BY_BLOCK_VERSION`). | Block version | Activates at height | Algorithm | | --- | --- | --- | | 1 | `0` | `cn_slow_hash_v0` | | 2 | `1` | `cn_slow_hash_v0` | | 3 | `2` | `cn_slow_hash_v0` | | 4 | `3` | `cn_lite_slow_hash_v1` | | 5 | `302,400` | `cn_turtle_lite_slow_hash_v2` | | 6 | `600,000` | `chukwa_slow_hash` | | 7 | `1,000,000` | `cn_upx` — **current** | `cn_upx` is xmrig's `cn/upx2`. See [Solo Mining](solo-mining.md). ## Ring size (mixin) "Mixin" is the number of decoys; the **ring size is mixin + 1**. Resolved by `getMixinAllowableRange()` in `src/utilities/Mixins.cpp`. Each row applies from its height until the next one. | From height | Minimum mixin | Maximum mixin | Default mixin | Default ring size | | --- | --- | --- | --- | --- | | `0` | `0` | unlimited | `3` | 4 | | `10,000` | `0` | `30` | `3` | 4 | | `302,400` | `3` | `7` | `3` | 4 | | `430,000` | `0` | `7` | `3` | 4 | | `658,500` | `1` | `3` | `3` | 4 | | `1,000,000` | `1` | `1` | `1` | 2 (fixed) | | `4,300,000` | `1` | `7` | `7` | **8** | The limit in force is the one that was correct **when the block was formed**, not the one current now — otherwise the chain could not be resynced past its own history. !!! warning "The V6 rule is transitional, and low mixin is a fingerprint" From height 4,300,000 the default ring size is 8, but the **old minimum is still accepted**, so outputs whose denomination does not yet have enough decoys on chain stay spendable at a lower mixin. The next fork is intended to raise the minimum to match the maximum, giving a fixed ring size. Until then, a low-mixin spend deanonymises the decoys other transactions rely on, and the ring size itself is a distinguishing feature of the transaction. Send at the default unless you have no choice. Since 0.4.8 the wallet retries at the best ring size actually achievable rather than refusing to send, and reports the ring it used alongside the network's. See error codes [`21`, `22` and `28`](error-codes.md). ## Fees | From height | Minimum fee | | --- | --- | | `0` | `5` atomic (`0.05 WRKZ`) | | `678,500` | `50,000` atomic (`500.00 WRKZ`) | | From height | Minimum fee per byte | | --- | --- | | `832,000` | `500.00` per 256-byte chunk | | `1,500,000` | `10.00` per 128-byte chunk | ## Transaction proof of work Every transaction carries a small proof of work over its unsigned prefix, hashed with `cn_upx`. | From height | Rule | | --- | --- | | `1,123,000` | Transaction PoW required. Fixed difficulty `20,000`; fusion transactions `60,000` | | `1,200,000` | Dynamic difficulty: `40,000 + (inputs + 4 × outputs) × 1,000` | | `1,500,000` | A transaction may **skip** the proof of work by paying at least `10,000` atomic units (`100.00 WRKZ`) in fees | A transaction with two inputs and six outputs therefore needs difficulty `40,000 + (2 + 24) × 1,000 = 66,000`. The search takes seconds on a desktop, longer on a phone, and is impractical in the single-threaded browser wallet — which is why the web wallet pays the bypass fee unless a [transaction PoW server](txpow-server.md) is configured. ## Other transaction limits | | | | --- | --- | | Max outputs in a normal transaction | `90`, from height `777,777` | | Max single output amount (node) | `125,000,000,000.00 WRKZ`, from height `800,000` | | Max single output amount (client) | `5,000,000,000.00 WRKZ` | | Dust threshold | `10` atomic before height `302,400`, `0` after | | Max transaction size | `1,000,000,000` bytes | ## P2P protocol | | | | --- | --- | | Current version | `19` | | Minimum accepted version | `16` | | IPv6 capability gate | `19` | | Lite block propagation from | `4` | | Default peer connections | `15` in, `15` out | | Peers per handshake | `250` | See [Networking](networking.md). ## Lite node and snapshots | | | | --- | --- | | Minimum depth below the tip for a lite height | `20,160` blocks (14 days) | | First blessed snapshot height | `4,000,000` | See [Lite Nodes](lite-node.md) and [Lite Node Snapshots](lite-snapshots.md). --- # Wallet Error Codes Every wallet surface built on `walletbackend` reports failures with the same numeric code: `wrkz-wallet-api` returns it as `errorCode` in the response body, `wrkz-service` as the JSON-RPC error code, `wrkz-wallet` prints the message, and the C API returns it from `wallet_*` calls (`wallet_error_code_to_string` maps it back to the text below). Defined in `src/errors/Errors.h`, with the messages in `src/errors/Errors.cpp`. Codes are stable: a value is never reused for a different meaning. !!! note "These are not HTTP status codes" A wallet-api call that fails validation answers HTTP `400` with an `errorCode` from this table in the body. A sweep answers HTTP `200` with a per-transaction `errorCode` for each attempt that failed. Read the body, not just the status line. | Code | Name | Meaning | | --- | --- | --- | | `0` | `SUCCESS` | The operation completed successfully. | | `1` | `FILENAME_NON_EXISTENT` | The filename you are attempting to open does not exist, or the wallet does not have permission to open it. | | `2` | `INVALID_WALLET_FILENAME` | We could not open/save to the filename given. Possibly invalid characters, or permission issues. | | `3` | `NOT_A_WALLET_FILE` | This file is not a wallet file, or is not a wallet file type supported by this wallet version. | | `4` | `WALLET_FILE_CORRUPTED` | This wallet file appears to have gotten corrupted. | | `5` | `WRONG_PASSWORD` | The password given for this wallet is incorrect. | | `6` | `UNSUPPORTED_WALLET_FILE_FORMAT_VERSION` | This wallet file appears to be from a newer or older version of the software, that we do not support. | | `7` | `INVALID_MNEMONIC` | The mnemonic seed given is invalid. | | `8` | `WALLET_FILE_ALREADY_EXISTS` | The wallet file you are attempting to create already exists. Please delete it first. | | `9` | `WILL_OVERFLOW` | This operation will cause integer overflow. Please decrease the amounts you are sending. | | `10` | `ADDRESS_NOT_IN_WALLET` | The address given does not exist in the wallet container, but is required to exist for this operation. | | `11` | `NOT_ENOUGH_BALANCE` | Not enough unlocked funds were found to cover this transaction in the subwallets specified (or all wallets, if not specified). (Sum of amounts + fee) | | `12` | `ADDRESS_WRONG_LENGTH` | The address given is too short or too long. | | `13` | `ADDRESS_WRONG_PREFIX` | The address does not have the correct prefix corresponding to this coin - it appears to be an address for another cryptocurrency. | | `14` | `ADDRESS_NOT_BASE58` | The address contains invalid characters, that are not in the base58 set. | | `15` | `ADDRESS_NOT_VALID` | The address given is not valid. Possibly invalid checksum. Most likely a typo. | | `16` | `INTEGRATED_ADDRESS_PAYMENT_ID_INVALID` | The payment ID stored in the integrated address supplied is not valid. | | `17` | `FEE_TOO_SMALL` | The fee given for this transaction is below the minimum allowed network fee. | | `18` | `NO_DESTINATIONS_GIVEN` | The destinations array (amounts/addresses) is empty. | | `19` | `AMOUNT_IS_ZERO` | One of the destination parameters has an amount given of zero. | | `20` | `FAILED_TO_CREATE_RING_SIGNATURE` | Failed to create ring signature - probably a programmer error, or a corrupted wallet. | | `21` | `MIXIN_TOO_SMALL` | The mixin value given is too low to be accepted by the network (based on the current height known by the wallet) | | `22` | `MIXIN_TOO_BIG` | The mixin value given is too high to be accepted by the network (based on the current height known by the wallet) | | `23` | `PAYMENT_ID_WRONG_LENGTH` | The payment ID given is not 16 or 64 characters long. | | `24` | `PAYMENT_ID_INVALID` | The payment ID given is not a hex string (A-F0-9) | | `25` | `ADDRESS_IS_INTEGRATED` | The address given is an integrated address, but integrated addresses aren't valid for this parameter, for example, change address. | | `26` | `CONFLICTING_PAYMENT_IDS` | Conflicting payment IDs were given. This could mean an integrated address + payment ID were given, where they are not the same, or that multiple integrated addresses with different payment IDs were given. | | `27` | `CANT_GET_FAKE_OUTPUTS` | Failed to get fake outputs from the daemon to obscure our transaction, and mixin is not zero. | | `28` | `NOT_ENOUGH_FAKE_OUTPUTS` | We could not get enough fake outputs for this transaction to complete. If possible, try lowering the mixin value used, or decrease the amount you are sending. | | `29` | `INVALID_GENERATED_KEYIMAGE` | The key image we generated is invalid - probably a programmer error, or a corrupted wallet. | | `30` | `DAEMON_OFFLINE` | We were not able to submit our request to the daemon. Ensure it is online and not frozen. | | `31` | `DAEMON_ERROR` | An error occured whilst the daemon processed the request. Possibly our software is outdated, the daemon is faulty, or there is a programmer error. Check your daemon logs for more info. (set_log 4) | | `32` | `TOO_MANY_INPUTS_TO_FIT_IN_BLOCK` | The transaction is too large (in BYTES, not AMOUNT) to fit in a block. Either decrease the amount you are sending, perform fusion transactions, or decrease mixin (if possible). | | `33` | `MNEMONIC_INVALID_WORD` | The mnemonic seed given has a word that is not present in the english word list. | | `34` | `MNEMONIC_WRONG_LENGTH` | The mnemonic seed given is the wrong length. | | `35` | `MNEMONIC_INVALID_CHECKSUM` | The mnemonic seed given has an invalid checksum word. | | `36` | `FULLY_OPTIMIZED` | Cannot send fusion transaction - wallet is already fully optimized. | | `37` | `FUSION_MIXIN_TOO_LARGE` | Cannot send fusion transacton - mixin is too large to meet input/output ratio requirements whilst remaining in size constraints. | | `38` | `SUBWALLET_ALREADY_EXISTS` | A subwallet with the given key already exists. | | `39` | `ILLEGAL_VIEW_WALLET_OPERATION` | This function cannot be called when using a view wallet. | | `40` | `ILLEGAL_NON_VIEW_WALLET_OPERATION` | This function can only be used when using a view wallet. | | `41` | `KEYS_NOT_DETERMINISTIC` | You cannot get a mnemonic seed for this address, as the view key is derived in terms of the spend key. | | `42` | `CANNOT_DELETE_PRIMARY_ADDRESS` | Each wallet has a primary address when created, this address cannot be removed. | | `43` | `TX_PRIVATE_KEY_NOT_FOUND` | Couldn't find the private key for this transaction. The transaction must exist, and have been sent by this program. Transaction private keys cannot be found upon rescanning/reimporting. | | `44` | `AMOUNTS_NOT_PRETTY` | The created transaction isn't comprised of only 'Pretty' amounts. This will cause the outputs to be unmixable. Almost certainly a programmer error. Cancelling transaction. | | `45` | `UNEXPECTED_FEE` | The fee of the created transaction is not the same as that which was specified (0 for fusion transactions). Almost certainly a programmer error. Cancelling transaction. | | `46` | `NEGATIVE_VALUE_GIVEN` | The input for this operation must be greater than or equal to zero, but a negative number was given. | | `47` | `INVALID_KEY_FORMAT` | The public/private key or hash given is not a 64 char hex string. | | `48` | `HASH_WRONG_LENGTH` | The hash given is not 64 characters long. | | `49` | `HASH_INVALID` | The hash given is not a hex string (A-Za-z0-9) | | `50` | `NON_INTEGER_GIVEN` | The number given is a float, not an integer. | | `51` | `INVALID_PUBLIC_KEY` | The public key given is not a valid ed25519 public key. | | `52` | `INVALID_PRIVATE_KEY` | The private key given is not a valid ed25519 private key. | | `53` | `INVALID_EXTRA_DATA` | The extra data given for the transaction could not be decoded. | | `54` | `UNKNOWN_ERROR` | An unknown error occurred. | | `55` | `DAEMON_STILL_PROCESSING` | The transaction was sent to the daemon, but the connection timed out before we could determine if the transaction succeeded. Wait a few minutes before retrying the transaction, as it may still succeed. | | `56` | `OUTPUT_DECOMPOSITION` | The transaction contains more outputs than what is permitted by the number of inputs that have been supplied for the transaction. Please try to send your transaction again. If the problem persists, please reduce the number of destinations that you are trying to send to. | | `57` | `PREPARED_TRANSACTION_EXPIRED` | The prepared transaction contains inputs that have since been spent or are no longer available, probably due to sending another transaction in between preparing this transaction and sending it. The prepared transaction has been cancelled. | | `58` | `PREPARED_TRANSACTION_NOT_FOUND` | The prepared transaction hash given does not exist, either because it never existed or because the wallet process was restarted and the previously prepared transactions were lost. Please re-prepare and re-send the transaction, ensuring you specify the correct transaction hash. | | `59` | `AMOUNT_UGLY` | The amount given does not have only a single significant digit. For example, 20000 or 100000 would be fine, but 20001 or 123456 would not. | | `60` | `UNLOCK_TIME_TOO_SMALL` | Unlock time is too small | | `61` | `SHORT_PAYMENT_ID_NEEDS_SINGLE_DESTINATION` | A short payment ID is encrypted so that only the receiver can read it, which means the transaction can only have one destination. Send to a single address, or use a long (64 character) payment ID instead. | | `62` | `LITE_NODE_CANNOT_RESCAN_THAT_LOW` | The daemon this wallet is connected to is a lite node and holds no block data below its lite height. Rescanning from lower than that would start at the lite height instead, and the transactions this wallet already holds from underneath would be lost with no way to find them again here. Connect a daemon that holds the whole chain, or rescan from the lite height. | ## Codes you should no longer see | Code | Name | Why | | --- | --- | --- | | `36` | `FULLY_OPTIMIZED` | Fusion transactions were removed in 0.4.7. Nothing raises this any more | | `37` | `FUSION_MIXIN_TOO_LARGE` | Same. The values are kept so old integrations parsing them do not break | ## The ones worth handling explicitly | Code | Handling | | --- | --- | | `11` `NOT_ENOUGH_BALANCE` | Includes the fee. Check unlocked, not total, balance | | `21` / `22` mixin bounds | The network ring size changes at fork heights - see [Network Parameters](network-parameters.md). The wallet retries at the best achievable ring rather than failing outright since 0.4.8 | | `28` `NOT_ENOUGH_FAKE_OUTPUTS` | A denomination without enough decoys. The wallet falls back to a smaller ring; a caller pinning `mixin` explicitly can still hit this | | `30` `DAEMON_OFFLINE` / `31` `DAEMON_ERROR` | Retry with backoff. `31` is worth logging with the daemon's own log | | `55` `DAEMON_STILL_PROCESSING` | **Do not retry immediately.** The transaction may have succeeded. Wait, then check by hash | | `57` / `58` prepared transactions | Prepared transactions live in memory and are lost on restart. Re-prepare rather than treating this as fatal | | `61` `SHORT_PAYMENT_ID_NEEDS_SINGLE_DESTINATION` | Short payment IDs are encrypted to one receiver. Use a long payment ID for a multi-destination transaction - see [Encrypted Payment IDs](encrypted-payment-ids.md) | | `62` `LITE_NODE_CANNOT_RESCAN_THAT_LOW` | The connected daemon is a [lite node](lite-node.md). Rescan from its lite height, or connect a full node | --- # Daemon Changelog Changes to `Wrkzd` — consensus, P2P, storage, the RPC surface and the console — that an operator or an RPC integrator has to know about. Wallet-side changes are in the [Wallet API changelog](wallet-api.md). The version string a binary reports is `...`; `Wrkzd --version` prints it, and it is set in `src/config/version.h.in`. Full commit history is on [GitHub](https://github.com/wrkzcoin/wrkzcoin/commits/development). --- ## 0.4.8 (build 280) — 2026-09-05 The largest release since 0.4.4. Lite nodes, snapshots, a stratum server, an optional transaction PoW service, and P2P version 19. ### Breaking and operator-visible - **Nodes advertise P2P version 19.** The minimum accepted version stays 16, so older peers still connect. IPv6 peer exchange is now gated on version 19 rather than being advertised unconditionally. - **A database written by an older storage scheme is refused at startup** instead of being silently deleted. Resync, or import a lite snapshot. See [Lite Node Snapshots](../guides/lite-snapshots.md). - **Lite mode cannot be combined with explorer mode.** `--lite --daemon-mode explorer` is refused at startup, because every explorer route below the lite height reads records lite mode does not store. - **Windows builds could not create a database before this release.** RocksDB was compiled without ZSTD under MSVC, so every MSVC-built `Wrkzd` from this tree failed at database creation. Fixed in the build system; a rebuild is the fix, not `--db-enable-compression=false`. ### Lite nodes and snapshots - `--lite` with `--lite-height ` keeps full block data only from `H` upward and stores just the indexes later blocks need below it. Permanent for the database. See [Lite Nodes](../guides/lite-node.md). - `/info` gains `lite` and `lite_start_height`, and keeps answering while the node's own tip is still below `H`. The `status` console command shows `Lite Node`, `Full Block Data From` and the index-only sync stage. - `/getwalletsyncdata` and `/getrawblocks` clamp a start height below the lite height up to it; `/get_global_indexes_for_range` refuses below it rather than narrowing the range. - Peers asking for a block below the lite height get it in `missed_ids` rather than losing the connection, and can no longer shut a lite node down. - **Snapshots**: `snapshot_export` writes the index-only region as a portable file; `--import-lite-snapshot ` loads one into an empty database. `--snapshot-info ` prints what a file carries. `--snapshot-stats` measures per-table storage. See [Lite Node Snapshots](../guides/lite-snapshots.md). ### Mining - **Stratum server in the daemon**, off unless `--stratum-bind-port` is given and bound to loopback when it is. A stock miner can now mine solo with no pool and no bridge. See [Solo Mining](../guides/solo-mining.md). - The merge-mining tag is sealed before a stratum job goes out, and blocks found over stratum are announced to peers immediately — they previously reached the network only through the next timed sync, which produced visible alt blocks. - Stratum shares are hashed off the dispatcher thread. - The daemon RPC speaks xmrig's solo-mining dialect: `/getinfo` and `/getheight` are served as aliases of `/info` and `/height`, and `/getheight` deliberately answers without a `hash` member, which is how those miners identify a CryptoNote daemon. - `/json_rpc` echoes the request `id` back, as JSON-RPC 2.0 requires. - `getblocktemplate` accepts `extra_nonce` as well as `reserve_size`. - CryptoNight keeps its scratchpad for the life of the thread, roughly doubling hash rate. The bundled `miner`'s `--limit`, Ctrl+C handling and hash-rate report are fixed. ### Consensus - **Default ring size rises to 8 at height 4,300,000.** The minimum accepted ring is judged on the smallest ring in a transaction, and a decoy shortage is reportable so wallets can fall back to a smaller ring instead of refusing to send. - Short payment IDs are encrypted to the receiver, under extra sub-tag `0x03`. See [Encrypted Payment IDs](../guides/encrypted-payment-ids.md). ### Networking - **Local IPC sockets**: `--rpc-ipc-path`, `--rpc-ipc-mode`, `--rpc-ipc-group` and `--rpc-ipc-require-token` serve daemon RPC over an AF_UNIX socket whose file mode decides who may connect. POSIX only. See [Local IPC and Console](../guides/ipc-and-console.md). - **`Wrkzd attach `** opens an interactive console against a running daemon over its IPC socket, for a node under a process manager. The `/console` route is registered on the IPC listener only, never on TCP. Inside an attached session, `exit` leaves and `stop` shuts the node down. - The daemon's own console routes its RPC calls over the IPC socket when one is bound, instead of making loopback TCP connections to itself. - A daemon whose stdin is at end of file runs headless with one notice instead of spinning on empty reads. - Seeds are retried when the node is stuck, DNS seed hostnames are re-resolved in the background, recently failed peers are skipped for 10 minutes, and one gray-list peer a minute is verified. `/info` reports `seed_nodes_count` and `last_seed_bootstrap`. ### Notification hooks - `--block-notify`, `--reorg-notify` and `--tx-notify` take either a command template or an `http(s)://` webhook URL, suppressed until the node is synced unless `--notify-during-sync` is given. See [Notification Hooks](../guides/notify-hooks.md). ### Storage and performance - New database tuning flags: `--db-compression-level`, `--db-row-cache-percent`, `--db-bottom-filters`, `--db-compression-dict-bytes`, `--db-block-size`. - `--rpc-sync-cache-size` (default 64 MB) keeps finished `/getwalletsyncdata` bodies, so wallets syncing past the same height are served from one build. - Optional sync fields on `/getwalletsyncdata`, advertised in `/info` as `sync_features`: `skipEmptyBlocks`, `encoding: "base64"` and `endHeight`. - The packed output index table is folded into key output info. - The daemon no longer compacts the whole database every minute while syncing, and the maintenance loop no longer makes the console look stuck. - Boost is no longer a dependency of the tree. - Bundled miniupnpc updated 2.1 → 2.3.3. --- ## 0.4.7 (build 270) — 2026-08-21 A stability and sync-performance release. - **Reorg safety.** A shared mutex guards chain reads during a reorg; RPC endpoints and the sparse chain builder retry safely instead of racing it; alt block reorg gained null guards, retry logic and exception safety. - **Alt chain management.** Stale alt chains deeper than 180 blocks behind the main chain are pruned, the alt chain count is capped and the weakest evicted by cumulative difficulty, total alt blocks are capped at 100, and pruning runs on every block add rather than only on main-chain blocks. A use-after-free in alt chain pruning was fixed by excluding the active cache. - **Sync recovery.** Network-consensus block trust and a dynamic checkpoint recover a node stalled on local ring-signature validation. - Transaction proof of work is skipped inside the checkpoint zone during sync. - Block batches are requested before the current one finishes validating; block and wallet-sync serving reads are batched; bloom filters were added with index and filter memory bounded to the block cache; an fsync per block is avoided while catching up. - RPC gains HTTP keep-alive, `TCP_NODELAY` and a wider thread pool. - `validateTransaction` argument order was corrected for pool admission, and the pool's priority comparator made a strict weak ordering. - rapidjson was replaced with nlohmann-json across the tree. --- ## 0.4.6 (build 265) — 2026-03-08 and earlier See the [release list on GitHub](https://github.com/wrkzcoin/wrkzcoin/releases) and the [commit history](https://github.com/wrkzcoin/wrkzcoin/commits/development). --- # Wallet API Changelog Changes to `wrkz-wallet-api` and the wallet backend it shares with `wrkz-wallet`, `wrkz-service` and the desktop, mobile and web wallets. Daemon and RPC changes are in the [Daemon changelog](daemon.md). Version numbers follow the daemon's — all binaries are built from one tree and report the same `...` string. --- ## 0.4.8 (build 280) — 2026-09-05 ### Endpoints No routes were added or removed in this release. The route table in [Endpoints](../wallet-api/endpoints.md) is current as of build 280. ### New options - `--rpc-ipc-path`, `--rpc-ipc-mode`, `--rpc-ipc-group` — serve the API over a local AF_UNIX socket alongside the TCP listener. POSIX only. Unlike the daemon's IPC socket, wallet-api still requires `--rpc-password` on IPC: these endpoints move money. See [Local IPC and Console](../guides/ipc-and-console.md). - `--tx-notify`, `--notify-during-sync` — fire a command or webhook for each transaction confirmed in a block for the open wallet. wallet-api does not scan the pool for incoming transfers, so unlike `wrkz-service` it has no unconfirmed event. See [Notification Hooks](../guides/notify-hooks.md). ### Behaviour - **Ring size fallback.** With the network minimum rising to 8 at height 4,300,000, a send whose denomination lacks decoys retries at the best ring size actually achievable rather than failing outright, and reports the ring size it used alongside the network's. `wrkz-service` does the same. - **Encrypted payment IDs.** Short payment IDs are encrypted to the receiver. Integrated addresses and the `paymentID` field are unchanged for callers; the encoding on the wire moved to extra sub-tag `0x03`. See [Encrypted Payment IDs](../guides/encrypted-payment-ids.md). - **Lite node support.** The backend reads `lite_start_height` from `/info`. A fresh wallet's scan height is floored to it with a warning; a wallet already scanned *below* it stops syncing rather than silently skipping blocks it can never see. Applies to every binary built on the wallet backend. - **Faster sync against a 0.4.8 daemon.** The backend negotiates the daemon's `sync_features` and uses empty-block skipping, base64 encoding and parallel height windows where offered. Older daemons are unaffected. - **Optional transaction PoW server.** The backend can ask a `wrkz-txpow-server` for a transaction's proof of work and falls back to the local CPU if the server is unreachable, refuses, times out or returns a nonce that does not verify. `wrkz-wallet-api`, `wrkz-wallet` and `wrkz-service` always compute locally; the setting is exposed only in the desktop, mobile and web wallets. See [Transaction PoW Server](../guides/txpow-server.md). - A `ipc://`-prefixed daemon address keeps its prefix when parsed, so `daemonHost` can name a local socket. ### Fixes - A scan height is never derived by taking `min()` against a zero. - A stalled sync reported the wrong heights and never cleared. - One block at the chain tip could halt sync for good. - Deliberate gaps in a sync response — the ones the wallet itself asked for with `skipEmptyBlocks` — no longer halt sync. - Wallets are saved through a temporary file, so an interrupted save cannot truncate the container. --- ## 0.4.7 (build 270) — 2026-08-21 ### Breaking - **Fusion / optimize transactions were removed** from the wallet backend, wallet API and C API. `POST /transactions/send/fusion/basic` and `POST /transactions/send/fusion/advanced` no longer exist and return `404`. The `optimize` command was removed from `wrkz-wallet` at the same time. Use the sweep endpoints below to consolidate a wallet's inputs. ### Added - **`POST /transactions/send/sweep`** — send an amount to one destination across as many transactions as it takes. Body: `destination`, optional `amount` (omitted or `0` sweeps the whole balance), optional `paymentID`. - **`POST /transactions/send/sweep/all`** — sweep the entire balance to one destination. Body: `destination`, optional `paymentID`. Both answer `200` with a `transactions` array holding one entry per transaction attempted, each either `{"success": true, "transactionHash": …}` or `{"success": false, "errorCode": …, "errorMessage": …}`. A partial failure is reported per entry, not as an HTTP error. - `sweep` and `sweep_all` commands in `wrkz-wallet`, which show the estimated transaction count and total fee before confirming. ### Fixes - **Deadlock on wallet open, create, import and close** caused by redundant mutex locks in the dispatcher. - The shared-mutex write-operation classification was tightened for the `/node`, `/sync/refresh` and `/export/json` routes, which are the ones that must not run concurrently with reads. - Sweeping a specific amount returned change to self instead of sending every input to the recipient. - The sweep fee estimate now iterates output decomposition and accounts for the PoW nonce overhead. - `sweepToAddress` accepts integrated addresses. - Sync threads default to `hardware_concurrency`, with larger block batches and buffers. --- ## 0.4.6 (build 265) — 2026-03-08 and earlier See the [release list on GitHub](https://github.com/wrkzcoin/wrkzcoin/releases) and the [commit history](https://github.com/wrkzcoin/wrkzcoin/commits/development). --- # Community Where to ask a question, report a bug, and follow releases - and which of those channels is the right one for what you have. ## Where to find us | | | | --- | --- | | **Discord** | [discord.com/invite/KN8PjyuvG4](https://discord.com/invite/KN8PjyuvG4) | | **X** | [@wrkzcoin](https://x.com/wrkzcoin) | | **GitHub** | [github.com/wrkzcoin](https://github.com/wrkzcoin) | The same three links sit in the footer of every page. ## Where to take what **Discord** is the fastest route for anything operational — a node that will not start, a wallet that will not sync, a question about which flags to use. Bring the daemon log and the exact command line; both matter more than the description of the symptom. **GitHub issues** are for reproducible bugs and feature requests against [wrkzcoin/wrkzcoin](https://github.com/wrkzcoin/wrkzcoin/issues). Use the issue templates: they ask for the version string (`Wrkzd --version`), the platform, and the steps, which is what makes an issue actionable. **X** carries release announcements and network notices, including fork heights. It is a broadcast channel, not a support channel. ## Contributing Pull requests go to the `development` branch of [wrkzcoin/wrkzcoin](https://github.com/wrkzcoin/wrkzcoin). Build instructions are in `COMPILE.md`. The repository's `README.md` has a note for contributing developers about license headers: any open source project may use this software, but the license headers may only be appended to, never altered. Documentation lives in `docs/` in the same repository, and every page on this site has an edit link in its top-right corner that opens the source file on GitHub. ## Security reports Do not open a public issue for a vulnerability that affects funds, consensus or node availability. Report it privately to a maintainer on Discord first, so a fix can be released before the detail is public. ## Other resources | | | | --- | --- | | Website | [wrkz.work](https://wrkz.work) | | Latest release version | [latest.wrkz.work](https://latest.wrkz.work) | | Public transaction PoW server | `txpow.wrkz.work:443` — see [Transaction PoW Server](guides/txpow-server.md) | ---