Hardware wallet window: invoices paid from PLEDGER, checksummed addresses, DEX signing, block confirmation animation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Opus 5.5 committed 2026-11-03 22:29:42 +08:00
1 parent 5b7013f240
commit add61a6b5e
16 files changed
+768 -63

No files matched your search

+79 -49
View File
@@ -16,12 +16,9 @@ namespace Decentralized
private enum Page { Home, Bank, Poinbase, Web3 }
private static Page _page = Page.Home;
private sealed class PendingPay { public string Address, Summary; public int Total; public Action Execute; }
private static PendingPay _pay;
private sealed class EditPrice { public GameItem Item; public BmSale Sale; public string Id; public long Ref; }
private static EditPrice _edit;
private static string _editPrice = "";
private static string _addr = "";
private static string _amount = "";
private static string _message;
@@ -36,8 +33,9 @@ namespace Decentralized
{
var page = p;
string name = L.T("tab." + p.ToString().ToLowerInvariant());
b.AddButton(page == _page ? $"[{name}]" : name, () => { _page = page; _message = null; _pay = null; _edit = null; Open(); });
b.AddButton(page == _page ? $"[{name}]" : name, () => { _page = page; _message = null; _edit = null; Open(); });
}
b.AddButton(L.T("tab.wallet"), WalletWindow.Open);
b.End();
b.AddSeparator();
b.BeginScroll(540);
@@ -54,7 +52,14 @@ namespace Decentralized
b.Show();
}
private static void Go(Page p) { _page = p; _message = null; _pay = null; _edit = null; Open(); }
private static void Go(Page p) { _page = p; _message = null; _edit = null; Open(); }
/// <summary>钱包那边交易落地后刷新终端(开着的话),带上结果消息。</summary>
public static void Refresh(string message)
{
if (message != null) _message = message;
if (CustomUIManager.Instance != null && CustomUIManager.Instance.IsOpen(Id)) Open();
}
private static void Entry(CustomUIBuilder b, string icon, string name, string line1, string line2, Page target)
{
@@ -241,7 +246,7 @@ namespace Decentralized
private static void Web3Page(CustomUIBuilder b)
{
var w = Session.Data.Hot;
if (_pay != null) { PayPage(b); return; }
if (WalletWindow.Pending != null && !WalletWindow.Pending.Contract) { PayPage(b); return; }
{
if (_web3Tab == Web3Tab.Mixer && !Unlock.MixerUnlocked) _web3Tab = Web3Tab.Trade;
if (_web3Tab == Web3Tab.Black && !Unlock.BlackUnlocked) _web3Tab = Web3Tab.Trade;
@@ -251,7 +256,7 @@ namespace Decentralized
if ((t == Web3Tab.Mixer && !Unlock.MixerUnlocked) || (t == Web3Tab.Black && !Unlock.BlackUnlocked)) continue;
var tab = t;
string name = L.T("web3.sub." + t.ToString().ToLowerInvariant());
b.AddButton(tab == _web3Tab ? $"[{name}]" : name, () => { _web3Tab = tab; _message = null; _pay = null; _edit = null; Open(); });
b.AddButton(tab == _web3Tab ? $"[{name}]" : name, () => { _web3Tab = tab; _message = null; _edit = null; Open(); });
}
b.End();
if (_web3Tab == Web3Tab.Mining) { ReportPage(b); return; }
@@ -312,7 +317,7 @@ namespace Decentralized
{
var tab = t;
string name = L.T("black.sub." + t.ToString().ToLowerInvariant());
b.AddButton(tab == _blackTab ? $"[{name}]" : name, () => { _blackTab = tab; _message = null; _pay = null; _edit = null; Open(); });
b.AddButton(tab == _blackTab ? $"[{name}]" : name, () => { _blackTab = tab; _message = null; _edit = null; Open(); });
}
b.End();
if (d.Black.Closed(day)) { b.AddLabel(L.T("black.rugged", d.Black.ClosedUntil - day + 1)); return; }
@@ -384,7 +389,7 @@ namespace Decentralized
GoodHead(c, g, L.T("black.stock", offer.Qty));
c.AddLabel(L.T("black.price", offer.Price * count) + (count > 1 ? " ×" + count : ""));
c.AddFlexibleSpace();
c.AddButton(L.T("black.buy"), () => BeginPay(d.Address("platform"), ItemName(offer.Item) + (count > 1 ? " ×" + count : ""), offer.Price * count, () => BuyOffer(offer, count)));
c.AddButton(L.T("black.buy"), () => { string asset = _payAsset; BeginPay(d.Address("platform"), ItemName(offer.Item) + (count > 1 ? " ×" + count : ""), asset, offer.Price * count, () => BuyOffer(offer, count, asset)); });
});
}
@@ -404,7 +409,11 @@ namespace Decentralized
}
b.End();
b.AddLabel(L.T("mixer.balance", _payAsset, Fmt.Num(d.Hot.Avail(_payAsset))) + " " + L.T("black.sell.rating", d.Black.Rating) + " " + L.T("black.sell.slots", d.Black.ActiveListings, Tuning.BmMaxListings));
b.AddButton(L.T("black.sell.all"), () => BeginPay(d.Address("platform"), L.T("black.sell.all") + L.T("pay.listfee", _sellMult.ToString("0.0", CultureInfo.InvariantCulture)), BlackDelivery.Sellable().Sum(it => BlackMarket.ListFee(BlackMarket.ListPriceOf(BlackDelivery.RefValue(it), _sellMult))), ListAll));
b.AddButton(L.T("black.sell.all"), () =>
{
string asset = _payAsset; double mult = _sellMult;
BeginPay(d.Address("platform"), L.T("black.sell.all") + L.T("pay.listfee", mult.ToString("0.0", CultureInfo.InvariantCulture)), asset, BlackDelivery.Sellable().Sum(it => BlackMarket.ListFee(BlackMarket.ListPriceOf(BlackDelivery.RefValue(it), mult))), () => ListAll(asset, mult));
});
b.AddSeparator();
var items = BlackDelivery.Sellable();
if (items.Count == 0) { b.AddLabel(L.T("black.sell.none")); return; }
@@ -428,7 +437,7 @@ namespace Decentralized
private static readonly System.Collections.Generic.Dictionary<BmGood, GameItem> SellCards = new System.Collections.Generic.Dictionary<BmGood, GameItem>();
private static void ListAll()
private static void ListAll(string asset, double mult)
{
var d = Session.Data;
int n = 0;
@@ -437,7 +446,7 @@ namespace Decentralized
string id = BlackDelivery.SellId(it);
if (id == null) continue;
var u = Items.IsGpu(it) ? Items.ToUnit(it) : null;
var s = d.Black.List(id, u?.Model, u?.FakeOf, u?.Health ?? 0, BlackDelivery.RefValue(it), _sellMult, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
var s = d.Black.List(id, u?.Model, u?.FakeOf, u?.Health ?? 0, BlackDelivery.RefValue(it), mult, d.Hot, asset, StoreStation.GetDayCounter(), Session.Price);
if (s == null) break;
it.Destroy();
n++;
@@ -447,13 +456,13 @@ namespace Decentralized
Open();
}
private static void ListItem(GameItem it, double mult)
private static void ListItem(GameItem it, double mult, string asset)
{
var d = Session.Data;
string id = BlackDelivery.SellId(it);
if (id == null) { Open(); return; }
var u = Items.IsGpu(it) ? Items.ToUnit(it) : null;
var s = d.Black.List(id, u?.Model, u?.FakeOf, u?.Health ?? 0, BlackDelivery.RefValue(it), mult, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
var s = d.Black.List(id, u?.Model, u?.FakeOf, u?.Health ?? 0, BlackDelivery.RefValue(it), mult, d.Hot, asset, StoreStation.GetDayCounter(), Session.Price);
if (s == null) { _message = d.Black.ActiveListings >= Tuning.BmMaxListings ? L.T("black.sell.full", Tuning.BmMaxListings) : L.T("msg.balance"); Open(); return; }
it.Destroy();
_message = L.T("black.sell.listed", ItemName(id), s.ListPrice);
@@ -483,7 +492,12 @@ namespace Decentralized
c.AddLabel(L.T("black.price", BlackMarket.Pay(BlackMarket.OrderTotal(listing.Price, count), _mode)) + (count > 1 ? " ×" + count : ""));
c.AddFlexibleSpace();
bool ok = _mode == PayMode.Escrow || seller.Advance;
c.AddButton(ok ? L.T("black.order") : L.T("black.noadvance"), () => { if (ok) BeginPay(seller.Name, ItemName(listing.Item) + (count > 1 ? " ×" + count : ""), BlackMarket.Pay(BlackMarket.OrderTotal(listing.Price, count), _mode), () => PlaceOrder(listing, count)); });
c.AddButton(ok ? L.T("black.order") : L.T("black.noadvance"), () =>
{
if (!ok) return;
string asset = _payAsset; var mode = _mode;
BeginPay(seller.Name, ItemName(listing.Item) + (count > 1 ? " ×" + count : ""), asset, BlackMarket.Pay(BlackMarket.OrderTotal(listing.Price, count), mode), () => PlaceOrder(listing, count, asset, mode));
});
});
}
@@ -518,36 +532,32 @@ namespace Decentralized
private static string ItemName(string id) => BlackDelivery.ItemName(id);
private static void BeginPay(string address, string summary, int total, Action execute)
/// <summary>开账单:收款地址、币种、金额(按信用点等值折成币),付款在硬件钱包里做。</summary>
private static void BeginPay(string address, string summary, string asset, double credits, Action execute) =>
BillFor(address, summary, asset, credits / Session.Price(asset), execute);
private static void BillFor(string address, string summary, string asset, double amount, Action execute)
{
_pay = new PendingPay { Address = address, Summary = summary, Total = total, Execute = execute };
_addr = "";
WalletWindow.Pending = new WalletWindow.Bill { Address = address, Summary = summary, Asset = asset, Amount = WalletWindow.RoundUp(amount), Execute = execute };
_message = null;
Open();
}
/// <summary>付款确认:手输收款地址才放款,输错不付。</summary>
/// <summary>待付账单:收款地址和金额,去钱包里转账;也可以作废。</summary>
private static void PayPage(CustomUIBuilder b)
{
var p = _pay;
var p = WalletWindow.Pending;
b.AddHeader(L.T("black.pay.confirm"));
b.AddLabel(L.T("black.pay.item", p.Summary, p.Total));
b.AddLabel(p.Summary);
b.AddLabel(L.T("black.pay.to", p.Address));
b.BeginPanel(RightW, -1f, TextAnchor.MiddleCenter, 0f, 0f);
b.AddInput(L.T("black.pay.input"), _addr, s => _addr = s, "addr", s => _addr = s);
b.End();
b.AddLabel(L.T("black.pay.amount", WalletWindow.Amt(p.Amount), p.Asset, Fmt.Credits(p.Amount * Session.Price(p.Asset))));
b.BeginRow();
b.AddButton(L.T("black.pay.go"), () =>
{
if (!string.Equals((_addr ?? "").Trim(), p.Address, StringComparison.OrdinalIgnoreCase)) { _message = L.T("black.pay.bad"); Open(); return; }
_pay = null;
p.Execute();
});
b.AddButton(L.T("black.pay.cancel"), () => { _pay = null; _message = null; Open(); });
b.AddButton(L.T("black.pay.wallet"), () => WalletWindow.OpenForBill(p));
b.AddButton(L.T("black.pay.cancel"), () => { WalletWindow.Pending = null; _message = null; Open(); });
b.End();
}
private static void BuyOffer(BmOffer o, int count)
private static void BuyOffer(BmOffer o, int count, string asset)
{
var d = Session.Data;
string item = o.Item, model = o.Model, fake = o.FakeOf;
@@ -555,7 +565,7 @@ namespace Decentralized
int bought = 0;
for (int i = 0; i < count; i++)
{
if (!d.Black.Buy(o, d.Hot, _payAsset, Session.Price)) break;
if (!d.Black.Buy(o, d.Hot, asset, Session.Price)) break;
Items.Give(BlackDelivery.Make(item, model, fake, health));
bought++;
}
@@ -565,10 +575,10 @@ namespace Decentralized
Open();
}
private static void PlaceOrder(BmListing l, int count)
private static void PlaceOrder(BmListing l, int count, string asset, PayMode mode)
{
var d = Session.Data;
var o = d.Black.Place(l, count, _mode, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price);
var o = d.Black.Place(l, count, mode, d.Hot, asset, StoreStation.GetDayCounter(), Session.Price);
if (o == null) { _message = L.T("msg.balance"); Open(); return; }
_message = L.T("black.ordered", ItemName(o.Item) + (o.Count > 1 ? " ×" + o.Count : ""), o.DueDay);
Session.Save();
@@ -613,17 +623,18 @@ namespace Decentralized
var d = Session.Data;
int fee = e.Sale != null ? BlackMarket.RepriceFee(price) : BlackMarket.ListFee(price);
_edit = null;
BeginPay(d.Address("platform"), ItemName(e.Id) + L.T(e.Sale != null ? "pay.repricefee" : "pay.listfee", mult.ToString("0.00", CultureInfo.InvariantCulture)), fee,
() => { if (e.Sale != null) RepriceSale(e.Sale, mult); else ListItem(e.Item, mult); });
string asset = _payAsset;
BeginPay(d.Address("platform"), ItemName(e.Id) + L.T(e.Sale != null ? "pay.repricefee" : "pay.listfee", mult.ToString("0.00", CultureInfo.InvariantCulture)), asset, fee,
() => { if (e.Sale != null) RepriceSale(e.Sale, mult, asset); else ListItem(e.Item, mult, asset); });
});
b.AddButton(L.T("black.edit.cancel"), () => { _edit = null; _message = null; Open(); });
b.End();
}
private static void RepriceSale(BmSale s, double mult)
private static void RepriceSale(BmSale s, double mult, string asset)
{
var d = Session.Data;
if (!d.Black.Reprice(s, mult, d.Hot, _payAsset, StoreStation.GetDayCounter(), Session.Price)) { _message = L.T("msg.balance"); Open(); return; }
if (!d.Black.Reprice(s, mult, d.Hot, asset, StoreStation.GetDayCounter(), Session.Price)) { _message = L.T("msg.balance"); Open(); return; }
_message = L.T("black.sell.repriced", ItemName(s.Item), s.ListPrice);
Session.Save();
Open();
@@ -687,15 +698,17 @@ namespace Decentralized
void Swap()
{
if (!ReadAmount(out double dv)) return;
string asset = _deskIn ? _deskAsset : Assets.Ponero;
BeginPay(d.Address("desk"), L.T("pay.swap", Fmt.Num(dv), asset), (int)Math.Round(dv * Session.Price(asset)), DeskSwap);
bool into = _deskIn; string other = _deskAsset;
string asset = into ? other : Assets.Ponero;
_web3Tab = Web3Tab.Trade;
BillFor(d.Address("desk"), L.T("pay.swap", Fmt.Num(dv), asset), asset, dv, () => DeskSwap(dv, into, other));
}
}
private static void DeskSwap()
private static void DeskSwap(double v, bool into, string other)
{
if (!ReadAmount(out double v)) return;
var d = Session.Data;
_deskIn = into; _deskAsset = other;
int day = StoreStation.GetDayCounter();
double got = _deskIn ? d.Black.ToPonero(d.Hot, _deskAsset, v, day, Session.Price) : d.Black.FromPonero(d.Hot, _deskAsset, v, day, Session.Price);
if (got < 0) { _message = L.T("black.desk.fail"); Open(); return; }
@@ -757,18 +770,22 @@ namespace Decentralized
double rate = Mixer.RoundRate(v * Session.Price(_mixAsset));
r.AddLabel(L.T("mixer.preview", Fmt.Num(v * Math.Pow(1 - Tuning.MixerFee, _mixRounds)), _mixAsset, (int)Math.Round((1 - Math.Pow(1 - rate, _mixRounds)) * 100), day + _mixRounds));
}
r.AddButton(L.T("mixer.go"), () => { if (!ReadAmount(out double mv)) return; BeginPay(d.Address("mixer"), L.T("pay.mix", Fmt.Num(mv), _mixAsset), (int)Math.Round(mv * Session.Price(_mixAsset)), MixDeposit); });
r.AddButton(L.T("mixer.go"), () =>
{
if (!ReadAmount(out double mv)) return;
string asset = _mixAsset; int rounds = _mixRounds;
BillFor(d.Address("mixer"), L.T("pay.mix", Fmt.Num(mv), asset), asset, mv, () => MixDeposit(mv, asset, rounds));
});
});
}
private static void MixDeposit()
private static void MixDeposit(double v, string asset, int rounds)
{
if (!ReadAmount(out double v)) return;
var d = Session.Data;
int day = StoreStation.GetDayCounter();
if (d.Hot.Avail(_mixAsset) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
var dep = d.Mixer.Deposit(d.Hot, _mixAsset, v, _mixRounds, day, Session.Price);
_message = dep == null ? L.T("mixer.min", Tuning.MixerMin) : L.T("mixer.done", Fmt.Num(v), _mixAsset, dep.ReadyDay);
if (d.Hot.Avail(asset) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
var dep = d.Mixer.Deposit(d.Hot, asset, v, rounds, day, Session.Price);
_message = dep == null ? L.T("mixer.min", Tuning.MixerMin) : L.T("mixer.done", Fmt.Num(v), asset, dep.ReadyDay);
Session.Save();
Open();
}
@@ -836,9 +853,22 @@ namespace Decentralized
Open();
}
/// <summary>交易:Poinbase 是托管账户,直接成交;DEX 是合约调用,要在硬件钱包上签名。</summary>
private static void Trade(Pool p, bool buy)
{
if (!ReadAmount(out double v)) return;
var w = Session.Data.Hot;
string pay = buy ? p.Quote : p.Base, get = buy ? p.Base : p.Quote;
if (w.Avail(pay) + 1e-9 < v) { _message = L.T(frozenPay(w, pay) ? "msg.frozen" : "msg.balance"); Open(); return; }
if (p.Venue != Venue.Dex) { DoTrade(p, buy, v); return; }
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 = () => DoTrade(p, buy, v) });
}
private static void DoTrade(Pool p, bool buy, double v)
{
var w = Session.Data.Hot;
string pay = buy ? p.Quote : p.Base, get = buy ? p.Base : p.Quote;
if (w.Avail(pay) + 1e-9 < v) { _message = L.T(frozenPay(w, pay) ? "msg.frozen" : "msg.balance"); Open(); return; }