diff --git a/res/fonts/NotoSansCJK-Subset.ttf b/res/fonts/NotoSansCJK-Subset.ttf index e13e2f9..e4e1e01 100644 Binary files a/res/fonts/NotoSansCJK-Subset.ttf and b/res/fonts/NotoSansCJK-Subset.ttf differ diff --git a/res/lang/de.json b/res/lang/de.json index 8cddb65..ea03e2d 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -1308,6 +1308,8 @@ "mining_est_block": "Gesch. Block", "mining_est_daily": "Gesch. täglich", "mining_est_daily_pool_sub": "grobe Solo-Äquivalenz, vor Pool-Gebühr", + "mining_est_daily_sub": "Ø", + "mining_est_daily_sub_net": "Ø, netto", "mining_filter_all": "Alle", "mining_filter_tip_all": "Alle Einnahmen anzeigen", "mining_filter_tip_pool": "Nur Pool-Einnahmen anzeigen", @@ -2108,6 +2110,9 @@ "tt_low_spec": "Alle aufwendigen visuellen Effekte deaktivieren\\nHotkey: Ctrl+Shift+Down", "tt_merge": "Mehrere UTXOs einer Adresse zusammenführen", "tt_mine_idle": "Mining automatisch starten, wenn das\\nSystem inaktiv ist (keine Tastatur-/Mauseingabe)", + "tt_mining_est_daily": "Ein langfristiger statistischer Durchschnitt für Ihren aktuellen Hashrate-Anteil am Netzwerk. Echte Mining-Belohnungen kommen zufällig (Poisson) – oft lange nichts, dann ein Batzen – das ist also KEIN gleichmäßiges Tageseinkommen.", + "tt_mining_est_daily_fee_fmt": "Angezeigt nach der %.1f%% Pool-Gebühr.", + "tt_mining_est_daily_nofee": "Vor Abzug der Pool-Gebühr angezeigt (Gebühr für diesen Pool unbekannt).", "tt_new_mining_address": "Erstellt eine transparente Adresse (R…) für Solo-Mining-Belohnungen. Sie kann keine Zahlungen empfangen – teilen Sie dafür eine abgeschirmte Adresse.", "tt_noise": "Körnungstextur-Intensität (0% = aus, 100% = maximum)", "tt_open_app_dir": "Den ObsidianDragon-Ordner (Einstellungen, Themes, Logs) im Dateimanager öffnen", diff --git a/res/lang/es.json b/res/lang/es.json index 4bb0fe0..eed85a7 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -1308,6 +1308,8 @@ "mining_est_block": "Bloque Est.", "mining_est_daily": "Diario Est.", "mining_est_daily_pool_sub": "equivalente solo aproximado, antes de la comisión del pool", + "mining_est_daily_sub": "prom.", + "mining_est_daily_sub_net": "prom., neto", "mining_filter_all": "Todos", "mining_filter_tip_all": "Mostrar todas las ganancias", "mining_filter_tip_pool": "Mostrar solo ganancias del pool", @@ -2108,6 +2110,9 @@ "tt_low_spec": "Desactivar todos los efectos visuales pesados\\nAtajo: Ctrl+Shift+Down", "tt_merge": "Consolidar múltiples UTXOs en una dirección", "tt_mine_idle": "Iniciar minería automáticamente cuando el\\nsistema esté inactivo (sin entrada de teclado/ratón)", + "tt_mining_est_daily": "Un promedio estadístico a largo plazo para tu proporción actual de hashrate en la red. Las recompensas reales de minería llegan al azar (Poisson): a menudo nada durante largos periodos y luego una suma; por eso NO es un ingreso diario constante.", + "tt_mining_est_daily_fee_fmt": "Se muestra tras la comisión del pool del %.1f%%.", + "tt_mining_est_daily_nofee": "Se muestra antes de la comisión del pool (comisión desconocida para este pool).", "tt_new_mining_address": "Crea una dirección transparente (R…) para recompensas de minería en solitario. No puede recibir pagos; para eso comparte una dirección blindada.", "tt_noise": "Intensidad de textura granulada (0% = apagado, 100% = máximo)", "tt_open_app_dir": "Abrir la carpeta de ObsidianDragon (configuración, temas, registros) en el explorador de archivos", diff --git a/res/lang/fr.json b/res/lang/fr.json index cf13c5d..d3a2cc4 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -1308,6 +1308,8 @@ "mining_est_block": "Bloc est.", "mining_est_daily": "Est. quotidien", "mining_est_daily_pool_sub": "équivalent solo approximatif, avant les frais du pool", + "mining_est_daily_sub": "moy.", + "mining_est_daily_sub_net": "moy., net", "mining_filter_all": "Tout", "mining_filter_tip_all": "Afficher tous les gains", "mining_filter_tip_pool": "Afficher uniquement les gains du pool", @@ -2108,6 +2110,9 @@ "tt_low_spec": "Désactiver tous les effets visuels lourds\\nRaccourci : Ctrl+Shift+Down", "tt_merge": "Consolider plusieurs UTXOs vers une adresse", "tt_mine_idle": "Démarrer le minage automatiquement quand le\\nsystème est inactif (aucune entrée clavier/souris)", + "tt_mining_est_daily": "Une moyenne statistique à long terme pour votre part actuelle de hashrate dans le réseau. Les récompenses réelles de minage arrivent au hasard (Poisson) — souvent rien pendant longtemps, puis une somme — ce n'est donc PAS un revenu quotidien régulier.", + "tt_mining_est_daily_fee_fmt": "Affiché après les %.1f%% de frais de pool.", + "tt_mining_est_daily_nofee": "Affiché avant les frais de pool (frais inconnus pour ce pool).", "tt_new_mining_address": "Crée une adresse transparente (R…) pour les récompenses de minage en solo. Elle ne peut pas recevoir de paiements — partagez une adresse blindée pour cela.", "tt_noise": "Intensité de texture grainée (0% = désactivé, 100% = maximum)", "tt_open_app_dir": "Ouvrir le dossier ObsidianDragon (paramètres, thèmes, journaux) dans le gestionnaire de fichiers", diff --git a/res/lang/ja.json b/res/lang/ja.json index ef1d586..fcb2d8c 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -1308,6 +1308,8 @@ "mining_est_block": "予測ブロック", "mining_est_daily": "予測日収", "mining_est_daily_pool_sub": "おおよそのソロ換算(プール手数料前)", + "mining_est_daily_sub": "平均", + "mining_est_daily_sub_net": "平均・手取り", "mining_filter_all": "すべて", "mining_filter_tip_all": "すべての収益を表示", "mining_filter_tip_pool": "プール収益のみ表示", @@ -2108,6 +2110,9 @@ "tt_low_spec": "すべての重い視覚効果を無効化\\nホットキー:Ctrl+Shift+Down", "tt_merge": "複数の UTXO を一つのアドレスに統合", "tt_mine_idle": "システムがアイドル状態(キーボード/マウス入力なし)\\nのとき自動的にマイニングを開始", + "tt_mining_est_daily": "ネットワーク全体に占める現在のハッシュレート比率に基づく長期的な統計平均です。実際のマイニング報酬はランダム(ポアソン分布)に発生し、長期間ゼロが続いた後にまとまって得られることが多いため、これは安定した日次収入ではありません。", + "tt_mining_est_daily_fee_fmt": "%.1f%% のプール手数料を差し引いた値です。", + "tt_mining_est_daily_nofee": "プール手数料を差し引く前の値です(このプールの手数料は不明)。", "tt_new_mining_address": "ソロマイニング報酬用の透明(R…)アドレスを作成します。支払いは受け取れません。支払いにはシールドアドレスを共有してください。", "tt_noise": "グレインテクスチャ強度(0% = オフ、100% = 最大)", "tt_open_app_dir": "ObsidianDragon フォルダ(設定、テーマ、ログ)をファイルマネージャーで開く", diff --git a/res/lang/ko.json b/res/lang/ko.json index f1b1fa4..58b30f1 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -1308,6 +1308,8 @@ "mining_est_block": "예상 블록", "mining_est_daily": "예상 일일 수익", "mining_est_daily_pool_sub": "대략적인 솔로 환산, 풀 수수료 전", + "mining_est_daily_sub": "평균", + "mining_est_daily_sub_net": "평균, 순", "mining_filter_all": "전체", "mining_filter_tip_all": "모든 수익 표시", "mining_filter_tip_pool": "풀 수익만 표시", @@ -2108,6 +2110,9 @@ "tt_low_spec": "모든 고부하 시각 효과 비활성화\\n단축키: Ctrl+Shift+Down", "tt_merge": "여러 UTXO를 하나의 주소로 통합", "tt_mine_idle": "시스템이 유휴 상태(키보드/마우스 입력 없음)일 때\\n자동으로 채굴 시작", + "tt_mining_est_daily": "네트워크에서 현재 해시레이트 비중에 대한 장기 통계 평균입니다. 실제 채굴 보상은 무작위(푸아송)로 도착하여 오랫동안 아무것도 없다가 한꺼번에 들어오는 경우가 많으므로, 이는 꾸준한 일일 수입이 아닙니다.", + "tt_mining_est_daily_fee_fmt": "%.1f%% 풀 수수료를 제외하고 표시됩니다.", + "tt_mining_est_daily_nofee": "풀 수수료를 제외하기 전 값입니다(이 풀의 수수료는 알 수 없음).", "tt_new_mining_address": "솔로 채굴 보상용 투명(R…) 주소를 만듭니다. 결제는 받을 수 없으니, 결제용으로는 차폐 주소를 공유하세요.", "tt_noise": "그레인 텍스처 강도 (0% = 끔, 100% = 최대)", "tt_open_app_dir": "파일 관리자에서 ObsidianDragon 폴더(설정, 테마, 로그)를 엽니다", diff --git a/res/lang/pt.json b/res/lang/pt.json index 67df707..80501e1 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -1308,6 +1308,8 @@ "mining_est_block": "Bloco Est.", "mining_est_daily": "Est. Diário", "mining_est_daily_pool_sub": "equivalente solo aproximado, antes da taxa do pool", + "mining_est_daily_sub": "méd.", + "mining_est_daily_sub_net": "méd., líq.", "mining_filter_all": "Todos", "mining_filter_tip_all": "Mostrar todos os ganhos", "mining_filter_tip_pool": "Mostrar apenas ganhos do pool", @@ -2108,6 +2110,9 @@ "tt_low_spec": "Desativar todos os efeitos visuais pesados\\nAtalho: Ctrl+Shift+Down", "tt_merge": "Consolidar múltiplos UTXOs em um endereço", "tt_mine_idle": "Iniciar mineração automaticamente quando o\\nsistema estiver ocioso (sem entrada de teclado/mouse)", + "tt_mining_est_daily": "Uma média estatística de longo prazo para a sua fração atual de hashrate na rede. As recompensas reais de mineração chegam de forma aleatória (Poisson) — muitas vezes nada por longos períodos e depois um valor — por isso NÃO é uma renda diária constante.", + "tt_mining_est_daily_fee_fmt": "Mostrado após a taxa de %.1f%% do pool.", + "tt_mining_est_daily_nofee": "Mostrado antes da taxa do pool (taxa desconhecida para este pool).", "tt_new_mining_address": "Cria um endereço transparente (R…) para recompensas de mineração solo. Ele não pode receber pagamentos — compartilhe um endereço blindado para isso.", "tt_noise": "Intensidade de textura granulada (0% = desligado, 100% = máximo)", "tt_open_app_dir": "Abrir a pasta ObsidianDragon (configurações, temas, logs) no gerenciador de arquivos", diff --git a/res/lang/ru.json b/res/lang/ru.json index cb30f28..96c7a91 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -1308,6 +1308,8 @@ "mining_est_block": "Расч. блок", "mining_est_daily": "Расч. за день", "mining_est_daily_pool_sub": "примерный соло-эквивалент, до комиссии пула", + "mining_est_daily_sub": "сред.", + "mining_est_daily_sub_net": "сред., нетто", "mining_filter_all": "Все", "mining_filter_tip_all": "Показать все доходы", "mining_filter_tip_pool": "Показать только доходы пула", @@ -2108,6 +2110,9 @@ "tt_low_spec": "Отключить все тяжёлые визуальные эффекты\\nГорячая клавиша: Ctrl+Shift+Down", "tt_merge": "Объединить несколько UTXO в один адрес", "tt_mine_idle": "Автоматически начать майнинг при\\nпростое системы (нет ввода с клавиатуры/мыши)", + "tt_mining_est_daily": "Долгосрочное статистическое среднее для вашей текущей доли хешрейта в сети. Реальные награды за майнинг приходят случайно (по Пуассону) — часто подолгу ничего, а затем сразу много, — поэтому это НЕ стабильный ежедневный доход.", + "tt_mining_est_daily_fee_fmt": "Показано за вычетом комиссии пула %.1f%%.", + "tt_mining_est_daily_nofee": "Показано до вычета комиссии пула (комиссия этого пула неизвестна).", "tt_new_mining_address": "Создаёт прозрачный адрес (R…) для наград за соло-майнинг. Он не принимает платежи — для них сообщайте экранированный адрес.", "tt_noise": "Интенсивность зернистой текстуры (0% = выкл., 100% = максимум)", "tt_open_app_dir": "Открыть папку ObsidianDragon (настройки, темы, логи) в файловом менеджере", diff --git a/res/lang/zh.json b/res/lang/zh.json index a6c7cd9..fef40fb 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -1308,6 +1308,8 @@ "mining_est_block": "预计区块", "mining_est_daily": "预计日收益", "mining_est_daily_pool_sub": "粗略的单人挖矿等值,扣除矿池费用前", + "mining_est_daily_sub": "平均", + "mining_est_daily_sub_net": "平均·净", "mining_filter_all": "全部", "mining_filter_tip_all": "显示所有收益", "mining_filter_tip_pool": "仅显示矿池收益", @@ -2108,6 +2110,9 @@ "tt_low_spec": "禁用所有重度视觉效果\\n快捷键:Ctrl+Shift+Down", "tt_merge": "将多个 UTXO 合并到一个地址", "tt_mine_idle": "系统空闲时自动开始挖矿\\n(无键盘/鼠标输入)", + "tt_mining_est_daily": "基于您当前在全网算力占比的长期统计平均值。实际挖矿奖励是随机到达的(泊松分布)——常常很长时间没有,然后一次性获得一笔,因此这并不是稳定的每日收入。", + "tt_mining_est_daily_fee_fmt": "已扣除 %.1f%% 矿池手续费。", + "tt_mining_est_daily_nofee": "未扣除矿池手续费(此矿池手续费未知)。", "tt_new_mining_address": "创建一个用于单人挖矿奖励的透明(R…)地址。它无法接收付款——收款请提供屏蔽地址。", "tt_noise": "颗粒纹理强度(0% = 关闭,100% = 最大)", "tt_open_app_dir": "在文件管理器中打开 ObsidianDragon 文件夹(设置、主题、日志)", diff --git a/src/ui/windows/mining_earnings.cpp b/src/ui/windows/mining_earnings.cpp index 186f23c..d529b73 100644 --- a/src/ui/windows/mining_earnings.cpp +++ b/src/ui/windows/mining_earnings.cpp @@ -10,6 +10,7 @@ #include "../../util/i18n.h" #include "../../util/platform.h" #include "../../config/settings.h" +#include "../../util/pool_registry.h" #include "../schema/ui_schema.h" #include "../material/type.h" #include "../material/draw_helpers.h" @@ -68,15 +69,38 @@ void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& minedAllTimeCount = earn.allTimeCount; const std::vector& recentMined = earn.recent; - // Est. Daily = expected reward for YOUR own hashrate share of the network. In pool mode this uses - // your local miner rate (pool_mining.hashrate_10s), NOT the pool's aggregate rate: pool payouts are - // share-proportional, so your expected daily is the same solo-equivalent value (a rough estimate, - // before the pool fee). Using the pool's total rate here would show the POOL's earnings, not yours. (M-05) - double estHashrate = s_pool_mode ? state.pool_mining.hashrate_10s : mining.localHashrate; + // Est. Daily is a statistical long-run AVERAGE of your reward for your hashrate share of the + // network -- NOT a steady income. Actual rewards arrive at random (Poisson): often nothing for + // long stretches, then a lump. We feed EstimateHoursToBlock a TRAILING-AVERAGE hashrate (not an + // instantaneous / 10s snapshot) so a spike or a momentary dip doesn't swing the figure, and in + // pool mode we subtract the known pool fee so the number is net. Pool payouts are share- + // proportional, so your expected daily equals this solo-equivalent value (using the pool's + // aggregate rate would show the POOL's earnings, not yours). (M-05 / A+B) + double estHashrate; + if (s_pool_mode) { + const auto& pm = state.pool_mining; + estHashrate = pm.hashrate_15m > 0.0 ? pm.hashrate_15m + : pm.hashrate_60s > 0.0 ? pm.hashrate_60s + : pm.hashrate_10s; // 15m avg preferred; fall back as it warms up + } else { + estHashrate = mining.localHashrate; + if (mining.hashrate_history.size() >= 10) { // enough samples -> trailing mean + double sum = 0.0; + for (double h : mining.hashrate_history) sum += h; + estHashrate = sum / (double)mining.hashrate_history.size(); + } + } double est_hours_2 = EstimateHoursToBlock(estHashrate, mining.networkHashrate, mining.difficulty); double estDailyBlocks = (est_hours_2 > 0) ? (24.0 / est_hours_2) : 0.0; double blockReward = schema::UI().drawElement("business", "block-reward").size; double estDaily = estDailyBlocks * blockReward; + // Pool fee: compile-time known fee for the configured pool (<0 = unknown/synthetic pool -> gross). + double estFeePct = -1.0; + if (s_pool_mode) { + const util::KnownPool* kp = util::findKnownPoolByUrl(state.pool_mining.pool_url); + if (kp && kp->feePercent >= 0.0) estFeePct = kp->feePercent; + } + if (s_pool_mode && estFeePct > 0.0) estDaily *= (1.0 - estFeePct / 100.0); bool estActive = isMiningActive && estDaily > 0; ImU32 greenCol2 = Success(); @@ -185,15 +209,19 @@ void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& snprintf(estVal, sizeof(estVal), "~%.4f", estDaily); else snprintf(estVal, sizeof(estVal), "%s", TR("grpc_na")); - // Disclose in pool mode that Est. Daily is a rough solo-equivalent (before the pool fee), so the - // number isn't silently mismatched to its plain "Est. Daily" label. (M-05) - const char* estSub = (s_pool_mode && estActive) ? TR("mining_est_daily_pool_sub") : nullptr; + // Flag that Est. Daily is a long-run AVERAGE in both modes (so it doesn't read as realized + // income), and whether it is net of the pool fee. Tooltip below carries the full variance note. + const char* estSub = !estActive ? nullptr + : (s_pool_mode && estFeePct > 0.0) ? TR("mining_est_daily_sub_net") + : TR("mining_est_daily_sub"); + // Neutral (not green/"income") colour for the estimate — it's a probability, not money earned. + const ImU32 estCol = estActive ? OnSurfaceMedium() : OnSurfaceDisabled(); EarningsEntry entries[] = { { TR("mining_today"), todayVal, todaySub, greenCol2 }, { TR("mining_yesterday"), yesterdayVal, yesterdaySub, OnSurface() }, { TR("mining_all_time"), allVal, allSub, OnSurface() }, - { TR("mining_est_daily"), estVal, estSub, estActive ? greenCol2 : OnSurfaceDisabled() }, + { TR("mining_est_daily"), estVal, estSub, estCol }, }; for (int ei = 0; ei < numCols; ei++) { @@ -223,6 +251,27 @@ void RenderMiningEarnings(App* app, const WalletState& state, const MiningInfo& ImVec2(centerX - valSz.x * 0.5f, valY), entries[ei].col, entries[ei].value); } } + + // Hover tooltip over the Est. Daily column (last column): explain the Poisson variance and, + // in pool mode, whether the figure is net of the fee. + { + float estColX = cardMin.x + pad + (numCols - 1) * colW; + ImVec2 estMin(estColX, ey); + ImVec2 estMax(estColX + colW, cardMin.y + earningsRowH); + if (material::IsRectHovered(estMin, estMax)) { + char tip[512]; + if (s_pool_mode && estFeePct > 0.0) { + char feeNote[160]; + snprintf(feeNote, sizeof(feeNote), TR("tt_mining_est_daily_fee_fmt"), estFeePct); + snprintf(tip, sizeof(tip), "%s\n%s", TR("tt_mining_est_daily"), feeNote); + } else if (s_pool_mode && estFeePct < 0.0) { // fee unknown (synthetic/unmatched pool) + snprintf(tip, sizeof(tip), "%s\n%s", TR("tt_mining_est_daily"), TR("tt_mining_est_daily_nofee")); + } else { // solo, or a known 0%-fee pool (net == gross) + snprintf(tip, sizeof(tip), "%s", TR("tt_mining_est_daily")); + } + material::Tooltip("%s", tip); + } + } } // === Separator between earnings & details === diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 150f699..1434aa0 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -2184,6 +2184,11 @@ void I18n::loadBuiltinEnglish() strings_["mining_difficulty_copied"] = "Difficulty copied"; strings_["mining_est_block"] = "Est. Block"; strings_["mining_est_daily"] = "Est. Daily"; + strings_["mining_est_daily_sub"] = "avg"; + strings_["mining_est_daily_sub_net"] = "avg, net"; + strings_["tt_mining_est_daily"] = "A long-run statistical average for your current hashrate share of the network. Real mining rewards arrive at random (Poisson) - often nothing for long stretches, then a lump - so this is NOT a steady daily income."; + strings_["tt_mining_est_daily_fee_fmt"] = "Shown after the %.1f%% pool fee."; + strings_["tt_mining_est_daily_nofee"] = "Shown before the pool fee (fee unknown for this pool)."; strings_["mining_filter_all"] = "All"; strings_["mining_filter_tip_all"] = "Show all earnings"; strings_["mining_filter_tip_pool"] = "Show pool earnings only";