Stage 6.1: CRA staking pool, SPARK balance token with mint/burn

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-04 00:06:16 +08:00
1 parent 58dd93eca6
commit 98e80412e6
12 files changed
+371 -9

No files matched your search

+99 -1
View File
@@ -381,6 +381,7 @@ namespace Decentralized
b.AddButton(tab == _web3Tab ? $"[{name}]" : name, () => { _web3Tab = tab; _message = null; _edit = null; Open(); });
}
b.End();
if (_web3Tab == Web3Tab.Stake) { StakePage(b); return; }
if (_web3Tab == Web3Tab.Mining) { ReportPage(b); return; }
if (_web3Tab == Web3Tab.Mixer) { MixerPage(b); return; }
if (_web3Tab == Web3Tab.Black) { BlackPage(b); return; }
@@ -844,7 +845,7 @@ namespace Decentralized
Open();
}
private enum Web3Tab { Trade, Mining, Mixer, Black }
private enum Web3Tab { Trade, Stake, Mining, Mixer, Black }
private static Web3Tab _web3Tab = Web3Tab.Trade;
private static string _mixAsset = Assets.PSC;
private static int _mixRounds = 2;
@@ -915,6 +916,103 @@ namespace Decentralized
Open();
}
private static readonly float[] StakeW = { 110f, 90f, 200f };
/// <summary>质押页:革命军 CRA 质押池(日息、解锁排队)和平衡代币 SPARK 的铸销。都是合约调用,要在钱包上签名。</summary>
private static void StakePage(CustomUIBuilder b)
{
var d = Session.Data;
var st = d.Stable;
var w = d.Hot;
int day = StoreStation.GetDayCounter();
Columns(b, l =>
{
l.AddHeader(L.T("stake.h"));
l.AddLabel(L.T("stake.rate", (st.CraRate * 100).ToString("0.0", CultureInfo.InvariantCulture), Tuning.CraUnlockDays) + (st.CraSubsidyLow ? " " + L.T("stake.low") : ""));
l.AddLabel(L.T("stake.cra", Fmt.Num(st.Cra), StableStatus(st, Assets.CRA, day)));
l.AddSeparator();
if (d.Staking.Lots.Count == 0) l.AddLabel(L.T("stake.none"));
else
{
TableHeader(l, StakeW, L.T("stake.col.wallet"), L.T("stake.col.amount"), L.T("stake.col.queue"));
foreach (var lot in d.Staking.Lots)
{
var ow = d.Own(lot.Wallet);
string queue = lot.Unlocking.Count == 0 ? "—" : string.Join(" ", lot.Unlocking.Select(u => Fmt.Num(u.Value) + L.T("stake.day", u.Key)));
TableRow(l, StakeW, ow != null ? WalletWindow.NameOf(ow) : lot.Wallet, Fmt.Num(lot.Amount), queue);
}
}
l.AddSeparator();
l.AddLabel(L.T("spark.price", Fmt.Num(st.Spark)));
}, r =>
{
r.AddHeader(L.T("stake.ops", WalletWindow.NameOf(w)));
r.AddLabel(L.T("stake.avail", Fmt.Num(w.Avail(Assets.CRA)), Fmt.Num(d.Staking.Of(w.Address)?.Amount ?? 0)));
r.AddLabel(L.T("stake.avail2", Fmt.Num(w.Avail(Assets.SPARK))));
AmountRow(r);
r.BeginRow();
r.AddButton(L.T("stake.do"), () => StakeCall(true));
r.AddButton(L.T("stake.undo"), () => StakeCall(false));
r.End();
r.AddSeparator();
r.AddHeader(L.T("spark.h"));
r.BeginRow();
r.AddButton(L.T("spark.mint"), () => SparkCall(true));
r.AddButton(L.T("spark.burn"), () => SparkCall(false));
r.End();
});
}
/// <summary>手续费用钱包里价值最高的那种币付。</summary>
private static string BestFeeAsset(Wallet w) =>
w.Balances.Where(k => k.Value > 1e-9 && Session.Price(k.Key) > 0).OrderByDescending(k => k.Value * Session.Price(k.Key)).Select(k => k.Key).FirstOrDefault() ?? Assets.PSC;
private static void StakeCall(bool into)
{
if (!ReadAmount(out double v)) return;
var d = Session.Data;
var w = d.Hot;
if (into ? w.Avail(Assets.CRA) + 1e-9 < v : (d.Staking.Of(w.Address)?.Amount ?? 0) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
_message = L.T("hw.wait");
Open();
WalletWindow.OpenForBill(new WalletWindow.Bill
{
Contract = true, Asset = Assets.CRA, Amount = v, Spend = into ? v : 0, FeeAsset = into ? null : BestFeeAsset(w),
Summary = L.T(into ? "stake.sum.in" : "stake.sum.out", Fmt.Num(v), Tuning.CraUnlockDays),
Execute = pw =>
{
int day = StoreStation.GetDayCounter();
bool ok = into ? d.Staking.Stake(pw, v, Session.Price) : d.Staking.RequestUnstake(pw.Address, v, day);
_message = ok ? L.T(into ? "stake.done.in" : "stake.done.out", Fmt.Num(v), day + Tuning.CraUnlockDays) : L.T("msg.balance");
Session.Save();
Open();
},
});
}
private static void SparkCall(bool mint)
{
if (!ReadAmount(out double v)) return;
var d = Session.Data;
var w = d.Hot;
string pay = mint ? Assets.CRA : Assets.SPARK, get = mint ? Assets.SPARK : Assets.CRA;
if (w.Avail(pay) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
double quote = mint ? v * d.Stable.Cra / d.Stable.Spark * (1 - Tuning.SparkFee) : v * d.Stable.Spark / d.Stable.Cra * (1 - Tuning.SparkFee);
_message = L.T("hw.wait");
Open();
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 =>
{
double got = mint ? d.Stable.Mint(pw, v) : d.Stable.Burn(pw, v);
_message = got < 0 ? L.T("msg.balance") : L.T("msg.trade", Fmt.Num(v), pay, Fmt.Num(got), get);
Session.Save();
Open();
},
});
}
private static bool frozenPay(Wallet w, string asset) => w.Frozen.ContainsKey(asset);
private static string AvgText(Wallet w, string asset) => w.AvgBuy(asset) is double a ? Fmt.Num(a) : L.T("avg.none");