From dbba8b4376c9327a183c257aa7e3029920b70777 Mon Sep 17 00:00:00 2001 From: ZHU MINCHAO <48154958+MinchaoZhu@users.noreply.github.com> Date: Tue, 3 Nov 2026 23:29:54 +0800 Subject: [PATCH] Chain drama: fee tiers, congestion, mempool drops, address poisoning, clipboard hijack, phishing; selected-button style Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 7 + art/sprites.py | 13 ++ src/Decentralized.Domain/ChainAddress.cs | 26 +++ src/Decentralized.Domain/Tuning.cs | 12 ++ src/Decentralized.Domain/TxRules.cs | 43 +++++ src/Decentralized.Domain/Wallet.cs | 3 + src/Decentralized/DealerStock.cs | 13 ++ .../ModFiles/Localization/en.csv | 22 ++- .../ModFiles/Localization/zh.csv | 22 ++- .../ModFiles/Textures/Items/ui_hw_sel.png | Bin 0 -> 112 bytes src/Decentralized/Night.cs | 28 ++++ src/Decentralized/Session.cs | 11 ++ src/Decentralized/TerminalWindow.cs | 4 +- src/Decentralized/WalletWindow.cs | 152 ++++++++++++++++-- tests/Decentralized.Domain.Tests/Program.cs | 21 +++ 15 files changed, 357 insertions(+), 20 deletions(-) create mode 100644 src/Decentralized.Domain/TxRules.cs create mode 100644 src/Decentralized/ModFiles/Textures/Items/ui_hw_sel.png diff --git a/CHANGELOG.md b/CHANGELOG.md index a61cb19..851a004 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,13 @@ - PLEDGER 主页余额改成三列(币种 | 数量 | 估值)右对齐,五种币全部显示(含 0,冻结的另注);点一行选中,整行反色高亮,发送时默认用选中的币。 - 钱包页:每个钱包可以改名(留空恢复默认,最长 16 字),名字在终端和 PLEDGER 上都用;非主钱包可以销毁(点两次确认,钱包里还有币时不让销毁)。钱包编号固定,销毁别的钱包后名字不会错位。 - 游戏内验证(这一轮):复制钱包 3 地址后在主钱包发送页粘贴,地址正确;选中 CRT 行高亮,进发送页默认 CRT;向钱包 3 转 1 CRT 后点销毁被拦下;钱包 2 二次确认后销毁,钱包 3 仍叫"钱包 3";钱包 2 改名为 cold,终端与 PLEDGER 都显示新名字;三列余额对齐。PLEDGER 上名字被标题样式转成大写,已改成普通粗体(编译通过,没重进游戏看)。没有验证:游戏输入框里直接 Ctrl+V。 +- 转账手续费档位(核对页选 慢 / 普通 / 快):0.5 / 1.5 / 4 信用点等值,用转出的币扣(DEX 签名用付出的币);需要的确认数随金额:3 次,≥ 2000 时 6 次,≥ 10000 时 12 次,动画随之变长。 +- 网络拥堵:每晚 12% 开始,持续 1–2 天,上报纸;拥堵时手续费 ×4,普通档确认 ×2 慢、慢档 ×3.5 慢,慢档 30% 被挤出内存池(未成交,手续费照扣,账单留着可以重付)。PLEDGER 主页和核对页显示"网络拥堵"。 +- 地址投毒:每笔转给外部地址的转账有 35% 被盯上,1–2 天后钱包收到一笔来自相似地址的 0 币转入(前 3 位和末 2 位校验位一样,缩写完全相同)。PLEDGER 发送页新增"最近"列表(← 转入 / → 转出,缩写地址),点一下填入地址,投毒地址混在里面;核对页对有往来的地址显示"有往来记录",投毒地址也会显示。 +- 剪贴板木马:被投毒的钱包上点"粘贴",30% 把地址换成相似地址(和一次转空并存)。 +- 钓鱼公告:每晚 5%,报纸上"Poinbase 充值地址迁移"或黑市论坛上"混币器新地址",给的是没人用的地址,转进去就没了。 +- PLEDGER 屏上的选择按钮(币种、手续费档位):选中的一个白底黑框黑字,四角带取景框角标,不再用方括号。 +- 游戏内验证(第三批):手续费 1.5 CRT 扣除;拥堵时核对页手续费 ×4、慢档提示;最近列表出现投毒的"← 0x123…99 0 PSC",点它进核对页显示真实地址 0x12342499 与"有往来记录",签名后币转进投毒地址;慢档连续 13 次里 4 次"未成交",手续费照扣;论坛出现钓鱼帖;选中样式截图看过。没有在游戏里看到:报纸上的拥堵 / 钓鱼新闻(F9 面板那次没打开,代码走的是已用过的新闻接口)、剪贴板木马(只有被投毒的钱包触发,没专门测)、大额转账的 6 / 12 次确认动画(单元测试覆盖确认数)。 - 存档不兼容旧版:链上的币归到新建的主钱包,Poinbase 账户从 0 开始。 - 游戏内验证(多钱包):两件硬件钱包(一正一投毒)作用于终端,钱包页出现钱包 2、钱包 3,标记看不出来;同时打开两台 PLEDGER;主钱包向钱包 3 转 100 PSC,核对页显示"自己的钱包:钱包 3",污点 5000 按 10000/16000 带走 3125;过一夜钱包 3 被转空(约 9393),晨间日志有记录;银行存入 500 进 Poinbase,提 200 CRC 到主钱包,再从主钱包向充值地址转 100 CRC,Poinbase 300 → 400,带过去污点 31。商城平台现货出现硬件钱包(158)。没有在游戏里验证:用鼠标拖拽(用探查通道执行了同一个"物品作用于物品"接口)、杰克逊柜台上的钱包和它的标价、Poinbase 空账户和"账户污点比例"两处改字(已编译,没重进游戏看)。 diff --git a/art/sprites.py b/art/sprites.py index 2800813..6a6521b 100644 --- a/art/sprites.py +++ b/art/sprites.py @@ -681,10 +681,23 @@ def rounded(size, r, fill, light=None, dark=None): return c +def hw_selected(): + """PLEDGER 屏上选中的按钮(16×16,九宫格边 5):墨水屏白底、1 像素黑框,四角向外加粗的取景框角标(准星)。""" + c = Canvas(16, 16) + c.rect(0, 0, 15, 15, 'P') + for i in range(16): + c.put(i, 1, 'n'); c.put(i, 14, 'n'); c.put(1, i, 'n'); c.put(14, i, 'n') + for (x, y, dx, dy) in ((0, 0, 1, 1), (15, 0, -1, 1), (0, 15, 1, -1), (15, 15, -1, -1)): + for k in range(4): + c.put(x + dx * k, y, 'n'); c.put(x, y + dy * k, 'n') + return c + + UI_ICONS = {'ui_bank': ui_bank, 'ui_poinbase': ui_poinbase, 'ui_web3': ui_web3} UI_ICONS['ui_hw_shell'] = lambda: rounded(32, 7, 'K', 'E', 'z') UI_ICONS['ui_hw_bezel'] = lambda: rounded(16, 4, 'n') UI_ICONS['ui_hw_paper'] = lambda: rounded(12, 2, 'P') +UI_ICONS['ui_hw_sel'] = hw_selected UI_ICONS['ui_hw_ok'] = lambda: hw_button('ok') UI_ICONS['ui_hw_no'] = lambda: hw_button('no') diff --git a/src/Decentralized.Domain/ChainAddress.cs b/src/Decentralized.Domain/ChainAddress.cs index e55f79a..3e23369 100644 --- a/src/Decentralized.Domain/ChainAddress.cs +++ b/src/Decentralized.Domain/ChainAddress.cs @@ -41,5 +41,31 @@ namespace Decentralized.Domain } public static bool Same(string a, string b) => Normalize(a) == Normalize(b); + + /// 缩写(钱包列表里的样子):0x + 主体前 3 位 + … + 校验位,例如 0x4b9…17。 + public static string Short(string a) + { + a = Normalize(a); + return WellFormed(a) ? a.Substring(0, 5) + "…" + a.Substring(2 + BodyLength) : a; + } + + /// + /// 相似地址(地址投毒、剪贴板木马用):主体前 3 位和校验位都和原地址一样,缩写看起来完全相同,中间几位不同;本身是合法地址, + /// 币转过去就进了骗子手里。找不到校验位相同的就只保留前 3 位。 + /// + public static string Lookalike(string a, Rng rng) + { + a = Normalize(a); + if (!Valid(a)) return Make(rng.NextUInt()); + string prefix = a.Substring(2, 3), check = a.Substring(2 + BodyLength), body0 = a.Substring(2, BodyLength); + for (int i = 0; i < 20000; i++) + { + string body = prefix + (rng.NextUInt() & 0xFFF).ToString("x3"); + if (body != body0 && Check(body) == check) return "0x" + body + check; + } + string fallback; + do fallback = prefix + (rng.NextUInt() & 0xFFF).ToString("x3"); while (fallback == body0); + return "0x" + fallback + Check(fallback); + } } } diff --git a/src/Decentralized.Domain/Tuning.cs b/src/Decentralized.Domain/Tuning.cs index e0ee2fc..4d85f6e 100644 --- a/src/Decentralized.Domain/Tuning.cs +++ b/src/Decentralized.Domain/Tuning.cs @@ -142,6 +142,18 @@ namespace Decentralized.Domain /// 被投毒的钱包:币的总价值达到 DrainMin 起,每晚有概率被转空;价值越高越快:期望天数从门槛处的 SlowDays(7 天)按价值线性降到 DrainFull 处的 1 天,每晚概率 = 1 / 期望天数。 public const double WalletDrainMin = 3000, WalletDrainFull = 20000, WalletDrainSlowDays = 7; + /// 转账手续费(信用点等值,按转出的币扣):慢 / 普通 / 快;拥堵时乘以倍数。 + public const double TxFeeSlow = 0.5, TxFeeNormal = 1.5, TxFeeFast = 4, TxCongestedFeeMult = 4; + /// 网络拥堵:每晚开始拥堵的概率、持续天数上限;拥堵时普通档、慢档的确认间隔倍数,慢档被挤出内存池的概率。 + public const double TxCongestionChance = 0.12, TxCongestedNormalSlow = 2, TxCongestedSlowSlow = 3.5, TxSlowDrop = 0.3; + public const int TxCongestionDaysMax = 2; + /// 确认数:默认 3;金额(信用点)≥ MidValue 时 6,≥ BigValue 时 12。 + public const int TxConfirmBase = 3, TxConfirmMid = 6, TxConfirmBig = 12; + public const double TxConfirmMidValue = 2000, TxConfirmBigValue = 10000; + /// 地址投毒:每笔转出后被盯上的概率,0 币转入在 1–2 天后出现。剪贴板木马(被投毒的钱包):粘贴时换成相似地址的概率。钓鱼公告:每晚出现的概率。 + public const double TxPoisonChance = 0.35, TxHijackChance = 0.3, TxPhishChance = 0.05; + public const int TxPoisonDelayMax = 2; + /// 主交易对(信用点一侧)的深度随天数增长。 public static double PoolDepth(int day) => PoolDepth0 * (1 + day / 60.0); } diff --git a/src/Decentralized.Domain/TxRules.cs b/src/Decentralized.Domain/TxRules.cs new file mode 100644 index 0000000..cf6a412 --- /dev/null +++ b/src/Decentralized.Domain/TxRules.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; + +namespace Decentralized.Domain +{ + /// 钱包里的一条转账记录:对方地址、币种、数量、哪天、转入还是转出。地址投毒的 0 币转入也记在这里。 + public sealed class TxRecord + { + public string Addr, Asset; + public double Amount; + public int Day; + public bool In; + } + + /// 手续费档位:慢最便宜,网络拥堵时可能被挤出内存池;快最贵,拥堵时也照常确认。 + public enum FeeTier { Slow, Normal, Fast } + + /// 链上转账的规则:手续费、需要的确认数、拥堵时慢档被丢弃的概率。 + public static class TxRules + { + /// 手续费(信用点等值,按转出的币扣):档位基础价 × 拥堵倍数。 + public static double FeeCredits(FeeTier t, bool congested) => + (t == FeeTier.Slow ? Tuning.TxFeeSlow : t == FeeTier.Normal ? Tuning.TxFeeNormal : Tuning.TxFeeFast) * (congested ? Tuning.TxCongestedFeeMult : 1); + + /// 需要的确认数:金额越大越多。 + public static int Confirmations(double valueCredits) => + valueCredits >= Tuning.TxConfirmBigValue ? Tuning.TxConfirmBig : valueCredits >= Tuning.TxConfirmMidValue ? Tuning.TxConfirmMid : Tuning.TxConfirmBase; + + /// 每次确认的间隔倍数:拥堵时慢档、普通档变慢。 + public static double Slowdown(FeeTier t, bool congested) => !congested ? 1 : t == FeeTier.Fast ? 1 : t == FeeTier.Normal ? Tuning.TxCongestedNormalSlow : Tuning.TxCongestedSlowSlow; + + /// 拥堵时慢档交易被挤出内存池的概率(交易作废,手续费照扣)。 + public static double DropChance(FeeTier t, bool congested) => congested && t == FeeTier.Slow ? Tuning.TxSlowDrop : 0; + + /// 转账记录最多保留几条。 + public const int HistoryMax = 20; + + public static void Record(List history, TxRecord r) + { + history.Insert(0, r); + if (history.Count > HistoryMax) history.RemoveRange(HistoryMax, history.Count - HistoryMax); + } + } +} diff --git a/src/Decentralized.Domain/Wallet.cs b/src/Decentralized.Domain/Wallet.cs index 51e8259..dd235cb 100644 --- a/src/Decentralized.Domain/Wallet.cs +++ b/src/Decentralized.Domain/Wallet.cs @@ -22,6 +22,9 @@ namespace Decentralized.Domain public string Name; /// 玩家起的名字;空时用默认名。 public string Label; + /// 转账记录(最新的在前),PLEDGER 发送页的"最近"列表从这里取。 + public List History = new List(); + /// 硬件钱包被动过手脚(供应链投毒):别人手里有助记词。玩家看不到。 public bool Poisoned; diff --git a/src/Decentralized/DealerStock.cs b/src/Decentralized/DealerStock.cs index ef5f9bb..91013af 100644 --- a/src/Decentralized/DealerStock.cs +++ b/src/Decentralized/DealerStock.cs @@ -69,6 +69,19 @@ namespace Decentralized Log.Info($"买入测试:卡 #{item.uniqueId} 买前报价 {before},买下后价值 {item.unitValue},真实 {CardValue.True(Items.ToUnit(item))},说法 {(Items.GetClaim(item)?.Encode() ?? "无")}"); })); ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Hardware wallets (test)", () => { Items.Give(Items.NewHwWallet(false)); Items.Give(Items.NewHwWallet(true)); Log.Info("测试:给了一个正品、一个被投毒的硬件钱包"); }); + ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Chain drama (test)", () => Log.Guard("链上事件测试", () => + { + // 今天拥堵;当前钱包最近转出过的地址立刻收到相似地址的 0 币转入;报纸和论坛各来一条钓鱼 + var d = Session.Data; + int day = StoreStation.GetDayCounter(); + d.CongestedFrom = day; d.CongestedUntil = day + 1; + var last = d.Hot.History.FirstOrDefault(r => !r.In); + if (last != null) TxRules.Record(d.Hot.History, new TxRecord { Addr = ChainAddress.Lookalike(last.Addr, d.Rng), Asset = Assets.PSC, Amount = 0, Day = day, In = true }); + string fake = ChainAddress.Make(d.Rng.NextUInt()); + News.Show("pd_news_phish_test_" + day, L.T("news.phish.title"), L.T("news.phish.desc", fake), 2); + d.Black.Forum.Add("phish:" + ChainAddress.Make(d.Rng.NextUInt())); + Log.Info($"测试:拥堵到第 {d.CongestedUntil} 天;投毒 {(last != null ? last.Addr : "无转出记录")};钓鱼 {fake}"); + })); ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Dr. Jackson stock (test)", () => { EmporiumEntry.Instance.ClearFrontInv(); diff --git a/src/Decentralized/ModFiles/Localization/en.csv b/src/Decentralized/ModFiles/Localization/en.csv index e9057dc..0a4d31b 100644 --- a/src/Decentralized/ModFiles/Localization/en.csv +++ b/src/Decentralized/ModFiles/Localization/en.csv @@ -268,7 +268,7 @@ black.kind.Contraband.3,Contraband (high) black.kind.Tech,High-tech (markup) nightlog.bm.ready,"Black market: {0} arrived; a courier will come by once you open." item.web3guide.name,Web3 Handbook -item.web3guide.desc,"Bank: depositing turns credits into CRC in your Poinbase account; withdrawing turns CRC back into credits and goes through compliance screening, which looks at the Poinbase account's taint. Trading: buys are paid in the quote coin, sells in the traded coin. Mixer: pick a coin, amount and rounds; each round takes a fee and lowers taint, rounds equal days of waiting, and the coins come back to the same address; the more you deposit, the worse the cleaning. Hardware wallets: on-chain coins sit in PLEDGER hardware wallets, each with its own balances and taint; open and connect them on the terminal's Wallets page. The Web3 page's trading, black market and mixer use the connected wallet, and coin-paying customers and mining rewards go into it too. Payments (black market, mixer, Ponero desk, listing fees) start as an invoice on the terminal; type the payee address on the wallet and hold ✓ to sign. The last two characters of an address are a checksum, so a one-character typo is usually caught; once in a while it happens to be valid and the coins go to an address nobody uses. DEX swaps also need a signature on the wallet. Poinbase is a custodial account and can withdraw straight to the connected wallet; depositing from a wallet means sending to Poinbase's deposit address, which takes only CRC and PSC. More hardware wallets are sold by Dr. Jackson; drag one onto the trading terminal to add it, up to 5. The black market has cheaper ones. A wallet you no longer want can be destroyed on the Wallets page once its coins are moved out. Black market: coin only, no CRC, 30% above normal channels, with extra markup on high-tech goods and GPUs; the coin you pay leaves its share of taint with the seller and the goods carry no stolen tag; GPUs may be fake, inspect them yourself. Orders are cheaper than spot but take days and may be intercepted; merchants sell by type at their own price factor, 5+ items get 5% off and 10+ get 10% off, and a bulk merchant appears at random with the lowest prices but six days of waiting; escrow costs 5%, is released on delivery and refunded if the seller runs; advance release takes another 10% off, but a scammer keeps the money and only sellers with 30+ trades accept; the platform itself can rug, be taken over by enforcement, or a seller can be arrested, after which your orders get traced and crime logged; forum rumors can be warnings. An intercepted parcel sometimes turns into enforcement at your door: signing logs the crime on the spot and seizes the goods, refusing only costs the payment. Selling: on the black market's Sell tab, list contraband, high-tech goods and GPUs from storage at 0.6x to 1.8x the reference price. The higher the multiplier, the slower the sale (below 0.9x about a day, 1.0x about two, 1.8x about fourteen), and the likelier and larger the taint on the payout, which also grows with how much you sold in the last 7 days; at most 10 listings at a time, 5% listing fee, repricing a listing costs 2% and the sale speed follows the new multiplier, 8% commission, payout held 3 days before reaching your wallet; a fake card may be spotted and returned, hurting your rating; if the platform rugs, listed goods and unreleased payouts are gone. Anonymous-coin swap (Web3 trade page, needs black-market standing): Ponero is a privacy coin and swapping in wipes the taint on that value; 6% fee each way with a daily limit; coin swapped out of Ponero has no origin and cash-out screening gets stricter." +item.web3guide.desc,"Bank: depositing turns credits into CRC in your Poinbase account; withdrawing turns CRC back into credits and goes through compliance screening, which looks at the Poinbase account's taint. Trading: buys are paid in the quote coin, sells in the traded coin. Mixer: pick a coin, amount and rounds; each round takes a fee and lowers taint, rounds equal days of waiting, and the coins come back to the same address; the more you deposit, the worse the cleaning. Hardware wallets: on-chain coins sit in PLEDGER hardware wallets, each with its own balances and taint; open and connect them on the terminal's Wallets page. The Web3 page's trading, black market and mixer use the connected wallet, and coin-paying customers and mining rewards go into it too. Payments (black market, mixer, Ponero desk, listing fees) start as an invoice on the terminal; type the payee address on the wallet and hold ✓ to sign. The last two characters of an address are a checksum, so a one-character typo is usually caught; once in a while it happens to be valid and the coins go to an address nobody uses. DEX swaps also need a signature on the wallet. Poinbase is a custodial account and can withdraw straight to the connected wallet; depositing from a wallet means sending to Poinbase's deposit address, which takes only CRC and PSC. More hardware wallets are sold by Dr. Jackson; drag one onto the trading terminal to add it, up to 5. The black market has cheaper ones. A wallet you no longer want can be destroyed on the Wallets page once its coins are moved out. Transfers pay a fee: slow, normal or fast; when the network is congested fees rise and slow transactions may be dropped from the mempool. Check the full address before sending. Black market: coin only, no CRC, 30% above normal channels, with extra markup on high-tech goods and GPUs; the coin you pay leaves its share of taint with the seller and the goods carry no stolen tag; GPUs may be fake, inspect them yourself. Orders are cheaper than spot but take days and may be intercepted; merchants sell by type at their own price factor, 5+ items get 5% off and 10+ get 10% off, and a bulk merchant appears at random with the lowest prices but six days of waiting; escrow costs 5%, is released on delivery and refunded if the seller runs; advance release takes another 10% off, but a scammer keeps the money and only sellers with 30+ trades accept; the platform itself can rug, be taken over by enforcement, or a seller can be arrested, after which your orders get traced and crime logged; forum rumors can be warnings. An intercepted parcel sometimes turns into enforcement at your door: signing logs the crime on the spot and seizes the goods, refusing only costs the payment. Selling: on the black market's Sell tab, list contraband, high-tech goods and GPUs from storage at 0.6x to 1.8x the reference price. The higher the multiplier, the slower the sale (below 0.9x about a day, 1.0x about two, 1.8x about fourteen), and the likelier and larger the taint on the payout, which also grows with how much you sold in the last 7 days; at most 10 listings at a time, 5% listing fee, repricing a listing costs 2% and the sale speed follows the new multiplier, 8% commission, payout held 3 days before reaching your wallet; a fake card may be spotted and returned, hurting your rating; if the platform rugs, listed goods and unreleased payouts are gone. Anonymous-coin swap (Web3 trade page, needs black-market standing): Ponero is a privacy coin and swapping in wipes the taint on that value; 6% fee each way with a daily limit; coin swapped out of Ponero has no origin and cash-out screening gets stricter." courier.odd1,...Package is here. courier.odd2,"(His hand stays in his pocket.) Sign here." courier.sign,Sign @@ -427,3 +427,23 @@ hw.balance.value,"≈ {0}" wallets.rename,Rename wallets.rename.h,"New name for {0}" wallets.rename.ok,OK +hw.fee.slow,Slow +hw.fee.normal,Normal +hw.fee.fast,Fast +hw.fee,"Fee {0} {1} (about {2} credits)" +hw.confs,"{0} confirmations needed" +hw.congested,Network congested +hw.congested.slow,Network congested: slow fee may be dropped from the mempool +hw.recent,Recent +hw.recent.out,"→ {0} {1} {2}" +hw.recent.in,"← {0} {1} {2}" +hw.review.history,Address in your history +hw.tx.stuck,Queued in the mempool… +hw.tx.dropped,Dropped from the mempool. Nothing was sent; the fee is still charged. +hw.dropped.h,Not sent +hw.err.fee,Not enough to cover the fee. +news.congest.title,Chain congestion +news.congest.desc,An airdrop rush has clogged the chain. Fees are several times higher and low-fee transactions may be dropped from the mempool. +news.phish.title,Poinbase notice: deposit address migration +news.phish.desc,"Poinbase has upgraded its wallet system. The old deposit address will be retired; please send assets to the new address {0}." +black.forum.phish,"Someone posted the mixer's new address: {0}, saying the old one is being watched." diff --git a/src/Decentralized/ModFiles/Localization/zh.csv b/src/Decentralized/ModFiles/Localization/zh.csv index e09b794..6496e1f 100644 --- a/src/Decentralized/ModFiles/Localization/zh.csv +++ b/src/Decentralized/ModFiles/Localization/zh.csv @@ -267,7 +267,7 @@ black.kind.Contraband.3,违禁品(高) black.kind.Tech,高科技物品(加价) nightlog.bm.ready,"黑市:{0} 到货,开店后会有快递员上门。" item.web3guide.name,Web3 手册 -item.web3guide.desc,"银行:存入把信用点换成 Poinbase 账户里的 CRC;提取把 CRC 换回信用点,要过合规审查,审查看 Poinbase 账户的污点。交易:买入按计价币的数量支付,卖出按所交易币的数量卖出。混币器:选币种、填数量和轮数,每轮扣手续费并降低污点,轮数即等待天数,到期提回同一个地址;存入越多,清洗越差。硬件钱包:链上的币放在 PLEDGER 硬件钱包里,每个钱包的余额和污点各算各的;在终端的钱包页打开、连接。Web3 页的交易、黑市、混币器用已连接的钱包,用币付款的顾客和挖矿收益也进它。付款(黑市、混币器、Ponero 兑换处、上架费)先在终端开账单,再在钱包里手输收款地址、按住 ✓ 签名;地址末尾两位是校验位,输错一位多半会被拦下,偶尔碰巧合法,币就转进了没人用的地址。DEX 兑换也要在钱包上签名。Poinbase 是托管账户,可以直接提到已连接的钱包;从钱包充值要转到 Poinbase 的充值地址,只收 CRC 和 PSC。更多硬件钱包向杰克逊博士购买,拖到交易终端上添加,最多 5 个;黑市上也有更便宜的。不要的钱包可以在钱包页销毁,要先把币转走。黑市:只收币,不收 CRC,价格比正常渠道贵 30%,高科技物品和显卡另有加价;付出去的币带着按比例的污点留在卖家那边,货不带赃物标签;显卡不保证真假,自己鉴定。订单比现货便宜,要等几天,路上可能被截获;商家按类型卖货,价格系数各不相同,一次订 5 件以上 95 折、10 件以上 9 折,大宗商家随机出现,价格最低但要等六天;托管多付 5% 托管费,收货后放款,卖家跑路时退款;提前放款再打九折,卖家若是骗子钱就没了,只有成交 30 单以上的卖家接受;平台自己也可能跑路,也可能被执法接管,或者卖家落网,之后你的订单会被追查、记罪证;论坛上的传闻可以当预兆。被截获的包裹有时会变成执法人员上门:签收当场记罪证并查封,拒签只损失货款。出售:在黑市的卖货页把储藏区里的违禁品、高科技物品和显卡上架,标价是参考价的 0.6 到 1.8 倍。倍率越高成交越慢(0.9 以下约一天,1.0 约两天,1.8 约十四天),货款带污点的可能性和比例也越高,近 7 天出售的量越大,污点越重;同时最多上架 10 件,上架收 5% 上架费,上架后可以改价(收 2% 改价费,标价按参考价重算,成交的快慢跟着新倍率走),成交扣 8% 抽成,货款托管 3 天后进钱包;卖假卡可能被买家看穿而退货,拉低好评;平台跑路时上架的货和未放款的货款都没了。匿名币兑换(Web3 交易页,要黑市声望):Ponero 是隐私币,兑入时这部分价值的污点被清除;兑入、兑出各收 6% 手续费,每天有额度;从 Ponero 兑出的币来路不明,出金审查会加重。" +item.web3guide.desc,"银行:存入把信用点换成 Poinbase 账户里的 CRC;提取把 CRC 换回信用点,要过合规审查,审查看 Poinbase 账户的污点。交易:买入按计价币的数量支付,卖出按所交易币的数量卖出。混币器:选币种、填数量和轮数,每轮扣手续费并降低污点,轮数即等待天数,到期提回同一个地址;存入越多,清洗越差。硬件钱包:链上的币放在 PLEDGER 硬件钱包里,每个钱包的余额和污点各算各的;在终端的钱包页打开、连接。Web3 页的交易、黑市、混币器用已连接的钱包,用币付款的顾客和挖矿收益也进它。付款(黑市、混币器、Ponero 兑换处、上架费)先在终端开账单,再在钱包里手输收款地址、按住 ✓ 签名;地址末尾两位是校验位,输错一位多半会被拦下,偶尔碰巧合法,币就转进了没人用的地址。DEX 兑换也要在钱包上签名。Poinbase 是托管账户,可以直接提到已连接的钱包;从钱包充值要转到 Poinbase 的充值地址,只收 CRC 和 PSC。更多硬件钱包向杰克逊博士购买,拖到交易终端上添加,最多 5 个;黑市上也有更便宜的。不要的钱包可以在钱包页销毁,要先把币转走。转账要付手续费,分慢、普通、快三档;网络拥堵时手续费变贵,慢档可能被挤出内存池。转账前核对完整地址。黑市:只收币,不收 CRC,价格比正常渠道贵 30%,高科技物品和显卡另有加价;付出去的币带着按比例的污点留在卖家那边,货不带赃物标签;显卡不保证真假,自己鉴定。订单比现货便宜,要等几天,路上可能被截获;商家按类型卖货,价格系数各不相同,一次订 5 件以上 95 折、10 件以上 9 折,大宗商家随机出现,价格最低但要等六天;托管多付 5% 托管费,收货后放款,卖家跑路时退款;提前放款再打九折,卖家若是骗子钱就没了,只有成交 30 单以上的卖家接受;平台自己也可能跑路,也可能被执法接管,或者卖家落网,之后你的订单会被追查、记罪证;论坛上的传闻可以当预兆。被截获的包裹有时会变成执法人员上门:签收当场记罪证并查封,拒签只损失货款。出售:在黑市的卖货页把储藏区里的违禁品、高科技物品和显卡上架,标价是参考价的 0.6 到 1.8 倍。倍率越高成交越慢(0.9 以下约一天,1.0 约两天,1.8 约十四天),货款带污点的可能性和比例也越高,近 7 天出售的量越大,污点越重;同时最多上架 10 件,上架收 5% 上架费,上架后可以改价(收 2% 改价费,标价按参考价重算,成交的快慢跟着新倍率走),成交扣 8% 抽成,货款托管 3 天后进钱包;卖假卡可能被买家看穿而退货,拉低好评;平台跑路时上架的货和未放款的货款都没了。匿名币兑换(Web3 交易页,要黑市声望):Ponero 是隐私币,兑入时这部分价值的污点被清除;兑入、兑出各收 6% 手续费,每天有额度;从 Ponero 兑出的币来路不明,出金审查会加重。" courier.odd1,……东西到了。 courier.odd2,"(他的手一直放在口袋里)在这儿签个字。" courier.sign,签收 @@ -426,3 +426,23 @@ hw.balance.value,"≈ {0}" wallets.rename,改名 wallets.rename.h,"{0} 新名字" wallets.rename.ok,确定 +hw.fee.slow,慢 +hw.fee.normal,普通 +hw.fee.fast,快 +hw.fee,"手续费 {0} {1}(约 {2} 信用点)" +hw.confs,"需要 {0} 次确认" +hw.congested,网络拥堵 +hw.congested.slow,网络拥堵:慢档可能被挤出内存池 +hw.recent,最近 +hw.recent.out,"→ {0} {1} {2}" +hw.recent.in,"← {0} {1} {2}" +hw.review.history,有往来记录 +hw.tx.stuck,内存池排队中… +hw.tx.dropped,交易被挤出内存池,没有成交,手续费照扣。 +hw.dropped.h,未成交 +hw.err.fee,余额不够付手续费。 +news.congest.title,链上拥堵 +news.congest.desc,一批空投抢跑让链上排起长队,手续费翻了几倍,低手续费的交易可能被挤出内存池。 +news.phish.title,Poinbase 公告:充值地址迁移 +news.phish.desc,"Poinbase 完成钱包系统升级,旧充值地址即将停用,请将资产转入新地址 {0}。" +black.forum.phish,"有人贴了混币器的新地址:{0},说旧的被盯上了。" diff --git a/src/Decentralized/ModFiles/Textures/Items/ui_hw_sel.png b/src/Decentralized/ModFiles/Textures/Items/ui_hw_sel.png new file mode 100644 index 0000000000000000000000000000000000000000..013aead0cc47dcb1fb0411fb0864a3a944c86d8a GIT binary patch literal 112 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`=AJH&Ar-fhCrl3e+uxIaXIF#x z;??O6;tHb10@=oT(*yDtdYt链上的夜间事件:网络拥堵、地址投毒的 0 币转入、钓鱼公告。 + private static void ChainNight(SaveData d, int day) + { + if (day >= d.CongestedUntil && d.Rng.Chance(Tuning.TxCongestionChance)) + { + d.CongestedFrom = day + 1; + d.CongestedUntil = day + d.Rng.Range(1, Tuning.TxCongestionDaysMax); + News.Show("pd_news_congest_" + day, L.T("news.congest.title"), L.T("news.congest.desc"), d.CongestedUntil - day); + } + foreach (var p in d.PoisonPlans.Where(x => x.Day <= day + 1).ToList()) + { + d.PoisonPlans.Remove(p); + var w = d.Own(p.Wallet); + if (w == null) continue; + string fake = ChainAddress.Lookalike(p.Target, d.Rng); + TxRules.Record(w.History, new TxRecord { Addr = fake, Asset = Assets.PSC, Amount = 0, Day = day + 1, In = true }); + Log.Info($"地址投毒:{p.Target} 的相似地址 {fake} 向 {w.Address} 转入 0 币"); + } + if (d.Rng.Chance(Tuning.TxPhishChance)) + { + string fake = ChainAddress.Make(d.Rng.NextUInt()); + if (Unlock.BlackUnlocked && d.Rng.Chance(0.5)) d.Black.Forum.Add("phish:" + fake); + else News.Show("pd_news_phish_" + day, L.T("news.phish.title"), L.T("news.phish.desc", fake), 2); + Log.Info("钓鱼公告:" + fake); + } + } + private static void Settle() { var d = Session.Data; @@ -158,6 +185,7 @@ namespace Decentralized Log.Info($"投毒钱包 {w.Address} 被转空:{v:0}"); ps.AddNightLog(L.T("nightlog.drained", WalletWindow.NameOf(w), Fmt.Credits(v)), "cc7755"); } + ChainNight(d, day); foreach (var name in report.Burnt) ps.AddNightLog(L.T("nightlog.burnt", name), "cc7755"); int unpowered = report.Rigs - report.PoweredRigs; if (unpowered > 0 && units.Count + unpowered > 0) ps.AddNightLog(L.T("nightlog.unpowered", unpowered), "cc7755"); diff --git a/src/Decentralized/Session.cs b/src/Decentralized/Session.cs index 231b799..9adac18 100644 --- a/src/Decentralized/Session.cs +++ b/src/Decentralized/Session.cs @@ -14,6 +14,13 @@ namespace Decentralized public List Burnt = new List(); } + /// 地址投毒的计划:某个钱包转给某地址后,到了这一天收到一笔来自相似地址的 0 币转入。 + public sealed class PoisonPlan + { + public string Wallet, Target; + public int Day; + } + /// 一局的 mod 状态。整体序列化成 JSON 存进游戏的 mod 存档数据;显卡的健康度等存在显卡物品的标签上。 public sealed class SaveData { @@ -63,6 +70,10 @@ namespace Decentralized if (!Addresses.TryGetValue(key, out var a) || !ChainAddress.Valid(a)) { a = ChainAddress.Make(Rng.NextUInt()); Addresses[key] = a; } return a; } + /// 网络拥堵的起止天(含)。 + public int CongestedFrom = -1, CongestedUntil = -1; + public List PoisonPlans = new List(); + /// 链上的区块高度:每签一笔交易往上走几块。 public long Block = 1204331; /// 当天用币付款的成交额(信用点),夜间清零。 diff --git a/src/Decentralized/TerminalWindow.cs b/src/Decentralized/TerminalWindow.cs index 1adf504..52eb007 100644 --- a/src/Decentralized/TerminalWindow.cs +++ b/src/Decentralized/TerminalWindow.cs @@ -497,7 +497,7 @@ namespace Decentralized b.AddHeader(L.T("black.forum.h")); if (d.Black.Forum.Count == 0) b.AddLabel(L.T("black.forum.quiet")); foreach (var f in d.Black.Forum) - b.AddLabel(f.StartsWith("scam:") ? L.T("black.forum.scam", BlackDelivery.DisplayName(f.Substring(5))) : f.StartsWith("busted:") ? L.T("black.forum.busted", f.Substring(7)) : L.T("black.forum." + f)); + b.AddLabel(f.StartsWith("phish:") ? L.T("black.forum.phish", f.Substring(6)) : f.StartsWith("scam:") ? L.T("black.forum.scam", BlackDelivery.DisplayName(f.Substring(5))) : f.StartsWith("busted:") ? L.T("black.forum.busted", f.Substring(7)) : L.T("black.forum." + f)); } private static void PlatformShelf(CustomUIBuilder b, SaveData d) @@ -989,7 +989,7 @@ namespace Decentralized double quote = buy ? v / p.Price : v * p.Price; _message = L.T("hw.wait"); Open(); - WalletWindow.OpenForBill(new WalletWindow.Bill { Contract = true, Summary = L.T("hw.swap", Fmt.Num(v), pay, Fmt.Num(quote), get), Execute = pw => DoTrade(p, buy, v, pw) }); + WalletWindow.OpenForBill(new WalletWindow.Bill { Contract = true, Asset = pay, Amount = v, Summary = L.T("hw.swap", Fmt.Num(v), pay, Fmt.Num(quote), get), Execute = pw => DoTrade(p, buy, v, pw) }); } private static void DoTrade(Pool p, bool buy, double v, Wallet w) diff --git a/src/Decentralized/WalletWindow.cs b/src/Decentralized/WalletWindow.cs index df2433c..3a71884 100644 --- a/src/Decentralized/WalletWindow.cs +++ b/src/Decentralized/WalletWindow.cs @@ -20,7 +20,7 @@ namespace Decentralized public static class WalletWindow { private const string IdPrefix = "pd_wallet_"; - private const float WinW = 452f, WinH = 624f, ScreenW = 384f, ScreenH = 470f, Bezel = 6f; + private const float WinW = 452f, WinH = 694f, ScreenW = 384f, ScreenH = 540f, Bezel = 6f; private enum Screen { Home, Send, Review, Broadcast, Done } /// 终端开出的账单:转账(有收款地址)或合约调用(DEX 兑换,没有地址)。付清后用付款的钱包执行。 @@ -43,6 +43,13 @@ namespace Decentralized public long Block; public float Start; public bool Finished; + /// 手续费(币的数量)与扣费的币。 + public double Fee; + public string FeeAsset; + /// 各阶段结束的时刻(秒):签名、内存池、打包、每次确认;被挤出内存池时只有前两段再加一段等待。 + public float[] Times; + public int Confs; + public bool Dropped; } /// 一台硬件钱包的屏幕状态。 @@ -51,6 +58,7 @@ namespace Decentralized public string Address; public Screen Screen = Screen.Home; public string To = "", Amount = "", Asset = Assets.PSC, Note; + public FeeTier Tier = FeeTier.Normal; public Tx Tx; public string Id => IdPrefix + Address; public Wallet Wallet => Session.Data.Own(Address); @@ -59,8 +67,24 @@ namespace Decentralized private static readonly Dictionary Devices = new Dictionary(); private static readonly string[] SendAssets = { Assets.PSC, Assets.CRC, Assets.CRT, Assets.CRA, Assets.Ponero }; - // 出块动画各阶段的结束时刻(秒):签名、内存池、打包、三次确认 - private static readonly float[] Phases = { 0.8f, 2.0f, 3.0f, 3.7f, 4.4f, 5.1f }; + /// 网络拥堵:夜间结算随机开始,持续一两天,期间手续费变贵、慢档和普通档确认变慢。 + public static bool Congested + { + get { var d = Session.Data; int day = StoreStation.GetDayCounter(); return d != null && day >= d.CongestedFrom && day <= d.CongestedUntil; } + } + + /// 手续费折成币的数量。 + private static double FeeIn(string asset, FeeTier tier) { double p = Session.Price(asset); return p > 0 ? TxRules.FeeCredits(tier, Congested) / p : 0; } + + /// 出块动画的时间表:签名 0.8 秒,内存池 1.2 秒,打包 1 秒,每次确认 0.6 秒;拥堵时按档位放慢。 + private static float[] Timeline(int confs, double slow, bool dropped) + { + var t = new List { 0.8f, 0.8f + 1.2f * (float)slow }; + if (dropped) { t.Add(t[1] + 1.5f * (float)slow); return t.ToArray(); } + t.Add(t[1] + 1.0f); + for (int i = 0; i < confs; i++) t.Add(t[t.Count - 1] + 0.6f * (float)slow); + return t.ToArray(); + } private static readonly Color Ink = new Color(0.12f, 0.12f, 0.14f), Key = new Color(0.16f, 0.16f, 0.19f), KeyText = new Color(0.93f, 0.93f, 0.90f), @@ -124,7 +148,7 @@ namespace Decentralized WalletDriver.Ensure(); var b = CustomUIManager.Window(dev.Id, "PLEDGER"); if (b == null) return; - _rows.Clear(); _clear.Clear(); + _rows.Clear(); _clear.Clear(); _picked.Clear(); int idx = Session.Data.Wallets.IndexOf(dev.Wallet); b.SetSize(WinW, WinH).SetDraggable(true).SetCloseOnEscape(true).UseMachineryStyle(); b.SetPosition(new Vector2(600f + 28f * idx, -24f * idx)); @@ -188,8 +212,9 @@ namespace Decentralized foreach (var btn in screen.GetComponentsInChildren