Compare commits
411 Commits
bbf53a130c
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| e66f2b6c8c | |||
| 7ee20bb497 | |||
| 718cb82c13 | |||
| 403e145b30 | |||
| 036199a011 | |||
| 715cfd622e | |||
| b75801f5c4 | |||
| 6ddca6aed0 | |||
| ca1ac22b17 | |||
| 6bfb56aeb9 | |||
| 6c08a08127 | |||
| f5bde67f64 | |||
| 516e5ec688 | |||
| 008cc82ee5 | |||
| 2e6188a36d | |||
| e191680782 | |||
| 4db609fb52 | |||
| 980a100edd | |||
| 46eec37013 | |||
| 1738468f8c | |||
| ba03de938e | |||
| d043538e2f | |||
| 2502487e44 | |||
| cec330ee47 | |||
| 0ff56bf5a4 | |||
| ddd53dc006 | |||
| ee59a9e2cd | |||
| 62998179ec | |||
| 061db16004 | |||
| ec48538881 | |||
| 658c0f355b | |||
| 41f5548593 | |||
| 1dbbd44759 | |||
| b0cc6bcef4 | |||
| dd9bc0bd2b | |||
| b2a8c4487e | |||
| 65bb98cd09 | |||
| 129a8e6449 | |||
| df14533ad3 | |||
| 24bb32eb61 | |||
| d3dcb5a015 | |||
| 7249d11899 | |||
| ee6903ae56 | |||
| 1c5bc0d4d6 | |||
| dfca9bbd92 | |||
| 840ead3645 | |||
| 1c8e97c0a4 | |||
| f1e66c08d1 | |||
| c83348ab63 | |||
| da56bb73a0 | |||
| ad59d62b82 | |||
| 07f6d2efc9 | |||
| e84cea27d9 | |||
| a457f0262e | |||
| 4c466d78d1 | |||
| 0d4eaa02e7 | |||
| dddf1b6ec7 | |||
| a523611779 | |||
| 7c90d72ded | |||
| bb2337b552 | |||
| 19e31388d9 | |||
| 30139a5698 | |||
| 94b57e91e3 | |||
| 10289b33b5 | |||
| 42df658275 | |||
| 9d2c4a8ad3 | |||
| 65c06d0e6b | |||
| c7f89e5cd3 | |||
| 34ded6b441 | |||
| 0ea423dcea | |||
| 61738ef24a | |||
| ccbb0783b5 | |||
| c6b32e86c0 | |||
| 5caffc84b0 | |||
| ebf01bd858 | |||
| d91be3b34e | |||
| 4b46d4a20c | |||
| 69cd8ba048 | |||
| 4543cdc495 | |||
| 71fb81670f | |||
| a092097141 | |||
| 50873d1112 | |||
| 7e8628f94c | |||
| 0ae52ee095 | |||
| 529a74f6a7 | |||
| 88f1415bcc | |||
| b8cfc1c79c | |||
| a0fd9aff82 | |||
| 14b08d1358 | |||
| d689b02de9 | |||
| 84498b1312 | |||
| 8c2b0521a6 | |||
| 4411cb45e2 | |||
| 7db6564a0e | |||
| faf8e4e430 | |||
| 1affea0273 | |||
| 32d8d5663a | |||
| 3568b12dbf | |||
| e74989ec2c | |||
| fe18628216 | |||
| 4dc6b2ef8f | |||
| 5afb339ba7 | |||
| 0e8816e220 | |||
| 2035f71c65 | |||
| d007f3de39 | |||
| 1fdb6656aa | |||
| 3305be6318 | |||
| 19482214f1 | |||
| 4aed72c73b | |||
| 3e6987d72f | |||
| 42c511b13a | |||
| ef9141c248 | |||
| 8c88afe16e | |||
| d48c6db21c | |||
| ced1773e70 | |||
| d76c109063 | |||
| 5aa3cc4d51 | |||
| 4e15a2b1ba | |||
| 021ba88522 | |||
| c1d5f79502 | |||
| db212a81c6 | |||
| 67f6156030 | |||
| 76ed09fad0 | |||
| 77219c5ce7 | |||
| b26aff392b | |||
| b0ded528b1 | |||
| 301b3df77e | |||
| ae4e6486c6 | |||
| d838243caa | |||
| 75f33e41eb | |||
| 1263058ba7 | |||
| 9bcdf51895 | |||
| 67feaea767 | |||
| 3e1cdd15f3 | |||
| af2db4366c | |||
| cb74fbbff0 | |||
| 121a79e768 | |||
| 262891229a | |||
| dcd09dc542 | |||
| 4635a56e89 | |||
| 9c533fa485 | |||
| de11850c74 | |||
| 280a71e973 | |||
| 7891c689fb | |||
| f8150434d4 | |||
| 530bf24abf | |||
| e3b53258b7 | |||
| f9e4db4abf | |||
| bdccddfeee | |||
| d68bc27aac | |||
| 51667b5a27 | |||
| 96e05b4ba6 | |||
| 36386b2b84 | |||
| 2fc577fb18 | |||
| 5180f74a68 | |||
| fc5ce019e2 | |||
| 7826be4b13 | |||
| 5d6e78c719 | |||
| a1e4acfe86 | |||
| 419f8bb9cb | |||
| c5ef9e42ed | |||
| f0243307f1 | |||
| 95ddfa05ee | |||
| ecc0043559 | |||
| 3a9ff5f021 | |||
| 1faaa54a07 | |||
| 61f8c54742 | |||
| 48fc9ba8bb | |||
| 2552234610 | |||
| ff20a8a44e | |||
| 245d9bf976 | |||
| 1c248d727a | |||
| 57291ef28c | |||
| 18c1de87db | |||
| 0cdbbd024e | |||
| 779c9682d4 | |||
| 33735b1d52 | |||
| 19e83eb12a | |||
| f12153cdd0 | |||
| 8abc0ec113 | |||
| 5a677f6202 | |||
| efec3a50a1 | |||
| 4ed69d4e9f | |||
| e0d6d6bc4d | |||
| e9121fe49e | |||
| b5001ad4db | |||
| 1266b6e68e | |||
| 57f52b7a6c | |||
| a6964621a8 | |||
| 14f5fdc559 | |||
| 1a00ec3359 | |||
| 5c490c8d53 | |||
| d0586e5e75 | |||
| 24e8fb4942 | |||
| 1393d9ae1a | |||
| 3ce62326f9 | |||
| b20e7efb16 | |||
| 4473e7e00a | |||
| 2e8e214689 | |||
| 69a6fb3e64 | |||
| ed6120eef9 | |||
| a5bd2dadd7 | |||
| 25ee1496b4 | |||
| e2bc3623b6 | |||
| b32fe07cb1 | |||
| a0532275dd | |||
| 7df00b0909 | |||
| 090e288c44 | |||
| 6544c10ac1 | |||
| b2e104358d | |||
| de70e68472 | |||
| 0fe12d65df | |||
| 37c8287a12 | |||
| 6ff80354df | |||
| f58d009703 | |||
| 0e2c786ebf | |||
| d54c7f9e11 | |||
| b3251e9244 | |||
| c40f4d5815 | |||
| 5547ab1cac | |||
| f0867084f3 | |||
| 5167b52cbd | |||
| e7d11f620a | |||
| 168cae9306 | |||
| 317d9028a3 | |||
| 09ab8d52c5 | |||
| 101c835c46 | |||
| c71c3c3378 | |||
| 2e115aef39 | |||
| 574307f6ac | |||
| 88851f5eea | |||
| 5796664b51 | |||
| b71f8ae0a8 | |||
| 555f541c84 | |||
| 2b70ee5cd8 | |||
| d8c055c125 | |||
| 2470675746 | |||
| 3a597482da | |||
| b9b2d469d4 | |||
| 25fef8ff4d | |||
| bc788d008e | |||
| 9ee8f9a43b | |||
| bf91c4eb6c | |||
| 4d78ca0d7d | |||
| 00ee61fe64 | |||
| 3a4998f57c | |||
| d27017daeb | |||
| 2182c060e6 | |||
| 4ee830c5dd | |||
| b6567ee196 | |||
| 1a8d6fd30f | |||
| 9389859ee9 | |||
| e63aba6959 | |||
| fa240e7b99 | |||
| e21f7bf8aa | |||
| 47f228fa47 | |||
| 9f82bba260 | |||
| 323cb341f1 | |||
| ee6cac41c4 | |||
| 094771af81 | |||
| e40962cdf2 | |||
| 63b3a04716 | |||
| cf77c6cbe0 | |||
| 560f2bcf91 | |||
| 255d9399fa | |||
| 1fb6dc44d9 | |||
| 2ba8a799ff | |||
| 41b380449e | |||
| 4a65dce947 | |||
| c8183241c3 | |||
| 0bf80d2757 | |||
| 6f9123f651 | |||
| 320c659689 | |||
| 6ff1fda870 | |||
| 8c51b092f8 | |||
| 788d71a549 | |||
| 3d4b013b0c | |||
| dc07491abb | |||
| 7e568e4bf1 | |||
| 85a1080b52 | |||
| dbeae3ac98 | |||
| 9ff5508989 | |||
| 79e5adcbd3 | |||
| 6531d0c4d2 | |||
| 142a6826af | |||
| 070a516f4e | |||
| 3cec333d84 | |||
| fc438ab962 | |||
| 8c2c1c2aaf | |||
| a605e35409 | |||
| 6ed80d2d79 | |||
| e978db85ca | |||
| e00772db6e | |||
| 8f22db5eea | |||
| 5c883d4b91 | |||
| 274f7ea1af | |||
| 3799330bb0 | |||
| 53a10e149d | |||
| 1bc7f5c8cd | |||
| 20b22410e9 | |||
| 7195c25376 | |||
| a6921bca60 | |||
| 8ba4233b9b | |||
| 732d892d4d | |||
| afd612be7e | |||
| 4d769c8719 | |||
| 0e1b19d0f2 | |||
| b24212fb8f | |||
| 64fe8fc6c9 | |||
| b9881278af | |||
| 85b53baeaf | |||
| eece57c025 | |||
| 8765fdf362 | |||
| 98e0cce8ec | |||
| 5c87bc6e87 | |||
| 946958b591 | |||
| f5561c0dac | |||
| b3c2282b53 | |||
| ca14aaddc7 | |||
| a5da5562cf | |||
| c676ec8287 | |||
| f474b0d633 | |||
| af252575cf | |||
| 74bd22958a | |||
| cd60bded9f | |||
| 59b8c4da81 | |||
| 950d7ace50 | |||
| d52d3d1b7f | |||
| 9569b0ba43 | |||
| 50b0419dfe | |||
| 4f7a4fb38e | |||
| f511c0d509 | |||
| 235504657d | |||
| 76c2ac5db8 | |||
| 89bd21018a | |||
| 9d7054b245 | |||
| 70608fcb7a | |||
| 0c819afdd4 | |||
| db4778e6a7 | |||
| eb6114ee19 | |||
| 5d317f6be3 | |||
| a677c09984 | |||
| 6611d57147 | |||
| 6a4e98b7ed | |||
| 4b9d6f7db5 | |||
| 043cdc7128 | |||
| 2daea67a1e | |||
| e6b91ca661 | |||
| c6e28fc4da | |||
| 268eba6321 | |||
| 3119440cd9 | |||
| 59c55e33f8 | |||
| a8b5d2f6a3 | |||
| 012341b1a4 | |||
| 5586f334a4 | |||
| 863d015628 | |||
| a78a13edf3 | |||
| e95ad50e41 | |||
| 975743f754 | |||
| 948ef419ac | |||
| 9edab31728 | |||
| ee8a08e569 | |||
| ddb810e2f3 | |||
| aa26ab5fbd | |||
| 40cec14ebf | |||
| 88d30c1612 | |||
| dbe6546f9f | |||
| 6e2db50675 | |||
| d2dccbac05 | |||
| 1860e9b277 | |||
| 648a6c29e0 | |||
| c013038ef7 | |||
| 20cbad687d | |||
| ddca8b2e43 | |||
| 50e9e7d75e | |||
| d755f6816b | |||
| 84d2b9c39d | |||
| 27e9a8df26 | |||
| 8d51f374cd | |||
| 7ab8f5d82c | |||
| e4b1b644b3 | |||
| 8ef8abeb37 | |||
| 09f876eb60 | |||
| aa3bd4e304 | |||
| 801fa2b96b | |||
| e0bfeb2f29 | |||
| 9ed4fbc476 | |||
| 2b3277529e | |||
| d79d013060 | |||
| 474250bb50 | |||
| 40dd6d45b2 | |||
| 1b97476a54 | |||
| d7bc5c638a | |||
| 39f193a264 | |||
| 4023af9466 | |||
| 853e3e0f17 | |||
| 1bcfcd1c73 | |||
| 6bb35bd3e1 | |||
| 2c5a658ea5 | |||
| f416ff3d09 | |||
| b3a0ce29ed | |||
| e2265b0bdf | |||
| 9368b945e0 | |||
| eda19ef6d5 | |||
| 7d48936f30 | |||
| bc2ef4ffaf | |||
| 0da1657b12 | |||
| 0d30ebc8e8 | |||
| c778e03f37 | |||
| 0a3f68f1eb | |||
| 1a5c4e8744 |
13
.gitignore
vendored
13
.gitignore
vendored
@@ -41,3 +41,16 @@ asmap.dat
|
|||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
# Local-only archive of superseded lite-wallet design/planning docs (untracked)
|
||||||
|
docs/_archive/
|
||||||
|
|
||||||
|
# ed25519 release-signing keys — the secret key must NEVER be committed
|
||||||
|
*.ed25519.key
|
||||||
|
*.ed25519.pub.b64
|
||||||
|
|
||||||
|
|
||||||
|
# Lite-backend deps are fetched (or `cargo vendor`-ed locally for offline); not committed.
|
||||||
|
third_party/silentdragonxlite/lib/vendor/
|
||||||
|
|
||||||
|
# Generated by configure_file from res/ObsidianDragon.manifest.in (do not track)
|
||||||
|
res/ObsidianDragon.manifest
|
||||||
|
|||||||
102
CLAUDE.md
Normal file
102
CLAUDE.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## What this is
|
||||||
|
|
||||||
|
ObsidianDragon is a portable, full-node GUI wallet for DragonX (DRGX), written in C++17 using SDL3 + Dear ImGui (immediate-mode). It drives a `dragonxd` full node over JSON-RPC and can embed/extract the daemon itself. A separate **Lite** variant (`ObsidianDragonLite`) drops the full node and instead talks to an external lite-wallet backend library.
|
||||||
|
|
||||||
|
## Build & run
|
||||||
|
|
||||||
|
`build.sh` is the single entry point for all builds. `setup.sh` (repo root) installs/validates dependencies.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build.sh # Dev build (native, no packaging) -> build/linux/bin/ObsidianDragon
|
||||||
|
./build.sh --lite # Dev build of the Lite variant -> build/linux/bin/ObsidianDragonLite
|
||||||
|
./build.sh --clean # Wipe the build dir first
|
||||||
|
./build.sh --linux-release # Release zip + AppImage -> release/linux/
|
||||||
|
./build.sh --win-release # Windows cross-compile (mingw-w64) -> release/windows/
|
||||||
|
./build.sh --mac-release # macOS .app bundle + DMG
|
||||||
|
./setup.sh --check # Report missing build deps without installing
|
||||||
|
```
|
||||||
|
|
||||||
|
Dev builds use `build/linux/` (or `build/mac/`). To re-build incrementally without re-running CMake config: `cmake --build build/linux -j$(nproc)`.
|
||||||
|
|
||||||
|
The wallet connects to the daemon using credentials in `~/.hush/DRAGONX/DRAGONX.conf` (`rpcuser`/`rpcpassword`/`rpcport`). It searches for `dragonxd`/`dragonx-cli` binaries in the **executable's own directory first**, so dropping custom node builds next to the wallet binary overrides the bundled ones.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Tests live in `tests/test_phase4.cpp` — a single large translation unit using a custom assertion harness (`EXPECT_TRUE`/`EXPECT_EQ`/`EXPECT_NEAR` macros, one `main()`, exit code = failure count). `include(CTest)` enables `BUILD_TESTING=ON` by default, so the `ObsidianDragonTests` executable is built alongside the app.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd build/linux && ctest --output-on-failure # run the suite
|
||||||
|
./build/linux/bin/ObsidianDragonTests # run the binary directly (same thing)
|
||||||
|
```
|
||||||
|
|
||||||
|
There is no per-test filtering — it is one binary that runs every assertion. The suite exercises the services layer, lite-wallet bridge, and pure helpers (parsers, formatters, model classes) without launching the GUI. Fixtures are under `tests/fixtures/` (path injected as `DRAGONX_TEST_FIXTURE_DIR`).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Entry & main loop.** `src/main.cpp` owns SDL3 window creation, ImGui/OpenGL(or DX11 on Windows) setup, and the frame loop. The `App` class is the central controller; because it is large it is split across four files that all implement the same class:
|
||||||
|
- `src/app.cpp` — core lifecycle, the per-frame `render()`, tab dispatch
|
||||||
|
- `src/app_network.cpp` — RPC orchestration, sync, peers, daemon lifecycle
|
||||||
|
- `src/app_security.cpp` — encryption, PIN/lock screen, key import/export, backup
|
||||||
|
- `src/app_wizard.cpp` — first-run wizard
|
||||||
|
|
||||||
|
**RPC.** All daemon calls go through `src/rpc/` (`rpc_client`, `connection`, `rpc_worker`). **Never block the main/UI thread with synchronous network I/O — dispatch through `RPCWorker`** (async). `rpc/types.h` holds the shared DTOs.
|
||||||
|
|
||||||
|
**Services** (`src/services/`) hold the non-UI state machines that the `App` owns: `NetworkRefreshService` + `RefreshScheduler` (polling/refresh of balance, peers, txs on intervals) and the `WalletSecurity*` controller/workflow stack (encryption & unlock flows).
|
||||||
|
|
||||||
|
**Data model** (`src/data/`): `WalletState`, `address_book`, `transaction_history_cache`, `exchange_info`. UI reads from these.
|
||||||
|
|
||||||
|
**UI** (`src/ui/`): `windows/` are the tabs and dialogs (one pair per screen, e.g. `send_tab`, `mining_tab`, `console_tab`), `pages/` are multi-section screens (Settings), `material/` is the design-system layer (the live helpers `color_theme`, `colors`, `type`/`typography`, `draw_helpers`, `layout`, `project_icons`, `components/buttons`), `schema/` loads the TOML UI schema/skins, `effects/` is GL post-processing (blur/acrylic).
|
||||||
|
|
||||||
|
**Lite wallet** (`src/wallet/`): the bridge to an external `litelib_*` C-ABI backend. `lite_client_bridge` loads the backend (via direct `litelib_*` externs in `linkedSdxl()`) and owns each Rust string through `lite_owned_string` (copy-before-free / free-once). On top sit `lite_connection_service`, `lite_sync_service`, `lite_result_parsers`, `lite_wallet_gateway`, `lite_wallet_state_mapper`, and `lite_wallet_lifecycle_service`, all driven by `lite_wallet_controller`. The real frontend entry points are `lite_wallet_lifecycle_ui_adapter` and `lite_wallet_server_selection_adapter` (used by `src/ui/pages/settings_page.cpp`); everything else is reachable through them. (The prebuilt-backend symbol check for `DRAGONX_ENABLE_LITE_BACKEND` is done in CMake against the symbols inventory — see below — not in C++.)
|
||||||
|
|
||||||
|
> ⚠️ **Do not regrow the `_plan`/`_batch` churn.** This directory previously held ~160 dead `lite_wallet_*_plan` / `*_batch*_receipt_custody_acceptance_confirmation_archive_handoff_*` files (filenames up to 250 chars) — auto-generated scaffolding that never reached the shipping binary. They were deleted. When extending lite-wallet behavior, **edit the named service/bridge/runtime files in place**; never add another "promotion/receipt/custody/handoff/stewardship" wrapper layer. `scripts/check-source-hygiene.sh` (wired as a `.git/hooks/pre-commit` hook) blocks >80-char filenames and chained churn-token names — run it in CI too.
|
||||||
|
|
||||||
|
**Chat** (`src/chat/*`): the HushChat protocol port (Contacts/Chat tabs, seed-derived identity, secretstream crypto, seed-encrypted sqlite store, two-variant send/receive transport). Runtime behavior is gated by `DRAGONX_ENABLE_CHAT`, now **default ON** (the sources always compile; the flag folds the feature away at runtime via `hushChatFeatureEnabledAtBuild()`). Full-node chat derives its identity from the wallet's mnemonic (`z_exportmnemonic`, portable/SDXLite-compatible) or, for legacy/non-mnemonic wallets, a stable z-address spending key (`z_exportkey`).
|
||||||
|
|
||||||
|
## Build variants & feature gating
|
||||||
|
|
||||||
|
Variants are selected with CMake options (set by `build.sh` flags), surfaced to C++ as compile definitions:
|
||||||
|
- `DRAGONX_BUILD_LITE` (`--lite`) → `DRAGONX_LITE_BUILD` define; renames the app to `ObsidianDragonLite` and excludes embedded-daemon / full-node assets (Sapling params, asmap, dragonxd).
|
||||||
|
- `DRAGONX_ENABLE_LITE_BACKEND` → links a real external lite backend. Requires `--lite`, link mode `imported`, ABI `sdxl-c-v1`, and a symbols inventory file (built by `scripts/build-lite-backend-artifact.sh`); CMake hard-fails if any required `litelib_*` symbol is missing. The backend **source is vendored in-tree** at `third_party/silentdragonxlite/` — the `qtlib` C-ABI wrapper (`lib/`, produces `libsilentdragonxlite.a`) and the `silentdragonxlitelib` core (`silentdragonxlite-cli/lib/`, with `proto/` + `res/`). `build-lite-backend-artifact.sh` defaults `--backend-dir` there, so the lite wallet builds **without** the upstream SilentDragonXLite repo. External build inputs are limited to the **Rust toolchain (rustc/cargo 1.63)** plus two project-controlled sources on `git.dragonx.is`: the librustzcash crates come from the mirror `git.dragonx.is/DragonX/librustzcash` (the 6 `git =` deps in the core `Cargo.toml`, pinned to rev `acff1444…`), and the **Sapling params are not committed** (gitignored) — the build fetches them from the `git.dragonx.is/DragonX/zcash-params` release `sapling-v1` and verifies their SHA-256 before rust-embed bakes them in (`ensure_sapling_params`; override the URL with `SAPLING_PARAMS_BASE_URL`). Other crate deps come from crates.io. For a fully offline build, `cargo vendor` into `third_party/silentdragonxlite/lib/vendor/` and add a `vendored-sources` redirect to `lib/.cargo/config.toml` (the build script symlinks `vendor/` into its prepared dir if present); `vendor/` is gitignored.
|
||||||
|
- `DRAGONX_ENABLE_CHAT` → `DRAGONX_ENABLE_CHAT` define gating the chat module.
|
||||||
|
|
||||||
|
Guard full-node-only code paths with `#if DRAGONX_LITE_BUILD` / chat code with `DRAGONX_ENABLE_CHAT`.
|
||||||
|
|
||||||
|
## Lite wallet status
|
||||||
|
|
||||||
|
The Lite variant is **functionally complete and runtime-verified on Linux + Windows** (work lives on branch `cleanup/lite-plan-churn`, **local-only — not pushed yet**):
|
||||||
|
- **Implemented:** lifecycle (create/open/restore + auto-open on startup), sync, refresh, send / shield / import / export / seed, persistence (the backend does *not* auto-save after sync/send/shield — the controller triggers `save` at those points), and passphrase **encryption** (encrypt/unlock/lock/decrypt + Settings UI + send-time & startup unlock; the backend locks immediately on `encrypt`). All controller-tested against the fake backend (`tests/fake_lite_backend.h`) and smoke-verified against the real SDXL backend via `tools/lite_smoke` (incl. a full sync). GUI is wired end-to-end with lite-appropriate wording; the full-node RPC connect loop / wizard / daemon strings are gated out of lite (lite "online" is derived from `lite_wallet_->walletOpen()`, not RPC).
|
||||||
|
- **Packaging:** `./build.sh --lite-backend --linux-release` (zip + AppImage, **verified**) and `--win-release` (cross-compiled `.exe`, **verified**; first build the Windows backend artifact with `scripts/build-lite-backend-artifact.sh --platform windows`). macOS `--lite-backend --mac-release` is **wired but not yet verified on this Linux box** (needs macOS/osxcross): the `.app`/launcher/rpath/`CFBundleExecutable` follow `ObsidianDragonLite`, full-node assets are skipped, and the lite variant gets its own `CFBundleName` ("DragonX Wallet Lite"), bundle id (`is.hush.dragonx.lite`), and DMG name so it can coexist with the full-node app. All variants correctly exclude full-node assets.
|
||||||
|
- **Rollout / kill-switch (implemented):** `wallet/lite_rollout_policy.{h,cpp}` is a pure, fail-open gate (local-only, no network) feeding `LiteWalletLifecycleService::availability()` (new `RolloutDisabled` reason). Inputs: the emergency env var `DRAGONX_LITE_KILL_SWITCH` (absolute — not even `force_on` bypasses it); a `lite_rollout` setting (`auto`/`force_on`/`force_off`); and an optional **locally-cached** manifest at `<config-dir>/lite_rollout.json` (`global_enabled`, `min_version`/`max_version`, `blocked_versions`, `rollout_permille`, `message`) keyed for staged rollout on a hashed, never-transmitted per-install id. A signed remote fetcher can populate that cache later without touching the policy. Resolved in `App::rebuildLiteWallet()`; the disable message surfaces via the lifecycle status. Unit-tested + runtime-verified (env / manifest / control).
|
||||||
|
- **Remaining (M5b):** verify the wired macOS `--lite` packaging on a Mac/osxcross, CI backend-artifact build + signing.
|
||||||
|
- **To publish:** rename branch → `feat/lite-wallet`, base the PR on `dev` (the full-node UX is already there), and handle the dormant gated-OFF HushChat content bundled in commit `af06b8b`.
|
||||||
|
|
||||||
|
The detailed milestone plan and design history (the v2 plan, backend artifact/ABI/signing design docs, the v1 plan, chat specs, etc.) are kept **untracked** under `docs/_archive/`.
|
||||||
|
|
||||||
|
## Miner updater (xmrig)
|
||||||
|
|
||||||
|
The mining tab's pool section has an **"Update miner…"** button that downloads/verifies/installs the latest DRG-XMRig from the project Gitea (`util/XmrigUpdater` + `ui/windows/xmrig_download_dialog.h`). Flow: query `git.dragonx.is/api/v1/repos/DragonX/drg-xmrig/releases/latest` → pick the asset for this platform (`linux-x64` / `win-x64` / `macos-x86_64`; no match → "Unavailable") → libcurl download (TLS verified) → verify the archive **SHA-256** (from the release body) **and** a detached **ed25519 signature** → miniz-extract the binary (flattening the versioned subdir) into `resources::getDaemonDirectory()`. The whole archive is verified, so extracted members are trusted by transitivity (no per-member hash check). The pure, no-I/O core is split into `xmrig_updater_core.cpp` for unit tests; an env-gated (`DRAGONX_TEST_NETWORK=1`) test exercises the worker live. A **"Browse all releases…"** button (the `/releases` list, newest first, pre-releases included) lets users pin an older or pre-release build — same verify/install path via `startInstallRelease()`; the picker UI is shared with the daemon updater (`ui/windows/release_list_view.h`).
|
||||||
|
|
||||||
|
**Signature verification is enforced** (`kXmrigRequireSignature = true` in `src/util/xmrig_updater.h`), checked against the public key pinned in `kXmrigSignaturePublicKeyBase64`. **Consequence for releases:** every `drg-xmrig` release MUST ship a detached signature per archive or the in-app updater refuses it. To cut a release: build the archives, then `scripts/sign-xmrig-release.sh sign <secret.key> <archive.zip>...` (OpenSSL-based, no extra deps) and upload each `<archive>.sig` as a release asset alongside its `.zip`. The signing **secret key must stay offline** (it is gitignored: `*.ed25519.key`); only its base64 public key is pinned in the source. To rotate the key, regenerate (`scripts/sign-xmrig-release.sh keygen`) and update `kXmrigSignaturePublicKeyBase64`. An emergency env override is not provided — disabling verification means setting `kXmrigSignaturePublicKeyBase64` empty (and rebuilding).
|
||||||
|
|
||||||
|
## Daemon updater (dragonxd)
|
||||||
|
|
||||||
|
Settings → **NODE & SECURITY → DAEMON BINARY** has a **"Check for updates…"** button that downloads/verifies/installs the latest **dragonxd full node** from the project Gitea — the full-node sibling of the xmrig updater (`util/DaemonUpdater` + `ui/windows/daemon_download_dialog.h`, pure no-I/O core in `daemon_updater_core.cpp`; gated full-node-only via `supportsFullNodeLifecycleActions()`). Flow: query `git.dragonx.is/api/v1/repos/DragonX/dragonx/releases/latest` → pick the archive for this platform (`linux-amd64` / `macos` / `win64`; no match → "Unavailable") → libcurl download (TLS verified) → verify the archive **SHA-256** (parsed from the release body's markdown **checksum table**, not xmrig's `<hash> <name>` lines) **and** a detached **ed25519 signature** → miniz-extract the three executables (`dragonxd`/`dragonx-cli`/`dragonx-tx`, flattening the versioned subdir) into `resources::getDaemonDirectory()`. The archive also bundles Sapling params/asmap, which the updater deliberately leaves to the wallet's own resource extraction. Install is **atomic and safe while the node runs** (POSIX `rename()` replaces the in-use binary; Windows moves the locked `.exe` aside to `.old`); the new binary takes effect on the **next daemon start**, so the Done screen offers **"Restart daemon now"** (`App::restartDaemon()`). A **"Browse all releases…"** button (shared `release_list_view.h` picker) lets users pin a specific/older/pre-release node build via `startInstallRelease()` — with a downgrade caution, since an older binary may not match current chain data.
|
||||||
|
|
||||||
|
**Signature verification is enforced** (`kDaemonRequireSignature = true` in `src/util/daemon_updater.h`), checked against `kDaemonSignaturePublicKeyBase64`. **Consequence for releases:** every `dragonx` release MUST ship a detached `<archive>.sig` per platform archive or the in-app updater refuses it (as of v1.0.2 the releases publish SHA-256 but **no** signatures yet — sign + upload them to enable in-app updates). To cut a release: `scripts/sign-daemon-release.sh sign <secret.key> dragonx-<ver>-{linux-amd64,macos,win64}.zip` (OpenSSL-based) and upload each `.sig` next to its `.zip`. The signing **secret key stays offline** (gitignored `*.ed25519.key`; this repo's is `dragonx-daemon.ed25519.key`); only the base64 public key is pinned. To rotate: `scripts/sign-daemon-release.sh keygen` and update `kDaemonSignaturePublicKeyBase64`. The generic SHA-256 / ed25519 primitives are shared with the miner updater (`util::sha256Hex` / `util::verifyXmrigSignature`).
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
The version has a **single source of truth**: `project(... VERSION 1.2.0 ...)` plus `DRAGONX_VERSION_SUFFIX` in `CMakeLists.txt`. CMake generates `build/.../generated/dragonx_generated_version.h` from `src/config/version.h.in`. Do not hand-edit generated version output or hardcode version strings — bump the `project()` version in `CMakeLists.txt`.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- **C++17.** Match the surrounding code's style per file.
|
||||||
|
- **Icons:** use the Material Design icon font defines (`ICON_MD_*`); never raw Unicode glyphs.
|
||||||
|
- **UI layout values** belong in `res/themes/ui.toml`, read via `schema::UI()` — do not hardcode pixel sizes/offsets in code.
|
||||||
|
- **i18n:** user-facing strings are translated via `src/util/i18n`; the English source of truth is the `strings_[...]` map in `src/util/i18n.cpp`, and the per-language translations live as the **source of truth** in `res/lang/` (`de`, `es`, `fr`, `ja`, `ko`, `pt`, `ru`, `zh`). **Edit those JSONs directly and additively** — write with `json.dump(..., indent=4, sort_keys=True, ensure_ascii=False)`; never bulk-regenerate/overwrite a whole file (that path silently dropped ~285 keys/language before). `scripts/add_missing_translations.py` back-fills only the keys missing from a JSON (non-destructive), and `scripts/build_cjk_subset.py` rebuilds the CJK subset font (`res/fonts/NotoSansCJK-Subset.ttf`) after new CJK glyphs are added.
|
||||||
|
- **Commits:** the history uses Conventional Commits (`feat(scope): …`, `fix(scope): …`). PRs target `master`.
|
||||||
532
CMakeLists.txt
532
CMakeLists.txt
@@ -15,11 +15,20 @@ if(APPLE)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
project(ObsidianDragon
|
project(ObsidianDragon
|
||||||
VERSION 1.1.2
|
VERSION 2.0.0
|
||||||
LANGUAGES C CXX
|
LANGUAGES C CXX
|
||||||
DESCRIPTION "DragonX Cryptocurrency Wallet"
|
DESCRIPTION "DragonX Cryptocurrency Wallet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Pre-release suffix (e.g. "-rc1", "-beta2"). Leave empty for stable releases.
|
||||||
|
set(DRAGONX_VERSION_SUFFIX "")
|
||||||
|
|
||||||
|
# ObsidianDragonLite is versioned INDEPENDENTLY of the full-node app above. The active variant's
|
||||||
|
# version flows to the generated header, the Windows .rc/manifest, and build.sh's release names via
|
||||||
|
# DRAGONX_APP_VERSION* (resolved in the lite/full block below).
|
||||||
|
set(DRAGONX_LITE_VERSION "1.0.0")
|
||||||
|
set(DRAGONX_LITE_VERSION_SUFFIX "")
|
||||||
|
|
||||||
# C++17 standard
|
# C++17 standard
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
@@ -33,6 +42,132 @@ endif()
|
|||||||
# Options
|
# Options
|
||||||
option(DRAGONX_USE_SYSTEM_SDL3 "Use system SDL3 instead of fetching" ON)
|
option(DRAGONX_USE_SYSTEM_SDL3 "Use system SDL3 instead of fetching" ON)
|
||||||
option(DRAGONX_ENABLE_EMBEDDED_DAEMON "Enable embedded dragonxd support" ON)
|
option(DRAGONX_ENABLE_EMBEDDED_DAEMON "Enable embedded dragonxd support" ON)
|
||||||
|
option(DRAGONX_BUILD_LITE "Build ObsidianDragonLite variant without full-node features" OFF)
|
||||||
|
option(DRAGONX_ENABLE_LITE_BACKEND "Enable real lite wallet backend integration" OFF)
|
||||||
|
option(DRAGONX_ENABLE_CHAT "Enable the HushChat protocol/UI integration" ON)
|
||||||
|
set(DRAGONX_LITE_BACKEND_LIBRARY "" CACHE FILEPATH "Path to a prebuilt SDXL-compatible lite backend library")
|
||||||
|
set(DRAGONX_LITE_BACKEND_INCLUDE_DIR "" CACHE PATH "Optional include directory for SDXL-compatible lite backend headers")
|
||||||
|
set(DRAGONX_LITE_BACKEND_EXTRA_LIBS "" CACHE STRING "Additional libraries needed by the SDXL-compatible lite backend")
|
||||||
|
set(DRAGONX_LITE_BACKEND_LINK_MODE "imported" CACHE STRING "Lite backend link mode; Phase 1 supports imported only")
|
||||||
|
set_property(CACHE DRAGONX_LITE_BACKEND_LINK_MODE PROPERTY STRINGS imported)
|
||||||
|
set(DRAGONX_LITE_BACKEND_ABI "sdxl-c-v1" CACHE STRING "Expected lite backend C ABI version")
|
||||||
|
set(DRAGONX_LITE_BACKEND_SYMBOLS_FILE "" CACHE FILEPATH "Path to generated lite backend exported-symbol inventory")
|
||||||
|
set(DRAGONX_LITE_BACKEND_MANIFEST "" CACHE FILEPATH "Optional path to generated lite backend artifact manifest")
|
||||||
|
option(DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE "Require verified signature metadata in the lite backend artifact manifest" OFF)
|
||||||
|
set(DRAGONX_LITE_BACKEND_REQUIRED_SYMBOLS
|
||||||
|
litelib_wallet_exists
|
||||||
|
litelib_initialize_new
|
||||||
|
litelib_initialize_new_from_phrase
|
||||||
|
litelib_initialize_existing
|
||||||
|
litelib_execute
|
||||||
|
litelib_rust_free_string
|
||||||
|
litelib_check_server_online
|
||||||
|
litelib_shutdown
|
||||||
|
)
|
||||||
|
|
||||||
|
if(DRAGONX_BUILD_LITE)
|
||||||
|
set(DRAGONX_APP_NAME "ObsidianDragonLite")
|
||||||
|
set(DRAGONX_BINARY_NAME "ObsidianDragonLite")
|
||||||
|
# NOTE: do NOT FORCE-write DRAGONX_ENABLE_EMBEDDED_DAEMON=OFF into the cache here. A forced
|
||||||
|
# cache write persists into a later full-node reconfigure of the same build dir and silently
|
||||||
|
# disables the embedded daemon — the binary still embeds/extracts, but isUsingEmbeddedDaemon()
|
||||||
|
# returns false, so it "unpacks dragonxd but never starts" (the 1.3.0 regression). It is also
|
||||||
|
# redundant: makeWalletCapabilities() already forces the embedded-daemon capability off for any
|
||||||
|
# lite build via `fullNodeBuild && embeddedDaemonCompiled`, so lite never launches a daemon
|
||||||
|
# regardless of this flag. build.sh sets the flag explicitly per variant to defeat stale caches.
|
||||||
|
set(DRAGONX_APP_VERSION "${DRAGONX_LITE_VERSION}")
|
||||||
|
set(DRAGONX_APP_VERSION_SUFFIX "${DRAGONX_LITE_VERSION_SUFFIX}")
|
||||||
|
else()
|
||||||
|
set(DRAGONX_APP_NAME "ObsidianDragon")
|
||||||
|
set(DRAGONX_BINARY_NAME "ObsidianDragon")
|
||||||
|
set(DRAGONX_APP_VERSION "${PROJECT_VERSION}")
|
||||||
|
set(DRAGONX_APP_VERSION_SUFFIX "${DRAGONX_VERSION_SUFFIX}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Split the active version into numeric components for the generated header + Windows VERSIONINFO.
|
||||||
|
string(REPLACE "." ";" _dragonx_ver_parts "${DRAGONX_APP_VERSION}")
|
||||||
|
list(GET _dragonx_ver_parts 0 DRAGONX_APP_VERSION_MAJOR)
|
||||||
|
list(GET _dragonx_ver_parts 1 DRAGONX_APP_VERSION_MINOR)
|
||||||
|
list(GET _dragonx_ver_parts 2 DRAGONX_APP_VERSION_PATCH)
|
||||||
|
|
||||||
|
set(DRAGONX_LITE_BACKEND_READY OFF)
|
||||||
|
if(DRAGONX_ENABLE_LITE_BACKEND)
|
||||||
|
if(NOT DRAGONX_BUILD_LITE)
|
||||||
|
message(FATAL_ERROR "DRAGONX_ENABLE_LITE_BACKEND is only supported with DRAGONX_BUILD_LITE=ON")
|
||||||
|
endif()
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_LINK_MODE STREQUAL "imported")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_LINK_MODE currently supports only 'imported'; runtime dynamic loading is a later bridge-runtime phase")
|
||||||
|
endif()
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_ABI STREQUAL "sdxl-c-v1")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_ABI must be sdxl-c-v1")
|
||||||
|
endif()
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_LIBRARY)
|
||||||
|
message(FATAL_ERROR "DRAGONX_ENABLE_LITE_BACKEND requires DRAGONX_LITE_BACKEND_LIBRARY to point at an SDXL-compatible artifact")
|
||||||
|
endif()
|
||||||
|
if(NOT EXISTS "${DRAGONX_LITE_BACKEND_LIBRARY}")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_LIBRARY does not exist: ${DRAGONX_LITE_BACKEND_LIBRARY}")
|
||||||
|
endif()
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_SYMBOLS_FILE)
|
||||||
|
message(FATAL_ERROR "DRAGONX_ENABLE_LITE_BACKEND requires DRAGONX_LITE_BACKEND_SYMBOLS_FILE generated by scripts/build-lite-backend-artifact.sh")
|
||||||
|
endif()
|
||||||
|
if(NOT EXISTS "${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_SYMBOLS_FILE does not exist: ${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||||
|
endif()
|
||||||
|
file(STRINGS "${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}" DRAGONX_LITE_BACKEND_SYMBOL_LINES)
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_SYMBOL_LINES)
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_SYMBOLS_FILE is empty: ${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||||
|
endif()
|
||||||
|
foreach(DRAGONX_LITE_REQUIRED_SYMBOL IN LISTS DRAGONX_LITE_BACKEND_REQUIRED_SYMBOLS)
|
||||||
|
list(FIND DRAGONX_LITE_BACKEND_SYMBOL_LINES "${DRAGONX_LITE_REQUIRED_SYMBOL}" DRAGONX_LITE_SYMBOL_INDEX)
|
||||||
|
if(DRAGONX_LITE_SYMBOL_INDEX EQUAL -1)
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_SYMBOLS_FILE is missing required symbol: ${DRAGONX_LITE_REQUIRED_SYMBOL}")
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
if(DRAGONX_LITE_BACKEND_MANIFEST AND NOT EXISTS "${DRAGONX_LITE_BACKEND_MANIFEST}")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_MANIFEST does not exist: ${DRAGONX_LITE_BACKEND_MANIFEST}")
|
||||||
|
endif()
|
||||||
|
if(DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE)
|
||||||
|
if(NOT DRAGONX_LITE_BACKEND_MANIFEST)
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE requires DRAGONX_LITE_BACKEND_MANIFEST")
|
||||||
|
endif()
|
||||||
|
file(READ "${DRAGONX_LITE_BACKEND_MANIFEST}" DRAGONX_LITE_BACKEND_MANIFEST_JSON)
|
||||||
|
string(JSON DRAGONX_LITE_SIGNATURE_STATUS ERROR_VARIABLE DRAGONX_LITE_SIGNATURE_STATUS_ERROR GET "${DRAGONX_LITE_BACKEND_MANIFEST_JSON}" signature_verification verification_status)
|
||||||
|
if(DRAGONX_LITE_SIGNATURE_STATUS_ERROR)
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_MANIFEST is missing signature verification status")
|
||||||
|
endif()
|
||||||
|
if(NOT DRAGONX_LITE_SIGNATURE_STATUS STREQUAL "verified")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE requires verified signature metadata")
|
||||||
|
endif()
|
||||||
|
string(JSON DRAGONX_LITE_SIGNATURE_VERIFIED_SHA ERROR_VARIABLE DRAGONX_LITE_SIGNATURE_VERIFIED_SHA_ERROR GET "${DRAGONX_LITE_BACKEND_MANIFEST_JSON}" signature_verification verified_artifact_sha256)
|
||||||
|
string(JSON DRAGONX_LITE_ARTIFACT_SHA ERROR_VARIABLE DRAGONX_LITE_ARTIFACT_SHA_ERROR GET "${DRAGONX_LITE_BACKEND_MANIFEST_JSON}" artifact sha256)
|
||||||
|
if(DRAGONX_LITE_SIGNATURE_VERIFIED_SHA_ERROR OR DRAGONX_LITE_ARTIFACT_SHA_ERROR)
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_MANIFEST is missing artifact/signature SHA-256 metadata")
|
||||||
|
endif()
|
||||||
|
if(NOT DRAGONX_LITE_SIGNATURE_VERIFIED_SHA STREQUAL DRAGONX_LITE_ARTIFACT_SHA)
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_MANIFEST signature metadata does not verify the artifact SHA-256")
|
||||||
|
endif()
|
||||||
|
string(JSON DRAGONX_LITE_SIGNATURE_PERFORMED ERROR_VARIABLE DRAGONX_LITE_SIGNATURE_PERFORMED_ERROR GET "${DRAGONX_LITE_BACKEND_MANIFEST_JSON}" signature_verification verification_performed)
|
||||||
|
if(DRAGONX_LITE_SIGNATURE_PERFORMED_ERROR OR NOT DRAGONX_LITE_SIGNATURE_PERFORMED)
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE requires verification_performed=true")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(dragonx_lite_backend UNKNOWN IMPORTED)
|
||||||
|
set_target_properties(dragonx_lite_backend PROPERTIES
|
||||||
|
IMPORTED_LOCATION "${DRAGONX_LITE_BACKEND_LIBRARY}"
|
||||||
|
)
|
||||||
|
if(DRAGONX_LITE_BACKEND_INCLUDE_DIR)
|
||||||
|
if(NOT IS_DIRECTORY "${DRAGONX_LITE_BACKEND_INCLUDE_DIR}")
|
||||||
|
message(FATAL_ERROR "DRAGONX_LITE_BACKEND_INCLUDE_DIR does not exist: ${DRAGONX_LITE_BACKEND_INCLUDE_DIR}")
|
||||||
|
endif()
|
||||||
|
set_target_properties(dragonx_lite_backend PROPERTIES
|
||||||
|
INTERFACE_INCLUDE_DIRECTORIES "${DRAGONX_LITE_BACKEND_INCLUDE_DIR}"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
set(DRAGONX_LITE_BACKEND_READY ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(CTest)
|
||||||
|
|
||||||
# Output directories
|
# Output directories
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||||
@@ -105,6 +240,32 @@ FetchContent_Declare(
|
|||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(tomlplusplus)
|
FetchContent_MakeAvailable(tomlplusplus)
|
||||||
|
|
||||||
|
# SQLite amalgamation - local Explorer block-summary cache
|
||||||
|
FetchContent_Declare(
|
||||||
|
sqlite3
|
||||||
|
URL https://www.sqlite.org/2024/sqlite-amalgamation-3450300.zip
|
||||||
|
URL_HASH SHA256=ea170e73e447703e8359308ca2e4366a3ae0c4304a8665896f068c736781c651
|
||||||
|
)
|
||||||
|
FetchContent_GetProperties(sqlite3)
|
||||||
|
if(NOT sqlite3_POPULATED)
|
||||||
|
FetchContent_Populate(sqlite3)
|
||||||
|
endif()
|
||||||
|
file(GLOB SQLITE3_AMALGAMATION_C CONFIGURE_DEPENDS
|
||||||
|
${sqlite3_SOURCE_DIR}/sqlite3.c
|
||||||
|
${sqlite3_SOURCE_DIR}/*/sqlite3.c
|
||||||
|
)
|
||||||
|
if(NOT SQLITE3_AMALGAMATION_C)
|
||||||
|
message(FATAL_ERROR "SQLite amalgamation source not found")
|
||||||
|
endif()
|
||||||
|
list(GET SQLITE3_AMALGAMATION_C 0 SQLITE3_SOURCE_FILE)
|
||||||
|
get_filename_component(SQLITE3_INCLUDE_DIR ${SQLITE3_SOURCE_FILE} DIRECTORY)
|
||||||
|
add_library(sqlite3_amalgamation STATIC ${SQLITE3_SOURCE_FILE})
|
||||||
|
target_include_directories(sqlite3_amalgamation PUBLIC ${SQLITE3_INCLUDE_DIR})
|
||||||
|
target_compile_definitions(sqlite3_amalgamation PRIVATE
|
||||||
|
SQLITE_THREADSAFE=1
|
||||||
|
SQLITE_OMIT_LOAD_EXTENSION
|
||||||
|
)
|
||||||
|
|
||||||
# libcurl for HTTPS RPC connections (more reliable than cpp-httplib with OpenSSL 3.x)
|
# libcurl for HTTPS RPC connections (more reliable than cpp-httplib with OpenSSL 3.x)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
# For Windows cross-compilation, fetch and build libcurl statically
|
# For Windows cross-compilation, fetch and build libcurl statically
|
||||||
@@ -244,22 +405,70 @@ set(APP_SOURCES
|
|||||||
src/app_network.cpp
|
src/app_network.cpp
|
||||||
src/app_security.cpp
|
src/app_security.cpp
|
||||||
src/app_wizard.cpp
|
src/app_wizard.cpp
|
||||||
|
src/services/network_refresh_service.cpp
|
||||||
|
src/services/refresh_scheduler.cpp
|
||||||
|
src/services/wallet_security_controller.cpp
|
||||||
|
src/services/wallet_security_workflow.cpp
|
||||||
|
src/services/wallet_security_workflow_executor.cpp
|
||||||
|
src/chat/chat_protocol.cpp
|
||||||
|
src/chat/chat_crypto.cpp
|
||||||
|
src/chat/chat_identity.cpp
|
||||||
|
src/chat/chat_store.cpp
|
||||||
|
src/chat/chat_service.cpp
|
||||||
|
src/chat/chat_database.cpp
|
||||||
|
src/chat/chat_outgoing.cpp
|
||||||
|
src/wallet/lite_owned_string.cpp
|
||||||
|
src/wallet/lite_rollout_policy.cpp
|
||||||
|
src/wallet/lite_client_bridge.cpp
|
||||||
|
src/wallet/lite_connection_service.cpp
|
||||||
|
src/wallet/lite_diagnostics.cpp
|
||||||
|
src/wallet/lite_wallet_controller.cpp
|
||||||
|
src/wallet/lite_result_parsers.cpp
|
||||||
|
src/wallet/lite_sync_service.cpp
|
||||||
|
src/wallet/lite_wallet_gateway.cpp
|
||||||
|
src/wallet/lite_wallet_state_mapper.cpp
|
||||||
|
src/wallet/lite_wallet_lifecycle_ui_adapter.cpp
|
||||||
|
src/wallet/lite_wallet_server_selection_adapter.cpp
|
||||||
|
src/wallet/lite_wallet_lifecycle_service.cpp
|
||||||
src/data/wallet_state.cpp
|
src/data/wallet_state.cpp
|
||||||
|
src/data/transaction_history_cache.cpp
|
||||||
src/ui/theme.cpp
|
src/ui/theme.cpp
|
||||||
src/ui/theme_loader.cpp
|
src/ui/theme_loader.cpp
|
||||||
|
src/ui/explorer/explorer_block_cache.cpp
|
||||||
src/ui/material/color_theme.cpp
|
src/ui/material/color_theme.cpp
|
||||||
src/ui/material/typography.cpp
|
src/ui/material/typography.cpp
|
||||||
src/ui/notifications.cpp
|
src/ui/notifications.cpp
|
||||||
src/ui/windows/main_window.cpp
|
src/ui/windows/main_window.cpp
|
||||||
src/ui/windows/balance_tab.cpp
|
src/ui/windows/balance_tab.cpp
|
||||||
|
src/ui/windows/balance_components.cpp
|
||||||
|
src/ui/windows/balance_address_list.cpp
|
||||||
|
src/ui/windows/balance_recent_tx.cpp
|
||||||
|
src/ui/windows/balance_tab_helpers.cpp
|
||||||
src/ui/windows/send_tab.cpp
|
src/ui/windows/send_tab.cpp
|
||||||
src/ui/windows/receive_tab.cpp
|
src/ui/windows/receive_tab.cpp
|
||||||
src/ui/windows/transactions_tab.cpp
|
src/ui/windows/transactions_tab.cpp
|
||||||
src/ui/windows/mining_tab.cpp
|
src/ui/windows/mining_tab.cpp
|
||||||
|
src/ui/windows/mining_earnings.cpp
|
||||||
|
src/ui/windows/mining_stats.cpp
|
||||||
|
src/ui/windows/mining_controls.cpp
|
||||||
|
src/ui/windows/mining_mode_toggle.cpp
|
||||||
|
src/ui/windows/mining_benchmark.cpp
|
||||||
|
src/ui/windows/mining_pool_panel.cpp
|
||||||
|
src/ui/windows/mining_tab_helpers.cpp
|
||||||
src/ui/windows/peers_tab.cpp
|
src/ui/windows/peers_tab.cpp
|
||||||
|
src/ui/windows/network_tab.cpp
|
||||||
src/ui/windows/explorer_tab.cpp
|
src/ui/windows/explorer_tab.cpp
|
||||||
src/ui/windows/market_tab.cpp
|
src/ui/windows/market_tab.cpp
|
||||||
src/ui/windows/console_tab.cpp
|
src/ui/windows/console_tab.cpp
|
||||||
|
src/ui/windows/console_command_executor.cpp
|
||||||
|
src/ui/windows/console_command_reference.cpp
|
||||||
|
src/ui/windows/console_input_model.cpp
|
||||||
|
src/ui/windows/console_model.cpp
|
||||||
|
src/ui/windows/console_output_model.cpp
|
||||||
|
src/ui/windows/console_scroll_controller.cpp
|
||||||
|
src/ui/windows/console_selection_controller.cpp
|
||||||
|
src/ui/windows/console_tab_helpers.cpp
|
||||||
|
src/ui/windows/console_text_layout.cpp
|
||||||
src/ui/windows/settings_window.cpp
|
src/ui/windows/settings_window.cpp
|
||||||
src/ui/pages/settings_page.cpp
|
src/ui/pages/settings_page.cpp
|
||||||
src/ui/windows/about_dialog.cpp
|
src/ui/windows/about_dialog.cpp
|
||||||
@@ -267,14 +476,13 @@ set(APP_SOURCES
|
|||||||
src/ui/windows/transaction_details_dialog.cpp
|
src/ui/windows/transaction_details_dialog.cpp
|
||||||
src/ui/windows/qr_popup_dialog.cpp
|
src/ui/windows/qr_popup_dialog.cpp
|
||||||
src/ui/windows/validate_address_dialog.cpp
|
src/ui/windows/validate_address_dialog.cpp
|
||||||
src/ui/windows/address_book_dialog.cpp
|
src/ui/windows/contacts_tab.cpp
|
||||||
|
src/ui/windows/chat_tab.cpp
|
||||||
src/ui/windows/shield_dialog.cpp
|
src/ui/windows/shield_dialog.cpp
|
||||||
src/ui/windows/request_payment_dialog.cpp
|
src/ui/windows/request_payment_dialog.cpp
|
||||||
src/ui/windows/block_info_dialog.cpp
|
src/ui/windows/block_info_dialog.cpp
|
||||||
src/ui/windows/import_key_dialog.cpp
|
|
||||||
src/ui/windows/export_all_keys_dialog.cpp
|
src/ui/windows/export_all_keys_dialog.cpp
|
||||||
src/ui/windows/export_transactions_dialog.cpp
|
src/ui/windows/export_transactions_dialog.cpp
|
||||||
src/ui/windows/backup_wallet_dialog.cpp
|
|
||||||
src/ui/widgets/qr_code.cpp
|
src/ui/widgets/qr_code.cpp
|
||||||
src/rpc/rpc_client.cpp
|
src/rpc/rpc_client.cpp
|
||||||
src/rpc/rpc_worker.cpp
|
src/rpc/rpc_worker.cpp
|
||||||
@@ -283,16 +491,31 @@ set(APP_SOURCES
|
|||||||
src/data/address_book.cpp
|
src/data/address_book.cpp
|
||||||
src/data/exchange_info.cpp
|
src/data/exchange_info.cpp
|
||||||
src/util/logger.cpp
|
src/util/logger.cpp
|
||||||
|
src/util/async_task_manager.cpp
|
||||||
|
src/util/amount_format.cpp
|
||||||
|
src/util/address_validation.cpp
|
||||||
src/util/base64.cpp
|
src/util/base64.cpp
|
||||||
src/util/single_instance.cpp
|
src/util/single_instance.cpp
|
||||||
src/util/i18n.cpp
|
src/util/i18n.cpp
|
||||||
|
src/util/text_format.cpp
|
||||||
src/util/platform.cpp
|
src/util/platform.cpp
|
||||||
src/util/payment_uri.cpp
|
src/util/payment_uri.cpp
|
||||||
src/util/texture_loader.cpp
|
src/util/texture_loader.cpp
|
||||||
src/util/noise_texture.cpp
|
src/util/noise_texture.cpp
|
||||||
src/daemon/embedded_daemon.cpp
|
src/daemon/embedded_daemon.cpp
|
||||||
|
src/daemon/seed_wallet_creator.cpp
|
||||||
|
src/daemon/daemon_controller.cpp
|
||||||
|
src/daemon/lifecycle_adapters.cpp
|
||||||
src/daemon/xmrig_manager.cpp
|
src/daemon/xmrig_manager.cpp
|
||||||
src/util/bootstrap.cpp
|
src/util/bootstrap.cpp
|
||||||
|
src/util/lite_server_probe.cpp
|
||||||
|
src/util/pool_registry_core.cpp
|
||||||
|
src/util/pool_stats_service.cpp
|
||||||
|
src/util/http_download.cpp
|
||||||
|
src/util/xmrig_updater.cpp
|
||||||
|
src/util/xmrig_updater_core.cpp
|
||||||
|
src/util/daemon_updater.cpp
|
||||||
|
src/util/daemon_updater_core.cpp
|
||||||
src/util/secure_vault.cpp
|
src/util/secure_vault.cpp
|
||||||
src/ui/effects/framebuffer.cpp
|
src/ui/effects/framebuffer.cpp
|
||||||
src/ui/effects/blur_shader.cpp
|
src/ui/effects/blur_shader.cpp
|
||||||
@@ -323,34 +546,77 @@ endif()
|
|||||||
|
|
||||||
set(APP_HEADERS
|
set(APP_HEADERS
|
||||||
src/app.h
|
src/app.h
|
||||||
|
src/services/network_refresh_service.h
|
||||||
|
src/services/refresh_scheduler.h
|
||||||
|
src/services/wallet_security_controller.h
|
||||||
|
src/services/wallet_security_workflow.h
|
||||||
|
src/services/wallet_security_workflow_executor.h
|
||||||
|
src/wallet/wallet_capabilities.h
|
||||||
|
src/wallet/wallet_backend.h
|
||||||
|
src/wallet/lite_owned_string.h
|
||||||
|
src/wallet/lite_rollout_policy.h
|
||||||
|
src/wallet/lite_client_bridge.h
|
||||||
|
src/wallet/lite_connection_service.h
|
||||||
|
src/wallet/lite_result_parsers.h
|
||||||
|
src/wallet/lite_sync_service.h
|
||||||
|
src/wallet/lite_wallet_gateway.h
|
||||||
|
src/wallet/lite_wallet_state_mapper.h
|
||||||
|
src/wallet/lite_wallet_lifecycle_ui_adapter.h
|
||||||
|
src/wallet/lite_wallet_server_selection_adapter.h
|
||||||
|
src/wallet/lite_wallet_lifecycle_service.h
|
||||||
|
src/chat/chat_protocol.h
|
||||||
|
src/chat/chat_crypto.h
|
||||||
|
src/chat/chat_identity.h
|
||||||
|
src/chat/chat_message.h
|
||||||
|
src/chat/chat_store.h
|
||||||
|
src/chat/chat_service.h
|
||||||
|
src/chat/chat_database.h
|
||||||
|
src/chat/chat_outgoing.h
|
||||||
src/config/version.h
|
src/config/version.h
|
||||||
src/data/wallet_state.h
|
src/data/wallet_state.h
|
||||||
|
src/data/transaction_history_cache.h
|
||||||
src/ui/theme.h
|
src/ui/theme.h
|
||||||
src/ui/theme_loader.h
|
src/ui/theme_loader.h
|
||||||
|
src/ui/explorer/explorer_block_cache.h
|
||||||
src/ui/notifications.h
|
src/ui/notifications.h
|
||||||
src/ui/windows/main_window.h
|
src/ui/windows/main_window.h
|
||||||
src/ui/windows/balance_tab.h
|
src/ui/windows/balance_tab.h
|
||||||
|
src/ui/windows/balance_address_list.h
|
||||||
|
src/ui/windows/balance_recent_tx.h
|
||||||
|
src/ui/windows/balance_tab_helpers.h
|
||||||
src/ui/windows/send_tab.h
|
src/ui/windows/send_tab.h
|
||||||
src/ui/windows/receive_tab.h
|
src/ui/windows/receive_tab.h
|
||||||
src/ui/windows/transactions_tab.h
|
src/ui/windows/transactions_tab.h
|
||||||
src/ui/windows/mining_tab.h
|
src/ui/windows/mining_tab.h
|
||||||
|
src/ui/windows/mining_benchmark.h
|
||||||
|
src/ui/windows/mining_pool_panel.h
|
||||||
|
src/ui/windows/mining_tab_helpers.h
|
||||||
src/ui/windows/peers_tab.h
|
src/ui/windows/peers_tab.h
|
||||||
src/ui/windows/explorer_tab.h
|
src/ui/windows/explorer_tab.h
|
||||||
src/ui/windows/market_tab.h
|
src/ui/windows/market_tab.h
|
||||||
|
src/ui/windows/console_channel.h
|
||||||
|
src/ui/windows/console_command_reference.h
|
||||||
|
src/ui/windows/console_input_model.h
|
||||||
|
src/ui/windows/console_model.h
|
||||||
|
src/ui/windows/console_output_model.h
|
||||||
|
src/ui/windows/console_scroll_controller.h
|
||||||
|
src/ui/windows/console_selection_controller.h
|
||||||
|
src/ui/windows/console_tab.h
|
||||||
|
src/ui/windows/console_tab_helpers.h
|
||||||
src/ui/windows/settings_window.h
|
src/ui/windows/settings_window.h
|
||||||
src/ui/windows/about_dialog.h
|
src/ui/windows/about_dialog.h
|
||||||
src/ui/windows/key_export_dialog.h
|
src/ui/windows/key_export_dialog.h
|
||||||
src/ui/windows/transaction_details_dialog.h
|
src/ui/windows/transaction_details_dialog.h
|
||||||
src/ui/windows/qr_popup_dialog.h
|
src/ui/windows/qr_popup_dialog.h
|
||||||
src/ui/windows/validate_address_dialog.h
|
src/ui/windows/validate_address_dialog.h
|
||||||
src/ui/windows/address_book_dialog.h
|
src/ui/windows/contacts_tab.h
|
||||||
|
src/ui/windows/chat_tab.h
|
||||||
|
src/ui/windows/contact_picker.h
|
||||||
src/ui/windows/shield_dialog.h
|
src/ui/windows/shield_dialog.h
|
||||||
src/ui/windows/request_payment_dialog.h
|
src/ui/windows/request_payment_dialog.h
|
||||||
src/ui/windows/block_info_dialog.h
|
src/ui/windows/block_info_dialog.h
|
||||||
src/ui/windows/import_key_dialog.h
|
|
||||||
src/ui/windows/export_all_keys_dialog.h
|
src/ui/windows/export_all_keys_dialog.h
|
||||||
src/ui/windows/export_transactions_dialog.h
|
src/ui/windows/export_transactions_dialog.h
|
||||||
src/ui/windows/backup_wallet_dialog.h
|
|
||||||
src/ui/widgets/qr_code.h
|
src/ui/widgets/qr_code.h
|
||||||
src/rpc/rpc_client.h
|
src/rpc/rpc_client.h
|
||||||
src/rpc/rpc_worker.h
|
src/rpc/rpc_worker.h
|
||||||
@@ -360,6 +626,8 @@ set(APP_HEADERS
|
|||||||
src/data/address_book.h
|
src/data/address_book.h
|
||||||
src/data/exchange_info.h
|
src/data/exchange_info.h
|
||||||
src/util/logger.h
|
src/util/logger.h
|
||||||
|
src/util/async_task_manager.h
|
||||||
|
src/util/amount_format.h
|
||||||
src/util/base64.h
|
src/util/base64.h
|
||||||
src/util/single_instance.h
|
src/util/single_instance.h
|
||||||
src/util/i18n.h
|
src/util/i18n.h
|
||||||
@@ -367,6 +635,9 @@ set(APP_HEADERS
|
|||||||
src/util/payment_uri.h
|
src/util/payment_uri.h
|
||||||
src/util/secure_vault.h
|
src/util/secure_vault.h
|
||||||
src/daemon/embedded_daemon.h
|
src/daemon/embedded_daemon.h
|
||||||
|
src/daemon/seed_wallet_creator.h
|
||||||
|
src/daemon/daemon_controller.h
|
||||||
|
src/daemon/lifecycle_adapters.h
|
||||||
src/daemon/xmrig_manager.h
|
src/daemon/xmrig_manager.h
|
||||||
src/ui/effects/framebuffer.h
|
src/ui/effects/framebuffer.h
|
||||||
src/ui/effects/blur_shader.h
|
src/ui/effects/blur_shader.h
|
||||||
@@ -405,10 +676,12 @@ if(WIN32)
|
|||||||
set(WIN_RC_FILE ${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc)
|
set(WIN_RC_FILE ${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Generate version.h from the single project(VERSION ...) declaration
|
# Generate version values from the single project(VERSION ...) declaration.
|
||||||
|
# Keep the build-specific app name in the build tree so full/lite configures do
|
||||||
|
# not rewrite a tracked source header.
|
||||||
configure_file(
|
configure_file(
|
||||||
${CMAKE_SOURCE_DIR}/src/config/version.h.in
|
${CMAKE_SOURCE_DIR}/src/config/version.h.in
|
||||||
${CMAKE_SOURCE_DIR}/src/config/version.h
|
${CMAKE_BINARY_DIR}/generated/dragonx_generated_version.h
|
||||||
@ONLY
|
@ONLY
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -419,6 +692,22 @@ configure_file(
|
|||||||
@ONLY
|
@ONLY
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# INCBIN uses .incbin assembler directives that reference font files at
|
||||||
|
# assembly time — CMake doesn't track these implicit dependencies.
|
||||||
|
# Tell CMake that the generated source depends on the actual font binaries
|
||||||
|
# so a font file change triggers recompilation.
|
||||||
|
set_source_files_properties(
|
||||||
|
${CMAKE_BINARY_DIR}/generated/embedded_fonts.cpp
|
||||||
|
PROPERTIES OBJECT_DEPENDS
|
||||||
|
"${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-R.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-Light.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/Ubuntu-Medium.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/UbuntuMono-R.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/MaterialIcons-Regular.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf;\
|
||||||
|
${CMAKE_SOURCE_DIR}/res/fonts/NotoSansCJK-Subset.ttf"
|
||||||
|
)
|
||||||
|
|
||||||
add_executable(ObsidianDragon
|
add_executable(ObsidianDragon
|
||||||
${APP_SOURCES}
|
${APP_SOURCES}
|
||||||
${CMAKE_BINARY_DIR}/generated/embedded_fonts.cpp
|
${CMAKE_BINARY_DIR}/generated/embedded_fonts.cpp
|
||||||
@@ -429,6 +718,8 @@ add_executable(ObsidianDragon
|
|||||||
${WIN_RC_FILE}
|
${WIN_RC_FILE}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
set_target_properties(ObsidianDragon PROPERTIES OUTPUT_NAME "${DRAGONX_BINARY_NAME}")
|
||||||
|
|
||||||
target_include_directories(ObsidianDragon PRIVATE
|
target_include_directories(ObsidianDragon PRIVATE
|
||||||
${CMAKE_SOURCE_DIR}/src
|
${CMAKE_SOURCE_DIR}/src
|
||||||
${CMAKE_SOURCE_DIR}/src/embedded
|
${CMAKE_SOURCE_DIR}/src/embedded
|
||||||
@@ -448,10 +739,63 @@ target_link_libraries(ObsidianDragon PRIVATE
|
|||||||
SDL3::SDL3
|
SDL3::SDL3
|
||||||
nlohmann_json::nlohmann_json
|
nlohmann_json::nlohmann_json
|
||||||
tomlplusplus::tomlplusplus
|
tomlplusplus::tomlplusplus
|
||||||
|
sqlite3_amalgamation
|
||||||
${CURL_LIBRARIES}
|
${CURL_LIBRARIES}
|
||||||
${SODIUM_LIBRARY}
|
${SODIUM_LIBRARY}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(DRAGONX_LITE_BACKEND_READY)
|
||||||
|
target_link_libraries(ObsidianDragon PRIVATE dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS})
|
||||||
|
|
||||||
|
# Real-backend smoke tool (only built when a real lite backend is linked).
|
||||||
|
add_executable(lite_smoke
|
||||||
|
tools/lite_smoke.cpp
|
||||||
|
src/wallet/lite_client_bridge.cpp
|
||||||
|
src/wallet/lite_owned_string.cpp
|
||||||
|
src/wallet/lite_rollout_policy.cpp
|
||||||
|
src/wallet/lite_connection_service.cpp
|
||||||
|
src/wallet/lite_result_parsers.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(lite_smoke PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
${CMAKE_BINARY_DIR}/generated
|
||||||
|
${SODIUM_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
target_compile_definitions(lite_smoke PRIVATE DRAGONX_ENABLE_LITE_BACKEND=1)
|
||||||
|
target_link_libraries(lite_smoke PRIVATE
|
||||||
|
dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS}
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
${SODIUM_LIBRARY}
|
||||||
|
)
|
||||||
|
if(UNIX)
|
||||||
|
target_link_libraries(lite_smoke PRIVATE ${CMAKE_DL_LIBS} pthread)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Real-backend SEND smoke tool — drives the exact GUI send path (bridge.execute("send", ...)).
|
||||||
|
add_executable(lite_send_smoke
|
||||||
|
tools/lite_send_smoke.cpp
|
||||||
|
src/wallet/lite_client_bridge.cpp
|
||||||
|
src/wallet/lite_owned_string.cpp
|
||||||
|
src/wallet/lite_rollout_policy.cpp
|
||||||
|
src/wallet/lite_connection_service.cpp
|
||||||
|
src/wallet/lite_result_parsers.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(lite_send_smoke PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
${CMAKE_BINARY_DIR}/generated
|
||||||
|
${SODIUM_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
target_compile_definitions(lite_send_smoke PRIVATE DRAGONX_ENABLE_LITE_BACKEND=1)
|
||||||
|
target_link_libraries(lite_send_smoke PRIVATE
|
||||||
|
dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS}
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
${SODIUM_LIBRARY}
|
||||||
|
)
|
||||||
|
if(UNIX)
|
||||||
|
target_link_libraries(lite_send_smoke PRIVATE ${CMAKE_DL_LIBS} pthread)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# Platform-specific settings
|
# Platform-specific settings
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(ObsidianDragon PRIVATE ws2_32 winmm imm32 version setupapi dwmapi crypt32 wldap32 psapi iphlpapi d3d11 dxgi d3dcompiler dcomp)
|
target_link_libraries(ObsidianDragon PRIVATE ws2_32 winmm imm32 version setupapi dwmapi crypt32 wldap32 psapi iphlpapi d3d11 dxgi d3dcompiler dcomp)
|
||||||
@@ -480,6 +824,10 @@ endif()
|
|||||||
# Compile definitions
|
# Compile definitions
|
||||||
target_compile_definitions(ObsidianDragon PRIVATE
|
target_compile_definitions(ObsidianDragon PRIVATE
|
||||||
DRAGONX_DEBUG
|
DRAGONX_DEBUG
|
||||||
|
DRAGONX_LITE_BUILD=$<BOOL:${DRAGONX_BUILD_LITE}>
|
||||||
|
DRAGONX_ENABLE_EMBEDDED_DAEMON=$<BOOL:${DRAGONX_ENABLE_EMBEDDED_DAEMON}>
|
||||||
|
DRAGONX_ENABLE_LITE_BACKEND=$<BOOL:${DRAGONX_LITE_BACKEND_READY}>
|
||||||
|
DRAGONX_ENABLE_CHAT=$<BOOL:${DRAGONX_ENABLE_CHAT}>
|
||||||
)
|
)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_USE_DX11)
|
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_USE_DX11)
|
||||||
@@ -487,6 +835,26 @@ else()
|
|||||||
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_HAS_GLAD)
|
target_compile_definitions(ObsidianDragon PRIVATE DRAGONX_HAS_GLAD)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_executable(HushChatFixtureCheck
|
||||||
|
tools/hushchat_fixture_check.cpp
|
||||||
|
src/chat/chat_protocol.cpp
|
||||||
|
src/chat/chat_fixture_tooling.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(HushChatFixtureCheck PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
${SODIUM_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(HushChatFixtureCheck PRIVATE
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
${SODIUM_LIBRARY}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(HushChatFixtureCheck PRIVATE
|
||||||
|
DRAGONX_ENABLE_CHAT=0
|
||||||
|
)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Copy resources
|
# Copy resources
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -510,6 +878,12 @@ endif()
|
|||||||
# so edits to res/lang/*.json are picked up by 'make' without re-running cmake.
|
# so edits to res/lang/*.json are picked up by 'make' without re-running cmake.
|
||||||
file(GLOB LANG_FILES ${CMAKE_SOURCE_DIR}/res/lang/*.json)
|
file(GLOB LANG_FILES ${CMAKE_SOURCE_DIR}/res/lang/*.json)
|
||||||
if(LANG_FILES)
|
if(LANG_FILES)
|
||||||
|
find_program(XXD_EXECUTABLE NAMES xxd)
|
||||||
|
if(NOT XXD_EXECUTABLE)
|
||||||
|
message(WARNING "xxd not found; runtime language JSON files will be copied, but embedded build/generated/embedded/lang_*.h files will not be regenerated")
|
||||||
|
endif()
|
||||||
|
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated/embedded)
|
||||||
|
|
||||||
foreach(LANG_FILE ${LANG_FILES})
|
foreach(LANG_FILE ${LANG_FILES})
|
||||||
get_filename_component(LANG_FILENAME ${LANG_FILE} NAME)
|
get_filename_component(LANG_FILENAME ${LANG_FILE} NAME)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
@@ -523,16 +897,18 @@ if(LANG_FILES)
|
|||||||
list(APPEND LANG_OUTPUTS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res/lang/${LANG_FILENAME})
|
list(APPEND LANG_OUTPUTS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res/lang/${LANG_FILENAME})
|
||||||
|
|
||||||
# Also regenerate the embedded header so the binary always has fresh translations
|
# Also regenerate the embedded header so the binary always has fresh translations
|
||||||
get_filename_component(LANG_CODE ${LANG_FILENAME} NAME_WE)
|
if(XXD_EXECUTABLE)
|
||||||
set(LANG_HEADER ${CMAKE_SOURCE_DIR}/src/embedded/lang_${LANG_CODE}.h)
|
get_filename_component(LANG_CODE ${LANG_FILENAME} NAME_WE)
|
||||||
add_custom_command(
|
set(LANG_HEADER ${CMAKE_BINARY_DIR}/generated/embedded/lang_${LANG_CODE}.h)
|
||||||
OUTPUT ${LANG_HEADER}
|
add_custom_command(
|
||||||
COMMAND xxd -i "res/lang/${LANG_FILENAME}" > "src/embedded/lang_${LANG_CODE}.h"
|
OUTPUT ${LANG_HEADER}
|
||||||
DEPENDS ${LANG_FILE}
|
COMMAND ${XXD_EXECUTABLE} -i "res/lang/${LANG_FILENAME}" > "${LANG_HEADER}"
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
DEPENDS ${LANG_FILE}
|
||||||
COMMENT "Embedding lang_${LANG_CODE}.h"
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||||
)
|
COMMENT "Embedding lang_${LANG_CODE}.h"
|
||||||
list(APPEND LANG_OUTPUTS ${LANG_HEADER})
|
)
|
||||||
|
list(APPEND LANG_OUTPUTS ${LANG_HEADER})
|
||||||
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
add_custom_target(copy_langs ALL DEPENDS ${LANG_OUTPUTS})
|
add_custom_target(copy_langs ALL DEPENDS ${LANG_OUTPUTS})
|
||||||
add_dependencies(ObsidianDragon copy_langs)
|
add_dependencies(ObsidianDragon copy_langs)
|
||||||
@@ -579,6 +955,7 @@ if(THEME_FILES AND Python3_EXECUTABLE)
|
|||||||
message(STATUS " Theme files: ${THEME_FILES} (build-time expansion via Python)")
|
message(STATUS " Theme files: ${THEME_FILES} (build-time expansion via Python)")
|
||||||
elseif(THEME_FILES)
|
elseif(THEME_FILES)
|
||||||
# Fallback: plain copy if Python is not available
|
# Fallback: plain copy if Python is not available
|
||||||
|
message(WARNING "Python3 not found; copying theme files without expand_themes.py layout merge")
|
||||||
foreach(THEME_FILE ${THEME_FILES})
|
foreach(THEME_FILE ${THEME_FILES})
|
||||||
get_filename_component(THEME_FILENAME ${THEME_FILE} NAME)
|
get_filename_component(THEME_FILENAME ${THEME_FILE} NAME)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
@@ -628,20 +1005,133 @@ install(TARGETS ObsidianDragon
|
|||||||
)
|
)
|
||||||
|
|
||||||
install(DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res
|
install(DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/res
|
||||||
DESTINATION share/ObsidianDragon
|
DESTINATION share/${DRAGONX_BINARY_NAME}
|
||||||
OPTIONAL
|
OPTIONAL
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Tests
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
add_executable(ObsidianDragonTests
|
||||||
|
tests/test_phase4.cpp
|
||||||
|
src/services/network_refresh_service.cpp
|
||||||
|
src/services/refresh_scheduler.cpp
|
||||||
|
src/services/wallet_security_controller.cpp
|
||||||
|
src/services/wallet_security_workflow.cpp
|
||||||
|
src/services/wallet_security_workflow_executor.cpp
|
||||||
|
src/chat/chat_protocol.cpp
|
||||||
|
src/chat/chat_crypto.cpp
|
||||||
|
src/chat/chat_identity.cpp
|
||||||
|
src/chat/chat_store.cpp
|
||||||
|
src/chat/chat_service.cpp
|
||||||
|
src/chat/chat_database.cpp
|
||||||
|
src/chat/chat_outgoing.cpp
|
||||||
|
src/wallet/lite_owned_string.cpp
|
||||||
|
src/wallet/lite_rollout_policy.cpp
|
||||||
|
src/wallet/lite_client_bridge.cpp
|
||||||
|
src/wallet/lite_connection_service.cpp
|
||||||
|
src/wallet/lite_diagnostics.cpp
|
||||||
|
src/wallet/lite_wallet_controller.cpp
|
||||||
|
src/wallet/lite_result_parsers.cpp
|
||||||
|
src/wallet/lite_sync_service.cpp
|
||||||
|
src/wallet/lite_wallet_gateway.cpp
|
||||||
|
src/wallet/lite_wallet_state_mapper.cpp
|
||||||
|
src/wallet/lite_wallet_lifecycle_ui_adapter.cpp
|
||||||
|
src/wallet/lite_wallet_server_selection_adapter.cpp
|
||||||
|
src/wallet/lite_wallet_lifecycle_service.cpp
|
||||||
|
src/ui/explorer/explorer_block_cache.cpp
|
||||||
|
src/ui/windows/balance_address_list.cpp
|
||||||
|
src/ui/windows/balance_recent_tx.cpp
|
||||||
|
src/ui/windows/console_input_model.cpp
|
||||||
|
src/ui/windows/console_model.cpp
|
||||||
|
src/ui/windows/console_output_model.cpp
|
||||||
|
src/ui/windows/console_scroll_controller.cpp
|
||||||
|
src/ui/windows/console_selection_controller.cpp
|
||||||
|
src/ui/windows/console_tab_helpers.cpp
|
||||||
|
src/ui/windows/console_text_layout.cpp
|
||||||
|
src/ui/windows/mining_benchmark.cpp
|
||||||
|
src/ui/windows/mining_pool_panel.cpp
|
||||||
|
src/ui/windows/mining_tab_helpers.cpp
|
||||||
|
src/util/payment_uri.cpp
|
||||||
|
src/util/amount_format.cpp
|
||||||
|
src/util/address_validation.cpp
|
||||||
|
src/util/i18n.cpp
|
||||||
|
src/util/text_format.cpp
|
||||||
|
src/data/wallet_state.cpp
|
||||||
|
src/data/transaction_history_cache.cpp
|
||||||
|
src/daemon/lifecycle_adapters.cpp
|
||||||
|
src/rpc/connection.cpp
|
||||||
|
src/config/settings.cpp
|
||||||
|
src/resources/embedded_resources.cpp
|
||||||
|
src/util/secure_vault.cpp
|
||||||
|
src/util/platform.cpp
|
||||||
|
src/util/logger.cpp
|
||||||
|
src/util/lite_server_probe.cpp
|
||||||
|
src/util/pool_registry_core.cpp
|
||||||
|
src/util/http_download.cpp
|
||||||
|
src/util/xmrig_updater.cpp
|
||||||
|
src/util/xmrig_updater_core.cpp
|
||||||
|
src/util/daemon_updater.cpp
|
||||||
|
src/util/daemon_updater_core.cpp
|
||||||
|
${MINIZ_SOURCES}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(ObsidianDragonTests PRIVATE
|
||||||
|
${CMAKE_SOURCE_DIR}/src
|
||||||
|
${CMAKE_SOURCE_DIR}/src/resources
|
||||||
|
${CMAKE_SOURCE_DIR}/libs
|
||||||
|
${CMAKE_BINARY_DIR}/generated
|
||||||
|
${IMGUI_DIR}
|
||||||
|
${SODIUM_INCLUDE_DIR}
|
||||||
|
${CURL_INCLUDE_DIRS}
|
||||||
|
${MINIZ_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(ObsidianDragonTests PRIVATE
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
sqlite3_amalgamation
|
||||||
|
${SODIUM_LIBRARY}
|
||||||
|
${CURL_LIBRARIES}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(ObsidianDragonTests PRIVATE
|
||||||
|
DRAGONX_ENABLE_CHAT=$<BOOL:${DRAGONX_ENABLE_CHAT}>
|
||||||
|
DRAGONX_LITE_BUILD=$<BOOL:${DRAGONX_BUILD_LITE}>
|
||||||
|
DRAGONX_ENABLE_EMBEDDED_DAEMON=$<BOOL:${DRAGONX_ENABLE_EMBEDDED_DAEMON}>
|
||||||
|
DRAGONX_ENABLE_LITE_BACKEND=$<BOOL:${DRAGONX_LITE_BACKEND_READY}>
|
||||||
|
DRAGONX_TEST_FIXTURE_DIR="${CMAKE_SOURCE_DIR}/tests/fixtures"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(DRAGONX_LITE_BACKEND_READY)
|
||||||
|
target_link_libraries(ObsidianDragonTests PRIVATE dragonx_lite_backend ${DRAGONX_LITE_BACKEND_EXTRA_LIBS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(UNIX)
|
||||||
|
target_link_libraries(ObsidianDragonTests PRIVATE ${CMAKE_DL_LIBS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_test(NAME ObsidianDragonPhase4Tests COMMAND ObsidianDragonTests)
|
||||||
|
endif()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Summary
|
# Summary
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
message(STATUS "")
|
message(STATUS "")
|
||||||
message(STATUS "DragonX ImGui Wallet Configuration:")
|
message(STATUS "DragonX ImGui Wallet Configuration:")
|
||||||
message(STATUS " Version: ${PROJECT_VERSION}")
|
message(STATUS " Version: ${DRAGONX_APP_VERSION}${DRAGONX_APP_VERSION_SUFFIX} (${DRAGONX_APP_NAME})")
|
||||||
message(STATUS " Build type: ${CMAKE_BUILD_TYPE}")
|
message(STATUS " Build type: ${CMAKE_BUILD_TYPE}")
|
||||||
message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
|
message(STATUS " C++ Standard: ${CMAKE_CXX_STANDARD}")
|
||||||
message(STATUS " ImGui dir: ${IMGUI_DIR}")
|
message(STATUS " ImGui dir: ${IMGUI_DIR}")
|
||||||
message(STATUS " SDL3 found: ${SDL3_FOUND}")
|
message(STATUS " SDL3 found: ${SDL3_FOUND}")
|
||||||
message(STATUS " Sodium lib: ${SODIUM_LIBRARY}")
|
message(STATUS " Sodium lib: ${SODIUM_LIBRARY}")
|
||||||
|
message(STATUS " Lite build: ${DRAGONX_BUILD_LITE}")
|
||||||
|
message(STATUS " Lite requested: ${DRAGONX_ENABLE_LITE_BACKEND}")
|
||||||
|
message(STATUS " Lite backend: ${DRAGONX_LITE_BACKEND_READY}")
|
||||||
|
message(STATUS " Lite lib: ${DRAGONX_LITE_BACKEND_LIBRARY}")
|
||||||
|
message(STATUS " Lite symbols: ${DRAGONX_LITE_BACKEND_SYMBOLS_FILE}")
|
||||||
|
message(STATUS " Lite manifest: ${DRAGONX_LITE_BACKEND_MANIFEST}")
|
||||||
|
message(STATUS " Lite signature: ${DRAGONX_LITE_BACKEND_REQUIRE_SIGNATURE}")
|
||||||
message(STATUS "")
|
message(STATUS "")
|
||||||
|
|||||||
49
README.md
49
README.md
@@ -1,6 +1,8 @@
|
|||||||
# DragonX Wallet - ImGui Edition
|
# ObsidianDragon - DragonX Wallet
|
||||||
|
|
||||||
A lightweight, portable cryptocurrency wallet for DragonX (DRGX), built with Dear ImGui.
|
A lightweight, portable full-node cryptocurrency wallet for DragonX (DRGX), built with Dear ImGui.
|
||||||
|
|
||||||
|
Current pre-release: **1.2.0-rc1**.
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
@@ -9,10 +11,13 @@ A lightweight, portable cryptocurrency wallet for DragonX (DRGX), built with Dea
|
|||||||
|
|
||||||
- **Full Node Support**: Connects to dragonxd for complete blockchain verification
|
- **Full Node Support**: Connects to dragonxd for complete blockchain verification
|
||||||
- **Shielded Transactions**: Full z-address support with encrypted memos
|
- **Shielded Transactions**: Full z-address support with encrypted memos
|
||||||
- **Integrated Mining**: CPU mining controls with hashrate monitoring
|
- **Address Management**: Labels, icons, favorites, hidden addresses, and address-to-address transfers
|
||||||
|
- **Integrated Mining**: Solo CPU mining plus pool mining through xmrig, with idle-mining controls
|
||||||
|
- **Explorer Tools**: Block/transaction lookup and bootstrap snapshot download
|
||||||
- **Market Data**: Real-time price charts from CoinGecko
|
- **Market Data**: Real-time price charts from CoinGecko
|
||||||
- **QR Codes**: Generate and display QR codes for receiving addresses
|
- **QR Codes**: Generate and display QR codes for receiving addresses
|
||||||
- **Multi-language**: i18n support (English, Spanish, more coming)
|
- **Multi-language**: i18n support for English, German, Spanish, French, Japanese, Korean, Portuguese, Russian, and Chinese
|
||||||
|
- **CJK Fonts**: Bundled CJK subset font for translated interfaces
|
||||||
- **Lightweight**: ~5-10MB binary vs ~50MB+ for Qt version
|
- **Lightweight**: ~5-10MB binary vs ~50MB+ for Qt version
|
||||||
- **Fast Builds**: Compiles in seconds, not minutes
|
- **Fast Builds**: Compiles in seconds, not minutes
|
||||||
|
|
||||||
@@ -116,7 +121,8 @@ cd ObsidianDragon/
|
|||||||
./ObsidianDragon
|
./ObsidianDragon
|
||||||
```
|
```
|
||||||
|
|
||||||
The wallet will automatically connect to the daemon using credentials from \`~/.hush/DRAGONX/DRAGONX.conf\`.
|
The wallet will automatically connect to the daemon using credentials from `~/.hush/DRAGONX/DRAGONX.conf`.
|
||||||
|
|
||||||
### Using Custom Node Binaries
|
### Using Custom Node Binaries
|
||||||
|
|
||||||
The wallet checks its **own directory first** when looking for DragonX node binaries. This means you can test new or different branch builds of `hush-arrakis-chain`/`hushd` without waiting for a new wallet release:
|
The wallet checks its **own directory first** when looking for DragonX node binaries. This means you can test new or different branch builds of `hush-arrakis-chain`/`hushd` without waiting for a new wallet release:
|
||||||
@@ -131,9 +137,10 @@ The wallet checks its **own directory first** when looking for DragonX node bina
|
|||||||
3. System-wide locations (`/usr/local/bin`, `~/dragonx/src`, etc.)
|
3. System-wide locations (`/usr/local/bin`, `~/dragonx/src`, etc.)
|
||||||
|
|
||||||
This is useful for testing new branches or hotfixes to the node software before they are bundled into a wallet release.
|
This is useful for testing new branches or hotfixes to the node software before they are bundled into a wallet release.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Configuration is stored in \`~/.hush/DRAGONX/DRAGONX.conf\`:
|
Configuration is stored in `~/.hush/DRAGONX/DRAGONX.conf`:
|
||||||
|
|
||||||
```
|
```
|
||||||
rpcuser=your_rpc_user
|
rpcuser=your_rpc_user
|
||||||
@@ -148,44 +155,46 @@ ObsidianDragon/
|
|||||||
├── src/
|
├── src/
|
||||||
│ ├── main.cpp # Entry point, SDL/ImGui setup
|
│ ├── main.cpp # Entry point, SDL/ImGui setup
|
||||||
│ ├── app.cpp/h # Main application class
|
│ ├── app.cpp/h # Main application class
|
||||||
│ ├── wallet_state.h # Wallet data structures
|
│ ├── data/ # WalletState, address book, exchange info
|
||||||
│ ├── version.h # Version definitions
|
│ ├── config/ # Settings persistence and committed/generated version.h
|
||||||
│ ├── ui/
|
│ ├── ui/
|
||||||
│ │ ├── theme.cpp/h # DragonX theme
|
│ │ ├── schema/ # TOML UI schema and skin manager
|
||||||
│ │ └── windows/ # UI tabs and dialogs
|
│ │ ├── material/ # Material components, typography, layout
|
||||||
|
│ │ ├── windows/ # Tabs and dialogs
|
||||||
|
│ │ └── pages/ # Multi-page screens such as Settings
|
||||||
│ ├── rpc/
|
│ ├── rpc/
|
||||||
│ │ ├── rpc_client.cpp # JSON-RPC client
|
│ │ ├── rpc_client.cpp # JSON-RPC client
|
||||||
│ │ └── connection.cpp # Daemon connection
|
│ │ └── connection.cpp # Daemon connection
|
||||||
│ ├── config/
|
│ ├── resources/ # Embedded resource extraction
|
||||||
│ │ └── settings.cpp # Settings persistence
|
│ ├── platform/ # Windows DX11/backdrop helpers
|
||||||
│ ├── util/
|
│ ├── util/
|
||||||
│ │ ├── i18n.cpp # Internationalization
|
│ │ ├── i18n.cpp # Internationalization
|
||||||
│ │ └── ...
|
│ │ └── ...
|
||||||
│ └── daemon/
|
│ └── daemon/
|
||||||
│ └── embedded_daemon.cpp
|
│ └── embedded_daemon.cpp
|
||||||
├── res/
|
├── res/
|
||||||
│ ├── fonts/ # Ubuntu font
|
│ ├── fonts/ # Ubuntu, icon, and CJK fonts
|
||||||
│ └── lang/ # Translation files
|
│ └── lang/ # Translation files
|
||||||
├── libs/
|
├── libs/
|
||||||
│ └── qrcode/ # QR code generation
|
│ └── qrcode/ # QR code generation
|
||||||
├── CMakeLists.txt
|
├── CMakeLists.txt
|
||||||
├── build-release.sh # Build script
|
├── build.sh # Release/cross-platform build script
|
||||||
└── create-appimage.sh # AppImage packaging
|
└── scripts/create-appimage.sh # AppImage packaging
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
Fetched automatically by CMake (no manual install needed):
|
Fetched or discovered by CMake:
|
||||||
|
|
||||||
- **[SDL3](https://github.com/libsdl-org/SDL)** — Cross-platform windowing/input
|
- **[SDL3](https://github.com/libsdl-org/SDL)** — Cross-platform windowing/input
|
||||||
- **[nlohmann/json](https://github.com/nlohmann/json)** — JSON parsing
|
- **[nlohmann/json](https://github.com/nlohmann/json)** — JSON parsing
|
||||||
- **[toml++](https://github.com/marzer/tomlplusplus)** — TOML parsing (UI schema/themes)
|
- **[toml++](https://github.com/marzer/tomlplusplus)** — TOML parsing (UI schema/themes)
|
||||||
- **[libcurl](https://curl.se/libcurl/)** — HTTPS RPC transport (system on Linux, fetched on Windows)
|
- **[libcurl](https://curl.se/libcurl/)** — HTTP/HTTPS transport for daemon RPC and network calls (system on Linux/macOS, fetched on Windows)
|
||||||
|
|
||||||
Bundled in `libs/`:
|
Bundled in `libs/`:
|
||||||
|
|
||||||
- **[Dear ImGui](https://github.com/ocornut/imgui)** — Immediate mode GUI
|
- **[Dear ImGui](https://github.com/ocornut/imgui)** — Immediate mode GUI
|
||||||
- **[libsodium](https://libsodium.org)** — Cryptographic operations (fetched by `scripts/fetch-libsodium.sh`)
|
- **[libsodium](https://libsodium.org)** — Cryptographic operations (system on Linux or fetched by `scripts/fetch-libsodium.sh`)
|
||||||
- **[QR-Code-generator](https://github.com/nayuki/QR-Code-generator)** — QR code rendering
|
- **[QR-Code-generator](https://github.com/nayuki/QR-Code-generator)** — QR code rendering
|
||||||
- **[miniz](https://github.com/richgel999/miniz)** — ZIP compression
|
- **[miniz](https://github.com/richgel999/miniz)** — ZIP compression
|
||||||
- **[GLAD](https://glad.dav1d.de/)** — OpenGL loader (Linux/macOS)
|
- **[GLAD](https://glad.dav1d.de/)** — OpenGL loader (Linux/macOS)
|
||||||
@@ -202,9 +211,11 @@ Bundled in `libs/`:
|
|||||||
|
|
||||||
## Translation
|
## Translation
|
||||||
|
|
||||||
|
Current language files live in `res/lang/` as `de`, `es`, `fr`, `ja`, `ko`, `pt`, `ru`, and `zh` JSON files, with built-in English fallbacks.
|
||||||
|
|
||||||
To add a new language:
|
To add a new language:
|
||||||
|
|
||||||
1. Copy \`res/lang/es.json\` to \`res/lang/<code>.json\`
|
1. Copy `res/lang/es.json` to `res/lang/<code>.json`
|
||||||
2. Translate all strings
|
2. Translate all strings
|
||||||
3. The language will appear in Settings automatically
|
3. The language will appear in Settings automatically
|
||||||
|
|
||||||
|
|||||||
@@ -361,7 +361,22 @@ https://www.apache.org/licenses/LICENSE-2.0
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 13. IconFontCppHeaders
|
## 13. Material Design Icons Pickaxe Subset Font
|
||||||
|
|
||||||
|
- **Location:** `res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf`
|
||||||
|
- **Source:** https://github.com/Templarian/MaterialDesign-Webfont
|
||||||
|
- **Derived from:** Pictogrammers Material Design Icons webfont (`materialdesignicons-webfont.ttf`)
|
||||||
|
- **Copyright:** Pictogrammers contributors
|
||||||
|
- **License:** Apache License 2.0
|
||||||
|
|
||||||
|
This bundled font is a local one-glyph subset containing only the MDI pickaxe
|
||||||
|
icon, remapped onto a BMP private-use codepoint for Dear ImGui compatibility.
|
||||||
|
The full text of the Apache License 2.0 is available at:
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. IconFontCppHeaders
|
||||||
|
|
||||||
- **Location:** `src/embedded/IconsMaterialDesign.h`
|
- **Location:** `src/embedded/IconsMaterialDesign.h`
|
||||||
- **Source:** https://github.com/juliettef/IconFontCppHeaders
|
- **Source:** https://github.com/juliettef/IconFontCppHeaders
|
||||||
@@ -390,7 +405,7 @@ freely, subject to the following restrictions:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 14. Ubuntu Font Family
|
## 15. Ubuntu Font Family
|
||||||
|
|
||||||
- **Location:** `res/fonts/Ubuntu-Light.ttf`, `Ubuntu-Medium.ttf`, `Ubuntu-R.ttf`
|
- **Location:** `res/fonts/Ubuntu-Light.ttf`, `Ubuntu-Medium.ttf`, `Ubuntu-R.ttf`
|
||||||
- **Source:** https://design.ubuntu.com/font
|
- **Source:** https://design.ubuntu.com/font
|
||||||
|
|||||||
437
build.sh
437
build.sh
@@ -20,7 +20,9 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
VERSION="1.1.1"
|
# VERSION is resolved per-variant from CMakeLists.txt (the single source of truth) after arg
|
||||||
|
# parsing — see the APP_BASENAME block below. Placeholder until then.
|
||||||
|
VERSION=""
|
||||||
|
|
||||||
# ── Colours ──────────────────────────────────────────────────────────────────
|
# ── Colours ──────────────────────────────────────────────────────────────────
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
@@ -41,6 +43,8 @@ DO_DEV=false
|
|||||||
DO_LINUX=false
|
DO_LINUX=false
|
||||||
DO_WIN=false
|
DO_WIN=false
|
||||||
DO_MAC=false
|
DO_MAC=false
|
||||||
|
DO_LITE=false
|
||||||
|
DO_LITE_BACKEND=false
|
||||||
CLEAN=false
|
CLEAN=false
|
||||||
BUILD_TYPE="Release"
|
BUILD_TYPE="Release"
|
||||||
|
|
||||||
@@ -54,6 +58,10 @@ Targets (at least one required, or none for dev build):
|
|||||||
--linux-release Linux release (zip + AppImage) -> release/linux/
|
--linux-release Linux release (zip + AppImage) -> release/linux/
|
||||||
--win-release Windows cross-compile (mingw-w64) -> release/windows/
|
--win-release Windows cross-compile (mingw-w64) -> release/windows/
|
||||||
--mac-release macOS .app bundle + DMG -> release/mac/
|
--mac-release macOS .app bundle + DMG -> release/mac/
|
||||||
|
--lite Build ObsidianDragonLite variant (no embedded daemon/full-node features)
|
||||||
|
--lite-backend Like --lite, and link the real SDXL litelib backend artifact
|
||||||
|
(auto-discovers build/lite-backend/<platform>/; build it with
|
||||||
|
scripts/build-lite-backend-artifact.sh, or set DRAGONX_LITE_BACKEND_DIR)
|
||||||
|
|
||||||
Build trees are stored under build/{linux,windows,mac}/
|
Build trees are stored under build/{linux,windows,mac}/
|
||||||
|
|
||||||
@@ -74,6 +82,7 @@ Examples:
|
|||||||
$0 --linux-release # Linux release (zip + AppImage)
|
$0 --linux-release # Linux release (zip + AppImage)
|
||||||
$0 --win-release # Windows cross-compile
|
$0 --win-release # Windows cross-compile
|
||||||
$0 --mac-release # macOS bundle + DMG (native or osxcross)
|
$0 --mac-release # macOS bundle + DMG (native or osxcross)
|
||||||
|
$0 --lite-backend --mac-release # macOS ObsidianDragonLite.app + DMG (lite backend)
|
||||||
$0 --clean --linux-release --win-release # Clean + both
|
$0 --clean --linux-release --win-release # Clean + both
|
||||||
EOF
|
EOF
|
||||||
exit 0
|
exit 0
|
||||||
@@ -85,6 +94,8 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--linux-release) DO_LINUX=true; shift ;;
|
--linux-release) DO_LINUX=true; shift ;;
|
||||||
--win-release) DO_WIN=true; shift ;;
|
--win-release) DO_WIN=true; shift ;;
|
||||||
--mac-release) DO_MAC=true; shift ;;
|
--mac-release) DO_MAC=true; shift ;;
|
||||||
|
--lite) DO_LITE=true; shift ;;
|
||||||
|
--lite-backend) DO_LITE=true; DO_LITE_BACKEND=true; shift ;;
|
||||||
-c|--clean) CLEAN=true; shift ;;
|
-c|--clean) CLEAN=true; shift ;;
|
||||||
-d|--debug) BUILD_TYPE="Debug"; shift ;;
|
-d|--debug) BUILD_TYPE="Debug"; shift ;;
|
||||||
-j) JOBS="$2"; shift 2 ;;
|
-j) JOBS="$2"; shift 2 ;;
|
||||||
@@ -98,6 +109,92 @@ if ! $DO_LINUX && ! $DO_WIN && ! $DO_MAC; then
|
|||||||
DO_DEV=true
|
DO_DEV=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
APP_BASENAME="ObsidianDragon"
|
||||||
|
CMAKE_LITE_ARGS=()
|
||||||
|
# Always set the variant flag EXPLICITLY (ON and OFF) so switching variants in a shared build dir
|
||||||
|
# can't reuse a stale cached value (e.g. a prior --lite build leaving DRAGONX_BUILD_LITE=ON).
|
||||||
|
if $DO_LITE; then
|
||||||
|
APP_BASENAME="ObsidianDragonLite"
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_BUILD_LITE=ON")
|
||||||
|
# Lite never embeds/launches a daemon; set it explicitly too for cache hygiene.
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_ENABLE_EMBEDDED_DAEMON=OFF")
|
||||||
|
info "Lite mode enabled: building ${APP_BASENAME}"
|
||||||
|
else
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_BUILD_LITE=OFF")
|
||||||
|
# Re-assert the embedded daemon ON for full-node builds, EXPLICITLY, so a build dir whose cache
|
||||||
|
# was poisoned OFF by a prior --lite configure (or any stale value) is healed — otherwise the
|
||||||
|
# full-node app extracts dragonxd but never launches it (isUsingEmbeddedDaemon() == false).
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_ENABLE_EMBEDDED_DAEMON=ON")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve the release version string for the active variant from CMakeLists.txt (single source of
|
||||||
|
# truth): the full-node app uses project() VERSION + DRAGONX_VERSION_SUFFIX; ObsidianDragonLite uses
|
||||||
|
# DRAGONX_LITE_VERSION + DRAGONX_LITE_VERSION_SUFFIX.
|
||||||
|
_cml="$SCRIPT_DIR/CMakeLists.txt"
|
||||||
|
_full_ver=$(sed -n 's/^[[:space:]]*VERSION[[:space:]]\+\([0-9][0-9.]*\).*/\1/p' "$_cml" | head -1)
|
||||||
|
_full_suffix=$(sed -n 's/^set(DRAGONX_VERSION_SUFFIX[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
|
||||||
|
_lite_ver=$(sed -n 's/^set(DRAGONX_LITE_VERSION[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
|
||||||
|
_lite_suffix=$(sed -n 's/^set(DRAGONX_LITE_VERSION_SUFFIX[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
|
||||||
|
if $DO_LITE; then
|
||||||
|
VERSION="${_lite_ver}${_lite_suffix}"
|
||||||
|
else
|
||||||
|
VERSION="${_full_ver}${_full_suffix}"
|
||||||
|
fi
|
||||||
|
[ -n "$_full_ver" ] && [ -n "$VERSION" ] || { err "Could not parse version from CMakeLists.txt"; exit 1; }
|
||||||
|
info "Release version: ${VERSION} (${APP_BASENAME})"
|
||||||
|
|
||||||
|
# ── Lite backend (real SDXL litelib) linking ─────────────────────────────────
|
||||||
|
# Enables DRAGONX_ENABLE_LITE_BACKEND with an imported artifact produced by
|
||||||
|
# scripts/build-lite-backend-artifact.sh. Auto-discovers build/lite-backend/<platform>/;
|
||||||
|
# override the directory with DRAGONX_LITE_BACKEND_DIR.
|
||||||
|
if $DO_LITE_BACKEND; then
|
||||||
|
# Artifact platform follows the cross target when exactly one non-host release is requested,
|
||||||
|
# so `--lite-backend --win-release` links the Windows backend (not the host's) automatically.
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Linux) lb_platform="linux" ;;
|
||||||
|
Darwin) lb_platform="macos" ;;
|
||||||
|
*) lb_platform="linux" ;;
|
||||||
|
esac
|
||||||
|
if $DO_WIN && ! $DO_LINUX && ! $DO_MAC; then lb_platform="windows"; fi
|
||||||
|
if $DO_MAC && ! $DO_LINUX && ! $DO_WIN; then lb_platform="macos"; fi
|
||||||
|
lb_dir="${DRAGONX_LITE_BACKEND_DIR:-$SCRIPT_DIR/build/lite-backend/$lb_platform}"
|
||||||
|
lb_lib=""
|
||||||
|
for cand in "$lb_dir"/libsilentdragonxlite.a "$lb_dir"/libsilentdragonxlite.so "$lb_dir"/silentdragonxlite.lib; do
|
||||||
|
[[ -f "$cand" ]] && { lb_lib="$cand"; break; }
|
||||||
|
done
|
||||||
|
lb_symbols="$lb_dir/lite-backend-symbols.txt"
|
||||||
|
lb_manifest="$lb_dir/lite-backend-artifact-manifest.json"
|
||||||
|
if [[ -z "$lb_lib" || ! -f "$lb_symbols" ]]; then
|
||||||
|
err "Lite backend artifact not found under: $lb_dir"
|
||||||
|
err "Build it first: ./scripts/build-lite-backend-artifact.sh --platform $lb_platform"
|
||||||
|
err "Or set DRAGONX_LITE_BACKEND_DIR to an existing artifact directory."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
CMAKE_LITE_ARGS+=(
|
||||||
|
"-DDRAGONX_ENABLE_LITE_BACKEND=ON"
|
||||||
|
"-DDRAGONX_LITE_BACKEND_LIBRARY=$lb_lib"
|
||||||
|
"-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE=$lb_symbols"
|
||||||
|
"-DDRAGONX_LITE_BACKEND_LINK_MODE=imported"
|
||||||
|
"-DDRAGONX_LITE_BACKEND_ABI=sdxl-c-v1"
|
||||||
|
)
|
||||||
|
[[ -f "$lb_manifest" ]] && CMAKE_LITE_ARGS+=("-DDRAGONX_LITE_BACKEND_MANIFEST=$lb_manifest")
|
||||||
|
# A Rust x86_64-pc-windows-gnu staticlib pulls in Win32 system libs (rustls/schannel, ring,
|
||||||
|
# dirs, std) that the app doesn't already link. The set is rustc's `--print native-static-libs`
|
||||||
|
# for the backend (winapi_* shims mapped to the real mingw import libs); all exist in mingw-w64.
|
||||||
|
if [[ "$lb_platform" == "windows" ]]; then
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_LITE_BACKEND_EXTRA_LIBS=advapi32;ws2_32;kernel32;bcrypt;cfgmgr32;credui;crypt32;cryptnet;fwpuclnt;gdi32;msimg32;ncrypt;ntdll;ole32;opengl32;secur32;shell32;synchronization;user32;winspool;userenv")
|
||||||
|
fi
|
||||||
|
info "Lite backend enabled ($lb_platform): $lb_lib"
|
||||||
|
else
|
||||||
|
# Explicit OFF so a prior --lite-backend configure in a shared build dir can't leave it ON
|
||||||
|
# (which would then fail the BUILD_LITE=OFF guard in CMake).
|
||||||
|
CMAKE_LITE_ARGS+=("-DDRAGONX_ENABLE_LITE_BACKEND=OFF")
|
||||||
|
fi
|
||||||
|
|
||||||
|
should_bundle_full_node_assets() {
|
||||||
|
! $DO_LITE
|
||||||
|
}
|
||||||
|
|
||||||
# ── Helper: find resource files ──────────────────────────────────────────────
|
# ── Helper: find resource files ──────────────────────────────────────────────
|
||||||
find_sapling_params() {
|
find_sapling_params() {
|
||||||
local dirs=(
|
local dirs=(
|
||||||
@@ -215,13 +312,14 @@ build_dev() {
|
|||||||
cmake "$SCRIPT_DIR" \
|
cmake "$SCRIPT_DIR" \
|
||||||
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
|
||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=ON
|
-DDRAGONX_USE_SYSTEM_SDL3=ON \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon" ]] || { err "Build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}" ]] || { err "Build failed"; exit 1; }
|
||||||
info "Dev binary: $bd/bin/ObsidianDragon ($(du -h bin/ObsidianDragon | cut -f1))"
|
info "Dev binary: $bd/bin/${APP_BASENAME} ($(du -h "bin/${APP_BASENAME}" | cut -f1))"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -242,42 +340,51 @@ build_release_linux() {
|
|||||||
cmake "$SCRIPT_DIR" \
|
cmake "$SCRIPT_DIR" \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=ON
|
-DDRAGONX_USE_SYSTEM_SDL3=ON \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon" ]] || { err "Linux build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}" ]] || { err "Linux build failed"; exit 1; }
|
||||||
|
|
||||||
info "Stripping ..."
|
info "Stripping ..."
|
||||||
strip bin/ObsidianDragon
|
strip "bin/${APP_BASENAME}"
|
||||||
info "Binary: $(du -h bin/ObsidianDragon | cut -f1)"
|
info "Binary: $(du -h "bin/${APP_BASENAME}" | cut -f1)"
|
||||||
|
|
||||||
# ── Bundle daemon ────────────────────────────────────────────────────────
|
if should_bundle_full_node_assets; then
|
||||||
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
|
# ── Bundle daemon ────────────────────────────────────────────────────
|
||||||
|
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
|
||||||
|
|
||||||
# ── Bundle Sapling params ────────────────────────────────────────────────
|
# ── Bundle Sapling params ────────────────────────────────────────────
|
||||||
SAPLING_SPEND="" SAPLING_OUTPUT=""
|
SAPLING_SPEND="" SAPLING_OUTPUT=""
|
||||||
find_sapling_params && {
|
find_sapling_params && {
|
||||||
cp -f "$SAPLING_SPEND" "bin/sapling-spend.params"
|
cp -f "$SAPLING_SPEND" "bin/sapling-spend.params"
|
||||||
cp -f "$SAPLING_OUTPUT" "bin/sapling-output.params"
|
cp -f "$SAPLING_OUTPUT" "bin/sapling-output.params"
|
||||||
info "Bundled Sapling params"
|
info "Bundled Sapling params"
|
||||||
} || warn "Sapling params not found — not bundled"
|
} || warn "Sapling params not found — not bundled"
|
||||||
|
else
|
||||||
|
info "Lite mode: skipping daemon and Sapling/asmap bundling"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Package: release/linux/ ──────────────────────────────────────────────
|
# ── Package: release/linux/ ──────────────────────────────────────────────
|
||||||
rm -rf "$out"
|
# Remove only THIS variant's prior artifacts so full-node and lite releases can coexist in the
|
||||||
|
# same output dir (both ObsidianDragon* and ObsidianDragonLite* end up under release/linux/).
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
rm -rf "$out/${APP_BASENAME}-"* "$out/${APP_BASENAME}.AppImage"
|
||||||
|
|
||||||
local DIST="ObsidianDragon-${VERSION}-Linux-x64"
|
local DIST="${APP_BASENAME}-${VERSION}-Linux-x64"
|
||||||
local dist_dir="$out/$DIST"
|
local dist_dir="$out/$DIST"
|
||||||
mkdir -p "$dist_dir"
|
mkdir -p "$dist_dir"
|
||||||
|
|
||||||
cp bin/ObsidianDragon "$dist_dir/"
|
cp "bin/${APP_BASENAME}" "$dist_dir/"
|
||||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$dist_dir/"
|
if should_bundle_full_node_assets; then
|
||||||
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$dist_dir/"
|
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$dist_dir/"
|
||||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$dist_dir/"
|
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$dist_dir/"
|
||||||
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$dist_dir/"
|
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$dist_dir/"
|
||||||
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$dist_dir/"
|
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$dist_dir/"
|
||||||
|
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$dist_dir/"
|
||||||
|
fi
|
||||||
# Bundle xmrig for mining support
|
# Bundle xmrig for mining support
|
||||||
local XMRIG_LINUX="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig"
|
local XMRIG_LINUX="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig"
|
||||||
[[ -f "$XMRIG_LINUX" ]] && { cp "$XMRIG_LINUX" "$dist_dir/"; chmod +x "$dist_dir/xmrig"; info "Bundled xmrig"; } || warn "xmrig not found — mining unavailable in zip"
|
[[ -f "$XMRIG_LINUX" ]] && { cp "$XMRIG_LINUX" "$dist_dir/"; chmod +x "$dist_dir/xmrig"; info "Bundled xmrig"; } || warn "xmrig not found — mining unavailable in zip"
|
||||||
@@ -299,27 +406,29 @@ build_release_linux() {
|
|||||||
"$APPDIR/usr/share/icons/hicolor/256x256/apps" \
|
"$APPDIR/usr/share/icons/hicolor/256x256/apps" \
|
||||||
"$APPDIR/usr/share/ObsidianDragon/res"
|
"$APPDIR/usr/share/ObsidianDragon/res"
|
||||||
|
|
||||||
cp bin/ObsidianDragon "$APPDIR/usr/bin/"
|
cp "bin/${APP_BASENAME}" "$APPDIR/usr/bin/"
|
||||||
cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true
|
cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true
|
||||||
|
|
||||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
|
if should_bundle_full_node_assets; then
|
||||||
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$APPDIR/usr/bin/"
|
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
|
||||||
# Daemon data files must be alongside the daemon binary (usr/bin/)
|
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$APPDIR/usr/bin/"
|
||||||
# because dragonxd searches relative to its own directory.
|
# Daemon data files must be alongside the daemon binary (usr/bin/)
|
||||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/bin/"
|
# because dragonxd searches relative to its own directory.
|
||||||
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$APPDIR/usr/bin/"
|
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/bin/"
|
||||||
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$APPDIR/usr/bin/"
|
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$APPDIR/usr/bin/"
|
||||||
|
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$APPDIR/usr/bin/"
|
||||||
|
fi
|
||||||
# Bundle xmrig for mining support
|
# Bundle xmrig for mining support
|
||||||
local XMRIG_LINUX_AI="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig"
|
local XMRIG_LINUX_AI="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig"
|
||||||
[[ -f "$XMRIG_LINUX_AI" ]] && { cp "$XMRIG_LINUX_AI" "$APPDIR/usr/bin/"; chmod +x "$APPDIR/usr/bin/xmrig"; }
|
[[ -f "$XMRIG_LINUX_AI" ]] && { cp "$XMRIG_LINUX_AI" "$APPDIR/usr/bin/"; chmod +x "$APPDIR/usr/bin/xmrig"; }
|
||||||
|
|
||||||
# Desktop entry
|
# Desktop entry
|
||||||
cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<'DESK'
|
cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<DESK
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=DragonX Wallet
|
Name=DragonX Wallet
|
||||||
Comment=DragonX Cryptocurrency Wallet
|
Comment=DragonX Cryptocurrency Wallet
|
||||||
Exec=ObsidianDragon
|
Exec=${APP_BASENAME}
|
||||||
Icon=ObsidianDragon
|
Icon=ObsidianDragon
|
||||||
Categories=Finance;Network;
|
Categories=Finance;Network;
|
||||||
Terminal=false
|
Terminal=false
|
||||||
@@ -350,14 +459,14 @@ SVG
|
|||||||
cp "$APPDIR/ObsidianDragon.svg" "$APPDIR/ObsidianDragon.png" 2>/dev/null || true
|
cp "$APPDIR/ObsidianDragon.svg" "$APPDIR/ObsidianDragon.png" 2>/dev/null || true
|
||||||
|
|
||||||
# AppRun
|
# AppRun
|
||||||
cat > "$APPDIR/AppRun" <<'APPRUN'
|
cat > "$APPDIR/AppRun" <<APPRUN
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
SELF=$(readlink -f "$0")
|
SELF=\$(readlink -f "\$0")
|
||||||
HERE=${SELF%/*}
|
HERE=\${SELF%/*}
|
||||||
export DRAGONX_RES_PATH="${HERE}/usr/share/ObsidianDragon/res"
|
export DRAGONX_RES_PATH="\${HERE}/usr/share/ObsidianDragon/res"
|
||||||
export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}"
|
export LD_LIBRARY_PATH="\${HERE}/usr/lib:\${LD_LIBRARY_PATH}"
|
||||||
cd "${HERE}/usr/share/ObsidianDragon"
|
cd "\${HERE}/usr/share/ObsidianDragon"
|
||||||
exec "${HERE}/usr/bin/ObsidianDragon" "$@"
|
exec "\${HERE}/usr/bin/${APP_BASENAME}" "\$@"
|
||||||
APPRUN
|
APPRUN
|
||||||
chmod +x "$APPDIR/AppRun"
|
chmod +x "$APPDIR/AppRun"
|
||||||
|
|
||||||
@@ -386,9 +495,9 @@ APPRUN
|
|||||||
local ARCH
|
local ARCH
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
cd "$bd"
|
cd "$bd"
|
||||||
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "ObsidianDragon-${VERSION}-${ARCH}.AppImage" 2>/dev/null && {
|
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "${APP_BASENAME}-${VERSION}-${ARCH}.AppImage" 2>/dev/null && {
|
||||||
cp "ObsidianDragon-${VERSION}-${ARCH}.AppImage" "$out/ObsidianDragon-${VERSION}.AppImage"
|
cp "${APP_BASENAME}-${VERSION}-${ARCH}.AppImage" "$out/${APP_BASENAME}-${VERSION}.AppImage"
|
||||||
info "AppImage: $out/ObsidianDragon-${VERSION}.AppImage ($(du -h "$out/ObsidianDragon-${VERSION}.AppImage" | cut -f1))"
|
info "AppImage: $out/${APP_BASENAME}-${VERSION}.AppImage ($(du -h "$out/${APP_BASENAME}-${VERSION}.AppImage" | cut -f1))"
|
||||||
} || warn "AppImage creation failed — binaries zip still in release/linux/"
|
} || warn "AppImage creation failed — binaries zip still in release/linux/"
|
||||||
|
|
||||||
info "Linux release artifacts: $out/"
|
info "Linux release artifacts: $out/"
|
||||||
@@ -497,26 +606,48 @@ HDR
|
|||||||
|
|
||||||
# ── Daemon binaries ──────────────────────────────────────────────
|
# ── Daemon binaries ──────────────────────────────────────────────
|
||||||
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
||||||
if [[ -d "$DD" && -f "$DD/dragonxd.exe" ]]; then
|
if should_bundle_full_node_assets; then
|
||||||
info "Embedding daemon binaries ..."
|
if [[ -d "$DD" && -f "$DD/dragonxd.exe" ]]; then
|
||||||
echo -e "\n#define HAS_EMBEDDED_DAEMON 1\n" >> "$GEN/embedded_data.h"
|
info "Embedding daemon binaries ..."
|
||||||
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
echo -e "\n#define HAS_EMBEDDED_DAEMON 1\n" >> "$GEN/embedded_data.h"
|
||||||
local sym=$(echo "$f" | sed 's/[^a-zA-Z0-9]/_/g')
|
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
||||||
if [[ -f "$DD/$f" ]]; then
|
local sym=$(echo "$f" | sed 's/[^a-zA-Z0-9]/_/g')
|
||||||
cp -f "$DD/$f" "$RES/$f"
|
if [[ -f "$DD/$f" ]]; then
|
||||||
info " Staged $f ($(du -h "$DD/$f" | cut -f1))"
|
cp -f "$DD/$f" "$RES/$f"
|
||||||
echo "INCBIN(${sym}, \"$RES/$f\");" >> "$GEN/embedded_data.h"
|
info " Staged $f ($(du -h "$DD/$f" | cut -f1))"
|
||||||
else
|
echo "INCBIN(${sym}, \"$RES/$f\");" >> "$GEN/embedded_data.h"
|
||||||
echo "extern \"C\" { static const uint8_t* g_${sym}_data = nullptr; }" >> "$GEN/embedded_data.h"
|
else
|
||||||
echo "static const unsigned int g_${sym}_size = 0;" >> "$GEN/embedded_data.h"
|
echo "extern \"C\" { static const uint8_t* g_${sym}_data = nullptr; }" >> "$GEN/embedded_data.h"
|
||||||
fi
|
echo "static const unsigned int g_${sym}_size = 0;" >> "$GEN/embedded_data.h"
|
||||||
done
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
warn "prebuilt-binaries/dragonxd-win/ not found — wallet-only build"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
warn "prebuilt-binaries/dragonxd-win/ not found — wallet-only build"
|
info "Lite mode: skipping embedded daemon binaries"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── xmrig binary (from prebuilt-binaries/xmrig-hac/) ────────────────
|
# ── xmrig binary (from prebuilt-binaries/xmrig-hac/) ────────────────
|
||||||
local XMRIG_DIR="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac"
|
local XMRIG_DIR="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac"
|
||||||
|
# The published DRG-XMRig archives ship the binary inside a versioned subdir, not as a flat
|
||||||
|
# xmrig.exe. Extract it from the matching win-x64 zip if it isn't already staged — otherwise
|
||||||
|
# the embed below never fires (HAS_EMBEDDED_XMRIG stays undefined) and the wallet ships with
|
||||||
|
# no miner ("xmrig binary not found" at runtime).
|
||||||
|
if [[ ! -f "$XMRIG_DIR/xmrig.exe" ]]; then
|
||||||
|
local _xz; _xz=$(ls "$XMRIG_DIR"/drg-xmrig-*-win-x64.zip 2>/dev/null | head -1)
|
||||||
|
if [[ -n "$_xz" ]] && command -v unzip >/dev/null 2>&1; then
|
||||||
|
local _xtmp; _xtmp=$(mktemp -d)
|
||||||
|
# -j flattens the versioned subdir; check the file (not unzip's exit code, which is
|
||||||
|
# non-zero if a pattern matches nothing).
|
||||||
|
unzip -j -o "$_xz" '*xmrig.exe' -d "$_xtmp" >/dev/null 2>&1 || true
|
||||||
|
if [[ -f "$_xtmp/xmrig.exe" ]]; then
|
||||||
|
cp -f "$_xtmp/xmrig.exe" "$XMRIG_DIR/xmrig.exe"
|
||||||
|
info " Extracted xmrig.exe from $(basename "$_xz")"
|
||||||
|
fi
|
||||||
|
rm -rf "$_xtmp"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
if [[ -f "$XMRIG_DIR/xmrig.exe" ]]; then
|
if [[ -f "$XMRIG_DIR/xmrig.exe" ]]; then
|
||||||
cp -f "$XMRIG_DIR/xmrig.exe" "$RES/xmrig.exe"
|
cp -f "$XMRIG_DIR/xmrig.exe" "$RES/xmrig.exe"
|
||||||
info " Staged xmrig.exe ($(du -h "$XMRIG_DIR/xmrig.exe" | cut -f1))"
|
info " Staged xmrig.exe ($(du -h "$XMRIG_DIR/xmrig.exe" | cut -f1))"
|
||||||
@@ -599,34 +730,40 @@ HDR
|
|||||||
cmake "$SCRIPT_DIR" \
|
cmake "$SCRIPT_DIR" \
|
||||||
-DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \
|
-DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=OFF
|
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon.exe" ]] || { err "Windows build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}.exe" ]] || { err "Windows build failed"; exit 1; }
|
||||||
info "Binary: $(du -h bin/ObsidianDragon.exe | cut -f1)"
|
info "Binary: $(du -h "bin/${APP_BASENAME}.exe" | cut -f1)"
|
||||||
|
|
||||||
# ── Package: release/windows/ ────────────────────────────────────────────
|
# ── Package: release/windows/ ────────────────────────────────────────────
|
||||||
rm -rf "$out"
|
# Remove only THIS variant's prior artifacts so full-node and lite releases coexist here.
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
rm -rf "$out/${APP_BASENAME}-"* "$out/${APP_BASENAME}.exe"
|
||||||
|
|
||||||
local DIST="ObsidianDragon-${VERSION}-Windows-x64"
|
local DIST="${APP_BASENAME}-${VERSION}-Windows-x64"
|
||||||
local dist_dir="$out/$DIST"
|
local dist_dir="$out/$DIST"
|
||||||
mkdir -p "$dist_dir"
|
mkdir -p "$dist_dir"
|
||||||
cp bin/ObsidianDragon.exe "$dist_dir/"
|
cp "bin/${APP_BASENAME}.exe" "$dist_dir/"
|
||||||
|
|
||||||
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
|
||||||
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
if should_bundle_full_node_assets; then
|
||||||
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
|
||||||
done
|
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
||||||
|
done
|
||||||
|
|
||||||
# Bundle Sapling params + asmap for the zip distribution
|
# Bundle Sapling params + asmap for the zip distribution
|
||||||
# (The single-file exe has these embedded via INCBIN, but the zip
|
# (The single-file exe has these embedded via INCBIN, but the zip
|
||||||
# needs them on disk so the daemon can find them in its work dir.)
|
# needs them on disk so the daemon can find them in its work dir.)
|
||||||
for f in sapling-spend.params sapling-output.params asmap.dat; do
|
for f in sapling-spend.params sapling-output.params asmap.dat; do
|
||||||
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
||||||
done
|
done
|
||||||
|
else
|
||||||
|
info "Lite mode: skipping daemon and Sapling/asmap assets in Windows zip"
|
||||||
|
fi
|
||||||
|
|
||||||
# Bundle xmrig for mining support
|
# Bundle xmrig for mining support
|
||||||
local XMRIG_WIN="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig.exe"
|
local XMRIG_WIN="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig.exe"
|
||||||
@@ -635,8 +772,8 @@ HDR
|
|||||||
cp -r bin/res "$dist_dir/" 2>/dev/null || true
|
cp -r bin/res "$dist_dir/" 2>/dev/null || true
|
||||||
|
|
||||||
# ── Single-file exe (all resources embedded) ────────────────────────────
|
# ── Single-file exe (all resources embedded) ────────────────────────────
|
||||||
cp bin/ObsidianDragon.exe "$out/ObsidianDragon-${VERSION}.exe"
|
cp "bin/${APP_BASENAME}.exe" "$out/${APP_BASENAME}-${VERSION}.exe"
|
||||||
info "Single-file exe: $out/ObsidianDragon-${VERSION}.exe ($(du -h "$out/ObsidianDragon-${VERSION}.exe" | cut -f1))"
|
info "Single-file exe: $out/${APP_BASENAME}-${VERSION}.exe ($(du -h "$out/${APP_BASENAME}-${VERSION}.exe" | cut -f1))"
|
||||||
|
|
||||||
# ── Zip ──────────────────────────────────────────────────────────────────
|
# ── Zip ──────────────────────────────────────────────────────────────────
|
||||||
if command -v zip &>/dev/null; then
|
if command -v zip &>/dev/null; then
|
||||||
@@ -818,7 +955,8 @@ TOOLCHAIN
|
|||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
||||||
${COMPILER_RT:+-DOSXCROSS_COMPILER_RT="$COMPILER_RT"}
|
${COMPILER_RT:+-DOSXCROSS_COMPILER_RT="$COMPILER_RT"} \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
else
|
else
|
||||||
# Build libsodium as universal if needed
|
# Build libsodium as universal if needed
|
||||||
local need_sodium=false
|
local need_sodium=false
|
||||||
@@ -844,39 +982,40 @@ TOOLCHAIN
|
|||||||
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
-DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
|
||||||
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
-DDRAGONX_USE_SYSTEM_SDL3=OFF \
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
|
||||||
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64"
|
-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
|
||||||
|
"${CMAKE_LITE_ARGS[@]}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
info "Building with $JOBS jobs ..."
|
info "Building with $JOBS jobs ..."
|
||||||
cmake --build . -j "$JOBS"
|
cmake --build . -j "$JOBS"
|
||||||
|
|
||||||
[[ -f "bin/ObsidianDragon" ]] || { err "macOS build failed"; exit 1; }
|
[[ -f "bin/${APP_BASENAME}" ]] || { err "macOS build failed"; exit 1; }
|
||||||
|
|
||||||
# Strip — use osxcross strip for cross-builds
|
# Strip — use osxcross strip for cross-builds
|
||||||
if $IS_CROSS; then
|
if $IS_CROSS; then
|
||||||
local STRIP_CMD="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-strip"
|
local STRIP_CMD="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-strip"
|
||||||
if [[ -x "$STRIP_CMD" ]]; then
|
if [[ -x "$STRIP_CMD" ]]; then
|
||||||
info "Stripping (osxcross) ..."
|
info "Stripping (osxcross) ..."
|
||||||
"$STRIP_CMD" bin/ObsidianDragon
|
"$STRIP_CMD" "bin/${APP_BASENAME}"
|
||||||
else
|
else
|
||||||
warn "osxcross strip not found at $STRIP_CMD — skipping"
|
warn "osxcross strip not found at $STRIP_CMD — skipping"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
info "Stripping ..."
|
info "Stripping ..."
|
||||||
strip bin/ObsidianDragon
|
strip "bin/${APP_BASENAME}"
|
||||||
# Verify universal binary
|
# Verify universal binary
|
||||||
if command -v lipo &>/dev/null; then
|
if command -v lipo &>/dev/null; then
|
||||||
info "Architecture info:"
|
info "Architecture info:"
|
||||||
lipo -info bin/ObsidianDragon
|
lipo -info "bin/${APP_BASENAME}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
info "Binary: $(du -h bin/ObsidianDragon | cut -f1)"
|
info "Binary: $(du -h "bin/${APP_BASENAME}" | cut -f1)"
|
||||||
|
|
||||||
# ── Create .app bundle ───────────────────────────────────────────────────
|
# ── Create .app bundle ───────────────────────────────────────────────────
|
||||||
rm -rf "$out"
|
rm -rf "$out"
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
|
||||||
local APP="$out/ObsidianDragon.app"
|
local APP="$out/${APP_BASENAME}.app"
|
||||||
local CONTENTS="$APP/Contents"
|
local CONTENTS="$APP/Contents"
|
||||||
local MACOS="$CONTENTS/MacOS"
|
local MACOS="$CONTENTS/MacOS"
|
||||||
local RESOURCES="$CONTENTS/Resources"
|
local RESOURCES="$CONTENTS/Resources"
|
||||||
@@ -885,39 +1024,43 @@ TOOLCHAIN
|
|||||||
mkdir -p "$MACOS" "$RESOURCES/res" "$FRAMEWORKS"
|
mkdir -p "$MACOS" "$RESOURCES/res" "$FRAMEWORKS"
|
||||||
|
|
||||||
# Main binary
|
# Main binary
|
||||||
cp bin/ObsidianDragon "$MACOS/"
|
cp "bin/${APP_BASENAME}" "$MACOS/"
|
||||||
chmod +x "$MACOS/ObsidianDragon"
|
chmod +x "$MACOS/${APP_BASENAME}"
|
||||||
|
|
||||||
# Resources
|
# Resources
|
||||||
cp -r bin/res/* "$RESOURCES/res/" 2>/dev/null || true
|
cp -r bin/res/* "$RESOURCES/res/" 2>/dev/null || true
|
||||||
|
|
||||||
# Daemon binaries (macOS native, from dragonxd-mac/)
|
if should_bundle_full_node_assets; then
|
||||||
local daemon_dir="$SCRIPT_DIR/prebuilt-binaries/dragonxd-mac"
|
# Daemon binaries (macOS native, from dragonxd-mac/)
|
||||||
if [[ -d "$daemon_dir" ]]; then
|
local daemon_dir="$SCRIPT_DIR/prebuilt-binaries/dragonxd-mac"
|
||||||
for f in dragonxd dragonx-cli dragonx-tx; do
|
if [[ -d "$daemon_dir" ]]; then
|
||||||
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; chmod +x "$MACOS/$f"; info " Bundled $f"; }
|
for f in dragonxd dragonx-cli dragonx-tx; do
|
||||||
done
|
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; chmod +x "$MACOS/$f"; info " Bundled $f"; }
|
||||||
for f in sapling-spend.params sapling-output.params; do
|
done
|
||||||
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; info " Bundled $f"; }
|
for f in sapling-spend.params sapling-output.params; do
|
||||||
done
|
[[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; info " Bundled $f"; }
|
||||||
elif ! $IS_CROSS; then
|
done
|
||||||
# Native macOS: try standard paths
|
elif ! $IS_CROSS; then
|
||||||
local daemon_paths=(
|
# Native macOS: try standard paths
|
||||||
"$SCRIPT_DIR/../dragonxd"
|
local daemon_paths=(
|
||||||
"$HOME/dragonx/src/dragonxd"
|
"$SCRIPT_DIR/../dragonxd"
|
||||||
)
|
"$HOME/dragonx/src/dragonxd"
|
||||||
for p in "${daemon_paths[@]}"; do
|
)
|
||||||
[[ -f "$p" ]] && { cp "$p" "$MACOS/dragonxd"; chmod +x "$MACOS/dragonxd"; info " Bundled dragonxd"; break; }
|
for p in "${daemon_paths[@]}"; do
|
||||||
done
|
[[ -f "$p" ]] && { cp "$p" "$MACOS/dragonxd"; chmod +x "$MACOS/dragonxd"; info " Bundled dragonxd"; break; }
|
||||||
local cli_paths=(
|
done
|
||||||
"$SCRIPT_DIR/../dragonx-cli"
|
local cli_paths=(
|
||||||
"$HOME/dragonx/src/dragonx-cli"
|
"$SCRIPT_DIR/../dragonx-cli"
|
||||||
)
|
"$HOME/dragonx/src/dragonx-cli"
|
||||||
for p in "${cli_paths[@]}"; do
|
)
|
||||||
[[ -f "$p" ]] && { cp "$p" "$MACOS/dragonx-cli"; chmod +x "$MACOS/dragonx-cli"; info " Bundled dragonx-cli"; break; }
|
for p in "${cli_paths[@]}"; do
|
||||||
done
|
[[ -f "$p" ]] && { cp "$p" "$MACOS/dragonx-cli"; chmod +x "$MACOS/dragonx-cli"; info " Bundled dragonx-cli"; break; }
|
||||||
|
done
|
||||||
|
else
|
||||||
|
warn "prebuilt-binaries/dragonxd-mac/ not found — place macOS daemon binaries there for bundling"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
warn "prebuilt-binaries/dragonxd-mac/ not found — place macOS daemon binaries there for bundling"
|
info "Lite mode: skipping macOS daemon and Sapling/asmap bundling"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# xmrig binary (from prebuilt-binaries/xmrig-hac/)
|
# xmrig binary (from prebuilt-binaries/xmrig-hac/)
|
||||||
@@ -930,11 +1073,13 @@ TOOLCHAIN
|
|||||||
warn "xmrig not found — mining unavailable in .app"
|
warn "xmrig not found — mining unavailable in .app"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# asmap.dat — placed in MacOS/ so the daemon finds it next to its binary
|
if should_bundle_full_node_assets; then
|
||||||
find_asmap 2>/dev/null && {
|
# asmap.dat — placed in MacOS/ so the daemon finds it next to its binary
|
||||||
cp "$ASMAP_DAT" "$MACOS/asmap.dat"
|
find_asmap 2>/dev/null && {
|
||||||
info " Bundled asmap.dat"
|
cp "$ASMAP_DAT" "$MACOS/asmap.dat"
|
||||||
}
|
info " Bundled asmap.dat"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# Bundle SDL3 dylib
|
# Bundle SDL3 dylib
|
||||||
local sdl_dylib=""
|
local sdl_dylib=""
|
||||||
@@ -954,26 +1099,34 @@ TOOLCHAIN
|
|||||||
# Fix the rpath so the binary finds SDL3 in Frameworks/
|
# Fix the rpath so the binary finds SDL3 in Frameworks/
|
||||||
if $IS_CROSS; then
|
if $IS_CROSS; then
|
||||||
local INSTALL_NAME_TOOL="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-install_name_tool"
|
local INSTALL_NAME_TOOL="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-install_name_tool"
|
||||||
[[ -x "$INSTALL_NAME_TOOL" ]] && "$INSTALL_NAME_TOOL" -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/ObsidianDragon" 2>/dev/null || true
|
[[ -x "$INSTALL_NAME_TOOL" ]] && "$INSTALL_NAME_TOOL" -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/${APP_BASENAME}" 2>/dev/null || true
|
||||||
else
|
else
|
||||||
install_name_tool -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/ObsidianDragon" 2>/dev/null || true
|
install_name_tool -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/${APP_BASENAME}" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
info " Bundled $sdl_name"
|
info " Bundled $sdl_name"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Launcher script (ensures working dir + dylib path)
|
# Launcher script (ensures working dir + dylib path). Uses ${APP_BASENAME} so the lite
|
||||||
mv "$MACOS/ObsidianDragon" "$MACOS/ObsidianDragon.bin"
|
# variant (ObsidianDragonLite) gets a correctly-named launcher + .bin pair.
|
||||||
cat > "$MACOS/ObsidianDragon" <<'LAUNCH'
|
mv "$MACOS/${APP_BASENAME}" "$MACOS/${APP_BASENAME}.bin"
|
||||||
|
cat > "$MACOS/${APP_BASENAME}" <<LAUNCH
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
DIR="\$(cd "\$(dirname "\$0")" && pwd)"
|
||||||
export DYLD_LIBRARY_PATH="$DIR/../Frameworks:$DYLD_LIBRARY_PATH"
|
export DYLD_LIBRARY_PATH="\$DIR/../Frameworks:\$DYLD_LIBRARY_PATH"
|
||||||
export DRAGONX_RES_PATH="$DIR/../Resources/res"
|
export DRAGONX_RES_PATH="\$DIR/../Resources/res"
|
||||||
cd "$DIR/../Resources"
|
cd "\$DIR/../Resources"
|
||||||
exec "$DIR/ObsidianDragon.bin" "$@"
|
exec "\$DIR/${APP_BASENAME}.bin" "\$@"
|
||||||
LAUNCH
|
LAUNCH
|
||||||
chmod +x "$MACOS/ObsidianDragon"
|
chmod +x "$MACOS/${APP_BASENAME}"
|
||||||
|
|
||||||
# Info.plist
|
# Info.plist — display name + bundle id differ per variant so lite and full-node .apps
|
||||||
|
# can coexist; the executable matches the launcher (${APP_BASENAME}); the icon is shared.
|
||||||
|
local APP_DISPLAY_NAME="DragonX Wallet"
|
||||||
|
local APP_BUNDLE_ID="is.hush.dragonx"
|
||||||
|
if $DO_LITE; then
|
||||||
|
APP_DISPLAY_NAME="DragonX Wallet Lite"
|
||||||
|
APP_BUNDLE_ID="is.hush.dragonx.lite"
|
||||||
|
fi
|
||||||
cat > "$CONTENTS/Info.plist" <<PLIST
|
cat > "$CONTENTS/Info.plist" <<PLIST
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||||
@@ -981,17 +1134,17 @@ LAUNCH
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>DragonX Wallet</string>
|
<string>${APP_DISPLAY_NAME}</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>DragonX Wallet</string>
|
<string>${APP_DISPLAY_NAME}</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>is.hush.dragonx</string>
|
<string>${APP_BUNDLE_ID}</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>${VERSION}</string>
|
<string>${VERSION}</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>${VERSION}</string>
|
<string>${VERSION}</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>ObsidianDragon</string>
|
<string>${APP_BASENAME}</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>ObsidianDragon</string>
|
<string>ObsidianDragon</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
@@ -1053,25 +1206,27 @@ PLIST
|
|||||||
info ".app bundle created: $APP"
|
info ".app bundle created: $APP"
|
||||||
|
|
||||||
# ── Zip the .app bundle ──────────────────────────────────────────────────
|
# ── Zip the .app bundle ──────────────────────────────────────────────────
|
||||||
local APP_ZIP="ObsidianDragon-${VERSION}-macOS-${MAC_ARCH}.app.zip"
|
local APP_ZIP="${APP_BASENAME}-${VERSION}-macOS-${MAC_ARCH}.app.zip"
|
||||||
if command -v zip &>/dev/null; then
|
if command -v zip &>/dev/null; then
|
||||||
(cd "$out" && zip -r "$APP_ZIP" "ObsidianDragon.app")
|
(cd "$out" && zip -r "$APP_ZIP" "${APP_BASENAME}.app")
|
||||||
info "App zip: $out/$APP_ZIP ($(du -h "$out/$APP_ZIP" | cut -f1))"
|
info "App zip: $out/$APP_ZIP ($(du -h "$out/$APP_ZIP" | cut -f1))"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Create DMG ───────────────────────────────────────────────────────────
|
# ── Create DMG ───────────────────────────────────────────────────────────
|
||||||
local DMG_NAME="DragonX_Wallet-${VERSION}-macOS-${MAC_ARCH}.dmg"
|
local DMG_BASENAME="DragonX_Wallet"
|
||||||
|
$DO_LITE && DMG_BASENAME="DragonX_Wallet_Lite"
|
||||||
|
local DMG_NAME="${DMG_BASENAME}-${VERSION}-macOS-${MAC_ARCH}.dmg"
|
||||||
|
|
||||||
if command -v create-dmg &>/dev/null; then
|
if command -v create-dmg &>/dev/null; then
|
||||||
# create-dmg (works on macOS; also available on Linux via npm)
|
# create-dmg (works on macOS; also available on Linux via npm)
|
||||||
info "Creating DMG with create-dmg ..."
|
info "Creating DMG with create-dmg ..."
|
||||||
create-dmg \
|
create-dmg \
|
||||||
--volname "DragonX Wallet" \
|
--volname "${APP_DISPLAY_NAME}" \
|
||||||
--volicon "$RESOURCES/ObsidianDragon.icns" \
|
--volicon "$RESOURCES/ObsidianDragon.icns" \
|
||||||
--window-pos 200 120 \
|
--window-pos 200 120 \
|
||||||
--window-size 600 400 \
|
--window-size 600 400 \
|
||||||
--icon-size 100 \
|
--icon-size 100 \
|
||||||
--icon "ObsidianDragon.app" 150 190 \
|
--icon "${APP_BASENAME}.app" 150 190 \
|
||||||
--app-drop-link 450 190 \
|
--app-drop-link 450 190 \
|
||||||
--no-internet-enable \
|
--no-internet-enable \
|
||||||
"$out/$DMG_NAME" \
|
"$out/$DMG_NAME" \
|
||||||
@@ -1086,7 +1241,7 @@ PLIST
|
|||||||
mkdir -p "$staging"
|
mkdir -p "$staging"
|
||||||
cp -a "$APP" "$staging/"
|
cp -a "$APP" "$staging/"
|
||||||
ln -s /Applications "$staging/Applications"
|
ln -s /Applications "$staging/Applications"
|
||||||
hdiutil create -volname "DragonX Wallet" \
|
hdiutil create -volname "${APP_DISPLAY_NAME}" \
|
||||||
-srcfolder "$staging" \
|
-srcfolder "$staging" \
|
||||||
-ov -format UDZO \
|
-ov -format UDZO \
|
||||||
"$out/$DMG_NAME" 2>/dev/null && {
|
"$out/$DMG_NAME" 2>/dev/null && {
|
||||||
@@ -1102,7 +1257,7 @@ PLIST
|
|||||||
cp -a "$APP" "$staging/"
|
cp -a "$APP" "$staging/"
|
||||||
# Can't create a real symlink to /Applications in an ISO, but the .app
|
# Can't create a real symlink to /Applications in an ISO, but the .app
|
||||||
# is the important part — users drag it to Applications manually.
|
# is the important part — users drag it to Applications manually.
|
||||||
genisoimage -V "DragonX Wallet" \
|
genisoimage -V "${APP_DISPLAY_NAME}" \
|
||||||
-D -R -apple -no-pad \
|
-D -R -apple -no-pad \
|
||||||
-o "$out/$DMG_NAME" \
|
-o "$out/$DMG_NAME" \
|
||||||
"$staging" 2>/dev/null && {
|
"$staging" 2>/dev/null && {
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
|
||||||
|
|
||||||
<!-- Application identity —————————————————————————————— -->
|
|
||||||
<assemblyIdentity
|
|
||||||
type="win32"
|
|
||||||
name="DragonX.ObsidianDragon.Wallet"
|
|
||||||
version="1.1.1.0"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<description>ObsidianDragon Wallet</description>
|
|
||||||
|
|
||||||
<!-- Common Controls v6 (themed buttons, etc.) ————————— -->
|
|
||||||
<dependency>
|
|
||||||
<dependentAssembly>
|
|
||||||
<assemblyIdentity
|
|
||||||
type="win32"
|
|
||||||
name="Microsoft.Windows.Common-Controls"
|
|
||||||
version="6.0.0.0"
|
|
||||||
processorArchitecture="*"
|
|
||||||
publicKeyToken="6595b64144ccf1df"
|
|
||||||
language="*"
|
|
||||||
/>
|
|
||||||
</dependentAssembly>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- DPI awareness (Per-Monitor V2) ————————————————————— -->
|
|
||||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
||||||
<windowsSettings>
|
|
||||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
|
||||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
|
|
||||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
|
||||||
</windowsSettings>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
<!-- Supported OS declarations (Windows 7 → 11) ———————— -->
|
|
||||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
|
||||||
<application>
|
|
||||||
<!-- Windows 7 -->
|
|
||||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
|
||||||
<!-- Windows 8 -->
|
|
||||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
|
||||||
<!-- Windows 8.1 -->
|
|
||||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
|
||||||
<!-- Windows 10 / 11 -->
|
|
||||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
|
||||||
</application>
|
|
||||||
</compatibility>
|
|
||||||
|
|
||||||
</assembly>
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<assemblyIdentity
|
<assemblyIdentity
|
||||||
type="win32"
|
type="win32"
|
||||||
name="DragonX.ObsidianDragon.Wallet"
|
name="DragonX.ObsidianDragon.Wallet"
|
||||||
version="@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0"
|
version="@DRAGONX_APP_VERSION_MAJOR@.@DRAGONX_APP_VERSION_MINOR@.@DRAGONX_APP_VERSION_PATCH@.0"
|
||||||
processorArchitecture="amd64"
|
processorArchitecture="amd64"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,8 @@
|
|||||||
#include <winver.h>
|
#include <winver.h>
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
|
FILEVERSION @DRAGONX_APP_VERSION_MAJOR@,@DRAGONX_APP_VERSION_MINOR@,@DRAGONX_APP_VERSION_PATCH@,0
|
||||||
PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
|
PRODUCTVERSION @DRAGONX_APP_VERSION_MAJOR@,@DRAGONX_APP_VERSION_MINOR@,@DRAGONX_APP_VERSION_PATCH@,0
|
||||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||||
FILEFLAGS 0x0L
|
FILEFLAGS 0x0L
|
||||||
FILEOS VOS_NT_WINDOWS32
|
FILEOS VOS_NT_WINDOWS32
|
||||||
@@ -32,13 +32,13 @@ BEGIN
|
|||||||
BLOCK "040904B0" // US-English, Unicode
|
BLOCK "040904B0" // US-English, Unicode
|
||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "DragonX Developers\0"
|
VALUE "CompanyName", "DragonX Developers\0"
|
||||||
VALUE "FileDescription", "ObsidianDragon Wallet\0"
|
VALUE "FileDescription", "@DRAGONX_APP_NAME@ Wallet\0"
|
||||||
VALUE "FileVersion", "@PROJECT_VERSION@\0"
|
VALUE "FileVersion", "@DRAGONX_APP_VERSION@@DRAGONX_APP_VERSION_SUFFIX@\0"
|
||||||
VALUE "InternalName", "ObsidianDragon\0"
|
VALUE "InternalName", "@DRAGONX_APP_NAME@\0"
|
||||||
VALUE "LegalCopyright", "Copyright 2024-2026 DragonX Developers. GPLv3.\0"
|
VALUE "LegalCopyright", "Copyright 2024-2026 DragonX Developers. GPLv3.\0"
|
||||||
VALUE "OriginalFilename", "ObsidianDragon.exe\0"
|
VALUE "OriginalFilename", "@DRAGONX_APP_NAME@.exe\0"
|
||||||
VALUE "ProductName", "ObsidianDragon\0"
|
VALUE "ProductName", "@DRAGONX_APP_NAME@\0"
|
||||||
VALUE "ProductVersion", "@PROJECT_VERSION@\0"
|
VALUE "ProductVersion", "@DRAGONX_APP_VERSION@@DRAGONX_APP_VERSION_SUFFIX@\0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|||||||
BIN
res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf
Normal file
BIN
res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
res/fonts/NotoSansCJK-Subset.ttf
Normal file
BIN
res/fonts/NotoSansCJK-Subset.ttf
Normal file
Binary file not shown.
BIN
res/fonts/UbuntuMono-R.ttf
Normal file
BIN
res/fonts/UbuntuMono-R.ttf
Normal file
Binary file not shown.
183
res/lang/de.json
183
res/lang/de.json
@@ -43,6 +43,8 @@
|
|||||||
"address_url": "Adress-URL",
|
"address_url": "Adress-URL",
|
||||||
"addresses_appear_here": "Ihre Empfangsadressen erscheinen hier, sobald Sie verbunden sind.",
|
"addresses_appear_here": "Ihre Empfangsadressen erscheinen hier, sobald Sie verbunden sind.",
|
||||||
"advanced": "ERWEITERT",
|
"advanced": "ERWEITERT",
|
||||||
|
"advanced_effects": "Erweiterte Effekte...",
|
||||||
|
"ago": "her",
|
||||||
"all_filter": "Alle",
|
"all_filter": "Alle",
|
||||||
"allow_custom_fees": "Benutzerdefinierte Gebühren erlauben",
|
"allow_custom_fees": "Benutzerdefinierte Gebühren erlauben",
|
||||||
"amount": "Betrag",
|
"amount": "Betrag",
|
||||||
@@ -90,12 +92,52 @@
|
|||||||
"block_timestamp": "Zeitstempel:",
|
"block_timestamp": "Zeitstempel:",
|
||||||
"block_transactions": "Transaktionen:",
|
"block_transactions": "Transaktionen:",
|
||||||
"blockchain_syncing": "Blockchain synchronisiert (%.1f%%)... Guthaben könnten ungenau sein.",
|
"blockchain_syncing": "Blockchain synchronisiert (%.1f%%)... Guthaben könnten ungenau sein.",
|
||||||
|
"bootstrap_daemon_running": "Daemon läuft",
|
||||||
|
"bootstrap_daemon_stopped": "Daemon gestoppt",
|
||||||
|
"bootstrap_daemon_stopping": "Daemon wird gestoppt...",
|
||||||
|
"bootstrap_desc": "Laden Sie einen Blockchain-Bootstrap herunter, um die anfängliche Synchronisierung drastisch zu beschleunigen. Dies lädt einen Snapshot der Blockchain herunter und extrahiert ihn in Ihr Datenverzeichnis.",
|
||||||
|
"bootstrap_downloading": "Bootstrap wird heruntergeladen...",
|
||||||
|
"bootstrap_extracting": "Blockchain-Daten werden extrahiert...",
|
||||||
|
"bootstrap_failed": "Bootstrap fehlgeschlagen",
|
||||||
|
"bootstrap_mirror": "Spiegel",
|
||||||
|
"bootstrap_mirror_tooltip": "Vom Spiegel herunterladen (bootstrap2.dragonx.is).\nVerwenden Sie dies, wenn der Hauptdownload langsam ist oder fehlschlägt.",
|
||||||
|
"bootstrap_restart_daemon": "Daemon neu starten",
|
||||||
|
"bootstrap_success": "Bootstrap abgeschlossen",
|
||||||
|
"bootstrap_success_desc": "Blockchain-Daten wurden erfolgreich extrahiert. Starten Sie den Daemon, um ab dem Bootstrap-Punkt zu synchronisieren.",
|
||||||
|
"bootstrap_trust_warning": "Verwenden Sie nur bootstrap.dragonx.is oder bootstrap2.dragonx.is. Die Verwendung von Dateien aus nicht vertrauenswürdigen Quellen könnte Ihren Knoten gefährden.",
|
||||||
|
"bootstrap_verifying": "Prüfsummen werden überprüft...",
|
||||||
|
"bootstrap_wallet_protected": "(wallet.dat ist geschützt)",
|
||||||
|
"bootstrap_warning": "Vorhandene Blockdaten (blocks, chainstate, notarizations) werden gelöscht und ersetzt. Ihre wallet.dat wird NICHT verändert oder gelöscht.",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"characters": "Zeichen",
|
"characters": "Zeichen",
|
||||||
|
"chat": "Chat",
|
||||||
|
"chat_cancel": "Abbrechen",
|
||||||
|
"chat_contact_request": "kontaktanfrage",
|
||||||
|
"chat_empty_hint": "Noch keine Unterhaltungen. Nachrichten, die du erhältst, erscheinen hier.",
|
||||||
|
"chat_locked_hint": "Entsperre deine Wallet, um deine Chats zu laden.",
|
||||||
|
"chat_new_button": "Neue Unterhaltung",
|
||||||
|
"chat_new_message": "Nachricht",
|
||||||
|
"chat_new_send": "Anfrage senden",
|
||||||
|
"chat_new_title": "Neue Unterhaltung",
|
||||||
|
"chat_new_zaddr": "z-Adresse des Empfängers",
|
||||||
|
"chat_select_hint": "Wähle eine Unterhaltung aus, um sie anzuzeigen.",
|
||||||
|
"chat_send": "Senden",
|
||||||
|
"chat_send_failed": "nicht gesendet",
|
||||||
|
"chat_toast_compose_failed": "Nachricht konnte nicht erstellt werden (zu lang?).",
|
||||||
|
"chat_toast_lite_busy": "Es wird bereits gesendet, oder es ist keine Wallet geöffnet.",
|
||||||
|
"chat_toast_no_zaddr": "Keine z-Adresse verfügbar, um den Chat zu senden.",
|
||||||
|
"chat_toast_not_connected": "Nicht verbunden – Nachricht nicht gesendet.",
|
||||||
|
"chat_toast_request_compose_failed": "Kontaktanfrage konnte nicht erstellt werden (ungültige Adresse / ungültiger Text?).",
|
||||||
|
"chat_toast_request_queued": "Kontaktanfrage in Warteschlange.",
|
||||||
|
"chat_toast_waiting_reply": "Warte auf die Antwort des Kontakts, bevor du ihm schreiben kannst.",
|
||||||
|
"chat_waiting_reply": "Warte auf die Antwort dieses Kontakts – sobald er antwortet, kannst du ihm schreiben.",
|
||||||
|
"chat_you": "Du",
|
||||||
|
"choose_icon": "Symbol wählen",
|
||||||
"clear": "Leeren",
|
"clear": "Leeren",
|
||||||
"clear_all_bans": "Alle Sperren aufheben",
|
"clear_all_bans": "Alle Sperren aufheben",
|
||||||
"clear_anyway": "Trotzdem löschen",
|
"clear_anyway": "Trotzdem löschen",
|
||||||
"clear_form_confirm": "Alle Formularfelder leeren?",
|
"clear_form_confirm": "Alle Formularfelder leeren?",
|
||||||
|
"clear_icon": "Symbol entfernen",
|
||||||
"clear_request": "Anfrage leeren",
|
"clear_request": "Anfrage leeren",
|
||||||
"click_copy_address": "Klicken zum Kopieren der Adresse",
|
"click_copy_address": "Klicken zum Kopieren der Adresse",
|
||||||
"click_copy_uri": "Klicken zum Kopieren der URI",
|
"click_copy_uri": "Klicken zum Kopieren der URI",
|
||||||
@@ -106,8 +148,12 @@
|
|||||||
"confirm_clear_ztx_title": "Z-Tx-Verlauf löschen bestätigen",
|
"confirm_clear_ztx_title": "Z-Tx-Verlauf löschen bestätigen",
|
||||||
"confirm_clear_ztx_warning1": "Das Löschen des Z-Transaktionsverlaufs kann dazu führen, dass Ihr geschirmtes Guthaben als 0 angezeigt wird, bis ein Wallet-Rescan durchgeführt wird.",
|
"confirm_clear_ztx_warning1": "Das Löschen des Z-Transaktionsverlaufs kann dazu führen, dass Ihr geschirmtes Guthaben als 0 angezeigt wird, bis ein Wallet-Rescan durchgeführt wird.",
|
||||||
"confirm_clear_ztx_warning2": "Wenn dies geschieht, müssen Sie Ihre Z-Adresse-Privatschlüssel mit aktiviertem Rescan neu importieren, um Ihr Guthaben wiederherzustellen.",
|
"confirm_clear_ztx_warning2": "Wenn dies geschieht, müssen Sie Ihre Z-Adresse-Privatschlüssel mit aktiviertem Rescan neu importieren, um Ihr Guthaben wiederherzustellen.",
|
||||||
|
"confirm_delete_blockchain_msg": "Dies stoppt den Daemon, löscht alle Blockchain-Daten (blocks, chainstate, peers) und startet eine neue Synchronisierung. Dies kann mehrere Stunden dauern.",
|
||||||
|
"confirm_delete_blockchain_safe": "Ihre wallet.dat, Konfiguration und Transaktionshistorie sind sicher und werden nicht gelöscht.",
|
||||||
|
"confirm_delete_blockchain_title": "Blockchain-Daten löschen",
|
||||||
"confirm_send": "Senden bestätigen",
|
"confirm_send": "Senden bestätigen",
|
||||||
"confirm_transaction": "Transaktion bestätigen",
|
"confirm_transaction": "Transaktion bestätigen",
|
||||||
|
"confirm_transfer": "Überweisung bestätigen",
|
||||||
"confirmations": "Bestätigungen",
|
"confirmations": "Bestätigungen",
|
||||||
"confirmations_display": "%d Bestätigungen | %s",
|
"confirmations_display": "%d Bestätigungen | %s",
|
||||||
"confirmed": "Bestätigt",
|
"confirmed": "Bestätigt",
|
||||||
@@ -172,6 +218,7 @@
|
|||||||
"console_welcome": "Willkommen bei ObsidianDragon Konsole",
|
"console_welcome": "Willkommen bei ObsidianDragon Konsole",
|
||||||
"console_zoom_in": "Vergrößern",
|
"console_zoom_in": "Vergrößern",
|
||||||
"console_zoom_out": "Verkleinern",
|
"console_zoom_out": "Verkleinern",
|
||||||
|
"copied": "Kopiert!",
|
||||||
"copy": "Kopieren",
|
"copy": "Kopieren",
|
||||||
"copy_address": "Vollständige Adresse kopieren",
|
"copy_address": "Vollständige Adresse kopieren",
|
||||||
"copy_error": "Fehler kopieren",
|
"copy_error": "Fehler kopieren",
|
||||||
@@ -180,22 +227,45 @@
|
|||||||
"copy_uri": "URI kopieren",
|
"copy_uri": "URI kopieren",
|
||||||
"current_price": "Aktueller Preis",
|
"current_price": "Aktueller Preis",
|
||||||
"custom_fees": "Benutzerdefinierte Gebühren",
|
"custom_fees": "Benutzerdefinierte Gebühren",
|
||||||
|
"daemon_version": "Daemon",
|
||||||
"dark": "Dunkel",
|
"dark": "Dunkel",
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"date_label": "Datum:",
|
"date_label": "Datum:",
|
||||||
"debug_logging": "FEHLERPROTOKOLLIERUNG",
|
"debug_logging": "FEHLERPROTOKOLLIERUNG",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
|
"delete_blockchain": "Blockchain löschen",
|
||||||
|
"delete_blockchain_confirm": "Löschen & Neu synchronisieren",
|
||||||
|
"deshielding_warning": "Warnung: Dies wird Gelder von einer privaten (Z) Adresse auf eine transparente (T) Adresse ent-schirmen.",
|
||||||
"difficulty": "Schwierigkeit",
|
"difficulty": "Schwierigkeit",
|
||||||
"disconnected": "Getrennt",
|
"disconnected": "Getrennt",
|
||||||
"dismiss": "Verwerfen",
|
"dismiss": "Verwerfen",
|
||||||
"display": "Anzeige",
|
"display": "Anzeige",
|
||||||
|
"download": "Herunterladen",
|
||||||
|
"download_bootstrap": "Bootstrap herunterladen",
|
||||||
"dragonx_green": "DragonX (Grün)",
|
"dragonx_green": "DragonX (Grün)",
|
||||||
"edit": "Bearbeiten",
|
"edit": "Bearbeiten",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"error_format": "Fehler: %s",
|
"error_format": "Fehler: %s",
|
||||||
"est_time_to_block": "Gesch. Zeit bis Block",
|
"est_time_to_block": "Gesch. Zeit bis Block",
|
||||||
"exit": "Beenden",
|
"exit": "Beenden",
|
||||||
"explorer": "EXPLORER",
|
"explorer": "Explorer",
|
||||||
|
"explorer_block_detail": "Block",
|
||||||
|
"explorer_block_hash": "Hash",
|
||||||
|
"explorer_block_height": "Höhe",
|
||||||
|
"explorer_block_merkle": "Merkle-Wurzel",
|
||||||
|
"explorer_block_size": "Größe",
|
||||||
|
"explorer_block_time": "Zeit",
|
||||||
|
"explorer_block_txs": "Transaktionen",
|
||||||
|
"explorer_chain_stats": "Kette",
|
||||||
|
"explorer_invalid_query": "Geben Sie eine Blockhöhe oder einen 64-stelligen Hash ein",
|
||||||
|
"explorer_mempool": "Mempool",
|
||||||
|
"explorer_mempool_size": "Größe",
|
||||||
|
"explorer_mempool_txs": "Transaktionen",
|
||||||
|
"explorer_recent_blocks": "Letzte Blöcke",
|
||||||
|
"explorer_search": "Suchen",
|
||||||
|
"explorer_section": "EXPLORER",
|
||||||
|
"explorer_tx_outputs": "Ausgaben",
|
||||||
|
"explorer_tx_size": "Größe",
|
||||||
"export": "Exportieren",
|
"export": "Exportieren",
|
||||||
"export_csv": "CSV exportieren",
|
"export_csv": "CSV exportieren",
|
||||||
"export_keys_btn": "Schlüssel exportieren",
|
"export_keys_btn": "Schlüssel exportieren",
|
||||||
@@ -224,14 +294,22 @@
|
|||||||
"fetch_prices": "Preise abrufen",
|
"fetch_prices": "Preise abrufen",
|
||||||
"file": "Datei",
|
"file": "Datei",
|
||||||
"file_save_location": "Datei wird gespeichert in: ~/.config/ObsidianDragon/",
|
"file_save_location": "Datei wird gespeichert in: ~/.config/ObsidianDragon/",
|
||||||
|
"filter": "Filtern...",
|
||||||
"font_scale": "Schriftgröße",
|
"font_scale": "Schriftgröße",
|
||||||
|
"force_quit": "Sofort beenden",
|
||||||
|
"force_quit_confirm_msg": "Dies wird den Daemon sofort beenden ohne sauberes Herunterfahren.\nDies kann den Blockchain-Index beschädigen und eine Neusynchronisierung erfordern.",
|
||||||
|
"force_quit_confirm_title": "Sofort beenden?",
|
||||||
|
"force_quit_warning": "Dies wird den Daemon sofort beenden ohne sauberes Herunterfahren. Kann eine Neusynchronisierung der Blockchain erfordern.",
|
||||||
|
"force_quit_yes": "Sofort beenden",
|
||||||
"from": "Von",
|
"from": "Von",
|
||||||
"from_upper": "VON",
|
"from_upper": "VON",
|
||||||
"full_details": "Alle Details",
|
"full_details": "Alle Details",
|
||||||
"general": "Allgemein",
|
"general": "Allgemein",
|
||||||
|
"generating": "Wird generiert",
|
||||||
"go_to_receive": "Zum Empfangen",
|
"go_to_receive": "Zum Empfangen",
|
||||||
"height": "Höhe",
|
"height": "Höhe",
|
||||||
"help": "Hilfe",
|
"help": "Hilfe",
|
||||||
|
"hidden_tag": " (versteckt)",
|
||||||
"hide": "Ausblenden",
|
"hide": "Ausblenden",
|
||||||
"hide_address": "Adresse ausblenden",
|
"hide_address": "Adresse ausblenden",
|
||||||
"hide_zero_balances": "Nullsalden ausblenden",
|
"hide_zero_balances": "Nullsalden ausblenden",
|
||||||
@@ -253,6 +331,9 @@
|
|||||||
"import_key_warning": "Warnung: Teilen Sie niemals Ihre privaten Schlüssel! Das Importieren von Schlüsseln aus nicht vertrauenswürdigen Quellen kann Ihr Wallet gefährden.",
|
"import_key_warning": "Warnung: Teilen Sie niemals Ihre privaten Schlüssel! Das Importieren von Schlüsseln aus nicht vertrauenswürdigen Quellen kann Ihr Wallet gefährden.",
|
||||||
"import_key_z_format": "Z-Adresse Ausgabeschlüssel (secret-extended-key-...)",
|
"import_key_z_format": "Z-Adresse Ausgabeschlüssel (secret-extended-key-...)",
|
||||||
"import_private_key": "Privaten Schlüssel importieren...",
|
"import_private_key": "Privaten Schlüssel importieren...",
|
||||||
|
"incorrect_passphrase": "Falsches Passwort",
|
||||||
|
"incorrect_pin": "Falsche PIN",
|
||||||
|
"insufficient_funds": "Unzureichendes Guthaben für diesen Betrag plus Gebühr.",
|
||||||
"invalid_address": "Ungültiges Adressformat",
|
"invalid_address": "Ungültiges Adressformat",
|
||||||
"ip_address": "IP-Adresse",
|
"ip_address": "IP-Adresse",
|
||||||
"keep": "Behalten",
|
"keep": "Behalten",
|
||||||
@@ -266,6 +347,7 @@
|
|||||||
"key_export_viewing_keys_zonly": "Anzeigeschlüssel sind nur für geschirmte (z) Adressen verfügbar",
|
"key_export_viewing_keys_zonly": "Anzeigeschlüssel sind nur für geschirmte (z) Adressen verfügbar",
|
||||||
"key_export_viewing_warning": "Dieser Betrachtungsschlüssel ermöglicht es anderen, Ihre eingehenden Transaktionen und Ihr Guthaben zu sehen, aber NICHT Ihre Gelder auszugeben. Teilen Sie ihn nur mit vertrauenswürdigen Parteien.",
|
"key_export_viewing_warning": "Dieser Betrachtungsschlüssel ermöglicht es anderen, Ihre eingehenden Transaktionen und Ihr Guthaben zu sehen, aber NICHT Ihre Gelder auszugeben. Teilen Sie ihn nur mit vertrauenswürdigen Parteien.",
|
||||||
"label": "Bezeichnung:",
|
"label": "Bezeichnung:",
|
||||||
|
"label_placeholder": "z.B. Ersparnisse, Mining...",
|
||||||
"language": "Sprache",
|
"language": "Sprache",
|
||||||
"light": "Hell",
|
"light": "Hell",
|
||||||
"loading": "Laden...",
|
"loading": "Laden...",
|
||||||
@@ -286,6 +368,7 @@
|
|||||||
"market_now": "Jetzt",
|
"market_now": "Jetzt",
|
||||||
"market_pct_shielded": "%.0f%% Abgeschirmt",
|
"market_pct_shielded": "%.0f%% Abgeschirmt",
|
||||||
"market_portfolio": "PORTFOLIO",
|
"market_portfolio": "PORTFOLIO",
|
||||||
|
"market_price_loading": "Preisdaten werden geladen...",
|
||||||
"market_price_unavailable": "Preisdaten nicht verfügbar",
|
"market_price_unavailable": "Preisdaten nicht verfügbar",
|
||||||
"market_refresh_price": "Preisdaten aktualisieren",
|
"market_refresh_price": "Preisdaten aktualisieren",
|
||||||
"market_trade_on": "Handeln auf %s",
|
"market_trade_on": "Handeln auf %s",
|
||||||
@@ -311,6 +394,13 @@
|
|||||||
"mining_address_copied": "Mining-Adresse kopiert",
|
"mining_address_copied": "Mining-Adresse kopiert",
|
||||||
"mining_all_time": "Gesamt",
|
"mining_all_time": "Gesamt",
|
||||||
"mining_already_saved": "Pool-URL bereits gespeichert",
|
"mining_already_saved": "Pool-URL bereits gespeichert",
|
||||||
|
"mining_benchmark_cancel": "Benchmark abbrechen",
|
||||||
|
"mining_benchmark_cooling": "Abkühlen",
|
||||||
|
"mining_benchmark_dismiss": "Schließen",
|
||||||
|
"mining_benchmark_result": "Optimal",
|
||||||
|
"mining_benchmark_stabilizing": "Stabilisierung",
|
||||||
|
"mining_benchmark_testing": "Testen",
|
||||||
|
"mining_benchmark_tooltip": "Optimale Thread-Anzahl für diese CPU finden",
|
||||||
"mining_block_copied": "Block-Hash kopiert",
|
"mining_block_copied": "Block-Hash kopiert",
|
||||||
"mining_chart_1m_ago": "vor 1m",
|
"mining_chart_1m_ago": "vor 1m",
|
||||||
"mining_chart_5m_ago": "vor 5m",
|
"mining_chart_5m_ago": "vor 5m",
|
||||||
@@ -330,8 +420,15 @@
|
|||||||
"mining_filter_tip_all": "Alle Einnahmen anzeigen",
|
"mining_filter_tip_all": "Alle Einnahmen anzeigen",
|
||||||
"mining_filter_tip_pool": "Nur Pool-Einnahmen anzeigen",
|
"mining_filter_tip_pool": "Nur Pool-Einnahmen anzeigen",
|
||||||
"mining_filter_tip_solo": "Nur Solo-Einnahmen anzeigen",
|
"mining_filter_tip_solo": "Nur Solo-Einnahmen anzeigen",
|
||||||
|
"mining_generate_z_address_hint": "Generieren Sie eine Z-Adresse im Empfangen-Tab als Auszahlungsadresse",
|
||||||
|
"mining_idle_gpu_off_tooltip": "Uneingeschränkt: EIN\nNur Tastatur-/Mauseingabe bestimmt den Leerlauf\nKlicken für GPU-bewusste Erkennung",
|
||||||
|
"mining_idle_gpu_on_tooltip": "GPU-bewusst: EIN\nGPU-Aktivität (Video, Spiele) verhindert Leerlauf-Mining\nKlicken für uneingeschränkten Modus",
|
||||||
"mining_idle_off_tooltip": "Leerlauf-Mining aktivieren",
|
"mining_idle_off_tooltip": "Leerlauf-Mining aktivieren",
|
||||||
"mining_idle_on_tooltip": "Leerlauf-Mining deaktivieren",
|
"mining_idle_on_tooltip": "Leerlauf-Mining deaktivieren",
|
||||||
|
"mining_idle_scale_off_tooltip": "Start/Stopp-Modus: EIN\nKlicken zum Wechsel auf Thread-Skalierung",
|
||||||
|
"mining_idle_scale_on_tooltip": "Thread-Skalierung: EIN\nKlicken zum Wechsel auf Start/Stopp-Modus",
|
||||||
|
"mining_idle_threads_active_tooltip": "Threads bei Benutzeraktivität",
|
||||||
|
"mining_idle_threads_idle_tooltip": "Threads im Leerlauf",
|
||||||
"mining_local_hashrate": "Lokale Hashrate",
|
"mining_local_hashrate": "Lokale Hashrate",
|
||||||
"mining_mine": "Minen",
|
"mining_mine": "Minen",
|
||||||
"mining_mining_addr": "Mining-Adr.",
|
"mining_mining_addr": "Mining-Adr.",
|
||||||
@@ -388,6 +485,7 @@
|
|||||||
"no_addresses_available": "Keine Adressen verfügbar",
|
"no_addresses_available": "Keine Adressen verfügbar",
|
||||||
"no_addresses_match": "Keine Adressen passen zum Filter",
|
"no_addresses_match": "Keine Adressen passen zum Filter",
|
||||||
"no_addresses_with_balance": "Keine Adressen mit Guthaben",
|
"no_addresses_with_balance": "Keine Adressen mit Guthaben",
|
||||||
|
"no_addresses_yet": "Noch keine Adressen",
|
||||||
"no_matching": "Keine passenden Transaktionen",
|
"no_matching": "Keine passenden Transaktionen",
|
||||||
"no_recent_receives": "Keine kürzlichen Empfänge",
|
"no_recent_receives": "Keine kürzlichen Empfänge",
|
||||||
"no_recent_sends": "Keine kürzlichen Sendungen",
|
"no_recent_sends": "Keine kürzlichen Sendungen",
|
||||||
@@ -453,6 +551,7 @@
|
|||||||
"peers_upper": "PEERS",
|
"peers_upper": "PEERS",
|
||||||
"peers_version": "Version",
|
"peers_version": "Version",
|
||||||
"pending": "Ausstehend",
|
"pending": "Ausstehend",
|
||||||
|
"pin_not_set": "PIN nicht gesetzt. Verwenden Sie das Passwort zum Entsperren.",
|
||||||
"ping": "Ping",
|
"ping": "Ping",
|
||||||
"price_chart": "Preisdiagramm",
|
"price_chart": "Preisdiagramm",
|
||||||
"qr_code": "QR-Code",
|
"qr_code": "QR-Code",
|
||||||
@@ -473,7 +572,9 @@
|
|||||||
"recent_received": "KÜRZLICH EMPFANGEN",
|
"recent_received": "KÜRZLICH EMPFANGEN",
|
||||||
"recent_sends": "KÜRZLICH GESENDET",
|
"recent_sends": "KÜRZLICH GESENDET",
|
||||||
"recipient": "EMPFÄNGER",
|
"recipient": "EMPFÄNGER",
|
||||||
|
"recipient_balance": "Empfänger: %.8f → %.8f DRGX",
|
||||||
"recv_type": "Empf.",
|
"recv_type": "Empf.",
|
||||||
|
"reduce_motion": "Bewegung reduzieren",
|
||||||
"refresh": "Aktualisieren",
|
"refresh": "Aktualisieren",
|
||||||
"refresh_now": "Jetzt aktualisieren",
|
"refresh_now": "Jetzt aktualisieren",
|
||||||
"remove_favorite": "Favorit entfernen",
|
"remove_favorite": "Favorit entfernen",
|
||||||
@@ -493,7 +594,10 @@
|
|||||||
"request_uri_copied": "Zahlungs-URI in Zwischenablage kopiert",
|
"request_uri_copied": "Zahlungs-URI in Zwischenablage kopiert",
|
||||||
"rescan": "Neu scannen",
|
"rescan": "Neu scannen",
|
||||||
"reset_to_defaults": "Standardwerte zurücksetzen",
|
"reset_to_defaults": "Standardwerte zurücksetzen",
|
||||||
|
"restarting_after_encryption": "Daemon wird nach Verschlüsselung neu gestartet...",
|
||||||
"restore_address": "Adresse wiederherstellen",
|
"restore_address": "Adresse wiederherstellen",
|
||||||
|
"result_preview": "Ergebnisvorschau",
|
||||||
|
"retry": "Wiederholen",
|
||||||
"review_send": "Senden prüfen",
|
"review_send": "Senden prüfen",
|
||||||
"rpc_host": "RPC-Host",
|
"rpc_host": "RPC-Host",
|
||||||
"rpc_pass": "Passwort",
|
"rpc_pass": "Passwort",
|
||||||
@@ -502,8 +606,55 @@
|
|||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"save_settings": "Einstellungen speichern",
|
"save_settings": "Einstellungen speichern",
|
||||||
"save_z_transactions": "Z-Tx in Tx-Liste speichern",
|
"save_z_transactions": "Z-Tx in Tx-Liste speichern",
|
||||||
|
"sb_auth_failed": "Authentifizierung fehlgeschlagen — rpcuser/rpcpassword prüfen",
|
||||||
|
"sb_block": "Block: %d",
|
||||||
|
"sb_connecting_daemon": "Verbindung zu dragonxd...",
|
||||||
|
"sb_connecting_err": "Verbindung zum Daemon — %s",
|
||||||
|
"sb_connecting_external": "Verbindung zu externem Daemon...",
|
||||||
|
"sb_connecting_generic": "Verbindung zum Daemon...",
|
||||||
|
"sb_daemon_crashed": "Daemon ist %d mal abgestürzt",
|
||||||
|
"sb_daemon_not_found": "Daemon nicht gefunden",
|
||||||
|
"sb_dragonxd_running": "dragonxd läuft",
|
||||||
|
"sb_dragonxd_stopped": "dragonxd gestoppt",
|
||||||
|
"sb_dragonxd_stopping": "dragonxd wird gestoppt...",
|
||||||
|
"sb_extracting_sapling": "Sapling-Parameter werden extrahiert...",
|
||||||
|
"sb_importing_keys": "Schlüssel importieren",
|
||||||
|
"sb_loading_config": "Konfiguration laden...",
|
||||||
|
"sb_mining_hs": "%.1f H/s",
|
||||||
|
"sb_net_ghs": "Netz: %.2f GH/s",
|
||||||
|
"sb_net_hs": "Netz: %.1f H/s",
|
||||||
|
"sb_net_khs": "Netz: %.2f KH/s",
|
||||||
|
"sb_net_mhs": "Netz: %.2f MH/s",
|
||||||
|
"sb_no_conf": "DRAGONX.conf nicht gefunden",
|
||||||
|
"sb_peers": "Peers: %zu",
|
||||||
|
"sb_rescanning": "Neuscan",
|
||||||
|
"sb_rescanning_pct": "Neuscan %.0f%%",
|
||||||
|
"sb_restarting_daemon": "Daemon wird neu gestartet...",
|
||||||
|
"sb_sapling_failed": "Sapling-Parameter-Extraktion fehlgeschlagen.",
|
||||||
|
"sb_sapling_not_found": "Sapling-Parameter nicht gefunden.",
|
||||||
|
"sb_starting_daemon": "dragonxd wird gestartet...",
|
||||||
|
"sb_syncing_basic": "Synchronisierung %.1f%% (%d übrig)",
|
||||||
|
"sb_syncing_eta": "Synchronisierung %.1f%% (%d übrig, %.0f Blk/s, ~%s)",
|
||||||
|
"sb_waiting_config": "Warten auf Daemon-Konfiguration...",
|
||||||
|
"sb_waiting_daemon": "Warten auf dragonxd...",
|
||||||
|
"sb_waiting_daemon_err": "Warten auf dragonxd — %s",
|
||||||
|
"sb_warming_up": "Aufwärmen...",
|
||||||
"search_placeholder": "Suchen...",
|
"search_placeholder": "Suchen...",
|
||||||
"security": "SICHERHEIT",
|
"security": "SICHERHEIT",
|
||||||
|
"seed_backup_button": "Wiederherstellungsphrase",
|
||||||
|
"seed_backup_close": "Schließen",
|
||||||
|
"seed_backup_copy": "Kopieren",
|
||||||
|
"seed_backup_intro": "Diese Wörter sind die Master-Sicherung Ihrer Wallet. Wer sie besitzt, kontrolliert Ihre Gelder.",
|
||||||
|
"seed_backup_load_failed": "Die Wiederherstellungsphrase konnte nicht geladen werden.",
|
||||||
|
"seed_backup_loading": "Ihre Wiederherstellungsphrase wird geladen…",
|
||||||
|
"seed_backup_locked": "Entsperren Sie Ihre Wallet, um ihre Wiederherstellungsphrase anzuzeigen.",
|
||||||
|
"seed_backup_none": "Diese Wallet ist älter als Wiederherstellungsphrasen und hat daher keine 24-Wort-Phrase. Sichern Sie sie stattdessen mit „Sicherung...“ oder durch den Export Ihrer privaten Schlüssel.",
|
||||||
|
"seed_backup_reminder": "Ihre Wallet hat eine 24-Wort-Wiederherstellungsphrase. Sichern Sie sie jetzt unter Einstellungen → Node & Sicherheit.",
|
||||||
|
"seed_backup_save": "In Datei speichern…",
|
||||||
|
"seed_backup_save_failed": "Konnte nicht schreiben ",
|
||||||
|
"seed_backup_saved": "Gespeichert unter ",
|
||||||
|
"seed_backup_title": "Wiederherstellungsphrase sichern",
|
||||||
|
"seed_backup_warning": "Schreiben Sie sie der Reihe nach auf, bewahren Sie sie offline auf und teilen oder fotografieren Sie sie niemals. Wenn Sie sie verlieren, können Ihre Gelder nicht wiederhergestellt werden.",
|
||||||
"select_address": "Adresse auswählen...",
|
"select_address": "Adresse auswählen...",
|
||||||
"select_receiving_address": "Empfangsadresse auswählen...",
|
"select_receiving_address": "Empfangsadresse auswählen...",
|
||||||
"select_source_address": "Quelladresse auswählen...",
|
"select_source_address": "Quelladresse auswählen...",
|
||||||
@@ -553,12 +704,15 @@
|
|||||||
"send_valid_transparent": "Gültige transparente Adresse",
|
"send_valid_transparent": "Gültige transparente Adresse",
|
||||||
"send_wallet_empty": "Ihre Wallet ist leer",
|
"send_wallet_empty": "Ihre Wallet ist leer",
|
||||||
"send_yes_clear": "Ja, leeren",
|
"send_yes_clear": "Ja, leeren",
|
||||||
|
"sender_balance": "Absender: %.8f → %.8f DRGX",
|
||||||
"sending": "Transaktion wird gesendet",
|
"sending": "Transaktion wird gesendet",
|
||||||
"sending_from": "SENDEN VON",
|
"sending_from": "SENDEN VON",
|
||||||
|
"sends_full_balance_warning": "Dies sendet das gesamte Guthaben. Die Sendeadresse wird ein Nullguthaben haben.",
|
||||||
"sent": "gesendet",
|
"sent": "gesendet",
|
||||||
"sent_filter": "Gesendet",
|
"sent_filter": "Gesendet",
|
||||||
"sent_type": "Gesendet",
|
"sent_type": "Gesendet",
|
||||||
"sent_upper": "GESENDET",
|
"sent_upper": "GESENDET",
|
||||||
|
"set_label": "Label setzen...",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"settings_about_text": "Eine geschirmte Kryptowährungs-Wallet für DragonX (DRGX), erstellt mit Dear ImGui für ein leichtes, portables Erlebnis.",
|
"settings_about_text": "Eine geschirmte Kryptowährungs-Wallet für DragonX (DRGX), erstellt mit Dear ImGui für ein leichtes, portables Erlebnis.",
|
||||||
"settings_acrylic_level": "Acrylstufe:",
|
"settings_acrylic_level": "Acrylstufe:",
|
||||||
@@ -647,13 +801,17 @@
|
|||||||
"shield_utxo_limit": "UTXO-Limit:",
|
"shield_utxo_limit": "UTXO-Limit:",
|
||||||
"shield_wildcard_hint": "Verwenden Sie '*' um von allen transparenten Adressen abzuschirmen",
|
"shield_wildcard_hint": "Verwenden Sie '*' um von allen transparenten Adressen abzuschirmen",
|
||||||
"shielded": "Abgeschirmt",
|
"shielded": "Abgeschirmt",
|
||||||
|
"shielded_address": "Geschirmte Adresse",
|
||||||
"shielded_to": "ABGESCHIRMT AN",
|
"shielded_to": "ABGESCHIRMT AN",
|
||||||
"shielded_type": "Abgeschirmt",
|
"shielded_type": "Abgeschirmt",
|
||||||
|
"shielding_notice": "Hinweis: Dies wird Gelder von einer transparenten (T) Adresse auf eine private (Z) Adresse schirmen.",
|
||||||
"show": "Anzeigen",
|
"show": "Anzeigen",
|
||||||
"show_hidden": "Ausgeblendete anzeigen (%d)",
|
"show_hidden": "Ausgeblendete anzeigen (%d)",
|
||||||
"show_qr_code": "QR-Code anzeigen",
|
"show_qr_code": "QR-Code anzeigen",
|
||||||
"showing_transactions": "Zeige %dâ%d von %d Transaktionen (gesamt: %zu)",
|
"showing_transactions": "Zeige %d–%d von %d Transaktionen (gesamt: %zu)",
|
||||||
|
"showing_x_of_y": "%d von %d Adressen angezeigt",
|
||||||
"simple_background": "Einfacher Hintergrund",
|
"simple_background": "Einfacher Hintergrund",
|
||||||
|
"slider_off": "Aus",
|
||||||
"start_mining": "Mining starten",
|
"start_mining": "Mining starten",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"stop_external": "Externen Daemon stoppen",
|
"stop_external": "Externen Daemon stoppen",
|
||||||
@@ -662,17 +820,26 @@
|
|||||||
"success": "Erfolg",
|
"success": "Erfolg",
|
||||||
"summary": "Zusammenfassung",
|
"summary": "Zusammenfassung",
|
||||||
"syncing": "Synchronisiere...",
|
"syncing": "Synchronisiere...",
|
||||||
|
"t_address": "T-Adresse",
|
||||||
"t_addresses": "T-Adressen",
|
"t_addresses": "T-Adressen",
|
||||||
"test_connection": "Testen",
|
"test_connection": "Testen",
|
||||||
"theme": "Design",
|
"theme": "Design",
|
||||||
"theme_effects": "Design-Effekte",
|
"theme_effects": "Design-Effekte",
|
||||||
|
"theme_language": "THEMA & SPRACHE",
|
||||||
"time_days_ago": "vor %d Tagen",
|
"time_days_ago": "vor %d Tagen",
|
||||||
"time_hours_ago": "vor %d Stunden",
|
"time_hours_ago": "vor %d Stunden",
|
||||||
"time_minutes_ago": "vor %d Minuten",
|
"time_minutes_ago": "vor %d Minuten",
|
||||||
"time_seconds_ago": "vor %d Sekunden",
|
"time_seconds_ago": "vor %d Sekunden",
|
||||||
|
"timeout_15min": "15 Min",
|
||||||
|
"timeout_1hour": "1 Stunde",
|
||||||
|
"timeout_1min": "1 Min",
|
||||||
|
"timeout_30min": "30 Min",
|
||||||
|
"timeout_5min": "5 Min",
|
||||||
|
"timeout_off": "Aus",
|
||||||
"to": "An",
|
"to": "An",
|
||||||
"to_upper": "AN",
|
"to_upper": "AN",
|
||||||
"tools": "WERKZEUGE",
|
"tools": "WERKZEUGE",
|
||||||
|
"tools_actions": "Werkzeuge & Aktionen...",
|
||||||
"total": "Gesamt",
|
"total": "Gesamt",
|
||||||
"transaction_id": "TRANSAKTIONS-ID",
|
"transaction_id": "TRANSAKTIONS-ID",
|
||||||
"transaction_sent": "Transaktion erfolgreich gesendet",
|
"transaction_sent": "Transaktion erfolgreich gesendet",
|
||||||
@@ -680,7 +847,13 @@
|
|||||||
"transaction_url": "Transaktions-URL",
|
"transaction_url": "Transaktions-URL",
|
||||||
"transactions": "Transaktionen",
|
"transactions": "Transaktionen",
|
||||||
"transactions_upper": "TRANSAKTIONEN",
|
"transactions_upper": "TRANSAKTIONEN",
|
||||||
|
"transfer_failed": "Überweisung fehlgeschlagen",
|
||||||
|
"transfer_funds": "Geld überweisen",
|
||||||
|
"transfer_sent": "Überweisung gesendet",
|
||||||
|
"transfer_sent_desc": "Ihre Überweisung wurde an das Netzwerk gesendet.",
|
||||||
|
"transfer_to": "Überweisen an:",
|
||||||
"transparent": "Transparent",
|
"transparent": "Transparent",
|
||||||
|
"transparent_address": "Transparente Adresse",
|
||||||
"tt_addr_url": "Basis-URL zum Anzeigen von Adressen in einem Block-Explorer",
|
"tt_addr_url": "Basis-URL zum Anzeigen von Adressen in einem Block-Explorer",
|
||||||
"tt_address_book": "Gespeicherte Adressen für schnelles Senden verwalten",
|
"tt_address_book": "Gespeicherte Adressen für schnelles Senden verwalten",
|
||||||
"tt_auto_lock": "Wallet nach dieser Inaktivitätszeit sperren",
|
"tt_auto_lock": "Wallet nach dieser Inaktivitätszeit sperren",
|
||||||
@@ -695,6 +868,8 @@
|
|||||||
"tt_custom_theme": "Benutzerdefiniertes Theme aktiv",
|
"tt_custom_theme": "Benutzerdefiniertes Theme aktiv",
|
||||||
"tt_debug_collapse": "Debug-Protokollierungsoptionen einklappen",
|
"tt_debug_collapse": "Debug-Protokollierungsoptionen einklappen",
|
||||||
"tt_debug_expand": "Debug-Protokollierungsoptionen ausklappen",
|
"tt_debug_expand": "Debug-Protokollierungsoptionen ausklappen",
|
||||||
|
"tt_delete_blockchain": "Alle Blockchain-Daten löschen und neu synchronisieren. wallet.dat und Konfiguration bleiben erhalten.",
|
||||||
|
"tt_download_bootstrap": "Blockchain-Bootstrap herunterladen, um die Synchronisierung zu beschleunigen\nVorhandene Blockdaten werden ersetzt",
|
||||||
"tt_encrypt": "wallet.dat mit einer Passphrase verschlüsseln",
|
"tt_encrypt": "wallet.dat mit einer Passphrase verschlüsseln",
|
||||||
"tt_export_all": "Alle privaten Schlüssel in eine Datei exportieren",
|
"tt_export_all": "Alle privaten Schlüssel in eine Datei exportieren",
|
||||||
"tt_export_csv": "Transaktionsverlauf als CSV-Tabelle exportieren",
|
"tt_export_csv": "Transaktionsverlauf als CSV-Tabelle exportieren",
|
||||||
@@ -712,6 +887,7 @@
|
|||||||
"tt_mine_idle": "Mining automatisch starten, wenn das\\nSystem inaktiv ist (keine Tastatur-/Mauseingabe)",
|
"tt_mine_idle": "Mining automatisch starten, wenn das\\nSystem inaktiv ist (keine Tastatur-/Mauseingabe)",
|
||||||
"tt_noise": "Körnungstextur-Intensität (0%% = aus, 100%% = maximum)",
|
"tt_noise": "Körnungstextur-Intensität (0%% = aus, 100%% = maximum)",
|
||||||
"tt_open_dir": "Klicken, um im Dateimanager zu öffnen",
|
"tt_open_dir": "Klicken, um im Dateimanager zu öffnen",
|
||||||
|
"tt_reduce_motion": "Animierte Übergänge und Saldo-Lerp für Barrierefreiheit deaktivieren",
|
||||||
"tt_remove_encrypt": "Verschlüsselung entfernen und Wallet ungeschützt speichern",
|
"tt_remove_encrypt": "Verschlüsselung entfernen und Wallet ungeschützt speichern",
|
||||||
"tt_remove_pin": "PIN entfernen und Passphrase zum Entsperren erfordern",
|
"tt_remove_pin": "PIN entfernen und Passphrase zum Entsperren erfordern",
|
||||||
"tt_report_bug": "Ein Problem im Projekt-Tracker melden",
|
"tt_report_bug": "Ein Problem im Projekt-Tracker melden",
|
||||||
@@ -727,6 +903,7 @@
|
|||||||
"tt_save_ztx": "Z-Adresse-Transaktionsverlauf lokal für schnelleres Laden speichern",
|
"tt_save_ztx": "Z-Adresse-Transaktionsverlauf lokal für schnelleres Laden speichern",
|
||||||
"tt_scan_themes": "Nach neuen Themes suchen.\\nTheme-Ordner ablegen in:\\n%s",
|
"tt_scan_themes": "Nach neuen Themes suchen.\\nTheme-Ordner ablegen in:\\n%s",
|
||||||
"tt_scanline": "CRT-Scanlinieneffekt in der Konsole",
|
"tt_scanline": "CRT-Scanlinieneffekt in der Konsole",
|
||||||
|
"tt_seed_backup": "Die 24-Wort-Wiederherstellungsphrase Ihrer Wallet anzeigen und sichern",
|
||||||
"tt_set_pin": "Eine 4-8-stellige PIN für schnelles Entsperren festlegen",
|
"tt_set_pin": "Eine 4-8-stellige PIN für schnelles Entsperren festlegen",
|
||||||
"tt_shield_mining": "Transparente Mining-Belohnungen an eine geschirmte Adresse verschieben",
|
"tt_shield_mining": "Transparente Mining-Belohnungen an eine geschirmte Adresse verschieben",
|
||||||
"tt_simple_bg": "Einen einfachen Verlauf für den Hintergrund verwenden\\nHotkey: Ctrl+Up",
|
"tt_simple_bg": "Einen einfachen Verlauf für den Hintergrund verwenden\\nHotkey: Ctrl+Up",
|
||||||
@@ -789,7 +966,9 @@
|
|||||||
"warning_upper": "WARNUNG!",
|
"warning_upper": "WARNUNG!",
|
||||||
"website": "Webseite",
|
"website": "Webseite",
|
||||||
"window_opacity": "Fenster-Transparenz",
|
"window_opacity": "Fenster-Transparenz",
|
||||||
|
"wizard_daemon_start_failed": "Daemon-Start fehlgeschlagen — wird automatisch wiederholt",
|
||||||
"yes_clear": "Ja, leeren",
|
"yes_clear": "Ja, leeren",
|
||||||
"your_addresses": "Ihre Adressen",
|
"your_addresses": "Ihre Adressen",
|
||||||
|
"z_address": "Z-Adresse",
|
||||||
"z_addresses": "Z-Adressen"
|
"z_addresses": "Z-Adressen"
|
||||||
}
|
}
|
||||||
|
|||||||
183
res/lang/es.json
183
res/lang/es.json
@@ -43,6 +43,8 @@
|
|||||||
"address_url": "URL de Dirección",
|
"address_url": "URL de Dirección",
|
||||||
"addresses_appear_here": "Tus direcciones de recepción aparecerán aquí una vez conectado.",
|
"addresses_appear_here": "Tus direcciones de recepción aparecerán aquí una vez conectado.",
|
||||||
"advanced": "AVANZADO",
|
"advanced": "AVANZADO",
|
||||||
|
"advanced_effects": "Efectos Avanzados...",
|
||||||
|
"ago": "atrás",
|
||||||
"all_filter": "Todos",
|
"all_filter": "Todos",
|
||||||
"allow_custom_fees": "Permitir comisiones personalizadas",
|
"allow_custom_fees": "Permitir comisiones personalizadas",
|
||||||
"amount": "Cantidad",
|
"amount": "Cantidad",
|
||||||
@@ -90,12 +92,52 @@
|
|||||||
"block_timestamp": "Fecha y Hora:",
|
"block_timestamp": "Fecha y Hora:",
|
||||||
"block_transactions": "Transacciones:",
|
"block_transactions": "Transacciones:",
|
||||||
"blockchain_syncing": "Sincronizando blockchain (%.1f%%)... Los saldos pueden ser inexactos.",
|
"blockchain_syncing": "Sincronizando blockchain (%.1f%%)... Los saldos pueden ser inexactos.",
|
||||||
|
"bootstrap_daemon_running": "Daemon ejecutándose",
|
||||||
|
"bootstrap_daemon_stopped": "Daemon detenido",
|
||||||
|
"bootstrap_daemon_stopping": "Deteniendo daemon...",
|
||||||
|
"bootstrap_desc": "Descarga un bootstrap de la blockchain para acelerar drásticamente la sincronización inicial. Esto descarga una instantánea de la blockchain y la extrae en tu directorio de datos.",
|
||||||
|
"bootstrap_downloading": "Descargando bootstrap...",
|
||||||
|
"bootstrap_extracting": "Extrayendo datos de blockchain...",
|
||||||
|
"bootstrap_failed": "Error en Bootstrap",
|
||||||
|
"bootstrap_mirror": "Espejo",
|
||||||
|
"bootstrap_mirror_tooltip": "Descargar desde espejo (bootstrap2.dragonx.is).\nUsa esto si la descarga principal es lenta o falla.",
|
||||||
|
"bootstrap_restart_daemon": "Reiniciar Daemon",
|
||||||
|
"bootstrap_success": "Bootstrap Completado",
|
||||||
|
"bootstrap_success_desc": "Los datos de la blockchain se han extraído correctamente. Inicie el daemon para comenzar a sincronizar desde el punto del bootstrap.",
|
||||||
|
"bootstrap_trust_warning": "Solo use bootstrap.dragonx.is o bootstrap2.dragonx.is. Usar archivos de fuentes no confiables podría comprometer su nodo.",
|
||||||
|
"bootstrap_verifying": "Verificando sumas de comprobación...",
|
||||||
|
"bootstrap_wallet_protected": "(wallet.dat está protegido)",
|
||||||
|
"bootstrap_warning": "Los datos de bloques existentes (blocks, chainstate, notarizations) se eliminarán y reemplazarán. Su wallet.dat NO será modificado ni eliminado.",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"characters": "caracteres",
|
"characters": "caracteres",
|
||||||
|
"chat": "Chat",
|
||||||
|
"chat_cancel": "Cancelar",
|
||||||
|
"chat_contact_request": "solicitud de contacto",
|
||||||
|
"chat_empty_hint": "Aún no hay conversaciones. Los mensajes que recibas aparecerán aquí.",
|
||||||
|
"chat_locked_hint": "Desbloquea tu monedero para cargar tus chats.",
|
||||||
|
"chat_new_button": "Nueva conversación",
|
||||||
|
"chat_new_message": "Mensaje",
|
||||||
|
"chat_new_send": "Enviar solicitud",
|
||||||
|
"chat_new_title": "Nueva conversación",
|
||||||
|
"chat_new_zaddr": "Dirección z del destinatario",
|
||||||
|
"chat_select_hint": "Selecciona una conversación para verla.",
|
||||||
|
"chat_send": "Enviar",
|
||||||
|
"chat_send_failed": "no enviado",
|
||||||
|
"chat_toast_compose_failed": "No se pudo componer el mensaje (¿demasiado largo?).",
|
||||||
|
"chat_toast_lite_busy": "Ya hay un envío en curso, o no hay ningún monedero abierto.",
|
||||||
|
"chat_toast_no_zaddr": "No hay ninguna dirección z disponible desde la que enviar el chat.",
|
||||||
|
"chat_toast_not_connected": "Sin conexión: mensaje de chat no enviado.",
|
||||||
|
"chat_toast_request_compose_failed": "No se pudo componer la solicitud de contacto (¿dirección o texto no válidos?).",
|
||||||
|
"chat_toast_request_queued": "Solicitud de contacto en cola.",
|
||||||
|
"chat_toast_waiting_reply": "Espera a que este contacto responda antes de poder escribirle.",
|
||||||
|
"chat_waiting_reply": "Esperando a que este contacto responda: podrás escribirle una vez lo haga.",
|
||||||
|
"chat_you": "Tú",
|
||||||
|
"choose_icon": "Elegir Icono",
|
||||||
"clear": "Limpiar",
|
"clear": "Limpiar",
|
||||||
"clear_all_bans": "Limpiar Todos los Bloqueos",
|
"clear_all_bans": "Limpiar Todos los Bloqueos",
|
||||||
"clear_anyway": "Limpiar de todos modos",
|
"clear_anyway": "Limpiar de todos modos",
|
||||||
"clear_form_confirm": "¿Limpiar todos los campos del formulario?",
|
"clear_form_confirm": "¿Limpiar todos los campos del formulario?",
|
||||||
|
"clear_icon": "Borrar Icono",
|
||||||
"clear_request": "Limpiar Solicitud",
|
"clear_request": "Limpiar Solicitud",
|
||||||
"click_copy_address": "Clic para copiar dirección",
|
"click_copy_address": "Clic para copiar dirección",
|
||||||
"click_copy_uri": "Clic para copiar URI",
|
"click_copy_uri": "Clic para copiar URI",
|
||||||
@@ -106,8 +148,12 @@
|
|||||||
"confirm_clear_ztx_title": "Confirmar limpieza del historial Z-Tx",
|
"confirm_clear_ztx_title": "Confirmar limpieza del historial Z-Tx",
|
||||||
"confirm_clear_ztx_warning1": "Limpiar el historial de z-transacciones puede hacer que su saldo blindado se muestre como 0 hasta que se realice un reescaneo de la billetera.",
|
"confirm_clear_ztx_warning1": "Limpiar el historial de z-transacciones puede hacer que su saldo blindado se muestre como 0 hasta que se realice un reescaneo de la billetera.",
|
||||||
"confirm_clear_ztx_warning2": "Si esto sucede, deberá reimportar las claves privadas de su dirección z con el reescaneo habilitado para recuperar su saldo.",
|
"confirm_clear_ztx_warning2": "Si esto sucede, deberá reimportar las claves privadas de su dirección z con el reescaneo habilitado para recuperar su saldo.",
|
||||||
|
"confirm_delete_blockchain_msg": "Esto detendrá el daemon, eliminará todos los datos de la blockchain (blocks, chainstate, peers) y comenzará una nueva sincronización desde cero. Esto puede tardar varias horas.",
|
||||||
|
"confirm_delete_blockchain_safe": "Su wallet.dat, configuración e historial de transacciones están seguros y no se eliminarán.",
|
||||||
|
"confirm_delete_blockchain_title": "Eliminar Datos de Blockchain",
|
||||||
"confirm_send": "Confirmar Envío",
|
"confirm_send": "Confirmar Envío",
|
||||||
"confirm_transaction": "Confirmar Transacción",
|
"confirm_transaction": "Confirmar Transacción",
|
||||||
|
"confirm_transfer": "Confirmar Transferencia",
|
||||||
"confirmations": "Confirmaciones",
|
"confirmations": "Confirmaciones",
|
||||||
"confirmations_display": "%d confirmaciones | %s",
|
"confirmations_display": "%d confirmaciones | %s",
|
||||||
"confirmed": "Confirmada",
|
"confirmed": "Confirmada",
|
||||||
@@ -172,6 +218,7 @@
|
|||||||
"console_welcome": "Bienvenido a la Consola de ObsidianDragon",
|
"console_welcome": "Bienvenido a la Consola de ObsidianDragon",
|
||||||
"console_zoom_in": "Acercar",
|
"console_zoom_in": "Acercar",
|
||||||
"console_zoom_out": "Alejar",
|
"console_zoom_out": "Alejar",
|
||||||
|
"copied": "¡Copiado!",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copy_address": "Copiar Dirección Completa",
|
"copy_address": "Copiar Dirección Completa",
|
||||||
"copy_error": "Copiar Error",
|
"copy_error": "Copiar Error",
|
||||||
@@ -180,22 +227,45 @@
|
|||||||
"copy_uri": "Copiar URI",
|
"copy_uri": "Copiar URI",
|
||||||
"current_price": "Precio Actual",
|
"current_price": "Precio Actual",
|
||||||
"custom_fees": "Comisiones personalizadas",
|
"custom_fees": "Comisiones personalizadas",
|
||||||
|
"daemon_version": "Daemon",
|
||||||
"dark": "Oscuro",
|
"dark": "Oscuro",
|
||||||
"date": "Fecha",
|
"date": "Fecha",
|
||||||
"date_label": "Fecha:",
|
"date_label": "Fecha:",
|
||||||
"debug_logging": "REGISTRO DE DEPURACIÓN",
|
"debug_logging": "REGISTRO DE DEPURACIÓN",
|
||||||
"delete": "Eliminar",
|
"delete": "Eliminar",
|
||||||
|
"delete_blockchain": "Eliminar Blockchain",
|
||||||
|
"delete_blockchain_confirm": "Eliminar y Resincronizar",
|
||||||
|
"deshielding_warning": "Advertencia: Esto des-protegerá fondos de una dirección privada (Z) a una dirección transparente (T).",
|
||||||
"difficulty": "Dificultad",
|
"difficulty": "Dificultad",
|
||||||
"disconnected": "Desconectado",
|
"disconnected": "Desconectado",
|
||||||
"dismiss": "Descartar",
|
"dismiss": "Descartar",
|
||||||
"display": "Pantalla",
|
"display": "Pantalla",
|
||||||
|
"download": "Descargar",
|
||||||
|
"download_bootstrap": "Descargar Bootstrap",
|
||||||
"dragonx_green": "DragonX (Verde)",
|
"dragonx_green": "DragonX (Verde)",
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"error_format": "Error: %s",
|
"error_format": "Error: %s",
|
||||||
"est_time_to_block": "Tiempo Est. al Bloque",
|
"est_time_to_block": "Tiempo Est. al Bloque",
|
||||||
"exit": "Salir",
|
"exit": "Salir",
|
||||||
"explorer": "EXPLORADOR",
|
"explorer": "Explorador",
|
||||||
|
"explorer_block_detail": "Bloque",
|
||||||
|
"explorer_block_hash": "Hash",
|
||||||
|
"explorer_block_height": "Altura",
|
||||||
|
"explorer_block_merkle": "Raíz Merkle",
|
||||||
|
"explorer_block_size": "Tamaño",
|
||||||
|
"explorer_block_time": "Hora",
|
||||||
|
"explorer_block_txs": "Transacciones",
|
||||||
|
"explorer_chain_stats": "Cadena",
|
||||||
|
"explorer_invalid_query": "Ingrese una altura de bloque o un hash de 64 caracteres",
|
||||||
|
"explorer_mempool": "Mempool",
|
||||||
|
"explorer_mempool_size": "Tamaño",
|
||||||
|
"explorer_mempool_txs": "Transacciones",
|
||||||
|
"explorer_recent_blocks": "Bloques Recientes",
|
||||||
|
"explorer_search": "Buscar",
|
||||||
|
"explorer_section": "EXPLORADOR",
|
||||||
|
"explorer_tx_outputs": "Salidas",
|
||||||
|
"explorer_tx_size": "Tamaño",
|
||||||
"export": "Exportar",
|
"export": "Exportar",
|
||||||
"export_csv": "Exportar CSV",
|
"export_csv": "Exportar CSV",
|
||||||
"export_keys_btn": "Exportar Claves",
|
"export_keys_btn": "Exportar Claves",
|
||||||
@@ -224,14 +294,22 @@
|
|||||||
"fetch_prices": "Obtener precios",
|
"fetch_prices": "Obtener precios",
|
||||||
"file": "Archivo",
|
"file": "Archivo",
|
||||||
"file_save_location": "El archivo se guardará en: ~/.config/ObsidianDragon/",
|
"file_save_location": "El archivo se guardará en: ~/.config/ObsidianDragon/",
|
||||||
|
"filter": "Filtrar...",
|
||||||
"font_scale": "Escala de fuente",
|
"font_scale": "Escala de fuente",
|
||||||
|
"force_quit": "Forzar Salida",
|
||||||
|
"force_quit_confirm_msg": "Esto matará inmediatamente el daemon sin un apagado limpio.\nEsto puede corromper el índice de la blockchain y requerir una resincronización.",
|
||||||
|
"force_quit_confirm_title": "¿Forzar Salida?",
|
||||||
|
"force_quit_warning": "Esto matará inmediatamente el daemon sin un apagado limpio. Puede requerir una resincronización de la blockchain.",
|
||||||
|
"force_quit_yes": "Forzar Salida",
|
||||||
"from": "Desde",
|
"from": "Desde",
|
||||||
"from_upper": "DESDE",
|
"from_upper": "DESDE",
|
||||||
"full_details": "Detalles Completos",
|
"full_details": "Detalles Completos",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
|
"generating": "Generando",
|
||||||
"go_to_receive": "Ir a Recibir",
|
"go_to_receive": "Ir a Recibir",
|
||||||
"height": "Altura",
|
"height": "Altura",
|
||||||
"help": "Ayuda",
|
"help": "Ayuda",
|
||||||
|
"hidden_tag": " (oculto)",
|
||||||
"hide": "Ocultar",
|
"hide": "Ocultar",
|
||||||
"hide_address": "Ocultar dirección",
|
"hide_address": "Ocultar dirección",
|
||||||
"hide_zero_balances": "Ocultar saldos 0",
|
"hide_zero_balances": "Ocultar saldos 0",
|
||||||
@@ -253,6 +331,9 @@
|
|||||||
"import_key_warning": "Advertencia: ¡Nunca compartas tus claves privadas! Importar claves de fuentes no confiables puede comprometer tu cartera.",
|
"import_key_warning": "Advertencia: ¡Nunca compartas tus claves privadas! Importar claves de fuentes no confiables puede comprometer tu cartera.",
|
||||||
"import_key_z_format": "Claves de gasto de direcciones Z (secret-extended-key-...)",
|
"import_key_z_format": "Claves de gasto de direcciones Z (secret-extended-key-...)",
|
||||||
"import_private_key": "Importar Clave Privada...",
|
"import_private_key": "Importar Clave Privada...",
|
||||||
|
"incorrect_passphrase": "Contraseña incorrecta",
|
||||||
|
"incorrect_pin": "PIN incorrecto",
|
||||||
|
"insufficient_funds": "Fondos insuficientes para este monto más la comisión.",
|
||||||
"invalid_address": "Formato de dirección inválido",
|
"invalid_address": "Formato de dirección inválido",
|
||||||
"ip_address": "Dirección IP",
|
"ip_address": "Dirección IP",
|
||||||
"keep": "Mantener",
|
"keep": "Mantener",
|
||||||
@@ -266,6 +347,7 @@
|
|||||||
"key_export_viewing_keys_zonly": "Las claves de visualización solo están disponibles para direcciones blindadas (z)",
|
"key_export_viewing_keys_zonly": "Las claves de visualización solo están disponibles para direcciones blindadas (z)",
|
||||||
"key_export_viewing_warning": "Esta clave de vista permite a otros ver tus transacciones entrantes y saldo, pero NO gastar tus fondos. Comparte solo con personas de confianza.",
|
"key_export_viewing_warning": "Esta clave de vista permite a otros ver tus transacciones entrantes y saldo, pero NO gastar tus fondos. Comparte solo con personas de confianza.",
|
||||||
"label": "Etiqueta:",
|
"label": "Etiqueta:",
|
||||||
|
"label_placeholder": "ej. Ahorros, Minería...",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"light": "Claro",
|
"light": "Claro",
|
||||||
"loading": "Cargando...",
|
"loading": "Cargando...",
|
||||||
@@ -286,6 +368,7 @@
|
|||||||
"market_now": "Ahora",
|
"market_now": "Ahora",
|
||||||
"market_pct_shielded": "%.0f%% Protegido",
|
"market_pct_shielded": "%.0f%% Protegido",
|
||||||
"market_portfolio": "PORTAFOLIO",
|
"market_portfolio": "PORTAFOLIO",
|
||||||
|
"market_price_loading": "Cargando datos de precio...",
|
||||||
"market_price_unavailable": "Datos de precio no disponibles",
|
"market_price_unavailable": "Datos de precio no disponibles",
|
||||||
"market_refresh_price": "Actualizar datos de precio",
|
"market_refresh_price": "Actualizar datos de precio",
|
||||||
"market_trade_on": "Operar en %s",
|
"market_trade_on": "Operar en %s",
|
||||||
@@ -311,6 +394,13 @@
|
|||||||
"mining_address_copied": "Dirección de minería copiada",
|
"mining_address_copied": "Dirección de minería copiada",
|
||||||
"mining_all_time": "Todo el Tiempo",
|
"mining_all_time": "Todo el Tiempo",
|
||||||
"mining_already_saved": "URL del pool ya guardada",
|
"mining_already_saved": "URL del pool ya guardada",
|
||||||
|
"mining_benchmark_cancel": "Cancelar benchmark",
|
||||||
|
"mining_benchmark_cooling": "Enfriando",
|
||||||
|
"mining_benchmark_dismiss": "Cerrar",
|
||||||
|
"mining_benchmark_result": "Óptimo",
|
||||||
|
"mining_benchmark_stabilizing": "Estabilizando",
|
||||||
|
"mining_benchmark_testing": "Probando",
|
||||||
|
"mining_benchmark_tooltip": "Encontrar el número óptimo de hilos para esta CPU",
|
||||||
"mining_block_copied": "Hash de bloque copiado",
|
"mining_block_copied": "Hash de bloque copiado",
|
||||||
"mining_chart_1m_ago": "hace 1m",
|
"mining_chart_1m_ago": "hace 1m",
|
||||||
"mining_chart_5m_ago": "hace 5m",
|
"mining_chart_5m_ago": "hace 5m",
|
||||||
@@ -330,8 +420,15 @@
|
|||||||
"mining_filter_tip_all": "Mostrar todas las ganancias",
|
"mining_filter_tip_all": "Mostrar todas las ganancias",
|
||||||
"mining_filter_tip_pool": "Mostrar solo ganancias del pool",
|
"mining_filter_tip_pool": "Mostrar solo ganancias del pool",
|
||||||
"mining_filter_tip_solo": "Mostrar solo ganancias solo",
|
"mining_filter_tip_solo": "Mostrar solo ganancias solo",
|
||||||
|
"mining_generate_z_address_hint": "Genere una dirección Z en la pestaña Recibir para usarla como dirección de pago",
|
||||||
|
"mining_idle_gpu_off_tooltip": "Sin restricción: ACTIVADO\nSolo la entrada de teclado/ratón determina el estado inactivo\nClic para activar detección de GPU",
|
||||||
|
"mining_idle_gpu_on_tooltip": "GPU-consciente: ACTIVADO\nLa actividad de GPU (video, juegos) previene la minería inactiva\nClic para modo sin restricción",
|
||||||
"mining_idle_off_tooltip": "Activar minería en reposo",
|
"mining_idle_off_tooltip": "Activar minería en reposo",
|
||||||
"mining_idle_on_tooltip": "Desactivar minería en reposo",
|
"mining_idle_on_tooltip": "Desactivar minería en reposo",
|
||||||
|
"mining_idle_scale_off_tooltip": "Modo inicio/parada: ACTIVADO\nClic para cambiar al modo de escala de hilos",
|
||||||
|
"mining_idle_scale_on_tooltip": "Escala de hilos: ACTIVADO\nClic para cambiar al modo de inicio/parada",
|
||||||
|
"mining_idle_threads_active_tooltip": "Hilos cuando el usuario está activo",
|
||||||
|
"mining_idle_threads_idle_tooltip": "Hilos cuando el sistema está inactivo",
|
||||||
"mining_local_hashrate": "Hashrate Local",
|
"mining_local_hashrate": "Hashrate Local",
|
||||||
"mining_mine": "Minar",
|
"mining_mine": "Minar",
|
||||||
"mining_mining_addr": "Dir. Minería",
|
"mining_mining_addr": "Dir. Minería",
|
||||||
@@ -388,6 +485,7 @@
|
|||||||
"no_addresses_available": "No hay direcciones disponibles",
|
"no_addresses_available": "No hay direcciones disponibles",
|
||||||
"no_addresses_match": "No hay direcciones que coincidan con el filtro",
|
"no_addresses_match": "No hay direcciones que coincidan con el filtro",
|
||||||
"no_addresses_with_balance": "No hay direcciones con saldo",
|
"no_addresses_with_balance": "No hay direcciones con saldo",
|
||||||
|
"no_addresses_yet": "Aún no hay direcciones",
|
||||||
"no_matching": "No hay transacciones coincidentes",
|
"no_matching": "No hay transacciones coincidentes",
|
||||||
"no_recent_receives": "No hay recepciones recientes",
|
"no_recent_receives": "No hay recepciones recientes",
|
||||||
"no_recent_sends": "No hay envíos recientes",
|
"no_recent_sends": "No hay envíos recientes",
|
||||||
@@ -453,6 +551,7 @@
|
|||||||
"peers_upper": "NODOS",
|
"peers_upper": "NODOS",
|
||||||
"peers_version": "Versión",
|
"peers_version": "Versión",
|
||||||
"pending": "Pendiente",
|
"pending": "Pendiente",
|
||||||
|
"pin_not_set": "PIN no configurado. Use la contraseña para desbloquear.",
|
||||||
"ping": "Ping",
|
"ping": "Ping",
|
||||||
"price_chart": "Gráfico de Precios",
|
"price_chart": "Gráfico de Precios",
|
||||||
"qr_code": "Código QR",
|
"qr_code": "Código QR",
|
||||||
@@ -473,7 +572,9 @@
|
|||||||
"recent_received": "RECIBIDOS RECIENTES",
|
"recent_received": "RECIBIDOS RECIENTES",
|
||||||
"recent_sends": "ENVÍOS RECIENTES",
|
"recent_sends": "ENVÍOS RECIENTES",
|
||||||
"recipient": "DESTINATARIO",
|
"recipient": "DESTINATARIO",
|
||||||
|
"recipient_balance": "Destinatario: %.8f → %.8f DRGX",
|
||||||
"recv_type": "Recibido",
|
"recv_type": "Recibido",
|
||||||
|
"reduce_motion": "Reducir Movimiento",
|
||||||
"refresh": "Actualizar",
|
"refresh": "Actualizar",
|
||||||
"refresh_now": "Actualizar Ahora",
|
"refresh_now": "Actualizar Ahora",
|
||||||
"remove_favorite": "Quitar favorito",
|
"remove_favorite": "Quitar favorito",
|
||||||
@@ -493,7 +594,10 @@
|
|||||||
"request_uri_copied": "URI de pago copiada al portapapeles",
|
"request_uri_copied": "URI de pago copiada al portapapeles",
|
||||||
"rescan": "Re-escanear",
|
"rescan": "Re-escanear",
|
||||||
"reset_to_defaults": "Restablecer Valores",
|
"reset_to_defaults": "Restablecer Valores",
|
||||||
|
"restarting_after_encryption": "Reiniciando daemon después del cifrado...",
|
||||||
"restore_address": "Restaurar dirección",
|
"restore_address": "Restaurar dirección",
|
||||||
|
"result_preview": "Vista previa del resultado",
|
||||||
|
"retry": "Reintentar",
|
||||||
"review_send": "Revisar Envío",
|
"review_send": "Revisar Envío",
|
||||||
"rpc_host": "Host RPC",
|
"rpc_host": "Host RPC",
|
||||||
"rpc_pass": "Contraseña",
|
"rpc_pass": "Contraseña",
|
||||||
@@ -502,8 +606,55 @@
|
|||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
"save_settings": "Guardar Configuración",
|
"save_settings": "Guardar Configuración",
|
||||||
"save_z_transactions": "Guardar Z-tx en lista",
|
"save_z_transactions": "Guardar Z-tx en lista",
|
||||||
|
"sb_auth_failed": "Autenticación fallida — verifique rpcuser/rpcpassword",
|
||||||
|
"sb_block": "Bloque: %d",
|
||||||
|
"sb_connecting_daemon": "Conectando a dragonxd...",
|
||||||
|
"sb_connecting_err": "Conectando al daemon — %s",
|
||||||
|
"sb_connecting_external": "Conectando a daemon externo...",
|
||||||
|
"sb_connecting_generic": "Conectando al daemon...",
|
||||||
|
"sb_daemon_crashed": "El daemon se bloqueó %d veces",
|
||||||
|
"sb_daemon_not_found": "Daemon no encontrado",
|
||||||
|
"sb_dragonxd_running": "dragonxd ejecutándose",
|
||||||
|
"sb_dragonxd_stopped": "dragonxd detenido",
|
||||||
|
"sb_dragonxd_stopping": "Deteniendo dragonxd...",
|
||||||
|
"sb_extracting_sapling": "Extrayendo parámetros Sapling...",
|
||||||
|
"sb_importing_keys": "Importando claves",
|
||||||
|
"sb_loading_config": "Cargando configuración...",
|
||||||
|
"sb_mining_hs": "%.1f H/s",
|
||||||
|
"sb_net_ghs": "Red: %.2f GH/s",
|
||||||
|
"sb_net_hs": "Red: %.1f H/s",
|
||||||
|
"sb_net_khs": "Red: %.2f KH/s",
|
||||||
|
"sb_net_mhs": "Red: %.2f MH/s",
|
||||||
|
"sb_no_conf": "DRAGONX.conf no encontrado",
|
||||||
|
"sb_peers": "Pares: %zu",
|
||||||
|
"sb_rescanning": "Reescaneando",
|
||||||
|
"sb_rescanning_pct": "Reescaneando %.0f%%",
|
||||||
|
"sb_restarting_daemon": "Reiniciando daemon...",
|
||||||
|
"sb_sapling_failed": "Error al extraer parámetros Sapling.",
|
||||||
|
"sb_sapling_not_found": "Parámetros Sapling no encontrados.",
|
||||||
|
"sb_starting_daemon": "Iniciando dragonxd...",
|
||||||
|
"sb_syncing_basic": "Sincronizando %.1f%% (%d restantes)",
|
||||||
|
"sb_syncing_eta": "Sincronizando %.1f%% (%d restantes, %.0f blk/s, ~%s)",
|
||||||
|
"sb_waiting_config": "Esperando configuración del daemon...",
|
||||||
|
"sb_waiting_daemon": "Esperando a dragonxd...",
|
||||||
|
"sb_waiting_daemon_err": "Esperando a dragonxd — %s",
|
||||||
|
"sb_warming_up": "Calentando...",
|
||||||
"search_placeholder": "Buscar...",
|
"search_placeholder": "Buscar...",
|
||||||
"security": "SEGURIDAD",
|
"security": "SEGURIDAD",
|
||||||
|
"seed_backup_button": "Frase de recuperación",
|
||||||
|
"seed_backup_close": "Cerrar",
|
||||||
|
"seed_backup_copy": "Copiar",
|
||||||
|
"seed_backup_intro": "Estas palabras son el respaldo maestro de tu cartera. Cualquiera que las tenga controla tus fondos.",
|
||||||
|
"seed_backup_load_failed": "No se pudo cargar la frase de recuperación.",
|
||||||
|
"seed_backup_loading": "Cargando tu frase de recuperación…",
|
||||||
|
"seed_backup_locked": "Desbloquea tu cartera para revelar su frase de recuperación.",
|
||||||
|
"seed_backup_none": "Esta cartera es anterior a las frases de recuperación, por lo que no tiene una frase de 24 palabras. Respáldala con «Respaldo...» o exportando tus claves privadas en su lugar.",
|
||||||
|
"seed_backup_reminder": "Tu cartera tiene una frase de recuperación de 24 palabras. Respáldala ahora en Configuración → Nodo y Seguridad.",
|
||||||
|
"seed_backup_save": "Guardar en archivo…",
|
||||||
|
"seed_backup_save_failed": "No se pudo escribir ",
|
||||||
|
"seed_backup_saved": "Guardado en ",
|
||||||
|
"seed_backup_title": "Respaldar frase de recuperación",
|
||||||
|
"seed_backup_warning": "Anótalas en orden, guárdalas sin conexión y nunca las compartas ni las fotografíes. Si las pierdes, tus fondos no se podrán recuperar.",
|
||||||
"select_address": "Seleccionar dirección...",
|
"select_address": "Seleccionar dirección...",
|
||||||
"select_receiving_address": "Seleccionar dirección de recepción...",
|
"select_receiving_address": "Seleccionar dirección de recepción...",
|
||||||
"select_source_address": "Seleccionar dirección de origen...",
|
"select_source_address": "Seleccionar dirección de origen...",
|
||||||
@@ -553,13 +704,16 @@
|
|||||||
"send_valid_transparent": "Dirección transparente válida",
|
"send_valid_transparent": "Dirección transparente válida",
|
||||||
"send_wallet_empty": "Tu cartera está vacía",
|
"send_wallet_empty": "Tu cartera está vacía",
|
||||||
"send_yes_clear": "Sí, Limpiar",
|
"send_yes_clear": "Sí, Limpiar",
|
||||||
|
"sender_balance": "Remitente: %.8f → %.8f DRGX",
|
||||||
"sending": "Enviando transacción",
|
"sending": "Enviando transacción",
|
||||||
"sending_from": "ENVIANDO DESDE",
|
"sending_from": "ENVIANDO DESDE",
|
||||||
|
"sends_full_balance_warning": "Esto envía el saldo completo. La dirección de envío tendrá saldo cero.",
|
||||||
"sent": "enviado",
|
"sent": "enviado",
|
||||||
"sent_filter": "Enviado",
|
"sent_filter": "Enviado",
|
||||||
"sent_type": "Enviado",
|
"sent_type": "Enviado",
|
||||||
"sent_upper": "ENVIADO",
|
"sent_upper": "ENVIADO",
|
||||||
"settings": "Configuración",
|
"set_label": "Establecer Etiqueta...",
|
||||||
|
"settings": "Ajustes",
|
||||||
"settings_about_text": "Una billetera de criptomonedas blindada para DragonX (DRGX), creada con Dear ImGui para una experiencia ligera y portátil.",
|
"settings_about_text": "Una billetera de criptomonedas blindada para DragonX (DRGX), creada con Dear ImGui para una experiencia ligera y portátil.",
|
||||||
"settings_acrylic_level": "Nivel de acrílico:",
|
"settings_acrylic_level": "Nivel de acrílico:",
|
||||||
"settings_address_book": "Libreta de direcciones...",
|
"settings_address_book": "Libreta de direcciones...",
|
||||||
@@ -647,13 +801,17 @@
|
|||||||
"shield_utxo_limit": "Límite UTXO:",
|
"shield_utxo_limit": "Límite UTXO:",
|
||||||
"shield_wildcard_hint": "Usa '*' para proteger desde todas las direcciones transparentes",
|
"shield_wildcard_hint": "Usa '*' para proteger desde todas las direcciones transparentes",
|
||||||
"shielded": "Blindada",
|
"shielded": "Blindada",
|
||||||
|
"shielded_address": "Dirección Protegida",
|
||||||
"shielded_to": "PROTEGIDA PARA",
|
"shielded_to": "PROTEGIDA PARA",
|
||||||
"shielded_type": "Protegido",
|
"shielded_type": "Protegido",
|
||||||
|
"shielding_notice": "Nota: Esto blindará fondos de una dirección transparente (T) a una dirección privada (Z).",
|
||||||
"show": "Mostrar",
|
"show": "Mostrar",
|
||||||
"show_hidden": "Mostrar ocultos (%d)",
|
"show_hidden": "Mostrar ocultos (%d)",
|
||||||
"show_qr_code": "Mostrar Código QR",
|
"show_qr_code": "Mostrar Código QR",
|
||||||
"showing_transactions": "Mostrando %d–%d de %d transacciones (total: %zu)",
|
"showing_transactions": "Mostrando %d–%d de %d transacciones (total: %zu)",
|
||||||
|
"showing_x_of_y": "Mostrando %d de %d direcciones",
|
||||||
"simple_background": "Fondo simple",
|
"simple_background": "Fondo simple",
|
||||||
|
"slider_off": "Apagado",
|
||||||
"start_mining": "Iniciar Minería",
|
"start_mining": "Iniciar Minería",
|
||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"stop_external": "Detener daemon externo",
|
"stop_external": "Detener daemon externo",
|
||||||
@@ -662,17 +820,26 @@
|
|||||||
"success": "Éxito",
|
"success": "Éxito",
|
||||||
"summary": "Resumen",
|
"summary": "Resumen",
|
||||||
"syncing": "Sincronizando...",
|
"syncing": "Sincronizando...",
|
||||||
|
"t_address": "Dirección T",
|
||||||
"t_addresses": "Direcciones T",
|
"t_addresses": "Direcciones T",
|
||||||
"test_connection": "Probar",
|
"test_connection": "Probar",
|
||||||
"theme": "Tema",
|
"theme": "Tema",
|
||||||
"theme_effects": "Efectos de tema",
|
"theme_effects": "Efectos de tema",
|
||||||
|
"theme_language": "TEMA E IDIOMA",
|
||||||
"time_days_ago": "hace %d días",
|
"time_days_ago": "hace %d días",
|
||||||
"time_hours_ago": "hace %d horas",
|
"time_hours_ago": "hace %d horas",
|
||||||
"time_minutes_ago": "hace %d minutos",
|
"time_minutes_ago": "hace %d minutos",
|
||||||
"time_seconds_ago": "hace %d segundos",
|
"time_seconds_ago": "hace %d segundos",
|
||||||
|
"timeout_15min": "15 min",
|
||||||
|
"timeout_1hour": "1 hora",
|
||||||
|
"timeout_1min": "1 min",
|
||||||
|
"timeout_30min": "30 min",
|
||||||
|
"timeout_5min": "5 min",
|
||||||
|
"timeout_off": "Apagado",
|
||||||
"to": "Para",
|
"to": "Para",
|
||||||
"to_upper": "PARA",
|
"to_upper": "PARA",
|
||||||
"tools": "HERRAMIENTAS",
|
"tools": "HERRAMIENTAS",
|
||||||
|
"tools_actions": "Herramientas y Acciones...",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"transaction_id": "ID DE TRANSACCIÓN",
|
"transaction_id": "ID DE TRANSACCIÓN",
|
||||||
"transaction_sent": "Transacción enviada exitosamente",
|
"transaction_sent": "Transacción enviada exitosamente",
|
||||||
@@ -680,7 +847,13 @@
|
|||||||
"transaction_url": "URL de Transacción",
|
"transaction_url": "URL de Transacción",
|
||||||
"transactions": "Transacciones",
|
"transactions": "Transacciones",
|
||||||
"transactions_upper": "TRANSACCIONES",
|
"transactions_upper": "TRANSACCIONES",
|
||||||
|
"transfer_failed": "Transferencia Fallida",
|
||||||
|
"transfer_funds": "Transferir Fondos",
|
||||||
|
"transfer_sent": "Transferencia Enviada",
|
||||||
|
"transfer_sent_desc": "Su transferencia ha sido enviada a la red.",
|
||||||
|
"transfer_to": "Transferir a:",
|
||||||
"transparent": "Transparente",
|
"transparent": "Transparente",
|
||||||
|
"transparent_address": "Dirección Transparente",
|
||||||
"tt_addr_url": "URL base para ver direcciones en un explorador de bloques",
|
"tt_addr_url": "URL base para ver direcciones en un explorador de bloques",
|
||||||
"tt_address_book": "Administrar direcciones guardadas para envío rápido",
|
"tt_address_book": "Administrar direcciones guardadas para envío rápido",
|
||||||
"tt_auto_lock": "Bloquear billetera después de este tiempo de inactividad",
|
"tt_auto_lock": "Bloquear billetera después de este tiempo de inactividad",
|
||||||
@@ -695,6 +868,8 @@
|
|||||||
"tt_custom_theme": "Tema personalizado activo",
|
"tt_custom_theme": "Tema personalizado activo",
|
||||||
"tt_debug_collapse": "Colapsar opciones de registro de depuración",
|
"tt_debug_collapse": "Colapsar opciones de registro de depuración",
|
||||||
"tt_debug_expand": "Expandir opciones de registro de depuración",
|
"tt_debug_expand": "Expandir opciones de registro de depuración",
|
||||||
|
"tt_delete_blockchain": "Eliminar todos los datos de la blockchain e iniciar una nueva sincronización. Se preservan wallet.dat y la configuración.",
|
||||||
|
"tt_download_bootstrap": "Descargar bootstrap de blockchain para acelerar la sincronización\nLos datos de bloques existentes serán reemplazados",
|
||||||
"tt_encrypt": "Cifrar wallet.dat con una contraseña",
|
"tt_encrypt": "Cifrar wallet.dat con una contraseña",
|
||||||
"tt_export_all": "Exportar todas las claves privadas a un archivo",
|
"tt_export_all": "Exportar todas las claves privadas a un archivo",
|
||||||
"tt_export_csv": "Exportar historial de transacciones como hoja de cálculo CSV",
|
"tt_export_csv": "Exportar historial de transacciones como hoja de cálculo CSV",
|
||||||
@@ -712,6 +887,7 @@
|
|||||||
"tt_mine_idle": "Iniciar minería automáticamente cuando el\\nsistema esté inactivo (sin entrada de teclado/ratón)",
|
"tt_mine_idle": "Iniciar minería automáticamente cuando el\\nsistema esté inactivo (sin entrada de teclado/ratón)",
|
||||||
"tt_noise": "Intensidad de textura granulada (0%% = apagado, 100%% = máximo)",
|
"tt_noise": "Intensidad de textura granulada (0%% = apagado, 100%% = máximo)",
|
||||||
"tt_open_dir": "Clic para abrir en explorador de archivos",
|
"tt_open_dir": "Clic para abrir en explorador de archivos",
|
||||||
|
"tt_reduce_motion": "Desactivar transiciones animadas y lerp de saldo para accesibilidad",
|
||||||
"tt_remove_encrypt": "Quitar cifrado y almacenar la billetera sin protección",
|
"tt_remove_encrypt": "Quitar cifrado y almacenar la billetera sin protección",
|
||||||
"tt_remove_pin": "Quitar PIN y requerir contraseña para desbloquear",
|
"tt_remove_pin": "Quitar PIN y requerir contraseña para desbloquear",
|
||||||
"tt_report_bug": "Reportar un problema en el rastreador del proyecto",
|
"tt_report_bug": "Reportar un problema en el rastreador del proyecto",
|
||||||
@@ -727,6 +903,7 @@
|
|||||||
"tt_save_ztx": "Almacenar historial de transacciones de z-address localmente para carga más rápida",
|
"tt_save_ztx": "Almacenar historial de transacciones de z-address localmente para carga más rápida",
|
||||||
"tt_scan_themes": "Buscar nuevos temas.\\nColoque carpetas de temas en:\\n%s",
|
"tt_scan_themes": "Buscar nuevos temas.\\nColoque carpetas de temas en:\\n%s",
|
||||||
"tt_scanline": "Efecto de líneas de escaneo CRT en la consola",
|
"tt_scanline": "Efecto de líneas de escaneo CRT en la consola",
|
||||||
|
"tt_seed_backup": "Muestra y respalda la frase de recuperación de 24 palabras de tu cartera",
|
||||||
"tt_set_pin": "Establecer un PIN de 4-8 dígitos para desbloqueo rápido",
|
"tt_set_pin": "Establecer un PIN de 4-8 dígitos para desbloqueo rápido",
|
||||||
"tt_shield_mining": "Mover recompensas de minería transparentes a una dirección blindada",
|
"tt_shield_mining": "Mover recompensas de minería transparentes a una dirección blindada",
|
||||||
"tt_simple_bg": "Usar un gradiente simple para el fondo\\nAtajo: Ctrl+Up",
|
"tt_simple_bg": "Usar un gradiente simple para el fondo\\nAtajo: Ctrl+Up",
|
||||||
@@ -789,7 +966,9 @@
|
|||||||
"warning_upper": "¡ADVERTENCIA!",
|
"warning_upper": "¡ADVERTENCIA!",
|
||||||
"website": "Sitio Web",
|
"website": "Sitio Web",
|
||||||
"window_opacity": "Opacidad de ventana",
|
"window_opacity": "Opacidad de ventana",
|
||||||
|
"wizard_daemon_start_failed": "Error al iniciar el daemon — se reintentará automáticamente",
|
||||||
"yes_clear": "Sí, Limpiar",
|
"yes_clear": "Sí, Limpiar",
|
||||||
"your_addresses": "Sus Direcciones",
|
"your_addresses": "Sus Direcciones",
|
||||||
|
"z_address": "Dirección Z",
|
||||||
"z_addresses": "Direcciones Z"
|
"z_addresses": "Direcciones Z"
|
||||||
}
|
}
|
||||||
|
|||||||
183
res/lang/fr.json
183
res/lang/fr.json
@@ -43,6 +43,8 @@
|
|||||||
"address_url": "URL de l'adresse",
|
"address_url": "URL de l'adresse",
|
||||||
"addresses_appear_here": "Vos adresses de réception apparaîtront ici une fois connecté.",
|
"addresses_appear_here": "Vos adresses de réception apparaîtront ici une fois connecté.",
|
||||||
"advanced": "AVANCÉ",
|
"advanced": "AVANCÉ",
|
||||||
|
"advanced_effects": "Effets avancés...",
|
||||||
|
"ago": "passé",
|
||||||
"all_filter": "Tout",
|
"all_filter": "Tout",
|
||||||
"allow_custom_fees": "Autoriser les frais personnalisés",
|
"allow_custom_fees": "Autoriser les frais personnalisés",
|
||||||
"amount": "Montant",
|
"amount": "Montant",
|
||||||
@@ -90,12 +92,52 @@
|
|||||||
"block_timestamp": "Horodatage :",
|
"block_timestamp": "Horodatage :",
|
||||||
"block_transactions": "Transactions :",
|
"block_transactions": "Transactions :",
|
||||||
"blockchain_syncing": "Synchronisation de la blockchain (%.1f%%)... Les soldes peuvent être inexacts.",
|
"blockchain_syncing": "Synchronisation de la blockchain (%.1f%%)... Les soldes peuvent être inexacts.",
|
||||||
|
"bootstrap_daemon_running": "Daemon en cours",
|
||||||
|
"bootstrap_daemon_stopped": "Daemon arrêté",
|
||||||
|
"bootstrap_daemon_stopping": "Arrêt du daemon...",
|
||||||
|
"bootstrap_desc": "Téléchargez un bootstrap de la blockchain pour accélérer considérablement la synchronisation initiale. Cela télécharge un instantané de la blockchain et l'extrait dans votre répertoire de données.",
|
||||||
|
"bootstrap_downloading": "Téléchargement du bootstrap...",
|
||||||
|
"bootstrap_extracting": "Extraction des données blockchain...",
|
||||||
|
"bootstrap_failed": "Échec du Bootstrap",
|
||||||
|
"bootstrap_mirror": "Miroir",
|
||||||
|
"bootstrap_mirror_tooltip": "Télécharger depuis le miroir (bootstrap2.dragonx.is).\nUtilisez ceci si le téléchargement principal est lent ou échoue.",
|
||||||
|
"bootstrap_restart_daemon": "Redémarrer le Daemon",
|
||||||
|
"bootstrap_success": "Bootstrap terminé",
|
||||||
|
"bootstrap_success_desc": "Les données de la blockchain ont été extraites avec succès. Démarrez le daemon pour commencer la synchronisation à partir du point de bootstrap.",
|
||||||
|
"bootstrap_trust_warning": "N'utilisez que bootstrap.dragonx.is ou bootstrap2.dragonx.is. L'utilisation de fichiers provenant de sources non fiables pourrait compromettre votre nœud.",
|
||||||
|
"bootstrap_verifying": "Vérification des sommes de contrôle...",
|
||||||
|
"bootstrap_wallet_protected": "(wallet.dat est protégé)",
|
||||||
|
"bootstrap_warning": "Les données de blocs existantes (blocks, chainstate, notarizations) seront supprimées et remplacées. Votre wallet.dat ne sera PAS modifié ni supprimé.",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"characters": "caractères",
|
"characters": "caractères",
|
||||||
|
"chat": "Discussion",
|
||||||
|
"chat_cancel": "Annuler",
|
||||||
|
"chat_contact_request": "demande de contact",
|
||||||
|
"chat_empty_hint": "Aucune conversation pour l'instant. Les messages que vous recevez apparaîtront ici.",
|
||||||
|
"chat_locked_hint": "Déverrouillez votre portefeuille pour charger vos discussions.",
|
||||||
|
"chat_new_button": "Nouvelle conversation",
|
||||||
|
"chat_new_message": "Message",
|
||||||
|
"chat_new_send": "Envoyer la demande",
|
||||||
|
"chat_new_title": "Nouvelle conversation",
|
||||||
|
"chat_new_zaddr": "Adresse Z du destinataire",
|
||||||
|
"chat_select_hint": "Sélectionnez une conversation pour l'afficher.",
|
||||||
|
"chat_send": "Envoyer",
|
||||||
|
"chat_send_failed": "non envoyé",
|
||||||
|
"chat_toast_compose_failed": "Impossible de composer le message (trop long ?).",
|
||||||
|
"chat_toast_lite_busy": "Un envoi est déjà en cours, ou aucun portefeuille n'est ouvert.",
|
||||||
|
"chat_toast_no_zaddr": "Aucune adresse Z disponible pour envoyer le message.",
|
||||||
|
"chat_toast_not_connected": "Non connecté — message non envoyé.",
|
||||||
|
"chat_toast_request_compose_failed": "Impossible de composer la demande de contact (adresse / texte invalide ?).",
|
||||||
|
"chat_toast_request_queued": "Demande de contact mise en file d'attente.",
|
||||||
|
"chat_toast_waiting_reply": "En attente de la réponse de ce contact avant de pouvoir lui écrire.",
|
||||||
|
"chat_waiting_reply": "En attente de la réponse de ce contact — vous pourrez lui écrire dès qu'il aura répondu.",
|
||||||
|
"chat_you": "Vous",
|
||||||
|
"choose_icon": "Choisir une icône",
|
||||||
"clear": "Effacer",
|
"clear": "Effacer",
|
||||||
"clear_all_bans": "Lever tous les bannissements",
|
"clear_all_bans": "Lever tous les bannissements",
|
||||||
"clear_anyway": "Effacer quand même",
|
"clear_anyway": "Effacer quand même",
|
||||||
"clear_form_confirm": "Effacer tous les champs du formulaire ?",
|
"clear_form_confirm": "Effacer tous les champs du formulaire ?",
|
||||||
|
"clear_icon": "Effacer l'icône",
|
||||||
"clear_request": "Effacer la demande",
|
"clear_request": "Effacer la demande",
|
||||||
"click_copy_address": "Cliquez pour copier l'adresse",
|
"click_copy_address": "Cliquez pour copier l'adresse",
|
||||||
"click_copy_uri": "Cliquez pour copier l'URI",
|
"click_copy_uri": "Cliquez pour copier l'URI",
|
||||||
@@ -106,8 +148,12 @@
|
|||||||
"confirm_clear_ztx_title": "Confirmer l'effacement de l'historique Z-Tx",
|
"confirm_clear_ztx_title": "Confirmer l'effacement de l'historique Z-Tx",
|
||||||
"confirm_clear_ztx_warning1": "L'effacement de l'historique des z-transactions peut faire apparaître votre solde blindé à 0 jusqu'à ce qu'un rescan du portefeuille soit effectué.",
|
"confirm_clear_ztx_warning1": "L'effacement de l'historique des z-transactions peut faire apparaître votre solde blindé à 0 jusqu'à ce qu'un rescan du portefeuille soit effectué.",
|
||||||
"confirm_clear_ztx_warning2": "Si cela se produit, vous devrez réimporter les clés privées de votre adresse z avec le rescan activé pour récupérer votre solde.",
|
"confirm_clear_ztx_warning2": "Si cela se produit, vous devrez réimporter les clés privées de votre adresse z avec le rescan activé pour récupérer votre solde.",
|
||||||
|
"confirm_delete_blockchain_msg": "Cela arrêtera le daemon, supprimera toutes les données de la blockchain (blocks, chainstate, peers) et démarrera une nouvelle synchronisation. Cela peut prendre plusieurs heures.",
|
||||||
|
"confirm_delete_blockchain_safe": "Votre wallet.dat, votre configuration et votre historique de transactions sont en sécurité et ne seront pas supprimés.",
|
||||||
|
"confirm_delete_blockchain_title": "Supprimer les données Blockchain",
|
||||||
"confirm_send": "Confirmer l'envoi",
|
"confirm_send": "Confirmer l'envoi",
|
||||||
"confirm_transaction": "Confirmer la transaction",
|
"confirm_transaction": "Confirmer la transaction",
|
||||||
|
"confirm_transfer": "Confirmer le transfert",
|
||||||
"confirmations": "Confirmations",
|
"confirmations": "Confirmations",
|
||||||
"confirmations_display": "%d confirmations | %s",
|
"confirmations_display": "%d confirmations | %s",
|
||||||
"confirmed": "Confirmé",
|
"confirmed": "Confirmé",
|
||||||
@@ -172,6 +218,7 @@
|
|||||||
"console_welcome": "Bienvenue dans la console ObsidianDragon",
|
"console_welcome": "Bienvenue dans la console ObsidianDragon",
|
||||||
"console_zoom_in": "Agrandir",
|
"console_zoom_in": "Agrandir",
|
||||||
"console_zoom_out": "Réduire",
|
"console_zoom_out": "Réduire",
|
||||||
|
"copied": "Copié !",
|
||||||
"copy": "Copier",
|
"copy": "Copier",
|
||||||
"copy_address": "Copier l'adresse complète",
|
"copy_address": "Copier l'adresse complète",
|
||||||
"copy_error": "Copier l'erreur",
|
"copy_error": "Copier l'erreur",
|
||||||
@@ -180,22 +227,45 @@
|
|||||||
"copy_uri": "Copier l'URI",
|
"copy_uri": "Copier l'URI",
|
||||||
"current_price": "Prix actuel",
|
"current_price": "Prix actuel",
|
||||||
"custom_fees": "Frais personnalisés",
|
"custom_fees": "Frais personnalisés",
|
||||||
|
"daemon_version": "Daemon",
|
||||||
"dark": "Sombre",
|
"dark": "Sombre",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"date_label": "Date :",
|
"date_label": "Date :",
|
||||||
"debug_logging": "JOURNALISATION DE DÉBOGAGE",
|
"debug_logging": "JOURNALISATION DE DÉBOGAGE",
|
||||||
"delete": "Supprimer",
|
"delete": "Supprimer",
|
||||||
|
"delete_blockchain": "Supprimer Blockchain",
|
||||||
|
"delete_blockchain_confirm": "Supprimer & Resynchroniser",
|
||||||
|
"deshielding_warning": "Attention : Cela va déblinder des fonds d'une adresse privée (Z) vers une adresse transparente (T).",
|
||||||
"difficulty": "Difficulté",
|
"difficulty": "Difficulté",
|
||||||
"disconnected": "Déconnecté",
|
"disconnected": "Déconnecté",
|
||||||
"dismiss": "Ignorer",
|
"dismiss": "Ignorer",
|
||||||
"display": "Affichage",
|
"display": "Affichage",
|
||||||
|
"download": "Télécharger",
|
||||||
|
"download_bootstrap": "Télécharger Bootstrap",
|
||||||
"dragonx_green": "DragonX (Vert)",
|
"dragonx_green": "DragonX (Vert)",
|
||||||
"edit": "Modifier",
|
"edit": "Modifier",
|
||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
"error_format": "Erreur : %s",
|
"error_format": "Erreur : %s",
|
||||||
"est_time_to_block": "Temps est. par bloc",
|
"est_time_to_block": "Temps est. par bloc",
|
||||||
"exit": "Quitter",
|
"exit": "Quitter",
|
||||||
"explorer": "EXPLORATEUR",
|
"explorer": "Explorateur",
|
||||||
|
"explorer_block_detail": "Bloc",
|
||||||
|
"explorer_block_hash": "Hash",
|
||||||
|
"explorer_block_height": "Hauteur",
|
||||||
|
"explorer_block_merkle": "Racine Merkle",
|
||||||
|
"explorer_block_size": "Taille",
|
||||||
|
"explorer_block_time": "Heure",
|
||||||
|
"explorer_block_txs": "Transactions",
|
||||||
|
"explorer_chain_stats": "Chaîne",
|
||||||
|
"explorer_invalid_query": "Entrez une hauteur de bloc ou un hash de 64 caractères",
|
||||||
|
"explorer_mempool": "Mempool",
|
||||||
|
"explorer_mempool_size": "Taille",
|
||||||
|
"explorer_mempool_txs": "Transactions",
|
||||||
|
"explorer_recent_blocks": "Blocs récents",
|
||||||
|
"explorer_search": "Rechercher",
|
||||||
|
"explorer_section": "EXPLORATEUR",
|
||||||
|
"explorer_tx_outputs": "Sorties",
|
||||||
|
"explorer_tx_size": "Taille",
|
||||||
"export": "Exporter",
|
"export": "Exporter",
|
||||||
"export_csv": "Exporter en CSV",
|
"export_csv": "Exporter en CSV",
|
||||||
"export_keys_btn": "Exporter les clés",
|
"export_keys_btn": "Exporter les clés",
|
||||||
@@ -224,14 +294,22 @@
|
|||||||
"fetch_prices": "Récupérer les prix",
|
"fetch_prices": "Récupérer les prix",
|
||||||
"file": "Fichier",
|
"file": "Fichier",
|
||||||
"file_save_location": "Le fichier sera enregistré dans : ~/.config/ObsidianDragon/",
|
"file_save_location": "Le fichier sera enregistré dans : ~/.config/ObsidianDragon/",
|
||||||
|
"filter": "Filtrer...",
|
||||||
"font_scale": "Taille de police",
|
"font_scale": "Taille de police",
|
||||||
|
"force_quit": "Forcer la fermeture",
|
||||||
|
"force_quit_confirm_msg": "Cela tuera immédiatement le daemon sans arrêt propre.\nCela peut corrompre l'index de la blockchain et nécessiter une resynchronisation.",
|
||||||
|
"force_quit_confirm_title": "Forcer la fermeture ?",
|
||||||
|
"force_quit_warning": "Cela tuera immédiatement le daemon sans arrêt propre. Peut nécessiter une resynchronisation de la blockchain.",
|
||||||
|
"force_quit_yes": "Forcer la fermeture",
|
||||||
"from": "De",
|
"from": "De",
|
||||||
"from_upper": "DE",
|
"from_upper": "DE",
|
||||||
"full_details": "Tous les détails",
|
"full_details": "Tous les détails",
|
||||||
"general": "Général",
|
"general": "Général",
|
||||||
|
"generating": "Génération",
|
||||||
"go_to_receive": "Aller à Recevoir",
|
"go_to_receive": "Aller à Recevoir",
|
||||||
"height": "Hauteur",
|
"height": "Hauteur",
|
||||||
"help": "Aide",
|
"help": "Aide",
|
||||||
|
"hidden_tag": " (masqué)",
|
||||||
"hide": "Masquer",
|
"hide": "Masquer",
|
||||||
"hide_address": "Masquer l'adresse",
|
"hide_address": "Masquer l'adresse",
|
||||||
"hide_zero_balances": "Masquer les soldes à 0",
|
"hide_zero_balances": "Masquer les soldes à 0",
|
||||||
@@ -253,6 +331,9 @@
|
|||||||
"import_key_warning": "Attention : Ne partagez jamais vos clés privées ! L'importation de clés provenant de sources non fiables peut compromettre votre portefeuille.",
|
"import_key_warning": "Attention : Ne partagez jamais vos clés privées ! L'importation de clés provenant de sources non fiables peut compromettre votre portefeuille.",
|
||||||
"import_key_z_format": "Clés de dépenses z-adresse (secret-extended-key-...)",
|
"import_key_z_format": "Clés de dépenses z-adresse (secret-extended-key-...)",
|
||||||
"import_private_key": "Importer une clé privée...",
|
"import_private_key": "Importer une clé privée...",
|
||||||
|
"incorrect_passphrase": "Mot de passe incorrect",
|
||||||
|
"incorrect_pin": "PIN incorrect",
|
||||||
|
"insufficient_funds": "Fonds insuffisants pour ce montant plus les frais.",
|
||||||
"invalid_address": "Format d'adresse invalide",
|
"invalid_address": "Format d'adresse invalide",
|
||||||
"ip_address": "Adresse IP",
|
"ip_address": "Adresse IP",
|
||||||
"keep": "Conserver",
|
"keep": "Conserver",
|
||||||
@@ -266,6 +347,7 @@
|
|||||||
"key_export_viewing_keys_zonly": "Les clés de visualisation ne sont disponibles que pour les adresses blindées (z)",
|
"key_export_viewing_keys_zonly": "Les clés de visualisation ne sont disponibles que pour les adresses blindées (z)",
|
||||||
"key_export_viewing_warning": "Cette clé de visualisation permet à d'autres de voir vos transactions entrantes et votre solde, mais PAS de dépenser vos fonds. Ne la partagez qu'avec des personnes de confiance.",
|
"key_export_viewing_warning": "Cette clé de visualisation permet à d'autres de voir vos transactions entrantes et votre solde, mais PAS de dépenser vos fonds. Ne la partagez qu'avec des personnes de confiance.",
|
||||||
"label": "Libellé :",
|
"label": "Libellé :",
|
||||||
|
"label_placeholder": "ex. Épargne, Minage...",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"light": "Clair",
|
"light": "Clair",
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
@@ -286,6 +368,7 @@
|
|||||||
"market_now": "Maintenant",
|
"market_now": "Maintenant",
|
||||||
"market_pct_shielded": "%.0f%% Blindé",
|
"market_pct_shielded": "%.0f%% Blindé",
|
||||||
"market_portfolio": "PORTEFEUILLE",
|
"market_portfolio": "PORTEFEUILLE",
|
||||||
|
"market_price_loading": "Chargement des données de prix...",
|
||||||
"market_price_unavailable": "Données de prix indisponibles",
|
"market_price_unavailable": "Données de prix indisponibles",
|
||||||
"market_refresh_price": "Actualiser les données de prix",
|
"market_refresh_price": "Actualiser les données de prix",
|
||||||
"market_trade_on": "Échanger sur %s",
|
"market_trade_on": "Échanger sur %s",
|
||||||
@@ -311,6 +394,13 @@
|
|||||||
"mining_address_copied": "Adresse de minage copiée",
|
"mining_address_copied": "Adresse de minage copiée",
|
||||||
"mining_all_time": "Tout le temps",
|
"mining_all_time": "Tout le temps",
|
||||||
"mining_already_saved": "URL du pool déjà enregistrée",
|
"mining_already_saved": "URL du pool déjà enregistrée",
|
||||||
|
"mining_benchmark_cancel": "Annuler le benchmark",
|
||||||
|
"mining_benchmark_cooling": "Refroidissement",
|
||||||
|
"mining_benchmark_dismiss": "Fermer",
|
||||||
|
"mining_benchmark_result": "Optimal",
|
||||||
|
"mining_benchmark_stabilizing": "Stabilisation",
|
||||||
|
"mining_benchmark_testing": "Test",
|
||||||
|
"mining_benchmark_tooltip": "Trouver le nombre optimal de threads pour ce CPU",
|
||||||
"mining_block_copied": "Hash du bloc copié",
|
"mining_block_copied": "Hash du bloc copié",
|
||||||
"mining_chart_1m_ago": "il y a 1m",
|
"mining_chart_1m_ago": "il y a 1m",
|
||||||
"mining_chart_5m_ago": "il y a 5m",
|
"mining_chart_5m_ago": "il y a 5m",
|
||||||
@@ -330,8 +420,15 @@
|
|||||||
"mining_filter_tip_all": "Afficher tous les gains",
|
"mining_filter_tip_all": "Afficher tous les gains",
|
||||||
"mining_filter_tip_pool": "Afficher uniquement les gains du pool",
|
"mining_filter_tip_pool": "Afficher uniquement les gains du pool",
|
||||||
"mining_filter_tip_solo": "Afficher uniquement les gains solo",
|
"mining_filter_tip_solo": "Afficher uniquement les gains solo",
|
||||||
|
"mining_generate_z_address_hint": "Générez une adresse Z dans l'onglet Recevoir pour l'utiliser comme adresse de paiement",
|
||||||
|
"mining_idle_gpu_off_tooltip": "Sans restriction : ACTIVÉ\nSeule l'entrée clavier/souris détermine l'inactivité\nCliquez pour activer la détection GPU",
|
||||||
|
"mining_idle_gpu_on_tooltip": "GPU-conscient : ACTIVÉ\nL'activité GPU (vidéo, jeux) empêche le minage inactif\nCliquez pour le mode sans restriction",
|
||||||
"mining_idle_off_tooltip": "Activer le minage au repos",
|
"mining_idle_off_tooltip": "Activer le minage au repos",
|
||||||
"mining_idle_on_tooltip": "Désactiver le minage au repos",
|
"mining_idle_on_tooltip": "Désactiver le minage au repos",
|
||||||
|
"mining_idle_scale_off_tooltip": "Mode démarrage/arrêt : ACTIVÉ\nCliquez pour passer au mode mise à l'échelle des threads",
|
||||||
|
"mining_idle_scale_on_tooltip": "Mise à l'échelle des threads : ACTIVÉ\nCliquez pour passer au mode démarrage/arrêt",
|
||||||
|
"mining_idle_threads_active_tooltip": "Threads quand l'utilisateur est actif",
|
||||||
|
"mining_idle_threads_idle_tooltip": "Threads quand le système est inactif",
|
||||||
"mining_local_hashrate": "Hashrate local",
|
"mining_local_hashrate": "Hashrate local",
|
||||||
"mining_mine": "Miner",
|
"mining_mine": "Miner",
|
||||||
"mining_mining_addr": "Adr. minage",
|
"mining_mining_addr": "Adr. minage",
|
||||||
@@ -388,6 +485,7 @@
|
|||||||
"no_addresses_available": "Aucune adresse disponible",
|
"no_addresses_available": "Aucune adresse disponible",
|
||||||
"no_addresses_match": "Aucune adresse ne correspond au filtre",
|
"no_addresses_match": "Aucune adresse ne correspond au filtre",
|
||||||
"no_addresses_with_balance": "Aucune adresse avec solde",
|
"no_addresses_with_balance": "Aucune adresse avec solde",
|
||||||
|
"no_addresses_yet": "Pas encore d'adresses",
|
||||||
"no_matching": "Aucune transaction correspondante",
|
"no_matching": "Aucune transaction correspondante",
|
||||||
"no_recent_receives": "Aucune réception récente",
|
"no_recent_receives": "Aucune réception récente",
|
||||||
"no_recent_sends": "Aucun envoi récent",
|
"no_recent_sends": "Aucun envoi récent",
|
||||||
@@ -453,6 +551,7 @@
|
|||||||
"peers_upper": "PAIRS",
|
"peers_upper": "PAIRS",
|
||||||
"peers_version": "Version",
|
"peers_version": "Version",
|
||||||
"pending": "En attente",
|
"pending": "En attente",
|
||||||
|
"pin_not_set": "PIN non défini. Utilisez le mot de passe pour déverrouiller.",
|
||||||
"ping": "Ping",
|
"ping": "Ping",
|
||||||
"price_chart": "Graphique des prix",
|
"price_chart": "Graphique des prix",
|
||||||
"qr_code": "Code QR",
|
"qr_code": "Code QR",
|
||||||
@@ -473,7 +572,9 @@
|
|||||||
"recent_received": "REÇUS RÉCENTS",
|
"recent_received": "REÇUS RÉCENTS",
|
||||||
"recent_sends": "ENVOIS RÉCENTS",
|
"recent_sends": "ENVOIS RÉCENTS",
|
||||||
"recipient": "DESTINATAIRE",
|
"recipient": "DESTINATAIRE",
|
||||||
|
"recipient_balance": "Destinataire : %.8f → %.8f DRGX",
|
||||||
"recv_type": "Reçu",
|
"recv_type": "Reçu",
|
||||||
|
"reduce_motion": "Réduire les animations",
|
||||||
"refresh": "Actualiser",
|
"refresh": "Actualiser",
|
||||||
"refresh_now": "Actualiser maintenant",
|
"refresh_now": "Actualiser maintenant",
|
||||||
"remove_favorite": "Retirer des favoris",
|
"remove_favorite": "Retirer des favoris",
|
||||||
@@ -493,7 +594,10 @@
|
|||||||
"request_uri_copied": "URI de paiement copiée dans le presse-papiers",
|
"request_uri_copied": "URI de paiement copiée dans le presse-papiers",
|
||||||
"rescan": "Re-scanner",
|
"rescan": "Re-scanner",
|
||||||
"reset_to_defaults": "Réinitialiser les paramètres",
|
"reset_to_defaults": "Réinitialiser les paramètres",
|
||||||
|
"restarting_after_encryption": "Redémarrage du daemon après chiffrement...",
|
||||||
"restore_address": "Restaurer l'adresse",
|
"restore_address": "Restaurer l'adresse",
|
||||||
|
"result_preview": "Aperçu du résultat",
|
||||||
|
"retry": "Réessayer",
|
||||||
"review_send": "Vérifier l'envoi",
|
"review_send": "Vérifier l'envoi",
|
||||||
"rpc_host": "Hôte RPC",
|
"rpc_host": "Hôte RPC",
|
||||||
"rpc_pass": "Mot de passe",
|
"rpc_pass": "Mot de passe",
|
||||||
@@ -502,8 +606,55 @@
|
|||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
"save_settings": "Enregistrer les paramètres",
|
"save_settings": "Enregistrer les paramètres",
|
||||||
"save_z_transactions": "Enregistrer les Z-tx dans la liste",
|
"save_z_transactions": "Enregistrer les Z-tx dans la liste",
|
||||||
|
"sb_auth_failed": "Authentification échouée — vérifiez rpcuser/rpcpassword",
|
||||||
|
"sb_block": "Bloc : %d",
|
||||||
|
"sb_connecting_daemon": "Connexion à dragonxd...",
|
||||||
|
"sb_connecting_err": "Connexion au daemon — %s",
|
||||||
|
"sb_connecting_external": "Connexion au daemon externe...",
|
||||||
|
"sb_connecting_generic": "Connexion au daemon...",
|
||||||
|
"sb_daemon_crashed": "Le daemon a planté %d fois",
|
||||||
|
"sb_daemon_not_found": "Daemon introuvable",
|
||||||
|
"sb_dragonxd_running": "dragonxd en cours",
|
||||||
|
"sb_dragonxd_stopped": "dragonxd arrêté",
|
||||||
|
"sb_dragonxd_stopping": "Arrêt de dragonxd...",
|
||||||
|
"sb_extracting_sapling": "Extraction des paramètres Sapling...",
|
||||||
|
"sb_importing_keys": "Importation des clés",
|
||||||
|
"sb_loading_config": "Chargement de la configuration...",
|
||||||
|
"sb_mining_hs": "%.1f H/s",
|
||||||
|
"sb_net_ghs": "Rés: %.2f GH/s",
|
||||||
|
"sb_net_hs": "Rés: %.1f H/s",
|
||||||
|
"sb_net_khs": "Rés: %.2f KH/s",
|
||||||
|
"sb_net_mhs": "Rés: %.2f MH/s",
|
||||||
|
"sb_no_conf": "DRAGONX.conf introuvable",
|
||||||
|
"sb_peers": "Pairs : %zu",
|
||||||
|
"sb_rescanning": "Rescan",
|
||||||
|
"sb_rescanning_pct": "Rescan %.0f%%",
|
||||||
|
"sb_restarting_daemon": "Redémarrage du daemon...",
|
||||||
|
"sb_sapling_failed": "Échec de l'extraction des paramètres Sapling.",
|
||||||
|
"sb_sapling_not_found": "Paramètres Sapling introuvables.",
|
||||||
|
"sb_starting_daemon": "Démarrage de dragonxd...",
|
||||||
|
"sb_syncing_basic": "Synchronisation %.1f%% (%d restants)",
|
||||||
|
"sb_syncing_eta": "Synchronisation %.1f%% (%d restants, %.0f blk/s, ~%s)",
|
||||||
|
"sb_waiting_config": "En attente de la configuration du daemon...",
|
||||||
|
"sb_waiting_daemon": "En attente de dragonxd...",
|
||||||
|
"sb_waiting_daemon_err": "En attente de dragonxd — %s",
|
||||||
|
"sb_warming_up": "Démarrage...",
|
||||||
"search_placeholder": "Rechercher...",
|
"search_placeholder": "Rechercher...",
|
||||||
"security": "SÉCURITÉ",
|
"security": "SÉCURITÉ",
|
||||||
|
"seed_backup_button": "Phrase de récupération",
|
||||||
|
"seed_backup_close": "Fermer",
|
||||||
|
"seed_backup_copy": "Copier",
|
||||||
|
"seed_backup_intro": "Ces mots sont la sauvegarde principale de votre portefeuille. Quiconque les possède contrôle vos fonds.",
|
||||||
|
"seed_backup_load_failed": "Impossible de charger la phrase de récupération.",
|
||||||
|
"seed_backup_loading": "Chargement de votre phrase de récupération…",
|
||||||
|
"seed_backup_locked": "Déverrouillez votre portefeuille pour révéler sa phrase de récupération.",
|
||||||
|
"seed_backup_none": "Ce portefeuille est antérieur aux phrases de récupération, il n'a donc pas de phrase de 24 mots. Sauvegardez-le plutôt avec « Sauvegarde » ou en exportant vos clés privées.",
|
||||||
|
"seed_backup_reminder": "Votre portefeuille possède une phrase de récupération de 24 mots. Sauvegardez-la maintenant dans Paramètres → Nœud et sécurité.",
|
||||||
|
"seed_backup_save": "Enregistrer dans un fichier…",
|
||||||
|
"seed_backup_save_failed": "Impossible d'écrire ",
|
||||||
|
"seed_backup_saved": "Enregistré dans ",
|
||||||
|
"seed_backup_title": "Sauvegarder la phrase de récupération",
|
||||||
|
"seed_backup_warning": "Notez-les dans l'ordre, conservez-les hors ligne, et ne les partagez ni ne les photographiez jamais. Si vous les perdez, vos fonds ne pourront pas être récupérés.",
|
||||||
"select_address": "Sélectionner une adresse...",
|
"select_address": "Sélectionner une adresse...",
|
||||||
"select_receiving_address": "Sélectionner une adresse de réception...",
|
"select_receiving_address": "Sélectionner une adresse de réception...",
|
||||||
"select_source_address": "Sélectionner une adresse source...",
|
"select_source_address": "Sélectionner une adresse source...",
|
||||||
@@ -553,12 +704,15 @@
|
|||||||
"send_valid_transparent": "Adresse transparente valide",
|
"send_valid_transparent": "Adresse transparente valide",
|
||||||
"send_wallet_empty": "Votre portefeuille est vide",
|
"send_wallet_empty": "Votre portefeuille est vide",
|
||||||
"send_yes_clear": "Oui, effacer",
|
"send_yes_clear": "Oui, effacer",
|
||||||
|
"sender_balance": "Expéditeur : %.8f → %.8f DRGX",
|
||||||
"sending": "Envoi de la transaction",
|
"sending": "Envoi de la transaction",
|
||||||
"sending_from": "ENVOI DEPUIS",
|
"sending_from": "ENVOI DEPUIS",
|
||||||
|
"sends_full_balance_warning": "Cela envoie le solde complet. L'adresse d'envoi aura un solde nul.",
|
||||||
"sent": "envoyé",
|
"sent": "envoyé",
|
||||||
"sent_filter": "Envoyé",
|
"sent_filter": "Envoyé",
|
||||||
"sent_type": "Envoyé",
|
"sent_type": "Envoyé",
|
||||||
"sent_upper": "ENVOYÉ",
|
"sent_upper": "ENVOYÉ",
|
||||||
|
"set_label": "Définir le libellé...",
|
||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"settings_about_text": "Un portefeuille de cryptomonnaie blindé pour DragonX (DRGX), construit avec Dear ImGui pour une expérience légère et portable.",
|
"settings_about_text": "Un portefeuille de cryptomonnaie blindé pour DragonX (DRGX), construit avec Dear ImGui pour une expérience légère et portable.",
|
||||||
"settings_acrylic_level": "Niveau acrylique :",
|
"settings_acrylic_level": "Niveau acrylique :",
|
||||||
@@ -647,13 +801,17 @@
|
|||||||
"shield_utxo_limit": "Limite UTXO :",
|
"shield_utxo_limit": "Limite UTXO :",
|
||||||
"shield_wildcard_hint": "Utilisez '*' pour blinder depuis toutes les adresses transparentes",
|
"shield_wildcard_hint": "Utilisez '*' pour blinder depuis toutes les adresses transparentes",
|
||||||
"shielded": "Blindé",
|
"shielded": "Blindé",
|
||||||
|
"shielded_address": "Adresse protégée",
|
||||||
"shielded_to": "BLINDÉ VERS",
|
"shielded_to": "BLINDÉ VERS",
|
||||||
"shielded_type": "Blindé",
|
"shielded_type": "Blindé",
|
||||||
|
"shielding_notice": "Note : Cela blindera des fonds d'une adresse transparente (T) vers une adresse privée (Z).",
|
||||||
"show": "Afficher",
|
"show": "Afficher",
|
||||||
"show_hidden": "Afficher masqués (%d)",
|
"show_hidden": "Afficher masqués (%d)",
|
||||||
"show_qr_code": "Afficher le code QR",
|
"show_qr_code": "Afficher le code QR",
|
||||||
"showing_transactions": "Affichage %dâ%d sur %d transactions (total : %zu)",
|
"showing_transactions": "Affichage %d–%d sur %d transactions (total : %zu)",
|
||||||
|
"showing_x_of_y": "Affichage de %d sur %d adresses",
|
||||||
"simple_background": "Arrière-plan simple",
|
"simple_background": "Arrière-plan simple",
|
||||||
|
"slider_off": "Désactivé",
|
||||||
"start_mining": "Démarrer le minage",
|
"start_mining": "Démarrer le minage",
|
||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"stop_external": "Arrêter le daemon externe",
|
"stop_external": "Arrêter le daemon externe",
|
||||||
@@ -662,17 +820,26 @@
|
|||||||
"success": "Succès",
|
"success": "Succès",
|
||||||
"summary": "Résumé",
|
"summary": "Résumé",
|
||||||
"syncing": "Synchronisation...",
|
"syncing": "Synchronisation...",
|
||||||
|
"t_address": "Adresse T",
|
||||||
"t_addresses": "Adresses T",
|
"t_addresses": "Adresses T",
|
||||||
"test_connection": "Tester",
|
"test_connection": "Tester",
|
||||||
"theme": "Thème",
|
"theme": "Thème",
|
||||||
"theme_effects": "Effets de thème",
|
"theme_effects": "Effets de thème",
|
||||||
|
"theme_language": "THÈME & LANGUE",
|
||||||
"time_days_ago": "il y a %d jours",
|
"time_days_ago": "il y a %d jours",
|
||||||
"time_hours_ago": "il y a %d heures",
|
"time_hours_ago": "il y a %d heures",
|
||||||
"time_minutes_ago": "il y a %d minutes",
|
"time_minutes_ago": "il y a %d minutes",
|
||||||
"time_seconds_ago": "il y a %d secondes",
|
"time_seconds_ago": "il y a %d secondes",
|
||||||
|
"timeout_15min": "15 min",
|
||||||
|
"timeout_1hour": "1 heure",
|
||||||
|
"timeout_1min": "1 min",
|
||||||
|
"timeout_30min": "30 min",
|
||||||
|
"timeout_5min": "5 min",
|
||||||
|
"timeout_off": "Désactivé",
|
||||||
"to": "À",
|
"to": "À",
|
||||||
"to_upper": "À",
|
"to_upper": "À",
|
||||||
"tools": "OUTILS",
|
"tools": "OUTILS",
|
||||||
|
"tools_actions": "Outils & Actions...",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"transaction_id": "ID DE TRANSACTION",
|
"transaction_id": "ID DE TRANSACTION",
|
||||||
"transaction_sent": "Transaction envoyée avec succès",
|
"transaction_sent": "Transaction envoyée avec succès",
|
||||||
@@ -680,7 +847,13 @@
|
|||||||
"transaction_url": "URL de transaction",
|
"transaction_url": "URL de transaction",
|
||||||
"transactions": "Transactions",
|
"transactions": "Transactions",
|
||||||
"transactions_upper": "TRANSACTIONS",
|
"transactions_upper": "TRANSACTIONS",
|
||||||
|
"transfer_failed": "Échec du transfert",
|
||||||
|
"transfer_funds": "Transférer des fonds",
|
||||||
|
"transfer_sent": "Transfert envoyé",
|
||||||
|
"transfer_sent_desc": "Votre transfert a été soumis au réseau.",
|
||||||
|
"transfer_to": "Transférer à :",
|
||||||
"transparent": "Transparent",
|
"transparent": "Transparent",
|
||||||
|
"transparent_address": "Adresse transparente",
|
||||||
"tt_addr_url": "URL de base pour consulter les adresses dans un explorateur de blocs",
|
"tt_addr_url": "URL de base pour consulter les adresses dans un explorateur de blocs",
|
||||||
"tt_address_book": "Gérer les adresses enregistrées pour un envoi rapide",
|
"tt_address_book": "Gérer les adresses enregistrées pour un envoi rapide",
|
||||||
"tt_auto_lock": "Verrouiller le portefeuille après cette durée d'inactivité",
|
"tt_auto_lock": "Verrouiller le portefeuille après cette durée d'inactivité",
|
||||||
@@ -695,6 +868,8 @@
|
|||||||
"tt_custom_theme": "Thème personnalisé actif",
|
"tt_custom_theme": "Thème personnalisé actif",
|
||||||
"tt_debug_collapse": "Réduire les options de journalisation de débogage",
|
"tt_debug_collapse": "Réduire les options de journalisation de débogage",
|
||||||
"tt_debug_expand": "Développer les options de journalisation de débogage",
|
"tt_debug_expand": "Développer les options de journalisation de débogage",
|
||||||
|
"tt_delete_blockchain": "Supprimer toutes les données de la blockchain et démarrer une nouvelle synchronisation. wallet.dat et la configuration sont préservés.",
|
||||||
|
"tt_download_bootstrap": "Télécharger le bootstrap blockchain pour accélérer la synchronisation\nLes données de blocs existantes seront remplacées",
|
||||||
"tt_encrypt": "Chiffrer wallet.dat avec une phrase secrète",
|
"tt_encrypt": "Chiffrer wallet.dat avec une phrase secrète",
|
||||||
"tt_export_all": "Exporter toutes les clés privées dans un fichier",
|
"tt_export_all": "Exporter toutes les clés privées dans un fichier",
|
||||||
"tt_export_csv": "Exporter l'historique des transactions en feuille de calcul CSV",
|
"tt_export_csv": "Exporter l'historique des transactions en feuille de calcul CSV",
|
||||||
@@ -712,6 +887,7 @@
|
|||||||
"tt_mine_idle": "Démarrer le minage automatiquement quand le\\nsystème est inactif (aucune entrée clavier/souris)",
|
"tt_mine_idle": "Démarrer le minage automatiquement quand le\\nsystème est inactif (aucune entrée clavier/souris)",
|
||||||
"tt_noise": "Intensité de texture grainée (0%% = désactivé, 100%% = maximum)",
|
"tt_noise": "Intensité de texture grainée (0%% = désactivé, 100%% = maximum)",
|
||||||
"tt_open_dir": "Cliquer pour ouvrir dans l'explorateur de fichiers",
|
"tt_open_dir": "Cliquer pour ouvrir dans l'explorateur de fichiers",
|
||||||
|
"tt_reduce_motion": "Désactiver les transitions animées et le lerp de solde pour l'accessibilité",
|
||||||
"tt_remove_encrypt": "Supprimer le chiffrement et stocker le portefeuille sans protection",
|
"tt_remove_encrypt": "Supprimer le chiffrement et stocker le portefeuille sans protection",
|
||||||
"tt_remove_pin": "Supprimer le PIN et exiger la phrase secrète pour déverrouiller",
|
"tt_remove_pin": "Supprimer le PIN et exiger la phrase secrète pour déverrouiller",
|
||||||
"tt_report_bug": "Signaler un problème dans le suivi de projet",
|
"tt_report_bug": "Signaler un problème dans le suivi de projet",
|
||||||
@@ -727,6 +903,7 @@
|
|||||||
"tt_save_ztx": "Stocker l'historique des transactions z-address localement pour un chargement plus rapide",
|
"tt_save_ztx": "Stocker l'historique des transactions z-address localement pour un chargement plus rapide",
|
||||||
"tt_scan_themes": "Rechercher de nouveaux thèmes.\\nPlacez les dossiers de thèmes dans :\\n%s",
|
"tt_scan_themes": "Rechercher de nouveaux thèmes.\\nPlacez les dossiers de thèmes dans :\\n%s",
|
||||||
"tt_scanline": "Effet de lignes de balayage CRT dans la console",
|
"tt_scanline": "Effet de lignes de balayage CRT dans la console",
|
||||||
|
"tt_seed_backup": "Afficher et sauvegarder la phrase de récupération de 24 mots de votre portefeuille",
|
||||||
"tt_set_pin": "Définir un PIN de 4-8 chiffres pour un déverrouillage rapide",
|
"tt_set_pin": "Définir un PIN de 4-8 chiffres pour un déverrouillage rapide",
|
||||||
"tt_shield_mining": "Déplacer les récompenses de minage transparentes vers une adresse blindée",
|
"tt_shield_mining": "Déplacer les récompenses de minage transparentes vers une adresse blindée",
|
||||||
"tt_simple_bg": "Utiliser un dégradé simple pour l'arrière-plan\\nRaccourci : Ctrl+Up",
|
"tt_simple_bg": "Utiliser un dégradé simple pour l'arrière-plan\\nRaccourci : Ctrl+Up",
|
||||||
@@ -789,7 +966,9 @@
|
|||||||
"warning_upper": "ATTENTION !",
|
"warning_upper": "ATTENTION !",
|
||||||
"website": "Site web",
|
"website": "Site web",
|
||||||
"window_opacity": "Opacité de la fenêtre",
|
"window_opacity": "Opacité de la fenêtre",
|
||||||
|
"wizard_daemon_start_failed": "Échec du démarrage du daemon — sera réessayé automatiquement",
|
||||||
"yes_clear": "Oui, effacer",
|
"yes_clear": "Oui, effacer",
|
||||||
"your_addresses": "Vos adresses",
|
"your_addresses": "Vos adresses",
|
||||||
|
"z_address": "Adresse Z",
|
||||||
"z_addresses": "Adresses Z"
|
"z_addresses": "Adresses Z"
|
||||||
}
|
}
|
||||||
|
|||||||
181
res/lang/ja.json
181
res/lang/ja.json
@@ -43,6 +43,8 @@
|
|||||||
"address_url": "アドレスURL",
|
"address_url": "アドレスURL",
|
||||||
"addresses_appear_here": "接続後、受信アドレスがここに表示されます。",
|
"addresses_appear_here": "接続後、受信アドレスがここに表示されます。",
|
||||||
"advanced": "詳細設定",
|
"advanced": "詳細設定",
|
||||||
|
"advanced_effects": "高度なエフェクト...",
|
||||||
|
"ago": "前",
|
||||||
"all_filter": "すべて",
|
"all_filter": "すべて",
|
||||||
"allow_custom_fees": "カスタム手数料を許可",
|
"allow_custom_fees": "カスタム手数料を許可",
|
||||||
"amount": "金額",
|
"amount": "金額",
|
||||||
@@ -90,12 +92,52 @@
|
|||||||
"block_timestamp": "タイムスタンプ:",
|
"block_timestamp": "タイムスタンプ:",
|
||||||
"block_transactions": "トランザクション:",
|
"block_transactions": "トランザクション:",
|
||||||
"blockchain_syncing": "ブロックチェーン同期中 (%.1f%%)... 残高が不正確な場合があります。",
|
"blockchain_syncing": "ブロックチェーン同期中 (%.1f%%)... 残高が不正確な場合があります。",
|
||||||
|
"bootstrap_daemon_running": "デーモン実行中",
|
||||||
|
"bootstrap_daemon_stopped": "デーモン停止",
|
||||||
|
"bootstrap_daemon_stopping": "デーモン停止中...",
|
||||||
|
"bootstrap_desc": "ブロックチェーンブートストラップをダウンロードして初期同期を劇的に高速化します。ブロックチェーンのスナップショットをダウンロードしてデータディレクトリに展開します。",
|
||||||
|
"bootstrap_downloading": "ブートストラップをダウンロード中...",
|
||||||
|
"bootstrap_extracting": "ブロックチェーンデータを展開中...",
|
||||||
|
"bootstrap_failed": "ブートストラップ失敗",
|
||||||
|
"bootstrap_mirror": "ミラー",
|
||||||
|
"bootstrap_mirror_tooltip": "ミラーからダウンロード (bootstrap2.dragonx.is)。\nメインのダウンロードが遅い場合や失敗する場合に使用してください。",
|
||||||
|
"bootstrap_restart_daemon": "デーモンを再起動",
|
||||||
|
"bootstrap_success": "ブートストラップ完了",
|
||||||
|
"bootstrap_success_desc": "ブロックチェーンデータが正常に展開されました。デーモンを起動してブートストラップポイントから同期を開始してください。",
|
||||||
|
"bootstrap_trust_warning": "bootstrap.dragonx.is または bootstrap2.dragonx.is のみを使用してください。信頼できないソースのファイルを使用するとノードが危険にさらされる可能性があります。",
|
||||||
|
"bootstrap_verifying": "チェックサムを検証中...",
|
||||||
|
"bootstrap_wallet_protected": "(wallet.dat は保護されています)",
|
||||||
|
"bootstrap_warning": "既存のブロックデータ(blocks、chainstate、notarizations)は削除され置き換えられます。wallet.dat は変更・削除されません。",
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
"characters": "文字",
|
"characters": "文字",
|
||||||
|
"chat": "チャット",
|
||||||
|
"chat_cancel": "キャンセル",
|
||||||
|
"chat_contact_request": "連絡リクエスト",
|
||||||
|
"chat_empty_hint": "まだ会話はありません。受信したメッセージはここに表示されます。",
|
||||||
|
"chat_locked_hint": "チャットを読み込むにはウォレットのロックを解除してください。",
|
||||||
|
"chat_new_button": "新しい会話",
|
||||||
|
"chat_new_message": "メッセージ",
|
||||||
|
"chat_new_send": "リクエストを送信",
|
||||||
|
"chat_new_title": "新しい会話",
|
||||||
|
"chat_new_zaddr": "宛先Zアドレス",
|
||||||
|
"chat_select_hint": "表示する会話を選択してください。",
|
||||||
|
"chat_send": "送信",
|
||||||
|
"chat_send_failed": "未送信",
|
||||||
|
"chat_toast_compose_failed": "メッセージを作成できませんでした(長すぎませんか?)。",
|
||||||
|
"chat_toast_lite_busy": "すでに送信処理が進行中か、ウォレットが開かれていません。",
|
||||||
|
"chat_toast_no_zaddr": "送信元に使えるZアドレスがありません。",
|
||||||
|
"chat_toast_not_connected": "未接続 — チャットメッセージは送信されませんでした。",
|
||||||
|
"chat_toast_request_compose_failed": "連絡リクエストを作成できませんでした(アドレスまたはテキストが無効?)。",
|
||||||
|
"chat_toast_request_queued": "連絡リクエストを送信待ちに追加しました。",
|
||||||
|
"chat_toast_waiting_reply": "メッセージを送るには、この相手からの返信を待つ必要があります。",
|
||||||
|
"chat_waiting_reply": "この相手からの返信を待っています — 返信があればメッセージを送れます。",
|
||||||
|
"chat_you": "自分",
|
||||||
|
"choose_icon": "アイコンを選択",
|
||||||
"clear": "クリア",
|
"clear": "クリア",
|
||||||
"clear_all_bans": "すべてのブロックを解除",
|
"clear_all_bans": "すべてのブロックを解除",
|
||||||
"clear_anyway": "それでもクリア",
|
"clear_anyway": "それでもクリア",
|
||||||
"clear_form_confirm": "すべてのフォームフィールドをクリアしますか?",
|
"clear_form_confirm": "すべてのフォームフィールドをクリアしますか?",
|
||||||
|
"clear_icon": "アイコンをクリア",
|
||||||
"clear_request": "リクエストをクリア",
|
"clear_request": "リクエストをクリア",
|
||||||
"click_copy_address": "クリックしてアドレスをコピー",
|
"click_copy_address": "クリックしてアドレスをコピー",
|
||||||
"click_copy_uri": "クリックしてURIをコピー",
|
"click_copy_uri": "クリックしてURIをコピー",
|
||||||
@@ -106,8 +148,12 @@
|
|||||||
"confirm_clear_ztx_title": "Z-Tx 履歴クリアの確認",
|
"confirm_clear_ztx_title": "Z-Tx 履歴クリアの確認",
|
||||||
"confirm_clear_ztx_warning1": "z-トランザクション履歴をクリアすると、ウォレットの再スキャンが実行されるまでシールド残高が0と表示される場合があります。",
|
"confirm_clear_ztx_warning1": "z-トランザクション履歴をクリアすると、ウォレットの再スキャンが実行されるまでシールド残高が0と表示される場合があります。",
|
||||||
"confirm_clear_ztx_warning2": "これが発生した場合、残高を回復するにはz-アドレスの秘密鍵を再スキャンを有効にして再インポートする必要があります。",
|
"confirm_clear_ztx_warning2": "これが発生した場合、残高を回復するにはz-アドレスの秘密鍵を再スキャンを有効にして再インポートする必要があります。",
|
||||||
|
"confirm_delete_blockchain_msg": "デーモンを停止し、すべてのブロックチェーンデータ(blocks、chainstate、peers)を削除して、最初から再同期を開始します。数時間かかる場合があります。",
|
||||||
|
"confirm_delete_blockchain_safe": "wallet.dat、設定、トランザクション履歴は安全で削除されません。",
|
||||||
|
"confirm_delete_blockchain_title": "ブロックチェーンデータを削除",
|
||||||
"confirm_send": "送金を確認",
|
"confirm_send": "送金を確認",
|
||||||
"confirm_transaction": "取引を確認",
|
"confirm_transaction": "取引を確認",
|
||||||
|
"confirm_transfer": "送金を確認",
|
||||||
"confirmations": "確認数",
|
"confirmations": "確認数",
|
||||||
"confirmations_display": "%d 確認 | %s",
|
"confirmations_display": "%d 確認 | %s",
|
||||||
"confirmed": "確認済み",
|
"confirmed": "確認済み",
|
||||||
@@ -172,6 +218,7 @@
|
|||||||
"console_welcome": "ObsidianDragonコンソールへようこそ",
|
"console_welcome": "ObsidianDragonコンソールへようこそ",
|
||||||
"console_zoom_in": "拡大",
|
"console_zoom_in": "拡大",
|
||||||
"console_zoom_out": "縮小",
|
"console_zoom_out": "縮小",
|
||||||
|
"copied": "コピーしました!",
|
||||||
"copy": "コピー",
|
"copy": "コピー",
|
||||||
"copy_address": "完全なアドレスをコピー",
|
"copy_address": "完全なアドレスをコピー",
|
||||||
"copy_error": "エラーをコピー",
|
"copy_error": "エラーをコピー",
|
||||||
@@ -180,15 +227,21 @@
|
|||||||
"copy_uri": "URIをコピー",
|
"copy_uri": "URIをコピー",
|
||||||
"current_price": "現在の価格",
|
"current_price": "現在の価格",
|
||||||
"custom_fees": "カスタム手数料",
|
"custom_fees": "カスタム手数料",
|
||||||
|
"daemon_version": "デーモン",
|
||||||
"dark": "ダーク",
|
"dark": "ダーク",
|
||||||
"date": "日付",
|
"date": "日付",
|
||||||
"date_label": "日付:",
|
"date_label": "日付:",
|
||||||
"debug_logging": "デバッグログ",
|
"debug_logging": "デバッグログ",
|
||||||
"delete": "削除",
|
"delete": "削除",
|
||||||
|
"delete_blockchain": "ブロックチェーンを削除",
|
||||||
|
"delete_blockchain_confirm": "削除して再同期",
|
||||||
|
"deshielding_warning": "警告:プライベート (Z) アドレスからトランスペアレント (T) アドレスへ資金をデシールドします。",
|
||||||
"difficulty": "難易度",
|
"difficulty": "難易度",
|
||||||
"disconnected": "切断済み",
|
"disconnected": "切断済み",
|
||||||
"dismiss": "閉じる",
|
"dismiss": "閉じる",
|
||||||
"display": "表示",
|
"display": "表示",
|
||||||
|
"download": "ダウンロード",
|
||||||
|
"download_bootstrap": "ブートストラップをダウンロード",
|
||||||
"dragonx_green": "DragonX(グリーン)",
|
"dragonx_green": "DragonX(グリーン)",
|
||||||
"edit": "編集",
|
"edit": "編集",
|
||||||
"error": "エラー",
|
"error": "エラー",
|
||||||
@@ -196,6 +249,23 @@
|
|||||||
"est_time_to_block": "予測ブロック時間",
|
"est_time_to_block": "予測ブロック時間",
|
||||||
"exit": "終了",
|
"exit": "終了",
|
||||||
"explorer": "エクスプローラー",
|
"explorer": "エクスプローラー",
|
||||||
|
"explorer_block_detail": "ブロック",
|
||||||
|
"explorer_block_hash": "ハッシュ",
|
||||||
|
"explorer_block_height": "高さ",
|
||||||
|
"explorer_block_merkle": "マークルルート",
|
||||||
|
"explorer_block_size": "サイズ",
|
||||||
|
"explorer_block_time": "時刻",
|
||||||
|
"explorer_block_txs": "トランザクション",
|
||||||
|
"explorer_chain_stats": "チェーン",
|
||||||
|
"explorer_invalid_query": "ブロック高さまたは64文字のハッシュを入力してください",
|
||||||
|
"explorer_mempool": "メモリプール",
|
||||||
|
"explorer_mempool_size": "サイズ",
|
||||||
|
"explorer_mempool_txs": "トランザクション",
|
||||||
|
"explorer_recent_blocks": "最近のブロック",
|
||||||
|
"explorer_search": "検索",
|
||||||
|
"explorer_section": "エクスプローラー",
|
||||||
|
"explorer_tx_outputs": "出力",
|
||||||
|
"explorer_tx_size": "サイズ",
|
||||||
"export": "エクスポート",
|
"export": "エクスポート",
|
||||||
"export_csv": "CSVエクスポート",
|
"export_csv": "CSVエクスポート",
|
||||||
"export_keys_btn": "鍵をエクスポート",
|
"export_keys_btn": "鍵をエクスポート",
|
||||||
@@ -224,14 +294,22 @@
|
|||||||
"fetch_prices": "価格を取得",
|
"fetch_prices": "価格を取得",
|
||||||
"file": "ファイル",
|
"file": "ファイル",
|
||||||
"file_save_location": "ファイルの保存先:~/.config/ObsidianDragon/",
|
"file_save_location": "ファイルの保存先:~/.config/ObsidianDragon/",
|
||||||
|
"filter": "フィルター...",
|
||||||
"font_scale": "フォントサイズ",
|
"font_scale": "フォントサイズ",
|
||||||
|
"force_quit": "強制終了",
|
||||||
|
"force_quit_confirm_msg": "クリーンシャットダウンなしでデーモンを即座に終了します。\nブロックチェーンインデックスが破損し、再同期が必要になる可能性があります。",
|
||||||
|
"force_quit_confirm_title": "強制終了しますか?",
|
||||||
|
"force_quit_warning": "クリーンシャットダウンなしでデーモンを即座に終了します。ブロックチェーンの再同期が必要になる場合があります。",
|
||||||
|
"force_quit_yes": "強制終了",
|
||||||
"from": "送信元",
|
"from": "送信元",
|
||||||
"from_upper": "送信元",
|
"from_upper": "送信元",
|
||||||
"full_details": "詳細情報",
|
"full_details": "詳細情報",
|
||||||
"general": "一般",
|
"general": "一般",
|
||||||
|
"generating": "生成中",
|
||||||
"go_to_receive": "受信へ移動",
|
"go_to_receive": "受信へ移動",
|
||||||
"height": "高さ",
|
"height": "高さ",
|
||||||
"help": "ヘルプ",
|
"help": "ヘルプ",
|
||||||
|
"hidden_tag": " (非表示)",
|
||||||
"hide": "非表示",
|
"hide": "非表示",
|
||||||
"hide_address": "アドレスを非表示",
|
"hide_address": "アドレスを非表示",
|
||||||
"hide_zero_balances": "残高0を非表示",
|
"hide_zero_balances": "残高0を非表示",
|
||||||
@@ -253,6 +331,9 @@
|
|||||||
"import_key_warning": "警告:秘密鍵を決して共有しないでください!信頼できないソースからの鍵のインポートはウォレットを危険にさらす可能性があります。",
|
"import_key_warning": "警告:秘密鍵を決して共有しないでください!信頼できないソースからの鍵のインポートはウォレットを危険にさらす可能性があります。",
|
||||||
"import_key_z_format": "Zアドレス支出鍵 (secret-extended-key-...)",
|
"import_key_z_format": "Zアドレス支出鍵 (secret-extended-key-...)",
|
||||||
"import_private_key": "秘密鍵をインポート...",
|
"import_private_key": "秘密鍵をインポート...",
|
||||||
|
"incorrect_passphrase": "パスフレーズが正しくありません",
|
||||||
|
"incorrect_pin": "PINが正しくありません",
|
||||||
|
"insufficient_funds": "この金額と手数料に対して残高が不足しています。",
|
||||||
"invalid_address": "無効なアドレス形式",
|
"invalid_address": "無効なアドレス形式",
|
||||||
"ip_address": "IPアドレス",
|
"ip_address": "IPアドレス",
|
||||||
"keep": "保持",
|
"keep": "保持",
|
||||||
@@ -266,6 +347,7 @@
|
|||||||
"key_export_viewing_keys_zonly": "ビューイングキーはシールド (z) アドレスでのみ利用可能です",
|
"key_export_viewing_keys_zonly": "ビューイングキーはシールド (z) アドレスでのみ利用可能です",
|
||||||
"key_export_viewing_warning": "この閲覧鍵を使うと、他者があなたの受信取引と残高を見ることができますが、資金を使うことはできません。信頼できる相手とのみ共有してください。",
|
"key_export_viewing_warning": "この閲覧鍵を使うと、他者があなたの受信取引と残高を見ることができますが、資金を使うことはできません。信頼できる相手とのみ共有してください。",
|
||||||
"label": "ラベル:",
|
"label": "ラベル:",
|
||||||
|
"label_placeholder": "例: 貯金、マイニング...",
|
||||||
"language": "言語",
|
"language": "言語",
|
||||||
"light": "ライト",
|
"light": "ライト",
|
||||||
"loading": "読み込み中...",
|
"loading": "読み込み中...",
|
||||||
@@ -286,6 +368,7 @@
|
|||||||
"market_now": "現在",
|
"market_now": "現在",
|
||||||
"market_pct_shielded": "%.0f%% シールド済み",
|
"market_pct_shielded": "%.0f%% シールド済み",
|
||||||
"market_portfolio": "ポートフォリオ",
|
"market_portfolio": "ポートフォリオ",
|
||||||
|
"market_price_loading": "価格データを読み込み中...",
|
||||||
"market_price_unavailable": "価格データが利用できません",
|
"market_price_unavailable": "価格データが利用できません",
|
||||||
"market_refresh_price": "価格データを更新",
|
"market_refresh_price": "価格データを更新",
|
||||||
"market_trade_on": "%s で取引",
|
"market_trade_on": "%s で取引",
|
||||||
@@ -311,6 +394,13 @@
|
|||||||
"mining_address_copied": "マイニングアドレスをコピーしました",
|
"mining_address_copied": "マイニングアドレスをコピーしました",
|
||||||
"mining_all_time": "全期間",
|
"mining_all_time": "全期間",
|
||||||
"mining_already_saved": "プールURLは既に保存済みです",
|
"mining_already_saved": "プールURLは既に保存済みです",
|
||||||
|
"mining_benchmark_cancel": "ベンチマークをキャンセル",
|
||||||
|
"mining_benchmark_cooling": "クーリング",
|
||||||
|
"mining_benchmark_dismiss": "閉じる",
|
||||||
|
"mining_benchmark_result": "最適",
|
||||||
|
"mining_benchmark_stabilizing": "安定化中",
|
||||||
|
"mining_benchmark_testing": "テスト中",
|
||||||
|
"mining_benchmark_tooltip": "このCPUに最適なスレッド数を検出",
|
||||||
"mining_block_copied": "ブロックハッシュをコピーしました",
|
"mining_block_copied": "ブロックハッシュをコピーしました",
|
||||||
"mining_chart_1m_ago": "1分前",
|
"mining_chart_1m_ago": "1分前",
|
||||||
"mining_chart_5m_ago": "5分前",
|
"mining_chart_5m_ago": "5分前",
|
||||||
@@ -330,8 +420,15 @@
|
|||||||
"mining_filter_tip_all": "すべての収益を表示",
|
"mining_filter_tip_all": "すべての収益を表示",
|
||||||
"mining_filter_tip_pool": "プール収益のみ表示",
|
"mining_filter_tip_pool": "プール収益のみ表示",
|
||||||
"mining_filter_tip_solo": "ソロ収益のみ表示",
|
"mining_filter_tip_solo": "ソロ収益のみ表示",
|
||||||
|
"mining_generate_z_address_hint": "受信タブでZアドレスを生成して支払いアドレスとして使用してください",
|
||||||
|
"mining_idle_gpu_off_tooltip": "制限なし: オン\nキーボード/マウス入力のみがアイドル状態を決定\nGPU検出を有効にするにはクリック",
|
||||||
|
"mining_idle_gpu_on_tooltip": "GPU対応: オン\nGPUアクティビティ(動画、ゲーム)がアイドルマイニングを防止\n制限なしモードに切り替えるにはクリック",
|
||||||
"mining_idle_off_tooltip": "アイドルマイニングを有効にする",
|
"mining_idle_off_tooltip": "アイドルマイニングを有効にする",
|
||||||
"mining_idle_on_tooltip": "アイドルマイニングを無効にする",
|
"mining_idle_on_tooltip": "アイドルマイニングを無効にする",
|
||||||
|
"mining_idle_scale_off_tooltip": "開始/停止モード: オン\nスレッドスケーリングモードに切り替えるにはクリック",
|
||||||
|
"mining_idle_scale_on_tooltip": "スレッドスケーリング: オン\n開始/停止モードに切り替えるにはクリック",
|
||||||
|
"mining_idle_threads_active_tooltip": "ユーザーアクティブ時のスレッド数",
|
||||||
|
"mining_idle_threads_idle_tooltip": "システムアイドル時のスレッド数",
|
||||||
"mining_local_hashrate": "ローカルハッシュレート",
|
"mining_local_hashrate": "ローカルハッシュレート",
|
||||||
"mining_mine": "マイニング",
|
"mining_mine": "マイニング",
|
||||||
"mining_mining_addr": "マイニングアドレス",
|
"mining_mining_addr": "マイニングアドレス",
|
||||||
@@ -388,6 +485,7 @@
|
|||||||
"no_addresses_available": "利用可能なアドレスがありません",
|
"no_addresses_available": "利用可能なアドレスがありません",
|
||||||
"no_addresses_match": "フィルタに一致するアドレスがありません",
|
"no_addresses_match": "フィルタに一致するアドレスがありません",
|
||||||
"no_addresses_with_balance": "残高のあるアドレスがありません",
|
"no_addresses_with_balance": "残高のあるアドレスがありません",
|
||||||
|
"no_addresses_yet": "アドレスがまだありません",
|
||||||
"no_matching": "一致する取引がありません",
|
"no_matching": "一致する取引がありません",
|
||||||
"no_recent_receives": "最近の受信がありません",
|
"no_recent_receives": "最近の受信がありません",
|
||||||
"no_recent_sends": "最近の送信がありません",
|
"no_recent_sends": "最近の送信がありません",
|
||||||
@@ -453,6 +551,7 @@
|
|||||||
"peers_upper": "ピア",
|
"peers_upper": "ピア",
|
||||||
"peers_version": "バージョン",
|
"peers_version": "バージョン",
|
||||||
"pending": "保留中",
|
"pending": "保留中",
|
||||||
|
"pin_not_set": "PINが設定されていません。パスフレーズで解除してください。",
|
||||||
"ping": "Ping",
|
"ping": "Ping",
|
||||||
"price_chart": "価格チャート",
|
"price_chart": "価格チャート",
|
||||||
"qr_code": "QRコード",
|
"qr_code": "QRコード",
|
||||||
@@ -473,7 +572,9 @@
|
|||||||
"recent_received": "最近の受信",
|
"recent_received": "最近の受信",
|
||||||
"recent_sends": "最近の送信",
|
"recent_sends": "最近の送信",
|
||||||
"recipient": "受取人",
|
"recipient": "受取人",
|
||||||
|
"recipient_balance": "受取人: %.8f → %.8f DRGX",
|
||||||
"recv_type": "受信",
|
"recv_type": "受信",
|
||||||
|
"reduce_motion": "モーションを減らす",
|
||||||
"refresh": "更新",
|
"refresh": "更新",
|
||||||
"refresh_now": "今すぐ更新",
|
"refresh_now": "今すぐ更新",
|
||||||
"remove_favorite": "お気に入りを削除",
|
"remove_favorite": "お気に入りを削除",
|
||||||
@@ -493,7 +594,10 @@
|
|||||||
"request_uri_copied": "支払いURIをクリップボードにコピーしました",
|
"request_uri_copied": "支払いURIをクリップボードにコピーしました",
|
||||||
"rescan": "再スキャン",
|
"rescan": "再スキャン",
|
||||||
"reset_to_defaults": "デフォルトにリセット",
|
"reset_to_defaults": "デフォルトにリセット",
|
||||||
|
"restarting_after_encryption": "暗号化後にデーモンを再起動中...",
|
||||||
"restore_address": "アドレスを復元",
|
"restore_address": "アドレスを復元",
|
||||||
|
"result_preview": "結果プレビュー",
|
||||||
|
"retry": "再試行",
|
||||||
"review_send": "送金を確認",
|
"review_send": "送金を確認",
|
||||||
"rpc_host": "RPCホスト",
|
"rpc_host": "RPCホスト",
|
||||||
"rpc_pass": "パスワード",
|
"rpc_pass": "パスワード",
|
||||||
@@ -502,8 +606,55 @@
|
|||||||
"save": "保存",
|
"save": "保存",
|
||||||
"save_settings": "設定を保存",
|
"save_settings": "設定を保存",
|
||||||
"save_z_transactions": "Z取引を取引リストに保存",
|
"save_z_transactions": "Z取引を取引リストに保存",
|
||||||
|
"sb_auth_failed": "認証失敗 — rpcuser/rpcpassword を確認してください",
|
||||||
|
"sb_block": "ブロック: %d",
|
||||||
|
"sb_connecting_daemon": "dragonxd に接続中...",
|
||||||
|
"sb_connecting_err": "デーモンに接続中 — %s",
|
||||||
|
"sb_connecting_external": "外部デーモンに接続中...",
|
||||||
|
"sb_connecting_generic": "デーモンに接続中...",
|
||||||
|
"sb_daemon_crashed": "デーモンが %d 回クラッシュしました",
|
||||||
|
"sb_daemon_not_found": "デーモンが見つかりません",
|
||||||
|
"sb_dragonxd_running": "dragonxd 実行中",
|
||||||
|
"sb_dragonxd_stopped": "dragonxd 停止",
|
||||||
|
"sb_dragonxd_stopping": "dragonxd を停止中...",
|
||||||
|
"sb_extracting_sapling": "Sapling パラメータを展開中...",
|
||||||
|
"sb_importing_keys": "鍵をインポート中",
|
||||||
|
"sb_loading_config": "設定を読み込み中...",
|
||||||
|
"sb_mining_hs": "%.1f H/s",
|
||||||
|
"sb_net_ghs": "ネット: %.2f GH/s",
|
||||||
|
"sb_net_hs": "ネット: %.1f H/s",
|
||||||
|
"sb_net_khs": "ネット: %.2f KH/s",
|
||||||
|
"sb_net_mhs": "ネット: %.2f MH/s",
|
||||||
|
"sb_no_conf": "DRAGONX.conf が見つかりません",
|
||||||
|
"sb_peers": "ピア: %zu",
|
||||||
|
"sb_rescanning": "再スキャン",
|
||||||
|
"sb_rescanning_pct": "再スキャン %.0f%%",
|
||||||
|
"sb_restarting_daemon": "デーモンを再起動中...",
|
||||||
|
"sb_sapling_failed": "Sapling パラメータの展開に失敗しました。",
|
||||||
|
"sb_sapling_not_found": "Sapling パラメータが見つかりません。",
|
||||||
|
"sb_starting_daemon": "dragonxd を起動中...",
|
||||||
|
"sb_syncing_basic": "同期中 %.1f%% (残り %d)",
|
||||||
|
"sb_syncing_eta": "同期中 %.1f%% (残り %d, %.0f ブロック/秒, ~%s)",
|
||||||
|
"sb_waiting_config": "デーモン設定を待機中...",
|
||||||
|
"sb_waiting_daemon": "dragonxd を待機中...",
|
||||||
|
"sb_waiting_daemon_err": "dragonxd を待機中 — %s",
|
||||||
|
"sb_warming_up": "ウォームアップ中...",
|
||||||
"search_placeholder": "検索...",
|
"search_placeholder": "検索...",
|
||||||
"security": "セキュリティ",
|
"security": "セキュリティ",
|
||||||
|
"seed_backup_button": "シードフレーズ",
|
||||||
|
"seed_backup_close": "閉じる",
|
||||||
|
"seed_backup_copy": "コピー",
|
||||||
|
"seed_backup_intro": "これらの単語はウォレットのマスターバックアップです。これらを持つ人は誰でもあなたの資金を管理できます。",
|
||||||
|
"seed_backup_load_failed": "シードフレーズを読み込めませんでした。",
|
||||||
|
"seed_backup_loading": "シードフレーズを読み込み中...",
|
||||||
|
"seed_backup_locked": "シードフレーズを表示するにはウォレットのロックを解除してください。",
|
||||||
|
"seed_backup_none": "このウォレットはシードフレーズより前のものなので、24単語のフレーズはありません。代わりに「バックアップ...」で、または秘密鍵をエクスポートしてバックアップしてください。",
|
||||||
|
"seed_backup_reminder": "ウォレットには24単語の復元シードフレーズがあります。今すぐ 設定 → ノードとセキュリティ でバックアップしてください。",
|
||||||
|
"seed_backup_save": "ファイルに保存...",
|
||||||
|
"seed_backup_save_failed": "書き込めませんでした: ",
|
||||||
|
"seed_backup_saved": "保存先: ",
|
||||||
|
"seed_backup_title": "シードフレーズをバックアップ",
|
||||||
|
"seed_backup_warning": "順番どおりに書き写し、オフラインで保管してください。決して共有や撮影をしないでください。紛失すると資金を復元できません。",
|
||||||
"select_address": "アドレスを選択...",
|
"select_address": "アドレスを選択...",
|
||||||
"select_receiving_address": "受信アドレスを選択...",
|
"select_receiving_address": "受信アドレスを選択...",
|
||||||
"select_source_address": "送信元アドレスを選択...",
|
"select_source_address": "送信元アドレスを選択...",
|
||||||
@@ -553,12 +704,15 @@
|
|||||||
"send_valid_transparent": "有効な透明アドレス",
|
"send_valid_transparent": "有効な透明アドレス",
|
||||||
"send_wallet_empty": "ウォレットは空です",
|
"send_wallet_empty": "ウォレットは空です",
|
||||||
"send_yes_clear": "はい、クリア",
|
"send_yes_clear": "はい、クリア",
|
||||||
|
"sender_balance": "送信者: %.8f → %.8f DRGX",
|
||||||
"sending": "取引を送信中",
|
"sending": "取引を送信中",
|
||||||
"sending_from": "送信元",
|
"sending_from": "送信元",
|
||||||
|
"sends_full_balance_warning": "全残高を送信します。送信アドレスの残高はゼロになります。",
|
||||||
"sent": "送信済み",
|
"sent": "送信済み",
|
||||||
"sent_filter": "送信済み",
|
"sent_filter": "送信済み",
|
||||||
"sent_type": "送信済み",
|
"sent_type": "送信済み",
|
||||||
"sent_upper": "送信済み",
|
"sent_upper": "送信済み",
|
||||||
|
"set_label": "ラベルを設定...",
|
||||||
"settings": "設定",
|
"settings": "設定",
|
||||||
"settings_about_text": "DragonX (DRGX) 用のシールド暗号通貨ウォレット。Dear ImGui で構築された軽量でポータブルな体験。",
|
"settings_about_text": "DragonX (DRGX) 用のシールド暗号通貨ウォレット。Dear ImGui で構築された軽量でポータブルな体験。",
|
||||||
"settings_acrylic_level": "アクリルレベル:",
|
"settings_acrylic_level": "アクリルレベル:",
|
||||||
@@ -647,13 +801,17 @@
|
|||||||
"shield_utxo_limit": "UTXO制限:",
|
"shield_utxo_limit": "UTXO制限:",
|
||||||
"shield_wildcard_hint": "'*' を使用してすべての透明アドレスからシールド",
|
"shield_wildcard_hint": "'*' を使用してすべての透明アドレスからシールド",
|
||||||
"shielded": "シールド",
|
"shielded": "シールド",
|
||||||
|
"shielded_address": "シールドアドレス",
|
||||||
"shielded_to": "シールド先",
|
"shielded_to": "シールド先",
|
||||||
"shielded_type": "シールド",
|
"shielded_type": "シールド",
|
||||||
|
"shielding_notice": "注意:トランスペアレント (T) アドレスからプライベート (Z) アドレスへ資金をシールドします。",
|
||||||
"show": "表示",
|
"show": "表示",
|
||||||
"show_hidden": "非表示を表示 (%d)",
|
"show_hidden": "非表示を表示 (%d)",
|
||||||
"show_qr_code": "QRコードを表示",
|
"show_qr_code": "QRコードを表示",
|
||||||
"showing_transactions": "%dâ%d / %d 件の取引を表示中(合計:%zu)",
|
"showing_transactions": "%d–%d / %d 件の取引を表示中(合計:%zu)",
|
||||||
|
"showing_x_of_y": "%d / %d アドレスを表示",
|
||||||
"simple_background": "シンプル背景",
|
"simple_background": "シンプル背景",
|
||||||
|
"slider_off": "オフ",
|
||||||
"start_mining": "マイニング開始",
|
"start_mining": "マイニング開始",
|
||||||
"status": "ステータス",
|
"status": "ステータス",
|
||||||
"stop_external": "外部デーモンを停止",
|
"stop_external": "外部デーモンを停止",
|
||||||
@@ -662,17 +820,26 @@
|
|||||||
"success": "成功",
|
"success": "成功",
|
||||||
"summary": "概要",
|
"summary": "概要",
|
||||||
"syncing": "同期中...",
|
"syncing": "同期中...",
|
||||||
|
"t_address": "Tアドレス",
|
||||||
"t_addresses": "Tアドレス",
|
"t_addresses": "Tアドレス",
|
||||||
"test_connection": "テスト",
|
"test_connection": "テスト",
|
||||||
"theme": "テーマ",
|
"theme": "テーマ",
|
||||||
"theme_effects": "テーマ効果",
|
"theme_effects": "テーマ効果",
|
||||||
|
"theme_language": "テーマと言語",
|
||||||
"time_days_ago": "%d日前",
|
"time_days_ago": "%d日前",
|
||||||
"time_hours_ago": "%d時間前",
|
"time_hours_ago": "%d時間前",
|
||||||
"time_minutes_ago": "%d分前",
|
"time_minutes_ago": "%d分前",
|
||||||
"time_seconds_ago": "%d秒前",
|
"time_seconds_ago": "%d秒前",
|
||||||
|
"timeout_15min": "15分",
|
||||||
|
"timeout_1hour": "1時間",
|
||||||
|
"timeout_1min": "1分",
|
||||||
|
"timeout_30min": "30分",
|
||||||
|
"timeout_5min": "5分",
|
||||||
|
"timeout_off": "オフ",
|
||||||
"to": "宛先",
|
"to": "宛先",
|
||||||
"to_upper": "宛先",
|
"to_upper": "宛先",
|
||||||
"tools": "ツール",
|
"tools": "ツール",
|
||||||
|
"tools_actions": "ツールとアクション...",
|
||||||
"total": "合計",
|
"total": "合計",
|
||||||
"transaction_id": "取引ID",
|
"transaction_id": "取引ID",
|
||||||
"transaction_sent": "取引の送信に成功しました",
|
"transaction_sent": "取引の送信に成功しました",
|
||||||
@@ -680,7 +847,13 @@
|
|||||||
"transaction_url": "取引URL",
|
"transaction_url": "取引URL",
|
||||||
"transactions": "取引",
|
"transactions": "取引",
|
||||||
"transactions_upper": "取引",
|
"transactions_upper": "取引",
|
||||||
|
"transfer_failed": "送金失敗",
|
||||||
|
"transfer_funds": "資金を送金",
|
||||||
|
"transfer_sent": "送金完了",
|
||||||
|
"transfer_sent_desc": "送金がネットワークに送信されました。",
|
||||||
|
"transfer_to": "送金先:",
|
||||||
"transparent": "透明",
|
"transparent": "透明",
|
||||||
|
"transparent_address": "トランスペアレントアドレス",
|
||||||
"tt_addr_url": "ブロックエクスプローラーでアドレスを表示するためのベース URL",
|
"tt_addr_url": "ブロックエクスプローラーでアドレスを表示するためのベース URL",
|
||||||
"tt_address_book": "クイック送信用の保存済みアドレスを管理",
|
"tt_address_book": "クイック送信用の保存済みアドレスを管理",
|
||||||
"tt_auto_lock": "この無操作時間後にウォレットをロック",
|
"tt_auto_lock": "この無操作時間後にウォレットをロック",
|
||||||
@@ -695,6 +868,8 @@
|
|||||||
"tt_custom_theme": "カスタムテーマがアクティブ",
|
"tt_custom_theme": "カスタムテーマがアクティブ",
|
||||||
"tt_debug_collapse": "デバッグログオプションを折りたたむ",
|
"tt_debug_collapse": "デバッグログオプションを折りたたむ",
|
||||||
"tt_debug_expand": "デバッグログオプションを展開",
|
"tt_debug_expand": "デバッグログオプションを展開",
|
||||||
|
"tt_delete_blockchain": "すべてのブロックチェーンデータを削除して新規同期を開始します。wallet.dat と設定は保持されます。",
|
||||||
|
"tt_download_bootstrap": "ブロックチェーンブートストラップをダウンロードして同期を高速化\n既存のブロックデータは置き換えられます",
|
||||||
"tt_encrypt": "パスフレーズで wallet.dat を暗号化",
|
"tt_encrypt": "パスフレーズで wallet.dat を暗号化",
|
||||||
"tt_export_all": "すべての秘密鍵をファイルにエクスポート",
|
"tt_export_all": "すべての秘密鍵をファイルにエクスポート",
|
||||||
"tt_export_csv": "トランザクション履歴を CSV スプレッドシートとしてエクスポート",
|
"tt_export_csv": "トランザクション履歴を CSV スプレッドシートとしてエクスポート",
|
||||||
@@ -712,6 +887,7 @@
|
|||||||
"tt_mine_idle": "システムがアイドル状態(キーボード/マウス入力なし)\\nのとき自動的にマイニングを開始",
|
"tt_mine_idle": "システムがアイドル状態(キーボード/マウス入力なし)\\nのとき自動的にマイニングを開始",
|
||||||
"tt_noise": "グレインテクスチャ強度(0%% = オフ、100%% = 最大)",
|
"tt_noise": "グレインテクスチャ強度(0%% = オフ、100%% = 最大)",
|
||||||
"tt_open_dir": "クリックしてファイルエクスプローラーで開く",
|
"tt_open_dir": "クリックしてファイルエクスプローラーで開く",
|
||||||
|
"tt_reduce_motion": "アクセシビリティのためにアニメーション遷移と残高補間を無効にする",
|
||||||
"tt_remove_encrypt": "暗号化を解除してウォレットを保護なしで保存",
|
"tt_remove_encrypt": "暗号化を解除してウォレットを保護なしで保存",
|
||||||
"tt_remove_pin": "PIN を削除しアンロックにパスフレーズを要求",
|
"tt_remove_pin": "PIN を削除しアンロックにパスフレーズを要求",
|
||||||
"tt_report_bug": "プロジェクトトラッカーで問題を報告",
|
"tt_report_bug": "プロジェクトトラッカーで問題を報告",
|
||||||
@@ -727,6 +903,7 @@
|
|||||||
"tt_save_ztx": "z-address トランザクション履歴をローカルに保存して高速読み込み",
|
"tt_save_ztx": "z-address トランザクション履歴をローカルに保存して高速読み込み",
|
||||||
"tt_scan_themes": "新しいテーマをスキャン。\\nテーマフォルダーをここに配置:\\n%s",
|
"tt_scan_themes": "新しいテーマをスキャン。\\nテーマフォルダーをここに配置:\\n%s",
|
||||||
"tt_scanline": "コンソールでの CRT スキャンライン効果",
|
"tt_scanline": "コンソールでの CRT スキャンライン効果",
|
||||||
|
"tt_seed_backup": "ウォレットの24単語の復元シードフレーズを表示してバックアップします",
|
||||||
"tt_set_pin": "クイックアンロック用の 4-8 桁 PIN を設定",
|
"tt_set_pin": "クイックアンロック用の 4-8 桁 PIN を設定",
|
||||||
"tt_shield_mining": "透明マイニング報酬をシールドアドレスに移動",
|
"tt_shield_mining": "透明マイニング報酬をシールドアドレスに移動",
|
||||||
"tt_simple_bg": "背景にシンプルなグラデーションを使用\\nホットキー:Ctrl+Up",
|
"tt_simple_bg": "背景にシンプルなグラデーションを使用\\nホットキー:Ctrl+Up",
|
||||||
@@ -789,7 +966,9 @@
|
|||||||
"warning_upper": "警告!",
|
"warning_upper": "警告!",
|
||||||
"website": "ウェブサイト",
|
"website": "ウェブサイト",
|
||||||
"window_opacity": "ウィンドウ透明度",
|
"window_opacity": "ウィンドウ透明度",
|
||||||
|
"wizard_daemon_start_failed": "デーモンの起動に失敗しました — 自動的に再試行されます",
|
||||||
"yes_clear": "はい、クリア",
|
"yes_clear": "はい、クリア",
|
||||||
"your_addresses": "あなたのアドレス",
|
"your_addresses": "あなたのアドレス",
|
||||||
|
"z_address": "Zアドレス",
|
||||||
"z_addresses": "Zアドレス"
|
"z_addresses": "Zアドレス"
|
||||||
}
|
}
|
||||||
|
|||||||
181
res/lang/ko.json
181
res/lang/ko.json
@@ -43,6 +43,8 @@
|
|||||||
"address_url": "주소 URL",
|
"address_url": "주소 URL",
|
||||||
"addresses_appear_here": "연결 후 수신 주소가 여기에 표시됩니다.",
|
"addresses_appear_here": "연결 후 수신 주소가 여기에 표시됩니다.",
|
||||||
"advanced": "고급 설정",
|
"advanced": "고급 설정",
|
||||||
|
"advanced_effects": "고급 효과...",
|
||||||
|
"ago": "전",
|
||||||
"all_filter": "전체",
|
"all_filter": "전체",
|
||||||
"allow_custom_fees": "사용자 정의 수수료 허용",
|
"allow_custom_fees": "사용자 정의 수수료 허용",
|
||||||
"amount": "금액",
|
"amount": "금액",
|
||||||
@@ -90,12 +92,52 @@
|
|||||||
"block_timestamp": "타임스탬프:",
|
"block_timestamp": "타임스탬프:",
|
||||||
"block_transactions": "트랜잭션:",
|
"block_transactions": "트랜잭션:",
|
||||||
"blockchain_syncing": "블록체인 동기화 중 (%.1f%%)... 잔액이 정확하지 않을 수 있습니다.",
|
"blockchain_syncing": "블록체인 동기화 중 (%.1f%%)... 잔액이 정확하지 않을 수 있습니다.",
|
||||||
|
"bootstrap_daemon_running": "데몬 실행 중",
|
||||||
|
"bootstrap_daemon_stopped": "데몬 중지됨",
|
||||||
|
"bootstrap_daemon_stopping": "데몬 중지 중...",
|
||||||
|
"bootstrap_desc": "블록체인 부트스트랩을 다운로드하여 초기 동기화를 대폭 가속합니다. 블록체인 스냅샷을 다운로드하고 데이터 디렉토리에 추출합니다.",
|
||||||
|
"bootstrap_downloading": "부트스트랩 다운로드 중...",
|
||||||
|
"bootstrap_extracting": "블록체인 데이터 추출 중...",
|
||||||
|
"bootstrap_failed": "부트스트랩 실패",
|
||||||
|
"bootstrap_mirror": "미러",
|
||||||
|
"bootstrap_mirror_tooltip": "미러에서 다운로드 (bootstrap2.dragonx.is).\n메인 다운로드가 느리거나 실패할 경우 사용하세요.",
|
||||||
|
"bootstrap_restart_daemon": "데몬 재시작",
|
||||||
|
"bootstrap_success": "부트스트랩 완료",
|
||||||
|
"bootstrap_success_desc": "블록체인 데이터가 성공적으로 추출되었습니다. 데몬을 시작하여 부트스트랩 지점부터 동기화를 시작하세요.",
|
||||||
|
"bootstrap_trust_warning": "bootstrap.dragonx.is 또는 bootstrap2.dragonx.is만 사용하세요. 신뢰할 수 없는 출처의 파일을 사용하면 노드가 손상될 수 있습니다.",
|
||||||
|
"bootstrap_verifying": "체크섬 확인 중...",
|
||||||
|
"bootstrap_wallet_protected": "(wallet.dat 보호됨)",
|
||||||
|
"bootstrap_warning": "기존 블록 데이터(blocks, chainstate, notarizations)가 삭제되고 교체됩니다. wallet.dat는 수정되거나 삭제되지 않습니다.",
|
||||||
"cancel": "취소",
|
"cancel": "취소",
|
||||||
"characters": "문자",
|
"characters": "문자",
|
||||||
|
"chat": "채팅",
|
||||||
|
"chat_cancel": "취소",
|
||||||
|
"chat_contact_request": "연락 요청",
|
||||||
|
"chat_empty_hint": "아직 대화가 없습니다. 받은 메시지가 여기에 표시됩니다.",
|
||||||
|
"chat_locked_hint": "채팅을 불러오려면 지갑 잠금을 해제하세요.",
|
||||||
|
"chat_new_button": "새 대화",
|
||||||
|
"chat_new_message": "메시지",
|
||||||
|
"chat_new_send": "요청 보내기",
|
||||||
|
"chat_new_title": "새 대화",
|
||||||
|
"chat_new_zaddr": "받는 사람 z-주소",
|
||||||
|
"chat_select_hint": "볼 대화를 선택하세요.",
|
||||||
|
"chat_send": "전송",
|
||||||
|
"chat_send_failed": "전송 안 됨",
|
||||||
|
"chat_toast_compose_failed": "메시지를 작성할 수 없습니다 (너무 긴가요?).",
|
||||||
|
"chat_toast_lite_busy": "이미 전송이 진행 중이거나 열린 지갑이 없습니다.",
|
||||||
|
"chat_toast_no_zaddr": "채팅을 보낼 z-주소가 없습니다.",
|
||||||
|
"chat_toast_not_connected": "연결되지 않음 — 채팅 메시지가 전송되지 않았습니다.",
|
||||||
|
"chat_toast_request_compose_failed": "연락 요청을 작성할 수 없습니다 (잘못된 주소 / 텍스트?).",
|
||||||
|
"chat_toast_request_queued": "연락 요청이 대기열에 추가되었습니다.",
|
||||||
|
"chat_toast_waiting_reply": "상대방이 답장해야 메시지를 보낼 수 있습니다.",
|
||||||
|
"chat_waiting_reply": "상대방의 답장을 기다리는 중입니다 — 답장하면 메시지를 보낼 수 있습니다.",
|
||||||
|
"chat_you": "나",
|
||||||
|
"choose_icon": "아이콘 선택",
|
||||||
"clear": "지우기",
|
"clear": "지우기",
|
||||||
"clear_all_bans": "모든 차단 해제",
|
"clear_all_bans": "모든 차단 해제",
|
||||||
"clear_anyway": "그래도 삭제",
|
"clear_anyway": "그래도 삭제",
|
||||||
"clear_form_confirm": "모든 양식 필드를 지우시겠습니까?",
|
"clear_form_confirm": "모든 양식 필드를 지우시겠습니까?",
|
||||||
|
"clear_icon": "아이콘 지우기",
|
||||||
"clear_request": "요청 지우기",
|
"clear_request": "요청 지우기",
|
||||||
"click_copy_address": "클릭하여 주소 복사",
|
"click_copy_address": "클릭하여 주소 복사",
|
||||||
"click_copy_uri": "클릭하여 URI 복사",
|
"click_copy_uri": "클릭하여 URI 복사",
|
||||||
@@ -106,8 +148,12 @@
|
|||||||
"confirm_clear_ztx_title": "Z-Tx 기록 삭제 확인",
|
"confirm_clear_ztx_title": "Z-Tx 기록 삭제 확인",
|
||||||
"confirm_clear_ztx_warning1": "z-트랜잭션 기록을 삭제하면 지갑 재스캔이 수행될 때까지 차폐 잔액이 0으로 표시될 수 있습니다.",
|
"confirm_clear_ztx_warning1": "z-트랜잭션 기록을 삭제하면 지갑 재스캔이 수행될 때까지 차폐 잔액이 0으로 표시될 수 있습니다.",
|
||||||
"confirm_clear_ztx_warning2": "이런 경우, 잔액을 복구하려면 재스캔을 활성화하여 z-주소 개인키를 다시 가져와야 합니다.",
|
"confirm_clear_ztx_warning2": "이런 경우, 잔액을 복구하려면 재스캔을 활성화하여 z-주소 개인키를 다시 가져와야 합니다.",
|
||||||
|
"confirm_delete_blockchain_msg": "데몬을 중지하고 모든 블록체인 데이터(blocks, chainstate, peers)를 삭제한 후 처음부터 다시 동기화합니다. 몇 시간이 걸릴 수 있습니다.",
|
||||||
|
"confirm_delete_blockchain_safe": "wallet.dat, 설정 및 거래 내역은 안전하며 삭제되지 않습니다.",
|
||||||
|
"confirm_delete_blockchain_title": "블록체인 데이터 삭제",
|
||||||
"confirm_send": "전송 확인",
|
"confirm_send": "전송 확인",
|
||||||
"confirm_transaction": "거래 확인",
|
"confirm_transaction": "거래 확인",
|
||||||
|
"confirm_transfer": "이체 확인",
|
||||||
"confirmations": "확인 수",
|
"confirmations": "확인 수",
|
||||||
"confirmations_display": "%d 확인 | %s",
|
"confirmations_display": "%d 확인 | %s",
|
||||||
"confirmed": "확인됨",
|
"confirmed": "확인됨",
|
||||||
@@ -172,6 +218,7 @@
|
|||||||
"console_welcome": "ObsidianDragon 콘솔에 오신 것을 환영합니다",
|
"console_welcome": "ObsidianDragon 콘솔에 오신 것을 환영합니다",
|
||||||
"console_zoom_in": "확대",
|
"console_zoom_in": "확대",
|
||||||
"console_zoom_out": "축소",
|
"console_zoom_out": "축소",
|
||||||
|
"copied": "복사됨!",
|
||||||
"copy": "복사",
|
"copy": "복사",
|
||||||
"copy_address": "전체 주소 복사",
|
"copy_address": "전체 주소 복사",
|
||||||
"copy_error": "오류 복사",
|
"copy_error": "오류 복사",
|
||||||
@@ -180,15 +227,21 @@
|
|||||||
"copy_uri": "URI 복사",
|
"copy_uri": "URI 복사",
|
||||||
"current_price": "현재 가격",
|
"current_price": "현재 가격",
|
||||||
"custom_fees": "사용자 정의 수수료",
|
"custom_fees": "사용자 정의 수수료",
|
||||||
|
"daemon_version": "데몬",
|
||||||
"dark": "다크",
|
"dark": "다크",
|
||||||
"date": "날짜",
|
"date": "날짜",
|
||||||
"date_label": "날짜:",
|
"date_label": "날짜:",
|
||||||
"debug_logging": "디버그 로깅",
|
"debug_logging": "디버그 로깅",
|
||||||
"delete": "삭제",
|
"delete": "삭제",
|
||||||
|
"delete_blockchain": "블록체인 삭제",
|
||||||
|
"delete_blockchain_confirm": "삭제 후 재동기화",
|
||||||
|
"deshielding_warning": "경고: 프라이빗 (Z) 주소에서 투명 (T) 주소로 자금을 디실딩합니다.",
|
||||||
"difficulty": "난이도",
|
"difficulty": "난이도",
|
||||||
"disconnected": "연결 끊김",
|
"disconnected": "연결 끊김",
|
||||||
"dismiss": "닫기",
|
"dismiss": "닫기",
|
||||||
"display": "디스플레이",
|
"display": "디스플레이",
|
||||||
|
"download": "다운로드",
|
||||||
|
"download_bootstrap": "부트스트랩 다운로드",
|
||||||
"dragonx_green": "DragonX(그린)",
|
"dragonx_green": "DragonX(그린)",
|
||||||
"edit": "편집",
|
"edit": "편집",
|
||||||
"error": "오류",
|
"error": "오류",
|
||||||
@@ -196,6 +249,23 @@
|
|||||||
"est_time_to_block": "예상 블록 시간",
|
"est_time_to_block": "예상 블록 시간",
|
||||||
"exit": "종료",
|
"exit": "종료",
|
||||||
"explorer": "탐색기",
|
"explorer": "탐색기",
|
||||||
|
"explorer_block_detail": "블록",
|
||||||
|
"explorer_block_hash": "해시",
|
||||||
|
"explorer_block_height": "높이",
|
||||||
|
"explorer_block_merkle": "머클 루트",
|
||||||
|
"explorer_block_size": "크기",
|
||||||
|
"explorer_block_time": "시간",
|
||||||
|
"explorer_block_txs": "트랜잭션",
|
||||||
|
"explorer_chain_stats": "체인",
|
||||||
|
"explorer_invalid_query": "블록 높이 또는 64자 해시를 입력하세요",
|
||||||
|
"explorer_mempool": "멤풀",
|
||||||
|
"explorer_mempool_size": "크기",
|
||||||
|
"explorer_mempool_txs": "트랜잭션",
|
||||||
|
"explorer_recent_blocks": "최근 블록",
|
||||||
|
"explorer_search": "검색",
|
||||||
|
"explorer_section": "탐색기",
|
||||||
|
"explorer_tx_outputs": "출력",
|
||||||
|
"explorer_tx_size": "크기",
|
||||||
"export": "내보내기",
|
"export": "내보내기",
|
||||||
"export_csv": "CSV 내보내기",
|
"export_csv": "CSV 내보내기",
|
||||||
"export_keys_btn": "키 내보내기",
|
"export_keys_btn": "키 내보내기",
|
||||||
@@ -224,14 +294,22 @@
|
|||||||
"fetch_prices": "가격 조회",
|
"fetch_prices": "가격 조회",
|
||||||
"file": "파일",
|
"file": "파일",
|
||||||
"file_save_location": "파일 저장 위치: ~/.config/ObsidianDragon/",
|
"file_save_location": "파일 저장 위치: ~/.config/ObsidianDragon/",
|
||||||
|
"filter": "필터...",
|
||||||
"font_scale": "글꼴 크기",
|
"font_scale": "글꼴 크기",
|
||||||
|
"force_quit": "강제 종료",
|
||||||
|
"force_quit_confirm_msg": "정상 종료 없이 데몬을 즉시 종료합니다.\n블록체인 인덱스가 손상되어 재동기화가 필요할 수 있습니다.",
|
||||||
|
"force_quit_confirm_title": "강제 종료하시겠습니까?",
|
||||||
|
"force_quit_warning": "정상 종료 없이 데몬을 즉시 종료합니다. 블록체인 재동기화가 필요할 수 있습니다.",
|
||||||
|
"force_quit_yes": "강제 종료",
|
||||||
"from": "보낸 곳",
|
"from": "보낸 곳",
|
||||||
"from_upper": "보낸 곳",
|
"from_upper": "보낸 곳",
|
||||||
"full_details": "전체 세부 정보",
|
"full_details": "전체 세부 정보",
|
||||||
"general": "일반",
|
"general": "일반",
|
||||||
|
"generating": "생성 중",
|
||||||
"go_to_receive": "수신으로 이동",
|
"go_to_receive": "수신으로 이동",
|
||||||
"height": "높이",
|
"height": "높이",
|
||||||
"help": "도움말",
|
"help": "도움말",
|
||||||
|
"hidden_tag": " (숨김)",
|
||||||
"hide": "숨기기",
|
"hide": "숨기기",
|
||||||
"hide_address": "주소 숨기기",
|
"hide_address": "주소 숨기기",
|
||||||
"hide_zero_balances": "잔액 0 숨기기",
|
"hide_zero_balances": "잔액 0 숨기기",
|
||||||
@@ -253,6 +331,9 @@
|
|||||||
"import_key_warning": "경고: 개인 키를 절대 공유하지 마세요! 신뢰할 수 없는 소스의 키를 가져오면 지갑이 위험해질 수 있습니다.",
|
"import_key_warning": "경고: 개인 키를 절대 공유하지 마세요! 신뢰할 수 없는 소스의 키를 가져오면 지갑이 위험해질 수 있습니다.",
|
||||||
"import_key_z_format": "Z 주소 지출 키 (secret-extended-key-...)",
|
"import_key_z_format": "Z 주소 지출 키 (secret-extended-key-...)",
|
||||||
"import_private_key": "개인 키 가져오기...",
|
"import_private_key": "개인 키 가져오기...",
|
||||||
|
"incorrect_passphrase": "잘못된 암호",
|
||||||
|
"incorrect_pin": "잘못된 PIN",
|
||||||
|
"insufficient_funds": "이 금액과 수수료를 위한 잔액이 부족합니다.",
|
||||||
"invalid_address": "잘못된 주소 형식",
|
"invalid_address": "잘못된 주소 형식",
|
||||||
"ip_address": "IP 주소",
|
"ip_address": "IP 주소",
|
||||||
"keep": "유지",
|
"keep": "유지",
|
||||||
@@ -266,6 +347,7 @@
|
|||||||
"key_export_viewing_keys_zonly": "보기 키는 차폐 (z) 주소에만 사용할 수 있습니다",
|
"key_export_viewing_keys_zonly": "보기 키는 차폐 (z) 주소에만 사용할 수 있습니다",
|
||||||
"key_export_viewing_warning": "이 조회 키를 사용하면 다른 사람이 수신 거래와 잔액을 볼 수 있지만 자금을 사용할 수는 없습니다. 신뢰할 수 있는 사람에게만 공유하세요.",
|
"key_export_viewing_warning": "이 조회 키를 사용하면 다른 사람이 수신 거래와 잔액을 볼 수 있지만 자금을 사용할 수는 없습니다. 신뢰할 수 있는 사람에게만 공유하세요.",
|
||||||
"label": "라벨:",
|
"label": "라벨:",
|
||||||
|
"label_placeholder": "예: 저축, 채굴...",
|
||||||
"language": "언어",
|
"language": "언어",
|
||||||
"light": "라이트",
|
"light": "라이트",
|
||||||
"loading": "로딩 중...",
|
"loading": "로딩 중...",
|
||||||
@@ -286,6 +368,7 @@
|
|||||||
"market_now": "현재",
|
"market_now": "현재",
|
||||||
"market_pct_shielded": "%.0f%% 차폐됨",
|
"market_pct_shielded": "%.0f%% 차폐됨",
|
||||||
"market_portfolio": "포트폴리오",
|
"market_portfolio": "포트폴리오",
|
||||||
|
"market_price_loading": "가격 데이터를 불러오는 중...",
|
||||||
"market_price_unavailable": "가격 데이터를 사용할 수 없습니다",
|
"market_price_unavailable": "가격 데이터를 사용할 수 없습니다",
|
||||||
"market_refresh_price": "가격 데이터 새로고침",
|
"market_refresh_price": "가격 데이터 새로고침",
|
||||||
"market_trade_on": "%s에서 거래",
|
"market_trade_on": "%s에서 거래",
|
||||||
@@ -311,6 +394,13 @@
|
|||||||
"mining_address_copied": "채굴 주소가 복사되었습니다",
|
"mining_address_copied": "채굴 주소가 복사되었습니다",
|
||||||
"mining_all_time": "전체 기간",
|
"mining_all_time": "전체 기간",
|
||||||
"mining_already_saved": "풀 URL이 이미 저장되어 있습니다",
|
"mining_already_saved": "풀 URL이 이미 저장되어 있습니다",
|
||||||
|
"mining_benchmark_cancel": "벤치마크 취소",
|
||||||
|
"mining_benchmark_cooling": "쿨링",
|
||||||
|
"mining_benchmark_dismiss": "닫기",
|
||||||
|
"mining_benchmark_result": "최적",
|
||||||
|
"mining_benchmark_stabilizing": "안정화 중",
|
||||||
|
"mining_benchmark_testing": "테스트 중",
|
||||||
|
"mining_benchmark_tooltip": "이 CPU에 최적의 스레드 수 찾기",
|
||||||
"mining_block_copied": "블록 해시가 복사되었습니다",
|
"mining_block_copied": "블록 해시가 복사되었습니다",
|
||||||
"mining_chart_1m_ago": "1분 전",
|
"mining_chart_1m_ago": "1분 전",
|
||||||
"mining_chart_5m_ago": "5분 전",
|
"mining_chart_5m_ago": "5분 전",
|
||||||
@@ -330,8 +420,15 @@
|
|||||||
"mining_filter_tip_all": "모든 수익 표시",
|
"mining_filter_tip_all": "모든 수익 표시",
|
||||||
"mining_filter_tip_pool": "풀 수익만 표시",
|
"mining_filter_tip_pool": "풀 수익만 표시",
|
||||||
"mining_filter_tip_solo": "솔로 수익만 표시",
|
"mining_filter_tip_solo": "솔로 수익만 표시",
|
||||||
|
"mining_generate_z_address_hint": "수신 탭에서 Z 주소를 생성하여 지급 주소로 사용하세요",
|
||||||
|
"mining_idle_gpu_off_tooltip": "무제한: 켜짐\n키보드/마우스 입력만 유휴 상태를 결정\nGPU 감지를 활성화하려면 클릭",
|
||||||
|
"mining_idle_gpu_on_tooltip": "GPU 감지: 켜짐\nGPU 활동(비디오, 게임)이 유휴 채굴을 방지\n무제한 모드로 전환하려면 클릭",
|
||||||
"mining_idle_off_tooltip": "유휴 채굴 활성화",
|
"mining_idle_off_tooltip": "유휴 채굴 활성화",
|
||||||
"mining_idle_on_tooltip": "유휴 채굴 비활성화",
|
"mining_idle_on_tooltip": "유휴 채굴 비활성화",
|
||||||
|
"mining_idle_scale_off_tooltip": "시작/중지 모드: 켜짐\n스레드 스케일링 모드로 전환하려면 클릭",
|
||||||
|
"mining_idle_scale_on_tooltip": "스레드 스케일링: 켜짐\n시작/중지 모드로 전환하려면 클릭",
|
||||||
|
"mining_idle_threads_active_tooltip": "사용자 활성 시 스레드",
|
||||||
|
"mining_idle_threads_idle_tooltip": "시스템 유휴 시 스레드",
|
||||||
"mining_local_hashrate": "로컬 해시레이트",
|
"mining_local_hashrate": "로컬 해시레이트",
|
||||||
"mining_mine": "채굴",
|
"mining_mine": "채굴",
|
||||||
"mining_mining_addr": "채굴 주소",
|
"mining_mining_addr": "채굴 주소",
|
||||||
@@ -388,6 +485,7 @@
|
|||||||
"no_addresses_available": "사용 가능한 주소 없음",
|
"no_addresses_available": "사용 가능한 주소 없음",
|
||||||
"no_addresses_match": "필터와 일치하는 주소가 없습니다",
|
"no_addresses_match": "필터와 일치하는 주소가 없습니다",
|
||||||
"no_addresses_with_balance": "잔액이 있는 주소가 없습니다",
|
"no_addresses_with_balance": "잔액이 있는 주소가 없습니다",
|
||||||
|
"no_addresses_yet": "아직 주소가 없습니다",
|
||||||
"no_matching": "일치하는 거래가 없습니다",
|
"no_matching": "일치하는 거래가 없습니다",
|
||||||
"no_recent_receives": "최근 수신 내역 없음",
|
"no_recent_receives": "최근 수신 내역 없음",
|
||||||
"no_recent_sends": "최근 전송 내역 없음",
|
"no_recent_sends": "최근 전송 내역 없음",
|
||||||
@@ -453,6 +551,7 @@
|
|||||||
"peers_upper": "피어",
|
"peers_upper": "피어",
|
||||||
"peers_version": "버전",
|
"peers_version": "버전",
|
||||||
"pending": "대기 중",
|
"pending": "대기 중",
|
||||||
|
"pin_not_set": "PIN이 설정되지 않았습니다. 암호를 사용하여 잠금 해제하세요.",
|
||||||
"ping": "Ping",
|
"ping": "Ping",
|
||||||
"price_chart": "가격 차트",
|
"price_chart": "가격 차트",
|
||||||
"qr_code": "QR 코드",
|
"qr_code": "QR 코드",
|
||||||
@@ -473,7 +572,9 @@
|
|||||||
"recent_received": "최근 수신",
|
"recent_received": "최근 수신",
|
||||||
"recent_sends": "최근 전송",
|
"recent_sends": "최근 전송",
|
||||||
"recipient": "수신자",
|
"recipient": "수신자",
|
||||||
|
"recipient_balance": "수신자: %.8f → %.8f DRGX",
|
||||||
"recv_type": "수신",
|
"recv_type": "수신",
|
||||||
|
"reduce_motion": "모션 줄이기",
|
||||||
"refresh": "새로고침",
|
"refresh": "새로고침",
|
||||||
"refresh_now": "지금 새로고침",
|
"refresh_now": "지금 새로고침",
|
||||||
"remove_favorite": "즐겨찾기 제거",
|
"remove_favorite": "즐겨찾기 제거",
|
||||||
@@ -493,7 +594,10 @@
|
|||||||
"request_uri_copied": "결제 URI가 클립보드에 복사되었습니다",
|
"request_uri_copied": "결제 URI가 클립보드에 복사되었습니다",
|
||||||
"rescan": "재스캔",
|
"rescan": "재스캔",
|
||||||
"reset_to_defaults": "기본값으로 재설정",
|
"reset_to_defaults": "기본값으로 재설정",
|
||||||
|
"restarting_after_encryption": "암호화 후 데몬 재시작 중...",
|
||||||
"restore_address": "주소 복원",
|
"restore_address": "주소 복원",
|
||||||
|
"result_preview": "결과 미리보기",
|
||||||
|
"retry": "재시도",
|
||||||
"review_send": "전송 검토",
|
"review_send": "전송 검토",
|
||||||
"rpc_host": "RPC 호스트",
|
"rpc_host": "RPC 호스트",
|
||||||
"rpc_pass": "비밀번호",
|
"rpc_pass": "비밀번호",
|
||||||
@@ -502,8 +606,55 @@
|
|||||||
"save": "저장",
|
"save": "저장",
|
||||||
"save_settings": "설정 저장",
|
"save_settings": "설정 저장",
|
||||||
"save_z_transactions": "Z 거래를 거래 목록에 저장",
|
"save_z_transactions": "Z 거래를 거래 목록에 저장",
|
||||||
|
"sb_auth_failed": "인증 실패 — rpcuser/rpcpassword를 확인하세요",
|
||||||
|
"sb_block": "블록: %d",
|
||||||
|
"sb_connecting_daemon": "dragonxd에 연결 중...",
|
||||||
|
"sb_connecting_err": "데몬 연결 중 — %s",
|
||||||
|
"sb_connecting_external": "외부 데몬에 연결 중...",
|
||||||
|
"sb_connecting_generic": "데몬에 연결 중...",
|
||||||
|
"sb_daemon_crashed": "데몬이 %d회 충돌함",
|
||||||
|
"sb_daemon_not_found": "데몬을 찾을 수 없음",
|
||||||
|
"sb_dragonxd_running": "dragonxd 실행 중",
|
||||||
|
"sb_dragonxd_stopped": "dragonxd 중지됨",
|
||||||
|
"sb_dragonxd_stopping": "dragonxd 중지 중...",
|
||||||
|
"sb_extracting_sapling": "Sapling 매개변수 추출 중...",
|
||||||
|
"sb_importing_keys": "키 가져오기 중",
|
||||||
|
"sb_loading_config": "설정 불러오는 중...",
|
||||||
|
"sb_mining_hs": "%.1f H/s",
|
||||||
|
"sb_net_ghs": "네트: %.2f GH/s",
|
||||||
|
"sb_net_hs": "네트: %.1f H/s",
|
||||||
|
"sb_net_khs": "네트: %.2f KH/s",
|
||||||
|
"sb_net_mhs": "네트: %.2f MH/s",
|
||||||
|
"sb_no_conf": "DRAGONX.conf를 찾을 수 없음",
|
||||||
|
"sb_peers": "피어: %zu",
|
||||||
|
"sb_rescanning": "재스캔",
|
||||||
|
"sb_rescanning_pct": "재스캔 %.0f%%",
|
||||||
|
"sb_restarting_daemon": "데몬 재시작 중...",
|
||||||
|
"sb_sapling_failed": "Sapling 매개변수 추출 실패.",
|
||||||
|
"sb_sapling_not_found": "Sapling 매개변수를 찾을 수 없음.",
|
||||||
|
"sb_starting_daemon": "dragonxd 시작 중...",
|
||||||
|
"sb_syncing_basic": "동기화 %.1f%% (%d 남음)",
|
||||||
|
"sb_syncing_eta": "동기화 %.1f%% (%d 남음, %.0f 블록/초, ~%s)",
|
||||||
|
"sb_waiting_config": "데몬 설정 대기 중...",
|
||||||
|
"sb_waiting_daemon": "dragonxd 대기 중...",
|
||||||
|
"sb_waiting_daemon_err": "dragonxd 대기 중 — %s",
|
||||||
|
"sb_warming_up": "워밍업 중...",
|
||||||
"search_placeholder": "검색...",
|
"search_placeholder": "검색...",
|
||||||
"security": "보안",
|
"security": "보안",
|
||||||
|
"seed_backup_button": "시드 문구",
|
||||||
|
"seed_backup_close": "닫기",
|
||||||
|
"seed_backup_copy": "복사",
|
||||||
|
"seed_backup_intro": "이 단어들은 지갑의 마스터 백업입니다. 이 단어들을 가진 사람은 누구나 당신의 자금을 통제할 수 있습니다.",
|
||||||
|
"seed_backup_load_failed": "시드 문구를 불러올 수 없습니다.",
|
||||||
|
"seed_backup_loading": "시드 문구를 불러오는 중…",
|
||||||
|
"seed_backup_locked": "시드 문구를 표시하려면 지갑의 잠금을 해제하세요.",
|
||||||
|
"seed_backup_none": "이 지갑은 시드 문구 도입 이전에 만들어져 24단어 문구가 없습니다. 대신 \"백업...\"을 사용하거나 개인 키를 내보내 백업하세요.",
|
||||||
|
"seed_backup_reminder": "지갑에 24단어 복구 시드 문구가 있습니다. 지금 설정 → 노드 및 보안에서 백업하세요.",
|
||||||
|
"seed_backup_save": "파일로 저장…",
|
||||||
|
"seed_backup_save_failed": "쓸 수 없습니다: ",
|
||||||
|
"seed_backup_saved": "저장됨: ",
|
||||||
|
"seed_backup_title": "시드 문구 백업",
|
||||||
|
"seed_backup_warning": "순서대로 종이에 적어 오프라인으로 보관하고, 절대 공유하거나 사진으로 찍지 마세요. 이 단어들을 잃어버리면 자금을 복구할 수 없습니다.",
|
||||||
"select_address": "주소 선택...",
|
"select_address": "주소 선택...",
|
||||||
"select_receiving_address": "수신 주소 선택...",
|
"select_receiving_address": "수신 주소 선택...",
|
||||||
"select_source_address": "보낼 주소 선택...",
|
"select_source_address": "보낼 주소 선택...",
|
||||||
@@ -553,12 +704,15 @@
|
|||||||
"send_valid_transparent": "유효한 투명 주소",
|
"send_valid_transparent": "유효한 투명 주소",
|
||||||
"send_wallet_empty": "지갑이 비어 있습니다",
|
"send_wallet_empty": "지갑이 비어 있습니다",
|
||||||
"send_yes_clear": "예, 지우기",
|
"send_yes_clear": "예, 지우기",
|
||||||
|
"sender_balance": "발신자: %.8f → %.8f DRGX",
|
||||||
"sending": "거래 전송 중",
|
"sending": "거래 전송 중",
|
||||||
"sending_from": "보내는 곳",
|
"sending_from": "보내는 곳",
|
||||||
|
"sends_full_balance_warning": "전체 잔액을 전송합니다. 보내는 주소의 잔액이 0이 됩니다.",
|
||||||
"sent": "전송됨",
|
"sent": "전송됨",
|
||||||
"sent_filter": "전송됨",
|
"sent_filter": "전송됨",
|
||||||
"sent_type": "전송됨",
|
"sent_type": "전송됨",
|
||||||
"sent_upper": "전송됨",
|
"sent_upper": "전송됨",
|
||||||
|
"set_label": "라벨 설정...",
|
||||||
"settings": "설정",
|
"settings": "설정",
|
||||||
"settings_about_text": "DragonX (DRGX)용 차폐 암호화폐 지갑으로, Dear ImGui로 제작되어 가볍고 휴대 가능합니다.",
|
"settings_about_text": "DragonX (DRGX)용 차폐 암호화폐 지갑으로, Dear ImGui로 제작되어 가볍고 휴대 가능합니다.",
|
||||||
"settings_acrylic_level": "아크릴 레벨:",
|
"settings_acrylic_level": "아크릴 레벨:",
|
||||||
@@ -647,13 +801,17 @@
|
|||||||
"shield_utxo_limit": "UTXO 제한:",
|
"shield_utxo_limit": "UTXO 제한:",
|
||||||
"shield_wildcard_hint": "'*'를 사용하여 모든 투명 주소에서 차폐",
|
"shield_wildcard_hint": "'*'를 사용하여 모든 투명 주소에서 차폐",
|
||||||
"shielded": "차폐",
|
"shielded": "차폐",
|
||||||
|
"shielded_address": "보호 주소",
|
||||||
"shielded_to": "차폐 대상",
|
"shielded_to": "차폐 대상",
|
||||||
"shielded_type": "차폐",
|
"shielded_type": "차폐",
|
||||||
|
"shielding_notice": "참고: 투명 (T) 주소에서 프라이빗 (Z) 주소로 자금을 실딩합니다.",
|
||||||
"show": "표시",
|
"show": "표시",
|
||||||
"show_hidden": "숨겨진 항목 표시 (%d)",
|
"show_hidden": "숨겨진 항목 표시 (%d)",
|
||||||
"show_qr_code": "QR 코드 표시",
|
"show_qr_code": "QR 코드 표시",
|
||||||
"showing_transactions": "%dâ%d / %d건의 거래 표시 중 (총: %zu)",
|
"showing_transactions": "%d–%d / %d건의 거래 표시 중 (총: %zu)",
|
||||||
|
"showing_x_of_y": "%d / %d 주소 표시",
|
||||||
"simple_background": "단순 배경",
|
"simple_background": "단순 배경",
|
||||||
|
"slider_off": "끔",
|
||||||
"start_mining": "채굴 시작",
|
"start_mining": "채굴 시작",
|
||||||
"status": "상태",
|
"status": "상태",
|
||||||
"stop_external": "외부 데몬 중지",
|
"stop_external": "외부 데몬 중지",
|
||||||
@@ -662,17 +820,26 @@
|
|||||||
"success": "성공",
|
"success": "성공",
|
||||||
"summary": "요약",
|
"summary": "요약",
|
||||||
"syncing": "동기화 중...",
|
"syncing": "동기화 중...",
|
||||||
|
"t_address": "T 주소",
|
||||||
"t_addresses": "T 주소",
|
"t_addresses": "T 주소",
|
||||||
"test_connection": "테스트",
|
"test_connection": "테스트",
|
||||||
"theme": "테마",
|
"theme": "테마",
|
||||||
"theme_effects": "테마 효과",
|
"theme_effects": "테마 효과",
|
||||||
|
"theme_language": "테마 및 언어",
|
||||||
"time_days_ago": "%d일 전",
|
"time_days_ago": "%d일 전",
|
||||||
"time_hours_ago": "%d시간 전",
|
"time_hours_ago": "%d시간 전",
|
||||||
"time_minutes_ago": "%d분 전",
|
"time_minutes_ago": "%d분 전",
|
||||||
"time_seconds_ago": "%d초 전",
|
"time_seconds_ago": "%d초 전",
|
||||||
|
"timeout_15min": "15분",
|
||||||
|
"timeout_1hour": "1시간",
|
||||||
|
"timeout_1min": "1분",
|
||||||
|
"timeout_30min": "30분",
|
||||||
|
"timeout_5min": "5분",
|
||||||
|
"timeout_off": "끔",
|
||||||
"to": "받는 곳",
|
"to": "받는 곳",
|
||||||
"to_upper": "받는 곳",
|
"to_upper": "받는 곳",
|
||||||
"tools": "도구",
|
"tools": "도구",
|
||||||
|
"tools_actions": "도구 및 작업...",
|
||||||
"total": "합계",
|
"total": "합계",
|
||||||
"transaction_id": "거래 ID",
|
"transaction_id": "거래 ID",
|
||||||
"transaction_sent": "거래 전송 성공",
|
"transaction_sent": "거래 전송 성공",
|
||||||
@@ -680,7 +847,13 @@
|
|||||||
"transaction_url": "거래 URL",
|
"transaction_url": "거래 URL",
|
||||||
"transactions": "거래",
|
"transactions": "거래",
|
||||||
"transactions_upper": "거래",
|
"transactions_upper": "거래",
|
||||||
|
"transfer_failed": "이체 실패",
|
||||||
|
"transfer_funds": "자금 이체",
|
||||||
|
"transfer_sent": "이체 전송됨",
|
||||||
|
"transfer_sent_desc": "이체가 네트워크에 제출되었습니다.",
|
||||||
|
"transfer_to": "이체 대상:",
|
||||||
"transparent": "투명",
|
"transparent": "투명",
|
||||||
|
"transparent_address": "투명 주소",
|
||||||
"tt_addr_url": "블록 탐색기에서 주소를 보기 위한 기본 URL",
|
"tt_addr_url": "블록 탐색기에서 주소를 보기 위한 기본 URL",
|
||||||
"tt_address_book": "빠른 전송을 위해 저장된 주소 관리",
|
"tt_address_book": "빠른 전송을 위해 저장된 주소 관리",
|
||||||
"tt_auto_lock": "이 비활성 시간 후 지갑 잠금",
|
"tt_auto_lock": "이 비활성 시간 후 지갑 잠금",
|
||||||
@@ -695,6 +868,8 @@
|
|||||||
"tt_custom_theme": "사용자 지정 테마 활성화됨",
|
"tt_custom_theme": "사용자 지정 테마 활성화됨",
|
||||||
"tt_debug_collapse": "디버그 로깅 옵션 접기",
|
"tt_debug_collapse": "디버그 로깅 옵션 접기",
|
||||||
"tt_debug_expand": "디버그 로깅 옵션 펼치기",
|
"tt_debug_expand": "디버그 로깅 옵션 펼치기",
|
||||||
|
"tt_delete_blockchain": "모든 블록체인 데이터를 삭제하고 새로 동기화합니다. wallet.dat 및 설정은 보존됩니다.",
|
||||||
|
"tt_download_bootstrap": "블록체인 부트스트랩을 다운로드하여 동기화 가속\n기존 블록 데이터가 교체됩니다",
|
||||||
"tt_encrypt": "비밀번호로 wallet.dat 암호화",
|
"tt_encrypt": "비밀번호로 wallet.dat 암호화",
|
||||||
"tt_export_all": "모든 개인키를 파일로 내보내기",
|
"tt_export_all": "모든 개인키를 파일로 내보내기",
|
||||||
"tt_export_csv": "거래 내역을 CSV 스프레드시트로 내보내기",
|
"tt_export_csv": "거래 내역을 CSV 스프레드시트로 내보내기",
|
||||||
@@ -712,6 +887,7 @@
|
|||||||
"tt_mine_idle": "시스템이 유휴 상태(키보드/마우스 입력 없음)일 때\\n자동으로 채굴 시작",
|
"tt_mine_idle": "시스템이 유휴 상태(키보드/마우스 입력 없음)일 때\\n자동으로 채굴 시작",
|
||||||
"tt_noise": "그레인 텍스처 강도 (0%% = 끔, 100%% = 최대)",
|
"tt_noise": "그레인 텍스처 강도 (0%% = 끔, 100%% = 최대)",
|
||||||
"tt_open_dir": "파일 탐색기에서 열려면 클릭",
|
"tt_open_dir": "파일 탐색기에서 열려면 클릭",
|
||||||
|
"tt_reduce_motion": "접근성을 위해 애니메이션 전환 및 잔액 보간 비활성화",
|
||||||
"tt_remove_encrypt": "암호화를 제거하고 지갑을 보호 없이 저장",
|
"tt_remove_encrypt": "암호화를 제거하고 지갑을 보호 없이 저장",
|
||||||
"tt_remove_pin": "PIN을 제거하고 잠금 해제 시 비밀번호 요구",
|
"tt_remove_pin": "PIN을 제거하고 잠금 해제 시 비밀번호 요구",
|
||||||
"tt_report_bug": "프로젝트 트래커에서 문제 보고",
|
"tt_report_bug": "프로젝트 트래커에서 문제 보고",
|
||||||
@@ -727,6 +903,7 @@
|
|||||||
"tt_save_ztx": "z-address 거래 기록을 로컬에 저장하여 빠른 로딩",
|
"tt_save_ztx": "z-address 거래 기록을 로컬에 저장하여 빠른 로딩",
|
||||||
"tt_scan_themes": "새 테마 검색.\\n테마 폴더를 여기에 배치:\\n%s",
|
"tt_scan_themes": "새 테마 검색.\\n테마 폴더를 여기에 배치:\\n%s",
|
||||||
"tt_scanline": "콘솔에서 CRT 스캔라인 효과",
|
"tt_scanline": "콘솔에서 CRT 스캔라인 효과",
|
||||||
|
"tt_seed_backup": "지갑의 24단어 복구 시드 문구를 표시하고 백업합니다",
|
||||||
"tt_set_pin": "빠른 잠금 해제를 위한 4-8자리 PIN 설정",
|
"tt_set_pin": "빠른 잠금 해제를 위한 4-8자리 PIN 설정",
|
||||||
"tt_shield_mining": "투명 채굴 보상을 차폐 주소로 이동",
|
"tt_shield_mining": "투명 채굴 보상을 차폐 주소로 이동",
|
||||||
"tt_simple_bg": "배경에 단순 그라데이션 사용\\n단축키: Ctrl+Up",
|
"tt_simple_bg": "배경에 단순 그라데이션 사용\\n단축키: Ctrl+Up",
|
||||||
@@ -789,7 +966,9 @@
|
|||||||
"warning_upper": "경고!",
|
"warning_upper": "경고!",
|
||||||
"website": "웹사이트",
|
"website": "웹사이트",
|
||||||
"window_opacity": "창 투명도",
|
"window_opacity": "창 투명도",
|
||||||
|
"wizard_daemon_start_failed": "데몬 시작 실패 — 자동으로 재시도됩니다",
|
||||||
"yes_clear": "예, 지우기",
|
"yes_clear": "예, 지우기",
|
||||||
"your_addresses": "내 주소",
|
"your_addresses": "내 주소",
|
||||||
|
"z_address": "Z 주소",
|
||||||
"z_addresses": "Z 주소"
|
"z_addresses": "Z 주소"
|
||||||
}
|
}
|
||||||
|
|||||||
187
res/lang/pt.json
187
res/lang/pt.json
@@ -43,6 +43,8 @@
|
|||||||
"address_url": "URL do Endereço",
|
"address_url": "URL do Endereço",
|
||||||
"addresses_appear_here": "Seus endereços de recebimento aparecerão aqui após a conexão.",
|
"addresses_appear_here": "Seus endereços de recebimento aparecerão aqui após a conexão.",
|
||||||
"advanced": "AVANÇADO",
|
"advanced": "AVANÇADO",
|
||||||
|
"advanced_effects": "Efeitos Avançados...",
|
||||||
|
"ago": "atrás",
|
||||||
"all_filter": "Todos",
|
"all_filter": "Todos",
|
||||||
"allow_custom_fees": "Permitir taxas personalizadas",
|
"allow_custom_fees": "Permitir taxas personalizadas",
|
||||||
"amount": "Valor",
|
"amount": "Valor",
|
||||||
@@ -90,12 +92,52 @@
|
|||||||
"block_timestamp": "Carimbo de Data:",
|
"block_timestamp": "Carimbo de Data:",
|
||||||
"block_transactions": "Transações:",
|
"block_transactions": "Transações:",
|
||||||
"blockchain_syncing": "Blockchain sincronizando (%.1f%%)... Os saldos podem ser imprecisos.",
|
"blockchain_syncing": "Blockchain sincronizando (%.1f%%)... Os saldos podem ser imprecisos.",
|
||||||
|
"bootstrap_daemon_running": "Daemon em execução",
|
||||||
|
"bootstrap_daemon_stopped": "Daemon parado",
|
||||||
|
"bootstrap_daemon_stopping": "Parando daemon...",
|
||||||
|
"bootstrap_desc": "Baixe um bootstrap da blockchain para acelerar drasticamente a sincronização inicial. Isso baixa um instantâneo da blockchain e o extrai no seu diretório de dados.",
|
||||||
|
"bootstrap_downloading": "Baixando bootstrap...",
|
||||||
|
"bootstrap_extracting": "Extraindo dados da blockchain...",
|
||||||
|
"bootstrap_failed": "Falha no Bootstrap",
|
||||||
|
"bootstrap_mirror": "Espelho",
|
||||||
|
"bootstrap_mirror_tooltip": "Baixar do espelho (bootstrap2.dragonx.is).\nUse isto se o download principal estiver lento ou falhando.",
|
||||||
|
"bootstrap_restart_daemon": "Reiniciar Daemon",
|
||||||
|
"bootstrap_success": "Bootstrap Completo",
|
||||||
|
"bootstrap_success_desc": "Os dados da blockchain foram extraídos com sucesso. Inicie o daemon para começar a sincronizar a partir do ponto do bootstrap.",
|
||||||
|
"bootstrap_trust_warning": "Use apenas bootstrap.dragonx.is ou bootstrap2.dragonx.is. Usar arquivos de fontes não confiáveis pode comprometer seu nó.",
|
||||||
|
"bootstrap_verifying": "Verificando somas de verificação...",
|
||||||
|
"bootstrap_wallet_protected": "(wallet.dat está protegido)",
|
||||||
|
"bootstrap_warning": "Os dados de blocos existentes (blocks, chainstate, notarizations) serão excluídos e substituídos. Seu wallet.dat NÃO será modificado ou excluído.",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"characters": "caracteres",
|
"characters": "caracteres",
|
||||||
|
"chat": "Chat",
|
||||||
|
"chat_cancel": "Cancelar",
|
||||||
|
"chat_contact_request": "solicitação de contato",
|
||||||
|
"chat_empty_hint": "Nenhuma conversa ainda. As mensagens que você receber aparecerão aqui.",
|
||||||
|
"chat_locked_hint": "Desbloqueie sua carteira para carregar suas conversas.",
|
||||||
|
"chat_new_button": "Nova conversa",
|
||||||
|
"chat_new_message": "Mensagem",
|
||||||
|
"chat_new_send": "Enviar solicitação",
|
||||||
|
"chat_new_title": "Nova conversa",
|
||||||
|
"chat_new_zaddr": "Endereço-z do destinatário",
|
||||||
|
"chat_select_hint": "Selecione uma conversa para visualizá-la.",
|
||||||
|
"chat_send": "Enviar",
|
||||||
|
"chat_send_failed": "não enviada",
|
||||||
|
"chat_toast_compose_failed": "Não foi possível compor a mensagem (muito longa?).",
|
||||||
|
"chat_toast_lite_busy": "Já há um envio em andamento, ou nenhuma carteira está aberta.",
|
||||||
|
"chat_toast_no_zaddr": "Nenhum endereço-z disponível para enviar o chat.",
|
||||||
|
"chat_toast_not_connected": "Não conectado — mensagem de chat não enviada.",
|
||||||
|
"chat_toast_request_compose_failed": "Não foi possível compor a solicitação de contato (endereço / texto inválido?).",
|
||||||
|
"chat_toast_request_queued": "Solicitação de contato na fila.",
|
||||||
|
"chat_toast_waiting_reply": "Aguardando a resposta do contato antes que você possa enviar mensagens a ele.",
|
||||||
|
"chat_waiting_reply": "Aguardando a resposta deste contato — você poderá enviar mensagens assim que ele responder.",
|
||||||
|
"chat_you": "Você",
|
||||||
|
"choose_icon": "Escolher Ícone",
|
||||||
"clear": "Limpar",
|
"clear": "Limpar",
|
||||||
"clear_all_bans": "Remover Todos os Banimentos",
|
"clear_all_bans": "Remover Todos os Banimentos",
|
||||||
"clear_anyway": "Limpar mesmo assim",
|
"clear_anyway": "Limpar mesmo assim",
|
||||||
"clear_form_confirm": "Limpar todos os campos do formulário?",
|
"clear_form_confirm": "Limpar todos os campos do formulário?",
|
||||||
|
"clear_icon": "Limpar Ícone",
|
||||||
"clear_request": "Limpar Solicitação",
|
"clear_request": "Limpar Solicitação",
|
||||||
"click_copy_address": "Clique para copiar o endereço",
|
"click_copy_address": "Clique para copiar o endereço",
|
||||||
"click_copy_uri": "Clique para copiar a URI",
|
"click_copy_uri": "Clique para copiar a URI",
|
||||||
@@ -106,8 +148,12 @@
|
|||||||
"confirm_clear_ztx_title": "Confirmar limpeza do histórico Z-Tx",
|
"confirm_clear_ztx_title": "Confirmar limpeza do histórico Z-Tx",
|
||||||
"confirm_clear_ztx_warning1": "Limpar o histórico de z-transações pode fazer com que seu saldo blindado apareça como 0 até que um reescaneamento da carteira seja realizado.",
|
"confirm_clear_ztx_warning1": "Limpar o histórico de z-transações pode fazer com que seu saldo blindado apareça como 0 até que um reescaneamento da carteira seja realizado.",
|
||||||
"confirm_clear_ztx_warning2": "Se isso acontecer, você precisará reimportar as chaves privadas do seu endereço z com reescaneamento habilitado para recuperar seu saldo.",
|
"confirm_clear_ztx_warning2": "Se isso acontecer, você precisará reimportar as chaves privadas do seu endereço z com reescaneamento habilitado para recuperar seu saldo.",
|
||||||
|
"confirm_delete_blockchain_msg": "Isso irá parar o daemon, excluir todos os dados da blockchain (blocks, chainstate, peers) e iniciar uma nova sincronização do zero. Isso pode levar várias horas.",
|
||||||
|
"confirm_delete_blockchain_safe": "Seu wallet.dat, configuração e histórico de transações estão seguros e não serão excluídos.",
|
||||||
|
"confirm_delete_blockchain_title": "Excluir Dados da Blockchain",
|
||||||
"confirm_send": "Confirmar Envio",
|
"confirm_send": "Confirmar Envio",
|
||||||
"confirm_transaction": "Confirmar Transação",
|
"confirm_transaction": "Confirmar Transação",
|
||||||
|
"confirm_transfer": "Confirmar Transferência",
|
||||||
"confirmations": "Confirmações",
|
"confirmations": "Confirmações",
|
||||||
"confirmations_display": "%d confirmações | %s",
|
"confirmations_display": "%d confirmações | %s",
|
||||||
"confirmed": "Confirmado",
|
"confirmed": "Confirmado",
|
||||||
@@ -172,6 +218,7 @@
|
|||||||
"console_welcome": "Bem-vindo ao Console ObsidianDragon",
|
"console_welcome": "Bem-vindo ao Console ObsidianDragon",
|
||||||
"console_zoom_in": "Aumentar zoom",
|
"console_zoom_in": "Aumentar zoom",
|
||||||
"console_zoom_out": "Diminuir zoom",
|
"console_zoom_out": "Diminuir zoom",
|
||||||
|
"copied": "Copiado!",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copy_address": "Copiar Endereço Completo",
|
"copy_address": "Copiar Endereço Completo",
|
||||||
"copy_error": "Copiar Erro",
|
"copy_error": "Copiar Erro",
|
||||||
@@ -180,22 +227,45 @@
|
|||||||
"copy_uri": "Copiar URI",
|
"copy_uri": "Copiar URI",
|
||||||
"current_price": "Preço Atual",
|
"current_price": "Preço Atual",
|
||||||
"custom_fees": "Taxas personalizadas",
|
"custom_fees": "Taxas personalizadas",
|
||||||
|
"daemon_version": "Daemon",
|
||||||
"dark": "Escuro",
|
"dark": "Escuro",
|
||||||
"date": "Data",
|
"date": "Data",
|
||||||
"date_label": "Data:",
|
"date_label": "Data:",
|
||||||
"debug_logging": "REGISTRO DE DEPURAÇÃO",
|
"debug_logging": "REGISTRO DE DEPURAÇÃO",
|
||||||
"delete": "Excluir",
|
"delete": "Excluir",
|
||||||
|
"delete_blockchain": "Excluir Blockchain",
|
||||||
|
"delete_blockchain_confirm": "Excluir e Ressincronizar",
|
||||||
|
"deshielding_warning": "Aviso: Isso irá des-blindar fundos de um endereço privado (Z) para um endereço transparente (T).",
|
||||||
"difficulty": "Dificuldade",
|
"difficulty": "Dificuldade",
|
||||||
"disconnected": "Desconectado",
|
"disconnected": "Desconectado",
|
||||||
"dismiss": "Dispensar",
|
"dismiss": "Dispensar",
|
||||||
"display": "Exibição",
|
"display": "Exibição",
|
||||||
|
"download": "Baixar",
|
||||||
|
"download_bootstrap": "Baixar Bootstrap",
|
||||||
"dragonx_green": "DragonX (Verde)",
|
"dragonx_green": "DragonX (Verde)",
|
||||||
"edit": "Editar",
|
"edit": "Editar",
|
||||||
"error": "Erro",
|
"error": "Erro",
|
||||||
"error_format": "Erro: %s",
|
"error_format": "Erro: %s",
|
||||||
"est_time_to_block": "Tempo Est. por Bloco",
|
"est_time_to_block": "Tempo Est. por Bloco",
|
||||||
"exit": "Sair",
|
"exit": "Sair",
|
||||||
"explorer": "EXPLORADOR",
|
"explorer": "Explorador",
|
||||||
|
"explorer_block_detail": "Bloco",
|
||||||
|
"explorer_block_hash": "Hash",
|
||||||
|
"explorer_block_height": "Altura",
|
||||||
|
"explorer_block_merkle": "Raiz Merkle",
|
||||||
|
"explorer_block_size": "Tamanho",
|
||||||
|
"explorer_block_time": "Hora",
|
||||||
|
"explorer_block_txs": "Transações",
|
||||||
|
"explorer_chain_stats": "Cadeia",
|
||||||
|
"explorer_invalid_query": "Insira uma altura de bloco ou um hash de 64 caracteres",
|
||||||
|
"explorer_mempool": "Mempool",
|
||||||
|
"explorer_mempool_size": "Tamanho",
|
||||||
|
"explorer_mempool_txs": "Transações",
|
||||||
|
"explorer_recent_blocks": "Blocos Recentes",
|
||||||
|
"explorer_search": "Pesquisar",
|
||||||
|
"explorer_section": "EXPLORADOR",
|
||||||
|
"explorer_tx_outputs": "Saídas",
|
||||||
|
"explorer_tx_size": "Tamanho",
|
||||||
"export": "Exportar",
|
"export": "Exportar",
|
||||||
"export_csv": "Exportar CSV",
|
"export_csv": "Exportar CSV",
|
||||||
"export_keys_btn": "Exportar Chaves",
|
"export_keys_btn": "Exportar Chaves",
|
||||||
@@ -224,14 +294,22 @@
|
|||||||
"fetch_prices": "Buscar preços",
|
"fetch_prices": "Buscar preços",
|
||||||
"file": "Arquivo",
|
"file": "Arquivo",
|
||||||
"file_save_location": "O arquivo será salvo em: ~/.config/ObsidianDragon/",
|
"file_save_location": "O arquivo será salvo em: ~/.config/ObsidianDragon/",
|
||||||
|
"filter": "Filtrar...",
|
||||||
"font_scale": "Escala da Fonte",
|
"font_scale": "Escala da Fonte",
|
||||||
|
"force_quit": "Forçar Saída",
|
||||||
|
"force_quit_confirm_msg": "Isso matará imediatamente o daemon sem um desligamento limpo.\nIsso pode corromper o índice da blockchain e exigir uma ressincronização.",
|
||||||
|
"force_quit_confirm_title": "Forçar Saída?",
|
||||||
|
"force_quit_warning": "Isso matará imediatamente o daemon sem um desligamento limpo. Pode exigir uma ressincronização da blockchain.",
|
||||||
|
"force_quit_yes": "Forçar Saída",
|
||||||
"from": "De",
|
"from": "De",
|
||||||
"from_upper": "DE",
|
"from_upper": "DE",
|
||||||
"full_details": "Detalhes Completos",
|
"full_details": "Detalhes Completos",
|
||||||
"general": "Geral",
|
"general": "Geral",
|
||||||
|
"generating": "Gerando",
|
||||||
"go_to_receive": "Ir para Receber",
|
"go_to_receive": "Ir para Receber",
|
||||||
"height": "Altura",
|
"height": "Altura",
|
||||||
"help": "Ajuda",
|
"help": "Ajuda",
|
||||||
|
"hidden_tag": " (oculto)",
|
||||||
"hide": "Ocultar",
|
"hide": "Ocultar",
|
||||||
"hide_address": "Ocultar endereço",
|
"hide_address": "Ocultar endereço",
|
||||||
"hide_zero_balances": "Ocultar saldos zero",
|
"hide_zero_balances": "Ocultar saldos zero",
|
||||||
@@ -253,6 +331,9 @@
|
|||||||
"import_key_warning": "Aviso: Nunca compartilhe suas chaves privadas! Importar chaves de fontes não confiáveis pode comprometer sua carteira.",
|
"import_key_warning": "Aviso: Nunca compartilhe suas chaves privadas! Importar chaves de fontes não confiáveis pode comprometer sua carteira.",
|
||||||
"import_key_z_format": "Chaves de gasto de z-endereço (secret-extended-key-...)",
|
"import_key_z_format": "Chaves de gasto de z-endereço (secret-extended-key-...)",
|
||||||
"import_private_key": "Importar Chave Privada...",
|
"import_private_key": "Importar Chave Privada...",
|
||||||
|
"incorrect_passphrase": "Senha incorreta",
|
||||||
|
"incorrect_pin": "PIN incorreto",
|
||||||
|
"insufficient_funds": "Fundos insuficientes para este valor mais taxa.",
|
||||||
"invalid_address": "Formato de endereço inválido",
|
"invalid_address": "Formato de endereço inválido",
|
||||||
"ip_address": "Endereço IP",
|
"ip_address": "Endereço IP",
|
||||||
"keep": "Manter",
|
"keep": "Manter",
|
||||||
@@ -266,6 +347,7 @@
|
|||||||
"key_export_viewing_keys_zonly": "As chaves de visualização estão disponíveis apenas para endereços blindados (z)",
|
"key_export_viewing_keys_zonly": "As chaves de visualização estão disponíveis apenas para endereços blindados (z)",
|
||||||
"key_export_viewing_warning": "Esta chave de visualização permite que outros vejam suas transações recebidas e saldo, mas NÃO gastem seus fundos. Compartilhe apenas com partes confiáveis.",
|
"key_export_viewing_warning": "Esta chave de visualização permite que outros vejam suas transações recebidas e saldo, mas NÃO gastem seus fundos. Compartilhe apenas com partes confiáveis.",
|
||||||
"label": "Rótulo:",
|
"label": "Rótulo:",
|
||||||
|
"label_placeholder": "ex. Poupança, Mineração...",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"light": "Claro",
|
"light": "Claro",
|
||||||
"loading": "Carregando...",
|
"loading": "Carregando...",
|
||||||
@@ -286,6 +368,7 @@
|
|||||||
"market_now": "Agora",
|
"market_now": "Agora",
|
||||||
"market_pct_shielded": "%.0f%% Blindado",
|
"market_pct_shielded": "%.0f%% Blindado",
|
||||||
"market_portfolio": "PORTFÓLIO",
|
"market_portfolio": "PORTFÓLIO",
|
||||||
|
"market_price_loading": "Carregando dados de preço...",
|
||||||
"market_price_unavailable": "Dados de preço indisponíveis",
|
"market_price_unavailable": "Dados de preço indisponíveis",
|
||||||
"market_refresh_price": "Atualizar dados de preço",
|
"market_refresh_price": "Atualizar dados de preço",
|
||||||
"market_trade_on": "Negociar no %s",
|
"market_trade_on": "Negociar no %s",
|
||||||
@@ -311,6 +394,13 @@
|
|||||||
"mining_address_copied": "Endereço de mineração copiado",
|
"mining_address_copied": "Endereço de mineração copiado",
|
||||||
"mining_all_time": "Todo o Tempo",
|
"mining_all_time": "Todo o Tempo",
|
||||||
"mining_already_saved": "URL do pool já salva",
|
"mining_already_saved": "URL do pool já salva",
|
||||||
|
"mining_benchmark_cancel": "Cancelar benchmark",
|
||||||
|
"mining_benchmark_cooling": "Resfriando",
|
||||||
|
"mining_benchmark_dismiss": "Fechar",
|
||||||
|
"mining_benchmark_result": "Ótimo",
|
||||||
|
"mining_benchmark_stabilizing": "Estabilizando",
|
||||||
|
"mining_benchmark_testing": "Testando",
|
||||||
|
"mining_benchmark_tooltip": "Encontrar o número ideal de threads para esta CPU",
|
||||||
"mining_block_copied": "Hash do bloco copiado",
|
"mining_block_copied": "Hash do bloco copiado",
|
||||||
"mining_chart_1m_ago": "1m atrás",
|
"mining_chart_1m_ago": "1m atrás",
|
||||||
"mining_chart_5m_ago": "5m atrás",
|
"mining_chart_5m_ago": "5m atrás",
|
||||||
@@ -330,8 +420,15 @@
|
|||||||
"mining_filter_tip_all": "Mostrar todos os ganhos",
|
"mining_filter_tip_all": "Mostrar todos os ganhos",
|
||||||
"mining_filter_tip_pool": "Mostrar apenas ganhos do pool",
|
"mining_filter_tip_pool": "Mostrar apenas ganhos do pool",
|
||||||
"mining_filter_tip_solo": "Mostrar apenas ganhos solo",
|
"mining_filter_tip_solo": "Mostrar apenas ganhos solo",
|
||||||
|
"mining_generate_z_address_hint": "Gere um endereço Z na aba Receber para usar como endereço de pagamento",
|
||||||
|
"mining_idle_gpu_off_tooltip": "Sem restrição: ATIVADO\nApenas entrada de teclado/mouse determina o estado ocioso\nClique para ativar detecção de GPU",
|
||||||
|
"mining_idle_gpu_on_tooltip": "GPU-consciente: ATIVADO\nAtividade de GPU (vídeo, jogos) impede mineração ociosa\nClique para modo sem restrição",
|
||||||
"mining_idle_off_tooltip": "Ativar mineração ociosa",
|
"mining_idle_off_tooltip": "Ativar mineração ociosa",
|
||||||
"mining_idle_on_tooltip": "Desativar mineração ociosa",
|
"mining_idle_on_tooltip": "Desativar mineração ociosa",
|
||||||
|
"mining_idle_scale_off_tooltip": "Modo iniciar/parar: ATIVADO\nClique para mudar para modo de escala de threads",
|
||||||
|
"mining_idle_scale_on_tooltip": "Escala de threads: ATIVADO\nClique para mudar para modo iniciar/parar",
|
||||||
|
"mining_idle_threads_active_tooltip": "Threads quando o usuário está ativo",
|
||||||
|
"mining_idle_threads_idle_tooltip": "Threads quando o sistema está ocioso",
|
||||||
"mining_local_hashrate": "Hashrate Local",
|
"mining_local_hashrate": "Hashrate Local",
|
||||||
"mining_mine": "Minerar",
|
"mining_mine": "Minerar",
|
||||||
"mining_mining_addr": "End. Mineração",
|
"mining_mining_addr": "End. Mineração",
|
||||||
@@ -388,6 +485,7 @@
|
|||||||
"no_addresses_available": "Nenhum endereço disponível",
|
"no_addresses_available": "Nenhum endereço disponível",
|
||||||
"no_addresses_match": "Nenhum endereço corresponde ao filtro",
|
"no_addresses_match": "Nenhum endereço corresponde ao filtro",
|
||||||
"no_addresses_with_balance": "Nenhum endereço com saldo",
|
"no_addresses_with_balance": "Nenhum endereço com saldo",
|
||||||
|
"no_addresses_yet": "Nenhum endereço ainda",
|
||||||
"no_matching": "Nenhuma transação correspondente",
|
"no_matching": "Nenhuma transação correspondente",
|
||||||
"no_recent_receives": "Nenhum recebimento recente",
|
"no_recent_receives": "Nenhum recebimento recente",
|
||||||
"no_recent_sends": "Nenhum envio recente",
|
"no_recent_sends": "Nenhum envio recente",
|
||||||
@@ -400,7 +498,7 @@
|
|||||||
"notes": "Notas",
|
"notes": "Notas",
|
||||||
"notes_optional": "Notas (opcional):",
|
"notes_optional": "Notas (opcional):",
|
||||||
"output_filename": "Nome do arquivo de saída:",
|
"output_filename": "Nome do arquivo de saída:",
|
||||||
"overview": "Visão Geral",
|
"overview": "Resumo",
|
||||||
"paste": "Colar",
|
"paste": "Colar",
|
||||||
"paste_from_clipboard": "Colar da Área de Transferência",
|
"paste_from_clipboard": "Colar da Área de Transferência",
|
||||||
"pay_from": "Pagar de",
|
"pay_from": "Pagar de",
|
||||||
@@ -453,6 +551,7 @@
|
|||||||
"peers_upper": "PARES",
|
"peers_upper": "PARES",
|
||||||
"peers_version": "Versão",
|
"peers_version": "Versão",
|
||||||
"pending": "Pendente",
|
"pending": "Pendente",
|
||||||
|
"pin_not_set": "PIN não definido. Use a senha para desbloquear.",
|
||||||
"ping": "Ping",
|
"ping": "Ping",
|
||||||
"price_chart": "Gráfico de Preços",
|
"price_chart": "Gráfico de Preços",
|
||||||
"qr_code": "Código QR",
|
"qr_code": "Código QR",
|
||||||
@@ -473,7 +572,9 @@
|
|||||||
"recent_received": "RECEBIDOS RECENTES",
|
"recent_received": "RECEBIDOS RECENTES",
|
||||||
"recent_sends": "ENVIOS RECENTES",
|
"recent_sends": "ENVIOS RECENTES",
|
||||||
"recipient": "DESTINATÁRIO",
|
"recipient": "DESTINATÁRIO",
|
||||||
|
"recipient_balance": "Destinatário: %.8f → %.8f DRGX",
|
||||||
"recv_type": "Receb.",
|
"recv_type": "Receb.",
|
||||||
|
"reduce_motion": "Reduzir Movimento",
|
||||||
"refresh": "Atualizar",
|
"refresh": "Atualizar",
|
||||||
"refresh_now": "Atualizar Agora",
|
"refresh_now": "Atualizar Agora",
|
||||||
"remove_favorite": "Remover favorito",
|
"remove_favorite": "Remover favorito",
|
||||||
@@ -493,7 +594,10 @@
|
|||||||
"request_uri_copied": "URI de pagamento copiada para a área de transferência",
|
"request_uri_copied": "URI de pagamento copiada para a área de transferência",
|
||||||
"rescan": "Reescanear",
|
"rescan": "Reescanear",
|
||||||
"reset_to_defaults": "Redefinir Padrões",
|
"reset_to_defaults": "Redefinir Padrões",
|
||||||
|
"restarting_after_encryption": "Reiniciando daemon após criptografia...",
|
||||||
"restore_address": "Restaurar endereço",
|
"restore_address": "Restaurar endereço",
|
||||||
|
"result_preview": "Pré-visualização do resultado",
|
||||||
|
"retry": "Tentar novamente",
|
||||||
"review_send": "Revisar Envio",
|
"review_send": "Revisar Envio",
|
||||||
"rpc_host": "Host RPC",
|
"rpc_host": "Host RPC",
|
||||||
"rpc_pass": "Senha",
|
"rpc_pass": "Senha",
|
||||||
@@ -502,8 +606,55 @@
|
|||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
"save_settings": "Salvar Configurações",
|
"save_settings": "Salvar Configurações",
|
||||||
"save_z_transactions": "Salvar Z-tx na lista de tx",
|
"save_z_transactions": "Salvar Z-tx na lista de tx",
|
||||||
|
"sb_auth_failed": "Autenticação falhou — verifique rpcuser/rpcpassword",
|
||||||
|
"sb_block": "Bloco: %d",
|
||||||
|
"sb_connecting_daemon": "Conectando ao dragonxd...",
|
||||||
|
"sb_connecting_err": "Conectando ao daemon — %s",
|
||||||
|
"sb_connecting_external": "Conectando ao daemon externo...",
|
||||||
|
"sb_connecting_generic": "Conectando ao daemon...",
|
||||||
|
"sb_daemon_crashed": "O daemon travou %d vezes",
|
||||||
|
"sb_daemon_not_found": "Daemon não encontrado",
|
||||||
|
"sb_dragonxd_running": "dragonxd em execução",
|
||||||
|
"sb_dragonxd_stopped": "dragonxd parado",
|
||||||
|
"sb_dragonxd_stopping": "Parando dragonxd...",
|
||||||
|
"sb_extracting_sapling": "Extraindo parâmetros Sapling...",
|
||||||
|
"sb_importing_keys": "Importando chaves",
|
||||||
|
"sb_loading_config": "Carregando configuração...",
|
||||||
|
"sb_mining_hs": "%.1f H/s",
|
||||||
|
"sb_net_ghs": "Rede: %.2f GH/s",
|
||||||
|
"sb_net_hs": "Rede: %.1f H/s",
|
||||||
|
"sb_net_khs": "Rede: %.2f KH/s",
|
||||||
|
"sb_net_mhs": "Rede: %.2f MH/s",
|
||||||
|
"sb_no_conf": "DRAGONX.conf não encontrado",
|
||||||
|
"sb_peers": "Pares: %zu",
|
||||||
|
"sb_rescanning": "Reescaneando",
|
||||||
|
"sb_rescanning_pct": "Reescaneando %.0f%%",
|
||||||
|
"sb_restarting_daemon": "Reiniciando daemon...",
|
||||||
|
"sb_sapling_failed": "Falha ao extrair parâmetros Sapling.",
|
||||||
|
"sb_sapling_not_found": "Parâmetros Sapling não encontrados.",
|
||||||
|
"sb_starting_daemon": "Iniciando dragonxd...",
|
||||||
|
"sb_syncing_basic": "Sincronizando %.1f%% (%d restantes)",
|
||||||
|
"sb_syncing_eta": "Sincronizando %.1f%% (%d restantes, %.0f blk/s, ~%s)",
|
||||||
|
"sb_waiting_config": "Aguardando configuração do daemon...",
|
||||||
|
"sb_waiting_daemon": "Aguardando dragonxd...",
|
||||||
|
"sb_waiting_daemon_err": "Aguardando dragonxd — %s",
|
||||||
|
"sb_warming_up": "Aquecendo...",
|
||||||
"search_placeholder": "Pesquisar...",
|
"search_placeholder": "Pesquisar...",
|
||||||
"security": "SEGURANÇA",
|
"security": "SEGURANÇA",
|
||||||
|
"seed_backup_button": "Frase de recuperação",
|
||||||
|
"seed_backup_close": "Fechar",
|
||||||
|
"seed_backup_copy": "Copiar",
|
||||||
|
"seed_backup_intro": "Estas palavras são o backup mestre da sua carteira. Qualquer pessoa que as tenha controla seus fundos.",
|
||||||
|
"seed_backup_load_failed": "Não foi possível carregar a frase de recuperação.",
|
||||||
|
"seed_backup_loading": "Carregando sua frase de recuperação…",
|
||||||
|
"seed_backup_locked": "Desbloqueie sua carteira para revelar sua frase de recuperação.",
|
||||||
|
"seed_backup_none": "Esta carteira é anterior às frases de recuperação, portanto não possui uma frase de 24 palavras. Faça o backup dela com \"Backup...\" ou exportando suas chaves privadas.",
|
||||||
|
"seed_backup_reminder": "Sua carteira tem uma frase de recuperação de 24 palavras. Faça o backup dela agora em Configurações → Nó e Segurança.",
|
||||||
|
"seed_backup_save": "Salvar em arquivo…",
|
||||||
|
"seed_backup_save_failed": "Não foi possível gravar ",
|
||||||
|
"seed_backup_saved": "Salvo em ",
|
||||||
|
"seed_backup_title": "Backup da frase de recuperação",
|
||||||
|
"seed_backup_warning": "Anote-as em ordem, guarde-as offline e nunca as compartilhe ou fotografe. Se você as perder, seus fundos não poderão ser recuperados.",
|
||||||
"select_address": "Selecionar endereço...",
|
"select_address": "Selecionar endereço...",
|
||||||
"select_receiving_address": "Selecionar endereço de recebimento...",
|
"select_receiving_address": "Selecionar endereço de recebimento...",
|
||||||
"select_source_address": "Selecionar endereço de origem...",
|
"select_source_address": "Selecionar endereço de origem...",
|
||||||
@@ -553,13 +704,16 @@
|
|||||||
"send_valid_transparent": "Endereço transparente válido",
|
"send_valid_transparent": "Endereço transparente válido",
|
||||||
"send_wallet_empty": "Sua carteira está vazia",
|
"send_wallet_empty": "Sua carteira está vazia",
|
||||||
"send_yes_clear": "Sim, Limpar",
|
"send_yes_clear": "Sim, Limpar",
|
||||||
|
"sender_balance": "Remetente: %.8f → %.8f DRGX",
|
||||||
"sending": "Enviando transação",
|
"sending": "Enviando transação",
|
||||||
"sending_from": "ENVIANDO DE",
|
"sending_from": "ENVIANDO DE",
|
||||||
|
"sends_full_balance_warning": "Isso envia o saldo total. O endereço de envio terá saldo zero.",
|
||||||
"sent": "enviado",
|
"sent": "enviado",
|
||||||
"sent_filter": "Enviado",
|
"sent_filter": "Enviado",
|
||||||
"sent_type": "Enviado",
|
"sent_type": "Enviado",
|
||||||
"sent_upper": "ENVIADO",
|
"sent_upper": "ENVIADO",
|
||||||
"settings": "Configurações",
|
"set_label": "Definir Rótulo...",
|
||||||
|
"settings": "Ajustes",
|
||||||
"settings_about_text": "Uma carteira de criptomoeda blindada para DragonX (DRGX), criada com Dear ImGui para uma experiência leve e portátil.",
|
"settings_about_text": "Uma carteira de criptomoeda blindada para DragonX (DRGX), criada com Dear ImGui para uma experiência leve e portátil.",
|
||||||
"settings_acrylic_level": "Nível acrílico:",
|
"settings_acrylic_level": "Nível acrílico:",
|
||||||
"settings_address_book": "Livro de endereços...",
|
"settings_address_book": "Livro de endereços...",
|
||||||
@@ -647,13 +801,17 @@
|
|||||||
"shield_utxo_limit": "Limite UTXO:",
|
"shield_utxo_limit": "Limite UTXO:",
|
||||||
"shield_wildcard_hint": "Use '*' para blindar de todos os endereços transparentes",
|
"shield_wildcard_hint": "Use '*' para blindar de todos os endereços transparentes",
|
||||||
"shielded": "Blindado",
|
"shielded": "Blindado",
|
||||||
|
"shielded_address": "Endereço Blindado",
|
||||||
"shielded_to": "BLINDADO PARA",
|
"shielded_to": "BLINDADO PARA",
|
||||||
"shielded_type": "Blindado",
|
"shielded_type": "Blindado",
|
||||||
|
"shielding_notice": "Nota: Isso blindará fundos de um endereço transparente (T) para um endereço privado (Z).",
|
||||||
"show": "Mostrar",
|
"show": "Mostrar",
|
||||||
"show_hidden": "Mostrar ocultos (%d)",
|
"show_hidden": "Mostrar ocultos (%d)",
|
||||||
"show_qr_code": "Mostrar Código QR",
|
"show_qr_code": "Mostrar Código QR",
|
||||||
"showing_transactions": "Mostrando %dâ%d de %d transações (total: %zu)",
|
"showing_transactions": "Mostrando %d–%d de %d transações (total: %zu)",
|
||||||
|
"showing_x_of_y": "Mostrando %d de %d endereços",
|
||||||
"simple_background": "Fundo simples",
|
"simple_background": "Fundo simples",
|
||||||
|
"slider_off": "Desligado",
|
||||||
"start_mining": "Iniciar Mineração",
|
"start_mining": "Iniciar Mineração",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"stop_external": "Parar daemon externo",
|
"stop_external": "Parar daemon externo",
|
||||||
@@ -662,17 +820,26 @@
|
|||||||
"success": "Sucesso",
|
"success": "Sucesso",
|
||||||
"summary": "Resumo",
|
"summary": "Resumo",
|
||||||
"syncing": "Sincronizando...",
|
"syncing": "Sincronizando...",
|
||||||
|
"t_address": "Endereço T",
|
||||||
"t_addresses": "Endereços T",
|
"t_addresses": "Endereços T",
|
||||||
"test_connection": "Testar",
|
"test_connection": "Testar",
|
||||||
"theme": "Tema",
|
"theme": "Tema",
|
||||||
"theme_effects": "Efeitos de tema",
|
"theme_effects": "Efeitos de tema",
|
||||||
|
"theme_language": "TEMA E IDIOMA",
|
||||||
"time_days_ago": "há %d dias",
|
"time_days_ago": "há %d dias",
|
||||||
"time_hours_ago": "há %d horas",
|
"time_hours_ago": "há %d horas",
|
||||||
"time_minutes_ago": "há %d minutos",
|
"time_minutes_ago": "há %d minutos",
|
||||||
"time_seconds_ago": "há %d segundos",
|
"time_seconds_ago": "há %d segundos",
|
||||||
|
"timeout_15min": "15 min",
|
||||||
|
"timeout_1hour": "1 hora",
|
||||||
|
"timeout_1min": "1 min",
|
||||||
|
"timeout_30min": "30 min",
|
||||||
|
"timeout_5min": "5 min",
|
||||||
|
"timeout_off": "Desligado",
|
||||||
"to": "Para",
|
"to": "Para",
|
||||||
"to_upper": "PARA",
|
"to_upper": "PARA",
|
||||||
"tools": "FERRAMENTAS",
|
"tools": "FERRAMENTAS",
|
||||||
|
"tools_actions": "Ferramentas e Ações...",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"transaction_id": "ID DA TRANSAÇÃO",
|
"transaction_id": "ID DA TRANSAÇÃO",
|
||||||
"transaction_sent": "Transação enviada com sucesso",
|
"transaction_sent": "Transação enviada com sucesso",
|
||||||
@@ -680,7 +847,13 @@
|
|||||||
"transaction_url": "URL da Transação",
|
"transaction_url": "URL da Transação",
|
||||||
"transactions": "Transações",
|
"transactions": "Transações",
|
||||||
"transactions_upper": "TRANSAÇÕES",
|
"transactions_upper": "TRANSAÇÕES",
|
||||||
|
"transfer_failed": "Transferência Falhou",
|
||||||
|
"transfer_funds": "Transferir Fundos",
|
||||||
|
"transfer_sent": "Transferência Enviada",
|
||||||
|
"transfer_sent_desc": "Sua transferência foi enviada à rede.",
|
||||||
|
"transfer_to": "Transferir para:",
|
||||||
"transparent": "Transparente",
|
"transparent": "Transparente",
|
||||||
|
"transparent_address": "Endereço Transparente",
|
||||||
"tt_addr_url": "URL base para visualizar endereços em um explorador de blocos",
|
"tt_addr_url": "URL base para visualizar endereços em um explorador de blocos",
|
||||||
"tt_address_book": "Gerenciar endereços salvos para envio rápido",
|
"tt_address_book": "Gerenciar endereços salvos para envio rápido",
|
||||||
"tt_auto_lock": "Bloquear carteira após este tempo de inatividade",
|
"tt_auto_lock": "Bloquear carteira após este tempo de inatividade",
|
||||||
@@ -695,6 +868,8 @@
|
|||||||
"tt_custom_theme": "Tema personalizado ativo",
|
"tt_custom_theme": "Tema personalizado ativo",
|
||||||
"tt_debug_collapse": "Recolher opções de registro de depuração",
|
"tt_debug_collapse": "Recolher opções de registro de depuração",
|
||||||
"tt_debug_expand": "Expandir opções de registro de depuração",
|
"tt_debug_expand": "Expandir opções de registro de depuração",
|
||||||
|
"tt_delete_blockchain": "Excluir todos os dados da blockchain e iniciar uma nova sincronização. wallet.dat e configuração são preservados.",
|
||||||
|
"tt_download_bootstrap": "Baixar bootstrap da blockchain para acelerar a sincronização\nDados de blocos existentes serão substituídos",
|
||||||
"tt_encrypt": "Encriptar wallet.dat com uma frase secreta",
|
"tt_encrypt": "Encriptar wallet.dat com uma frase secreta",
|
||||||
"tt_export_all": "Exportar todas as chaves privadas para um arquivo",
|
"tt_export_all": "Exportar todas as chaves privadas para um arquivo",
|
||||||
"tt_export_csv": "Exportar histórico de transações como planilha CSV",
|
"tt_export_csv": "Exportar histórico de transações como planilha CSV",
|
||||||
@@ -712,6 +887,7 @@
|
|||||||
"tt_mine_idle": "Iniciar mineração automaticamente quando o\\nsistema estiver ocioso (sem entrada de teclado/mouse)",
|
"tt_mine_idle": "Iniciar mineração automaticamente quando o\\nsistema estiver ocioso (sem entrada de teclado/mouse)",
|
||||||
"tt_noise": "Intensidade de textura granulada (0%% = desligado, 100%% = máximo)",
|
"tt_noise": "Intensidade de textura granulada (0%% = desligado, 100%% = máximo)",
|
||||||
"tt_open_dir": "Clique para abrir no explorador de arquivos",
|
"tt_open_dir": "Clique para abrir no explorador de arquivos",
|
||||||
|
"tt_reduce_motion": "Desativar transições animadas e lerp de saldo para acessibilidade",
|
||||||
"tt_remove_encrypt": "Remover encriptação e armazenar a carteira desprotegida",
|
"tt_remove_encrypt": "Remover encriptação e armazenar a carteira desprotegida",
|
||||||
"tt_remove_pin": "Remover PIN e exigir frase secreta para desbloquear",
|
"tt_remove_pin": "Remover PIN e exigir frase secreta para desbloquear",
|
||||||
"tt_report_bug": "Reportar um problema no rastreador do projeto",
|
"tt_report_bug": "Reportar um problema no rastreador do projeto",
|
||||||
@@ -727,6 +903,7 @@
|
|||||||
"tt_save_ztx": "Armazenar histórico de transações z-address localmente para carregamento mais rápido",
|
"tt_save_ztx": "Armazenar histórico de transações z-address localmente para carregamento mais rápido",
|
||||||
"tt_scan_themes": "Procurar novos temas.\\nColoque pastas de temas em:\\n%s",
|
"tt_scan_themes": "Procurar novos temas.\\nColoque pastas de temas em:\\n%s",
|
||||||
"tt_scanline": "Efeito de linhas de varredura CRT no console",
|
"tt_scanline": "Efeito de linhas de varredura CRT no console",
|
||||||
|
"tt_seed_backup": "Mostrar e fazer backup da frase de recuperação de 24 palavras da sua carteira",
|
||||||
"tt_set_pin": "Definir um PIN de 4-8 dígitos para desbloqueio rápido",
|
"tt_set_pin": "Definir um PIN de 4-8 dígitos para desbloqueio rápido",
|
||||||
"tt_shield_mining": "Mover recompensas de mineração transparentes para um endereço blindado",
|
"tt_shield_mining": "Mover recompensas de mineração transparentes para um endereço blindado",
|
||||||
"tt_simple_bg": "Usar um gradiente simples para o fundo\\nAtalho: Ctrl+Up",
|
"tt_simple_bg": "Usar um gradiente simples para o fundo\\nAtalho: Ctrl+Up",
|
||||||
@@ -789,7 +966,9 @@
|
|||||||
"warning_upper": "AVISO!",
|
"warning_upper": "AVISO!",
|
||||||
"website": "Website",
|
"website": "Website",
|
||||||
"window_opacity": "Opacidade da Janela",
|
"window_opacity": "Opacidade da Janela",
|
||||||
|
"wizard_daemon_start_failed": "Falha ao iniciar o daemon — será tentado novamente automaticamente",
|
||||||
"yes_clear": "Sim, Limpar",
|
"yes_clear": "Sim, Limpar",
|
||||||
"your_addresses": "Seus Endereços",
|
"your_addresses": "Seus Endereços",
|
||||||
|
"z_address": "Endereço Z",
|
||||||
"z_addresses": "Endereços Z"
|
"z_addresses": "Endereços Z"
|
||||||
}
|
}
|
||||||
|
|||||||
187
res/lang/ru.json
187
res/lang/ru.json
@@ -42,7 +42,9 @@
|
|||||||
"address_upper": "АДРЕС",
|
"address_upper": "АДРЕС",
|
||||||
"address_url": "URL адреса",
|
"address_url": "URL адреса",
|
||||||
"addresses_appear_here": "Ваши адреса для получения появятся здесь после подключения.",
|
"addresses_appear_here": "Ваши адреса для получения появятся здесь после подключения.",
|
||||||
"advanced": "РАСШИРЕННЫЕ",
|
"advanced": "ПРОЧЕЕ",
|
||||||
|
"advanced_effects": "Расширенные эффекты...",
|
||||||
|
"ago": "назад",
|
||||||
"all_filter": "Все",
|
"all_filter": "Все",
|
||||||
"allow_custom_fees": "Разрешить пользовательские комиссии",
|
"allow_custom_fees": "Разрешить пользовательские комиссии",
|
||||||
"amount": "Сумма",
|
"amount": "Сумма",
|
||||||
@@ -90,12 +92,52 @@
|
|||||||
"block_timestamp": "Временная метка:",
|
"block_timestamp": "Временная метка:",
|
||||||
"block_transactions": "Транзакции:",
|
"block_transactions": "Транзакции:",
|
||||||
"blockchain_syncing": "Синхронизация блокчейна (%.1f%%)... Балансы могут быть неточными.",
|
"blockchain_syncing": "Синхронизация блокчейна (%.1f%%)... Балансы могут быть неточными.",
|
||||||
|
"bootstrap_daemon_running": "Демон запущен",
|
||||||
|
"bootstrap_daemon_stopped": "Демон остановлен",
|
||||||
|
"bootstrap_daemon_stopping": "Остановка демона...",
|
||||||
|
"bootstrap_desc": "Загрузите бутстрап блокчейна для значительного ускорения начальной синхронизации. Это загружает снимок блокчейна и извлекает его в ваш каталог данных.",
|
||||||
|
"bootstrap_downloading": "Загрузка бутстрапа...",
|
||||||
|
"bootstrap_extracting": "Извлечение данных блокчейна...",
|
||||||
|
"bootstrap_failed": "Ошибка бутстрапа",
|
||||||
|
"bootstrap_mirror": "Зеркало",
|
||||||
|
"bootstrap_mirror_tooltip": "Скачать с зеркала (bootstrap2.dragonx.is).\nИспользуйте, если основная загрузка медленная или не работает.",
|
||||||
|
"bootstrap_restart_daemon": "Перезапустить демон",
|
||||||
|
"bootstrap_success": "Бутстрап завершён",
|
||||||
|
"bootstrap_success_desc": "Данные блокчейна успешно извлечены. Запустите демон для начала синхронизации с точки бутстрапа.",
|
||||||
|
"bootstrap_trust_warning": "Используйте только bootstrap.dragonx.is или bootstrap2.dragonx.is. Использование файлов из ненадёжных источников может скомпрометировать ваш узел.",
|
||||||
|
"bootstrap_verifying": "Проверка контрольных сумм...",
|
||||||
|
"bootstrap_wallet_protected": "(wallet.dat защищён)",
|
||||||
|
"bootstrap_warning": "Существующие данные блоков (blocks, chainstate, notarizations) будут удалены и заменены. Ваш wallet.dat НЕ будет изменён или удалён.",
|
||||||
"cancel": "Отмена",
|
"cancel": "Отмена",
|
||||||
"characters": "символов",
|
"characters": "символов",
|
||||||
|
"chat": "Чат",
|
||||||
|
"chat_cancel": "Отмена",
|
||||||
|
"chat_contact_request": "запрос контакта",
|
||||||
|
"chat_empty_hint": "Пока нет переписок. Полученные сообщения появятся здесь.",
|
||||||
|
"chat_locked_hint": "Разблокируйте кошелёк, чтобы загрузить переписку.",
|
||||||
|
"chat_new_button": "Новая переписка",
|
||||||
|
"chat_new_message": "Сообщение",
|
||||||
|
"chat_new_send": "Отправить запрос",
|
||||||
|
"chat_new_title": "Новая переписка",
|
||||||
|
"chat_new_zaddr": "Z-адрес получателя",
|
||||||
|
"chat_select_hint": "Выберите переписку для просмотра.",
|
||||||
|
"chat_send": "Отправить",
|
||||||
|
"chat_send_failed": "не отправлено",
|
||||||
|
"chat_toast_compose_failed": "Не удалось составить сообщение (слишком длинное?).",
|
||||||
|
"chat_toast_lite_busy": "Отправка уже выполняется, или кошелёк не открыт.",
|
||||||
|
"chat_toast_no_zaddr": "Нет доступного Z-адреса для отправки сообщений.",
|
||||||
|
"chat_toast_not_connected": "Нет подключения — сообщение не отправлено.",
|
||||||
|
"chat_toast_request_compose_failed": "Не удалось составить запрос контакта (неверный адрес / текст?).",
|
||||||
|
"chat_toast_request_queued": "Запрос контакта поставлен в очередь.",
|
||||||
|
"chat_toast_waiting_reply": "Ожидание ответа от контакта — вы сможете писать ему только после этого.",
|
||||||
|
"chat_waiting_reply": "Ожидание ответа от контакта — вы сможете писать ему, как только он ответит.",
|
||||||
|
"chat_you": "Вы",
|
||||||
|
"choose_icon": "Выбрать иконку",
|
||||||
"clear": "Очистить",
|
"clear": "Очистить",
|
||||||
"clear_all_bans": "Снять все блокировки",
|
"clear_all_bans": "Снять все блокировки",
|
||||||
"clear_anyway": "Всё равно очистить",
|
"clear_anyway": "Всё равно очистить",
|
||||||
"clear_form_confirm": "Очистить все поля формы?",
|
"clear_form_confirm": "Очистить все поля формы?",
|
||||||
|
"clear_icon": "Удалить иконку",
|
||||||
"clear_request": "Очистить запрос",
|
"clear_request": "Очистить запрос",
|
||||||
"click_copy_address": "Нажмите, чтобы скопировать адрес",
|
"click_copy_address": "Нажмите, чтобы скопировать адрес",
|
||||||
"click_copy_uri": "Нажмите, чтобы скопировать URI",
|
"click_copy_uri": "Нажмите, чтобы скопировать URI",
|
||||||
@@ -106,8 +148,12 @@
|
|||||||
"confirm_clear_ztx_title": "Подтвердить очистку истории Z-Tx",
|
"confirm_clear_ztx_title": "Подтвердить очистку истории Z-Tx",
|
||||||
"confirm_clear_ztx_warning1": "Очистка истории z-транзакций может привести к отображению защищённого баланса как 0, пока не будет выполнено пересканирование кошелька.",
|
"confirm_clear_ztx_warning1": "Очистка истории z-транзакций может привести к отображению защищённого баланса как 0, пока не будет выполнено пересканирование кошелька.",
|
||||||
"confirm_clear_ztx_warning2": "Если это произойдёт, вам потребуется повторно импортировать приватные ключи вашего z-адреса с включённым пересканированием для восстановления баланса.",
|
"confirm_clear_ztx_warning2": "Если это произойдёт, вам потребуется повторно импортировать приватные ключи вашего z-адреса с включённым пересканированием для восстановления баланса.",
|
||||||
|
"confirm_delete_blockchain_msg": "Это остановит демон, удалит все данные блокчейна (blocks, chainstate, peers) и начнёт синхронизацию с нуля. Это может занять несколько часов.",
|
||||||
|
"confirm_delete_blockchain_safe": "Ваш wallet.dat, конфигурация и история транзакций в безопасности и не будут удалены.",
|
||||||
|
"confirm_delete_blockchain_title": "Удалить данные блокчейна",
|
||||||
"confirm_send": "Подтвердить отправку",
|
"confirm_send": "Подтвердить отправку",
|
||||||
"confirm_transaction": "Подтвердить транзакцию",
|
"confirm_transaction": "Подтвердить транзакцию",
|
||||||
|
"confirm_transfer": "Подтвердить перевод",
|
||||||
"confirmations": "Подтверждения",
|
"confirmations": "Подтверждения",
|
||||||
"confirmations_display": "%d подтверждений | %s",
|
"confirmations_display": "%d подтверждений | %s",
|
||||||
"confirmed": "Подтверждено",
|
"confirmed": "Подтверждено",
|
||||||
@@ -172,6 +218,7 @@
|
|||||||
"console_welcome": "Добро пожаловать в консоль ObsidianDragon",
|
"console_welcome": "Добро пожаловать в консоль ObsidianDragon",
|
||||||
"console_zoom_in": "Увеличить",
|
"console_zoom_in": "Увеличить",
|
||||||
"console_zoom_out": "Уменьшить",
|
"console_zoom_out": "Уменьшить",
|
||||||
|
"copied": "Скопировано!",
|
||||||
"copy": "Копировать",
|
"copy": "Копировать",
|
||||||
"copy_address": "Копировать полный адрес",
|
"copy_address": "Копировать полный адрес",
|
||||||
"copy_error": "Копировать ошибку",
|
"copy_error": "Копировать ошибку",
|
||||||
@@ -180,22 +227,45 @@
|
|||||||
"copy_uri": "Копировать URI",
|
"copy_uri": "Копировать URI",
|
||||||
"current_price": "Текущая цена",
|
"current_price": "Текущая цена",
|
||||||
"custom_fees": "Пользовательские комиссии",
|
"custom_fees": "Пользовательские комиссии",
|
||||||
|
"daemon_version": "Демон",
|
||||||
"dark": "Тёмная",
|
"dark": "Тёмная",
|
||||||
"date": "Дата",
|
"date": "Дата",
|
||||||
"date_label": "Дата:",
|
"date_label": "Дата:",
|
||||||
"debug_logging": "ЖУРНАЛ ОТЛАДКИ",
|
"debug_logging": "ЖУРНАЛ ОТЛАДКИ",
|
||||||
"delete": "Удалить",
|
"delete": "Удалить",
|
||||||
|
"delete_blockchain": "Удалить блокчейн",
|
||||||
|
"delete_blockchain_confirm": "Удалить и пересинхронизировать",
|
||||||
|
"deshielding_warning": "Внимание: это переведёт средства из приватного (Z) адреса на прозрачный (T) адрес.",
|
||||||
"difficulty": "Сложность",
|
"difficulty": "Сложность",
|
||||||
"disconnected": "Отключено",
|
"disconnected": "Отключено",
|
||||||
"dismiss": "Отклонить",
|
"dismiss": "Отклонить",
|
||||||
"display": "Отображение",
|
"display": "Отображение",
|
||||||
|
"download": "Скачать",
|
||||||
|
"download_bootstrap": "Скачать бутстрап",
|
||||||
"dragonx_green": "DragonX (Зелёная)",
|
"dragonx_green": "DragonX (Зелёная)",
|
||||||
"edit": "Редактировать",
|
"edit": "Редактировать",
|
||||||
"error": "Ошибка",
|
"error": "Ошибка",
|
||||||
"error_format": "Ошибка: %s",
|
"error_format": "Ошибка: %s",
|
||||||
"est_time_to_block": "Расч. время до блока",
|
"est_time_to_block": "Расч. время до блока",
|
||||||
"exit": "Выход",
|
"exit": "Выход",
|
||||||
"explorer": "ОБОЗРЕВАТЕЛЬ",
|
"explorer": "Проводник",
|
||||||
|
"explorer_block_detail": "Блок",
|
||||||
|
"explorer_block_hash": "Хеш",
|
||||||
|
"explorer_block_height": "Высота",
|
||||||
|
"explorer_block_merkle": "Корень Меркла",
|
||||||
|
"explorer_block_size": "Размер",
|
||||||
|
"explorer_block_time": "Время",
|
||||||
|
"explorer_block_txs": "Транзакции",
|
||||||
|
"explorer_chain_stats": "Цепочка",
|
||||||
|
"explorer_invalid_query": "Введите высоту блока или 64-символьный хеш",
|
||||||
|
"explorer_mempool": "Мемпул",
|
||||||
|
"explorer_mempool_size": "Размер",
|
||||||
|
"explorer_mempool_txs": "Транзакции",
|
||||||
|
"explorer_recent_blocks": "Последние блоки",
|
||||||
|
"explorer_search": "Поиск",
|
||||||
|
"explorer_section": "ОБОЗРЕВАТЕЛЬ",
|
||||||
|
"explorer_tx_outputs": "Выходы",
|
||||||
|
"explorer_tx_size": "Размер",
|
||||||
"export": "Экспорт",
|
"export": "Экспорт",
|
||||||
"export_csv": "Экспорт в CSV",
|
"export_csv": "Экспорт в CSV",
|
||||||
"export_keys_btn": "Экспорт ключей",
|
"export_keys_btn": "Экспорт ключей",
|
||||||
@@ -224,14 +294,22 @@
|
|||||||
"fetch_prices": "Получить цены",
|
"fetch_prices": "Получить цены",
|
||||||
"file": "Файл",
|
"file": "Файл",
|
||||||
"file_save_location": "Файл будет сохранён в: ~/.config/ObsidianDragon/",
|
"file_save_location": "Файл будет сохранён в: ~/.config/ObsidianDragon/",
|
||||||
|
"filter": "Фильтр...",
|
||||||
"font_scale": "Масштаб шрифта",
|
"font_scale": "Масштаб шрифта",
|
||||||
|
"force_quit": "Принудительный выход",
|
||||||
|
"force_quit_confirm_msg": "Это немедленно завершит демон без корректного завершения.\nЭто может повредить индекс блокчейна и потребовать повторной синхронизации.",
|
||||||
|
"force_quit_confirm_title": "Принудительный выход?",
|
||||||
|
"force_quit_warning": "Это немедленно завершит демон без корректного завершения. Может потребоваться повторная синхронизация блокчейна.",
|
||||||
|
"force_quit_yes": "Принудительный выход",
|
||||||
"from": "От",
|
"from": "От",
|
||||||
"from_upper": "ОТ",
|
"from_upper": "ОТ",
|
||||||
"full_details": "Полные детали",
|
"full_details": "Полные детали",
|
||||||
"general": "Общие",
|
"general": "Общие",
|
||||||
|
"generating": "Генерация",
|
||||||
"go_to_receive": "Перейти к получению",
|
"go_to_receive": "Перейти к получению",
|
||||||
"height": "Высота",
|
"height": "Высота",
|
||||||
"help": "Справка",
|
"help": "Справка",
|
||||||
|
"hidden_tag": " (скрыт)",
|
||||||
"hide": "Скрыть",
|
"hide": "Скрыть",
|
||||||
"hide_address": "Скрыть адрес",
|
"hide_address": "Скрыть адрес",
|
||||||
"hide_zero_balances": "Скрыть нулевые балансы",
|
"hide_zero_balances": "Скрыть нулевые балансы",
|
||||||
@@ -253,6 +331,9 @@
|
|||||||
"import_key_warning": "Предупреждение: Никогда не делитесь своими приватными ключами! Импорт ключей из ненадёжных источников может скомпрометировать ваш кошелёк.",
|
"import_key_warning": "Предупреждение: Никогда не делитесь своими приватными ключами! Импорт ключей из ненадёжных источников может скомпрометировать ваш кошелёк.",
|
||||||
"import_key_z_format": "Ключи расходования z-адресов (secret-extended-key-...)",
|
"import_key_z_format": "Ключи расходования z-адресов (secret-extended-key-...)",
|
||||||
"import_private_key": "Импорт приватного ключа...",
|
"import_private_key": "Импорт приватного ключа...",
|
||||||
|
"incorrect_passphrase": "Неверный пароль",
|
||||||
|
"incorrect_pin": "Неверный PIN",
|
||||||
|
"insufficient_funds": "Недостаточно средств для этой суммы плюс комиссия.",
|
||||||
"invalid_address": "Неверный формат адреса",
|
"invalid_address": "Неверный формат адреса",
|
||||||
"ip_address": "IP-адрес",
|
"ip_address": "IP-адрес",
|
||||||
"keep": "Сохранить",
|
"keep": "Сохранить",
|
||||||
@@ -266,6 +347,7 @@
|
|||||||
"key_export_viewing_keys_zonly": "Ключи просмотра доступны только для экранированных (z) адресов",
|
"key_export_viewing_keys_zonly": "Ключи просмотра доступны только для экранированных (z) адресов",
|
||||||
"key_export_viewing_warning": "Этот ключ просмотра позволяет другим видеть входящие транзакции и баланс, но НЕ тратить ваши средства. Делитесь только с доверенными лицами.",
|
"key_export_viewing_warning": "Этот ключ просмотра позволяет другим видеть входящие транзакции и баланс, но НЕ тратить ваши средства. Делитесь только с доверенными лицами.",
|
||||||
"label": "Метка:",
|
"label": "Метка:",
|
||||||
|
"label_placeholder": "напр. Накопления, Майнинг...",
|
||||||
"language": "Язык",
|
"language": "Язык",
|
||||||
"light": "Светлая",
|
"light": "Светлая",
|
||||||
"loading": "Загрузка...",
|
"loading": "Загрузка...",
|
||||||
@@ -286,6 +368,7 @@
|
|||||||
"market_now": "Сейчас",
|
"market_now": "Сейчас",
|
||||||
"market_pct_shielded": "%.0f%% Экранировано",
|
"market_pct_shielded": "%.0f%% Экранировано",
|
||||||
"market_portfolio": "ПОРТФЕЛЬ",
|
"market_portfolio": "ПОРТФЕЛЬ",
|
||||||
|
"market_price_loading": "Загрузка данных о ценах...",
|
||||||
"market_price_unavailable": "Данные о ценах недоступны",
|
"market_price_unavailable": "Данные о ценах недоступны",
|
||||||
"market_refresh_price": "Обновить данные о ценах",
|
"market_refresh_price": "Обновить данные о ценах",
|
||||||
"market_trade_on": "Торговать на %s",
|
"market_trade_on": "Торговать на %s",
|
||||||
@@ -311,6 +394,13 @@
|
|||||||
"mining_address_copied": "Адрес майнинга скопирован",
|
"mining_address_copied": "Адрес майнинга скопирован",
|
||||||
"mining_all_time": "За всё время",
|
"mining_all_time": "За всё время",
|
||||||
"mining_already_saved": "URL пула уже сохранён",
|
"mining_already_saved": "URL пула уже сохранён",
|
||||||
|
"mining_benchmark_cancel": "Отменить тест",
|
||||||
|
"mining_benchmark_cooling": "Охлаждение",
|
||||||
|
"mining_benchmark_dismiss": "Закрыть",
|
||||||
|
"mining_benchmark_result": "Оптимально",
|
||||||
|
"mining_benchmark_stabilizing": "Стабилизация",
|
||||||
|
"mining_benchmark_testing": "Тестирование",
|
||||||
|
"mining_benchmark_tooltip": "Найти оптимальное количество потоков для этого процессора",
|
||||||
"mining_block_copied": "Хэш блока скопирован",
|
"mining_block_copied": "Хэш блока скопирован",
|
||||||
"mining_chart_1m_ago": "1м назад",
|
"mining_chart_1m_ago": "1м назад",
|
||||||
"mining_chart_5m_ago": "5м назад",
|
"mining_chart_5m_ago": "5м назад",
|
||||||
@@ -330,8 +420,15 @@
|
|||||||
"mining_filter_tip_all": "Показать все доходы",
|
"mining_filter_tip_all": "Показать все доходы",
|
||||||
"mining_filter_tip_pool": "Показать только доходы пула",
|
"mining_filter_tip_pool": "Показать только доходы пула",
|
||||||
"mining_filter_tip_solo": "Показать только доходы соло",
|
"mining_filter_tip_solo": "Показать только доходы соло",
|
||||||
|
"mining_generate_z_address_hint": "Создайте Z-адрес на вкладке «Получить» для использования в качестве адреса выплат",
|
||||||
|
"mining_idle_gpu_off_tooltip": "Без ограничений: ВКЛ\nТолько ввод с клавиатуры/мыши определяет состояние простоя\nНажмите для GPU-контроля",
|
||||||
|
"mining_idle_gpu_on_tooltip": "GPU-контроль: ВКЛ\nАктивность GPU (видео, игры) предотвращает майнинг в простое\nНажмите для режима без ограничений",
|
||||||
"mining_idle_off_tooltip": "Включить майнинг в простое",
|
"mining_idle_off_tooltip": "Включить майнинг в простое",
|
||||||
"mining_idle_on_tooltip": "Отключить майнинг в простое",
|
"mining_idle_on_tooltip": "Отключить майнинг в простое",
|
||||||
|
"mining_idle_scale_off_tooltip": "Режим старт/стоп: ВКЛ\nНажмите для переключения на масштабирование потоков",
|
||||||
|
"mining_idle_scale_on_tooltip": "Масштабирование потоков: ВКЛ\nНажмите для переключения на режим старт/стоп",
|
||||||
|
"mining_idle_threads_active_tooltip": "Потоки при активности пользователя",
|
||||||
|
"mining_idle_threads_idle_tooltip": "Потоки при простое системы",
|
||||||
"mining_local_hashrate": "Локальный хешрейт",
|
"mining_local_hashrate": "Локальный хешрейт",
|
||||||
"mining_mine": "Майнить",
|
"mining_mine": "Майнить",
|
||||||
"mining_mining_addr": "Адрес майн.",
|
"mining_mining_addr": "Адрес майн.",
|
||||||
@@ -388,6 +485,7 @@
|
|||||||
"no_addresses_available": "Нет доступных адресов",
|
"no_addresses_available": "Нет доступных адресов",
|
||||||
"no_addresses_match": "Нет адресов, соответствующих фильтру",
|
"no_addresses_match": "Нет адресов, соответствующих фильтру",
|
||||||
"no_addresses_with_balance": "Нет адресов с балансом",
|
"no_addresses_with_balance": "Нет адресов с балансом",
|
||||||
|
"no_addresses_yet": "Пока нет адресов",
|
||||||
"no_matching": "Нет подходящих транзакций",
|
"no_matching": "Нет подходящих транзакций",
|
||||||
"no_recent_receives": "Нет недавних получений",
|
"no_recent_receives": "Нет недавних получений",
|
||||||
"no_recent_sends": "Нет недавних отправлений",
|
"no_recent_sends": "Нет недавних отправлений",
|
||||||
@@ -453,6 +551,7 @@
|
|||||||
"peers_upper": "УЗЛЫ",
|
"peers_upper": "УЗЛЫ",
|
||||||
"peers_version": "Версия",
|
"peers_version": "Версия",
|
||||||
"pending": "Ожидание",
|
"pending": "Ожидание",
|
||||||
|
"pin_not_set": "PIN не установлен. Используйте пароль для разблокировки.",
|
||||||
"ping": "Пинг",
|
"ping": "Пинг",
|
||||||
"price_chart": "График цен",
|
"price_chart": "График цен",
|
||||||
"qr_code": "QR-код",
|
"qr_code": "QR-код",
|
||||||
@@ -473,7 +572,9 @@
|
|||||||
"recent_received": "НЕДАВНО ПОЛУЧЕНО",
|
"recent_received": "НЕДАВНО ПОЛУЧЕНО",
|
||||||
"recent_sends": "НЕДАВНО ОТПРАВЛЕНО",
|
"recent_sends": "НЕДАВНО ОТПРАВЛЕНО",
|
||||||
"recipient": "ПОЛУЧАТЕЛЬ",
|
"recipient": "ПОЛУЧАТЕЛЬ",
|
||||||
|
"recipient_balance": "Получатель: %.8f → %.8f DRGX",
|
||||||
"recv_type": "Получ.",
|
"recv_type": "Получ.",
|
||||||
|
"reduce_motion": "Уменьшить анимацию",
|
||||||
"refresh": "Обновить",
|
"refresh": "Обновить",
|
||||||
"refresh_now": "Обновить сейчас",
|
"refresh_now": "Обновить сейчас",
|
||||||
"remove_favorite": "Удалить из избранного",
|
"remove_favorite": "Удалить из избранного",
|
||||||
@@ -493,7 +594,10 @@
|
|||||||
"request_uri_copied": "URI платежа скопирован в буфер обмена",
|
"request_uri_copied": "URI платежа скопирован в буфер обмена",
|
||||||
"rescan": "Пересканировать",
|
"rescan": "Пересканировать",
|
||||||
"reset_to_defaults": "Сбросить настройки",
|
"reset_to_defaults": "Сбросить настройки",
|
||||||
|
"restarting_after_encryption": "Перезапуск демона после шифрования...",
|
||||||
"restore_address": "Восстановить адрес",
|
"restore_address": "Восстановить адрес",
|
||||||
|
"result_preview": "Предпросмотр результата",
|
||||||
|
"retry": "Повторить",
|
||||||
"review_send": "Проверить отправку",
|
"review_send": "Проверить отправку",
|
||||||
"rpc_host": "RPC-хост",
|
"rpc_host": "RPC-хост",
|
||||||
"rpc_pass": "Пароль",
|
"rpc_pass": "Пароль",
|
||||||
@@ -502,8 +606,55 @@
|
|||||||
"save": "Сохранить",
|
"save": "Сохранить",
|
||||||
"save_settings": "Сохранить настройки",
|
"save_settings": "Сохранить настройки",
|
||||||
"save_z_transactions": "Сохранять Z-tx в списке транзакций",
|
"save_z_transactions": "Сохранять Z-tx в списке транзакций",
|
||||||
|
"sb_auth_failed": "Ошибка авторизации — проверьте rpcuser/rpcpassword",
|
||||||
|
"sb_block": "Блок: %d",
|
||||||
|
"sb_connecting_daemon": "Подключение к dragonxd...",
|
||||||
|
"sb_connecting_err": "Подключение к демону — %s",
|
||||||
|
"sb_connecting_external": "Подключение к внешнему демону...",
|
||||||
|
"sb_connecting_generic": "Подключение к демону...",
|
||||||
|
"sb_daemon_crashed": "Демон упал %d раз",
|
||||||
|
"sb_daemon_not_found": "Демон не найден",
|
||||||
|
"sb_dragonxd_running": "dragonxd запущен",
|
||||||
|
"sb_dragonxd_stopped": "dragonxd остановлен",
|
||||||
|
"sb_dragonxd_stopping": "Остановка dragonxd...",
|
||||||
|
"sb_extracting_sapling": "Извлечение параметров Sapling...",
|
||||||
|
"sb_importing_keys": "Импорт ключей",
|
||||||
|
"sb_loading_config": "Загрузка конфигурации...",
|
||||||
|
"sb_mining_hs": "%.1f H/s",
|
||||||
|
"sb_net_ghs": "Сеть: %.2f GH/s",
|
||||||
|
"sb_net_hs": "Сеть: %.1f H/s",
|
||||||
|
"sb_net_khs": "Сеть: %.2f KH/s",
|
||||||
|
"sb_net_mhs": "Сеть: %.2f MH/s",
|
||||||
|
"sb_no_conf": "DRAGONX.conf не найден",
|
||||||
|
"sb_peers": "Пиры: %zu",
|
||||||
|
"sb_rescanning": "Пересканирование",
|
||||||
|
"sb_rescanning_pct": "Пересканирование %.0f%%",
|
||||||
|
"sb_restarting_daemon": "Перезапуск демона...",
|
||||||
|
"sb_sapling_failed": "Ошибка извлечения параметров Sapling.",
|
||||||
|
"sb_sapling_not_found": "Параметры Sapling не найдены.",
|
||||||
|
"sb_starting_daemon": "Запуск dragonxd...",
|
||||||
|
"sb_syncing_basic": "Синхронизация %.1f%% (%d осталось)",
|
||||||
|
"sb_syncing_eta": "Синхронизация %.1f%% (%d осталось, %.0f блк/с, ~%s)",
|
||||||
|
"sb_waiting_config": "Ожидание конфигурации демона...",
|
||||||
|
"sb_waiting_daemon": "Ожидание dragonxd...",
|
||||||
|
"sb_waiting_daemon_err": "Ожидание dragonxd — %s",
|
||||||
|
"sb_warming_up": "Прогрев...",
|
||||||
"search_placeholder": "Поиск...",
|
"search_placeholder": "Поиск...",
|
||||||
"security": "БЕЗОПАСНОСТЬ",
|
"security": "БЕЗОПАСНОСТЬ",
|
||||||
|
"seed_backup_button": "Сид-фраза",
|
||||||
|
"seed_backup_close": "Закрыть",
|
||||||
|
"seed_backup_copy": "Копировать",
|
||||||
|
"seed_backup_intro": "Эти слова — главная резервная копия вашего кошелька. Любой, у кого они есть, управляет вашими средствами.",
|
||||||
|
"seed_backup_load_failed": "Не удалось загрузить сид-фразу.",
|
||||||
|
"seed_backup_loading": "Загрузка вашей сид-фразы…",
|
||||||
|
"seed_backup_locked": "Разблокируйте кошелёк, чтобы показать его сид-фразу.",
|
||||||
|
"seed_backup_none": "Этот кошелёк создан до появления сид-фраз, поэтому у него нет фразы из 24 слов. Создайте резервную копию с помощью «Резервная копия...» или вместо этого экспортируйте свои приватные ключи.",
|
||||||
|
"seed_backup_reminder": "У вашего кошелька есть сид-фраза восстановления из 24 слов. Создайте её резервную копию прямо сейчас в Настройки → Узел и безопасность.",
|
||||||
|
"seed_backup_save": "Сохранить в файл…",
|
||||||
|
"seed_backup_save_failed": "Не удалось записать ",
|
||||||
|
"seed_backup_saved": "Сохранено в ",
|
||||||
|
"seed_backup_title": "Резервная копия сид-фразы",
|
||||||
|
"seed_backup_warning": "Запишите их по порядку, храните офлайн и никогда не делитесь ими и не фотографируйте их. Если вы их потеряете, ваши средства не удастся восстановить.",
|
||||||
"select_address": "Выбрать адрес...",
|
"select_address": "Выбрать адрес...",
|
||||||
"select_receiving_address": "Выбрать адрес получения...",
|
"select_receiving_address": "Выбрать адрес получения...",
|
||||||
"select_source_address": "Выбрать адрес-источник...",
|
"select_source_address": "Выбрать адрес-источник...",
|
||||||
@@ -553,12 +704,15 @@
|
|||||||
"send_valid_transparent": "Действительный прозрачный адрес",
|
"send_valid_transparent": "Действительный прозрачный адрес",
|
||||||
"send_wallet_empty": "Ваш кошелёк пуст",
|
"send_wallet_empty": "Ваш кошелёк пуст",
|
||||||
"send_yes_clear": "Да, очистить",
|
"send_yes_clear": "Да, очистить",
|
||||||
|
"sender_balance": "Отправитель: %.8f → %.8f DRGX",
|
||||||
"sending": "Отправка транзакции",
|
"sending": "Отправка транзакции",
|
||||||
"sending_from": "ОТПРАВКА С",
|
"sending_from": "ОТПРАВКА С",
|
||||||
|
"sends_full_balance_warning": "Это отправит весь баланс. Адрес отправителя останется с нулевым балансом.",
|
||||||
"sent": "отправлено",
|
"sent": "отправлено",
|
||||||
"sent_filter": "Отправлено",
|
"sent_filter": "Отправлено",
|
||||||
"sent_type": "Отправлено",
|
"sent_type": "Отправлено",
|
||||||
"sent_upper": "ОТПРАВЛЕНО",
|
"sent_upper": "ОТПРАВЛЕНО",
|
||||||
|
"set_label": "Установить метку...",
|
||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
"settings_about_text": "Защищённый криптовалютный кошелёк для DragonX (DRGX), созданный на Dear ImGui для лёгкого и портативного использования.",
|
"settings_about_text": "Защищённый криптовалютный кошелёк для DragonX (DRGX), созданный на Dear ImGui для лёгкого и портативного использования.",
|
||||||
"settings_acrylic_level": "Уровень акрила:",
|
"settings_acrylic_level": "Уровень акрила:",
|
||||||
@@ -647,13 +801,17 @@
|
|||||||
"shield_utxo_limit": "Лимит UTXO:",
|
"shield_utxo_limit": "Лимит UTXO:",
|
||||||
"shield_wildcard_hint": "Используйте '*' для экранирования со всех прозрачных адресов",
|
"shield_wildcard_hint": "Используйте '*' для экранирования со всех прозрачных адресов",
|
||||||
"shielded": "Экранированный",
|
"shielded": "Экранированный",
|
||||||
|
"shielded_address": "Экранированный адрес",
|
||||||
"shielded_to": "ЭКРАНИРОВАНО НА",
|
"shielded_to": "ЭКРАНИРОВАНО НА",
|
||||||
"shielded_type": "Экранированный",
|
"shielded_type": "Экранированный",
|
||||||
|
"shielding_notice": "Примечание: это переведёт средства с прозрачного (T) адреса на приватный (Z) адрес.",
|
||||||
"show": "Показать",
|
"show": "Показать",
|
||||||
"show_hidden": "Показать скрытые (%d)",
|
"show_hidden": "Показать скрытые (%d)",
|
||||||
"show_qr_code": "Показать QR-код",
|
"show_qr_code": "Показать QR-код",
|
||||||
"showing_transactions": "Показано %dâ%d из %d транзакций (всего: %zu)",
|
"showing_transactions": "Показано %d–%d из %d транзакций (всего: %zu)",
|
||||||
|
"showing_x_of_y": "Показано %d из %d адресов",
|
||||||
"simple_background": "Простой фон",
|
"simple_background": "Простой фон",
|
||||||
|
"slider_off": "Выкл",
|
||||||
"start_mining": "Начать майнинг",
|
"start_mining": "Начать майнинг",
|
||||||
"status": "Статус",
|
"status": "Статус",
|
||||||
"stop_external": "Остановить внешний daemon",
|
"stop_external": "Остановить внешний daemon",
|
||||||
@@ -662,17 +820,26 @@
|
|||||||
"success": "Успешно",
|
"success": "Успешно",
|
||||||
"summary": "Итоги",
|
"summary": "Итоги",
|
||||||
"syncing": "Синхронизация...",
|
"syncing": "Синхронизация...",
|
||||||
|
"t_address": "T-адрес",
|
||||||
"t_addresses": "T-адреса",
|
"t_addresses": "T-адреса",
|
||||||
"test_connection": "Тест",
|
"test_connection": "Тест",
|
||||||
"theme": "Тема",
|
"theme": "Тема",
|
||||||
"theme_effects": "Эффекты темы",
|
"theme_effects": "Эффекты темы",
|
||||||
|
"theme_language": "ТЕМА И ЯЗЫК",
|
||||||
"time_days_ago": "%d дней назад",
|
"time_days_ago": "%d дней назад",
|
||||||
"time_hours_ago": "%d часов назад",
|
"time_hours_ago": "%d часов назад",
|
||||||
"time_minutes_ago": "%d минут назад",
|
"time_minutes_ago": "%d минут назад",
|
||||||
"time_seconds_ago": "%d секунд назад",
|
"time_seconds_ago": "%d секунд назад",
|
||||||
|
"timeout_15min": "15 мин",
|
||||||
|
"timeout_1hour": "1 час",
|
||||||
|
"timeout_1min": "1 мин",
|
||||||
|
"timeout_30min": "30 мин",
|
||||||
|
"timeout_5min": "5 мин",
|
||||||
|
"timeout_off": "Выкл",
|
||||||
"to": "Кому",
|
"to": "Кому",
|
||||||
"to_upper": "КОМУ",
|
"to_upper": "КОМУ",
|
||||||
"tools": "ИНСТРУМЕНТЫ",
|
"tools": "УТИЛИТЫ",
|
||||||
|
"tools_actions": "Инструменты и действия...",
|
||||||
"total": "Итого",
|
"total": "Итого",
|
||||||
"transaction_id": "ID ТРАНЗАКЦИИ",
|
"transaction_id": "ID ТРАНЗАКЦИИ",
|
||||||
"transaction_sent": "Транзакция успешно отправлена",
|
"transaction_sent": "Транзакция успешно отправлена",
|
||||||
@@ -680,7 +847,13 @@
|
|||||||
"transaction_url": "URL транзакции",
|
"transaction_url": "URL транзакции",
|
||||||
"transactions": "Транзакции",
|
"transactions": "Транзакции",
|
||||||
"transactions_upper": "ТРАНЗАКЦИИ",
|
"transactions_upper": "ТРАНЗАКЦИИ",
|
||||||
|
"transfer_failed": "Ошибка перевода",
|
||||||
|
"transfer_funds": "Перевести средства",
|
||||||
|
"transfer_sent": "Перевод отправлен",
|
||||||
|
"transfer_sent_desc": "Ваш перевод отправлен в сеть.",
|
||||||
|
"transfer_to": "Перевести на:",
|
||||||
"transparent": "Прозрачный",
|
"transparent": "Прозрачный",
|
||||||
|
"transparent_address": "Прозрачный адрес",
|
||||||
"tt_addr_url": "Базовый URL для просмотра адресов в обозревателе блоков",
|
"tt_addr_url": "Базовый URL для просмотра адресов в обозревателе блоков",
|
||||||
"tt_address_book": "Управление сохранёнными адресами для быстрой отправки",
|
"tt_address_book": "Управление сохранёнными адресами для быстрой отправки",
|
||||||
"tt_auto_lock": "Заблокировать кошелёк после этого времени бездействия",
|
"tt_auto_lock": "Заблокировать кошелёк после этого времени бездействия",
|
||||||
@@ -695,6 +868,8 @@
|
|||||||
"tt_custom_theme": "Пользовательская тема активна",
|
"tt_custom_theme": "Пользовательская тема активна",
|
||||||
"tt_debug_collapse": "Свернуть параметры журнала отладки",
|
"tt_debug_collapse": "Свернуть параметры журнала отладки",
|
||||||
"tt_debug_expand": "Развернуть параметры журнала отладки",
|
"tt_debug_expand": "Развернуть параметры журнала отладки",
|
||||||
|
"tt_delete_blockchain": "Удалить все данные блокчейна и начать синхронизацию заново. wallet.dat и конфигурация сохраняются.",
|
||||||
|
"tt_download_bootstrap": "Скачать бутстрап блокчейна для ускорения синхронизации\nСуществующие данные блоков будут заменены",
|
||||||
"tt_encrypt": "Зашифровать wallet.dat паролем",
|
"tt_encrypt": "Зашифровать wallet.dat паролем",
|
||||||
"tt_export_all": "Экспортировать все приватные ключи в файл",
|
"tt_export_all": "Экспортировать все приватные ключи в файл",
|
||||||
"tt_export_csv": "Экспортировать историю транзакций в виде таблицы CSV",
|
"tt_export_csv": "Экспортировать историю транзакций в виде таблицы CSV",
|
||||||
@@ -712,6 +887,7 @@
|
|||||||
"tt_mine_idle": "Автоматически начать майнинг при\\nпростое системы (нет ввода с клавиатуры/мыши)",
|
"tt_mine_idle": "Автоматически начать майнинг при\\nпростое системы (нет ввода с клавиатуры/мыши)",
|
||||||
"tt_noise": "Интенсивность зернистой текстуры (0%% = выкл., 100%% = максимум)",
|
"tt_noise": "Интенсивность зернистой текстуры (0%% = выкл., 100%% = максимум)",
|
||||||
"tt_open_dir": "Нажмите, чтобы открыть в проводнике",
|
"tt_open_dir": "Нажмите, чтобы открыть в проводнике",
|
||||||
|
"tt_reduce_motion": "Отключить анимированные переходы и плавное изменение баланса для доступности",
|
||||||
"tt_remove_encrypt": "Удалить шифрование и хранить кошелёк без защиты",
|
"tt_remove_encrypt": "Удалить шифрование и хранить кошелёк без защиты",
|
||||||
"tt_remove_pin": "Удалить PIN и требовать пароль для разблокировки",
|
"tt_remove_pin": "Удалить PIN и требовать пароль для разблокировки",
|
||||||
"tt_report_bug": "Сообщить о проблеме в трекере проекта",
|
"tt_report_bug": "Сообщить о проблеме в трекере проекта",
|
||||||
@@ -727,6 +903,7 @@
|
|||||||
"tt_save_ztx": "Хранить историю транзакций z-адреса локально для более быстрой загрузки",
|
"tt_save_ztx": "Хранить историю транзакций z-адреса локально для более быстрой загрузки",
|
||||||
"tt_scan_themes": "Поиск новых тем.\\nРазместите папки тем в:\\n%s",
|
"tt_scan_themes": "Поиск новых тем.\\nРазместите папки тем в:\\n%s",
|
||||||
"tt_scanline": "Эффект развёртки ЭЛТ в консоли",
|
"tt_scanline": "Эффект развёртки ЭЛТ в консоли",
|
||||||
|
"tt_seed_backup": "Показать и создать резервную копию сид-фразы восстановления вашего кошелька из 24 слов",
|
||||||
"tt_set_pin": "Установить 4-8-значный PIN для быстрой разблокировки",
|
"tt_set_pin": "Установить 4-8-значный PIN для быстрой разблокировки",
|
||||||
"tt_shield_mining": "Перевести прозрачные вознаграждения за майнинг на экранированный адрес",
|
"tt_shield_mining": "Перевести прозрачные вознаграждения за майнинг на экранированный адрес",
|
||||||
"tt_simple_bg": "Использовать простой градиент для фона\\nГорячая клавиша: Ctrl+Up",
|
"tt_simple_bg": "Использовать простой градиент для фона\\nГорячая клавиша: Ctrl+Up",
|
||||||
@@ -789,7 +966,9 @@
|
|||||||
"warning_upper": "ПРЕДУПРЕЖДЕНИЕ!",
|
"warning_upper": "ПРЕДУПРЕЖДЕНИЕ!",
|
||||||
"website": "Веб-сайт",
|
"website": "Веб-сайт",
|
||||||
"window_opacity": "Прозрачность окна",
|
"window_opacity": "Прозрачность окна",
|
||||||
|
"wizard_daemon_start_failed": "Не удалось запустить демон — будет повторено автоматически",
|
||||||
"yes_clear": "Да, очистить",
|
"yes_clear": "Да, очистить",
|
||||||
"your_addresses": "Ваши адреса",
|
"your_addresses": "Ваши адреса",
|
||||||
|
"z_address": "Z-адрес",
|
||||||
"z_addresses": "Z-адреса"
|
"z_addresses": "Z-адреса"
|
||||||
}
|
}
|
||||||
|
|||||||
181
res/lang/zh.json
181
res/lang/zh.json
@@ -43,6 +43,8 @@
|
|||||||
"address_url": "地址 URL",
|
"address_url": "地址 URL",
|
||||||
"addresses_appear_here": "连接后,您的接收地址将显示在此处。",
|
"addresses_appear_here": "连接后,您的接收地址将显示在此处。",
|
||||||
"advanced": "高级",
|
"advanced": "高级",
|
||||||
|
"advanced_effects": "高级特效...",
|
||||||
|
"ago": "前",
|
||||||
"all_filter": "全部",
|
"all_filter": "全部",
|
||||||
"allow_custom_fees": "允许自定义手续费",
|
"allow_custom_fees": "允许自定义手续费",
|
||||||
"amount": "金额",
|
"amount": "金额",
|
||||||
@@ -90,12 +92,52 @@
|
|||||||
"block_timestamp": "时间戳:",
|
"block_timestamp": "时间戳:",
|
||||||
"block_transactions": "交易:",
|
"block_transactions": "交易:",
|
||||||
"blockchain_syncing": "区块链同步中 (%.1f%%)... 余额可能不准确。",
|
"blockchain_syncing": "区块链同步中 (%.1f%%)... 余额可能不准确。",
|
||||||
|
"bootstrap_daemon_running": "守护进程运行中",
|
||||||
|
"bootstrap_daemon_stopped": "守护进程已停止",
|
||||||
|
"bootstrap_daemon_stopping": "正在停止守护进程...",
|
||||||
|
"bootstrap_desc": "下载区块链引导程序以大幅加速初始同步。这将下载区块链快照并将其提取到您的数据目录中。",
|
||||||
|
"bootstrap_downloading": "正在下载引导程序...",
|
||||||
|
"bootstrap_extracting": "正在提取区块链数据...",
|
||||||
|
"bootstrap_failed": "引导程序失败",
|
||||||
|
"bootstrap_mirror": "镜像",
|
||||||
|
"bootstrap_mirror_tooltip": "从镜像下载 (bootstrap2.dragonx.is)。\n如果主下载速度慢或失败,请使用此选项。",
|
||||||
|
"bootstrap_restart_daemon": "重启守护进程",
|
||||||
|
"bootstrap_success": "引导程序完成",
|
||||||
|
"bootstrap_success_desc": "区块链数据已成功提取。启动守护进程以从引导点开始同步。",
|
||||||
|
"bootstrap_trust_warning": "仅使用 bootstrap.dragonx.is 或 bootstrap2.dragonx.is。使用不受信任来源的文件可能会危及您的节点。",
|
||||||
|
"bootstrap_verifying": "正在验证校验和...",
|
||||||
|
"bootstrap_wallet_protected": "(wallet.dat 已受保护)",
|
||||||
|
"bootstrap_warning": "现有区块数据(blocks、chainstate、notarizations)将被删除并替换。您的 wallet.dat 不会被修改或删除。",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"characters": "字符",
|
"characters": "字符",
|
||||||
|
"chat": "聊天",
|
||||||
|
"chat_cancel": "取消",
|
||||||
|
"chat_contact_request": "联系人请求",
|
||||||
|
"chat_empty_hint": "暂无对话。您收到的消息将显示在此处。",
|
||||||
|
"chat_locked_hint": "解锁钱包以加载您的聊天记录。",
|
||||||
|
"chat_new_button": "新建对话",
|
||||||
|
"chat_new_message": "消息",
|
||||||
|
"chat_new_send": "发送请求",
|
||||||
|
"chat_new_title": "新建对话",
|
||||||
|
"chat_new_zaddr": "收款方 z 地址",
|
||||||
|
"chat_select_hint": "选择一个对话以查看。",
|
||||||
|
"chat_send": "发送",
|
||||||
|
"chat_send_failed": "未发送",
|
||||||
|
"chat_toast_compose_failed": "无法编写该消息(内容过长?)。",
|
||||||
|
"chat_toast_lite_busy": "已有发送正在进行中,或未打开任何钱包。",
|
||||||
|
"chat_toast_no_zaddr": "没有可用于发送聊天的 z 地址。",
|
||||||
|
"chat_toast_not_connected": "未连接——聊天消息未发送。",
|
||||||
|
"chat_toast_request_compose_failed": "无法编写联系人请求(地址或文本无效?)。",
|
||||||
|
"chat_toast_request_queued": "联系人请求已排队。",
|
||||||
|
"chat_toast_waiting_reply": "等待该联系人回复——对方回复后您即可向其发送消息。",
|
||||||
|
"chat_waiting_reply": "等待该联系人回复——对方回复后您即可向其发送消息。",
|
||||||
|
"chat_you": "我",
|
||||||
|
"choose_icon": "选择图标",
|
||||||
"clear": "清除",
|
"clear": "清除",
|
||||||
"clear_all_bans": "解除所有封禁",
|
"clear_all_bans": "解除所有封禁",
|
||||||
"clear_anyway": "仍然清除",
|
"clear_anyway": "仍然清除",
|
||||||
"clear_form_confirm": "清除所有表单字段?",
|
"clear_form_confirm": "清除所有表单字段?",
|
||||||
|
"clear_icon": "清除图标",
|
||||||
"clear_request": "清除请求",
|
"clear_request": "清除请求",
|
||||||
"click_copy_address": "点击复制地址",
|
"click_copy_address": "点击复制地址",
|
||||||
"click_copy_uri": "点击复制 URI",
|
"click_copy_uri": "点击复制 URI",
|
||||||
@@ -106,8 +148,12 @@
|
|||||||
"confirm_clear_ztx_title": "确认清除 Z-Tx 历史",
|
"confirm_clear_ztx_title": "确认清除 Z-Tx 历史",
|
||||||
"confirm_clear_ztx_warning1": "清除 z-交易历史可能导致您的屏蔽余额显示为 0,直到执行钱包重新扫描。",
|
"confirm_clear_ztx_warning1": "清除 z-交易历史可能导致您的屏蔽余额显示为 0,直到执行钱包重新扫描。",
|
||||||
"confirm_clear_ztx_warning2": "如果发生这种情况,您需要在启用重新扫描的情况下重新导入 z-地址私钥以恢复余额。",
|
"confirm_clear_ztx_warning2": "如果发生这种情况,您需要在启用重新扫描的情况下重新导入 z-地址私钥以恢复余额。",
|
||||||
|
"confirm_delete_blockchain_msg": "这将停止守护进程,删除所有区块链数据(blocks、chainstate、peers),并从头开始重新同步。这可能需要几个小时。",
|
||||||
|
"confirm_delete_blockchain_safe": "您的 wallet.dat、配置和交易历史是安全的,不会被删除。",
|
||||||
|
"confirm_delete_blockchain_title": "删除区块链数据",
|
||||||
"confirm_send": "确认发送",
|
"confirm_send": "确认发送",
|
||||||
"confirm_transaction": "确认交易",
|
"confirm_transaction": "确认交易",
|
||||||
|
"confirm_transfer": "确认转账",
|
||||||
"confirmations": "确认数",
|
"confirmations": "确认数",
|
||||||
"confirmations_display": "%d 次确认 | %s",
|
"confirmations_display": "%d 次确认 | %s",
|
||||||
"confirmed": "已确认",
|
"confirmed": "已确认",
|
||||||
@@ -172,6 +218,7 @@
|
|||||||
"console_welcome": "欢迎使用 ObsidianDragon 控制台",
|
"console_welcome": "欢迎使用 ObsidianDragon 控制台",
|
||||||
"console_zoom_in": "放大",
|
"console_zoom_in": "放大",
|
||||||
"console_zoom_out": "缩小",
|
"console_zoom_out": "缩小",
|
||||||
|
"copied": "已复制!",
|
||||||
"copy": "复制",
|
"copy": "复制",
|
||||||
"copy_address": "复制完整地址",
|
"copy_address": "复制完整地址",
|
||||||
"copy_error": "复制错误",
|
"copy_error": "复制错误",
|
||||||
@@ -180,15 +227,21 @@
|
|||||||
"copy_uri": "复制 URI",
|
"copy_uri": "复制 URI",
|
||||||
"current_price": "当前价格",
|
"current_price": "当前价格",
|
||||||
"custom_fees": "自定义手续费",
|
"custom_fees": "自定义手续费",
|
||||||
|
"daemon_version": "守护进程",
|
||||||
"dark": "深色",
|
"dark": "深色",
|
||||||
"date": "日期",
|
"date": "日期",
|
||||||
"date_label": "日期:",
|
"date_label": "日期:",
|
||||||
"debug_logging": "调试日志",
|
"debug_logging": "调试日志",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
|
"delete_blockchain": "删除区块链",
|
||||||
|
"delete_blockchain_confirm": "删除并重新同步",
|
||||||
|
"deshielding_warning": "警告:这将把资金从隐私 (Z) 地址转移到透明 (T) 地址。",
|
||||||
"difficulty": "难度",
|
"difficulty": "难度",
|
||||||
"disconnected": "已断开",
|
"disconnected": "已断开",
|
||||||
"dismiss": "关闭",
|
"dismiss": "关闭",
|
||||||
"display": "显示",
|
"display": "显示",
|
||||||
|
"download": "下载",
|
||||||
|
"download_bootstrap": "下载引导程序",
|
||||||
"dragonx_green": "DragonX(绿色)",
|
"dragonx_green": "DragonX(绿色)",
|
||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
"error": "错误",
|
"error": "错误",
|
||||||
@@ -196,6 +249,23 @@
|
|||||||
"est_time_to_block": "预计出块时间",
|
"est_time_to_block": "预计出块时间",
|
||||||
"exit": "退出",
|
"exit": "退出",
|
||||||
"explorer": "浏览器",
|
"explorer": "浏览器",
|
||||||
|
"explorer_block_detail": "区块",
|
||||||
|
"explorer_block_hash": "哈希",
|
||||||
|
"explorer_block_height": "高度",
|
||||||
|
"explorer_block_merkle": "Merkle 根",
|
||||||
|
"explorer_block_size": "大小",
|
||||||
|
"explorer_block_time": "时间",
|
||||||
|
"explorer_block_txs": "交易",
|
||||||
|
"explorer_chain_stats": "链",
|
||||||
|
"explorer_invalid_query": "输入区块高度或64位哈希",
|
||||||
|
"explorer_mempool": "内存池",
|
||||||
|
"explorer_mempool_size": "大小",
|
||||||
|
"explorer_mempool_txs": "交易",
|
||||||
|
"explorer_recent_blocks": "最近区块",
|
||||||
|
"explorer_search": "搜索",
|
||||||
|
"explorer_section": "浏览器",
|
||||||
|
"explorer_tx_outputs": "输出",
|
||||||
|
"explorer_tx_size": "大小",
|
||||||
"export": "导出",
|
"export": "导出",
|
||||||
"export_csv": "导出 CSV",
|
"export_csv": "导出 CSV",
|
||||||
"export_keys_btn": "导出密钥",
|
"export_keys_btn": "导出密钥",
|
||||||
@@ -224,14 +294,22 @@
|
|||||||
"fetch_prices": "获取价格",
|
"fetch_prices": "获取价格",
|
||||||
"file": "文件",
|
"file": "文件",
|
||||||
"file_save_location": "文件将保存至:~/.config/ObsidianDragon/",
|
"file_save_location": "文件将保存至:~/.config/ObsidianDragon/",
|
||||||
|
"filter": "筛选...",
|
||||||
"font_scale": "字体大小",
|
"font_scale": "字体大小",
|
||||||
|
"force_quit": "强制退出",
|
||||||
|
"force_quit_confirm_msg": "这将立即终止守护进程而不进行正常关闭。\n这可能会损坏区块链索引并需要重新同步。",
|
||||||
|
"force_quit_confirm_title": "强制退出?",
|
||||||
|
"force_quit_warning": "这将立即终止守护进程而不进行正常关闭。可能需要重新同步区块链。",
|
||||||
|
"force_quit_yes": "强制退出",
|
||||||
"from": "从",
|
"from": "从",
|
||||||
"from_upper": "从",
|
"from_upper": "从",
|
||||||
"full_details": "完整详情",
|
"full_details": "完整详情",
|
||||||
"general": "常规",
|
"general": "常规",
|
||||||
|
"generating": "正在生成",
|
||||||
"go_to_receive": "前往接收",
|
"go_to_receive": "前往接收",
|
||||||
"height": "高度",
|
"height": "高度",
|
||||||
"help": "帮助",
|
"help": "帮助",
|
||||||
|
"hidden_tag": " (已隐藏)",
|
||||||
"hide": "隐藏",
|
"hide": "隐藏",
|
||||||
"hide_address": "隐藏地址",
|
"hide_address": "隐藏地址",
|
||||||
"hide_zero_balances": "隐藏零余额",
|
"hide_zero_balances": "隐藏零余额",
|
||||||
@@ -253,6 +331,9 @@
|
|||||||
"import_key_warning": "警告:切勿分享您的私钥!从不可信来源导入密钥可能会危及您的钱包安全。",
|
"import_key_warning": "警告:切勿分享您的私钥!从不可信来源导入密钥可能会危及您的钱包安全。",
|
||||||
"import_key_z_format": "Z 地址花费密钥 (secret-extended-key-...)",
|
"import_key_z_format": "Z 地址花费密钥 (secret-extended-key-...)",
|
||||||
"import_private_key": "导入私钥...",
|
"import_private_key": "导入私钥...",
|
||||||
|
"incorrect_passphrase": "密码错误",
|
||||||
|
"incorrect_pin": "PIN 错误",
|
||||||
|
"insufficient_funds": "余额不足以支付此金额加手续费。",
|
||||||
"invalid_address": "无效的地址格式",
|
"invalid_address": "无效的地址格式",
|
||||||
"ip_address": "IP 地址",
|
"ip_address": "IP 地址",
|
||||||
"keep": "保留",
|
"keep": "保留",
|
||||||
@@ -266,6 +347,7 @@
|
|||||||
"key_export_viewing_keys_zonly": "查看密钥仅适用于屏蔽 (z) 地址",
|
"key_export_viewing_keys_zonly": "查看密钥仅适用于屏蔽 (z) 地址",
|
||||||
"key_export_viewing_warning": "此查看密钥允许他人查看您的入账交易和余额,但不能花费您的资金。仅与信任的人分享。",
|
"key_export_viewing_warning": "此查看密钥允许他人查看您的入账交易和余额,但不能花费您的资金。仅与信任的人分享。",
|
||||||
"label": "标签:",
|
"label": "标签:",
|
||||||
|
"label_placeholder": "例如 储蓄、挖矿...",
|
||||||
"language": "语言",
|
"language": "语言",
|
||||||
"light": "浅色",
|
"light": "浅色",
|
||||||
"loading": "加载中...",
|
"loading": "加载中...",
|
||||||
@@ -286,6 +368,7 @@
|
|||||||
"market_now": "现在",
|
"market_now": "现在",
|
||||||
"market_pct_shielded": "%.0f%% 屏蔽",
|
"market_pct_shielded": "%.0f%% 屏蔽",
|
||||||
"market_portfolio": "投资组合",
|
"market_portfolio": "投资组合",
|
||||||
|
"market_price_loading": "正在加载价格数据...",
|
||||||
"market_price_unavailable": "价格数据不可用",
|
"market_price_unavailable": "价格数据不可用",
|
||||||
"market_refresh_price": "刷新价格数据",
|
"market_refresh_price": "刷新价格数据",
|
||||||
"market_trade_on": "在 %s 交易",
|
"market_trade_on": "在 %s 交易",
|
||||||
@@ -311,6 +394,13 @@
|
|||||||
"mining_address_copied": "挖矿地址已复制",
|
"mining_address_copied": "挖矿地址已复制",
|
||||||
"mining_all_time": "所有时间",
|
"mining_all_time": "所有时间",
|
||||||
"mining_already_saved": "矿池 URL 已保存",
|
"mining_already_saved": "矿池 URL 已保存",
|
||||||
|
"mining_benchmark_cancel": "取消基准测试",
|
||||||
|
"mining_benchmark_cooling": "冷却中",
|
||||||
|
"mining_benchmark_dismiss": "关闭",
|
||||||
|
"mining_benchmark_result": "最佳",
|
||||||
|
"mining_benchmark_stabilizing": "稳定中",
|
||||||
|
"mining_benchmark_testing": "测试中",
|
||||||
|
"mining_benchmark_tooltip": "为此 CPU 找到最佳线程数",
|
||||||
"mining_block_copied": "区块哈希已复制",
|
"mining_block_copied": "区块哈希已复制",
|
||||||
"mining_chart_1m_ago": "1分钟前",
|
"mining_chart_1m_ago": "1分钟前",
|
||||||
"mining_chart_5m_ago": "5分钟前",
|
"mining_chart_5m_ago": "5分钟前",
|
||||||
@@ -330,8 +420,15 @@
|
|||||||
"mining_filter_tip_all": "显示所有收益",
|
"mining_filter_tip_all": "显示所有收益",
|
||||||
"mining_filter_tip_pool": "仅显示矿池收益",
|
"mining_filter_tip_pool": "仅显示矿池收益",
|
||||||
"mining_filter_tip_solo": "仅显示单人收益",
|
"mining_filter_tip_solo": "仅显示单人收益",
|
||||||
|
"mining_generate_z_address_hint": "在接收标签页生成一个 Z 地址作为支付地址",
|
||||||
|
"mining_idle_gpu_off_tooltip": "无限制:开启\n仅键盘/鼠标输入决定空闲状态\n点击启用GPU感知检测",
|
||||||
|
"mining_idle_gpu_on_tooltip": "GPU感知:开启\nGPU活动(视频、游戏)阻止空闲挖矿\n点击切换到无限制模式",
|
||||||
"mining_idle_off_tooltip": "启用空闲挖矿",
|
"mining_idle_off_tooltip": "启用空闲挖矿",
|
||||||
"mining_idle_on_tooltip": "禁用空闲挖矿",
|
"mining_idle_on_tooltip": "禁用空闲挖矿",
|
||||||
|
"mining_idle_scale_off_tooltip": "启动/停止模式:开启\n点击切换到线程缩放模式",
|
||||||
|
"mining_idle_scale_on_tooltip": "线程缩放:开启\n点击切换到启动/停止模式",
|
||||||
|
"mining_idle_threads_active_tooltip": "用户活跃时的线程数",
|
||||||
|
"mining_idle_threads_idle_tooltip": "系统空闲时的线程数",
|
||||||
"mining_local_hashrate": "本地算力",
|
"mining_local_hashrate": "本地算力",
|
||||||
"mining_mine": "挖矿",
|
"mining_mine": "挖矿",
|
||||||
"mining_mining_addr": "挖矿地址",
|
"mining_mining_addr": "挖矿地址",
|
||||||
@@ -388,6 +485,7 @@
|
|||||||
"no_addresses_available": "无可用地址",
|
"no_addresses_available": "无可用地址",
|
||||||
"no_addresses_match": "没有匹配过滤器的地址",
|
"no_addresses_match": "没有匹配过滤器的地址",
|
||||||
"no_addresses_with_balance": "没有有余额的地址",
|
"no_addresses_with_balance": "没有有余额的地址",
|
||||||
|
"no_addresses_yet": "暂无地址",
|
||||||
"no_matching": "没有匹配的交易",
|
"no_matching": "没有匹配的交易",
|
||||||
"no_recent_receives": "没有最近的接收",
|
"no_recent_receives": "没有最近的接收",
|
||||||
"no_recent_sends": "没有最近的发送",
|
"no_recent_sends": "没有最近的发送",
|
||||||
@@ -453,6 +551,7 @@
|
|||||||
"peers_upper": "节点",
|
"peers_upper": "节点",
|
||||||
"peers_version": "版本",
|
"peers_version": "版本",
|
||||||
"pending": "待处理",
|
"pending": "待处理",
|
||||||
|
"pin_not_set": "未设置 PIN。使用密码解锁。",
|
||||||
"ping": "延迟",
|
"ping": "延迟",
|
||||||
"price_chart": "价格图表",
|
"price_chart": "价格图表",
|
||||||
"qr_code": "二维码",
|
"qr_code": "二维码",
|
||||||
@@ -473,7 +572,9 @@
|
|||||||
"recent_received": "最近接收",
|
"recent_received": "最近接收",
|
||||||
"recent_sends": "最近发送",
|
"recent_sends": "最近发送",
|
||||||
"recipient": "收款方",
|
"recipient": "收款方",
|
||||||
|
"recipient_balance": "接收方: %.8f → %.8f DRGX",
|
||||||
"recv_type": "接收",
|
"recv_type": "接收",
|
||||||
|
"reduce_motion": "减少动画",
|
||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
"refresh_now": "立即刷新",
|
"refresh_now": "立即刷新",
|
||||||
"remove_favorite": "移除收藏",
|
"remove_favorite": "移除收藏",
|
||||||
@@ -493,7 +594,10 @@
|
|||||||
"request_uri_copied": "付款 URI 已复制到剪贴板",
|
"request_uri_copied": "付款 URI 已复制到剪贴板",
|
||||||
"rescan": "重新扫描",
|
"rescan": "重新扫描",
|
||||||
"reset_to_defaults": "重置为默认值",
|
"reset_to_defaults": "重置为默认值",
|
||||||
|
"restarting_after_encryption": "加密后重启守护进程...",
|
||||||
"restore_address": "恢复地址",
|
"restore_address": "恢复地址",
|
||||||
|
"result_preview": "结果预览",
|
||||||
|
"retry": "重试",
|
||||||
"review_send": "审核发送",
|
"review_send": "审核发送",
|
||||||
"rpc_host": "RPC 主机",
|
"rpc_host": "RPC 主机",
|
||||||
"rpc_pass": "密码",
|
"rpc_pass": "密码",
|
||||||
@@ -502,8 +606,55 @@
|
|||||||
"save": "保存",
|
"save": "保存",
|
||||||
"save_settings": "保存设置",
|
"save_settings": "保存设置",
|
||||||
"save_z_transactions": "将 Z 交易保存到列表",
|
"save_z_transactions": "将 Z 交易保存到列表",
|
||||||
|
"sb_auth_failed": "认证失败 — 请检查 rpcuser/rpcpassword",
|
||||||
|
"sb_block": "区块: %d",
|
||||||
|
"sb_connecting_daemon": "正在连接 dragonxd...",
|
||||||
|
"sb_connecting_err": "连接守护进程 — %s",
|
||||||
|
"sb_connecting_external": "正在连接外部守护进程...",
|
||||||
|
"sb_connecting_generic": "正在连接守护进程...",
|
||||||
|
"sb_daemon_crashed": "守护进程崩溃 %d 次",
|
||||||
|
"sb_daemon_not_found": "未找到守护进程",
|
||||||
|
"sb_dragonxd_running": "dragonxd 运行中",
|
||||||
|
"sb_dragonxd_stopped": "dragonxd 已停止",
|
||||||
|
"sb_dragonxd_stopping": "正在停止 dragonxd...",
|
||||||
|
"sb_extracting_sapling": "正在提取 Sapling 参数...",
|
||||||
|
"sb_importing_keys": "正在导入密钥",
|
||||||
|
"sb_loading_config": "正在加载配置...",
|
||||||
|
"sb_mining_hs": "%.1f H/s",
|
||||||
|
"sb_net_ghs": "网络: %.2f GH/s",
|
||||||
|
"sb_net_hs": "网络: %.1f H/s",
|
||||||
|
"sb_net_khs": "网络: %.2f KH/s",
|
||||||
|
"sb_net_mhs": "网络: %.2f MH/s",
|
||||||
|
"sb_no_conf": "未找到 DRAGONX.conf",
|
||||||
|
"sb_peers": "节点: %zu",
|
||||||
|
"sb_rescanning": "重新扫描",
|
||||||
|
"sb_rescanning_pct": "重新扫描 %.0f%%",
|
||||||
|
"sb_restarting_daemon": "正在重启守护进程...",
|
||||||
|
"sb_sapling_failed": "提取 Sapling 参数失败。",
|
||||||
|
"sb_sapling_not_found": "未找到 Sapling 参数。",
|
||||||
|
"sb_starting_daemon": "正在启动 dragonxd...",
|
||||||
|
"sb_syncing_basic": "同步中 %.1f%% (剩余 %d)",
|
||||||
|
"sb_syncing_eta": "同步中 %.1f%% (剩余 %d, %.0f 块/秒, ~%s)",
|
||||||
|
"sb_waiting_config": "等待守护进程配置...",
|
||||||
|
"sb_waiting_daemon": "等待 dragonxd 启动...",
|
||||||
|
"sb_waiting_daemon_err": "等待 dragonxd — %s",
|
||||||
|
"sb_warming_up": "正在预热...",
|
||||||
"search_placeholder": "搜索...",
|
"search_placeholder": "搜索...",
|
||||||
"security": "安全",
|
"security": "安全",
|
||||||
|
"seed_backup_button": "助记词",
|
||||||
|
"seed_backup_close": "关闭",
|
||||||
|
"seed_backup_copy": "复制",
|
||||||
|
"seed_backup_intro": "这些词是您钱包的主备份。任何拥有它们的人都能控制您的资金。",
|
||||||
|
"seed_backup_load_failed": "无法加载助记词。",
|
||||||
|
"seed_backup_loading": "正在加载您的助记词…",
|
||||||
|
"seed_backup_locked": "解锁您的钱包以显示其助记词。",
|
||||||
|
"seed_backup_none": "此钱包早于助记词功能,因此没有 24 词助记词。请改用“备份钱包”或导出私钥来备份它。",
|
||||||
|
"seed_backup_reminder": "您的钱包有一个 24 词恢复助记词。请立即在“设置 → 节点与安全”中备份它。",
|
||||||
|
"seed_backup_save": "保存到文件…",
|
||||||
|
"seed_backup_save_failed": "无法写入 ",
|
||||||
|
"seed_backup_saved": "已保存到 ",
|
||||||
|
"seed_backup_title": "备份助记词",
|
||||||
|
"seed_backup_warning": "请按顺序将它们抄写下来,离线保存,切勿分享或拍照。一旦丢失,您的资金将无法恢复。",
|
||||||
"select_address": "选择地址...",
|
"select_address": "选择地址...",
|
||||||
"select_receiving_address": "选择接收地址...",
|
"select_receiving_address": "选择接收地址...",
|
||||||
"select_source_address": "选择来源地址...",
|
"select_source_address": "选择来源地址...",
|
||||||
@@ -553,12 +704,15 @@
|
|||||||
"send_valid_transparent": "有效的透明地址",
|
"send_valid_transparent": "有效的透明地址",
|
||||||
"send_wallet_empty": "您的钱包是空的",
|
"send_wallet_empty": "您的钱包是空的",
|
||||||
"send_yes_clear": "是,清除",
|
"send_yes_clear": "是,清除",
|
||||||
|
"sender_balance": "发送方: %.8f → %.8f DRGX",
|
||||||
"sending": "正在发送交易",
|
"sending": "正在发送交易",
|
||||||
"sending_from": "发送来源",
|
"sending_from": "发送来源",
|
||||||
|
"sends_full_balance_warning": "这将发送全部余额。发送地址将变为零余额。",
|
||||||
"sent": "已发送",
|
"sent": "已发送",
|
||||||
"sent_filter": "已发送",
|
"sent_filter": "已发送",
|
||||||
"sent_type": "已发送",
|
"sent_type": "已发送",
|
||||||
"sent_upper": "已发送",
|
"sent_upper": "已发送",
|
||||||
|
"set_label": "设置标签...",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"settings_about_text": "DragonX (DRGX) 屏蔽加密货币钱包,使用 Dear ImGui 构建,提供轻量、便携的体验。",
|
"settings_about_text": "DragonX (DRGX) 屏蔽加密货币钱包,使用 Dear ImGui 构建,提供轻量、便携的体验。",
|
||||||
"settings_acrylic_level": "亚克力级别:",
|
"settings_acrylic_level": "亚克力级别:",
|
||||||
@@ -647,13 +801,17 @@
|
|||||||
"shield_utxo_limit": "UTXO 限制:",
|
"shield_utxo_limit": "UTXO 限制:",
|
||||||
"shield_wildcard_hint": "使用 '*' 从所有透明地址屏蔽",
|
"shield_wildcard_hint": "使用 '*' 从所有透明地址屏蔽",
|
||||||
"shielded": "屏蔽",
|
"shielded": "屏蔽",
|
||||||
|
"shielded_address": "隐蔽地址",
|
||||||
"shielded_to": "屏蔽至",
|
"shielded_to": "屏蔽至",
|
||||||
"shielded_type": "屏蔽",
|
"shielded_type": "屏蔽",
|
||||||
|
"shielding_notice": "注意:这将把资金从透明 (T) 地址转移到隐私 (Z) 地址。",
|
||||||
"show": "显示",
|
"show": "显示",
|
||||||
"show_hidden": "显示已隐藏 (%d)",
|
"show_hidden": "显示已隐藏 (%d)",
|
||||||
"show_qr_code": "显示二维码",
|
"show_qr_code": "显示二维码",
|
||||||
"showing_transactions": "显示第 %dâ%d 笔,共 %d 笔交易(总计:%zu)",
|
"showing_transactions": "显示第 %d–%d 笔,共 %d 笔交易(总计:%zu)",
|
||||||
|
"showing_x_of_y": "显示 %d / %d 个地址",
|
||||||
"simple_background": "简单背景",
|
"simple_background": "简单背景",
|
||||||
|
"slider_off": "关闭",
|
||||||
"start_mining": "开始挖矿",
|
"start_mining": "开始挖矿",
|
||||||
"status": "状态",
|
"status": "状态",
|
||||||
"stop_external": "停止外部守护进程",
|
"stop_external": "停止外部守护进程",
|
||||||
@@ -662,17 +820,26 @@
|
|||||||
"success": "成功",
|
"success": "成功",
|
||||||
"summary": "摘要",
|
"summary": "摘要",
|
||||||
"syncing": "同步中...",
|
"syncing": "同步中...",
|
||||||
|
"t_address": "T 地址",
|
||||||
"t_addresses": "T 地址",
|
"t_addresses": "T 地址",
|
||||||
"test_connection": "测试",
|
"test_connection": "测试",
|
||||||
"theme": "主题",
|
"theme": "主题",
|
||||||
"theme_effects": "主题效果",
|
"theme_effects": "主题效果",
|
||||||
|
"theme_language": "主题与语言",
|
||||||
"time_days_ago": "%d 天前",
|
"time_days_ago": "%d 天前",
|
||||||
"time_hours_ago": "%d 小时前",
|
"time_hours_ago": "%d 小时前",
|
||||||
"time_minutes_ago": "%d 分钟前",
|
"time_minutes_ago": "%d 分钟前",
|
||||||
"time_seconds_ago": "%d 秒前",
|
"time_seconds_ago": "%d 秒前",
|
||||||
|
"timeout_15min": "15分钟",
|
||||||
|
"timeout_1hour": "1小时",
|
||||||
|
"timeout_1min": "1分钟",
|
||||||
|
"timeout_30min": "30分钟",
|
||||||
|
"timeout_5min": "5分钟",
|
||||||
|
"timeout_off": "关闭",
|
||||||
"to": "至",
|
"to": "至",
|
||||||
"to_upper": "至",
|
"to_upper": "至",
|
||||||
"tools": "工具",
|
"tools": "工具",
|
||||||
|
"tools_actions": "工具与操作...",
|
||||||
"total": "合计",
|
"total": "合计",
|
||||||
"transaction_id": "交易 ID",
|
"transaction_id": "交易 ID",
|
||||||
"transaction_sent": "交易发送成功",
|
"transaction_sent": "交易发送成功",
|
||||||
@@ -680,7 +847,13 @@
|
|||||||
"transaction_url": "交易 URL",
|
"transaction_url": "交易 URL",
|
||||||
"transactions": "交易",
|
"transactions": "交易",
|
||||||
"transactions_upper": "交易",
|
"transactions_upper": "交易",
|
||||||
|
"transfer_failed": "转账失败",
|
||||||
|
"transfer_funds": "转账",
|
||||||
|
"transfer_sent": "转账已发送",
|
||||||
|
"transfer_sent_desc": "您的转账已提交到网络。",
|
||||||
|
"transfer_to": "转账至:",
|
||||||
"transparent": "透明",
|
"transparent": "透明",
|
||||||
|
"transparent_address": "透明地址",
|
||||||
"tt_addr_url": "在区块浏览器中查看地址的基础 URL",
|
"tt_addr_url": "在区块浏览器中查看地址的基础 URL",
|
||||||
"tt_address_book": "管理已保存的地址以快速发送",
|
"tt_address_book": "管理已保存的地址以快速发送",
|
||||||
"tt_auto_lock": "在此不活动时间后锁定钱包",
|
"tt_auto_lock": "在此不活动时间后锁定钱包",
|
||||||
@@ -695,6 +868,8 @@
|
|||||||
"tt_custom_theme": "自定义主题已激活",
|
"tt_custom_theme": "自定义主题已激活",
|
||||||
"tt_debug_collapse": "折叠调试日志选项",
|
"tt_debug_collapse": "折叠调试日志选项",
|
||||||
"tt_debug_expand": "展开调试日志选项",
|
"tt_debug_expand": "展开调试日志选项",
|
||||||
|
"tt_delete_blockchain": "删除所有区块链数据并重新同步。wallet.dat 和配置将被保留。",
|
||||||
|
"tt_download_bootstrap": "下载区块链引导程序以加速同步\n现有区块数据将被替换",
|
||||||
"tt_encrypt": "使用密码加密 wallet.dat",
|
"tt_encrypt": "使用密码加密 wallet.dat",
|
||||||
"tt_export_all": "将所有私钥导出到文件",
|
"tt_export_all": "将所有私钥导出到文件",
|
||||||
"tt_export_csv": "将交易历史导出为 CSV 电子表格",
|
"tt_export_csv": "将交易历史导出为 CSV 电子表格",
|
||||||
@@ -712,6 +887,7 @@
|
|||||||
"tt_mine_idle": "系统空闲时自动开始挖矿\\n(无键盘/鼠标输入)",
|
"tt_mine_idle": "系统空闲时自动开始挖矿\\n(无键盘/鼠标输入)",
|
||||||
"tt_noise": "颗粒纹理强度(0%% = 关闭,100%% = 最大)",
|
"tt_noise": "颗粒纹理强度(0%% = 关闭,100%% = 最大)",
|
||||||
"tt_open_dir": "点击在文件管理器中打开",
|
"tt_open_dir": "点击在文件管理器中打开",
|
||||||
|
"tt_reduce_motion": "禁用动画过渡和余额渐变以提高无障碍性",
|
||||||
"tt_remove_encrypt": "移除加密并以未受保护状态存储钱包",
|
"tt_remove_encrypt": "移除加密并以未受保护状态存储钱包",
|
||||||
"tt_remove_pin": "移除 PIN 并要求密码解锁",
|
"tt_remove_pin": "移除 PIN 并要求密码解锁",
|
||||||
"tt_report_bug": "在项目跟踪器中报告问题",
|
"tt_report_bug": "在项目跟踪器中报告问题",
|
||||||
@@ -727,6 +903,7 @@
|
|||||||
"tt_save_ztx": "将 z-address 交易历史存储在本地以加快加载速度",
|
"tt_save_ztx": "将 z-address 交易历史存储在本地以加快加载速度",
|
||||||
"tt_scan_themes": "扫描新主题。\\n将主题文件夹放在:\\n%s",
|
"tt_scan_themes": "扫描新主题。\\n将主题文件夹放在:\\n%s",
|
||||||
"tt_scanline": "控制台中的 CRT 扫描线效果",
|
"tt_scanline": "控制台中的 CRT 扫描线效果",
|
||||||
|
"tt_seed_backup": "显示并备份您钱包的 24 词恢复助记词",
|
||||||
"tt_set_pin": "设置 4-8 位 PIN 以快速解锁",
|
"tt_set_pin": "设置 4-8 位 PIN 以快速解锁",
|
||||||
"tt_shield_mining": "将透明挖矿奖励转移到屏蔽地址",
|
"tt_shield_mining": "将透明挖矿奖励转移到屏蔽地址",
|
||||||
"tt_simple_bg": "使用简单渐变作为背景\\n快捷键:Ctrl+Up",
|
"tt_simple_bg": "使用简单渐变作为背景\\n快捷键:Ctrl+Up",
|
||||||
@@ -789,7 +966,9 @@
|
|||||||
"warning_upper": "警告!",
|
"warning_upper": "警告!",
|
||||||
"website": "网站",
|
"website": "网站",
|
||||||
"window_opacity": "窗口透明度",
|
"window_opacity": "窗口透明度",
|
||||||
|
"wizard_daemon_start_failed": "启动守护进程失败 — 将自动重试",
|
||||||
"yes_clear": "是,清除",
|
"yes_clear": "是,清除",
|
||||||
"your_addresses": "您的地址",
|
"your_addresses": "您的地址",
|
||||||
|
"z_address": "Z 地址",
|
||||||
"z_addresses": "Z 地址"
|
"z_addresses": "Z 地址"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "log
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#E8E6F0"
|
--on-background = "#E8E6F0"
|
||||||
--on-surface = "#E8E6F0"
|
--on-surface = "#E8E6F0"
|
||||||
--on-surface-medium = "rgba(232,230,240,0.72)"
|
--on-surface-medium = "rgba(232,230,240,0.85)"
|
||||||
--on-surface-disabled = "rgba(232,230,240,0.40)"
|
--on-surface-disabled = "rgba(232,230,240,0.58)"
|
||||||
--error = "#FF5C72"
|
--error = "#FF5C72"
|
||||||
--on-error = "#000000"
|
--on-error = "#000000"
|
||||||
--success = "#3DE8A0"
|
--success = "#3DE8A0"
|
||||||
@@ -61,7 +61,7 @@ images = { background_image = "backgrounds/texture/pop-dark_bg.png", logo = "log
|
|||||||
--sidebar-badge = "rgba(232,230,240,1.0)"
|
--sidebar-badge = "rgba(232,230,240,1.0)"
|
||||||
--sidebar-divider = "rgba(200,190,240,0.05)"
|
--sidebar-divider = "rgba(200,190,240,0.05)"
|
||||||
--chart-line = "rgba(124,108,255,0.12)"
|
--chart-line = "rgba(124,108,255,0.12)"
|
||||||
--window-control = "rgba(232,230,240,0.72)"
|
--window-control = "rgba(232,230,240,0.85)"
|
||||||
--window-control-hover = "rgba(124,108,255,0.12)"
|
--window-control-hover = "rgba(124,108,255,0.12)"
|
||||||
--window-close-hover = "rgba(255,92,114,0.75)"
|
--window-close-hover = "rgba(255,92,114,0.75)"
|
||||||
--spinner-track = "rgba(200,190,240,0.08)"
|
--spinner-track = "rgba(200,190,240,0.08)"
|
||||||
|
|||||||
@@ -19,16 +19,16 @@ images = { background_image = "backgrounds/texture/pop-light_bg.png", logo = "lo
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#1E1E2A"
|
--on-background = "#1E1E2A"
|
||||||
--on-surface = "#1E1E2A"
|
--on-surface = "#1E1E2A"
|
||||||
--on-surface-medium = "rgba(30,30,42,0.72)"
|
--on-surface-medium = "rgba(30,30,42,0.86)"
|
||||||
--on-surface-disabled = "rgba(30,30,42,0.38)"
|
--on-surface-disabled = "rgba(30,30,42,0.62)"
|
||||||
--error = "#E0304A"
|
--error = "#E0304A"
|
||||||
--on-error = "#FFFFFF"
|
--on-error = "#FFFFFF"
|
||||||
--success = "#18A860"
|
--success = "#18A860"
|
||||||
--on-success = "#FFFFFF"
|
--on-success = "#FFFFFF"
|
||||||
--warning = "#E09020"
|
--warning = "#E09020"
|
||||||
--on-warning = "#000000"
|
--on-warning = "#000000"
|
||||||
--divider = "rgba(30,30,60,0.12)"
|
--divider = "rgba(30,30,60,0.20)"
|
||||||
--outline = "rgba(30,30,60,0.15)"
|
--outline = "rgba(30,30,60,0.24)"
|
||||||
--scrim = "rgba(0,0,0,0.45)"
|
--scrim = "rgba(0,0,0,0.45)"
|
||||||
--surface-hover = "rgba(96,64,224,0.05)"
|
--surface-hover = "rgba(96,64,224,0.05)"
|
||||||
--surface-alt = "rgba(96,64,224,0.02)"
|
--surface-alt = "rgba(96,64,224,0.02)"
|
||||||
@@ -47,14 +47,14 @@ images = { background_image = "backgrounds/texture/pop-light_bg.png", logo = "lo
|
|||||||
--chart-hover-ring = "rgba(96,64,224,0.30)"
|
--chart-hover-ring = "rgba(96,64,224,0.30)"
|
||||||
--tooltip-bg = "rgba(36,34,52,0.94)"
|
--tooltip-bg = "rgba(36,34,52,0.94)"
|
||||||
--tooltip-border = "rgba(96,64,224,0.16)"
|
--tooltip-border = "rgba(96,64,224,0.16)"
|
||||||
--glass-fill = "rgba(255,255,255,0.55)"
|
--glass-fill = "rgba(255,255,255,0.20)"
|
||||||
--glass-border = "rgba(30,30,60,0.10)"
|
--glass-border = "rgba(30,30,60,0.10)"
|
||||||
--glass-noise-tint = "rgba(96,64,224,0.02)"
|
--glass-noise-tint = "rgba(96,64,224,0.02)"
|
||||||
--tactile-top = "rgba(255,255,255,0.40)"
|
--tactile-top = "rgba(255,255,255,0.40)"
|
||||||
--tactile-bottom = "rgba(255,255,255,0.05)"
|
--tactile-bottom = "rgba(255,255,255,0.05)"
|
||||||
--hover-overlay = "rgba(96,64,224,0.04)"
|
--hover-overlay = "rgba(96,64,224,0.10)"
|
||||||
--active-overlay = "rgba(96,64,224,0.08)"
|
--active-overlay = "rgba(96,64,224,0.08)"
|
||||||
--rim-light = "rgba(96,64,224,0.06)"
|
--rim-light = "rgba(30,30,42,0.22)"
|
||||||
--status-divider = "rgba(30,30,60,0.08)"
|
--status-divider = "rgba(30,30,60,0.08)"
|
||||||
--sidebar-hover = "rgba(96,64,224,0.07)"
|
--sidebar-hover = "rgba(96,64,224,0.07)"
|
||||||
--sidebar-icon = "rgba(30,30,42,0.50)"
|
--sidebar-icon = "rgba(30,30,42,0.50)"
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/l
|
|||||||
--on-secondary = "#000000"
|
--on-secondary = "#000000"
|
||||||
--on-background = "#D0D0D4"
|
--on-background = "#D0D0D4"
|
||||||
--on-surface = "#D0D0D4"
|
--on-surface = "#D0D0D4"
|
||||||
--on-surface-medium = "rgba(208,208,212,0.75)"
|
--on-surface-medium = "rgba(208,208,212,0.85)"
|
||||||
--on-surface-disabled = "rgba(208,208,212,0.45)"
|
--on-surface-disabled = "rgba(208,208,212,0.58)"
|
||||||
--error = "#B07080"
|
--error = "#B07080"
|
||||||
--on-error = "#000000"
|
--on-error = "#000000"
|
||||||
--success = "#7AAE7C"
|
--success = "#7AAE7C"
|
||||||
@@ -61,7 +61,7 @@ images = { background_image = "backgrounds/texture/dark_bg.png", logo = "logos/l
|
|||||||
--sidebar-badge = "rgba(208,208,212,1.0)"
|
--sidebar-badge = "rgba(208,208,212,1.0)"
|
||||||
--sidebar-divider = "rgba(220,220,225,0.05)"
|
--sidebar-divider = "rgba(220,220,225,0.05)"
|
||||||
--chart-line = "rgba(220,220,225,0.08)"
|
--chart-line = "rgba(220,220,225,0.08)"
|
||||||
--window-control = "rgba(208,208,212,0.75)"
|
--window-control = "rgba(208,208,212,0.85)"
|
||||||
--window-control-hover = "rgba(220,220,225,0.10)"
|
--window-control-hover = "rgba(220,220,225,0.10)"
|
||||||
--window-close-hover = "rgba(200,50,60,0.70)"
|
--window-close-hover = "rgba(200,50,60,0.70)"
|
||||||
--spinner-track = "rgba(220,220,225,0.08)"
|
--spinner-track = "rgba(220,220,225,0.08)"
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ elevation = { --elevation-0 = "#FDF8F0", --elevation-1 = "#F5EDE0", --elevation-
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#3A2E22"
|
--on-background = "#3A2E22"
|
||||||
--on-surface = "#3A2E22"
|
--on-surface = "#3A2E22"
|
||||||
--on-surface-medium = "rgba(58,46,34,0.68)"
|
--on-surface-medium = "rgba(58,46,34,0.86)"
|
||||||
--on-surface-disabled = "rgba(58,46,34,0.38)"
|
--on-surface-disabled = "rgba(58,46,34,0.62)"
|
||||||
--error = "#A0524A"
|
--error = "#A0524A"
|
||||||
--on-error = "#FFFFFF"
|
--on-error = "#FFFFFF"
|
||||||
--success = "#6A8A5C"
|
--success = "#4E8A42"
|
||||||
--on-success = "#FFFFFF"
|
--on-success = "#FFFFFF"
|
||||||
--warning = "#C08840"
|
--warning = "#C08840"
|
||||||
--on-warning = "#000000"
|
--on-warning = "#000000"
|
||||||
--divider = "rgba(140,110,70,0.14)"
|
--divider = "rgba(140,110,70,0.20)"
|
||||||
--outline = "rgba(140,110,70,0.16)"
|
--outline = "rgba(140,110,70,0.24)"
|
||||||
--scrim = "rgba(30,20,10,0.45)"
|
--scrim = "rgba(30,20,10,0.45)"
|
||||||
--surface-hover = "rgba(176,120,64,0.06)"
|
--surface-hover = "rgba(176,120,64,0.06)"
|
||||||
--surface-alt = "rgba(176,120,64,0.03)"
|
--surface-alt = "rgba(176,120,64,0.03)"
|
||||||
@@ -48,14 +48,14 @@ elevation = { --elevation-0 = "#FDF8F0", --elevation-1 = "#F5EDE0", --elevation-
|
|||||||
--chart-hover-ring = "rgba(176,120,64,0.28)"
|
--chart-hover-ring = "rgba(176,120,64,0.28)"
|
||||||
--tooltip-bg = "rgba(50,38,24,0.94)"
|
--tooltip-bg = "rgba(50,38,24,0.94)"
|
||||||
--tooltip-border = "rgba(176,120,64,0.12)"
|
--tooltip-border = "rgba(176,120,64,0.12)"
|
||||||
--glass-fill = "rgba(255,252,245,0.58)"
|
--glass-fill = "rgba(255,252,245,0.20)"
|
||||||
--glass-border = "rgba(176,120,64,0.14)"
|
--glass-border = "rgba(176,120,64,0.14)"
|
||||||
--glass-noise-tint = "rgba(180,140,80,0.03)"
|
--glass-noise-tint = "rgba(180,140,80,0.03)"
|
||||||
--tactile-top = "rgba(255,255,248,0.50)"
|
--tactile-top = "rgba(255,255,248,0.50)"
|
||||||
--tactile-bottom = "rgba(255,255,248,0.08)"
|
--tactile-bottom = "rgba(255,255,248,0.08)"
|
||||||
--hover-overlay = "rgba(176,120,64,0.05)"
|
--hover-overlay = "rgba(176,120,64,0.10)"
|
||||||
--active-overlay = "rgba(176,120,64,0.10)"
|
--active-overlay = "rgba(176,120,64,0.10)"
|
||||||
--rim-light = "rgba(212,160,108,0.10)"
|
--rim-light = "rgba(58,46,34,0.22)"
|
||||||
--status-divider = "rgba(176,120,64,0.10)"
|
--status-divider = "rgba(176,120,64,0.10)"
|
||||||
--sidebar-hover = "rgba(176,120,64,0.08)"
|
--sidebar-hover = "rgba(176,120,64,0.08)"
|
||||||
--sidebar-icon = "rgba(58,46,34,0.50)"
|
--sidebar-icon = "rgba(58,46,34,0.50)"
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ elevation = { --elevation-0 = "#FDFBFF", --elevation-1 = "#F5F0FA", --elevation-
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#1C1525"
|
--on-background = "#1C1525"
|
||||||
--on-surface = "#1C1525"
|
--on-surface = "#1C1525"
|
||||||
--on-surface-medium = "rgba(28,21,37,0.72)"
|
--on-surface-medium = "rgba(28,21,37,0.86)"
|
||||||
--on-surface-disabled = "rgba(28,21,37,0.40)"
|
--on-surface-disabled = "rgba(28,21,37,0.62)"
|
||||||
--error = "#C62828"
|
--error = "#C62828"
|
||||||
--on-error = "#FFFFFF"
|
--on-error = "#FFFFFF"
|
||||||
--success = "#2E7D32"
|
--success = "#2E7D32"
|
||||||
--on-success = "#FFFFFF"
|
--on-success = "#FFFFFF"
|
||||||
--warning = "#E65100"
|
--warning = "#E65100"
|
||||||
--on-warning = "#000000"
|
--on-warning = "#000000"
|
||||||
--divider = "rgba(120,80,160,0.12)"
|
--divider = "rgba(120,80,160,0.20)"
|
||||||
--outline = "rgba(120,80,160,0.14)"
|
--outline = "rgba(120,80,160,0.24)"
|
||||||
--scrim = "rgba(20,10,30,0.45)"
|
--scrim = "rgba(20,10,30,0.45)"
|
||||||
--surface-hover = "rgba(140,107,175,0.06)"
|
--surface-hover = "rgba(140,107,175,0.06)"
|
||||||
--surface-alt = "rgba(140,107,175,0.03)"
|
--surface-alt = "rgba(140,107,175,0.03)"
|
||||||
@@ -48,14 +48,14 @@ elevation = { --elevation-0 = "#FDFBFF", --elevation-1 = "#F5F0FA", --elevation-
|
|||||||
--chart-hover-ring = "rgba(140,107,175,0.28)"
|
--chart-hover-ring = "rgba(140,107,175,0.28)"
|
||||||
--tooltip-bg = "rgba(32,24,48,0.94)"
|
--tooltip-bg = "rgba(32,24,48,0.94)"
|
||||||
--tooltip-border = "rgba(140,107,175,0.12)"
|
--tooltip-border = "rgba(140,107,175,0.12)"
|
||||||
--glass-fill = "rgba(255,255,255,0.55)"
|
--glass-fill = "rgba(255,255,255,0.20)"
|
||||||
--glass-border = "rgba(140,107,175,0.14)"
|
--glass-border = "rgba(140,107,175,0.14)"
|
||||||
--glass-noise-tint = "rgba(180,140,220,0.03)"
|
--glass-noise-tint = "rgba(180,140,220,0.03)"
|
||||||
--tactile-top = "rgba(255,255,255,0.50)"
|
--tactile-top = "rgba(255,255,255,0.50)"
|
||||||
--tactile-bottom = "rgba(255,255,255,0.08)"
|
--tactile-bottom = "rgba(255,255,255,0.08)"
|
||||||
--hover-overlay = "rgba(140,107,175,0.05)"
|
--hover-overlay = "rgba(140,107,175,0.10)"
|
||||||
--active-overlay = "rgba(140,107,175,0.10)"
|
--active-overlay = "rgba(140,107,175,0.10)"
|
||||||
--rim-light = "rgba(180,140,255,0.10)"
|
--rim-light = "rgba(28,21,37,0.22)"
|
||||||
--status-divider = "rgba(140,107,175,0.10)"
|
--status-divider = "rgba(140,107,175,0.10)"
|
||||||
--sidebar-hover = "rgba(140,107,175,0.08)"
|
--sidebar-hover = "rgba(140,107,175,0.08)"
|
||||||
--sidebar-icon = "rgba(28,21,37,0.50)"
|
--sidebar-icon = "rgba(28,21,37,0.50)"
|
||||||
|
|||||||
@@ -19,16 +19,16 @@ elevation = { --elevation-0 = "#FAFAFA", --elevation-1 = "#F2F3F5", --elevation-
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#2A2C30"
|
--on-background = "#2A2C30"
|
||||||
--on-surface = "#2A2C30"
|
--on-surface = "#2A2C30"
|
||||||
--on-surface-medium = "rgba(42,44,48,0.68)"
|
--on-surface-medium = "rgba(42,44,48,0.86)"
|
||||||
--on-surface-disabled = "rgba(42,44,48,0.38)"
|
--on-surface-disabled = "rgba(42,44,48,0.62)"
|
||||||
--error = "#8C5A62"
|
--error = "#8C5A62"
|
||||||
--on-error = "#FFFFFF"
|
--on-error = "#FFFFFF"
|
||||||
--success = "#5A7E5C"
|
--success = "#3D7A42"
|
||||||
--on-success = "#FFFFFF"
|
--on-success = "#FFFFFF"
|
||||||
--warning = "#8A7A52"
|
--warning = "#9A7A2E"
|
||||||
--on-warning = "#000000"
|
--on-warning = "#000000"
|
||||||
--divider = "rgba(42,44,48,0.12)"
|
--divider = "rgba(42,44,48,0.20)"
|
||||||
--outline = "rgba(42,44,48,0.14)"
|
--outline = "rgba(42,44,48,0.24)"
|
||||||
--scrim = "rgba(0,0,0,0.42)"
|
--scrim = "rgba(0,0,0,0.42)"
|
||||||
--surface-hover = "rgba(42,44,48,0.04)"
|
--surface-hover = "rgba(42,44,48,0.04)"
|
||||||
--surface-alt = "rgba(42,44,48,0.02)"
|
--surface-alt = "rgba(42,44,48,0.02)"
|
||||||
@@ -47,14 +47,14 @@ elevation = { --elevation-0 = "#FAFAFA", --elevation-1 = "#F2F3F5", --elevation-
|
|||||||
--chart-hover-ring = "rgba(42,44,48,0.24)"
|
--chart-hover-ring = "rgba(42,44,48,0.24)"
|
||||||
--tooltip-bg = "rgba(50,52,58,0.92)"
|
--tooltip-bg = "rgba(50,52,58,0.92)"
|
||||||
--tooltip-border = "rgba(42,44,48,0.10)"
|
--tooltip-border = "rgba(42,44,48,0.10)"
|
||||||
--glass-fill = "rgba(255,255,255,0.55)"
|
--glass-fill = "rgba(255,255,255,0.20)"
|
||||||
--glass-border = "rgba(42,44,48,0.10)"
|
--glass-border = "rgba(42,44,48,0.10)"
|
||||||
--glass-noise-tint = "rgba(42,44,48,0.015)"
|
--glass-noise-tint = "rgba(42,44,48,0.015)"
|
||||||
--tactile-top = "rgba(255,255,255,0.35)"
|
--tactile-top = "rgba(255,255,255,0.35)"
|
||||||
--tactile-bottom = "rgba(255,255,255,0.04)"
|
--tactile-bottom = "rgba(255,255,255,0.04)"
|
||||||
--hover-overlay = "rgba(42,44,48,0.04)"
|
--hover-overlay = "rgba(42,44,48,0.10)"
|
||||||
--active-overlay = "rgba(42,44,48,0.08)"
|
--active-overlay = "rgba(42,44,48,0.08)"
|
||||||
--rim-light = "rgba(42,44,48,0.06)"
|
--rim-light = "rgba(42,44,48,0.22)"
|
||||||
--status-divider = "rgba(42,44,48,0.08)"
|
--status-divider = "rgba(42,44,48,0.08)"
|
||||||
--sidebar-hover = "rgba(42,44,48,0.05)"
|
--sidebar-hover = "rgba(42,44,48,0.05)"
|
||||||
--sidebar-icon = "rgba(42,44,48,0.45)"
|
--sidebar-icon = "rgba(42,44,48,0.45)"
|
||||||
|
|||||||
@@ -20,16 +20,16 @@ elevation = { --elevation-0 = "#FAFAF8", --elevation-1 = "#F0EEEC", --elevation-
|
|||||||
--on-secondary = "#FFFFFF"
|
--on-secondary = "#FFFFFF"
|
||||||
--on-background = "#2C2A28"
|
--on-background = "#2C2A28"
|
||||||
--on-surface = "#2C2A28"
|
--on-surface = "#2C2A28"
|
||||||
--on-surface-medium = "rgba(44,42,40,0.68)"
|
--on-surface-medium = "rgba(44,42,40,0.86)"
|
||||||
--on-surface-disabled = "rgba(44,42,40,0.38)"
|
--on-surface-disabled = "rgba(44,42,40,0.62)"
|
||||||
--error = "#8C5250"
|
--error = "#8C5250"
|
||||||
--on-error = "#FFFFFF"
|
--on-error = "#FFFFFF"
|
||||||
--success = "#5C7A62"
|
--success = "#3F7A48"
|
||||||
--on-success = "#FFFFFF"
|
--on-success = "#FFFFFF"
|
||||||
--warning = "#8A7A4C"
|
--warning = "#9A7A2E"
|
||||||
--on-warning = "#000000"
|
--on-warning = "#000000"
|
||||||
--divider = "rgba(80,75,68,0.12)"
|
--divider = "rgba(80,75,68,0.20)"
|
||||||
--outline = "rgba(80,75,68,0.14)"
|
--outline = "rgba(80,75,68,0.24)"
|
||||||
--scrim = "rgba(20,18,16,0.42)"
|
--scrim = "rgba(20,18,16,0.42)"
|
||||||
--surface-hover = "rgba(110,117,128,0.05)"
|
--surface-hover = "rgba(110,117,128,0.05)"
|
||||||
--surface-alt = "rgba(110,117,128,0.025)"
|
--surface-alt = "rgba(110,117,128,0.025)"
|
||||||
@@ -48,14 +48,14 @@ elevation = { --elevation-0 = "#FAFAF8", --elevation-1 = "#F0EEEC", --elevation-
|
|||||||
--chart-hover-ring = "rgba(110,117,128,0.24)"
|
--chart-hover-ring = "rgba(110,117,128,0.24)"
|
||||||
--tooltip-bg = "rgba(44,42,40,0.94)"
|
--tooltip-bg = "rgba(44,42,40,0.94)"
|
||||||
--tooltip-border = "rgba(110,117,128,0.10)"
|
--tooltip-border = "rgba(110,117,128,0.10)"
|
||||||
--glass-fill = "rgba(255,255,254,0.62)"
|
--glass-fill = "rgba(255,255,254,0.20)"
|
||||||
--glass-border = "rgba(110,117,128,0.10)"
|
--glass-border = "rgba(110,117,128,0.10)"
|
||||||
--glass-noise-tint = "rgba(80,75,68,0.02)"
|
--glass-noise-tint = "rgba(80,75,68,0.02)"
|
||||||
--tactile-top = "rgba(255,255,255,0.45)"
|
--tactile-top = "rgba(255,255,255,0.45)"
|
||||||
--tactile-bottom = "rgba(255,255,255,0.06)"
|
--tactile-bottom = "rgba(255,255,255,0.06)"
|
||||||
--hover-overlay = "rgba(110,117,128,0.04)"
|
--hover-overlay = "rgba(110,117,128,0.10)"
|
||||||
--active-overlay = "rgba(110,117,128,0.08)"
|
--active-overlay = "rgba(110,117,128,0.08)"
|
||||||
--rim-light = "rgba(180,175,168,0.10)"
|
--rim-light = "rgba(44,42,40,0.22)"
|
||||||
--status-divider = "rgba(110,117,128,0.08)"
|
--status-divider = "rgba(110,117,128,0.08)"
|
||||||
--sidebar-hover = "rgba(110,117,128,0.06)"
|
--sidebar-hover = "rgba(110,117,128,0.06)"
|
||||||
--sidebar-icon = "rgba(44,42,40,0.48)"
|
--sidebar-icon = "rgba(44,42,40,0.48)"
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ images = { background_image = "backgrounds/texture/obsidian_bg.png", logo = "log
|
|||||||
--on-secondary = "#000000"
|
--on-secondary = "#000000"
|
||||||
--on-background = "#E8E0F0"
|
--on-background = "#E8E0F0"
|
||||||
--on-surface = "#E8E0F0"
|
--on-surface = "#E8E0F0"
|
||||||
--on-surface-medium = "rgba(232,224,240,0.75)"
|
--on-surface-medium = "rgba(232,224,240,0.85)"
|
||||||
--on-surface-disabled = "rgba(232,224,240,0.45)"
|
--on-surface-disabled = "rgba(232,224,240,0.58)"
|
||||||
--error = "#CF6679"
|
--error = "#CF6679"
|
||||||
--on-error = "#000000"
|
--on-error = "#000000"
|
||||||
--success = "#81C784"
|
--success = "#81C784"
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ images = { background_image = "backgrounds/texture/drgx_bg.png", logo = "logos/l
|
|||||||
--on-secondary = "#000000"
|
--on-secondary = "#000000"
|
||||||
--on-background = "#F0E0D8"
|
--on-background = "#F0E0D8"
|
||||||
--on-surface = "#F0E0D8"
|
--on-surface = "#F0E0D8"
|
||||||
--on-surface-medium = "rgba(240,224,216,0.7)"
|
--on-surface-medium = "rgba(240,224,216,0.85)"
|
||||||
--on-surface-disabled = "rgba(240,224,216,0.44)"
|
--on-surface-disabled = "rgba(240,224,216,0.58)"
|
||||||
--error = "#FF5252"
|
--error = "#FF5252"
|
||||||
--on-error = "#000000"
|
--on-error = "#000000"
|
||||||
--success = "#81C784"
|
--success = "#81C784"
|
||||||
@@ -485,12 +485,12 @@ suggestion-trunc-len = { size = 50 }
|
|||||||
fee-rounding = { size = 10.0 }
|
fee-rounding = { size = 10.0 }
|
||||||
amount-bar-max-btn-width = { size = 80.0 }
|
amount-bar-max-btn-width = { size = 80.0 }
|
||||||
amount-bar-height = { size = 22.0 }
|
amount-bar-height = { size = 22.0 }
|
||||||
confirm-popup-max-width = { size = 420.0 }
|
confirm-popup-max-width = { size = 560.0 }
|
||||||
confirm-addr-card-height = { size = 28.0 }
|
confirm-addr-card-height = { size = 28.0 }
|
||||||
confirm-amount-card-height = { size = 70.0 }
|
confirm-amount-card-height = { size = 96.0 }
|
||||||
confirm-row-step = { size = 16.0 }
|
confirm-row-step = { size = 24.0 }
|
||||||
confirm-val-col-x = { size = 90.0 }
|
confirm-val-col-x = { size = 150.0 }
|
||||||
confirm-usd-col-x = { size = 80.0 }
|
confirm-usd-col-x = { size = 116.0 }
|
||||||
progress-card-height = { size = 36.0 }
|
progress-card-height = { size = 36.0 }
|
||||||
progress-card-height-txid = { size = 52.0 }
|
progress-card-height-txid = { size = 52.0 }
|
||||||
progress-card-pad-x = { size = 12.0 }
|
progress-card-pad-x = { size = 12.0 }
|
||||||
@@ -516,10 +516,10 @@ error-icon-inset = { size = 20.0 }
|
|||||||
error-btn-rounding = { size = 4.0 }
|
error-btn-rounding = { size = 4.0 }
|
||||||
progress-glass-rounding-ratio = { size = 0.75 }
|
progress-glass-rounding-ratio = { size = 0.75 }
|
||||||
confirm-addr-card-min-height = { size = 24.0 }
|
confirm-addr-card-min-height = { size = 24.0 }
|
||||||
confirm-val-col-min-x = { size = 70.0 }
|
confirm-val-col-min-x = { size = 112.0 }
|
||||||
confirm-usd-col-min-x = { size = 60.0 }
|
confirm-usd-col-min-x = { size = 92.0 }
|
||||||
confirm-amount-card-min-height = { size = 54.0 }
|
confirm-amount-card-min-height = { size = 82.0 }
|
||||||
confirm-row-step-min = { size = 12.0 }
|
confirm-row-step-min = { size = 20.0 }
|
||||||
action-btn-min-height = { size = 26.0 }
|
action-btn-min-height = { size = 26.0 }
|
||||||
recent-icon-min-size = { size = 3.5 }
|
recent-icon-min-size = { size = 3.5 }
|
||||||
recent-icon-size = { size = 5.0 }
|
recent-icon-size = { size = 5.0 }
|
||||||
@@ -566,7 +566,7 @@ txid-trunc-len = { size = 14 }
|
|||||||
txid-label-x-offset = { size = 20.0 }
|
txid-label-x-offset = { size = 20.0 }
|
||||||
txid-copy-btn-right-offset = { size = 50.0 }
|
txid-copy-btn-right-offset = { size = 50.0 }
|
||||||
txid-copy-btn-y-offset = { size = 2.0 }
|
txid-copy-btn-y-offset = { size = 2.0 }
|
||||||
confirm-popup-width-ratio = { size = 0.85 }
|
confirm-popup-width-ratio = { size = 0.92 }
|
||||||
confirm-glass-rounding-ratio = { size = 0.75 }
|
confirm-glass-rounding-ratio = { size = 0.75 }
|
||||||
confirm-addr-trunc-len = { size = 48 }
|
confirm-addr-trunc-len = { size = 48 }
|
||||||
confirm-divider-thickness = { size = 1.0 }
|
confirm-divider-thickness = { size = 1.0 }
|
||||||
@@ -874,7 +874,7 @@ accent-stripe-inset-ratio = { size = 0.0 }
|
|||||||
accent-stripe-left-offset = { size = 0.0 }
|
accent-stripe-left-offset = { size = 0.0 }
|
||||||
accent-stripe-width = { size = 4.0 }
|
accent-stripe-width = { size = 4.0 }
|
||||||
accent-stripe-rounding = { size = 1.5 }
|
accent-stripe-rounding = { size = 1.5 }
|
||||||
chart-y-axis-min-padding = { size = 40.0 }
|
chart-y-axis-min-padding = { size = 54.0 }
|
||||||
chart-y-axis-padding = { size = 70.0 }
|
chart-y-axis-padding = { size = 70.0 }
|
||||||
chart-dot-min-radius = { size = 1.5 }
|
chart-dot-min-radius = { size = 1.5 }
|
||||||
chart-dot-radius = { size = 2.0 }
|
chart-dot-radius = { size = 2.0 }
|
||||||
@@ -908,6 +908,7 @@ scroll-fade-zone = { size = 24.0 }
|
|||||||
|
|
||||||
[tabs.console]
|
[tabs.console]
|
||||||
input-area-padding = 8.0
|
input-area-padding = 8.0
|
||||||
|
bg-darken-alpha = { size = 110.0 } # black overlay alpha (0-255) for the terminal-dark output + input
|
||||||
output-line-spacing = 2.0
|
output-line-spacing = 2.0
|
||||||
output = { line-spacing = 2 }
|
output = { line-spacing = 2 }
|
||||||
scroll-multiplier = { size = 3.0 }
|
scroll-multiplier = { size = 3.0 }
|
||||||
@@ -930,7 +931,7 @@ scanline-speed = { size = 40.0 }
|
|||||||
scanline-height = { size = 36.0 }
|
scanline-height = { size = 36.0 }
|
||||||
scanline-alpha = { size = 8.0 }
|
scanline-alpha = { size = 8.0 }
|
||||||
scanline-gap = { size = 2.0 }
|
scanline-gap = { size = 2.0 }
|
||||||
scanline-line-alpha = { size = 4.0 }
|
scanline-line-alpha = { size = 2.0 }
|
||||||
scanline-glow-spread = { size = 4.0 }
|
scanline-glow-spread = { size = 4.0 }
|
||||||
scanline-glow-intensity = { size = 0.6 }
|
scanline-glow-intensity = { size = 0.6 }
|
||||||
scanline-glow-color = { size = 255.0 }
|
scanline-glow-color = { size = 255.0 }
|
||||||
@@ -1218,6 +1219,9 @@ notification-progress = { color = "var(--primary)", height = 4, position = 18 }
|
|||||||
fill-alpha = { size = 12.0 }
|
fill-alpha = { size = 12.0 }
|
||||||
noise-alpha = { size = 14.0 }
|
noise-alpha = { size = 14.0 }
|
||||||
|
|
||||||
|
[components.overlay-dialog]
|
||||||
|
confirm-btn-height = { size = 40.0 }
|
||||||
|
|
||||||
[components.qr-code]
|
[components.qr-code]
|
||||||
module-scale = { size = 4 }
|
module-scale = { size = 4 }
|
||||||
border-modules = { size = 2 }
|
border-modules = { size = 2 }
|
||||||
@@ -1230,8 +1234,9 @@ width = { size = 140.0 }
|
|||||||
collapsed-width = { size = 64.0 }
|
collapsed-width = { size = 64.0 }
|
||||||
collapse-anim-speed = { size = 10.0 }
|
collapse-anim-speed = { size = 10.0 }
|
||||||
auto-collapse-threshold = { size = 800.0 }
|
auto-collapse-threshold = { size = 800.0 }
|
||||||
section-gap = { size = 4.0 }
|
section-gap = { size = 8.0 }
|
||||||
section-label-pad-left = { size = 16.0 }
|
section-label-pad-left = { size = 16.0 }
|
||||||
|
section-label-pad-bottom = { size = 4.0 }
|
||||||
item-height = { size = 36.0 }
|
item-height = { size = 36.0 }
|
||||||
item-pad-x = { size = 8.0 }
|
item-pad-x = { size = 8.0 }
|
||||||
min-height = { size = 360.0 }
|
min-height = { size = 360.0 }
|
||||||
@@ -1248,8 +1253,8 @@ icon-half-size = { size = 7.0 }
|
|||||||
icon-label-gap = { size = 8.0 }
|
icon-label-gap = { size = 8.0 }
|
||||||
badge-radius-dot = { size = 4.0 }
|
badge-radius-dot = { size = 4.0 }
|
||||||
badge-radius-number = { size = 8.0 }
|
badge-radius-number = { size = 8.0 }
|
||||||
button-spacing = { size = 4.0 }
|
button-spacing = { size = 6.0 }
|
||||||
bottom-padding = { size = 0.0 }
|
bottom-padding = { size = 4.0 }
|
||||||
exit-icon-gap = { size = 4.0 }
|
exit-icon-gap = { size = 4.0 }
|
||||||
cutout-shadow-alpha = { size = 55 }
|
cutout-shadow-alpha = { size = 55 }
|
||||||
cutout-highlight-alpha = { size = 8 }
|
cutout-highlight-alpha = { size = 8 }
|
||||||
@@ -1278,7 +1283,7 @@ page-fade-speed = { size = 8.0 }
|
|||||||
collapse-hysteresis = { size = 60.0 }
|
collapse-hysteresis = { size = 60.0 }
|
||||||
header-icon = { icon-dark = "logos/logo_ObsidianDragon_dark.png", icon-light = "logos/logo_ObsidianDragon_light.png" }
|
header-icon = { icon-dark = "logos/logo_ObsidianDragon_dark.png", icon-light = "logos/logo_ObsidianDragon_light.png" }
|
||||||
coin-icon = { icon = "logos/logo_dragonx_128.png" }
|
coin-icon = { icon = "logos/logo_dragonx_128.png" }
|
||||||
header-title = { font = "subtitle1", size = 14.0, pad-x = 22.0, pad-y = 6.0, logo-gap = 4.0, opacity = 0.7, offset-y = 4.0 }
|
header-title = { font = "subtitle1", size = 12.0, pad-x = 8.0, pad-y = 10.0, logo-gap = 4.0, opacity = 0.7, offset-y = -2.0 }
|
||||||
|
|
||||||
[components.main-window.window]
|
[components.main-window.window]
|
||||||
padding = [12, 36]
|
padding = [12, 36]
|
||||||
@@ -1323,10 +1328,13 @@ wallet-btn-padding = { size = 24.0 }
|
|||||||
rpc-label-min-width = { size = 70.0 }
|
rpc-label-min-width = { size = 70.0 }
|
||||||
rpc-label-width = { size = 85.0 }
|
rpc-label-width = { size = 85.0 }
|
||||||
security-combo-width = { size = 120.0 }
|
security-combo-width = { size = 120.0 }
|
||||||
|
node-grid-breakpoint = { size = 900.0 }
|
||||||
port-input-min-width = { size = 60.0 }
|
port-input-min-width = { size = 60.0 }
|
||||||
port-input-width-ratio = { size = 0.4 }
|
port-input-width-ratio = { size = 0.4 }
|
||||||
idle-combo-width = { size = 64.0 }
|
idle-combo-width = { size = 64.0 }
|
||||||
about-logo-size = { size = 64.0 }
|
# Reserved height basis for the About-card logo; the logo is drawn scaled to the
|
||||||
|
# card's actual height (aspect-preserved) and capped to this * aspect in width.
|
||||||
|
about-logo-size = { size = 150.0 }
|
||||||
|
|
||||||
[components.main-layout]
|
[components.main-layout]
|
||||||
app-bar-height = { size = 64.0 }
|
app-bar-height = { size = 64.0 }
|
||||||
@@ -1377,6 +1385,17 @@ summary = { min-width = 280.0, max-width = 400.0, width-ratio = 0.32, min-height
|
|||||||
side-panel = { min-width = 280.0, max-width = 450.0, width-ratio = 0.4, min-height = 200.0, max-height = 350.0, height-ratio = 0.8 }
|
side-panel = { min-width = 280.0, max-width = 450.0, width-ratio = 0.4, min-height = 200.0, max-height = 350.0, height-ratio = 0.8 }
|
||||||
table = { min-height = 150.0, height-ratio = 0.45, min-remaining = 100.0, default-reserve = 30.0 }
|
table = { min-height = 150.0, height-ratio = 0.45, min-remaining = 100.0, default-reserve = 30.0 }
|
||||||
|
|
||||||
|
[dialog]
|
||||||
|
width-default = 480.0
|
||||||
|
width-lg = 600.0
|
||||||
|
width-xl = 660.0
|
||||||
|
min-width = 280.0
|
||||||
|
form-width = 400.0
|
||||||
|
action-width = 100.0
|
||||||
|
action-gap = 8.0
|
||||||
|
max-height-ratio = 0.94
|
||||||
|
compact-bottom-ratio = 0.64
|
||||||
|
|
||||||
[button]
|
[button]
|
||||||
min-width = 180.0
|
min-width = 180.0
|
||||||
width = 140.0
|
width = 140.0
|
||||||
@@ -1509,7 +1528,6 @@ title = { font = "h5" }
|
|||||||
input = { width = 320.0, height = 40.0 }
|
input = { width = 320.0, height = 40.0 }
|
||||||
unlock-button = { width = 320.0, height = 44.0, font = "subtitle1" }
|
unlock-button = { width = 320.0, height = 44.0, font = "subtitle1" }
|
||||||
error-text = { font = "caption" }
|
error-text = { font = "caption" }
|
||||||
backdrop-alpha = { opacity = 0.0 }
|
|
||||||
mode-toggle = { font = "caption" }
|
mode-toggle = { font = "caption" }
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1275,6 +1275,831 @@ TRANSLATIONS = {
|
|||||||
"pt": "Clique para copiar", "ru": "Нажмите для копирования", "zh": "点击复制",
|
"pt": "Clique para copiar", "ru": "Нажмите для копирования", "zh": "点击复制",
|
||||||
"ja": "クリックしてコピー", "ko": "복사하려면 클릭"
|
"ja": "クリックしてコピー", "ko": "복사하려면 클릭"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
# ── NEW BATCH: 100 missing keys ──────────────────────────────────────
|
||||||
|
|
||||||
|
"advanced_effects": {
|
||||||
|
"es": "Efectos Avanzados...", "de": "Erweiterte Effekte...", "fr": "Effets avancés...",
|
||||||
|
"pt": "Efeitos Avançados...", "ru": "Расширенные эффекты...", "zh": "高级特效...",
|
||||||
|
"ja": "高度なエフェクト...", "ko": "고급 효과..."
|
||||||
|
},
|
||||||
|
"ago": {
|
||||||
|
"es": "atrás", "de": "her", "fr": "passé",
|
||||||
|
"pt": "atrás", "ru": "назад", "zh": "前",
|
||||||
|
"ja": "前", "ko": "전"
|
||||||
|
},
|
||||||
|
"bootstrap_daemon_running": {
|
||||||
|
"es": "Daemon ejecutándose", "de": "Daemon läuft", "fr": "Daemon en cours",
|
||||||
|
"pt": "Daemon em execução", "ru": "Демон запущен", "zh": "守护进程运行中",
|
||||||
|
"ja": "デーモン実行中", "ko": "데몬 실행 중"
|
||||||
|
},
|
||||||
|
"bootstrap_daemon_stopped": {
|
||||||
|
"es": "Daemon detenido", "de": "Daemon gestoppt", "fr": "Daemon arrêté",
|
||||||
|
"pt": "Daemon parado", "ru": "Демон остановлен", "zh": "守护进程已停止",
|
||||||
|
"ja": "デーモン停止", "ko": "데몬 중지됨"
|
||||||
|
},
|
||||||
|
"bootstrap_daemon_stopping": {
|
||||||
|
"es": "Deteniendo daemon...", "de": "Daemon wird gestoppt...", "fr": "Arrêt du daemon...",
|
||||||
|
"pt": "Parando daemon...", "ru": "Остановка демона...", "zh": "正在停止守护进程...",
|
||||||
|
"ja": "デーモン停止中...", "ko": "데몬 중지 중..."
|
||||||
|
},
|
||||||
|
"bootstrap_desc": {
|
||||||
|
"es": "Descarga un bootstrap de la blockchain para acelerar drásticamente la sincronización inicial. Esto descarga una instantánea de la blockchain y la extrae en tu directorio de datos.",
|
||||||
|
"de": "Laden Sie einen Blockchain-Bootstrap herunter, um die anfängliche Synchronisierung drastisch zu beschleunigen. Dies lädt einen Snapshot der Blockchain herunter und extrahiert ihn in Ihr Datenverzeichnis.",
|
||||||
|
"fr": "Téléchargez un bootstrap de la blockchain pour accélérer considérablement la synchronisation initiale. Cela télécharge un instantané de la blockchain et l'extrait dans votre répertoire de données.",
|
||||||
|
"pt": "Baixe um bootstrap da blockchain para acelerar drasticamente a sincronização inicial. Isso baixa um instantâneo da blockchain e o extrai no seu diretório de dados.",
|
||||||
|
"ru": "Загрузите бутстрап блокчейна для значительного ускорения начальной синхронизации. Это загружает снимок блокчейна и извлекает его в ваш каталог данных.",
|
||||||
|
"zh": "下载区块链引导程序以大幅加速初始同步。这将下载区块链快照并将其提取到您的数据目录中。",
|
||||||
|
"ja": "ブロックチェーンブートストラップをダウンロードして初期同期を劇的に高速化します。ブロックチェーンのスナップショットをダウンロードしてデータディレクトリに展開します。",
|
||||||
|
"ko": "블록체인 부트스트랩을 다운로드하여 초기 동기화를 대폭 가속합니다. 블록체인 스냅샷을 다운로드하고 데이터 디렉토리에 추출합니다."
|
||||||
|
},
|
||||||
|
"bootstrap_downloading": {
|
||||||
|
"es": "Descargando bootstrap...", "de": "Bootstrap wird heruntergeladen...", "fr": "Téléchargement du bootstrap...",
|
||||||
|
"pt": "Baixando bootstrap...", "ru": "Загрузка бутстрапа...", "zh": "正在下载引导程序...",
|
||||||
|
"ja": "ブートストラップをダウンロード中...", "ko": "부트스트랩 다운로드 중..."
|
||||||
|
},
|
||||||
|
"bootstrap_extracting": {
|
||||||
|
"es": "Extrayendo datos de blockchain...", "de": "Blockchain-Daten werden extrahiert...", "fr": "Extraction des données blockchain...",
|
||||||
|
"pt": "Extraindo dados da blockchain...", "ru": "Извлечение данных блокчейна...", "zh": "正在提取区块链数据...",
|
||||||
|
"ja": "ブロックチェーンデータを展開中...", "ko": "블록체인 데이터 추출 중..."
|
||||||
|
},
|
||||||
|
"bootstrap_failed": {
|
||||||
|
"es": "Error en Bootstrap", "de": "Bootstrap fehlgeschlagen", "fr": "Échec du Bootstrap",
|
||||||
|
"pt": "Falha no Bootstrap", "ru": "Ошибка бутстрапа", "zh": "引导程序失败",
|
||||||
|
"ja": "ブートストラップ失敗", "ko": "부트스트랩 실패"
|
||||||
|
},
|
||||||
|
"bootstrap_mirror": {
|
||||||
|
"es": "Espejo", "de": "Spiegel", "fr": "Miroir",
|
||||||
|
"pt": "Espelho", "ru": "Зеркало", "zh": "镜像",
|
||||||
|
"ja": "ミラー", "ko": "미러"
|
||||||
|
},
|
||||||
|
"bootstrap_mirror_tooltip": {
|
||||||
|
"es": "Descargar desde espejo (bootstrap2.dragonx.is).\nUsa esto si la descarga principal es lenta o falla.",
|
||||||
|
"de": "Vom Spiegel herunterladen (bootstrap2.dragonx.is).\nVerwenden Sie dies, wenn der Hauptdownload langsam ist oder fehlschlägt.",
|
||||||
|
"fr": "Télécharger depuis le miroir (bootstrap2.dragonx.is).\nUtilisez ceci si le téléchargement principal est lent ou échoue.",
|
||||||
|
"pt": "Baixar do espelho (bootstrap2.dragonx.is).\nUse isto se o download principal estiver lento ou falhando.",
|
||||||
|
"ru": "Скачать с зеркала (bootstrap2.dragonx.is).\nИспользуйте, если основная загрузка медленная или не работает.",
|
||||||
|
"zh": "从镜像下载 (bootstrap2.dragonx.is)。\n如果主下载速度慢或失败,请使用此选项。",
|
||||||
|
"ja": "ミラーからダウンロード (bootstrap2.dragonx.is)。\nメインのダウンロードが遅い場合や失敗する場合に使用してください。",
|
||||||
|
"ko": "미러에서 다운로드 (bootstrap2.dragonx.is).\n메인 다운로드가 느리거나 실패할 경우 사용하세요."
|
||||||
|
},
|
||||||
|
"bootstrap_restart_daemon": {
|
||||||
|
"es": "Reiniciar Daemon", "de": "Daemon neu starten", "fr": "Redémarrer le Daemon",
|
||||||
|
"pt": "Reiniciar Daemon", "ru": "Перезапустить демон", "zh": "重启守护进程",
|
||||||
|
"ja": "デーモンを再起動", "ko": "데몬 재시작"
|
||||||
|
},
|
||||||
|
"bootstrap_success": {
|
||||||
|
"es": "Bootstrap Completado", "de": "Bootstrap abgeschlossen", "fr": "Bootstrap terminé",
|
||||||
|
"pt": "Bootstrap Completo", "ru": "Бутстрап завершён", "zh": "引导程序完成",
|
||||||
|
"ja": "ブートストラップ完了", "ko": "부트스트랩 완료"
|
||||||
|
},
|
||||||
|
"bootstrap_success_desc": {
|
||||||
|
"es": "Los datos de la blockchain se han extraído correctamente. Inicie el daemon para comenzar a sincronizar desde el punto del bootstrap.",
|
||||||
|
"de": "Blockchain-Daten wurden erfolgreich extrahiert. Starten Sie den Daemon, um ab dem Bootstrap-Punkt zu synchronisieren.",
|
||||||
|
"fr": "Les données de la blockchain ont été extraites avec succès. Démarrez le daemon pour commencer la synchronisation à partir du point de bootstrap.",
|
||||||
|
"pt": "Os dados da blockchain foram extraídos com sucesso. Inicie o daemon para começar a sincronizar a partir do ponto do bootstrap.",
|
||||||
|
"ru": "Данные блокчейна успешно извлечены. Запустите демон для начала синхронизации с точки бутстрапа.",
|
||||||
|
"zh": "区块链数据已成功提取。启动守护进程以从引导点开始同步。",
|
||||||
|
"ja": "ブロックチェーンデータが正常に展開されました。デーモンを起動してブートストラップポイントから同期を開始してください。",
|
||||||
|
"ko": "블록체인 데이터가 성공적으로 추출되었습니다. 데몬을 시작하여 부트스트랩 지점부터 동기화를 시작하세요."
|
||||||
|
},
|
||||||
|
"bootstrap_trust_warning": {
|
||||||
|
"es": "Solo use bootstrap.dragonx.is o bootstrap2.dragonx.is. Usar archivos de fuentes no confiables podría comprometer su nodo.",
|
||||||
|
"de": "Verwenden Sie nur bootstrap.dragonx.is oder bootstrap2.dragonx.is. Die Verwendung von Dateien aus nicht vertrauenswürdigen Quellen könnte Ihren Knoten gefährden.",
|
||||||
|
"fr": "N'utilisez que bootstrap.dragonx.is ou bootstrap2.dragonx.is. L'utilisation de fichiers provenant de sources non fiables pourrait compromettre votre nœud.",
|
||||||
|
"pt": "Use apenas bootstrap.dragonx.is ou bootstrap2.dragonx.is. Usar arquivos de fontes não confiáveis pode comprometer seu nó.",
|
||||||
|
"ru": "Используйте только bootstrap.dragonx.is или bootstrap2.dragonx.is. Использование файлов из ненадёжных источников может скомпрометировать ваш узел.",
|
||||||
|
"zh": "仅使用 bootstrap.dragonx.is 或 bootstrap2.dragonx.is。使用不受信任来源的文件可能会危及您的节点。",
|
||||||
|
"ja": "bootstrap.dragonx.is または bootstrap2.dragonx.is のみを使用してください。信頼できないソースのファイルを使用するとノードが危険にさらされる可能性があります。",
|
||||||
|
"ko": "bootstrap.dragonx.is 또는 bootstrap2.dragonx.is만 사용하세요. 신뢰할 수 없는 출처의 파일을 사용하면 노드가 손상될 수 있습니다."
|
||||||
|
},
|
||||||
|
"bootstrap_verifying": {
|
||||||
|
"es": "Verificando sumas de comprobación...", "de": "Prüfsummen werden überprüft...", "fr": "Vérification des sommes de contrôle...",
|
||||||
|
"pt": "Verificando somas de verificação...", "ru": "Проверка контрольных сумм...", "zh": "正在验证校验和...",
|
||||||
|
"ja": "チェックサムを検証中...", "ko": "체크섬 확인 중..."
|
||||||
|
},
|
||||||
|
"bootstrap_wallet_protected": {
|
||||||
|
"es": "(wallet.dat está protegido)", "de": "(wallet.dat ist geschützt)", "fr": "(wallet.dat est protégé)",
|
||||||
|
"pt": "(wallet.dat está protegido)", "ru": "(wallet.dat защищён)", "zh": "(wallet.dat 已受保护)",
|
||||||
|
"ja": "(wallet.dat は保護されています)", "ko": "(wallet.dat 보호됨)"
|
||||||
|
},
|
||||||
|
"bootstrap_warning": {
|
||||||
|
"es": "Los datos de bloques existentes (blocks, chainstate, notarizations) se eliminarán y reemplazarán. Su wallet.dat NO será modificado ni eliminado.",
|
||||||
|
"de": "Vorhandene Blockdaten (blocks, chainstate, notarizations) werden gelöscht und ersetzt. Ihre wallet.dat wird NICHT verändert oder gelöscht.",
|
||||||
|
"fr": "Les données de blocs existantes (blocks, chainstate, notarizations) seront supprimées et remplacées. Votre wallet.dat ne sera PAS modifié ni supprimé.",
|
||||||
|
"pt": "Os dados de blocos existentes (blocks, chainstate, notarizations) serão excluídos e substituídos. Seu wallet.dat NÃO será modificado ou excluído.",
|
||||||
|
"ru": "Существующие данные блоков (blocks, chainstate, notarizations) будут удалены и заменены. Ваш wallet.dat НЕ будет изменён или удалён.",
|
||||||
|
"zh": "现有区块数据(blocks、chainstate、notarizations)将被删除并替换。您的 wallet.dat 不会被修改或删除。",
|
||||||
|
"ja": "既存のブロックデータ(blocks、chainstate、notarizations)は削除され置き換えられます。wallet.dat は変更・削除されません。",
|
||||||
|
"ko": "기존 블록 데이터(blocks, chainstate, notarizations)가 삭제되고 교체됩니다. wallet.dat는 수정되거나 삭제되지 않습니다."
|
||||||
|
},
|
||||||
|
"choose_icon": {
|
||||||
|
"es": "Elegir Icono", "de": "Symbol wählen", "fr": "Choisir une icône",
|
||||||
|
"pt": "Escolher Ícone", "ru": "Выбрать иконку", "zh": "选择图标",
|
||||||
|
"ja": "アイコンを選択", "ko": "아이콘 선택"
|
||||||
|
},
|
||||||
|
"clear_icon": {
|
||||||
|
"es": "Borrar Icono", "de": "Symbol entfernen", "fr": "Effacer l'icône",
|
||||||
|
"pt": "Limpar Ícone", "ru": "Удалить иконку", "zh": "清除图标",
|
||||||
|
"ja": "アイコンをクリア", "ko": "아이콘 지우기"
|
||||||
|
},
|
||||||
|
"confirm_delete_blockchain_msg": {
|
||||||
|
"es": "Esto detendrá el daemon, eliminará todos los datos de la blockchain (blocks, chainstate, peers) y comenzará una nueva sincronización desde cero. Esto puede tardar varias horas.",
|
||||||
|
"de": "Dies stoppt den Daemon, löscht alle Blockchain-Daten (blocks, chainstate, peers) und startet eine neue Synchronisierung. Dies kann mehrere Stunden dauern.",
|
||||||
|
"fr": "Cela arrêtera le daemon, supprimera toutes les données de la blockchain (blocks, chainstate, peers) et démarrera une nouvelle synchronisation. Cela peut prendre plusieurs heures.",
|
||||||
|
"pt": "Isso irá parar o daemon, excluir todos os dados da blockchain (blocks, chainstate, peers) e iniciar uma nova sincronização do zero. Isso pode levar várias horas.",
|
||||||
|
"ru": "Это остановит демон, удалит все данные блокчейна (blocks, chainstate, peers) и начнёт синхронизацию с нуля. Это может занять несколько часов.",
|
||||||
|
"zh": "这将停止守护进程,删除所有区块链数据(blocks、chainstate、peers),并从头开始重新同步。这可能需要几个小时。",
|
||||||
|
"ja": "デーモンを停止し、すべてのブロックチェーンデータ(blocks、chainstate、peers)を削除して、最初から再同期を開始します。数時間かかる場合があります。",
|
||||||
|
"ko": "데몬을 중지하고 모든 블록체인 데이터(blocks, chainstate, peers)를 삭제한 후 처음부터 다시 동기화합니다. 몇 시간이 걸릴 수 있습니다."
|
||||||
|
},
|
||||||
|
"confirm_delete_blockchain_safe": {
|
||||||
|
"es": "Su wallet.dat, configuración e historial de transacciones están seguros y no se eliminarán.",
|
||||||
|
"de": "Ihre wallet.dat, Konfiguration und Transaktionshistorie sind sicher und werden nicht gelöscht.",
|
||||||
|
"fr": "Votre wallet.dat, votre configuration et votre historique de transactions sont en sécurité et ne seront pas supprimés.",
|
||||||
|
"pt": "Seu wallet.dat, configuração e histórico de transações estão seguros e não serão excluídos.",
|
||||||
|
"ru": "Ваш wallet.dat, конфигурация и история транзакций в безопасности и не будут удалены.",
|
||||||
|
"zh": "您的 wallet.dat、配置和交易历史是安全的,不会被删除。",
|
||||||
|
"ja": "wallet.dat、設定、トランザクション履歴は安全で削除されません。",
|
||||||
|
"ko": "wallet.dat, 설정 및 거래 내역은 안전하며 삭제되지 않습니다."
|
||||||
|
},
|
||||||
|
"confirm_delete_blockchain_title": {
|
||||||
|
"es": "Eliminar Datos de Blockchain", "de": "Blockchain-Daten löschen", "fr": "Supprimer les données Blockchain",
|
||||||
|
"pt": "Excluir Dados da Blockchain", "ru": "Удалить данные блокчейна", "zh": "删除区块链数据",
|
||||||
|
"ja": "ブロックチェーンデータを削除", "ko": "블록체인 데이터 삭제"
|
||||||
|
},
|
||||||
|
"confirm_transfer": {
|
||||||
|
"es": "Confirmar Transferencia", "de": "Überweisung bestätigen", "fr": "Confirmer le transfert",
|
||||||
|
"pt": "Confirmar Transferência", "ru": "Подтвердить перевод", "zh": "确认转账",
|
||||||
|
"ja": "送金を確認", "ko": "이체 확인"
|
||||||
|
},
|
||||||
|
"copied": {
|
||||||
|
"es": "¡Copiado!", "de": "Kopiert!", "fr": "Copié !",
|
||||||
|
"pt": "Copiado!", "ru": "Скопировано!", "zh": "已复制!",
|
||||||
|
"ja": "コピーしました!", "ko": "복사됨!"
|
||||||
|
},
|
||||||
|
"daemon_version": {
|
||||||
|
"es": "Daemon", "de": "Daemon", "fr": "Daemon",
|
||||||
|
"pt": "Daemon", "ru": "Демон", "zh": "守护进程",
|
||||||
|
"ja": "デーモン", "ko": "데몬"
|
||||||
|
},
|
||||||
|
"delete_blockchain": {
|
||||||
|
"es": "Eliminar Blockchain", "de": "Blockchain löschen", "fr": "Supprimer Blockchain",
|
||||||
|
"pt": "Excluir Blockchain", "ru": "Удалить блокчейн", "zh": "删除区块链",
|
||||||
|
"ja": "ブロックチェーンを削除", "ko": "블록체인 삭제"
|
||||||
|
},
|
||||||
|
"delete_blockchain_confirm": {
|
||||||
|
"es": "Eliminar y Resincronizar", "de": "Löschen & Neu synchronisieren", "fr": "Supprimer & Resynchroniser",
|
||||||
|
"pt": "Excluir e Ressincronizar", "ru": "Удалить и пересинхронизировать", "zh": "删除并重新同步",
|
||||||
|
"ja": "削除して再同期", "ko": "삭제 후 재동기화"
|
||||||
|
},
|
||||||
|
"deshielding_warning": {
|
||||||
|
"es": "Advertencia: Esto des-protegerá fondos de una dirección privada (Z) a una dirección transparente (T).",
|
||||||
|
"de": "Warnung: Dies wird Gelder von einer privaten (Z) Adresse auf eine transparente (T) Adresse ent-schirmen.",
|
||||||
|
"fr": "Attention : Cela va déblinder des fonds d'une adresse privée (Z) vers une adresse transparente (T).",
|
||||||
|
"pt": "Aviso: Isso irá des-blindar fundos de um endereço privado (Z) para um endereço transparente (T).",
|
||||||
|
"ru": "Внимание: это переведёт средства из приватного (Z) адреса на прозрачный (T) адрес.",
|
||||||
|
"zh": "警告:这将把资金从隐私 (Z) 地址转移到透明 (T) 地址。",
|
||||||
|
"ja": "警告:プライベート (Z) アドレスからトランスペアレント (T) アドレスへ資金をデシールドします。",
|
||||||
|
"ko": "경고: 프라이빗 (Z) 주소에서 투명 (T) 주소로 자금을 디실딩합니다."
|
||||||
|
},
|
||||||
|
"download": {
|
||||||
|
"es": "Descargar", "de": "Herunterladen", "fr": "Télécharger",
|
||||||
|
"pt": "Baixar", "ru": "Скачать", "zh": "下载",
|
||||||
|
"ja": "ダウンロード", "ko": "다운로드"
|
||||||
|
},
|
||||||
|
"download_bootstrap": {
|
||||||
|
"es": "Descargar Bootstrap", "de": "Bootstrap herunterladen", "fr": "Télécharger Bootstrap",
|
||||||
|
"pt": "Baixar Bootstrap", "ru": "Скачать бутстрап", "zh": "下载引导程序",
|
||||||
|
"ja": "ブートストラップをダウンロード", "ko": "부트스트랩 다운로드"
|
||||||
|
},
|
||||||
|
"explorer_block_detail": {
|
||||||
|
"es": "Bloque", "de": "Block", "fr": "Bloc",
|
||||||
|
"pt": "Bloco", "ru": "Блок", "zh": "区块",
|
||||||
|
"ja": "ブロック", "ko": "블록"
|
||||||
|
},
|
||||||
|
"explorer_block_hash": {
|
||||||
|
"es": "Hash", "de": "Hash", "fr": "Hash",
|
||||||
|
"pt": "Hash", "ru": "Хеш", "zh": "哈希",
|
||||||
|
"ja": "ハッシュ", "ko": "해시"
|
||||||
|
},
|
||||||
|
"explorer_block_height": {
|
||||||
|
"es": "Altura", "de": "Höhe", "fr": "Hauteur",
|
||||||
|
"pt": "Altura", "ru": "Высота", "zh": "高度",
|
||||||
|
"ja": "高さ", "ko": "높이"
|
||||||
|
},
|
||||||
|
"explorer_block_merkle": {
|
||||||
|
"es": "Raíz Merkle", "de": "Merkle-Wurzel", "fr": "Racine Merkle",
|
||||||
|
"pt": "Raiz Merkle", "ru": "Корень Меркла", "zh": "Merkle 根",
|
||||||
|
"ja": "マークルルート", "ko": "머클 루트"
|
||||||
|
},
|
||||||
|
"explorer_block_size": {
|
||||||
|
"es": "Tamaño", "de": "Größe", "fr": "Taille",
|
||||||
|
"pt": "Tamanho", "ru": "Размер", "zh": "大小",
|
||||||
|
"ja": "サイズ", "ko": "크기"
|
||||||
|
},
|
||||||
|
"explorer_block_time": {
|
||||||
|
"es": "Hora", "de": "Zeit", "fr": "Heure",
|
||||||
|
"pt": "Hora", "ru": "Время", "zh": "时间",
|
||||||
|
"ja": "時刻", "ko": "시간"
|
||||||
|
},
|
||||||
|
"explorer_block_txs": {
|
||||||
|
"es": "Transacciones", "de": "Transaktionen", "fr": "Transactions",
|
||||||
|
"pt": "Transações", "ru": "Транзакции", "zh": "交易",
|
||||||
|
"ja": "トランザクション", "ko": "트랜잭션"
|
||||||
|
},
|
||||||
|
"explorer_chain_stats": {
|
||||||
|
"es": "Cadena", "de": "Kette", "fr": "Chaîne",
|
||||||
|
"pt": "Cadeia", "ru": "Цепочка", "zh": "链",
|
||||||
|
"ja": "チェーン", "ko": "체인"
|
||||||
|
},
|
||||||
|
"explorer_invalid_query": {
|
||||||
|
"es": "Ingrese una altura de bloque o un hash de 64 caracteres",
|
||||||
|
"de": "Geben Sie eine Blockhöhe oder einen 64-stelligen Hash ein",
|
||||||
|
"fr": "Entrez une hauteur de bloc ou un hash de 64 caractères",
|
||||||
|
"pt": "Insira uma altura de bloco ou um hash de 64 caracteres",
|
||||||
|
"ru": "Введите высоту блока или 64-символьный хеш",
|
||||||
|
"zh": "输入区块高度或64位哈希",
|
||||||
|
"ja": "ブロック高さまたは64文字のハッシュを入力してください",
|
||||||
|
"ko": "블록 높이 또는 64자 해시를 입력하세요"
|
||||||
|
},
|
||||||
|
"explorer_mempool": {
|
||||||
|
"es": "Mempool", "de": "Mempool", "fr": "Mempool",
|
||||||
|
"pt": "Mempool", "ru": "Мемпул", "zh": "内存池",
|
||||||
|
"ja": "メモリプール", "ko": "멤풀"
|
||||||
|
},
|
||||||
|
"explorer_mempool_size": {
|
||||||
|
"es": "Tamaño", "de": "Größe", "fr": "Taille",
|
||||||
|
"pt": "Tamanho", "ru": "Размер", "zh": "大小",
|
||||||
|
"ja": "サイズ", "ko": "크기"
|
||||||
|
},
|
||||||
|
"explorer_mempool_txs": {
|
||||||
|
"es": "Transacciones", "de": "Transaktionen", "fr": "Transactions",
|
||||||
|
"pt": "Transações", "ru": "Транзакции", "zh": "交易",
|
||||||
|
"ja": "トランザクション", "ko": "트랜잭션"
|
||||||
|
},
|
||||||
|
"explorer_recent_blocks": {
|
||||||
|
"es": "Bloques Recientes", "de": "Letzte Blöcke", "fr": "Blocs récents",
|
||||||
|
"pt": "Blocos Recentes", "ru": "Последние блоки", "zh": "最近区块",
|
||||||
|
"ja": "最近のブロック", "ko": "최근 블록"
|
||||||
|
},
|
||||||
|
"explorer_search": {
|
||||||
|
"es": "Buscar", "de": "Suchen", "fr": "Rechercher",
|
||||||
|
"pt": "Pesquisar", "ru": "Поиск", "zh": "搜索",
|
||||||
|
"ja": "検索", "ko": "검색"
|
||||||
|
},
|
||||||
|
"explorer_tx_outputs": {
|
||||||
|
"es": "Salidas", "de": "Ausgaben", "fr": "Sorties",
|
||||||
|
"pt": "Saídas", "ru": "Выходы", "zh": "输出",
|
||||||
|
"ja": "出力", "ko": "출력"
|
||||||
|
},
|
||||||
|
"explorer_tx_size": {
|
||||||
|
"es": "Tamaño", "de": "Größe", "fr": "Taille",
|
||||||
|
"pt": "Tamanho", "ru": "Размер", "zh": "大小",
|
||||||
|
"ja": "サイズ", "ko": "크기"
|
||||||
|
},
|
||||||
|
"filter": {
|
||||||
|
"es": "Filtrar...", "de": "Filtern...", "fr": "Filtrer...",
|
||||||
|
"pt": "Filtrar...", "ru": "Фильтр...", "zh": "筛选...",
|
||||||
|
"ja": "フィルター...", "ko": "필터..."
|
||||||
|
},
|
||||||
|
"force_quit": {
|
||||||
|
"es": "Forzar Salida", "de": "Sofort beenden", "fr": "Forcer la fermeture",
|
||||||
|
"pt": "Forçar Saída", "ru": "Принудительный выход", "zh": "强制退出",
|
||||||
|
"ja": "強制終了", "ko": "강제 종료"
|
||||||
|
},
|
||||||
|
"force_quit_confirm_msg": {
|
||||||
|
"es": "Esto matará inmediatamente el daemon sin un apagado limpio.\nEsto puede corromper el índice de la blockchain y requerir una resincronización.",
|
||||||
|
"de": "Dies wird den Daemon sofort beenden ohne sauberes Herunterfahren.\nDies kann den Blockchain-Index beschädigen und eine Neusynchronisierung erfordern.",
|
||||||
|
"fr": "Cela tuera immédiatement le daemon sans arrêt propre.\nCela peut corrompre l'index de la blockchain et nécessiter une resynchronisation.",
|
||||||
|
"pt": "Isso matará imediatamente o daemon sem um desligamento limpo.\nIsso pode corromper o índice da blockchain e exigir uma ressincronização.",
|
||||||
|
"ru": "Это немедленно завершит демон без корректного завершения.\nЭто может повредить индекс блокчейна и потребовать повторной синхронизации.",
|
||||||
|
"zh": "这将立即终止守护进程而不进行正常关闭。\n这可能会损坏区块链索引并需要重新同步。",
|
||||||
|
"ja": "クリーンシャットダウンなしでデーモンを即座に終了します。\nブロックチェーンインデックスが破損し、再同期が必要になる可能性があります。",
|
||||||
|
"ko": "정상 종료 없이 데몬을 즉시 종료합니다.\n블록체인 인덱스가 손상되어 재동기화가 필요할 수 있습니다."
|
||||||
|
},
|
||||||
|
"force_quit_confirm_title": {
|
||||||
|
"es": "¿Forzar Salida?", "de": "Sofort beenden?", "fr": "Forcer la fermeture ?",
|
||||||
|
"pt": "Forçar Saída?", "ru": "Принудительный выход?", "zh": "强制退出?",
|
||||||
|
"ja": "強制終了しますか?", "ko": "강제 종료하시겠습니까?"
|
||||||
|
},
|
||||||
|
"force_quit_warning": {
|
||||||
|
"es": "Esto matará inmediatamente el daemon sin un apagado limpio. Puede requerir una resincronización de la blockchain.",
|
||||||
|
"de": "Dies wird den Daemon sofort beenden ohne sauberes Herunterfahren. Kann eine Neusynchronisierung der Blockchain erfordern.",
|
||||||
|
"fr": "Cela tuera immédiatement le daemon sans arrêt propre. Peut nécessiter une resynchronisation de la blockchain.",
|
||||||
|
"pt": "Isso matará imediatamente o daemon sem um desligamento limpo. Pode exigir uma ressincronização da blockchain.",
|
||||||
|
"ru": "Это немедленно завершит демон без корректного завершения. Может потребоваться повторная синхронизация блокчейна.",
|
||||||
|
"zh": "这将立即终止守护进程而不进行正常关闭。可能需要重新同步区块链。",
|
||||||
|
"ja": "クリーンシャットダウンなしでデーモンを即座に終了します。ブロックチェーンの再同期が必要になる場合があります。",
|
||||||
|
"ko": "정상 종료 없이 데몬을 즉시 종료합니다. 블록체인 재동기화가 필요할 수 있습니다."
|
||||||
|
},
|
||||||
|
"force_quit_yes": {
|
||||||
|
"es": "Forzar Salida", "de": "Sofort beenden", "fr": "Forcer la fermeture",
|
||||||
|
"pt": "Forçar Saída", "ru": "Принудительный выход", "zh": "强制退出",
|
||||||
|
"ja": "強制終了", "ko": "강제 종료"
|
||||||
|
},
|
||||||
|
"generating": {
|
||||||
|
"es": "Generando", "de": "Wird generiert", "fr": "Génération",
|
||||||
|
"pt": "Gerando", "ru": "Генерация", "zh": "正在生成",
|
||||||
|
"ja": "生成中", "ko": "생성 중"
|
||||||
|
},
|
||||||
|
"hidden_tag": {
|
||||||
|
"es": " (oculto)", "de": " (versteckt)", "fr": " (masqué)",
|
||||||
|
"pt": " (oculto)", "ru": " (скрыт)", "zh": " (已隐藏)",
|
||||||
|
"ja": " (非表示)", "ko": " (숨김)"
|
||||||
|
},
|
||||||
|
"incorrect_passphrase": {
|
||||||
|
"es": "Contraseña incorrecta", "de": "Falsches Passwort", "fr": "Mot de passe incorrect",
|
||||||
|
"pt": "Senha incorreta", "ru": "Неверный пароль", "zh": "密码错误",
|
||||||
|
"ja": "パスフレーズが正しくありません", "ko": "잘못된 암호"
|
||||||
|
},
|
||||||
|
"incorrect_pin": {
|
||||||
|
"es": "PIN incorrecto", "de": "Falsche PIN", "fr": "PIN incorrect",
|
||||||
|
"pt": "PIN incorreto", "ru": "Неверный PIN", "zh": "PIN 错误",
|
||||||
|
"ja": "PINが正しくありません", "ko": "잘못된 PIN"
|
||||||
|
},
|
||||||
|
"insufficient_funds": {
|
||||||
|
"es": "Fondos insuficientes para este monto más la comisión.", "de": "Unzureichendes Guthaben für diesen Betrag plus Gebühr.",
|
||||||
|
"fr": "Fonds insuffisants pour ce montant plus les frais.", "pt": "Fundos insuficientes para este valor mais taxa.",
|
||||||
|
"ru": "Недостаточно средств для этой суммы плюс комиссия.", "zh": "余额不足以支付此金额加手续费。",
|
||||||
|
"ja": "この金額と手数料に対して残高が不足しています。", "ko": "이 금액과 수수료를 위한 잔액이 부족합니다."
|
||||||
|
},
|
||||||
|
"label_placeholder": {
|
||||||
|
"es": "ej. Ahorros, Minería...", "de": "z.B. Ersparnisse, Mining...", "fr": "ex. Épargne, Minage...",
|
||||||
|
"pt": "ex. Poupança, Mineração...", "ru": "напр. Накопления, Майнинг...", "zh": "例如 储蓄、挖矿...",
|
||||||
|
"ja": "例: 貯金、マイニング...", "ko": "예: 저축, 채굴..."
|
||||||
|
},
|
||||||
|
"mining_benchmark_cancel": {
|
||||||
|
"es": "Cancelar benchmark", "de": "Benchmark abbrechen", "fr": "Annuler le benchmark",
|
||||||
|
"pt": "Cancelar benchmark", "ru": "Отменить тест", "zh": "取消基准测试",
|
||||||
|
"ja": "ベンチマークをキャンセル", "ko": "벤치마크 취소"
|
||||||
|
},
|
||||||
|
"mining_benchmark_cooling": {
|
||||||
|
"es": "Enfriando", "de": "Abkühlen", "fr": "Refroidissement",
|
||||||
|
"pt": "Resfriando", "ru": "Охлаждение", "zh": "冷却中",
|
||||||
|
"ja": "クーリング", "ko": "쿨링"
|
||||||
|
},
|
||||||
|
"mining_benchmark_dismiss": {
|
||||||
|
"es": "Cerrar", "de": "Schließen", "fr": "Fermer",
|
||||||
|
"pt": "Fechar", "ru": "Закрыть", "zh": "关闭",
|
||||||
|
"ja": "閉じる", "ko": "닫기"
|
||||||
|
},
|
||||||
|
"mining_benchmark_result": {
|
||||||
|
"es": "Óptimo", "de": "Optimal", "fr": "Optimal",
|
||||||
|
"pt": "Ótimo", "ru": "Оптимально", "zh": "最佳",
|
||||||
|
"ja": "最適", "ko": "최적"
|
||||||
|
},
|
||||||
|
"mining_benchmark_stabilizing": {
|
||||||
|
"es": "Estabilizando", "de": "Stabilisierung", "fr": "Stabilisation",
|
||||||
|
"pt": "Estabilizando", "ru": "Стабилизация", "zh": "稳定中",
|
||||||
|
"ja": "安定化中", "ko": "안정화 중"
|
||||||
|
},
|
||||||
|
"mining_benchmark_testing": {
|
||||||
|
"es": "Probando", "de": "Testen", "fr": "Test",
|
||||||
|
"pt": "Testando", "ru": "Тестирование", "zh": "测试中",
|
||||||
|
"ja": "テスト中", "ko": "테스트 중"
|
||||||
|
},
|
||||||
|
"mining_benchmark_tooltip": {
|
||||||
|
"es": "Encontrar el número óptimo de hilos para esta CPU",
|
||||||
|
"de": "Optimale Thread-Anzahl für diese CPU finden",
|
||||||
|
"fr": "Trouver le nombre optimal de threads pour ce CPU",
|
||||||
|
"pt": "Encontrar o número ideal de threads para esta CPU",
|
||||||
|
"ru": "Найти оптимальное количество потоков для этого процессора",
|
||||||
|
"zh": "为此 CPU 找到最佳线程数",
|
||||||
|
"ja": "このCPUに最適なスレッド数を検出",
|
||||||
|
"ko": "이 CPU에 최적의 스레드 수 찾기"
|
||||||
|
},
|
||||||
|
"mining_generate_z_address_hint": {
|
||||||
|
"es": "Genere una dirección Z en la pestaña Recibir para usarla como dirección de pago",
|
||||||
|
"de": "Generieren Sie eine Z-Adresse im Empfangen-Tab als Auszahlungsadresse",
|
||||||
|
"fr": "Générez une adresse Z dans l'onglet Recevoir pour l'utiliser comme adresse de paiement",
|
||||||
|
"pt": "Gere um endereço Z na aba Receber para usar como endereço de pagamento",
|
||||||
|
"ru": "Создайте Z-адрес на вкладке «Получить» для использования в качестве адреса выплат",
|
||||||
|
"zh": "在接收标签页生成一个 Z 地址作为支付地址",
|
||||||
|
"ja": "受信タブでZアドレスを生成して支払いアドレスとして使用してください",
|
||||||
|
"ko": "수신 탭에서 Z 주소를 생성하여 지급 주소로 사용하세요"
|
||||||
|
},
|
||||||
|
"mining_idle_gpu_off_tooltip": {
|
||||||
|
"es": "Sin restricción: ACTIVADO\nSolo la entrada de teclado/ratón determina el estado inactivo\nClic para activar detección de GPU",
|
||||||
|
"de": "Uneingeschränkt: EIN\nNur Tastatur-/Mauseingabe bestimmt den Leerlauf\nKlicken für GPU-bewusste Erkennung",
|
||||||
|
"fr": "Sans restriction : ACTIVÉ\nSeule l'entrée clavier/souris détermine l'inactivité\nCliquez pour activer la détection GPU",
|
||||||
|
"pt": "Sem restrição: ATIVADO\nApenas entrada de teclado/mouse determina o estado ocioso\nClique para ativar detecção de GPU",
|
||||||
|
"ru": "Без ограничений: ВКЛ\nТолько ввод с клавиатуры/мыши определяет состояние простоя\nНажмите для GPU-контроля",
|
||||||
|
"zh": "无限制:开启\n仅键盘/鼠标输入决定空闲状态\n点击启用GPU感知检测",
|
||||||
|
"ja": "制限なし: オン\nキーボード/マウス入力のみがアイドル状態を決定\nGPU検出を有効にするにはクリック",
|
||||||
|
"ko": "무제한: 켜짐\n키보드/마우스 입력만 유휴 상태를 결정\nGPU 감지를 활성화하려면 클릭"
|
||||||
|
},
|
||||||
|
"mining_idle_gpu_on_tooltip": {
|
||||||
|
"es": "GPU-consciente: ACTIVADO\nLa actividad de GPU (video, juegos) previene la minería inactiva\nClic para modo sin restricción",
|
||||||
|
"de": "GPU-bewusst: EIN\nGPU-Aktivität (Video, Spiele) verhindert Leerlauf-Mining\nKlicken für uneingeschränkten Modus",
|
||||||
|
"fr": "GPU-conscient : ACTIVÉ\nL'activité GPU (vidéo, jeux) empêche le minage inactif\nCliquez pour le mode sans restriction",
|
||||||
|
"pt": "GPU-consciente: ATIVADO\nAtividade de GPU (vídeo, jogos) impede mineração ociosa\nClique para modo sem restrição",
|
||||||
|
"ru": "GPU-контроль: ВКЛ\nАктивность GPU (видео, игры) предотвращает майнинг в простое\nНажмите для режима без ограничений",
|
||||||
|
"zh": "GPU感知:开启\nGPU活动(视频、游戏)阻止空闲挖矿\n点击切换到无限制模式",
|
||||||
|
"ja": "GPU対応: オン\nGPUアクティビティ(動画、ゲーム)がアイドルマイニングを防止\n制限なしモードに切り替えるにはクリック",
|
||||||
|
"ko": "GPU 감지: 켜짐\nGPU 활동(비디오, 게임)이 유휴 채굴을 방지\n무제한 모드로 전환하려면 클릭"
|
||||||
|
},
|
||||||
|
"mining_idle_scale_off_tooltip": {
|
||||||
|
"es": "Modo inicio/parada: ACTIVADO\nClic para cambiar al modo de escala de hilos",
|
||||||
|
"de": "Start/Stopp-Modus: EIN\nKlicken zum Wechsel auf Thread-Skalierung",
|
||||||
|
"fr": "Mode démarrage/arrêt : ACTIVÉ\nCliquez pour passer au mode mise à l'échelle des threads",
|
||||||
|
"pt": "Modo iniciar/parar: ATIVADO\nClique para mudar para modo de escala de threads",
|
||||||
|
"ru": "Режим старт/стоп: ВКЛ\nНажмите для переключения на масштабирование потоков",
|
||||||
|
"zh": "启动/停止模式:开启\n点击切换到线程缩放模式",
|
||||||
|
"ja": "開始/停止モード: オン\nスレッドスケーリングモードに切り替えるにはクリック",
|
||||||
|
"ko": "시작/중지 모드: 켜짐\n스레드 스케일링 모드로 전환하려면 클릭"
|
||||||
|
},
|
||||||
|
"mining_idle_scale_on_tooltip": {
|
||||||
|
"es": "Escala de hilos: ACTIVADO\nClic para cambiar al modo de inicio/parada",
|
||||||
|
"de": "Thread-Skalierung: EIN\nKlicken zum Wechsel auf Start/Stopp-Modus",
|
||||||
|
"fr": "Mise à l'échelle des threads : ACTIVÉ\nCliquez pour passer au mode démarrage/arrêt",
|
||||||
|
"pt": "Escala de threads: ATIVADO\nClique para mudar para modo iniciar/parar",
|
||||||
|
"ru": "Масштабирование потоков: ВКЛ\nНажмите для переключения на режим старт/стоп",
|
||||||
|
"zh": "线程缩放:开启\n点击切换到启动/停止模式",
|
||||||
|
"ja": "スレッドスケーリング: オン\n開始/停止モードに切り替えるにはクリック",
|
||||||
|
"ko": "스레드 스케일링: 켜짐\n시작/중지 모드로 전환하려면 클릭"
|
||||||
|
},
|
||||||
|
"mining_idle_threads_active_tooltip": {
|
||||||
|
"es": "Hilos cuando el usuario está activo", "de": "Threads bei Benutzeraktivität",
|
||||||
|
"fr": "Threads quand l'utilisateur est actif", "pt": "Threads quando o usuário está ativo",
|
||||||
|
"ru": "Потоки при активности пользователя", "zh": "用户活跃时的线程数",
|
||||||
|
"ja": "ユーザーアクティブ時のスレッド数", "ko": "사용자 활성 시 스레드"
|
||||||
|
},
|
||||||
|
"mining_idle_threads_idle_tooltip": {
|
||||||
|
"es": "Hilos cuando el sistema está inactivo", "de": "Threads im Leerlauf",
|
||||||
|
"fr": "Threads quand le système est inactif", "pt": "Threads quando o sistema está ocioso",
|
||||||
|
"ru": "Потоки при простое системы", "zh": "系统空闲时的线程数",
|
||||||
|
"ja": "システムアイドル時のスレッド数", "ko": "시스템 유휴 시 스레드"
|
||||||
|
},
|
||||||
|
"no_addresses_yet": {
|
||||||
|
"es": "Aún no hay direcciones", "de": "Noch keine Adressen", "fr": "Pas encore d'adresses",
|
||||||
|
"pt": "Nenhum endereço ainda", "ru": "Пока нет адресов", "zh": "暂无地址",
|
||||||
|
"ja": "アドレスがまだありません", "ko": "아직 주소가 없습니다"
|
||||||
|
},
|
||||||
|
"pin_not_set": {
|
||||||
|
"es": "PIN no configurado. Use la contraseña para desbloquear.",
|
||||||
|
"de": "PIN nicht gesetzt. Verwenden Sie das Passwort zum Entsperren.",
|
||||||
|
"fr": "PIN non défini. Utilisez le mot de passe pour déverrouiller.",
|
||||||
|
"pt": "PIN não definido. Use a senha para desbloquear.",
|
||||||
|
"ru": "PIN не установлен. Используйте пароль для разблокировки.",
|
||||||
|
"zh": "未设置 PIN。使用密码解锁。",
|
||||||
|
"ja": "PINが設定されていません。パスフレーズで解除してください。",
|
||||||
|
"ko": "PIN이 설정되지 않았습니다. 암호를 사용하여 잠금 해제하세요."
|
||||||
|
},
|
||||||
|
"recipient_balance": {
|
||||||
|
"es": "Destinatario: %.8f → %.8f DRGX", "de": "Empfänger: %.8f → %.8f DRGX",
|
||||||
|
"fr": "Destinataire : %.8f → %.8f DRGX", "pt": "Destinatário: %.8f → %.8f DRGX",
|
||||||
|
"ru": "Получатель: %.8f → %.8f DRGX", "zh": "接收方: %.8f → %.8f DRGX",
|
||||||
|
"ja": "受取人: %.8f → %.8f DRGX", "ko": "수신자: %.8f → %.8f DRGX"
|
||||||
|
},
|
||||||
|
"reduce_motion": {
|
||||||
|
"es": "Reducir Movimiento", "de": "Bewegung reduzieren", "fr": "Réduire les animations",
|
||||||
|
"pt": "Reduzir Movimento", "ru": "Уменьшить анимацию", "zh": "减少动画",
|
||||||
|
"ja": "モーションを減らす", "ko": "모션 줄이기"
|
||||||
|
},
|
||||||
|
"restarting_after_encryption": {
|
||||||
|
"es": "Reiniciando daemon después del cifrado...", "de": "Daemon wird nach Verschlüsselung neu gestartet...",
|
||||||
|
"fr": "Redémarrage du daemon après chiffrement...", "pt": "Reiniciando daemon após criptografia...",
|
||||||
|
"ru": "Перезапуск демона после шифрования...", "zh": "加密后重启守护进程...",
|
||||||
|
"ja": "暗号化後にデーモンを再起動中...", "ko": "암호화 후 데몬 재시작 중..."
|
||||||
|
},
|
||||||
|
"result_preview": {
|
||||||
|
"es": "Vista previa del resultado", "de": "Ergebnisvorschau", "fr": "Aperçu du résultat",
|
||||||
|
"pt": "Pré-visualização do resultado", "ru": "Предпросмотр результата", "zh": "结果预览",
|
||||||
|
"ja": "結果プレビュー", "ko": "결과 미리보기"
|
||||||
|
},
|
||||||
|
"retry": {
|
||||||
|
"es": "Reintentar", "de": "Wiederholen", "fr": "Réessayer",
|
||||||
|
"pt": "Tentar novamente", "ru": "Повторить", "zh": "重试",
|
||||||
|
"ja": "再試行", "ko": "재시도"
|
||||||
|
},
|
||||||
|
"sender_balance": {
|
||||||
|
"es": "Remitente: %.8f → %.8f DRGX", "de": "Absender: %.8f → %.8f DRGX",
|
||||||
|
"fr": "Expéditeur : %.8f → %.8f DRGX", "pt": "Remetente: %.8f → %.8f DRGX",
|
||||||
|
"ru": "Отправитель: %.8f → %.8f DRGX", "zh": "发送方: %.8f → %.8f DRGX",
|
||||||
|
"ja": "送信者: %.8f → %.8f DRGX", "ko": "발신자: %.8f → %.8f DRGX"
|
||||||
|
},
|
||||||
|
"sends_full_balance_warning": {
|
||||||
|
"es": "Esto envía el saldo completo. La dirección de envío tendrá saldo cero.",
|
||||||
|
"de": "Dies sendet das gesamte Guthaben. Die Sendeadresse wird ein Nullguthaben haben.",
|
||||||
|
"fr": "Cela envoie le solde complet. L'adresse d'envoi aura un solde nul.",
|
||||||
|
"pt": "Isso envia o saldo total. O endereço de envio terá saldo zero.",
|
||||||
|
"ru": "Это отправит весь баланс. Адрес отправителя останется с нулевым балансом.",
|
||||||
|
"zh": "这将发送全部余额。发送地址将变为零余额。",
|
||||||
|
"ja": "全残高を送信します。送信アドレスの残高はゼロになります。",
|
||||||
|
"ko": "전체 잔액을 전송합니다. 보내는 주소의 잔액이 0이 됩니다."
|
||||||
|
},
|
||||||
|
"set_label": {
|
||||||
|
"es": "Establecer Etiqueta...", "de": "Label setzen...", "fr": "Définir le libellé...",
|
||||||
|
"pt": "Definir Rótulo...", "ru": "Установить метку...", "zh": "设置标签...",
|
||||||
|
"ja": "ラベルを設定...", "ko": "라벨 설정..."
|
||||||
|
},
|
||||||
|
"shielded_address": {
|
||||||
|
"es": "Dirección Protegida", "de": "Geschirmte Adresse", "fr": "Adresse protégée",
|
||||||
|
"pt": "Endereço Blindado", "ru": "Экранированный адрес", "zh": "隐蔽地址",
|
||||||
|
"ja": "シールドアドレス", "ko": "보호 주소"
|
||||||
|
},
|
||||||
|
"shielding_notice": {
|
||||||
|
"es": "Nota: Esto blindará fondos de una dirección transparente (T) a una dirección privada (Z).",
|
||||||
|
"de": "Hinweis: Dies wird Gelder von einer transparenten (T) Adresse auf eine private (Z) Adresse schirmen.",
|
||||||
|
"fr": "Note : Cela blindera des fonds d'une adresse transparente (T) vers une adresse privée (Z).",
|
||||||
|
"pt": "Nota: Isso blindará fundos de um endereço transparente (T) para um endereço privado (Z).",
|
||||||
|
"ru": "Примечание: это переведёт средства с прозрачного (T) адреса на приватный (Z) адрес.",
|
||||||
|
"zh": "注意:这将把资金从透明 (T) 地址转移到隐私 (Z) 地址。",
|
||||||
|
"ja": "注意:トランスペアレント (T) アドレスからプライベート (Z) アドレスへ資金をシールドします。",
|
||||||
|
"ko": "참고: 투명 (T) 주소에서 프라이빗 (Z) 주소로 자금을 실딩합니다."
|
||||||
|
},
|
||||||
|
"showing_x_of_y": {
|
||||||
|
"es": "Mostrando %d de %d direcciones", "de": "%d von %d Adressen angezeigt",
|
||||||
|
"fr": "Affichage de %d sur %d adresses", "pt": "Mostrando %d de %d endereços",
|
||||||
|
"ru": "Показано %d из %d адресов", "zh": "显示 %d / %d 个地址",
|
||||||
|
"ja": "%d / %d アドレスを表示", "ko": "%d / %d 주소 표시"
|
||||||
|
},
|
||||||
|
"t_address": {
|
||||||
|
"es": "Dirección T", "de": "T-Adresse", "fr": "Adresse T",
|
||||||
|
"pt": "Endereço T", "ru": "T-адрес", "zh": "T 地址",
|
||||||
|
"ja": "Tアドレス", "ko": "T 주소"
|
||||||
|
},
|
||||||
|
"theme_language": {
|
||||||
|
"es": "TEMA E IDIOMA", "de": "THEMA & SPRACHE", "fr": "THÈME & LANGUE",
|
||||||
|
"pt": "TEMA E IDIOMA", "ru": "ТЕМА И ЯЗЫК", "zh": "主题与语言",
|
||||||
|
"ja": "テーマと言語", "ko": "테마 및 언어"
|
||||||
|
},
|
||||||
|
"tools_actions": {
|
||||||
|
"es": "Herramientas y Acciones...", "de": "Werkzeuge & Aktionen...", "fr": "Outils & Actions...",
|
||||||
|
"pt": "Ferramentas e Ações...", "ru": "Инструменты и действия...", "zh": "工具与操作...",
|
||||||
|
"ja": "ツールとアクション...", "ko": "도구 및 작업..."
|
||||||
|
},
|
||||||
|
"transfer_failed": {
|
||||||
|
"es": "Transferencia Fallida", "de": "Überweisung fehlgeschlagen", "fr": "Échec du transfert",
|
||||||
|
"pt": "Transferência Falhou", "ru": "Ошибка перевода", "zh": "转账失败",
|
||||||
|
"ja": "送金失敗", "ko": "이체 실패"
|
||||||
|
},
|
||||||
|
"transfer_funds": {
|
||||||
|
"es": "Transferir Fondos", "de": "Geld überweisen", "fr": "Transférer des fonds",
|
||||||
|
"pt": "Transferir Fundos", "ru": "Перевести средства", "zh": "转账",
|
||||||
|
"ja": "資金を送金", "ko": "자금 이체"
|
||||||
|
},
|
||||||
|
"transfer_sent": {
|
||||||
|
"es": "Transferencia Enviada", "de": "Überweisung gesendet", "fr": "Transfert envoyé",
|
||||||
|
"pt": "Transferência Enviada", "ru": "Перевод отправлен", "zh": "转账已发送",
|
||||||
|
"ja": "送金完了", "ko": "이체 전송됨"
|
||||||
|
},
|
||||||
|
"transfer_sent_desc": {
|
||||||
|
"es": "Su transferencia ha sido enviada a la red.",
|
||||||
|
"de": "Ihre Überweisung wurde an das Netzwerk gesendet.",
|
||||||
|
"fr": "Votre transfert a été soumis au réseau.",
|
||||||
|
"pt": "Sua transferência foi enviada à rede.",
|
||||||
|
"ru": "Ваш перевод отправлен в сеть.",
|
||||||
|
"zh": "您的转账已提交到网络。",
|
||||||
|
"ja": "送金がネットワークに送信されました。",
|
||||||
|
"ko": "이체가 네트워크에 제출되었습니다."
|
||||||
|
},
|
||||||
|
"transfer_to": {
|
||||||
|
"es": "Transferir a:", "de": "Überweisen an:", "fr": "Transférer à :",
|
||||||
|
"pt": "Transferir para:", "ru": "Перевести на:", "zh": "转账至:",
|
||||||
|
"ja": "送金先:", "ko": "이체 대상:"
|
||||||
|
},
|
||||||
|
"transparent_address": {
|
||||||
|
"es": "Dirección Transparente", "de": "Transparente Adresse", "fr": "Adresse transparente",
|
||||||
|
"pt": "Endereço Transparente", "ru": "Прозрачный адрес", "zh": "透明地址",
|
||||||
|
"ja": "トランスペアレントアドレス", "ko": "투명 주소"
|
||||||
|
},
|
||||||
|
"tt_delete_blockchain": {
|
||||||
|
"es": "Eliminar todos los datos de la blockchain e iniciar una nueva sincronización. Se preservan wallet.dat y la configuración.",
|
||||||
|
"de": "Alle Blockchain-Daten löschen und neu synchronisieren. wallet.dat und Konfiguration bleiben erhalten.",
|
||||||
|
"fr": "Supprimer toutes les données de la blockchain et démarrer une nouvelle synchronisation. wallet.dat et la configuration sont préservés.",
|
||||||
|
"pt": "Excluir todos os dados da blockchain e iniciar uma nova sincronização. wallet.dat e configuração são preservados.",
|
||||||
|
"ru": "Удалить все данные блокчейна и начать синхронизацию заново. wallet.dat и конфигурация сохраняются.",
|
||||||
|
"zh": "删除所有区块链数据并重新同步。wallet.dat 和配置将被保留。",
|
||||||
|
"ja": "すべてのブロックチェーンデータを削除して新規同期を開始します。wallet.dat と設定は保持されます。",
|
||||||
|
"ko": "모든 블록체인 데이터를 삭제하고 새로 동기화합니다. wallet.dat 및 설정은 보존됩니다."
|
||||||
|
},
|
||||||
|
"tt_download_bootstrap": {
|
||||||
|
"es": "Descargar bootstrap de blockchain para acelerar la sincronización\nLos datos de bloques existentes serán reemplazados",
|
||||||
|
"de": "Blockchain-Bootstrap herunterladen, um die Synchronisierung zu beschleunigen\nVorhandene Blockdaten werden ersetzt",
|
||||||
|
"fr": "Télécharger le bootstrap blockchain pour accélérer la synchronisation\nLes données de blocs existantes seront remplacées",
|
||||||
|
"pt": "Baixar bootstrap da blockchain para acelerar a sincronização\nDados de blocos existentes serão substituídos",
|
||||||
|
"ru": "Скачать бутстрап блокчейна для ускорения синхронизации\nСуществующие данные блоков будут заменены",
|
||||||
|
"zh": "下载区块链引导程序以加速同步\n现有区块数据将被替换",
|
||||||
|
"ja": "ブロックチェーンブートストラップをダウンロードして同期を高速化\n既存のブロックデータは置き換えられます",
|
||||||
|
"ko": "블록체인 부트스트랩을 다운로드하여 동기화 가속\n기존 블록 데이터가 교체됩니다"
|
||||||
|
},
|
||||||
|
"tt_reduce_motion": {
|
||||||
|
"es": "Desactivar transiciones animadas y lerp de saldo para accesibilidad",
|
||||||
|
"de": "Animierte Übergänge und Saldo-Lerp für Barrierefreiheit deaktivieren",
|
||||||
|
"fr": "Désactiver les transitions animées et le lerp de solde pour l'accessibilité",
|
||||||
|
"pt": "Desativar transições animadas e lerp de saldo para acessibilidade",
|
||||||
|
"ru": "Отключить анимированные переходы и плавное изменение баланса для доступности",
|
||||||
|
"zh": "禁用动画过渡和余额渐变以提高无障碍性",
|
||||||
|
"ja": "アクセシビリティのためにアニメーション遷移と残高補間を無効にする",
|
||||||
|
"ko": "접근성을 위해 애니메이션 전환 및 잔액 보간 비활성화"
|
||||||
|
},
|
||||||
|
"wizard_daemon_start_failed": {
|
||||||
|
"es": "Error al iniciar el daemon — se reintentará automáticamente",
|
||||||
|
"de": "Daemon-Start fehlgeschlagen — wird automatisch wiederholt",
|
||||||
|
"fr": "Échec du démarrage du daemon — sera réessayé automatiquement",
|
||||||
|
"pt": "Falha ao iniciar o daemon — será tentado novamente automaticamente",
|
||||||
|
"ru": "Не удалось запустить демон — будет повторено автоматически",
|
||||||
|
"zh": "启动守护进程失败 — 将自动重试",
|
||||||
|
"ja": "デーモンの起動に失敗しました — 自動的に再試行されます",
|
||||||
|
"ko": "데몬 시작 실패 — 자동으로 재시도됩니다"
|
||||||
|
},
|
||||||
|
"z_address": {
|
||||||
|
"es": "Dirección Z", "de": "Z-Adresse", "fr": "Adresse Z",
|
||||||
|
"pt": "Endereço Z", "ru": "Z-адрес", "zh": "Z 地址",
|
||||||
|
"ja": "Zアドレス", "ko": "Z 주소"
|
||||||
|
},
|
||||||
|
|
||||||
|
# ── Status bar strings ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
"sb_warming_up": {
|
||||||
|
"es": "Calentando...", "de": "Aufwärmen...", "fr": "Démarrage...",
|
||||||
|
"pt": "Aquecendo...", "ru": "Прогрев...", "zh": "正在预热...",
|
||||||
|
"ja": "ウォームアップ中...", "ko": "워밍업 중..."
|
||||||
|
},
|
||||||
|
"sb_block": {
|
||||||
|
"es": "Bloque: %d", "de": "Block: %d", "fr": "Bloc : %d",
|
||||||
|
"pt": "Bloco: %d", "ru": "Блок: %d", "zh": "区块: %d",
|
||||||
|
"ja": "ブロック: %d", "ko": "블록: %d"
|
||||||
|
},
|
||||||
|
"sb_peers": {
|
||||||
|
"es": "Pares: %zu", "de": "Peers: %zu", "fr": "Pairs : %zu",
|
||||||
|
"pt": "Pares: %zu", "ru": "Пиры: %zu", "zh": "节点: %zu",
|
||||||
|
"ja": "ピア: %zu", "ko": "피어: %zu"
|
||||||
|
},
|
||||||
|
"sb_net_ghs": {
|
||||||
|
"es": "Red: %.2f GH/s", "de": "Netz: %.2f GH/s", "fr": "Rés: %.2f GH/s",
|
||||||
|
"pt": "Rede: %.2f GH/s", "ru": "Сеть: %.2f GH/s", "zh": "网络: %.2f GH/s",
|
||||||
|
"ja": "ネット: %.2f GH/s", "ko": "네트: %.2f GH/s"
|
||||||
|
},
|
||||||
|
"sb_net_mhs": {
|
||||||
|
"es": "Red: %.2f MH/s", "de": "Netz: %.2f MH/s", "fr": "Rés: %.2f MH/s",
|
||||||
|
"pt": "Rede: %.2f MH/s", "ru": "Сеть: %.2f MH/s", "zh": "网络: %.2f MH/s",
|
||||||
|
"ja": "ネット: %.2f MH/s", "ko": "네트: %.2f MH/s"
|
||||||
|
},
|
||||||
|
"sb_net_khs": {
|
||||||
|
"es": "Red: %.2f KH/s", "de": "Netz: %.2f KH/s", "fr": "Rés: %.2f KH/s",
|
||||||
|
"pt": "Rede: %.2f KH/s", "ru": "Сеть: %.2f KH/s", "zh": "网络: %.2f KH/s",
|
||||||
|
"ja": "ネット: %.2f KH/s", "ko": "네트: %.2f KH/s"
|
||||||
|
},
|
||||||
|
"sb_net_hs": {
|
||||||
|
"es": "Red: %.1f H/s", "de": "Netz: %.1f H/s", "fr": "Rés: %.1f H/s",
|
||||||
|
"pt": "Rede: %.1f H/s", "ru": "Сеть: %.1f H/s", "zh": "网络: %.1f H/s",
|
||||||
|
"ja": "ネット: %.1f H/s", "ko": "네트: %.1f H/s"
|
||||||
|
},
|
||||||
|
"sb_mining_hs": {
|
||||||
|
"es": "%.1f H/s", "de": "%.1f H/s", "fr": "%.1f H/s",
|
||||||
|
"pt": "%.1f H/s", "ru": "%.1f H/s", "zh": "%.1f H/s",
|
||||||
|
"ja": "%.1f H/s", "ko": "%.1f H/s"
|
||||||
|
},
|
||||||
|
"sb_syncing_eta": {
|
||||||
|
"es": "Sincronizando %.1f%% (%d restantes, %.0f blk/s, ~%s)",
|
||||||
|
"de": "Synchronisierung %.1f%% (%d übrig, %.0f Blk/s, ~%s)",
|
||||||
|
"fr": "Synchronisation %.1f%% (%d restants, %.0f blk/s, ~%s)",
|
||||||
|
"pt": "Sincronizando %.1f%% (%d restantes, %.0f blk/s, ~%s)",
|
||||||
|
"ru": "Синхронизация %.1f%% (%d осталось, %.0f блк/с, ~%s)",
|
||||||
|
"zh": "同步中 %.1f%% (剩余 %d, %.0f 块/秒, ~%s)",
|
||||||
|
"ja": "同期中 %.1f%% (残り %d, %.0f ブロック/秒, ~%s)",
|
||||||
|
"ko": "동기화 %.1f%% (%d 남음, %.0f 블록/초, ~%s)"
|
||||||
|
},
|
||||||
|
"sb_syncing_basic": {
|
||||||
|
"es": "Sincronizando %.1f%% (%d restantes)",
|
||||||
|
"de": "Synchronisierung %.1f%% (%d übrig)",
|
||||||
|
"fr": "Synchronisation %.1f%% (%d restants)",
|
||||||
|
"pt": "Sincronizando %.1f%% (%d restantes)",
|
||||||
|
"ru": "Синхронизация %.1f%% (%d осталось)",
|
||||||
|
"zh": "同步中 %.1f%% (剩余 %d)",
|
||||||
|
"ja": "同期中 %.1f%% (残り %d)",
|
||||||
|
"ko": "동기화 %.1f%% (%d 남음)"
|
||||||
|
},
|
||||||
|
"sb_rescanning_pct": {
|
||||||
|
"es": "Reescaneando %.0f%%", "de": "Neuscan %.0f%%", "fr": "Rescan %.0f%%",
|
||||||
|
"pt": "Reescaneando %.0f%%", "ru": "Пересканирование %.0f%%", "zh": "重新扫描 %.0f%%",
|
||||||
|
"ja": "再スキャン %.0f%%", "ko": "재스캔 %.0f%%"
|
||||||
|
},
|
||||||
|
"sb_rescanning": {
|
||||||
|
"es": "Reescaneando", "de": "Neuscan", "fr": "Rescan",
|
||||||
|
"pt": "Reescaneando", "ru": "Пересканирование", "zh": "重新扫描",
|
||||||
|
"ja": "再スキャン", "ko": "재스캔"
|
||||||
|
},
|
||||||
|
"sb_importing_keys": {
|
||||||
|
"es": "Importando claves", "de": "Schlüssel importieren", "fr": "Importation des clés",
|
||||||
|
"pt": "Importando chaves", "ru": "Импорт ключей", "zh": "正在导入密钥",
|
||||||
|
"ja": "鍵をインポート中", "ko": "키 가져오기 중"
|
||||||
|
},
|
||||||
|
"sb_daemon_not_found": {
|
||||||
|
"es": "Daemon no encontrado", "de": "Daemon nicht gefunden", "fr": "Daemon introuvable",
|
||||||
|
"pt": "Daemon não encontrado", "ru": "Демон не найден", "zh": "未找到守护进程",
|
||||||
|
"ja": "デーモンが見つかりません", "ko": "데몬을 찾을 수 없음"
|
||||||
|
},
|
||||||
|
"sb_loading_config": {
|
||||||
|
"es": "Cargando configuración...", "de": "Konfiguration laden...", "fr": "Chargement de la configuration...",
|
||||||
|
"pt": "Carregando configuração...", "ru": "Загрузка конфигурации...", "zh": "正在加载配置...",
|
||||||
|
"ja": "設定を読み込み中...", "ko": "설정 불러오는 중..."
|
||||||
|
},
|
||||||
|
"sb_waiting_config": {
|
||||||
|
"es": "Esperando configuración del daemon...", "de": "Warten auf Daemon-Konfiguration...", "fr": "En attente de la configuration du daemon...",
|
||||||
|
"pt": "Aguardando configuração do daemon...", "ru": "Ожидание конфигурации демона...", "zh": "等待守护进程配置...",
|
||||||
|
"ja": "デーモン設定を待機中...", "ko": "데몬 설정 대기 중..."
|
||||||
|
},
|
||||||
|
"sb_no_conf": {
|
||||||
|
"es": "DRAGONX.conf no encontrado", "de": "DRAGONX.conf nicht gefunden", "fr": "DRAGONX.conf introuvable",
|
||||||
|
"pt": "DRAGONX.conf não encontrado", "ru": "DRAGONX.conf не найден", "zh": "未找到 DRAGONX.conf",
|
||||||
|
"ja": "DRAGONX.conf が見つかりません", "ko": "DRAGONX.conf를 찾을 수 없음"
|
||||||
|
},
|
||||||
|
"sb_starting_daemon": {
|
||||||
|
"es": "Iniciando dragonxd...", "de": "dragonxd wird gestartet...", "fr": "Démarrage de dragonxd...",
|
||||||
|
"pt": "Iniciando dragonxd...", "ru": "Запуск dragonxd...", "zh": "正在启动 dragonxd...",
|
||||||
|
"ja": "dragonxd を起動中...", "ko": "dragonxd 시작 중..."
|
||||||
|
},
|
||||||
|
"sb_connecting_daemon": {
|
||||||
|
"es": "Conectando a dragonxd...", "de": "Verbindung zu dragonxd...", "fr": "Connexion à dragonxd...",
|
||||||
|
"pt": "Conectando ao dragonxd...", "ru": "Подключение к dragonxd...", "zh": "正在连接 dragonxd...",
|
||||||
|
"ja": "dragonxd に接続中...", "ko": "dragonxd에 연결 중..."
|
||||||
|
},
|
||||||
|
"sb_auth_failed": {
|
||||||
|
"es": "Autenticación fallida — verifique rpcuser/rpcpassword",
|
||||||
|
"de": "Authentifizierung fehlgeschlagen — rpcuser/rpcpassword prüfen",
|
||||||
|
"fr": "Authentification échouée — vérifiez rpcuser/rpcpassword",
|
||||||
|
"pt": "Autenticação falhou — verifique rpcuser/rpcpassword",
|
||||||
|
"ru": "Ошибка авторизации — проверьте rpcuser/rpcpassword",
|
||||||
|
"zh": "认证失败 — 请检查 rpcuser/rpcpassword",
|
||||||
|
"ja": "認証失敗 — rpcuser/rpcpassword を確認してください",
|
||||||
|
"ko": "인증 실패 — rpcuser/rpcpassword를 확인하세요"
|
||||||
|
},
|
||||||
|
"sb_waiting_daemon": {
|
||||||
|
"es": "Esperando a dragonxd...", "de": "Warten auf dragonxd...", "fr": "En attente de dragonxd...",
|
||||||
|
"pt": "Aguardando dragonxd...", "ru": "Ожидание dragonxd...", "zh": "等待 dragonxd 启动...",
|
||||||
|
"ja": "dragonxd を待機中...", "ko": "dragonxd 대기 중..."
|
||||||
|
},
|
||||||
|
"sb_waiting_daemon_err": {
|
||||||
|
"es": "Esperando a dragonxd — %s", "de": "Warten auf dragonxd — %s", "fr": "En attente de dragonxd — %s",
|
||||||
|
"pt": "Aguardando dragonxd — %s", "ru": "Ожидание dragonxd — %s", "zh": "等待 dragonxd — %s",
|
||||||
|
"ja": "dragonxd を待機中 — %s", "ko": "dragonxd 대기 중 — %s"
|
||||||
|
},
|
||||||
|
"sb_connecting_external": {
|
||||||
|
"es": "Conectando a daemon externo...", "de": "Verbindung zu externem Daemon...", "fr": "Connexion au daemon externe...",
|
||||||
|
"pt": "Conectando ao daemon externo...", "ru": "Подключение к внешнему демону...", "zh": "正在连接外部守护进程...",
|
||||||
|
"ja": "外部デーモンに接続中...", "ko": "외부 데몬에 연결 중..."
|
||||||
|
},
|
||||||
|
"sb_connecting_generic": {
|
||||||
|
"es": "Conectando al daemon...", "de": "Verbindung zum Daemon...", "fr": "Connexion au daemon...",
|
||||||
|
"pt": "Conectando ao daemon...", "ru": "Подключение к демону...", "zh": "正在连接守护进程...",
|
||||||
|
"ja": "デーモンに接続中...", "ko": "데몬에 연결 중..."
|
||||||
|
},
|
||||||
|
"sb_connecting_err": {
|
||||||
|
"es": "Conectando al daemon — %s", "de": "Verbindung zum Daemon — %s", "fr": "Connexion au daemon — %s",
|
||||||
|
"pt": "Conectando ao daemon — %s", "ru": "Подключение к демону — %s", "zh": "连接守护进程 — %s",
|
||||||
|
"ja": "デーモンに接続中 — %s", "ko": "데몬 연결 중 — %s"
|
||||||
|
},
|
||||||
|
"sb_daemon_crashed": {
|
||||||
|
"es": "El daemon se bloqueó %d veces", "de": "Daemon ist %d mal abgestürzt", "fr": "Le daemon a planté %d fois",
|
||||||
|
"pt": "O daemon travou %d vezes", "ru": "Демон упал %d раз", "zh": "守护进程崩溃 %d 次",
|
||||||
|
"ja": "デーモンが %d 回クラッシュしました", "ko": "데몬이 %d회 충돌함"
|
||||||
|
},
|
||||||
|
"sb_extracting_sapling": {
|
||||||
|
"es": "Extrayendo parámetros Sapling...", "de": "Sapling-Parameter werden extrahiert...", "fr": "Extraction des paramètres Sapling...",
|
||||||
|
"pt": "Extraindo parâmetros Sapling...", "ru": "Извлечение параметров Sapling...", "zh": "正在提取 Sapling 参数...",
|
||||||
|
"ja": "Sapling パラメータを展開中...", "ko": "Sapling 매개변수 추출 중..."
|
||||||
|
},
|
||||||
|
"sb_sapling_failed": {
|
||||||
|
"es": "Error al extraer parámetros Sapling.", "de": "Sapling-Parameter-Extraktion fehlgeschlagen.", "fr": "Échec de l'extraction des paramètres Sapling.",
|
||||||
|
"pt": "Falha ao extrair parâmetros Sapling.", "ru": "Ошибка извлечения параметров Sapling.", "zh": "提取 Sapling 参数失败。",
|
||||||
|
"ja": "Sapling パラメータの展開に失敗しました。", "ko": "Sapling 매개변수 추출 실패."
|
||||||
|
},
|
||||||
|
"sb_sapling_not_found": {
|
||||||
|
"es": "Parámetros Sapling no encontrados.", "de": "Sapling-Parameter nicht gefunden.", "fr": "Paramètres Sapling introuvables.",
|
||||||
|
"pt": "Parâmetros Sapling não encontrados.", "ru": "Параметры Sapling не найдены.", "zh": "未找到 Sapling 参数。",
|
||||||
|
"ja": "Sapling パラメータが見つかりません。", "ko": "Sapling 매개변수를 찾을 수 없음."
|
||||||
|
},
|
||||||
|
"sb_dragonxd_running": {
|
||||||
|
"es": "dragonxd ejecutándose", "de": "dragonxd läuft", "fr": "dragonxd en cours",
|
||||||
|
"pt": "dragonxd em execução", "ru": "dragonxd запущен", "zh": "dragonxd 运行中",
|
||||||
|
"ja": "dragonxd 実行中", "ko": "dragonxd 실행 중"
|
||||||
|
},
|
||||||
|
"sb_dragonxd_stopping": {
|
||||||
|
"es": "Deteniendo dragonxd...", "de": "dragonxd wird gestoppt...", "fr": "Arrêt de dragonxd...",
|
||||||
|
"pt": "Parando dragonxd...", "ru": "Остановка dragonxd...", "zh": "正在停止 dragonxd...",
|
||||||
|
"ja": "dragonxd を停止中...", "ko": "dragonxd 중지 중..."
|
||||||
|
},
|
||||||
|
"sb_dragonxd_stopped": {
|
||||||
|
"es": "dragonxd detenido", "de": "dragonxd gestoppt", "fr": "dragonxd arrêté",
|
||||||
|
"pt": "dragonxd parado", "ru": "dragonxd остановлен", "zh": "dragonxd 已停止",
|
||||||
|
"ja": "dragonxd 停止", "ko": "dragonxd 중지됨"
|
||||||
|
},
|
||||||
|
"sb_restarting_daemon": {
|
||||||
|
"es": "Reiniciando daemon...", "de": "Daemon wird neu gestartet...", "fr": "Redémarrage du daemon...",
|
||||||
|
"pt": "Reiniciando daemon...", "ru": "Перезапуск демона...", "zh": "正在重启守护进程...",
|
||||||
|
"ja": "デーモンを再起動中...", "ko": "데몬 재시작 중..."
|
||||||
|
},
|
||||||
|
|
||||||
|
# --- Sidebar / section label fixes ---
|
||||||
|
"explorer_section": {
|
||||||
|
"es": "EXPLORADOR", "de": "EXPLORER", "fr": "EXPLORATEUR",
|
||||||
|
"pt": "EXPLORADOR", "ru": "ОБОЗРЕВАТЕЛЬ", "zh": "浏览器",
|
||||||
|
"ja": "エクスプローラー", "ko": "탐색기"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
801
scripts/build-lite-backend-artifact.sh
Executable file
801
scripts/build-lite-backend-artifact.sh
Executable file
@@ -0,0 +1,801 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
ABI_VERSION="sdxl-c-v1"
|
||||||
|
LINK_MODE="imported"
|
||||||
|
BACKEND_DIR="$PROJECT_ROOT/third_party/silentdragonxlite/lib"
|
||||||
|
BACKEND_SOURCE_DIR=""
|
||||||
|
BUILD_BACKEND_DIR=""
|
||||||
|
BACKEND_DEPENDENCY_DIR=""
|
||||||
|
BACKEND_DEPENDENCY_OVERRIDE_REQUESTED=false
|
||||||
|
OUT_DIR="$PROJECT_ROOT/build/lite-backend"
|
||||||
|
PLATFORM=""
|
||||||
|
RUST_TARGET=""
|
||||||
|
CARGO_TARGET_DIR_VALUE="${CARGO_TARGET_DIR:-}"
|
||||||
|
ARTIFACT_PATH=""
|
||||||
|
BUILD_ARTIFACT=true
|
||||||
|
BUILDER="${DRAGONX_LITE_BACKEND_BUILDER:-local}"
|
||||||
|
JOBS="${JOBS:-}"
|
||||||
|
SOURCE_DATE_EPOCH_VALUE="${SOURCE_DATE_EPOCH:-}"
|
||||||
|
REPRODUCIBLE=false
|
||||||
|
SIGNATURE_REQUIRED=false
|
||||||
|
SIGNATURE_FILE=""
|
||||||
|
SIGNATURE_FORMAT=""
|
||||||
|
SIGNATURE_VERIFICATION_TOOL=""
|
||||||
|
SIGNATURE_VERIFICATION_COMMAND=""
|
||||||
|
SIGNATURE_KEY_FINGERPRINT=""
|
||||||
|
SIGNATURE_CERTIFICATE_IDENTITY=""
|
||||||
|
SIGNATURE_CERTIFICATE_ISSUER=""
|
||||||
|
SIGNATURE_TRANSPARENCY_LOG_URL=""
|
||||||
|
SIGNATURE_VERIFIED_SHA256=""
|
||||||
|
SIGNATURE_POLICY_NAME="dragonx-lite-backend-signature-policy-v1"
|
||||||
|
SIGNATURE_POLICY_DEFINED_MANIFEST_VALUE=true
|
||||||
|
SIGNATURE_REQUIRED_MANIFEST_VALUE=false
|
||||||
|
SIGNATURE_METADATA_PROVIDED=false
|
||||||
|
SIGNATURE_VERIFICATION_PERFORMED=false
|
||||||
|
SIGNATURE_VERIFICATION_STATUS="not-provided"
|
||||||
|
SIGNATURE_FILE_SHA256=""
|
||||||
|
|
||||||
|
REQUIRED_SYMBOLS=(
|
||||||
|
litelib_wallet_exists
|
||||||
|
litelib_initialize_new
|
||||||
|
litelib_initialize_new_from_phrase
|
||||||
|
litelib_initialize_existing
|
||||||
|
litelib_execute
|
||||||
|
litelib_rust_free_string
|
||||||
|
litelib_check_server_online
|
||||||
|
litelib_shutdown
|
||||||
|
)
|
||||||
|
|
||||||
|
EXTRA_CARGO_ARGS=()
|
||||||
|
EXTRA_REMAP_PATH_PREFIXES=()
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Build or inventory the SDXL-compatible lite backend artifact.
|
||||||
|
|
||||||
|
Usage: $0 [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--platform linux|windows|macos Artifact platform. Defaults to host platform.
|
||||||
|
--rust-target TRIPLE Cargo target triple for cross builds.
|
||||||
|
--cargo-target-dir PATH Isolated Cargo target directory for clean builds.
|
||||||
|
--backend-dir PATH SilentDragonXLite/lib source directory.
|
||||||
|
--silentdragonxlitelib-dir PATH Override the wrapper's silentdragonxlitelib dependency path.
|
||||||
|
--out-dir PATH Output directory for copied artifact and metadata.
|
||||||
|
--artifact PATH Inventory an existing artifact instead of building.
|
||||||
|
--no-build Do not run cargo; requires --artifact.
|
||||||
|
--reproducible Add deterministic Rust path remaps for clean builds.
|
||||||
|
--remap-path-prefix FROM=TO Extra rustc path remap used with --reproducible.
|
||||||
|
--builder NAME Redacted builder/provenance label. Default: local.
|
||||||
|
--signature-required Fail if verified signature metadata is not supplied.
|
||||||
|
--signature-file PATH Existing sidecar signature file to record.
|
||||||
|
--signature-format FORMAT Signature format: minisign, gpg, sigstore, external, or other.
|
||||||
|
--signature-verification-tool T Verification tool and version used by the release builder.
|
||||||
|
--signature-verification-command C
|
||||||
|
Verification command already run by the release builder.
|
||||||
|
--signature-key-fingerprint F Reviewed public-key fingerprint, when applicable.
|
||||||
|
--signature-certificate-identity ID
|
||||||
|
Reviewed certificate identity, when applicable.
|
||||||
|
--signature-certificate-issuer I
|
||||||
|
Reviewed certificate issuer, when applicable.
|
||||||
|
--signature-transparency-log-url URL
|
||||||
|
Transparency log entry, when applicable.
|
||||||
|
--signature-verified-sha256 SHA Artifact SHA-256 verified by the signature check.
|
||||||
|
-j, --jobs N Cargo parallel jobs.
|
||||||
|
--cargo-arg ARG Extra argument forwarded to cargo build.
|
||||||
|
-h, --help Show this help.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
<out>/<platform>/<artifact>
|
||||||
|
<out>/<platform>/lite-backend-symbols.txt
|
||||||
|
<out>/<platform>/lite-backend-artifact-manifest.json
|
||||||
|
|
||||||
|
The script captures symbols, checksums, and optional read-only signature
|
||||||
|
verification metadata only. It does not load the library, resolve function
|
||||||
|
pointers, call SDXL, sign, upload, or publish artifacts.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
info() { printf '[lite-backend] %s\n' "$*"; }
|
||||||
|
warn() { printf '[lite-backend] warning: %s\n' "$*" >&2; }
|
||||||
|
die() { printf '[lite-backend] ERROR: %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
absolute_path() {
|
||||||
|
local path="$1"
|
||||||
|
if [[ "$path" = /* ]]; then
|
||||||
|
printf '%s\n' "$path"
|
||||||
|
else
|
||||||
|
printf '%s/%s\n' "$PWD" "$path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
host_platform() {
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Linux) printf 'linux\n' ;;
|
||||||
|
Darwin) printf 'macos\n' ;;
|
||||||
|
MINGW*|MSYS*|CYGWIN*) printf 'windows\n' ;;
|
||||||
|
*) die "unsupported host platform: $(uname -s)" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_platform() {
|
||||||
|
case "$1" in
|
||||||
|
linux|Linux) printf 'linux\n' ;;
|
||||||
|
windows|win|Win|Windows) printf 'windows\n' ;;
|
||||||
|
macos|mac|darwin|Darwin) printf 'macos\n' ;;
|
||||||
|
"") host_platform ;;
|
||||||
|
*) die "unsupported platform: $1" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--platform)
|
||||||
|
[[ $# -ge 2 ]] || die "--platform requires a value"
|
||||||
|
PLATFORM="$(normalize_platform "$2")"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--rust-target)
|
||||||
|
[[ $# -ge 2 ]] || die "--rust-target requires a value"
|
||||||
|
RUST_TARGET="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--cargo-target-dir)
|
||||||
|
[[ $# -ge 2 ]] || die "--cargo-target-dir requires a value"
|
||||||
|
CARGO_TARGET_DIR_VALUE="$(absolute_path "$2")"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--backend-dir)
|
||||||
|
[[ $# -ge 2 ]] || die "--backend-dir requires a value"
|
||||||
|
BACKEND_DIR="$(absolute_path "$2")"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--silentdragonxlitelib-dir)
|
||||||
|
[[ $# -ge 2 ]] || die "--silentdragonxlitelib-dir requires a value"
|
||||||
|
BACKEND_DEPENDENCY_DIR="$(absolute_path "$2")"
|
||||||
|
BACKEND_DEPENDENCY_OVERRIDE_REQUESTED=true
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--out-dir)
|
||||||
|
[[ $# -ge 2 ]] || die "--out-dir requires a value"
|
||||||
|
OUT_DIR="$(absolute_path "$2")"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--artifact)
|
||||||
|
[[ $# -ge 2 ]] || die "--artifact requires a value"
|
||||||
|
ARTIFACT_PATH="$(absolute_path "$2")"
|
||||||
|
BUILD_ARTIFACT=false
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--no-build)
|
||||||
|
BUILD_ARTIFACT=false
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--reproducible)
|
||||||
|
REPRODUCIBLE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--remap-path-prefix)
|
||||||
|
[[ $# -ge 2 ]] || die "--remap-path-prefix requires FROM=TO"
|
||||||
|
[[ "$2" == *=* ]] || die "--remap-path-prefix requires FROM=TO"
|
||||||
|
EXTRA_REMAP_PATH_PREFIXES+=("$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--builder)
|
||||||
|
[[ $# -ge 2 ]] || die "--builder requires a value"
|
||||||
|
BUILDER="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--signature-required)
|
||||||
|
SIGNATURE_REQUIRED=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--signature-file|--signature-path)
|
||||||
|
[[ $# -ge 2 ]] || die "$1 requires a value"
|
||||||
|
SIGNATURE_FILE="$(absolute_path "$2")"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--signature-format)
|
||||||
|
[[ $# -ge 2 ]] || die "--signature-format requires a value"
|
||||||
|
SIGNATURE_FORMAT="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--signature-verification-tool|--signature-tool)
|
||||||
|
[[ $# -ge 2 ]] || die "$1 requires a value"
|
||||||
|
SIGNATURE_VERIFICATION_TOOL="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--signature-verification-command)
|
||||||
|
[[ $# -ge 2 ]] || die "--signature-verification-command requires a value"
|
||||||
|
SIGNATURE_VERIFICATION_COMMAND="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--signature-key-fingerprint)
|
||||||
|
[[ $# -ge 2 ]] || die "--signature-key-fingerprint requires a value"
|
||||||
|
SIGNATURE_KEY_FINGERPRINT="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--signature-certificate-identity)
|
||||||
|
[[ $# -ge 2 ]] || die "--signature-certificate-identity requires a value"
|
||||||
|
SIGNATURE_CERTIFICATE_IDENTITY="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--signature-certificate-issuer)
|
||||||
|
[[ $# -ge 2 ]] || die "--signature-certificate-issuer requires a value"
|
||||||
|
SIGNATURE_CERTIFICATE_ISSUER="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--signature-transparency-log-url)
|
||||||
|
[[ $# -ge 2 ]] || die "--signature-transparency-log-url requires a value"
|
||||||
|
SIGNATURE_TRANSPARENCY_LOG_URL="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--signature-verified-sha256)
|
||||||
|
[[ $# -ge 2 ]] || die "--signature-verified-sha256 requires a value"
|
||||||
|
SIGNATURE_VERIFIED_SHA256="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-j|--jobs)
|
||||||
|
[[ $# -ge 2 ]] || die "--jobs requires a value"
|
||||||
|
JOBS="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--cargo-arg)
|
||||||
|
[[ $# -ge 2 ]] || die "--cargo-arg requires a value"
|
||||||
|
EXTRA_CARGO_ARGS+=("$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*) die "unknown option: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
PLATFORM="$(normalize_platform "$PLATFORM")"
|
||||||
|
BACKEND_SOURCE_DIR="$BACKEND_DIR"
|
||||||
|
BUILD_BACKEND_DIR="$BACKEND_SOURCE_DIR"
|
||||||
|
|
||||||
|
if [[ "$PLATFORM" == "windows" && -z "$RUST_TARGET" ]]; then
|
||||||
|
RUST_TARGET="x86_64-pc-windows-gnu"
|
||||||
|
fi
|
||||||
|
if [[ "$PLATFORM" == "macos" && -z "$RUST_TARGET" && "$(host_platform)" != "macos" ]]; then
|
||||||
|
die "macOS artifacts require --rust-target when not running on macOS"
|
||||||
|
fi
|
||||||
|
if [[ "$BUILD_ARTIFACT" == false && -z "$ARTIFACT_PATH" ]]; then
|
||||||
|
die "--no-build requires --artifact"
|
||||||
|
fi
|
||||||
|
|
||||||
|
backend_dependency_path_from_cargo() {
|
||||||
|
local cargo_toml="$1"
|
||||||
|
awk '
|
||||||
|
/^[[:space:]]*silentdragonxlitelib[[:space:]]*=/ {
|
||||||
|
original = $0
|
||||||
|
path = $0
|
||||||
|
sub(/.*path[[:space:]]*=[[:space:]]*"/, "", path)
|
||||||
|
sub(/".*/, "", path)
|
||||||
|
if (path != original) print path
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
' "$cargo_toml"
|
||||||
|
}
|
||||||
|
|
||||||
|
canonical_dependency_path() {
|
||||||
|
local path="$1"
|
||||||
|
if [[ -d "$path" ]]; then
|
||||||
|
(cd "$path" && pwd -P)
|
||||||
|
else
|
||||||
|
absolute_path "$path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_backend_dependency_source() {
|
||||||
|
[[ -n "$BACKEND_DEPENDENCY_DIR" ]] || return
|
||||||
|
|
||||||
|
if [[ ! -f "$BACKEND_DEPENDENCY_DIR/Cargo.toml" ]]; then
|
||||||
|
if [[ "$BUILD_ARTIFACT" == true || "$BACKEND_DEPENDENCY_OVERRIDE_REQUESTED" == true ]]; then
|
||||||
|
die "Cargo.toml not found in $BACKEND_DEPENDENCY_DIR"
|
||||||
|
fi
|
||||||
|
warn "Cargo.toml not found in silentdragonxlitelib source: $BACKEND_DEPENDENCY_DIR"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -Eq '^[[:space:]]*name[[:space:]]*=[[:space:]]*"silentdragonxlitelib"' "$BACKEND_DEPENDENCY_DIR/Cargo.toml"; then
|
||||||
|
if [[ "$BUILD_ARTIFACT" == true || "$BACKEND_DEPENDENCY_OVERRIDE_REQUESTED" == true ]]; then
|
||||||
|
die "dependency path does not look like silentdragonxlitelib: $BACKEND_DEPENDENCY_DIR"
|
||||||
|
fi
|
||||||
|
warn "dependency path does not look like silentdragonxlitelib: $BACKEND_DEPENDENCY_DIR"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure the Sapling proving params are present in the core crate (rust-embed bakes them in at build
|
||||||
|
# time). They are the fixed Zcash trusted-setup output — not buildable — so fetch + verify them from
|
||||||
|
# git.dragonx.is when absent. Override the source with SAPLING_PARAMS_BASE_URL.
|
||||||
|
SAPLING_PARAMS_BASE_URL="${SAPLING_PARAMS_BASE_URL:-https://git.dragonx.is/DragonX/zcash-params/releases/download/sapling-v1}"
|
||||||
|
ensure_sapling_params() {
|
||||||
|
local dir="$1"
|
||||||
|
[[ -n "$dir" ]] || return 0
|
||||||
|
mkdir -p "$dir"
|
||||||
|
local specs=(
|
||||||
|
"sapling-spend.params:8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13"
|
||||||
|
"sapling-output.params:2f0ebbcbb9bb0bcffe95a397e7eba89c29eb4dde6191c339db88570e3f3fb0e4"
|
||||||
|
)
|
||||||
|
local spec name want path got
|
||||||
|
for spec in "${specs[@]}"; do
|
||||||
|
name="${spec%%:*}"; want="${spec##*:}"; path="$dir/$name"
|
||||||
|
if [[ -f "$path" ]] && [[ "$(compute_sha256 "$path")" == "$want" ]]; then
|
||||||
|
info "sapling param present and verified: $name"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
info "fetching $name from $SAPLING_PARAMS_BASE_URL"
|
||||||
|
curl -fsSL "$SAPLING_PARAMS_BASE_URL/$name" -o "$path" || die "failed to download sapling param: $name"
|
||||||
|
got="$(compute_sha256 "$path")"
|
||||||
|
[[ "$got" == "$want" ]] || { rm -f "$path"; die "sapling param $name sha256 mismatch (got $got, want $want)"; }
|
||||||
|
info "downloaded and verified $name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_backend_source() {
|
||||||
|
BUILD_BACKEND_DIR="$BACKEND_SOURCE_DIR"
|
||||||
|
|
||||||
|
if [[ "$BACKEND_DEPENDENCY_OVERRIDE_REQUESTED" == false ]]; then
|
||||||
|
if [[ -f "$BACKEND_SOURCE_DIR/Cargo.toml" ]]; then
|
||||||
|
local configured_dependency_path
|
||||||
|
configured_dependency_path="$(backend_dependency_path_from_cargo "$BACKEND_SOURCE_DIR/Cargo.toml")"
|
||||||
|
if [[ -n "$configured_dependency_path" ]]; then
|
||||||
|
if [[ "$configured_dependency_path" = /* ]]; then
|
||||||
|
BACKEND_DEPENDENCY_DIR="$(canonical_dependency_path "$configured_dependency_path")"
|
||||||
|
warn "backend Cargo.toml uses an absolute silentdragonxlitelib path; use --silentdragonxlitelib-dir for portable builders"
|
||||||
|
else
|
||||||
|
BACKEND_DEPENDENCY_DIR="$(canonical_dependency_path "$BACKEND_SOURCE_DIR/$configured_dependency_path")"
|
||||||
|
info "using relative silentdragonxlitelib dependency at $BACKEND_DEPENDENCY_DIR"
|
||||||
|
fi
|
||||||
|
validate_backend_dependency_source
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$BACKEND_SOURCE_DIR/Cargo.toml" ]] || die "Cargo.toml not found in $BACKEND_SOURCE_DIR"
|
||||||
|
validate_backend_dependency_source
|
||||||
|
[[ "$BACKEND_DEPENDENCY_DIR" != *\"* ]] || die "--silentdragonxlitelib-dir path cannot contain a double quote"
|
||||||
|
|
||||||
|
local prepared_root="$OUT_DIR/.prepared-backend/$PLATFORM"
|
||||||
|
[[ "$prepared_root" == */.prepared-backend/* ]] || die "refusing unsafe prepared backend path: $prepared_root"
|
||||||
|
rm -rf "$prepared_root"
|
||||||
|
mkdir -p "$prepared_root"
|
||||||
|
|
||||||
|
ln -s "$BACKEND_SOURCE_DIR/src" "$prepared_root/src"
|
||||||
|
[[ -f "$BACKEND_SOURCE_DIR/Cargo.lock" ]] && ln -s "$BACKEND_SOURCE_DIR/Cargo.lock" "$prepared_root/Cargo.lock"
|
||||||
|
[[ -d "$BACKEND_SOURCE_DIR/.cargo" ]] && ln -s "$BACKEND_SOURCE_DIR/.cargo" "$prepared_root/.cargo"
|
||||||
|
[[ -d "$BACKEND_SOURCE_DIR/libsodium-mingw" ]] && ln -s "$BACKEND_SOURCE_DIR/libsodium-mingw" "$prepared_root/libsodium-mingw"
|
||||||
|
# Vendored crate deps (offline builds): the .cargo/config.toml's vendored-sources directory is
|
||||||
|
# "vendor" relative to the build root, so expose it inside the prepared root too.
|
||||||
|
[[ -d "$BACKEND_SOURCE_DIR/vendor" ]] && ln -s "$BACKEND_SOURCE_DIR/vendor" "$prepared_root/vendor"
|
||||||
|
[[ -f "$BACKEND_SOURCE_DIR/silentdragonxlitelib.h" ]] && ln -s "$BACKEND_SOURCE_DIR/silentdragonxlitelib.h" "$prepared_root/silentdragonxlitelib.h"
|
||||||
|
|
||||||
|
local replacement="silentdragonxlitelib = { path = \"$BACKEND_DEPENDENCY_DIR\" }"
|
||||||
|
awk -v replacement="$replacement" '
|
||||||
|
BEGIN { replaced = 0 }
|
||||||
|
/^[[:space:]]*silentdragonxlitelib[[:space:]]*=/ {
|
||||||
|
print replacement
|
||||||
|
replaced = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print }
|
||||||
|
END { if (replaced != 1) exit 42 }
|
||||||
|
' "$BACKEND_SOURCE_DIR/Cargo.toml" > "$prepared_root/Cargo.toml" \
|
||||||
|
|| die "failed to prepare backend Cargo.toml with portable silentdragonxlitelib path"
|
||||||
|
|
||||||
|
BUILD_BACKEND_DIR="$prepared_root"
|
||||||
|
info "prepared backend source at $BUILD_BACKEND_DIR with silentdragonxlitelib from $BACKEND_DEPENDENCY_DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_backend_source
|
||||||
|
|
||||||
|
artifact_kind() {
|
||||||
|
local name="${1##*/}"
|
||||||
|
case "$name" in
|
||||||
|
*.a|*.lib) printf 'static-library\n' ;;
|
||||||
|
*.so|*.dylib|*.dll) printf 'shared-library\n' ;;
|
||||||
|
*) printf 'unknown\n' ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
cargo_output_candidates() {
|
||||||
|
local cargo_target_root="$BUILD_BACKEND_DIR/target"
|
||||||
|
if [[ -n "$CARGO_TARGET_DIR_VALUE" ]]; then
|
||||||
|
cargo_target_root="$CARGO_TARGET_DIR_VALUE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local base="$cargo_target_root/release"
|
||||||
|
if [[ -n "$RUST_TARGET" ]]; then
|
||||||
|
base="$cargo_target_root/$RUST_TARGET/release"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$PLATFORM" in
|
||||||
|
linux)
|
||||||
|
printf '%s\n' "$base/libsilentdragonxlite.a" "$base/silentdragonxlite.a" "$base/libsilentdragonxlite.so"
|
||||||
|
;;
|
||||||
|
windows)
|
||||||
|
printf '%s\n' "$base/silentdragonxlite.lib" "$base/libsilentdragonxlite.a" "$base/silentdragonxlite.dll"
|
||||||
|
;;
|
||||||
|
macos)
|
||||||
|
printf '%s\n' "$base/libsilentdragonxlite.a" "$base/silentdragonxlite.a" "$base/libsilentdragonxlite.dylib" "$base/silentdragonxlite.dylib"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
source_revision_for() {
|
||||||
|
local dir="$1"
|
||||||
|
local revision_file
|
||||||
|
for revision_file in "$dir/DRAGONX_SOURCE_REVISION" "$dir/../DRAGONX_SOURCE_REVISION"; do
|
||||||
|
if [[ -f "$revision_file" ]]; then
|
||||||
|
sed -n '1p' "$revision_file"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if git -C "$dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
git -C "$dir" rev-parse HEAD 2>/dev/null || printf 'unknown'
|
||||||
|
else
|
||||||
|
printf 'unknown'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
default_source_date_epoch() {
|
||||||
|
if git -C "$PROJECT_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
||||||
|
git -C "$PROJECT_ROOT" log -1 --format=%ct 2>/dev/null || printf '0'
|
||||||
|
else
|
||||||
|
printf '0'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
append_rustflag() {
|
||||||
|
local rustflag="$1"
|
||||||
|
if [[ -n "${RUSTFLAGS:-}" ]]; then
|
||||||
|
export RUSTFLAGS="${RUSTFLAGS} ${rustflag}"
|
||||||
|
else
|
||||||
|
export RUSTFLAGS="$rustflag"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
append_rust_path_remap() {
|
||||||
|
local from_path="$1"
|
||||||
|
local to_path="$2"
|
||||||
|
[[ -n "$from_path" && -n "$to_path" ]] || return
|
||||||
|
append_rustflag "--remap-path-prefix=${from_path}=${to_path}"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_reproducible_rustflags() {
|
||||||
|
local cargo_target_root="$BUILD_BACKEND_DIR/target"
|
||||||
|
if [[ -n "$CARGO_TARGET_DIR_VALUE" ]]; then
|
||||||
|
cargo_target_root="$CARGO_TARGET_DIR_VALUE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
append_rust_path_remap "$PROJECT_ROOT" "/dragonx-project"
|
||||||
|
append_rust_path_remap "$BACKEND_SOURCE_DIR" "/dragonx-lite-backend"
|
||||||
|
if [[ "$BUILD_BACKEND_DIR" != "$BACKEND_SOURCE_DIR" ]]; then
|
||||||
|
append_rust_path_remap "$BUILD_BACKEND_DIR" "/dragonx-lite-backend"
|
||||||
|
fi
|
||||||
|
append_rust_path_remap "$BACKEND_DEPENDENCY_DIR" "/dragonx-lite-backend-dependency"
|
||||||
|
for path_remap in "${EXTRA_REMAP_PATH_PREFIXES[@]}"; do
|
||||||
|
append_rustflag "--remap-path-prefix=${path_remap}"
|
||||||
|
done
|
||||||
|
|
||||||
|
local cargo_home="${CARGO_HOME:-}"
|
||||||
|
if [[ -z "$cargo_home" && -n "${HOME:-}" ]]; then
|
||||||
|
cargo_home="$HOME/.cargo"
|
||||||
|
fi
|
||||||
|
if [[ -n "$cargo_home" && -d "$cargo_home" ]]; then
|
||||||
|
append_rust_path_remap "$cargo_home" "/cargo-home"
|
||||||
|
fi
|
||||||
|
append_rust_path_remap "$cargo_target_root" "/dragonx-lite-cargo-target"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_with_cargo() {
|
||||||
|
command -v cargo >/dev/null 2>&1 || die "cargo was not found"
|
||||||
|
[[ -f "$BUILD_BACKEND_DIR/Cargo.toml" ]] || die "Cargo.toml not found in $BUILD_BACKEND_DIR"
|
||||||
|
|
||||||
|
if [[ -z "$SOURCE_DATE_EPOCH_VALUE" ]]; then
|
||||||
|
SOURCE_DATE_EPOCH_VALUE="$(default_source_date_epoch)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
export CARGO_INCREMENTAL=0
|
||||||
|
export SOURCE_DATE_EPOCH="$SOURCE_DATE_EPOCH_VALUE"
|
||||||
|
if [[ -n "$CARGO_TARGET_DIR_VALUE" ]]; then
|
||||||
|
export CARGO_TARGET_DIR="$CARGO_TARGET_DIR_VALUE"
|
||||||
|
fi
|
||||||
|
if [[ "$REPRODUCIBLE" == true ]]; then
|
||||||
|
apply_reproducible_rustflags
|
||||||
|
fi
|
||||||
|
if [[ "$PLATFORM" == "windows" && -d "$BUILD_BACKEND_DIR/libsodium-mingw" ]]; then
|
||||||
|
export SODIUM_LIB_DIR="$BUILD_BACKEND_DIR/libsodium-mingw"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "$BACKEND_DEPENDENCY_DIR" ]] && ensure_sapling_params "$BACKEND_DEPENDENCY_DIR/zcash-params"
|
||||||
|
|
||||||
|
local cargo_cmd=(cargo build --locked --lib --release)
|
||||||
|
if [[ -n "$RUST_TARGET" ]]; then
|
||||||
|
cargo_cmd+=(--target "$RUST_TARGET")
|
||||||
|
fi
|
||||||
|
if [[ -n "$JOBS" ]]; then
|
||||||
|
cargo_cmd+=(-j "$JOBS")
|
||||||
|
fi
|
||||||
|
cargo_cmd+=("${EXTRA_CARGO_ARGS[@]}")
|
||||||
|
|
||||||
|
info "building backend in $BUILD_BACKEND_DIR"
|
||||||
|
(cd "$BUILD_BACKEND_DIR" && "${cargo_cmd[@]}")
|
||||||
|
|
||||||
|
while IFS= read -r candidate; do
|
||||||
|
if [[ -f "$candidate" ]]; then
|
||||||
|
ARTIFACT_PATH="$candidate"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done < <(cargo_output_candidates)
|
||||||
|
|
||||||
|
die "cargo finished, but no expected backend artifact was found under $BUILD_BACKEND_DIR/target"
|
||||||
|
}
|
||||||
|
|
||||||
|
select_nm_tool() {
|
||||||
|
if [[ "$PLATFORM" == "windows" ]] && command -v x86_64-w64-mingw32-nm >/dev/null 2>&1; then
|
||||||
|
printf 'x86_64-w64-mingw32-nm\n'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if command -v llvm-nm >/dev/null 2>&1; then
|
||||||
|
printf 'llvm-nm\n'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
if command -v nm >/dev/null 2>&1; then
|
||||||
|
printf 'nm\n'
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
die "no symbol inventory tool found; install nm, llvm-nm, or x86_64-w64-mingw32-nm"
|
||||||
|
}
|
||||||
|
|
||||||
|
compute_sha256() {
|
||||||
|
local file="$1"
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
sha256sum "$file" | awk '{print $1}'
|
||||||
|
elif command -v shasum >/dev/null 2>&1; then
|
||||||
|
shasum -a 256 "$file" | awk '{print $1}'
|
||||||
|
else
|
||||||
|
die "sha256sum or shasum is required"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
json_escape() {
|
||||||
|
local value="$1"
|
||||||
|
value="${value//\\/\\\\}"
|
||||||
|
value="${value//\"/\\\"}"
|
||||||
|
value="${value//$'\n'/\\n}"
|
||||||
|
value="${value//$'\r'/}"
|
||||||
|
value="${value//$'\t'/\\t}"
|
||||||
|
printf '"%s"' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_array() {
|
||||||
|
local first=true
|
||||||
|
printf '['
|
||||||
|
for value in "$@"; do
|
||||||
|
if [[ "$first" == true ]]; then
|
||||||
|
first=false
|
||||||
|
else
|
||||||
|
printf ','
|
||||||
|
fi
|
||||||
|
json_escape "$value"
|
||||||
|
done
|
||||||
|
printf ']'
|
||||||
|
}
|
||||||
|
|
||||||
|
json_array_from_file() {
|
||||||
|
local file="$1"
|
||||||
|
local values=()
|
||||||
|
if [[ -f "$file" ]]; then
|
||||||
|
mapfile -t values < "$file"
|
||||||
|
fi
|
||||||
|
json_array "${values[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
signature_metadata_requested() {
|
||||||
|
[[ "$SIGNATURE_REQUIRED" == true || \
|
||||||
|
-n "$SIGNATURE_FILE" || \
|
||||||
|
-n "$SIGNATURE_FORMAT" || \
|
||||||
|
-n "$SIGNATURE_VERIFICATION_TOOL" || \
|
||||||
|
-n "$SIGNATURE_VERIFICATION_COMMAND" || \
|
||||||
|
-n "$SIGNATURE_KEY_FINGERPRINT" || \
|
||||||
|
-n "$SIGNATURE_CERTIFICATE_IDENTITY" || \
|
||||||
|
-n "$SIGNATURE_CERTIFICATE_ISSUER" || \
|
||||||
|
-n "$SIGNATURE_TRANSPARENCY_LOG_URL" || \
|
||||||
|
-n "$SIGNATURE_VERIFIED_SHA256" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_signature_metadata() {
|
||||||
|
SIGNATURE_REQUIRED_MANIFEST_VALUE=false
|
||||||
|
if [[ "$SIGNATURE_REQUIRED" == true ]]; then
|
||||||
|
SIGNATURE_REQUIRED_MANIFEST_VALUE=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! signature_metadata_requested; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -n "$SIGNATURE_FILE" ]] || die "signature metadata requires --signature-file"
|
||||||
|
[[ -f "$SIGNATURE_FILE" ]] || die "signature file does not exist: $SIGNATURE_FILE"
|
||||||
|
[[ -n "$SIGNATURE_FORMAT" ]] || die "signature metadata requires --signature-format"
|
||||||
|
case "$SIGNATURE_FORMAT" in
|
||||||
|
minisign|gpg|sigstore|external|other) ;;
|
||||||
|
*) die "unsupported --signature-format: $SIGNATURE_FORMAT" ;;
|
||||||
|
esac
|
||||||
|
[[ -n "$SIGNATURE_VERIFICATION_TOOL" ]] || die "signature metadata requires --signature-verification-tool"
|
||||||
|
[[ -n "$SIGNATURE_VERIFIED_SHA256" ]] || die "signature metadata requires --signature-verified-sha256"
|
||||||
|
[[ "$SIGNATURE_VERIFIED_SHA256" == "$SHA256_DIGEST" ]] || die "signature verified SHA-256 does not match artifact SHA-256"
|
||||||
|
if [[ -z "$SIGNATURE_KEY_FINGERPRINT" && -z "$SIGNATURE_CERTIFICATE_IDENTITY" ]]; then
|
||||||
|
die "signature metadata requires --signature-key-fingerprint or --signature-certificate-identity"
|
||||||
|
fi
|
||||||
|
|
||||||
|
SIGNATURE_METADATA_PROVIDED=true
|
||||||
|
SIGNATURE_VERIFICATION_PERFORMED=true
|
||||||
|
SIGNATURE_VERIFICATION_STATUS="verified"
|
||||||
|
SIGNATURE_FILE_SHA256="$(compute_sha256 "$SIGNATURE_FILE")"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$BUILD_ARTIFACT" == true ]]; then
|
||||||
|
build_with_cargo
|
||||||
|
fi
|
||||||
|
if [[ -z "$SOURCE_DATE_EPOCH_VALUE" ]]; then
|
||||||
|
SOURCE_DATE_EPOCH_VALUE="$(default_source_date_epoch)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ -f "$ARTIFACT_PATH" ]] || die "artifact not found: $ARTIFACT_PATH"
|
||||||
|
|
||||||
|
KIND="$(artifact_kind "$ARTIFACT_PATH")"
|
||||||
|
[[ "$KIND" != "unknown" ]] || die "artifact kind is unsupported: $ARTIFACT_PATH"
|
||||||
|
|
||||||
|
PLATFORM_OUT_DIR="$OUT_DIR/$PLATFORM"
|
||||||
|
mkdir -p "$PLATFORM_OUT_DIR"
|
||||||
|
|
||||||
|
ARTIFACT_NAME="$(basename "$ARTIFACT_PATH")"
|
||||||
|
ARTIFACT_OUTPUT="$PLATFORM_OUT_DIR/$ARTIFACT_NAME"
|
||||||
|
if [[ "$(absolute_path "$ARTIFACT_PATH")" != "$(absolute_path "$ARTIFACT_OUTPUT")" ]]; then
|
||||||
|
cp -p "$ARTIFACT_PATH" "$ARTIFACT_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
SYMBOLS_FILE="$PLATFORM_OUT_DIR/lite-backend-symbols.txt"
|
||||||
|
RAW_SYMBOLS_FILE="$PLATFORM_OUT_DIR/lite-backend-symbols.raw.txt"
|
||||||
|
NM_TOOL="$(select_nm_tool)"
|
||||||
|
|
||||||
|
info "capturing exported symbols with $NM_TOOL"
|
||||||
|
if ! "$NM_TOOL" -g --defined-only "$ARTIFACT_OUTPUT" > "$RAW_SYMBOLS_FILE" 2> "$PLATFORM_OUT_DIR/lite-backend-symbols.err.txt"; then
|
||||||
|
die "symbol inventory failed; see $PLATFORM_OUT_DIR/lite-backend-symbols.err.txt"
|
||||||
|
fi
|
||||||
|
awk '{print $NF}' "$RAW_SYMBOLS_FILE" \
|
||||||
|
| sed 's/^_//' \
|
||||||
|
| grep -E '^(litelib_[A-Za-z0-9_]*|blake3_PW)$' \
|
||||||
|
| sort -u > "$SYMBOLS_FILE" || true
|
||||||
|
|
||||||
|
[[ -s "$SYMBOLS_FILE" ]] || die "no SDXL C ABI symbols were found in $ARTIFACT_OUTPUT"
|
||||||
|
|
||||||
|
MISSING_SYMBOLS=()
|
||||||
|
for required in "${REQUIRED_SYMBOLS[@]}"; do
|
||||||
|
if ! grep -Fxq "$required" "$SYMBOLS_FILE"; then
|
||||||
|
MISSING_SYMBOLS+=("$required")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ ${#MISSING_SYMBOLS[@]} -ne 0 ]]; then
|
||||||
|
printf '%s\n' "${MISSING_SYMBOLS[@]}" > "$PLATFORM_OUT_DIR/lite-backend-missing-symbols.txt"
|
||||||
|
die "artifact is missing required symbols; see $PLATFORM_OUT_DIR/lite-backend-missing-symbols.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
SHA256_DIGEST="$(compute_sha256 "$ARTIFACT_OUTPUT")"
|
||||||
|
validate_signature_metadata
|
||||||
|
ARTIFACT_SIZE_BYTES="$(wc -c < "$ARTIFACT_OUTPUT" | tr -d ' ')"
|
||||||
|
PROJECT_REVISION="$(source_revision_for "$PROJECT_ROOT")"
|
||||||
|
BACKEND_REVISION="$(source_revision_for "$BACKEND_SOURCE_DIR")"
|
||||||
|
BACKEND_DEPENDENCY_REVISION=""
|
||||||
|
if [[ -n "$BACKEND_DEPENDENCY_DIR" ]]; then
|
||||||
|
BACKEND_DEPENDENCY_REVISION="$(source_revision_for "$BACKEND_DEPENDENCY_DIR")"
|
||||||
|
fi
|
||||||
|
ARTIFACT_SET_ID="$PLATFORM-${SHA256_DIGEST:0:16}"
|
||||||
|
REPRODUCIBLE_MANIFEST_VALUE=false
|
||||||
|
if [[ "$BUILD_ARTIFACT" == true && "$REPRODUCIBLE" == true ]]; then
|
||||||
|
REPRODUCIBLE_MANIFEST_VALUE=true
|
||||||
|
fi
|
||||||
|
PORTABLE_DEPENDENCY_OVERRIDE_MANIFEST_VALUE=false
|
||||||
|
if [[ "$BACKEND_DEPENDENCY_OVERRIDE_REQUESTED" == true ]]; then
|
||||||
|
PORTABLE_DEPENDENCY_OVERRIDE_MANIFEST_VALUE=true
|
||||||
|
fi
|
||||||
|
FILE_DESCRIPTION="unknown"
|
||||||
|
if command -v file >/dev/null 2>&1; then
|
||||||
|
FILE_DESCRIPTION="$(file -b "$ARTIFACT_OUTPUT")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
MANIFEST_FILE="$PLATFORM_OUT_DIR/lite-backend-artifact-manifest.json"
|
||||||
|
{
|
||||||
|
printf '{\n'
|
||||||
|
printf ' "schema": "dragonx.lite.backend-artifact.v1",\n'
|
||||||
|
printf ' "generated_by": "scripts/build-lite-backend-artifact.sh",\n'
|
||||||
|
printf ' "read_only_inventory": true,\n'
|
||||||
|
printf ' "artifact_mutation_requested": false,\n'
|
||||||
|
printf ' "upload_requested": false,\n'
|
||||||
|
printf ' "signing_requested": false,\n'
|
||||||
|
printf ' "publication_requested": false,\n'
|
||||||
|
printf ' "signature_verification": {\n'
|
||||||
|
printf ' "policy_name": '; json_escape "$SIGNATURE_POLICY_NAME"; printf ',\n'
|
||||||
|
printf ' "policy_defined": %s,\n' "$SIGNATURE_POLICY_DEFINED_MANIFEST_VALUE"
|
||||||
|
printf ' "required_for_release": %s,\n' "$SIGNATURE_REQUIRED_MANIFEST_VALUE"
|
||||||
|
printf ' "metadata_read_only": true,\n'
|
||||||
|
printf ' "metadata_provided": %s,\n' "$SIGNATURE_METADATA_PROVIDED"
|
||||||
|
printf ' "verification_performed": %s,\n' "$SIGNATURE_VERIFICATION_PERFORMED"
|
||||||
|
printf ' "verification_status": '; json_escape "$SIGNATURE_VERIFICATION_STATUS"; printf ',\n'
|
||||||
|
printf ' "signature_format": '; json_escape "$SIGNATURE_FORMAT"; printf ',\n'
|
||||||
|
printf ' "signature_path": '; json_escape "$SIGNATURE_FILE"; printf ',\n'
|
||||||
|
printf ' "signature_file_sha256": '; json_escape "$SIGNATURE_FILE_SHA256"; printf ',\n'
|
||||||
|
printf ' "verification_tool": '; json_escape "$SIGNATURE_VERIFICATION_TOOL"; printf ',\n'
|
||||||
|
printf ' "verification_command": '; json_escape "$SIGNATURE_VERIFICATION_COMMAND"; printf ',\n'
|
||||||
|
printf ' "key_fingerprint": '; json_escape "$SIGNATURE_KEY_FINGERPRINT"; printf ',\n'
|
||||||
|
printf ' "certificate_identity": '; json_escape "$SIGNATURE_CERTIFICATE_IDENTITY"; printf ',\n'
|
||||||
|
printf ' "certificate_issuer": '; json_escape "$SIGNATURE_CERTIFICATE_ISSUER"; printf ',\n'
|
||||||
|
printf ' "transparency_log_url": '; json_escape "$SIGNATURE_TRANSPARENCY_LOG_URL"; printf ',\n'
|
||||||
|
printf ' "verified_artifact_sha256": '; json_escape "$SIGNATURE_VERIFIED_SHA256"; printf '\n'
|
||||||
|
printf ' },\n'
|
||||||
|
printf ' "abi_version": '; json_escape "$ABI_VERSION"; printf ',\n'
|
||||||
|
printf ' "link_mode": '; json_escape "$LINK_MODE"; printf ',\n'
|
||||||
|
printf ' "platform": '; json_escape "$PLATFORM"; printf ',\n'
|
||||||
|
printf ' "rust_target": '; json_escape "$RUST_TARGET"; printf ',\n'
|
||||||
|
printf ' "artifact": {\n'
|
||||||
|
printf ' "path": '; json_escape "$ARTIFACT_OUTPUT"; printf ',\n'
|
||||||
|
printf ' "kind": '; json_escape "$KIND"; printf ',\n'
|
||||||
|
printf ' "size_bytes": %s,\n' "$ARTIFACT_SIZE_BYTES"
|
||||||
|
printf ' "sha256": '; json_escape "$SHA256_DIGEST"; printf ',\n'
|
||||||
|
printf ' "file_description": '; json_escape "$FILE_DESCRIPTION"; printf '\n'
|
||||||
|
printf ' },\n'
|
||||||
|
printf ' "symbol_inventory": {\n'
|
||||||
|
printf ' "tool": '; json_escape "$NM_TOOL"; printf ',\n'
|
||||||
|
printf ' "symbols_path": '; json_escape "$SYMBOLS_FILE"; printf ',\n'
|
||||||
|
printf ' "raw_symbols_path": '; json_escape "$RAW_SYMBOLS_FILE"; printf ',\n'
|
||||||
|
printf ' "required_symbols": '; json_array "${REQUIRED_SYMBOLS[@]}"; printf ',\n'
|
||||||
|
printf ' "exported_symbols": '; json_array_from_file "$SYMBOLS_FILE"; printf ',\n'
|
||||||
|
printf ' "missing_required_symbols": []\n'
|
||||||
|
printf ' },\n'
|
||||||
|
printf ' "provenance": {\n'
|
||||||
|
printf ' "owner_ready": true,\n'
|
||||||
|
printf ' "metadata_provided": true,\n'
|
||||||
|
printf ' "source": '; json_escape "$BACKEND_SOURCE_DIR"; printf ',\n'
|
||||||
|
printf ' "cargo_build_source": '; json_escape "$BUILD_BACKEND_DIR"; printf ',\n'
|
||||||
|
printf ' "portable_dependency_override": %s,\n' "$PORTABLE_DEPENDENCY_OVERRIDE_MANIFEST_VALUE"
|
||||||
|
printf ' "silentdragonxlitelib_source": '; json_escape "$BACKEND_DEPENDENCY_DIR"; printf ',\n'
|
||||||
|
printf ' "builder": '; json_escape "$BUILDER"; printf ',\n'
|
||||||
|
printf ' "source_revision": '; json_escape "$BACKEND_REVISION"; printf ',\n'
|
||||||
|
printf ' "silentdragonxlitelib_revision": '; json_escape "$BACKEND_DEPENDENCY_REVISION"; printf ',\n'
|
||||||
|
printf ' "project_revision": '; json_escape "$PROJECT_REVISION"; printf ',\n'
|
||||||
|
printf ' "artifact_set_id": '; json_escape "$ARTIFACT_SET_ID"; printf ',\n'
|
||||||
|
printf ' "source_date_epoch": '; json_escape "$SOURCE_DATE_EPOCH_VALUE"; printf ',\n'
|
||||||
|
printf ' "reproducible": %s,\n' "$REPRODUCIBLE_MANIFEST_VALUE"
|
||||||
|
printf ' "redacted": true\n'
|
||||||
|
printf ' }\n'
|
||||||
|
printf '}\n'
|
||||||
|
} > "$MANIFEST_FILE"
|
||||||
|
|
||||||
|
info "artifact: $ARTIFACT_OUTPUT"
|
||||||
|
info "symbols: $SYMBOLS_FILE"
|
||||||
|
info "manifest: $MANIFEST_FILE"
|
||||||
|
info "sha256: $SHA256_DIGEST"
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
CMake configure example:
|
||||||
|
cmake -S "$PROJECT_ROOT" -B "$PROJECT_ROOT/build/lite" \\
|
||||||
|
-DDRAGONX_BUILD_LITE=ON \\
|
||||||
|
-DDRAGONX_ENABLE_LITE_BACKEND=ON \\
|
||||||
|
-DDRAGONX_LITE_BACKEND_LIBRARY="$ARTIFACT_OUTPUT" \\
|
||||||
|
-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE="$SYMBOLS_FILE" \\
|
||||||
|
-DDRAGONX_LITE_BACKEND_MANIFEST="$MANIFEST_FILE" \\
|
||||||
|
-DDRAGONX_LITE_BACKEND_LINK_MODE=$LINK_MODE \\
|
||||||
|
-DDRAGONX_LITE_BACKEND_ABI=$ABI_VERSION
|
||||||
|
EOF
|
||||||
131
scripts/build_cjk_subset.py
Normal file
131
scripts/build_cjk_subset.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Build a NotoSansCJK subset font containing all characters used by
|
||||||
|
the zh, ja, and ko translation files, plus common CJK punctuation
|
||||||
|
and symbols.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 scripts/build_cjk_subset.py
|
||||||
|
|
||||||
|
Requires: pip install fonttools brotli
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
from fontTools import subset as ftsubset
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
LANG_DIR = os.path.join(ROOT, 'res', 'lang')
|
||||||
|
SOURCE_FONT = '/tmp/NotoSansCJKsc-Regular.otf'
|
||||||
|
OUTPUT_FONT = os.path.join(ROOT, 'res', 'fonts', 'NotoSansCJK-Subset.ttf')
|
||||||
|
|
||||||
|
# Collect all characters used in CJK translation files
|
||||||
|
needed = set()
|
||||||
|
for lang in ['zh', 'ja', 'ko']:
|
||||||
|
path = os.path.join(LANG_DIR, f'{lang}.json')
|
||||||
|
if not os.path.exists(path):
|
||||||
|
continue
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
for v in data.values():
|
||||||
|
if isinstance(v, str):
|
||||||
|
for c in v:
|
||||||
|
cp = ord(c)
|
||||||
|
if cp > 0x7F: # non-ASCII only (ASCII handled by Ubuntu font)
|
||||||
|
needed.add(cp)
|
||||||
|
|
||||||
|
# Also add common CJK ranges that future translations might use:
|
||||||
|
# - CJK punctuation and symbols (3000-303F)
|
||||||
|
# - Hiragana (3040-309F)
|
||||||
|
# - Katakana (30A0-30FF)
|
||||||
|
# - Bopomofo (3100-312F)
|
||||||
|
# - CJK quotation marks, brackets
|
||||||
|
for cp in range(0x3000, 0x3100):
|
||||||
|
needed.add(cp)
|
||||||
|
for cp in range(0x3100, 0x3130):
|
||||||
|
needed.add(cp)
|
||||||
|
# Fullwidth ASCII variants (commonly mixed in CJK text)
|
||||||
|
for cp in range(0xFF01, 0xFF5F):
|
||||||
|
needed.add(cp)
|
||||||
|
|
||||||
|
print(f"Total non-ASCII characters to include: {len(needed)}")
|
||||||
|
|
||||||
|
# Check which of these the source font supports
|
||||||
|
font = TTFont(SOURCE_FONT)
|
||||||
|
cmap = font.getBestCmap()
|
||||||
|
supportable = needed & set(cmap.keys())
|
||||||
|
unsupported = needed - set(cmap.keys())
|
||||||
|
|
||||||
|
print(f"Supported by source font: {len(supportable)}")
|
||||||
|
if unsupported:
|
||||||
|
print(f"Not in source font (will use fallback): {len(unsupported)}")
|
||||||
|
for cp in sorted(unsupported)[:10]:
|
||||||
|
print(f" U+{cp:04X} {chr(cp)}")
|
||||||
|
|
||||||
|
# Build the subset using pyftsubset CLI-style API
|
||||||
|
args = [
|
||||||
|
SOURCE_FONT,
|
||||||
|
f'--output-file={OUTPUT_FONT}',
|
||||||
|
f'--unicodes={",".join(f"U+{cp:04X}" for cp in sorted(supportable))}',
|
||||||
|
'--no-hinting',
|
||||||
|
'--desubroutinize',
|
||||||
|
]
|
||||||
|
|
||||||
|
ftsubset.main(args)
|
||||||
|
|
||||||
|
# Convert CFF outlines to TrueType (glyf) outlines.
|
||||||
|
# stb_truetype (used by ImGui) doesn't handle CID-keyed CFF fonts properly.
|
||||||
|
from fontTools.pens.cu2quPen import Cu2QuPen
|
||||||
|
from fontTools.pens.ttGlyphPen import TTGlyphPen
|
||||||
|
from fontTools.ttLib import newTable
|
||||||
|
|
||||||
|
tmp_otf = OUTPUT_FONT + '.tmp.otf'
|
||||||
|
os.rename(OUTPUT_FONT, tmp_otf)
|
||||||
|
|
||||||
|
conv = TTFont(tmp_otf)
|
||||||
|
if 'CFF ' in conv:
|
||||||
|
print("Converting CFF -> TrueType outlines...")
|
||||||
|
glyphOrder = conv.getGlyphOrder()
|
||||||
|
glyphSet = conv.getGlyphSet()
|
||||||
|
glyf_table = newTable("glyf")
|
||||||
|
glyf_table.glyphs = {}
|
||||||
|
glyf_table.glyphOrder = glyphOrder
|
||||||
|
loca_table = newTable("loca")
|
||||||
|
from fontTools.ttLib.tables._g_l_y_f import Glyph as TTGlyph
|
||||||
|
for gname in glyphOrder:
|
||||||
|
try:
|
||||||
|
ttPen = TTGlyphPen(glyphSet)
|
||||||
|
cu2quPen = Cu2QuPen(ttPen, max_err=1.0, reverse_direction=True)
|
||||||
|
glyphSet[gname].draw(cu2quPen)
|
||||||
|
glyf_table.glyphs[gname] = ttPen.glyph()
|
||||||
|
except Exception:
|
||||||
|
glyf_table.glyphs[gname] = TTGlyph()
|
||||||
|
del conv['CFF ']
|
||||||
|
if 'VORG' in conv:
|
||||||
|
del conv['VORG']
|
||||||
|
conv['glyf'] = glyf_table
|
||||||
|
conv['loca'] = loca_table
|
||||||
|
conv['head'].indexToLocFormat = 1
|
||||||
|
if 'maxp' in conv:
|
||||||
|
conv['maxp'].version = 0x00010000
|
||||||
|
conv.sfntVersion = "\x00\x01\x00\x00"
|
||||||
|
conv.save(OUTPUT_FONT)
|
||||||
|
conv.close()
|
||||||
|
os.remove(tmp_otf)
|
||||||
|
|
||||||
|
size = os.path.getsize(OUTPUT_FONT)
|
||||||
|
print(f"\nOutput: {OUTPUT_FONT}")
|
||||||
|
print(f"Size: {size / 1024:.0f} KB")
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
verify = TTFont(OUTPUT_FONT)
|
||||||
|
verify_cmap = set(verify.getBestCmap().keys())
|
||||||
|
still_missing = needed - verify_cmap
|
||||||
|
print(f"Verified glyphs in subset: {len(verify_cmap)}")
|
||||||
|
if still_missing:
|
||||||
|
# These are chars not in the source font - expected for some Hangul/Hiragana
|
||||||
|
print(f"Not coverable by this font: {len(still_missing)} (need additional font)")
|
||||||
|
for cp in sorted(still_missing)[:10]:
|
||||||
|
print(f" U+{cp:04X} {chr(cp)}")
|
||||||
|
else:
|
||||||
|
print("All needed characters are covered!")
|
||||||
56
scripts/check-source-hygiene.sh
Executable file
56
scripts/check-source-hygiene.sh
Executable file
@@ -0,0 +1,56 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Source-tree hygiene guard.
|
||||||
|
#
|
||||||
|
# Blocks two failure modes that an AI coding session previously introduced in
|
||||||
|
# src/wallet/ (the lite-wallet "_plan"/"_batch" churn): pathologically long
|
||||||
|
# filenames (which also break the Windows MAX_PATH 260-char limit during the
|
||||||
|
# cross-build) and the runaway "receipt/custody/handoff/stewardship" naming
|
||||||
|
# explosion where each session wrapped the previous artifact in one more layer.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/check-source-hygiene.sh # check working-tree src/
|
||||||
|
# scripts/check-source-hygiene.sh --staged # check staged files (pre-commit)
|
||||||
|
#
|
||||||
|
# Install as a git pre-commit hook:
|
||||||
|
# ln -sf ../../scripts/check-source-hygiene.sh .git/hooks/pre-commit
|
||||||
|
# # (the hook invokes it with --staged automatically when named pre-commit)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MAX_LEN=80
|
||||||
|
# Naming-explosion tokens. Two or more chained in one basename is the smell.
|
||||||
|
CHURN_RE='receipt|custody|handoff|stewardship|promotion_activation|acceptance_confirmation|archive_handoff|post_closure'
|
||||||
|
|
||||||
|
mode="${1:-}"
|
||||||
|
if [[ "$mode" == "--staged" || "$(basename "$0")" == "pre-commit" ]]; then
|
||||||
|
mapfile -t files < <(git diff --cached --name-only --diff-filter=AR | grep -E '\.(cpp|h|hpp|cc)$' || true)
|
||||||
|
else
|
||||||
|
mapfile -t files < <(git ls-files 'src/**/*.cpp' 'src/**/*.h' 2>/dev/null; \
|
||||||
|
find src -type f \( -name '*.cpp' -o -name '*.h' \) 2>/dev/null)
|
||||||
|
# de-dup
|
||||||
|
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort -u)
|
||||||
|
fi
|
||||||
|
|
||||||
|
fail=0
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
[[ -z "$f" ]] && continue
|
||||||
|
base="$(basename "$f")"
|
||||||
|
len=${#base}
|
||||||
|
if (( len > MAX_LEN )); then
|
||||||
|
echo "✗ filename too long ($len > $MAX_LEN chars): $f" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
# count distinct churn tokens in the basename ( || true: grep exits 1 on no match)
|
||||||
|
n=$(printf '%s' "$base" | grep -oE "$CHURN_RE" | sort -u | wc -l || true)
|
||||||
|
if (( n >= 2 )); then
|
||||||
|
echo "✗ runaway naming pattern ($n churn tokens) — refactor in place, don't add a layer: $f" >&2
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( fail )); then
|
||||||
|
echo "" >&2
|
||||||
|
echo "Source hygiene check failed. See docs in scripts/check-source-hygiene.sh." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "source hygiene OK (${#files[@]} files checked)"
|
||||||
64
scripts/check_cjk_coverage.py
Normal file
64
scripts/check_cjk_coverage.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check which characters in translation files fall outside the font glyph ranges."""
|
||||||
|
import json
|
||||||
|
import unicodedata
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
|
||||||
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
LANG_DIR = os.path.join(SCRIPT_DIR, '..', 'res', 'lang')
|
||||||
|
|
||||||
|
# Glyph ranges from typography.cpp (regular font + CJK merge)
|
||||||
|
RANGES = [
|
||||||
|
# Regular font ranges
|
||||||
|
(0x0020, 0x00FF), # Basic Latin + Latin-1 Supplement
|
||||||
|
(0x0100, 0x024F), # Latin Extended-A + B
|
||||||
|
(0x0370, 0x03FF), # Greek and Coptic
|
||||||
|
(0x0400, 0x04FF), # Cyrillic
|
||||||
|
(0x0500, 0x052F), # Cyrillic Supplement
|
||||||
|
(0x2000, 0x206F), # General Punctuation
|
||||||
|
(0x2190, 0x21FF), # Arrows
|
||||||
|
(0x2200, 0x22FF), # Mathematical Operators
|
||||||
|
(0x2600, 0x26FF), # Miscellaneous Symbols
|
||||||
|
# CJK ranges
|
||||||
|
(0x2E80, 0x2FDF), # CJK Radicals
|
||||||
|
(0x3000, 0x30FF), # CJK Symbols, Hiragana, Katakana
|
||||||
|
(0x3100, 0x312F), # Bopomofo
|
||||||
|
(0x31F0, 0x31FF), # Katakana Extensions
|
||||||
|
(0x3400, 0x4DBF), # CJK Extension A
|
||||||
|
(0x4E00, 0x9FFF), # CJK Unified Ideographs
|
||||||
|
(0xAC00, 0xD7AF), # Hangul Syllables
|
||||||
|
(0xFF00, 0xFFEF), # Fullwidth Forms
|
||||||
|
]
|
||||||
|
|
||||||
|
def in_ranges(cp):
|
||||||
|
return any(lo <= cp <= hi for lo, hi in RANGES)
|
||||||
|
|
||||||
|
for path in sorted(glob.glob(os.path.join(LANG_DIR, '*.json'))):
|
||||||
|
lang = os.path.basename(path).replace('.json', '')
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
missing = {}
|
||||||
|
for key, val in data.items():
|
||||||
|
if not isinstance(val, str):
|
||||||
|
continue
|
||||||
|
for c in val:
|
||||||
|
cp = ord(c)
|
||||||
|
if cp > 0x7F and not in_ranges(cp):
|
||||||
|
if c not in missing:
|
||||||
|
missing[c] = []
|
||||||
|
missing[c].append(key)
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
print(f"\n=== {lang}.json: {len(missing)} missing characters ===")
|
||||||
|
for c in sorted(missing, key=lambda x: ord(x)):
|
||||||
|
cp = ord(c)
|
||||||
|
name = unicodedata.name(c, 'UNKNOWN')
|
||||||
|
keys = missing[c][:3]
|
||||||
|
key_str = ', '.join(keys)
|
||||||
|
if len(missing[c]) > 3:
|
||||||
|
key_str += f' (+{len(missing[c])-3} more)'
|
||||||
|
print(f" U+{cp:04X} {c} ({name}) — used in: {key_str}")
|
||||||
|
else:
|
||||||
|
print(f"=== {lang}.json: OK (all characters covered) ===")
|
||||||
47
scripts/check_font_coverage.py
Normal file
47
scripts/check_font_coverage.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Check which characters needed by translations are missing from bundled fonts."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
|
||||||
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
FONTS_DIR = os.path.join(ROOT, 'res', 'fonts')
|
||||||
|
LANG_DIR = os.path.join(ROOT, 'res', 'lang')
|
||||||
|
|
||||||
|
# Load font cmaps
|
||||||
|
cjk = TTFont(os.path.join(FONTS_DIR, 'NotoSansCJK-Subset.ttf'))
|
||||||
|
cjk_cmap = set(cjk.getBestCmap().keys())
|
||||||
|
|
||||||
|
ubuntu = TTFont(os.path.join(FONTS_DIR, 'Ubuntu-R.ttf'))
|
||||||
|
ubuntu_cmap = set(ubuntu.getBestCmap().keys())
|
||||||
|
|
||||||
|
combined = cjk_cmap | ubuntu_cmap
|
||||||
|
|
||||||
|
print(f"CJK subset font glyphs: {len(cjk_cmap)}")
|
||||||
|
print(f"Ubuntu font glyphs: {len(ubuntu_cmap)}")
|
||||||
|
print(f"Combined: {len(combined)}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for lang in ['zh', 'ja', 'ko', 'ru', 'de', 'es', 'fr', 'pt']:
|
||||||
|
path = os.path.join(LANG_DIR, f'{lang}.json')
|
||||||
|
if not os.path.exists(path):
|
||||||
|
continue
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
|
||||||
|
needed = set()
|
||||||
|
for v in data.values():
|
||||||
|
if isinstance(v, str):
|
||||||
|
for c in v:
|
||||||
|
needed.add(ord(c))
|
||||||
|
|
||||||
|
missing = sorted(needed - combined)
|
||||||
|
if missing:
|
||||||
|
print(f"{lang}.json: {len(needed)} chars needed, {len(missing)} MISSING")
|
||||||
|
for cp in missing[:20]:
|
||||||
|
c = chr(cp)
|
||||||
|
print(f" U+{cp:04X} {c}")
|
||||||
|
if len(missing) > 20:
|
||||||
|
print(f" ... and {len(missing) - 20} more")
|
||||||
|
else:
|
||||||
|
print(f"{lang}.json: OK ({len(needed)} chars, all covered)")
|
||||||
214
scripts/convert_cjk_to_ttf.py
Normal file
214
scripts/convert_cjk_to_ttf.py
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Convert CJK subset from CID-keyed CFF/OTF to TrueType/TTF.
|
||||||
|
|
||||||
|
stb_truetype (used by ImGui) doesn't handle CID-keyed CFF fonts properly,
|
||||||
|
so we need glyf-based TrueType outlines instead.
|
||||||
|
|
||||||
|
Two approaches:
|
||||||
|
1. Direct CFF->TTF conversion via cu2qu (fontTools)
|
||||||
|
2. Download NotoSansSC-Regular.ttf (already TTF) and re-subset
|
||||||
|
|
||||||
|
This script tries approach 1 first, falls back to approach 2.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import glob
|
||||||
|
|
||||||
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
PROJECT_ROOT = os.path.dirname(SCRIPT_DIR)
|
||||||
|
FONT_DIR = os.path.join(PROJECT_ROOT, "res", "fonts")
|
||||||
|
LANG_DIR = os.path.join(PROJECT_ROOT, "res", "lang")
|
||||||
|
|
||||||
|
SRC_OTF = os.path.join(FONT_DIR, "NotoSansCJK-Subset.otf")
|
||||||
|
DST_TTF = os.path.join(FONT_DIR, "NotoSansCJK-Subset.ttf")
|
||||||
|
|
||||||
|
|
||||||
|
def get_needed_codepoints():
|
||||||
|
"""Collect all unique codepoints from CJK translation files."""
|
||||||
|
codepoints = set()
|
||||||
|
for lang_file in glob.glob(os.path.join(LANG_DIR, "*.json")):
|
||||||
|
with open(lang_file, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
for value in data.values():
|
||||||
|
if isinstance(value, str):
|
||||||
|
for ch in value:
|
||||||
|
cp = ord(ch)
|
||||||
|
# Include CJK + Hangul + fullwidth + CJK symbols/kana
|
||||||
|
if cp >= 0x2E80:
|
||||||
|
codepoints.add(cp)
|
||||||
|
return codepoints
|
||||||
|
|
||||||
|
|
||||||
|
def convert_cff_to_ttf():
|
||||||
|
"""Convert existing OTF/CFF font to TTF using fontTools cu2qu."""
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
from fontTools.pens.cu2quPen import Cu2QuPen
|
||||||
|
from fontTools.pens.ttGlyphPen import TTGlyphPen
|
||||||
|
|
||||||
|
print(f"Loading {SRC_OTF}...")
|
||||||
|
font = TTFont(SRC_OTF)
|
||||||
|
|
||||||
|
# Verify it's CFF
|
||||||
|
if "CFF " not in font:
|
||||||
|
print("Font is not CFF, skipping conversion")
|
||||||
|
return False
|
||||||
|
|
||||||
|
cff = font["CFF "]
|
||||||
|
top = cff.cff.topDictIndex[0]
|
||||||
|
print(f"ROS: {getattr(top, 'ROS', None)}")
|
||||||
|
print(f"CID-keyed: {getattr(top, 'FDSelect', None) is not None}")
|
||||||
|
|
||||||
|
glyphOrder = font.getGlyphOrder()
|
||||||
|
print(f"Glyphs: {len(glyphOrder)}")
|
||||||
|
|
||||||
|
# Use fontTools' built-in otf2ttf if available
|
||||||
|
try:
|
||||||
|
from fontTools.otf2ttf import otf_to_ttf
|
||||||
|
otf_to_ttf(font)
|
||||||
|
font.save(DST_TTF)
|
||||||
|
print(f"Saved TTF: {DST_TTF} ({os.path.getsize(DST_TTF)} bytes)")
|
||||||
|
font.close()
|
||||||
|
return True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Manual conversion using cu2qu
|
||||||
|
print("Using manual CFF->TTF conversion with cu2qu...")
|
||||||
|
|
||||||
|
from fontTools.pens.recordingPen import RecordingPen
|
||||||
|
from fontTools.pens.pointPen import SegmentToPointPen
|
||||||
|
from fontTools import ttLib
|
||||||
|
from fontTools.ttLib.tables._g_l_y_f import Glyph as TTGlyph
|
||||||
|
import struct
|
||||||
|
|
||||||
|
# Get glyph set
|
||||||
|
glyphSet = font.getGlyphSet()
|
||||||
|
|
||||||
|
# Create new glyf table
|
||||||
|
from fontTools.ttLib import newTable
|
||||||
|
|
||||||
|
glyf_table = newTable("glyf")
|
||||||
|
glyf_table.glyphs = {}
|
||||||
|
glyf_table.glyphOrder = glyphOrder
|
||||||
|
|
||||||
|
loca_table = newTable("loca")
|
||||||
|
|
||||||
|
max_error = 1.0 # em-units tolerance for cubic->quadratic
|
||||||
|
|
||||||
|
for gname in glyphOrder:
|
||||||
|
try:
|
||||||
|
ttPen = TTGlyphPen(glyphSet)
|
||||||
|
cu2quPen = Cu2QuPen(ttPen, max_err=max_error, reverse_direction=True)
|
||||||
|
glyphSet[gname].draw(cu2quPen)
|
||||||
|
glyf_table.glyphs[gname] = ttPen.glyph()
|
||||||
|
except Exception as e:
|
||||||
|
# Fallback: empty glyph
|
||||||
|
glyf_table.glyphs[gname] = TTGlyph()
|
||||||
|
|
||||||
|
# Replace CFF with glyf
|
||||||
|
del font["CFF "]
|
||||||
|
if "VORG" in font:
|
||||||
|
del font["VORG"]
|
||||||
|
|
||||||
|
font["glyf"] = glyf_table
|
||||||
|
font["loca"] = loca_table
|
||||||
|
|
||||||
|
# Add required tables for TTF
|
||||||
|
# head table needs indexToLocFormat
|
||||||
|
font["head"].indexToLocFormat = 1 # long format
|
||||||
|
|
||||||
|
# Create maxp for TrueType
|
||||||
|
if "maxp" in font:
|
||||||
|
font["maxp"].version = 0x00010000
|
||||||
|
|
||||||
|
# Update sfntVersion
|
||||||
|
font.sfntVersion = "\x00\x01\x00\x00" # TrueType
|
||||||
|
|
||||||
|
font.save(DST_TTF)
|
||||||
|
print(f"Saved TTF: {DST_TTF} ({os.path.getsize(DST_TTF)} bytes)")
|
||||||
|
font.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def download_and_subset():
|
||||||
|
"""Download NotoSansSC-Regular.ttf and subset it."""
|
||||||
|
import urllib.request
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
from fontTools import subset
|
||||||
|
|
||||||
|
# Google Fonts provides static TTF files
|
||||||
|
url = "https://github.com/notofonts/noto-cjk/raw/main/Sans/SubsetOTF/SC/NotoSansSC-Regular.otf"
|
||||||
|
# Actually, we want TTF. Let's try the variable font approach.
|
||||||
|
# Or better: use google-fonts API for static TTF
|
||||||
|
|
||||||
|
# NotoSansSC static TTF from Google Fonts CDN
|
||||||
|
tmp_font = "/tmp/NotoSansSC-Regular.ttf"
|
||||||
|
|
||||||
|
if not os.path.exists(tmp_font):
|
||||||
|
print(f"Downloading NotoSansSC-Regular.ttf...")
|
||||||
|
url = "https://github.com/notofonts/noto-cjk/raw/main/Sans/OTC/NotoSansCJK-Regular.ttc"
|
||||||
|
# This is a TTC (font collection), too large.
|
||||||
|
# Use the OTF we already have and convert it.
|
||||||
|
return False
|
||||||
|
|
||||||
|
print(f"Using {tmp_font}")
|
||||||
|
font = TTFont(tmp_font)
|
||||||
|
cmap = font.getBestCmap()
|
||||||
|
print(f"Source has {len(cmap)} cmap entries")
|
||||||
|
|
||||||
|
needed = get_needed_codepoints()
|
||||||
|
print(f"Need {len(needed)} CJK codepoints")
|
||||||
|
|
||||||
|
# Subset
|
||||||
|
subsetter = subset.Subsetter()
|
||||||
|
subsetter.populate(unicodes=needed)
|
||||||
|
subsetter.subset(font)
|
||||||
|
|
||||||
|
font.save(DST_TTF)
|
||||||
|
print(f"Saved: {DST_TTF} ({os.path.getsize(DST_TTF)} bytes)")
|
||||||
|
font.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def verify_result():
|
||||||
|
"""Verify the output TTF has glyf outlines and correct characters."""
|
||||||
|
from fontTools.ttLib import TTFont
|
||||||
|
|
||||||
|
font = TTFont(DST_TTF)
|
||||||
|
cmap = font.getBestCmap()
|
||||||
|
|
||||||
|
print(f"\n--- Verification ---")
|
||||||
|
print(f"Format: {font.sfntVersion!r}")
|
||||||
|
print(f"Has glyf: {'glyf' in font}")
|
||||||
|
print(f"Has CFF: {'CFF ' in font}")
|
||||||
|
print(f"Cmap entries: {len(cmap)}")
|
||||||
|
|
||||||
|
# Check key characters
|
||||||
|
test_chars = {
|
||||||
|
"历": 0x5386, "史": 0x53F2, # Chinese: history
|
||||||
|
"概": 0x6982, "述": 0x8FF0, # Chinese: overview
|
||||||
|
"设": 0x8BBE, "置": 0x7F6E, # Chinese: settings
|
||||||
|
}
|
||||||
|
for name, cp in test_chars.items():
|
||||||
|
status = "YES" if cp in cmap else "NO"
|
||||||
|
print(f" {name} (U+{cp:04X}): {status}")
|
||||||
|
|
||||||
|
size = os.path.getsize(DST_TTF)
|
||||||
|
print(f"File size: {size} bytes ({size/1024:.1f} KB)")
|
||||||
|
font.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=== CJK Font CFF -> TTF Converter ===\n")
|
||||||
|
|
||||||
|
if convert_cff_to_ttf():
|
||||||
|
verify_result()
|
||||||
|
else:
|
||||||
|
print("Direct conversion failed, trying download approach...")
|
||||||
|
if download_and_subset():
|
||||||
|
verify_result()
|
||||||
|
else:
|
||||||
|
print("ERROR: Could not convert font")
|
||||||
|
sys.exit(1)
|
||||||
@@ -8,7 +8,7 @@ set -e
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
||||||
APPDIR="${BUILD_DIR}/AppDir"
|
APPDIR="${BUILD_DIR}/AppDir"
|
||||||
VERSION="1.1.1"
|
VERSION="1.2.0"
|
||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
|
|||||||
36
scripts/fix_mojibake.py
Normal file
36
scripts/fix_mojibake.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Fix mojibake en-dash (and other common patterns) in translation JSON files."""
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
LANG_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'res', 'lang')
|
||||||
|
|
||||||
|
# Common mojibake patterns: UTF-8 bytes interpreted as Latin-1
|
||||||
|
MOJIBAKE_FIXES = {
|
||||||
|
'\u00e2\u0080\u0093': '\u2013', # en dash
|
||||||
|
'\u00e2\u0080\u0094': '\u2014', # em dash
|
||||||
|
'\u00e2\u0080\u0099': '\u2019', # right single quote
|
||||||
|
'\u00e2\u0080\u009c': '\u201c', # left double quote
|
||||||
|
'\u00e2\u0080\u009d': '\u201d', # right double quote
|
||||||
|
'\u00e2\u0080\u00a6': '\u2026', # ellipsis
|
||||||
|
}
|
||||||
|
|
||||||
|
total_fixed = 0
|
||||||
|
for path in sorted(glob.glob(os.path.join(LANG_DIR, '*.json'))):
|
||||||
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
|
raw = f.read()
|
||||||
|
|
||||||
|
original = raw
|
||||||
|
for bad, good in MOJIBAKE_FIXES.items():
|
||||||
|
if bad in raw:
|
||||||
|
count = raw.count(bad)
|
||||||
|
raw = raw.replace(bad, good)
|
||||||
|
lang = os.path.basename(path)
|
||||||
|
print(f" {lang}: fixed {count} x {repr(good)}")
|
||||||
|
total_fixed += count
|
||||||
|
|
||||||
|
if raw != original:
|
||||||
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(raw)
|
||||||
|
|
||||||
|
print(f"\nTotal fixes: {total_fixed}")
|
||||||
35
scripts/gen-lite-checkpoints.sh
Executable file
35
scripts/gen-lite-checkpoints.sh
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Generate SDXL lite-wallet mainnet checkpoint entries from a fully-synced dragonxd.
|
||||||
|
# Each entry is (height,"blockhash","serialized_sapling_tree") in checkpoints.rs format.
|
||||||
|
# Fills the 1,770,000 -> tip gap so wallets reseed close to their birthday on rescan,
|
||||||
|
# bounding the (divergence-prone) compact-block replay span. Usage:
|
||||||
|
# scripts/gen-lite-checkpoints.sh [start] [step] > /tmp/new_checkpoints.txt
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CLI=${DRAGONX_CLI:-/home/d/dragonx/src/dragonx-cli}
|
||||||
|
START=${1:-1770000}
|
||||||
|
STEP=${2:-10000}
|
||||||
|
|
||||||
|
tip=$("$CLI" getblockcount)
|
||||||
|
end=$(( (tip / STEP) * STEP ))
|
||||||
|
|
||||||
|
# Sanity: confirm the method reproduces a KNOWN checkpoint tree before trusting it.
|
||||||
|
ref_hash=$("$CLI" getblockhash 1760000 | tr -d '"[:space:]')
|
||||||
|
ref_tree=$("$CLI" getblockmerkletree 1760000 | tr -d '"[:space:]')
|
||||||
|
expect_hash="0000545a45b8d4ee4e4b423cb1ea74d67e3a04c320c6ea2f59ee06c08f91a117"
|
||||||
|
if [ "$ref_hash" != "$expect_hash" ]; then
|
||||||
|
echo "ABORT: getblockhash 1760000 = $ref_hash != known $expect_hash" >&2; exit 1
|
||||||
|
fi
|
||||||
|
echo "# self-check: 1760000 hash matches; tree len=${#ref_tree}" >&2
|
||||||
|
|
||||||
|
n=0
|
||||||
|
h=$START
|
||||||
|
while [ "$h" -le "$end" ]; do
|
||||||
|
hash=$("$CLI" getblockhash "$h" | tr -d '"[:space:]')
|
||||||
|
tree=$("$CLI" getblockmerkletree "$h" | tr -d '"[:space:]')
|
||||||
|
if [ -z "$hash" ] || [ -z "$tree" ]; then echo "ABORT: empty hash/tree at $h" >&2; exit 1; fi
|
||||||
|
printf '\t(%s,"%s",\n\t\t"%s"\n\t),\n' "$h" "$hash" "$tree"
|
||||||
|
n=$((n+1))
|
||||||
|
h=$((h+STEP))
|
||||||
|
done
|
||||||
|
echo "# generated $n checkpoints from $START to $end (tip=$tip)" >&2
|
||||||
@@ -1,645 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate German (de) translations for ObsidianDragon wallet."""
|
|
||||||
import json, os
|
|
||||||
|
|
||||||
translations = {
|
|
||||||
"24h_change": "24h Änderung",
|
|
||||||
"24h_volume": "24h Volumen",
|
|
||||||
"about": "Über",
|
|
||||||
"about_block_explorer": "Block-Explorer",
|
|
||||||
"about_block_height": "Blockhöhe:",
|
|
||||||
"about_build_date": "Erstellungsdatum:",
|
|
||||||
"about_build_type": "Build-Typ:",
|
|
||||||
"about_chain": "Chain:",
|
|
||||||
"about_connections": "Verbindungen:",
|
|
||||||
"about_credits": "Danksagungen",
|
|
||||||
"about_daemon": "Daemon:",
|
|
||||||
"about_debug": "Debug",
|
|
||||||
"about_dragonx": "Über ObsidianDragon",
|
|
||||||
"about_edition": "ImGui Edition",
|
|
||||||
"about_github": "GitHub",
|
|
||||||
"about_imgui": "ImGui:",
|
|
||||||
"about_license": "Lizenz",
|
|
||||||
"about_license_text": "Diese Software wird unter der GNU General Public License v3 (GPLv3) veröffentlicht. Sie dürfen diese Software gemäß den Lizenzbedingungen frei verwenden, modifizieren und verbreiten.",
|
|
||||||
"about_peers_count": "%zu Peers",
|
|
||||||
"about_release": "Release",
|
|
||||||
"about_title": "Über ObsidianDragon",
|
|
||||||
"about_version": "Version:",
|
|
||||||
"about_website": "Webseite",
|
|
||||||
"acrylic": "Acryl",
|
|
||||||
"add": "Hinzufügen",
|
|
||||||
"address": "Adresse",
|
|
||||||
"address_book_add": "Adresse hinzufügen",
|
|
||||||
"address_book_add_new": "Neue hinzufügen",
|
|
||||||
"address_book_added": "Adresse zum Buch hinzugefügt",
|
|
||||||
"address_book_count": "%zu Adressen gespeichert",
|
|
||||||
"address_book_deleted": "Eintrag gelöscht",
|
|
||||||
"address_book_edit": "Adresse bearbeiten",
|
|
||||||
"address_book_empty": "Keine gespeicherten Adressen. Klicken Sie auf 'Neue hinzufügen', um eine hinzuzufügen.",
|
|
||||||
"address_book_exists": "Adresse existiert bereits im Buch",
|
|
||||||
"address_book_title": "Adressbuch",
|
|
||||||
"address_book_update_failed": "Aktualisierung fehlgeschlagen - Adresse könnte doppelt sein",
|
|
||||||
"address_book_updated": "Adresse aktualisiert",
|
|
||||||
"address_copied": "Adresse in Zwischenablage kopiert",
|
|
||||||
"address_details": "Adressdetails",
|
|
||||||
"address_label": "Adresse:",
|
|
||||||
"address_upper": "ADRESSE",
|
|
||||||
"address_url": "Adress-URL",
|
|
||||||
"addresses_appear_here": "Ihre Empfangsadressen erscheinen hier, sobald Sie verbunden sind.",
|
|
||||||
"advanced": "ERWEITERT",
|
|
||||||
"all_filter": "Alle",
|
|
||||||
"allow_custom_fees": "Benutzerdefinierte Gebühren erlauben",
|
|
||||||
"amount": "Betrag",
|
|
||||||
"amount_details": "BETRAGSDETAILS",
|
|
||||||
"amount_exceeds_balance": "Betrag übersteigt Guthaben",
|
|
||||||
"amount_label": "Betrag:",
|
|
||||||
"appearance": "ERSCHEINUNGSBILD",
|
|
||||||
"auto_shield": "Mining automatisch abschirmen",
|
|
||||||
"available": "Verfügbar",
|
|
||||||
"backup_backing_up": "Sicherung läuft...",
|
|
||||||
"backup_create": "Sicherung erstellen",
|
|
||||||
"backup_created": "Wallet-Sicherung erstellt",
|
|
||||||
"backup_data": "SICHERUNG & DATEN",
|
|
||||||
"backup_description": "Erstellen Sie eine Sicherung Ihrer wallet.dat-Datei. Diese Datei enthält alle Ihre privaten Schlüssel und den Transaktionsverlauf. Bewahren Sie die Sicherung an einem sicheren Ort auf.",
|
|
||||||
"backup_destination": "Sicherungsziel:",
|
|
||||||
"backup_tip_external": "Speichern Sie Sicherungen auf externen Laufwerken oder Cloud-Speicher",
|
|
||||||
"backup_tip_multiple": "Erstellen Sie mehrere Sicherungen an verschiedenen Orten",
|
|
||||||
"backup_tip_test": "Testen Sie regelmäßig die Wiederherstellung aus der Sicherung",
|
|
||||||
"backup_tips": "Tipps:",
|
|
||||||
"backup_title": "Wallet sichern",
|
|
||||||
"backup_wallet": "Wallet sichern...",
|
|
||||||
"backup_wallet_not_found": "Warnung: wallet.dat nicht am erwarteten Speicherort gefunden",
|
|
||||||
"balance": "Guthaben",
|
|
||||||
"balance_layout": "Guthaben-Layout",
|
|
||||||
"ban": "Sperren",
|
|
||||||
"banned_peers": "Gesperrte Peers",
|
|
||||||
"block": "Block",
|
|
||||||
"block_bits": "Bits:",
|
|
||||||
"block_click_next": "Klicken für nächsten Block",
|
|
||||||
"block_click_prev": "Klicken für vorherigen Block",
|
|
||||||
"block_explorer": "Block-Explorer",
|
|
||||||
"block_get_info": "Block-Info abrufen",
|
|
||||||
"block_hash": "Block-Hash:",
|
|
||||||
"block_height": "Blockhöhe:",
|
|
||||||
"block_info_title": "Block-Informationen",
|
|
||||||
"block_merkle_root": "Merkle-Root:",
|
|
||||||
"block_nav_next": "Weiter >>",
|
|
||||||
"block_nav_prev": "<< Zurück",
|
|
||||||
"block_next": "Nächster Block:",
|
|
||||||
"block_previous": "Vorheriger Block:",
|
|
||||||
"block_size": "Größe:",
|
|
||||||
"block_timestamp": "Zeitstempel:",
|
|
||||||
"block_transactions": "Transaktionen:",
|
|
||||||
"blockchain_syncing": "Blockchain synchronisiert (%.1f%%)... Guthaben könnten ungenau sein.",
|
|
||||||
"cancel": "Abbrechen",
|
|
||||||
"characters": "Zeichen",
|
|
||||||
"clear": "Leeren",
|
|
||||||
"clear_all_bans": "Alle Sperren aufheben",
|
|
||||||
"clear_form_confirm": "Alle Formularfelder leeren?",
|
|
||||||
"clear_request": "Anfrage leeren",
|
|
||||||
"click_copy_address": "Klicken zum Kopieren der Adresse",
|
|
||||||
"click_copy_uri": "Klicken zum Kopieren der URI",
|
|
||||||
"close": "Schließen",
|
|
||||||
"conf_count": "%d Best.",
|
|
||||||
"confirm_and_send": "Bestätigen & Senden",
|
|
||||||
"confirm_send": "Senden bestätigen",
|
|
||||||
"confirm_transaction": "Transaktion bestätigen",
|
|
||||||
"confirmations": "Bestätigungen",
|
|
||||||
"confirmations_display": "%d Bestätigungen | %s",
|
|
||||||
"confirmed": "Bestätigt",
|
|
||||||
"connected": "Verbunden",
|
|
||||||
"connected_peers": "Verbundene Peers",
|
|
||||||
"connecting": "Verbinde...",
|
|
||||||
"console": "Konsole",
|
|
||||||
"console_auto_scroll": "Automatisch scrollen",
|
|
||||||
"console_available_commands": "Verfügbare Befehle:",
|
|
||||||
"console_capturing_output": "Erfasse Daemon-Ausgabe...",
|
|
||||||
"console_clear": "Leeren",
|
|
||||||
"console_clear_console": "Konsole leeren",
|
|
||||||
"console_cleared": "Konsole geleert",
|
|
||||||
"console_click_commands": "Befehle oben klicken zum Einfügen",
|
|
||||||
"console_click_insert": "Klicken zum Einfügen",
|
|
||||||
"console_click_insert_params": "Klicken zum Einfügen mit Parametern",
|
|
||||||
"console_close": "Schließen",
|
|
||||||
"console_commands": "Befehle",
|
|
||||||
"console_common_rpc": "Häufige RPC-Befehle:",
|
|
||||||
"console_completions": "Vervollständigungen:",
|
|
||||||
"console_connected": "Verbunden mit Daemon",
|
|
||||||
"console_copy_all": "Alles kopieren",
|
|
||||||
"console_copy_selected": "Kopieren",
|
|
||||||
"console_daemon": "Daemon",
|
|
||||||
"console_daemon_error": "Daemon-Fehler!",
|
|
||||||
"console_daemon_started": "Daemon gestartet",
|
|
||||||
"console_daemon_stopped": "Daemon gestoppt",
|
|
||||||
"console_disconnected": "Vom Daemon getrennt",
|
|
||||||
"console_errors": "Fehler",
|
|
||||||
"console_filter_hint": "Ausgabe filtern...",
|
|
||||||
"console_help_clear": " clear - Konsole leeren",
|
|
||||||
"console_help_getbalance": " getbalance - Transparentes Guthaben anzeigen",
|
|
||||||
"console_help_getblockcount": " getblockcount - Aktuelle Blockhöhe anzeigen",
|
|
||||||
"console_help_getinfo": " getinfo - Knoteninformationen anzeigen",
|
|
||||||
"console_help_getmininginfo": " getmininginfo - Mining-Status anzeigen",
|
|
||||||
"console_help_getpeerinfo": " getpeerinfo - Verbundene Peers anzeigen",
|
|
||||||
"console_help_gettotalbalance": " gettotalbalance - Gesamtguthaben anzeigen",
|
|
||||||
"console_help_help": " help - Diese Hilfe anzeigen",
|
|
||||||
"console_help_setgenerate": " setgenerate - Mining steuern",
|
|
||||||
"console_help_stop": " stop - Daemon stoppen",
|
|
||||||
"console_line_count": "%zu Zeilen",
|
|
||||||
"console_new_lines": "%d neue Zeilen",
|
|
||||||
"console_no_daemon": "Kein Daemon",
|
|
||||||
"console_not_connected": "Fehler: Nicht mit Daemon verbunden",
|
|
||||||
"console_rpc_reference": "RPC-Befehlsreferenz",
|
|
||||||
"console_scanline": "Konsolen-Scanline",
|
|
||||||
"console_search_commands": "Befehle suchen...",
|
|
||||||
"console_select_all": "Alles auswählen",
|
|
||||||
"console_show_daemon_output": "Daemon-Ausgabe anzeigen",
|
|
||||||
"console_show_errors_only": "Nur Fehler anzeigen",
|
|
||||||
"console_show_rpc_ref": "RPC-Befehlsreferenz anzeigen",
|
|
||||||
"console_showing_lines": "Zeige %zu von %zu Zeilen",
|
|
||||||
"console_starting_node": "Knoten wird gestartet...",
|
|
||||||
"console_status_error": "Fehler",
|
|
||||||
"console_status_running": "Läuft",
|
|
||||||
"console_status_starting": "Startet",
|
|
||||||
"console_status_stopped": "Gestoppt",
|
|
||||||
"console_status_stopping": "Stoppt",
|
|
||||||
"console_status_unknown": "Unbekannt",
|
|
||||||
"console_tab_completion": "Tab zur Vervollständigung",
|
|
||||||
"console_type_help": "Geben Sie 'help' ein für verfügbare Befehle",
|
|
||||||
"console_welcome": "Willkommen bei ObsidianDragon Konsole",
|
|
||||||
"console_zoom_in": "Vergrößern",
|
|
||||||
"console_zoom_out": "Verkleinern",
|
|
||||||
"copy": "Kopieren",
|
|
||||||
"copy_address": "Vollständige Adresse kopieren",
|
|
||||||
"copy_error": "Fehler kopieren",
|
|
||||||
"copy_to_clipboard": "In Zwischenablage kopieren",
|
|
||||||
"copy_txid": "TxID kopieren",
|
|
||||||
"copy_uri": "URI kopieren",
|
|
||||||
"current_price": "Aktueller Preis",
|
|
||||||
"custom_fees": "Benutzerdefinierte Gebühren",
|
|
||||||
"dark": "Dunkel",
|
|
||||||
"date": "Datum",
|
|
||||||
"date_label": "Datum:",
|
|
||||||
"delete": "Löschen",
|
|
||||||
"difficulty": "Schwierigkeit",
|
|
||||||
"disconnected": "Getrennt",
|
|
||||||
"dismiss": "Verwerfen",
|
|
||||||
"display": "Anzeige",
|
|
||||||
"dragonx_green": "DragonX (Grün)",
|
|
||||||
"edit": "Bearbeiten",
|
|
||||||
"error": "Fehler",
|
|
||||||
"est_time_to_block": "Gesch. Zeit bis Block",
|
|
||||||
"exit": "Beenden",
|
|
||||||
"explorer": "EXPLORER",
|
|
||||||
"export": "Exportieren",
|
|
||||||
"export_csv": "CSV exportieren",
|
|
||||||
"export_keys_btn": "Schlüssel exportieren",
|
|
||||||
"export_keys_danger": "ACHTUNG: Dies exportiert ALLE privaten Schlüssel aus Ihrer Wallet! Jeder mit Zugriff auf diese Datei kann Ihre Gelder stehlen. Sicher aufbewahren und nach Gebrauch löschen.",
|
|
||||||
"export_keys_include_t": "T-Adressen einschließen (transparent)",
|
|
||||||
"export_keys_include_z": "Z-Adressen einschließen (abgeschirmt)",
|
|
||||||
"export_keys_options": "Export-Optionen:",
|
|
||||||
"export_keys_success": "Schlüssel erfolgreich exportiert",
|
|
||||||
"export_keys_title": "Alle privaten Schlüssel exportieren",
|
|
||||||
"export_private_key": "Privaten Schlüssel exportieren",
|
|
||||||
"export_tx_count": "%zu Transaktionen als CSV exportieren.",
|
|
||||||
"export_tx_file_fail": "CSV-Datei konnte nicht erstellt werden",
|
|
||||||
"export_tx_none": "Keine Transaktionen zum Exportieren",
|
|
||||||
"export_tx_success": "Transaktionen erfolgreich exportiert",
|
|
||||||
"export_tx_title": "Transaktionen als CSV exportieren",
|
|
||||||
"export_viewing_key": "Betrachtungsschlüssel exportieren",
|
|
||||||
"failed_create_shielded": "Abgeschirmte Adresse konnte nicht erstellt werden",
|
|
||||||
"failed_create_transparent": "Transparente Adresse konnte nicht erstellt werden",
|
|
||||||
"fee": "Gebühr",
|
|
||||||
"fee_high": "Hoch",
|
|
||||||
"fee_label": "Gebühr:",
|
|
||||||
"fee_low": "Niedrig",
|
|
||||||
"fee_normal": "Normal",
|
|
||||||
"fetch_prices": "Preise abrufen",
|
|
||||||
"file": "Datei",
|
|
||||||
"file_save_location": "Datei wird gespeichert in: ~/.config/ObsidianDragon/",
|
|
||||||
"font_scale": "Schriftgröße",
|
|
||||||
"from": "Von",
|
|
||||||
"from_upper": "VON",
|
|
||||||
"full_details": "Alle Details",
|
|
||||||
"general": "Allgemein",
|
|
||||||
"go_to_receive": "Zum Empfangen",
|
|
||||||
"height": "Höhe",
|
|
||||||
"help": "Hilfe",
|
|
||||||
"hide": "Ausblenden",
|
|
||||||
"history": "Verlauf",
|
|
||||||
"immature_type": "Unreif",
|
|
||||||
"import": "Importieren",
|
|
||||||
"import_key_btn": "Schlüssel importieren",
|
|
||||||
"import_key_formats": "Unterstützte Schlüsselformate:",
|
|
||||||
"import_key_full_rescan": "(0 = vollständiger Rescan)",
|
|
||||||
"import_key_label": "Privater Schlüssel:",
|
|
||||||
"import_key_no_valid": "Keine gültigen Schlüssel in der Eingabe gefunden",
|
|
||||||
"import_key_rescan": "Blockchain nach Import neu scannen",
|
|
||||||
"import_key_start_height": "Starthöhe:",
|
|
||||||
"import_key_success": "Schlüssel erfolgreich importiert",
|
|
||||||
"import_key_t_format": "T-Adresse WIF private Schlüssel",
|
|
||||||
"import_key_title": "Privaten Schlüssel importieren",
|
|
||||||
"import_key_tooltip": "Geben Sie einen oder mehrere private Schlüssel ein, einen pro Zeile.\nUnterstützt sowohl z-Adresse als auch t-Adresse Schlüssel.\nZeilen die mit # beginnen werden als Kommentare behandelt.",
|
|
||||||
"import_key_warning": "Warnung: Teilen Sie niemals Ihre privaten Schlüssel! Das Importieren von Schlüsseln aus nicht vertrauenswürdigen Quellen kann Ihr Wallet gefährden.",
|
|
||||||
"import_key_z_format": "Z-Adresse Ausgabeschlüssel (secret-extended-key-...)",
|
|
||||||
"import_private_key": "Privaten Schlüssel importieren...",
|
|
||||||
"invalid_address": "Ungültiges Adressformat",
|
|
||||||
"ip_address": "IP-Adresse",
|
|
||||||
"keep": "Behalten",
|
|
||||||
"keep_daemon": "Daemon weiterlaufen lassen",
|
|
||||||
"key_export_fetching": "Schlüssel wird aus Wallet abgerufen...",
|
|
||||||
"key_export_private_key": "Privater Schlüssel:",
|
|
||||||
"key_export_private_warning": "Halten Sie diesen Schlüssel GEHEIM! Jeder mit diesem Schlüssel kann Ihre Gelder ausgeben. Teilen Sie ihn niemals online oder mit nicht vertrauenswürdigen Parteien.",
|
|
||||||
"key_export_reveal": "Schlüssel anzeigen",
|
|
||||||
"key_export_viewing_key": "Betrachtungsschlüssel:",
|
|
||||||
"key_export_viewing_warning": "Dieser Betrachtungsschlüssel ermöglicht es anderen, Ihre eingehenden Transaktionen und Ihr Guthaben zu sehen, aber NICHT Ihre Gelder auszugeben. Teilen Sie ihn nur mit vertrauenswürdigen Parteien.",
|
|
||||||
"label": "Bezeichnung:",
|
|
||||||
"language": "Sprache",
|
|
||||||
"light": "Hell",
|
|
||||||
"loading": "Laden...",
|
|
||||||
"loading_addresses": "Adressen werden geladen...",
|
|
||||||
"local_hashrate": "Lokale Hashrate",
|
|
||||||
"low_spec_mode": "Energiesparmodus",
|
|
||||||
"market": "Markt",
|
|
||||||
"market_12h": "12h",
|
|
||||||
"market_18h": "18h",
|
|
||||||
"market_24h": "24h",
|
|
||||||
"market_24h_volume": "24H VOLUMEN",
|
|
||||||
"market_6h": "6h",
|
|
||||||
"market_attribution": "Preisdaten von NonKYC",
|
|
||||||
"market_btc_price": "BTC PREIS",
|
|
||||||
"market_cap": "Marktkapitalisierung",
|
|
||||||
"market_no_history": "Kein Preisverlauf verfügbar",
|
|
||||||
"market_no_price": "Keine Preisdaten",
|
|
||||||
"market_now": "Jetzt",
|
|
||||||
"market_pct_shielded": "%.0f%% Abgeschirmt",
|
|
||||||
"market_portfolio": "PORTFOLIO",
|
|
||||||
"market_price_unavailable": "Preisdaten nicht verfügbar",
|
|
||||||
"market_refresh_price": "Preisdaten aktualisieren",
|
|
||||||
"market_trade_on": "Handeln auf %s",
|
|
||||||
"mature": "Reif",
|
|
||||||
"max": "Max",
|
|
||||||
"memo": "Memo (optional, verschlüsselt)",
|
|
||||||
"memo_label": "Memo:",
|
|
||||||
"memo_optional": "MEMO (OPTIONAL)",
|
|
||||||
"memo_upper": "MEMO",
|
|
||||||
"memo_z_only": "Hinweis: Memos sind nur beim Senden an abgeschirmte (z) Adressen verfügbar",
|
|
||||||
"merge_description": "Mehrere UTXOs zu einer einzelnen abgeschirmten Adresse zusammenführen. Dies kann die Wallet-Größe reduzieren und die Privatsphäre verbessern.",
|
|
||||||
"merge_funds": "Gelder zusammenführen",
|
|
||||||
"merge_started": "Zusammenführung gestartet",
|
|
||||||
"merge_title": "An Adresse zusammenführen",
|
|
||||||
"mine_when_idle": "Im Leerlauf minen",
|
|
||||||
"mined": "gemined",
|
|
||||||
"mined_filter": "Gemined",
|
|
||||||
"mined_type": "Gemined",
|
|
||||||
"mined_upper": "GEMINED",
|
|
||||||
"miner_fee": "Miner-Gebühr",
|
|
||||||
"mining": "Mining",
|
|
||||||
"mining_active": "Aktiv",
|
|
||||||
"mining_address_copied": "Mining-Adresse kopiert",
|
|
||||||
"mining_all_time": "Gesamt",
|
|
||||||
"mining_already_saved": "Pool-URL bereits gespeichert",
|
|
||||||
"mining_block_copied": "Block-Hash kopiert",
|
|
||||||
"mining_chart_1m_ago": "vor 1m",
|
|
||||||
"mining_chart_5m_ago": "vor 5m",
|
|
||||||
"mining_chart_now": "Jetzt",
|
|
||||||
"mining_click": "Klicken",
|
|
||||||
"mining_click_copy_address": "Klicken zum Kopieren der Adresse",
|
|
||||||
"mining_click_copy_block": "Klicken zum Kopieren des Block-Hash",
|
|
||||||
"mining_click_copy_difficulty": "Klicken zum Kopieren der Schwierigkeit",
|
|
||||||
"mining_connected": "Verbunden",
|
|
||||||
"mining_connecting": "Verbinde...",
|
|
||||||
"mining_control": "Mining-Steuerung",
|
|
||||||
"mining_difficulty_copied": "Schwierigkeit kopiert",
|
|
||||||
"mining_est_block": "Gesch. Block",
|
|
||||||
"mining_est_daily": "Gesch. täglich",
|
|
||||||
"mining_filter_all": "Alle",
|
|
||||||
"mining_filter_tip_all": "Alle Einnahmen anzeigen",
|
|
||||||
"mining_filter_tip_pool": "Nur Pool-Einnahmen anzeigen",
|
|
||||||
"mining_filter_tip_solo": "Nur Solo-Einnahmen anzeigen",
|
|
||||||
"mining_idle_off_tooltip": "Leerlauf-Mining aktivieren",
|
|
||||||
"mining_idle_on_tooltip": "Leerlauf-Mining deaktivieren",
|
|
||||||
"mining_local_hashrate": "Lokale Hashrate",
|
|
||||||
"mining_mine": "Minen",
|
|
||||||
"mining_mining_addr": "Mining-Adr.",
|
|
||||||
"mining_network": "Netzwerk",
|
|
||||||
"mining_no_blocks_yet": "Noch keine Blöcke gefunden",
|
|
||||||
"mining_no_payouts_yet": "Noch keine Pool-Auszahlungen",
|
|
||||||
"mining_no_saved_addresses": "Keine gespeicherten Adressen",
|
|
||||||
"mining_no_saved_pools": "Keine gespeicherten Pools",
|
|
||||||
"mining_off": "Mining ist AUS",
|
|
||||||
"mining_on": "Mining ist AN",
|
|
||||||
"mining_open_in_explorer": "Im Explorer öffnen",
|
|
||||||
"mining_payout_address": "Auszahlungsadresse",
|
|
||||||
"mining_payout_tooltip": "Adresse für Mining-Belohnungen",
|
|
||||||
"mining_pool": "Pool",
|
|
||||||
"mining_pool_hashrate": "Pool-Hashrate",
|
|
||||||
"mining_pool_url": "Pool-URL",
|
|
||||||
"mining_recent_blocks": "LETZTE BLÖCKE",
|
|
||||||
"mining_recent_payouts": "LETZTE POOL-AUSZAHLUNGEN",
|
|
||||||
"mining_remove": "Entfernen",
|
|
||||||
"mining_reset_defaults": "Standardwerte zurücksetzen",
|
|
||||||
"mining_save_payout_address": "Auszahlungsadresse speichern",
|
|
||||||
"mining_save_pool_url": "Pool-URL speichern",
|
|
||||||
"mining_saved_addresses": "Gespeicherte Adressen:",
|
|
||||||
"mining_saved_pools": "Gespeicherte Pools:",
|
|
||||||
"mining_shares": "Shares",
|
|
||||||
"mining_show_chart": "Diagramm",
|
|
||||||
"mining_show_log": "Protokoll",
|
|
||||||
"mining_solo": "Solo",
|
|
||||||
"mining_starting": "Startet...",
|
|
||||||
"mining_starting_tooltip": "Miner startet...",
|
|
||||||
"mining_statistics": "Mining-Statistiken",
|
|
||||||
"mining_stop": "Stopp",
|
|
||||||
"mining_stop_solo_for_pool": "Solo-Mining stoppen bevor Pool-Mining gestartet wird",
|
|
||||||
"mining_stop_solo_for_pool_settings": "Solo-Mining stoppen um Pool-Einstellungen zu ändern",
|
|
||||||
"mining_stopping": "Stoppt...",
|
|
||||||
"mining_stopping_tooltip": "Miner stoppt...",
|
|
||||||
"mining_syncing_tooltip": "Blockchain synchronisiert...",
|
|
||||||
"mining_threads": "Mining-Threads",
|
|
||||||
"mining_to_save": "zum Speichern",
|
|
||||||
"mining_today": "Heute",
|
|
||||||
"mining_uptime": "Laufzeit",
|
|
||||||
"mining_yesterday": "Gestern",
|
|
||||||
"network": "Netzwerk",
|
|
||||||
"network_fee": "NETZWERKGEBÜHR",
|
|
||||||
"network_hashrate": "Netzwerk-Hashrate",
|
|
||||||
"new": "+ Neu",
|
|
||||||
"new_shielded_created": "Neue abgeschirmte Adresse erstellt",
|
|
||||||
"new_t_address": "Neue T-Adresse",
|
|
||||||
"new_t_transparent": "Neue t-Adresse (Transparent)",
|
|
||||||
"new_transparent_created": "Neue transparente Adresse erstellt",
|
|
||||||
"new_z_address": "Neue Z-Adresse",
|
|
||||||
"new_z_shielded": "Neue z-Adresse (Abgeschirmt)",
|
|
||||||
"no_addresses": "Keine Adressen gefunden. Erstellen Sie eine mit den Schaltflächen oben.",
|
|
||||||
"no_addresses_available": "Keine Adressen verfügbar",
|
|
||||||
"no_addresses_match": "Keine Adressen passen zum Filter",
|
|
||||||
"no_addresses_with_balance": "Keine Adressen mit Guthaben",
|
|
||||||
"no_matching": "Keine passenden Transaktionen",
|
|
||||||
"no_recent_receives": "Keine kürzlichen Empfänge",
|
|
||||||
"no_recent_sends": "Keine kürzlichen Sendungen",
|
|
||||||
"no_transactions": "Keine Transaktionen gefunden",
|
|
||||||
"node": "KNOTEN",
|
|
||||||
"node_security": "KNOTEN & SICHERHEIT",
|
|
||||||
"noise": "Rauschen",
|
|
||||||
"not_connected": "Nicht mit Daemon verbunden...",
|
|
||||||
"not_connected_to_daemon": "Nicht mit Daemon verbunden",
|
|
||||||
"notes": "Notizen",
|
|
||||||
"notes_optional": "Notizen (optional):",
|
|
||||||
"output_filename": "Ausgabedateiname:",
|
|
||||||
"overview": "Übersicht",
|
|
||||||
"paste": "Einfügen",
|
|
||||||
"paste_from_clipboard": "Aus Zwischenablage einfügen",
|
|
||||||
"pay_from": "Zahlen von",
|
|
||||||
"payment_request": "ZAHLUNGSANFRAGE",
|
|
||||||
"payment_request_copied": "Zahlungsanfrage kopiert",
|
|
||||||
"payment_uri_copied": "Zahlungs-URI kopiert",
|
|
||||||
"peers": "Peers",
|
|
||||||
"peers_avg_ping": "Durchschn. Ping",
|
|
||||||
"peers_ban_24h": "Peer 24h sperren",
|
|
||||||
"peers_ban_score": "Sperr-Score: %d",
|
|
||||||
"peers_banned": "Gesperrt",
|
|
||||||
"peers_banned_count": "Gesperrt: %d",
|
|
||||||
"peers_best_block": "Bester Block",
|
|
||||||
"peers_blockchain": "BLOCKCHAIN",
|
|
||||||
"peers_blocks": "Blöcke",
|
|
||||||
"peers_blocks_left": "%d Blöcke übrig",
|
|
||||||
"peers_clear_all_bans": "Alle Sperren aufheben",
|
|
||||||
"peers_click_copy": "Klicken zum Kopieren",
|
|
||||||
"peers_connected": "Verbunden",
|
|
||||||
"peers_connected_count": "Verbunden: %d",
|
|
||||||
"peers_copy_ip": "IP kopieren",
|
|
||||||
"peers_dir_in": "Ein",
|
|
||||||
"peers_dir_out": "Aus",
|
|
||||||
"peers_hash_copied": "Hash kopiert",
|
|
||||||
"peers_hashrate": "Hashrate",
|
|
||||||
"peers_in_out": "Ein/Aus",
|
|
||||||
"peers_longest": "Längste",
|
|
||||||
"peers_longest_chain": "Längste Chain",
|
|
||||||
"peers_memory": "Speicher",
|
|
||||||
"peers_no_banned": "Keine gesperrten Peers",
|
|
||||||
"peers_no_connected": "Keine verbundenen Peers",
|
|
||||||
"peers_no_tls": "Kein TLS",
|
|
||||||
"peers_notarized": "Notarisiert",
|
|
||||||
"peers_p2p_port": "P2P-Port",
|
|
||||||
"peers_protocol": "Protokoll",
|
|
||||||
"peers_received": "Empfangen",
|
|
||||||
"peers_refresh": "Aktualisieren",
|
|
||||||
"peers_refresh_tooltip": "Peer-Liste aktualisieren",
|
|
||||||
"peers_refreshing": "Aktualisiere...",
|
|
||||||
"peers_sent": "Gesendet",
|
|
||||||
"peers_tt_id": "ID: %d",
|
|
||||||
"peers_tt_received": "Empfangen: %s",
|
|
||||||
"peers_tt_sent": "Gesendet: %s",
|
|
||||||
"peers_tt_services": "Dienste: %s",
|
|
||||||
"peers_tt_start_height": "Starthöhe: %d",
|
|
||||||
"peers_tt_synced": "Synchronisiert H/B: %d/%d",
|
|
||||||
"peers_tt_tls_cipher": "TLS: %s",
|
|
||||||
"peers_unban": "Entsperren",
|
|
||||||
"peers_upper": "PEERS",
|
|
||||||
"peers_version": "Version",
|
|
||||||
"pending": "Ausstehend",
|
|
||||||
"ping": "Ping",
|
|
||||||
"price_chart": "Preisdiagramm",
|
|
||||||
"qr_code": "QR-Code",
|
|
||||||
"qr_failed": "QR-Code konnte nicht generiert werden",
|
|
||||||
"qr_title": "QR-Code",
|
|
||||||
"qr_unavailable": "QR nicht verfügbar",
|
|
||||||
"receive": "Empfangen",
|
|
||||||
"received": "empfangen",
|
|
||||||
"received_filter": "Empfangen",
|
|
||||||
"received_label": "Empfangen",
|
|
||||||
"received_upper": "EMPFANGEN",
|
|
||||||
"receiving_addresses": "Ihre Empfangsadressen",
|
|
||||||
"recent_received": "KÜRZLICH EMPFANGEN",
|
|
||||||
"recent_sends": "KÜRZLICH GESENDET",
|
|
||||||
"recipient": "EMPFÄNGER",
|
|
||||||
"recv_type": "Empf.",
|
|
||||||
"refresh": "Aktualisieren",
|
|
||||||
"refresh_now": "Jetzt aktualisieren",
|
|
||||||
"report_bug": "Fehler melden",
|
|
||||||
"request_amount": "Betrag (optional):",
|
|
||||||
"request_copy_uri": "URI kopieren",
|
|
||||||
"request_description": "Erstellen Sie eine Zahlungsanfrage, die andere scannen oder kopieren können. Der QR-Code enthält Ihre Adresse und optionalen Betrag/Memo.",
|
|
||||||
"request_label": "Bezeichnung (optional):",
|
|
||||||
"request_memo": "Memo (optional):",
|
|
||||||
"request_payment": "Zahlung anfordern",
|
|
||||||
"request_payment_uri": "Zahlungs-URI:",
|
|
||||||
"request_receive_address": "Empfangsadresse:",
|
|
||||||
"request_select_address": "Adresse auswählen...",
|
|
||||||
"request_shielded_addrs": "-- Abgeschirmte Adressen --",
|
|
||||||
"request_title": "Zahlung anfordern",
|
|
||||||
"request_transparent_addrs": "-- Transparente Adressen --",
|
|
||||||
"request_uri_copied": "Zahlungs-URI in Zwischenablage kopiert",
|
|
||||||
"rescan": "Neu scannen",
|
|
||||||
"reset_to_defaults": "Standardwerte zurücksetzen",
|
|
||||||
"review_send": "Senden prüfen",
|
|
||||||
"rpc_host": "RPC-Host",
|
|
||||||
"rpc_pass": "Passwort",
|
|
||||||
"rpc_port": "Port",
|
|
||||||
"rpc_user": "Benutzername",
|
|
||||||
"save": "Speichern",
|
|
||||||
"save_settings": "Einstellungen speichern",
|
|
||||||
"save_z_transactions": "Z-Tx in Tx-Liste speichern",
|
|
||||||
"search_placeholder": "Suchen...",
|
|
||||||
"security": "SICHERHEIT",
|
|
||||||
"select_address": "Adresse auswählen...",
|
|
||||||
"select_receiving_address": "Empfangsadresse auswählen...",
|
|
||||||
"select_source_address": "Quelladresse auswählen...",
|
|
||||||
"send": "Senden",
|
|
||||||
"send_amount": "Betrag",
|
|
||||||
"send_amount_details": "BETRAGSDETAILS",
|
|
||||||
"send_amount_upper": "BETRAG",
|
|
||||||
"send_clear_fields": "Alle Formularfelder leeren?",
|
|
||||||
"send_copy_error": "Fehler kopieren",
|
|
||||||
"send_dismiss": "Verwerfen",
|
|
||||||
"send_error_copied": "Fehler in Zwischenablage kopiert",
|
|
||||||
"send_error_prefix": "Fehler: %s",
|
|
||||||
"send_exceeds_available": "Übersteigt verfügbar (%.8f)",
|
|
||||||
"send_fee": "Gebühr",
|
|
||||||
"send_fee_high": "Hoch",
|
|
||||||
"send_fee_low": "Niedrig",
|
|
||||||
"send_fee_normal": "Normal",
|
|
||||||
"send_form_restored": "Formular wiederhergestellt",
|
|
||||||
"send_from_this_address": "Von dieser Adresse senden",
|
|
||||||
"send_go_to_receive": "Zum Empfangen",
|
|
||||||
"send_keep": "Behalten",
|
|
||||||
"send_network_fee": "NETZWERKGEBÜHR",
|
|
||||||
"send_no_balance": "Kein Guthaben",
|
|
||||||
"send_no_recent": "Keine kürzlichen Sendungen",
|
|
||||||
"send_recent_sends": "KÜRZLICH GESENDET",
|
|
||||||
"send_recipient": "EMPFÄNGER",
|
|
||||||
"send_select_source": "Quelladresse auswählen...",
|
|
||||||
"send_sending_from": "SENDEN VON",
|
|
||||||
"send_submitting": "Transaktion wird übermittelt...",
|
|
||||||
"send_switch_to_receive": "Wechseln Sie zu Empfangen, um Ihre Adresse zu erhalten und Gelder zu empfangen.",
|
|
||||||
"send_to": "Senden an",
|
|
||||||
"send_tooltip_enter_amount": "Geben Sie einen Betrag zum Senden ein",
|
|
||||||
"send_tooltip_exceeds_balance": "Betrag übersteigt verfügbares Guthaben",
|
|
||||||
"send_tooltip_in_progress": "Transaktion bereits in Bearbeitung",
|
|
||||||
"send_tooltip_invalid_address": "Geben Sie eine gültige Empfängeradresse ein",
|
|
||||||
"send_tooltip_not_connected": "Nicht mit Daemon verbunden",
|
|
||||||
"send_tooltip_select_source": "Wählen Sie zuerst eine Quelladresse",
|
|
||||||
"send_tooltip_syncing": "Warten Sie auf die Blockchain-Synchronisierung",
|
|
||||||
"send_total": "Gesamt",
|
|
||||||
"send_transaction": "Transaktion senden",
|
|
||||||
"send_tx_failed": "Transaktion fehlgeschlagen",
|
|
||||||
"send_tx_sent": "Transaktion gesendet!",
|
|
||||||
"send_tx_success": "Transaktion erfolgreich gesendet!",
|
|
||||||
"send_txid_copied": "TxID in Zwischenablage kopiert",
|
|
||||||
"send_txid_label": "TxID: %s",
|
|
||||||
"send_valid_shielded": "Gültige abgeschirmte Adresse",
|
|
||||||
"send_valid_transparent": "Gültige transparente Adresse",
|
|
||||||
"send_wallet_empty": "Ihre Wallet ist leer",
|
|
||||||
"send_yes_clear": "Ja, leeren",
|
|
||||||
"sending": "Transaktion wird gesendet",
|
|
||||||
"sending_from": "SENDEN VON",
|
|
||||||
"sent": "gesendet",
|
|
||||||
"sent_filter": "Gesendet",
|
|
||||||
"sent_type": "Gesendet",
|
|
||||||
"sent_upper": "GESENDET",
|
|
||||||
"settings": "Einstellungen",
|
|
||||||
"setup_wizard": "Einrichtungsassistent",
|
|
||||||
"share": "Teilen",
|
|
||||||
"shield_check_status": "Status prüfen",
|
|
||||||
"shield_completed": "Vorgang erfolgreich abgeschlossen!",
|
|
||||||
"shield_description": "Schirmen Sie Ihre Mining-Belohnungen ab, indem Sie Coinbase-Ausgaben von transparenten Adressen an eine abgeschirmte Adresse senden. Dies verbessert die Privatsphäre, indem Ihre Mining-Einkünfte verborgen werden.",
|
|
||||||
"shield_from_address": "Von Adresse:",
|
|
||||||
"shield_funds": "Gelder abschirmen",
|
|
||||||
"shield_in_progress": "Vorgang läuft...",
|
|
||||||
"shield_max_utxos": "Max. UTXOs pro Vorgang",
|
|
||||||
"shield_merge_done": "Abschirmung/Zusammenführung abgeschlossen!",
|
|
||||||
"shield_select_z": "z-Adresse auswählen...",
|
|
||||||
"shield_started": "Abschirmvorgang gestartet",
|
|
||||||
"shield_title": "Coinbase-Belohnungen abschirmen",
|
|
||||||
"shield_to_address": "An Adresse (Abgeschirmt):",
|
|
||||||
"shield_utxo_limit": "UTXO-Limit:",
|
|
||||||
"shield_wildcard_hint": "Verwenden Sie '*' um von allen transparenten Adressen abzuschirmen",
|
|
||||||
"shielded": "Abgeschirmt",
|
|
||||||
"shielded_to": "ABGESCHIRMT AN",
|
|
||||||
"shielded_type": "Abgeschirmt",
|
|
||||||
"show": "Anzeigen",
|
|
||||||
"show_qr_code": "QR-Code anzeigen",
|
|
||||||
"showing_transactions": "Zeige %d\xe2\x80\x93%d von %d Transaktionen (gesamt: %zu)",
|
|
||||||
"simple_background": "Einfacher Hintergrund",
|
|
||||||
"start_mining": "Mining starten",
|
|
||||||
"status": "Status",
|
|
||||||
"stop_external": "Externen Daemon stoppen",
|
|
||||||
"stop_mining": "Mining stoppen",
|
|
||||||
"submitting_transaction": "Transaktion wird übermittelt...",
|
|
||||||
"success": "Erfolg",
|
|
||||||
"summary": "Zusammenfassung",
|
|
||||||
"syncing": "Synchronisiere...",
|
|
||||||
"t_addresses": "T-Adressen",
|
|
||||||
"test_connection": "Testen",
|
|
||||||
"theme": "Design",
|
|
||||||
"theme_effects": "Design-Effekte",
|
|
||||||
"time_days_ago": "vor %d Tagen",
|
|
||||||
"time_hours_ago": "vor %d Stunden",
|
|
||||||
"time_minutes_ago": "vor %d Minuten",
|
|
||||||
"time_seconds_ago": "vor %d Sekunden",
|
|
||||||
"to": "An",
|
|
||||||
"to_upper": "AN",
|
|
||||||
"tools": "WERKZEUGE",
|
|
||||||
"total": "Gesamt",
|
|
||||||
"transaction_id": "TRANSAKTIONS-ID",
|
|
||||||
"transaction_sent": "Transaktion erfolgreich gesendet",
|
|
||||||
"transaction_sent_msg": "Transaktion gesendet!",
|
|
||||||
"transaction_url": "Transaktions-URL",
|
|
||||||
"transactions": "Transaktionen",
|
|
||||||
"transactions_upper": "TRANSAKTIONEN",
|
|
||||||
"transparent": "Transparent",
|
|
||||||
"tx_confirmations": "%d Bestätigungen",
|
|
||||||
"tx_details_title": "Transaktionsdetails",
|
|
||||||
"tx_from_address": "Von Adresse:",
|
|
||||||
"tx_id_label": "Transaktions-ID:",
|
|
||||||
"tx_immature": "UNREIF",
|
|
||||||
"tx_mined": "GEMINED",
|
|
||||||
"tx_received": "EMPFANGEN",
|
|
||||||
"tx_sent": "GESENDET",
|
|
||||||
"tx_to_address": "An Adresse:",
|
|
||||||
"tx_view_explorer": "Im Explorer anzeigen",
|
|
||||||
"txs_count": "%d Txs",
|
|
||||||
"type": "Typ",
|
|
||||||
"ui_opacity": "UI-Transparenz",
|
|
||||||
"unban": "Entsperren",
|
|
||||||
"unconfirmed": "Unbestätigt",
|
|
||||||
"undo_clear": "Leeren rückgängig",
|
|
||||||
"unknown": "Unbekannt",
|
|
||||||
"use_embedded_daemon": "Eingebetteten dragonxd verwenden",
|
|
||||||
"use_tor": "Tor verwenden",
|
|
||||||
"validate_btn": "Validieren",
|
|
||||||
"validate_description": "Geben Sie eine DragonX-Adresse ein, um zu prüfen, ob sie gültig ist und ob sie zu dieser Wallet gehört.",
|
|
||||||
"validate_invalid": "UNGÜLTIG",
|
|
||||||
"validate_is_mine": "Diese Wallet besitzt diese Adresse",
|
|
||||||
"validate_not_mine": "Nicht im Besitz dieser Wallet",
|
|
||||||
"validate_ownership": "Eigentum:",
|
|
||||||
"validate_results": "Ergebnisse:",
|
|
||||||
"validate_shielded_type": "Abgeschirmt (z-Adresse)",
|
|
||||||
"validate_status": "Status:",
|
|
||||||
"validate_title": "Adresse validieren",
|
|
||||||
"validate_transparent_type": "Transparent (t-Adresse)",
|
|
||||||
"validate_type": "Typ:",
|
|
||||||
"validate_valid": "GÜLTIG",
|
|
||||||
"validating": "Validiere...",
|
|
||||||
"verbose_logging": "Ausführliches Logging",
|
|
||||||
"version": "Version",
|
|
||||||
"view": "Ansicht",
|
|
||||||
"view_details": "Details anzeigen",
|
|
||||||
"view_on_explorer": "Im Explorer anzeigen",
|
|
||||||
"waiting_for_daemon": "Warte auf Daemon-Verbindung...",
|
|
||||||
"wallet": "WALLET",
|
|
||||||
"wallet_empty": "Ihre Wallet ist leer",
|
|
||||||
"wallet_empty_hint": "Wechseln Sie zu Empfangen, um Ihre Adresse zu erhalten und Gelder zu empfangen.",
|
|
||||||
"warning": "Warnung",
|
|
||||||
"warning_upper": "WARNUNG!",
|
|
||||||
"website": "Webseite",
|
|
||||||
"window_opacity": "Fenster-Transparenz",
|
|
||||||
"yes_clear": "Ja, leeren",
|
|
||||||
"your_addresses": "Ihre Adressen",
|
|
||||||
"z_addresses": "Z-Adressen",
|
|
||||||
}
|
|
||||||
|
|
||||||
out = os.path.join(os.path.dirname(__file__), "..", "res", "lang", "de.json")
|
|
||||||
with open(out, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(translations, f, indent=4, ensure_ascii=False, sort_keys=True)
|
|
||||||
print(f"Wrote {len(translations)} German translations to {os.path.abspath(out)}")
|
|
||||||
@@ -1,665 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate complete Spanish (es.json) translations for ObsidianDragon wallet."""
|
|
||||||
import json
|
|
||||||
|
|
||||||
es = {
|
|
||||||
# ---- Navigation & Tabs ----
|
|
||||||
"overview": "Resumen",
|
|
||||||
"balance": "Saldo",
|
|
||||||
"send": "Enviar",
|
|
||||||
"receive": "Recibir",
|
|
||||||
"transactions": "Transacciones",
|
|
||||||
"history": "Historial",
|
|
||||||
"mining": "Minería",
|
|
||||||
"peers": "Nodos",
|
|
||||||
"market": "Mercado",
|
|
||||||
"settings": "Configuración",
|
|
||||||
"console": "Consola",
|
|
||||||
"tools": "HERRAMIENTAS",
|
|
||||||
"advanced": "AVANZADO",
|
|
||||||
"network": "Red",
|
|
||||||
|
|
||||||
# ---- Settings sections ----
|
|
||||||
"appearance": "APARIENCIA",
|
|
||||||
"wallet": "CARTERA",
|
|
||||||
"node_security": "NODO Y SEGURIDAD",
|
|
||||||
"node": "NODO",
|
|
||||||
"security": "SEGURIDAD",
|
|
||||||
"explorer": "EXPLORADOR",
|
|
||||||
"about": "Acerca de",
|
|
||||||
"backup_data": "RESPALDO Y DATOS",
|
|
||||||
"general": "General",
|
|
||||||
|
|
||||||
# ---- Settings options ----
|
|
||||||
"balance_layout": "Diseño de Saldo",
|
|
||||||
"low_spec_mode": "Modo bajo rendimiento",
|
|
||||||
"simple_background": "Fondo simple",
|
|
||||||
"console_scanline": "Líneas de consola",
|
|
||||||
"theme_effects": "Efectos de tema",
|
|
||||||
"language": "Idioma",
|
|
||||||
"save_z_transactions": "Guardar Z-tx en lista",
|
|
||||||
"allow_custom_fees": "Permitir comisiones personalizadas",
|
|
||||||
"custom_fees": "Comisiones personalizadas",
|
|
||||||
"auto_shield": "Auto-proteger minería",
|
|
||||||
"fetch_prices": "Obtener precios",
|
|
||||||
"use_tor": "Usar Tor",
|
|
||||||
"font_scale": "Escala de fuente",
|
|
||||||
"keep_daemon": "Mantener daemon activo",
|
|
||||||
"stop_external": "Detener daemon externo",
|
|
||||||
"mine_when_idle": "Minar en reposo",
|
|
||||||
"verbose_logging": "Registro detallado",
|
|
||||||
"acrylic": "Acrílico",
|
|
||||||
"noise": "Ruido",
|
|
||||||
"ui_opacity": "Opacidad de UI",
|
|
||||||
"window_opacity": "Opacidad de ventana",
|
|
||||||
|
|
||||||
# ---- Settings buttons ----
|
|
||||||
"save_settings": "Guardar Configuración",
|
|
||||||
"reset_to_defaults": "Restablecer Valores",
|
|
||||||
"report_bug": "Reportar Error",
|
|
||||||
"website": "Sitio Web",
|
|
||||||
"setup_wizard": "Asistente de Configuración",
|
|
||||||
"rescan": "Re-escanear",
|
|
||||||
"test_connection": "Probar",
|
|
||||||
|
|
||||||
# ---- Settings fields ----
|
|
||||||
"rpc_host": "Host RPC",
|
|
||||||
"rpc_port": "Puerto",
|
|
||||||
"rpc_user": "Usuario",
|
|
||||||
"rpc_pass": "Contraseña",
|
|
||||||
"transaction_url": "URL de Transacción",
|
|
||||||
"address_url": "URL de Dirección",
|
|
||||||
"block_explorer": "Explorador de Bloques",
|
|
||||||
|
|
||||||
# ---- Common actions ----
|
|
||||||
"add": "Agregar",
|
|
||||||
"edit": "Editar",
|
|
||||||
"delete": "Eliminar",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"close": "Cerrar",
|
|
||||||
"clear": "Limpiar",
|
|
||||||
"copy": "Copiar",
|
|
||||||
"paste": "Pegar",
|
|
||||||
"save": "Guardar",
|
|
||||||
"refresh": "Actualizar",
|
|
||||||
"export": "Exportar",
|
|
||||||
"import": "Importar",
|
|
||||||
"show": "Mostrar",
|
|
||||||
"hide": "Ocultar",
|
|
||||||
"share": "Compartir",
|
|
||||||
"confirm_and_send": "Confirmar y Enviar",
|
|
||||||
"confirm_send": "Confirmar Envío",
|
|
||||||
"confirm_transaction": "Confirmar Transacción",
|
|
||||||
"review_send": "Revisar Envío",
|
|
||||||
"copy_address": "Copiar Dirección Completa",
|
|
||||||
"copy_to_clipboard": "Copiar al Portapapeles",
|
|
||||||
"paste_from_clipboard": "Pegar del Portapapeles",
|
|
||||||
"copy_txid": "Copiar TxID",
|
|
||||||
"copy_uri": "Copiar URI",
|
|
||||||
"copy_error": "Copiar Error",
|
|
||||||
"search_placeholder": "Buscar...",
|
|
||||||
"exit": "Salir",
|
|
||||||
"help": "Ayuda",
|
|
||||||
"file": "Archivo",
|
|
||||||
"display": "Pantalla",
|
|
||||||
"new": "+ Nuevo",
|
|
||||||
"dismiss": "Descartar",
|
|
||||||
"keep": "Mantener",
|
|
||||||
"yes_clear": "Sí, Limpiar",
|
|
||||||
"undo_clear": "Deshacer Limpieza",
|
|
||||||
|
|
||||||
# ---- Common labels ----
|
|
||||||
"address": "Dirección",
|
|
||||||
"address_label": "Dirección:",
|
|
||||||
"amount": "Cantidad",
|
|
||||||
"amount_label": "Cantidad:",
|
|
||||||
"date": "Fecha",
|
|
||||||
"date_label": "Fecha:",
|
|
||||||
"fee": "Comisión",
|
|
||||||
"fee_label": "Comisión:",
|
|
||||||
"label": "Etiqueta:",
|
|
||||||
"memo": "Memo (opcional, encriptado)",
|
|
||||||
"memo_label": "Memo:",
|
|
||||||
"notes": "Notas",
|
|
||||||
"notes_optional": "Notas (opcional):",
|
|
||||||
"total": "Total",
|
|
||||||
"from": "Desde",
|
|
||||||
"to_upper": "PARA",
|
|
||||||
"from_upper": "DESDE",
|
|
||||||
"max": "Máximo",
|
|
||||||
"characters": "caracteres",
|
|
||||||
"ping": "Ping",
|
|
||||||
"height": "Altura",
|
|
||||||
"block": "Bloque",
|
|
||||||
"available": "Disponible",
|
|
||||||
"unknown": "Desconocido",
|
|
||||||
"loading": "Cargando...",
|
|
||||||
"validating": "Validando...",
|
|
||||||
"warning_upper": "¡ADVERTENCIA!",
|
|
||||||
"output_filename": "Nombre del archivo:",
|
|
||||||
"file_save_location": "El archivo se guardará en: ~/.config/ObsidianDragon/",
|
|
||||||
"light": "Claro",
|
|
||||||
"dark": "Oscuro",
|
|
||||||
|
|
||||||
# ---- Status ----
|
|
||||||
"connected": "Conectado",
|
|
||||||
"disconnected": "Desconectado",
|
|
||||||
"connecting": "Conectando...",
|
|
||||||
"confirmed": "Confirmada",
|
|
||||||
"confirmations": "Confirmaciones",
|
|
||||||
"not_connected_to_daemon": "No conectado al daemon",
|
|
||||||
"waiting_for_daemon": "Esperando conexión al daemon...",
|
|
||||||
"blockchain_syncing": "Sincronizando blockchain (%.1f%%)... Los saldos pueden ser inexactos.",
|
|
||||||
"error": "Error",
|
|
||||||
"success": "Éxito",
|
|
||||||
"warning": "Advertencia",
|
|
||||||
|
|
||||||
# ---- Time ----
|
|
||||||
"time_days_ago": "hace %d días",
|
|
||||||
"time_hours_ago": "hace %d horas",
|
|
||||||
"time_minutes_ago": "hace %d minutos",
|
|
||||||
"time_seconds_ago": "hace %d segundos",
|
|
||||||
|
|
||||||
# ---- Transaction types/filters ----
|
|
||||||
"sent_type": "Enviado",
|
|
||||||
"sent_filter": "Enviado",
|
|
||||||
"sent_upper": "ENVIADO",
|
|
||||||
"received_label": "Recibido",
|
|
||||||
"received_filter": "Recibido",
|
|
||||||
"received_upper": "RECIBIDO",
|
|
||||||
"mined_type": "Minado",
|
|
||||||
"mined_filter": "Minado",
|
|
||||||
"mined_upper": "MINADO",
|
|
||||||
"immature_type": "Inmaduro",
|
|
||||||
"mature": "Maduro",
|
|
||||||
"recv_type": "Recibido",
|
|
||||||
"all_filter": "Todos",
|
|
||||||
"shielded_type": "Protegido",
|
|
||||||
|
|
||||||
# ---- Balance / Overview ----
|
|
||||||
"address_upper": "DIRECCIÓN",
|
|
||||||
"address_details": "Detalles de Dirección",
|
|
||||||
"amount_details": "DETALLES DE CANTIDAD",
|
|
||||||
"transactions_upper": "TRANSACCIONES",
|
|
||||||
"addresses_appear_here": "Tus direcciones de recepción aparecerán aquí una vez conectado.",
|
|
||||||
"wallet_empty": "Tu cartera está vacía",
|
|
||||||
"wallet_empty_hint": "Cambia a Recibir para obtener tu dirección y empezar a recibir fondos.",
|
|
||||||
"loading_addresses": "Cargando direcciones...",
|
|
||||||
"no_addresses_match": "No hay direcciones que coincidan con el filtro",
|
|
||||||
"no_addresses_with_balance": "No hay direcciones con saldo",
|
|
||||||
"click_copy_address": "Clic para copiar dirección",
|
|
||||||
"click_copy_uri": "Clic para copiar URI",
|
|
||||||
"address_copied": "Dirección copiada al portapapeles",
|
|
||||||
"about_dragonx": "Acerca de DragonX",
|
|
||||||
"dragonx_green": "DragonX (Verde)",
|
|
||||||
|
|
||||||
# ---- Transactions tab ----
|
|
||||||
"no_transactions": "No se encontraron transacciones",
|
|
||||||
"no_matching": "No hay transacciones coincidentes",
|
|
||||||
"showing_transactions": "Mostrando %d\u2013%d de %d transacciones (total: %zu)",
|
|
||||||
"conf_count": "%d conf",
|
|
||||||
"confirmations_display": "%d confirmaciones | %s",
|
|
||||||
"txs_count": "%d txs",
|
|
||||||
"view_details": "Ver Detalles",
|
|
||||||
"full_details": "Detalles Completos",
|
|
||||||
"export_csv": "Exportar CSV",
|
|
||||||
"transaction_id": "ID DE TRANSACCIÓN",
|
|
||||||
|
|
||||||
# ---- Receive tab ----
|
|
||||||
"select_receiving_address": "Seleccionar dirección de recepción...",
|
|
||||||
"payment_request": "SOLICITUD DE PAGO",
|
|
||||||
"recent_received": "RECIBIDOS RECIENTES",
|
|
||||||
"no_recent_receives": "No hay recepciones recientes",
|
|
||||||
"qr_unavailable": "QR no disponible",
|
|
||||||
"clear_request": "Limpiar Solicitud",
|
|
||||||
"clear_form_confirm": "¿Limpiar todos los campos del formulario?",
|
|
||||||
"payment_request_copied": "Solicitud de pago copiada",
|
|
||||||
"payment_uri_copied": "URI de pago copiada",
|
|
||||||
"failed_create_shielded": "Error al crear dirección protegida",
|
|
||||||
"failed_create_transparent": "Error al crear dirección transparente",
|
|
||||||
"new_shielded_created": "Nueva dirección protegida creada",
|
|
||||||
"new_transparent_created": "Nueva dirección transparente creada",
|
|
||||||
|
|
||||||
# ---- Send tab ----
|
|
||||||
"send_sending_from": "ENVIANDO DESDE",
|
|
||||||
"send_select_source": "Seleccionar dirección de origen...",
|
|
||||||
"send_no_balance": "Sin saldo",
|
|
||||||
"send_recipient": "DESTINATARIO",
|
|
||||||
"send_amount_upper": "CANTIDAD",
|
|
||||||
"send_amount": "Cantidad",
|
|
||||||
"send_amount_details": "DETALLES DE CANTIDAD",
|
|
||||||
"send_fee": "Comisión",
|
|
||||||
"send_fee_low": "Baja",
|
|
||||||
"send_fee_normal": "Normal",
|
|
||||||
"send_fee_high": "Alta",
|
|
||||||
"send_network_fee": "COMISIÓN DE RED",
|
|
||||||
"send_total": "Total",
|
|
||||||
"send_recent_sends": "ENVÍOS RECIENTES",
|
|
||||||
"send_no_recent": "No hay envíos recientes",
|
|
||||||
"send_clear_fields": "¿Limpiar todos los campos del formulario?",
|
|
||||||
"send_yes_clear": "Sí, Limpiar",
|
|
||||||
"send_keep": "Mantener",
|
|
||||||
"send_form_restored": "Formulario restaurado",
|
|
||||||
"send_valid_shielded": "Dirección protegida válida",
|
|
||||||
"send_valid_transparent": "Dirección transparente válida",
|
|
||||||
"send_exceeds_available": "Excede disponible (%.8f)",
|
|
||||||
"send_submitting": "Enviando transacción...",
|
|
||||||
"send_tx_sent": "¡Transacción enviada!",
|
|
||||||
"send_tx_success": "¡Transacción enviada exitosamente!",
|
|
||||||
"send_tx_failed": "Error en la transacción",
|
|
||||||
"send_error_prefix": "Error: %s",
|
|
||||||
"send_error_copied": "Error copiado al portapapeles",
|
|
||||||
"send_copy_error": "Copiar Error",
|
|
||||||
"send_dismiss": "Descartar",
|
|
||||||
"send_txid_copied": "TxID copiado al portapapeles",
|
|
||||||
"send_txid_label": "TxID: %s",
|
|
||||||
"send_go_to_receive": "Ir a Recibir",
|
|
||||||
"send_wallet_empty": "Tu cartera está vacía",
|
|
||||||
"send_switch_to_receive": "Cambia a Recibir para obtener tu dirección y empezar a recibir fondos.",
|
|
||||||
"send_tooltip_select_source": "Selecciona una dirección de origen primero",
|
|
||||||
"send_tooltip_invalid_address": "Ingresa una dirección de destinatario válida",
|
|
||||||
"send_tooltip_enter_amount": "Ingresa una cantidad a enviar",
|
|
||||||
"send_tooltip_exceeds_balance": "La cantidad excede el saldo disponible",
|
|
||||||
"send_tooltip_not_connected": "No conectado al daemon",
|
|
||||||
"send_tooltip_syncing": "Espera a que se sincronice el blockchain",
|
|
||||||
"send_tooltip_in_progress": "Transacción ya en progreso",
|
|
||||||
"sending_from": "ENVIANDO DESDE",
|
|
||||||
"select_source_address": "Seleccionar dirección de origen...",
|
|
||||||
"recipient": "DESTINATARIO",
|
|
||||||
"memo_optional": "MEMO (OPCIONAL)",
|
|
||||||
"memo_upper": "MEMO",
|
|
||||||
"network_fee": "COMISIÓN DE RED",
|
|
||||||
"fee_low": "Baja",
|
|
||||||
"fee_normal": "Normal",
|
|
||||||
"fee_high": "Alta",
|
|
||||||
"recent_sends": "ENVÍOS RECIENTES",
|
|
||||||
"no_recent_sends": "No hay envíos recientes",
|
|
||||||
"shielded_to": "PROTEGIDA PARA",
|
|
||||||
"submitting_transaction": "Enviando transacción...",
|
|
||||||
"transaction_sent_msg": "¡Transacción enviada!",
|
|
||||||
"amount_exceeds_balance": "La cantidad excede el saldo",
|
|
||||||
|
|
||||||
# ---- Mining tab ----
|
|
||||||
"mining_solo": "Solo",
|
|
||||||
"mining_pool": "Pool",
|
|
||||||
"mining_pool_url": "URL del Pool",
|
|
||||||
"mining_pool_hashrate": "Hashrate del Pool",
|
|
||||||
"mining_local_hashrate": "Hashrate Local",
|
|
||||||
"mining_payout_address": "Dirección de Pago",
|
|
||||||
"mining_payout_tooltip": "Dirección para recibir recompensas de minería",
|
|
||||||
"mining_mine": "Minar",
|
|
||||||
"mining_stop": "Detener",
|
|
||||||
"mining_starting": "Iniciando...",
|
|
||||||
"mining_stopping": "Deteniendo...",
|
|
||||||
"mining_active": "Activo",
|
|
||||||
"mining_connected": "Conectado",
|
|
||||||
"mining_connecting": "Conectando...",
|
|
||||||
"mining_network": "Red",
|
|
||||||
"mining_shares": "Shares",
|
|
||||||
"mining_uptime": "Tiempo activo",
|
|
||||||
"mining_mining_addr": "Dir. Minería",
|
|
||||||
"mining_est_block": "Bloque Est.",
|
|
||||||
"mining_est_daily": "Diario Est.",
|
|
||||||
"mining_today": "Hoy",
|
|
||||||
"mining_yesterday": "Ayer",
|
|
||||||
"mining_all_time": "Todo el Tiempo",
|
|
||||||
"mining_recent_blocks": "BLOQUES RECIENTES",
|
|
||||||
"mining_recent_payouts": "PAGOS DE POOL RECIENTES",
|
|
||||||
"mining_no_blocks_yet": "Aún no se han encontrado bloques",
|
|
||||||
"mining_no_payouts_yet": "Aún no hay pagos del pool",
|
|
||||||
"mining_show_chart": "Gráfico",
|
|
||||||
"mining_show_log": "Registro",
|
|
||||||
"mining_chart_now": "Ahora",
|
|
||||||
"mining_chart_start": "Inicio",
|
|
||||||
"mining_chart_1m_ago": "hace 1m",
|
|
||||||
"mining_chart_5m_ago": "hace 5m",
|
|
||||||
"mining_save_pool_url": "Guardar URL del pool",
|
|
||||||
"mining_save_payout_address": "Guardar dirección de pago",
|
|
||||||
"mining_saved_pools": "Pools Guardados:",
|
|
||||||
"mining_saved_addresses": "Direcciones Guardadas:",
|
|
||||||
"mining_no_saved_pools": "No hay pools guardados",
|
|
||||||
"mining_no_saved_addresses": "No hay direcciones guardadas",
|
|
||||||
"mining_already_saved": "URL del pool ya guardada",
|
|
||||||
"mining_remove": "Eliminar",
|
|
||||||
"mining_reset_defaults": "Restablecer Valores",
|
|
||||||
"mining_click": "Clic",
|
|
||||||
"mining_to_save": "para guardar",
|
|
||||||
"mining_click_copy_address": "Clic para copiar dirección",
|
|
||||||
"mining_click_copy_block": "Clic para copiar hash de bloque",
|
|
||||||
"mining_click_copy_difficulty": "Clic para copiar dificultad",
|
|
||||||
"mining_address_copied": "Dirección de minería copiada",
|
|
||||||
"mining_block_copied": "Hash de bloque copiado",
|
|
||||||
"mining_difficulty_copied": "Dificultad copiada",
|
|
||||||
"mining_open_in_explorer": "Abrir en explorador",
|
|
||||||
"mining_starting_tooltip": "El minero está iniciando...",
|
|
||||||
"mining_stopping_tooltip": "El minero está deteniéndose...",
|
|
||||||
"mining_syncing_tooltip": "El blockchain está sincronizando...",
|
|
||||||
"mining_idle_on_tooltip": "Desactivar minería en reposo",
|
|
||||||
"mining_idle_off_tooltip": "Activar minería en reposo",
|
|
||||||
"mining_stop_solo_for_pool": "Detener minería solo antes de iniciar minería en pool",
|
|
||||||
"mining_stop_solo_for_pool_settings": "Detener minería solo para cambiar configuración del pool",
|
|
||||||
"mining_filter_all": "Todos",
|
|
||||||
"mining_filter_tip_all": "Mostrar todas las ganancias",
|
|
||||||
"mining_filter_tip_solo": "Mostrar solo ganancias solo",
|
|
||||||
"mining_filter_tip_pool": "Mostrar solo ganancias del pool",
|
|
||||||
"local_hashrate": "Tasa Hash Local",
|
|
||||||
"est_time_to_block": "Tiempo Est. al Bloque",
|
|
||||||
"difficulty": "Dificultad",
|
|
||||||
"current_price": "Precio Actual",
|
|
||||||
"market_cap": "Cap. de Mercado",
|
|
||||||
|
|
||||||
# ---- Peers tab ----
|
|
||||||
"peers_blockchain": "BLOCKCHAIN",
|
|
||||||
"peers_blocks": "Bloques",
|
|
||||||
"peers_connected": "Conectados",
|
|
||||||
"peers_connected_count": "Conectados: %d",
|
|
||||||
"peers_banned": "Bloqueados",
|
|
||||||
"peers_banned_count": "Bloqueados: %d",
|
|
||||||
"peers_upper": "NODOS",
|
|
||||||
"peers_avg_ping": "Ping Prom.",
|
|
||||||
"peers_best_block": "Mejor Bloque",
|
|
||||||
"peers_hashrate": "Hashrate",
|
|
||||||
"peers_longest": "Más Larga",
|
|
||||||
"peers_longest_chain": "Cadena Más Larga",
|
|
||||||
"peers_memory": "Memoria",
|
|
||||||
"peers_notarized": "Notarizado",
|
|
||||||
"peers_p2p_port": "Puerto P2P",
|
|
||||||
"peers_protocol": "Protocolo",
|
|
||||||
"peers_version": "Versión",
|
|
||||||
"peers_in_out": "Ent/Sal",
|
|
||||||
"peers_dir_in": "Ent",
|
|
||||||
"peers_dir_out": "Sal",
|
|
||||||
"peers_received": "Recibido",
|
|
||||||
"peers_sent": "Enviado",
|
|
||||||
"peers_refresh": "Actualizar",
|
|
||||||
"peers_refresh_tooltip": "Actualizar lista de nodos",
|
|
||||||
"peers_refreshing": "Actualizando...",
|
|
||||||
"peers_no_connected": "No hay nodos conectados",
|
|
||||||
"peers_no_banned": "No hay nodos bloqueados",
|
|
||||||
"peers_ban_24h": "Bloquear Nodo 24h",
|
|
||||||
"peers_unban": "Desbloquear",
|
|
||||||
"peers_clear_all_bans": "Limpiar Todos los Bloqueos",
|
|
||||||
"peers_copy_ip": "Copiar IP",
|
|
||||||
"peers_click_copy": "Clic para copiar",
|
|
||||||
"peers_hash_copied": "Hash copiado",
|
|
||||||
"peers_no_tls": "Sin TLS",
|
|
||||||
"peers_blocks_left": "%d bloques restantes",
|
|
||||||
"peers_ban_score": "Puntuación: %d",
|
|
||||||
"peers_tt_id": "ID: %d",
|
|
||||||
"peers_tt_sent": "Enviado: %s",
|
|
||||||
"peers_tt_received": "Recibido: %s",
|
|
||||||
"peers_tt_services": "Servicios: %s",
|
|
||||||
"peers_tt_start_height": "Altura Inicial: %d",
|
|
||||||
"peers_tt_synced": "Sinc H/B: %d/%d",
|
|
||||||
"peers_tt_tls_cipher": "TLS: %s",
|
|
||||||
"connected_peers": "Nodos Conectados",
|
|
||||||
"banned_peers": "Nodos Bloqueados",
|
|
||||||
"ban": "Bloquear",
|
|
||||||
"clear_all_bans": "Limpiar Todos los Bloqueos",
|
|
||||||
"ip_address": "Dirección IP",
|
|
||||||
|
|
||||||
# ---- Market tab ----
|
|
||||||
"market_btc_price": "PRECIO BTC",
|
|
||||||
"market_24h_volume": "VOLUMEN 24H",
|
|
||||||
"market_portfolio": "PORTAFOLIO",
|
|
||||||
"market_pct_shielded": "%.0f%% Protegido",
|
|
||||||
"market_attribution": "Datos de precios de NonKYC",
|
|
||||||
"market_no_price": "Sin datos de precio",
|
|
||||||
"market_no_history": "No hay historial de precios disponible",
|
|
||||||
"market_price_unavailable": "Datos de precio no disponibles",
|
|
||||||
"market_refresh_price": "Actualizar datos de precio",
|
|
||||||
"market_trade_on": "Operar en %s",
|
|
||||||
"market_now": "Ahora",
|
|
||||||
"market_6h": "6h",
|
|
||||||
"market_12h": "12h",
|
|
||||||
"market_18h": "18h",
|
|
||||||
"market_24h": "24h",
|
|
||||||
"24h_change": "Cambio 24h",
|
|
||||||
"24h_volume": "Volumen 24h",
|
|
||||||
|
|
||||||
# ---- Console tab ----
|
|
||||||
"console_welcome": "Bienvenido a la Consola de ObsidianDragon",
|
|
||||||
"console_type_help": "Escribe 'help' para ver los comandos disponibles",
|
|
||||||
"console_available_commands": "Comandos disponibles:",
|
|
||||||
"console_common_rpc": "Comandos RPC comunes:",
|
|
||||||
"console_rpc_reference": "Referencia de Comandos RPC",
|
|
||||||
"console_auto_scroll": "Auto-desplazamiento",
|
|
||||||
"console_clear": "Limpiar",
|
|
||||||
"console_clear_console": "Limpiar Consola",
|
|
||||||
"console_cleared": "Consola limpiada",
|
|
||||||
"console_commands": "Comandos",
|
|
||||||
"console_completions": "Completaciones:",
|
|
||||||
"console_tab_completion": "Tab para completar",
|
|
||||||
"console_connected": "Conectado al daemon",
|
|
||||||
"console_disconnected": "Desconectado del daemon",
|
|
||||||
"console_not_connected": "Error: No conectado al daemon",
|
|
||||||
"console_no_daemon": "Sin daemon",
|
|
||||||
"console_daemon": "Daemon",
|
|
||||||
"console_daemon_error": "¡Error del daemon!",
|
|
||||||
"console_daemon_started": "Daemon iniciado",
|
|
||||||
"console_daemon_stopped": "Daemon detenido",
|
|
||||||
"console_errors": "Errores",
|
|
||||||
"console_filter_hint": "Filtrar salida...",
|
|
||||||
"console_search_commands": "Buscar comandos...",
|
|
||||||
"console_copy_all": "Copiar Todo",
|
|
||||||
"console_copy_selected": "Copiar",
|
|
||||||
"console_select_all": "Seleccionar Todo",
|
|
||||||
"console_zoom_in": "Acercar",
|
|
||||||
"console_zoom_out": "Alejar",
|
|
||||||
"console_show_daemon_output": "Mostrar salida del daemon",
|
|
||||||
"console_show_errors_only": "Mostrar solo errores",
|
|
||||||
"console_show_rpc_ref": "Mostrar referencia de comandos RPC",
|
|
||||||
"console_capturing_output": "Capturando salida del daemon...",
|
|
||||||
"console_starting_node": "Iniciando nodo...",
|
|
||||||
"console_line_count": "%zu líneas",
|
|
||||||
"console_new_lines": "%d nuevas líneas",
|
|
||||||
"console_showing_lines": "Mostrando %zu de %zu líneas",
|
|
||||||
"console_click_commands": "Clic en los comandos de arriba para insertarlos",
|
|
||||||
"console_click_insert": "Clic para insertar",
|
|
||||||
"console_click_insert_params": "Clic para insertar con parámetros",
|
|
||||||
"console_close": "Cerrar",
|
|
||||||
"console_status_running": "Ejecutando",
|
|
||||||
"console_status_stopped": "Detenido",
|
|
||||||
"console_status_starting": "Iniciando",
|
|
||||||
"console_status_stopping": "Deteniendo",
|
|
||||||
"console_status_error": "Error",
|
|
||||||
"console_status_unknown": "Desconocido",
|
|
||||||
"console_help_help": " help - Mostrar este mensaje de ayuda",
|
|
||||||
"console_help_getinfo": " getinfo - Mostrar información del nodo",
|
|
||||||
"console_help_getblockcount": " getblockcount - Mostrar altura actual del bloque",
|
|
||||||
"console_help_getbalance": " getbalance - Mostrar saldo transparente",
|
|
||||||
"console_help_gettotalbalance": " gettotalbalance - Mostrar saldo total",
|
|
||||||
"console_help_getmininginfo": " getmininginfo - Mostrar estado de minería",
|
|
||||||
"console_help_getpeerinfo": " getpeerinfo - Mostrar nodos conectados",
|
|
||||||
"console_help_setgenerate": " setgenerate - Controlar minería",
|
|
||||||
"console_help_stop": " stop - Detener el daemon",
|
|
||||||
"console_help_clear": " clear - Limpiar la consola",
|
|
||||||
|
|
||||||
# ---- About dialog ----
|
|
||||||
"about_title": "Acerca de ObsidianDragon",
|
|
||||||
"about_edition": "Edición ImGui",
|
|
||||||
"about_version": "Versión:",
|
|
||||||
"about_imgui": "ImGui:",
|
|
||||||
"about_build_date": "Fecha de Compilación:",
|
|
||||||
"about_build_type": "Tipo de Compilación:",
|
|
||||||
"about_debug": "Depuración",
|
|
||||||
"about_release": "Producción",
|
|
||||||
"about_daemon": "Daemon:",
|
|
||||||
"about_chain": "Cadena:",
|
|
||||||
"about_block_height": "Altura de Bloque:",
|
|
||||||
"about_connections": "Conexiones:",
|
|
||||||
"about_peers_count": "%zu nodos",
|
|
||||||
"about_credits": "Créditos",
|
|
||||||
"about_license": "Licencia",
|
|
||||||
"about_license_text": "Este software se distribuye bajo la Licencia Pública General de GNU v3 (GPLv3). Usted es libre de usar, modificar y distribuir este software bajo los términos de la licencia.",
|
|
||||||
"about_website": "Sitio Web",
|
|
||||||
"about_github": "GitHub",
|
|
||||||
"about_block_explorer": "Explorador de Bloques",
|
|
||||||
|
|
||||||
# ---- Address Book dialog ----
|
|
||||||
"address_book_title": "Libreta de Direcciones",
|
|
||||||
"address_book_add_new": "Agregar Nueva",
|
|
||||||
"address_book_add": "Agregar Dirección",
|
|
||||||
"address_book_edit": "Editar Dirección",
|
|
||||||
"address_book_empty": "No hay direcciones guardadas. Haz clic en 'Agregar Nueva' para añadir una.",
|
|
||||||
"address_book_count": "%zu direcciones guardadas",
|
|
||||||
"address_book_deleted": "Entrada eliminada",
|
|
||||||
"address_book_added": "Dirección agregada a la libreta",
|
|
||||||
"address_book_exists": "La dirección ya existe en la libreta",
|
|
||||||
"address_book_updated": "Dirección actualizada",
|
|
||||||
"address_book_update_failed": "Error al actualizar - la dirección puede estar duplicada",
|
|
||||||
|
|
||||||
# ---- Backup dialog ----
|
|
||||||
"backup_title": "Respaldar Cartera",
|
|
||||||
"backup_description": "Crea un respaldo de tu archivo wallet.dat. Este archivo contiene todas tus claves privadas e historial de transacciones. Guarda el respaldo en un lugar seguro.",
|
|
||||||
"backup_destination": "Destino del respaldo:",
|
|
||||||
"backup_wallet_not_found": "Advertencia: wallet.dat no encontrado en la ubicación esperada",
|
|
||||||
"backup_create": "Crear Respaldo",
|
|
||||||
"backup_created": "Respaldo de cartera creado",
|
|
||||||
"backup_backing_up": "Respaldando...",
|
|
||||||
"backup_tips": "Consejos:",
|
|
||||||
"backup_tip_external": "Guarda respaldos en unidades externas o almacenamiento en la nube",
|
|
||||||
"backup_tip_multiple": "Crea múltiples respaldos en diferentes ubicaciones",
|
|
||||||
"backup_tip_test": "Prueba restaurar desde el respaldo periódicamente",
|
|
||||||
"backup_wallet": "Respaldar Cartera...",
|
|
||||||
|
|
||||||
# ---- Block Info dialog ----
|
|
||||||
"block_info_title": "Información del Bloque",
|
|
||||||
"block_height": "Altura del Bloque:",
|
|
||||||
"block_get_info": "Obtener Info del Bloque",
|
|
||||||
"block_hash": "Hash del Bloque:",
|
|
||||||
"block_timestamp": "Fecha y Hora:",
|
|
||||||
"block_transactions": "Transacciones:",
|
|
||||||
"block_size": "Tamaño:",
|
|
||||||
"block_bits": "Bits:",
|
|
||||||
"block_merkle_root": "Raíz Merkle:",
|
|
||||||
"block_previous": "Bloque Anterior:",
|
|
||||||
"block_next": "Bloque Siguiente:",
|
|
||||||
"block_click_prev": "Clic para ver bloque anterior",
|
|
||||||
"block_click_next": "Clic para ver bloque siguiente",
|
|
||||||
"block_nav_prev": "<< Anterior",
|
|
||||||
"block_nav_next": "Siguiente >>",
|
|
||||||
|
|
||||||
# ---- Export Keys dialog ----
|
|
||||||
"export_keys_title": "Exportar Todas las Claves Privadas",
|
|
||||||
"export_keys_danger": "PELIGRO: ¡Esto exportará TODAS las claves privadas de tu cartera! Cualquiera con acceso a este archivo puede robar tus fondos. Guárdalo de forma segura y elimínalo después de usar.",
|
|
||||||
"export_keys_options": "Opciones de exportación:",
|
|
||||||
"export_keys_include_z": "Incluir direcciones Z (protegidas)",
|
|
||||||
"export_keys_include_t": "Incluir direcciones T (transparentes)",
|
|
||||||
"export_keys_btn": "Exportar Claves",
|
|
||||||
"export_keys_success": "Claves exportadas exitosamente",
|
|
||||||
"export_private_key": "Exportar Clave Privada",
|
|
||||||
"export_viewing_key": "Exportar Clave de Vista",
|
|
||||||
|
|
||||||
# ---- Export Transactions dialog ----
|
|
||||||
"export_tx_title": "Exportar Transacciones a CSV",
|
|
||||||
"export_tx_count": "Exportar %zu transacciones a archivo CSV.",
|
|
||||||
"export_tx_none": "No hay transacciones para exportar",
|
|
||||||
"export_tx_file_fail": "Error al crear archivo CSV",
|
|
||||||
"export_tx_success": "Transacciones exportadas exitosamente",
|
|
||||||
|
|
||||||
# ---- Import Key dialog ----
|
|
||||||
"import_key_title": "Importar Clave Privada",
|
|
||||||
"import_key_warning": "Advertencia: ¡Nunca compartas tus claves privadas! Importar claves de fuentes no confiables puede comprometer tu cartera.",
|
|
||||||
"import_key_label": "Clave(s) Privada(s):",
|
|
||||||
"import_key_tooltip": "Ingresa una o más claves privadas, una por línea.\nSoporta claves de direcciones z y t.\nLas líneas que empiezan con # se tratan como comentarios.",
|
|
||||||
"import_key_btn": "Importar Clave(s)",
|
|
||||||
"import_key_no_valid": "No se encontraron claves válidas en la entrada",
|
|
||||||
"import_key_success": "Claves importadas exitosamente",
|
|
||||||
"import_key_rescan": "Re-escanear blockchain después de importar",
|
|
||||||
"import_key_start_height": "Altura inicial:",
|
|
||||||
"import_key_full_rescan": "(0 = re-escaneo completo)",
|
|
||||||
"import_key_formats": "Formatos de clave soportados:",
|
|
||||||
"import_key_z_format": "Claves de gasto de direcciones Z (secret-extended-key-...)",
|
|
||||||
"import_key_t_format": "Claves privadas WIF de direcciones T",
|
|
||||||
"import_private_key": "Importar Clave Privada...",
|
|
||||||
"invalid_address": "Formato de dirección inválido",
|
|
||||||
|
|
||||||
# ---- Key Export dialog ----
|
|
||||||
"key_export_private_key": "Clave Privada:",
|
|
||||||
"key_export_viewing_key": "Clave de Vista:",
|
|
||||||
"key_export_private_warning": "¡Mantén esta clave en SECRETO! Cualquiera con esta clave puede gastar tus fondos. Nunca la compartas en línea ni con personas no confiables.",
|
|
||||||
"key_export_viewing_warning": "Esta clave de vista permite a otros ver tus transacciones entrantes y saldo, pero NO gastar tus fondos. Comparte solo con personas de confianza.",
|
|
||||||
"key_export_fetching": "Obteniendo clave de la cartera...",
|
|
||||||
"key_export_reveal": "Revelar Clave",
|
|
||||||
|
|
||||||
# ---- QR dialog ----
|
|
||||||
"qr_title": "Código QR",
|
|
||||||
"qr_failed": "Error al generar código QR",
|
|
||||||
|
|
||||||
# ---- Request Payment dialog ----
|
|
||||||
"request_title": "Solicitar Pago",
|
|
||||||
"request_description": "Genera una solicitud de pago que otros pueden escanear o copiar. El código QR contiene tu dirección y cantidad/memo opcionales.",
|
|
||||||
"request_receive_address": "Dirección de Recepción:",
|
|
||||||
"request_select_address": "Seleccionar dirección...",
|
|
||||||
"request_shielded_addrs": "-- Direcciones Protegidas --",
|
|
||||||
"request_transparent_addrs": "-- Direcciones Transparentes --",
|
|
||||||
"request_amount": "Cantidad (opcional):",
|
|
||||||
"request_label": "Etiqueta (opcional):",
|
|
||||||
"request_memo": "Memo (opcional):",
|
|
||||||
"request_payment_uri": "URI de Pago:",
|
|
||||||
"request_copy_uri": "Copiar URI",
|
|
||||||
"request_uri_copied": "URI de pago copiada al portapapeles",
|
|
||||||
|
|
||||||
# ---- Shield dialog ----
|
|
||||||
"shield_title": "Proteger Recompensas de Coinbase",
|
|
||||||
"shield_description": "Protege tus recompensas de minería enviando salidas coinbase de direcciones transparentes a una dirección protegida. Esto mejora la privacidad ocultando tus ingresos de minería.",
|
|
||||||
"shield_from_address": "Dirección de Origen:",
|
|
||||||
"shield_wildcard_hint": "Usa '*' para proteger desde todas las direcciones transparentes",
|
|
||||||
"shield_to_address": "Dirección Destino (Protegida):",
|
|
||||||
"shield_select_z": "Seleccionar dirección z...",
|
|
||||||
"shield_utxo_limit": "Límite UTXO:",
|
|
||||||
"shield_max_utxos": "UTXOs máximos por operación",
|
|
||||||
"shield_funds": "Proteger Fondos",
|
|
||||||
"shield_started": "Operación de protección iniciada",
|
|
||||||
"shield_check_status": "Verificar Estado",
|
|
||||||
"shield_completed": "¡Operación completada exitosamente!",
|
|
||||||
"shield_merge_done": "¡Protección/fusión completada!",
|
|
||||||
"shield_in_progress": "Operación en progreso...",
|
|
||||||
"merge_title": "Fusionar a Dirección",
|
|
||||||
"merge_description": "Fusiona múltiples UTXOs en una sola dirección protegida. Esto puede ayudar a reducir el tamaño de la cartera y mejorar la privacidad.",
|
|
||||||
"merge_funds": "Fusionar Fondos",
|
|
||||||
"merge_started": "Operación de fusión iniciada",
|
|
||||||
|
|
||||||
# ---- Transaction Details dialog ----
|
|
||||||
"tx_details_title": "Detalles de Transacción",
|
|
||||||
"tx_received": "RECIBIDO",
|
|
||||||
"tx_sent": "ENVIADO",
|
|
||||||
"tx_mined": "MINADO",
|
|
||||||
"tx_immature": "INMADURO",
|
|
||||||
"tx_confirmations": "%d confirmaciones",
|
|
||||||
"tx_id_label": "ID de Transacción:",
|
|
||||||
"tx_to_address": "Dirección Destino:",
|
|
||||||
"tx_from_address": "Dirección Origen:",
|
|
||||||
"tx_view_explorer": "Ver en Explorador",
|
|
||||||
"pending": "Pendiente",
|
|
||||||
|
|
||||||
# ---- Validate Address dialog ----
|
|
||||||
"validate_title": "Validar Dirección",
|
|
||||||
"validate_description": "Ingresa una dirección DragonX para verificar si es válida y si pertenece a esta cartera.",
|
|
||||||
"validate_btn": "Validar",
|
|
||||||
"validate_results": "Resultados:",
|
|
||||||
"validate_status": "Estado:",
|
|
||||||
"validate_valid": "VÁLIDA",
|
|
||||||
"validate_invalid": "INVÁLIDA",
|
|
||||||
"validate_type": "Tipo:",
|
|
||||||
"validate_ownership": "Propiedad:",
|
|
||||||
"validate_is_mine": "Esta cartera es dueña de esta dirección",
|
|
||||||
"validate_not_mine": "No es propiedad de esta cartera",
|
|
||||||
"validate_shielded_type": "Protegida (dirección z)",
|
|
||||||
"validate_transparent_type": "Transparente (dirección t)",
|
|
||||||
|
|
||||||
# ---- Misc ----
|
|
||||||
"transaction_sent": "Transacción enviada exitosamente",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Load existing to preserve anything we might have missed
|
|
||||||
import os
|
|
||||||
existing_path = os.path.join(os.path.dirname(__file__), '..', 'res', 'lang', 'es.json')
|
|
||||||
out_path = existing_path
|
|
||||||
|
|
||||||
with open(out_path, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(dict(sorted(es.items())), f, indent=4, ensure_ascii=False)
|
|
||||||
f.write('\n')
|
|
||||||
|
|
||||||
print(f"Wrote {len(es)} Spanish translations to {out_path}")
|
|
||||||
@@ -1,646 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate French (fr) translations for ObsidianDragon wallet."""
|
|
||||||
import json, os
|
|
||||||
|
|
||||||
translations = {
|
|
||||||
"24h_change": "Variation 24h",
|
|
||||||
"24h_volume": "Volume 24h",
|
|
||||||
"about": "À propos",
|
|
||||||
"about_block_explorer": "Explorateur de blocs",
|
|
||||||
"about_block_height": "Hauteur de bloc :",
|
|
||||||
"about_build_date": "Date de compilation :",
|
|
||||||
"about_build_type": "Type de build :",
|
|
||||||
"about_chain": "Chaîne :",
|
|
||||||
"about_connections": "Connexions :",
|
|
||||||
"about_credits": "Crédits",
|
|
||||||
"about_daemon": "Daemon :",
|
|
||||||
"about_debug": "Débogage",
|
|
||||||
"about_dragonx": "À propos d'ObsidianDragon",
|
|
||||||
"about_edition": "Édition ImGui",
|
|
||||||
"about_github": "GitHub",
|
|
||||||
"about_imgui": "ImGui :",
|
|
||||||
"about_license": "Licence",
|
|
||||||
"about_license_text": "Ce logiciel est publié sous la licence publique générale GNU v3 (GPLv3). Vous êtes libre d'utiliser, de modifier et de distribuer ce logiciel selon les termes de la licence.",
|
|
||||||
"about_peers_count": "%zu pairs",
|
|
||||||
"about_release": "Version",
|
|
||||||
"about_title": "À propos d'ObsidianDragon",
|
|
||||||
"about_version": "Version :",
|
|
||||||
"about_website": "Site web",
|
|
||||||
"acrylic": "Acrylique",
|
|
||||||
"add": "Ajouter",
|
|
||||||
"address": "Adresse",
|
|
||||||
"address_book_add": "Ajouter une adresse",
|
|
||||||
"address_book_add_new": "Ajouter",
|
|
||||||
"address_book_added": "Adresse ajoutée au carnet",
|
|
||||||
"address_book_count": "%zu adresses enregistrées",
|
|
||||||
"address_book_deleted": "Entrée supprimée",
|
|
||||||
"address_book_edit": "Modifier l'adresse",
|
|
||||||
"address_book_empty": "Aucune adresse enregistrée. Cliquez sur 'Ajouter' pour en créer une.",
|
|
||||||
"address_book_exists": "L'adresse existe déjà dans le carnet",
|
|
||||||
"address_book_title": "Carnet d'adresses",
|
|
||||||
"address_book_update_failed": "Échec de la mise à jour - l'adresse est peut-être en double",
|
|
||||||
"address_book_updated": "Adresse mise à jour",
|
|
||||||
"address_copied": "Adresse copiée dans le presse-papiers",
|
|
||||||
"address_details": "Détails de l'adresse",
|
|
||||||
"address_label": "Adresse :",
|
|
||||||
"address_upper": "ADRESSE",
|
|
||||||
"address_url": "URL de l'adresse",
|
|
||||||
"addresses_appear_here": "Vos adresses de réception apparaîtront ici une fois connecté.",
|
|
||||||
"advanced": "AVANCÉ",
|
|
||||||
"all_filter": "Tout",
|
|
||||||
"allow_custom_fees": "Autoriser les frais personnalisés",
|
|
||||||
"amount": "Montant",
|
|
||||||
"amount_details": "DÉTAILS DU MONTANT",
|
|
||||||
"amount_exceeds_balance": "Le montant dépasse le solde",
|
|
||||||
"amount_label": "Montant :",
|
|
||||||
"appearance": "APPARENCE",
|
|
||||||
"auto_shield": "Auto-blindage du minage",
|
|
||||||
"available": "Disponible",
|
|
||||||
"backup_backing_up": "Sauvegarde en cours...",
|
|
||||||
"backup_create": "Créer une sauvegarde",
|
|
||||||
"backup_created": "Sauvegarde du portefeuille créée",
|
|
||||||
"backup_data": "SAUVEGARDE & DONNÉES",
|
|
||||||
"backup_description": "Créez une sauvegarde de votre fichier wallet.dat. Ce fichier contient toutes vos clés privées et l'historique des transactions. Conservez la sauvegarde dans un endroit sûr.",
|
|
||||||
"backup_destination": "Destination de sauvegarde :",
|
|
||||||
"backup_tip_external": "Stockez les sauvegardes sur des disques externes ou un stockage cloud",
|
|
||||||
"backup_tip_multiple": "Créez plusieurs sauvegardes à différents endroits",
|
|
||||||
"backup_tip_test": "Testez périodiquement la restauration à partir de la sauvegarde",
|
|
||||||
"backup_tips": "Conseils :",
|
|
||||||
"backup_title": "Sauvegarder le portefeuille",
|
|
||||||
"backup_wallet": "Sauvegarder le portefeuille...",
|
|
||||||
"backup_wallet_not_found": "Attention : wallet.dat introuvable à l'emplacement prévu",
|
|
||||||
"balance": "Solde",
|
|
||||||
"balance_layout": "Disposition du solde",
|
|
||||||
"ban": "Bannir",
|
|
||||||
"banned_peers": "Pairs bannis",
|
|
||||||
"block": "Bloc",
|
|
||||||
"block_bits": "Bits :",
|
|
||||||
"block_click_next": "Cliquez pour voir le bloc suivant",
|
|
||||||
"block_click_prev": "Cliquez pour voir le bloc précédent",
|
|
||||||
"block_explorer": "Explorateur de blocs",
|
|
||||||
"block_get_info": "Obtenir les infos du bloc",
|
|
||||||
"block_hash": "Hash du bloc :",
|
|
||||||
"block_height": "Hauteur du bloc :",
|
|
||||||
"block_info_title": "Informations sur le bloc",
|
|
||||||
"block_merkle_root": "Racine de Merkle :",
|
|
||||||
"block_nav_next": "Suivant >>",
|
|
||||||
"block_nav_prev": "<< Précédent",
|
|
||||||
"block_next": "Bloc suivant :",
|
|
||||||
"block_previous": "Bloc précédent :",
|
|
||||||
"block_size": "Taille :",
|
|
||||||
"block_timestamp": "Horodatage :",
|
|
||||||
"block_transactions": "Transactions :",
|
|
||||||
"blockchain_syncing": "Synchronisation de la blockchain (%.1f%%)... Les soldes peuvent être inexacts.",
|
|
||||||
"cancel": "Annuler",
|
|
||||||
"characters": "caractères",
|
|
||||||
"clear": "Effacer",
|
|
||||||
"clear_all_bans": "Lever tous les bannissements",
|
|
||||||
"clear_form_confirm": "Effacer tous les champs du formulaire ?",
|
|
||||||
"clear_request": "Effacer la demande",
|
|
||||||
"click_copy_address": "Cliquez pour copier l'adresse",
|
|
||||||
"click_copy_uri": "Cliquez pour copier l'URI",
|
|
||||||
"close": "Fermer",
|
|
||||||
"conf_count": "%d conf.",
|
|
||||||
"confirm_and_send": "Confirmer & Envoyer",
|
|
||||||
"confirm_send": "Confirmer l'envoi",
|
|
||||||
"confirm_transaction": "Confirmer la transaction",
|
|
||||||
"confirmations": "Confirmations",
|
|
||||||
"confirmations_display": "%d confirmations | %s",
|
|
||||||
"confirmed": "Confirmé",
|
|
||||||
"connected": "Connecté",
|
|
||||||
"connected_peers": "Pairs connectés",
|
|
||||||
"connecting": "Connexion...",
|
|
||||||
"console": "Console",
|
|
||||||
"console_auto_scroll": "Défilement auto",
|
|
||||||
"console_available_commands": "Commandes disponibles :",
|
|
||||||
"console_capturing_output": "Capture de la sortie du daemon...",
|
|
||||||
"console_clear": "Effacer",
|
|
||||||
"console_clear_console": "Effacer la console",
|
|
||||||
"console_cleared": "Console effacée",
|
|
||||||
"console_click_commands": "Cliquez sur les commandes ci-dessus pour les insérer",
|
|
||||||
"console_click_insert": "Cliquez pour insérer",
|
|
||||||
"console_click_insert_params": "Cliquez pour insérer avec paramètres",
|
|
||||||
"console_close": "Fermer",
|
|
||||||
"console_commands": "Commandes",
|
|
||||||
"console_common_rpc": "Commandes RPC courantes :",
|
|
||||||
"console_completions": "Complétions :",
|
|
||||||
"console_connected": "Connecté au daemon",
|
|
||||||
"console_copy_all": "Tout copier",
|
|
||||||
"console_copy_selected": "Copier",
|
|
||||||
"console_daemon": "Daemon",
|
|
||||||
"console_daemon_error": "Erreur du daemon !",
|
|
||||||
"console_daemon_started": "Daemon démarré",
|
|
||||||
"console_daemon_stopped": "Daemon arrêté",
|
|
||||||
"console_disconnected": "Déconnecté du daemon",
|
|
||||||
"console_errors": "Erreurs",
|
|
||||||
"console_filter_hint": "Filtrer la sortie...",
|
|
||||||
"console_help_clear": " clear - Effacer la console",
|
|
||||||
"console_help_getbalance": " getbalance - Afficher le solde transparent",
|
|
||||||
"console_help_getblockcount": " getblockcount - Afficher la hauteur de bloc actuelle",
|
|
||||||
"console_help_getinfo": " getinfo - Afficher les infos du nœud",
|
|
||||||
"console_help_getmininginfo": " getmininginfo - Afficher le statut du minage",
|
|
||||||
"console_help_getpeerinfo": " getpeerinfo - Afficher les pairs connectés",
|
|
||||||
"console_help_gettotalbalance": " gettotalbalance - Afficher le solde total",
|
|
||||||
"console_help_help": " help - Afficher ce message d'aide",
|
|
||||||
"console_help_setgenerate": " setgenerate - Contrôler le minage",
|
|
||||||
"console_help_stop": " stop - Arrêter le daemon",
|
|
||||||
"console_line_count": "%zu lignes",
|
|
||||||
"console_new_lines": "%d nouvelles lignes",
|
|
||||||
"console_no_daemon": "Pas de daemon",
|
|
||||||
"console_not_connected": "Erreur : Non connecté au daemon",
|
|
||||||
"console_rpc_reference": "Référence des commandes RPC",
|
|
||||||
"console_scanline": "Scanline de la console",
|
|
||||||
"console_search_commands": "Rechercher des commandes...",
|
|
||||||
"console_select_all": "Tout sélectionner",
|
|
||||||
"console_show_daemon_output": "Afficher la sortie du daemon",
|
|
||||||
"console_show_errors_only": "Afficher uniquement les erreurs",
|
|
||||||
"console_show_rpc_ref": "Afficher la référence des commandes RPC",
|
|
||||||
"console_showing_lines": "Affichage de %zu sur %zu lignes",
|
|
||||||
"console_starting_node": "Démarrage du nœud...",
|
|
||||||
"console_status_error": "Erreur",
|
|
||||||
"console_status_running": "En cours",
|
|
||||||
"console_status_starting": "Démarrage",
|
|
||||||
"console_status_stopped": "Arrêté",
|
|
||||||
"console_status_stopping": "Arrêt",
|
|
||||||
"console_status_unknown": "Inconnu",
|
|
||||||
"console_tab_completion": "Tab pour compléter",
|
|
||||||
"console_type_help": "Tapez 'help' pour les commandes disponibles",
|
|
||||||
"console_welcome": "Bienvenue dans la console ObsidianDragon",
|
|
||||||
"console_zoom_in": "Agrandir",
|
|
||||||
"console_zoom_out": "Réduire",
|
|
||||||
"copy": "Copier",
|
|
||||||
"copy_address": "Copier l'adresse complète",
|
|
||||||
"copy_error": "Copier l'erreur",
|
|
||||||
"copy_to_clipboard": "Copier dans le presse-papiers",
|
|
||||||
"copy_txid": "Copier le TxID",
|
|
||||||
"copy_uri": "Copier l'URI",
|
|
||||||
"current_price": "Prix actuel",
|
|
||||||
"custom_fees": "Frais personnalisés",
|
|
||||||
"dark": "Sombre",
|
|
||||||
"date": "Date",
|
|
||||||
"date_label": "Date :",
|
|
||||||
"delete": "Supprimer",
|
|
||||||
"difficulty": "Difficulté",
|
|
||||||
"disconnected": "Déconnecté",
|
|
||||||
"dismiss": "Ignorer",
|
|
||||||
"display": "Affichage",
|
|
||||||
"dragonx_green": "DragonX (Vert)",
|
|
||||||
"edit": "Modifier",
|
|
||||||
"error": "Erreur",
|
|
||||||
"est_time_to_block": "Temps est. par bloc",
|
|
||||||
"exit": "Quitter",
|
|
||||||
"explorer": "EXPLORATEUR",
|
|
||||||
"export": "Exporter",
|
|
||||||
"export_csv": "Exporter en CSV",
|
|
||||||
"export_keys_btn": "Exporter les clés",
|
|
||||||
"export_keys_danger": "DANGER : Ceci exportera TOUTES les clés privées de votre portefeuille ! Toute personne ayant accès à ce fichier peut voler vos fonds. Conservez-le en sécurité et supprimez-le après utilisation.",
|
|
||||||
"export_keys_include_t": "Inclure les adresses T (transparentes)",
|
|
||||||
"export_keys_include_z": "Inclure les adresses Z (blindées)",
|
|
||||||
"export_keys_options": "Options d'exportation :",
|
|
||||||
"export_keys_success": "Clés exportées avec succès",
|
|
||||||
"export_keys_title": "Exporter toutes les clés privées",
|
|
||||||
"export_private_key": "Exporter la clé privée",
|
|
||||||
"export_tx_count": "Exporter %zu transactions en fichier CSV.",
|
|
||||||
"export_tx_file_fail": "Impossible de créer le fichier CSV",
|
|
||||||
"export_tx_none": "Aucune transaction à exporter",
|
|
||||||
"export_tx_success": "Transactions exportées avec succès",
|
|
||||||
"export_tx_title": "Exporter les transactions en CSV",
|
|
||||||
"export_viewing_key": "Exporter la clé de visualisation",
|
|
||||||
"failed_create_shielded": "Échec de la création de l'adresse blindée",
|
|
||||||
"failed_create_transparent": "Échec de la création de l'adresse transparente",
|
|
||||||
"fee": "Frais",
|
|
||||||
"fee_high": "Élevés",
|
|
||||||
"fee_label": "Frais :",
|
|
||||||
"fee_low": "Faibles",
|
|
||||||
"fee_normal": "Normal",
|
|
||||||
"fetch_prices": "Récupérer les prix",
|
|
||||||
"file": "Fichier",
|
|
||||||
"file_save_location": "Le fichier sera enregistré dans : ~/.config/ObsidianDragon/",
|
|
||||||
"font_scale": "Taille de police",
|
|
||||||
"from": "De",
|
|
||||||
"from_upper": "DE",
|
|
||||||
"full_details": "Tous les détails",
|
|
||||||
"general": "Général",
|
|
||||||
"go_to_receive": "Aller à Recevoir",
|
|
||||||
"height": "Hauteur",
|
|
||||||
"help": "Aide",
|
|
||||||
"hide": "Masquer",
|
|
||||||
"history": "Historique",
|
|
||||||
"immature_type": "Immature",
|
|
||||||
"import": "Importer",
|
|
||||||
"import_key_btn": "Importer clé(s)",
|
|
||||||
"import_key_formats": "Formats de clés pris en charge :",
|
|
||||||
"import_key_full_rescan": "(0 = rescan complet)",
|
|
||||||
"import_key_label": "Clé(s) privée(s) :",
|
|
||||||
"import_key_no_valid": "Aucune clé valide trouvée dans l'entrée",
|
|
||||||
"import_key_rescan": "Re-scanner la blockchain après l'importation",
|
|
||||||
"import_key_start_height": "Hauteur de départ :",
|
|
||||||
"import_key_success": "Clés importées avec succès",
|
|
||||||
"import_key_t_format": "Clés privées WIF d'adresses T",
|
|
||||||
"import_key_title": "Importer une clé privée",
|
|
||||||
"import_key_tooltip": "Entrez une ou plusieurs clés privées, une par ligne.\nPrend en charge les clés z-adresse et t-adresse.\nLes lignes commençant par # sont traitées comme des commentaires.",
|
|
||||||
"import_key_warning": "Attention : Ne partagez jamais vos clés privées ! L'importation de clés provenant de sources non fiables peut compromettre votre portefeuille.",
|
|
||||||
"import_key_z_format": "Clés de dépenses z-adresse (secret-extended-key-...)",
|
|
||||||
"import_private_key": "Importer une clé privée...",
|
|
||||||
"invalid_address": "Format d'adresse invalide",
|
|
||||||
"ip_address": "Adresse IP",
|
|
||||||
"keep": "Conserver",
|
|
||||||
"keep_daemon": "Garder le daemon en marche",
|
|
||||||
"key_export_fetching": "Récupération de la clé depuis le portefeuille...",
|
|
||||||
"key_export_private_key": "Clé privée :",
|
|
||||||
"key_export_private_warning": "Gardez cette clé SECRÈTE ! Toute personne possédant cette clé peut dépenser vos fonds. Ne la partagez jamais en ligne ou avec des tiers non fiables.",
|
|
||||||
"key_export_reveal": "Révéler la clé",
|
|
||||||
"key_export_viewing_key": "Clé de visualisation :",
|
|
||||||
"key_export_viewing_warning": "Cette clé de visualisation permet à d'autres de voir vos transactions entrantes et votre solde, mais PAS de dépenser vos fonds. Ne la partagez qu'avec des personnes de confiance.",
|
|
||||||
"label": "Libellé :",
|
|
||||||
"language": "Langue",
|
|
||||||
"light": "Clair",
|
|
||||||
"loading": "Chargement...",
|
|
||||||
"loading_addresses": "Chargement des adresses...",
|
|
||||||
"local_hashrate": "Hashrate local",
|
|
||||||
"low_spec_mode": "Mode économie",
|
|
||||||
"market": "Marché",
|
|
||||||
"market_12h": "12h",
|
|
||||||
"market_18h": "18h",
|
|
||||||
"market_24h": "24h",
|
|
||||||
"market_24h_volume": "VOLUME 24H",
|
|
||||||
"market_6h": "6h",
|
|
||||||
"market_attribution": "Données de prix de NonKYC",
|
|
||||||
"market_btc_price": "PRIX BTC",
|
|
||||||
"market_cap": "Capitalisation",
|
|
||||||
"market_no_history": "Aucun historique de prix disponible",
|
|
||||||
"market_no_price": "Pas de données de prix",
|
|
||||||
"market_now": "Maintenant",
|
|
||||||
"market_pct_shielded": "%.0f%% Blindé",
|
|
||||||
"market_portfolio": "PORTEFEUILLE",
|
|
||||||
"market_price_unavailable": "Données de prix indisponibles",
|
|
||||||
"market_refresh_price": "Actualiser les données de prix",
|
|
||||||
"market_trade_on": "Échanger sur %s",
|
|
||||||
"mature": "Mature",
|
|
||||||
"max": "Max",
|
|
||||||
"memo": "Mémo (optionnel, chiffré)",
|
|
||||||
"memo_label": "Mémo :",
|
|
||||||
"memo_optional": "MÉMO (OPTIONNEL)",
|
|
||||||
"memo_upper": "MÉMO",
|
|
||||||
"memo_z_only": "Note : Les mémos ne sont disponibles que lors de l'envoi vers des adresses blindées (z)",
|
|
||||||
"merge_description": "Fusionnez plusieurs UTXOs en une seule adresse blindée. Cela peut réduire la taille du portefeuille et améliorer la confidentialité.",
|
|
||||||
"merge_funds": "Fusionner les fonds",
|
|
||||||
"merge_started": "Opération de fusion démarrée",
|
|
||||||
"merge_title": "Fusionner vers une adresse",
|
|
||||||
"mine_when_idle": "Miner au repos",
|
|
||||||
"mined": "miné",
|
|
||||||
"mined_filter": "Miné",
|
|
||||||
"mined_type": "Miné",
|
|
||||||
"mined_upper": "MINÉ",
|
|
||||||
"miner_fee": "Frais de mineur",
|
|
||||||
"mining": "Minage",
|
|
||||||
"mining_active": "Actif",
|
|
||||||
"mining_address_copied": "Adresse de minage copiée",
|
|
||||||
"mining_all_time": "Tout le temps",
|
|
||||||
"mining_already_saved": "URL du pool déjà enregistrée",
|
|
||||||
"mining_block_copied": "Hash du bloc copié",
|
|
||||||
"mining_chart_1m_ago": "il y a 1m",
|
|
||||||
"mining_chart_5m_ago": "il y a 5m",
|
|
||||||
"mining_chart_now": "Maintenant",
|
|
||||||
"mining_chart_start": "Début",
|
|
||||||
"mining_click": "Cliquer",
|
|
||||||
"mining_click_copy_address": "Cliquez pour copier l'adresse",
|
|
||||||
"mining_click_copy_block": "Cliquez pour copier le hash du bloc",
|
|
||||||
"mining_click_copy_difficulty": "Cliquez pour copier la difficulté",
|
|
||||||
"mining_connected": "Connecté",
|
|
||||||
"mining_connecting": "Connexion...",
|
|
||||||
"mining_control": "Contrôle du minage",
|
|
||||||
"mining_difficulty_copied": "Difficulté copiée",
|
|
||||||
"mining_est_block": "Bloc est.",
|
|
||||||
"mining_est_daily": "Est. quotidien",
|
|
||||||
"mining_filter_all": "Tout",
|
|
||||||
"mining_filter_tip_all": "Afficher tous les gains",
|
|
||||||
"mining_filter_tip_pool": "Afficher uniquement les gains du pool",
|
|
||||||
"mining_filter_tip_solo": "Afficher uniquement les gains solo",
|
|
||||||
"mining_idle_off_tooltip": "Activer le minage au repos",
|
|
||||||
"mining_idle_on_tooltip": "Désactiver le minage au repos",
|
|
||||||
"mining_local_hashrate": "Hashrate local",
|
|
||||||
"mining_mine": "Miner",
|
|
||||||
"mining_mining_addr": "Adr. minage",
|
|
||||||
"mining_network": "Réseau",
|
|
||||||
"mining_no_blocks_yet": "Aucun bloc trouvé pour l'instant",
|
|
||||||
"mining_no_payouts_yet": "Aucun paiement de pool pour l'instant",
|
|
||||||
"mining_no_saved_addresses": "Aucune adresse enregistrée",
|
|
||||||
"mining_no_saved_pools": "Aucun pool enregistré",
|
|
||||||
"mining_off": "Le minage est DÉSACTIVÉ",
|
|
||||||
"mining_on": "Le minage est ACTIVÉ",
|
|
||||||
"mining_open_in_explorer": "Ouvrir dans l'explorateur",
|
|
||||||
"mining_payout_address": "Adresse de paiement",
|
|
||||||
"mining_payout_tooltip": "Adresse pour recevoir les récompenses de minage",
|
|
||||||
"mining_pool": "Pool",
|
|
||||||
"mining_pool_hashrate": "Hashrate du pool",
|
|
||||||
"mining_pool_url": "URL du pool",
|
|
||||||
"mining_recent_blocks": "BLOCS RÉCENTS",
|
|
||||||
"mining_recent_payouts": "PAIEMENTS DE POOL RÉCENTS",
|
|
||||||
"mining_remove": "Supprimer",
|
|
||||||
"mining_reset_defaults": "Réinitialiser les paramètres",
|
|
||||||
"mining_save_payout_address": "Enregistrer l'adresse de paiement",
|
|
||||||
"mining_save_pool_url": "Enregistrer l'URL du pool",
|
|
||||||
"mining_saved_addresses": "Adresses enregistrées :",
|
|
||||||
"mining_saved_pools": "Pools enregistrés :",
|
|
||||||
"mining_shares": "Parts",
|
|
||||||
"mining_show_chart": "Graphique",
|
|
||||||
"mining_show_log": "Journal",
|
|
||||||
"mining_solo": "Solo",
|
|
||||||
"mining_starting": "Démarrage...",
|
|
||||||
"mining_starting_tooltip": "Le mineur démarre...",
|
|
||||||
"mining_statistics": "Statistiques de minage",
|
|
||||||
"mining_stop": "Arrêter",
|
|
||||||
"mining_stop_solo_for_pool": "Arrêtez le minage solo avant de démarrer le minage en pool",
|
|
||||||
"mining_stop_solo_for_pool_settings": "Arrêtez le minage solo pour modifier les paramètres du pool",
|
|
||||||
"mining_stopping": "Arrêt...",
|
|
||||||
"mining_stopping_tooltip": "Le mineur s'arrête...",
|
|
||||||
"mining_syncing_tooltip": "La blockchain se synchronise...",
|
|
||||||
"mining_threads": "Threads de minage",
|
|
||||||
"mining_to_save": "pour enregistrer",
|
|
||||||
"mining_today": "Aujourd'hui",
|
|
||||||
"mining_uptime": "Temps de fonctionnement",
|
|
||||||
"mining_yesterday": "Hier",
|
|
||||||
"network": "Réseau",
|
|
||||||
"network_fee": "FRAIS RÉSEAU",
|
|
||||||
"network_hashrate": "Hashrate du réseau",
|
|
||||||
"new": "+ Nouveau",
|
|
||||||
"new_shielded_created": "Nouvelle adresse blindée créée",
|
|
||||||
"new_t_address": "Nouvelle adresse T",
|
|
||||||
"new_t_transparent": "Nouvelle adresse t (Transparente)",
|
|
||||||
"new_transparent_created": "Nouvelle adresse transparente créée",
|
|
||||||
"new_z_address": "Nouvelle adresse Z",
|
|
||||||
"new_z_shielded": "Nouvelle adresse z (Blindée)",
|
|
||||||
"no_addresses": "Aucune adresse trouvée. Créez-en une avec les boutons ci-dessus.",
|
|
||||||
"no_addresses_available": "Aucune adresse disponible",
|
|
||||||
"no_addresses_match": "Aucune adresse ne correspond au filtre",
|
|
||||||
"no_addresses_with_balance": "Aucune adresse avec solde",
|
|
||||||
"no_matching": "Aucune transaction correspondante",
|
|
||||||
"no_recent_receives": "Aucune réception récente",
|
|
||||||
"no_recent_sends": "Aucun envoi récent",
|
|
||||||
"no_transactions": "Aucune transaction trouvée",
|
|
||||||
"node": "NŒUD",
|
|
||||||
"node_security": "NŒUD & SÉCURITÉ",
|
|
||||||
"noise": "Bruit",
|
|
||||||
"not_connected": "Non connecté au daemon...",
|
|
||||||
"not_connected_to_daemon": "Non connecté au daemon",
|
|
||||||
"notes": "Notes",
|
|
||||||
"notes_optional": "Notes (optionnel) :",
|
|
||||||
"output_filename": "Nom du fichier de sortie :",
|
|
||||||
"overview": "Aperçu",
|
|
||||||
"paste": "Coller",
|
|
||||||
"paste_from_clipboard": "Coller depuis le presse-papiers",
|
|
||||||
"pay_from": "Payer depuis",
|
|
||||||
"payment_request": "DEMANDE DE PAIEMENT",
|
|
||||||
"payment_request_copied": "Demande de paiement copiée",
|
|
||||||
"payment_uri_copied": "URI de paiement copiée",
|
|
||||||
"peers": "Pairs",
|
|
||||||
"peers_avg_ping": "Ping moyen",
|
|
||||||
"peers_ban_24h": "Bannir le pair 24h",
|
|
||||||
"peers_ban_score": "Score de ban : %d",
|
|
||||||
"peers_banned": "Bannis",
|
|
||||||
"peers_banned_count": "Bannis : %d",
|
|
||||||
"peers_best_block": "Meilleur bloc",
|
|
||||||
"peers_blockchain": "BLOCKCHAIN",
|
|
||||||
"peers_blocks": "Blocs",
|
|
||||||
"peers_blocks_left": "%d blocs restants",
|
|
||||||
"peers_clear_all_bans": "Lever tous les bannissements",
|
|
||||||
"peers_click_copy": "Cliquez pour copier",
|
|
||||||
"peers_connected": "Connectés",
|
|
||||||
"peers_connected_count": "Connectés : %d",
|
|
||||||
"peers_copy_ip": "Copier l'IP",
|
|
||||||
"peers_dir_in": "Ent.",
|
|
||||||
"peers_dir_out": "Sort.",
|
|
||||||
"peers_hash_copied": "Hash copié",
|
|
||||||
"peers_hashrate": "Hashrate",
|
|
||||||
"peers_in_out": "Ent./Sort.",
|
|
||||||
"peers_longest": "Plus longue",
|
|
||||||
"peers_longest_chain": "Plus longue chaîne",
|
|
||||||
"peers_memory": "Mémoire",
|
|
||||||
"peers_no_banned": "Aucun pair banni",
|
|
||||||
"peers_no_connected": "Aucun pair connecté",
|
|
||||||
"peers_no_tls": "Pas de TLS",
|
|
||||||
"peers_notarized": "Notarisé",
|
|
||||||
"peers_p2p_port": "Port P2P",
|
|
||||||
"peers_protocol": "Protocole",
|
|
||||||
"peers_received": "Reçu",
|
|
||||||
"peers_refresh": "Actualiser",
|
|
||||||
"peers_refresh_tooltip": "Actualiser la liste des pairs",
|
|
||||||
"peers_refreshing": "Actualisation...",
|
|
||||||
"peers_sent": "Envoyé",
|
|
||||||
"peers_tt_id": "ID : %d",
|
|
||||||
"peers_tt_received": "Reçu : %s",
|
|
||||||
"peers_tt_sent": "Envoyé : %s",
|
|
||||||
"peers_tt_services": "Services : %s",
|
|
||||||
"peers_tt_start_height": "Hauteur de départ : %d",
|
|
||||||
"peers_tt_synced": "Synchronisé H/B : %d/%d",
|
|
||||||
"peers_tt_tls_cipher": "TLS : %s",
|
|
||||||
"peers_unban": "Débannir",
|
|
||||||
"peers_upper": "PAIRS",
|
|
||||||
"peers_version": "Version",
|
|
||||||
"pending": "En attente",
|
|
||||||
"ping": "Ping",
|
|
||||||
"price_chart": "Graphique des prix",
|
|
||||||
"qr_code": "Code QR",
|
|
||||||
"qr_failed": "Échec de la génération du code QR",
|
|
||||||
"qr_title": "Code QR",
|
|
||||||
"qr_unavailable": "QR indisponible",
|
|
||||||
"receive": "Recevoir",
|
|
||||||
"received": "reçu",
|
|
||||||
"received_filter": "Reçu",
|
|
||||||
"received_label": "Reçu",
|
|
||||||
"received_upper": "REÇU",
|
|
||||||
"receiving_addresses": "Vos adresses de réception",
|
|
||||||
"recent_received": "REÇUS RÉCENTS",
|
|
||||||
"recent_sends": "ENVOIS RÉCENTS",
|
|
||||||
"recipient": "DESTINATAIRE",
|
|
||||||
"recv_type": "Reçu",
|
|
||||||
"refresh": "Actualiser",
|
|
||||||
"refresh_now": "Actualiser maintenant",
|
|
||||||
"report_bug": "Signaler un bug",
|
|
||||||
"request_amount": "Montant (optionnel) :",
|
|
||||||
"request_copy_uri": "Copier l'URI",
|
|
||||||
"request_description": "Générez une demande de paiement que d'autres peuvent scanner ou copier. Le code QR contient votre adresse et un montant/mémo optionnel.",
|
|
||||||
"request_label": "Libellé (optionnel) :",
|
|
||||||
"request_memo": "Mémo (optionnel) :",
|
|
||||||
"request_payment": "Demander un paiement",
|
|
||||||
"request_payment_uri": "URI de paiement :",
|
|
||||||
"request_receive_address": "Adresse de réception :",
|
|
||||||
"request_select_address": "Sélectionner une adresse...",
|
|
||||||
"request_shielded_addrs": "-- Adresses blindées --",
|
|
||||||
"request_title": "Demander un paiement",
|
|
||||||
"request_transparent_addrs": "-- Adresses transparentes --",
|
|
||||||
"request_uri_copied": "URI de paiement copiée dans le presse-papiers",
|
|
||||||
"rescan": "Re-scanner",
|
|
||||||
"reset_to_defaults": "Réinitialiser les paramètres",
|
|
||||||
"review_send": "Vérifier l'envoi",
|
|
||||||
"rpc_host": "Hôte RPC",
|
|
||||||
"rpc_pass": "Mot de passe",
|
|
||||||
"rpc_port": "Port",
|
|
||||||
"rpc_user": "Nom d'utilisateur",
|
|
||||||
"save": "Enregistrer",
|
|
||||||
"save_settings": "Enregistrer les paramètres",
|
|
||||||
"save_z_transactions": "Enregistrer les Z-tx dans la liste",
|
|
||||||
"search_placeholder": "Rechercher...",
|
|
||||||
"security": "SÉCURITÉ",
|
|
||||||
"select_address": "Sélectionner une adresse...",
|
|
||||||
"select_receiving_address": "Sélectionner une adresse de réception...",
|
|
||||||
"select_source_address": "Sélectionner une adresse source...",
|
|
||||||
"send": "Envoyer",
|
|
||||||
"send_amount": "Montant",
|
|
||||||
"send_amount_details": "DÉTAILS DU MONTANT",
|
|
||||||
"send_amount_upper": "MONTANT",
|
|
||||||
"send_clear_fields": "Effacer tous les champs du formulaire ?",
|
|
||||||
"send_copy_error": "Copier l'erreur",
|
|
||||||
"send_dismiss": "Ignorer",
|
|
||||||
"send_error_copied": "Erreur copiée dans le presse-papiers",
|
|
||||||
"send_error_prefix": "Erreur : %s",
|
|
||||||
"send_exceeds_available": "Dépasse le disponible (%.8f)",
|
|
||||||
"send_fee": "Frais",
|
|
||||||
"send_fee_high": "Élevés",
|
|
||||||
"send_fee_low": "Faibles",
|
|
||||||
"send_fee_normal": "Normal",
|
|
||||||
"send_form_restored": "Formulaire restauré",
|
|
||||||
"send_from_this_address": "Envoyer depuis cette adresse",
|
|
||||||
"send_go_to_receive": "Aller à Recevoir",
|
|
||||||
"send_keep": "Conserver",
|
|
||||||
"send_network_fee": "FRAIS RÉSEAU",
|
|
||||||
"send_no_balance": "Pas de solde",
|
|
||||||
"send_no_recent": "Aucun envoi récent",
|
|
||||||
"send_recent_sends": "ENVOIS RÉCENTS",
|
|
||||||
"send_recipient": "DESTINATAIRE",
|
|
||||||
"send_select_source": "Sélectionner une adresse source...",
|
|
||||||
"send_sending_from": "ENVOI DEPUIS",
|
|
||||||
"send_submitting": "Soumission de la transaction...",
|
|
||||||
"send_switch_to_receive": "Passez à Recevoir pour obtenir votre adresse et commencer à recevoir des fonds.",
|
|
||||||
"send_to": "Envoyer à",
|
|
||||||
"send_tooltip_enter_amount": "Entrez un montant à envoyer",
|
|
||||||
"send_tooltip_exceeds_balance": "Le montant dépasse le solde disponible",
|
|
||||||
"send_tooltip_in_progress": "Transaction déjà en cours",
|
|
||||||
"send_tooltip_invalid_address": "Entrez une adresse de destinataire valide",
|
|
||||||
"send_tooltip_not_connected": "Non connecté au daemon",
|
|
||||||
"send_tooltip_select_source": "Sélectionnez d'abord une adresse source",
|
|
||||||
"send_tooltip_syncing": "Attendez la synchronisation de la blockchain",
|
|
||||||
"send_total": "Total",
|
|
||||||
"send_transaction": "Envoyer la transaction",
|
|
||||||
"send_tx_failed": "Transaction échouée",
|
|
||||||
"send_tx_sent": "Transaction envoyée !",
|
|
||||||
"send_tx_success": "Transaction envoyée avec succès !",
|
|
||||||
"send_txid_copied": "TxID copié dans le presse-papiers",
|
|
||||||
"send_txid_label": "TxID : %s",
|
|
||||||
"send_valid_shielded": "Adresse blindée valide",
|
|
||||||
"send_valid_transparent": "Adresse transparente valide",
|
|
||||||
"send_wallet_empty": "Votre portefeuille est vide",
|
|
||||||
"send_yes_clear": "Oui, effacer",
|
|
||||||
"sending": "Envoi de la transaction",
|
|
||||||
"sending_from": "ENVOI DEPUIS",
|
|
||||||
"sent": "envoyé",
|
|
||||||
"sent_filter": "Envoyé",
|
|
||||||
"sent_type": "Envoyé",
|
|
||||||
"sent_upper": "ENVOYÉ",
|
|
||||||
"settings": "Paramètres",
|
|
||||||
"setup_wizard": "Assistant de configuration",
|
|
||||||
"share": "Partager",
|
|
||||||
"shield_check_status": "Vérifier le statut",
|
|
||||||
"shield_completed": "Opération terminée avec succès !",
|
|
||||||
"shield_description": "Blindez vos récompenses de minage en envoyant les sorties coinbase des adresses transparentes vers une adresse blindée. Cela améliore la confidentialité en masquant vos revenus de minage.",
|
|
||||||
"shield_from_address": "Depuis l'adresse :",
|
|
||||||
"shield_funds": "Blinder les fonds",
|
|
||||||
"shield_in_progress": "Opération en cours...",
|
|
||||||
"shield_max_utxos": "UTXOs max par opération",
|
|
||||||
"shield_merge_done": "Blindage/fusion terminé !",
|
|
||||||
"shield_select_z": "Sélectionner une z-adresse...",
|
|
||||||
"shield_started": "Opération de blindage démarrée",
|
|
||||||
"shield_title": "Blinder les récompenses coinbase",
|
|
||||||
"shield_to_address": "Vers l'adresse (blindée) :",
|
|
||||||
"shield_utxo_limit": "Limite UTXO :",
|
|
||||||
"shield_wildcard_hint": "Utilisez '*' pour blinder depuis toutes les adresses transparentes",
|
|
||||||
"shielded": "Blindé",
|
|
||||||
"shielded_to": "BLINDÉ VERS",
|
|
||||||
"shielded_type": "Blindé",
|
|
||||||
"show": "Afficher",
|
|
||||||
"show_qr_code": "Afficher le code QR",
|
|
||||||
"showing_transactions": "Affichage %d\xe2\x80\x93%d sur %d transactions (total : %zu)",
|
|
||||||
"simple_background": "Arrière-plan simple",
|
|
||||||
"start_mining": "Démarrer le minage",
|
|
||||||
"status": "Statut",
|
|
||||||
"stop_external": "Arrêter le daemon externe",
|
|
||||||
"stop_mining": "Arrêter le minage",
|
|
||||||
"submitting_transaction": "Soumission de la transaction...",
|
|
||||||
"success": "Succès",
|
|
||||||
"summary": "Résumé",
|
|
||||||
"syncing": "Synchronisation...",
|
|
||||||
"t_addresses": "Adresses T",
|
|
||||||
"test_connection": "Tester",
|
|
||||||
"theme": "Thème",
|
|
||||||
"theme_effects": "Effets de thème",
|
|
||||||
"time_days_ago": "il y a %d jours",
|
|
||||||
"time_hours_ago": "il y a %d heures",
|
|
||||||
"time_minutes_ago": "il y a %d minutes",
|
|
||||||
"time_seconds_ago": "il y a %d secondes",
|
|
||||||
"to": "À",
|
|
||||||
"to_upper": "À",
|
|
||||||
"tools": "OUTILS",
|
|
||||||
"total": "Total",
|
|
||||||
"transaction_id": "ID DE TRANSACTION",
|
|
||||||
"transaction_sent": "Transaction envoyée avec succès",
|
|
||||||
"transaction_sent_msg": "Transaction envoyée !",
|
|
||||||
"transaction_url": "URL de transaction",
|
|
||||||
"transactions": "Transactions",
|
|
||||||
"transactions_upper": "TRANSACTIONS",
|
|
||||||
"transparent": "Transparent",
|
|
||||||
"tx_confirmations": "%d confirmations",
|
|
||||||
"tx_details_title": "Détails de la transaction",
|
|
||||||
"tx_from_address": "Adresse d'origine :",
|
|
||||||
"tx_id_label": "ID de transaction :",
|
|
||||||
"tx_immature": "IMMATURE",
|
|
||||||
"tx_mined": "MINÉ",
|
|
||||||
"tx_received": "REÇU",
|
|
||||||
"tx_sent": "ENVOYÉ",
|
|
||||||
"tx_to_address": "Adresse de destination :",
|
|
||||||
"tx_view_explorer": "Voir dans l'explorateur",
|
|
||||||
"txs_count": "%d txs",
|
|
||||||
"type": "Type",
|
|
||||||
"ui_opacity": "Opacité de l'interface",
|
|
||||||
"unban": "Débannir",
|
|
||||||
"unconfirmed": "Non confirmé",
|
|
||||||
"undo_clear": "Annuler l'effacement",
|
|
||||||
"unknown": "Inconnu",
|
|
||||||
"use_embedded_daemon": "Utiliser le dragonxd intégré",
|
|
||||||
"use_tor": "Utiliser Tor",
|
|
||||||
"validate_btn": "Valider",
|
|
||||||
"validate_description": "Entrez une adresse DragonX pour vérifier si elle est valide et si elle appartient à ce portefeuille.",
|
|
||||||
"validate_invalid": "INVALIDE",
|
|
||||||
"validate_is_mine": "Ce portefeuille possède cette adresse",
|
|
||||||
"validate_not_mine": "N'appartient pas à ce portefeuille",
|
|
||||||
"validate_ownership": "Propriété :",
|
|
||||||
"validate_results": "Résultats :",
|
|
||||||
"validate_shielded_type": "Blindée (z-adresse)",
|
|
||||||
"validate_status": "Statut :",
|
|
||||||
"validate_title": "Valider l'adresse",
|
|
||||||
"validate_transparent_type": "Transparente (t-adresse)",
|
|
||||||
"validate_type": "Type :",
|
|
||||||
"validate_valid": "VALIDE",
|
|
||||||
"validating": "Validation...",
|
|
||||||
"verbose_logging": "Journalisation détaillée",
|
|
||||||
"version": "Version",
|
|
||||||
"view": "Afficher",
|
|
||||||
"view_details": "Voir les détails",
|
|
||||||
"view_on_explorer": "Voir dans l'explorateur",
|
|
||||||
"waiting_for_daemon": "En attente de la connexion au daemon...",
|
|
||||||
"wallet": "PORTEFEUILLE",
|
|
||||||
"wallet_empty": "Votre portefeuille est vide",
|
|
||||||
"wallet_empty_hint": "Passez à Recevoir pour obtenir votre adresse et commencer à recevoir des fonds.",
|
|
||||||
"warning": "Attention",
|
|
||||||
"warning_upper": "ATTENTION !",
|
|
||||||
"website": "Site web",
|
|
||||||
"window_opacity": "Opacité de la fenêtre",
|
|
||||||
"yes_clear": "Oui, effacer",
|
|
||||||
"your_addresses": "Vos adresses",
|
|
||||||
"z_addresses": "Adresses Z",
|
|
||||||
}
|
|
||||||
|
|
||||||
out = os.path.join(os.path.dirname(__file__), "..", "res", "lang", "fr.json")
|
|
||||||
with open(out, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(translations, f, indent=4, ensure_ascii=False, sort_keys=True)
|
|
||||||
print(f"Wrote {len(translations)} French translations to {os.path.abspath(out)}")
|
|
||||||
@@ -1,646 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate Japanese (ja) translations for ObsidianDragon wallet."""
|
|
||||||
import json, os
|
|
||||||
|
|
||||||
translations = {
|
|
||||||
"24h_change": "24時間変動",
|
|
||||||
"24h_volume": "24時間出来高",
|
|
||||||
"about": "概要",
|
|
||||||
"about_block_explorer": "ブロックエクスプローラー",
|
|
||||||
"about_block_height": "ブロック高:",
|
|
||||||
"about_build_date": "ビルド日:",
|
|
||||||
"about_build_type": "ビルドタイプ:",
|
|
||||||
"about_chain": "チェーン:",
|
|
||||||
"about_connections": "接続数:",
|
|
||||||
"about_credits": "クレジット",
|
|
||||||
"about_daemon": "デーモン:",
|
|
||||||
"about_debug": "デバッグ",
|
|
||||||
"about_dragonx": "ObsidianDragonについて",
|
|
||||||
"about_edition": "ImGui エディション",
|
|
||||||
"about_github": "GitHub",
|
|
||||||
"about_imgui": "ImGui:",
|
|
||||||
"about_license": "ライセンス",
|
|
||||||
"about_license_text": "本ソフトウェアはGNU General Public License v3 (GPLv3)の下で公開されています。ライセンス条項に従い、自由に使用、変更、配布できます。",
|
|
||||||
"about_peers_count": "%zu ピア",
|
|
||||||
"about_release": "リリース",
|
|
||||||
"about_title": "ObsidianDragonについて",
|
|
||||||
"about_version": "バージョン:",
|
|
||||||
"about_website": "ウェブサイト",
|
|
||||||
"acrylic": "アクリル",
|
|
||||||
"add": "追加",
|
|
||||||
"address": "アドレス",
|
|
||||||
"address_book_add": "アドレスを追加",
|
|
||||||
"address_book_add_new": "新規追加",
|
|
||||||
"address_book_added": "アドレスをアドレス帳に追加しました",
|
|
||||||
"address_book_count": "%zu 件のアドレスを保存済み",
|
|
||||||
"address_book_deleted": "エントリを削除しました",
|
|
||||||
"address_book_edit": "アドレスを編集",
|
|
||||||
"address_book_empty": "保存されたアドレスがありません。「新規追加」をクリックして追加してください。",
|
|
||||||
"address_book_exists": "アドレスは既にアドレス帳に存在します",
|
|
||||||
"address_book_title": "アドレス帳",
|
|
||||||
"address_book_update_failed": "更新に失敗しました — アドレスが重複している可能性があります",
|
|
||||||
"address_book_updated": "アドレスを更新しました",
|
|
||||||
"address_copied": "アドレスをクリップボードにコピーしました",
|
|
||||||
"address_details": "アドレス詳細",
|
|
||||||
"address_label": "アドレス:",
|
|
||||||
"address_upper": "アドレス",
|
|
||||||
"address_url": "アドレスURL",
|
|
||||||
"addresses_appear_here": "接続後、受信アドレスがここに表示されます。",
|
|
||||||
"advanced": "詳細設定",
|
|
||||||
"all_filter": "すべて",
|
|
||||||
"allow_custom_fees": "カスタム手数料を許可",
|
|
||||||
"amount": "金額",
|
|
||||||
"amount_details": "金額の詳細",
|
|
||||||
"amount_exceeds_balance": "金額が残高を超えています",
|
|
||||||
"amount_label": "金額:",
|
|
||||||
"appearance": "外観",
|
|
||||||
"auto_shield": "マイニング自動シールド",
|
|
||||||
"available": "利用可能",
|
|
||||||
"backup_backing_up": "バックアップ中...",
|
|
||||||
"backup_create": "バックアップを作成",
|
|
||||||
"backup_created": "ウォレットのバックアップを作成しました",
|
|
||||||
"backup_data": "バックアップとデータ",
|
|
||||||
"backup_description": "wallet.datファイルのバックアップを作成します。このファイルにはすべての秘密鍵と取引履歴が含まれています。バックアップは安全な場所に保管してください。",
|
|
||||||
"backup_destination": "バックアップ先:",
|
|
||||||
"backup_tip_external": "外部ドライブまたはクラウドストレージにバックアップを保存",
|
|
||||||
"backup_tip_multiple": "異なる場所に複数のバックアップを作成",
|
|
||||||
"backup_tip_test": "定期的にバックアップからの復元をテスト",
|
|
||||||
"backup_tips": "ヒント:",
|
|
||||||
"backup_title": "ウォレットのバックアップ",
|
|
||||||
"backup_wallet": "ウォレットをバックアップ...",
|
|
||||||
"backup_wallet_not_found": "警告:予想される場所にwallet.datが見つかりません",
|
|
||||||
"balance": "残高",
|
|
||||||
"balance_layout": "残高レイアウト",
|
|
||||||
"ban": "ブロック",
|
|
||||||
"banned_peers": "ブロック済みピア",
|
|
||||||
"block": "ブロック",
|
|
||||||
"block_bits": "ビット:",
|
|
||||||
"block_click_next": "クリックして次のブロックを表示",
|
|
||||||
"block_click_prev": "クリックして前のブロックを表示",
|
|
||||||
"block_explorer": "ブロックエクスプローラー",
|
|
||||||
"block_get_info": "ブロック情報を取得",
|
|
||||||
"block_hash": "ブロックハッシュ:",
|
|
||||||
"block_height": "ブロック高:",
|
|
||||||
"block_info_title": "ブロック情報",
|
|
||||||
"block_merkle_root": "マークルルート:",
|
|
||||||
"block_nav_next": "次へ >>",
|
|
||||||
"block_nav_prev": "<< 前へ",
|
|
||||||
"block_next": "次のブロック:",
|
|
||||||
"block_previous": "前のブロック:",
|
|
||||||
"block_size": "サイズ:",
|
|
||||||
"block_timestamp": "タイムスタンプ:",
|
|
||||||
"block_transactions": "トランザクション:",
|
|
||||||
"blockchain_syncing": "ブロックチェーン同期中 (%.1f%%)... 残高が不正確な場合があります。",
|
|
||||||
"cancel": "キャンセル",
|
|
||||||
"characters": "文字",
|
|
||||||
"clear": "クリア",
|
|
||||||
"clear_all_bans": "すべてのブロックを解除",
|
|
||||||
"clear_form_confirm": "すべてのフォームフィールドをクリアしますか?",
|
|
||||||
"clear_request": "リクエストをクリア",
|
|
||||||
"click_copy_address": "クリックしてアドレスをコピー",
|
|
||||||
"click_copy_uri": "クリックしてURIをコピー",
|
|
||||||
"close": "閉じる",
|
|
||||||
"conf_count": "%d 確認",
|
|
||||||
"confirm_and_send": "確認して送金",
|
|
||||||
"confirm_send": "送金を確認",
|
|
||||||
"confirm_transaction": "取引を確認",
|
|
||||||
"confirmations": "確認数",
|
|
||||||
"confirmations_display": "%d 確認 | %s",
|
|
||||||
"confirmed": "確認済み",
|
|
||||||
"connected": "接続済み",
|
|
||||||
"connected_peers": "接続中のピア",
|
|
||||||
"connecting": "接続中...",
|
|
||||||
"console": "コンソール",
|
|
||||||
"console_auto_scroll": "自動スクロール",
|
|
||||||
"console_available_commands": "利用可能なコマンド:",
|
|
||||||
"console_capturing_output": "デーモン出力をキャプチャ中...",
|
|
||||||
"console_clear": "クリア",
|
|
||||||
"console_clear_console": "コンソールをクリア",
|
|
||||||
"console_cleared": "コンソールをクリアしました",
|
|
||||||
"console_click_commands": "上のコマンドをクリックして挿入",
|
|
||||||
"console_click_insert": "クリックして挿入",
|
|
||||||
"console_click_insert_params": "クリックしてパラメータ付きで挿入",
|
|
||||||
"console_close": "閉じる",
|
|
||||||
"console_commands": "コマンド",
|
|
||||||
"console_common_rpc": "一般的なRPCコマンド:",
|
|
||||||
"console_completions": "補完:",
|
|
||||||
"console_connected": "デーモンに接続済み",
|
|
||||||
"console_copy_all": "すべてコピー",
|
|
||||||
"console_copy_selected": "コピー",
|
|
||||||
"console_daemon": "デーモン",
|
|
||||||
"console_daemon_error": "デーモンエラー!",
|
|
||||||
"console_daemon_started": "デーモンが起動しました",
|
|
||||||
"console_daemon_stopped": "デーモンが停止しました",
|
|
||||||
"console_disconnected": "デーモンから切断されました",
|
|
||||||
"console_errors": "エラー",
|
|
||||||
"console_filter_hint": "出力をフィルタ...",
|
|
||||||
"console_help_clear": " clear - コンソールをクリア",
|
|
||||||
"console_help_getbalance": " getbalance - 透明残高を表示",
|
|
||||||
"console_help_getblockcount": " getblockcount - 現在のブロック高を表示",
|
|
||||||
"console_help_getinfo": " getinfo - ノード情報を表示",
|
|
||||||
"console_help_getmininginfo": " getmininginfo - マイニング状況を表示",
|
|
||||||
"console_help_getpeerinfo": " getpeerinfo - 接続中のピアを表示",
|
|
||||||
"console_help_gettotalbalance": " gettotalbalance - 合計残高を表示",
|
|
||||||
"console_help_help": " help - このヘルプを表示",
|
|
||||||
"console_help_setgenerate": " setgenerate - マイニングを制御",
|
|
||||||
"console_help_stop": " stop - デーモンを停止",
|
|
||||||
"console_line_count": "%zu 行",
|
|
||||||
"console_new_lines": "%d 新しい行",
|
|
||||||
"console_no_daemon": "デーモンなし",
|
|
||||||
"console_not_connected": "エラー:デーモンに接続されていません",
|
|
||||||
"console_rpc_reference": "RPCコマンドリファレンス",
|
|
||||||
"console_scanline": "コンソールスキャンライン",
|
|
||||||
"console_search_commands": "コマンドを検索...",
|
|
||||||
"console_select_all": "すべて選択",
|
|
||||||
"console_show_daemon_output": "デーモン出力を表示",
|
|
||||||
"console_show_errors_only": "エラーのみ表示",
|
|
||||||
"console_show_rpc_ref": "RPCコマンドリファレンスを表示",
|
|
||||||
"console_showing_lines": "%zu / %zu 行を表示中",
|
|
||||||
"console_starting_node": "ノードを起動中...",
|
|
||||||
"console_status_error": "エラー",
|
|
||||||
"console_status_running": "実行中",
|
|
||||||
"console_status_starting": "起動中",
|
|
||||||
"console_status_stopped": "停止済み",
|
|
||||||
"console_status_stopping": "停止中",
|
|
||||||
"console_status_unknown": "不明",
|
|
||||||
"console_tab_completion": "Tabで補完",
|
|
||||||
"console_type_help": "'help'と入力して利用可能なコマンドを表示",
|
|
||||||
"console_welcome": "ObsidianDragonコンソールへようこそ",
|
|
||||||
"console_zoom_in": "拡大",
|
|
||||||
"console_zoom_out": "縮小",
|
|
||||||
"copy": "コピー",
|
|
||||||
"copy_address": "完全なアドレスをコピー",
|
|
||||||
"copy_error": "エラーをコピー",
|
|
||||||
"copy_to_clipboard": "クリップボードにコピー",
|
|
||||||
"copy_txid": "TxIDをコピー",
|
|
||||||
"copy_uri": "URIをコピー",
|
|
||||||
"current_price": "現在の価格",
|
|
||||||
"custom_fees": "カスタム手数料",
|
|
||||||
"dark": "ダーク",
|
|
||||||
"date": "日付",
|
|
||||||
"date_label": "日付:",
|
|
||||||
"delete": "削除",
|
|
||||||
"difficulty": "難易度",
|
|
||||||
"disconnected": "切断済み",
|
|
||||||
"dismiss": "閉じる",
|
|
||||||
"display": "表示",
|
|
||||||
"dragonx_green": "DragonX(グリーン)",
|
|
||||||
"edit": "編集",
|
|
||||||
"error": "エラー",
|
|
||||||
"est_time_to_block": "予測ブロック時間",
|
|
||||||
"exit": "終了",
|
|
||||||
"explorer": "エクスプローラー",
|
|
||||||
"export": "エクスポート",
|
|
||||||
"export_csv": "CSVエクスポート",
|
|
||||||
"export_keys_btn": "鍵をエクスポート",
|
|
||||||
"export_keys_danger": "危険:ウォレットからすべての秘密鍵がエクスポートされます!このファイルにアクセスできる人は誰でもあなたの資金を盗めます。安全に保管し、使用後は削除してください。",
|
|
||||||
"export_keys_include_t": "Tアドレスを含める(透明)",
|
|
||||||
"export_keys_include_z": "Zアドレスを含める(シールド)",
|
|
||||||
"export_keys_options": "エクスポートオプション:",
|
|
||||||
"export_keys_success": "鍵のエクスポートに成功しました",
|
|
||||||
"export_keys_title": "すべての秘密鍵をエクスポート",
|
|
||||||
"export_private_key": "秘密鍵をエクスポート",
|
|
||||||
"export_tx_count": "%zu件の取引をCSVファイルにエクスポート。",
|
|
||||||
"export_tx_file_fail": "CSVファイルの作成に失敗しました",
|
|
||||||
"export_tx_none": "エクスポートする取引がありません",
|
|
||||||
"export_tx_success": "取引のエクスポートに成功しました",
|
|
||||||
"export_tx_title": "取引をCSVにエクスポート",
|
|
||||||
"export_viewing_key": "閲覧鍵をエクスポート",
|
|
||||||
"failed_create_shielded": "シールドアドレスの作成に失敗しました",
|
|
||||||
"failed_create_transparent": "透明アドレスの作成に失敗しました",
|
|
||||||
"fee": "手数料",
|
|
||||||
"fee_high": "高い",
|
|
||||||
"fee_label": "手数料:",
|
|
||||||
"fee_low": "低い",
|
|
||||||
"fee_normal": "通常",
|
|
||||||
"fetch_prices": "価格を取得",
|
|
||||||
"file": "ファイル",
|
|
||||||
"file_save_location": "ファイルの保存先:~/.config/ObsidianDragon/",
|
|
||||||
"font_scale": "フォントサイズ",
|
|
||||||
"from": "送信元",
|
|
||||||
"from_upper": "送信元",
|
|
||||||
"full_details": "詳細情報",
|
|
||||||
"general": "一般",
|
|
||||||
"go_to_receive": "受信へ移動",
|
|
||||||
"height": "高さ",
|
|
||||||
"help": "ヘルプ",
|
|
||||||
"hide": "非表示",
|
|
||||||
"history": "履歴",
|
|
||||||
"immature_type": "未成熟",
|
|
||||||
"import": "インポート",
|
|
||||||
"import_key_btn": "鍵をインポート",
|
|
||||||
"import_key_formats": "サポートされる鍵形式:",
|
|
||||||
"import_key_full_rescan": "(0 = 完全再スキャン)",
|
|
||||||
"import_key_label": "秘密鍵:",
|
|
||||||
"import_key_no_valid": "入力に有効な鍵が見つかりません",
|
|
||||||
"import_key_rescan": "インポート後にブロックチェーンを再スキャン",
|
|
||||||
"import_key_start_height": "開始高:",
|
|
||||||
"import_key_success": "鍵のインポートに成功しました",
|
|
||||||
"import_key_t_format": "TアドレスWIF秘密鍵",
|
|
||||||
"import_key_title": "秘密鍵をインポート",
|
|
||||||
"import_key_tooltip": "1行に1つずつ秘密鍵を入力してください。\nzアドレスとtアドレスの鍵の両方に対応しています。\n#で始まる行はコメントとして扱われます。",
|
|
||||||
"import_key_warning": "警告:秘密鍵を決して共有しないでください!信頼できないソースからの鍵のインポートはウォレットを危険にさらす可能性があります。",
|
|
||||||
"import_key_z_format": "Zアドレス支出鍵 (secret-extended-key-...)",
|
|
||||||
"import_private_key": "秘密鍵をインポート...",
|
|
||||||
"invalid_address": "無効なアドレス形式",
|
|
||||||
"ip_address": "IPアドレス",
|
|
||||||
"keep": "保持",
|
|
||||||
"keep_daemon": "デーモンを実行し続ける",
|
|
||||||
"key_export_fetching": "ウォレットから鍵を取得中...",
|
|
||||||
"key_export_private_key": "秘密鍵:",
|
|
||||||
"key_export_private_warning": "この鍵は秘密にしてください!この鍵を持つ人は誰でもあなたの資金を使えます。オンラインや信頼できない相手と共有しないでください。",
|
|
||||||
"key_export_reveal": "鍵を表示",
|
|
||||||
"key_export_viewing_key": "閲覧鍵:",
|
|
||||||
"key_export_viewing_warning": "この閲覧鍵を使うと、他者があなたの受信取引と残高を見ることができますが、資金を使うことはできません。信頼できる相手とのみ共有してください。",
|
|
||||||
"label": "ラベル:",
|
|
||||||
"language": "言語",
|
|
||||||
"light": "ライト",
|
|
||||||
"loading": "読み込み中...",
|
|
||||||
"loading_addresses": "アドレスを読み込み中...",
|
|
||||||
"local_hashrate": "ローカルハッシュレート",
|
|
||||||
"low_spec_mode": "省電力モード",
|
|
||||||
"market": "市場",
|
|
||||||
"market_12h": "12時間",
|
|
||||||
"market_18h": "18時間",
|
|
||||||
"market_24h": "24時間",
|
|
||||||
"market_24h_volume": "24時間出来高",
|
|
||||||
"market_6h": "6時間",
|
|
||||||
"market_attribution": "価格データ:NonKYC提供",
|
|
||||||
"market_btc_price": "BTC価格",
|
|
||||||
"market_cap": "時価総額",
|
|
||||||
"market_no_history": "価格履歴がありません",
|
|
||||||
"market_no_price": "価格データなし",
|
|
||||||
"market_now": "現在",
|
|
||||||
"market_pct_shielded": "%.0f%% シールド済み",
|
|
||||||
"market_portfolio": "ポートフォリオ",
|
|
||||||
"market_price_unavailable": "価格データが利用できません",
|
|
||||||
"market_refresh_price": "価格データを更新",
|
|
||||||
"market_trade_on": "%s で取引",
|
|
||||||
"mature": "成熟済み",
|
|
||||||
"max": "最大",
|
|
||||||
"memo": "メモ(任意、暗号化)",
|
|
||||||
"memo_label": "メモ:",
|
|
||||||
"memo_optional": "メモ(任意)",
|
|
||||||
"memo_upper": "メモ",
|
|
||||||
"memo_z_only": "注:メモはシールド (z) アドレスへの送金時のみ利用可能です",
|
|
||||||
"merge_description": "複数のUTXOを単一のシールドアドレスに統合します。ウォレットサイズの縮小とプライバシーの向上に役立ちます。",
|
|
||||||
"merge_funds": "資金を統合",
|
|
||||||
"merge_started": "統合操作を開始しました",
|
|
||||||
"merge_title": "アドレスに統合",
|
|
||||||
"mine_when_idle": "アイドル時にマイニング",
|
|
||||||
"mined": "採掘済み",
|
|
||||||
"mined_filter": "採掘済み",
|
|
||||||
"mined_type": "採掘済み",
|
|
||||||
"mined_upper": "採掘済み",
|
|
||||||
"miner_fee": "マイナー手数料",
|
|
||||||
"mining": "マイニング",
|
|
||||||
"mining_active": "アクティブ",
|
|
||||||
"mining_address_copied": "マイニングアドレスをコピーしました",
|
|
||||||
"mining_all_time": "全期間",
|
|
||||||
"mining_already_saved": "プールURLは既に保存済みです",
|
|
||||||
"mining_block_copied": "ブロックハッシュをコピーしました",
|
|
||||||
"mining_chart_1m_ago": "1分前",
|
|
||||||
"mining_chart_5m_ago": "5分前",
|
|
||||||
"mining_chart_now": "現在",
|
|
||||||
"mining_chart_start": "開始",
|
|
||||||
"mining_click": "クリック",
|
|
||||||
"mining_click_copy_address": "クリックしてアドレスをコピー",
|
|
||||||
"mining_click_copy_block": "クリックしてブロックハッシュをコピー",
|
|
||||||
"mining_click_copy_difficulty": "クリックして難易度をコピー",
|
|
||||||
"mining_connected": "接続済み",
|
|
||||||
"mining_connecting": "接続中...",
|
|
||||||
"mining_control": "マイニング制御",
|
|
||||||
"mining_difficulty_copied": "難易度をコピーしました",
|
|
||||||
"mining_est_block": "予測ブロック",
|
|
||||||
"mining_est_daily": "予測日収",
|
|
||||||
"mining_filter_all": "すべて",
|
|
||||||
"mining_filter_tip_all": "すべての収益を表示",
|
|
||||||
"mining_filter_tip_pool": "プール収益のみ表示",
|
|
||||||
"mining_filter_tip_solo": "ソロ収益のみ表示",
|
|
||||||
"mining_idle_off_tooltip": "アイドルマイニングを有効にする",
|
|
||||||
"mining_idle_on_tooltip": "アイドルマイニングを無効にする",
|
|
||||||
"mining_local_hashrate": "ローカルハッシュレート",
|
|
||||||
"mining_mine": "マイニング",
|
|
||||||
"mining_mining_addr": "マイニングアドレス",
|
|
||||||
"mining_network": "ネットワーク",
|
|
||||||
"mining_no_blocks_yet": "まだブロックが見つかっていません",
|
|
||||||
"mining_no_payouts_yet": "まだプール支払いがありません",
|
|
||||||
"mining_no_saved_addresses": "保存されたアドレスがありません",
|
|
||||||
"mining_no_saved_pools": "保存されたプールがありません",
|
|
||||||
"mining_off": "マイニングはオフです",
|
|
||||||
"mining_on": "マイニングはオンです",
|
|
||||||
"mining_open_in_explorer": "エクスプローラーで開く",
|
|
||||||
"mining_payout_address": "支払いアドレス",
|
|
||||||
"mining_payout_tooltip": "マイニング報酬の受取アドレス",
|
|
||||||
"mining_pool": "プール",
|
|
||||||
"mining_pool_hashrate": "プールハッシュレート",
|
|
||||||
"mining_pool_url": "プールURL",
|
|
||||||
"mining_recent_blocks": "最近のブロック",
|
|
||||||
"mining_recent_payouts": "最近のプール支払い",
|
|
||||||
"mining_remove": "削除",
|
|
||||||
"mining_reset_defaults": "デフォルトにリセット",
|
|
||||||
"mining_save_payout_address": "支払いアドレスを保存",
|
|
||||||
"mining_save_pool_url": "プールURLを保存",
|
|
||||||
"mining_saved_addresses": "保存済みアドレス:",
|
|
||||||
"mining_saved_pools": "保存済みプール:",
|
|
||||||
"mining_shares": "シェア",
|
|
||||||
"mining_show_chart": "チャート",
|
|
||||||
"mining_show_log": "ログ",
|
|
||||||
"mining_solo": "ソロ",
|
|
||||||
"mining_starting": "起動中...",
|
|
||||||
"mining_starting_tooltip": "マイナーを起動中...",
|
|
||||||
"mining_statistics": "マイニング統計",
|
|
||||||
"mining_stop": "停止",
|
|
||||||
"mining_stop_solo_for_pool": "プールマイニングを開始する前にソロマイニングを停止してください",
|
|
||||||
"mining_stop_solo_for_pool_settings": "プール設定を変更するにはソロマイニングを停止してください",
|
|
||||||
"mining_stopping": "停止中...",
|
|
||||||
"mining_stopping_tooltip": "マイナーを停止中...",
|
|
||||||
"mining_syncing_tooltip": "ブロックチェーン同期中...",
|
|
||||||
"mining_threads": "マイニングスレッド",
|
|
||||||
"mining_to_save": "保存する",
|
|
||||||
"mining_today": "今日",
|
|
||||||
"mining_uptime": "稼働時間",
|
|
||||||
"mining_yesterday": "昨日",
|
|
||||||
"network": "ネットワーク",
|
|
||||||
"network_fee": "ネットワーク手数料",
|
|
||||||
"network_hashrate": "ネットワークハッシュレート",
|
|
||||||
"new": "+ 新規",
|
|
||||||
"new_shielded_created": "新しいシールドアドレスを作成しました",
|
|
||||||
"new_t_address": "新しいTアドレス",
|
|
||||||
"new_t_transparent": "新しいtアドレス(透明)",
|
|
||||||
"new_transparent_created": "新しい透明アドレスを作成しました",
|
|
||||||
"new_z_address": "新しいZアドレス",
|
|
||||||
"new_z_shielded": "新しいzアドレス(シールド)",
|
|
||||||
"no_addresses": "アドレスが見つかりません。上のボタンを使用して作成してください。",
|
|
||||||
"no_addresses_available": "利用可能なアドレスがありません",
|
|
||||||
"no_addresses_match": "フィルタに一致するアドレスがありません",
|
|
||||||
"no_addresses_with_balance": "残高のあるアドレスがありません",
|
|
||||||
"no_matching": "一致する取引がありません",
|
|
||||||
"no_recent_receives": "最近の受信がありません",
|
|
||||||
"no_recent_sends": "最近の送信がありません",
|
|
||||||
"no_transactions": "取引が見つかりません",
|
|
||||||
"node": "ノード",
|
|
||||||
"node_security": "ノードとセキュリティ",
|
|
||||||
"noise": "ノイズ",
|
|
||||||
"not_connected": "デーモンに未接続...",
|
|
||||||
"not_connected_to_daemon": "デーモンに未接続",
|
|
||||||
"notes": "メモ",
|
|
||||||
"notes_optional": "メモ(任意):",
|
|
||||||
"output_filename": "出力ファイル名:",
|
|
||||||
"overview": "概要",
|
|
||||||
"paste": "貼り付け",
|
|
||||||
"paste_from_clipboard": "クリップボードから貼り付け",
|
|
||||||
"pay_from": "支払い元",
|
|
||||||
"payment_request": "支払い請求",
|
|
||||||
"payment_request_copied": "支払い請求をコピーしました",
|
|
||||||
"payment_uri_copied": "支払いURIをコピーしました",
|
|
||||||
"peers": "ピア",
|
|
||||||
"peers_avg_ping": "平均Ping",
|
|
||||||
"peers_ban_24h": "ピアを24時間ブロック",
|
|
||||||
"peers_ban_score": "ブロックスコア:%d",
|
|
||||||
"peers_banned": "ブロック済み",
|
|
||||||
"peers_banned_count": "ブロック済み:%d",
|
|
||||||
"peers_best_block": "最良ブロック",
|
|
||||||
"peers_blockchain": "ブロックチェーン",
|
|
||||||
"peers_blocks": "ブロック",
|
|
||||||
"peers_blocks_left": "残り %d ブロック",
|
|
||||||
"peers_clear_all_bans": "すべてのブロックを解除",
|
|
||||||
"peers_click_copy": "クリックしてコピー",
|
|
||||||
"peers_connected": "接続済み",
|
|
||||||
"peers_connected_count": "接続済み:%d",
|
|
||||||
"peers_copy_ip": "IPをコピー",
|
|
||||||
"peers_dir_in": "入",
|
|
||||||
"peers_dir_out": "出",
|
|
||||||
"peers_hash_copied": "ハッシュをコピーしました",
|
|
||||||
"peers_hashrate": "ハッシュレート",
|
|
||||||
"peers_in_out": "入/出",
|
|
||||||
"peers_longest": "最長",
|
|
||||||
"peers_longest_chain": "最長チェーン",
|
|
||||||
"peers_memory": "メモリ",
|
|
||||||
"peers_no_banned": "ブロック済みピアなし",
|
|
||||||
"peers_no_connected": "接続済みピアなし",
|
|
||||||
"peers_no_tls": "TLSなし",
|
|
||||||
"peers_notarized": "公証済み",
|
|
||||||
"peers_p2p_port": "P2Pポート",
|
|
||||||
"peers_protocol": "プロトコル",
|
|
||||||
"peers_received": "受信",
|
|
||||||
"peers_refresh": "更新",
|
|
||||||
"peers_refresh_tooltip": "ピアリストを更新",
|
|
||||||
"peers_refreshing": "更新中...",
|
|
||||||
"peers_sent": "送信",
|
|
||||||
"peers_tt_id": "ID:%d",
|
|
||||||
"peers_tt_received": "受信:%s",
|
|
||||||
"peers_tt_sent": "送信:%s",
|
|
||||||
"peers_tt_services": "サービス:%s",
|
|
||||||
"peers_tt_start_height": "開始高:%d",
|
|
||||||
"peers_tt_synced": "同期済み H/B:%d/%d",
|
|
||||||
"peers_tt_tls_cipher": "TLS:%s",
|
|
||||||
"peers_unban": "ブロック解除",
|
|
||||||
"peers_upper": "ピア",
|
|
||||||
"peers_version": "バージョン",
|
|
||||||
"pending": "保留中",
|
|
||||||
"ping": "Ping",
|
|
||||||
"price_chart": "価格チャート",
|
|
||||||
"qr_code": "QRコード",
|
|
||||||
"qr_failed": "QRコードの生成に失敗しました",
|
|
||||||
"qr_title": "QRコード",
|
|
||||||
"qr_unavailable": "QR利用不可",
|
|
||||||
"receive": "受信",
|
|
||||||
"received": "受信済み",
|
|
||||||
"received_filter": "受信済み",
|
|
||||||
"received_label": "受信済み",
|
|
||||||
"received_upper": "受信済み",
|
|
||||||
"receiving_addresses": "あなたの受信アドレス",
|
|
||||||
"recent_received": "最近の受信",
|
|
||||||
"recent_sends": "最近の送信",
|
|
||||||
"recipient": "受取人",
|
|
||||||
"recv_type": "受信",
|
|
||||||
"refresh": "更新",
|
|
||||||
"refresh_now": "今すぐ更新",
|
|
||||||
"report_bug": "バグを報告",
|
|
||||||
"request_amount": "金額(任意):",
|
|
||||||
"request_copy_uri": "URIをコピー",
|
|
||||||
"request_description": "他の人がスキャンまたはコピーできる支払い請求を生成します。QRコードにはアドレスとオプションの金額/メモが含まれます。",
|
|
||||||
"request_label": "ラベル(任意):",
|
|
||||||
"request_memo": "メモ(任意):",
|
|
||||||
"request_payment": "支払いを請求",
|
|
||||||
"request_payment_uri": "支払いURI:",
|
|
||||||
"request_receive_address": "受信アドレス:",
|
|
||||||
"request_select_address": "アドレスを選択...",
|
|
||||||
"request_shielded_addrs": "-- シールドアドレス --",
|
|
||||||
"request_title": "支払いを請求",
|
|
||||||
"request_transparent_addrs": "-- 透明アドレス --",
|
|
||||||
"request_uri_copied": "支払いURIをクリップボードにコピーしました",
|
|
||||||
"rescan": "再スキャン",
|
|
||||||
"reset_to_defaults": "デフォルトにリセット",
|
|
||||||
"review_send": "送金を確認",
|
|
||||||
"rpc_host": "RPCホスト",
|
|
||||||
"rpc_pass": "パスワード",
|
|
||||||
"rpc_port": "ポート",
|
|
||||||
"rpc_user": "ユーザー名",
|
|
||||||
"save": "保存",
|
|
||||||
"save_settings": "設定を保存",
|
|
||||||
"save_z_transactions": "Z取引を取引リストに保存",
|
|
||||||
"search_placeholder": "検索...",
|
|
||||||
"security": "セキュリティ",
|
|
||||||
"select_address": "アドレスを選択...",
|
|
||||||
"select_receiving_address": "受信アドレスを選択...",
|
|
||||||
"select_source_address": "送信元アドレスを選択...",
|
|
||||||
"send": "送金",
|
|
||||||
"send_amount": "金額",
|
|
||||||
"send_amount_details": "金額の詳細",
|
|
||||||
"send_amount_upper": "金額",
|
|
||||||
"send_clear_fields": "すべてのフォームフィールドをクリアしますか?",
|
|
||||||
"send_copy_error": "エラーをコピー",
|
|
||||||
"send_dismiss": "閉じる",
|
|
||||||
"send_error_copied": "エラーをクリップボードにコピーしました",
|
|
||||||
"send_error_prefix": "エラー:%s",
|
|
||||||
"send_exceeds_available": "利用可能額を超過 (%.8f)",
|
|
||||||
"send_fee": "手数料",
|
|
||||||
"send_fee_high": "高い",
|
|
||||||
"send_fee_low": "低い",
|
|
||||||
"send_fee_normal": "通常",
|
|
||||||
"send_form_restored": "フォームが復元されました",
|
|
||||||
"send_from_this_address": "このアドレスから送金",
|
|
||||||
"send_go_to_receive": "受信へ移動",
|
|
||||||
"send_keep": "保持",
|
|
||||||
"send_network_fee": "ネットワーク手数料",
|
|
||||||
"send_no_balance": "残高なし",
|
|
||||||
"send_no_recent": "最近の送信なし",
|
|
||||||
"send_recent_sends": "最近の送信",
|
|
||||||
"send_recipient": "受取人",
|
|
||||||
"send_select_source": "送信元アドレスを選択...",
|
|
||||||
"send_sending_from": "送信元",
|
|
||||||
"send_submitting": "取引を送信中...",
|
|
||||||
"send_switch_to_receive": "受信に切り替えてアドレスを取得し、資金の受け取りを開始してください。",
|
|
||||||
"send_to": "送金先",
|
|
||||||
"send_tooltip_enter_amount": "送金額を入力してください",
|
|
||||||
"send_tooltip_exceeds_balance": "金額が利用可能残高を超えています",
|
|
||||||
"send_tooltip_in_progress": "取引は既に進行中です",
|
|
||||||
"send_tooltip_invalid_address": "有効な受取人アドレスを入力してください",
|
|
||||||
"send_tooltip_not_connected": "デーモンに未接続",
|
|
||||||
"send_tooltip_select_source": "まず送信元アドレスを選択してください",
|
|
||||||
"send_tooltip_syncing": "ブロックチェーンの同期をお待ちください",
|
|
||||||
"send_total": "合計",
|
|
||||||
"send_transaction": "取引を送信",
|
|
||||||
"send_tx_failed": "取引に失敗しました",
|
|
||||||
"send_tx_sent": "取引を送信しました!",
|
|
||||||
"send_tx_success": "取引の送信に成功しました!",
|
|
||||||
"send_txid_copied": "TxIDをクリップボードにコピーしました",
|
|
||||||
"send_txid_label": "TxID:%s",
|
|
||||||
"send_valid_shielded": "有効なシールドアドレス",
|
|
||||||
"send_valid_transparent": "有効な透明アドレス",
|
|
||||||
"send_wallet_empty": "ウォレットは空です",
|
|
||||||
"send_yes_clear": "はい、クリア",
|
|
||||||
"sending": "取引を送信中",
|
|
||||||
"sending_from": "送信元",
|
|
||||||
"sent": "送信済み",
|
|
||||||
"sent_filter": "送信済み",
|
|
||||||
"sent_type": "送信済み",
|
|
||||||
"sent_upper": "送信済み",
|
|
||||||
"settings": "設定",
|
|
||||||
"setup_wizard": "セットアップウィザード",
|
|
||||||
"share": "共有",
|
|
||||||
"shield_check_status": "ステータスを確認",
|
|
||||||
"shield_completed": "操作が正常に完了しました!",
|
|
||||||
"shield_description": "透明アドレスのcoinbase出力をシールドアドレスに送信して、マイニング報酬をシールドします。マイニング収入を隠すことでプライバシーが向上します。",
|
|
||||||
"shield_from_address": "送信元アドレス:",
|
|
||||||
"shield_funds": "資金をシールド",
|
|
||||||
"shield_in_progress": "操作進行中...",
|
|
||||||
"shield_max_utxos": "1回の操作あたりの最大UTXO数",
|
|
||||||
"shield_merge_done": "シールド/統合が完了しました!",
|
|
||||||
"shield_select_z": "zアドレスを選択...",
|
|
||||||
"shield_started": "シールド操作を開始しました",
|
|
||||||
"shield_title": "Coinbase報酬をシールド",
|
|
||||||
"shield_to_address": "送信先アドレス(シールド):",
|
|
||||||
"shield_utxo_limit": "UTXO制限:",
|
|
||||||
"shield_wildcard_hint": "'*' を使用してすべての透明アドレスからシールド",
|
|
||||||
"shielded": "シールド",
|
|
||||||
"shielded_to": "シールド先",
|
|
||||||
"shielded_type": "シールド",
|
|
||||||
"show": "表示",
|
|
||||||
"show_qr_code": "QRコードを表示",
|
|
||||||
"showing_transactions": "%d\xe2\x80\x93%d / %d 件の取引を表示中(合計:%zu)",
|
|
||||||
"simple_background": "シンプル背景",
|
|
||||||
"start_mining": "マイニング開始",
|
|
||||||
"status": "ステータス",
|
|
||||||
"stop_external": "外部デーモンを停止",
|
|
||||||
"stop_mining": "マイニング停止",
|
|
||||||
"submitting_transaction": "取引を送信中...",
|
|
||||||
"success": "成功",
|
|
||||||
"summary": "概要",
|
|
||||||
"syncing": "同期中...",
|
|
||||||
"t_addresses": "Tアドレス",
|
|
||||||
"test_connection": "テスト",
|
|
||||||
"theme": "テーマ",
|
|
||||||
"theme_effects": "テーマ効果",
|
|
||||||
"time_days_ago": "%d日前",
|
|
||||||
"time_hours_ago": "%d時間前",
|
|
||||||
"time_minutes_ago": "%d分前",
|
|
||||||
"time_seconds_ago": "%d秒前",
|
|
||||||
"to": "宛先",
|
|
||||||
"to_upper": "宛先",
|
|
||||||
"tools": "ツール",
|
|
||||||
"total": "合計",
|
|
||||||
"transaction_id": "取引ID",
|
|
||||||
"transaction_sent": "取引の送信に成功しました",
|
|
||||||
"transaction_sent_msg": "取引を送信しました!",
|
|
||||||
"transaction_url": "取引URL",
|
|
||||||
"transactions": "取引",
|
|
||||||
"transactions_upper": "取引",
|
|
||||||
"transparent": "透明",
|
|
||||||
"tx_confirmations": "%d 確認",
|
|
||||||
"tx_details_title": "取引の詳細",
|
|
||||||
"tx_from_address": "送信元アドレス:",
|
|
||||||
"tx_id_label": "取引ID:",
|
|
||||||
"tx_immature": "未成熟",
|
|
||||||
"tx_mined": "採掘済み",
|
|
||||||
"tx_received": "受信済み",
|
|
||||||
"tx_sent": "送信済み",
|
|
||||||
"tx_to_address": "送信先アドレス:",
|
|
||||||
"tx_view_explorer": "エクスプローラーで表示",
|
|
||||||
"txs_count": "%d 件",
|
|
||||||
"type": "タイプ",
|
|
||||||
"ui_opacity": "UI透明度",
|
|
||||||
"unban": "ブロック解除",
|
|
||||||
"unconfirmed": "未確認",
|
|
||||||
"undo_clear": "クリアを元に戻す",
|
|
||||||
"unknown": "不明",
|
|
||||||
"use_embedded_daemon": "内蔵dragonxdを使用",
|
|
||||||
"use_tor": "Torを使用",
|
|
||||||
"validate_btn": "検証",
|
|
||||||
"validate_description": "DragonXアドレスを入力して、有効かどうか、そしてこのウォレットに属しているかどうかを確認します。",
|
|
||||||
"validate_invalid": "無効",
|
|
||||||
"validate_is_mine": "このウォレットがこのアドレスを所有しています",
|
|
||||||
"validate_not_mine": "このウォレットに属していません",
|
|
||||||
"validate_ownership": "所有者:",
|
|
||||||
"validate_results": "結果:",
|
|
||||||
"validate_shielded_type": "シールド(zアドレス)",
|
|
||||||
"validate_status": "ステータス:",
|
|
||||||
"validate_title": "アドレスを検証",
|
|
||||||
"validate_transparent_type": "透明(tアドレス)",
|
|
||||||
"validate_type": "タイプ:",
|
|
||||||
"validate_valid": "有効",
|
|
||||||
"validating": "検証中...",
|
|
||||||
"verbose_logging": "詳細ログ",
|
|
||||||
"version": "バージョン",
|
|
||||||
"view": "表示",
|
|
||||||
"view_details": "詳細を表示",
|
|
||||||
"view_on_explorer": "エクスプローラーで表示",
|
|
||||||
"waiting_for_daemon": "デーモン接続を待機中...",
|
|
||||||
"wallet": "ウォレット",
|
|
||||||
"wallet_empty": "ウォレットは空です",
|
|
||||||
"wallet_empty_hint": "受信に切り替えてアドレスを取得し、資金の受け取りを開始してください。",
|
|
||||||
"warning": "警告",
|
|
||||||
"warning_upper": "警告!",
|
|
||||||
"website": "ウェブサイト",
|
|
||||||
"window_opacity": "ウィンドウ透明度",
|
|
||||||
"yes_clear": "はい、クリア",
|
|
||||||
"your_addresses": "あなたのアドレス",
|
|
||||||
"z_addresses": "Zアドレス",
|
|
||||||
}
|
|
||||||
|
|
||||||
out = os.path.join(os.path.dirname(__file__), "..", "res", "lang", "ja.json")
|
|
||||||
with open(out, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(translations, f, indent=4, ensure_ascii=False, sort_keys=True)
|
|
||||||
print(f"Wrote {len(translations)} Japanese translations to {os.path.abspath(out)}")
|
|
||||||
@@ -1,646 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate Korean (ko) translations for ObsidianDragon wallet."""
|
|
||||||
import json, os
|
|
||||||
|
|
||||||
translations = {
|
|
||||||
"24h_change": "24시간 변동",
|
|
||||||
"24h_volume": "24시간 거래량",
|
|
||||||
"about": "정보",
|
|
||||||
"about_block_explorer": "블록 탐색기",
|
|
||||||
"about_block_height": "블록 높이:",
|
|
||||||
"about_build_date": "빌드 날짜:",
|
|
||||||
"about_build_type": "빌드 유형:",
|
|
||||||
"about_chain": "체인:",
|
|
||||||
"about_connections": "연결:",
|
|
||||||
"about_credits": "크레딧",
|
|
||||||
"about_daemon": "데몬:",
|
|
||||||
"about_debug": "디버그",
|
|
||||||
"about_dragonx": "ObsidianDragon 정보",
|
|
||||||
"about_edition": "ImGui 에디션",
|
|
||||||
"about_github": "GitHub",
|
|
||||||
"about_imgui": "ImGui:",
|
|
||||||
"about_license": "라이선스",
|
|
||||||
"about_license_text": "본 소프트웨어는 GNU General Public License v3 (GPLv3) 하에 배포됩니다. 라이선스 조건에 따라 자유롭게 사용, 수정 및 배포할 수 있습니다.",
|
|
||||||
"about_peers_count": "%zu 피어",
|
|
||||||
"about_release": "릴리스",
|
|
||||||
"about_title": "ObsidianDragon 정보",
|
|
||||||
"about_version": "버전:",
|
|
||||||
"about_website": "웹사이트",
|
|
||||||
"acrylic": "아크릴",
|
|
||||||
"add": "추가",
|
|
||||||
"address": "주소",
|
|
||||||
"address_book_add": "주소 추가",
|
|
||||||
"address_book_add_new": "새로 추가",
|
|
||||||
"address_book_added": "주소록에 주소를 추가했습니다",
|
|
||||||
"address_book_count": "저장된 주소 %zu개",
|
|
||||||
"address_book_deleted": "항목이 삭제되었습니다",
|
|
||||||
"address_book_edit": "주소 편집",
|
|
||||||
"address_book_empty": "저장된 주소가 없습니다. '새로 추가'를 클릭하여 추가하세요.",
|
|
||||||
"address_book_exists": "주소가 이미 주소록에 있습니다",
|
|
||||||
"address_book_title": "주소록",
|
|
||||||
"address_book_update_failed": "업데이트 실패 — 주소가 중복될 수 있습니다",
|
|
||||||
"address_book_updated": "주소가 업데이트되었습니다",
|
|
||||||
"address_copied": "주소가 클립보드에 복사되었습니다",
|
|
||||||
"address_details": "주소 상세",
|
|
||||||
"address_label": "주소:",
|
|
||||||
"address_upper": "주소",
|
|
||||||
"address_url": "주소 URL",
|
|
||||||
"addresses_appear_here": "연결 후 수신 주소가 여기에 표시됩니다.",
|
|
||||||
"advanced": "고급 설정",
|
|
||||||
"all_filter": "전체",
|
|
||||||
"allow_custom_fees": "사용자 정의 수수료 허용",
|
|
||||||
"amount": "금액",
|
|
||||||
"amount_details": "금액 상세",
|
|
||||||
"amount_exceeds_balance": "금액이 잔액을 초과합니다",
|
|
||||||
"amount_label": "금액:",
|
|
||||||
"appearance": "외관",
|
|
||||||
"auto_shield": "채굴 자동 차폐",
|
|
||||||
"available": "사용 가능",
|
|
||||||
"backup_backing_up": "백업 중...",
|
|
||||||
"backup_create": "백업 생성",
|
|
||||||
"backup_created": "지갑 백업이 생성되었습니다",
|
|
||||||
"backup_data": "백업 및 데이터",
|
|
||||||
"backup_description": "wallet.dat 파일의 백업을 생성합니다. 이 파일에는 모든 개인 키와 거래 내역이 포함되어 있습니다. 백업을 안전한 곳에 보관하세요.",
|
|
||||||
"backup_destination": "백업 위치:",
|
|
||||||
"backup_tip_external": "외장 드라이브 또는 클라우드 스토리지에 백업 저장",
|
|
||||||
"backup_tip_multiple": "서로 다른 위치에 여러 백업 생성",
|
|
||||||
"backup_tip_test": "정기적으로 백업 복원 테스트",
|
|
||||||
"backup_tips": "팁:",
|
|
||||||
"backup_title": "지갑 백업",
|
|
||||||
"backup_wallet": "지갑 백업...",
|
|
||||||
"backup_wallet_not_found": "경고: 예상 위치에서 wallet.dat를 찾을 수 없습니다",
|
|
||||||
"balance": "잔액",
|
|
||||||
"balance_layout": "잔액 레이아웃",
|
|
||||||
"ban": "차단",
|
|
||||||
"banned_peers": "차단된 피어",
|
|
||||||
"block": "블록",
|
|
||||||
"block_bits": "비트:",
|
|
||||||
"block_click_next": "클릭하여 다음 블록 보기",
|
|
||||||
"block_click_prev": "클릭하여 이전 블록 보기",
|
|
||||||
"block_explorer": "블록 탐색기",
|
|
||||||
"block_get_info": "블록 정보 조회",
|
|
||||||
"block_hash": "블록 해시:",
|
|
||||||
"block_height": "블록 높이:",
|
|
||||||
"block_info_title": "블록 정보",
|
|
||||||
"block_merkle_root": "머클 루트:",
|
|
||||||
"block_nav_next": "다음 >>",
|
|
||||||
"block_nav_prev": "<< 이전",
|
|
||||||
"block_next": "다음 블록:",
|
|
||||||
"block_previous": "이전 블록:",
|
|
||||||
"block_size": "크기:",
|
|
||||||
"block_timestamp": "타임스탬프:",
|
|
||||||
"block_transactions": "트랜잭션:",
|
|
||||||
"blockchain_syncing": "블록체인 동기화 중 (%.1f%%)... 잔액이 정확하지 않을 수 있습니다.",
|
|
||||||
"cancel": "취소",
|
|
||||||
"characters": "문자",
|
|
||||||
"clear": "지우기",
|
|
||||||
"clear_all_bans": "모든 차단 해제",
|
|
||||||
"clear_form_confirm": "모든 양식 필드를 지우시겠습니까?",
|
|
||||||
"clear_request": "요청 지우기",
|
|
||||||
"click_copy_address": "클릭하여 주소 복사",
|
|
||||||
"click_copy_uri": "클릭하여 URI 복사",
|
|
||||||
"close": "닫기",
|
|
||||||
"conf_count": "%d 확인",
|
|
||||||
"confirm_and_send": "확인 후 전송",
|
|
||||||
"confirm_send": "전송 확인",
|
|
||||||
"confirm_transaction": "거래 확인",
|
|
||||||
"confirmations": "확인 수",
|
|
||||||
"confirmations_display": "%d 확인 | %s",
|
|
||||||
"confirmed": "확인됨",
|
|
||||||
"connected": "연결됨",
|
|
||||||
"connected_peers": "연결된 피어",
|
|
||||||
"connecting": "연결 중...",
|
|
||||||
"console": "콘솔",
|
|
||||||
"console_auto_scroll": "자동 스크롤",
|
|
||||||
"console_available_commands": "사용 가능한 명령어:",
|
|
||||||
"console_capturing_output": "데몬 출력 캡처 중...",
|
|
||||||
"console_clear": "지우기",
|
|
||||||
"console_clear_console": "콘솔 지우기",
|
|
||||||
"console_cleared": "콘솔이 지워졌습니다",
|
|
||||||
"console_click_commands": "위의 명령어를 클릭하여 삽입",
|
|
||||||
"console_click_insert": "클릭하여 삽입",
|
|
||||||
"console_click_insert_params": "클릭하여 매개변수와 함께 삽입",
|
|
||||||
"console_close": "닫기",
|
|
||||||
"console_commands": "명령어",
|
|
||||||
"console_common_rpc": "일반 RPC 명령어:",
|
|
||||||
"console_completions": "자동 완성:",
|
|
||||||
"console_connected": "데몬에 연결됨",
|
|
||||||
"console_copy_all": "모두 복사",
|
|
||||||
"console_copy_selected": "복사",
|
|
||||||
"console_daemon": "데몬",
|
|
||||||
"console_daemon_error": "데몬 오류!",
|
|
||||||
"console_daemon_started": "데몬이 시작되었습니다",
|
|
||||||
"console_daemon_stopped": "데몬이 중지되었습니다",
|
|
||||||
"console_disconnected": "데몬 연결이 끊어졌습니다",
|
|
||||||
"console_errors": "오류",
|
|
||||||
"console_filter_hint": "출력 필터...",
|
|
||||||
"console_help_clear": " clear - 콘솔 지우기",
|
|
||||||
"console_help_getbalance": " getbalance - 투명 잔액 표시",
|
|
||||||
"console_help_getblockcount": " getblockcount - 현재 블록 높이 표시",
|
|
||||||
"console_help_getinfo": " getinfo - 노드 정보 표시",
|
|
||||||
"console_help_getmininginfo": " getmininginfo - 채굴 상태 표시",
|
|
||||||
"console_help_getpeerinfo": " getpeerinfo - 연결된 피어 표시",
|
|
||||||
"console_help_gettotalbalance": " gettotalbalance - 총 잔액 표시",
|
|
||||||
"console_help_help": " help - 도움말 표시",
|
|
||||||
"console_help_setgenerate": " setgenerate - 채굴 제어",
|
|
||||||
"console_help_stop": " stop - 데몬 중지",
|
|
||||||
"console_line_count": "%zu줄",
|
|
||||||
"console_new_lines": "%d 새 줄",
|
|
||||||
"console_no_daemon": "데몬 없음",
|
|
||||||
"console_not_connected": "오류: 데몬에 연결되지 않았습니다",
|
|
||||||
"console_rpc_reference": "RPC 명령어 참조",
|
|
||||||
"console_scanline": "콘솔 스캔라인",
|
|
||||||
"console_search_commands": "명령어 검색...",
|
|
||||||
"console_select_all": "모두 선택",
|
|
||||||
"console_show_daemon_output": "데몬 출력 표시",
|
|
||||||
"console_show_errors_only": "오류만 표시",
|
|
||||||
"console_show_rpc_ref": "RPC 명령어 참조 표시",
|
|
||||||
"console_showing_lines": "%zu / %zu줄 표시 중",
|
|
||||||
"console_starting_node": "노드 시작 중...",
|
|
||||||
"console_status_error": "오류",
|
|
||||||
"console_status_running": "실행 중",
|
|
||||||
"console_status_starting": "시작 중",
|
|
||||||
"console_status_stopped": "중지됨",
|
|
||||||
"console_status_stopping": "중지 중",
|
|
||||||
"console_status_unknown": "알 수 없음",
|
|
||||||
"console_tab_completion": "Tab으로 자동 완성",
|
|
||||||
"console_type_help": "'help'를 입력하여 사용 가능한 명령어 보기",
|
|
||||||
"console_welcome": "ObsidianDragon 콘솔에 오신 것을 환영합니다",
|
|
||||||
"console_zoom_in": "확대",
|
|
||||||
"console_zoom_out": "축소",
|
|
||||||
"copy": "복사",
|
|
||||||
"copy_address": "전체 주소 복사",
|
|
||||||
"copy_error": "오류 복사",
|
|
||||||
"copy_to_clipboard": "클립보드에 복사",
|
|
||||||
"copy_txid": "TxID 복사",
|
|
||||||
"copy_uri": "URI 복사",
|
|
||||||
"current_price": "현재 가격",
|
|
||||||
"custom_fees": "사용자 정의 수수료",
|
|
||||||
"dark": "다크",
|
|
||||||
"date": "날짜",
|
|
||||||
"date_label": "날짜:",
|
|
||||||
"delete": "삭제",
|
|
||||||
"difficulty": "난이도",
|
|
||||||
"disconnected": "연결 끊김",
|
|
||||||
"dismiss": "닫기",
|
|
||||||
"display": "디스플레이",
|
|
||||||
"dragonx_green": "DragonX(그린)",
|
|
||||||
"edit": "편집",
|
|
||||||
"error": "오류",
|
|
||||||
"est_time_to_block": "예상 블록 시간",
|
|
||||||
"exit": "종료",
|
|
||||||
"explorer": "탐색기",
|
|
||||||
"export": "내보내기",
|
|
||||||
"export_csv": "CSV 내보내기",
|
|
||||||
"export_keys_btn": "키 내보내기",
|
|
||||||
"export_keys_danger": "위험: 지갑의 모든 개인 키가 내보내집니다! 이 파일에 접근할 수 있는 사람은 누구나 자금을 훔칠 수 있습니다. 안전하게 보관하고 사용 후 삭제하세요.",
|
|
||||||
"export_keys_include_t": "T 주소 포함 (투명)",
|
|
||||||
"export_keys_include_z": "Z 주소 포함 (차폐)",
|
|
||||||
"export_keys_options": "내보내기 옵션:",
|
|
||||||
"export_keys_success": "키 내보내기 성공",
|
|
||||||
"export_keys_title": "모든 개인 키 내보내기",
|
|
||||||
"export_private_key": "개인 키 내보내기",
|
|
||||||
"export_tx_count": "%zu건의 거래를 CSV 파일로 내보냈습니다.",
|
|
||||||
"export_tx_file_fail": "CSV 파일 생성 실패",
|
|
||||||
"export_tx_none": "내보낼 거래가 없습니다",
|
|
||||||
"export_tx_success": "거래 내보내기 성공",
|
|
||||||
"export_tx_title": "거래를 CSV로 내보내기",
|
|
||||||
"export_viewing_key": "조회 키 내보내기",
|
|
||||||
"failed_create_shielded": "차폐 주소 생성 실패",
|
|
||||||
"failed_create_transparent": "투명 주소 생성 실패",
|
|
||||||
"fee": "수수료",
|
|
||||||
"fee_high": "높음",
|
|
||||||
"fee_label": "수수료:",
|
|
||||||
"fee_low": "낮음",
|
|
||||||
"fee_normal": "보통",
|
|
||||||
"fetch_prices": "가격 조회",
|
|
||||||
"file": "파일",
|
|
||||||
"file_save_location": "파일 저장 위치: ~/.config/ObsidianDragon/",
|
|
||||||
"font_scale": "글꼴 크기",
|
|
||||||
"from": "보낸 곳",
|
|
||||||
"from_upper": "보낸 곳",
|
|
||||||
"full_details": "전체 세부 정보",
|
|
||||||
"general": "일반",
|
|
||||||
"go_to_receive": "수신으로 이동",
|
|
||||||
"height": "높이",
|
|
||||||
"help": "도움말",
|
|
||||||
"hide": "숨기기",
|
|
||||||
"history": "내역",
|
|
||||||
"immature_type": "미성숙",
|
|
||||||
"import": "가져오기",
|
|
||||||
"import_key_btn": "키 가져오기",
|
|
||||||
"import_key_formats": "지원되는 키 형식:",
|
|
||||||
"import_key_full_rescan": "(0 = 전체 재스캔)",
|
|
||||||
"import_key_label": "개인 키:",
|
|
||||||
"import_key_no_valid": "입력에서 유효한 키를 찾을 수 없습니다",
|
|
||||||
"import_key_rescan": "가져오기 후 블록체인 재스캔",
|
|
||||||
"import_key_start_height": "시작 높이:",
|
|
||||||
"import_key_success": "키 가져오기 성공",
|
|
||||||
"import_key_t_format": "T 주소 WIF 개인 키",
|
|
||||||
"import_key_title": "개인 키 가져오기",
|
|
||||||
"import_key_tooltip": "한 줄에 하나의 개인 키를 입력하세요.\nz 주소와 t 주소 키 모두 지원됩니다.\n#으로 시작하는 줄은 주석으로 처리됩니다.",
|
|
||||||
"import_key_warning": "경고: 개인 키를 절대 공유하지 마세요! 신뢰할 수 없는 소스의 키를 가져오면 지갑이 위험해질 수 있습니다.",
|
|
||||||
"import_key_z_format": "Z 주소 지출 키 (secret-extended-key-...)",
|
|
||||||
"import_private_key": "개인 키 가져오기...",
|
|
||||||
"invalid_address": "잘못된 주소 형식",
|
|
||||||
"ip_address": "IP 주소",
|
|
||||||
"keep": "유지",
|
|
||||||
"keep_daemon": "데몬 계속 실행",
|
|
||||||
"key_export_fetching": "지갑에서 키를 가져오는 중...",
|
|
||||||
"key_export_private_key": "개인 키:",
|
|
||||||
"key_export_private_warning": "이 키를 비밀로 유지하세요! 이 키를 가진 사람은 누구나 자금을 사용할 수 있습니다. 온라인이나 신뢰할 수 없는 사람과 공유하지 마세요.",
|
|
||||||
"key_export_reveal": "키 표시",
|
|
||||||
"key_export_viewing_key": "조회 키:",
|
|
||||||
"key_export_viewing_warning": "이 조회 키를 사용하면 다른 사람이 수신 거래와 잔액을 볼 수 있지만 자금을 사용할 수는 없습니다. 신뢰할 수 있는 사람에게만 공유하세요.",
|
|
||||||
"label": "라벨:",
|
|
||||||
"language": "언어",
|
|
||||||
"light": "라이트",
|
|
||||||
"loading": "로딩 중...",
|
|
||||||
"loading_addresses": "주소 로딩 중...",
|
|
||||||
"local_hashrate": "로컬 해시레이트",
|
|
||||||
"low_spec_mode": "저사양 모드",
|
|
||||||
"market": "시장",
|
|
||||||
"market_12h": "12시간",
|
|
||||||
"market_18h": "18시간",
|
|
||||||
"market_24h": "24시간",
|
|
||||||
"market_24h_volume": "24시간 거래량",
|
|
||||||
"market_6h": "6시간",
|
|
||||||
"market_attribution": "가격 데이터: NonKYC 제공",
|
|
||||||
"market_btc_price": "BTC 가격",
|
|
||||||
"market_cap": "시가총액",
|
|
||||||
"market_no_history": "가격 내역 없음",
|
|
||||||
"market_no_price": "가격 데이터 없음",
|
|
||||||
"market_now": "현재",
|
|
||||||
"market_pct_shielded": "%.0f%% 차폐됨",
|
|
||||||
"market_portfolio": "포트폴리오",
|
|
||||||
"market_price_unavailable": "가격 데이터를 사용할 수 없습니다",
|
|
||||||
"market_refresh_price": "가격 데이터 새로고침",
|
|
||||||
"market_trade_on": "%s에서 거래",
|
|
||||||
"mature": "성숙됨",
|
|
||||||
"max": "최대",
|
|
||||||
"memo": "메모 (선택, 암호화)",
|
|
||||||
"memo_label": "메모:",
|
|
||||||
"memo_optional": "메모 (선택)",
|
|
||||||
"memo_upper": "메모",
|
|
||||||
"memo_z_only": "참고: 메모는 차폐 (z) 주소로 전송할 때만 사용할 수 있습니다",
|
|
||||||
"merge_description": "여러 UTXO를 단일 차폐 주소로 통합합니다. 지갑 크기를 줄이고 프라이버시를 향상시킵니다.",
|
|
||||||
"merge_funds": "자금 통합",
|
|
||||||
"merge_started": "통합 작업이 시작되었습니다",
|
|
||||||
"merge_title": "주소로 통합",
|
|
||||||
"mine_when_idle": "유휴 시 채굴",
|
|
||||||
"mined": "채굴됨",
|
|
||||||
"mined_filter": "채굴됨",
|
|
||||||
"mined_type": "채굴됨",
|
|
||||||
"mined_upper": "채굴됨",
|
|
||||||
"miner_fee": "채굴 수수료",
|
|
||||||
"mining": "채굴",
|
|
||||||
"mining_active": "활성",
|
|
||||||
"mining_address_copied": "채굴 주소가 복사되었습니다",
|
|
||||||
"mining_all_time": "전체 기간",
|
|
||||||
"mining_already_saved": "풀 URL이 이미 저장되어 있습니다",
|
|
||||||
"mining_block_copied": "블록 해시가 복사되었습니다",
|
|
||||||
"mining_chart_1m_ago": "1분 전",
|
|
||||||
"mining_chart_5m_ago": "5분 전",
|
|
||||||
"mining_chart_now": "현재",
|
|
||||||
"mining_chart_start": "시작",
|
|
||||||
"mining_click": "클릭",
|
|
||||||
"mining_click_copy_address": "클릭하여 주소 복사",
|
|
||||||
"mining_click_copy_block": "클릭하여 블록 해시 복사",
|
|
||||||
"mining_click_copy_difficulty": "클릭하여 난이도 복사",
|
|
||||||
"mining_connected": "연결됨",
|
|
||||||
"mining_connecting": "연결 중...",
|
|
||||||
"mining_control": "채굴 제어",
|
|
||||||
"mining_difficulty_copied": "난이도가 복사되었습니다",
|
|
||||||
"mining_est_block": "예상 블록",
|
|
||||||
"mining_est_daily": "예상 일일 수익",
|
|
||||||
"mining_filter_all": "전체",
|
|
||||||
"mining_filter_tip_all": "모든 수익 표시",
|
|
||||||
"mining_filter_tip_pool": "풀 수익만 표시",
|
|
||||||
"mining_filter_tip_solo": "솔로 수익만 표시",
|
|
||||||
"mining_idle_off_tooltip": "유휴 채굴 활성화",
|
|
||||||
"mining_idle_on_tooltip": "유휴 채굴 비활성화",
|
|
||||||
"mining_local_hashrate": "로컬 해시레이트",
|
|
||||||
"mining_mine": "채굴",
|
|
||||||
"mining_mining_addr": "채굴 주소",
|
|
||||||
"mining_network": "네트워크",
|
|
||||||
"mining_no_blocks_yet": "아직 블록을 찾지 못했습니다",
|
|
||||||
"mining_no_payouts_yet": "아직 풀 지급이 없습니다",
|
|
||||||
"mining_no_saved_addresses": "저장된 주소 없음",
|
|
||||||
"mining_no_saved_pools": "저장된 풀 없음",
|
|
||||||
"mining_off": "채굴이 꺼져 있습니다",
|
|
||||||
"mining_on": "채굴이 켜져 있습니다",
|
|
||||||
"mining_open_in_explorer": "탐색기에서 열기",
|
|
||||||
"mining_payout_address": "지급 주소",
|
|
||||||
"mining_payout_tooltip": "채굴 보상 수신 주소",
|
|
||||||
"mining_pool": "풀",
|
|
||||||
"mining_pool_hashrate": "풀 해시레이트",
|
|
||||||
"mining_pool_url": "풀 URL",
|
|
||||||
"mining_recent_blocks": "최근 블록",
|
|
||||||
"mining_recent_payouts": "최근 풀 지급",
|
|
||||||
"mining_remove": "제거",
|
|
||||||
"mining_reset_defaults": "기본값으로 재설정",
|
|
||||||
"mining_save_payout_address": "지급 주소 저장",
|
|
||||||
"mining_save_pool_url": "풀 URL 저장",
|
|
||||||
"mining_saved_addresses": "저장된 주소:",
|
|
||||||
"mining_saved_pools": "저장된 풀:",
|
|
||||||
"mining_shares": "셰어",
|
|
||||||
"mining_show_chart": "차트",
|
|
||||||
"mining_show_log": "로그",
|
|
||||||
"mining_solo": "솔로",
|
|
||||||
"mining_starting": "시작 중...",
|
|
||||||
"mining_starting_tooltip": "채굴기 시작 중...",
|
|
||||||
"mining_statistics": "채굴 통계",
|
|
||||||
"mining_stop": "중지",
|
|
||||||
"mining_stop_solo_for_pool": "풀 채굴을 시작하려면 솔로 채굴을 먼저 중지하세요",
|
|
||||||
"mining_stop_solo_for_pool_settings": "풀 설정을 변경하려면 솔로 채굴을 중지하세요",
|
|
||||||
"mining_stopping": "중지 중...",
|
|
||||||
"mining_stopping_tooltip": "채굴기 중지 중...",
|
|
||||||
"mining_syncing_tooltip": "블록체인 동기화 중...",
|
|
||||||
"mining_threads": "채굴 스레드",
|
|
||||||
"mining_to_save": "저장하려면",
|
|
||||||
"mining_today": "오늘",
|
|
||||||
"mining_uptime": "가동 시간",
|
|
||||||
"mining_yesterday": "어제",
|
|
||||||
"network": "네트워크",
|
|
||||||
"network_fee": "네트워크 수수료",
|
|
||||||
"network_hashrate": "네트워크 해시레이트",
|
|
||||||
"new": "+ 새로 만들기",
|
|
||||||
"new_shielded_created": "새 차폐 주소가 생성되었습니다",
|
|
||||||
"new_t_address": "새 T 주소",
|
|
||||||
"new_t_transparent": "새 t 주소 (투명)",
|
|
||||||
"new_transparent_created": "새 투명 주소가 생성되었습니다",
|
|
||||||
"new_z_address": "새 Z 주소",
|
|
||||||
"new_z_shielded": "새 z 주소 (차폐)",
|
|
||||||
"no_addresses": "주소가 없습니다. 위의 버튼을 사용하여 생성하세요.",
|
|
||||||
"no_addresses_available": "사용 가능한 주소 없음",
|
|
||||||
"no_addresses_match": "필터와 일치하는 주소가 없습니다",
|
|
||||||
"no_addresses_with_balance": "잔액이 있는 주소가 없습니다",
|
|
||||||
"no_matching": "일치하는 거래가 없습니다",
|
|
||||||
"no_recent_receives": "최근 수신 내역 없음",
|
|
||||||
"no_recent_sends": "최근 전송 내역 없음",
|
|
||||||
"no_transactions": "거래 내역이 없습니다",
|
|
||||||
"node": "노드",
|
|
||||||
"node_security": "노드 및 보안",
|
|
||||||
"noise": "노이즈",
|
|
||||||
"not_connected": "데몬에 연결되지 않음...",
|
|
||||||
"not_connected_to_daemon": "데몬에 연결되지 않음",
|
|
||||||
"notes": "메모",
|
|
||||||
"notes_optional": "메모 (선택):",
|
|
||||||
"output_filename": "출력 파일명:",
|
|
||||||
"overview": "개요",
|
|
||||||
"paste": "붙여넣기",
|
|
||||||
"paste_from_clipboard": "클립보드에서 붙여넣기",
|
|
||||||
"pay_from": "보낼 곳",
|
|
||||||
"payment_request": "결제 요청",
|
|
||||||
"payment_request_copied": "결제 요청이 복사되었습니다",
|
|
||||||
"payment_uri_copied": "결제 URI가 복사되었습니다",
|
|
||||||
"peers": "피어",
|
|
||||||
"peers_avg_ping": "평균 Ping",
|
|
||||||
"peers_ban_24h": "피어 24시간 차단",
|
|
||||||
"peers_ban_score": "차단 점수: %d",
|
|
||||||
"peers_banned": "차단됨",
|
|
||||||
"peers_banned_count": "차단됨: %d",
|
|
||||||
"peers_best_block": "최고 블록",
|
|
||||||
"peers_blockchain": "블록체인",
|
|
||||||
"peers_blocks": "블록",
|
|
||||||
"peers_blocks_left": "남은 블록: %d",
|
|
||||||
"peers_clear_all_bans": "모든 차단 해제",
|
|
||||||
"peers_click_copy": "클릭하여 복사",
|
|
||||||
"peers_connected": "연결됨",
|
|
||||||
"peers_connected_count": "연결됨: %d",
|
|
||||||
"peers_copy_ip": "IP 복사",
|
|
||||||
"peers_dir_in": "수신",
|
|
||||||
"peers_dir_out": "송신",
|
|
||||||
"peers_hash_copied": "해시가 복사되었습니다",
|
|
||||||
"peers_hashrate": "해시레이트",
|
|
||||||
"peers_in_out": "수신/송신",
|
|
||||||
"peers_longest": "최장",
|
|
||||||
"peers_longest_chain": "최장 체인",
|
|
||||||
"peers_memory": "메모리",
|
|
||||||
"peers_no_banned": "차단된 피어 없음",
|
|
||||||
"peers_no_connected": "연결된 피어 없음",
|
|
||||||
"peers_no_tls": "TLS 없음",
|
|
||||||
"peers_notarized": "공증됨",
|
|
||||||
"peers_p2p_port": "P2P 포트",
|
|
||||||
"peers_protocol": "프로토콜",
|
|
||||||
"peers_received": "수신됨",
|
|
||||||
"peers_refresh": "새로고침",
|
|
||||||
"peers_refresh_tooltip": "피어 목록 새로고침",
|
|
||||||
"peers_refreshing": "새로고침 중...",
|
|
||||||
"peers_sent": "전송됨",
|
|
||||||
"peers_tt_id": "ID: %d",
|
|
||||||
"peers_tt_received": "수신: %s",
|
|
||||||
"peers_tt_sent": "전송: %s",
|
|
||||||
"peers_tt_services": "서비스: %s",
|
|
||||||
"peers_tt_start_height": "시작 높이: %d",
|
|
||||||
"peers_tt_synced": "동기화 H/B: %d/%d",
|
|
||||||
"peers_tt_tls_cipher": "TLS: %s",
|
|
||||||
"peers_unban": "차단 해제",
|
|
||||||
"peers_upper": "피어",
|
|
||||||
"peers_version": "버전",
|
|
||||||
"pending": "대기 중",
|
|
||||||
"ping": "Ping",
|
|
||||||
"price_chart": "가격 차트",
|
|
||||||
"qr_code": "QR 코드",
|
|
||||||
"qr_failed": "QR 코드 생성 실패",
|
|
||||||
"qr_title": "QR 코드",
|
|
||||||
"qr_unavailable": "QR 사용 불가",
|
|
||||||
"receive": "수신",
|
|
||||||
"received": "수신됨",
|
|
||||||
"received_filter": "수신됨",
|
|
||||||
"received_label": "수신됨",
|
|
||||||
"received_upper": "수신됨",
|
|
||||||
"receiving_addresses": "수신 주소",
|
|
||||||
"recent_received": "최근 수신",
|
|
||||||
"recent_sends": "최근 전송",
|
|
||||||
"recipient": "수신자",
|
|
||||||
"recv_type": "수신",
|
|
||||||
"refresh": "새로고침",
|
|
||||||
"refresh_now": "지금 새로고침",
|
|
||||||
"report_bug": "버그 신고",
|
|
||||||
"request_amount": "금액 (선택):",
|
|
||||||
"request_copy_uri": "URI 복사",
|
|
||||||
"request_description": "다른 사람이 스캔하거나 복사할 수 있는 결제 요청을 생성합니다. QR 코드에는 주소와 선택적 금액/메모가 포함됩니다.",
|
|
||||||
"request_label": "라벨 (선택):",
|
|
||||||
"request_memo": "메모 (선택):",
|
|
||||||
"request_payment": "결제 요청",
|
|
||||||
"request_payment_uri": "결제 URI:",
|
|
||||||
"request_receive_address": "수신 주소:",
|
|
||||||
"request_select_address": "주소 선택...",
|
|
||||||
"request_shielded_addrs": "-- 차폐 주소 --",
|
|
||||||
"request_title": "결제 요청",
|
|
||||||
"request_transparent_addrs": "-- 투명 주소 --",
|
|
||||||
"request_uri_copied": "결제 URI가 클립보드에 복사되었습니다",
|
|
||||||
"rescan": "재스캔",
|
|
||||||
"reset_to_defaults": "기본값으로 재설정",
|
|
||||||
"review_send": "전송 검토",
|
|
||||||
"rpc_host": "RPC 호스트",
|
|
||||||
"rpc_pass": "비밀번호",
|
|
||||||
"rpc_port": "포트",
|
|
||||||
"rpc_user": "사용자명",
|
|
||||||
"save": "저장",
|
|
||||||
"save_settings": "설정 저장",
|
|
||||||
"save_z_transactions": "Z 거래를 거래 목록에 저장",
|
|
||||||
"search_placeholder": "검색...",
|
|
||||||
"security": "보안",
|
|
||||||
"select_address": "주소 선택...",
|
|
||||||
"select_receiving_address": "수신 주소 선택...",
|
|
||||||
"select_source_address": "보낼 주소 선택...",
|
|
||||||
"send": "전송",
|
|
||||||
"send_amount": "금액",
|
|
||||||
"send_amount_details": "금액 상세",
|
|
||||||
"send_amount_upper": "금액",
|
|
||||||
"send_clear_fields": "모든 양식 필드를 지우시겠습니까?",
|
|
||||||
"send_copy_error": "오류 복사",
|
|
||||||
"send_dismiss": "닫기",
|
|
||||||
"send_error_copied": "오류가 클립보드에 복사되었습니다",
|
|
||||||
"send_error_prefix": "오류: %s",
|
|
||||||
"send_exceeds_available": "사용 가능 금액 초과 (%.8f)",
|
|
||||||
"send_fee": "수수료",
|
|
||||||
"send_fee_high": "높음",
|
|
||||||
"send_fee_low": "낮음",
|
|
||||||
"send_fee_normal": "보통",
|
|
||||||
"send_form_restored": "양식이 복원되었습니다",
|
|
||||||
"send_from_this_address": "이 주소에서 전송",
|
|
||||||
"send_go_to_receive": "수신으로 이동",
|
|
||||||
"send_keep": "유지",
|
|
||||||
"send_network_fee": "네트워크 수수료",
|
|
||||||
"send_no_balance": "잔액 없음",
|
|
||||||
"send_no_recent": "최근 전송 없음",
|
|
||||||
"send_recent_sends": "최근 전송",
|
|
||||||
"send_recipient": "수신자",
|
|
||||||
"send_select_source": "보낼 주소 선택...",
|
|
||||||
"send_sending_from": "보내는 곳",
|
|
||||||
"send_submitting": "거래 제출 중...",
|
|
||||||
"send_switch_to_receive": "수신으로 전환하여 주소를 받고 자금 수신을 시작하세요.",
|
|
||||||
"send_to": "받는 곳",
|
|
||||||
"send_tooltip_enter_amount": "전송할 금액을 입력하세요",
|
|
||||||
"send_tooltip_exceeds_balance": "금액이 사용 가능 잔액을 초과합니다",
|
|
||||||
"send_tooltip_in_progress": "거래가 이미 진행 중입니다",
|
|
||||||
"send_tooltip_invalid_address": "유효한 수신자 주소를 입력하세요",
|
|
||||||
"send_tooltip_not_connected": "데몬에 연결되지 않음",
|
|
||||||
"send_tooltip_select_source": "먼저 보낼 주소를 선택하세요",
|
|
||||||
"send_tooltip_syncing": "블록체인 동기화를 기다려 주세요",
|
|
||||||
"send_total": "합계",
|
|
||||||
"send_transaction": "거래 전송",
|
|
||||||
"send_tx_failed": "거래 실패",
|
|
||||||
"send_tx_sent": "거래가 전송되었습니다!",
|
|
||||||
"send_tx_success": "거래 전송 성공!",
|
|
||||||
"send_txid_copied": "TxID가 클립보드에 복사되었습니다",
|
|
||||||
"send_txid_label": "TxID: %s",
|
|
||||||
"send_valid_shielded": "유효한 차폐 주소",
|
|
||||||
"send_valid_transparent": "유효한 투명 주소",
|
|
||||||
"send_wallet_empty": "지갑이 비어 있습니다",
|
|
||||||
"send_yes_clear": "예, 지우기",
|
|
||||||
"sending": "거래 전송 중",
|
|
||||||
"sending_from": "보내는 곳",
|
|
||||||
"sent": "전송됨",
|
|
||||||
"sent_filter": "전송됨",
|
|
||||||
"sent_type": "전송됨",
|
|
||||||
"sent_upper": "전송됨",
|
|
||||||
"settings": "설정",
|
|
||||||
"setup_wizard": "설정 마법사",
|
|
||||||
"share": "공유",
|
|
||||||
"shield_check_status": "상태 확인",
|
|
||||||
"shield_completed": "작업이 성공적으로 완료되었습니다!",
|
|
||||||
"shield_description": "투명 주소의 코인베이스 출력을 차폐 주소로 전송하여 채굴 보상을 차폐합니다. 채굴 수입을 숨겨 프라이버시가 향상됩니다.",
|
|
||||||
"shield_from_address": "보내는 주소:",
|
|
||||||
"shield_funds": "자금 차폐",
|
|
||||||
"shield_in_progress": "작업 진행 중...",
|
|
||||||
"shield_max_utxos": "작업당 최대 UTXO 수",
|
|
||||||
"shield_merge_done": "차폐/통합이 완료되었습니다!",
|
|
||||||
"shield_select_z": "z 주소 선택...",
|
|
||||||
"shield_started": "차폐 작업이 시작되었습니다",
|
|
||||||
"shield_title": "코인베이스 보상 차폐",
|
|
||||||
"shield_to_address": "받는 주소 (차폐):",
|
|
||||||
"shield_utxo_limit": "UTXO 제한:",
|
|
||||||
"shield_wildcard_hint": "'*'를 사용하여 모든 투명 주소에서 차폐",
|
|
||||||
"shielded": "차폐",
|
|
||||||
"shielded_to": "차폐 대상",
|
|
||||||
"shielded_type": "차폐",
|
|
||||||
"show": "표시",
|
|
||||||
"show_qr_code": "QR 코드 표시",
|
|
||||||
"showing_transactions": "%d\xe2\x80\x93%d / %d건의 거래 표시 중 (총: %zu)",
|
|
||||||
"simple_background": "단순 배경",
|
|
||||||
"start_mining": "채굴 시작",
|
|
||||||
"status": "상태",
|
|
||||||
"stop_external": "외부 데몬 중지",
|
|
||||||
"stop_mining": "채굴 중지",
|
|
||||||
"submitting_transaction": "거래 제출 중...",
|
|
||||||
"success": "성공",
|
|
||||||
"summary": "요약",
|
|
||||||
"syncing": "동기화 중...",
|
|
||||||
"t_addresses": "T 주소",
|
|
||||||
"test_connection": "테스트",
|
|
||||||
"theme": "테마",
|
|
||||||
"theme_effects": "테마 효과",
|
|
||||||
"time_days_ago": "%d일 전",
|
|
||||||
"time_hours_ago": "%d시간 전",
|
|
||||||
"time_minutes_ago": "%d분 전",
|
|
||||||
"time_seconds_ago": "%d초 전",
|
|
||||||
"to": "받는 곳",
|
|
||||||
"to_upper": "받는 곳",
|
|
||||||
"tools": "도구",
|
|
||||||
"total": "합계",
|
|
||||||
"transaction_id": "거래 ID",
|
|
||||||
"transaction_sent": "거래 전송 성공",
|
|
||||||
"transaction_sent_msg": "거래가 전송되었습니다!",
|
|
||||||
"transaction_url": "거래 URL",
|
|
||||||
"transactions": "거래",
|
|
||||||
"transactions_upper": "거래",
|
|
||||||
"transparent": "투명",
|
|
||||||
"tx_confirmations": "%d 확인",
|
|
||||||
"tx_details_title": "거래 상세",
|
|
||||||
"tx_from_address": "보낸 주소:",
|
|
||||||
"tx_id_label": "거래 ID:",
|
|
||||||
"tx_immature": "미성숙",
|
|
||||||
"tx_mined": "채굴됨",
|
|
||||||
"tx_received": "수신됨",
|
|
||||||
"tx_sent": "전송됨",
|
|
||||||
"tx_to_address": "받는 주소:",
|
|
||||||
"tx_view_explorer": "탐색기에서 보기",
|
|
||||||
"txs_count": "%d건",
|
|
||||||
"type": "유형",
|
|
||||||
"ui_opacity": "UI 투명도",
|
|
||||||
"unban": "차단 해제",
|
|
||||||
"unconfirmed": "미확인",
|
|
||||||
"undo_clear": "지우기 취소",
|
|
||||||
"unknown": "알 수 없음",
|
|
||||||
"use_embedded_daemon": "내장 dragonxd 사용",
|
|
||||||
"use_tor": "Tor 사용",
|
|
||||||
"validate_btn": "검증",
|
|
||||||
"validate_description": "DragonX 주소를 입력하여 유효한지 그리고 이 지갑에 속하는지 확인합니다.",
|
|
||||||
"validate_invalid": "유효하지 않음",
|
|
||||||
"validate_is_mine": "이 지갑이 이 주소를 소유합니다",
|
|
||||||
"validate_not_mine": "이 지갑에 속하지 않음",
|
|
||||||
"validate_ownership": "소유자:",
|
|
||||||
"validate_results": "결과:",
|
|
||||||
"validate_shielded_type": "차폐 (z 주소)",
|
|
||||||
"validate_status": "상태:",
|
|
||||||
"validate_title": "주소 검증",
|
|
||||||
"validate_transparent_type": "투명 (t 주소)",
|
|
||||||
"validate_type": "유형:",
|
|
||||||
"validate_valid": "유효함",
|
|
||||||
"validating": "검증 중...",
|
|
||||||
"verbose_logging": "상세 로깅",
|
|
||||||
"version": "버전",
|
|
||||||
"view": "보기",
|
|
||||||
"view_details": "상세 보기",
|
|
||||||
"view_on_explorer": "탐색기에서 보기",
|
|
||||||
"waiting_for_daemon": "데몬 연결 대기 중...",
|
|
||||||
"wallet": "지갑",
|
|
||||||
"wallet_empty": "지갑이 비어 있습니다",
|
|
||||||
"wallet_empty_hint": "수신으로 전환하여 주소를 받고 자금 수신을 시작하세요.",
|
|
||||||
"warning": "경고",
|
|
||||||
"warning_upper": "경고!",
|
|
||||||
"website": "웹사이트",
|
|
||||||
"window_opacity": "창 투명도",
|
|
||||||
"yes_clear": "예, 지우기",
|
|
||||||
"your_addresses": "내 주소",
|
|
||||||
"z_addresses": "Z 주소",
|
|
||||||
}
|
|
||||||
|
|
||||||
out = os.path.join(os.path.dirname(__file__), "..", "res", "lang", "ko.json")
|
|
||||||
with open(out, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(translations, f, indent=4, ensure_ascii=False, sort_keys=True)
|
|
||||||
print(f"Wrote {len(translations)} Korean translations to {os.path.abspath(out)}")
|
|
||||||
@@ -1,646 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate Portuguese (pt) translations for ObsidianDragon wallet."""
|
|
||||||
import json, os
|
|
||||||
|
|
||||||
translations = {
|
|
||||||
"24h_change": "Variação 24h",
|
|
||||||
"24h_volume": "Volume 24h",
|
|
||||||
"about": "Sobre",
|
|
||||||
"about_block_explorer": "Explorador de Blocos",
|
|
||||||
"about_block_height": "Altura do Bloco:",
|
|
||||||
"about_build_date": "Data de Compilação:",
|
|
||||||
"about_build_type": "Tipo de Build:",
|
|
||||||
"about_chain": "Chain:",
|
|
||||||
"about_connections": "Conexões:",
|
|
||||||
"about_credits": "Créditos",
|
|
||||||
"about_daemon": "Daemon:",
|
|
||||||
"about_debug": "Depuração",
|
|
||||||
"about_dragonx": "Sobre o ObsidianDragon",
|
|
||||||
"about_edition": "Edição ImGui",
|
|
||||||
"about_github": "GitHub",
|
|
||||||
"about_imgui": "ImGui:",
|
|
||||||
"about_license": "Licença",
|
|
||||||
"about_license_text": "Este software é disponibilizado sob a Licença Pública Geral GNU v3 (GPLv3). Você é livre para usar, modificar e distribuir este software sob os termos da licença.",
|
|
||||||
"about_peers_count": "%zu pares",
|
|
||||||
"about_release": "Versão",
|
|
||||||
"about_title": "Sobre o ObsidianDragon",
|
|
||||||
"about_version": "Versão:",
|
|
||||||
"about_website": "Website",
|
|
||||||
"acrylic": "Acrílico",
|
|
||||||
"add": "Adicionar",
|
|
||||||
"address": "Endereço",
|
|
||||||
"address_book_add": "Adicionar Endereço",
|
|
||||||
"address_book_add_new": "Adicionar Novo",
|
|
||||||
"address_book_added": "Endereço adicionado ao livro",
|
|
||||||
"address_book_count": "%zu endereços salvos",
|
|
||||||
"address_book_deleted": "Entrada excluída",
|
|
||||||
"address_book_edit": "Editar Endereço",
|
|
||||||
"address_book_empty": "Nenhum endereço salvo. Clique em 'Adicionar Novo' para criar um.",
|
|
||||||
"address_book_exists": "Endereço já existe no livro",
|
|
||||||
"address_book_title": "Livro de Endereços",
|
|
||||||
"address_book_update_failed": "Falha na atualização - endereço pode ser duplicado",
|
|
||||||
"address_book_updated": "Endereço atualizado",
|
|
||||||
"address_copied": "Endereço copiado para a área de transferência",
|
|
||||||
"address_details": "Detalhes do Endereço",
|
|
||||||
"address_label": "Endereço:",
|
|
||||||
"address_upper": "ENDEREÇO",
|
|
||||||
"address_url": "URL do Endereço",
|
|
||||||
"addresses_appear_here": "Seus endereços de recebimento aparecerão aqui após a conexão.",
|
|
||||||
"advanced": "AVANÇADO",
|
|
||||||
"all_filter": "Todos",
|
|
||||||
"allow_custom_fees": "Permitir taxas personalizadas",
|
|
||||||
"amount": "Valor",
|
|
||||||
"amount_details": "DETALHES DO VALOR",
|
|
||||||
"amount_exceeds_balance": "Valor excede o saldo",
|
|
||||||
"amount_label": "Valor:",
|
|
||||||
"appearance": "APARÊNCIA",
|
|
||||||
"auto_shield": "Auto-blindar mineração",
|
|
||||||
"available": "Disponível",
|
|
||||||
"backup_backing_up": "Fazendo backup...",
|
|
||||||
"backup_create": "Criar Backup",
|
|
||||||
"backup_created": "Backup da carteira criado",
|
|
||||||
"backup_data": "BACKUP & DADOS",
|
|
||||||
"backup_description": "Crie um backup do seu arquivo wallet.dat. Este arquivo contém todas as suas chaves privadas e histórico de transações. Guarde o backup em um local seguro.",
|
|
||||||
"backup_destination": "Destino do backup:",
|
|
||||||
"backup_tip_external": "Armazene backups em unidades externas ou armazenamento em nuvem",
|
|
||||||
"backup_tip_multiple": "Crie múltiplos backups em diferentes locais",
|
|
||||||
"backup_tip_test": "Teste a restauração do backup periodicamente",
|
|
||||||
"backup_tips": "Dicas:",
|
|
||||||
"backup_title": "Backup da Carteira",
|
|
||||||
"backup_wallet": "Fazer Backup da Carteira...",
|
|
||||||
"backup_wallet_not_found": "Aviso: wallet.dat não encontrado no local esperado",
|
|
||||||
"balance": "Saldo",
|
|
||||||
"balance_layout": "Layout do Saldo",
|
|
||||||
"ban": "Banir",
|
|
||||||
"banned_peers": "Pares Banidos",
|
|
||||||
"block": "Bloco",
|
|
||||||
"block_bits": "Bits:",
|
|
||||||
"block_click_next": "Clique para ver o próximo bloco",
|
|
||||||
"block_click_prev": "Clique para ver o bloco anterior",
|
|
||||||
"block_explorer": "Explorador de Blocos",
|
|
||||||
"block_get_info": "Obter Info do Bloco",
|
|
||||||
"block_hash": "Hash do Bloco:",
|
|
||||||
"block_height": "Altura do Bloco:",
|
|
||||||
"block_info_title": "Informações do Bloco",
|
|
||||||
"block_merkle_root": "Raiz Merkle:",
|
|
||||||
"block_nav_next": "Próximo >>",
|
|
||||||
"block_nav_prev": "<< Anterior",
|
|
||||||
"block_next": "Próximo Bloco:",
|
|
||||||
"block_previous": "Bloco Anterior:",
|
|
||||||
"block_size": "Tamanho:",
|
|
||||||
"block_timestamp": "Carimbo de Data:",
|
|
||||||
"block_transactions": "Transações:",
|
|
||||||
"blockchain_syncing": "Blockchain sincronizando (%.1f%%)... Os saldos podem ser imprecisos.",
|
|
||||||
"cancel": "Cancelar",
|
|
||||||
"characters": "caracteres",
|
|
||||||
"clear": "Limpar",
|
|
||||||
"clear_all_bans": "Remover Todos os Banimentos",
|
|
||||||
"clear_form_confirm": "Limpar todos os campos do formulário?",
|
|
||||||
"clear_request": "Limpar Solicitação",
|
|
||||||
"click_copy_address": "Clique para copiar o endereço",
|
|
||||||
"click_copy_uri": "Clique para copiar a URI",
|
|
||||||
"close": "Fechar",
|
|
||||||
"conf_count": "%d conf.",
|
|
||||||
"confirm_and_send": "Confirmar & Enviar",
|
|
||||||
"confirm_send": "Confirmar Envio",
|
|
||||||
"confirm_transaction": "Confirmar Transação",
|
|
||||||
"confirmations": "Confirmações",
|
|
||||||
"confirmations_display": "%d confirmações | %s",
|
|
||||||
"confirmed": "Confirmado",
|
|
||||||
"connected": "Conectado",
|
|
||||||
"connected_peers": "Pares Conectados",
|
|
||||||
"connecting": "Conectando...",
|
|
||||||
"console": "Console",
|
|
||||||
"console_auto_scroll": "Rolagem automática",
|
|
||||||
"console_available_commands": "Comandos disponíveis:",
|
|
||||||
"console_capturing_output": "Capturando saída do daemon...",
|
|
||||||
"console_clear": "Limpar",
|
|
||||||
"console_clear_console": "Limpar Console",
|
|
||||||
"console_cleared": "Console limpo",
|
|
||||||
"console_click_commands": "Clique nos comandos acima para inseri-los",
|
|
||||||
"console_click_insert": "Clique para inserir",
|
|
||||||
"console_click_insert_params": "Clique para inserir com parâmetros",
|
|
||||||
"console_close": "Fechar",
|
|
||||||
"console_commands": "Comandos",
|
|
||||||
"console_common_rpc": "Comandos RPC comuns:",
|
|
||||||
"console_completions": "Completações:",
|
|
||||||
"console_connected": "Conectado ao daemon",
|
|
||||||
"console_copy_all": "Copiar Tudo",
|
|
||||||
"console_copy_selected": "Copiar",
|
|
||||||
"console_daemon": "Daemon",
|
|
||||||
"console_daemon_error": "Erro do daemon!",
|
|
||||||
"console_daemon_started": "Daemon iniciado",
|
|
||||||
"console_daemon_stopped": "Daemon parado",
|
|
||||||
"console_disconnected": "Desconectado do daemon",
|
|
||||||
"console_errors": "Erros",
|
|
||||||
"console_filter_hint": "Filtrar saída...",
|
|
||||||
"console_help_clear": " clear - Limpar o console",
|
|
||||||
"console_help_getbalance": " getbalance - Mostrar saldo transparente",
|
|
||||||
"console_help_getblockcount": " getblockcount - Mostrar altura atual do bloco",
|
|
||||||
"console_help_getinfo": " getinfo - Mostrar informações do nó",
|
|
||||||
"console_help_getmininginfo": " getmininginfo - Mostrar status da mineração",
|
|
||||||
"console_help_getpeerinfo": " getpeerinfo - Mostrar pares conectados",
|
|
||||||
"console_help_gettotalbalance": " gettotalbalance - Mostrar saldo total",
|
|
||||||
"console_help_help": " help - Mostrar esta mensagem de ajuda",
|
|
||||||
"console_help_setgenerate": " setgenerate - Controlar mineração",
|
|
||||||
"console_help_stop": " stop - Parar o daemon",
|
|
||||||
"console_line_count": "%zu linhas",
|
|
||||||
"console_new_lines": "%d novas linhas",
|
|
||||||
"console_no_daemon": "Sem daemon",
|
|
||||||
"console_not_connected": "Erro: Não conectado ao daemon",
|
|
||||||
"console_rpc_reference": "Referência de Comandos RPC",
|
|
||||||
"console_scanline": "Scanline do console",
|
|
||||||
"console_search_commands": "Pesquisar comandos...",
|
|
||||||
"console_select_all": "Selecionar Tudo",
|
|
||||||
"console_show_daemon_output": "Mostrar saída do daemon",
|
|
||||||
"console_show_errors_only": "Mostrar apenas erros",
|
|
||||||
"console_show_rpc_ref": "Mostrar referência de comandos RPC",
|
|
||||||
"console_showing_lines": "Mostrando %zu de %zu linhas",
|
|
||||||
"console_starting_node": "Iniciando nó...",
|
|
||||||
"console_status_error": "Erro",
|
|
||||||
"console_status_running": "Em execução",
|
|
||||||
"console_status_starting": "Iniciando",
|
|
||||||
"console_status_stopped": "Parado",
|
|
||||||
"console_status_stopping": "Parando",
|
|
||||||
"console_status_unknown": "Desconhecido",
|
|
||||||
"console_tab_completion": "Tab para completar",
|
|
||||||
"console_type_help": "Digite 'help' para comandos disponíveis",
|
|
||||||
"console_welcome": "Bem-vindo ao Console ObsidianDragon",
|
|
||||||
"console_zoom_in": "Aumentar zoom",
|
|
||||||
"console_zoom_out": "Diminuir zoom",
|
|
||||||
"copy": "Copiar",
|
|
||||||
"copy_address": "Copiar Endereço Completo",
|
|
||||||
"copy_error": "Copiar Erro",
|
|
||||||
"copy_to_clipboard": "Copiar para Área de Transferência",
|
|
||||||
"copy_txid": "Copiar TxID",
|
|
||||||
"copy_uri": "Copiar URI",
|
|
||||||
"current_price": "Preço Atual",
|
|
||||||
"custom_fees": "Taxas personalizadas",
|
|
||||||
"dark": "Escuro",
|
|
||||||
"date": "Data",
|
|
||||||
"date_label": "Data:",
|
|
||||||
"delete": "Excluir",
|
|
||||||
"difficulty": "Dificuldade",
|
|
||||||
"disconnected": "Desconectado",
|
|
||||||
"dismiss": "Dispensar",
|
|
||||||
"display": "Exibição",
|
|
||||||
"dragonx_green": "DragonX (Verde)",
|
|
||||||
"edit": "Editar",
|
|
||||||
"error": "Erro",
|
|
||||||
"est_time_to_block": "Tempo Est. por Bloco",
|
|
||||||
"exit": "Sair",
|
|
||||||
"explorer": "EXPLORADOR",
|
|
||||||
"export": "Exportar",
|
|
||||||
"export_csv": "Exportar CSV",
|
|
||||||
"export_keys_btn": "Exportar Chaves",
|
|
||||||
"export_keys_danger": "PERIGO: Isto exportará TODAS as chaves privadas da sua carteira! Qualquer pessoa com acesso a este arquivo pode roubar seus fundos. Guarde com segurança e exclua após o uso.",
|
|
||||||
"export_keys_include_t": "Incluir endereços T (transparentes)",
|
|
||||||
"export_keys_include_z": "Incluir endereços Z (blindados)",
|
|
||||||
"export_keys_options": "Opções de exportação:",
|
|
||||||
"export_keys_success": "Chaves exportadas com sucesso",
|
|
||||||
"export_keys_title": "Exportar Todas as Chaves Privadas",
|
|
||||||
"export_private_key": "Exportar Chave Privada",
|
|
||||||
"export_tx_count": "Exportar %zu transações para arquivo CSV.",
|
|
||||||
"export_tx_file_fail": "Falha ao criar arquivo CSV",
|
|
||||||
"export_tx_none": "Nenhuma transação para exportar",
|
|
||||||
"export_tx_success": "Transações exportadas com sucesso",
|
|
||||||
"export_tx_title": "Exportar Transações para CSV",
|
|
||||||
"export_viewing_key": "Exportar Chave de Visualização",
|
|
||||||
"failed_create_shielded": "Falha ao criar endereço blindado",
|
|
||||||
"failed_create_transparent": "Falha ao criar endereço transparente",
|
|
||||||
"fee": "Taxa",
|
|
||||||
"fee_high": "Alta",
|
|
||||||
"fee_label": "Taxa:",
|
|
||||||
"fee_low": "Baixa",
|
|
||||||
"fee_normal": "Normal",
|
|
||||||
"fetch_prices": "Buscar preços",
|
|
||||||
"file": "Arquivo",
|
|
||||||
"file_save_location": "O arquivo será salvo em: ~/.config/ObsidianDragon/",
|
|
||||||
"font_scale": "Escala da Fonte",
|
|
||||||
"from": "De",
|
|
||||||
"from_upper": "DE",
|
|
||||||
"full_details": "Detalhes Completos",
|
|
||||||
"general": "Geral",
|
|
||||||
"go_to_receive": "Ir para Receber",
|
|
||||||
"height": "Altura",
|
|
||||||
"help": "Ajuda",
|
|
||||||
"hide": "Ocultar",
|
|
||||||
"history": "Histórico",
|
|
||||||
"immature_type": "Imaturo",
|
|
||||||
"import": "Importar",
|
|
||||||
"import_key_btn": "Importar Chave(s)",
|
|
||||||
"import_key_formats": "Formatos de chave suportados:",
|
|
||||||
"import_key_full_rescan": "(0 = rescan completo)",
|
|
||||||
"import_key_label": "Chave(s) Privada(s):",
|
|
||||||
"import_key_no_valid": "Nenhuma chave válida encontrada na entrada",
|
|
||||||
"import_key_rescan": "Reescanear blockchain após importação",
|
|
||||||
"import_key_start_height": "Altura inicial:",
|
|
||||||
"import_key_success": "Chaves importadas com sucesso",
|
|
||||||
"import_key_t_format": "Chaves privadas WIF de endereços T",
|
|
||||||
"import_key_title": "Importar Chave Privada",
|
|
||||||
"import_key_tooltip": "Digite uma ou mais chaves privadas, uma por linha.\nSuporta chaves de z-endereço e t-endereço.\nLinhas começando com # são tratadas como comentários.",
|
|
||||||
"import_key_warning": "Aviso: Nunca compartilhe suas chaves privadas! Importar chaves de fontes não confiáveis pode comprometer sua carteira.",
|
|
||||||
"import_key_z_format": "Chaves de gasto de z-endereço (secret-extended-key-...)",
|
|
||||||
"import_private_key": "Importar Chave Privada...",
|
|
||||||
"invalid_address": "Formato de endereço inválido",
|
|
||||||
"ip_address": "Endereço IP",
|
|
||||||
"keep": "Manter",
|
|
||||||
"keep_daemon": "Manter daemon em execução",
|
|
||||||
"key_export_fetching": "Buscando chave da carteira...",
|
|
||||||
"key_export_private_key": "Chave Privada:",
|
|
||||||
"key_export_private_warning": "Mantenha esta chave em SEGREDO! Qualquer pessoa com esta chave pode gastar seus fundos. Nunca a compartilhe online ou com terceiros não confiáveis.",
|
|
||||||
"key_export_reveal": "Revelar Chave",
|
|
||||||
"key_export_viewing_key": "Chave de Visualização:",
|
|
||||||
"key_export_viewing_warning": "Esta chave de visualização permite que outros vejam suas transações recebidas e saldo, mas NÃO gastem seus fundos. Compartilhe apenas com partes confiáveis.",
|
|
||||||
"label": "Rótulo:",
|
|
||||||
"language": "Idioma",
|
|
||||||
"light": "Claro",
|
|
||||||
"loading": "Carregando...",
|
|
||||||
"loading_addresses": "Carregando endereços...",
|
|
||||||
"local_hashrate": "Hashrate Local",
|
|
||||||
"low_spec_mode": "Modo econômico",
|
|
||||||
"market": "Mercado",
|
|
||||||
"market_12h": "12h",
|
|
||||||
"market_18h": "18h",
|
|
||||||
"market_24h": "24h",
|
|
||||||
"market_24h_volume": "VOLUME 24H",
|
|
||||||
"market_6h": "6h",
|
|
||||||
"market_attribution": "Dados de preço do NonKYC",
|
|
||||||
"market_btc_price": "PREÇO BTC",
|
|
||||||
"market_cap": "Capitalização",
|
|
||||||
"market_no_history": "Nenhum histórico de preços disponível",
|
|
||||||
"market_no_price": "Sem dados de preço",
|
|
||||||
"market_now": "Agora",
|
|
||||||
"market_pct_shielded": "%.0f%% Blindado",
|
|
||||||
"market_portfolio": "PORTFÓLIO",
|
|
||||||
"market_price_unavailable": "Dados de preço indisponíveis",
|
|
||||||
"market_refresh_price": "Atualizar dados de preço",
|
|
||||||
"market_trade_on": "Negociar no %s",
|
|
||||||
"mature": "Maduro",
|
|
||||||
"max": "Máx",
|
|
||||||
"memo": "Memo (opcional, criptografado)",
|
|
||||||
"memo_label": "Memo:",
|
|
||||||
"memo_optional": "MEMO (OPCIONAL)",
|
|
||||||
"memo_upper": "MEMO",
|
|
||||||
"memo_z_only": "Nota: Memos só estão disponíveis ao enviar para endereços blindados (z)",
|
|
||||||
"merge_description": "Fundir múltiplos UTXOs em um único endereço blindado. Isso pode ajudar a reduzir o tamanho da carteira e melhorar a privacidade.",
|
|
||||||
"merge_funds": "Fundir Fundos",
|
|
||||||
"merge_started": "Operação de fusão iniciada",
|
|
||||||
"merge_title": "Fundir para Endereço",
|
|
||||||
"mine_when_idle": "Minerar quando ocioso",
|
|
||||||
"mined": "minerado",
|
|
||||||
"mined_filter": "Minerado",
|
|
||||||
"mined_type": "Minerado",
|
|
||||||
"mined_upper": "MINERADO",
|
|
||||||
"miner_fee": "Taxa de Minerador",
|
|
||||||
"mining": "Mineração",
|
|
||||||
"mining_active": "Ativo",
|
|
||||||
"mining_address_copied": "Endereço de mineração copiado",
|
|
||||||
"mining_all_time": "Todo o Tempo",
|
|
||||||
"mining_already_saved": "URL do pool já salva",
|
|
||||||
"mining_block_copied": "Hash do bloco copiado",
|
|
||||||
"mining_chart_1m_ago": "1m atrás",
|
|
||||||
"mining_chart_5m_ago": "5m atrás",
|
|
||||||
"mining_chart_now": "Agora",
|
|
||||||
"mining_chart_start": "Início",
|
|
||||||
"mining_click": "Clique",
|
|
||||||
"mining_click_copy_address": "Clique para copiar o endereço",
|
|
||||||
"mining_click_copy_block": "Clique para copiar o hash do bloco",
|
|
||||||
"mining_click_copy_difficulty": "Clique para copiar a dificuldade",
|
|
||||||
"mining_connected": "Conectado",
|
|
||||||
"mining_connecting": "Conectando...",
|
|
||||||
"mining_control": "Controle de Mineração",
|
|
||||||
"mining_difficulty_copied": "Dificuldade copiada",
|
|
||||||
"mining_est_block": "Bloco Est.",
|
|
||||||
"mining_est_daily": "Est. Diário",
|
|
||||||
"mining_filter_all": "Todos",
|
|
||||||
"mining_filter_tip_all": "Mostrar todos os ganhos",
|
|
||||||
"mining_filter_tip_pool": "Mostrar apenas ganhos do pool",
|
|
||||||
"mining_filter_tip_solo": "Mostrar apenas ganhos solo",
|
|
||||||
"mining_idle_off_tooltip": "Ativar mineração ociosa",
|
|
||||||
"mining_idle_on_tooltip": "Desativar mineração ociosa",
|
|
||||||
"mining_local_hashrate": "Hashrate Local",
|
|
||||||
"mining_mine": "Minerar",
|
|
||||||
"mining_mining_addr": "End. Mineração",
|
|
||||||
"mining_network": "Rede",
|
|
||||||
"mining_no_blocks_yet": "Nenhum bloco encontrado ainda",
|
|
||||||
"mining_no_payouts_yet": "Nenhum pagamento de pool ainda",
|
|
||||||
"mining_no_saved_addresses": "Nenhum endereço salvo",
|
|
||||||
"mining_no_saved_pools": "Nenhum pool salvo",
|
|
||||||
"mining_off": "Mineração está DESLIGADA",
|
|
||||||
"mining_on": "Mineração está LIGADA",
|
|
||||||
"mining_open_in_explorer": "Abrir no explorador",
|
|
||||||
"mining_payout_address": "Endereço de Pagamento",
|
|
||||||
"mining_payout_tooltip": "Endereço para receber recompensas de mineração",
|
|
||||||
"mining_pool": "Pool",
|
|
||||||
"mining_pool_hashrate": "Hashrate do Pool",
|
|
||||||
"mining_pool_url": "URL do Pool",
|
|
||||||
"mining_recent_blocks": "BLOCOS RECENTES",
|
|
||||||
"mining_recent_payouts": "PAGAMENTOS DE POOL RECENTES",
|
|
||||||
"mining_remove": "Remover",
|
|
||||||
"mining_reset_defaults": "Redefinir Padrões",
|
|
||||||
"mining_save_payout_address": "Salvar endereço de pagamento",
|
|
||||||
"mining_save_pool_url": "Salvar URL do pool",
|
|
||||||
"mining_saved_addresses": "Endereços Salvos:",
|
|
||||||
"mining_saved_pools": "Pools Salvos:",
|
|
||||||
"mining_shares": "Shares",
|
|
||||||
"mining_show_chart": "Gráfico",
|
|
||||||
"mining_show_log": "Log",
|
|
||||||
"mining_solo": "Solo",
|
|
||||||
"mining_starting": "Iniciando...",
|
|
||||||
"mining_starting_tooltip": "Minerador está iniciando...",
|
|
||||||
"mining_statistics": "Estatísticas de Mineração",
|
|
||||||
"mining_stop": "Parar",
|
|
||||||
"mining_stop_solo_for_pool": "Pare a mineração solo antes de iniciar a mineração em pool",
|
|
||||||
"mining_stop_solo_for_pool_settings": "Pare a mineração solo para alterar as configurações do pool",
|
|
||||||
"mining_stopping": "Parando...",
|
|
||||||
"mining_stopping_tooltip": "Minerador está parando...",
|
|
||||||
"mining_syncing_tooltip": "Blockchain está sincronizando...",
|
|
||||||
"mining_threads": "Threads de Mineração",
|
|
||||||
"mining_to_save": "para salvar",
|
|
||||||
"mining_today": "Hoje",
|
|
||||||
"mining_uptime": "Tempo Ativo",
|
|
||||||
"mining_yesterday": "Ontem",
|
|
||||||
"network": "Rede",
|
|
||||||
"network_fee": "TAXA DA REDE",
|
|
||||||
"network_hashrate": "Hashrate da Rede",
|
|
||||||
"new": "+ Novo",
|
|
||||||
"new_shielded_created": "Novo endereço blindado criado",
|
|
||||||
"new_t_address": "Novo Endereço T",
|
|
||||||
"new_t_transparent": "Novo endereço t (Transparente)",
|
|
||||||
"new_transparent_created": "Novo endereço transparente criado",
|
|
||||||
"new_z_address": "Novo Endereço Z",
|
|
||||||
"new_z_shielded": "Novo endereço z (Blindado)",
|
|
||||||
"no_addresses": "Nenhum endereço encontrado. Crie um usando os botões acima.",
|
|
||||||
"no_addresses_available": "Nenhum endereço disponível",
|
|
||||||
"no_addresses_match": "Nenhum endereço corresponde ao filtro",
|
|
||||||
"no_addresses_with_balance": "Nenhum endereço com saldo",
|
|
||||||
"no_matching": "Nenhuma transação correspondente",
|
|
||||||
"no_recent_receives": "Nenhum recebimento recente",
|
|
||||||
"no_recent_sends": "Nenhum envio recente",
|
|
||||||
"no_transactions": "Nenhuma transação encontrada",
|
|
||||||
"node": "NÓ",
|
|
||||||
"node_security": "NÓ & SEGURANÇA",
|
|
||||||
"noise": "Ruído",
|
|
||||||
"not_connected": "Não conectado ao daemon...",
|
|
||||||
"not_connected_to_daemon": "Não conectado ao daemon",
|
|
||||||
"notes": "Notas",
|
|
||||||
"notes_optional": "Notas (opcional):",
|
|
||||||
"output_filename": "Nome do arquivo de saída:",
|
|
||||||
"overview": "Visão Geral",
|
|
||||||
"paste": "Colar",
|
|
||||||
"paste_from_clipboard": "Colar da Área de Transferência",
|
|
||||||
"pay_from": "Pagar de",
|
|
||||||
"payment_request": "SOLICITAÇÃO DE PAGAMENTO",
|
|
||||||
"payment_request_copied": "Solicitação de pagamento copiada",
|
|
||||||
"payment_uri_copied": "URI de pagamento copiada",
|
|
||||||
"peers": "Pares",
|
|
||||||
"peers_avg_ping": "Ping Médio",
|
|
||||||
"peers_ban_24h": "Banir Par 24h",
|
|
||||||
"peers_ban_score": "Score de Ban: %d",
|
|
||||||
"peers_banned": "Banidos",
|
|
||||||
"peers_banned_count": "Banidos: %d",
|
|
||||||
"peers_best_block": "Melhor Bloco",
|
|
||||||
"peers_blockchain": "BLOCKCHAIN",
|
|
||||||
"peers_blocks": "Blocos",
|
|
||||||
"peers_blocks_left": "%d blocos restantes",
|
|
||||||
"peers_clear_all_bans": "Remover Todos os Banimentos",
|
|
||||||
"peers_click_copy": "Clique para copiar",
|
|
||||||
"peers_connected": "Conectados",
|
|
||||||
"peers_connected_count": "Conectados: %d",
|
|
||||||
"peers_copy_ip": "Copiar IP",
|
|
||||||
"peers_dir_in": "Ent.",
|
|
||||||
"peers_dir_out": "Saí.",
|
|
||||||
"peers_hash_copied": "Hash copiado",
|
|
||||||
"peers_hashrate": "Hashrate",
|
|
||||||
"peers_in_out": "Ent./Saí.",
|
|
||||||
"peers_longest": "Mais longa",
|
|
||||||
"peers_longest_chain": "Chain Mais Longa",
|
|
||||||
"peers_memory": "Memória",
|
|
||||||
"peers_no_banned": "Nenhum par banido",
|
|
||||||
"peers_no_connected": "Nenhum par conectado",
|
|
||||||
"peers_no_tls": "Sem TLS",
|
|
||||||
"peers_notarized": "Notarizado",
|
|
||||||
"peers_p2p_port": "Porta P2P",
|
|
||||||
"peers_protocol": "Protocolo",
|
|
||||||
"peers_received": "Recebido",
|
|
||||||
"peers_refresh": "Atualizar",
|
|
||||||
"peers_refresh_tooltip": "Atualizar lista de pares",
|
|
||||||
"peers_refreshing": "Atualizando...",
|
|
||||||
"peers_sent": "Enviado",
|
|
||||||
"peers_tt_id": "ID: %d",
|
|
||||||
"peers_tt_received": "Recebido: %s",
|
|
||||||
"peers_tt_sent": "Enviado: %s",
|
|
||||||
"peers_tt_services": "Serviços: %s",
|
|
||||||
"peers_tt_start_height": "Altura Inicial: %d",
|
|
||||||
"peers_tt_synced": "Sincronizado H/B: %d/%d",
|
|
||||||
"peers_tt_tls_cipher": "TLS: %s",
|
|
||||||
"peers_unban": "Desbanir",
|
|
||||||
"peers_upper": "PARES",
|
|
||||||
"peers_version": "Versão",
|
|
||||||
"pending": "Pendente",
|
|
||||||
"ping": "Ping",
|
|
||||||
"price_chart": "Gráfico de Preços",
|
|
||||||
"qr_code": "Código QR",
|
|
||||||
"qr_failed": "Falha ao gerar código QR",
|
|
||||||
"qr_title": "Código QR",
|
|
||||||
"qr_unavailable": "QR indisponível",
|
|
||||||
"receive": "Receber",
|
|
||||||
"received": "recebido",
|
|
||||||
"received_filter": "Recebido",
|
|
||||||
"received_label": "Recebido",
|
|
||||||
"received_upper": "RECEBIDO",
|
|
||||||
"receiving_addresses": "Seus Endereços de Recebimento",
|
|
||||||
"recent_received": "RECEBIDOS RECENTES",
|
|
||||||
"recent_sends": "ENVIOS RECENTES",
|
|
||||||
"recipient": "DESTINATÁRIO",
|
|
||||||
"recv_type": "Receb.",
|
|
||||||
"refresh": "Atualizar",
|
|
||||||
"refresh_now": "Atualizar Agora",
|
|
||||||
"report_bug": "Reportar Bug",
|
|
||||||
"request_amount": "Valor (opcional):",
|
|
||||||
"request_copy_uri": "Copiar URI",
|
|
||||||
"request_description": "Gere uma solicitação de pagamento que outros podem escanear ou copiar. O código QR contém seu endereço e valor/memo opcionais.",
|
|
||||||
"request_label": "Rótulo (opcional):",
|
|
||||||
"request_memo": "Memo (opcional):",
|
|
||||||
"request_payment": "Solicitar Pagamento",
|
|
||||||
"request_payment_uri": "URI de Pagamento:",
|
|
||||||
"request_receive_address": "Endereço de Recebimento:",
|
|
||||||
"request_select_address": "Selecionar endereço...",
|
|
||||||
"request_shielded_addrs": "-- Endereços Blindados --",
|
|
||||||
"request_title": "Solicitar Pagamento",
|
|
||||||
"request_transparent_addrs": "-- Endereços Transparentes --",
|
|
||||||
"request_uri_copied": "URI de pagamento copiada para a área de transferência",
|
|
||||||
"rescan": "Reescanear",
|
|
||||||
"reset_to_defaults": "Redefinir Padrões",
|
|
||||||
"review_send": "Revisar Envio",
|
|
||||||
"rpc_host": "Host RPC",
|
|
||||||
"rpc_pass": "Senha",
|
|
||||||
"rpc_port": "Porta",
|
|
||||||
"rpc_user": "Usuário",
|
|
||||||
"save": "Salvar",
|
|
||||||
"save_settings": "Salvar Configurações",
|
|
||||||
"save_z_transactions": "Salvar Z-tx na lista de tx",
|
|
||||||
"search_placeholder": "Pesquisar...",
|
|
||||||
"security": "SEGURANÇA",
|
|
||||||
"select_address": "Selecionar endereço...",
|
|
||||||
"select_receiving_address": "Selecionar endereço de recebimento...",
|
|
||||||
"select_source_address": "Selecionar endereço de origem...",
|
|
||||||
"send": "Enviar",
|
|
||||||
"send_amount": "Valor",
|
|
||||||
"send_amount_details": "DETALHES DO VALOR",
|
|
||||||
"send_amount_upper": "VALOR",
|
|
||||||
"send_clear_fields": "Limpar todos os campos do formulário?",
|
|
||||||
"send_copy_error": "Copiar Erro",
|
|
||||||
"send_dismiss": "Dispensar",
|
|
||||||
"send_error_copied": "Erro copiado para a área de transferência",
|
|
||||||
"send_error_prefix": "Erro: %s",
|
|
||||||
"send_exceeds_available": "Excede o disponível (%.8f)",
|
|
||||||
"send_fee": "Taxa",
|
|
||||||
"send_fee_high": "Alta",
|
|
||||||
"send_fee_low": "Baixa",
|
|
||||||
"send_fee_normal": "Normal",
|
|
||||||
"send_form_restored": "Formulário restaurado",
|
|
||||||
"send_from_this_address": "Enviar deste endereço",
|
|
||||||
"send_go_to_receive": "Ir para Receber",
|
|
||||||
"send_keep": "Manter",
|
|
||||||
"send_network_fee": "TAXA DA REDE",
|
|
||||||
"send_no_balance": "Sem saldo",
|
|
||||||
"send_no_recent": "Nenhum envio recente",
|
|
||||||
"send_recent_sends": "ENVIOS RECENTES",
|
|
||||||
"send_recipient": "DESTINATÁRIO",
|
|
||||||
"send_select_source": "Selecionar endereço de origem...",
|
|
||||||
"send_sending_from": "ENVIANDO DE",
|
|
||||||
"send_submitting": "Enviando transação...",
|
|
||||||
"send_switch_to_receive": "Mude para Receber para obter seu endereço e começar a receber fundos.",
|
|
||||||
"send_to": "Enviar para",
|
|
||||||
"send_tooltip_enter_amount": "Digite um valor para enviar",
|
|
||||||
"send_tooltip_exceeds_balance": "Valor excede o saldo disponível",
|
|
||||||
"send_tooltip_in_progress": "Transação já em andamento",
|
|
||||||
"send_tooltip_invalid_address": "Digite um endereço de destinatário válido",
|
|
||||||
"send_tooltip_not_connected": "Não conectado ao daemon",
|
|
||||||
"send_tooltip_select_source": "Selecione primeiro um endereço de origem",
|
|
||||||
"send_tooltip_syncing": "Aguarde a sincronização da blockchain",
|
|
||||||
"send_total": "Total",
|
|
||||||
"send_transaction": "Enviar Transação",
|
|
||||||
"send_tx_failed": "Transação falhou",
|
|
||||||
"send_tx_sent": "Transação enviada!",
|
|
||||||
"send_tx_success": "Transação enviada com sucesso!",
|
|
||||||
"send_txid_copied": "TxID copiado para a área de transferência",
|
|
||||||
"send_txid_label": "TxID: %s",
|
|
||||||
"send_valid_shielded": "Endereço blindado válido",
|
|
||||||
"send_valid_transparent": "Endereço transparente válido",
|
|
||||||
"send_wallet_empty": "Sua carteira está vazia",
|
|
||||||
"send_yes_clear": "Sim, Limpar",
|
|
||||||
"sending": "Enviando transação",
|
|
||||||
"sending_from": "ENVIANDO DE",
|
|
||||||
"sent": "enviado",
|
|
||||||
"sent_filter": "Enviado",
|
|
||||||
"sent_type": "Enviado",
|
|
||||||
"sent_upper": "ENVIADO",
|
|
||||||
"settings": "Configurações",
|
|
||||||
"setup_wizard": "Assistente de Configuração",
|
|
||||||
"share": "Compartilhar",
|
|
||||||
"shield_check_status": "Verificar Status",
|
|
||||||
"shield_completed": "Operação concluída com sucesso!",
|
|
||||||
"shield_description": "Blinde suas recompensas de mineração enviando saídas coinbase de endereços transparentes para um endereço blindado. Isso melhora a privacidade ocultando sua renda de mineração.",
|
|
||||||
"shield_from_address": "Do Endereço:",
|
|
||||||
"shield_funds": "Blindar Fundos",
|
|
||||||
"shield_in_progress": "Operação em andamento...",
|
|
||||||
"shield_max_utxos": "Máx. UTXOs por operação",
|
|
||||||
"shield_merge_done": "Blindagem/fusão concluída!",
|
|
||||||
"shield_select_z": "Selecionar z-endereço...",
|
|
||||||
"shield_started": "Operação de blindagem iniciada",
|
|
||||||
"shield_title": "Blindar Recompensas Coinbase",
|
|
||||||
"shield_to_address": "Para Endereço (Blindado):",
|
|
||||||
"shield_utxo_limit": "Limite UTXO:",
|
|
||||||
"shield_wildcard_hint": "Use '*' para blindar de todos os endereços transparentes",
|
|
||||||
"shielded": "Blindado",
|
|
||||||
"shielded_to": "BLINDADO PARA",
|
|
||||||
"shielded_type": "Blindado",
|
|
||||||
"show": "Mostrar",
|
|
||||||
"show_qr_code": "Mostrar Código QR",
|
|
||||||
"showing_transactions": "Mostrando %d\xe2\x80\x93%d de %d transações (total: %zu)",
|
|
||||||
"simple_background": "Fundo simples",
|
|
||||||
"start_mining": "Iniciar Mineração",
|
|
||||||
"status": "Status",
|
|
||||||
"stop_external": "Parar daemon externo",
|
|
||||||
"stop_mining": "Parar Mineração",
|
|
||||||
"submitting_transaction": "Enviando transação...",
|
|
||||||
"success": "Sucesso",
|
|
||||||
"summary": "Resumo",
|
|
||||||
"syncing": "Sincronizando...",
|
|
||||||
"t_addresses": "Endereços T",
|
|
||||||
"test_connection": "Testar",
|
|
||||||
"theme": "Tema",
|
|
||||||
"theme_effects": "Efeitos de tema",
|
|
||||||
"time_days_ago": "há %d dias",
|
|
||||||
"time_hours_ago": "há %d horas",
|
|
||||||
"time_minutes_ago": "há %d minutos",
|
|
||||||
"time_seconds_ago": "há %d segundos",
|
|
||||||
"to": "Para",
|
|
||||||
"to_upper": "PARA",
|
|
||||||
"tools": "FERRAMENTAS",
|
|
||||||
"total": "Total",
|
|
||||||
"transaction_id": "ID DA TRANSAÇÃO",
|
|
||||||
"transaction_sent": "Transação enviada com sucesso",
|
|
||||||
"transaction_sent_msg": "Transação enviada!",
|
|
||||||
"transaction_url": "URL da Transação",
|
|
||||||
"transactions": "Transações",
|
|
||||||
"transactions_upper": "TRANSAÇÕES",
|
|
||||||
"transparent": "Transparente",
|
|
||||||
"tx_confirmations": "%d confirmações",
|
|
||||||
"tx_details_title": "Detalhes da Transação",
|
|
||||||
"tx_from_address": "Endereço de Origem:",
|
|
||||||
"tx_id_label": "ID da Transação:",
|
|
||||||
"tx_immature": "IMATURO",
|
|
||||||
"tx_mined": "MINERADO",
|
|
||||||
"tx_received": "RECEBIDO",
|
|
||||||
"tx_sent": "ENVIADO",
|
|
||||||
"tx_to_address": "Endereço de Destino:",
|
|
||||||
"tx_view_explorer": "Ver no Explorador",
|
|
||||||
"txs_count": "%d txs",
|
|
||||||
"type": "Tipo",
|
|
||||||
"ui_opacity": "Opacidade da Interface",
|
|
||||||
"unban": "Desbanir",
|
|
||||||
"unconfirmed": "Não confirmado",
|
|
||||||
"undo_clear": "Desfazer Limpeza",
|
|
||||||
"unknown": "Desconhecido",
|
|
||||||
"use_embedded_daemon": "Usar dragonxd integrado",
|
|
||||||
"use_tor": "Usar Tor",
|
|
||||||
"validate_btn": "Validar",
|
|
||||||
"validate_description": "Digite um endereço DragonX para verificar se é válido e se pertence a esta carteira.",
|
|
||||||
"validate_invalid": "INVÁLIDO",
|
|
||||||
"validate_is_mine": "Esta carteira possui este endereço",
|
|
||||||
"validate_not_mine": "Não pertence a esta carteira",
|
|
||||||
"validate_ownership": "Propriedade:",
|
|
||||||
"validate_results": "Resultados:",
|
|
||||||
"validate_shielded_type": "Blindado (z-endereço)",
|
|
||||||
"validate_status": "Status:",
|
|
||||||
"validate_title": "Validar Endereço",
|
|
||||||
"validate_transparent_type": "Transparente (t-endereço)",
|
|
||||||
"validate_type": "Tipo:",
|
|
||||||
"validate_valid": "VÁLIDO",
|
|
||||||
"validating": "Validando...",
|
|
||||||
"verbose_logging": "Log detalhado",
|
|
||||||
"version": "Versão",
|
|
||||||
"view": "Visualizar",
|
|
||||||
"view_details": "Ver Detalhes",
|
|
||||||
"view_on_explorer": "Ver no Explorador",
|
|
||||||
"waiting_for_daemon": "Aguardando conexão com o daemon...",
|
|
||||||
"wallet": "CARTEIRA",
|
|
||||||
"wallet_empty": "Sua carteira está vazia",
|
|
||||||
"wallet_empty_hint": "Mude para Receber para obter seu endereço e começar a receber fundos.",
|
|
||||||
"warning": "Aviso",
|
|
||||||
"warning_upper": "AVISO!",
|
|
||||||
"website": "Website",
|
|
||||||
"window_opacity": "Opacidade da Janela",
|
|
||||||
"yes_clear": "Sim, Limpar",
|
|
||||||
"your_addresses": "Seus Endereços",
|
|
||||||
"z_addresses": "Endereços Z",
|
|
||||||
}
|
|
||||||
|
|
||||||
out = os.path.join(os.path.dirname(__file__), "..", "res", "lang", "pt.json")
|
|
||||||
with open(out, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(translations, f, indent=4, ensure_ascii=False, sort_keys=True)
|
|
||||||
print(f"Wrote {len(translations)} Portuguese translations to {os.path.abspath(out)}")
|
|
||||||
@@ -1,646 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate Russian (ru) translations for ObsidianDragon wallet."""
|
|
||||||
import json, os
|
|
||||||
|
|
||||||
translations = {
|
|
||||||
"24h_change": "Изменение за 24ч",
|
|
||||||
"24h_volume": "Объём за 24ч",
|
|
||||||
"about": "О программе",
|
|
||||||
"about_block_explorer": "Обозреватель блоков",
|
|
||||||
"about_block_height": "Высота блока:",
|
|
||||||
"about_build_date": "Дата сборки:",
|
|
||||||
"about_build_type": "Тип сборки:",
|
|
||||||
"about_chain": "Цепочка:",
|
|
||||||
"about_connections": "Подключения:",
|
|
||||||
"about_credits": "Благодарности",
|
|
||||||
"about_daemon": "Daemon:",
|
|
||||||
"about_debug": "Отладка",
|
|
||||||
"about_dragonx": "Об ObsidianDragon",
|
|
||||||
"about_edition": "Редакция ImGui",
|
|
||||||
"about_github": "GitHub",
|
|
||||||
"about_imgui": "ImGui:",
|
|
||||||
"about_license": "Лицензия",
|
|
||||||
"about_license_text": "Это программное обеспечение выпущено под лицензией GNU General Public License v3 (GPLv3). Вы можете свободно использовать, изменять и распространять это ПО в соответствии с условиями лицензии.",
|
|
||||||
"about_peers_count": "%zu узлов",
|
|
||||||
"about_release": "Релиз",
|
|
||||||
"about_title": "Об ObsidianDragon",
|
|
||||||
"about_version": "Версия:",
|
|
||||||
"about_website": "Веб-сайт",
|
|
||||||
"acrylic": "Акрил",
|
|
||||||
"add": "Добавить",
|
|
||||||
"address": "Адрес",
|
|
||||||
"address_book_add": "Добавить адрес",
|
|
||||||
"address_book_add_new": "Добавить новый",
|
|
||||||
"address_book_added": "Адрес добавлен в книгу",
|
|
||||||
"address_book_count": "%zu адресов сохранено",
|
|
||||||
"address_book_deleted": "Запись удалена",
|
|
||||||
"address_book_edit": "Редактировать адрес",
|
|
||||||
"address_book_empty": "Нет сохранённых адресов. Нажмите 'Добавить новый', чтобы создать.",
|
|
||||||
"address_book_exists": "Адрес уже существует в книге",
|
|
||||||
"address_book_title": "Адресная книга",
|
|
||||||
"address_book_update_failed": "Не удалось обновить — адрес может быть дубликатом",
|
|
||||||
"address_book_updated": "Адрес обновлён",
|
|
||||||
"address_copied": "Адрес скопирован в буфер обмена",
|
|
||||||
"address_details": "Детали адреса",
|
|
||||||
"address_label": "Адрес:",
|
|
||||||
"address_upper": "АДРЕС",
|
|
||||||
"address_url": "URL адреса",
|
|
||||||
"addresses_appear_here": "Ваши адреса для получения появятся здесь после подключения.",
|
|
||||||
"advanced": "РАСШИРЕННЫЕ",
|
|
||||||
"all_filter": "Все",
|
|
||||||
"allow_custom_fees": "Разрешить пользовательские комиссии",
|
|
||||||
"amount": "Сумма",
|
|
||||||
"amount_details": "ДЕТАЛИ СУММЫ",
|
|
||||||
"amount_exceeds_balance": "Сумма превышает баланс",
|
|
||||||
"amount_label": "Сумма:",
|
|
||||||
"appearance": "ВНЕШНИЙ ВИД",
|
|
||||||
"auto_shield": "Авто-экранирование майнинга",
|
|
||||||
"available": "Доступно",
|
|
||||||
"backup_backing_up": "Создание резервной копии...",
|
|
||||||
"backup_create": "Создать резервную копию",
|
|
||||||
"backup_created": "Резервная копия кошелька создана",
|
|
||||||
"backup_data": "РЕЗЕРВНОЕ КОПИРОВАНИЕ И ДАННЫЕ",
|
|
||||||
"backup_description": "Создайте резервную копию файла wallet.dat. Этот файл содержит все ваши приватные ключи и историю транзакций. Храните копию в безопасном месте.",
|
|
||||||
"backup_destination": "Место сохранения:",
|
|
||||||
"backup_tip_external": "Храните резервные копии на внешних дисках или в облаке",
|
|
||||||
"backup_tip_multiple": "Создавайте несколько копий в разных местах",
|
|
||||||
"backup_tip_test": "Периодически проверяйте восстановление из резервной копии",
|
|
||||||
"backup_tips": "Советы:",
|
|
||||||
"backup_title": "Резервное копирование кошелька",
|
|
||||||
"backup_wallet": "Создать резервную копию...",
|
|
||||||
"backup_wallet_not_found": "Предупреждение: wallet.dat не найден в ожидаемом расположении",
|
|
||||||
"balance": "Баланс",
|
|
||||||
"balance_layout": "Макет баланса",
|
|
||||||
"ban": "Заблокировать",
|
|
||||||
"banned_peers": "Заблокированные узлы",
|
|
||||||
"block": "Блок",
|
|
||||||
"block_bits": "Биты:",
|
|
||||||
"block_click_next": "Нажмите для следующего блока",
|
|
||||||
"block_click_prev": "Нажмите для предыдущего блока",
|
|
||||||
"block_explorer": "Обозреватель блоков",
|
|
||||||
"block_get_info": "Получить информацию о блоке",
|
|
||||||
"block_hash": "Хэш блока:",
|
|
||||||
"block_height": "Высота блока:",
|
|
||||||
"block_info_title": "Информация о блоке",
|
|
||||||
"block_merkle_root": "Корень Меркла:",
|
|
||||||
"block_nav_next": "Далее >>",
|
|
||||||
"block_nav_prev": "<< Назад",
|
|
||||||
"block_next": "Следующий блок:",
|
|
||||||
"block_previous": "Предыдущий блок:",
|
|
||||||
"block_size": "Размер:",
|
|
||||||
"block_timestamp": "Временная метка:",
|
|
||||||
"block_transactions": "Транзакции:",
|
|
||||||
"blockchain_syncing": "Синхронизация блокчейна (%.1f%%)... Балансы могут быть неточными.",
|
|
||||||
"cancel": "Отмена",
|
|
||||||
"characters": "символов",
|
|
||||||
"clear": "Очистить",
|
|
||||||
"clear_all_bans": "Снять все блокировки",
|
|
||||||
"clear_form_confirm": "Очистить все поля формы?",
|
|
||||||
"clear_request": "Очистить запрос",
|
|
||||||
"click_copy_address": "Нажмите, чтобы скопировать адрес",
|
|
||||||
"click_copy_uri": "Нажмите, чтобы скопировать URI",
|
|
||||||
"close": "Закрыть",
|
|
||||||
"conf_count": "%d подтв.",
|
|
||||||
"confirm_and_send": "Подтвердить и отправить",
|
|
||||||
"confirm_send": "Подтвердить отправку",
|
|
||||||
"confirm_transaction": "Подтвердить транзакцию",
|
|
||||||
"confirmations": "Подтверждения",
|
|
||||||
"confirmations_display": "%d подтверждений | %s",
|
|
||||||
"confirmed": "Подтверждено",
|
|
||||||
"connected": "Подключено",
|
|
||||||
"connected_peers": "Подключённые узлы",
|
|
||||||
"connecting": "Подключение...",
|
|
||||||
"console": "Консоль",
|
|
||||||
"console_auto_scroll": "Авто-прокрутка",
|
|
||||||
"console_available_commands": "Доступные команды:",
|
|
||||||
"console_capturing_output": "Захват вывода daemon...",
|
|
||||||
"console_clear": "Очистить",
|
|
||||||
"console_clear_console": "Очистить консоль",
|
|
||||||
"console_cleared": "Консоль очищена",
|
|
||||||
"console_click_commands": "Нажмите на команды выше, чтобы вставить их",
|
|
||||||
"console_click_insert": "Нажмите для вставки",
|
|
||||||
"console_click_insert_params": "Нажмите для вставки с параметрами",
|
|
||||||
"console_close": "Закрыть",
|
|
||||||
"console_commands": "Команды",
|
|
||||||
"console_common_rpc": "Частые RPC-команды:",
|
|
||||||
"console_completions": "Дополнения:",
|
|
||||||
"console_connected": "Подключено к daemon",
|
|
||||||
"console_copy_all": "Копировать всё",
|
|
||||||
"console_copy_selected": "Копировать",
|
|
||||||
"console_daemon": "Daemon",
|
|
||||||
"console_daemon_error": "Ошибка daemon!",
|
|
||||||
"console_daemon_started": "Daemon запущен",
|
|
||||||
"console_daemon_stopped": "Daemon остановлен",
|
|
||||||
"console_disconnected": "Отключено от daemon",
|
|
||||||
"console_errors": "Ошибки",
|
|
||||||
"console_filter_hint": "Фильтр вывода...",
|
|
||||||
"console_help_clear": " clear - Очистить консоль",
|
|
||||||
"console_help_getbalance": " getbalance - Показать прозрачный баланс",
|
|
||||||
"console_help_getblockcount": " getblockcount - Показать текущую высоту блока",
|
|
||||||
"console_help_getinfo": " getinfo - Показать информацию об узле",
|
|
||||||
"console_help_getmininginfo": " getmininginfo - Показать статус майнинга",
|
|
||||||
"console_help_getpeerinfo": " getpeerinfo - Показать подключённые узлы",
|
|
||||||
"console_help_gettotalbalance": " gettotalbalance - Показать общий баланс",
|
|
||||||
"console_help_help": " help - Показать эту справку",
|
|
||||||
"console_help_setgenerate": " setgenerate - Управление майнингом",
|
|
||||||
"console_help_stop": " stop - Остановить daemon",
|
|
||||||
"console_line_count": "%zu строк",
|
|
||||||
"console_new_lines": "%d новых строк",
|
|
||||||
"console_no_daemon": "Нет daemon",
|
|
||||||
"console_not_connected": "Ошибка: Не подключено к daemon",
|
|
||||||
"console_rpc_reference": "Справочник RPC-команд",
|
|
||||||
"console_scanline": "Скан-линия консоли",
|
|
||||||
"console_search_commands": "Поиск команд...",
|
|
||||||
"console_select_all": "Выбрать всё",
|
|
||||||
"console_show_daemon_output": "Показать вывод daemon",
|
|
||||||
"console_show_errors_only": "Показать только ошибки",
|
|
||||||
"console_show_rpc_ref": "Показать справочник RPC-команд",
|
|
||||||
"console_showing_lines": "Показано %zu из %zu строк",
|
|
||||||
"console_starting_node": "Запуск узла...",
|
|
||||||
"console_status_error": "Ошибка",
|
|
||||||
"console_status_running": "Работает",
|
|
||||||
"console_status_starting": "Запуск",
|
|
||||||
"console_status_stopped": "Остановлен",
|
|
||||||
"console_status_stopping": "Остановка",
|
|
||||||
"console_status_unknown": "Неизвестно",
|
|
||||||
"console_tab_completion": "Tab для дополнения",
|
|
||||||
"console_type_help": "Введите 'help' для списка команд",
|
|
||||||
"console_welcome": "Добро пожаловать в консоль ObsidianDragon",
|
|
||||||
"console_zoom_in": "Увеличить",
|
|
||||||
"console_zoom_out": "Уменьшить",
|
|
||||||
"copy": "Копировать",
|
|
||||||
"copy_address": "Копировать полный адрес",
|
|
||||||
"copy_error": "Копировать ошибку",
|
|
||||||
"copy_to_clipboard": "Копировать в буфер обмена",
|
|
||||||
"copy_txid": "Копировать TxID",
|
|
||||||
"copy_uri": "Копировать URI",
|
|
||||||
"current_price": "Текущая цена",
|
|
||||||
"custom_fees": "Пользовательские комиссии",
|
|
||||||
"dark": "Тёмная",
|
|
||||||
"date": "Дата",
|
|
||||||
"date_label": "Дата:",
|
|
||||||
"delete": "Удалить",
|
|
||||||
"difficulty": "Сложность",
|
|
||||||
"disconnected": "Отключено",
|
|
||||||
"dismiss": "Отклонить",
|
|
||||||
"display": "Отображение",
|
|
||||||
"dragonx_green": "DragonX (Зелёная)",
|
|
||||||
"edit": "Редактировать",
|
|
||||||
"error": "Ошибка",
|
|
||||||
"est_time_to_block": "Расч. время до блока",
|
|
||||||
"exit": "Выход",
|
|
||||||
"explorer": "ОБОЗРЕВАТЕЛЬ",
|
|
||||||
"export": "Экспорт",
|
|
||||||
"export_csv": "Экспорт в CSV",
|
|
||||||
"export_keys_btn": "Экспорт ключей",
|
|
||||||
"export_keys_danger": "ОПАСНОСТЬ: Будут экспортированы ВСЕ приватные ключи из вашего кошелька! Любой, кто получит доступ к этому файлу, сможет украсть ваши средства. Храните его в безопасности и удалите после использования.",
|
|
||||||
"export_keys_include_t": "Включить T-адреса (прозрачные)",
|
|
||||||
"export_keys_include_z": "Включить Z-адреса (экранированные)",
|
|
||||||
"export_keys_options": "Параметры экспорта:",
|
|
||||||
"export_keys_success": "Ключи успешно экспортированы",
|
|
||||||
"export_keys_title": "Экспорт всех приватных ключей",
|
|
||||||
"export_private_key": "Экспорт приватного ключа",
|
|
||||||
"export_tx_count": "Экспортировать %zu транзакций в файл CSV.",
|
|
||||||
"export_tx_file_fail": "Не удалось создать файл CSV",
|
|
||||||
"export_tx_none": "Нет транзакций для экспорта",
|
|
||||||
"export_tx_success": "Транзакции успешно экспортированы",
|
|
||||||
"export_tx_title": "Экспорт транзакций в CSV",
|
|
||||||
"export_viewing_key": "Экспорт ключа просмотра",
|
|
||||||
"failed_create_shielded": "Не удалось создать экранированный адрес",
|
|
||||||
"failed_create_transparent": "Не удалось создать прозрачный адрес",
|
|
||||||
"fee": "Комиссия",
|
|
||||||
"fee_high": "Высокая",
|
|
||||||
"fee_label": "Комиссия:",
|
|
||||||
"fee_low": "Низкая",
|
|
||||||
"fee_normal": "Обычная",
|
|
||||||
"fetch_prices": "Получить цены",
|
|
||||||
"file": "Файл",
|
|
||||||
"file_save_location": "Файл будет сохранён в: ~/.config/ObsidianDragon/",
|
|
||||||
"font_scale": "Масштаб шрифта",
|
|
||||||
"from": "От",
|
|
||||||
"from_upper": "ОТ",
|
|
||||||
"full_details": "Полные детали",
|
|
||||||
"general": "Общие",
|
|
||||||
"go_to_receive": "Перейти к получению",
|
|
||||||
"height": "Высота",
|
|
||||||
"help": "Справка",
|
|
||||||
"hide": "Скрыть",
|
|
||||||
"history": "История",
|
|
||||||
"immature_type": "Незрелая",
|
|
||||||
"import": "Импорт",
|
|
||||||
"import_key_btn": "Импорт ключей",
|
|
||||||
"import_key_formats": "Поддерживаемые форматы ключей:",
|
|
||||||
"import_key_full_rescan": "(0 = полное сканирование)",
|
|
||||||
"import_key_label": "Приватный ключ(и):",
|
|
||||||
"import_key_no_valid": "В введённых данных не найдено действительных ключей",
|
|
||||||
"import_key_rescan": "Пересканировать блокчейн после импорта",
|
|
||||||
"import_key_start_height": "Начальная высота:",
|
|
||||||
"import_key_success": "Ключи успешно импортированы",
|
|
||||||
"import_key_t_format": "Приватные ключи WIF для T-адресов",
|
|
||||||
"import_key_title": "Импорт приватного ключа",
|
|
||||||
"import_key_tooltip": "Введите один или несколько приватных ключей, по одному на строку.\nПоддерживаются ключи z-адресов и t-адресов.\nСтроки, начинающиеся с #, считаются комментариями.",
|
|
||||||
"import_key_warning": "Предупреждение: Никогда не делитесь своими приватными ключами! Импорт ключей из ненадёжных источников может скомпрометировать ваш кошелёк.",
|
|
||||||
"import_key_z_format": "Ключи расходования z-адресов (secret-extended-key-...)",
|
|
||||||
"import_private_key": "Импорт приватного ключа...",
|
|
||||||
"invalid_address": "Неверный формат адреса",
|
|
||||||
"ip_address": "IP-адрес",
|
|
||||||
"keep": "Сохранить",
|
|
||||||
"keep_daemon": "Оставить daemon работающим",
|
|
||||||
"key_export_fetching": "Получение ключа из кошелька...",
|
|
||||||
"key_export_private_key": "Приватный ключ:",
|
|
||||||
"key_export_private_warning": "Держите этот ключ в ТАЙНЕ! Любой, кто владеет этим ключом, может потратить ваши средства. Никогда не делитесь им в интернете или с ненадёжными лицами.",
|
|
||||||
"key_export_reveal": "Показать ключ",
|
|
||||||
"key_export_viewing_key": "Ключ просмотра:",
|
|
||||||
"key_export_viewing_warning": "Этот ключ просмотра позволяет другим видеть входящие транзакции и баланс, но НЕ тратить ваши средства. Делитесь только с доверенными лицами.",
|
|
||||||
"label": "Метка:",
|
|
||||||
"language": "Язык",
|
|
||||||
"light": "Светлая",
|
|
||||||
"loading": "Загрузка...",
|
|
||||||
"loading_addresses": "Загрузка адресов...",
|
|
||||||
"local_hashrate": "Локальный хешрейт",
|
|
||||||
"low_spec_mode": "Режим экономии",
|
|
||||||
"market": "Рынок",
|
|
||||||
"market_12h": "12ч",
|
|
||||||
"market_18h": "18ч",
|
|
||||||
"market_24h": "24ч",
|
|
||||||
"market_24h_volume": "ОБЪЁМ 24Ч",
|
|
||||||
"market_6h": "6ч",
|
|
||||||
"market_attribution": "Данные о ценах с NonKYC",
|
|
||||||
"market_btc_price": "ЦЕНА BTC",
|
|
||||||
"market_cap": "Рыночная капитализация",
|
|
||||||
"market_no_history": "Нет истории цен",
|
|
||||||
"market_no_price": "Нет данных о ценах",
|
|
||||||
"market_now": "Сейчас",
|
|
||||||
"market_pct_shielded": "%.0f%% Экранировано",
|
|
||||||
"market_portfolio": "ПОРТФЕЛЬ",
|
|
||||||
"market_price_unavailable": "Данные о ценах недоступны",
|
|
||||||
"market_refresh_price": "Обновить данные о ценах",
|
|
||||||
"market_trade_on": "Торговать на %s",
|
|
||||||
"mature": "Зрелая",
|
|
||||||
"max": "Макс",
|
|
||||||
"memo": "Заметка (необязательно, зашифровано)",
|
|
||||||
"memo_label": "Заметка:",
|
|
||||||
"memo_optional": "ЗАМЕТКА (НЕОБЯЗАТЕЛЬНО)",
|
|
||||||
"memo_upper": "ЗАМЕТКА",
|
|
||||||
"memo_z_only": "Примечание: Заметки доступны только при отправке на экранированные (z) адреса",
|
|
||||||
"merge_description": "Объедините несколько UTXO в один экранированный адрес. Это может уменьшить размер кошелька и улучшить конфиденциальность.",
|
|
||||||
"merge_funds": "Объединить средства",
|
|
||||||
"merge_started": "Операция объединения начата",
|
|
||||||
"merge_title": "Объединить на адрес",
|
|
||||||
"mine_when_idle": "Майнить в простое",
|
|
||||||
"mined": "добыто",
|
|
||||||
"mined_filter": "Добытые",
|
|
||||||
"mined_type": "Добытая",
|
|
||||||
"mined_upper": "ДОБЫТО",
|
|
||||||
"miner_fee": "Комиссия майнера",
|
|
||||||
"mining": "Майнинг",
|
|
||||||
"mining_active": "Активен",
|
|
||||||
"mining_address_copied": "Адрес майнинга скопирован",
|
|
||||||
"mining_all_time": "За всё время",
|
|
||||||
"mining_already_saved": "URL пула уже сохранён",
|
|
||||||
"mining_block_copied": "Хэш блока скопирован",
|
|
||||||
"mining_chart_1m_ago": "1м назад",
|
|
||||||
"mining_chart_5m_ago": "5м назад",
|
|
||||||
"mining_chart_now": "Сейчас",
|
|
||||||
"mining_chart_start": "Старт",
|
|
||||||
"mining_click": "Нажмите",
|
|
||||||
"mining_click_copy_address": "Нажмите, чтобы скопировать адрес",
|
|
||||||
"mining_click_copy_block": "Нажмите, чтобы скопировать хэш блока",
|
|
||||||
"mining_click_copy_difficulty": "Нажмите, чтобы скопировать сложность",
|
|
||||||
"mining_connected": "Подключено",
|
|
||||||
"mining_connecting": "Подключение...",
|
|
||||||
"mining_control": "Управление майнингом",
|
|
||||||
"mining_difficulty_copied": "Сложность скопирована",
|
|
||||||
"mining_est_block": "Расч. блок",
|
|
||||||
"mining_est_daily": "Расч. за день",
|
|
||||||
"mining_filter_all": "Все",
|
|
||||||
"mining_filter_tip_all": "Показать все доходы",
|
|
||||||
"mining_filter_tip_pool": "Показать только доходы пула",
|
|
||||||
"mining_filter_tip_solo": "Показать только доходы соло",
|
|
||||||
"mining_idle_off_tooltip": "Включить майнинг в простое",
|
|
||||||
"mining_idle_on_tooltip": "Отключить майнинг в простое",
|
|
||||||
"mining_local_hashrate": "Локальный хешрейт",
|
|
||||||
"mining_mine": "Майнить",
|
|
||||||
"mining_mining_addr": "Адрес майн.",
|
|
||||||
"mining_network": "Сеть",
|
|
||||||
"mining_no_blocks_yet": "Блоки пока не найдены",
|
|
||||||
"mining_no_payouts_yet": "Выплат пула пока нет",
|
|
||||||
"mining_no_saved_addresses": "Нет сохранённых адресов",
|
|
||||||
"mining_no_saved_pools": "Нет сохранённых пулов",
|
|
||||||
"mining_off": "Майнинг ВЫКЛЮЧЕН",
|
|
||||||
"mining_on": "Майнинг ВКЛЮЧЁН",
|
|
||||||
"mining_open_in_explorer": "Открыть в обозревателе",
|
|
||||||
"mining_payout_address": "Адрес выплат",
|
|
||||||
"mining_payout_tooltip": "Адрес для получения вознаграждений за майнинг",
|
|
||||||
"mining_pool": "Пул",
|
|
||||||
"mining_pool_hashrate": "Хешрейт пула",
|
|
||||||
"mining_pool_url": "URL пула",
|
|
||||||
"mining_recent_blocks": "ПОСЛЕДНИЕ БЛОКИ",
|
|
||||||
"mining_recent_payouts": "ПОСЛЕДНИЕ ВЫПЛАТЫ ПУЛА",
|
|
||||||
"mining_remove": "Удалить",
|
|
||||||
"mining_reset_defaults": "Сбросить настройки",
|
|
||||||
"mining_save_payout_address": "Сохранить адрес выплат",
|
|
||||||
"mining_save_pool_url": "Сохранить URL пула",
|
|
||||||
"mining_saved_addresses": "Сохранённые адреса:",
|
|
||||||
"mining_saved_pools": "Сохранённые пулы:",
|
|
||||||
"mining_shares": "Шары",
|
|
||||||
"mining_show_chart": "График",
|
|
||||||
"mining_show_log": "Журнал",
|
|
||||||
"mining_solo": "Соло",
|
|
||||||
"mining_starting": "Запуск...",
|
|
||||||
"mining_starting_tooltip": "Майнер запускается...",
|
|
||||||
"mining_statistics": "Статистика майнинга",
|
|
||||||
"mining_stop": "Стоп",
|
|
||||||
"mining_stop_solo_for_pool": "Остановите соло-майнинг перед запуском пул-майнинга",
|
|
||||||
"mining_stop_solo_for_pool_settings": "Остановите соло-майнинг для изменения настроек пула",
|
|
||||||
"mining_stopping": "Остановка...",
|
|
||||||
"mining_stopping_tooltip": "Майнер останавливается...",
|
|
||||||
"mining_syncing_tooltip": "Блокчейн синхронизируется...",
|
|
||||||
"mining_threads": "Потоки майнинга",
|
|
||||||
"mining_to_save": "для сохранения",
|
|
||||||
"mining_today": "Сегодня",
|
|
||||||
"mining_uptime": "Время работы",
|
|
||||||
"mining_yesterday": "Вчера",
|
|
||||||
"network": "Сеть",
|
|
||||||
"network_fee": "СЕТЕВАЯ КОМИССИЯ",
|
|
||||||
"network_hashrate": "Хешрейт сети",
|
|
||||||
"new": "+ Новый",
|
|
||||||
"new_shielded_created": "Создан новый экранированный адрес",
|
|
||||||
"new_t_address": "Новый T-адрес",
|
|
||||||
"new_t_transparent": "Новый t-адрес (Прозрачный)",
|
|
||||||
"new_transparent_created": "Создан новый прозрачный адрес",
|
|
||||||
"new_z_address": "Новый Z-адрес",
|
|
||||||
"new_z_shielded": "Новый z-адрес (Экранированный)",
|
|
||||||
"no_addresses": "Адреса не найдены. Создайте один, используя кнопки выше.",
|
|
||||||
"no_addresses_available": "Нет доступных адресов",
|
|
||||||
"no_addresses_match": "Нет адресов, соответствующих фильтру",
|
|
||||||
"no_addresses_with_balance": "Нет адресов с балансом",
|
|
||||||
"no_matching": "Нет подходящих транзакций",
|
|
||||||
"no_recent_receives": "Нет недавних получений",
|
|
||||||
"no_recent_sends": "Нет недавних отправлений",
|
|
||||||
"no_transactions": "Транзакции не найдены",
|
|
||||||
"node": "УЗЕЛ",
|
|
||||||
"node_security": "УЗЕЛ И БЕЗОПАСНОСТЬ",
|
|
||||||
"noise": "Шум",
|
|
||||||
"not_connected": "Не подключено к daemon...",
|
|
||||||
"not_connected_to_daemon": "Не подключено к daemon",
|
|
||||||
"notes": "Заметки",
|
|
||||||
"notes_optional": "Заметки (необязательно):",
|
|
||||||
"output_filename": "Имя выходного файла:",
|
|
||||||
"overview": "Обзор",
|
|
||||||
"paste": "Вставить",
|
|
||||||
"paste_from_clipboard": "Вставить из буфера обмена",
|
|
||||||
"pay_from": "Оплатить с",
|
|
||||||
"payment_request": "ЗАПРОС НА ОПЛАТУ",
|
|
||||||
"payment_request_copied": "Запрос на оплату скопирован",
|
|
||||||
"payment_uri_copied": "URI платежа скопирован",
|
|
||||||
"peers": "Узлы",
|
|
||||||
"peers_avg_ping": "Средний пинг",
|
|
||||||
"peers_ban_24h": "Заблокировать узел на 24ч",
|
|
||||||
"peers_ban_score": "Очки блокировки: %d",
|
|
||||||
"peers_banned": "Заблокированные",
|
|
||||||
"peers_banned_count": "Заблокировано: %d",
|
|
||||||
"peers_best_block": "Лучший блок",
|
|
||||||
"peers_blockchain": "БЛОКЧЕЙН",
|
|
||||||
"peers_blocks": "Блоки",
|
|
||||||
"peers_blocks_left": "Осталось %d блоков",
|
|
||||||
"peers_clear_all_bans": "Снять все блокировки",
|
|
||||||
"peers_click_copy": "Нажмите, чтобы скопировать",
|
|
||||||
"peers_connected": "Подключено",
|
|
||||||
"peers_connected_count": "Подключено: %d",
|
|
||||||
"peers_copy_ip": "Копировать IP",
|
|
||||||
"peers_dir_in": "Вх.",
|
|
||||||
"peers_dir_out": "Исх.",
|
|
||||||
"peers_hash_copied": "Хэш скопирован",
|
|
||||||
"peers_hashrate": "Хешрейт",
|
|
||||||
"peers_in_out": "Вх./Исх.",
|
|
||||||
"peers_longest": "Длиннейшая",
|
|
||||||
"peers_longest_chain": "Длиннейшая цепь",
|
|
||||||
"peers_memory": "Память",
|
|
||||||
"peers_no_banned": "Нет заблокированных узлов",
|
|
||||||
"peers_no_connected": "Нет подключённых узлов",
|
|
||||||
"peers_no_tls": "Без TLS",
|
|
||||||
"peers_notarized": "Нотаризован",
|
|
||||||
"peers_p2p_port": "P2P-порт",
|
|
||||||
"peers_protocol": "Протокол",
|
|
||||||
"peers_received": "Получено",
|
|
||||||
"peers_refresh": "Обновить",
|
|
||||||
"peers_refresh_tooltip": "Обновить список узлов",
|
|
||||||
"peers_refreshing": "Обновление...",
|
|
||||||
"peers_sent": "Отправлено",
|
|
||||||
"peers_tt_id": "ID: %d",
|
|
||||||
"peers_tt_received": "Получено: %s",
|
|
||||||
"peers_tt_sent": "Отправлено: %s",
|
|
||||||
"peers_tt_services": "Сервисы: %s",
|
|
||||||
"peers_tt_start_height": "Начальная высота: %d",
|
|
||||||
"peers_tt_synced": "Синхронизировано В/Б: %d/%d",
|
|
||||||
"peers_tt_tls_cipher": "TLS: %s",
|
|
||||||
"peers_unban": "Разблокировать",
|
|
||||||
"peers_upper": "УЗЛЫ",
|
|
||||||
"peers_version": "Версия",
|
|
||||||
"pending": "Ожидание",
|
|
||||||
"ping": "Пинг",
|
|
||||||
"price_chart": "График цен",
|
|
||||||
"qr_code": "QR-код",
|
|
||||||
"qr_failed": "Не удалось сгенерировать QR-код",
|
|
||||||
"qr_title": "QR-код",
|
|
||||||
"qr_unavailable": "QR недоступен",
|
|
||||||
"receive": "Получить",
|
|
||||||
"received": "получено",
|
|
||||||
"received_filter": "Получено",
|
|
||||||
"received_label": "Получено",
|
|
||||||
"received_upper": "ПОЛУЧЕНО",
|
|
||||||
"receiving_addresses": "Ваши адреса для получения",
|
|
||||||
"recent_received": "НЕДАВНО ПОЛУЧЕНО",
|
|
||||||
"recent_sends": "НЕДАВНО ОТПРАВЛЕНО",
|
|
||||||
"recipient": "ПОЛУЧАТЕЛЬ",
|
|
||||||
"recv_type": "Получ.",
|
|
||||||
"refresh": "Обновить",
|
|
||||||
"refresh_now": "Обновить сейчас",
|
|
||||||
"report_bug": "Сообщить об ошибке",
|
|
||||||
"request_amount": "Сумма (необязательно):",
|
|
||||||
"request_copy_uri": "Копировать URI",
|
|
||||||
"request_description": "Создайте запрос на оплату, который другие могут отсканировать или скопировать. QR-код содержит ваш адрес и опциональную сумму/заметку.",
|
|
||||||
"request_label": "Метка (необязательно):",
|
|
||||||
"request_memo": "Заметка (необязательно):",
|
|
||||||
"request_payment": "Запрос оплаты",
|
|
||||||
"request_payment_uri": "URI платежа:",
|
|
||||||
"request_receive_address": "Адрес получения:",
|
|
||||||
"request_select_address": "Выбрать адрес...",
|
|
||||||
"request_shielded_addrs": "-- Экранированные адреса --",
|
|
||||||
"request_title": "Запрос оплаты",
|
|
||||||
"request_transparent_addrs": "-- Прозрачные адреса --",
|
|
||||||
"request_uri_copied": "URI платежа скопирован в буфер обмена",
|
|
||||||
"rescan": "Пересканировать",
|
|
||||||
"reset_to_defaults": "Сбросить настройки",
|
|
||||||
"review_send": "Проверить отправку",
|
|
||||||
"rpc_host": "RPC-хост",
|
|
||||||
"rpc_pass": "Пароль",
|
|
||||||
"rpc_port": "Порт",
|
|
||||||
"rpc_user": "Имя пользователя",
|
|
||||||
"save": "Сохранить",
|
|
||||||
"save_settings": "Сохранить настройки",
|
|
||||||
"save_z_transactions": "Сохранять Z-tx в списке транзакций",
|
|
||||||
"search_placeholder": "Поиск...",
|
|
||||||
"security": "БЕЗОПАСНОСТЬ",
|
|
||||||
"select_address": "Выбрать адрес...",
|
|
||||||
"select_receiving_address": "Выбрать адрес получения...",
|
|
||||||
"select_source_address": "Выбрать адрес-источник...",
|
|
||||||
"send": "Отправить",
|
|
||||||
"send_amount": "Сумма",
|
|
||||||
"send_amount_details": "ДЕТАЛИ СУММЫ",
|
|
||||||
"send_amount_upper": "СУММА",
|
|
||||||
"send_clear_fields": "Очистить все поля формы?",
|
|
||||||
"send_copy_error": "Копировать ошибку",
|
|
||||||
"send_dismiss": "Отклонить",
|
|
||||||
"send_error_copied": "Ошибка скопирована в буфер обмена",
|
|
||||||
"send_error_prefix": "Ошибка: %s",
|
|
||||||
"send_exceeds_available": "Превышает доступное (%.8f)",
|
|
||||||
"send_fee": "Комиссия",
|
|
||||||
"send_fee_high": "Высокая",
|
|
||||||
"send_fee_low": "Низкая",
|
|
||||||
"send_fee_normal": "Обычная",
|
|
||||||
"send_form_restored": "Форма восстановлена",
|
|
||||||
"send_from_this_address": "Отправить с этого адреса",
|
|
||||||
"send_go_to_receive": "Перейти к получению",
|
|
||||||
"send_keep": "Сохранить",
|
|
||||||
"send_network_fee": "СЕТЕВАЯ КОМИССИЯ",
|
|
||||||
"send_no_balance": "Нет баланса",
|
|
||||||
"send_no_recent": "Нет недавних отправлений",
|
|
||||||
"send_recent_sends": "НЕДАВНО ОТПРАВЛЕНО",
|
|
||||||
"send_recipient": "ПОЛУЧАТЕЛЬ",
|
|
||||||
"send_select_source": "Выбрать адрес-источник...",
|
|
||||||
"send_sending_from": "ОТПРАВКА С",
|
|
||||||
"send_submitting": "Отправка транзакции...",
|
|
||||||
"send_switch_to_receive": "Перейдите к получению, чтобы получить свой адрес и начать получать средства.",
|
|
||||||
"send_to": "Отправить на",
|
|
||||||
"send_tooltip_enter_amount": "Введите сумму для отправки",
|
|
||||||
"send_tooltip_exceeds_balance": "Сумма превышает доступный баланс",
|
|
||||||
"send_tooltip_in_progress": "Транзакция уже выполняется",
|
|
||||||
"send_tooltip_invalid_address": "Введите действительный адрес получателя",
|
|
||||||
"send_tooltip_not_connected": "Не подключено к daemon",
|
|
||||||
"send_tooltip_select_source": "Сначала выберите адрес-источник",
|
|
||||||
"send_tooltip_syncing": "Дождитесь синхронизации блокчейна",
|
|
||||||
"send_total": "Итого",
|
|
||||||
"send_transaction": "Отправить транзакцию",
|
|
||||||
"send_tx_failed": "Транзакция не удалась",
|
|
||||||
"send_tx_sent": "Транзакция отправлена!",
|
|
||||||
"send_tx_success": "Транзакция успешно отправлена!",
|
|
||||||
"send_txid_copied": "TxID скопирован в буфер обмена",
|
|
||||||
"send_txid_label": "TxID: %s",
|
|
||||||
"send_valid_shielded": "Действительный экранированный адрес",
|
|
||||||
"send_valid_transparent": "Действительный прозрачный адрес",
|
|
||||||
"send_wallet_empty": "Ваш кошелёк пуст",
|
|
||||||
"send_yes_clear": "Да, очистить",
|
|
||||||
"sending": "Отправка транзакции",
|
|
||||||
"sending_from": "ОТПРАВКА С",
|
|
||||||
"sent": "отправлено",
|
|
||||||
"sent_filter": "Отправлено",
|
|
||||||
"sent_type": "Отправлено",
|
|
||||||
"sent_upper": "ОТПРАВЛЕНО",
|
|
||||||
"settings": "Настройки",
|
|
||||||
"setup_wizard": "Мастер настройки",
|
|
||||||
"share": "Поделиться",
|
|
||||||
"shield_check_status": "Проверить статус",
|
|
||||||
"shield_completed": "Операция успешно завершена!",
|
|
||||||
"shield_description": "Экранируйте вознаграждения за майнинг, отправив coinbase-выходы с прозрачных адресов на экранированный адрес. Это улучшает конфиденциальность, скрывая ваш доход от майнинга.",
|
|
||||||
"shield_from_address": "С адреса:",
|
|
||||||
"shield_funds": "Экранировать средства",
|
|
||||||
"shield_in_progress": "Операция выполняется...",
|
|
||||||
"shield_max_utxos": "Макс. UTXO за операцию",
|
|
||||||
"shield_merge_done": "Экранирование/объединение завершено!",
|
|
||||||
"shield_select_z": "Выбрать z-адрес...",
|
|
||||||
"shield_started": "Операция экранирования начата",
|
|
||||||
"shield_title": "Экранировать вознаграждения coinbase",
|
|
||||||
"shield_to_address": "На адрес (экранированный):",
|
|
||||||
"shield_utxo_limit": "Лимит UTXO:",
|
|
||||||
"shield_wildcard_hint": "Используйте '*' для экранирования со всех прозрачных адресов",
|
|
||||||
"shielded": "Экранированный",
|
|
||||||
"shielded_to": "ЭКРАНИРОВАНО НА",
|
|
||||||
"shielded_type": "Экранированный",
|
|
||||||
"show": "Показать",
|
|
||||||
"show_qr_code": "Показать QR-код",
|
|
||||||
"showing_transactions": "Показано %d\xe2\x80\x93%d из %d транзакций (всего: %zu)",
|
|
||||||
"simple_background": "Простой фон",
|
|
||||||
"start_mining": "Начать майнинг",
|
|
||||||
"status": "Статус",
|
|
||||||
"stop_external": "Остановить внешний daemon",
|
|
||||||
"stop_mining": "Остановить майнинг",
|
|
||||||
"submitting_transaction": "Отправка транзакции...",
|
|
||||||
"success": "Успешно",
|
|
||||||
"summary": "Итоги",
|
|
||||||
"syncing": "Синхронизация...",
|
|
||||||
"t_addresses": "T-адреса",
|
|
||||||
"test_connection": "Тест",
|
|
||||||
"theme": "Тема",
|
|
||||||
"theme_effects": "Эффекты темы",
|
|
||||||
"time_days_ago": "%d дней назад",
|
|
||||||
"time_hours_ago": "%d часов назад",
|
|
||||||
"time_minutes_ago": "%d минут назад",
|
|
||||||
"time_seconds_ago": "%d секунд назад",
|
|
||||||
"to": "Кому",
|
|
||||||
"to_upper": "КОМУ",
|
|
||||||
"tools": "ИНСТРУМЕНТЫ",
|
|
||||||
"total": "Итого",
|
|
||||||
"transaction_id": "ID ТРАНЗАКЦИИ",
|
|
||||||
"transaction_sent": "Транзакция успешно отправлена",
|
|
||||||
"transaction_sent_msg": "Транзакция отправлена!",
|
|
||||||
"transaction_url": "URL транзакции",
|
|
||||||
"transactions": "Транзакции",
|
|
||||||
"transactions_upper": "ТРАНЗАКЦИИ",
|
|
||||||
"transparent": "Прозрачный",
|
|
||||||
"tx_confirmations": "%d подтверждений",
|
|
||||||
"tx_details_title": "Детали транзакции",
|
|
||||||
"tx_from_address": "Адрес отправителя:",
|
|
||||||
"tx_id_label": "ID транзакции:",
|
|
||||||
"tx_immature": "НЕЗРЕЛАЯ",
|
|
||||||
"tx_mined": "ДОБЫТА",
|
|
||||||
"tx_received": "ПОЛУЧЕНО",
|
|
||||||
"tx_sent": "ОТПРАВЛЕНО",
|
|
||||||
"tx_to_address": "Адрес получателя:",
|
|
||||||
"tx_view_explorer": "Посмотреть в обозревателе",
|
|
||||||
"txs_count": "%d тр.",
|
|
||||||
"type": "Тип",
|
|
||||||
"ui_opacity": "Прозрачность интерфейса",
|
|
||||||
"unban": "Разблокировать",
|
|
||||||
"unconfirmed": "Не подтверждено",
|
|
||||||
"undo_clear": "Отменить очистку",
|
|
||||||
"unknown": "Неизвестно",
|
|
||||||
"use_embedded_daemon": "Использовать встроенный dragonxd",
|
|
||||||
"use_tor": "Использовать Tor",
|
|
||||||
"validate_btn": "Проверить",
|
|
||||||
"validate_description": "Введите адрес DragonX, чтобы проверить его действительность и принадлежность к этому кошельку.",
|
|
||||||
"validate_invalid": "НЕДЕЙСТВИТЕЛЕН",
|
|
||||||
"validate_is_mine": "Этот кошелёк владеет этим адресом",
|
|
||||||
"validate_not_mine": "Не принадлежит этому кошельку",
|
|
||||||
"validate_ownership": "Принадлежность:",
|
|
||||||
"validate_results": "Результаты:",
|
|
||||||
"validate_shielded_type": "Экранированный (z-адрес)",
|
|
||||||
"validate_status": "Статус:",
|
|
||||||
"validate_title": "Проверить адрес",
|
|
||||||
"validate_transparent_type": "Прозрачный (t-адрес)",
|
|
||||||
"validate_type": "Тип:",
|
|
||||||
"validate_valid": "ДЕЙСТВИТЕЛЕН",
|
|
||||||
"validating": "Проверка...",
|
|
||||||
"verbose_logging": "Подробное логирование",
|
|
||||||
"version": "Версия",
|
|
||||||
"view": "Просмотр",
|
|
||||||
"view_details": "Подробнее",
|
|
||||||
"view_on_explorer": "Посмотреть в обозревателе",
|
|
||||||
"waiting_for_daemon": "Ожидание подключения к daemon...",
|
|
||||||
"wallet": "КОШЕЛЁК",
|
|
||||||
"wallet_empty": "Ваш кошелёк пуст",
|
|
||||||
"wallet_empty_hint": "Перейдите к получению, чтобы получить свой адрес и начать получать средства.",
|
|
||||||
"warning": "Предупреждение",
|
|
||||||
"warning_upper": "ПРЕДУПРЕЖДЕНИЕ!",
|
|
||||||
"website": "Веб-сайт",
|
|
||||||
"window_opacity": "Прозрачность окна",
|
|
||||||
"yes_clear": "Да, очистить",
|
|
||||||
"your_addresses": "Ваши адреса",
|
|
||||||
"z_addresses": "Z-адреса",
|
|
||||||
}
|
|
||||||
|
|
||||||
out = os.path.join(os.path.dirname(__file__), "..", "res", "lang", "ru.json")
|
|
||||||
with open(out, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(translations, f, indent=4, ensure_ascii=False, sort_keys=True)
|
|
||||||
print(f"Wrote {len(translations)} Russian translations to {os.path.abspath(out)}")
|
|
||||||
@@ -1,646 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Generate Chinese Simplified (zh) translations for ObsidianDragon wallet."""
|
|
||||||
import json, os
|
|
||||||
|
|
||||||
translations = {
|
|
||||||
"24h_change": "24小时变化",
|
|
||||||
"24h_volume": "24小时交易量",
|
|
||||||
"about": "关于",
|
|
||||||
"about_block_explorer": "区块浏览器",
|
|
||||||
"about_block_height": "区块高度:",
|
|
||||||
"about_build_date": "构建日期:",
|
|
||||||
"about_build_type": "构建类型:",
|
|
||||||
"about_chain": "链:",
|
|
||||||
"about_connections": "连接数:",
|
|
||||||
"about_credits": "致谢",
|
|
||||||
"about_daemon": "守护进程:",
|
|
||||||
"about_debug": "调试",
|
|
||||||
"about_dragonx": "关于 ObsidianDragon",
|
|
||||||
"about_edition": "ImGui 版本",
|
|
||||||
"about_github": "GitHub",
|
|
||||||
"about_imgui": "ImGui:",
|
|
||||||
"about_license": "许可证",
|
|
||||||
"about_license_text": "本软件根据 GNU 通用公共许可证 v3 (GPLv3) 发布。您可以根据许可证条款自由使用、修改和分发本软件。",
|
|
||||||
"about_peers_count": "%zu 个节点",
|
|
||||||
"about_release": "发布版",
|
|
||||||
"about_title": "关于 ObsidianDragon",
|
|
||||||
"about_version": "版本:",
|
|
||||||
"about_website": "网站",
|
|
||||||
"acrylic": "亚克力",
|
|
||||||
"add": "添加",
|
|
||||||
"address": "地址",
|
|
||||||
"address_book_add": "添加地址",
|
|
||||||
"address_book_add_new": "添加新地址",
|
|
||||||
"address_book_added": "地址已添加到通讯录",
|
|
||||||
"address_book_count": "已保存 %zu 个地址",
|
|
||||||
"address_book_deleted": "条目已删除",
|
|
||||||
"address_book_edit": "编辑地址",
|
|
||||||
"address_book_empty": "没有保存的地址。点击'添加新地址'创建一个。",
|
|
||||||
"address_book_exists": "地址已存在于通讯录中",
|
|
||||||
"address_book_title": "地址簿",
|
|
||||||
"address_book_update_failed": "更新失败——地址可能重复",
|
|
||||||
"address_book_updated": "地址已更新",
|
|
||||||
"address_copied": "地址已复制到剪贴板",
|
|
||||||
"address_details": "地址详情",
|
|
||||||
"address_label": "地址:",
|
|
||||||
"address_upper": "地址",
|
|
||||||
"address_url": "地址 URL",
|
|
||||||
"addresses_appear_here": "连接后,您的接收地址将显示在此处。",
|
|
||||||
"advanced": "高级",
|
|
||||||
"all_filter": "全部",
|
|
||||||
"allow_custom_fees": "允许自定义手续费",
|
|
||||||
"amount": "金额",
|
|
||||||
"amount_details": "金额详情",
|
|
||||||
"amount_exceeds_balance": "金额超过余额",
|
|
||||||
"amount_label": "金额:",
|
|
||||||
"appearance": "外观",
|
|
||||||
"auto_shield": "自动屏蔽挖矿",
|
|
||||||
"available": "可用",
|
|
||||||
"backup_backing_up": "正在备份...",
|
|
||||||
"backup_create": "创建备份",
|
|
||||||
"backup_created": "钱包备份已创建",
|
|
||||||
"backup_data": "备份与数据",
|
|
||||||
"backup_description": "创建 wallet.dat 文件的备份。此文件包含您所有的私钥和交易历史。请将备份存放在安全的地方。",
|
|
||||||
"backup_destination": "备份目标:",
|
|
||||||
"backup_tip_external": "将备份存储在外部驱动器或云存储中",
|
|
||||||
"backup_tip_multiple": "在不同位置创建多个备份",
|
|
||||||
"backup_tip_test": "定期测试从备份恢复",
|
|
||||||
"backup_tips": "提示:",
|
|
||||||
"backup_title": "备份钱包",
|
|
||||||
"backup_wallet": "备份钱包...",
|
|
||||||
"backup_wallet_not_found": "警告:在预期位置未找到 wallet.dat",
|
|
||||||
"balance": "余额",
|
|
||||||
"balance_layout": "余额布局",
|
|
||||||
"ban": "封禁",
|
|
||||||
"banned_peers": "已封禁节点",
|
|
||||||
"block": "区块",
|
|
||||||
"block_bits": "比特:",
|
|
||||||
"block_click_next": "点击查看下一个区块",
|
|
||||||
"block_click_prev": "点击查看上一个区块",
|
|
||||||
"block_explorer": "区块浏览器",
|
|
||||||
"block_get_info": "获取区块信息",
|
|
||||||
"block_hash": "区块哈希:",
|
|
||||||
"block_height": "区块高度:",
|
|
||||||
"block_info_title": "区块信息",
|
|
||||||
"block_merkle_root": "默克尔根:",
|
|
||||||
"block_nav_next": "下一个 >>",
|
|
||||||
"block_nav_prev": "<< 上一个",
|
|
||||||
"block_next": "下一个区块:",
|
|
||||||
"block_previous": "上一个区块:",
|
|
||||||
"block_size": "大小:",
|
|
||||||
"block_timestamp": "时间戳:",
|
|
||||||
"block_transactions": "交易:",
|
|
||||||
"blockchain_syncing": "区块链同步中 (%.1f%%)... 余额可能不准确。",
|
|
||||||
"cancel": "取消",
|
|
||||||
"characters": "字符",
|
|
||||||
"clear": "清除",
|
|
||||||
"clear_all_bans": "解除所有封禁",
|
|
||||||
"clear_form_confirm": "清除所有表单字段?",
|
|
||||||
"clear_request": "清除请求",
|
|
||||||
"click_copy_address": "点击复制地址",
|
|
||||||
"click_copy_uri": "点击复制 URI",
|
|
||||||
"close": "关闭",
|
|
||||||
"conf_count": "%d 确认",
|
|
||||||
"confirm_and_send": "确认并发送",
|
|
||||||
"confirm_send": "确认发送",
|
|
||||||
"confirm_transaction": "确认交易",
|
|
||||||
"confirmations": "确认数",
|
|
||||||
"confirmations_display": "%d 次确认 | %s",
|
|
||||||
"confirmed": "已确认",
|
|
||||||
"connected": "已连接",
|
|
||||||
"connected_peers": "已连接节点",
|
|
||||||
"connecting": "连接中...",
|
|
||||||
"console": "控制台",
|
|
||||||
"console_auto_scroll": "自动滚动",
|
|
||||||
"console_available_commands": "可用命令:",
|
|
||||||
"console_capturing_output": "正在捕获守护进程输出...",
|
|
||||||
"console_clear": "清除",
|
|
||||||
"console_clear_console": "清除控制台",
|
|
||||||
"console_cleared": "控制台已清除",
|
|
||||||
"console_click_commands": "点击上方命令以插入",
|
|
||||||
"console_click_insert": "点击插入",
|
|
||||||
"console_click_insert_params": "点击插入(含参数)",
|
|
||||||
"console_close": "关闭",
|
|
||||||
"console_commands": "命令",
|
|
||||||
"console_common_rpc": "常用 RPC 命令:",
|
|
||||||
"console_completions": "补全:",
|
|
||||||
"console_connected": "已连接到守护进程",
|
|
||||||
"console_copy_all": "全部复制",
|
|
||||||
"console_copy_selected": "复制",
|
|
||||||
"console_daemon": "守护进程",
|
|
||||||
"console_daemon_error": "守护进程错误!",
|
|
||||||
"console_daemon_started": "守护进程已启动",
|
|
||||||
"console_daemon_stopped": "守护进程已停止",
|
|
||||||
"console_disconnected": "已断开与守护进程的连接",
|
|
||||||
"console_errors": "错误",
|
|
||||||
"console_filter_hint": "过滤输出...",
|
|
||||||
"console_help_clear": " clear - 清除控制台",
|
|
||||||
"console_help_getbalance": " getbalance - 显示透明余额",
|
|
||||||
"console_help_getblockcount": " getblockcount - 显示当前区块高度",
|
|
||||||
"console_help_getinfo": " getinfo - 显示节点信息",
|
|
||||||
"console_help_getmininginfo": " getmininginfo - 显示挖矿状态",
|
|
||||||
"console_help_getpeerinfo": " getpeerinfo - 显示已连接节点",
|
|
||||||
"console_help_gettotalbalance": " gettotalbalance - 显示总余额",
|
|
||||||
"console_help_help": " help - 显示此帮助信息",
|
|
||||||
"console_help_setgenerate": " setgenerate - 控制挖矿",
|
|
||||||
"console_help_stop": " stop - 停止守护进程",
|
|
||||||
"console_line_count": "%zu 行",
|
|
||||||
"console_new_lines": "%d 新行",
|
|
||||||
"console_no_daemon": "无守护进程",
|
|
||||||
"console_not_connected": "错误:未连接到守护进程",
|
|
||||||
"console_rpc_reference": "RPC 命令参考",
|
|
||||||
"console_scanline": "控制台扫描线",
|
|
||||||
"console_search_commands": "搜索命令...",
|
|
||||||
"console_select_all": "全选",
|
|
||||||
"console_show_daemon_output": "显示守护进程输出",
|
|
||||||
"console_show_errors_only": "仅显示错误",
|
|
||||||
"console_show_rpc_ref": "显示 RPC 命令参考",
|
|
||||||
"console_showing_lines": "显示 %zu / %zu 行",
|
|
||||||
"console_starting_node": "正在启动节点...",
|
|
||||||
"console_status_error": "错误",
|
|
||||||
"console_status_running": "运行中",
|
|
||||||
"console_status_starting": "启动中",
|
|
||||||
"console_status_stopped": "已停止",
|
|
||||||
"console_status_stopping": "停止中",
|
|
||||||
"console_status_unknown": "未知",
|
|
||||||
"console_tab_completion": "Tab 补全",
|
|
||||||
"console_type_help": "输入 'help' 查看可用命令",
|
|
||||||
"console_welcome": "欢迎使用 ObsidianDragon 控制台",
|
|
||||||
"console_zoom_in": "放大",
|
|
||||||
"console_zoom_out": "缩小",
|
|
||||||
"copy": "复制",
|
|
||||||
"copy_address": "复制完整地址",
|
|
||||||
"copy_error": "复制错误",
|
|
||||||
"copy_to_clipboard": "复制到剪贴板",
|
|
||||||
"copy_txid": "复制交易ID",
|
|
||||||
"copy_uri": "复制 URI",
|
|
||||||
"current_price": "当前价格",
|
|
||||||
"custom_fees": "自定义手续费",
|
|
||||||
"dark": "深色",
|
|
||||||
"date": "日期",
|
|
||||||
"date_label": "日期:",
|
|
||||||
"delete": "删除",
|
|
||||||
"difficulty": "难度",
|
|
||||||
"disconnected": "已断开",
|
|
||||||
"dismiss": "关闭",
|
|
||||||
"display": "显示",
|
|
||||||
"dragonx_green": "DragonX(绿色)",
|
|
||||||
"edit": "编辑",
|
|
||||||
"error": "错误",
|
|
||||||
"est_time_to_block": "预计出块时间",
|
|
||||||
"exit": "退出",
|
|
||||||
"explorer": "浏览器",
|
|
||||||
"export": "导出",
|
|
||||||
"export_csv": "导出 CSV",
|
|
||||||
"export_keys_btn": "导出密钥",
|
|
||||||
"export_keys_danger": "危险:这将导出您钱包中的所有私钥!任何获得此文件的人都可以窃取您的资金。请安全保管并在使用后删除。",
|
|
||||||
"export_keys_include_t": "包含 T 地址(透明)",
|
|
||||||
"export_keys_include_z": "包含 Z 地址(屏蔽)",
|
|
||||||
"export_keys_options": "导出选项:",
|
|
||||||
"export_keys_success": "密钥导出成功",
|
|
||||||
"export_keys_title": "导出所有私钥",
|
|
||||||
"export_private_key": "导出私钥",
|
|
||||||
"export_tx_count": "导出 %zu 笔交易到 CSV 文件。",
|
|
||||||
"export_tx_file_fail": "无法创建 CSV 文件",
|
|
||||||
"export_tx_none": "没有交易可导出",
|
|
||||||
"export_tx_success": "交易导出成功",
|
|
||||||
"export_tx_title": "导出交易到 CSV",
|
|
||||||
"export_viewing_key": "导出查看密钥",
|
|
||||||
"failed_create_shielded": "无法创建屏蔽地址",
|
|
||||||
"failed_create_transparent": "无法创建透明地址",
|
|
||||||
"fee": "手续费",
|
|
||||||
"fee_high": "高",
|
|
||||||
"fee_label": "手续费:",
|
|
||||||
"fee_low": "低",
|
|
||||||
"fee_normal": "普通",
|
|
||||||
"fetch_prices": "获取价格",
|
|
||||||
"file": "文件",
|
|
||||||
"file_save_location": "文件将保存至:~/.config/ObsidianDragon/",
|
|
||||||
"font_scale": "字体大小",
|
|
||||||
"from": "从",
|
|
||||||
"from_upper": "从",
|
|
||||||
"full_details": "完整详情",
|
|
||||||
"general": "常规",
|
|
||||||
"go_to_receive": "前往接收",
|
|
||||||
"height": "高度",
|
|
||||||
"help": "帮助",
|
|
||||||
"hide": "隐藏",
|
|
||||||
"history": "历史",
|
|
||||||
"immature_type": "未成熟",
|
|
||||||
"import": "导入",
|
|
||||||
"import_key_btn": "导入密钥",
|
|
||||||
"import_key_formats": "支持的密钥格式:",
|
|
||||||
"import_key_full_rescan": "(0 = 完整重扫)",
|
|
||||||
"import_key_label": "私钥:",
|
|
||||||
"import_key_no_valid": "输入中未找到有效密钥",
|
|
||||||
"import_key_rescan": "导入后重新扫描区块链",
|
|
||||||
"import_key_start_height": "起始高度:",
|
|
||||||
"import_key_success": "密钥导入成功",
|
|
||||||
"import_key_t_format": "T 地址 WIF 私钥",
|
|
||||||
"import_key_title": "导入私钥",
|
|
||||||
"import_key_tooltip": "输入一个或多个私钥,每行一个。\n支持 z 地址和 t 地址密钥。\n以 # 开头的行视为注释。",
|
|
||||||
"import_key_warning": "警告:切勿分享您的私钥!从不可信来源导入密钥可能会危及您的钱包安全。",
|
|
||||||
"import_key_z_format": "Z 地址花费密钥 (secret-extended-key-...)",
|
|
||||||
"import_private_key": "导入私钥...",
|
|
||||||
"invalid_address": "无效的地址格式",
|
|
||||||
"ip_address": "IP 地址",
|
|
||||||
"keep": "保留",
|
|
||||||
"keep_daemon": "保持守护进程运行",
|
|
||||||
"key_export_fetching": "正在从钱包获取密钥...",
|
|
||||||
"key_export_private_key": "私钥:",
|
|
||||||
"key_export_private_warning": "请保密此密钥!任何拥有此密钥的人都可以花费您的资金。切勿在网上或与不可信的人分享。",
|
|
||||||
"key_export_reveal": "显示密钥",
|
|
||||||
"key_export_viewing_key": "查看密钥:",
|
|
||||||
"key_export_viewing_warning": "此查看密钥允许他人查看您的入账交易和余额,但不能花费您的资金。仅与信任的人分享。",
|
|
||||||
"label": "标签:",
|
|
||||||
"language": "语言",
|
|
||||||
"light": "浅色",
|
|
||||||
"loading": "加载中...",
|
|
||||||
"loading_addresses": "正在加载地址...",
|
|
||||||
"local_hashrate": "本地算力",
|
|
||||||
"low_spec_mode": "低配模式",
|
|
||||||
"market": "市场",
|
|
||||||
"market_12h": "12小时",
|
|
||||||
"market_18h": "18小时",
|
|
||||||
"market_24h": "24小时",
|
|
||||||
"market_24h_volume": "24小时交易量",
|
|
||||||
"market_6h": "6小时",
|
|
||||||
"market_attribution": "价格数据来自 NonKYC",
|
|
||||||
"market_btc_price": "BTC 价格",
|
|
||||||
"market_cap": "市值",
|
|
||||||
"market_no_history": "无价格历史",
|
|
||||||
"market_no_price": "无价格数据",
|
|
||||||
"market_now": "现在",
|
|
||||||
"market_pct_shielded": "%.0f%% 屏蔽",
|
|
||||||
"market_portfolio": "投资组合",
|
|
||||||
"market_price_unavailable": "价格数据不可用",
|
|
||||||
"market_refresh_price": "刷新价格数据",
|
|
||||||
"market_trade_on": "在 %s 交易",
|
|
||||||
"mature": "已成熟",
|
|
||||||
"max": "最大",
|
|
||||||
"memo": "备注(可选,加密)",
|
|
||||||
"memo_label": "备注:",
|
|
||||||
"memo_optional": "备注(可选)",
|
|
||||||
"memo_upper": "备注",
|
|
||||||
"memo_z_only": "注意:备注仅在发送到屏蔽 (z) 地址时可用",
|
|
||||||
"merge_description": "将多个 UTXO 合并到一个屏蔽地址。这可以帮助减小钱包大小并提高隐私性。",
|
|
||||||
"merge_funds": "合并资金",
|
|
||||||
"merge_started": "合并操作已开始",
|
|
||||||
"merge_title": "合并到地址",
|
|
||||||
"mine_when_idle": "空闲时挖矿",
|
|
||||||
"mined": "已挖得",
|
|
||||||
"mined_filter": "已挖得",
|
|
||||||
"mined_type": "已挖得",
|
|
||||||
"mined_upper": "已挖得",
|
|
||||||
"miner_fee": "矿工费",
|
|
||||||
"mining": "挖矿",
|
|
||||||
"mining_active": "活跃",
|
|
||||||
"mining_address_copied": "挖矿地址已复制",
|
|
||||||
"mining_all_time": "所有时间",
|
|
||||||
"mining_already_saved": "矿池 URL 已保存",
|
|
||||||
"mining_block_copied": "区块哈希已复制",
|
|
||||||
"mining_chart_1m_ago": "1分钟前",
|
|
||||||
"mining_chart_5m_ago": "5分钟前",
|
|
||||||
"mining_chart_now": "现在",
|
|
||||||
"mining_chart_start": "开始",
|
|
||||||
"mining_click": "点击",
|
|
||||||
"mining_click_copy_address": "点击复制地址",
|
|
||||||
"mining_click_copy_block": "点击复制区块哈希",
|
|
||||||
"mining_click_copy_difficulty": "点击复制难度",
|
|
||||||
"mining_connected": "已连接",
|
|
||||||
"mining_connecting": "连接中...",
|
|
||||||
"mining_control": "挖矿控制",
|
|
||||||
"mining_difficulty_copied": "难度已复制",
|
|
||||||
"mining_est_block": "预计区块",
|
|
||||||
"mining_est_daily": "预计日收益",
|
|
||||||
"mining_filter_all": "全部",
|
|
||||||
"mining_filter_tip_all": "显示所有收益",
|
|
||||||
"mining_filter_tip_pool": "仅显示矿池收益",
|
|
||||||
"mining_filter_tip_solo": "仅显示单人收益",
|
|
||||||
"mining_idle_off_tooltip": "启用空闲挖矿",
|
|
||||||
"mining_idle_on_tooltip": "禁用空闲挖矿",
|
|
||||||
"mining_local_hashrate": "本地算力",
|
|
||||||
"mining_mine": "挖矿",
|
|
||||||
"mining_mining_addr": "挖矿地址",
|
|
||||||
"mining_network": "网络",
|
|
||||||
"mining_no_blocks_yet": "尚未找到区块",
|
|
||||||
"mining_no_payouts_yet": "尚无矿池支付",
|
|
||||||
"mining_no_saved_addresses": "没有保存的地址",
|
|
||||||
"mining_no_saved_pools": "没有保存的矿池",
|
|
||||||
"mining_off": "挖矿已关闭",
|
|
||||||
"mining_on": "挖矿已开启",
|
|
||||||
"mining_open_in_explorer": "在浏览器中打开",
|
|
||||||
"mining_payout_address": "支付地址",
|
|
||||||
"mining_payout_tooltip": "接收挖矿奖励的地址",
|
|
||||||
"mining_pool": "矿池",
|
|
||||||
"mining_pool_hashrate": "矿池算力",
|
|
||||||
"mining_pool_url": "矿池 URL",
|
|
||||||
"mining_recent_blocks": "最近区块",
|
|
||||||
"mining_recent_payouts": "最近矿池支付",
|
|
||||||
"mining_remove": "移除",
|
|
||||||
"mining_reset_defaults": "重置默认值",
|
|
||||||
"mining_save_payout_address": "保存支付地址",
|
|
||||||
"mining_save_pool_url": "保存矿池 URL",
|
|
||||||
"mining_saved_addresses": "已保存地址:",
|
|
||||||
"mining_saved_pools": "已保存矿池:",
|
|
||||||
"mining_shares": "份额",
|
|
||||||
"mining_show_chart": "图表",
|
|
||||||
"mining_show_log": "日志",
|
|
||||||
"mining_solo": "单人",
|
|
||||||
"mining_starting": "启动中...",
|
|
||||||
"mining_starting_tooltip": "矿工正在启动...",
|
|
||||||
"mining_statistics": "挖矿统计",
|
|
||||||
"mining_stop": "停止",
|
|
||||||
"mining_stop_solo_for_pool": "启动矿池挖矿前请先停止单人挖矿",
|
|
||||||
"mining_stop_solo_for_pool_settings": "请停止单人挖矿以更改矿池设置",
|
|
||||||
"mining_stopping": "停止中...",
|
|
||||||
"mining_stopping_tooltip": "矿工正在停止...",
|
|
||||||
"mining_syncing_tooltip": "区块链同步中...",
|
|
||||||
"mining_threads": "挖矿线程",
|
|
||||||
"mining_to_save": "保存",
|
|
||||||
"mining_today": "今天",
|
|
||||||
"mining_uptime": "运行时间",
|
|
||||||
"mining_yesterday": "昨天",
|
|
||||||
"network": "网络",
|
|
||||||
"network_fee": "网络手续费",
|
|
||||||
"network_hashrate": "全网算力",
|
|
||||||
"new": "+ 新建",
|
|
||||||
"new_shielded_created": "新屏蔽地址已创建",
|
|
||||||
"new_t_address": "新 T 地址",
|
|
||||||
"new_t_transparent": "新 t 地址(透明)",
|
|
||||||
"new_transparent_created": "新透明地址已创建",
|
|
||||||
"new_z_address": "新 Z 地址",
|
|
||||||
"new_z_shielded": "新 z 地址(屏蔽)",
|
|
||||||
"no_addresses": "未找到地址。请使用上方按钮创建一个。",
|
|
||||||
"no_addresses_available": "无可用地址",
|
|
||||||
"no_addresses_match": "没有匹配过滤器的地址",
|
|
||||||
"no_addresses_with_balance": "没有有余额的地址",
|
|
||||||
"no_matching": "没有匹配的交易",
|
|
||||||
"no_recent_receives": "没有最近的接收",
|
|
||||||
"no_recent_sends": "没有最近的发送",
|
|
||||||
"no_transactions": "未找到交易",
|
|
||||||
"node": "节点",
|
|
||||||
"node_security": "节点与安全",
|
|
||||||
"noise": "噪点",
|
|
||||||
"not_connected": "未连接到守护进程...",
|
|
||||||
"not_connected_to_daemon": "未连接到守护进程",
|
|
||||||
"notes": "备注",
|
|
||||||
"notes_optional": "备注(可选):",
|
|
||||||
"output_filename": "输出文件名:",
|
|
||||||
"overview": "概览",
|
|
||||||
"paste": "粘贴",
|
|
||||||
"paste_from_clipboard": "从剪贴板粘贴",
|
|
||||||
"pay_from": "付款来源",
|
|
||||||
"payment_request": "付款请求",
|
|
||||||
"payment_request_copied": "付款请求已复制",
|
|
||||||
"payment_uri_copied": "付款 URI 已复制",
|
|
||||||
"peers": "节点",
|
|
||||||
"peers_avg_ping": "平均延迟",
|
|
||||||
"peers_ban_24h": "封禁节点 24 小时",
|
|
||||||
"peers_ban_score": "封禁评分:%d",
|
|
||||||
"peers_banned": "已封禁",
|
|
||||||
"peers_banned_count": "已封禁:%d",
|
|
||||||
"peers_best_block": "最佳区块",
|
|
||||||
"peers_blockchain": "区块链",
|
|
||||||
"peers_blocks": "区块",
|
|
||||||
"peers_blocks_left": "剩余 %d 个区块",
|
|
||||||
"peers_clear_all_bans": "解除所有封禁",
|
|
||||||
"peers_click_copy": "点击复制",
|
|
||||||
"peers_connected": "已连接",
|
|
||||||
"peers_connected_count": "已连接:%d",
|
|
||||||
"peers_copy_ip": "复制 IP",
|
|
||||||
"peers_dir_in": "入",
|
|
||||||
"peers_dir_out": "出",
|
|
||||||
"peers_hash_copied": "哈希已复制",
|
|
||||||
"peers_hashrate": "算力",
|
|
||||||
"peers_in_out": "入/出",
|
|
||||||
"peers_longest": "最长",
|
|
||||||
"peers_longest_chain": "最长链",
|
|
||||||
"peers_memory": "内存",
|
|
||||||
"peers_no_banned": "无已封禁节点",
|
|
||||||
"peers_no_connected": "无已连接节点",
|
|
||||||
"peers_no_tls": "无 TLS",
|
|
||||||
"peers_notarized": "已公证",
|
|
||||||
"peers_p2p_port": "P2P 端口",
|
|
||||||
"peers_protocol": "协议",
|
|
||||||
"peers_received": "已接收",
|
|
||||||
"peers_refresh": "刷新",
|
|
||||||
"peers_refresh_tooltip": "刷新节点列表",
|
|
||||||
"peers_refreshing": "刷新中...",
|
|
||||||
"peers_sent": "已发送",
|
|
||||||
"peers_tt_id": "ID:%d",
|
|
||||||
"peers_tt_received": "已接收:%s",
|
|
||||||
"peers_tt_sent": "已发送:%s",
|
|
||||||
"peers_tt_services": "服务:%s",
|
|
||||||
"peers_tt_start_height": "起始高度:%d",
|
|
||||||
"peers_tt_synced": "已同步 H/B:%d/%d",
|
|
||||||
"peers_tt_tls_cipher": "TLS:%s",
|
|
||||||
"peers_unban": "解除封禁",
|
|
||||||
"peers_upper": "节点",
|
|
||||||
"peers_version": "版本",
|
|
||||||
"pending": "待处理",
|
|
||||||
"ping": "延迟",
|
|
||||||
"price_chart": "价格图表",
|
|
||||||
"qr_code": "二维码",
|
|
||||||
"qr_failed": "无法生成二维码",
|
|
||||||
"qr_title": "二维码",
|
|
||||||
"qr_unavailable": "二维码不可用",
|
|
||||||
"receive": "接收",
|
|
||||||
"received": "已接收",
|
|
||||||
"received_filter": "已接收",
|
|
||||||
"received_label": "已接收",
|
|
||||||
"received_upper": "已接收",
|
|
||||||
"receiving_addresses": "您的接收地址",
|
|
||||||
"recent_received": "最近接收",
|
|
||||||
"recent_sends": "最近发送",
|
|
||||||
"recipient": "收款方",
|
|
||||||
"recv_type": "接收",
|
|
||||||
"refresh": "刷新",
|
|
||||||
"refresh_now": "立即刷新",
|
|
||||||
"report_bug": "报告错误",
|
|
||||||
"request_amount": "金额(可选):",
|
|
||||||
"request_copy_uri": "复制 URI",
|
|
||||||
"request_description": "生成一个付款请求,他人可以扫描或复制。二维码包含您的地址和可选的金额/备注。",
|
|
||||||
"request_label": "标签(可选):",
|
|
||||||
"request_memo": "备注(可选):",
|
|
||||||
"request_payment": "请求付款",
|
|
||||||
"request_payment_uri": "付款 URI:",
|
|
||||||
"request_receive_address": "接收地址:",
|
|
||||||
"request_select_address": "选择地址...",
|
|
||||||
"request_shielded_addrs": "-- 屏蔽地址 --",
|
|
||||||
"request_title": "请求付款",
|
|
||||||
"request_transparent_addrs": "-- 透明地址 --",
|
|
||||||
"request_uri_copied": "付款 URI 已复制到剪贴板",
|
|
||||||
"rescan": "重新扫描",
|
|
||||||
"reset_to_defaults": "重置为默认值",
|
|
||||||
"review_send": "审核发送",
|
|
||||||
"rpc_host": "RPC 主机",
|
|
||||||
"rpc_pass": "密码",
|
|
||||||
"rpc_port": "端口",
|
|
||||||
"rpc_user": "用户名",
|
|
||||||
"save": "保存",
|
|
||||||
"save_settings": "保存设置",
|
|
||||||
"save_z_transactions": "将 Z 交易保存到列表",
|
|
||||||
"search_placeholder": "搜索...",
|
|
||||||
"security": "安全",
|
|
||||||
"select_address": "选择地址...",
|
|
||||||
"select_receiving_address": "选择接收地址...",
|
|
||||||
"select_source_address": "选择来源地址...",
|
|
||||||
"send": "发送",
|
|
||||||
"send_amount": "金额",
|
|
||||||
"send_amount_details": "金额详情",
|
|
||||||
"send_amount_upper": "金额",
|
|
||||||
"send_clear_fields": "清除所有表单字段?",
|
|
||||||
"send_copy_error": "复制错误",
|
|
||||||
"send_dismiss": "关闭",
|
|
||||||
"send_error_copied": "错误已复制到剪贴板",
|
|
||||||
"send_error_prefix": "错误:%s",
|
|
||||||
"send_exceeds_available": "超过可用额 (%.8f)",
|
|
||||||
"send_fee": "手续费",
|
|
||||||
"send_fee_high": "高",
|
|
||||||
"send_fee_low": "低",
|
|
||||||
"send_fee_normal": "普通",
|
|
||||||
"send_form_restored": "表单已恢复",
|
|
||||||
"send_from_this_address": "从此地址发送",
|
|
||||||
"send_go_to_receive": "前往接收",
|
|
||||||
"send_keep": "保留",
|
|
||||||
"send_network_fee": "网络手续费",
|
|
||||||
"send_no_balance": "无余额",
|
|
||||||
"send_no_recent": "没有最近的发送",
|
|
||||||
"send_recent_sends": "最近发送",
|
|
||||||
"send_recipient": "收款方",
|
|
||||||
"send_select_source": "选择来源地址...",
|
|
||||||
"send_sending_from": "发送来源",
|
|
||||||
"send_submitting": "正在提交交易...",
|
|
||||||
"send_switch_to_receive": "切换到接收页面获取您的地址并开始接收资金。",
|
|
||||||
"send_to": "发送至",
|
|
||||||
"send_tooltip_enter_amount": "请输入发送金额",
|
|
||||||
"send_tooltip_exceeds_balance": "金额超过可用余额",
|
|
||||||
"send_tooltip_in_progress": "交易正在进行中",
|
|
||||||
"send_tooltip_invalid_address": "请输入有效的收款地址",
|
|
||||||
"send_tooltip_not_connected": "未连接到守护进程",
|
|
||||||
"send_tooltip_select_source": "请先选择来源地址",
|
|
||||||
"send_tooltip_syncing": "请等待区块链同步",
|
|
||||||
"send_total": "合计",
|
|
||||||
"send_transaction": "发送交易",
|
|
||||||
"send_tx_failed": "交易失败",
|
|
||||||
"send_tx_sent": "交易已发送!",
|
|
||||||
"send_tx_success": "交易发送成功!",
|
|
||||||
"send_txid_copied": "交易ID 已复制到剪贴板",
|
|
||||||
"send_txid_label": "TxID:%s",
|
|
||||||
"send_valid_shielded": "有效的屏蔽地址",
|
|
||||||
"send_valid_transparent": "有效的透明地址",
|
|
||||||
"send_wallet_empty": "您的钱包是空的",
|
|
||||||
"send_yes_clear": "是,清除",
|
|
||||||
"sending": "正在发送交易",
|
|
||||||
"sending_from": "发送来源",
|
|
||||||
"sent": "已发送",
|
|
||||||
"sent_filter": "已发送",
|
|
||||||
"sent_type": "已发送",
|
|
||||||
"sent_upper": "已发送",
|
|
||||||
"settings": "设置",
|
|
||||||
"setup_wizard": "设置向导",
|
|
||||||
"share": "分享",
|
|
||||||
"shield_check_status": "检查状态",
|
|
||||||
"shield_completed": "操作成功完成!",
|
|
||||||
"shield_description": "通过将透明地址的 coinbase 输出发送到屏蔽地址来屏蔽您的挖矿奖励。这可以隐藏您的挖矿收入,提高隐私性。",
|
|
||||||
"shield_from_address": "从地址:",
|
|
||||||
"shield_funds": "屏蔽资金",
|
|
||||||
"shield_in_progress": "操作进行中...",
|
|
||||||
"shield_max_utxos": "每次操作最大 UTXO 数",
|
|
||||||
"shield_merge_done": "屏蔽/合并完成!",
|
|
||||||
"shield_select_z": "选择 z 地址...",
|
|
||||||
"shield_started": "屏蔽操作已开始",
|
|
||||||
"shield_title": "屏蔽 Coinbase 奖励",
|
|
||||||
"shield_to_address": "至地址(屏蔽):",
|
|
||||||
"shield_utxo_limit": "UTXO 限制:",
|
|
||||||
"shield_wildcard_hint": "使用 '*' 从所有透明地址屏蔽",
|
|
||||||
"shielded": "屏蔽",
|
|
||||||
"shielded_to": "屏蔽至",
|
|
||||||
"shielded_type": "屏蔽",
|
|
||||||
"show": "显示",
|
|
||||||
"show_qr_code": "显示二维码",
|
|
||||||
"showing_transactions": "显示第 %d\xe2\x80\x93%d 笔,共 %d 笔交易(总计:%zu)",
|
|
||||||
"simple_background": "简单背景",
|
|
||||||
"start_mining": "开始挖矿",
|
|
||||||
"status": "状态",
|
|
||||||
"stop_external": "停止外部守护进程",
|
|
||||||
"stop_mining": "停止挖矿",
|
|
||||||
"submitting_transaction": "正在提交交易...",
|
|
||||||
"success": "成功",
|
|
||||||
"summary": "摘要",
|
|
||||||
"syncing": "同步中...",
|
|
||||||
"t_addresses": "T 地址",
|
|
||||||
"test_connection": "测试",
|
|
||||||
"theme": "主题",
|
|
||||||
"theme_effects": "主题效果",
|
|
||||||
"time_days_ago": "%d 天前",
|
|
||||||
"time_hours_ago": "%d 小时前",
|
|
||||||
"time_minutes_ago": "%d 分钟前",
|
|
||||||
"time_seconds_ago": "%d 秒前",
|
|
||||||
"to": "至",
|
|
||||||
"to_upper": "至",
|
|
||||||
"tools": "工具",
|
|
||||||
"total": "合计",
|
|
||||||
"transaction_id": "交易 ID",
|
|
||||||
"transaction_sent": "交易发送成功",
|
|
||||||
"transaction_sent_msg": "交易已发送!",
|
|
||||||
"transaction_url": "交易 URL",
|
|
||||||
"transactions": "交易",
|
|
||||||
"transactions_upper": "交易",
|
|
||||||
"transparent": "透明",
|
|
||||||
"tx_confirmations": "%d 次确认",
|
|
||||||
"tx_details_title": "交易详情",
|
|
||||||
"tx_from_address": "发送地址:",
|
|
||||||
"tx_id_label": "交易 ID:",
|
|
||||||
"tx_immature": "未成熟",
|
|
||||||
"tx_mined": "已挖得",
|
|
||||||
"tx_received": "已接收",
|
|
||||||
"tx_sent": "已发送",
|
|
||||||
"tx_to_address": "接收地址:",
|
|
||||||
"tx_view_explorer": "在浏览器中查看",
|
|
||||||
"txs_count": "%d 笔交易",
|
|
||||||
"type": "类型",
|
|
||||||
"ui_opacity": "界面透明度",
|
|
||||||
"unban": "解除封禁",
|
|
||||||
"unconfirmed": "未确认",
|
|
||||||
"undo_clear": "撤销清除",
|
|
||||||
"unknown": "未知",
|
|
||||||
"use_embedded_daemon": "使用内置 dragonxd",
|
|
||||||
"use_tor": "使用 Tor",
|
|
||||||
"validate_btn": "验证",
|
|
||||||
"validate_description": "输入一个 DragonX 地址来检查它是否有效以及是否属于此钱包。",
|
|
||||||
"validate_invalid": "无效",
|
|
||||||
"validate_is_mine": "此钱包拥有该地址",
|
|
||||||
"validate_not_mine": "不属于此钱包",
|
|
||||||
"validate_ownership": "所有权:",
|
|
||||||
"validate_results": "结果:",
|
|
||||||
"validate_shielded_type": "屏蔽(z 地址)",
|
|
||||||
"validate_status": "状态:",
|
|
||||||
"validate_title": "验证地址",
|
|
||||||
"validate_transparent_type": "透明(t 地址)",
|
|
||||||
"validate_type": "类型:",
|
|
||||||
"validate_valid": "有效",
|
|
||||||
"validating": "验证中...",
|
|
||||||
"verbose_logging": "详细日志",
|
|
||||||
"version": "版本",
|
|
||||||
"view": "查看",
|
|
||||||
"view_details": "查看详情",
|
|
||||||
"view_on_explorer": "在浏览器中查看",
|
|
||||||
"waiting_for_daemon": "等待守护进程连接...",
|
|
||||||
"wallet": "钱包",
|
|
||||||
"wallet_empty": "您的钱包是空的",
|
|
||||||
"wallet_empty_hint": "切换到接收页面获取您的地址并开始接收资金。",
|
|
||||||
"warning": "警告",
|
|
||||||
"warning_upper": "警告!",
|
|
||||||
"website": "网站",
|
|
||||||
"window_opacity": "窗口透明度",
|
|
||||||
"yes_clear": "是,清除",
|
|
||||||
"your_addresses": "您的地址",
|
|
||||||
"z_addresses": "Z 地址",
|
|
||||||
}
|
|
||||||
|
|
||||||
out = os.path.join(os.path.dirname(__file__), "..", "res", "lang", "zh.json")
|
|
||||||
with open(out, "w", encoding="utf-8") as f:
|
|
||||||
json.dump(translations, f, indent=4, ensure_ascii=False, sort_keys=True)
|
|
||||||
print(f"Wrote {len(translations)} Chinese translations to {os.path.abspath(out)}")
|
|
||||||
@@ -7,7 +7,7 @@ set -e
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
BUILD_DIR="${SCRIPT_DIR}/build/linux"
|
||||||
VERSION="1.1.1"
|
VERSION="1.2.0"
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
|
|||||||
67
scripts/sign-daemon-release.sh
Executable file
67
scripts/sign-daemon-release.sh
Executable file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Sign dragonx full-node release archives for the wallet's in-app daemon updater (ed25519).
|
||||||
|
#
|
||||||
|
# The wallet verifies a detached ed25519 signature over the EXACT archive bytes against a public
|
||||||
|
# key pinned in src/util/daemon_updater.h (kDaemonSignaturePublicKeyBase64). Verification is
|
||||||
|
# MANDATORY (kDaemonRequireSignature = true): an in-app update is refused unless a valid signature
|
||||||
|
# is published. For each archive <name>.zip this produces <name>.zip.sig holding the base64 of the
|
||||||
|
# raw 64-byte ed25519 signature — upload that .sig next to the .zip as a release asset.
|
||||||
|
#
|
||||||
|
# Uses OpenSSL (>= 1.1.1) only — no Python/PyNaCl needed. OpenSSL's ed25519 is PureEdDSA (RFC 8032),
|
||||||
|
# the same primitive libsodium's crypto_sign_verify_detached checks, so signatures are compatible
|
||||||
|
# (the same flow the wallet's unit tests verify for the miner updater).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/sign-daemon-release.sh keygen [out-prefix] # -> <prefix>.ed25519.{key,pub.b64}
|
||||||
|
# scripts/sign-daemon-release.sh pubkey <secret.key> # print the base64 public key to pin
|
||||||
|
# scripts/sign-daemon-release.sh sign <secret.key> <file>...# -> <file>.sig per file
|
||||||
|
#
|
||||||
|
# Keep the secret key (.ed25519.key) OFFLINE. Paste the base64 public key into
|
||||||
|
# kDaemonSignaturePublicKeyBase64 in src/util/daemon_updater.h.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
die() { echo "error: $*" >&2; exit 1; }
|
||||||
|
command -v openssl >/dev/null || die "openssl not found (need >= 1.1.1 with ed25519)"
|
||||||
|
|
||||||
|
# Raw 32-byte ed25519 public key (base64) from a private key file. The DER SubjectPublicKeyInfo for
|
||||||
|
# ed25519 is a fixed 12-byte prefix + the 32-byte key, so the trailing 32 bytes are the raw key.
|
||||||
|
pubkey_b64() { openssl pkey -in "$1" -pubout -outform DER | tail -c 32 | openssl base64 -A; }
|
||||||
|
|
||||||
|
cmd="${1:-}"; shift || true
|
||||||
|
case "$cmd" in
|
||||||
|
keygen)
|
||||||
|
prefix="${1:-dragonx-daemon}"
|
||||||
|
[ -e "$prefix.ed25519.key" ] && die "$prefix.ed25519.key already exists — refusing to overwrite"
|
||||||
|
openssl genpkey -algorithm ed25519 -out "$prefix.ed25519.key"
|
||||||
|
chmod 600 "$prefix.ed25519.key"
|
||||||
|
pub="$(pubkey_b64 "$prefix.ed25519.key")"
|
||||||
|
printf '%s\n' "$pub" > "$prefix.ed25519.pub.b64"
|
||||||
|
echo "secret key : $prefix.ed25519.key (KEEP OFFLINE, mode 600)"
|
||||||
|
echo "public key : $prefix.ed25519.pub.b64"
|
||||||
|
echo
|
||||||
|
echo "Pin this in src/util/daemon_updater.h (kDaemonSignaturePublicKeyBase64):"
|
||||||
|
echo " $pub"
|
||||||
|
;;
|
||||||
|
pubkey)
|
||||||
|
[ $# -ge 1 ] || die "usage: pubkey <secret.key>"
|
||||||
|
pubkey_b64 "$1"
|
||||||
|
;;
|
||||||
|
sign)
|
||||||
|
[ $# -ge 2 ] || die "usage: sign <secret.key> <file>..."
|
||||||
|
key="$1"; shift
|
||||||
|
[ -f "$key" ] || die "no such key: $key"
|
||||||
|
for f in "$@"; do
|
||||||
|
[ -f "$f" ] || die "no such file: $f"
|
||||||
|
raw="$(mktemp)"
|
||||||
|
openssl pkeyutl -sign -inkey "$key" -rawin -in "$f" -out "$raw"
|
||||||
|
openssl base64 -A -in "$raw" > "$f.sig"
|
||||||
|
printf '\n' >> "$f.sig"
|
||||||
|
rm -f "$raw"
|
||||||
|
echo "signed: $f -> $f.sig"
|
||||||
|
done
|
||||||
|
echo "Upload each .sig as a release asset next to its archive."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "usage: $0 {keygen [prefix] | pubkey <secret.key> | sign <secret.key> <file>...}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
66
scripts/sign-xmrig-release.sh
Executable file
66
scripts/sign-xmrig-release.sh
Executable file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Sign DRG-XMRig release archives for the wallet's in-app updater (opt-in ed25519 signatures).
|
||||||
|
#
|
||||||
|
# The wallet verifies a detached ed25519 signature over the EXACT archive bytes against a public
|
||||||
|
# key pinned in src/util/xmrig_updater.h (kXmrigSignaturePublicKeyBase64). For each archive
|
||||||
|
# <name>.zip this produces <name>.zip.sig holding the base64 of the raw 64-byte ed25519 signature —
|
||||||
|
# upload that .sig next to the .zip as a release asset.
|
||||||
|
#
|
||||||
|
# Uses OpenSSL (>= 1.1.1) only — no Python/PyNaCl needed. OpenSSL's ed25519 is PureEdDSA (RFC 8032),
|
||||||
|
# the same primitive libsodium's crypto_sign_verify_detached checks, so signatures are compatible
|
||||||
|
# (verified by the wallet's unit tests + an interop check).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/sign-xmrig-release.sh keygen [out-prefix] # -> <prefix>.ed25519.{key,pub.b64}
|
||||||
|
# scripts/sign-xmrig-release.sh pubkey <secret.key> # print the base64 public key to pin
|
||||||
|
# scripts/sign-xmrig-release.sh sign <secret.key> <file>...# -> <file>.sig per file
|
||||||
|
#
|
||||||
|
# Keep the secret key (.ed25519.key) OFFLINE. Paste the base64 public key into
|
||||||
|
# kXmrigSignaturePublicKeyBase64 in src/util/xmrig_updater.h.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
die() { echo "error: $*" >&2; exit 1; }
|
||||||
|
command -v openssl >/dev/null || die "openssl not found (need >= 1.1.1 with ed25519)"
|
||||||
|
|
||||||
|
# Raw 32-byte ed25519 public key (base64) from a private key file. The DER SubjectPublicKeyInfo for
|
||||||
|
# ed25519 is a fixed 12-byte prefix + the 32-byte key, so the trailing 32 bytes are the raw key.
|
||||||
|
pubkey_b64() { openssl pkey -in "$1" -pubout -outform DER | tail -c 32 | openssl base64 -A; }
|
||||||
|
|
||||||
|
cmd="${1:-}"; shift || true
|
||||||
|
case "$cmd" in
|
||||||
|
keygen)
|
||||||
|
prefix="${1:-drg-xmrig}"
|
||||||
|
[ -e "$prefix.ed25519.key" ] && die "$prefix.ed25519.key already exists — refusing to overwrite"
|
||||||
|
openssl genpkey -algorithm ed25519 -out "$prefix.ed25519.key"
|
||||||
|
chmod 600 "$prefix.ed25519.key"
|
||||||
|
pub="$(pubkey_b64 "$prefix.ed25519.key")"
|
||||||
|
printf '%s\n' "$pub" > "$prefix.ed25519.pub.b64"
|
||||||
|
echo "secret key : $prefix.ed25519.key (KEEP OFFLINE, mode 600)"
|
||||||
|
echo "public key : $prefix.ed25519.pub.b64"
|
||||||
|
echo
|
||||||
|
echo "Pin this in src/util/xmrig_updater.h (kXmrigSignaturePublicKeyBase64):"
|
||||||
|
echo " $pub"
|
||||||
|
;;
|
||||||
|
pubkey)
|
||||||
|
[ $# -ge 1 ] || die "usage: pubkey <secret.key>"
|
||||||
|
pubkey_b64 "$1"
|
||||||
|
;;
|
||||||
|
sign)
|
||||||
|
[ $# -ge 2 ] || die "usage: sign <secret.key> <file>..."
|
||||||
|
key="$1"; shift
|
||||||
|
[ -f "$key" ] || die "no such key: $key"
|
||||||
|
for f in "$@"; do
|
||||||
|
[ -f "$f" ] || die "no such file: $f"
|
||||||
|
raw="$(mktemp)"
|
||||||
|
openssl pkeyutl -sign -inkey "$key" -rawin -in "$f" -out "$raw"
|
||||||
|
openssl base64 -A -in "$raw" > "$f.sig"
|
||||||
|
printf '\n' >> "$f.sig"
|
||||||
|
rm -f "$raw"
|
||||||
|
echo "signed: $f -> $f.sig"
|
||||||
|
done
|
||||||
|
echo "Upload each .sig as a release asset next to its archive."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "usage: $0 {keygen [prefix] | pubkey <secret.key> | sign <secret.key> <file>...}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
73
setup.sh
73
setup.sh
@@ -120,20 +120,20 @@ pkgs_core_debian="build-essential cmake git pkg-config
|
|||||||
libgl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev
|
libgl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev
|
||||||
libxinerama-dev libxi-dev libxkbcommon-dev libwayland-dev
|
libxinerama-dev libxi-dev libxkbcommon-dev libwayland-dev
|
||||||
libsodium-dev libcurl4-openssl-dev
|
libsodium-dev libcurl4-openssl-dev
|
||||||
autoconf automake libtool wget"
|
autoconf automake libtool wget python3 xxd"
|
||||||
|
|
||||||
pkgs_core_fedora="gcc gcc-c++ cmake git pkg-config
|
pkgs_core_fedora="gcc gcc-c++ cmake git pkg-config
|
||||||
mesa-libGL-devel libX11-devel libXcursor-devel libXrandr-devel
|
mesa-libGL-devel libX11-devel libXcursor-devel libXrandr-devel
|
||||||
libXinerama-devel libXi-devel libxkbcommon-devel wayland-devel
|
libXinerama-devel libXi-devel libxkbcommon-devel wayland-devel
|
||||||
libsodium-devel libcurl-devel
|
libsodium-devel libcurl-devel
|
||||||
autoconf automake libtool wget"
|
autoconf automake libtool wget python3 vim-common"
|
||||||
|
|
||||||
pkgs_core_arch="base-devel cmake git pkg-config
|
pkgs_core_arch="base-devel cmake git pkg-config
|
||||||
mesa libx11 libxcursor libxrandr libxinerama libxi
|
mesa libx11 libxcursor libxrandr libxinerama libxi
|
||||||
libxkbcommon wayland libsodium curl
|
libxkbcommon wayland libsodium curl
|
||||||
autoconf automake libtool wget"
|
autoconf automake libtool wget python xxd"
|
||||||
|
|
||||||
pkgs_core_macos="cmake"
|
pkgs_core_macos="cmake python xxd"
|
||||||
|
|
||||||
# Windows cross-compile (from Linux)
|
# Windows cross-compile (from Linux)
|
||||||
pkgs_win_debian="mingw-w64 zip"
|
pkgs_win_debian="mingw-w64 zip"
|
||||||
@@ -245,7 +245,7 @@ if [[ -z "$core_pkgs" ]]; then
|
|||||||
else
|
else
|
||||||
# Check if key tools are already present
|
# Check if key tools are already present
|
||||||
NEED_CORE=false
|
NEED_CORE=false
|
||||||
has_cmd cmake && has_cmd g++ && has_cmd pkg-config || NEED_CORE=true
|
has_cmd cmake && has_cmd g++ && has_cmd pkg-config && has_cmd python3 && has_cmd xxd || NEED_CORE=true
|
||||||
|
|
||||||
if $NEED_CORE; then
|
if $NEED_CORE; then
|
||||||
install_pkgs "$core_pkgs" "core build"
|
install_pkgs "$core_pkgs" "core build"
|
||||||
@@ -258,6 +258,8 @@ check_tool cmake "cmake"
|
|||||||
check_tool g++ "g++ (C++ compiler)"
|
check_tool g++ "g++ (C++ compiler)"
|
||||||
check_tool git "git"
|
check_tool git "git"
|
||||||
check_tool make "make"
|
check_tool make "make"
|
||||||
|
check_tool python3 "python3 (theme expansion)"
|
||||||
|
check_tool xxd "xxd (embedded language headers)"
|
||||||
|
|
||||||
# ── 2. libsodium ────────────────────────────────────────────────────────────
|
# ── 2. libsodium ────────────────────────────────────────────────────────────
|
||||||
header "libsodium"
|
header "libsodium"
|
||||||
@@ -440,6 +442,53 @@ copy_daemon_data() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Stale-daemon guard ───────────────────────────────────────────────────────
|
||||||
|
# A prebuilt daemon binary is only rebuilt on its platform's flag (--win/--mac),
|
||||||
|
# and build.sh merely BUNDLES whatever binary already exists — so a daemon left
|
||||||
|
# over from an old source revision silently ships in the wallet (e.g. the Network
|
||||||
|
# tab once reported v1.0.1 while the source was v1.0.2). These helpers compare the
|
||||||
|
# version baked into a prebuilt binary against the dragonx source and flag drift.
|
||||||
|
STALE_DAEMON=0
|
||||||
|
|
||||||
|
# MAJOR.MINOR.REVISION from the checked-out dragonx source (empty if unavailable).
|
||||||
|
dragonx_source_version() {
|
||||||
|
local hdr="$DRAGONX_SRC/src/clientversion.h"
|
||||||
|
[[ -f "$hdr" ]] || return 1
|
||||||
|
local maj min rev
|
||||||
|
maj=$(awk '/#define[ \t]+CLIENT_VERSION_MAJOR/{print $3; exit}' "$hdr")
|
||||||
|
min=$(awk '/#define[ \t]+CLIENT_VERSION_MINOR/{print $3; exit}' "$hdr")
|
||||||
|
rev=$(awk '/#define[ \t]+CLIENT_VERSION_REVISION/{print $3; exit}' "$hdr")
|
||||||
|
[[ -n "$maj" && -n "$min" && -n "$rev" ]] || return 1
|
||||||
|
printf '%s.%s.%s' "$maj" "$min" "$rev"
|
||||||
|
}
|
||||||
|
|
||||||
|
# vX.Y.Z baked into a built daemon binary (the daemon embeds "vX.Y.Z-<githash>").
|
||||||
|
# Uses grep -a so no `strings`/binutils dependency is required.
|
||||||
|
dragonx_binary_version() {
|
||||||
|
local bin="$1"
|
||||||
|
[[ -f "$bin" ]] || return 1
|
||||||
|
LC_ALL=C grep -aoE 'v[0-9]+\.[0-9]+\.[0-9]+-[0-9a-f]{6,}' "$bin" 2>/dev/null \
|
||||||
|
| head -1 | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Compare a prebuilt daemon against the source; warn (and set STALE_DAEMON) on drift.
|
||||||
|
# $1 = label, $2 = binary path, $3 = rebuild flag(s) (e.g. "--win", "" for Linux)
|
||||||
|
daemon_version_guard() {
|
||||||
|
local label="$1" bin="$2" rebuild_hint="$3"
|
||||||
|
[[ -f "$bin" ]] || return 0
|
||||||
|
local src bv
|
||||||
|
src=$(dragonx_source_version) || return 0 # no source checked out → can't compare
|
||||||
|
bv=$(dragonx_binary_version "$bin")
|
||||||
|
[[ -n "$bv" ]] || return 0 # couldn't read the binary's version
|
||||||
|
if [[ "$bv" == "$src" ]]; then
|
||||||
|
ok " $label daemon is v$bv (matches dragonx source)"
|
||||||
|
else
|
||||||
|
warn " $label daemon is v$bv but dragonx source is v$src — STALE"
|
||||||
|
warn " rebuild so the wallet ships the current daemon: ./setup.sh${rebuild_hint:+ $rebuild_hint}"
|
||||||
|
STALE_DAEMON=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ── Linux daemon ─────────────────────────────────────────────────────────────
|
# ── Linux daemon ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# Skip Linux daemon build if only cross-compile targets were requested
|
# Skip Linux daemon build if only cross-compile targets were requested
|
||||||
@@ -459,11 +508,13 @@ fi
|
|||||||
if $CHECK_ONLY; then
|
if $CHECK_ONLY; then
|
||||||
if [[ -f "$DRAGONXD_LINUX/dragonxd" ]] || [[ -f "$DRAGONXD_LINUX/hushd" ]]; then
|
if [[ -f "$DRAGONXD_LINUX/dragonxd" ]] || [[ -f "$DRAGONXD_LINUX/hushd" ]]; then
|
||||||
ok "dragonxd daemon (Linux) present"
|
ok "dragonxd daemon (Linux) present"
|
||||||
|
daemon_version_guard "Linux" "$DRAGONXD_LINUX/dragonxd" ""
|
||||||
else
|
else
|
||||||
miss "dragonxd daemon (Linux) not built"
|
miss "dragonxd daemon (Linux) not built"
|
||||||
fi
|
fi
|
||||||
elif $SKIP_LINUX_DAEMON; then
|
elif $SKIP_LINUX_DAEMON; then
|
||||||
skip "dragonxd (Linux) — skipped, binaries already present (cross-compile only)"
|
skip "dragonxd (Linux) — skipped, binaries already present (cross-compile only)"
|
||||||
|
daemon_version_guard "Linux" "$DRAGONXD_LINUX/dragonxd" ""
|
||||||
else
|
else
|
||||||
clone_dragonx_if_needed
|
clone_dragonx_if_needed
|
||||||
|
|
||||||
@@ -498,9 +549,11 @@ fi
|
|||||||
|
|
||||||
if ! $SETUP_WIN; then
|
if ! $SETUP_WIN; then
|
||||||
skip "dragonxd (Windows) — use --win to cross-compile"
|
skip "dragonxd (Windows) — use --win to cross-compile"
|
||||||
|
daemon_version_guard "Windows" "$DRAGONXD_WIN/dragonxd.exe" "--win"
|
||||||
elif $CHECK_ONLY; then
|
elif $CHECK_ONLY; then
|
||||||
if [[ -f "$DRAGONXD_WIN/dragonxd.exe" ]] || [[ -f "$DRAGONXD_WIN/hushd.exe" ]]; then
|
if [[ -f "$DRAGONXD_WIN/dragonxd.exe" ]] || [[ -f "$DRAGONXD_WIN/hushd.exe" ]]; then
|
||||||
ok "dragonxd daemon (Windows) present"
|
ok "dragonxd daemon (Windows) present"
|
||||||
|
daemon_version_guard "Windows" "$DRAGONXD_WIN/dragonxd.exe" "--win"
|
||||||
else
|
else
|
||||||
miss "dragonxd daemon (Windows) not built"
|
miss "dragonxd daemon (Windows) not built"
|
||||||
fi
|
fi
|
||||||
@@ -556,9 +609,11 @@ fi
|
|||||||
|
|
||||||
if ! $SETUP_MAC; then
|
if ! $SETUP_MAC; then
|
||||||
skip "dragonxd (macOS) — use --mac to cross-compile"
|
skip "dragonxd (macOS) — use --mac to cross-compile"
|
||||||
|
daemon_version_guard "macOS" "$DRAGONXD_MAC/dragonxd" "--mac"
|
||||||
elif $CHECK_ONLY; then
|
elif $CHECK_ONLY; then
|
||||||
if [[ -f "$DRAGONXD_MAC/dragonxd" ]] || [[ -f "$DRAGONXD_MAC/hushd" ]]; then
|
if [[ -f "$DRAGONXD_MAC/dragonxd" ]] || [[ -f "$DRAGONXD_MAC/hushd" ]]; then
|
||||||
ok "dragonxd daemon (macOS) present"
|
ok "dragonxd daemon (macOS) present"
|
||||||
|
daemon_version_guard "macOS" "$DRAGONXD_MAC/dragonxd" "--mac"
|
||||||
else
|
else
|
||||||
miss "dragonxd daemon (macOS) not built"
|
miss "dragonxd daemon (macOS) not built"
|
||||||
fi
|
fi
|
||||||
@@ -621,6 +676,14 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Prominent reminder if any prebuilt daemon drifted from the source — these are bundled verbatim
|
||||||
|
# by build.sh, so a stale binary ships in the wallet (and shows an old version in the Network tab).
|
||||||
|
if [[ "$STALE_DAEMON" -eq 1 ]]; then
|
||||||
|
warn "One or more prebuilt daemons are OLDER than the dragonx source (see above)."
|
||||||
|
warn "build.sh bundles them as-is, so rebuild the stale platform(s) before releasing:"
|
||||||
|
warn " Linux: ./setup.sh · Windows: ./setup.sh --win · macOS: ./setup.sh --mac"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── 7. xmrig-hac (mining binary) ────────────────────────────────────────────
|
# ── 7. xmrig-hac (mining binary) ────────────────────────────────────────────
|
||||||
header "xmrig-hac Mining Binary"
|
header "xmrig-hac Mining Binary"
|
||||||
|
|
||||||
|
|||||||
2682
src/app.cpp
2682
src/app.cpp
File diff suppressed because it is too large
Load Diff
544
src/app.h
544
src/app.h
@@ -9,11 +9,23 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <nlohmann/json_fwd.hpp>
|
||||||
|
#include "data/transaction_history_cache.h"
|
||||||
|
#include "data/address_book.h"
|
||||||
#include "data/wallet_state.h"
|
#include "data/wallet_state.h"
|
||||||
#include "rpc/connection.h"
|
#include "rpc/connection.h"
|
||||||
|
#include "services/network_refresh_service.h"
|
||||||
|
#include "services/wallet_security_controller.h"
|
||||||
|
#include "services/wallet_security_workflow.h"
|
||||||
|
#include "util/async_task_manager.h"
|
||||||
|
#include "util/pool_stats_service.h"
|
||||||
|
#include "wallet/wallet_capabilities.h"
|
||||||
|
#include "chat/chat_service.h"
|
||||||
|
#include "chat/chat_database.h"
|
||||||
#include "ui/sidebar.h"
|
#include "ui/sidebar.h"
|
||||||
#include "ui/windows/console_tab.h"
|
#include "ui/windows/console_tab.h"
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
@@ -25,8 +37,9 @@ namespace dragonx {
|
|||||||
class RPCWorker;
|
class RPCWorker;
|
||||||
}
|
}
|
||||||
namespace config { class Settings; }
|
namespace config { class Settings; }
|
||||||
namespace daemon { class EmbeddedDaemon; class XmrigManager; }
|
namespace daemon { class DaemonController; class EmbeddedDaemon; class XmrigManager; }
|
||||||
namespace util { class Bootstrap; class SecureVault; }
|
namespace util { class Bootstrap; class SecureVault; }
|
||||||
|
namespace wallet { class LiteWalletController; struct LiteWalletAppRefreshModel; }
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
@@ -125,6 +138,13 @@ public:
|
|||||||
* @brief Whether we are in the shutdown phase
|
* @brief Whether we are in the shutdown phase
|
||||||
*/
|
*/
|
||||||
bool isShuttingDown() const { return shutting_down_; }
|
bool isShuttingDown() const { return shutting_down_; }
|
||||||
|
wallet::WalletCapabilities walletCapabilities() const { return wallet::currentWalletCapabilities(); }
|
||||||
|
bool isLiteBuild() const { return wallet::isLiteBuild(walletCapabilities()); }
|
||||||
|
bool supportsEmbeddedDaemon() const { return wallet::supportsEmbeddedDaemon(walletCapabilities()); }
|
||||||
|
bool supportsFullNodeLifecycleActions() const { return wallet::supportsFullNodeLifecycleActions(walletCapabilities()); }
|
||||||
|
bool supportsSoloMining() const { return wallet::supportsSoloMining(walletCapabilities()); }
|
||||||
|
bool supportsPoolMining() const { return wallet::supportsPoolMining(walletCapabilities()); }
|
||||||
|
bool supportsLiteBackend() const { return wallet::supportsLiteBackend(walletCapabilities()); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Render the shutdown overlay (called instead of normal UI during shutdown)
|
* @brief Render the shutdown overlay (called instead of normal UI during shutdown)
|
||||||
@@ -140,10 +160,42 @@ public:
|
|||||||
// Accessors for subsystems
|
// Accessors for subsystems
|
||||||
rpc::RPCClient* rpc() { return rpc_.get(); }
|
rpc::RPCClient* rpc() { return rpc_.get(); }
|
||||||
rpc::RPCWorker* worker() { return worker_.get(); }
|
rpc::RPCWorker* worker() { return worker_.get(); }
|
||||||
|
// Console backend accessors (fast-lane-preferring, defined in app.cpp where the
|
||||||
|
// subsystem types are complete) used by the shared console executor.
|
||||||
|
rpc::RPCClient* consoleRpc();
|
||||||
|
rpc::RPCWorker* consoleWorker();
|
||||||
|
daemon::EmbeddedDaemon* consoleDaemon();
|
||||||
|
daemon::XmrigManager* consoleXmrig();
|
||||||
config::Settings* settings() { return settings_.get(); }
|
config::Settings* settings() { return settings_.get(); }
|
||||||
|
// Lite wallet controller (non-null only in lite builds with a linked backend).
|
||||||
|
wallet::LiteWalletController* liteWallet() { return lite_wallet_.get(); }
|
||||||
|
// HushChat service (identity + in-memory message store); the Chat tab reads its store.
|
||||||
|
chat::ChatService& chatService() { return chat_service_; }
|
||||||
|
// HushChat composing: construct, broadcast (broadcastChatMemos), and locally echo an outgoing
|
||||||
|
// message (to a conversation whose peer key we know) / a new-conversation contact request.
|
||||||
|
void sendChatMessage(const std::string& conversationId, const std::string& text);
|
||||||
|
void startChatConversation(const std::string& peerZaddr, const std::string& text);
|
||||||
|
// Debug/sweep convenience: give the Chat tab a demo identity + a few sample conversations
|
||||||
|
// (in-memory only, not persisted) so the screenshot sweep captures the populated UI. No-op when
|
||||||
|
// the chat feature is off.
|
||||||
|
void seedChatDemoData();
|
||||||
|
// Reason the lite wallet failed to auto-open this session (empty if none / opened OK).
|
||||||
|
const std::string& liteOpenError() const { return lite_open_error_; }
|
||||||
|
// Show the lite send-time unlock modal (called when a spend is attempted on a locked wallet).
|
||||||
|
void requestLiteUnlock() { lite_unlock_prompt_ = true; }
|
||||||
|
// Lock the lite wallet AND immediately tear down the chat session (the lite backend `lock`
|
||||||
|
// doesn't update state_.locked until the next poll, so chat secrets would otherwise linger).
|
||||||
|
bool lockLiteWallet();
|
||||||
|
// (Re)build the lite controller from current settings so a changed lite-server selection
|
||||||
|
// takes effect. No-op on non-lite/unlinked builds; preserves a live wallet (see app.cpp).
|
||||||
|
void rebuildLiteWallet(bool force = false);
|
||||||
WalletState& state() { return state_; }
|
WalletState& state() { return state_; }
|
||||||
const WalletState& state() const { return state_; }
|
const WalletState& state() const { return state_; }
|
||||||
const WalletState& getWalletState() const { return state_; }
|
const WalletState& getWalletState() const { return state_; }
|
||||||
|
// Shared contact store (Contacts tab / Send picker / future Chat roster). App-owned so
|
||||||
|
// every surface reads one source of truth instead of a per-dialog singleton.
|
||||||
|
data::AddressBook& addressBook() { return address_book_; }
|
||||||
|
const data::AddressBook& addressBook() const { return address_book_; }
|
||||||
|
|
||||||
// Connection state (convenience wrappers)
|
// Connection state (convenience wrappers)
|
||||||
bool isConnected() const { return state_.connected; }
|
bool isConnected() const { return state_.connected; }
|
||||||
@@ -176,6 +228,21 @@ public:
|
|||||||
int getXmrigRequestedThreads() const {
|
int getXmrigRequestedThreads() const {
|
||||||
return xmrig_manager_ ? xmrig_manager_->getRequestedThreads() : 0;
|
return xmrig_manager_ ? xmrig_manager_->getRequestedThreads() : 0;
|
||||||
}
|
}
|
||||||
|
// True while the pool miner process is live — used to refuse replacing the binary under it.
|
||||||
|
bool isPoolMinerRunning() const {
|
||||||
|
return xmrig_manager_ && xmrig_manager_->isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-balance: latest per-pool hashrate snapshot (for the mining tab pool list),
|
||||||
|
// and a request to refresh it now (Refresh button / switching into Auto mode).
|
||||||
|
util::PoolStatsService::Snapshot poolStatsSnapshot() const {
|
||||||
|
return pool_stats_service_.snapshot();
|
||||||
|
}
|
||||||
|
void requestPoolBalanceRefresh() { balance_refresh_pending_ = true; }
|
||||||
|
|
||||||
|
// Installed miner version (detected from `xmrig --version`, cached; kicks the one-shot
|
||||||
|
// detection on first call) so the mining tab can show it before mining starts.
|
||||||
|
std::string poolMiningInstalledVersion();
|
||||||
|
|
||||||
// Mine-when-idle state query
|
// Mine-when-idle state query
|
||||||
bool isIdleMiningActive() const { return idle_mining_active_; }
|
bool isIdleMiningActive() const { return idle_mining_active_; }
|
||||||
@@ -183,7 +250,9 @@ public:
|
|||||||
// Peers
|
// Peers
|
||||||
const std::vector<PeerInfo>& getPeers() const { return state_.peers; }
|
const std::vector<PeerInfo>& getPeers() const { return state_.peers; }
|
||||||
const std::vector<BannedPeer>& getBannedPeers() const { return state_.bannedPeers; }
|
const std::vector<BannedPeer>& getBannedPeers() const { return state_.bannedPeers; }
|
||||||
bool isPeerRefreshInProgress() const { return peer_refresh_in_progress_.load(std::memory_order_relaxed); }
|
bool isPeerRefreshInProgress() const {
|
||||||
|
return network_refresh_.jobInProgress(services::NetworkRefreshService::Job::Peers);
|
||||||
|
}
|
||||||
void banPeer(const std::string& ip, int duration_seconds = 86400);
|
void banPeer(const std::string& ip, int duration_seconds = 86400);
|
||||||
void unbanPeer(const std::string& ip);
|
void unbanPeer(const std::string& ip);
|
||||||
void clearBans();
|
void clearBans();
|
||||||
@@ -202,32 +271,65 @@ public:
|
|||||||
void unfavoriteAddress(const std::string& addr);
|
void unfavoriteAddress(const std::string& addr);
|
||||||
bool isAddressFavorite(const std::string& addr) const;
|
bool isAddressFavorite(const std::string& addr) const;
|
||||||
|
|
||||||
|
// Address metadata (labels, icons, custom ordering)
|
||||||
|
void setAddressLabel(const std::string& addr, const std::string& label);
|
||||||
|
void setAddressIcon(const std::string& addr, const std::string& icon);
|
||||||
|
std::string getAddressLabel(const std::string& addr) const;
|
||||||
|
std::string getAddressIcon(const std::string& addr) const;
|
||||||
|
int getAddressSortOrder(const std::string& addr) const;
|
||||||
|
void setAddressSortOrder(const std::string& addr, int order);
|
||||||
|
int getNextSortOrder() const;
|
||||||
|
void swapAddressOrder(const std::string& a, const std::string& b);
|
||||||
|
// Assign dense sort orders (0..N-1) to the given addresses in the given order and
|
||||||
|
// persist once. Used by drag-reorder so a drop always takes effect (even from the
|
||||||
|
// default un-ordered state, where a pairwise swap would be a no-op).
|
||||||
|
void reorderAddresses(const std::vector<std::string>& orderedAddrs);
|
||||||
|
bool isMiningAddress(const std::string& addr) const;
|
||||||
|
void setMiningAddress(const std::string& addr, bool mining);
|
||||||
|
void invalidateAddressValidationCache();
|
||||||
|
|
||||||
// Key export/import
|
// Key export/import
|
||||||
void exportPrivateKey(const std::string& address, std::function<void(const std::string&)> callback);
|
void exportPrivateKey(const std::string& address, std::function<void(const std::string&)> callback);
|
||||||
void exportAllKeys(std::function<void(const std::string&)> callback);
|
// callback receives (keys, exportedCount, totalAddresses) so callers can detect a keyless/partial export.
|
||||||
|
void exportAllKeys(std::function<void(const std::string&, int, int)> callback);
|
||||||
void importPrivateKey(const std::string& key, std::function<void(bool, const std::string&)> callback);
|
void importPrivateKey(const std::string& key, std::function<void(bool, const std::string&)> callback);
|
||||||
|
|
||||||
// Wallet backup
|
// Wallet backup
|
||||||
void backupWallet(const std::string& destination, std::function<void(bool, const std::string&)> callback);
|
void backupWallet(const std::string& destination, std::function<void(bool, const std::string&)> callback);
|
||||||
|
// Export the wallet's BIP39 seed phrase (z_exportmnemonic). The callback receives
|
||||||
|
// (ok, noMnemonic, phrase, error): ok+phrase on success; noMnemonic=true when the
|
||||||
|
// wallet's seed is not mnemonic-derived (legacy wallet). Full-node only; the phrase
|
||||||
|
// is a secret and is wiped after the callback returns.
|
||||||
|
void exportSeedPhrase(std::function<void(bool ok, bool noMnemonic,
|
||||||
|
const std::string& phrase,
|
||||||
|
const std::string& error)> callback);
|
||||||
|
|
||||||
// Transaction operations
|
// Transaction operations
|
||||||
void sendTransaction(const std::string& from, const std::string& to,
|
void sendTransaction(const std::string& from, const std::string& to,
|
||||||
double amount, double fee, const std::string& memo,
|
double amount, double fee, const std::string& memo,
|
||||||
std::function<void(bool success, const std::string& result)> callback);
|
std::function<void(bool success, const std::string& result)> callback);
|
||||||
|
|
||||||
|
// Register a daemon async operation id (z_shieldcoinbase / z_mergetoaddress /
|
||||||
|
// auto-shield) with the shared opid poller so its eventual success/failure is
|
||||||
|
// surfaced and balances/transactions refresh on completion. z_sendmany uses the
|
||||||
|
// richer pending-send path internally; this is for operations with no optimistic
|
||||||
|
// transaction row of their own.
|
||||||
|
void trackOperation(const std::string& opid);
|
||||||
|
|
||||||
// Force refresh
|
// Force refresh
|
||||||
void refreshNow();
|
void refreshNow();
|
||||||
void refreshMiningInfo();
|
void refreshMiningInfo();
|
||||||
void refreshPeerInfo();
|
void refreshPeerInfo();
|
||||||
void refreshMarketData();
|
void refreshMarketData();
|
||||||
|
// Fetch the live exchange/pair list from CoinGecko once per session (venues are
|
||||||
|
// near-static); populates state.market.exchanges. Safe to call every frame.
|
||||||
|
void refreshExchanges();
|
||||||
|
// Fetch historical USD price series (CoinGecko market_chart) that back the portfolio
|
||||||
|
// sparkline intervals; self-throttled to ~30 min. Safe to call every frame.
|
||||||
|
void refreshMarketChart();
|
||||||
|
|
||||||
/// @brief Per-category refresh intervals, adjusted by active tab
|
/// @brief Per-category refresh intervals, adjusted by active tab
|
||||||
struct RefreshIntervals {
|
using RefreshIntervals = services::NetworkRefreshService::Intervals;
|
||||||
float core; // balance + sync status
|
|
||||||
float transactions; // tx list + enrichment
|
|
||||||
float addresses; // address lists + balances
|
|
||||||
float peers; // peer info (0 = disabled)
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @brief Get recommended refresh intervals for a given page
|
/// @brief Get recommended refresh intervals for a given page
|
||||||
static RefreshIntervals getIntervalsForPage(ui::NavPage page);
|
static RefreshIntervals getIntervalsForPage(ui::NavPage page);
|
||||||
@@ -236,10 +338,22 @@ public:
|
|||||||
void setCurrentPage(ui::NavPage page);
|
void setCurrentPage(ui::NavPage page);
|
||||||
ui::NavPage getCurrentPage() const { return current_page_; }
|
ui::NavPage getCurrentPage() const { return current_page_; }
|
||||||
|
|
||||||
|
// Debug: screenshot sweep — cycles every skin x every enabled tab, one PNG each, into a
|
||||||
|
// timestamped folder under the config dir. Driven from App::render(); main.cpp polls
|
||||||
|
// wantsScreenshotThisFrame() after drawing the frame, saves screenshotSweepPath(), then calls
|
||||||
|
// onScreenshotCaptured() to advance. Transient — restores the original skin/page when done.
|
||||||
|
void startScreenshotSweep();
|
||||||
|
bool wantsScreenshotThisFrame() const { return sweep_capture_this_frame_; }
|
||||||
|
const std::string& screenshotSweepPath() const { return sweep_current_path_; }
|
||||||
|
void onScreenshotCaptured();
|
||||||
|
std::string screenshotDir() const; // <config>/screenshots (fixed; sweeps overwrite in place)
|
||||||
|
|
||||||
// Dialog triggers (used by settings page to open modal dialogs)
|
// Dialog triggers (used by settings page to open modal dialogs)
|
||||||
void showImportKeyDialog() { show_import_key_ = true; }
|
void showImportKeyDialog() { show_import_key_ = true; }
|
||||||
void showExportKeyDialog() { show_export_key_ = true; }
|
void showExportKeyDialog() { show_export_key_ = true; }
|
||||||
void showBackupDialog() { show_backup_ = true; }
|
void showBackupDialog() { show_backup_ = true; }
|
||||||
|
void showSeedBackupDialog() { show_seed_backup_ = true; }
|
||||||
|
void showSeedMigrationDialog(); // opens the migration modal (resumes a pending one at Sweep)
|
||||||
void showAboutDialog() { show_about_ = true; }
|
void showAboutDialog() { show_about_ = true; }
|
||||||
|
|
||||||
// Legacy tab compat — maps int to NavPage
|
// Legacy tab compat — maps int to NavPage
|
||||||
@@ -259,10 +373,27 @@ public:
|
|||||||
bool startEmbeddedDaemon();
|
bool startEmbeddedDaemon();
|
||||||
void stopEmbeddedDaemon();
|
void stopEmbeddedDaemon();
|
||||||
bool isEmbeddedDaemonRunning() const;
|
bool isEmbeddedDaemonRunning() const;
|
||||||
bool isUsingEmbeddedDaemon() const { return use_embedded_daemon_; }
|
bool isUsingEmbeddedDaemon() const { return supportsEmbeddedDaemon() && use_embedded_daemon_; }
|
||||||
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use; }
|
void setUseEmbeddedDaemon(bool use) { use_embedded_daemon_ = use && supportsEmbeddedDaemon(); }
|
||||||
void rescanBlockchain(); // restart daemon with -rescan flag
|
void rescanBlockchain(); // restart daemon with -rescan flag (full-history nodes)
|
||||||
|
// Runtime rescanblockchain RPC starting at a snapshot-available height. Unlike the
|
||||||
|
// -rescan restart, this works on bootstrapped/pruned nodes (which lack pre-snapshot
|
||||||
|
// block data), reconciling the wallet's stale spent-state without a daemon restart.
|
||||||
|
void runtimeRescan(int startHeight);
|
||||||
|
// Async binary-search probe for the lowest block height the node still has on disk.
|
||||||
|
// cb(ok, lowestHeight, fullHistory): fullHistory==true when genesis is present (a normal,
|
||||||
|
// non-bootstrapped node). Runs on the UI thread via the RPC worker callbacks.
|
||||||
|
void detectLowestAvailableBlockHeight(std::function<void(bool ok, int lowestHeight, bool fullHistory)> cb);
|
||||||
|
// Flag that a bootstrap just finished so the wallet auto-reconciles spent-state once the
|
||||||
|
// daemon is back up (consumed in update()).
|
||||||
|
void markPostBootstrapRescanPending() { post_bootstrap_rescan_pending_ = true; }
|
||||||
|
bool runtimeRescanActive() const { return runtime_rescan_active_; }
|
||||||
|
void repairWallet(); // restart daemon with -zapwallettxes=2 (wipe & rebuild wallet tx records)
|
||||||
|
void reinstallBundledDaemon(); // stop daemon, overwrite installed binary with the bundled one, restart
|
||||||
void deleteBlockchainData(); // stop daemon, delete chain data, restart fresh
|
void deleteBlockchainData(); // stop daemon, delete chain data, restart fresh
|
||||||
|
bool stopDaemonForBootstrap(); // stop daemon + disconnect for bootstrap, returns true if was running
|
||||||
|
bool isBootstrapDownloading() const { return bootstrap_downloading_; }
|
||||||
|
void setBootstrapDownloading(bool v) { bootstrap_downloading_ = v; }
|
||||||
|
|
||||||
// Get daemon memory usage in MB (uses embedded daemon handle if available,
|
// Get daemon memory usage in MB (uses embedded daemon handle if available,
|
||||||
// falls back to platform-level process scan for external daemons)
|
// falls back to platform-level process scan for external daemons)
|
||||||
@@ -308,6 +439,12 @@ public:
|
|||||||
|
|
||||||
// Wallet encryption helpers
|
// Wallet encryption helpers
|
||||||
void encryptWalletWithPassphrase(const std::string& passphrase);
|
void encryptWalletWithPassphrase(const std::string& passphrase);
|
||||||
|
// Post-encrypt daemon restart: the daemon shuts itself down after
|
||||||
|
// encryptwallet, so restart it off the main thread. Shared by the
|
||||||
|
// immediate and deferred encryption continuations. When
|
||||||
|
// announceRestartStatus is true, connection_status_ is updated first so
|
||||||
|
// the loading overlay explains the restart.
|
||||||
|
void restartDaemonAfterEncryption(const char* taskName, bool announceRestartStatus);
|
||||||
void unlockWallet(const std::string& passphrase, int timeout);
|
void unlockWallet(const std::string& passphrase, int timeout);
|
||||||
void lockWallet();
|
void lockWallet();
|
||||||
void changePassphrase(const std::string& oldPass, const std::string& newPass);
|
void changePassphrase(const std::string& oldPass, const std::string& newPass);
|
||||||
@@ -327,10 +464,7 @@ public:
|
|||||||
void showChangePassphraseDialog() { show_change_passphrase_ = true; }
|
void showChangePassphraseDialog() { show_change_passphrase_ = true; }
|
||||||
void showDecryptDialog() {
|
void showDecryptDialog() {
|
||||||
show_decrypt_dialog_ = true;
|
show_decrypt_dialog_ = true;
|
||||||
decrypt_phase_ = 0; // passphrase entry
|
wallet_security_workflow_.reset();
|
||||||
decrypt_step_ = 0;
|
|
||||||
decrypt_status_.clear();
|
|
||||||
decrypt_in_progress_ = false;
|
|
||||||
memset(decrypt_pass_buf_, 0, sizeof(decrypt_pass_buf_));
|
memset(decrypt_pass_buf_, 0, sizeof(decrypt_pass_buf_));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,8 +476,100 @@ public:
|
|||||||
|
|
||||||
/// @brief Check if RPC worker has queued results waiting to be processed
|
/// @brief Check if RPC worker has queued results waiting to be processed
|
||||||
bool hasPendingRPCResults() const;
|
bool hasPendingRPCResults() const;
|
||||||
|
bool hasTransactionSendProgress() const { return send_progress_active_ || send_submissions_in_flight_ > 0 || !pending_opids_.empty(); }
|
||||||
|
std::string transactionSendProgressText() const;
|
||||||
|
std::string transactionRefreshProgressText() const;
|
||||||
|
|
||||||
|
// Copy a SECRET (seed phrase, private key) to the clipboard and arm an auto-clear: after a
|
||||||
|
// short delay the clipboard is wiped IF it still holds this secret (so we don't clobber
|
||||||
|
// something the user copied afterwards). Only a hash of the secret is retained, never the
|
||||||
|
// plaintext. Call pumpSecretClipboardClear() each frame to action the clear.
|
||||||
|
void copySecretToClipboard(const std::string& secret);
|
||||||
|
void pumpSecretClipboardClear();
|
||||||
|
bool isTransactionRefreshInProgress() const {
|
||||||
|
return network_refresh_.jobInProgress(services::NetworkRefreshService::Job::Transactions);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class AppDaemonLifecycleRuntime;
|
||||||
|
friend class AppDaemonLifecycleTaskContext;
|
||||||
|
|
||||||
|
// Global keyboard-shortcut handling, dispatched once per frame from update().
|
||||||
|
void handleGlobalShortcuts();
|
||||||
|
|
||||||
|
bool sendStopCommandSafely(rpc::RPCClient& client, const char* context);
|
||||||
|
void maybeFinishTransactionSendProgress();
|
||||||
|
// Shared body of createNewZAddress/createNewTAddress (which are thin public forwarders).
|
||||||
|
// `shielded` selects the z_getnewaddress/getnewaddress RPC, the "shielded"/"transparent" type
|
||||||
|
// string, and the z_addresses/t_addresses target (the new AddressInfo is pushed into both that
|
||||||
|
// list and state_.addresses). Lite builds derive locally via the controller and early-return.
|
||||||
|
void createNewAddress(bool shielded, std::function<void(const std::string&)> callback);
|
||||||
|
void upsertPendingSendTransaction(const std::string& opid,
|
||||||
|
const std::string& from,
|
||||||
|
const std::string& to,
|
||||||
|
double amount,
|
||||||
|
const std::string& memo,
|
||||||
|
double fee = 0.0);
|
||||||
|
// Work around a dragonxd note-selection bug: its z_sendmany picks notes to cover the recipient
|
||||||
|
// total but not the miner fee, so a shielded send whose largest notes sum exactly to the amount
|
||||||
|
// fails with "Insufficient shielded funds, have H, need H+fee" despite ample balance. When a
|
||||||
|
// failed opid matches that (H >= the requested amount), re-issue the send once with a tiny
|
||||||
|
// self-output that lifts the daemon's selection target past the boundary so it grabs another
|
||||||
|
// note; the recipient still receives the exact amount. Returns true if a retry was issued.
|
||||||
|
bool maybeRetrySendForFeeGap(const std::string& opid, const std::string& rawMsg);
|
||||||
|
void resendWithFeeGapWorkaround(const std::string& from, const std::string& to,
|
||||||
|
double amount, double fee, const std::string& memo,
|
||||||
|
std::function<void(bool, const std::string&)> callback);
|
||||||
|
// Shared z_sendmany submit path for sendTransaction (single recipient, markFeeGapRetry=false)
|
||||||
|
// and resendWithFeeGapWorkaround (recipient + self-output, markFeeGapRetry=true). Owns the
|
||||||
|
// in-flight increment, the worker post + call, and the result closure (dirty flags, opid
|
||||||
|
// tracking, pending-send bookkeeping, callback delivery). Callers build `recipients` (and pass
|
||||||
|
// the recipient `to`/`amount`/`memo`/`fee` used to record the optimistic pending-send row).
|
||||||
|
// When markFeeGapRetry is set, the returned opid is recorded in send_feegap_retried_opids_ so a
|
||||||
|
// retry of a retry is reported as a real error.
|
||||||
|
void submitZSendMany(const std::string& from, const std::string& to, double amount, double fee,
|
||||||
|
const std::string& memo, const nlohmann::json& recipients,
|
||||||
|
const char* traceLabel, bool markFeeGapRetry,
|
||||||
|
std::function<void(bool, const std::string&)> callback);
|
||||||
|
void markPendingSendTransactionSucceeded(const std::string& opid,
|
||||||
|
const std::string& txid);
|
||||||
|
void removePendingSendTransactions(const std::vector<std::string>& opids,
|
||||||
|
bool restoreBalances);
|
||||||
|
// Apply a signed per-address balance delta for a pending send: walk z_addresses then
|
||||||
|
// t_addresses for `fromAddress` and clamp its balance at >=0. A positive `signedAmount`
|
||||||
|
// restores a debit; a negative one applies it. When `includeAggregates` is set, adjust the
|
||||||
|
// private/transparent bucket (chosen by the address's leading 'z') and totalBalance with the
|
||||||
|
// same clamp. Shared by the three pending-send delta sites so they can't drift.
|
||||||
|
void applyPendingSendDelta(const std::string& fromAddress, double signedAmount,
|
||||||
|
bool includeAggregates);
|
||||||
|
// Deliver a deferred z_sendmany result to its waiting UI callback once the opid
|
||||||
|
// reaches a terminal status. Returns true if a callback was registered (and fired).
|
||||||
|
bool invokeSendResultCallback(const std::string& opid, bool ok,
|
||||||
|
const std::string& result);
|
||||||
|
void applyPendingSendBalanceDeltas(bool includeAggregateBalances);
|
||||||
|
std::string transactionHistoryCacheWalletIdentity() const;
|
||||||
|
bool ensureTransactionHistoryCacheUnlockedFor(const std::string& walletIdentity);
|
||||||
|
void unlockTransactionHistoryCacheWithPassphrase(const std::string& passphrase);
|
||||||
|
// Shared main-thread continuations for a wallet unlock attempt, so the passphrase
|
||||||
|
// and PIN paths cannot drift. applyUnlockFailure applies the escalating lockout
|
||||||
|
// curve on every failed path (a PIN RPC failure used to skip it).
|
||||||
|
void applyUnlockSuccess(const std::string& passphrase, int timeout);
|
||||||
|
void applyUnlockFailure(const std::string& errorMessage);
|
||||||
|
// Clear all rescan + witness-rebuild progress/accumulator state. Shared by the four
|
||||||
|
// rescan-completion sites so a new witness field can't be forgotten in one copy.
|
||||||
|
void resetWitnessRescanProgress();
|
||||||
|
void loadTransactionHistoryCacheIfAvailable();
|
||||||
|
void storeTransactionHistoryCacheIfAvailable();
|
||||||
|
void wipePendingTransactionHistoryCachePassphrase();
|
||||||
|
void resetTransactionHistoryCacheSession();
|
||||||
|
void pruneShieldedHistoryScanProgress();
|
||||||
|
void invalidateShieldedHistoryScanProgress(bool persistCache);
|
||||||
|
|
||||||
|
// Auto-balance pool selection: drive the periodic hashrate refresh and apply a
|
||||||
|
// freshly-completed snapshot (weighted-random pick + optional miner restart).
|
||||||
|
void updatePoolAutoBalance();
|
||||||
|
void applyPoolAutoBalance(const util::PoolStatsService::Snapshot& snap);
|
||||||
|
|
||||||
// Subsystems
|
// Subsystems
|
||||||
std::unique_ptr<rpc::RPCClient> rpc_;
|
std::unique_ptr<rpc::RPCClient> rpc_;
|
||||||
std::unique_ptr<rpc::RPCWorker> worker_;
|
std::unique_ptr<rpc::RPCWorker> worker_;
|
||||||
@@ -358,8 +584,67 @@ private:
|
|||||||
rpc::ConnectionConfig saved_config_;
|
rpc::ConnectionConfig saved_config_;
|
||||||
|
|
||||||
std::unique_ptr<config::Settings> settings_;
|
std::unique_ptr<config::Settings> settings_;
|
||||||
std::unique_ptr<daemon::EmbeddedDaemon> embedded_daemon_;
|
std::unique_ptr<wallet::LiteWalletController> lite_wallet_; // lite builds w/ linked backend
|
||||||
|
// Pending send_tab callback for an in-flight lite send (delivered in update() once the
|
||||||
|
// controller's async broadcast result arrives). Only one lite send runs at a time.
|
||||||
|
std::function<void(bool, const std::string&)> lite_send_callback_;
|
||||||
|
// One-shot guard: auto-open an existing lite wallet on the first update() tick (kept off
|
||||||
|
// init() so a slow initialize_existing network call doesn't freeze startup before the window).
|
||||||
|
bool lite_autoopen_done_ = false;
|
||||||
|
double lite_open_last_attempt_ = 0.0; // ImGui time of the last async open attempt (retry timer)
|
||||||
|
// Reason an existing lite wallet failed to auto-open (e.g. server unreachable). Surfaced in
|
||||||
|
// the UI so a stuck "disconnected" state isn't silent; cleared once a wallet opens.
|
||||||
|
std::string lite_open_error_;
|
||||||
|
|
||||||
|
// HushChat (experimental; gated by DRAGONX_ENABLE_CHAT — inert when OFF). App owns the chat
|
||||||
|
// service so the transaction-refresh harvest can decrypt incoming memos into threaded messages.
|
||||||
|
// The identity is derived from the wallet's OWN SDXLite-compatible seed phrase (full-node
|
||||||
|
// z_exportmnemonic / lite exportSeed → the same KDF), so it is portable across both variants.
|
||||||
|
chat::ChatService chat_service_;
|
||||||
|
chat::ChatDatabase chat_db_; // persistent backing (seed-derived encryption at rest)
|
||||||
|
bool chat_identity_provisioned_ = false; // identity set on the service this session
|
||||||
|
bool chat_identity_fetch_in_flight_ = false; // a z_exportmnemonic worker job is pending
|
||||||
|
bool chat_identity_unavailable_ = false; // provisioning failed definitively (e.g. non-mnemonic wallet)
|
||||||
|
// Provision the chat identity once the wallet seed is reachable+unlocked (per-tick, both
|
||||||
|
// variants); derives via deriveChatIdentityFromSecret and wipes the secret. No-op when the
|
||||||
|
// feature is off, already provisioned, in flight, or unavailable.
|
||||||
|
void maybeProvisionChatIdentity();
|
||||||
|
// One-time nudge: on a full-node wallet that has a mnemonic, remind the user (once per
|
||||||
|
// install) to back up their seed phrase. Cheap early-outs keep it idle until it can act.
|
||||||
|
void maybeRemindSeedBackup();
|
||||||
|
|
||||||
|
// Seed-wallet migration (Phase 1: create a new mnemonic wallet in isolation, no funds moved).
|
||||||
|
void beginCreateSeedWallet(); // starts the isolated create on a background thread
|
||||||
|
void pumpSeedMigration(); // main thread: pick up background progress/result each frame
|
||||||
|
// Phase 2: sweep all legacy funds into the new wallet, then adopt it as the primary wallet.
|
||||||
|
void refreshSeedMigrationBalance(); // query the legacy total (shown on the Sweep step)
|
||||||
|
void beginSweepToSeedWallet(); // z_mergetoaddress ["ANY_TADDR","ANY_ZADDR"] -> dest
|
||||||
|
void pollSweepStatus(); // Confirming step: poll sweep confirmations + legacy balance
|
||||||
|
void beginAdoptSeedWallet(); // stop daemon -> swap wallet.dat -> restart with -rescan
|
||||||
|
void provisionChatIdentityFromSecret(std::string secret);
|
||||||
|
std::string chatReplyZaddr(); // a stable (persisted) wallet z-addr for chat
|
||||||
|
std::string generateChatLocalId(const char* prefix, int numBytes) const; // unique echo id / cid
|
||||||
|
bool broadcastChatMemos(const chat::OutgoingChatMemos& memos); // returns true if submitted
|
||||||
|
bool broadcastChatMemosLite(const chat::OutgoingChatMemos& memos); // lite two-recipient send
|
||||||
|
void ingestLiteChatMemos(const wallet::LiteWalletAppRefreshModel& model); // lite chat receive harvest
|
||||||
|
// Lite first-run welcome prompt: dismissed for the session once the user picks an action.
|
||||||
|
bool lite_firstrun_dismissed_ = false;
|
||||||
|
// Lite send-time unlock: set to show the unlock modal when a spend is attempted while locked.
|
||||||
|
bool lite_unlock_prompt_ = false;
|
||||||
|
// One-shot: prompt to unlock on startup once we learn the auto-opened wallet is encrypted+locked.
|
||||||
|
bool lite_startup_lock_checked_ = false;
|
||||||
|
std::unique_ptr<daemon::DaemonController> daemon_controller_;
|
||||||
std::unique_ptr<daemon::XmrigManager> xmrig_manager_;
|
std::unique_ptr<daemon::XmrigManager> xmrig_manager_;
|
||||||
|
// Auto-balance runtime state (pool mining, full-node only). The service fetches
|
||||||
|
// pool hashrates off-thread; the RNG drives the weighted-random pick.
|
||||||
|
util::PoolStatsService pool_stats_service_;
|
||||||
|
std::mt19937 balance_rng_;
|
||||||
|
long long last_balance_eval_ms_ = 0; // steady-clock ms of the last refresh kick
|
||||||
|
bool balance_refresh_pending_ = false; // UI asked for an immediate refresh
|
||||||
|
bool balance_snapshot_seen_ = false; // the current in-flight snapshot was applied
|
||||||
|
bool exchanges_fetch_started_ = false; // once-per-session CoinGecko tickers fetch
|
||||||
|
bool chart_fetch_in_flight_ = false; // a market_chart history fetch is on the worker
|
||||||
|
util::AsyncTaskManager async_tasks_;
|
||||||
bool pending_antivirus_dialog_ = false; // Show Windows Defender help dialog
|
bool pending_antivirus_dialog_ = false; // Show Windows Defender help dialog
|
||||||
|
|
||||||
// Wallet state
|
// Wallet state
|
||||||
@@ -372,11 +657,18 @@ private:
|
|||||||
std::string shutdown_status_;
|
std::string shutdown_status_;
|
||||||
std::thread shutdown_thread_;
|
std::thread shutdown_thread_;
|
||||||
float shutdown_timer_ = 0.0f;
|
float shutdown_timer_ = 0.0f;
|
||||||
|
bool force_quit_confirm_ = false;
|
||||||
std::chrono::steady_clock::time_point shutdown_start_time_;
|
std::chrono::steady_clock::time_point shutdown_start_time_;
|
||||||
|
|
||||||
// Daemon restart (e.g. after changing debug log categories)
|
// Daemon restart (e.g. after changing debug log categories)
|
||||||
std::atomic<bool> daemon_restarting_{false};
|
std::atomic<bool> daemon_restarting_{false};
|
||||||
std::thread daemon_restart_thread_;
|
|
||||||
|
// Set by the deleteBlockchainData worker (item count); the main loop surfaces a completion toast
|
||||||
|
// and resets it to -1. Atomic because the worker thread writes it and the UI thread reads/clears it.
|
||||||
|
std::atomic<int> pending_delete_result_{-1};
|
||||||
|
|
||||||
|
// Encryption state check timeout
|
||||||
|
float encryption_check_timer_ = 0.0f;
|
||||||
|
|
||||||
// UI State
|
// UI State
|
||||||
bool quit_requested_ = false;
|
bool quit_requested_ = false;
|
||||||
@@ -386,10 +678,46 @@ private:
|
|||||||
bool show_import_key_ = false;
|
bool show_import_key_ = false;
|
||||||
bool show_export_key_ = false;
|
bool show_export_key_ = false;
|
||||||
bool show_backup_ = false;
|
bool show_backup_ = false;
|
||||||
bool show_address_book_ = false;
|
bool show_seed_backup_ = false;
|
||||||
|
|
||||||
|
// Seed-phrase backup dialog state. seed_backup_phrase_ holds a SECRET (the revealed
|
||||||
|
// mnemonic) and is wiped with sodium_memzero when the dialog closes.
|
||||||
|
std::string seed_backup_phrase_;
|
||||||
|
std::string seed_backup_status_;
|
||||||
|
bool seed_backup_fetch_started_ = false;
|
||||||
|
bool seed_backup_loading_ = false;
|
||||||
|
bool seed_backup_no_mnemonic_ = false;
|
||||||
|
bool seed_backup_reminder_in_flight_ = false; // guards the one-time backup nudge probe
|
||||||
|
|
||||||
|
// --- Seed-wallet migration (Phase 1: create; Phase 2: sweep + adopt) ---
|
||||||
|
enum class SeedMigrationStep { Intro, Working, ShowSeed, Sweep, Sweeping, Confirming, Adopting, Done, Error };
|
||||||
|
bool show_seed_migration_ = false;
|
||||||
|
SeedMigrationStep seed_migration_step_ = SeedMigrationStep::Intro;
|
||||||
|
bool seed_migration_in_flight_ = false; // main-thread guard while the bg create task runs
|
||||||
|
std::string seed_migration_seed_; // SECRET — the revealed phrase, wiped on close
|
||||||
|
std::string seed_migration_dest_; // new shielded z-address (Phase 2 sweep target)
|
||||||
|
std::string seed_migration_temp_dir_; // temp datadir root holding the new wallet (kept)
|
||||||
|
bool seed_migration_backed_up_ = false; // "I've written it down" confirmation
|
||||||
|
std::string seed_migration_status_; // main-thread display (progress / error text)
|
||||||
|
double seed_migration_balance_ = 0.0; // legacy total to sweep (shown on the Sweep step)
|
||||||
|
std::string seed_migration_sweep_txid_; // the z_mergetoaddress sweep transaction id
|
||||||
|
int seed_migration_sweep_confs_ = 0; // confirmations of the sweep tx (adopt gate: >= 1)
|
||||||
|
double seed_migration_legacy_remaining_ = -1.0; // legacy balance after sweep (-1 = unknown)
|
||||||
|
float seed_migration_poll_timer_ = 0.0f; // throttles the Confirming-step poll
|
||||||
|
bool seed_migration_confirm_in_flight_ = false; // guards the confirm poll
|
||||||
|
// Cross-thread handoff from the background create task (guarded by seed_migration_mutex_).
|
||||||
|
std::mutex seed_migration_mutex_;
|
||||||
|
std::string seed_migration_progress_; // latest progress line
|
||||||
|
bool seed_migration_done_ = false; // create result ready to consume
|
||||||
|
bool seed_migration_ok_ = false;
|
||||||
|
std::string seed_migration_r_seed_, seed_migration_r_dest_, seed_migration_r_tmp_, seed_migration_r_err_;
|
||||||
|
// Cross-thread handoff from the background adopt task (guarded by seed_migration_mutex_).
|
||||||
|
bool seed_migration_adopt_done_ = false;
|
||||||
|
bool seed_migration_adopt_ok_ = false;
|
||||||
|
std::string seed_migration_adopt_err_;
|
||||||
|
|
||||||
// Embedded daemon state
|
// Embedded daemon state
|
||||||
bool use_embedded_daemon_ = true;
|
bool use_embedded_daemon_ = wallet::supportsEmbeddedDaemon(wallet::currentWalletCapabilities());
|
||||||
std::string daemon_status_;
|
std::string daemon_status_;
|
||||||
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory detection
|
mutable std::string daemon_mem_diag_; // diagnostic info for daemon memory detection
|
||||||
size_t daemon_output_offset_ = 0; // for incremental output parsing (rescan detection)
|
size_t daemon_output_offset_ = 0; // for incremental output parsing (rescan detection)
|
||||||
@@ -406,6 +734,16 @@ private:
|
|||||||
// Connection
|
// Connection
|
||||||
std::string connection_status_ = "Disconnected";
|
std::string connection_status_ = "Disconnected";
|
||||||
bool connection_in_progress_ = false;
|
bool connection_in_progress_ = false;
|
||||||
|
bool remote_rpc_plaintext_warning_shown_ = false;
|
||||||
|
// Startup daemon-launch diagnostics: bound the "RPC port busy, no config" wait before warning,
|
||||||
|
// and show the embedded-daemon start failure (binary/params/spawn) only once. Reset on connect.
|
||||||
|
int daemon_wait_attempts_ = 0;
|
||||||
|
bool daemon_start_error_shown_ = false;
|
||||||
|
int daemon_last_seen_crashes_ = 0; // surface each new embedded-daemon crash reason once
|
||||||
|
bool refresh_policy_syncing_ = false; // whether the sync-throttle refresh profile is active
|
||||||
|
// Auto-clear for secrets copied to the clipboard. Only a hash of the copied secret is kept.
|
||||||
|
std::uint64_t clipboard_secret_hash_ = 0;
|
||||||
|
double clipboard_clear_deadline_ = 0.0;
|
||||||
float loading_timer_ = 0.0f; // spinner animation for loading overlay
|
float loading_timer_ = 0.0f; // spinner animation for loading overlay
|
||||||
|
|
||||||
// Current page (sidebar navigation)
|
// Current page (sidebar navigation)
|
||||||
@@ -413,6 +751,21 @@ private:
|
|||||||
ui::NavPage prev_page_ = ui::NavPage::Overview;
|
ui::NavPage prev_page_ = ui::NavPage::Overview;
|
||||||
float page_alpha_ = 1.0f; // 0→1 fade on page switch
|
float page_alpha_ = 1.0f; // 0→1 fade on page switch
|
||||||
bool sidebar_collapsed_ = false; // true = icon-only mode
|
bool sidebar_collapsed_ = false; // true = icon-only mode
|
||||||
|
|
||||||
|
// Debug screenshot sweep state.
|
||||||
|
bool screenshot_sweep_active_ = false;
|
||||||
|
bool sweep_capture_this_frame_ = false;
|
||||||
|
int sweep_skin_idx_ = 0;
|
||||||
|
int sweep_page_idx_ = 0;
|
||||||
|
int sweep_settle_frames_ = 0; // frames to let a new skin/page settle before capturing
|
||||||
|
std::vector<std::string> sweep_skins_; // skin ids to cycle
|
||||||
|
std::vector<ui::NavPage> sweep_pages_; // enabled pages to cycle
|
||||||
|
std::string sweep_dir_; // output folder for this sweep
|
||||||
|
std::string sweep_current_path_; // PNG path for the frame about to be captured
|
||||||
|
std::string sweep_saved_skin_; // restore on completion
|
||||||
|
ui::NavPage sweep_saved_page_ = ui::NavPage::Overview;
|
||||||
|
void updateScreenshotSweep(); // called at the top of render() while active
|
||||||
|
void applySweepTarget(); // apply current (skin,page), compute path, arm settle
|
||||||
bool sidebar_user_toggled_ = false; // user manually toggled — suppress auto-collapse
|
bool sidebar_user_toggled_ = false; // user manually toggled — suppress auto-collapse
|
||||||
float sidebar_width_anim_ = 0.0f; // animated width (0 = uninitialized)
|
float sidebar_width_anim_ = 0.0f; // animated width (0 = uninitialized)
|
||||||
float prev_dpi_scale_ = 0.0f; // detect DPI changes to snap sidebar width
|
float prev_dpi_scale_ = 0.0f; // detect DPI changes to snap sidebar width
|
||||||
@@ -433,8 +786,9 @@ private:
|
|||||||
int coin_logo_h_ = 0;
|
int coin_logo_h_ = 0;
|
||||||
bool coin_logo_loaded_ = false;
|
bool coin_logo_loaded_ = false;
|
||||||
|
|
||||||
// Console tab
|
// Console tab + its backend executor (full-node RPC or lite backend), created lazily.
|
||||||
ui::ConsoleTab console_tab_;
|
ui::ConsoleTab console_tab_;
|
||||||
|
std::unique_ptr<ui::ConsoleCommandExecutor> console_exec_;
|
||||||
|
|
||||||
// Pending payment from URI
|
// Pending payment from URI
|
||||||
bool pending_payment_valid_ = false;
|
bool pending_payment_valid_ = false;
|
||||||
@@ -443,64 +797,35 @@ private:
|
|||||||
std::string pending_memo_;
|
std::string pending_memo_;
|
||||||
std::string pending_label_;
|
std::string pending_label_;
|
||||||
|
|
||||||
// Per-category timers (in seconds since last refresh)
|
// Per-category refresh timers, policy, and worker queue guards.
|
||||||
float core_timer_ = 0.0f; // balance + sync status
|
services::NetworkRefreshService network_refresh_;
|
||||||
float address_timer_ = 0.0f; // address lists
|
|
||||||
float transaction_timer_ = 0.0f; // transaction list
|
|
||||||
float peer_timer_ = 0.0f; // peer info
|
|
||||||
float price_timer_ = 0.0f;
|
|
||||||
float fast_refresh_timer_ = 0.0f; // For mining stats
|
|
||||||
|
|
||||||
// Default refresh intervals (seconds)
|
|
||||||
static constexpr float CORE_INTERVAL_DEFAULT = 5.0f;
|
|
||||||
static constexpr float ADDRESS_INTERVAL_DEFAULT = 15.0f;
|
|
||||||
static constexpr float TX_INTERVAL_DEFAULT = 10.0f;
|
|
||||||
static constexpr float PEER_INTERVAL_DEFAULT = 10.0f;
|
|
||||||
static constexpr float PRICE_INTERVAL = 60.0f;
|
|
||||||
static constexpr float FAST_REFRESH_INTERVAL = 1.0f;
|
|
||||||
|
|
||||||
// Active intervals — adjusted by tab priority via applyRefreshPolicy()
|
|
||||||
float active_core_interval_ = CORE_INTERVAL_DEFAULT;
|
|
||||||
float active_tx_interval_ = TX_INTERVAL_DEFAULT;
|
|
||||||
float active_addr_interval_ = ADDRESS_INTERVAL_DEFAULT;
|
|
||||||
float active_peer_interval_ = PEER_INTERVAL_DEFAULT;
|
|
||||||
|
|
||||||
// Per-category refresh guards (prevent worker queue pileup)
|
|
||||||
std::atomic<bool> core_refresh_in_progress_{false};
|
|
||||||
std::atomic<bool> address_refresh_in_progress_{false};
|
|
||||||
std::atomic<bool> tx_refresh_in_progress_{false};
|
|
||||||
|
|
||||||
// Mining refresh guard (prevents worker queue pileup)
|
|
||||||
std::atomic<bool> mining_refresh_in_progress_{false};
|
|
||||||
int mining_slow_counter_ = 0; // counts fast ticks; fires slow refresh every N
|
int mining_slow_counter_ = 0; // counts fast ticks; fires slow refresh every N
|
||||||
|
|
||||||
// Mining toggle guard (prevents concurrent setgenerate calls)
|
// Mining toggle guard (prevents concurrent setgenerate calls)
|
||||||
std::atomic<bool> mining_toggle_in_progress_{false};
|
std::atomic<bool> mining_toggle_in_progress_{false};
|
||||||
|
|
||||||
// Peer refresh guard (visual feedback for refresh button)
|
// True from a successful startPoolMining() until the miner is confirmed connected/hashing in the
|
||||||
std::atomic<bool> peer_refresh_in_progress_{false};
|
// poll — drives the "connecting…" → "connected" feedback for pool mining (which has a connect delay).
|
||||||
|
std::atomic<bool> pool_starting_{false};
|
||||||
|
|
||||||
// Auto-shield guard (prevents concurrent auto-shield operations)
|
// Auto-shield guard (prevents concurrent auto-shield operations)
|
||||||
std::atomic<bool> auto_shield_pending_{false};
|
std::atomic<bool> auto_shield_pending_{false};
|
||||||
|
|
||||||
// P4: Incremental transaction cache
|
// P4: Incremental transaction cache
|
||||||
int last_tx_block_height_ = -1; // block height at last full tx fetch
|
int last_tx_block_height_ = -1; // block height at last full tx fetch
|
||||||
float tx_age_timer_ = 0.0f; // seconds since last tx fetch
|
|
||||||
static constexpr float TX_MAX_AGE = 15.0f; // force tx refresh every N seconds even without new blocks
|
|
||||||
static constexpr int MAX_VIEWTX_PER_CYCLE = 25; // cap z_viewtransaction calls per refresh
|
static constexpr int MAX_VIEWTX_PER_CYCLE = 25; // cap z_viewtransaction calls per refresh
|
||||||
|
std::size_t shielded_history_scan_cursor_ = 0;
|
||||||
|
bool shielded_history_scan_pending_ = false;
|
||||||
|
// False until the first full shielded-history scan finishes. Drives the History tab's
|
||||||
|
// "Loading older history…" progress so the user knows transactions are still streaming in
|
||||||
|
// after the first batch appears; goes quiet for the routine per-block re-scans afterward.
|
||||||
|
bool initial_history_scan_complete_ = false;
|
||||||
|
std::unordered_map<std::string, int> shielded_history_scan_heights_;
|
||||||
|
|
||||||
// P4b: z_viewtransaction result cache — avoids re-calling the RPC for
|
// P4b: z_viewtransaction result cache — avoids re-calling the RPC for
|
||||||
// txids we've already enriched. Keyed by txid.
|
// txids we've already enriched. Keyed by txid.
|
||||||
struct ViewTxCacheEntry {
|
using ViewTxCacheEntry = services::NetworkRefreshService::TransactionViewCacheEntry;
|
||||||
std::string from_address; // first spend address
|
services::NetworkRefreshService::TransactionViewCache viewtx_cache_;
|
||||||
struct Output {
|
|
||||||
std::string address;
|
|
||||||
double value = 0.0;
|
|
||||||
std::string memo;
|
|
||||||
};
|
|
||||||
std::vector<Output> outgoing_outputs;
|
|
||||||
};
|
|
||||||
std::unordered_map<std::string, ViewTxCacheEntry> viewtx_cache_;
|
|
||||||
|
|
||||||
// P4c: Confirmed transaction cache — deeply-confirmed txns (>= 10 confs)
|
// P4c: Confirmed transaction cache — deeply-confirmed txns (>= 10 confs)
|
||||||
// are accumulated here and reused across refresh cycles. Only
|
// are accumulated here and reused across refresh cycles. Only
|
||||||
@@ -511,14 +836,58 @@ private:
|
|||||||
|
|
||||||
// Dirty flags for demand-driven refresh
|
// Dirty flags for demand-driven refresh
|
||||||
bool addresses_dirty_ = true; // true → refreshAddresses() will run
|
bool addresses_dirty_ = true; // true → refreshAddresses() will run
|
||||||
|
bool address_validation_cache_dirty_ = true;
|
||||||
bool transactions_dirty_ = false; // true → force tx refresh regardless of block height
|
bool transactions_dirty_ = false; // true → force tx refresh regardless of block height
|
||||||
bool encryption_state_prefetched_ = false; // suppress duplicate getwalletinfo on connect
|
bool encryption_state_prefetched_ = false; // suppress duplicate getwalletinfo on connect
|
||||||
|
bool rescan_status_poll_in_progress_ = false;
|
||||||
|
// True once we've actually observed the rescan running (daemon restarted into -rescan warmup).
|
||||||
|
// Gates the "rescan complete" detection so a getrescaninfo poll that hits the still-running
|
||||||
|
// pre-restart daemon (which reports rescanning=false) can't fire a false "complete" instantly.
|
||||||
|
bool rescan_confirmed_active_ = false;
|
||||||
|
// A runtime rescanblockchain RPC is in flight (vs the -rescan daemon restart). While set,
|
||||||
|
// the per-second mining/rescan-status pollers are suppressed (the daemon holds cs_main for
|
||||||
|
// the whole scan and would block them); completion is signalled by the rescan RPC callback.
|
||||||
|
bool runtime_rescan_active_ = false;
|
||||||
|
// Set when a bootstrap completes; consumed once the daemon is connected to auto-run a rescan
|
||||||
|
// that reconciles the preserved wallet.dat against the freshly-imported chain.
|
||||||
|
bool post_bootstrap_rescan_pending_ = false;
|
||||||
|
// Largest "blocks remaining" seen during the current witness-rebuild phase. The daemon's
|
||||||
|
// "Building Witnesses for block" fraction resets every call (it's re-invoked per connected
|
||||||
|
// block, each walking from its own start height to the tip), so we derive a stable, monotonic
|
||||||
|
// overall percentage from how far "remaining" has fallen below this peak. Reset per phase.
|
||||||
|
int witness_rebuild_total_blocks_ = 0;
|
||||||
|
// The daemon's primary witness signal is "Setting Initial Sapling Witness for tx <hash>, <i>
|
||||||
|
// of <N>", logged once per wallet tx as its initial witness is set. The <i> is the tx's slot in
|
||||||
|
// an UNORDERED map, so it bounces wildly (was the cause of the resetting progress). The honest
|
||||||
|
// monotonic metric is how many DISTINCT txs have been witnessed (the set only grows; it also
|
||||||
|
// dedups the daemon's occasional double-prints) over the reported total N.
|
||||||
|
std::unordered_set<std::string> witness_seen_txids_;
|
||||||
|
int witness_total_txs_ = 0;
|
||||||
|
bool opid_poll_in_progress_ = false;
|
||||||
|
// Consecutive Core-refresh cycles where BOTH core RPCs failed → likely a dead
|
||||||
|
// connection. After kCoreFailuresBeforeDisconnect, tear down and reconnect.
|
||||||
|
int consecutive_core_failures_ = 0;
|
||||||
|
|
||||||
// Pending z_sendmany operation tracking
|
// Pending z_sendmany operation tracking
|
||||||
|
bool send_progress_active_ = false;
|
||||||
|
int send_submissions_in_flight_ = 0;
|
||||||
std::vector<std::string> pending_opids_; // opids to poll for completion
|
std::vector<std::string> pending_opids_; // opids to poll for completion
|
||||||
float opid_poll_timer_ = 0.0f;
|
struct PendingSendInfo {
|
||||||
static constexpr float OPID_POLL_INTERVAL = 2.0f;
|
std::string from;
|
||||||
|
std::string to;
|
||||||
|
std::string memo;
|
||||||
|
double amount = 0.0;
|
||||||
|
double fee = 0.0;
|
||||||
|
std::int64_t timestamp = 0;
|
||||||
|
};
|
||||||
|
std::unordered_map<std::string, PendingSendInfo> pending_send_info_;
|
||||||
|
// Opids issued as a fee-gap auto-retry (see maybeRetrySendForFeeGap). Tracked so a retry that
|
||||||
|
// fails again is reported to the user instead of looping.
|
||||||
|
std::unordered_set<std::string> send_feegap_retried_opids_;
|
||||||
|
// z_sendmany UI callbacks held until the opid reaches a terminal status, so the
|
||||||
|
// user isn't told "sent successfully" before the tx is actually built/broadcast.
|
||||||
|
std::unordered_map<std::string, std::function<void(bool, const std::string&)>>
|
||||||
|
pending_send_callbacks_;
|
||||||
// Txids from completed z_sendmany operations.
|
// Txids from completed z_sendmany operations.
|
||||||
// Ensures shielded sends are discoverable by z_viewtransaction
|
// Ensures shielded sends are discoverable by z_viewtransaction
|
||||||
// even when they don't appear in listtransactions or
|
// even when they don't appear in listtransactions or
|
||||||
@@ -528,21 +897,24 @@ private:
|
|||||||
// First-run wizard state
|
// First-run wizard state
|
||||||
WizardPhase wizard_phase_ = WizardPhase::None;
|
WizardPhase wizard_phase_ = WizardPhase::None;
|
||||||
std::unique_ptr<util::Bootstrap> bootstrap_;
|
std::unique_ptr<util::Bootstrap> bootstrap_;
|
||||||
|
bool bootstrap_downloading_ = false; // true while settings bootstrap dialog is active
|
||||||
std::string wizard_pending_passphrase_; // held until daemon connects
|
std::string wizard_pending_passphrase_; // held until daemon connects
|
||||||
std::string wizard_saved_passphrase_; // held until PinSetup completes/skipped
|
std::string wizard_saved_passphrase_; // held until PinSetup completes/skipped
|
||||||
|
|
||||||
// Deferred encryption (wizard background task)
|
// Wallet security flow state shared by wizard/settings encryption paths.
|
||||||
std::string deferred_encrypt_passphrase_;
|
services::WalletSecurityController wallet_security_;
|
||||||
std::string deferred_encrypt_pin_;
|
services::WalletSecurityWorkflow wallet_security_workflow_;
|
||||||
bool deferred_encrypt_pending_ = false;
|
|
||||||
|
|
||||||
// Wizard: stopping an external daemon before bootstrap
|
// Wizard: stopping an external daemon before bootstrap
|
||||||
bool wizard_stopping_external_ = false;
|
bool wizard_stopping_external_ = false;
|
||||||
std::string wizard_stop_status_;
|
std::string wizard_stop_status_;
|
||||||
std::thread wizard_stop_thread_;
|
|
||||||
|
|
||||||
// PIN vault
|
// PIN vault
|
||||||
std::unique_ptr<util::SecureVault> vault_;
|
std::unique_ptr<util::SecureVault> vault_;
|
||||||
|
data::TransactionHistoryCache transaction_history_cache_;
|
||||||
|
data::AddressBook address_book_; // shared contact store; loaded once in init(), self-saves on mutation
|
||||||
|
std::string pending_transaction_history_cache_passphrase_;
|
||||||
|
bool transaction_history_cache_loaded_ = false;
|
||||||
|
|
||||||
// Lock screen state
|
// Lock screen state
|
||||||
bool lock_screen_was_visible_ = false; // tracks lock→unlock transitions for auto-focus
|
bool lock_screen_was_visible_ = false; // tracks lock→unlock transitions for auto-focus
|
||||||
@@ -584,14 +956,7 @@ private:
|
|||||||
|
|
||||||
// Decrypt wallet dialog state
|
// Decrypt wallet dialog state
|
||||||
bool show_decrypt_dialog_ = false;
|
bool show_decrypt_dialog_ = false;
|
||||||
int decrypt_phase_ = 0; // 0=passphrase, 1=working, 2=done, 3=error
|
|
||||||
int decrypt_step_ = 0; // 0=unlock, 1=export, 2=stop, 3=rename, 4=restart, 5=import
|
|
||||||
char decrypt_pass_buf_[256] = {};
|
char decrypt_pass_buf_[256] = {};
|
||||||
std::string decrypt_status_;
|
|
||||||
bool decrypt_in_progress_ = false;
|
|
||||||
std::chrono::steady_clock::time_point decrypt_step_start_time_{};
|
|
||||||
std::chrono::steady_clock::time_point decrypt_overall_start_time_{};
|
|
||||||
std::atomic<bool> decrypt_import_active_{false}; // background z_importwallet running
|
|
||||||
|
|
||||||
// Wizard PIN setup state
|
// Wizard PIN setup state
|
||||||
char wizard_pin_buf_[16] = {};
|
char wizard_pin_buf_[16] = {};
|
||||||
@@ -607,10 +972,13 @@ private:
|
|||||||
|
|
||||||
// Private methods - rendering
|
// Private methods - rendering
|
||||||
void renderStatusBar();
|
void renderStatusBar();
|
||||||
void renderAboutDialog();
|
void renderLiteFirstRunPrompt(); // lite-only welcome modal when no wallet exists yet
|
||||||
|
void renderLiteUnlockPrompt(); // lite-only send-time unlock modal
|
||||||
void renderImportKeyDialog();
|
void renderImportKeyDialog();
|
||||||
void renderExportKeyDialog();
|
void renderExportKeyDialog();
|
||||||
void renderBackupDialog();
|
void renderBackupDialog();
|
||||||
|
void renderSeedBackupDialog(); // full-node "Back up seed phrase" modal (z_exportmnemonic)
|
||||||
|
void renderSeedMigrationDialog(); // "Migrate to a seed wallet" guided modal (Phase 1: create)
|
||||||
void renderFirstRunWizard();
|
void renderFirstRunWizard();
|
||||||
void renderLockScreen();
|
void renderLockScreen();
|
||||||
void renderEncryptWalletDialog();
|
void renderEncryptWalletDialog();
|
||||||
@@ -623,6 +991,16 @@ private:
|
|||||||
void tryConnect();
|
void tryConnect();
|
||||||
void onConnected();
|
void onConnected();
|
||||||
void onDisconnected(const std::string& reason);
|
void onDisconnected(const std::string& reason);
|
||||||
|
// Set the "node is initializing" UI state (status line + overlay description) from the
|
||||||
|
// embedded/external daemon's launch state and its own console output (current phase + block
|
||||||
|
// height), so a connect probe that times out while the daemon loads shows WHAT it's doing.
|
||||||
|
// `reachableButBusy` is true when the probe connected but got no RPC reply (a timeout),
|
||||||
|
// false when the daemon is merely launching (not bound yet). Returns the status title.
|
||||||
|
std::string applyDaemonInitStatus(bool reachableButBusy);
|
||||||
|
// Tear down a connection that died mid-session (daemon crash / restart / dropped
|
||||||
|
// socket) so update()'s reconnect branch re-enters tryConnect(). Unlike onDisconnected
|
||||||
|
// alone, this also rpc_->disconnect()s so rpc_->isConnected() actually flips to false.
|
||||||
|
void handleLostConnection(const std::string& reason);
|
||||||
void applyDefaultBanlist();
|
void applyDefaultBanlist();
|
||||||
|
|
||||||
// Private methods - data refresh
|
// Private methods - data refresh
|
||||||
@@ -630,13 +1008,17 @@ private:
|
|||||||
void refreshCoreData(); // Balance + blockchain info (can use fast_worker_)
|
void refreshCoreData(); // Balance + blockchain info (can use fast_worker_)
|
||||||
void refreshAddressData(); // Address lists + balances
|
void refreshAddressData(); // Address lists + balances
|
||||||
void refreshTransactionData(); // Transaction list + z_viewtransaction enrichment
|
void refreshTransactionData(); // Transaction list + z_viewtransaction enrichment
|
||||||
void refreshEncryptionState(); // Wallet encryption/lock state
|
void refreshRecentTransactionData(); // Lightweight recent/unconfirmed tx poll
|
||||||
|
bool refreshEncryptionState(); // Wallet encryption/lock state
|
||||||
void refreshBalance(); // Legacy: balance-only refresh (used by specific callers)
|
void refreshBalance(); // Legacy: balance-only refresh (used by specific callers)
|
||||||
void refreshAddresses(); // Legacy: standalone address refresh
|
void refreshAddresses(); // Legacy: standalone address refresh
|
||||||
void refreshPrice();
|
void refreshPrice();
|
||||||
void refreshWalletEncryptionState();
|
void refreshWalletEncryptionState();
|
||||||
void applyRefreshPolicy(ui::NavPage page);
|
void applyRefreshPolicy(ui::NavPage page);
|
||||||
|
bool currentPageNeedsWalletDataRefresh() const;
|
||||||
|
bool shouldRunWalletTransactionRefresh() const;
|
||||||
bool shouldRefreshTransactions() const;
|
bool shouldRefreshTransactions() const;
|
||||||
|
bool shouldRefreshRecentTransactions() const;
|
||||||
void checkAutoLock();
|
void checkAutoLock();
|
||||||
void checkIdleMining();
|
void checkIdleMining();
|
||||||
};
|
};
|
||||||
|
|||||||
3512
src/app_network.cpp
3512
src/app_network.cpp
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
|||||||
#include "rpc/rpc_worker.h"
|
#include "rpc/rpc_worker.h"
|
||||||
#include "rpc/connection.h"
|
#include "rpc/connection.h"
|
||||||
#include "config/settings.h"
|
#include "config/settings.h"
|
||||||
|
#include "daemon/daemon_controller.h"
|
||||||
#include "daemon/embedded_daemon.h"
|
#include "daemon/embedded_daemon.h"
|
||||||
#include "ui/notifications.h"
|
#include "ui/notifications.h"
|
||||||
#include "ui/material/color_theme.h"
|
#include "ui/material/color_theme.h"
|
||||||
@@ -39,13 +40,48 @@ namespace dragonx {
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct WizardLowSpecSnapshot {
|
||||||
|
bool valid = false;
|
||||||
|
float blur = 0.0f;
|
||||||
|
float uiOp = 0.0f;
|
||||||
|
bool fx = false;
|
||||||
|
bool scanline = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WizardUiState {
|
||||||
|
float blur_amount = 1.5f;
|
||||||
|
bool theme_effects = true;
|
||||||
|
float ui_opacity = 1.0f;
|
||||||
|
bool low_spec = false;
|
||||||
|
bool scanline = true;
|
||||||
|
std::string balance_layout = "classic";
|
||||||
|
int language_index = 0;
|
||||||
|
bool appearance_init = false;
|
||||||
|
WizardLowSpecSnapshot low_spec_snapshot;
|
||||||
|
float card0_max_h = 0.0f;
|
||||||
|
float card1_max_h = 0.0f;
|
||||||
|
double external_last_check = -10.0;
|
||||||
|
bool daemon_prestarted = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
WizardUiState s_wizardUi;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void App::restartWizard()
|
void App::restartWizard()
|
||||||
{
|
{
|
||||||
|
if (!supportsFullNodeLifecycleActions()) {
|
||||||
|
ui::Notifications::instance().warning("Lite wallet lifecycle requests are available from Settings as dry-run readiness checks");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG_LOGF("[App] Restarting setup wizard — stopping daemon...\n");
|
DEBUG_LOGF("[App] Restarting setup wizard — stopping daemon...\n");
|
||||||
|
|
||||||
// Reset crash counter for fresh wizard attempt
|
// Reset crash counter for fresh wizard attempt
|
||||||
if (embedded_daemon_) {
|
if (daemon_controller_) {
|
||||||
embedded_daemon_->resetCrashCount();
|
daemon_controller_->resetCrashCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disconnect RPC
|
// Disconnect RPC
|
||||||
@@ -56,10 +92,11 @@ void App::restartWizard()
|
|||||||
|
|
||||||
// Stop the embedded daemon in a background thread to avoid
|
// Stop the embedded daemon in a background thread to avoid
|
||||||
// blocking the UI for up to 32 seconds (RPC stop + process wait).
|
// blocking the UI for up to 32 seconds (RPC stop + process wait).
|
||||||
if (embedded_daemon_ && isEmbeddedDaemonRunning()) {
|
if (daemon_controller_ && isEmbeddedDaemonRunning()) {
|
||||||
std::thread([this]() {
|
async_tasks_.submit("wizard-restart-stop-daemon", [this](const util::AsyncTaskManager::Token& token) {
|
||||||
|
if (token.cancelled()) return;
|
||||||
stopEmbeddedDaemon();
|
stopEmbeddedDaemon();
|
||||||
}).detach();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter wizard — the wizard completion handler already calls
|
// Enter wizard — the wizard completion handler already calls
|
||||||
@@ -73,6 +110,7 @@ void App::restartWizard()
|
|||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
|
|
||||||
void App::renderFirstRunWizard() {
|
void App::renderFirstRunWizard() {
|
||||||
|
auto& wizardUi = s_wizardUi;
|
||||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||||
ImGui::SetNextWindowPos(viewport->WorkPos);
|
ImGui::SetNextWindowPos(viewport->WorkPos);
|
||||||
ImGui::SetNextWindowSize(viewport->WorkSize);
|
ImGui::SetNextWindowSize(viewport->WorkSize);
|
||||||
@@ -243,15 +281,14 @@ void App::renderFirstRunWizard() {
|
|||||||
(textCol & 0x00FFFFFF) | IM_COL32(0,0,0,40), 1.0f * dp);
|
(textCol & 0x00FFFFFF) | IM_COL32(0,0,0,40), 1.0f * dp);
|
||||||
cy += 14.0f * dp;
|
cy += 14.0f * dp;
|
||||||
|
|
||||||
// Statics for appearance settings
|
float& wiz_blur_amount = wizardUi.blur_amount;
|
||||||
static float wiz_blur_amount = 1.5f;
|
bool& wiz_theme_effects = wizardUi.theme_effects;
|
||||||
static bool wiz_theme_effects = true;
|
float& wiz_ui_opacity = wizardUi.ui_opacity;
|
||||||
static float wiz_ui_opacity = 1.0f;
|
bool& wiz_low_spec = wizardUi.low_spec;
|
||||||
static bool wiz_low_spec = false;
|
bool& wiz_scanline = wizardUi.scanline;
|
||||||
static bool wiz_scanline = true;
|
std::string& wiz_balance_layout = wizardUi.balance_layout;
|
||||||
static std::string wiz_balance_layout = "classic";
|
int& wiz_language_index = wizardUi.language_index;
|
||||||
static int wiz_language_index = 0;
|
bool& wiz_appearance_init = wizardUi.appearance_init;
|
||||||
static bool wiz_appearance_init = false;
|
|
||||||
if (!wiz_appearance_init) {
|
if (!wiz_appearance_init) {
|
||||||
wiz_blur_amount = settings_->getBlurMultiplier();
|
wiz_blur_amount = settings_->getBlurMultiplier();
|
||||||
wiz_theme_effects = settings_->getThemeEffectsEnabled();
|
wiz_theme_effects = settings_->getThemeEffectsEnabled();
|
||||||
@@ -398,7 +435,7 @@ void App::renderFirstRunWizard() {
|
|||||||
|
|
||||||
// --- Low-spec mode checkbox ---
|
// --- Low-spec mode checkbox ---
|
||||||
// Snapshot for restoring settings when low-spec is turned off
|
// Snapshot for restoring settings when low-spec is turned off
|
||||||
static struct { bool valid; float blur; float uiOp; bool fx; bool scanline; } wiz_lsSnap = {};
|
WizardLowSpecSnapshot& wiz_lsSnap = wizardUi.low_spec_snapshot;
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(cx, cy));
|
ImGui::SetCursorScreenPos(ImVec2(cx, cy));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f * dp);
|
||||||
@@ -574,7 +611,7 @@ void App::renderFirstRunWizard() {
|
|||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Continue##app", ImVec2(btnW, btnH))) {
|
if (ui::material::TactileButton("Continue##app", ImVec2(btnW, btnH))) {
|
||||||
// Save appearance choices, advance to Bootstrap
|
// Save appearance choices, advance to Bootstrap
|
||||||
settings_->setAcrylicEnabled(wiz_blur_amount > 0.001f);
|
settings_->setAcrylicEnabled(wiz_blur_amount > 0.001f);
|
||||||
settings_->setAcrylicQuality(wiz_blur_amount > 0.001f
|
settings_->setAcrylicQuality(wiz_blur_amount > 0.001f
|
||||||
@@ -596,7 +633,7 @@ void App::renderFirstRunWizard() {
|
|||||||
|
|
||||||
cy += cardPad;
|
cy += cardPad;
|
||||||
// Lock card height to the tallest content ever seen
|
// Lock card height to the tallest content ever seen
|
||||||
static float card0MaxH = 0.0f;
|
float& card0MaxH = wizardUi.card0_max_h;
|
||||||
card0MaxH = std::max(card0MaxH, cy - card0Top);
|
card0MaxH = std::max(card0MaxH, cy - card0Top);
|
||||||
card0Bot = card0Top + card0MaxH;
|
card0Bot = card0Top + card0MaxH;
|
||||||
|
|
||||||
@@ -726,7 +763,7 @@ void App::renderFirstRunWizard() {
|
|||||||
float cancelBX = rightX + (colW - cancelW) * 0.5f;
|
float cancelBX = rightX + (colW - cancelW) * 0.5f;
|
||||||
ImGui::SetCursorScreenPos(ImVec2(cancelBX, cy));
|
ImGui::SetCursorScreenPos(ImVec2(cancelBX, cy));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Cancel##bs", ImVec2(cancelW, cancelH))) {
|
if (ui::material::TactileButton("Cancel##bs", ImVec2(cancelW, cancelH))) {
|
||||||
bootstrap_->cancel();
|
bootstrap_->cancel();
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
@@ -737,6 +774,8 @@ void App::renderFirstRunWizard() {
|
|||||||
auto finalProg = bootstrap_->getProgress();
|
auto finalProg = bootstrap_->getProgress();
|
||||||
if (finalProg.state == util::Bootstrap::State::Completed) {
|
if (finalProg.state == util::Bootstrap::State::Completed) {
|
||||||
bootstrap_.reset();
|
bootstrap_.reset();
|
||||||
|
// Reconcile the preserved wallet.dat against the new chain once the daemon is up.
|
||||||
|
markPostBootstrapRescanPending();
|
||||||
wizard_phase_ = WizardPhase::EncryptOffer;
|
wizard_phase_ = WizardPhase::EncryptOffer;
|
||||||
} else {
|
} else {
|
||||||
wizard_phase_ = WizardPhase::BootstrapFailed;
|
wizard_phase_ = WizardPhase::BootstrapFailed;
|
||||||
@@ -772,27 +811,22 @@ void App::renderFirstRunWizard() {
|
|||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Retry##bs", ImVec2(btnW2, btnH2))) {
|
ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
|
||||||
|
if (ui::material::TactileButton("Retry##bs", ImVec2(btnW2, btnH2))) {
|
||||||
// Stop embedded daemon before bootstrap to avoid chain data corruption
|
// Stop embedded daemon before bootstrap to avoid chain data corruption
|
||||||
if (isEmbeddedDaemonRunning()) {
|
stopDaemonForBootstrap();
|
||||||
DEBUG_LOGF("[Wizard] Stopping embedded daemon before bootstrap retry...\n");
|
|
||||||
if (rpc_ && rpc_->isConnected()) {
|
|
||||||
try { rpc_->call("stop"); } catch (...) {}
|
|
||||||
rpc_->disconnect();
|
|
||||||
}
|
|
||||||
onDisconnected("Bootstrap retry");
|
|
||||||
}
|
|
||||||
bootstrap_ = std::make_unique<util::Bootstrap>();
|
bootstrap_ = std::make_unique<util::Bootstrap>();
|
||||||
std::string dataDir = util::Platform::getDragonXDataDir();
|
std::string dataDir = util::Platform::getDragonXDataDir();
|
||||||
bootstrap_->start(dataDir);
|
bootstrap_->start(dataDir);
|
||||||
wizard_phase_ = WizardPhase::BootstrapInProgress;
|
wizard_phase_ = WizardPhase::BootstrapInProgress;
|
||||||
}
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
ImGui::PopStyleColor(3);
|
ImGui::PopStyleColor(3);
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(bx + btnW2 + 12.0f * dp, cy));
|
ImGui::SetCursorScreenPos(ImVec2(bx + btnW2 + 12.0f * dp, cy));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Skip##bsfail", ImVec2(btnW2, btnH2))) {
|
if (ui::material::TactileButton("Skip##bsfail", ImVec2(btnW2, btnH2))) {
|
||||||
wizard_phase_ = WizardPhase::EncryptOffer;
|
wizard_phase_ = WizardPhase::EncryptOffer;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
@@ -808,17 +842,23 @@ void App::renderFirstRunWizard() {
|
|||||||
if (isFocused) {
|
if (isFocused) {
|
||||||
static std::atomic<bool> s_extCached{false};
|
static std::atomic<bool> s_extCached{false};
|
||||||
static std::atomic<bool> s_checkInFlight{false};
|
static std::atomic<bool> s_checkInFlight{false};
|
||||||
static double s_extLastCheck = -10.0;
|
double& s_extLastCheck = wizardUi.external_last_check;
|
||||||
double now = ImGui::GetTime();
|
double now = ImGui::GetTime();
|
||||||
if (now - s_extLastCheck >= 2.0 && !s_checkInFlight.load()) {
|
if (now - s_extLastCheck >= 2.0 && !s_checkInFlight.load()) {
|
||||||
s_extLastCheck = now;
|
s_extLastCheck = now;
|
||||||
bool embeddedRunning = isEmbeddedDaemonRunning();
|
bool embeddedRunning = isEmbeddedDaemonRunning();
|
||||||
s_checkInFlight.store(true);
|
s_checkInFlight.store(true);
|
||||||
std::thread([embeddedRunning]() {
|
async_tasks_.submit("wizard-external-daemon-check", [embeddedRunning](const util::AsyncTaskManager::Token& token) {
|
||||||
|
if (token.cancelled()) {
|
||||||
|
s_checkInFlight.store(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
bool inUse = daemon::EmbeddedDaemon::isRpcPortInUse();
|
bool inUse = daemon::EmbeddedDaemon::isRpcPortInUse();
|
||||||
s_extCached.store(inUse && !embeddedRunning);
|
if (!token.cancelled()) {
|
||||||
|
s_extCached.store(inUse && !embeddedRunning);
|
||||||
|
}
|
||||||
s_checkInFlight.store(false);
|
s_checkInFlight.store(false);
|
||||||
}).detach();
|
});
|
||||||
}
|
}
|
||||||
externalRunning = s_extCached.load();
|
externalRunning = s_extCached.load();
|
||||||
}
|
}
|
||||||
@@ -856,22 +896,22 @@ void App::renderFirstRunWizard() {
|
|||||||
IM_COL32(220, 60, 60, 255)));
|
IM_COL32(220, 60, 60, 255)));
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255));
|
ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 255));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Stop Daemon##wiz", ImVec2(stopW, btnH2))) {
|
if (ui::material::TactileButton("Stop Daemon##wiz", ImVec2(stopW, btnH2))) {
|
||||||
wizard_stopping_external_ = true;
|
wizard_stopping_external_ = true;
|
||||||
wizard_stop_status_ = "Sending stop command...";
|
wizard_stop_status_ = "Sending stop command...";
|
||||||
if (wizard_stop_thread_.joinable()) wizard_stop_thread_.join();
|
async_tasks_.submit("wizard-stop-external-daemon", [this](const util::AsyncTaskManager::Token& token) {
|
||||||
wizard_stop_thread_ = std::thread([this]() {
|
|
||||||
auto config = rpc::Connection::autoDetectConfig();
|
auto config = rpc::Connection::autoDetectConfig();
|
||||||
if (!config.rpcuser.empty() && !config.rpcpassword.empty()) {
|
if (!config.rpcuser.empty() && !config.rpcpassword.empty()) {
|
||||||
auto tmp_rpc = std::make_unique<rpc::RPCClient>();
|
auto tmp_rpc = std::make_unique<rpc::RPCClient>();
|
||||||
if (tmp_rpc->connect(config.host, config.port,
|
if (tmp_rpc->connect(config.host, config.port,
|
||||||
config.rpcuser, config.rpcpassword)) {
|
config.rpcuser, config.rpcpassword,
|
||||||
try { tmp_rpc->call("stop"); } catch (...) {}
|
config.use_tls)) {
|
||||||
|
sendStopCommandSafely(*tmp_rpc, "Wizard external daemon stop");
|
||||||
tmp_rpc->disconnect();
|
tmp_rpc->disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wizard_stop_status_ = "Waiting for daemon to shut down...";
|
wizard_stop_status_ = "Waiting for daemon to shut down...";
|
||||||
for (int i = 0; i < 60; i++) {
|
for (int i = 0; i < 60 && !token.cancelled(); i++) {
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
if (!daemon::EmbeddedDaemon::isRpcPortInUse()) {
|
if (!daemon::EmbeddedDaemon::isRpcPortInUse()) {
|
||||||
wizard_stop_status_ = "Daemon stopped.";
|
wizard_stop_status_ = "Daemon stopped.";
|
||||||
@@ -879,6 +919,7 @@ void App::renderFirstRunWizard() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (token.cancelled()) return;
|
||||||
wizard_stop_status_ = "Daemon did not stop — try manually.";
|
wizard_stop_status_ = "Daemon did not stop — try manually.";
|
||||||
wizard_stopping_external_ = false;
|
wizard_stopping_external_ = false;
|
||||||
});
|
});
|
||||||
@@ -888,7 +929,7 @@ void App::renderFirstRunWizard() {
|
|||||||
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(bx + stopW + 12.0f * dp, cy));
|
ImGui::SetCursorScreenPos(ImVec2(bx + stopW + 12.0f * dp, cy));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Skip##extd", ImVec2(skipW2, btnH2))) {
|
if (ui::material::TactileButton("Skip##extd", ImVec2(skipW2, btnH2))) {
|
||||||
wizard_phase_ = WizardPhase::EncryptOffer;
|
wizard_phase_ = WizardPhase::EncryptOffer;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
@@ -953,21 +994,16 @@ void App::renderFirstRunWizard() {
|
|||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Download##bs", ImVec2(dlBtnW, btnH2))) {
|
ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
|
||||||
|
if (ui::material::TactileButton("Download##bs", ImVec2(dlBtnW, btnH2))) {
|
||||||
// Stop embedded daemon before bootstrap to avoid chain data corruption
|
// Stop embedded daemon before bootstrap to avoid chain data corruption
|
||||||
if (isEmbeddedDaemonRunning()) {
|
stopDaemonForBootstrap();
|
||||||
DEBUG_LOGF("[Wizard] Stopping embedded daemon before bootstrap...\n");
|
|
||||||
if (rpc_ && rpc_->isConnected()) {
|
|
||||||
try { rpc_->call("stop"); } catch (...) {}
|
|
||||||
rpc_->disconnect();
|
|
||||||
}
|
|
||||||
onDisconnected("Bootstrap");
|
|
||||||
}
|
|
||||||
bootstrap_ = std::make_unique<util::Bootstrap>();
|
bootstrap_ = std::make_unique<util::Bootstrap>();
|
||||||
std::string dataDir = util::Platform::getDragonXDataDir();
|
std::string dataDir = util::Platform::getDragonXDataDir();
|
||||||
bootstrap_->start(dataDir);
|
bootstrap_->start(dataDir);
|
||||||
wizard_phase_ = WizardPhase::BootstrapInProgress;
|
wizard_phase_ = WizardPhase::BootstrapInProgress;
|
||||||
}
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
ImGui::PopStyleColor(3);
|
ImGui::PopStyleColor(3);
|
||||||
|
|
||||||
@@ -977,23 +1013,18 @@ void App::renderFirstRunWizard() {
|
|||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnSurface()));
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnSurface()));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Mirror##bs_mirror", ImVec2(mirrorW, btnH2))) {
|
ImGui::BeginDisabled(!supportsFullNodeLifecycleActions());
|
||||||
if (isEmbeddedDaemonRunning()) {
|
if (ui::material::TactileButton("Mirror##bs_mirror", ImVec2(mirrorW, btnH2))) {
|
||||||
DEBUG_LOGF("[Wizard] Stopping embedded daemon before bootstrap (mirror)...\n");
|
stopDaemonForBootstrap();
|
||||||
if (rpc_ && rpc_->isConnected()) {
|
|
||||||
try { rpc_->call("stop"); } catch (...) {}
|
|
||||||
rpc_->disconnect();
|
|
||||||
}
|
|
||||||
onDisconnected("Bootstrap");
|
|
||||||
}
|
|
||||||
bootstrap_ = std::make_unique<util::Bootstrap>();
|
bootstrap_ = std::make_unique<util::Bootstrap>();
|
||||||
std::string dataDir = util::Platform::getDragonXDataDir();
|
std::string dataDir = util::Platform::getDragonXDataDir();
|
||||||
std::string mirrorUrl = std::string(util::Bootstrap::kMirrorUrl) + "/" + util::Bootstrap::kZipName;
|
std::string mirrorUrl = std::string(util::Bootstrap::kMirrorUrl) + "/" + util::Bootstrap::kZipName;
|
||||||
bootstrap_->start(dataDir, mirrorUrl);
|
bootstrap_->start(dataDir, mirrorUrl);
|
||||||
wizard_phase_ = WizardPhase::BootstrapInProgress;
|
wizard_phase_ = WizardPhase::BootstrapInProgress;
|
||||||
}
|
}
|
||||||
|
ImGui::EndDisabled();
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Download from mirror (bootstrap2.dragonx.is).\nUse this if the main download is slow or failing.");
|
ui::material::Tooltip("Download from mirror (bootstrap2.dragonx.is).\nUse this if the main download is slow or failing.");
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
ImGui::PopStyleColor(3);
|
ImGui::PopStyleColor(3);
|
||||||
@@ -1001,7 +1032,7 @@ void App::renderFirstRunWizard() {
|
|||||||
// --- Skip button ---
|
// --- Skip button ---
|
||||||
ImGui::SetCursorScreenPos(ImVec2(bx + dlBtnW + 8.0f * dp + mirrorW + 8.0f * dp, cy));
|
ImGui::SetCursorScreenPos(ImVec2(bx + dlBtnW + 8.0f * dp + mirrorW + 8.0f * dp, cy));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Skip##bs", ImVec2(skipW2, btnH2))) {
|
if (ui::material::TactileButton("Skip##bs", ImVec2(skipW2, btnH2))) {
|
||||||
wizard_phase_ = WizardPhase::EncryptOffer;
|
wizard_phase_ = WizardPhase::EncryptOffer;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
@@ -1012,7 +1043,7 @@ void App::renderFirstRunWizard() {
|
|||||||
|
|
||||||
cy += cardPad;
|
cy += cardPad;
|
||||||
// Lock card height to the tallest content ever seen (but not when collapsed)
|
// Lock card height to the tallest content ever seen (but not when collapsed)
|
||||||
static float card1MaxH = 0.0f;
|
float& card1MaxH = wizardUi.card1_max_h;
|
||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
card1Bot = card1Top + (cy - card1Top);
|
card1Bot = card1Top + (cy - card1Top);
|
||||||
} else {
|
} else {
|
||||||
@@ -1037,7 +1068,7 @@ void App::renderFirstRunWizard() {
|
|||||||
// Pre-start daemon when encrypt card becomes focused so it's ready
|
// Pre-start daemon when encrypt card becomes focused so it's ready
|
||||||
// by the time the user finishes typing their passphrase
|
// by the time the user finishes typing their passphrase
|
||||||
if (isFocused) {
|
if (isFocused) {
|
||||||
static bool wiz_daemon_prestarted = false;
|
bool& wiz_daemon_prestarted = wizardUi.daemon_prestarted;
|
||||||
if (!wiz_daemon_prestarted) {
|
if (!wiz_daemon_prestarted) {
|
||||||
wiz_daemon_prestarted = true;
|
wiz_daemon_prestarted = true;
|
||||||
if (!state_.connected && isUsingEmbeddedDaemon() && !isEmbeddedDaemonRunning()) {
|
if (!state_.connected && isUsingEmbeddedDaemon() && !isEmbeddedDaemonRunning()) {
|
||||||
@@ -1096,7 +1127,7 @@ void App::renderFirstRunWizard() {
|
|||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::ColorConvertU32ToFloat4(ui::material::PrimaryVariant()));
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Continue##encok", ImVec2(btnW2, btnH2))) {
|
if (ui::material::TactileButton("Continue##encok", ImVec2(btnW2, btnH2))) {
|
||||||
wizard_phase_ = WizardPhase::Done;
|
wizard_phase_ = WizardPhase::Done;
|
||||||
settings_->setWizardCompleted(true);
|
settings_->setWizardCompleted(true);
|
||||||
settings_->save();
|
settings_->save();
|
||||||
@@ -1254,10 +1285,24 @@ void App::renderFirstRunWizard() {
|
|||||||
cy += captionFont->LegacySize + 6.0f * dp;
|
cy += captionFont->LegacySize + 6.0f * dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Warn + block if the passphrase has leading/trailing whitespace. Silently trimming it would
|
||||||
|
// change the passphrase the user believes they set and lock them out on the next unlock.
|
||||||
|
bool passEdgeSpace = false;
|
||||||
|
if (size_t pl = strlen(encrypt_pass_buf_)) {
|
||||||
|
char a = encrypt_pass_buf_[0], b = encrypt_pass_buf_[pl - 1];
|
||||||
|
passEdgeSpace = (a == ' ' || a == '\t' || b == ' ' || b == '\t');
|
||||||
|
}
|
||||||
|
if (passEdgeSpace) {
|
||||||
|
dl->AddText(captionFont, captionFont->LegacySize, ImVec2(cx, cy),
|
||||||
|
ui::material::Error(), "Passphrase has leading/trailing spaces — remove them");
|
||||||
|
cy += captionFont->LegacySize + 6.0f * dp;
|
||||||
|
}
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
{
|
{
|
||||||
bool passValid = strlen(encrypt_pass_buf_) >= 8 &&
|
bool passValid = strlen(encrypt_pass_buf_) >= 8 &&
|
||||||
strcmp(encrypt_pass_buf_, encrypt_confirm_buf_) == 0;
|
strcmp(encrypt_pass_buf_, encrypt_confirm_buf_) == 0 &&
|
||||||
|
!passEdgeSpace;
|
||||||
// PIN is optional: if entered, must be valid + confirmed
|
// PIN is optional: if entered, must be valid + confirmed
|
||||||
std::string pinStr(wizard_pin_buf_);
|
std::string pinStr(wizard_pin_buf_);
|
||||||
bool pinEntered = !pinStr.empty();
|
bool pinEntered = !pinStr.empty();
|
||||||
@@ -1279,12 +1324,11 @@ void App::renderFirstRunWizard() {
|
|||||||
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(ui::material::OnPrimary()));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
ImGui::BeginDisabled(!canEncrypt);
|
ImGui::BeginDisabled(!canEncrypt);
|
||||||
if (ImGui::Button("Encrypt & Continue##wiz", ImVec2(encBtnW, btnH2))) {
|
if (ui::material::TactileButton("Encrypt & Continue##wiz", ImVec2(encBtnW, btnH2))) {
|
||||||
// Save passphrase + optional PIN for background processing
|
// Save passphrase + optional PIN for background processing
|
||||||
deferred_encrypt_passphrase_ = std::string(encrypt_pass_buf_);
|
wallet_security_.beginDeferredEncryption(
|
||||||
if (pinEntered && pinOk)
|
std::string(encrypt_pass_buf_),
|
||||||
deferred_encrypt_pin_ = pinStr;
|
(pinEntered && pinOk) ? pinStr : std::string());
|
||||||
deferred_encrypt_pending_ = true;
|
|
||||||
|
|
||||||
// Clear sensitive buffers
|
// Clear sensitive buffers
|
||||||
memset(encrypt_pass_buf_, 0, sizeof(encrypt_pass_buf_));
|
memset(encrypt_pass_buf_, 0, sizeof(encrypt_pass_buf_));
|
||||||
@@ -1294,7 +1338,10 @@ void App::renderFirstRunWizard() {
|
|||||||
|
|
||||||
// Start daemon + finish wizard immediately
|
// Start daemon + finish wizard immediately
|
||||||
if (!isEmbeddedDaemonRunning() && isUsingEmbeddedDaemon()) {
|
if (!isEmbeddedDaemonRunning() && isUsingEmbeddedDaemon()) {
|
||||||
startEmbeddedDaemon();
|
if (!startEmbeddedDaemon()) {
|
||||||
|
ui::Notifications::instance().warning(
|
||||||
|
TR("wizard_daemon_start_failed"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tryConnect();
|
tryConnect();
|
||||||
wizard_phase_ = WizardPhase::Done;
|
wizard_phase_ = WizardPhase::Done;
|
||||||
@@ -1308,14 +1355,25 @@ void App::renderFirstRunWizard() {
|
|||||||
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(bx + encBtnW + 12.0f * dp, cy));
|
ImGui::SetCursorScreenPos(ImVec2(bx + encBtnW + 12.0f * dp, cy));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 8.0f * dp);
|
||||||
if (ImGui::Button("Skip##enc", ImVec2(skipW2, btnH2))) {
|
if (ui::material::TactileButton("Skip##enc", ImVec2(skipW2, btnH2))) {
|
||||||
wizard_phase_ = WizardPhase::Done;
|
static bool s_skipEncConfirm = false;
|
||||||
settings_->setWizardCompleted(true);
|
if (!s_skipEncConfirm) {
|
||||||
settings_->save();
|
// Skipping stores private keys UNENCRYPTED — require a confirming second click.
|
||||||
if (!isEmbeddedDaemonRunning() && isUsingEmbeddedDaemon()) {
|
s_skipEncConfirm = true;
|
||||||
startEmbeddedDaemon();
|
encrypt_status_ = "Continue WITHOUT encryption? Keys will be stored unencrypted — click Skip again to confirm.";
|
||||||
|
} else {
|
||||||
|
s_skipEncConfirm = false;
|
||||||
|
wizard_phase_ = WizardPhase::Done;
|
||||||
|
settings_->setWizardCompleted(true);
|
||||||
|
settings_->save();
|
||||||
|
if (!isEmbeddedDaemonRunning() && isUsingEmbeddedDaemon()) {
|
||||||
|
if (!startEmbeddedDaemon()) {
|
||||||
|
ui::Notifications::instance().warning(
|
||||||
|
TR("wizard_daemon_start_failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tryConnect();
|
||||||
}
|
}
|
||||||
tryConnect();
|
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
cy += btnH2;
|
cy += btnH2;
|
||||||
|
|||||||
173
src/chat/chat_crypto.cpp
Normal file
173
src/chat/chat_crypto.cpp
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// DragonX Wallet - HushChat crypto primitives (implementation).
|
||||||
|
|
||||||
|
#include "chat_crypto.h"
|
||||||
|
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Local constants tied to the libsodium primitive. (The dev-only chat_fixture_tooling.h
|
||||||
|
// declares equivalents, but that header is not linked into the app.)
|
||||||
|
constexpr std::size_t kStreamHeaderBytes = 24; // crypto_secretstream_xchacha20poly1305_HEADERBYTES
|
||||||
|
constexpr std::size_t kStreamABytes = 17; // crypto_secretstream_xchacha20poly1305_ABYTES
|
||||||
|
|
||||||
|
static_assert(kChatKeyBytes == crypto_kx_PUBLICKEYBYTES, "kx public key size mismatch");
|
||||||
|
static_assert(kChatKeyBytes == crypto_kx_SECRETKEYBYTES, "kx secret key size mismatch");
|
||||||
|
|
||||||
|
// Decode exactly outLen bytes from a lowercase/uppercase hex string; reject any other length.
|
||||||
|
bool hexToFixed(const std::string& hex, unsigned char* out, std::size_t outLen) {
|
||||||
|
if (hex.size() != outLen * 2) return false;
|
||||||
|
std::size_t binLen = 0;
|
||||||
|
if (sodium_hex2bin(out, outLen, hex.data(), hex.size(), nullptr, &binLen, nullptr) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return binLen == outLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a variable-length hex string into bytes.
|
||||||
|
bool hexToBytes(const std::string& hex, std::vector<unsigned char>& out) {
|
||||||
|
if (hex.empty() || (hex.size() % 2) != 0) return false;
|
||||||
|
out.resize(hex.size() / 2);
|
||||||
|
std::size_t binLen = 0;
|
||||||
|
if (sodium_hex2bin(out.data(), out.size(), hex.data(), hex.size(), nullptr, &binLen, nullptr) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
out.resize(binLen);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string bytesToHex(const unsigned char* bytes, std::size_t n) {
|
||||||
|
std::string hex(n * 2 + 1, '\0');
|
||||||
|
sodium_bin2hex(&hex[0], hex.size(), bytes, n);
|
||||||
|
hex.resize(n * 2); // drop the NUL sodium_bin2hex appends
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
const char* chatCryptoStatusName(ChatCryptoStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case ChatCryptoStatus::Ok: return "Ok";
|
||||||
|
case ChatCryptoStatus::SodiumInitFailed: return "SodiumInitFailed";
|
||||||
|
case ChatCryptoStatus::BadPeerKey: return "BadPeerKey";
|
||||||
|
case ChatCryptoStatus::BadHeaderHex: return "BadHeaderHex";
|
||||||
|
case ChatCryptoStatus::BadCiphertextHex: return "BadCiphertextHex";
|
||||||
|
case ChatCryptoStatus::CiphertextTooShort: return "CiphertextTooShort";
|
||||||
|
case ChatCryptoStatus::SessionKeyFailed: return "SessionKeyFailed";
|
||||||
|
case ChatCryptoStatus::EncryptFailed: return "EncryptFailed";
|
||||||
|
case ChatCryptoStatus::DecryptFailed: return "DecryptFailed";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
void wipeChatKeyPair(ChatKeyPair& keys) {
|
||||||
|
sodium_memzero(keys.public_key.data(), keys.public_key.size());
|
||||||
|
sodium_memzero(keys.secret_key.data(), keys.secret_key.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatCryptoStatus encryptOutgoing(const ChatKeyPair& mine,
|
||||||
|
const std::string& peerPublicKeyHex,
|
||||||
|
const std::string& plaintext,
|
||||||
|
std::string& outStreamHeaderHex,
|
||||||
|
std::string& outCiphertextHex) {
|
||||||
|
static_assert(kStreamHeaderBytes == crypto_secretstream_xchacha20poly1305_HEADERBYTES, "");
|
||||||
|
static_assert(kStreamABytes == crypto_secretstream_xchacha20poly1305_ABYTES, "");
|
||||||
|
|
||||||
|
if (sodium_init() < 0) return ChatCryptoStatus::SodiumInitFailed;
|
||||||
|
|
||||||
|
unsigned char peerPk[crypto_kx_PUBLICKEYBYTES];
|
||||||
|
if (!hexToFixed(peerPublicKeyHex, peerPk, sizeof peerPk)) return ChatCryptoStatus::BadPeerKey;
|
||||||
|
|
||||||
|
unsigned char rx[crypto_kx_SESSIONKEYBYTES];
|
||||||
|
unsigned char tx[crypto_kx_SESSIONKEYBYTES];
|
||||||
|
if (crypto_kx_server_session_keys(rx, tx, mine.public_key.data(), mine.secret_key.data(), peerPk) != 0) {
|
||||||
|
sodium_memzero(rx, sizeof rx);
|
||||||
|
sodium_memzero(tx, sizeof tx);
|
||||||
|
return ChatCryptoStatus::SessionKeyFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto_secretstream_xchacha20poly1305_state state;
|
||||||
|
unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
|
||||||
|
ChatCryptoStatus result = ChatCryptoStatus::EncryptFailed;
|
||||||
|
|
||||||
|
if (crypto_secretstream_xchacha20poly1305_init_push(&state, header, tx) == 0) {
|
||||||
|
std::vector<unsigned char> ciphertext(plaintext.size() + crypto_secretstream_xchacha20poly1305_ABYTES);
|
||||||
|
unsigned long long ctLen = 0;
|
||||||
|
// Only report Ok if the push actually succeeded — otherwise ctLen stays 0 and we would
|
||||||
|
// ship a valid header with an empty ciphertext.
|
||||||
|
if (crypto_secretstream_xchacha20poly1305_push(
|
||||||
|
&state, ciphertext.data(), &ctLen,
|
||||||
|
reinterpret_cast<const unsigned char*>(plaintext.data()), plaintext.size(),
|
||||||
|
nullptr, 0, crypto_secretstream_xchacha20poly1305_TAG_FINAL) == 0) {
|
||||||
|
outStreamHeaderHex = bytesToHex(header, sizeof header);
|
||||||
|
outCiphertextHex = bytesToHex(ciphertext.data(), static_cast<std::size_t>(ctLen));
|
||||||
|
result = ChatCryptoStatus::Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sodium_memzero(rx, sizeof rx);
|
||||||
|
sodium_memzero(tx, sizeof tx);
|
||||||
|
sodium_memzero(&state, sizeof state);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatCryptoStatus decryptIncoming(const ChatKeyPair& mine,
|
||||||
|
const std::string& peerPublicKeyHex,
|
||||||
|
const std::string& streamHeaderHex,
|
||||||
|
const std::string& ciphertextHex,
|
||||||
|
std::string& outPlaintext) {
|
||||||
|
if (sodium_init() < 0) return ChatCryptoStatus::SodiumInitFailed;
|
||||||
|
|
||||||
|
unsigned char peerPk[crypto_kx_PUBLICKEYBYTES];
|
||||||
|
if (!hexToFixed(peerPublicKeyHex, peerPk, sizeof peerPk)) return ChatCryptoStatus::BadPeerKey;
|
||||||
|
|
||||||
|
unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
|
||||||
|
if (!hexToFixed(streamHeaderHex, header, sizeof header)) return ChatCryptoStatus::BadHeaderHex;
|
||||||
|
|
||||||
|
std::vector<unsigned char> ciphertext;
|
||||||
|
if (!hexToBytes(ciphertextHex, ciphertext)) return ChatCryptoStatus::BadCiphertextHex;
|
||||||
|
// Guard the size_t subtraction below (a ciphertext shorter than the auth tag can't be
|
||||||
|
// authentic). Exactly ABYTES is the valid empty-plaintext case, so it round-trips
|
||||||
|
// symmetrically with encryptOutgoing (message-content policy belongs to the caller).
|
||||||
|
if (ciphertext.size() < crypto_secretstream_xchacha20poly1305_ABYTES) {
|
||||||
|
return ChatCryptoStatus::CiphertextTooShort;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char rx[crypto_kx_SESSIONKEYBYTES];
|
||||||
|
unsigned char tx[crypto_kx_SESSIONKEYBYTES];
|
||||||
|
if (crypto_kx_client_session_keys(rx, tx, mine.public_key.data(), mine.secret_key.data(), peerPk) != 0) {
|
||||||
|
sodium_memzero(rx, sizeof rx);
|
||||||
|
sodium_memzero(tx, sizeof tx);
|
||||||
|
return ChatCryptoStatus::SessionKeyFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
crypto_secretstream_xchacha20poly1305_state state;
|
||||||
|
ChatCryptoStatus result = ChatCryptoStatus::DecryptFailed;
|
||||||
|
|
||||||
|
if (crypto_secretstream_xchacha20poly1305_init_pull(&state, header, rx) == 0) {
|
||||||
|
std::vector<unsigned char> plain(ciphertext.size() - crypto_secretstream_xchacha20poly1305_ABYTES);
|
||||||
|
unsigned long long plainLen = 0;
|
||||||
|
unsigned char tag = 0;
|
||||||
|
if (crypto_secretstream_xchacha20poly1305_pull(
|
||||||
|
&state, plain.data(), &plainLen, &tag,
|
||||||
|
ciphertext.data(), ciphertext.size(), nullptr, 0) == 0 &&
|
||||||
|
tag == crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
|
||||||
|
outPlaintext.assign(reinterpret_cast<const char*>(plain.data()),
|
||||||
|
static_cast<std::size_t>(plainLen));
|
||||||
|
result = ChatCryptoStatus::Ok;
|
||||||
|
}
|
||||||
|
sodium_memzero(plain.data(), plain.size()); // wipe the decrypted scratch
|
||||||
|
}
|
||||||
|
|
||||||
|
sodium_memzero(rx, sizeof rx);
|
||||||
|
sodium_memzero(tx, sizeof tx);
|
||||||
|
sodium_memzero(&state, sizeof state);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
65
src/chat/chat_crypto.h
Normal file
65
src/chat/chat_crypto.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// DragonX Wallet - HushChat crypto primitives.
|
||||||
|
//
|
||||||
|
// crypto_kx (X25519) session-key agreement + crypto_secretstream_xchacha20poly1305
|
||||||
|
// message encryption, byte-exact per the HushChat wire format so DragonX interoperates
|
||||||
|
// with SilentDragonXLite. See docs/_archive/contacts-chat-phase1-detail-2026-07-05.md
|
||||||
|
// (Appendix A.3/A.4). Pure crypto — no gating, no I/O; the feature gate lives at the
|
||||||
|
// service layer. NEVER logs plaintext, ciphertext, keys, or session material.
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
// crypto_kx key sizes (== crypto_kx_PUBLICKEYBYTES / SECRETKEYBYTES == 32).
|
||||||
|
constexpr std::size_t kChatKeyBytes = 32;
|
||||||
|
|
||||||
|
using ChatPublicKey = std::array<unsigned char, kChatKeyBytes>;
|
||||||
|
using ChatSecretKey = std::array<unsigned char, kChatKeyBytes>;
|
||||||
|
|
||||||
|
struct ChatKeyPair {
|
||||||
|
ChatPublicKey public_key{};
|
||||||
|
ChatSecretKey secret_key{};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ChatCryptoStatus {
|
||||||
|
Ok,
|
||||||
|
SodiumInitFailed,
|
||||||
|
BadPeerKey, // peer public-key hex missing / wrong length / not hex
|
||||||
|
BadHeaderHex, // secretstream header hex missing / wrong length / not hex
|
||||||
|
BadCiphertextHex, // ciphertext hex malformed
|
||||||
|
CiphertextTooShort, // ciphertext shorter than the auth tag
|
||||||
|
SessionKeyFailed, // crypto_kx_*_session_keys rejected the peer key
|
||||||
|
EncryptFailed,
|
||||||
|
DecryptFailed // init_pull / pull / tag mismatch — the single neutral auth failure
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* chatCryptoStatusName(ChatCryptoStatus status);
|
||||||
|
|
||||||
|
// Encrypt `plaintext` addressed to peer `peerPublicKeyHex` (64 lowercase hex chars).
|
||||||
|
// Sender takes the crypto_kx "server" role (server_tx), matching SDXL's send path.
|
||||||
|
// Outputs the secretstream header hex (the memo "e" field) and the ciphertext hex
|
||||||
|
// (the payload memo). Returns Ok on success.
|
||||||
|
ChatCryptoStatus encryptOutgoing(const ChatKeyPair& mine,
|
||||||
|
const std::string& peerPublicKeyHex,
|
||||||
|
const std::string& plaintext,
|
||||||
|
std::string& outStreamHeaderHex,
|
||||||
|
std::string& outCiphertextHex);
|
||||||
|
|
||||||
|
// Decrypt an incoming message addressed to us from peer `peerPublicKeyHex`.
|
||||||
|
// Receiver takes the crypto_kx "client" role (client_rx), matching SDXL's receive path.
|
||||||
|
// Requires the memo "e" (streamHeaderHex) and the payload ciphertext hex. Enforces the
|
||||||
|
// Poly1305 auth tag AND that the stream tag is TAG_FINAL. Returns Ok + fills outPlaintext.
|
||||||
|
ChatCryptoStatus decryptIncoming(const ChatKeyPair& mine,
|
||||||
|
const std::string& peerPublicKeyHex,
|
||||||
|
const std::string& streamHeaderHex,
|
||||||
|
const std::string& ciphertextHex,
|
||||||
|
std::string& outPlaintext);
|
||||||
|
|
||||||
|
// Zero both key arrays (call when discarding an identity's keys).
|
||||||
|
void wipeChatKeyPair(ChatKeyPair& keys);
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
330
src/chat/chat_database.cpp
Normal file
330
src/chat/chat_database.cpp
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
// DragonX Wallet - HushChat persistent message store (implementation).
|
||||||
|
|
||||||
|
#include "chat_database.h"
|
||||||
|
|
||||||
|
#include "../util/logger.h"
|
||||||
|
#include "../util/platform.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <sodium.h>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Domain-separated KDF contexts (used as the keyed-BLAKE2b key, like chat_identity). Both lengths
|
||||||
|
// sit inside crypto_generichash's key-length bounds. Bumping a context rotates that derivation.
|
||||||
|
constexpr char kStorageKeyContext[] = "DragonX-HushChat-Storage-v1";
|
||||||
|
constexpr char kWalletTagContext[] = "DragonX-HushChat-WalletId-v1";
|
||||||
|
constexpr std::size_t kStorageKeyContextLen = sizeof(kStorageKeyContext) - 1;
|
||||||
|
constexpr std::size_t kWalletTagContextLen = sizeof(kWalletTagContext) - 1;
|
||||||
|
|
||||||
|
std::string toHex(const unsigned char* data, std::size_t len)
|
||||||
|
{
|
||||||
|
static const char* kHex = "0123456789abcdef";
|
||||||
|
std::string out;
|
||||||
|
out.reserve(len * 2);
|
||||||
|
for (std::size_t i = 0; i < len; ++i) {
|
||||||
|
out.push_back(kHex[data[i] >> 4]);
|
||||||
|
out.push_back(kHex[data[i] & 0x0F]);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyed-BLAKE2b: out = generichash(in=secret, key=context). Deterministic, so the same seed always
|
||||||
|
// derives the same storage key + wallet tag across sessions.
|
||||||
|
bool deriveKeyed(const std::string& secret, const char* context, std::size_t contextLen,
|
||||||
|
unsigned char* out, std::size_t outLen)
|
||||||
|
{
|
||||||
|
return crypto_generichash(out, outLen,
|
||||||
|
reinterpret_cast<const unsigned char*>(secret.data()), secret.size(),
|
||||||
|
reinterpret_cast<const unsigned char*>(context), contextLen) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string associatedData(const std::string& walletTag)
|
||||||
|
{
|
||||||
|
return std::string("obsidian-dragon-hushchat-v1:") + walletTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ChatDatabase::ChatDatabase() : database_path_(defaultDatabasePath()) {}
|
||||||
|
ChatDatabase::ChatDatabase(std::string databasePath) : database_path_(std::move(databasePath)) {}
|
||||||
|
|
||||||
|
ChatDatabase::~ChatDatabase()
|
||||||
|
{
|
||||||
|
lock();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ChatDatabase::defaultDatabasePath()
|
||||||
|
{
|
||||||
|
return (fs::path(util::Platform::getConfigDir()) / "chat_messages.sqlite").string();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatDatabase::unlockWithSecret(const std::string& secret)
|
||||||
|
{
|
||||||
|
if (sodium_init() < 0) return false;
|
||||||
|
|
||||||
|
if (!deriveKeyed(secret, kStorageKeyContext, kStorageKeyContextLen, key_.data(), key_.size()))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
unsigned char tag[32];
|
||||||
|
if (!deriveKeyed(secret, kWalletTagContext, kWalletTagContextLen, tag, sizeof(tag))) {
|
||||||
|
sodium_memzero(key_.data(), key_.size());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
wallet_tag_ = toHex(tag, sizeof(tag));
|
||||||
|
sodium_memzero(tag, sizeof(tag));
|
||||||
|
key_ready_ = true;
|
||||||
|
|
||||||
|
if (!ensureOpen()) {
|
||||||
|
lock();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatDatabase::lock()
|
||||||
|
{
|
||||||
|
sodium_memzero(key_.data(), key_.size());
|
||||||
|
key_ready_ = false;
|
||||||
|
wallet_tag_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatDatabase::append(const ChatMessage& message)
|
||||||
|
{
|
||||||
|
if (!key_ready_ || !ensureOpen()) return false;
|
||||||
|
|
||||||
|
std::vector<unsigned char> nonce;
|
||||||
|
std::vector<unsigned char> cipher;
|
||||||
|
std::string plain = serialize(message); // full plaintext (decrypted body + metadata)
|
||||||
|
const bool encrypted = encrypt(plain, nonce, cipher);
|
||||||
|
if (!plain.empty()) sodium_memzero(&plain[0], plain.size()); // don't leave it on the heap
|
||||||
|
if (!encrypted) return false;
|
||||||
|
|
||||||
|
const std::string dedup = dedupHash(message.txid, message.payload_position);
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt = nullptr;
|
||||||
|
if (sqlite3_prepare_v2(db_,
|
||||||
|
"INSERT OR IGNORE INTO chat_messages (wallet_tag, dedup_hash, nonce, payload) "
|
||||||
|
"VALUES (?, ?, ?, ?)",
|
||||||
|
-1, &stmt, nullptr) != SQLITE_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sqlite3_bind_text(stmt, 1, wallet_tag_.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_text(stmt, 2, dedup.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_blob(stmt, 3, nonce.data(), static_cast<int>(nonce.size()), SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_blob(stmt, 4, cipher.data(), static_cast<int>(cipher.size()), SQLITE_TRANSIENT);
|
||||||
|
const bool done = sqlite3_step(stmt) == SQLITE_DONE;
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
if (!done) return false;
|
||||||
|
return sqlite3_changes(db_) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ChatMessage> ChatDatabase::load()
|
||||||
|
{
|
||||||
|
std::vector<ChatMessage> out;
|
||||||
|
if (!key_ready_ || !ensureOpen()) return out;
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt = nullptr;
|
||||||
|
if (sqlite3_prepare_v2(db_,
|
||||||
|
"SELECT nonce, payload FROM chat_messages WHERE wallet_tag = ? ORDER BY rowid",
|
||||||
|
-1, &stmt, nullptr) != SQLITE_OK) {
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
sqlite3_bind_text(stmt, 1, wallet_tag_.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
|
const auto* noncePtr = static_cast<const unsigned char*>(sqlite3_column_blob(stmt, 0));
|
||||||
|
const int nonceLen = sqlite3_column_bytes(stmt, 0);
|
||||||
|
const auto* cipherPtr = static_cast<const unsigned char*>(sqlite3_column_blob(stmt, 1));
|
||||||
|
const int cipherLen = sqlite3_column_bytes(stmt, 1);
|
||||||
|
if (!noncePtr || !cipherPtr) continue;
|
||||||
|
|
||||||
|
std::vector<unsigned char> nonce(noncePtr, noncePtr + nonceLen);
|
||||||
|
std::vector<unsigned char> cipher(cipherPtr, cipherPtr + cipherLen);
|
||||||
|
std::string plain;
|
||||||
|
if (!decrypt(nonce, cipher, plain)) continue; // wrong wallet / tampered — skip
|
||||||
|
|
||||||
|
ChatMessage message;
|
||||||
|
if (deserialize(plain, message)) out.push_back(std::move(message));
|
||||||
|
sodium_memzero(&plain[0], plain.size());
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatDatabase::clearWallet()
|
||||||
|
{
|
||||||
|
if (wallet_tag_.empty() || !ensureOpen()) return;
|
||||||
|
sqlite3_stmt* stmt = nullptr;
|
||||||
|
if (sqlite3_prepare_v2(db_, "DELETE FROM chat_messages WHERE wallet_tag = ?", -1, &stmt, nullptr)
|
||||||
|
!= SQLITE_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sqlite3_bind_text(stmt, 1, wallet_tag_.c_str(), -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_step(stmt);
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatDatabase::ensureOpen()
|
||||||
|
{
|
||||||
|
if (db_) return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs::path path(database_path_);
|
||||||
|
if (!path.parent_path().empty()) fs::create_directories(path.parent_path());
|
||||||
|
} catch (const std::exception& exception) {
|
||||||
|
DEBUG_LOGF("Failed to create chat database directory: %s\n", exception.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3* openedDb = nullptr;
|
||||||
|
if (sqlite3_open(database_path_.c_str(), &openedDb) != SQLITE_OK) {
|
||||||
|
DEBUG_LOGF("Failed to open chat database: %s\n",
|
||||||
|
openedDb ? sqlite3_errmsg(openedDb) : "unknown error");
|
||||||
|
if (openedDb) sqlite3_close(openedDb);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ = openedDb;
|
||||||
|
sqlite3_busy_timeout(db_, 2000);
|
||||||
|
exec("PRAGMA journal_mode=WAL");
|
||||||
|
exec("PRAGMA synchronous=NORMAL");
|
||||||
|
|
||||||
|
if (!createSchema()) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatDatabase::exec(const char* sql)
|
||||||
|
{
|
||||||
|
if (!db_) return false;
|
||||||
|
char* error = nullptr;
|
||||||
|
if (sqlite3_exec(db_, sql, nullptr, nullptr, &error) != SQLITE_OK) {
|
||||||
|
DEBUG_LOGF("Chat database SQL error: %s\n", error ? error : sqlite3_errmsg(db_));
|
||||||
|
if (error) sqlite3_free(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatDatabase::createSchema()
|
||||||
|
{
|
||||||
|
return exec("CREATE TABLE IF NOT EXISTS chat_messages ("
|
||||||
|
"wallet_tag TEXT NOT NULL, "
|
||||||
|
"dedup_hash TEXT NOT NULL, "
|
||||||
|
"nonce BLOB NOT NULL, "
|
||||||
|
"payload BLOB NOT NULL, "
|
||||||
|
"PRIMARY KEY (wallet_tag, dedup_hash))");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ChatDatabase::dedupHash(const std::string& txid, std::size_t position) const
|
||||||
|
{
|
||||||
|
const std::string input = txid + ":" + std::to_string(position);
|
||||||
|
unsigned char hash[32];
|
||||||
|
crypto_generichash(hash, sizeof(hash),
|
||||||
|
reinterpret_cast<const unsigned char*>(input.data()), input.size(),
|
||||||
|
key_.data(), key_.size()); // keyed by the storage key → txid stays private
|
||||||
|
return toHex(hash, sizeof(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ChatDatabase::serialize(const ChatMessage& message) const
|
||||||
|
{
|
||||||
|
nlohmann::json json;
|
||||||
|
json["d"] = static_cast<int>(message.direction);
|
||||||
|
json["k"] = static_cast<int>(message.kind);
|
||||||
|
json["txid"] = message.txid;
|
||||||
|
json["cid"] = message.conversation_id;
|
||||||
|
json["z"] = message.peer_zaddr;
|
||||||
|
json["p"] = message.peer_public_key_hex;
|
||||||
|
json["b"] = message.body;
|
||||||
|
json["ts"] = message.timestamp;
|
||||||
|
json["pos"] = static_cast<std::uint64_t>(message.payload_position);
|
||||||
|
json["dl"] = static_cast<int>(message.delivery);
|
||||||
|
return json.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatDatabase::deserialize(const std::string& json, ChatMessage& out) const
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const auto parsed = nlohmann::json::parse(json);
|
||||||
|
out.direction = static_cast<ChatDirection>(parsed.value("d", 0));
|
||||||
|
out.kind = static_cast<ChatMessageKind>(parsed.value("k", 0));
|
||||||
|
out.txid = parsed.value("txid", std::string());
|
||||||
|
out.conversation_id = parsed.value("cid", std::string());
|
||||||
|
out.peer_zaddr = parsed.value("z", std::string());
|
||||||
|
out.peer_public_key_hex = parsed.value("p", std::string());
|
||||||
|
out.body = parsed.value("b", std::string());
|
||||||
|
out.timestamp = parsed.value("ts", static_cast<std::int64_t>(0));
|
||||||
|
out.payload_position = static_cast<std::size_t>(parsed.value("pos", static_cast<std::uint64_t>(0)));
|
||||||
|
out.delivery = static_cast<ChatDelivery>(parsed.value("dl", 0)); // old rows → Sent
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatDatabase::encrypt(const std::string& plain,
|
||||||
|
std::vector<unsigned char>& nonce,
|
||||||
|
std::vector<unsigned char>& cipher) const
|
||||||
|
{
|
||||||
|
if (!key_ready_) return false;
|
||||||
|
nonce.resize(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
||||||
|
randombytes_buf(nonce.data(), nonce.size());
|
||||||
|
|
||||||
|
const std::string ad = associatedData(wallet_tag_);
|
||||||
|
cipher.resize(plain.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES);
|
||||||
|
unsigned long long cipherLen = 0;
|
||||||
|
if (crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||||
|
cipher.data(), &cipherLen,
|
||||||
|
reinterpret_cast<const unsigned char*>(plain.data()), plain.size(),
|
||||||
|
reinterpret_cast<const unsigned char*>(ad.data()), ad.size(),
|
||||||
|
nullptr, nonce.data(), key_.data()) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cipher.resize(static_cast<std::size_t>(cipherLen));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatDatabase::decrypt(const std::vector<unsigned char>& nonce,
|
||||||
|
const std::vector<unsigned char>& cipher,
|
||||||
|
std::string& plain) const
|
||||||
|
{
|
||||||
|
if (!key_ready_) return false;
|
||||||
|
if (nonce.size() != crypto_aead_xchacha20poly1305_ietf_NPUBBYTES) return false;
|
||||||
|
if (cipher.size() < crypto_aead_xchacha20poly1305_ietf_ABYTES) return false;
|
||||||
|
|
||||||
|
const std::string ad = associatedData(wallet_tag_);
|
||||||
|
std::vector<unsigned char> out(cipher.size());
|
||||||
|
unsigned long long outLen = 0;
|
||||||
|
if (crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||||
|
out.data(), &outLen, nullptr,
|
||||||
|
cipher.data(), cipher.size(),
|
||||||
|
reinterpret_cast<const unsigned char*>(ad.data()), ad.size(),
|
||||||
|
nonce.data(), key_.data()) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
plain.assign(reinterpret_cast<const char*>(out.data()), static_cast<std::size_t>(outLen));
|
||||||
|
sodium_memzero(out.data(), out.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatDatabase::close()
|
||||||
|
{
|
||||||
|
if (db_) {
|
||||||
|
sqlite3_close(db_);
|
||||||
|
db_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
74
src/chat/chat_database.h
Normal file
74
src/chat/chat_database.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// DragonX Wallet - HushChat persistent message store (Phase 2).
|
||||||
|
//
|
||||||
|
// Sqlite-backed, encrypted at rest with a SEED-DERIVED key (no wallet passphrase). Every record —
|
||||||
|
// message bodies, peer z-addresses, threading (conversation id), and timestamps — is AEAD-encrypted
|
||||||
|
// under a key derived from the wallet's own seed secret (the same secret used for the chat
|
||||||
|
// identity), and even the per-message dedup key is a KEYED hash of the txid — so the database
|
||||||
|
// reveals nothing about your conversations to disk-level access without the seed. Rows are
|
||||||
|
// partitioned by a seed-derived wallet tag so one file can hold several wallets, each readable only
|
||||||
|
// with its own seed. Not thread-safe — drive from the main thread. Mirrors the lifecycle of
|
||||||
|
// data::TransactionHistoryCache.
|
||||||
|
|
||||||
|
#include "chat_message.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct sqlite3;
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
class ChatDatabase {
|
||||||
|
public:
|
||||||
|
ChatDatabase();
|
||||||
|
explicit ChatDatabase(std::string databasePath);
|
||||||
|
~ChatDatabase();
|
||||||
|
|
||||||
|
ChatDatabase(const ChatDatabase&) = delete;
|
||||||
|
ChatDatabase& operator=(const ChatDatabase&) = delete;
|
||||||
|
|
||||||
|
static std::string defaultDatabasePath();
|
||||||
|
|
||||||
|
// Derive the storage key + wallet tag from the wallet's seed secret and open the DB. The caller
|
||||||
|
// still owns and must wipe `secret`. Returns false on sodium/db failure (DB then stays locked).
|
||||||
|
bool unlockWithSecret(const std::string& secret);
|
||||||
|
void lock(); // wipe the key material (DB handle stays open); load()/append() then no-op
|
||||||
|
bool hasKey() const { return key_ready_; }
|
||||||
|
|
||||||
|
// Persist one message (INSERT OR IGNORE, deduped by a keyed hash of txid+payload_position).
|
||||||
|
// Returns true if newly inserted; false on duplicate or while locked.
|
||||||
|
bool append(const ChatMessage& message);
|
||||||
|
|
||||||
|
// Decrypt and return every stored message for the unlocked wallet, in insertion order. Empty
|
||||||
|
// while locked or if none. Rows that fail to decrypt/parse are skipped.
|
||||||
|
std::vector<ChatMessage> load();
|
||||||
|
|
||||||
|
void clearWallet(); // delete the unlocked wallet's rows
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool ensureOpen();
|
||||||
|
bool exec(const char* sql);
|
||||||
|
bool createSchema();
|
||||||
|
std::string dedupHash(const std::string& txid, std::size_t position) const;
|
||||||
|
std::string serialize(const ChatMessage& message) const;
|
||||||
|
bool deserialize(const std::string& json, ChatMessage& out) const;
|
||||||
|
bool encrypt(const std::string& plain,
|
||||||
|
std::vector<unsigned char>& nonce,
|
||||||
|
std::vector<unsigned char>& cipher) const;
|
||||||
|
bool decrypt(const std::vector<unsigned char>& nonce,
|
||||||
|
const std::vector<unsigned char>& cipher,
|
||||||
|
std::string& plain) const;
|
||||||
|
void close();
|
||||||
|
|
||||||
|
sqlite3* db_ = nullptr;
|
||||||
|
std::string database_path_;
|
||||||
|
std::array<unsigned char, 32> key_{}; // AEAD storage key (seed-derived)
|
||||||
|
std::string wallet_tag_; // seed-derived row partition (a keyed hash, hex)
|
||||||
|
bool key_ready_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
1594
src/chat/chat_fixture_tooling.cpp
Normal file
1594
src/chat/chat_fixture_tooling.cpp
Normal file
File diff suppressed because it is too large
Load Diff
505
src/chat/chat_fixture_tooling.h
Normal file
505
src/chat/chat_fixture_tooling.h
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "chat_protocol.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// HushChat compatibility-fixture / capture-manifest / seed-projection tooling.
|
||||||
|
//
|
||||||
|
// Everything in this header is DEAD AT RUNTIME in the shipping app and test
|
||||||
|
// binaries — its only caller is the standalone dev CLI
|
||||||
|
// tools/hushchat_fixture_check.cpp. It is deliberately compiled ONLY into the
|
||||||
|
// HushChatFixtureCheck target so this validation scaffolding (and its libsodium
|
||||||
|
// seed-projection path) never reaches the wallet binary. Keep it that way: do
|
||||||
|
// not add chat_fixture_tooling.cpp to APP_SOURCES or the test target.
|
||||||
|
//
|
||||||
|
// It builds on the runtime types declared in chat_protocol.h.
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
enum class HushChatDecryptPreflightError {
|
||||||
|
None,
|
||||||
|
FeatureDisabled,
|
||||||
|
NonMessageHeader,
|
||||||
|
InvalidHeaderNumber,
|
||||||
|
UnsupportedVersion,
|
||||||
|
MissingReplyAddress,
|
||||||
|
MissingConversationId,
|
||||||
|
InvalidSecretstreamHeader,
|
||||||
|
InvalidPublicKey,
|
||||||
|
EmptyCiphertext,
|
||||||
|
OversizedCiphertext,
|
||||||
|
OddLengthCiphertext,
|
||||||
|
InvalidCiphertextHex,
|
||||||
|
TruncatedCiphertext
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatDecryptPreflightInput {
|
||||||
|
HushChatHeader header;
|
||||||
|
std::string ciphertext_hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatDecryptPreflightResult {
|
||||||
|
bool ok = false;
|
||||||
|
bool feature_enabled = false;
|
||||||
|
HushChatDecryptPreflightError error = HushChatDecryptPreflightError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
std::size_t ciphertext_size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatHexDecodeError {
|
||||||
|
None,
|
||||||
|
Empty,
|
||||||
|
OddLength,
|
||||||
|
InvalidHex,
|
||||||
|
UnexpectedByteLength
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatHexDecodeResult {
|
||||||
|
bool ok = false;
|
||||||
|
HushChatHexDecodeError error = HushChatHexDecodeError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
std::vector<unsigned char> bytes;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatDecryptDirection {
|
||||||
|
Incoming,
|
||||||
|
Outgoing
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatSessionKeySelection {
|
||||||
|
ClientRx,
|
||||||
|
ServerTx
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatDecryptInputError {
|
||||||
|
None,
|
||||||
|
FeatureDisabled,
|
||||||
|
InvalidStoredChatKey,
|
||||||
|
DecryptPreflightFailed,
|
||||||
|
InvalidPeerPublicKey,
|
||||||
|
InvalidStreamHeader,
|
||||||
|
InvalidCiphertext
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatDecryptInputMaterial {
|
||||||
|
std::string stored_chat_key_hex;
|
||||||
|
HushChatHeader header;
|
||||||
|
std::string ciphertext_hex;
|
||||||
|
HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming;
|
||||||
|
std::string peer_public_key_hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatPreparedDecryptInput {
|
||||||
|
std::vector<unsigned char> stored_chat_key_bytes;
|
||||||
|
std::vector<unsigned char> seed_bytes;
|
||||||
|
std::vector<unsigned char> peer_public_key_bytes;
|
||||||
|
std::vector<unsigned char> stream_header_bytes;
|
||||||
|
std::vector<unsigned char> ciphertext_bytes;
|
||||||
|
HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming;
|
||||||
|
HushChatSessionKeySelection session_key_selection = HushChatSessionKeySelection::ClientRx;
|
||||||
|
std::size_t plaintext_capacity = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatDecryptInputPreparationResult {
|
||||||
|
bool ok = false;
|
||||||
|
bool feature_enabled = false;
|
||||||
|
HushChatDecryptInputError error = HushChatDecryptInputError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
HushChatHexDecodeError hex_error = HushChatHexDecodeError::None;
|
||||||
|
HushChatDecryptPreflightError preflight_error = HushChatDecryptPreflightError::None;
|
||||||
|
HushChatPreparedDecryptInput prepared;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatDecryptFixtureReadinessResult {
|
||||||
|
bool ready = false;
|
||||||
|
std::size_t stored_chat_key_size = 0;
|
||||||
|
std::size_t seed_size = 0;
|
||||||
|
std::size_t peer_public_key_size = 0;
|
||||||
|
std::size_t stream_header_size = 0;
|
||||||
|
std::size_t ciphertext_size = 0;
|
||||||
|
std::size_t plaintext_capacity = 0;
|
||||||
|
HushChatSessionKeySelection session_key_selection = HushChatSessionKeySelection::ClientRx;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatCompatibilityFixtureError {
|
||||||
|
None,
|
||||||
|
FeatureDisabled,
|
||||||
|
MissingFixtureId,
|
||||||
|
InvalidLocalPublicKey,
|
||||||
|
InvalidPeerPublicKey,
|
||||||
|
InvalidHeaderMemo,
|
||||||
|
InvalidMemoPair,
|
||||||
|
NonMemoHeader,
|
||||||
|
HeaderPublicKeyMismatch,
|
||||||
|
DecryptInputFailed,
|
||||||
|
NotFixtureReady,
|
||||||
|
ExpectedStoredChatKeyLengthMismatch,
|
||||||
|
ExpectedSeedLengthMismatch,
|
||||||
|
ExpectedLocalPublicKeyLengthMismatch,
|
||||||
|
ExpectedPeerPublicKeyLengthMismatch,
|
||||||
|
ExpectedStreamHeaderLengthMismatch,
|
||||||
|
ExpectedCiphertextLengthMismatch,
|
||||||
|
ExpectedPlaintextLengthMismatch,
|
||||||
|
ExpectedRoleMismatch,
|
||||||
|
InvalidPlaintextHash
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCompatibilityFixture {
|
||||||
|
std::string fixture_id;
|
||||||
|
std::string stored_chat_key_hex;
|
||||||
|
std::string local_public_key_hex;
|
||||||
|
std::string peer_public_key_hex;
|
||||||
|
std::string header_memo;
|
||||||
|
std::string ciphertext_memo;
|
||||||
|
HushChatDecryptDirection direction = HushChatDecryptDirection::Incoming;
|
||||||
|
HushChatSessionKeySelection expected_session_key_selection = HushChatSessionKeySelection::ClientRx;
|
||||||
|
std::size_t expected_stored_chat_key_size = 32;
|
||||||
|
std::size_t expected_seed_size = 32;
|
||||||
|
std::size_t expected_local_public_key_size = 32;
|
||||||
|
std::size_t expected_peer_public_key_size = 32;
|
||||||
|
std::size_t expected_stream_header_size = 24;
|
||||||
|
std::size_t expected_ciphertext_size = 0;
|
||||||
|
std::size_t expected_plaintext_size = 0;
|
||||||
|
std::string expected_plaintext_hash_hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCompatibilityFixtureVerificationResult {
|
||||||
|
bool ok = false;
|
||||||
|
bool feature_enabled = false;
|
||||||
|
HushChatCompatibilityFixtureError error = HushChatCompatibilityFixtureError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
HushChatHexDecodeError hex_error = HushChatHexDecodeError::None;
|
||||||
|
HushChatDecryptInputError decrypt_input_error = HushChatDecryptInputError::None;
|
||||||
|
HushChatDecryptPreflightError preflight_error = HushChatDecryptPreflightError::None;
|
||||||
|
HushChatHeader header;
|
||||||
|
HushChatDecryptInputPreparationResult preparation;
|
||||||
|
HushChatDecryptFixtureReadinessResult readiness;
|
||||||
|
std::size_t local_public_key_size = 0;
|
||||||
|
std::size_t peer_public_key_size = 0;
|
||||||
|
std::size_t plaintext_hash_size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatCompatibilityFixtureKind {
|
||||||
|
IncomingMemo,
|
||||||
|
OutgoingMemo,
|
||||||
|
SeedPublicKeyProjection,
|
||||||
|
CorruptedAuthFailure,
|
||||||
|
ContactExclusion
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatCompatibilityFixtureFileStatus {
|
||||||
|
Pending,
|
||||||
|
Ready
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatCompatibilityFixtureFileError {
|
||||||
|
None,
|
||||||
|
FeatureDisabled,
|
||||||
|
InvalidJson,
|
||||||
|
JsonNotObject,
|
||||||
|
InvalidSchema,
|
||||||
|
MissingKind,
|
||||||
|
UnknownKind,
|
||||||
|
MissingStatus,
|
||||||
|
UnknownStatus,
|
||||||
|
MissingFixtureId,
|
||||||
|
MissingPendingReason,
|
||||||
|
MissingFixtureObject,
|
||||||
|
InvalidFixtureField,
|
||||||
|
FixtureVerificationFailed,
|
||||||
|
ContactFixtureNotExcluded,
|
||||||
|
FileReadFailed
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCompatibilityFixtureFile {
|
||||||
|
std::string schema;
|
||||||
|
HushChatCompatibilityFixtureKind kind = HushChatCompatibilityFixtureKind::IncomingMemo;
|
||||||
|
HushChatCompatibilityFixtureFileStatus status = HushChatCompatibilityFixtureFileStatus::Pending;
|
||||||
|
std::string fixture_id;
|
||||||
|
std::string pending_reason;
|
||||||
|
HushChatCompatibilityFixture fixture;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCompatibilityFixtureFileParseResult {
|
||||||
|
bool ok = false;
|
||||||
|
bool feature_enabled = false;
|
||||||
|
bool pending = false;
|
||||||
|
bool verified = false;
|
||||||
|
bool excluded_from_decrypt = false;
|
||||||
|
HushChatCompatibilityFixtureFileError error = HushChatCompatibilityFixtureFileError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
HushChatCompatibilityFixtureFile file;
|
||||||
|
HushChatCompatibilityFixtureVerificationResult verification;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatSeedPublicKeyProjectionError {
|
||||||
|
None,
|
||||||
|
FeatureDisabled,
|
||||||
|
MissingFixtureId,
|
||||||
|
InvalidStoredChatKey,
|
||||||
|
InvalidLocalPublicKey,
|
||||||
|
ExpectedStoredChatKeyLengthMismatch,
|
||||||
|
ExpectedSeedLengthMismatch,
|
||||||
|
ExpectedLocalPublicKeyLengthMismatch,
|
||||||
|
SodiumInitializationFailed,
|
||||||
|
KeypairProjectionFailed,
|
||||||
|
ProjectedPublicKeyMismatch
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatSeedPublicKeyProjectionResult {
|
||||||
|
bool ok = false;
|
||||||
|
bool feature_enabled = false;
|
||||||
|
HushChatSeedPublicKeyProjectionError error = HushChatSeedPublicKeyProjectionError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
HushChatHexDecodeError hex_error = HushChatHexDecodeError::None;
|
||||||
|
std::size_t stored_chat_key_size = 0;
|
||||||
|
std::size_t seed_size = 0;
|
||||||
|
std::size_t local_public_key_size = 0;
|
||||||
|
std::size_t projected_public_key_size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatCorruptedAuthFailureReadinessError {
|
||||||
|
None,
|
||||||
|
FeatureDisabled,
|
||||||
|
FixturePending,
|
||||||
|
WrongFixtureKind,
|
||||||
|
FixtureNotVerified,
|
||||||
|
SeedProjectionNotVerified
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCorruptedAuthFailureReadinessResult {
|
||||||
|
bool ok = false;
|
||||||
|
bool feature_enabled = false;
|
||||||
|
bool structurally_ready_for_future_auth_check = false;
|
||||||
|
bool requires_future_secretstream_auth_failure = false;
|
||||||
|
bool decrypted = false;
|
||||||
|
bool authenticated = false;
|
||||||
|
HushChatCorruptedAuthFailureReadinessError error = HushChatCorruptedAuthFailureReadinessError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatCompatibilityFixtureImportError {
|
||||||
|
None,
|
||||||
|
FeatureDisabled,
|
||||||
|
MissingRequiredKind,
|
||||||
|
DuplicateKind,
|
||||||
|
FixtureLoadFailed,
|
||||||
|
FixtureKindMismatch,
|
||||||
|
FixturePending,
|
||||||
|
FixtureInvalid,
|
||||||
|
FixtureNotVerified,
|
||||||
|
SeedProjectionFailed,
|
||||||
|
AuthFailureScaffoldFailed,
|
||||||
|
ContactFixtureNotExcluded
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCompatibilityFixtureImportCandidate {
|
||||||
|
HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo;
|
||||||
|
std::string path;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCompatibilityFixtureImportItem {
|
||||||
|
HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo;
|
||||||
|
HushChatCompatibilityFixtureKind loaded_kind = HushChatCompatibilityFixtureKind::IncomingMemo;
|
||||||
|
std::string path;
|
||||||
|
bool supplied = false;
|
||||||
|
bool pending = false;
|
||||||
|
bool replacement_eligible = false;
|
||||||
|
bool seed_projection_verified = false;
|
||||||
|
bool future_auth_failure_required = false;
|
||||||
|
bool structurally_ready_for_future_auth_check = false;
|
||||||
|
HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
HushChatCompatibilityFixtureFileParseResult parsed;
|
||||||
|
HushChatSeedPublicKeyProjectionResult seed_projection;
|
||||||
|
HushChatCorruptedAuthFailureReadinessResult auth_failure_readiness;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCompatibilityFixtureImportChecklistResult {
|
||||||
|
bool ok = false;
|
||||||
|
bool feature_enabled = false;
|
||||||
|
bool replacement_ready = false;
|
||||||
|
HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
std::size_t required_count = 0;
|
||||||
|
std::size_t supplied_count = 0;
|
||||||
|
std::size_t missing_count = 0;
|
||||||
|
std::size_t pending_count = 0;
|
||||||
|
std::size_t verified_count = 0;
|
||||||
|
std::size_t seed_projection_verified_count = 0;
|
||||||
|
std::size_t future_auth_failure_required_count = 0;
|
||||||
|
std::size_t auth_failure_structural_ready_count = 0;
|
||||||
|
std::size_t excluded_count = 0;
|
||||||
|
std::size_t rejected_count = 0;
|
||||||
|
std::vector<HushChatCompatibilityFixtureImportItem> items;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCompatibilityFixtureReplacementReportItem {
|
||||||
|
HushChatCompatibilityFixtureKind expected_kind = HushChatCompatibilityFixtureKind::IncomingMemo;
|
||||||
|
HushChatCompatibilityFixtureKind loaded_kind = HushChatCompatibilityFixtureKind::IncomingMemo;
|
||||||
|
std::string path;
|
||||||
|
bool supplied = false;
|
||||||
|
bool pending = false;
|
||||||
|
bool replacement_eligible = false;
|
||||||
|
bool refused = true;
|
||||||
|
bool seed_projection_verified = false;
|
||||||
|
bool future_auth_failure_required = false;
|
||||||
|
bool structurally_ready_for_future_auth_check = false;
|
||||||
|
bool cont_excluded = false;
|
||||||
|
bool decrypted = false;
|
||||||
|
bool authenticated = false;
|
||||||
|
HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCompatibilityFixtureReplacementDryRunResult {
|
||||||
|
bool ok = false;
|
||||||
|
bool feature_enabled = false;
|
||||||
|
bool dry_run_only = true;
|
||||||
|
bool redacted_report = true;
|
||||||
|
bool would_replace = false;
|
||||||
|
bool replacement_refused = true;
|
||||||
|
HushChatCompatibilityFixtureImportError error = HushChatCompatibilityFixtureImportError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
std::size_t required_count = 0;
|
||||||
|
std::size_t supplied_count = 0;
|
||||||
|
std::size_t missing_count = 0;
|
||||||
|
std::size_t pending_count = 0;
|
||||||
|
std::size_t verified_count = 0;
|
||||||
|
std::size_t seed_projection_verified_count = 0;
|
||||||
|
std::size_t future_auth_failure_required_count = 0;
|
||||||
|
std::size_t auth_failure_structural_ready_count = 0;
|
||||||
|
std::size_t excluded_count = 0;
|
||||||
|
std::size_t rejected_count = 0;
|
||||||
|
std::vector<HushChatCompatibilityFixtureReplacementReportItem> report_items;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatCaptureManifestError {
|
||||||
|
None,
|
||||||
|
FeatureDisabled,
|
||||||
|
FileReadFailed,
|
||||||
|
InvalidJson,
|
||||||
|
JsonNotObject,
|
||||||
|
InvalidSchema,
|
||||||
|
MissingManifestId,
|
||||||
|
MissingStatus,
|
||||||
|
UnknownStatus,
|
||||||
|
MissingFixtureDirectory,
|
||||||
|
MissingDryRunCommand,
|
||||||
|
InvalidDryRunCommand,
|
||||||
|
MissingProvenance,
|
||||||
|
MissingSourceClient,
|
||||||
|
InvalidSourceClient,
|
||||||
|
MissingSourceClientVersion,
|
||||||
|
MissingCaptureDate,
|
||||||
|
MissingNetwork,
|
||||||
|
MissingCaptureMethod,
|
||||||
|
MissingHandling,
|
||||||
|
MissingHandlingFlag,
|
||||||
|
HandlingFlagNotTrue,
|
||||||
|
MissingCategories,
|
||||||
|
InvalidCategoryEntry,
|
||||||
|
UnknownCategory,
|
||||||
|
DuplicateCategory,
|
||||||
|
MissingRequiredCategory,
|
||||||
|
ProhibitedFieldPresent
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatCaptureManifestStatus {
|
||||||
|
Staged
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCaptureManifestCategoryReport {
|
||||||
|
HushChatCompatibilityFixtureKind kind = HushChatCompatibilityFixtureKind::IncomingMemo;
|
||||||
|
std::string staged_filename;
|
||||||
|
bool declared = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatCaptureManifestValidationResult {
|
||||||
|
bool ok = false;
|
||||||
|
bool feature_enabled = false;
|
||||||
|
bool redacted_report = true;
|
||||||
|
bool validates_provenance_only = true;
|
||||||
|
bool no_sensitive_material_declared = false;
|
||||||
|
bool has_dry_run_command = false;
|
||||||
|
HushChatCaptureManifestError error = HushChatCaptureManifestError::None;
|
||||||
|
const char* error_name = "None";
|
||||||
|
HushChatCaptureManifestStatus status = HushChatCaptureManifestStatus::Staged;
|
||||||
|
std::string manifest_path;
|
||||||
|
std::string fixture_directory;
|
||||||
|
std::size_t required_count = 0;
|
||||||
|
std::size_t declared_count = 0;
|
||||||
|
std::size_t missing_count = 0;
|
||||||
|
std::size_t duplicate_count = 0;
|
||||||
|
std::size_t prohibited_field_count = 0;
|
||||||
|
std::size_t handling_flag_count = 0;
|
||||||
|
std::vector<HushChatCaptureManifestCategoryReport> categories;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr std::size_t kHushChatSecretstreamABytes = 17;
|
||||||
|
constexpr std::size_t kHushChatStoredChatKeyByteLength = 32;
|
||||||
|
constexpr std::size_t kHushChatStoredChatKeyHexLength = kHushChatStoredChatKeyByteLength * 2;
|
||||||
|
constexpr std::size_t kHushChatSeedByteLength = 32;
|
||||||
|
constexpr std::size_t kHushChatPublicKeyByteLength = kHushChatPublicKeyHexLength / 2;
|
||||||
|
constexpr std::size_t kHushChatSecretstreamHeaderByteLength = kHushChatSecretstreamHeaderHexLength / 2;
|
||||||
|
constexpr const char* kHushChatCompatibilityFixtureSchema = "dragonx.hushchat.compat-fixture.v1";
|
||||||
|
constexpr const char* kHushChatCaptureManifestSchema = "dragonx.hushchat.capture-manifest.v1";
|
||||||
|
|
||||||
|
HushChatDecryptPreflightResult validateHushChatMemoDecryptPreflight(
|
||||||
|
const HushChatDecryptPreflightInput& input,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
HushChatHexDecodeResult decodeHushChatHexBytes(const std::string& hex,
|
||||||
|
std::size_t expectedByteLength);
|
||||||
|
HushChatDecryptInputPreparationResult prepareHushChatDecryptInput(
|
||||||
|
const HushChatDecryptInputMaterial& material,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
HushChatDecryptFixtureReadinessResult inspectHushChatDecryptFixtureReadiness(
|
||||||
|
const HushChatPreparedDecryptInput& prepared);
|
||||||
|
HushChatSessionKeySelection hushChatSessionKeySelectionForDirection(HushChatDecryptDirection direction);
|
||||||
|
HushChatCompatibilityFixtureVerificationResult verifyHushChatCompatibilityFixture(
|
||||||
|
const HushChatCompatibilityFixture& fixture,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
HushChatCompatibilityFixtureFileParseResult parseHushChatCompatibilityFixtureFile(
|
||||||
|
const std::string& jsonText,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
HushChatCompatibilityFixtureFileParseResult loadHushChatCompatibilityFixtureFile(
|
||||||
|
const std::string& path,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
HushChatSeedPublicKeyProjectionResult verifyHushChatSeedPublicKeyProjection(
|
||||||
|
const HushChatCompatibilityFixture& fixture,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
HushChatCorruptedAuthFailureReadinessResult inspectHushChatCorruptedAuthFailureReadiness(
|
||||||
|
const HushChatCompatibilityFixtureFileParseResult& parsed,
|
||||||
|
const HushChatSeedPublicKeyProjectionResult& seedProjection,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
std::vector<HushChatCompatibilityFixtureKind> hushChatRequiredCompatibilityFixtureKinds();
|
||||||
|
HushChatCompatibilityFixtureImportChecklistResult inspectHushChatCompatibilityFixtureImportChecklist(
|
||||||
|
const std::vector<HushChatCompatibilityFixtureImportCandidate>& candidates,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
HushChatCompatibilityFixtureReplacementDryRunResult inspectHushChatCompatibilityFixtureReplacementDryRun(
|
||||||
|
const std::vector<HushChatCompatibilityFixtureImportCandidate>& candidates,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
HushChatCaptureManifestValidationResult validateHushChatCaptureManifest(
|
||||||
|
const std::string& jsonText,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
HushChatCaptureManifestValidationResult loadHushChatCaptureManifestFile(
|
||||||
|
const std::string& path,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
const char* hushChatDecryptPreflightErrorName(HushChatDecryptPreflightError error);
|
||||||
|
const char* hushChatHexDecodeErrorName(HushChatHexDecodeError error);
|
||||||
|
const char* hushChatDecryptDirectionName(HushChatDecryptDirection direction);
|
||||||
|
const char* hushChatSessionKeySelectionName(HushChatSessionKeySelection selection);
|
||||||
|
const char* hushChatDecryptInputErrorName(HushChatDecryptInputError error);
|
||||||
|
const char* hushChatCompatibilityFixtureErrorName(HushChatCompatibilityFixtureError error);
|
||||||
|
const char* hushChatCompatibilityFixtureKindName(HushChatCompatibilityFixtureKind kind);
|
||||||
|
const char* hushChatCompatibilityFixtureFileStatusName(HushChatCompatibilityFixtureFileStatus status);
|
||||||
|
const char* hushChatCompatibilityFixtureFileErrorName(HushChatCompatibilityFixtureFileError error);
|
||||||
|
const char* hushChatSeedPublicKeyProjectionErrorName(HushChatSeedPublicKeyProjectionError error);
|
||||||
|
const char* hushChatCorruptedAuthFailureReadinessErrorName(HushChatCorruptedAuthFailureReadinessError error);
|
||||||
|
const char* hushChatCompatibilityFixtureImportErrorName(HushChatCompatibilityFixtureImportError error);
|
||||||
|
const char* hushChatCaptureManifestErrorName(HushChatCaptureManifestError error);
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
70
src/chat/chat_identity.cpp
Normal file
70
src/chat/chat_identity.cpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// DragonX Wallet - HushChat identity derivation (implementation).
|
||||||
|
|
||||||
|
#include "chat_identity.h"
|
||||||
|
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
// The KDF context is used as the BLAKE2b key, so its length must sit within the primitive's
|
||||||
|
// key bounds.
|
||||||
|
static_assert(kChatIdentityKdfContextLen >= crypto_generichash_KEYBYTES_MIN,
|
||||||
|
"chat identity KDF context is shorter than BLAKE2b's minimum key length");
|
||||||
|
static_assert(kChatIdentityKdfContextLen <= crypto_generichash_KEYBYTES_MAX,
|
||||||
|
"chat identity KDF context is longer than BLAKE2b's maximum key length");
|
||||||
|
|
||||||
|
const char* chatIdentityStatusName(ChatIdentityStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case ChatIdentityStatus::Ready: return "Ready";
|
||||||
|
case ChatIdentityStatus::FeatureDisabled: return "FeatureDisabled";
|
||||||
|
case ChatIdentityStatus::SecretUnavailable: return "SecretUnavailable";
|
||||||
|
case ChatIdentityStatus::DerivationFailed: return "DerivationFailed";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string chatIdentityPublicKeyHex(const ChatKeyPair& keys) {
|
||||||
|
char hex[crypto_kx_PUBLICKEYBYTES * 2 + 1];
|
||||||
|
sodium_bin2hex(hex, sizeof hex, keys.public_key.data(), keys.public_key.size());
|
||||||
|
return std::string(hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatIdentityResult deriveChatIdentityFromSecret(const std::string& stableSecret,
|
||||||
|
ChatKeyPair& outKeys,
|
||||||
|
bool featureEnabled) {
|
||||||
|
ChatIdentityResult result;
|
||||||
|
auto finish = [&result](ChatIdentityStatus status) -> ChatIdentityResult {
|
||||||
|
result.status = status;
|
||||||
|
result.error_name = chatIdentityStatusName(status);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!featureEnabled) return finish(ChatIdentityStatus::FeatureDisabled);
|
||||||
|
if (stableSecret.empty()) return finish(ChatIdentityStatus::SecretUnavailable);
|
||||||
|
if (sodium_init() < 0) return finish(ChatIdentityStatus::DerivationFailed);
|
||||||
|
|
||||||
|
unsigned char kxSeed[crypto_kx_SEEDBYTES];
|
||||||
|
static_assert(sizeof(kxSeed) == kChatKeyBytes, "kx seed size mismatch");
|
||||||
|
|
||||||
|
const int hashStatus = crypto_generichash(
|
||||||
|
kxSeed, sizeof kxSeed,
|
||||||
|
reinterpret_cast<const unsigned char*>(stableSecret.data()), stableSecret.size(),
|
||||||
|
reinterpret_cast<const unsigned char*>(kChatIdentityKdfContext), kChatIdentityKdfContextLen);
|
||||||
|
if (hashStatus != 0) {
|
||||||
|
sodium_memzero(kxSeed, sizeof kxSeed);
|
||||||
|
return finish(ChatIdentityStatus::DerivationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int keypairStatus =
|
||||||
|
crypto_kx_seed_keypair(outKeys.public_key.data(), outKeys.secret_key.data(), kxSeed);
|
||||||
|
sodium_memzero(kxSeed, sizeof kxSeed); // wipe the seed immediately, success or failure
|
||||||
|
if (keypairStatus != 0) {
|
||||||
|
wipeChatKeyPair(outKeys);
|
||||||
|
return finish(ChatIdentityStatus::DerivationFailed);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.public_key_hex = chatIdentityPublicKeyHex(outKeys);
|
||||||
|
return finish(ChatIdentityStatus::Ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
57
src/chat/chat_identity.h
Normal file
57
src/chat/chat_identity.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// DragonX Wallet - HushChat identity derivation.
|
||||||
|
//
|
||||||
|
// The DragonX-native chat identity is an X25519 (crypto_kx) keypair derived from a stable
|
||||||
|
// per-wallet secret via a domain-separated keyed BLAKE2b KDF. This deliberately does NOT
|
||||||
|
// use SDXL's UTF-8-hex-seed quirk (that quirk is only for the Phase-4 "import an existing
|
||||||
|
// SDXL identity" path). Interop is unaffected: identity derivation is local — peers only
|
||||||
|
// exchange public keys. See docs/_archive/contacts-chat-tab-plan-2026-07-05.md §5.6.
|
||||||
|
|
||||||
|
#include "chat_crypto.h" // ChatKeyPair
|
||||||
|
#include "chat_protocol.h" // hushChatFeatureEnabledAtBuild()
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
// Domain-separation label — used as the BLAKE2b key so a different app/version cannot
|
||||||
|
// derive the same identity from the same wallet secret. A char[] (not const char*) so its
|
||||||
|
// length is a compile-time constant for the KEYBYTES-bounds static_assert.
|
||||||
|
inline constexpr char kChatIdentityKdfContext[] = "DragonX-HushChat-Identity-v1";
|
||||||
|
inline constexpr std::size_t kChatIdentityKdfContextLen = sizeof(kChatIdentityKdfContext) - 1;
|
||||||
|
|
||||||
|
enum class ChatIdentityStatus {
|
||||||
|
Ready,
|
||||||
|
FeatureDisabled, // DRAGONX_ENABLE_CHAT off (or caller passed featureEnabled=false)
|
||||||
|
SecretUnavailable, // no stable secret (wallet locked / not open / empty)
|
||||||
|
DerivationFailed // libsodium init or KDF/keypair failure
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* chatIdentityStatusName(ChatIdentityStatus status);
|
||||||
|
|
||||||
|
struct ChatIdentityResult {
|
||||||
|
ChatIdentityStatus status = ChatIdentityStatus::FeatureDisabled;
|
||||||
|
std::string public_key_hex; // 64 lowercase hex chars when Ready
|
||||||
|
const char* error_name = "FeatureDisabled"; // == chatIdentityStatusName(status)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pure, no-I/O derivation: hashes `stableSecret` (variable-length: a mnemonic on lite, a
|
||||||
|
// spending key on full-node) with the KDF context as the BLAKE2b key into a clean 32-byte
|
||||||
|
// crypto_kx seed, then crypto_kx_seed_keypair() into `outKeys`. Deterministic for a given
|
||||||
|
// secret. On any non-Ready result `outKeys` is left wiped. featureEnabled defaults to the
|
||||||
|
// build predicate but tests pass true to exercise the crypto in an OFF build.
|
||||||
|
//
|
||||||
|
// OWNERSHIP: `stableSecret` is BORROWED (const&) and is NOT wiped here — the caller owns it
|
||||||
|
// and MUST sodium_memzero its backing buffer after this returns. The per-variant provider
|
||||||
|
// that fetches the wallet secret (mnemonic / spending key) should hold it in a wipeable
|
||||||
|
// buffer, not a plain std::string literal, in production.
|
||||||
|
ChatIdentityResult deriveChatIdentityFromSecret(const std::string& stableSecret,
|
||||||
|
ChatKeyPair& outKeys,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
|
||||||
|
// 64-char lowercase hex of the public key.
|
||||||
|
std::string chatIdentityPublicKeyHex(const ChatKeyPair& keys);
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
30
src/chat/chat_message.h
Normal file
30
src/chat/chat_message.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// DragonX Wallet - HushChat decrypted message model. Held in memory by ChatStore and persisted at
|
||||||
|
// rest (encrypted under a seed-derived key) by ChatDatabase.
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
enum class ChatDirection { Incoming, Outgoing };
|
||||||
|
enum class ChatMessageKind { Message, ContactRequest };
|
||||||
|
// Outgoing delivery: Sent = the broadcast was submitted; Failed = it wasn't (not connected, no
|
||||||
|
// spendable address, a send already in progress). Always Sent for incoming.
|
||||||
|
enum class ChatDelivery { Sent, Failed };
|
||||||
|
|
||||||
|
struct ChatMessage {
|
||||||
|
ChatDirection direction = ChatDirection::Incoming;
|
||||||
|
ChatMessageKind kind = ChatMessageKind::Message;
|
||||||
|
std::string txid;
|
||||||
|
std::string conversation_id; // cid — the conversation thread key
|
||||||
|
std::string peer_zaddr; // header "z": peer's reply z-address
|
||||||
|
std::string peer_public_key_hex; // header "p": peer's crypto_kx public key
|
||||||
|
std::string body; // decrypted plaintext (Message) or request text (ContactRequest)
|
||||||
|
std::int64_t timestamp = 0; // tx time in seconds; set by the ingesting caller
|
||||||
|
std::size_t payload_position = 0; // together with txid, the dedup key
|
||||||
|
ChatDelivery delivery = ChatDelivery::Sent; // outgoing only
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
108
src/chat/chat_outgoing.cpp
Normal file
108
src/chat/chat_outgoing.cpp
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// DragonX Wallet - HushChat outgoing memo construction (implementation).
|
||||||
|
|
||||||
|
#include "chat_outgoing.h"
|
||||||
|
|
||||||
|
#include "chat_protocol.h" // kHushChat* constants
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Serialize the HushChat header. nlohmann emits object keys in sorted (alphabetical) order —
|
||||||
|
// cid,e,h,p,t,v,z — which is exactly SilentDragonXLite's on-wire key order.
|
||||||
|
std::string buildHeaderMemo(const std::string& replyZaddr,
|
||||||
|
const std::string& conversationId,
|
||||||
|
const char* type,
|
||||||
|
const std::string& streamHeaderHex,
|
||||||
|
const std::string& publicKeyHex)
|
||||||
|
{
|
||||||
|
nlohmann::json header;
|
||||||
|
header["h"] = 1; // header number (>= 1)
|
||||||
|
header["v"] = kHushChatSupportedVersion; // 0
|
||||||
|
header["z"] = replyZaddr; // where the peer should reply (my address)
|
||||||
|
header["cid"] = conversationId;
|
||||||
|
header["t"] = type; // "Memo" or "Cont"
|
||||||
|
header["e"] = streamHeaderHex; // 48-hex secretstream header (Memo) / "" (Cont)
|
||||||
|
header["p"] = publicKeyHex; // my 64-hex crypto_kx public key
|
||||||
|
return header.dump();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool present(const std::string& value) { return !value.empty(); }
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::array<ChatSendOutput, 2> chatSendOutputs(const OutgoingChatMemos& memos, bool utf8Prefix)
|
||||||
|
{
|
||||||
|
const std::string prefix = utf8Prefix ? "utf8:" : "";
|
||||||
|
return {{
|
||||||
|
{ memos.recipientZaddr, prefix + memos.headerMemo }, // header — the lower memo position
|
||||||
|
{ memos.recipientZaddr, prefix + memos.payloadMemo },
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatComposeStatus buildOutgoingMessage(const ChatKeyPair& mine,
|
||||||
|
const std::string& myPublicKeyHex,
|
||||||
|
const std::string& myReplyZaddr,
|
||||||
|
const std::string& peerPublicKeyHex,
|
||||||
|
const std::string& peerZaddr,
|
||||||
|
const std::string& conversationId,
|
||||||
|
const std::string& plaintext,
|
||||||
|
OutgoingChatMemos& out)
|
||||||
|
{
|
||||||
|
if (plaintext.empty()) return ChatComposeStatus::EmptyBody;
|
||||||
|
if (!present(myPublicKeyHex) || !present(myReplyZaddr) || !present(peerZaddr) ||
|
||||||
|
!present(conversationId)) {
|
||||||
|
return ChatComposeStatus::MissingField;
|
||||||
|
}
|
||||||
|
if (peerPublicKeyHex.size() != kHushChatPublicKeyHexLength) return ChatComposeStatus::BadPeerKey;
|
||||||
|
|
||||||
|
std::string streamHeaderHex;
|
||||||
|
std::string ciphertextHex;
|
||||||
|
if (encryptOutgoing(mine, peerPublicKeyHex, plaintext, streamHeaderHex, ciphertextHex)
|
||||||
|
!= ChatCryptoStatus::Ok) {
|
||||||
|
return ChatComposeStatus::EncryptFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutgoingChatMemos memos;
|
||||||
|
memos.recipientZaddr = peerZaddr;
|
||||||
|
memos.headerMemo = buildHeaderMemo(myReplyZaddr, conversationId, "Memo", streamHeaderHex, myPublicKeyHex);
|
||||||
|
memos.payloadMemo = ciphertextHex;
|
||||||
|
if (memos.headerMemo.size() > kHushChatMemoByteLimit ||
|
||||||
|
memos.payloadMemo.size() > kHushChatMemoByteLimit) {
|
||||||
|
return ChatComposeStatus::TooLong;
|
||||||
|
}
|
||||||
|
out = std::move(memos);
|
||||||
|
return ChatComposeStatus::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatComposeStatus buildOutgoingContactRequest(const std::string& myPublicKeyHex,
|
||||||
|
const std::string& myReplyZaddr,
|
||||||
|
const std::string& peerZaddr,
|
||||||
|
const std::string& conversationId,
|
||||||
|
const std::string& requestText,
|
||||||
|
OutgoingChatMemos& out)
|
||||||
|
{
|
||||||
|
if (requestText.empty()) return ChatComposeStatus::EmptyBody;
|
||||||
|
// The receive parser treats any memo starting with '{' as a header, so a request payload must
|
||||||
|
// not start with one (see isContactPayloadCandidate in chat_protocol.cpp).
|
||||||
|
if (requestText.front() == '{') return ChatComposeStatus::BadRequestText;
|
||||||
|
if (!present(myPublicKeyHex) || !present(myReplyZaddr) || !present(peerZaddr) ||
|
||||||
|
!present(conversationId)) {
|
||||||
|
return ChatComposeStatus::MissingField;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutgoingChatMemos memos;
|
||||||
|
memos.recipientZaddr = peerZaddr;
|
||||||
|
memos.headerMemo = buildHeaderMemo(myReplyZaddr, conversationId, "Cont", "", myPublicKeyHex);
|
||||||
|
memos.payloadMemo = requestText;
|
||||||
|
if (memos.headerMemo.size() > kHushChatMemoByteLimit ||
|
||||||
|
memos.payloadMemo.size() > kHushChatMemoByteLimit) {
|
||||||
|
return ChatComposeStatus::TooLong;
|
||||||
|
}
|
||||||
|
out = std::move(memos);
|
||||||
|
return ChatComposeStatus::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
66
src/chat/chat_outgoing.h
Normal file
66
src/chat/chat_outgoing.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// DragonX Wallet - HushChat outgoing memo construction (the inverse of the receive parser).
|
||||||
|
//
|
||||||
|
// Given the sender's identity and the peer, produce the header memo JSON + payload memo that,
|
||||||
|
// sent as two 0-value memo outputs to the peer's z-address (header at the LOWER memo position),
|
||||||
|
// another HushChat client parses and decrypts. The byte format matches SilentDragonXLite: the
|
||||||
|
// header keys serialize alphabetically (nlohmann default) to cid,e,h,p,t,v,z. Pure — no I/O, no
|
||||||
|
// network; broadcasting the memos is the caller's job (the transport lands in a later phase).
|
||||||
|
|
||||||
|
#include "chat_crypto.h" // ChatKeyPair
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
struct OutgoingChatMemos {
|
||||||
|
std::string recipientZaddr; // the peer's z-address (recipient of both memo outputs)
|
||||||
|
std::string headerMemo; // JSON header — MUST occupy the lower memo position on the wire
|
||||||
|
std::string payloadMemo; // ciphertext hex (Message) or plaintext (ContactRequest)
|
||||||
|
};
|
||||||
|
|
||||||
|
// One of the two 0-value memo outputs a HushChat send produces (amount is always 0).
|
||||||
|
struct ChatSendOutput {
|
||||||
|
std::string address; // the peer's z-address
|
||||||
|
std::string memo; // memo encoded for the target transport (utf8:-prefixed or raw)
|
||||||
|
};
|
||||||
|
|
||||||
|
// The two outputs for a HushChat send, HEADER FIRST (it must occupy the lower memo position).
|
||||||
|
// `utf8Prefix` prepends the daemon's "utf8:" marker required by full-node z_sendmany (which then
|
||||||
|
// UTF-8-encodes the bytes on-chain, byte-identical to SDXLite's Memo::from_str); lite backends take
|
||||||
|
// raw UTF-8, so pass false there.
|
||||||
|
std::array<ChatSendOutput, 2> chatSendOutputs(const OutgoingChatMemos& memos, bool utf8Prefix);
|
||||||
|
|
||||||
|
enum class ChatComposeStatus {
|
||||||
|
Ok,
|
||||||
|
EmptyBody,
|
||||||
|
MissingField,
|
||||||
|
BadPeerKey,
|
||||||
|
BadRequestText, // a contact request text must not start with '{' (parser would read it as a header)
|
||||||
|
EncryptFailed,
|
||||||
|
TooLong // a resulting memo exceeds the HushChat 512-byte memo limit
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build an ENCRYPTED message to a peer whose public key you already learned from a memo they sent
|
||||||
|
// you. `mine` is the sender's identity keypair; `myPublicKeyHex` its public half (goes in header p).
|
||||||
|
ChatComposeStatus buildOutgoingMessage(const ChatKeyPair& mine,
|
||||||
|
const std::string& myPublicKeyHex,
|
||||||
|
const std::string& myReplyZaddr,
|
||||||
|
const std::string& peerPublicKeyHex,
|
||||||
|
const std::string& peerZaddr,
|
||||||
|
const std::string& conversationId,
|
||||||
|
const std::string& plaintext,
|
||||||
|
OutgoingChatMemos& out);
|
||||||
|
|
||||||
|
// Build a plaintext contact request — no peer public key needed yet; this is how the peer first
|
||||||
|
// learns your public key + reply address. The payload is the (plaintext) request text.
|
||||||
|
ChatComposeStatus buildOutgoingContactRequest(const std::string& myPublicKeyHex,
|
||||||
|
const std::string& myReplyZaddr,
|
||||||
|
const std::string& peerZaddr,
|
||||||
|
const std::string& conversationId,
|
||||||
|
const std::string& requestText,
|
||||||
|
OutgoingChatMemos& out);
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
312
src/chat/chat_protocol.cpp
Normal file
312
src/chat/chat_protocol.cpp
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
#include "chat_protocol.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool isHexString(const std::string& value)
|
||||||
|
{
|
||||||
|
for (unsigned char ch : value) {
|
||||||
|
if (!std::isxdigit(ch)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCiphertextPayloadCandidate(const std::string& value)
|
||||||
|
{
|
||||||
|
return !value.empty() && value.size() <= kHushChatMemoByteLimit &&
|
||||||
|
value.size() % 2 == 0 && isHexString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isContactPayloadCandidate(const std::string& value)
|
||||||
|
{
|
||||||
|
return !value.empty() && value.size() <= kHushChatMemoByteLimit && value.front() != '{';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readRequiredString(const nlohmann::json& object,
|
||||||
|
const char* key,
|
||||||
|
std::string& value,
|
||||||
|
std::string& error)
|
||||||
|
{
|
||||||
|
auto it = object.find(key);
|
||||||
|
if (it == object.end()) {
|
||||||
|
error = std::string("missing field: ") + key;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!it->is_string()) {
|
||||||
|
error = std::string("field is not a string: ") + key;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = it->get<std::string>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readRequiredInt(const nlohmann::json& object,
|
||||||
|
const char* key,
|
||||||
|
int& value,
|
||||||
|
std::string& error)
|
||||||
|
{
|
||||||
|
auto it = object.find(key);
|
||||||
|
if (it == object.end()) {
|
||||||
|
error = std::string("missing field: ") + key;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!it->is_number_integer()) {
|
||||||
|
error = std::string("field is not an integer: ") + key;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = it->get<int>();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
HushChatHeaderParseResult fail(std::string error)
|
||||||
|
{
|
||||||
|
HushChatHeaderParseResult result;
|
||||||
|
result.error = std::move(error);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
const char* hushChatHeaderTypeName(HushChatHeaderType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case HushChatHeaderType::Message:
|
||||||
|
return "Memo";
|
||||||
|
case HushChatHeaderType::ContactRequest:
|
||||||
|
return "Cont";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* hushChatMemoGroupingIssueName(HushChatMemoGroupingIssue issue)
|
||||||
|
{
|
||||||
|
switch (issue) {
|
||||||
|
case HushChatMemoGroupingIssue::InvalidHeader:
|
||||||
|
return "InvalidHeader";
|
||||||
|
case HushChatMemoGroupingIssue::MissingPayload:
|
||||||
|
return "MissingPayload";
|
||||||
|
case HushChatMemoGroupingIssue::DuplicateHeader:
|
||||||
|
return "DuplicateHeader";
|
||||||
|
case HushChatMemoGroupingIssue::OversizedMemo:
|
||||||
|
return "OversizedMemo";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
HushChatHeaderParseResult parseHushChatHeaderMemo(const std::string& memo)
|
||||||
|
{
|
||||||
|
if (memo.empty()) return fail("empty memo");
|
||||||
|
if (memo.size() > kHushChatMemoByteLimit) return fail("memo exceeds HushChat memo byte limit");
|
||||||
|
if (memo.front() != '{') return fail("memo is not a HushChat header JSON object");
|
||||||
|
|
||||||
|
nlohmann::json object;
|
||||||
|
try {
|
||||||
|
object = nlohmann::json::parse(memo);
|
||||||
|
} catch (const nlohmann::json::parse_error& e) {
|
||||||
|
return fail(std::string("invalid JSON: ") + e.what());
|
||||||
|
}
|
||||||
|
if (!object.is_object()) return fail("header memo JSON is not an object");
|
||||||
|
|
||||||
|
HushChatHeader header;
|
||||||
|
std::string type;
|
||||||
|
std::string error;
|
||||||
|
if (!readRequiredInt(object, "h", header.header_number, error)) return fail(error);
|
||||||
|
if (!readRequiredInt(object, "v", header.version, error)) return fail(error);
|
||||||
|
if (!readRequiredString(object, "z", header.reply_zaddr, error)) return fail(error);
|
||||||
|
if (!readRequiredString(object, "cid", header.conversation_id, error)) return fail(error);
|
||||||
|
if (!readRequiredString(object, "t", type, error)) return fail(error);
|
||||||
|
if (!readRequiredString(object, "e", header.secretstream_header_hex, error)) return fail(error);
|
||||||
|
if (!readRequiredString(object, "p", header.public_key_hex, error)) return fail(error);
|
||||||
|
|
||||||
|
if (header.header_number < 1) return fail("header number must be positive");
|
||||||
|
if (header.version != kHushChatSupportedVersion) return fail("unsupported HushChat version");
|
||||||
|
if (header.reply_zaddr.empty()) return fail("reply z-address is empty");
|
||||||
|
if (header.conversation_id.empty()) return fail("conversation id is empty");
|
||||||
|
|
||||||
|
if (type == "Memo") {
|
||||||
|
header.type = HushChatHeaderType::Message;
|
||||||
|
} else if (type == "Cont") {
|
||||||
|
header.type = HushChatHeaderType::ContactRequest;
|
||||||
|
} else {
|
||||||
|
return fail("unknown HushChat header type");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.public_key_hex.size() != kHushChatPublicKeyHexLength || !isHexString(header.public_key_hex)) {
|
||||||
|
return fail("public key must be 32 bytes encoded as hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.type == HushChatHeaderType::Message) {
|
||||||
|
if (header.secretstream_header_hex.size() != kHushChatSecretstreamHeaderHexLength ||
|
||||||
|
!isHexString(header.secretstream_header_hex)) {
|
||||||
|
return fail("message header must include a 24 byte secretstream header encoded as hex");
|
||||||
|
}
|
||||||
|
} else if (!header.secretstream_header_hex.empty()) {
|
||||||
|
return fail("contact request header must not include a secretstream header");
|
||||||
|
}
|
||||||
|
|
||||||
|
HushChatHeaderParseResult result;
|
||||||
|
result.ok = true;
|
||||||
|
result.header = std::move(header);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HushChatMemoGroupingResult groupHushChatMemoOutputs(const std::vector<HushChatMemoOutput>& outputs)
|
||||||
|
{
|
||||||
|
struct OrderedOutput {
|
||||||
|
std::size_t input_index = 0;
|
||||||
|
HushChatMemoOutput output;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<OrderedOutput> ordered;
|
||||||
|
ordered.reserve(outputs.size());
|
||||||
|
for (std::size_t index = 0; index < outputs.size(); ++index) {
|
||||||
|
ordered.push_back(OrderedOutput{index, outputs[index]});
|
||||||
|
}
|
||||||
|
std::stable_sort(ordered.begin(), ordered.end(), [](const OrderedOutput& left, const OrderedOutput& right) {
|
||||||
|
if (left.output.position == right.output.position) return left.input_index < right.input_index;
|
||||||
|
return left.output.position < right.output.position;
|
||||||
|
});
|
||||||
|
|
||||||
|
HushChatMemoGroupingResult result;
|
||||||
|
std::optional<HushChatMemoOutput> pending_header_output;
|
||||||
|
std::optional<HushChatHeader> pending_header;
|
||||||
|
// A payload memo seen BEFORE its header. The full-node daemon shuffles the two 0-value memo
|
||||||
|
// outputs of a chat tx (transaction_builder ShuffleOutputs), so the header is NOT guaranteed to
|
||||||
|
// occupy the lower position. We hold an orphan payload until its header arrives and pair them
|
||||||
|
// regardless of on-chain order (a chat tx carries exactly one header + one payload).
|
||||||
|
std::optional<HushChatMemoOutput> pending_payload_output;
|
||||||
|
|
||||||
|
auto addIssue = [&](HushChatMemoGroupingIssue issue, std::size_t position, std::string detail) {
|
||||||
|
result.issues.push_back(HushChatMemoGroupingIssueInfo{issue, position, std::move(detail)});
|
||||||
|
};
|
||||||
|
|
||||||
|
auto payloadMatchesHeader = [&](const std::string& memo) {
|
||||||
|
return pending_header->type == HushChatHeaderType::Message
|
||||||
|
? isCiphertextPayloadCandidate(memo)
|
||||||
|
: isContactPayloadCandidate(memo);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto emitPair = [&](const HushChatMemoOutput& payloadOutput) {
|
||||||
|
HushChatMemoPair pair;
|
||||||
|
pair.header = std::move(*pending_header);
|
||||||
|
pair.header_position = pending_header_output->position;
|
||||||
|
pair.payload_position = payloadOutput.position;
|
||||||
|
pair.payload_memo = payloadOutput.memo;
|
||||||
|
result.pairs.push_back(std::move(pair));
|
||||||
|
pending_header_output.reset();
|
||||||
|
pending_header.reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pair a held (early) payload with the now-pending header, if it matches the header's type.
|
||||||
|
auto tryPairHeldPayload = [&]() {
|
||||||
|
if (!pending_header || !pending_payload_output) return;
|
||||||
|
if (payloadMatchesHeader(pending_payload_output->memo)) {
|
||||||
|
emitPair(*pending_payload_output);
|
||||||
|
pending_payload_output.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto clearPendingAsMissing = [&]() {
|
||||||
|
if (!pending_header_output) return;
|
||||||
|
addIssue(HushChatMemoGroupingIssue::MissingPayload,
|
||||||
|
pending_header_output->position,
|
||||||
|
"header did not have a matching payload memo");
|
||||||
|
pending_header_output.reset();
|
||||||
|
pending_header.reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& entry : ordered) {
|
||||||
|
const auto& output = entry.output;
|
||||||
|
if (output.memo.size() > kHushChatMemoByteLimit) {
|
||||||
|
addIssue(HushChatMemoGroupingIssue::OversizedMemo,
|
||||||
|
output.position,
|
||||||
|
"memo exceeds HushChat memo byte limit");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!output.memo.empty() && output.memo.front() == '{') {
|
||||||
|
auto parsed = parseHushChatHeaderMemo(output.memo);
|
||||||
|
if (!parsed.ok) {
|
||||||
|
addIssue(HushChatMemoGroupingIssue::InvalidHeader, output.position, parsed.error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending_header_output) {
|
||||||
|
addIssue(HushChatMemoGroupingIssue::DuplicateHeader,
|
||||||
|
output.position,
|
||||||
|
"encountered another HushChat header before a payload");
|
||||||
|
clearPendingAsMissing();
|
||||||
|
}
|
||||||
|
|
||||||
|
pending_header_output = output;
|
||||||
|
pending_header = std::move(parsed.header);
|
||||||
|
tryPairHeldPayload(); // the payload may have arrived first (shuffled output order)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-header memo.
|
||||||
|
if (pending_header_output && pending_header) {
|
||||||
|
if (payloadMatchesHeader(output.memo)) {
|
||||||
|
emitPair(output);
|
||||||
|
} else {
|
||||||
|
++result.ignored_memo_count;
|
||||||
|
}
|
||||||
|
} else if (!pending_payload_output && !output.memo.empty()) {
|
||||||
|
// No header yet — hold this as a candidate payload for a header still to come.
|
||||||
|
pending_payload_output = output;
|
||||||
|
} else {
|
||||||
|
++result.ignored_memo_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearPendingAsMissing();
|
||||||
|
if (pending_payload_output) ++result.ignored_memo_count; // orphan payload, no header arrived
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
HushChatTransactionExtractionResult extractHushChatTransactionMetadata(
|
||||||
|
const HushChatTransactionInput& transaction,
|
||||||
|
bool featureEnabled)
|
||||||
|
{
|
||||||
|
HushChatTransactionExtractionResult result;
|
||||||
|
result.feature_enabled = featureEnabled;
|
||||||
|
if (!featureEnabled || transaction.txid.empty()) return result;
|
||||||
|
|
||||||
|
auto grouped = groupHushChatMemoOutputs(transaction.outputs);
|
||||||
|
result.ignored_memo_count = grouped.ignored_memo_count;
|
||||||
|
result.issues.reserve(grouped.issues.size());
|
||||||
|
for (const auto& issue : grouped.issues) {
|
||||||
|
result.issues.push_back(HushChatMemoGroupingIssueInfo{
|
||||||
|
issue.issue,
|
||||||
|
issue.position,
|
||||||
|
hushChatMemoGroupingIssueName(issue.issue)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
result.metadata.reserve(grouped.pairs.size());
|
||||||
|
for (const auto& pair : grouped.pairs) {
|
||||||
|
HushChatTransactionMetadata metadata;
|
||||||
|
metadata.txid = transaction.txid;
|
||||||
|
metadata.type = pair.header.type;
|
||||||
|
metadata.conversation_id = pair.header.conversation_id;
|
||||||
|
metadata.reply_zaddr = pair.header.reply_zaddr;
|
||||||
|
metadata.header_position = pair.header_position;
|
||||||
|
metadata.payload_position = pair.payload_position;
|
||||||
|
metadata.payload_size = pair.payload_memo.size();
|
||||||
|
metadata.sender_public_key_hex = pair.header.public_key_hex;
|
||||||
|
metadata.secretstream_header_hex = pair.header.secretstream_header_hex;
|
||||||
|
metadata.payload_memo = pair.payload_memo;
|
||||||
|
result.metadata.push_back(std::move(metadata));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
110
src/chat/chat_protocol.h
Normal file
110
src/chat/chat_protocol.h
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifndef DRAGONX_ENABLE_CHAT
|
||||||
|
#define DRAGONX_ENABLE_CHAT 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
enum class HushChatHeaderType {
|
||||||
|
Message,
|
||||||
|
ContactRequest
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatHeader {
|
||||||
|
int header_number = 0;
|
||||||
|
int version = 0;
|
||||||
|
std::string reply_zaddr;
|
||||||
|
std::string conversation_id;
|
||||||
|
HushChatHeaderType type = HushChatHeaderType::Message;
|
||||||
|
std::string secretstream_header_hex;
|
||||||
|
std::string public_key_hex;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatHeaderParseResult {
|
||||||
|
bool ok = false;
|
||||||
|
HushChatHeader header;
|
||||||
|
std::string error;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatMemoOutput {
|
||||||
|
std::size_t position = 0;
|
||||||
|
std::string memo;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatMemoPair {
|
||||||
|
HushChatHeader header;
|
||||||
|
std::size_t header_position = 0;
|
||||||
|
std::size_t payload_position = 0;
|
||||||
|
std::string payload_memo;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HushChatMemoGroupingIssue {
|
||||||
|
InvalidHeader,
|
||||||
|
MissingPayload,
|
||||||
|
DuplicateHeader,
|
||||||
|
OversizedMemo
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatMemoGroupingIssueInfo {
|
||||||
|
HushChatMemoGroupingIssue issue = HushChatMemoGroupingIssue::InvalidHeader;
|
||||||
|
std::size_t position = 0;
|
||||||
|
std::string detail;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatMemoGroupingResult {
|
||||||
|
std::vector<HushChatMemoPair> pairs;
|
||||||
|
std::vector<HushChatMemoGroupingIssueInfo> issues;
|
||||||
|
std::size_t ignored_memo_count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatTransactionInput {
|
||||||
|
std::string txid;
|
||||||
|
std::vector<HushChatMemoOutput> outputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatTransactionMetadata {
|
||||||
|
std::string txid;
|
||||||
|
HushChatHeaderType type = HushChatHeaderType::Message;
|
||||||
|
std::string conversation_id;
|
||||||
|
std::string reply_zaddr;
|
||||||
|
std::size_t header_position = 0;
|
||||||
|
std::size_t payload_position = 0;
|
||||||
|
std::size_t payload_size = 0;
|
||||||
|
// Decrypt inputs carried through from the paired header + payload memos so the chat
|
||||||
|
// service can actually decrypt (a Message) or read the request (a ContactRequest).
|
||||||
|
std::string sender_public_key_hex; // header "p": peer crypto_kx public key (hex)
|
||||||
|
std::string secretstream_header_hex; // header "e": secretstream header (hex; empty for ContactRequest)
|
||||||
|
std::string payload_memo; // ciphertext hex (Message) or plaintext request text (ContactRequest)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HushChatTransactionExtractionResult {
|
||||||
|
bool feature_enabled = false;
|
||||||
|
std::vector<HushChatTransactionMetadata> metadata;
|
||||||
|
std::vector<HushChatMemoGroupingIssueInfo> issues;
|
||||||
|
std::size_t ignored_memo_count = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr int kHushChatSupportedVersion = 0;
|
||||||
|
constexpr std::size_t kHushChatMemoByteLimit = 512;
|
||||||
|
constexpr std::size_t kHushChatPublicKeyHexLength = 64;
|
||||||
|
constexpr std::size_t kHushChatSecretstreamHeaderHexLength = 48;
|
||||||
|
|
||||||
|
constexpr bool hushChatFeatureEnabledAtBuild()
|
||||||
|
{
|
||||||
|
return DRAGONX_ENABLE_CHAT != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
HushChatHeaderParseResult parseHushChatHeaderMemo(const std::string& memo);
|
||||||
|
HushChatMemoGroupingResult groupHushChatMemoOutputs(const std::vector<HushChatMemoOutput>& outputs);
|
||||||
|
HushChatTransactionExtractionResult extractHushChatTransactionMetadata(
|
||||||
|
const HushChatTransactionInput& transaction,
|
||||||
|
bool featureEnabled = hushChatFeatureEnabledAtBuild());
|
||||||
|
const char* hushChatHeaderTypeName(HushChatHeaderType type);
|
||||||
|
const char* hushChatMemoGroupingIssueName(HushChatMemoGroupingIssue issue);
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
108
src/chat/chat_service.cpp
Normal file
108
src/chat/chat_service.cpp
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// DragonX Wallet - HushChat service (implementation).
|
||||||
|
|
||||||
|
#include "chat_service.h"
|
||||||
|
|
||||||
|
#include "chat_database.h"
|
||||||
|
#include "chat_identity.h" // chatIdentityPublicKeyHex
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
ChatService::~ChatService() {
|
||||||
|
clearIdentity();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatService::setIdentity(const ChatKeyPair& keys) {
|
||||||
|
identity_ = keys;
|
||||||
|
has_identity_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatService::clearIdentity() {
|
||||||
|
wipeChatKeyPair(identity_);
|
||||||
|
has_identity_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ChatService::ingest(const std::vector<HushChatTransactionMetadata>& metadata,
|
||||||
|
const std::unordered_map<std::string, std::int64_t>& txTimestamps,
|
||||||
|
std::int64_t fallbackTimestamp) {
|
||||||
|
if (!has_identity_) return 0;
|
||||||
|
|
||||||
|
int added = 0;
|
||||||
|
for (const auto& meta : metadata) {
|
||||||
|
ChatMessage message;
|
||||||
|
message.direction = ChatDirection::Incoming;
|
||||||
|
message.txid = meta.txid;
|
||||||
|
message.conversation_id = meta.conversation_id;
|
||||||
|
message.peer_zaddr = meta.reply_zaddr;
|
||||||
|
message.peer_public_key_hex = meta.sender_public_key_hex;
|
||||||
|
const auto timeIt = txTimestamps.find(meta.txid);
|
||||||
|
message.timestamp = timeIt != txTimestamps.end() ? timeIt->second : fallbackTimestamp;
|
||||||
|
message.payload_position = meta.payload_position;
|
||||||
|
|
||||||
|
if (meta.type == HushChatHeaderType::ContactRequest) {
|
||||||
|
message.kind = ChatMessageKind::ContactRequest;
|
||||||
|
message.body = meta.payload_memo; // plaintext request text
|
||||||
|
} else {
|
||||||
|
message.kind = ChatMessageKind::Message;
|
||||||
|
std::string plaintext;
|
||||||
|
const ChatCryptoStatus status = decryptIncoming(
|
||||||
|
identity_, meta.sender_public_key_hex, meta.secretstream_header_hex,
|
||||||
|
meta.payload_memo, plaintext);
|
||||||
|
if (status != ChatCryptoStatus::Ok) continue; // drop undecryptable silently
|
||||||
|
message.body = std::move(plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In-memory store dedups (txid+position); only persist the genuinely new ones. On the next
|
||||||
|
// session loadFromDatabase() repopulates the store, so re-scanning the chain re-ingests but
|
||||||
|
// the store dedup prevents a duplicate write.
|
||||||
|
if (store_.append(message)) {
|
||||||
|
if (db_) db_->append(message);
|
||||||
|
++added;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatService::loadFromDatabase() {
|
||||||
|
if (!db_) return;
|
||||||
|
for (const auto& message : db_->load()) {
|
||||||
|
store_.append(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ChatService::identityPublicKeyHex() const {
|
||||||
|
if (!has_identity_) return {};
|
||||||
|
return chatIdentityPublicKeyHex(identity_);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatComposeStatus ChatService::composeMessage(const std::string& myReplyZaddr,
|
||||||
|
const std::string& peerPublicKeyHex,
|
||||||
|
const std::string& peerZaddr,
|
||||||
|
const std::string& conversationId,
|
||||||
|
const std::string& plaintext,
|
||||||
|
OutgoingChatMemos& out) const {
|
||||||
|
if (!has_identity_) return ChatComposeStatus::MissingField;
|
||||||
|
return buildOutgoingMessage(identity_, chatIdentityPublicKeyHex(identity_), myReplyZaddr,
|
||||||
|
peerPublicKeyHex, peerZaddr, conversationId, plaintext, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatComposeStatus ChatService::composeContactRequest(const std::string& myReplyZaddr,
|
||||||
|
const std::string& peerZaddr,
|
||||||
|
const std::string& conversationId,
|
||||||
|
const std::string& requestText,
|
||||||
|
OutgoingChatMemos& out) const {
|
||||||
|
if (!has_identity_) return ChatComposeStatus::MissingField;
|
||||||
|
return buildOutgoingContactRequest(chatIdentityPublicKeyHex(identity_), myReplyZaddr,
|
||||||
|
peerZaddr, conversationId, requestText, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatService::recordOutgoing(const ChatMessage& message) {
|
||||||
|
if (store_.append(message)) {
|
||||||
|
if (db_) db_->append(message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
85
src/chat/chat_service.h
Normal file
85
src/chat/chat_service.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// DragonX Wallet - HushChat service: turns harvested memo metadata into decrypted, threaded
|
||||||
|
// messages. Owns the long-lived chat identity keypair (a secret) + the in-memory store.
|
||||||
|
// Move-disabled (the secret stays pinned); wipes the secret on destruction/clear.
|
||||||
|
// Not thread-safe — drive from the main thread (where refresh results are applied).
|
||||||
|
|
||||||
|
#include "chat_crypto.h" // ChatKeyPair
|
||||||
|
#include "chat_protocol.h" // HushChatTransactionMetadata
|
||||||
|
#include "chat_outgoing.h" // OutgoingChatMemos, ChatComposeStatus
|
||||||
|
#include "chat_store.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
class ChatDatabase; // optional persistent backing (Phase 2); set via setPersistence
|
||||||
|
|
||||||
|
class ChatService {
|
||||||
|
public:
|
||||||
|
ChatService() = default;
|
||||||
|
~ChatService();
|
||||||
|
ChatService(const ChatService&) = delete;
|
||||||
|
ChatService& operator=(const ChatService&) = delete;
|
||||||
|
ChatService(ChatService&&) = delete;
|
||||||
|
ChatService& operator=(ChatService&&) = delete;
|
||||||
|
|
||||||
|
// Provision (or replace) the chat identity. Copies the keypair — the caller should wipe
|
||||||
|
// its own copy afterwards (see chat_identity.h ownership note).
|
||||||
|
void setIdentity(const ChatKeyPair& keys);
|
||||||
|
bool hasIdentity() const { return has_identity_; }
|
||||||
|
void clearIdentity(); // wipes the held secret key
|
||||||
|
|
||||||
|
// Decrypt/record each metadata entry (a Message is decrypted; a ContactRequest carries its
|
||||||
|
// plaintext through) and thread it into the store. Each message is stamped with its own
|
||||||
|
// transaction time via `txTimestamps` (keyed by txid), falling back to `fallbackTimestamp`
|
||||||
|
// when the txid isn't present. Newly-added messages are also persisted (if a database is
|
||||||
|
// attached). Returns the number of NEW messages added; 0 with no identity. Undecryptable
|
||||||
|
// Messages are dropped silently (no logging of memo/plaintext).
|
||||||
|
int ingest(const std::vector<HushChatTransactionMetadata>& metadata,
|
||||||
|
const std::unordered_map<std::string, std::int64_t>& txTimestamps,
|
||||||
|
std::int64_t fallbackTimestamp = 0);
|
||||||
|
|
||||||
|
// Attach a persistent backing store (Phase 2). Not owned. Pass nullptr to detach. New messages
|
||||||
|
// from ingest() are written through; loadFromDatabase() rehydrates the in-memory store from it.
|
||||||
|
void setPersistence(ChatDatabase* db) { db_ = db; }
|
||||||
|
// Load previously-persisted messages (already decrypted at ingest, re-encrypted at rest under
|
||||||
|
// the seed-derived key) into the in-memory store. No-op without an unlocked database.
|
||||||
|
void loadFromDatabase();
|
||||||
|
|
||||||
|
// --- Outgoing (compose) ---
|
||||||
|
// My chat public key (hex), or "" without an identity — goes in an outgoing header's "p".
|
||||||
|
std::string identityPublicKeyHex() const;
|
||||||
|
// Construct the outgoing memos for an ENCRYPTED message, using the held identity to encrypt.
|
||||||
|
ChatComposeStatus composeMessage(const std::string& myReplyZaddr,
|
||||||
|
const std::string& peerPublicKeyHex,
|
||||||
|
const std::string& peerZaddr,
|
||||||
|
const std::string& conversationId,
|
||||||
|
const std::string& plaintext,
|
||||||
|
OutgoingChatMemos& out) const;
|
||||||
|
// Construct the outgoing memos for a plaintext contact request (no peer key needed yet).
|
||||||
|
ChatComposeStatus composeContactRequest(const std::string& myReplyZaddr,
|
||||||
|
const std::string& peerZaddr,
|
||||||
|
const std::string& conversationId,
|
||||||
|
const std::string& requestText,
|
||||||
|
OutgoingChatMemos& out) const;
|
||||||
|
// Echo a locally-composed outgoing message into the store (and DB). Returns true if new. (We
|
||||||
|
// never harvest our own sent memos — they land on the peer's address — so this echo is the
|
||||||
|
// only local record of what we sent.)
|
||||||
|
bool recordOutgoing(const ChatMessage& message);
|
||||||
|
|
||||||
|
const ChatStore& store() const { return store_; }
|
||||||
|
ChatStore& store() { return store_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ChatKeyPair identity_{};
|
||||||
|
bool has_identity_ = false;
|
||||||
|
ChatStore store_;
|
||||||
|
ChatDatabase* db_ = nullptr; // optional; not owned
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
39
src/chat/chat_store.cpp
Normal file
39
src/chat/chat_store.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// DragonX Wallet - HushChat in-memory message store (implementation).
|
||||||
|
|
||||||
|
#include "chat_store.h"
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
std::string ChatStore::dedupKey(const ChatMessage& message) {
|
||||||
|
return message.txid + ":" + std::to_string(message.payload_position);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChatStore::append(const ChatMessage& message) {
|
||||||
|
if (!seen_.insert(dedupKey(message)).second) return false;
|
||||||
|
messages_.push_back(message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ChatMessage> ChatStore::conversation(const std::string& conversationId) const {
|
||||||
|
std::vector<ChatMessage> out;
|
||||||
|
for (const auto& message : messages_) {
|
||||||
|
if (message.conversation_id == conversationId) out.push_back(message);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ChatStore::conversationIds() const {
|
||||||
|
std::vector<std::string> ids;
|
||||||
|
std::unordered_set<std::string> seenIds;
|
||||||
|
for (const auto& message : messages_) {
|
||||||
|
if (seenIds.insert(message.conversation_id).second) ids.push_back(message.conversation_id);
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatStore::clear() {
|
||||||
|
messages_.clear();
|
||||||
|
seen_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
38
src/chat/chat_store.h
Normal file
38
src/chat/chat_store.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// DragonX Wallet - HushChat in-memory message store: the fast read model / dedup view. Durable
|
||||||
|
// persistence lives in ChatDatabase; ChatService rehydrates this store from it on unlock.
|
||||||
|
|
||||||
|
#include "chat_message.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace dragonx::chat {
|
||||||
|
|
||||||
|
// Threads messages by conversation_id (cid) and deduplicates by (txid, payload_position) so
|
||||||
|
// re-scanning the chain never double-inserts. Not thread-safe — drive from the main thread.
|
||||||
|
class ChatStore {
|
||||||
|
public:
|
||||||
|
// Returns true if newly inserted, false if a duplicate was ignored.
|
||||||
|
bool append(const ChatMessage& message);
|
||||||
|
|
||||||
|
// Messages in a conversation, in insertion order.
|
||||||
|
std::vector<ChatMessage> conversation(const std::string& conversationId) const;
|
||||||
|
|
||||||
|
// Distinct conversation ids, in first-seen order.
|
||||||
|
std::vector<std::string> conversationIds() const;
|
||||||
|
|
||||||
|
std::size_t size() const { return messages_.size(); }
|
||||||
|
bool empty() const { return messages_.empty(); }
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::string dedupKey(const ChatMessage& message);
|
||||||
|
|
||||||
|
std::vector<ChatMessage> messages_;
|
||||||
|
std::unordered_set<std::string> seen_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace dragonx::chat
|
||||||
@@ -11,8 +11,12 @@
|
|||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <ctime>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#include "../util/logger.h"
|
#include "../util/logger.h"
|
||||||
|
#include "../util/platform.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <shlobj.h>
|
#include <shlobj.h>
|
||||||
@@ -30,35 +34,87 @@ namespace config {
|
|||||||
Settings::Settings() = default;
|
Settings::Settings() = default;
|
||||||
Settings::~Settings() = default;
|
Settings::~Settings() = default;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
Settings::LiteServerSelectionPreferenceMode parseLiteServerSelectionPreferenceMode(
|
||||||
|
const json& value)
|
||||||
|
{
|
||||||
|
if (!value.is_string()) return Settings::LiteServerSelectionPreferenceMode::Sticky;
|
||||||
|
const std::string mode = value.get<std::string>();
|
||||||
|
if (mode == "random" || mode == "Random") {
|
||||||
|
return Settings::LiteServerSelectionPreferenceMode::Random;
|
||||||
|
}
|
||||||
|
return Settings::LiteServerSelectionPreferenceMode::Sticky;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* liteServerSelectionPreferenceModeName(
|
||||||
|
Settings::LiteServerSelectionPreferenceMode mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case Settings::LiteServerSelectionPreferenceMode::Sticky:
|
||||||
|
return "sticky";
|
||||||
|
case Settings::LiteServerSelectionPreferenceMode::Random:
|
||||||
|
return "random";
|
||||||
|
}
|
||||||
|
return "sticky";
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings::PoolSelectMode parsePoolSelectMode(const json& value)
|
||||||
|
{
|
||||||
|
if (!value.is_string()) return Settings::PoolSelectMode::Manual;
|
||||||
|
const std::string mode = value.get<std::string>();
|
||||||
|
if (mode == "auto_balance" || mode == "auto") {
|
||||||
|
return Settings::PoolSelectMode::AutoBalance;
|
||||||
|
}
|
||||||
|
return Settings::PoolSelectMode::Manual;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* poolSelectModeName(Settings::PoolSelectMode mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case Settings::PoolSelectMode::Manual: return "manual";
|
||||||
|
case Settings::PoolSelectMode::AutoBalance: return "auto_balance";
|
||||||
|
}
|
||||||
|
return "manual";
|
||||||
|
}
|
||||||
|
|
||||||
|
// True if j[key] exists and holds a JSON value convertible to T. Guards every scalar
|
||||||
|
// read so a malformed value (wrong type / missing key) leaves the field's default
|
||||||
|
// instead of throwing out of the whole load().
|
||||||
|
template <typename T>
|
||||||
|
bool jsonHasType(const json& v)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<T, bool>) return v.is_boolean();
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) return v.is_string();
|
||||||
|
else if constexpr (std::is_floating_point_v<T>) return v.is_number();
|
||||||
|
else if constexpr (std::is_integral_v<T>) return v.is_number_integer();
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reads j[key] into field only when present AND of the matching type.
|
||||||
|
template <typename T>
|
||||||
|
void loadScalar(const json& j, const char* key, T& field)
|
||||||
|
{
|
||||||
|
if (j.contains(key) && jsonHasType<T>(j[key])) field = j[key].get<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as loadScalar but clamps the read value into [lo, hi].
|
||||||
|
template <typename T>
|
||||||
|
void loadClamped(const json& j, const char* key, T& field, T lo, T hi)
|
||||||
|
{
|
||||||
|
if (j.contains(key) && jsonHasType<T>(j[key]))
|
||||||
|
field = std::max(lo, std::min(hi, j[key].get<T>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
std::string Settings::getDefaultPath()
|
std::string Settings::getDefaultPath()
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
// Single per-platform, per-variant config dir (util::Platform::getConfigDir handles the
|
||||||
char path[MAX_PATH];
|
// _WIN32 / __APPLE__ / XDG split and the DRAGONX_APP_NAME variant suffix in one place).
|
||||||
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, path))) {
|
const std::string dir = util::Platform::getConfigDir();
|
||||||
std::string dir = std::string(path) + "\\ObsidianDragon";
|
|
||||||
fs::create_directories(dir);
|
|
||||||
return dir + "\\settings.json";
|
|
||||||
}
|
|
||||||
return "settings.json";
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pw = getpwuid(getuid());
|
|
||||||
home = pw->pw_dir;
|
|
||||||
}
|
|
||||||
std::string dir = std::string(home) + "/Library/Application Support/ObsidianDragon";
|
|
||||||
fs::create_directories(dir);
|
fs::create_directories(dir);
|
||||||
return dir + "/settings.json";
|
return (fs::path(dir) / "settings.json").string();
|
||||||
#else
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pw = getpwuid(getuid());
|
|
||||||
home = pw->pw_dir;
|
|
||||||
}
|
|
||||||
std::string dir = std::string(home) + "/.config/ObsidianDragon";
|
|
||||||
fs::create_directories(dir);
|
|
||||||
return dir + "/settings.json";
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::load()
|
bool Settings::load()
|
||||||
@@ -79,29 +135,30 @@ bool Settings::load(const std::string& path)
|
|||||||
json j;
|
json j;
|
||||||
file >> j;
|
file >> j;
|
||||||
|
|
||||||
if (j.contains("theme")) theme_ = j["theme"].get<std::string>();
|
loadScalar(j, "theme", theme_);
|
||||||
if (j.contains("save_ztxs")) save_ztxs_ = j["save_ztxs"].get<bool>();
|
loadScalar(j, "save_ztxs", save_ztxs_);
|
||||||
if (j.contains("auto_shield")) auto_shield_ = j["auto_shield"].get<bool>();
|
loadScalar(j, "auto_shield", auto_shield_);
|
||||||
if (j.contains("use_tor")) use_tor_ = j["use_tor"].get<bool>();
|
loadScalar(j, "use_tor", use_tor_);
|
||||||
if (j.contains("allow_custom_fees")) allow_custom_fees_ = j["allow_custom_fees"].get<bool>();
|
loadScalar(j, "allow_custom_fees", allow_custom_fees_);
|
||||||
if (j.contains("default_fee")) default_fee_ = j["default_fee"].get<double>();
|
loadScalar(j, "default_fee", default_fee_);
|
||||||
if (j.contains("fetch_prices")) fetch_prices_ = j["fetch_prices"].get<bool>();
|
loadScalar(j, "fetch_prices", fetch_prices_);
|
||||||
if (j.contains("tx_explorer_url")) tx_explorer_url_ = j["tx_explorer_url"].get<std::string>();
|
loadScalar(j, "tx_explorer_url", tx_explorer_url_);
|
||||||
if (j.contains("address_explorer_url")) address_explorer_url_ = j["address_explorer_url"].get<std::string>();
|
loadScalar(j, "address_explorer_url", address_explorer_url_);
|
||||||
if (j.contains("language")) language_ = j["language"].get<std::string>();
|
loadScalar(j, "language", language_);
|
||||||
if (j.contains("skin_id")) skin_id_ = j["skin_id"].get<std::string>();
|
loadScalar(j, "skin_id", skin_id_);
|
||||||
if (j.contains("acrylic_enabled")) acrylic_enabled_ = j["acrylic_enabled"].get<bool>();
|
loadScalar(j, "chat_reply_zaddr", chat_reply_zaddr_);
|
||||||
if (j.contains("acrylic_quality")) acrylic_quality_ = j["acrylic_quality"].get<int>();
|
loadScalar(j, "acrylic_enabled", acrylic_enabled_);
|
||||||
if (j.contains("blur_multiplier")) blur_multiplier_ = j["blur_multiplier"].get<float>();
|
loadScalar(j, "acrylic_quality", acrylic_quality_);
|
||||||
if (j.contains("noise_opacity")) noise_opacity_ = j["noise_opacity"].get<float>();
|
loadScalar(j, "blur_multiplier", blur_multiplier_);
|
||||||
if (j.contains("gradient_background")) gradient_background_ = j["gradient_background"].get<bool>();
|
loadScalar(j, "noise_opacity", noise_opacity_);
|
||||||
|
loadScalar(j, "gradient_background", gradient_background_);
|
||||||
// Migrate legacy reduced_transparency bool -> ui_opacity float
|
// Migrate legacy reduced_transparency bool -> ui_opacity float
|
||||||
if (j.contains("ui_opacity")) {
|
if (j.contains("ui_opacity")) {
|
||||||
ui_opacity_ = j["ui_opacity"].get<float>();
|
ui_opacity_ = j["ui_opacity"].get<float>();
|
||||||
} else if (j.contains("reduced_transparency") && j["reduced_transparency"].get<bool>()) {
|
} else if (j.contains("reduced_transparency") && j["reduced_transparency"].get<bool>()) {
|
||||||
ui_opacity_ = 1.0f; // legacy: reduced = fully opaque
|
ui_opacity_ = 1.0f; // legacy: reduced = fully opaque
|
||||||
}
|
}
|
||||||
if (j.contains("window_opacity")) window_opacity_ = j["window_opacity"].get<float>();
|
loadScalar(j, "window_opacity", window_opacity_);
|
||||||
if (j.contains("balance_layout")) {
|
if (j.contains("balance_layout")) {
|
||||||
if (j["balance_layout"].is_string())
|
if (j["balance_layout"].is_string())
|
||||||
balance_layout_ = j["balance_layout"].get<std::string>();
|
balance_layout_ = j["balance_layout"].get<std::string>();
|
||||||
@@ -115,7 +172,7 @@ bool Settings::load(const std::string& path)
|
|||||||
if (idx >= 0 && idx < 9) balance_layout_ = legacyIds[idx];
|
if (idx >= 0 && idx < 9) balance_layout_ = legacyIds[idx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (j.contains("scanline_enabled")) scanline_enabled_ = j["scanline_enabled"].get<bool>();
|
loadScalar(j, "scanline_enabled", scanline_enabled_);
|
||||||
if (j.contains("hidden_addresses") && j["hidden_addresses"].is_array()) {
|
if (j.contains("hidden_addresses") && j["hidden_addresses"].is_array()) {
|
||||||
hidden_addresses_.clear();
|
hidden_addresses_.clear();
|
||||||
for (const auto& a : j["hidden_addresses"])
|
for (const auto& a : j["hidden_addresses"])
|
||||||
@@ -126,37 +183,123 @@ bool Settings::load(const std::string& path)
|
|||||||
for (const auto& a : j["favorite_addresses"])
|
for (const auto& a : j["favorite_addresses"])
|
||||||
if (a.is_string()) favorite_addresses_.insert(a.get<std::string>());
|
if (a.is_string()) favorite_addresses_.insert(a.get<std::string>());
|
||||||
}
|
}
|
||||||
if (j.contains("wizard_completed")) wizard_completed_ = j["wizard_completed"].get<bool>();
|
if (j.contains("address_meta") && j["address_meta"].is_object()) {
|
||||||
if (j.contains("auto_lock_timeout")) auto_lock_timeout_ = j["auto_lock_timeout"].get<int>();
|
address_meta_.clear();
|
||||||
if (j.contains("unlock_duration")) unlock_duration_ = j["unlock_duration"].get<int>();
|
for (auto& [addr, meta] : j["address_meta"].items()) {
|
||||||
if (j.contains("pin_enabled")) pin_enabled_ = j["pin_enabled"].get<bool>();
|
AddressMeta m;
|
||||||
if (j.contains("keep_daemon_running")) keep_daemon_running_ = j["keep_daemon_running"].get<bool>();
|
if (meta.contains("label") && meta["label"].is_string())
|
||||||
if (j.contains("stop_external_daemon")) stop_external_daemon_ = j["stop_external_daemon"].get<bool>();
|
m.label = meta["label"].get<std::string>();
|
||||||
if (j.contains("verbose_logging")) verbose_logging_ = j["verbose_logging"].get<bool>();
|
if (meta.contains("icon") && meta["icon"].is_string())
|
||||||
|
m.icon = meta["icon"].get<std::string>();
|
||||||
|
if (meta.contains("order") && meta["order"].is_number_integer())
|
||||||
|
m.sortOrder = meta["order"].get<int>();
|
||||||
|
if (meta.contains("mining") && meta["mining"].is_boolean())
|
||||||
|
m.mining = meta["mining"].get<bool>();
|
||||||
|
address_meta_[addr] = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadScalar(j, "wizard_completed", wizard_completed_);
|
||||||
|
loadScalar(j, "seed_backup_reminded", seed_backup_reminded_);
|
||||||
|
loadScalar(j, "seed_migration_pending", seed_migration_pending_);
|
||||||
|
loadScalar(j, "seed_migration_dest", seed_migration_dest_);
|
||||||
|
loadScalar(j, "seed_migration_temp_dir", seed_migration_temp_dir_);
|
||||||
|
loadScalar(j, "seed_migration_sweep_txid", seed_migration_sweep_txid_);
|
||||||
|
loadScalar(j, "auto_lock_timeout", auto_lock_timeout_);
|
||||||
|
loadScalar(j, "unlock_duration", unlock_duration_);
|
||||||
|
loadScalar(j, "pin_enabled", pin_enabled_);
|
||||||
|
loadScalar(j, "keep_daemon_running", keep_daemon_running_);
|
||||||
|
loadScalar(j, "stop_external_daemon", stop_external_daemon_);
|
||||||
|
loadScalar(j, "max_connections", max_connections_);
|
||||||
|
if (j.contains("lite_wallet") && j["lite_wallet"].is_object()) {
|
||||||
|
const auto& lite = j["lite_wallet"];
|
||||||
|
if (lite.contains("server_selection_mode")) {
|
||||||
|
lite_server_selection_mode_ = parseLiteServerSelectionPreferenceMode(
|
||||||
|
lite["server_selection_mode"]);
|
||||||
|
}
|
||||||
|
if (lite.contains("sticky_server_url") && lite["sticky_server_url"].is_string()) {
|
||||||
|
lite_sticky_server_url_ = lite["sticky_server_url"].get<std::string>();
|
||||||
|
}
|
||||||
|
if (lite.contains("chain_name") && lite["chain_name"].is_string()) {
|
||||||
|
lite_chain_name_ = lite["chain_name"].get<std::string>();
|
||||||
|
}
|
||||||
|
// Migration: the SDXL backend only accepts main/test/regtest and hard-panics
|
||||||
|
// (process abort) on any other chain name. Older builds persisted the "DRAGONX"
|
||||||
|
// ticker here, which crashed the lite backend on launch. Rewrite any invalid
|
||||||
|
// value to "main" and flag a re-save so the corrected setting persists.
|
||||||
|
if (lite_chain_name_ != "main" && lite_chain_name_ != "test" &&
|
||||||
|
lite_chain_name_ != "regtest") {
|
||||||
|
lite_chain_name_ = "main";
|
||||||
|
needs_upgrade_save_ = true;
|
||||||
|
}
|
||||||
|
if (lite.contains("random_selection_seed") && lite["random_selection_seed"].is_number_unsigned()) {
|
||||||
|
lite_random_selection_seed_ = lite["random_selection_seed"].get<std::size_t>();
|
||||||
|
} else if (lite.contains("random_selection_seed") && lite["random_selection_seed"].is_number_integer()) {
|
||||||
|
const auto seed = lite["random_selection_seed"].get<long long>();
|
||||||
|
lite_random_selection_seed_ = seed > 0 ? static_cast<std::size_t>(seed) : 0;
|
||||||
|
}
|
||||||
|
if (lite.contains("persist_selected_server") && lite["persist_selected_server"].is_boolean()) {
|
||||||
|
lite_persist_selected_server_ = lite["persist_selected_server"].get<bool>();
|
||||||
|
}
|
||||||
|
if (lite.contains("servers") && lite["servers"].is_array()) {
|
||||||
|
lite_servers_.clear();
|
||||||
|
for (const auto& server : lite["servers"]) {
|
||||||
|
if (!server.is_object()) continue;
|
||||||
|
LiteServerPreference preference;
|
||||||
|
if (server.contains("url") && server["url"].is_string()) {
|
||||||
|
preference.url = server["url"].get<std::string>();
|
||||||
|
}
|
||||||
|
if (server.contains("label") && server["label"].is_string()) {
|
||||||
|
preference.label = server["label"].get<std::string>();
|
||||||
|
}
|
||||||
|
if (server.contains("enabled") && server["enabled"].is_boolean()) {
|
||||||
|
preference.enabled = server["enabled"].get<bool>();
|
||||||
|
}
|
||||||
|
lite_servers_.push_back(preference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lite.contains("rollout_override") && lite["rollout_override"].is_string()) {
|
||||||
|
const auto v = lite["rollout_override"].get<std::string>();
|
||||||
|
lite_rollout_override_ = (v == "force_on" || v == "force_off") ? v : "auto";
|
||||||
|
}
|
||||||
|
if (lite.contains("install_id") && lite["install_id"].is_string()) {
|
||||||
|
lite_install_id_ = lite["install_id"].get<std::string>();
|
||||||
|
}
|
||||||
|
if (lite.contains("hidden_servers") && lite["hidden_servers"].is_array()) {
|
||||||
|
lite_hidden_servers_.clear();
|
||||||
|
for (const auto& u : lite["hidden_servers"])
|
||||||
|
if (u.is_string()) lite_hidden_servers_.insert(u.get<std::string>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadScalar(j, "verbose_logging", verbose_logging_);
|
||||||
if (j.contains("debug_categories") && j["debug_categories"].is_array()) {
|
if (j.contains("debug_categories") && j["debug_categories"].is_array()) {
|
||||||
debug_categories_.clear();
|
debug_categories_.clear();
|
||||||
for (const auto& c : j["debug_categories"])
|
for (const auto& c : j["debug_categories"])
|
||||||
if (c.is_string()) debug_categories_.insert(c.get<std::string>());
|
if (c.is_string()) debug_categories_.insert(c.get<std::string>());
|
||||||
}
|
}
|
||||||
if (j.contains("theme_effects_enabled")) theme_effects_enabled_ = j["theme_effects_enabled"].get<bool>();
|
loadScalar(j, "theme_effects_enabled", theme_effects_enabled_);
|
||||||
if (j.contains("low_spec_mode")) low_spec_mode_ = j["low_spec_mode"].get<bool>();
|
loadScalar(j, "low_spec_mode", low_spec_mode_);
|
||||||
if (j.contains("selected_exchange")) selected_exchange_ = j["selected_exchange"].get<std::string>();
|
loadScalar(j, "reduce_motion", reduce_motion_);
|
||||||
if (j.contains("selected_pair")) selected_pair_ = j["selected_pair"].get<std::string>();
|
loadScalar(j, "selected_exchange", selected_exchange_);
|
||||||
if (j.contains("pool_url")) pool_url_ = j["pool_url"].get<std::string>();
|
loadScalar(j, "selected_pair", selected_pair_);
|
||||||
|
loadScalar(j, "pool_url", pool_url_);
|
||||||
// Migrate old default pool URL that was missing the stratum port
|
// Migrate old default pool URL that was missing the stratum port
|
||||||
if (pool_url_ == "pool.dragonx.is") pool_url_ = "pool.dragonx.is:3433";
|
if (pool_url_ == "pool.dragonx.is") pool_url_ = "pool.dragonx.is:3433";
|
||||||
if (j.contains("pool_algo")) pool_algo_ = j["pool_algo"].get<std::string>();
|
loadScalar(j, "pool_algo", pool_algo_);
|
||||||
if (j.contains("pool_worker")) pool_worker_ = j["pool_worker"].get<std::string>();
|
loadScalar(j, "pool_worker", pool_worker_);
|
||||||
if (j.contains("pool_threads")) pool_threads_ = j["pool_threads"].get<int>();
|
loadScalar(j, "pool_threads", pool_threads_);
|
||||||
if (j.contains("pool_tls")) pool_tls_ = j["pool_tls"].get<bool>();
|
loadScalar(j, "pool_tls", pool_tls_);
|
||||||
if (j.contains("pool_hugepages")) pool_hugepages_ = j["pool_hugepages"].get<bool>();
|
loadScalar(j, "pool_hugepages", pool_hugepages_);
|
||||||
if (j.contains("pool_mode")) pool_mode_ = j["pool_mode"].get<bool>();
|
loadScalar(j, "pool_mode", pool_mode_);
|
||||||
if (j.contains("mine_when_idle")) mine_when_idle_ = j["mine_when_idle"].get<bool>();
|
if (j.contains("pool_select_mode")) pool_select_mode_ = parsePoolSelectMode(j["pool_select_mode"]);
|
||||||
if (j.contains("mine_idle_delay")) mine_idle_delay_= std::max(30, j["mine_idle_delay"].get<int>());
|
loadScalar(j, "mine_when_idle", mine_when_idle_);
|
||||||
if (j.contains("idle_thread_scaling")) idle_thread_scaling_ = j["idle_thread_scaling"].get<bool>();
|
loadScalar(j, "xmrig_version", xmrig_version_);
|
||||||
if (j.contains("idle_threads_active")) idle_threads_active_ = j["idle_threads_active"].get<int>();
|
// Lower-bounded only (min 30s), matching setMineIdleDelay; now type-guarded too.
|
||||||
if (j.contains("idle_threads_idle")) idle_threads_idle_ = j["idle_threads_idle"].get<int>();
|
if (j.contains("mine_idle_delay") && j["mine_idle_delay"].is_number_integer())
|
||||||
if (j.contains("idle_gpu_aware")) idle_gpu_aware_ = j["idle_gpu_aware"].get<bool>();
|
mine_idle_delay_ = std::max(30, j["mine_idle_delay"].get<int>());
|
||||||
|
loadScalar(j, "idle_thread_scaling", idle_thread_scaling_);
|
||||||
|
loadScalar(j, "idle_threads_active", idle_threads_active_);
|
||||||
|
loadScalar(j, "idle_threads_idle", idle_threads_idle_);
|
||||||
|
loadScalar(j, "idle_gpu_aware", idle_gpu_aware_);
|
||||||
if (j.contains("saved_pool_urls") && j["saved_pool_urls"].is_array()) {
|
if (j.contains("saved_pool_urls") && j["saved_pool_urls"].is_array()) {
|
||||||
saved_pool_urls_.clear();
|
saved_pool_urls_.clear();
|
||||||
for (const auto& u : j["saved_pool_urls"])
|
for (const auto& u : j["saved_pool_urls"])
|
||||||
@@ -167,15 +310,55 @@ bool Settings::load(const std::string& path)
|
|||||||
for (const auto& w : j["saved_pool_workers"])
|
for (const auto& w : j["saved_pool_workers"])
|
||||||
if (w.is_string()) saved_pool_workers_.push_back(w.get<std::string>());
|
if (w.is_string()) saved_pool_workers_.push_back(w.get<std::string>());
|
||||||
}
|
}
|
||||||
if (j.contains("font_scale") && j["font_scale"].is_number())
|
if (j.contains("portfolio_entries") && j["portfolio_entries"].is_array()) {
|
||||||
font_scale_ = std::max(1.0f, std::min(1.5f, j["font_scale"].get<float>()));
|
portfolio_entries_.clear();
|
||||||
if (j.contains("window_width") && j["window_width"].is_number_integer())
|
for (const auto& e : j["portfolio_entries"]) {
|
||||||
window_width_ = j["window_width"].get<int>();
|
if (!e.is_object()) continue;
|
||||||
if (j.contains("window_height") && j["window_height"].is_number_integer())
|
PortfolioEntry entry;
|
||||||
window_height_ = j["window_height"].get<int>();
|
if (e.contains("label") && e["label"].is_string())
|
||||||
|
entry.label = e["label"].get<std::string>();
|
||||||
|
if (e.contains("addresses") && e["addresses"].is_array())
|
||||||
|
for (const auto& a : e["addresses"])
|
||||||
|
if (a.is_string()) entry.addresses.push_back(a.get<std::string>());
|
||||||
|
if (e.contains("icon") && e["icon"].is_string())
|
||||||
|
entry.icon = e["icon"].get<std::string>();
|
||||||
|
if (e.contains("color") && e["color"].is_number())
|
||||||
|
entry.color = e["color"].get<unsigned int>();
|
||||||
|
if (e.contains("outline_opacity") && e["outline_opacity"].is_number_integer())
|
||||||
|
entry.outlineOpacity = e["outline_opacity"].get<int>();
|
||||||
|
if (e.contains("price_basis") && e["price_basis"].is_number_integer())
|
||||||
|
entry.priceBasis = e["price_basis"].get<int>();
|
||||||
|
if (e.contains("manual_price") && e["manual_price"].is_number())
|
||||||
|
entry.manualPrice = e["manual_price"].get<double>();
|
||||||
|
if (e.contains("manual_currency") && e["manual_currency"].is_string())
|
||||||
|
entry.manualCurrency = e["manual_currency"].get<std::string>();
|
||||||
|
if (e.contains("show_drgx") && e["show_drgx"].is_boolean())
|
||||||
|
entry.showDrgx = e["show_drgx"].get<bool>();
|
||||||
|
if (e.contains("show_value") && e["show_value"].is_boolean())
|
||||||
|
entry.showValue = e["show_value"].get<bool>();
|
||||||
|
if (e.contains("show_24h") && e["show_24h"].is_boolean())
|
||||||
|
entry.show24h = e["show_24h"].get<bool>();
|
||||||
|
if (e.contains("show_sparkline") && e["show_sparkline"].is_boolean())
|
||||||
|
entry.showSparkline = e["show_sparkline"].get<bool>();
|
||||||
|
if (e.contains("sparkline_interval") && e["sparkline_interval"].is_number_integer())
|
||||||
|
entry.sparklineInterval = e["sparkline_interval"].get<int>();
|
||||||
|
if (e.contains("grid_col") && e["grid_col"].is_number_integer())
|
||||||
|
entry.gridCol = e["grid_col"].get<int>();
|
||||||
|
if (e.contains("grid_row") && e["grid_row"].is_number_integer())
|
||||||
|
entry.gridRow = e["grid_row"].get<int>();
|
||||||
|
if (e.contains("grid_w") && e["grid_w"].is_number_integer())
|
||||||
|
entry.gridW = e["grid_w"].get<int>();
|
||||||
|
if (e.contains("grid_h") && e["grid_h"].is_number_integer())
|
||||||
|
entry.gridH = e["grid_h"].get<int>();
|
||||||
|
if (!entry.label.empty()) portfolio_entries_.push_back(std::move(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loadClamped(j, "font_scale", font_scale_, 1.0f, 1.5f);
|
||||||
|
loadScalar(j, "window_width", window_width_);
|
||||||
|
loadScalar(j, "window_height", window_height_);
|
||||||
|
|
||||||
// Version tracking — detect upgrades so we can re-save with new defaults
|
// Version tracking — detect upgrades so we can re-save with new defaults
|
||||||
if (j.contains("settings_version")) settings_version_ = j["settings_version"].get<std::string>();
|
loadScalar(j, "settings_version", settings_version_);
|
||||||
if (settings_version_ != DRAGONX_VERSION) {
|
if (settings_version_ != DRAGONX_VERSION) {
|
||||||
DEBUG_LOGF("Settings version %s differs from wallet %s — will re-save\n",
|
DEBUG_LOGF("Settings version %s differs from wallet %s — will re-save\n",
|
||||||
settings_version_.empty() ? "(none)" : settings_version_.c_str(),
|
settings_version_.empty() ? "(none)" : settings_version_.c_str(),
|
||||||
@@ -186,6 +369,17 @@ bool Settings::load(const std::string& path)
|
|||||||
return true;
|
return true;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
DEBUG_LOGF("Failed to parse settings: %s\n", e.what());
|
DEBUG_LOGF("Failed to parse settings: %s\n", e.what());
|
||||||
|
// The file exists but is unparseable (truncated/corrupt). Quarantine it so the
|
||||||
|
// next save() doesn't silently overwrite it with defaults — the user's data stays
|
||||||
|
// recoverable. Proceed with in-memory defaults.
|
||||||
|
file.close();
|
||||||
|
std::error_code ec;
|
||||||
|
const std::string quarantine =
|
||||||
|
path + ".corrupt-" + std::to_string(static_cast<long long>(std::time(nullptr)));
|
||||||
|
fs::rename(path, quarantine, ec);
|
||||||
|
if (!ec) {
|
||||||
|
DEBUG_LOGF("Quarantined corrupt settings to %s\n", quarantine.c_str());
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,6 +407,7 @@ bool Settings::save(const std::string& path)
|
|||||||
j["address_explorer_url"] = address_explorer_url_;
|
j["address_explorer_url"] = address_explorer_url_;
|
||||||
j["language"] = language_;
|
j["language"] = language_;
|
||||||
j["skin_id"] = skin_id_;
|
j["skin_id"] = skin_id_;
|
||||||
|
j["chat_reply_zaddr"] = chat_reply_zaddr_;
|
||||||
j["acrylic_enabled"] = acrylic_enabled_;
|
j["acrylic_enabled"] = acrylic_enabled_;
|
||||||
j["acrylic_quality"] = acrylic_quality_;
|
j["acrylic_quality"] = acrylic_quality_;
|
||||||
j["blur_multiplier"] = blur_multiplier_;
|
j["blur_multiplier"] = blur_multiplier_;
|
||||||
@@ -228,18 +423,59 @@ bool Settings::save(const std::string& path)
|
|||||||
j["favorite_addresses"] = json::array();
|
j["favorite_addresses"] = json::array();
|
||||||
for (const auto& addr : favorite_addresses_)
|
for (const auto& addr : favorite_addresses_)
|
||||||
j["favorite_addresses"].push_back(addr);
|
j["favorite_addresses"].push_back(addr);
|
||||||
|
{
|
||||||
|
json meta_obj = json::object();
|
||||||
|
for (const auto& [addr, m] : address_meta_) {
|
||||||
|
if (m.label.empty() && m.icon.empty() && m.sortOrder < 0 && !m.mining) continue;
|
||||||
|
json entry = json::object();
|
||||||
|
if (!m.label.empty()) entry["label"] = m.label;
|
||||||
|
if (!m.icon.empty()) entry["icon"] = m.icon;
|
||||||
|
if (m.sortOrder >= 0) entry["order"] = m.sortOrder;
|
||||||
|
if (m.mining) entry["mining"] = true;
|
||||||
|
meta_obj[addr] = entry;
|
||||||
|
}
|
||||||
|
j["address_meta"] = meta_obj;
|
||||||
|
}
|
||||||
j["wizard_completed"] = wizard_completed_;
|
j["wizard_completed"] = wizard_completed_;
|
||||||
|
j["seed_backup_reminded"] = seed_backup_reminded_;
|
||||||
|
j["seed_migration_pending"] = seed_migration_pending_;
|
||||||
|
j["seed_migration_dest"] = seed_migration_dest_;
|
||||||
|
j["seed_migration_temp_dir"] = seed_migration_temp_dir_;
|
||||||
|
j["seed_migration_sweep_txid"] = seed_migration_sweep_txid_;
|
||||||
j["auto_lock_timeout"] = auto_lock_timeout_;
|
j["auto_lock_timeout"] = auto_lock_timeout_;
|
||||||
j["unlock_duration"] = unlock_duration_;
|
j["unlock_duration"] = unlock_duration_;
|
||||||
j["pin_enabled"] = pin_enabled_;
|
j["pin_enabled"] = pin_enabled_;
|
||||||
j["keep_daemon_running"] = keep_daemon_running_;
|
j["keep_daemon_running"] = keep_daemon_running_;
|
||||||
j["stop_external_daemon"] = stop_external_daemon_;
|
j["stop_external_daemon"] = stop_external_daemon_;
|
||||||
|
j["max_connections"] = max_connections_;
|
||||||
|
{
|
||||||
|
json lite = json::object();
|
||||||
|
lite["server_selection_mode"] = liteServerSelectionPreferenceModeName(lite_server_selection_mode_);
|
||||||
|
lite["sticky_server_url"] = lite_sticky_server_url_;
|
||||||
|
lite["chain_name"] = lite_chain_name_;
|
||||||
|
lite["random_selection_seed"] = lite_random_selection_seed_;
|
||||||
|
lite["persist_selected_server"] = lite_persist_selected_server_;
|
||||||
|
lite["servers"] = json::array();
|
||||||
|
for (const auto& server : lite_servers_) {
|
||||||
|
json entry = json::object();
|
||||||
|
entry["url"] = server.url;
|
||||||
|
entry["label"] = server.label;
|
||||||
|
entry["enabled"] = server.enabled;
|
||||||
|
lite["servers"].push_back(entry);
|
||||||
|
}
|
||||||
|
lite["rollout_override"] = lite_rollout_override_;
|
||||||
|
lite["install_id"] = lite_install_id_;
|
||||||
|
lite["hidden_servers"] = json::array();
|
||||||
|
for (const auto& u : lite_hidden_servers_) lite["hidden_servers"].push_back(u);
|
||||||
|
j["lite_wallet"] = lite;
|
||||||
|
}
|
||||||
j["verbose_logging"] = verbose_logging_;
|
j["verbose_logging"] = verbose_logging_;
|
||||||
j["debug_categories"] = json::array();
|
j["debug_categories"] = json::array();
|
||||||
for (const auto& cat : debug_categories_)
|
for (const auto& cat : debug_categories_)
|
||||||
j["debug_categories"].push_back(cat);
|
j["debug_categories"].push_back(cat);
|
||||||
j["theme_effects_enabled"] = theme_effects_enabled_;
|
j["theme_effects_enabled"] = theme_effects_enabled_;
|
||||||
j["low_spec_mode"] = low_spec_mode_;
|
j["low_spec_mode"] = low_spec_mode_;
|
||||||
|
j["reduce_motion"] = reduce_motion_;
|
||||||
j["selected_exchange"] = selected_exchange_;
|
j["selected_exchange"] = selected_exchange_;
|
||||||
j["selected_pair"] = selected_pair_;
|
j["selected_pair"] = selected_pair_;
|
||||||
j["pool_url"] = pool_url_;
|
j["pool_url"] = pool_url_;
|
||||||
@@ -249,7 +485,9 @@ bool Settings::save(const std::string& path)
|
|||||||
j["pool_tls"] = pool_tls_;
|
j["pool_tls"] = pool_tls_;
|
||||||
j["pool_hugepages"] = pool_hugepages_;
|
j["pool_hugepages"] = pool_hugepages_;
|
||||||
j["pool_mode"] = pool_mode_;
|
j["pool_mode"] = pool_mode_;
|
||||||
|
j["pool_select_mode"] = poolSelectModeName(pool_select_mode_);
|
||||||
j["mine_when_idle"] = mine_when_idle_;
|
j["mine_when_idle"] = mine_when_idle_;
|
||||||
|
j["xmrig_version"] = xmrig_version_;
|
||||||
j["mine_idle_delay"]= mine_idle_delay_;
|
j["mine_idle_delay"]= mine_idle_delay_;
|
||||||
j["idle_thread_scaling"] = idle_thread_scaling_;
|
j["idle_thread_scaling"] = idle_thread_scaling_;
|
||||||
j["idle_threads_active"] = idle_threads_active_;
|
j["idle_threads_active"] = idle_threads_active_;
|
||||||
@@ -261,6 +499,29 @@ bool Settings::save(const std::string& path)
|
|||||||
j["saved_pool_workers"] = json::array();
|
j["saved_pool_workers"] = json::array();
|
||||||
for (const auto& w : saved_pool_workers_)
|
for (const auto& w : saved_pool_workers_)
|
||||||
j["saved_pool_workers"].push_back(w);
|
j["saved_pool_workers"].push_back(w);
|
||||||
|
j["portfolio_entries"] = json::array();
|
||||||
|
for (const auto& e : portfolio_entries_) {
|
||||||
|
json entry;
|
||||||
|
entry["label"] = e.label;
|
||||||
|
entry["addresses"] = json::array();
|
||||||
|
for (const auto& a : e.addresses) entry["addresses"].push_back(a);
|
||||||
|
entry["icon"] = e.icon;
|
||||||
|
entry["color"] = e.color;
|
||||||
|
entry["outline_opacity"] = e.outlineOpacity;
|
||||||
|
entry["price_basis"] = e.priceBasis;
|
||||||
|
entry["manual_price"] = e.manualPrice;
|
||||||
|
entry["manual_currency"] = e.manualCurrency;
|
||||||
|
entry["show_drgx"] = e.showDrgx;
|
||||||
|
entry["show_value"] = e.showValue;
|
||||||
|
entry["show_24h"] = e.show24h;
|
||||||
|
entry["show_sparkline"] = e.showSparkline;
|
||||||
|
entry["sparkline_interval"] = e.sparklineInterval;
|
||||||
|
entry["grid_col"] = e.gridCol;
|
||||||
|
entry["grid_row"] = e.gridRow;
|
||||||
|
entry["grid_w"] = e.gridW;
|
||||||
|
entry["grid_h"] = e.gridH;
|
||||||
|
j["portfolio_entries"].push_back(std::move(entry));
|
||||||
|
}
|
||||||
j["font_scale"] = font_scale_;
|
j["font_scale"] = font_scale_;
|
||||||
j["settings_version"] = std::string(DRAGONX_VERSION);
|
j["settings_version"] = std::string(DRAGONX_VERSION);
|
||||||
if (window_width_ > 0 && window_height_ > 0) {
|
if (window_width_ > 0 && window_height_ > 0) {
|
||||||
@@ -269,17 +530,11 @@ bool Settings::save(const std::string& path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Ensure directory exists
|
// Atomic + durable: write to a temp file, fsync, then rename over the real file.
|
||||||
fs::path p(path);
|
// A crash mid-write can no longer truncate settings.json (which would silently
|
||||||
fs::create_directories(p.parent_path());
|
// reset every preference on the next launch). Owner-only (0600) — it carries the
|
||||||
|
// lite-server list and address metadata.
|
||||||
std::ofstream file(path);
|
return util::Platform::writeFileAtomically(path, j.dump(4), /*restrictPermissions=*/true);
|
||||||
if (!file.is_open()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
file << j.dump(4);
|
|
||||||
return true;
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
DEBUG_LOGF("Failed to save settings: %s\n", e.what());
|
DEBUG_LOGF("Failed to save settings: %s\n", e.what());
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -53,6 +55,51 @@ public:
|
|||||||
*/
|
*/
|
||||||
static std::string getDefaultPath();
|
static std::string getDefaultPath();
|
||||||
|
|
||||||
|
enum class LiteServerSelectionPreferenceMode {
|
||||||
|
Sticky,
|
||||||
|
Random
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pool selection mode for the mining tab: Manual (user picks the pool) or
|
||||||
|
// AutoBalance (the wallet spreads miners across the official pools by hashrate).
|
||||||
|
enum class PoolSelectMode {
|
||||||
|
Manual,
|
||||||
|
AutoBalance
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LiteServerPreference {
|
||||||
|
std::string url;
|
||||||
|
std::string label;
|
||||||
|
bool enabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A user-defined portfolio entry: a custom label tied to a group of wallet addresses.
|
||||||
|
// The Market tab's portfolio card sums these addresses' balances under the label.
|
||||||
|
struct PortfolioEntry {
|
||||||
|
std::string label;
|
||||||
|
std::vector<std::string> addresses;
|
||||||
|
std::string icon; // project_icons wallet-icon name; empty = no icon
|
||||||
|
unsigned int color = 0; // packed IM_COL32 accent; 0 = theme default
|
||||||
|
int outlineOpacity = 25; // accent-outline opacity, percent (0-100)
|
||||||
|
// Per-group price data. priceBasis: 0 = live market (USD), 1 = live market (BTC),
|
||||||
|
// 2 = DRGX only (no fiat value), 3 = manual price. Defaults preserve prior behavior
|
||||||
|
// (show DRGX + USD value).
|
||||||
|
int priceBasis = 0;
|
||||||
|
double manualPrice = 0.0; // price per DRGX for the Manual basis
|
||||||
|
std::string manualCurrency = "USD";
|
||||||
|
bool showDrgx = true; // show the DRGX amount on the card
|
||||||
|
bool showValue = true; // show the converted/fiat value on the card
|
||||||
|
bool show24h = false; // show the 24h % change (live-market bases only)
|
||||||
|
bool showSparkline = false; // show a price-trend sparkline (live-market bases only)
|
||||||
|
int sparklineInterval = 0; // 0=minute 1=hour 2=day 3=week 4=month (resample of price history)
|
||||||
|
// Dashboard grid placement on the Market portfolio. col/row in fine ~32px square cells
|
||||||
|
// (-1 = auto-place), w/h in cells (span). Set when the user drags/resizes a card.
|
||||||
|
int gridCol = -1;
|
||||||
|
int gridRow = -1;
|
||||||
|
int gridW = 8; // default group width (cells); minimum is 8
|
||||||
|
int gridH = 3; // default group height (cells); minimum is 2, card design adapts
|
||||||
|
};
|
||||||
|
|
||||||
// Theme
|
// Theme
|
||||||
std::string getTheme() const { return theme_; }
|
std::string getTheme() const { return theme_; }
|
||||||
void setTheme(const std::string& theme) { theme_ = theme; }
|
void setTheme(const std::string& theme) { theme_ = theme; }
|
||||||
@@ -61,6 +108,12 @@ public:
|
|||||||
std::string getSkinId() const { return skin_id_; }
|
std::string getSkinId() const { return skin_id_; }
|
||||||
void setSkinId(const std::string& id) { skin_id_ = id; }
|
void setSkinId(const std::string& id) { skin_id_ = id; }
|
||||||
|
|
||||||
|
// Stable z-address chosen for HushChat: the reply-to address in outgoing headers, and (for
|
||||||
|
// legacy/non-mnemonic wallets) the source of the seed-derived chat identity. Persisted so the
|
||||||
|
// identity + reply address don't shift when new addresses are generated.
|
||||||
|
std::string getChatReplyZaddr() const { return chat_reply_zaddr_; }
|
||||||
|
void setChatReplyZaddr(const std::string& z) { chat_reply_zaddr_ = z; }
|
||||||
|
|
||||||
// Privacy
|
// Privacy
|
||||||
bool getSaveZtxs() const { return save_ztxs_; }
|
bool getSaveZtxs() const { return save_ztxs_; }
|
||||||
void setSaveZtxs(bool save) { save_ztxs_ = save; }
|
void setSaveZtxs(bool save) { save_ztxs_ = save; }
|
||||||
@@ -141,10 +194,75 @@ public:
|
|||||||
void unfavoriteAddress(const std::string& addr) { favorite_addresses_.erase(addr); }
|
void unfavoriteAddress(const std::string& addr) { favorite_addresses_.erase(addr); }
|
||||||
int getFavoriteAddressCount() const { return (int)favorite_addresses_.size(); }
|
int getFavoriteAddressCount() const { return (int)favorite_addresses_.size(); }
|
||||||
|
|
||||||
|
// Address metadata (labels, icons, custom ordering)
|
||||||
|
struct AddressMeta {
|
||||||
|
std::string label;
|
||||||
|
std::string icon; // material icon name, e.g. "savings"
|
||||||
|
int sortOrder = -1; // -1 = auto (use default sort)
|
||||||
|
bool mining = false;
|
||||||
|
};
|
||||||
|
const AddressMeta& getAddressMeta(const std::string& addr) const {
|
||||||
|
static const AddressMeta empty{};
|
||||||
|
auto it = address_meta_.find(addr);
|
||||||
|
return it != address_meta_.end() ? it->second : empty;
|
||||||
|
}
|
||||||
|
void setAddressLabel(const std::string& addr, const std::string& label) {
|
||||||
|
address_meta_[addr].label = label;
|
||||||
|
}
|
||||||
|
void setAddressIcon(const std::string& addr, const std::string& icon) {
|
||||||
|
address_meta_[addr].icon = icon;
|
||||||
|
}
|
||||||
|
void setAddressSortOrder(const std::string& addr, int order) {
|
||||||
|
address_meta_[addr].sortOrder = order;
|
||||||
|
}
|
||||||
|
bool isMiningAddress(const std::string& addr) const {
|
||||||
|
auto it = address_meta_.find(addr);
|
||||||
|
return it != address_meta_.end() && it->second.mining;
|
||||||
|
}
|
||||||
|
void setMiningAddress(const std::string& addr, bool mining) {
|
||||||
|
address_meta_[addr].mining = mining;
|
||||||
|
}
|
||||||
|
std::set<std::string> getMiningAddresses() const {
|
||||||
|
std::set<std::string> addresses;
|
||||||
|
for (const auto& [addr, meta] : address_meta_) {
|
||||||
|
if (meta.mining) addresses.insert(addr);
|
||||||
|
}
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
int getNextSortOrder() const {
|
||||||
|
int mx = -1;
|
||||||
|
for (const auto& [k, v] : address_meta_)
|
||||||
|
if (v.sortOrder > mx) mx = v.sortOrder;
|
||||||
|
return mx + 1;
|
||||||
|
}
|
||||||
|
void swapAddressOrder(const std::string& a, const std::string& b) {
|
||||||
|
int oa = address_meta_[a].sortOrder;
|
||||||
|
int ob = address_meta_[b].sortOrder;
|
||||||
|
address_meta_[a].sortOrder = ob;
|
||||||
|
address_meta_[b].sortOrder = oa;
|
||||||
|
}
|
||||||
|
|
||||||
// First-run wizard
|
// First-run wizard
|
||||||
bool getWizardCompleted() const { return wizard_completed_; }
|
bool getWizardCompleted() const { return wizard_completed_; }
|
||||||
void setWizardCompleted(bool v) { wizard_completed_ = v; }
|
void setWizardCompleted(bool v) { wizard_completed_ = v; }
|
||||||
|
|
||||||
|
// Whether the one-time "back up your seed phrase" reminder has already been shown.
|
||||||
|
bool getSeedBackupReminded() const { return seed_backup_reminded_; }
|
||||||
|
void setSeedBackupReminded(bool v) { seed_backup_reminded_ = v; }
|
||||||
|
|
||||||
|
// Pending "migrate to a seed wallet" state (Phase 1 created the wallet; a later sweep/adopt
|
||||||
|
// step consumes it). dest = the new wallet's sweep-target z-address; tempDir = its datadir.
|
||||||
|
bool getSeedMigrationPending() const { return seed_migration_pending_; }
|
||||||
|
void setSeedMigrationPending(bool v) { seed_migration_pending_ = v; }
|
||||||
|
std::string getSeedMigrationDest() const { return seed_migration_dest_; }
|
||||||
|
void setSeedMigrationDest(const std::string& v) { seed_migration_dest_ = v; }
|
||||||
|
std::string getSeedMigrationTempDir() const { return seed_migration_temp_dir_; }
|
||||||
|
void setSeedMigrationTempDir(const std::string& v) { seed_migration_temp_dir_ = v; }
|
||||||
|
// The sweep transaction id, persisted once the sweep is submitted — non-empty means the
|
||||||
|
// migration is past the sweep, so a resume goes to the confirm/adopt stage (not sweep again).
|
||||||
|
std::string getSeedMigrationSweepTxid() const { return seed_migration_sweep_txid_; }
|
||||||
|
void setSeedMigrationSweepTxid(const std::string& v) { seed_migration_sweep_txid_ = v; }
|
||||||
|
|
||||||
// Security — auto-lock timeout (seconds; 0 = disabled)
|
// Security — auto-lock timeout (seconds; 0 = disabled)
|
||||||
int getAutoLockTimeout() const { return auto_lock_timeout_; }
|
int getAutoLockTimeout() const { return auto_lock_timeout_; }
|
||||||
void setAutoLockTimeout(int seconds) { auto_lock_timeout_ = seconds; }
|
void setAutoLockTimeout(int seconds) { auto_lock_timeout_ = seconds; }
|
||||||
@@ -165,6 +283,43 @@ public:
|
|||||||
bool getStopExternalDaemon() const { return stop_external_daemon_; }
|
bool getStopExternalDaemon() const { return stop_external_daemon_; }
|
||||||
void setStopExternalDaemon(bool v) { stop_external_daemon_ = v; }
|
void setStopExternalDaemon(bool v) { stop_external_daemon_ = v; }
|
||||||
|
|
||||||
|
// Daemon — maximum peer connections (0 = daemon default)
|
||||||
|
int getMaxConnections() const { return max_connections_; }
|
||||||
|
void setMaxConnections(int v) { max_connections_ = std::max(0, v); }
|
||||||
|
|
||||||
|
// Lite wallet server selection
|
||||||
|
LiteServerSelectionPreferenceMode getLiteServerSelectionMode() const { return lite_server_selection_mode_; }
|
||||||
|
void setLiteServerSelectionMode(LiteServerSelectionPreferenceMode mode) { lite_server_selection_mode_ = mode; }
|
||||||
|
std::string getLiteStickyServerUrl() const { return lite_sticky_server_url_; }
|
||||||
|
void setLiteStickyServerUrl(const std::string& url) { lite_sticky_server_url_ = url; }
|
||||||
|
std::string getLiteChainName() const { return lite_chain_name_; }
|
||||||
|
void setLiteChainName(const std::string& chainName) { lite_chain_name_ = chainName; }
|
||||||
|
std::size_t getLiteRandomSelectionSeed() const { return lite_random_selection_seed_; }
|
||||||
|
void setLiteRandomSelectionSeed(std::size_t seed) { lite_random_selection_seed_ = seed; }
|
||||||
|
bool getLitePersistSelectedServer() const { return lite_persist_selected_server_; }
|
||||||
|
void setLitePersistSelectedServer(bool persist) { lite_persist_selected_server_ = persist; }
|
||||||
|
const std::vector<LiteServerPreference>& getLiteServers() const { return lite_servers_; }
|
||||||
|
void setLiteServers(const std::vector<LiteServerPreference>& servers) { lite_servers_ = servers; }
|
||||||
|
|
||||||
|
// User-defined portfolio entries (Market tab). "All funds" is implicit, not stored here.
|
||||||
|
const std::vector<PortfolioEntry>& getPortfolioEntries() const { return portfolio_entries_; }
|
||||||
|
void setPortfolioEntries(const std::vector<PortfolioEntry>& entries) { portfolio_entries_ = entries; }
|
||||||
|
|
||||||
|
// Lite servers the user has hidden from the Network tab (kept by URL, shown via a toggle).
|
||||||
|
const std::set<std::string>& getLiteHiddenServers() const { return lite_hidden_servers_; }
|
||||||
|
bool isLiteServerHidden(const std::string& url) const { return lite_hidden_servers_.count(url) > 0; }
|
||||||
|
void hideLiteServer(const std::string& url) { lite_hidden_servers_.insert(url); }
|
||||||
|
void unhideLiteServer(const std::string& url) { lite_hidden_servers_.erase(url); }
|
||||||
|
|
||||||
|
// Lite wallet rollout / kill-switch (see wallet/lite_rollout_policy.h).
|
||||||
|
// Override: "auto" (honor rollout manifest), "force_on", or "force_off".
|
||||||
|
std::string getLiteRolloutOverride() const { return lite_rollout_override_; }
|
||||||
|
void setLiteRolloutOverride(const std::string& v) { lite_rollout_override_ = v; }
|
||||||
|
// Stable, locally-generated install id used only to derive the staged-rollout bucket.
|
||||||
|
// Never transmitted; carries no PII. Generated on first use if empty.
|
||||||
|
std::string getLiteInstallId() const { return lite_install_id_; }
|
||||||
|
void setLiteInstallId(const std::string& v) { lite_install_id_ = v; }
|
||||||
|
|
||||||
// Verbose diagnostic logging (connection attempts, daemon state, port owner, etc.)
|
// Verbose diagnostic logging (connection attempts, daemon state, port owner, etc.)
|
||||||
bool getVerboseLogging() const { return verbose_logging_; }
|
bool getVerboseLogging() const { return verbose_logging_; }
|
||||||
void setVerboseLogging(bool v) { verbose_logging_ = v; }
|
void setVerboseLogging(bool v) { verbose_logging_ = v; }
|
||||||
@@ -186,6 +341,10 @@ public:
|
|||||||
bool getLowSpecMode() const { return low_spec_mode_; }
|
bool getLowSpecMode() const { return low_spec_mode_; }
|
||||||
void setLowSpecMode(bool v) { low_spec_mode_ = v; }
|
void setLowSpecMode(bool v) { low_spec_mode_ = v; }
|
||||||
|
|
||||||
|
// Reduce motion — disables animated transitions for accessibility
|
||||||
|
bool getReduceMotion() const { return reduce_motion_; }
|
||||||
|
void setReduceMotion(bool v) { reduce_motion_ = v; }
|
||||||
|
|
||||||
// Market — last selected exchange + pair
|
// Market — last selected exchange + pair
|
||||||
std::string getSelectedExchange() const { return selected_exchange_; }
|
std::string getSelectedExchange() const { return selected_exchange_; }
|
||||||
void setSelectedExchange(const std::string& v) { selected_exchange_ = v; }
|
void setSelectedExchange(const std::string& v) { selected_exchange_ = v; }
|
||||||
@@ -207,6 +366,12 @@ public:
|
|||||||
void setPoolHugepages(bool v) { pool_hugepages_ = v; }
|
void setPoolHugepages(bool v) { pool_hugepages_ = v; }
|
||||||
bool getPoolMode() const { return pool_mode_; }
|
bool getPoolMode() const { return pool_mode_; }
|
||||||
void setPoolMode(bool v) { pool_mode_ = v; }
|
void setPoolMode(bool v) { pool_mode_ = v; }
|
||||||
|
PoolSelectMode getPoolSelectMode() const { return pool_select_mode_; }
|
||||||
|
void setPoolSelectMode(PoolSelectMode v) { pool_select_mode_ = v; }
|
||||||
|
|
||||||
|
// Installed DRG-XMRig release tag (for in-app miner update detection); empty if unknown/bundled.
|
||||||
|
std::string getXmrigVersion() const { return xmrig_version_; }
|
||||||
|
void setXmrigVersion(const std::string& v) { xmrig_version_ = v; }
|
||||||
|
|
||||||
// Mine when idle (auto-start mining when system is idle)
|
// Mine when idle (auto-start mining when system is idle)
|
||||||
bool getMineWhenIdle() const { return mine_when_idle_; }
|
bool getMineWhenIdle() const { return mine_when_idle_; }
|
||||||
@@ -268,6 +433,7 @@ private:
|
|||||||
// Settings values
|
// Settings values
|
||||||
std::string theme_ = "dragonx";
|
std::string theme_ = "dragonx";
|
||||||
std::string skin_id_ = "dragonx";
|
std::string skin_id_ = "dragonx";
|
||||||
|
std::string chat_reply_zaddr_;
|
||||||
bool save_ztxs_ = true;
|
bool save_ztxs_ = true;
|
||||||
bool auto_shield_ = true;
|
bool auto_shield_ = true;
|
||||||
bool use_tor_ = false;
|
bool use_tor_ = false;
|
||||||
@@ -293,18 +459,48 @@ private:
|
|||||||
bool scanline_enabled_ = true;
|
bool scanline_enabled_ = true;
|
||||||
std::set<std::string> hidden_addresses_;
|
std::set<std::string> hidden_addresses_;
|
||||||
std::set<std::string> favorite_addresses_;
|
std::set<std::string> favorite_addresses_;
|
||||||
|
std::map<std::string, AddressMeta> address_meta_;
|
||||||
bool wizard_completed_ = false;
|
bool wizard_completed_ = false;
|
||||||
|
bool seed_backup_reminded_ = false;
|
||||||
|
bool seed_migration_pending_ = false;
|
||||||
|
std::string seed_migration_dest_;
|
||||||
|
std::string seed_migration_temp_dir_;
|
||||||
|
std::string seed_migration_sweep_txid_;
|
||||||
int auto_lock_timeout_ = 900; // 15 minutes
|
int auto_lock_timeout_ = 900; // 15 minutes
|
||||||
int unlock_duration_ = 600; // 10 minutes
|
int unlock_duration_ = 600; // 10 minutes
|
||||||
bool pin_enabled_ = false;
|
bool pin_enabled_ = false;
|
||||||
bool keep_daemon_running_ = false;
|
bool keep_daemon_running_ = false;
|
||||||
bool stop_external_daemon_ = false;
|
bool stop_external_daemon_ = false;
|
||||||
|
int max_connections_ = 0; // 0 = daemon default
|
||||||
|
|
||||||
|
// Lite wallet server preferences. These are user/server settings only;
|
||||||
|
// wallet secrets, wallet files, and lifecycle state are never stored here.
|
||||||
|
LiteServerSelectionPreferenceMode lite_server_selection_mode_ = LiteServerSelectionPreferenceMode::Sticky;
|
||||||
|
std::string lite_sticky_server_url_ = "https://lite.dragonx.is";
|
||||||
|
std::string lite_chain_name_ = "main"; // SDXL backend chain id; must be main/test/regtest
|
||||||
|
std::size_t lite_random_selection_seed_ = 0;
|
||||||
|
bool lite_persist_selected_server_ = true;
|
||||||
|
std::string lite_rollout_override_ = "auto"; // auto|force_on|force_off
|
||||||
|
std::string lite_install_id_; // random local-only id; rollout-bucket source
|
||||||
|
std::vector<LiteServerPreference> lite_servers_ = {
|
||||||
|
{"https://lite.dragonx.is", "DragonX Lite", true},
|
||||||
|
{"https://lite1.dragonx.is", "DragonX Lite 1", true},
|
||||||
|
{"https://lite2.dragonx.is", "DragonX Lite 2", true},
|
||||||
|
{"https://lite3.dragonx.is", "DragonX Lite 3", true},
|
||||||
|
{"https://lite4.dragonx.is", "DragonX Lite 4", true},
|
||||||
|
{"https://lite5.dragonx.is", "DragonX Lite 5", true}
|
||||||
|
};
|
||||||
|
std::set<std::string> lite_hidden_servers_; // server URLs hidden from the Network tab
|
||||||
|
|
||||||
|
std::vector<PortfolioEntry> portfolio_entries_; // Market tab custom portfolio groups
|
||||||
|
|
||||||
bool verbose_logging_ = false;
|
bool verbose_logging_ = false;
|
||||||
std::set<std::string> debug_categories_;
|
std::set<std::string> debug_categories_;
|
||||||
bool theme_effects_enabled_ = true;
|
bool theme_effects_enabled_ = true;
|
||||||
bool low_spec_mode_ = false;
|
bool low_spec_mode_ = false;
|
||||||
std::string selected_exchange_ = "TradeOgre";
|
bool reduce_motion_ = false;
|
||||||
std::string selected_pair_ = "DRGX/BTC";
|
std::string selected_exchange_ = "Nonkyc.io";
|
||||||
|
std::string selected_pair_ = "DRGX/USDT";
|
||||||
|
|
||||||
// Pool mining
|
// Pool mining
|
||||||
std::string pool_url_ = "pool.dragonx.is:3433";
|
std::string pool_url_ = "pool.dragonx.is:3433";
|
||||||
@@ -314,6 +510,8 @@ private:
|
|||||||
bool pool_tls_ = false;
|
bool pool_tls_ = false;
|
||||||
bool pool_hugepages_ = true;
|
bool pool_hugepages_ = true;
|
||||||
bool pool_mode_ = false; // false=solo, true=pool
|
bool pool_mode_ = false; // false=solo, true=pool
|
||||||
|
PoolSelectMode pool_select_mode_ = PoolSelectMode::Manual; // manual vs auto-balance pool choice
|
||||||
|
std::string xmrig_version_; // installed DRG-XMRig release tag (update detection)
|
||||||
bool mine_when_idle_ = false; // auto-start mining when system idle
|
bool mine_when_idle_ = false; // auto-start mining when system idle
|
||||||
int mine_idle_delay_= 120; // seconds of idle before mining starts
|
int mine_idle_delay_= 120; // seconds of idle before mining starts
|
||||||
bool idle_thread_scaling_ = false; // scale threads instead of start/stop
|
bool idle_thread_scaling_ = false; // scale threads instead of start/stop
|
||||||
|
|||||||
@@ -1,31 +1,3 @@
|
|||||||
// DragonX Wallet - ImGui Edition
|
|
||||||
// Copyright 2024-2026 The Hush Developers
|
|
||||||
// Released under the GPLv3
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// !! DO NOT EDIT version.h — it is generated from version.h.in by CMake.
|
#include "dragonx_generated_version.h"
|
||||||
// !! Change the version in CMakeLists.txt: project(... VERSION x.y.z ...)
|
|
||||||
|
|
||||||
#define DRAGONX_VERSION "1.1.2"
|
|
||||||
#define DRAGONX_VERSION_MAJOR 1
|
|
||||||
#define DRAGONX_VERSION_MINOR 1
|
|
||||||
#define DRAGONX_VERSION_PATCH 2
|
|
||||||
|
|
||||||
#define DRAGONX_APP_NAME "ObsidianDragon"
|
|
||||||
#define DRAGONX_ORG_NAME "Hush"
|
|
||||||
|
|
||||||
// Default RPC settings
|
|
||||||
#define DRAGONX_DEFAULT_RPC_HOST "127.0.0.1"
|
|
||||||
#define DRAGONX_DEFAULT_RPC_PORT "21769"
|
|
||||||
|
|
||||||
// Coin parameters
|
|
||||||
#define DRAGONX_TICKER "DRGX"
|
|
||||||
#define DRAGONX_COIN_NAME "DragonX"
|
|
||||||
#define DRAGONX_URI_SCHEME "drgx"
|
|
||||||
#define DRAGONX_ZATOSHI_PER_COIN 100000000
|
|
||||||
#define DRAGONX_DEFAULT_FEE 0.0001
|
|
||||||
|
|
||||||
// Config file names
|
|
||||||
#define DRAGONX_CONF_FILENAME "DRAGONX.conf"
|
|
||||||
#define DRAGONX_WALLET_FILENAME "wallet.dat"
|
|
||||||
|
|||||||
@@ -4,15 +4,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// !! DO NOT EDIT version.h — it is generated from version.h.in by CMake.
|
// !! DO NOT EDIT generated version output — it is generated from version.h.in by CMake.
|
||||||
// !! Change the version in CMakeLists.txt: project(... VERSION x.y.z ...)
|
// !! Change the version in CMakeLists.txt: project(... VERSION x.y.z ...) for the full-node app,
|
||||||
|
// !! or DRAGONX_LITE_VERSION for ObsidianDragonLite. DRAGONX_APP_VERSION is the active variant.
|
||||||
|
|
||||||
#define DRAGONX_VERSION "@PROJECT_VERSION@"
|
#define DRAGONX_VERSION "@DRAGONX_APP_VERSION@@DRAGONX_APP_VERSION_SUFFIX@"
|
||||||
#define DRAGONX_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
#define DRAGONX_VERSION_MAJOR @DRAGONX_APP_VERSION_MAJOR@
|
||||||
#define DRAGONX_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
#define DRAGONX_VERSION_MINOR @DRAGONX_APP_VERSION_MINOR@
|
||||||
#define DRAGONX_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
#define DRAGONX_VERSION_PATCH @DRAGONX_APP_VERSION_PATCH@
|
||||||
|
|
||||||
#define DRAGONX_APP_NAME "ObsidianDragon"
|
#define DRAGONX_APP_NAME "@DRAGONX_APP_NAME@"
|
||||||
#define DRAGONX_ORG_NAME "Hush"
|
#define DRAGONX_ORG_NAME "Hush"
|
||||||
|
|
||||||
// Default RPC settings
|
// Default RPC settings
|
||||||
|
|||||||
128
src/daemon/daemon_controller.cpp
Normal file
128
src/daemon/daemon_controller.cpp
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
#include "daemon_controller.h"
|
||||||
|
#include "../config/settings.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace daemon {
|
||||||
|
|
||||||
|
DaemonController::DaemonController()
|
||||||
|
: daemon_(std::make_unique<EmbeddedDaemon>())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DaemonController::~DaemonController() = default;
|
||||||
|
|
||||||
|
void DaemonController::setStateCallback(StateCallback callback)
|
||||||
|
{
|
||||||
|
daemon_->setStateCallback(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaemonController::syncSettings(const config::Settings* settings)
|
||||||
|
{
|
||||||
|
if (!settings) return;
|
||||||
|
daemon_->setDebugCategories(settings->getDebugCategories());
|
||||||
|
daemon_->setMaxConnections(settings->getMaxConnections());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaemonController::start(const config::Settings* settings)
|
||||||
|
{
|
||||||
|
syncSettings(settings);
|
||||||
|
return daemon_->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaemonController::stop(int waitMs)
|
||||||
|
{
|
||||||
|
daemon_->stop(waitMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaemonController::isRunning() const
|
||||||
|
{
|
||||||
|
return daemon_->isRunning();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaemonController::externalDaemonDetected() const
|
||||||
|
{
|
||||||
|
return daemon_->externalDaemonDetected();
|
||||||
|
}
|
||||||
|
|
||||||
|
DaemonController::State DaemonController::state() const
|
||||||
|
{
|
||||||
|
return daemon_->getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& DaemonController::lastError() const
|
||||||
|
{
|
||||||
|
return daemon_->getLastError();
|
||||||
|
}
|
||||||
|
|
||||||
|
int DaemonController::crashCount() const
|
||||||
|
{
|
||||||
|
return daemon_->getCrashCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
int DaemonController::lastBlockHeight() const
|
||||||
|
{
|
||||||
|
return daemon_ ? daemon_->getLastBlockHeight() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double DaemonController::memoryUsageMB() const
|
||||||
|
{
|
||||||
|
return daemon_ ? daemon_->getMemoryUsageMB() : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> DaemonController::recentLines(std::size_t count) const
|
||||||
|
{
|
||||||
|
return daemon_ ? daemon_->getRecentLines(count) : std::vector<std::string>{};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DaemonController::outputSince(std::size_t& offset) const
|
||||||
|
{
|
||||||
|
return daemon_ ? daemon_->getOutputSince(offset) : std::string{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaemonController::resetCrashCount()
|
||||||
|
{
|
||||||
|
daemon_->resetCrashCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaemonController::setRescanOnNextStart(bool enabled)
|
||||||
|
{
|
||||||
|
daemon_->setRescanOnNextStart(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaemonController::rescanOnNextStart() const
|
||||||
|
{
|
||||||
|
return daemon_->rescanOnNextStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaemonController::setZapOnNextStart(bool enabled)
|
||||||
|
{
|
||||||
|
daemon_->setZapOnNextStart(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DaemonController::zapOnNextStart() const
|
||||||
|
{
|
||||||
|
return daemon_->zapOnNextStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DaemonController::prepareLifecycleOperation(const LifecycleDecision& decision,
|
||||||
|
const config::Settings* settings)
|
||||||
|
{
|
||||||
|
if (settings) syncSettings(settings);
|
||||||
|
if (decision.resetCrashCount) resetCrashCount();
|
||||||
|
if (decision.setRescanOnNextStart) setRescanOnNextStart(true);
|
||||||
|
if (decision.setZapOnNextStart) setZapOnNextStart(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
DaemonController::ShutdownDecision DaemonController::shutdownDecision(
|
||||||
|
bool keepDaemonRunning, bool stopExternalDaemon) const
|
||||||
|
{
|
||||||
|
return evaluateShutdownPolicy(static_cast<bool>(daemon_),
|
||||||
|
daemon_ && daemon_->externalDaemonDetected(),
|
||||||
|
keepDaemonRunning,
|
||||||
|
stopExternalDaemon);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace daemon
|
||||||
|
} // namespace dragonx
|
||||||
238
src/daemon/daemon_controller.h
Normal file
238
src/daemon/daemon_controller.h
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "embedded_daemon.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace config { class Settings; }
|
||||||
|
namespace daemon {
|
||||||
|
|
||||||
|
class DaemonController {
|
||||||
|
public:
|
||||||
|
using State = EmbeddedDaemon::State;
|
||||||
|
using StateCallback = EmbeddedDaemon::StateCallback;
|
||||||
|
|
||||||
|
enum class ShutdownAction {
|
||||||
|
DisconnectOnly,
|
||||||
|
StopDaemon
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ShutdownDecision {
|
||||||
|
ShutdownAction action = ShutdownAction::DisconnectOnly;
|
||||||
|
const char* logReason = "no embedded daemon";
|
||||||
|
const char* status = "Disconnecting...";
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class LifecycleOperation {
|
||||||
|
ManualRestart,
|
||||||
|
Rescan,
|
||||||
|
RepairWallet, // restart with -zapwallettxes=2 (wipe & rebuild wallet tx records)
|
||||||
|
DeleteBlockchainData,
|
||||||
|
BootstrapStop
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LifecycleDecision {
|
||||||
|
LifecycleOperation operation = LifecycleOperation::ManualRestart;
|
||||||
|
bool allowed = false;
|
||||||
|
bool wasRunning = false;
|
||||||
|
const char* taskName = "";
|
||||||
|
const char* status = "";
|
||||||
|
const char* warning = "";
|
||||||
|
bool resetCrashCount = false;
|
||||||
|
bool setRescanOnNextStart = false;
|
||||||
|
bool disconnectRpc = false;
|
||||||
|
int restartDelayMs = 0;
|
||||||
|
bool setZapOnNextStart = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LifecycleTaskContext {
|
||||||
|
public:
|
||||||
|
virtual ~LifecycleTaskContext() = default;
|
||||||
|
virtual bool cancelled() const = 0;
|
||||||
|
virtual bool shuttingDown() const = 0;
|
||||||
|
virtual void sleepForMs(int milliseconds) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LifecycleRuntime {
|
||||||
|
public:
|
||||||
|
virtual ~LifecycleRuntime() = default;
|
||||||
|
virtual void stopDaemonWithPolicy() = 0;
|
||||||
|
virtual bool startDaemon() = 0;
|
||||||
|
virtual int deleteBlockchainData() = 0;
|
||||||
|
virtual void resetOutputOffset() = 0;
|
||||||
|
virtual void requestRpcStopAndDisconnect(const char* context, const char* reason) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LifecycleExecutionResult {
|
||||||
|
bool completed = false;
|
||||||
|
bool cancelled = false;
|
||||||
|
bool stopped = false;
|
||||||
|
bool started = false;
|
||||||
|
int deletedItems = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
DaemonController();
|
||||||
|
~DaemonController();
|
||||||
|
|
||||||
|
DaemonController(const DaemonController&) = delete;
|
||||||
|
DaemonController& operator=(const DaemonController&) = delete;
|
||||||
|
|
||||||
|
EmbeddedDaemon* daemon() { return daemon_.get(); }
|
||||||
|
const EmbeddedDaemon* daemon() const { return daemon_.get(); }
|
||||||
|
|
||||||
|
void setStateCallback(StateCallback callback);
|
||||||
|
void syncSettings(const config::Settings* settings);
|
||||||
|
|
||||||
|
bool start(const config::Settings* settings);
|
||||||
|
void stop(int waitMs);
|
||||||
|
|
||||||
|
bool isRunning() const;
|
||||||
|
bool externalDaemonDetected() const;
|
||||||
|
State state() const;
|
||||||
|
const std::string& lastError() const;
|
||||||
|
int crashCount() const;
|
||||||
|
int lastBlockHeight() const;
|
||||||
|
double memoryUsageMB() const;
|
||||||
|
std::vector<std::string> recentLines(std::size_t count) const;
|
||||||
|
std::string outputSince(std::size_t& offset) const;
|
||||||
|
|
||||||
|
void resetCrashCount();
|
||||||
|
void setRescanOnNextStart(bool enabled);
|
||||||
|
bool rescanOnNextStart() const;
|
||||||
|
void setZapOnNextStart(bool enabled);
|
||||||
|
bool zapOnNextStart() const;
|
||||||
|
|
||||||
|
static ShutdownDecision evaluateShutdownPolicy(bool hasDaemon,
|
||||||
|
bool externalDaemonDetected,
|
||||||
|
bool keepDaemonRunning,
|
||||||
|
bool stopExternalDaemon) {
|
||||||
|
if (!hasDaemon) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (keepDaemonRunning) {
|
||||||
|
return {ShutdownAction::DisconnectOnly,
|
||||||
|
"keep_daemon_running enabled",
|
||||||
|
"Disconnecting (daemon stays running)..."};
|
||||||
|
}
|
||||||
|
if (externalDaemonDetected && !stopExternalDaemon) {
|
||||||
|
return {ShutdownAction::DisconnectOnly,
|
||||||
|
"external daemon (not ours to stop)",
|
||||||
|
"Disconnecting (daemon stays running)..."};
|
||||||
|
}
|
||||||
|
return {ShutdownAction::StopDaemon,
|
||||||
|
"stopping managed daemon",
|
||||||
|
"Sending stop command to daemon..."};
|
||||||
|
}
|
||||||
|
static LifecycleDecision evaluateLifecycleOperation(LifecycleOperation operation,
|
||||||
|
bool usingEmbeddedDaemon,
|
||||||
|
bool hasDaemon,
|
||||||
|
bool daemonRunning,
|
||||||
|
bool restartInProgress = false) {
|
||||||
|
switch (operation) {
|
||||||
|
case LifecycleOperation::ManualRestart:
|
||||||
|
if (!usingEmbeddedDaemon || restartInProgress) return {};
|
||||||
|
return {operation, true, daemonRunning, "daemon-restart", "Restarting daemon...", "",
|
||||||
|
true, false, true, 500};
|
||||||
|
case LifecycleOperation::Rescan:
|
||||||
|
if (!usingEmbeddedDaemon || !hasDaemon) {
|
||||||
|
return {operation, false, daemonRunning, "", "",
|
||||||
|
"Rescan requires embedded daemon. Restart your daemon with -rescan manually."};
|
||||||
|
}
|
||||||
|
return {operation, true, daemonRunning, "rescan-blockchain", "Starting rescan...", "",
|
||||||
|
false, true, false, 3000};
|
||||||
|
case LifecycleOperation::RepairWallet:
|
||||||
|
if (!usingEmbeddedDaemon || !hasDaemon) {
|
||||||
|
return {operation, false, daemonRunning, "", "",
|
||||||
|
"Wallet repair requires embedded daemon. Restart your daemon with -zapwallettxes=2 manually."};
|
||||||
|
}
|
||||||
|
return {operation, true, daemonRunning, "repair-wallet", "Repairing wallet...", "",
|
||||||
|
false, false, false, 3000, true};
|
||||||
|
case LifecycleOperation::DeleteBlockchainData:
|
||||||
|
if (!usingEmbeddedDaemon || !hasDaemon) {
|
||||||
|
return {operation, false, daemonRunning, "", "",
|
||||||
|
"Delete blockchain requires embedded daemon. Stop your daemon manually and delete the data directory."};
|
||||||
|
}
|
||||||
|
return {operation, true, daemonRunning, "delete-blockchain-data", "Deleting blockchain data...", "",
|
||||||
|
false, false, false, 3000};
|
||||||
|
case LifecycleOperation::BootstrapStop:
|
||||||
|
return {operation, true, daemonRunning, "bootstrap-stop-daemon", "Stopping daemon for bootstrap...", "",
|
||||||
|
false, false, true, 0};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
void prepareLifecycleOperation(const LifecycleDecision& decision,
|
||||||
|
const config::Settings* settings = nullptr);
|
||||||
|
static inline LifecycleExecutionResult executeLifecycleOperation(const LifecycleDecision& decision,
|
||||||
|
LifecycleRuntime& runtime,
|
||||||
|
LifecycleTaskContext& task)
|
||||||
|
{
|
||||||
|
LifecycleExecutionResult result;
|
||||||
|
if (!decision.allowed) return result;
|
||||||
|
|
||||||
|
auto waitForDelay = [&]() {
|
||||||
|
int waitTicks = std::max(0, decision.restartDelayMs / 100);
|
||||||
|
for (int i = 0; i < waitTicks && !task.cancelled() && !task.shuttingDown(); ++i) {
|
||||||
|
task.sleepForMs(100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto cancelled = [&]() {
|
||||||
|
result.cancelled = task.cancelled() || task.shuttingDown();
|
||||||
|
return result.cancelled;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (decision.operation) {
|
||||||
|
case LifecycleOperation::BootstrapStop:
|
||||||
|
if (decision.wasRunning) {
|
||||||
|
runtime.requestRpcStopAndDisconnect("Bootstrap daemon stop", "Bootstrap");
|
||||||
|
result.stopped = true;
|
||||||
|
}
|
||||||
|
result.completed = true;
|
||||||
|
return result;
|
||||||
|
case LifecycleOperation::ManualRestart:
|
||||||
|
if (decision.wasRunning) {
|
||||||
|
runtime.stopDaemonWithPolicy();
|
||||||
|
result.stopped = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LifecycleOperation::Rescan:
|
||||||
|
case LifecycleOperation::RepairWallet:
|
||||||
|
case LifecycleOperation::DeleteBlockchainData:
|
||||||
|
runtime.stopDaemonWithPolicy();
|
||||||
|
result.stopped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelled()) return result;
|
||||||
|
waitForDelay();
|
||||||
|
if (cancelled()) return result;
|
||||||
|
|
||||||
|
if (decision.operation == LifecycleOperation::DeleteBlockchainData) {
|
||||||
|
result.deletedItems = runtime.deleteBlockchainData();
|
||||||
|
if (cancelled()) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decision.operation == LifecycleOperation::Rescan ||
|
||||||
|
decision.operation == LifecycleOperation::RepairWallet ||
|
||||||
|
decision.operation == LifecycleOperation::DeleteBlockchainData) {
|
||||||
|
runtime.resetOutputOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
result.started = runtime.startDaemon();
|
||||||
|
result.completed = !cancelled();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
ShutdownDecision shutdownDecision(bool keepDaemonRunning,
|
||||||
|
bool stopExternalDaemon) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<EmbeddedDaemon> daemon_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace daemon
|
||||||
|
} // namespace dragonx
|
||||||
@@ -205,9 +205,18 @@ std::vector<std::string> EmbeddedDaemon::getChainParams()
|
|||||||
"-ac_reward=300000000",
|
"-ac_reward=300000000",
|
||||||
"-ac_blocktime=36",
|
"-ac_blocktime=36",
|
||||||
"-ac_private=1",
|
"-ac_private=1",
|
||||||
"-addnode=176.126.87.241",
|
"-addnode=node.dragonx.is",
|
||||||
|
"-addnode=node1.dragonx.is",
|
||||||
|
"-addnode=node2.dragonx.is",
|
||||||
|
"-addnode=node3.dragonx.is",
|
||||||
|
"-addnode=node4.dragonx.is",
|
||||||
"-experimentalfeatures",
|
"-experimentalfeatures",
|
||||||
"-developerencryptwallet",
|
"-developerencryptwallet",
|
||||||
|
// Create fresh wallets from a BIP39 mnemonic so their 24-word phrase can be
|
||||||
|
// exported (z_exportmnemonic) and is portable to SDXLite/ObsidianDragonLite.
|
||||||
|
// The daemon reads this ONLY inside GenerateNewSeed() when a wallet has no seed
|
||||||
|
// yet, so it is inert on existing wallets — safe to pass unconditionally.
|
||||||
|
"-usemnemonic=1",
|
||||||
dbcache_arg
|
dbcache_arg
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -439,9 +448,10 @@ bool EmbeddedDaemon::start(const std::string& binary_path)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if something is already listening on the RPC port
|
// Check if something is already listening on the RPC port. An isolated instance (migrate-to-
|
||||||
|
// seed) runs on its own non-default port alongside the main daemon, so it skips this bail.
|
||||||
int rpc_port = std::atoi(DRAGONX_DEFAULT_RPC_PORT);
|
int rpc_port = std::atoi(DRAGONX_DEFAULT_RPC_PORT);
|
||||||
if (isPortInUse(rpc_port)) {
|
if (!skip_port_check_ && isPortInUse(rpc_port)) {
|
||||||
std::string owner = getPortOwnerInfo(rpc_port);
|
std::string owner = getPortOwnerInfo(rpc_port);
|
||||||
VERBOSE_LOGF("[INFO] Port %d is already in use by %s — external daemon detected, will connect to it.\\n", rpc_port, owner.c_str());
|
VERBOSE_LOGF("[INFO] Port %d is already in use by %s — external daemon detected, will connect to it.\\n", rpc_port, owner.c_str());
|
||||||
external_daemon_detected_ = true;
|
external_daemon_detected_ = true;
|
||||||
@@ -473,12 +483,36 @@ bool EmbeddedDaemon::start(const std::string& binary_path)
|
|||||||
args.push_back("-debug=" + cat);
|
args.push_back("-debug=" + cat);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add -rescan flag if requested (one-shot)
|
// Append max connections if configured (0 = daemon default)
|
||||||
if (rescan_on_next_start_.exchange(false)) {
|
if (max_connections_ > 0) {
|
||||||
|
args.push_back("-maxconnections=" + std::to_string(max_connections_));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add wallet-repair flag if requested (one-shot). -zapwallettxes=2 wipes all wallet tx/note
|
||||||
|
// records and rebuilds them from the chain; it implies -rescan, so don't also pass -rescan.
|
||||||
|
if (zap_on_next_start_.exchange(false)) {
|
||||||
|
DEBUG_LOGF("[INFO] Adding -zapwallettxes=2 flag for wallet repair (zap & rebuild)\n");
|
||||||
|
args.push_back("-zapwallettxes=2");
|
||||||
|
rescan_on_next_start_.store(false); // implied by zap; avoid redundant -rescan
|
||||||
|
} else if (rescan_on_next_start_.exchange(false)) {
|
||||||
|
// Add -rescan flag if requested (one-shot)
|
||||||
DEBUG_LOGF("[INFO] Adding -rescan flag for blockchain rescan\n");
|
DEBUG_LOGF("[INFO] Adding -rescan flag for blockchain rescan\n");
|
||||||
args.push_back("-rescan");
|
args.push_back("-rescan");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// One-shot isolated-datadir override (migrate-to-seed flow): run this start against a
|
||||||
|
// throwaway datadir, plus any extra args (e.g. -connect=0). Consumed here so later starts
|
||||||
|
// revert to the normal datadir. The datadir's basename MUST be the assetchain name (DRAGONX)
|
||||||
|
// or the daemon mis-resolves its conf/port; it reads <datadir>/DRAGONX.conf automatically, so
|
||||||
|
// no -conf is passed (an explicit -conf confuses the Komodo/Hush path resolution).
|
||||||
|
if (!override_datadir_.empty()) {
|
||||||
|
DEBUG_LOGF("[INFO] Isolated start override: -datadir=%s\n", override_datadir_.c_str());
|
||||||
|
args.push_back("-datadir=" + override_datadir_);
|
||||||
|
}
|
||||||
|
for (const auto& a : override_extra_args_) args.push_back(a);
|
||||||
|
override_datadir_.clear();
|
||||||
|
override_extra_args_.clear();
|
||||||
|
|
||||||
if (!startProcess(daemon_path, args)) {
|
if (!startProcess(daemon_path, args)) {
|
||||||
DEBUG_LOGF("[ERROR] Failed to start dragonxd process: %s\\n", last_error_.c_str());
|
DEBUG_LOGF("[ERROR] Failed to start dragonxd process: %s\\n", last_error_.c_str());
|
||||||
setState(State::Error, "Failed to start dragonxd process");
|
setState(State::Error, "Failed to start dragonxd process");
|
||||||
@@ -1200,5 +1234,10 @@ bool EmbeddedDaemon::isRpcPortInUse()
|
|||||||
return isPortInUse(port);
|
return isPortInUse(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EmbeddedDaemon::tcpPortInUse(int port)
|
||||||
|
{
|
||||||
|
return isPortInUse(port);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace daemon
|
} // namespace daemon
|
||||||
} // namespace dragonx
|
} // namespace dragonx
|
||||||
|
|||||||
@@ -117,6 +117,26 @@ public:
|
|||||||
*/
|
*/
|
||||||
std::vector<std::string> getRecentLines(int maxLines = 8) const;
|
std::vector<std::string> getRecentLines(int maxLines = 8) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extract the latest block height from daemon output (thread-safe).
|
||||||
|
* Parses the last "height=N" from UpdateTip lines without copying
|
||||||
|
* the entire output buffer. Returns -1 if no UpdateTip found.
|
||||||
|
*/
|
||||||
|
int getLastBlockHeight() const {
|
||||||
|
std::lock_guard<std::mutex> lk(output_mutex_);
|
||||||
|
// Search backwards from the end for "height="
|
||||||
|
size_t pos = process_output_.rfind("height=");
|
||||||
|
if (pos == std::string::npos) return -1;
|
||||||
|
pos += 7; // skip "height="
|
||||||
|
int h = 0;
|
||||||
|
for (size_t i = pos; i < process_output_.size(); ++i) {
|
||||||
|
char c = process_output_[i];
|
||||||
|
if (c >= '0' && c <= '9') h = h * 10 + (c - '0');
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
return h > 0 ? h : -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether start() detected an existing daemon on the RPC port.
|
* @brief Whether start() detected an existing daemon on the RPC port.
|
||||||
* When true the wallet should connect to it instead of showing an error.
|
* When true the wallet should connect to it instead of showing an error.
|
||||||
@@ -152,12 +172,48 @@ public:
|
|||||||
void setDebugCategories(const std::set<std::string>& cats) { debug_categories_ = cats; }
|
void setDebugCategories(const std::set<std::string>& cats) { debug_categories_ = cats; }
|
||||||
const std::set<std::string>& getDebugCategories() const { return debug_categories_; }
|
const std::set<std::string>& getDebugCategories() const { return debug_categories_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set maximum peer connections (0 = use daemon default)
|
||||||
|
*/
|
||||||
|
void setMaxConnections(int v) { max_connections_ = v; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Request a blockchain rescan on the next daemon start
|
* @brief Request a blockchain rescan on the next daemon start
|
||||||
*/
|
*/
|
||||||
void setRescanOnNextStart(bool v) { rescan_on_next_start_ = v; }
|
void setRescanOnNextStart(bool v) { rescan_on_next_start_ = v; }
|
||||||
bool rescanOnNextStart() const { return rescan_on_next_start_.load(); }
|
bool rescanOnNextStart() const { return rescan_on_next_start_.load(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Request a wallet repair (-zapwallettxes=2) on the next daemon start. This deletes all
|
||||||
|
* wallet transaction/note records and rebuilds them from the chain (keys are kept); the
|
||||||
|
* daemon implicitly rescans afterwards. One-shot, like the rescan flag.
|
||||||
|
*/
|
||||||
|
void setZapOnNextStart(bool v) { zap_on_next_start_ = v; }
|
||||||
|
bool zapOnNextStart() const { return zap_on_next_start_.load(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief One-shot isolated-datadir override for the NEXT start(): run the daemon against a
|
||||||
|
* different datadir (with its own DRAGONX.conf) plus the given extra args. Used by the
|
||||||
|
* "migrate to a seed wallet" flow to mint a fresh mnemonic wallet in a throwaway datadir
|
||||||
|
* without touching the real one. Consumed on the next start(); later starts are normal.
|
||||||
|
* The caller must serialize this with start() (no concurrent starts).
|
||||||
|
*/
|
||||||
|
void setNextStartOverride(const std::string& datadir, std::vector<std::string> extraArgs) {
|
||||||
|
override_datadir_ = datadir;
|
||||||
|
override_extra_args_ = std::move(extraArgs);
|
||||||
|
}
|
||||||
|
void clearNextStartOverride() { override_datadir_.clear(); override_extra_args_.clear(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Skip the "default RPC port already in use → external daemon" bail in start().
|
||||||
|
* Set true only for an isolated instance running on its OWN (non-default) port
|
||||||
|
* alongside the main daemon (migrate-to-seed flow).
|
||||||
|
*/
|
||||||
|
void setSkipPortCheck(bool v) { skip_port_check_ = v; }
|
||||||
|
|
||||||
|
/** @brief Is an arbitrary TCP port currently in use on localhost? (used to pick a free port) */
|
||||||
|
static bool tcpPortInUse(int port);
|
||||||
|
|
||||||
/** Get number of consecutive daemon crashes (resets on successful start or manual reset) */
|
/** Get number of consecutive daemon crashes (resets on successful start or manual reset) */
|
||||||
int getCrashCount() const { return crash_count_.load(); }
|
int getCrashCount() const { return crash_count_.load(); }
|
||||||
/** Reset crash counter (call on successful connection or manual restart) */
|
/** Reset crash counter (call on successful connection or manual restart) */
|
||||||
@@ -194,8 +250,13 @@ private:
|
|||||||
std::thread monitor_thread_;
|
std::thread monitor_thread_;
|
||||||
std::atomic<bool> should_stop_{false};
|
std::atomic<bool> should_stop_{false};
|
||||||
std::set<std::string> debug_categories_;
|
std::set<std::string> debug_categories_;
|
||||||
|
int max_connections_ = 0; // 0 = daemon default
|
||||||
std::atomic<int> crash_count_{0}; // consecutive crash counter
|
std::atomic<int> crash_count_{0}; // consecutive crash counter
|
||||||
std::atomic<bool> rescan_on_next_start_{false}; // -rescan flag for next start
|
std::atomic<bool> rescan_on_next_start_{false}; // -rescan flag for next start
|
||||||
|
std::atomic<bool> zap_on_next_start_{false}; // -zapwallettxes=2 flag for next start
|
||||||
|
std::string override_datadir_; // one-shot: -datadir for the next start
|
||||||
|
std::vector<std::string> override_extra_args_; // one-shot: extra args for the next start
|
||||||
|
bool skip_port_check_ = false; // isolated instance on a non-default port
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace daemon
|
} // namespace daemon
|
||||||
|
|||||||
93
src/daemon/lifecycle_adapters.cpp
Normal file
93
src/daemon/lifecycle_adapters.cpp
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#include "lifecycle_adapters.h"
|
||||||
|
#include "../util/logger.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace daemon {
|
||||||
|
|
||||||
|
AsyncLifecycleTaskContext::AsyncLifecycleTaskContext(
|
||||||
|
const util::AsyncTaskManager::Token& token,
|
||||||
|
const std::atomic<bool>& shuttingDown)
|
||||||
|
: token_(token), shuttingDown_(shuttingDown)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncLifecycleTaskContext::cancelled() const
|
||||||
|
{
|
||||||
|
return token_.cancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncLifecycleTaskContext::shuttingDown() const
|
||||||
|
{
|
||||||
|
return shuttingDown_.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncLifecycleTaskContext::sleepForMs(int milliseconds)
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(std::max(0, milliseconds)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImmediateLifecycleTaskContext::cancelled() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ImmediateLifecycleTaskContext::shuttingDown() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ImmediateLifecycleTaskContext::sleepForMs(int)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int BlockchainDataCleaner::removeBlockchainData(const std::filesystem::path& dataDir)
|
||||||
|
{
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
static constexpr std::array<const char*, 4> directories = {
|
||||||
|
"blocks", "chainstate", "database", "notarizations"
|
||||||
|
};
|
||||||
|
static constexpr std::array<const char*, 5> files = {
|
||||||
|
"peers.dat", "fee_estimates.dat", "banlist.dat", "db.log", ".lock"
|
||||||
|
};
|
||||||
|
|
||||||
|
int removed = 0;
|
||||||
|
for (const char* directoryName : directories) {
|
||||||
|
fs::path path = dataDir / directoryName;
|
||||||
|
std::error_code existsError;
|
||||||
|
if (!fs::exists(path, existsError)) continue;
|
||||||
|
|
||||||
|
std::error_code removeError;
|
||||||
|
auto count = fs::remove_all(path, removeError);
|
||||||
|
if (!removeError) {
|
||||||
|
removed += static_cast<int>(count);
|
||||||
|
DEBUG_LOGF("[DaemonLifecycle] Removed %s (%d entries)\n",
|
||||||
|
directoryName, static_cast<int>(count));
|
||||||
|
} else {
|
||||||
|
DEBUG_LOGF("[DaemonLifecycle] Failed to remove %s: %s\n",
|
||||||
|
directoryName, removeError.message().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const char* fileName : files) {
|
||||||
|
fs::path path = dataDir / fileName;
|
||||||
|
std::error_code removeError;
|
||||||
|
if (fs::remove(path, removeError)) {
|
||||||
|
++removed;
|
||||||
|
DEBUG_LOGF("[DaemonLifecycle] Removed %s\n", fileName);
|
||||||
|
} else if (removeError) {
|
||||||
|
DEBUG_LOGF("[DaemonLifecycle] Failed to remove %s: %s\n",
|
||||||
|
fileName, removeError.message().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace daemon
|
||||||
|
} // namespace dragonx
|
||||||
39
src/daemon/lifecycle_adapters.h
Normal file
39
src/daemon/lifecycle_adapters.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "daemon_controller.h"
|
||||||
|
#include "../util/async_task_manager.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace daemon {
|
||||||
|
|
||||||
|
class AsyncLifecycleTaskContext final : public DaemonController::LifecycleTaskContext {
|
||||||
|
public:
|
||||||
|
AsyncLifecycleTaskContext(const util::AsyncTaskManager::Token& token,
|
||||||
|
const std::atomic<bool>& shuttingDown);
|
||||||
|
|
||||||
|
bool cancelled() const override;
|
||||||
|
bool shuttingDown() const override;
|
||||||
|
void sleepForMs(int milliseconds) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const util::AsyncTaskManager::Token& token_;
|
||||||
|
const std::atomic<bool>& shuttingDown_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImmediateLifecycleTaskContext final : public DaemonController::LifecycleTaskContext {
|
||||||
|
public:
|
||||||
|
bool cancelled() const override;
|
||||||
|
bool shuttingDown() const override;
|
||||||
|
void sleepForMs(int milliseconds) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BlockchainDataCleaner final {
|
||||||
|
public:
|
||||||
|
static int removeBlockchainData(const std::filesystem::path& dataDir);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace daemon
|
||||||
|
} // namespace dragonx
|
||||||
138
src/daemon/seed_wallet_creator.cpp
Normal file
138
src/daemon/seed_wallet_creator.cpp
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#include "daemon/seed_wallet_creator.h"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
#include "daemon/embedded_daemon.h"
|
||||||
|
#include "rpc/rpc_client.h"
|
||||||
|
#include "util/platform.h"
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace daemon {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Random alphanumeric token for the isolated node's throwaway RPC credentials (libsodium CSPRNG;
|
||||||
|
// sodium_init() has already run at app startup for the chat crypto).
|
||||||
|
std::string randomToken(int n)
|
||||||
|
{
|
||||||
|
static const char cs[] =
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
std::string s;
|
||||||
|
s.reserve(n);
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
s.push_back(cs[randombytes_uniform(sizeof(cs) - 1)]);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A free localhost port for the isolated node — just above the default so it never collides with
|
||||||
|
// the main daemon (which keeps running on the default port throughout).
|
||||||
|
int pickFreePort()
|
||||||
|
{
|
||||||
|
for (int p = 21770; p < 21900; ++p)
|
||||||
|
if (!EmbeddedDaemon::tcpPortInUse(p))
|
||||||
|
return p;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
SeedWalletResult SeedWalletCreator::create(bool keepDatadir,
|
||||||
|
const std::function<void(const std::string&)>& progress)
|
||||||
|
{
|
||||||
|
auto report = [&](const std::string& m) { if (progress) progress(m); };
|
||||||
|
SeedWalletResult r;
|
||||||
|
std::error_code ec;
|
||||||
|
|
||||||
|
// 1. Isolated throwaway datadir. The Komodo/Hush daemon requires the datadir's basename to be
|
||||||
|
// the assetchain name (DRAGONX) — mirroring ~/.hush/DRAGONX — or it mis-resolves its conf and
|
||||||
|
// RPC port. So the wallet lives in <base>/DRAGONX; `base` is the migration root we clean up.
|
||||||
|
const std::string base = util::Platform::getConfigDir() + "/seed-migrate";
|
||||||
|
const std::string dataDir = base + "/DRAGONX";
|
||||||
|
fs::remove_all(base, ec);
|
||||||
|
fs::create_directories(dataDir, ec);
|
||||||
|
if (ec) { r.error = "Could not create the temporary wallet directory."; return r; }
|
||||||
|
|
||||||
|
// 2. Free port + fresh throwaway RPC credentials for the isolated node.
|
||||||
|
const int port = pickFreePort();
|
||||||
|
if (port <= 0) { r.error = "No free local port for the isolated node."; return r; }
|
||||||
|
const std::string user = randomToken(16);
|
||||||
|
const std::string pass = randomToken(32);
|
||||||
|
|
||||||
|
// 3. Minimal conf for the isolated node (own creds/port). The DRAGONX RPC is plaintext HTTP on
|
||||||
|
// localhost — `-tls=only` applies to P2P, not the RPC — so the client below connects without
|
||||||
|
// TLS, exactly as the main GUI does (its conf has no rpctls key either).
|
||||||
|
const std::string conf = "rpcuser=" + user + "\n"
|
||||||
|
"rpcpassword=" + pass + "\n"
|
||||||
|
"rpcport=" + std::to_string(port) + "\n"
|
||||||
|
"server=1\n";
|
||||||
|
if (!util::Platform::writeFileAtomically(dataDir + "/DRAGONX.conf", conf,
|
||||||
|
/*restrictPermissions=*/true)) {
|
||||||
|
r.error = "Could not write the isolated node config.";
|
||||||
|
fs::remove_all(base, ec);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Start the isolated daemon: fresh mnemonic wallet (-usemnemonic=1), no network/sync.
|
||||||
|
report("Starting an isolated node…");
|
||||||
|
EmbeddedDaemon temp;
|
||||||
|
temp.setSkipPortCheck(true); // runs on `port`, beside the main daemon on the default port
|
||||||
|
temp.setNextStartOverride(dataDir, {"-usemnemonic=1", "-connect=0", "-listen=0",
|
||||||
|
"-maxconnections=0"});
|
||||||
|
if (!temp.start("")) {
|
||||||
|
r.error = "Could not start the isolated node: " + temp.getLastError();
|
||||||
|
fs::remove_all(base, ec);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Connect to it, retrying until the RPC is responsive and past warmup.
|
||||||
|
report("Creating your new seed wallet…");
|
||||||
|
rpc::RPCClient cli;
|
||||||
|
bool ready = false;
|
||||||
|
for (int i = 0; i < 90 && !ready; ++i) {
|
||||||
|
if (cli.connect("127.0.0.1", std::to_string(port), user, pass, /*useTls=*/false)) {
|
||||||
|
try { cli.call("getinfo"); ready = true; } // succeeds only once past warmup (-28)
|
||||||
|
catch (...) { cli.disconnect(); }
|
||||||
|
}
|
||||||
|
if (!ready) std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
}
|
||||||
|
if (!ready) {
|
||||||
|
r.error = "The isolated node did not become ready in time.";
|
||||||
|
temp.stop(20000);
|
||||||
|
fs::remove_all(base, ec);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Export the new seed phrase + a fresh shielded receive address (the future sweep target).
|
||||||
|
try {
|
||||||
|
auto m = cli.call("z_exportmnemonic");
|
||||||
|
if (m.contains("mnemonic") && m["mnemonic"].is_string())
|
||||||
|
r.seedPhrase = m["mnemonic"].get<std::string>();
|
||||||
|
r.destAddress = cli.call("z_getnewaddress").get<std::string>();
|
||||||
|
r.ok = !r.seedPhrase.empty() && !r.destAddress.empty();
|
||||||
|
if (!r.ok) r.error = "The isolated node returned an empty seed or address.";
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
r.error = std::string("Seed export failed: ") + e.what();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Stop the isolated node (graceful; it flushes its tiny empty chain quickly).
|
||||||
|
cli.disconnect();
|
||||||
|
temp.stop(20000);
|
||||||
|
|
||||||
|
// 8. Keep the temp wallet for a later sweep/adopt step, or scrub it. tempDatadir is the
|
||||||
|
// migration root `base`; the new wallet.dat lives in <base>/DRAGONX.
|
||||||
|
r.tempDatadir = base;
|
||||||
|
if (!keepDatadir || !r.ok) {
|
||||||
|
fs::remove_all(base, ec);
|
||||||
|
r.tempDatadir.clear();
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace daemon
|
||||||
|
} // namespace dragonx
|
||||||
32
src/daemon/seed_wallet_creator.h
Normal file
32
src/daemon/seed_wallet_creator.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace daemon {
|
||||||
|
|
||||||
|
struct SeedWalletResult {
|
||||||
|
bool ok = false;
|
||||||
|
std::string seedPhrase; // SECRET — the caller must wipe it after use
|
||||||
|
std::string destAddress; // new shielded z-address (the Phase 2 sweep target)
|
||||||
|
std::string tempDatadir; // datadir holding the new wallet.dat (kept iff keepDatadir + ok)
|
||||||
|
std::string error;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mint a fresh BIP39 mnemonic wallet in an ISOLATED throwaway datadir by running a second
|
||||||
|
// dragonxd on its own port with no network, export its seed + a new z-address, then stop it.
|
||||||
|
//
|
||||||
|
// This is the safe first step of "migrate to a seed wallet": it moves no funds and never touches
|
||||||
|
// the main daemon or the real wallet.dat. Blocking — call it on a background thread.
|
||||||
|
//
|
||||||
|
// keepDatadir: keep the temp datadir + its wallet.dat so a later sweep/adopt step can use it, or
|
||||||
|
// delete it immediately (used when only the seed itself is wanted).
|
||||||
|
class SeedWalletCreator {
|
||||||
|
public:
|
||||||
|
static SeedWalletResult create(bool keepDatadir,
|
||||||
|
const std::function<void(const std::string&)>& progress = {});
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace daemon
|
||||||
|
} // namespace dragonx
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "xmrig_manager.h"
|
#include "xmrig_manager.h"
|
||||||
#include "../resources/embedded_resources.h"
|
#include "../resources/embedded_resources.h"
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
#include "../util/logger.h"
|
#include "../util/logger.h"
|
||||||
|
#include "../util/pool_registry.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
@@ -718,6 +720,11 @@ void XmrigManager::fetchStatsHttp() {
|
|||||||
|
|
||||||
std::lock_guard<std::mutex> lk(stats_mutex_);
|
std::lock_guard<std::mutex> lk(stats_mutex_);
|
||||||
|
|
||||||
|
// Miner version (top-level in /2/summary) — lets the UI show the actually
|
||||||
|
// running miner's version even when no release tag was persisted (bundled miner).
|
||||||
|
if (resp.contains("version") && resp["version"].is_string())
|
||||||
|
stats_.version = resp["version"].get<std::string>();
|
||||||
|
|
||||||
if (resp.contains("hashrate") && resp["hashrate"].contains("total")) {
|
if (resp.contains("hashrate") && resp["hashrate"].contains("total")) {
|
||||||
auto& total = resp["hashrate"]["total"];
|
auto& total = resp["hashrate"]["total"];
|
||||||
if (total.is_array() && total.size() >= 3) {
|
if (total.is_array() && total.size() >= 3) {
|
||||||
@@ -768,10 +775,14 @@ void XmrigManager::fetchStatsHttp() {
|
|||||||
void XmrigManager::fetchPoolApiStats() {
|
void XmrigManager::fetchPoolApiStats() {
|
||||||
if (state_ != State::Running || pool_host_.empty()) return;
|
if (state_ != State::Running || pool_host_.empty()) return;
|
||||||
|
|
||||||
// Query the pool's public stats API
|
// Resolve the stats endpoint + JSON schema for this pool. Known pools carry their
|
||||||
std::string url = "https://" + pool_host_ + "/api/stats";
|
// own API shape (pool.dragonx.is = custom /api/stats; pool.dragonx.cc = Miningcore
|
||||||
std::string responseData;
|
// /api/pools); unknown/custom hosts fall back to the .is convention.
|
||||||
|
const util::KnownPool* known = util::findKnownPoolByUrl(pool_host_);
|
||||||
|
const std::string url = known ? known->statsUrl
|
||||||
|
: ("https://" + pool_host_ + "/api/stats");
|
||||||
|
|
||||||
|
std::string responseData;
|
||||||
CURL* curl = curl_easy_init();
|
CURL* curl = curl_easy_init();
|
||||||
if (!curl) return;
|
if (!curl) return;
|
||||||
|
|
||||||
@@ -782,31 +793,99 @@ void XmrigManager::fetchPoolApiStats() {
|
|||||||
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 3000L);
|
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, 3000L);
|
||||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
|
// pool.dragonx.cc sits behind Cloudflare and 403s odd User-Agents.
|
||||||
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; ObsidianDragon)");
|
||||||
|
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
|
||||||
|
|
||||||
CURLcode res = curl_easy_perform(curl);
|
CURLcode res = curl_easy_perform(curl);
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
if (res != CURLE_OK) return;
|
if (res != CURLE_OK) return;
|
||||||
|
|
||||||
try {
|
bool ok = false;
|
||||||
json resp = json::parse(responseData);
|
const double poolHR = util::parsePoolHashrate(
|
||||||
|
known ? known->schema : util::PoolStatsSchema::DragonXIs,
|
||||||
|
responseData, known ? known->miningcorePoolId : std::string{}, ok);
|
||||||
|
if (!ok) return;
|
||||||
|
|
||||||
// Pool stats API format: { "pools": { "<name>": { "hashrate": ... } } }
|
std::lock_guard<std::mutex> lk(stats_mutex_);
|
||||||
double poolHR = 0;
|
stats_.pool_hashrate = poolHR;
|
||||||
if (resp.contains("pools") && resp["pools"].is_object()) {
|
}
|
||||||
for (auto& [key, pool] : resp["pools"].items()) {
|
|
||||||
if (pool.contains("hashrate") && pool["hashrate"].is_number()) {
|
// ============================================================================
|
||||||
poolHR = pool["hashrate"].get<double>();
|
// Installed-miner version detection (`<binary> --version`, cached)
|
||||||
break; // Use the first pool entry
|
// ============================================================================
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::mutex g_installed_ver_mutex;
|
||||||
|
std::string g_installed_ver;
|
||||||
|
std::atomic<bool> g_ver_detect_started{false};
|
||||||
|
|
||||||
|
// Extract the first "D.D[.D...]" version token from `--version` output (skips the
|
||||||
|
// build date, which uses '-' separators). Returns e.g. "6.21.0", or "" if none.
|
||||||
|
std::string parseMinerVersion(const std::string& out)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < out.size(); ++i) {
|
||||||
|
if (std::isdigit(static_cast<unsigned char>(out[i]))) {
|
||||||
|
size_t j = i;
|
||||||
|
int dots = 0;
|
||||||
|
while (j < out.size() &&
|
||||||
|
(std::isdigit(static_cast<unsigned char>(out[j])) || out[j] == '.')) {
|
||||||
|
if (out[j] == '.') ++dots;
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
if (dots >= 1 && (j - i) >= 3) {
|
||||||
|
// Include a trailing build suffix like "-hac" / "-drg1" (e.g. "6.25.1-hac"),
|
||||||
|
// matching what the running miner's API reports.
|
||||||
|
size_t end = j;
|
||||||
|
if (end < out.size() && out[end] == '-') {
|
||||||
|
size_t k = end + 1;
|
||||||
|
while (k < out.size() && std::isalnum(static_cast<unsigned char>(out[k]))) ++k;
|
||||||
|
if (k > end + 1) end = k;
|
||||||
}
|
}
|
||||||
|
return out.substr(i, end - i);
|
||||||
|
}
|
||||||
|
i = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void XmrigManager::startVersionDetection()
|
||||||
|
{
|
||||||
|
if (g_ver_detect_started.exchange(true)) return; // one-shot
|
||||||
|
std::thread([]() {
|
||||||
|
const std::string bin = findXmrigBinary();
|
||||||
|
std::string ver;
|
||||||
|
if (!bin.empty()) {
|
||||||
|
const std::string cmd = "\"" + bin + "\" --version 2>&1";
|
||||||
|
#ifdef _WIN32
|
||||||
|
FILE* fp = _popen(cmd.c_str(), "r");
|
||||||
|
#else
|
||||||
|
FILE* fp = popen(cmd.c_str(), "r");
|
||||||
|
#endif
|
||||||
|
if (fp) {
|
||||||
|
std::string out;
|
||||||
|
char buf[256];
|
||||||
|
size_t n;
|
||||||
|
while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) out.append(buf, n);
|
||||||
|
#ifdef _WIN32
|
||||||
|
_pclose(fp);
|
||||||
|
#else
|
||||||
|
pclose(fp);
|
||||||
|
#endif
|
||||||
|
ver = parseMinerVersion(out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
std::lock_guard<std::mutex> lk(g_installed_ver_mutex);
|
||||||
|
g_installed_ver = ver;
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lk(stats_mutex_);
|
std::string XmrigManager::installedVersion()
|
||||||
stats_.pool_hashrate = poolHR;
|
{
|
||||||
} catch (...) {
|
std::lock_guard<std::mutex> lk(g_installed_ver_mutex);
|
||||||
// Malformed response — ignore
|
return g_installed_ver;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace daemon
|
} // namespace daemon
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public:
|
|||||||
double pool_diff = 0;
|
double pool_diff = 0;
|
||||||
std::string pool_url;
|
std::string pool_url;
|
||||||
std::string algo;
|
std::string algo;
|
||||||
|
std::string version; // miner version reported by the running xmrig API
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
// Memory usage
|
// Memory usage
|
||||||
int64_t memory_free = 0; // bytes
|
int64_t memory_free = 0; // bytes
|
||||||
@@ -137,6 +138,18 @@ public:
|
|||||||
*/
|
*/
|
||||||
static std::string findXmrigBinary();
|
static std::string findXmrigBinary();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Kick a one-shot background `<binary> --version` detection (idempotent).
|
||||||
|
* Lets the UI show the installed miner's version before mining is ever started.
|
||||||
|
*/
|
||||||
|
static void startVersionDetection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cached installed-miner version parsed by startVersionDetection().
|
||||||
|
* Empty until detection completes (or if no binary/parse failed). Thread-safe.
|
||||||
|
*/
|
||||||
|
static std::string installedVersion();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool generateConfig(const Config& cfg, const std::string& outPath);
|
bool generateConfig(const Config& cfg, const std::string& outPath);
|
||||||
bool startProcess(const std::string& xmrigPath, const std::string& cfgPath, int threads);
|
bool startProcess(const std::string& xmrigPath, const std::string& cfgPath, int threads);
|
||||||
|
|||||||
@@ -9,13 +9,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
#include "../util/logger.h"
|
#include "../util/logger.h"
|
||||||
|
#include "../util/platform.h"
|
||||||
#ifdef _WIN32
|
|
||||||
#include <shlobj.h>
|
|
||||||
#else
|
|
||||||
#include <pwd.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
@@ -28,33 +22,11 @@ AddressBook::~AddressBook() = default;
|
|||||||
|
|
||||||
std::string AddressBook::getDefaultPath()
|
std::string AddressBook::getDefaultPath()
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
// Co-locate with settings.json in the per-variant config dir (Lite -> ObsidianDragonLite/).
|
||||||
char path[MAX_PATH];
|
// util::Platform::getConfigDir() owns the per-platform + per-variant path in one place.
|
||||||
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, path))) {
|
const std::string dir = util::Platform::getConfigDir();
|
||||||
std::string dir = std::string(path) + "\\ObsidianDragon";
|
|
||||||
fs::create_directories(dir);
|
|
||||||
return dir + "\\addressbook.json";
|
|
||||||
}
|
|
||||||
return "addressbook.json";
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pw = getpwuid(getuid());
|
|
||||||
home = pw->pw_dir;
|
|
||||||
}
|
|
||||||
std::string dir = std::string(home) + "/Library/Application Support/ObsidianDragon";
|
|
||||||
fs::create_directories(dir);
|
fs::create_directories(dir);
|
||||||
return dir + "/addressbook.json";
|
return (fs::path(dir) / "addressbook.json").string();
|
||||||
#else
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pw = getpwuid(getuid());
|
|
||||||
home = pw->pw_dir;
|
|
||||||
}
|
|
||||||
std::string dir = std::string(home) + "/.config/ObsidianDragon";
|
|
||||||
fs::create_directories(dir);
|
|
||||||
return dir + "/addressbook.json";
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AddressBook::load()
|
bool AddressBook::load()
|
||||||
@@ -113,17 +85,13 @@ bool AddressBook::save()
|
|||||||
j["entries"].push_back(e);
|
j["entries"].push_back(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure directory exists
|
// Atomic + durable: temp file + fsync + rename, so a crash mid-write can't
|
||||||
fs::path p(file_path_);
|
// truncate addressbook.json (which is fully rewritten on every entry change).
|
||||||
fs::create_directories(p.parent_path());
|
// Owner-only (0600) — it holds the user's saved contacts.
|
||||||
|
if (!util::Platform::writeFileAtomically(file_path_, j.dump(2), /*restrictPermissions=*/true)) {
|
||||||
std::ofstream file(file_path_);
|
DEBUG_LOGF("Could not write address book: %s\n", file_path_.c_str());
|
||||||
if (!file.is_open()) {
|
|
||||||
DEBUG_LOGF("Could not open address book for writing: %s\n", file_path_.c_str());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
file << j.dump(2);
|
|
||||||
DEBUG_LOGF("Address book saved: %zu entries\n", entries_.size());
|
DEBUG_LOGF("Address book saved: %zu entries\n", entries_.size());
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,19 @@
|
|||||||
|
|
||||||
#include "exchange_info.h"
|
#include "exchange_info.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
namespace data {
|
namespace data {
|
||||||
|
|
||||||
const std::vector<ExchangeInfo>& getExchangeRegistry()
|
const std::vector<ExchangeInfo>& getExchangeRegistry()
|
||||||
{
|
{
|
||||||
|
// Offline seed / fallback. The live list from CoinGecko (parseCoinGeckoTickers)
|
||||||
|
// supersedes this when available; keep the known venues here so the tab is never
|
||||||
|
// empty without network.
|
||||||
static const std::vector<ExchangeInfo> registry = {
|
static const std::vector<ExchangeInfo> registry = {
|
||||||
{
|
{
|
||||||
"Nonkyc.io",
|
"Nonkyc.io",
|
||||||
@@ -17,9 +25,68 @@ const std::vector<ExchangeInfo>& getExchangeRegistry()
|
|||||||
{"DRGX", "USDT", "DRGX/USDT", "https://nonkyc.io/market/DRGX_USDT"},
|
{"DRGX", "USDT", "DRGX/USDT", "https://nonkyc.io/market/DRGX_USDT"},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"OurBit",
|
||||||
|
"https://www.ourbit.com",
|
||||||
|
{
|
||||||
|
{"DRGX", "USDT", "DRGX/USDT", "https://www.ourbit.com/exchange/DRGX_USDT"},
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return registry;
|
return registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Extract the scheme://host origin from a URL (for ExchangeInfo.baseUrl).
|
||||||
|
std::string originOf(const std::string& url)
|
||||||
|
{
|
||||||
|
const auto scheme = url.find("://");
|
||||||
|
if (scheme == std::string::npos) return url;
|
||||||
|
const auto host = url.find('/', scheme + 3);
|
||||||
|
return (host == std::string::npos) ? url : url.substr(0, host);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::vector<ExchangeInfo> parseCoinGeckoTickers(const std::string& body)
|
||||||
|
{
|
||||||
|
std::vector<ExchangeInfo> exchanges;
|
||||||
|
try {
|
||||||
|
const nlohmann::json j = nlohmann::json::parse(body);
|
||||||
|
if (!j.contains("tickers") || !j["tickers"].is_array()) return {};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, size_t> byName; // exchange name -> index
|
||||||
|
for (const auto& t : j["tickers"]) {
|
||||||
|
if (!t.is_object()) continue;
|
||||||
|
const std::string base = t.value("base", std::string{});
|
||||||
|
const std::string target = t.value("target", std::string{});
|
||||||
|
std::string market;
|
||||||
|
if (t.contains("market") && t["market"].is_object())
|
||||||
|
market = t["market"].value("name", std::string{});
|
||||||
|
const std::string tradeUrl = t.value("trade_url", std::string{});
|
||||||
|
if (base.empty() || target.empty() || market.empty()) continue;
|
||||||
|
|
||||||
|
auto it = byName.find(market);
|
||||||
|
if (it == byName.end()) {
|
||||||
|
byName[market] = exchanges.size();
|
||||||
|
exchanges.push_back(ExchangeInfo{market, originOf(tradeUrl), {}});
|
||||||
|
it = byName.find(market);
|
||||||
|
}
|
||||||
|
ExchangePair pair{base, target, base + "/" + target, tradeUrl};
|
||||||
|
// Skip duplicate pairs on the same exchange.
|
||||||
|
bool dup = false;
|
||||||
|
for (const auto& p : exchanges[it->second].pairs)
|
||||||
|
if (p.displayName == pair.displayName) { dup = true; break; }
|
||||||
|
if (!dup) exchanges[it->second].pairs.push_back(std::move(pair));
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
// Drop exchanges that ended up with no pairs (defensive).
|
||||||
|
exchanges.erase(std::remove_if(exchanges.begin(), exchanges.end(),
|
||||||
|
[](const ExchangeInfo& e) { return e.pairs.empty(); }),
|
||||||
|
exchanges.end());
|
||||||
|
return exchanges;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace data
|
} // namespace data
|
||||||
} // namespace dragonx
|
} // namespace dragonx
|
||||||
|
|||||||
@@ -30,9 +30,19 @@ struct ExchangeInfo {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the static registry of supported exchanges + pairs
|
* @brief Returns the static registry of supported exchanges + pairs.
|
||||||
|
* Used as the offline fallback / seed when the live CoinGecko list is unavailable.
|
||||||
*/
|
*/
|
||||||
const std::vector<ExchangeInfo>& getExchangeRegistry();
|
const std::vector<ExchangeInfo>& getExchangeRegistry();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse a CoinGecko /coins/{id}/tickers JSON body into the exchange registry.
|
||||||
|
*
|
||||||
|
* Groups tickers by exchange (market.name), preserving first-seen order, into
|
||||||
|
* ExchangeInfo entries (each pair carries base/target/displayName/trade_url). Returns
|
||||||
|
* an empty vector on parse failure or when no usable tickers are present. Pure (no I/O).
|
||||||
|
*/
|
||||||
|
std::vector<ExchangeInfo> parseCoinGeckoTickers(const std::string& json);
|
||||||
|
|
||||||
} // namespace data
|
} // namespace data
|
||||||
} // namespace dragonx
|
} // namespace dragonx
|
||||||
|
|||||||
99
src/data/market_series.h
Normal file
99
src/data/market_series.h
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
// DragonX Wallet - ImGui Edition
|
||||||
|
// Copyright 2024-2026 The Hush Developers
|
||||||
|
// Released under the GPLv3
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
#include "wallet_state.h" // MarketInfo
|
||||||
|
|
||||||
|
// Pure (no-I/O, no-ImGui) price-series math backing the market chart and the portfolio-group
|
||||||
|
// sparklines. Kept header-only + inline so it can be unit-tested without the GUI (see
|
||||||
|
// tests/test_phase4.cpp), mirroring data/portfolio.h.
|
||||||
|
namespace dragonx {
|
||||||
|
namespace data {
|
||||||
|
|
||||||
|
// Resample a ~1-sample/minute price history to the given interval by averaging each block of K
|
||||||
|
// minute-samples into one point. interval: 0=min 1=hour 2=day 3=week 4=month.
|
||||||
|
inline std::vector<double> resampleHistory(const std::vector<double>& hist, int interval)
|
||||||
|
{
|
||||||
|
static const int kMins[5] = {1, 60, 1440, 10080, 43200};
|
||||||
|
int k = kMins[(interval >= 0 && interval < 5) ? interval : 0];
|
||||||
|
if (k <= 1) return hist;
|
||||||
|
std::vector<double> out;
|
||||||
|
for (size_t i = 0; i < hist.size(); i += (size_t)k) {
|
||||||
|
double sum = 0.0; size_t cnt = 0;
|
||||||
|
for (size_t j = i; j < hist.size() && j < i + (size_t)k; ++j) { sum += hist[j]; ++cnt; }
|
||||||
|
if (cnt) out.push_back(sum / (double)cnt);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bucket a timestamped (unix-seconds, price) series into fixed-width time windows, averaging the
|
||||||
|
// samples in each window into one point. Input is oldest->newest; output preserves that order.
|
||||||
|
inline std::vector<double> bucketBySeconds(
|
||||||
|
const std::vector<std::pair<std::time_t, double>>& series, long windowSec)
|
||||||
|
{
|
||||||
|
std::vector<double> out;
|
||||||
|
if (series.empty() || windowSec <= 0) return out;
|
||||||
|
long curBucket = 0; double sum = 0.0; int cnt = 0;
|
||||||
|
for (const auto& sample : series) {
|
||||||
|
long b = (long)(sample.first / windowSec);
|
||||||
|
if (cnt > 0 && b != curBucket) { out.push_back(sum / cnt); sum = 0.0; cnt = 0; }
|
||||||
|
curBucket = b;
|
||||||
|
sum += sample.second; ++cnt;
|
||||||
|
}
|
||||||
|
if (cnt > 0) out.push_back(sum / cnt);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the price series backing a group's sparkline for the chosen interval:
|
||||||
|
// 0=minute -> the live in-session buffer; 1=hour -> intraday (5-min) data bucketed to hours;
|
||||||
|
// 2=day / 3=week / 4=month -> the ~1yr daily series bucketed accordingly.
|
||||||
|
// Falls back to the in-session buffer when the historical fetch hasn't populated yet.
|
||||||
|
inline std::vector<double> sparklineSeries(const MarketInfo& m, int interval)
|
||||||
|
{
|
||||||
|
const long kDay = 86400;
|
||||||
|
switch (interval) {
|
||||||
|
case 1: { auto v = bucketBySeconds(m.price_chart_intraday, 3600); if (v.size() >= 2) return v; break; }
|
||||||
|
case 2: { auto v = bucketBySeconds(m.price_chart_daily, kDay); if (v.size() >= 2) return v; break; }
|
||||||
|
case 3: { auto v = bucketBySeconds(m.price_chart_daily, 7 * kDay); if (v.size() >= 2) return v; break; }
|
||||||
|
case 4: { auto v = bucketBySeconds(m.price_chart_daily, 30 * kDay); if (v.size() >= 2) return v; break; }
|
||||||
|
default: break; // minute interval, or no historical data yet -> live buffer below
|
||||||
|
}
|
||||||
|
return resampleHistory(m.price_history, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timestamped price series backing the main chart for the selected RANGE (Live/1H/1D/1W/1M),
|
||||||
|
// ending at `now`. The interval buttons select a time window ending at `now`: 1H = last hour,
|
||||||
|
// 1D = last 24h, 1W = last 7 days, 1M = last 30 days. 1H/1D come from the 5-minute intraday
|
||||||
|
// series; 1W/1M from the daily series. Live (or an un-fetched/empty range) uses the minute buffer.
|
||||||
|
inline std::vector<std::pair<std::time_t, double>> chartSeries(const MarketInfo& m, int interval,
|
||||||
|
std::time_t now)
|
||||||
|
{
|
||||||
|
const long kDay = 86400;
|
||||||
|
auto lastWindow = [now](const std::vector<std::pair<std::time_t, double>>& src, long rangeSec) {
|
||||||
|
std::vector<std::pair<std::time_t, double>> out;
|
||||||
|
std::time_t cutoff = now - (std::time_t)rangeSec;
|
||||||
|
for (const auto& s : src) if (s.first >= cutoff) out.push_back(s);
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
switch (interval) {
|
||||||
|
case 1: { auto v = lastWindow(m.price_chart_intraday, 3600); if (v.size() >= 2) return v; break; } // 1H
|
||||||
|
case 2: { auto v = lastWindow(m.price_chart_intraday, kDay); if (v.size() >= 2) return v; break; } // 1D
|
||||||
|
case 3: { auto v = lastWindow(m.price_chart_daily, 7 * kDay); if (v.size() >= 2) return v; break; } // 1W
|
||||||
|
case 4: { auto v = lastWindow(m.price_chart_daily, 30 * kDay); if (v.size() >= 2) return v; break; } // 1M
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
std::vector<std::pair<std::time_t, double>> out;
|
||||||
|
const auto& h = m.price_history;
|
||||||
|
for (size_t i = 0; i < h.size(); i++)
|
||||||
|
out.push_back({ now - (std::time_t)((h.size() - 1 - i) * 60), h[i] });
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace data
|
||||||
|
} // namespace dragonx
|
||||||
61
src/data/portfolio.h
Normal file
61
src/data/portfolio.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// DragonX Wallet - ImGui Edition
|
||||||
|
// Copyright 2024-2026 The Hush Developers
|
||||||
|
// Released under the GPLv3
|
||||||
|
//
|
||||||
|
// Portfolio helpers — pure balance math for the Market tab's configurable portfolio.
|
||||||
|
// A portfolio entry is a user label tied to a group of wallet addresses (persisted in
|
||||||
|
// Settings as PortfolioEntry); these helpers sum/query that group against the live
|
||||||
|
// per-address balance list. No I/O, no ImGui — unit-testable.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wallet_state.h" // AddressInfo
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace data {
|
||||||
|
|
||||||
|
// Sum the DRGX balance of `entryAddresses` by looking them up in the wallet's per-address
|
||||||
|
// list. Addresses not present in the wallet contribute 0 (e.g. removed/rescanned). Pure.
|
||||||
|
inline double SumPortfolioBalance(const std::vector<std::string>& entryAddresses,
|
||||||
|
const std::vector<AddressInfo>& walletAddresses)
|
||||||
|
{
|
||||||
|
if (entryAddresses.empty()) return 0.0;
|
||||||
|
std::unordered_set<std::string> want(entryAddresses.begin(), entryAddresses.end());
|
||||||
|
double sum = 0.0;
|
||||||
|
for (const auto& a : walletAddresses)
|
||||||
|
if (want.count(a.address)) sum += a.balance;
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whether `address` is part of the entry's address group.
|
||||||
|
inline bool PortfolioEntryContains(const std::vector<std::string>& entryAddresses,
|
||||||
|
const std::string& address)
|
||||||
|
{
|
||||||
|
for (const auto& a : entryAddresses)
|
||||||
|
if (a == address) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add `address` to the group if absent (returns true if it was added).
|
||||||
|
inline bool PortfolioEntryAdd(std::vector<std::string>& entryAddresses, const std::string& address)
|
||||||
|
{
|
||||||
|
if (address.empty() || PortfolioEntryContains(entryAddresses, address)) return false;
|
||||||
|
entryAddresses.push_back(address);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove `address` from the group if present (returns true if it was removed).
|
||||||
|
inline bool PortfolioEntryRemove(std::vector<std::string>& entryAddresses, const std::string& address)
|
||||||
|
{
|
||||||
|
for (auto it = entryAddresses.begin(); it != entryAddresses.end(); ++it) {
|
||||||
|
if (*it == address) { entryAddresses.erase(it); return true; }
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace data
|
||||||
|
} // namespace dragonx
|
||||||
538
src/data/transaction_history_cache.cpp
Normal file
538
src/data/transaction_history_cache.cpp
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
#include "transaction_history_cache.h"
|
||||||
|
|
||||||
|
#include "../util/logger.h"
|
||||||
|
#include "../util/platform.h"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace data {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int kSchemaVersion = 1;
|
||||||
|
constexpr std::size_t kKeyBytes = 32;
|
||||||
|
|
||||||
|
struct Statement {
|
||||||
|
sqlite3_stmt* handle = nullptr;
|
||||||
|
|
||||||
|
Statement(sqlite3* db, const char* sql)
|
||||||
|
{
|
||||||
|
if (sqlite3_prepare_v2(db, sql, -1, &handle, nullptr) != SQLITE_OK) {
|
||||||
|
handle = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~Statement()
|
||||||
|
{
|
||||||
|
if (handle) sqlite3_finalize(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Statement(const Statement&) = delete;
|
||||||
|
Statement& operator=(const Statement&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool bindText(sqlite3_stmt* statement, int index, const std::string& value)
|
||||||
|
{
|
||||||
|
return sqlite3_bind_text(statement, index, value.c_str(), -1, SQLITE_TRANSIENT) == SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bindBlob(sqlite3_stmt* statement, int index, const std::vector<unsigned char>& value)
|
||||||
|
{
|
||||||
|
return sqlite3_bind_blob(statement, index, value.data(), static_cast<int>(value.size()), SQLITE_TRANSIENT) == SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<unsigned char> readBlob(sqlite3_stmt* statement, int index)
|
||||||
|
{
|
||||||
|
const void* data = sqlite3_column_blob(statement, index);
|
||||||
|
int bytes = sqlite3_column_bytes(statement, index);
|
||||||
|
if (!data || bytes <= 0) return {};
|
||||||
|
const auto* begin = static_cast<const unsigned char*>(data);
|
||||||
|
return std::vector<unsigned char>(begin, begin + bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string hexEncode(const unsigned char* bytes, std::size_t length)
|
||||||
|
{
|
||||||
|
static constexpr char kHex[] = "0123456789abcdef";
|
||||||
|
std::string output;
|
||||||
|
output.resize(length * 2);
|
||||||
|
for (std::size_t index = 0; index < length; ++index) {
|
||||||
|
output[index * 2] = kHex[(bytes[index] >> 4) & 0x0F];
|
||||||
|
output[index * 2 + 1] = kHex[bytes[index] & 0x0F];
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
json transactionToJson(const TransactionInfo& transaction)
|
||||||
|
{
|
||||||
|
return json{
|
||||||
|
{"txid", transaction.txid},
|
||||||
|
{"type", transaction.type},
|
||||||
|
{"amount", transaction.amount},
|
||||||
|
{"timestamp", transaction.timestamp},
|
||||||
|
{"confirmations", transaction.confirmations},
|
||||||
|
{"address", transaction.address},
|
||||||
|
{"from_address", transaction.from_address},
|
||||||
|
{"memo", transaction.memo}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionInfo transactionFromJson(const json& source)
|
||||||
|
{
|
||||||
|
TransactionInfo transaction;
|
||||||
|
transaction.txid = source.value("txid", std::string());
|
||||||
|
transaction.type = source.value("type", std::string());
|
||||||
|
transaction.amount = source.value("amount", 0.0);
|
||||||
|
transaction.timestamp = source.value("timestamp", static_cast<std::int64_t>(0));
|
||||||
|
transaction.confirmations = source.value("confirmations", 0);
|
||||||
|
transaction.address = source.value("address", std::string());
|
||||||
|
transaction.from_address = source.value("from_address", std::string());
|
||||||
|
transaction.memo = source.value("memo", std::string());
|
||||||
|
return transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string associatedDataForWallet(const std::string& walletHash)
|
||||||
|
{
|
||||||
|
return std::string("obsidian-dragon-tx-history-v1:") + walletHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TransactionHistoryCache::TransactionHistoryCache()
|
||||||
|
: TransactionHistoryCache(defaultDatabasePath())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionHistoryCache::TransactionHistoryCache(std::string databasePath)
|
||||||
|
: database_path_(std::move(databasePath))
|
||||||
|
{
|
||||||
|
if (sodium_init() < 0) {
|
||||||
|
DEBUG_LOGF("Failed to initialize libsodium for transaction history cache\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionHistoryCache::~TransactionHistoryCache()
|
||||||
|
{
|
||||||
|
lockKey();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string TransactionHistoryCache::defaultDatabasePath()
|
||||||
|
{
|
||||||
|
return (fs::path(util::Platform::getConfigDir()) / "transaction_history.sqlite").string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string TransactionHistoryCache::walletIdentityFromAddresses(
|
||||||
|
const std::vector<std::string>& shieldedAddresses,
|
||||||
|
const std::vector<std::string>& transparentAddresses)
|
||||||
|
{
|
||||||
|
std::vector<std::string> addresses;
|
||||||
|
addresses.reserve(shieldedAddresses.size() + transparentAddresses.size());
|
||||||
|
for (const auto& address : shieldedAddresses) {
|
||||||
|
if (!address.empty()) addresses.push_back("z:" + address);
|
||||||
|
}
|
||||||
|
for (const auto& address : transparentAddresses) {
|
||||||
|
if (!address.empty()) addresses.push_back("t:" + address);
|
||||||
|
}
|
||||||
|
if (addresses.empty()) return {};
|
||||||
|
|
||||||
|
std::sort(addresses.begin(), addresses.end());
|
||||||
|
std::string identity = "wallet-addresses-v1\n";
|
||||||
|
for (const auto& address : addresses) {
|
||||||
|
identity += address;
|
||||||
|
identity += '\n';
|
||||||
|
}
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string TransactionHistoryCache::walletIdentityHash(const std::string& walletIdentity)
|
||||||
|
{
|
||||||
|
unsigned char digest[crypto_generichash_BYTES];
|
||||||
|
crypto_generichash(digest, sizeof(digest),
|
||||||
|
reinterpret_cast<const unsigned char*>(walletIdentity.data()),
|
||||||
|
walletIdentity.size(), nullptr, 0);
|
||||||
|
return hexEncode(digest, sizeof(digest));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransactionHistoryCache::ensureOpen()
|
||||||
|
{
|
||||||
|
if (db_) return true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs::path path(database_path_);
|
||||||
|
if (!path.parent_path().empty()) fs::create_directories(path.parent_path());
|
||||||
|
} catch (const std::exception& exception) {
|
||||||
|
DEBUG_LOGF("Failed to create transaction history cache directory: %s\n", exception.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3* openedDb = nullptr;
|
||||||
|
if (sqlite3_open(database_path_.c_str(), &openedDb) != SQLITE_OK) {
|
||||||
|
DEBUG_LOGF("Failed to open transaction history cache: %s\n",
|
||||||
|
openedDb ? sqlite3_errmsg(openedDb) : "unknown error");
|
||||||
|
if (openedDb) sqlite3_close(openedDb);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
db_ = openedDb;
|
||||||
|
sqlite3_busy_timeout(db_, 2000);
|
||||||
|
exec("PRAGMA journal_mode=WAL");
|
||||||
|
exec("PRAGMA synchronous=NORMAL");
|
||||||
|
|
||||||
|
if (!createSchema()) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
// Owner-only (0600): although the payload is encrypted, don't leave the cache (or its
|
||||||
|
// WAL/SHM sidecars) world-readable. Best-effort; sidecars may not exist until first write.
|
||||||
|
::chmod(database_path_.c_str(), 0600);
|
||||||
|
::chmod((database_path_ + "-wal").c_str(), 0600);
|
||||||
|
::chmod((database_path_ + "-shm").c_str(), 0600);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransactionHistoryCache::unlockWithPassphrase(const std::string& walletIdentity,
|
||||||
|
const std::string& passphrase)
|
||||||
|
{
|
||||||
|
if (walletIdentity.empty() || passphrase.empty() || !ensureOpen()) return false;
|
||||||
|
|
||||||
|
std::string walletHash = walletIdentityHash(walletIdentity);
|
||||||
|
std::vector<unsigned char> salt = getOrCreateSalt(walletHash);
|
||||||
|
if (salt.empty()) return false;
|
||||||
|
|
||||||
|
if (!deriveKey(passphrase, salt)) return false;
|
||||||
|
unlocked_wallet_hash_ = std::move(walletHash);
|
||||||
|
key_ready_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransactionHistoryCache::lockKey()
|
||||||
|
{
|
||||||
|
if (key_ready_) sodium_memzero(key_.data(), key_.size());
|
||||||
|
key_ready_ = false;
|
||||||
|
unlocked_wallet_hash_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransactionHistoryCache::isUnlockedFor(const std::string& walletIdentity) const
|
||||||
|
{
|
||||||
|
return key_ready_ && !walletIdentity.empty() &&
|
||||||
|
unlocked_wallet_hash_ == walletIdentityHash(walletIdentity);
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionHistoryCache::LoadResult TransactionHistoryCache::load(
|
||||||
|
const std::string& walletIdentity,
|
||||||
|
int currentTipHeight,
|
||||||
|
const std::string& currentTipHash)
|
||||||
|
{
|
||||||
|
LoadResult result;
|
||||||
|
if (!isUnlockedFor(walletIdentity) || !ensureOpen()) return result;
|
||||||
|
|
||||||
|
std::string walletHash = walletIdentityHash(walletIdentity);
|
||||||
|
int tipHeight = 0;
|
||||||
|
std::string tipHash;
|
||||||
|
std::time_t updatedAt = 0;
|
||||||
|
std::vector<unsigned char> nonce;
|
||||||
|
std::vector<unsigned char> cipherText;
|
||||||
|
if (!readSnapshot(walletHash, tipHeight, tipHash, updatedAt, nonce, cipherText)) return result;
|
||||||
|
|
||||||
|
if ((currentTipHeight > 0 && tipHeight > currentTipHeight) ||
|
||||||
|
(currentTipHeight > 0 && tipHeight == currentTipHeight &&
|
||||||
|
!currentTipHash.empty() && !tipHash.empty() && tipHash != currentTipHash)) {
|
||||||
|
clearWalletByHash(walletHash);
|
||||||
|
result.invalidated = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string plainText;
|
||||||
|
if (!decryptPayload(walletHash, nonce, cipherText, plainText)) return result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
json payload = json::parse(plainText);
|
||||||
|
if (payload.value("schema_version", 0) != kSchemaVersion) return result;
|
||||||
|
if (payload.value("wallet_hash", std::string()) != walletHash) return result;
|
||||||
|
if (!payload.contains("transactions") || !payload["transactions"].is_array()) return result;
|
||||||
|
|
||||||
|
result.transactions.reserve(payload["transactions"].size());
|
||||||
|
for (const auto& transactionJson : payload["transactions"]) {
|
||||||
|
if (transactionJson.is_object()) {
|
||||||
|
result.transactions.push_back(transactionFromJson(transactionJson));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (payload.contains("shielded_scan_heights") && payload["shielded_scan_heights"].is_object()) {
|
||||||
|
for (auto it = payload["shielded_scan_heights"].begin();
|
||||||
|
it != payload["shielded_scan_heights"].end(); ++it) {
|
||||||
|
if (!it.key().empty() && it.value().is_number_integer()) {
|
||||||
|
result.shieldedScanHeights[it.key()] = it.value().get<int>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.tipHeight = tipHeight;
|
||||||
|
result.tipHash = tipHash;
|
||||||
|
result.updatedAt = updatedAt;
|
||||||
|
result.loaded = true;
|
||||||
|
} catch (...) {
|
||||||
|
result.transactions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
sodium_memzero(plainText.data(), plainText.size());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransactionHistoryCache::replace(const std::string& walletIdentity,
|
||||||
|
int tipHeight,
|
||||||
|
const std::string& tipHash,
|
||||||
|
const std::vector<TransactionInfo>& transactions,
|
||||||
|
std::time_t updatedAt,
|
||||||
|
const std::unordered_map<std::string, int>& shieldedScanHeights)
|
||||||
|
{
|
||||||
|
if (!isUnlockedFor(walletIdentity) || !ensureOpen()) return false;
|
||||||
|
|
||||||
|
std::string walletHash = walletIdentityHash(walletIdentity);
|
||||||
|
json payload;
|
||||||
|
payload["schema_version"] = kSchemaVersion;
|
||||||
|
payload["wallet_hash"] = walletHash;
|
||||||
|
payload["tip_height"] = tipHeight;
|
||||||
|
payload["tip_hash"] = tipHash;
|
||||||
|
payload["updated_at"] = static_cast<std::int64_t>(updatedAt);
|
||||||
|
payload["transactions"] = json::array();
|
||||||
|
for (const auto& transaction : transactions) {
|
||||||
|
payload["transactions"].push_back(transactionToJson(transaction));
|
||||||
|
}
|
||||||
|
payload["shielded_scan_heights"] = json::object();
|
||||||
|
for (const auto& [address, height] : shieldedScanHeights) {
|
||||||
|
if (!address.empty() && height >= 0) {
|
||||||
|
payload["shielded_scan_heights"][address] = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string plainText = payload.dump();
|
||||||
|
std::vector<unsigned char> nonce;
|
||||||
|
std::vector<unsigned char> cipherText;
|
||||||
|
bool encrypted = encryptPayload(walletHash, plainText, nonce, cipherText);
|
||||||
|
sodium_memzero(plainText.data(), plainText.size());
|
||||||
|
if (!encrypted) return false;
|
||||||
|
|
||||||
|
Statement statement(db_,
|
||||||
|
"INSERT OR REPLACE INTO transaction_history_snapshots "
|
||||||
|
"(wallet_hash, schema_version, tip_height, tip_hash, updated_at, nonce, ciphertext) "
|
||||||
|
"VALUES (?, ?, ?, ?, ?, ?, ?)");
|
||||||
|
if (!statement.handle) return false;
|
||||||
|
|
||||||
|
if (!bindText(statement.handle, 1, walletHash)) return false;
|
||||||
|
sqlite3_bind_int(statement.handle, 2, kSchemaVersion);
|
||||||
|
sqlite3_bind_int(statement.handle, 3, std::max(0, tipHeight));
|
||||||
|
if (!bindText(statement.handle, 4, tipHash)) return false;
|
||||||
|
sqlite3_bind_int64(statement.handle, 5, static_cast<sqlite3_int64>(updatedAt));
|
||||||
|
if (!bindBlob(statement.handle, 6, nonce)) return false;
|
||||||
|
if (!bindBlob(statement.handle, 7, cipherText)) return false;
|
||||||
|
|
||||||
|
if (sqlite3_step(statement.handle) != SQLITE_DONE) return false;
|
||||||
|
pruneOtherWallets(walletHash); // bound DB growth — drop stale-hash snapshots/salts
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransactionHistoryCache::clearWallet(const std::string& walletIdentity)
|
||||||
|
{
|
||||||
|
if (walletIdentity.empty()) return;
|
||||||
|
clearWalletByHash(walletIdentityHash(walletIdentity));
|
||||||
|
}
|
||||||
|
|
||||||
|
int TransactionHistoryCache::snapshotCount()
|
||||||
|
{
|
||||||
|
if (!ensureOpen()) return 0;
|
||||||
|
Statement statement(db_, "SELECT COUNT(*) FROM transaction_history_snapshots");
|
||||||
|
if (!statement.handle || sqlite3_step(statement.handle) != SQLITE_ROW) return 0;
|
||||||
|
return sqlite3_column_int(statement.handle, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransactionHistoryCache::exec(const char* sql)
|
||||||
|
{
|
||||||
|
if (!db_) return false;
|
||||||
|
char* error = nullptr;
|
||||||
|
int result = sqlite3_exec(db_, sql, nullptr, nullptr, &error);
|
||||||
|
if (result != SQLITE_OK) {
|
||||||
|
DEBUG_LOGF("Transaction history cache SQL error: %s\n", error ? error : sqlite3_errmsg(db_));
|
||||||
|
if (error) sqlite3_free(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransactionHistoryCache::createSchema()
|
||||||
|
{
|
||||||
|
return exec("CREATE TABLE IF NOT EXISTS transaction_history_keys ("
|
||||||
|
"wallet_hash TEXT PRIMARY KEY,"
|
||||||
|
"salt BLOB NOT NULL)") &&
|
||||||
|
exec("CREATE TABLE IF NOT EXISTS transaction_history_snapshots ("
|
||||||
|
"wallet_hash TEXT PRIMARY KEY,"
|
||||||
|
"schema_version INTEGER NOT NULL,"
|
||||||
|
"tip_height INTEGER NOT NULL,"
|
||||||
|
"tip_hash TEXT NOT NULL,"
|
||||||
|
"updated_at INTEGER NOT NULL,"
|
||||||
|
"nonce BLOB NOT NULL,"
|
||||||
|
"ciphertext BLOB NOT NULL)");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<unsigned char> TransactionHistoryCache::getOrCreateSalt(const std::string& walletHash)
|
||||||
|
{
|
||||||
|
if (!ensureOpen()) return {};
|
||||||
|
|
||||||
|
{
|
||||||
|
Statement statement(db_, "SELECT salt FROM transaction_history_keys WHERE wallet_hash = ?");
|
||||||
|
if (!statement.handle) return {};
|
||||||
|
if (!bindText(statement.handle, 1, walletHash)) return {};
|
||||||
|
if (sqlite3_step(statement.handle) == SQLITE_ROW) {
|
||||||
|
auto salt = readBlob(statement.handle, 0);
|
||||||
|
if (salt.size() == crypto_pwhash_SALTBYTES) return salt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<unsigned char> salt(crypto_pwhash_SALTBYTES);
|
||||||
|
randombytes_buf(salt.data(), salt.size());
|
||||||
|
|
||||||
|
Statement insert(db_,
|
||||||
|
"INSERT OR REPLACE INTO transaction_history_keys (wallet_hash, salt) VALUES (?, ?)");
|
||||||
|
if (!insert.handle) return {};
|
||||||
|
if (!bindText(insert.handle, 1, walletHash)) return {};
|
||||||
|
if (!bindBlob(insert.handle, 2, salt)) return {};
|
||||||
|
if (sqlite3_step(insert.handle) != SQLITE_DONE) return {};
|
||||||
|
return salt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransactionHistoryCache::deriveKey(const std::string& passphrase,
|
||||||
|
const std::vector<unsigned char>& salt)
|
||||||
|
{
|
||||||
|
if (salt.size() != crypto_pwhash_SALTBYTES) return false;
|
||||||
|
unsigned char derived[kKeyBytes];
|
||||||
|
int result = crypto_pwhash(derived, sizeof(derived),
|
||||||
|
passphrase.c_str(), passphrase.size(),
|
||||||
|
salt.data(),
|
||||||
|
crypto_pwhash_OPSLIMIT_INTERACTIVE,
|
||||||
|
crypto_pwhash_MEMLIMIT_INTERACTIVE,
|
||||||
|
crypto_pwhash_ALG_ARGON2ID13);
|
||||||
|
if (result != 0) return false;
|
||||||
|
std::copy(derived, derived + sizeof(derived), key_.begin());
|
||||||
|
sodium_memzero(derived, sizeof(derived));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransactionHistoryCache::encryptPayload(const std::string& walletHash,
|
||||||
|
const std::string& plainText,
|
||||||
|
std::vector<unsigned char>& nonce,
|
||||||
|
std::vector<unsigned char>& cipherText) const
|
||||||
|
{
|
||||||
|
if (!key_ready_) return false;
|
||||||
|
nonce.resize(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
|
||||||
|
randombytes_buf(nonce.data(), nonce.size());
|
||||||
|
|
||||||
|
std::string associatedData = associatedDataForWallet(walletHash);
|
||||||
|
cipherText.resize(plainText.size() + crypto_aead_xchacha20poly1305_ietf_ABYTES);
|
||||||
|
unsigned long long cipherLength = 0;
|
||||||
|
int result = crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||||
|
cipherText.data(), &cipherLength,
|
||||||
|
reinterpret_cast<const unsigned char*>(plainText.data()), plainText.size(),
|
||||||
|
reinterpret_cast<const unsigned char*>(associatedData.data()), associatedData.size(),
|
||||||
|
nullptr, nonce.data(), key_.data());
|
||||||
|
if (result != 0) return false;
|
||||||
|
cipherText.resize(static_cast<std::size_t>(cipherLength));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransactionHistoryCache::decryptPayload(const std::string& walletHash,
|
||||||
|
const std::vector<unsigned char>& nonce,
|
||||||
|
const std::vector<unsigned char>& cipherText,
|
||||||
|
std::string& plainText) const
|
||||||
|
{
|
||||||
|
if (!key_ready_ || nonce.size() != crypto_aead_xchacha20poly1305_ietf_NPUBBYTES ||
|
||||||
|
cipherText.size() < crypto_aead_xchacha20poly1305_ietf_ABYTES) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string associatedData = associatedDataForWallet(walletHash);
|
||||||
|
std::vector<unsigned char> plain(cipherText.size() - crypto_aead_xchacha20poly1305_ietf_ABYTES);
|
||||||
|
unsigned long long plainLength = 0;
|
||||||
|
int result = crypto_aead_xchacha20poly1305_ietf_decrypt(
|
||||||
|
plain.data(), &plainLength, nullptr,
|
||||||
|
cipherText.data(), cipherText.size(),
|
||||||
|
reinterpret_cast<const unsigned char*>(associatedData.data()), associatedData.size(),
|
||||||
|
nonce.data(), key_.data());
|
||||||
|
if (result != 0) return false;
|
||||||
|
|
||||||
|
plainText.assign(reinterpret_cast<const char*>(plain.data()), static_cast<std::size_t>(plainLength));
|
||||||
|
sodium_memzero(plain.data(), plain.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TransactionHistoryCache::readSnapshot(const std::string& walletHash,
|
||||||
|
int& tipHeight,
|
||||||
|
std::string& tipHash,
|
||||||
|
std::time_t& updatedAt,
|
||||||
|
std::vector<unsigned char>& nonce,
|
||||||
|
std::vector<unsigned char>& cipherText)
|
||||||
|
{
|
||||||
|
Statement statement(db_,
|
||||||
|
"SELECT tip_height, tip_hash, updated_at, nonce, ciphertext "
|
||||||
|
"FROM transaction_history_snapshots WHERE wallet_hash = ?");
|
||||||
|
if (!statement.handle) return false;
|
||||||
|
if (!bindText(statement.handle, 1, walletHash)) return false;
|
||||||
|
if (sqlite3_step(statement.handle) != SQLITE_ROW) return false;
|
||||||
|
|
||||||
|
tipHeight = sqlite3_column_int(statement.handle, 0);
|
||||||
|
const unsigned char* tipHashText = sqlite3_column_text(statement.handle, 1);
|
||||||
|
tipHash = tipHashText ? reinterpret_cast<const char*>(tipHashText) : std::string();
|
||||||
|
updatedAt = static_cast<std::time_t>(sqlite3_column_int64(statement.handle, 2));
|
||||||
|
nonce = readBlob(statement.handle, 3);
|
||||||
|
cipherText = readBlob(statement.handle, 4);
|
||||||
|
return !nonce.empty() && !cipherText.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransactionHistoryCache::clearWalletByHash(const std::string& walletHash)
|
||||||
|
{
|
||||||
|
if (!ensureOpen()) return;
|
||||||
|
Statement statement(db_, "DELETE FROM transaction_history_snapshots WHERE wallet_hash = ?");
|
||||||
|
if (!statement.handle) return;
|
||||||
|
if (!bindText(statement.handle, 1, walletHash)) return;
|
||||||
|
sqlite3_step(statement.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransactionHistoryCache::pruneOtherWallets(const std::string& keepWalletHash)
|
||||||
|
{
|
||||||
|
if (!ensureOpen() || keepWalletHash.empty()) return;
|
||||||
|
// Table names are hardcoded literals (no injection surface). Prune both the snapshot
|
||||||
|
// blobs and the now-orphaned salt rows so a stale salt can't outlive its ciphertext.
|
||||||
|
for (const char* table : {"transaction_history_snapshots", "transaction_history_keys"}) {
|
||||||
|
const std::string sql = std::string("DELETE FROM ") + table + " WHERE wallet_hash <> ?";
|
||||||
|
Statement statement(db_, sql.c_str());
|
||||||
|
if (!statement.handle) continue;
|
||||||
|
if (!bindText(statement.handle, 1, keepWalletHash)) continue;
|
||||||
|
sqlite3_step(statement.handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransactionHistoryCache::close()
|
||||||
|
{
|
||||||
|
if (!db_) return;
|
||||||
|
sqlite3_close(db_);
|
||||||
|
db_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace data
|
||||||
|
} // namespace dragonx
|
||||||
93
src/data/transaction_history_cache.h
Normal file
93
src/data/transaction_history_cache.h
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wallet_state.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <ctime>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct sqlite3;
|
||||||
|
|
||||||
|
namespace dragonx {
|
||||||
|
namespace data {
|
||||||
|
|
||||||
|
class TransactionHistoryCache {
|
||||||
|
public:
|
||||||
|
struct LoadResult {
|
||||||
|
bool loaded = false;
|
||||||
|
bool invalidated = false;
|
||||||
|
int tipHeight = 0;
|
||||||
|
std::string tipHash;
|
||||||
|
std::time_t updatedAt = 0;
|
||||||
|
std::vector<TransactionInfo> transactions;
|
||||||
|
std::unordered_map<std::string, int> shieldedScanHeights;
|
||||||
|
};
|
||||||
|
|
||||||
|
TransactionHistoryCache();
|
||||||
|
explicit TransactionHistoryCache(std::string databasePath);
|
||||||
|
~TransactionHistoryCache();
|
||||||
|
|
||||||
|
TransactionHistoryCache(const TransactionHistoryCache&) = delete;
|
||||||
|
TransactionHistoryCache& operator=(const TransactionHistoryCache&) = delete;
|
||||||
|
|
||||||
|
static std::string defaultDatabasePath();
|
||||||
|
static std::string walletIdentityFromAddresses(const std::vector<std::string>& shieldedAddresses,
|
||||||
|
const std::vector<std::string>& transparentAddresses);
|
||||||
|
static std::string walletIdentityHash(const std::string& walletIdentity);
|
||||||
|
|
||||||
|
bool ensureOpen();
|
||||||
|
bool unlockWithPassphrase(const std::string& walletIdentity, const std::string& passphrase);
|
||||||
|
void lockKey();
|
||||||
|
bool hasKey() const { return key_ready_; }
|
||||||
|
bool isUnlockedFor(const std::string& walletIdentity) const;
|
||||||
|
|
||||||
|
LoadResult load(const std::string& walletIdentity,
|
||||||
|
int currentTipHeight,
|
||||||
|
const std::string& currentTipHash);
|
||||||
|
bool replace(const std::string& walletIdentity,
|
||||||
|
int tipHeight,
|
||||||
|
const std::string& tipHash,
|
||||||
|
const std::vector<TransactionInfo>& transactions,
|
||||||
|
std::time_t updatedAt,
|
||||||
|
const std::unordered_map<std::string, int>& shieldedScanHeights = {});
|
||||||
|
void clearWallet(const std::string& walletIdentity);
|
||||||
|
int snapshotCount();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool exec(const char* sql);
|
||||||
|
bool createSchema();
|
||||||
|
std::vector<unsigned char> getOrCreateSalt(const std::string& walletHash);
|
||||||
|
bool deriveKey(const std::string& passphrase,
|
||||||
|
const std::vector<unsigned char>& salt);
|
||||||
|
bool encryptPayload(const std::string& walletHash,
|
||||||
|
const std::string& plainText,
|
||||||
|
std::vector<unsigned char>& nonce,
|
||||||
|
std::vector<unsigned char>& cipherText) const;
|
||||||
|
bool decryptPayload(const std::string& walletHash,
|
||||||
|
const std::vector<unsigned char>& nonce,
|
||||||
|
const std::vector<unsigned char>& cipherText,
|
||||||
|
std::string& plainText) const;
|
||||||
|
bool readSnapshot(const std::string& walletHash,
|
||||||
|
int& tipHeight,
|
||||||
|
std::string& tipHash,
|
||||||
|
std::time_t& updatedAt,
|
||||||
|
std::vector<unsigned char>& nonce,
|
||||||
|
std::vector<unsigned char>& cipherText);
|
||||||
|
void clearWalletByHash(const std::string& walletHash);
|
||||||
|
// Delete snapshot + salt rows for every wallet hash except the live one, bounding the
|
||||||
|
// DB so generating a new address (which changes the hash) doesn't orphan history forever.
|
||||||
|
void pruneOtherWallets(const std::string& keepWalletHash);
|
||||||
|
void close();
|
||||||
|
|
||||||
|
sqlite3* db_ = nullptr;
|
||||||
|
std::string database_path_;
|
||||||
|
std::array<unsigned char, 32> key_{};
|
||||||
|
bool key_ready_ = false;
|
||||||
|
std::string unlocked_wallet_hash_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace data
|
||||||
|
} // namespace dragonx
|
||||||
@@ -3,12 +3,44 @@
|
|||||||
// Released under the GPLv3
|
// Released under the GPLv3
|
||||||
|
|
||||||
#include "wallet_state.h"
|
#include "wallet_state.h"
|
||||||
|
#include <algorithm>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
|
|
||||||
|
std::vector<size_t> sortedSpendableAddressIndices(const std::vector<AddressInfo>& addresses,
|
||||||
|
bool requirePositiveBalance)
|
||||||
|
{
|
||||||
|
std::vector<size_t> indices;
|
||||||
|
indices.reserve(addresses.size());
|
||||||
|
for (size_t i = 0; i < addresses.size(); ++i) {
|
||||||
|
const auto& address = addresses[i];
|
||||||
|
if (!address.isSpendable()) continue;
|
||||||
|
if (requirePositiveBalance && address.balance <= 0.0) continue;
|
||||||
|
indices.push_back(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(indices.begin(), indices.end(), [&](size_t lhs, size_t rhs) {
|
||||||
|
return addresses[lhs].balance > addresses[rhs].balance;
|
||||||
|
});
|
||||||
|
return indices;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bestSpendableAddressIndex(const std::vector<AddressInfo>& addresses)
|
||||||
|
{
|
||||||
|
int bestIndex = -1;
|
||||||
|
double bestBalance = 0.0;
|
||||||
|
for (size_t i = 0; i < addresses.size(); ++i) {
|
||||||
|
if (addresses[i].isSpendable() && addresses[i].balance > bestBalance) {
|
||||||
|
bestBalance = addresses[i].balance;
|
||||||
|
bestIndex = static_cast<int>(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestIndex;
|
||||||
|
}
|
||||||
|
|
||||||
std::string TransactionInfo::getTimeString() const
|
std::string TransactionInfo::getTimeString() const
|
||||||
{
|
{
|
||||||
if (timestamp == 0) return "Unknown";
|
if (timestamp == 0) return "Unknown";
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <ctime>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "exchange_info.h"
|
||||||
|
|
||||||
namespace dragonx {
|
namespace dragonx {
|
||||||
|
|
||||||
@@ -18,6 +22,7 @@ struct AddressInfo {
|
|||||||
std::string address;
|
std::string address;
|
||||||
double balance = 0.0;
|
double balance = 0.0;
|
||||||
std::string type; // "shielded" or "transparent"
|
std::string type; // "shielded" or "transparent"
|
||||||
|
bool has_spending_key = true; // false for view-only (imported via z_importviewingkey)
|
||||||
|
|
||||||
// For display
|
// For display
|
||||||
std::string label;
|
std::string label;
|
||||||
@@ -25,8 +30,13 @@ struct AddressInfo {
|
|||||||
// Derived
|
// Derived
|
||||||
bool isZAddr() const { return !address.empty() && address[0] == 'z'; }
|
bool isZAddr() const { return !address.empty() && address[0] == 'z'; }
|
||||||
bool isShielded() const { return type == "shielded"; }
|
bool isShielded() const { return type == "shielded"; }
|
||||||
|
bool isSpendable() const { return has_spending_key; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::vector<size_t> sortedSpendableAddressIndices(const std::vector<AddressInfo>& addresses,
|
||||||
|
bool requirePositiveBalance = true);
|
||||||
|
int bestSpendableAddressIndex(const std::vector<AddressInfo>& addresses);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Represents a wallet transaction
|
* @brief Represents a wallet transaction
|
||||||
*/
|
*/
|
||||||
@@ -121,6 +131,19 @@ struct SyncInfo {
|
|||||||
float rescan_progress = 0.0f; // 0.0 - 1.0
|
float rescan_progress = 0.0f; // 0.0 - 1.0
|
||||||
std::string rescan_status; // e.g. "Rescanning... 25%"
|
std::string rescan_status; // e.g. "Rescanning... 25%"
|
||||||
|
|
||||||
|
// Sapling note witness rebuild — a distinct, often-long phase after a rescan/zap. The daemon
|
||||||
|
// reports it in TWO sub-phases with different signals, so we track which is active:
|
||||||
|
// 1 = initial pass ("Setting Initial Sapling Witness for tx <hash>, <i> of <N>") — progress
|
||||||
|
// is distinct-txs-witnessed / N (the <i> bounces, so it can't be used directly).
|
||||||
|
// 2 = witness-cache walk ("Building Witnesses for block <h> <frac> complete, <n> remaining")
|
||||||
|
// — progress derived from how far "remaining" has fallen from its per-phase peak.
|
||||||
|
// The two are sequential with different scales, so progress is NOT carried across the boundary
|
||||||
|
// (that would pin the bar at the initial pass's ~100% through the whole cache walk).
|
||||||
|
bool building_witnesses = false;
|
||||||
|
int witness_phase = 0; // 0 none, 1 initial-witness pass, 2 witness-cache walk
|
||||||
|
float witness_progress = 0.0f; // 0.0 - 1.0, within the current sub-phase
|
||||||
|
int witness_remaining = 0; // blocks left in the cache walk (0 if unknown / phase 1)
|
||||||
|
|
||||||
bool isSynced() const { return !syncing && blocks > 0 && blocks >= headers - 2; }
|
bool isSynced() const { return !syncing && blocks > 0 && blocks >= headers - 2; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,10 +157,27 @@ struct MarketInfo {
|
|||||||
double change_24h = 0.0;
|
double change_24h = 0.0;
|
||||||
double market_cap = 0.0;
|
double market_cap = 0.0;
|
||||||
std::string last_updated;
|
std::string last_updated;
|
||||||
|
std::chrono::steady_clock::time_point last_fetch_time{};
|
||||||
|
bool price_loading = false;
|
||||||
|
std::string price_error;
|
||||||
|
|
||||||
// Price history for chart
|
// Live in-session price history: ~1 sample/minute, capped at MAX_HISTORY samples.
|
||||||
|
// Backs the main chart and the "minute" portfolio-sparkline interval.
|
||||||
std::vector<double> price_history;
|
std::vector<double> price_history;
|
||||||
static constexpr int MAX_HISTORY = 24; // 24 hours
|
static constexpr int MAX_HISTORY = 24; // 24 samples (~24 minutes at the 60s refresh)
|
||||||
|
|
||||||
|
// Historical USD price series fetched from CoinGecko market_chart, so the portfolio-group
|
||||||
|
// sparklines can show real hour/day/week/month trends instead of resampling the ~24-minute
|
||||||
|
// in-session buffer. Timestamped (unix seconds), oldest->newest; empty until the first fetch.
|
||||||
|
// Refreshed on a slow cadence (~30 min) since historical data moves slowly.
|
||||||
|
std::vector<std::pair<std::time_t, double>> price_chart_intraday; // ~24h @ 5-minute granularity
|
||||||
|
std::vector<std::pair<std::time_t, double>> price_chart_daily; // ~1yr @ daily granularity
|
||||||
|
std::chrono::steady_clock::time_point chart_last_fetch_time{};
|
||||||
|
bool chart_loaded = false;
|
||||||
|
|
||||||
|
// Exchanges/pairs fetched live from CoinGecko (empty until fetched; the Market tab
|
||||||
|
// falls back to data::getExchangeRegistry() while empty).
|
||||||
|
std::vector<data::ExchangeInfo> exchanges;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,6 +188,7 @@ struct PoolMiningState {
|
|||||||
bool xmrig_running = false;
|
bool xmrig_running = false;
|
||||||
std::string pool_url;
|
std::string pool_url;
|
||||||
std::string algo;
|
std::string algo;
|
||||||
|
std::string version; // running miner's version (from its API)
|
||||||
|
|
||||||
double hashrate_10s = 0;
|
double hashrate_10s = 0;
|
||||||
double hashrate_60s = 0;
|
double hashrate_60s = 0;
|
||||||
@@ -179,6 +220,15 @@ struct PoolMiningState {
|
|||||||
struct WalletState {
|
struct WalletState {
|
||||||
// Connection
|
// Connection
|
||||||
bool connected = false;
|
bool connected = false;
|
||||||
|
bool warming_up = false; // daemon reachable but in RPC warmup (error -28)
|
||||||
|
// True when the daemon is up/launching but not yet answering RPC (e.g. the connect probe
|
||||||
|
// times out because the node is loading the block index). Distinct from warming_up, which
|
||||||
|
// needs a JSON-RPC -28 reply; here getinfo never returns, so we infer the state from the
|
||||||
|
// daemon's launch state + its own console output. Drives the same loading overlay so the
|
||||||
|
// user sees WHAT the node is doing instead of a bare "Connection failed".
|
||||||
|
bool daemon_initializing = false;
|
||||||
|
std::string warmup_status; // user-friendly title, e.g. "Processing blocks..."
|
||||||
|
std::string warmup_description; // subtitle explaining the stage
|
||||||
int daemon_version = 0;
|
int daemon_version = 0;
|
||||||
std::string daemon_subversion;
|
std::string daemon_subversion;
|
||||||
int protocol_version = 0;
|
int protocol_version = 0;
|
||||||
@@ -249,6 +299,10 @@ struct WalletState {
|
|||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
connected = false;
|
connected = false;
|
||||||
|
warming_up = false;
|
||||||
|
daemon_initializing = false;
|
||||||
|
warmup_status.clear();
|
||||||
|
warmup_description.clear();
|
||||||
daemon_version = 0;
|
daemon_version = 0;
|
||||||
daemon_subversion.clear();
|
daemon_subversion.clear();
|
||||||
protocol_version = 0;
|
protocol_version = 0;
|
||||||
|
|||||||
@@ -11,5 +11,7 @@
|
|||||||
INCBIN(ubuntu_regular, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-R.ttf");
|
INCBIN(ubuntu_regular, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-R.ttf");
|
||||||
INCBIN(ubuntu_light, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-Light.ttf");
|
INCBIN(ubuntu_light, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-Light.ttf");
|
||||||
INCBIN(ubuntu_medium, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-Medium.ttf");
|
INCBIN(ubuntu_medium, "@CMAKE_SOURCE_DIR@/res/fonts/Ubuntu-Medium.ttf");
|
||||||
|
INCBIN(ubuntu_mono, "@CMAKE_SOURCE_DIR@/res/fonts/UbuntuMono-R.ttf");
|
||||||
INCBIN(material_icons, "@CMAKE_SOURCE_DIR@/res/fonts/MaterialIcons-Regular.ttf");
|
INCBIN(material_icons, "@CMAKE_SOURCE_DIR@/res/fonts/MaterialIcons-Regular.ttf");
|
||||||
INCBIN(noto_cjk_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoSansCJK-Subset.otf");
|
INCBIN(mdi_pickaxe_subset, "@CMAKE_SOURCE_DIR@/res/fonts/MaterialDesignIcons-Pickaxe-Subset.ttf");
|
||||||
|
INCBIN(noto_cjk_subset, "@CMAKE_SOURCE_DIR@/res/fonts/NotoSansCJK-Subset.ttf");
|
||||||
|
|||||||
@@ -24,9 +24,15 @@ extern "C" {
|
|||||||
extern const unsigned char g_ubuntu_medium_data[];
|
extern const unsigned char g_ubuntu_medium_data[];
|
||||||
extern const unsigned int g_ubuntu_medium_size;
|
extern const unsigned int g_ubuntu_medium_size;
|
||||||
|
|
||||||
|
extern const unsigned char g_ubuntu_mono_data[];
|
||||||
|
extern const unsigned int g_ubuntu_mono_size;
|
||||||
|
|
||||||
extern const unsigned char g_material_icons_data[];
|
extern const unsigned char g_material_icons_data[];
|
||||||
extern const unsigned int g_material_icons_size;
|
extern const unsigned int g_material_icons_size;
|
||||||
|
|
||||||
|
extern const unsigned char g_mdi_pickaxe_subset_data[];
|
||||||
|
extern const unsigned int g_mdi_pickaxe_subset_size;
|
||||||
|
|
||||||
extern const unsigned char g_noto_cjk_subset_data[];
|
extern const unsigned char g_noto_cjk_subset_data[];
|
||||||
extern const unsigned int g_noto_cjk_subset_size;
|
extern const unsigned int g_noto_cjk_subset_size;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user