Files
probably-decentralized/src/Decentralized/Funds.cs
T

116 lines
5.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using Decentralized.Domain;
using UnityEngine;
namespace Decentralized
{
/// <summary>
/// Poinbase 的出金:转到钱包地址。托管账户发起,几秒钟的确认动画(模拟交易所触发合约执行)走完才落地;
/// 手续费固定中间档,从转出的币里扣。动画走完前币还在账户里,中途存档、退出都不会丢币。
/// 地址格式或校验位不对拒绝;自己的钱包到账;别的格式正确的地址按黑洞处理(币就没了)。
/// </summary>
internal static class Funds
{
internal sealed class Tx
{
public int Id, Confs;
public string Asset, To;
public double Amount, Fee;
public float Start, Total;
public bool Finished;
}
internal static readonly List<Tx> Active = new List<Tx>();
private static int seq;
/// <summary>地址检查:返回 null 表示可以转,否则是错误文案的键。</summary>
internal static string Check(string to)
{
var d = Session.Data;
to = ChainAddress.Normalize(to ?? "");
if (!ChainAddress.Valid(to)) return "funds.err.addr";
if (ChainAddress.Same(to, d.Address("poinbase"))) return "funds.err.self";
if (d.Own(to) == null && WalletWindow.Known(to)) return "funds.err.known";
return null;
}
private static bool Congested
{
get { var d = Session.Data; int day = StoreStation.GetDayCounter(); return day >= d.CongestedFrom && day <= d.CongestedUntil; }
}
/// <summary>开始一笔出金。余额不足或手续费吃掉全部数额返回错误键,成功返回 null。</summary>
internal static string Start(string asset, double amount, string to)
{
var d = Session.Data;
var err = Check(to);
if (err != null) return err;
var pb = d.Poinbase;
if (pb.Avail(asset) + 1e-9 < amount) return pb.Frozen.ContainsKey(asset) ? "msg.frozen" : "msg.balance";
double price = Session.Price(asset);
double fee = price > 0 ? TxRules.FeeCredits(FeeTier.Normal, Congested, d.Gas) / price : 0;
if (fee >= amount) return "funds.err.fee";
double value = amount * price;
int confs = TxRules.Confirmations(value);
Active.Add(new Tx
{
Id = ++seq, Asset = asset, To = ChainAddress.Normalize(to), Amount = amount, Fee = fee, Confs = confs,
Start = Time.unscaledTime, Total = (float)(Tuning.FundsBaseSecs + confs * Tuning.FundsConfSecs),
});
WalletDriver.Ensure();
return null;
}
internal static float Progress(Tx t) => t.Total <= 0 ? 1 : Mathf.Clamp01((Time.unscaledTime - t.Start) / t.Total);
internal static int Conf(Tx t) => Math.Max(1, Math.Min(t.Confs, 1 + (int)((Time.unscaledTime - t.Start) / Math.Max(0.01f, t.Total) * t.Confs)));
internal static string StatusText(Tx t) => L.T("funds.confirm", Conf(t), t.Confs);
/// <summary>每帧:更新开着的窗口里的进度条,走完的落地。</summary>
internal static void Tick()
{
if (Active.Count == 0) return;
var m = CustomUIManager.Instance;
foreach (var t in Active.ToList())
{
if (Time.unscaledTime - t.Start >= t.Total) { Finish(t); continue; }
m?.GetElement(TerminalWindow.Id, "funds_status_" + t.Id)?.SetText(StatusText(t));
m?.GetElement(TerminalWindow.Id, "funds_prog_" + t.Id)?.SetProgress(Progress(t));
}
}
private static void Finish(Tx t)
{
if (t.Finished) return;
t.Finished = true;
Active.Remove(t);
Log.Guard("出金落地", () =>
{
var d = Session.Data;
var pb = d.Poinbase;
double amt = Math.Min(t.Amount, pb.Avail(t.Asset));
double fee = Math.Min(t.Fee, amt);
if (fee > 0) Wallet.Transfer(pb, new Wallet(), t.Asset, fee, Session.Price);
double net = amt - fee;
var own = d.Own(t.To);
Wallet.Transfer(pb, own ?? new Wallet(), t.Asset, net, Session.Price);
d.Block += 1;
d.Ledger.Add(new LedgerEntry { Block = d.Block, Day = StoreStation.GetDayCounter(), From = d.Address("poinbase"), To = t.To, Asset = t.Asset, Amount = net, Fee = fee });
if (own != null)
{
TxRules.Record(own.History, new TxRecord { Addr = d.Address("poinbase"), Asset = t.Asset, Amount = net, Day = StoreStation.GetDayCounter(), In = true });
WalletWindow.RefreshDevice(own, true);
}
else Log.Info($"出金转进没人用的地址 {t.To}:{net} {t.Asset}");
Session.Save();
TerminalWindow.Refresh(own != null
? L.T("funds.done", Fmt.Num(net), t.Asset, WalletWindow.NameOf(own))
: L.T("funds.done.void", Fmt.Num(net), t.Asset));
});
}
}
}