Prediction markets use on-chain wallets (signed contract calls) instead of the Poinbase account

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ZHU MINCHAOandClaude Sonnet 5.5 committed 2026-11-04 07:33:42 +08:00
1 parent e5a6e3e22c
commit 723724aa4b
8 files changed
+138 -73

No files matched your search

+35 -30
View File
@@ -69,6 +69,8 @@ namespace Decentralized.Domain
public sealed class PredictPos
{
public int Market;
/// <summary>持仓所在的钱包地址(下注签名的钱包,结算和卖出都回到它)。</summary>
public string Wallet;
public bool Yes;
public double Shares, Cost, Taint, Basis, Insider;
}
@@ -113,7 +115,8 @@ namespace Decentralized.Domain
public List<string> Prev;
public PredictMarket Find(int id) => Markets.Find(m => m.Id == id);
public PredictPos PosOf(int id) => Positions.Find(p => p.Market == id);
public PredictPos PosOf(int id, string wallet) => Positions.Find(p => p.Market == id && p.Wallet == wallet);
public List<PredictPos> PosAll(int id) => Positions.Where(p => p.Market == id).ToList();
private static double Share(Wallet acct, double amount, Func<string, double> price)
{
@@ -130,26 +133,27 @@ namespace Decentralized.Domain
if (!m.IsOpen(day)) return PredictResult.Closed;
if (spend <= 0) return PredictResult.Amount;
if (acct.Avail(Assets.CRC) + 1e-9 < spend) return PredictResult.Funds;
var pos = PosOf(m.Id);
if (pos != null && pos.Yes != yes) return PredictResult.Opposite;
var all = PosAll(m.Id);
if (all.Any(x => x.Yes != yes)) return PredictResult.Opposite;
var pos = PosOf(m.Id, acct.Address);
double cost = spend / (1 + Tuning.PredictFee);
shares = m.SharesFor(yes, cost);
if ((pos?.Shares ?? 0) + shares > m.ShareCap + 1e-9) { shares = 0; return PredictResult.Cap; }
if (all.Sum(x => x.Shares) + shares > m.ShareCap + 1e-9) { shares = 0; return PredictResult.Cap; }
double share = Share(acct, spend, pr);
double taint = acct.Taint * share, basis = acct.Basis * share;
acct.Add(Assets.CRC, -spend); acct.Taint -= taint; acct.Basis -= basis;
if (yes) m.QYes += shares; else m.QNo += shares;
if (pos == null) Positions.Add(pos = new PredictPos { Market = m.Id, Yes = yes });
if (pos == null) Positions.Add(pos = new PredictPos { Market = m.Id, Yes = yes, Wallet = acct.Address });
pos.Shares += shares; pos.Cost += cost; pos.Taint += taint; pos.Basis += basis;
if (insider) pos.Insider += shares;
return PredictResult.Ok;
}
/// <summary>全部卖出(盘口关闭前):收回 LMSR 的卖价减 2% 手续费,污点按比例退回,赚的部分是干净的。返回收回的 CRC。</summary>
/// <summary>全部卖出这个钱包在盘口里的持仓(盘口关闭前):收回 LMSR 的卖价减 2% 手续费,污点按比例退回,赚的部分是干净的。返回收回的 CRC。</summary>
public PredictResult SellAll(Wallet acct, PredictMarket m, int day, out double got)
{
got = 0;
var pos = PosOf(m.Id);
var pos = PosOf(m.Id, acct.Address);
if (pos == null) return PredictResult.NoPosition;
if (!m.IsOpen(day)) return PredictResult.Closed;
double value = m.SellValue(pos.Yes, pos.Shares);
@@ -167,10 +171,10 @@ namespace Decentralized.Domain
}
/// <summary>
/// 夜间:先登记今晚的观测,再结算 ToDay 到了的盘口。outcome 由接入层给出(事件 / 派系读观测,纯度和行情读游戏数值),null 表示拿不到数据。
/// 夜间:先登记今晚的观测,再结算 ToDay 到了的盘口(赢的钱回到下注的钱包,wallet 按地址找)。outcome 由接入层给出(事件 / 派系读观测,纯度和行情读游戏数值),null 表示拿不到数据。
/// 赢家每份兑付 1 信用点;内幕持仓达到份额上限的 <see cref="Tuning.PredictInsiderShare"/> 后,有概率被查:只退本金,收益的一半记罪证。
/// </summary>
public List<PredictEvent> Night(int day, IEnumerable<string> activeNow, Func<PredictMarket, bool?> outcome, Wallet acct, Rng rng)
public List<PredictEvent> Night(int day, IEnumerable<string> activeNow, Func<PredictMarket, bool?> outcome, Func<string, Wallet> wallet, Rng rng)
{
var now = activeNow.Distinct().ToList();
if (Prev != null) Started[day] = now.Where(x => !Prev.Contains(x)).ToList();
@@ -180,38 +184,39 @@ namespace Decentralized.Domain
foreach (var m in Markets.Where(x => x.Outcome < 0 && x.ToDay <= day).ToList())
{
bool? res = outcome(m);
var pos = PosOf(m.Id);
var all = PosAll(m.Id);
foreach (var pos in all) Positions.Remove(pos);
double cost = all.Sum(x => x.Cost);
if (res == null)
{
m.Outcome = 2;
if (pos != null) { Positions.Remove(pos); Pay(acct, pos, pos.Cost); evs.Add(new PredictEvent { Kind = "void", Title = m.Title, Market = m, Amount = pos.Cost }); }
Record(day, m, 2, 0, pos != null);
foreach (var pos in all) Pay(wallet(pos.Wallet), pos, pos.Cost);
if (all.Count > 0) evs.Add(new PredictEvent { Kind = "void", Title = m.Title, Market = m, Amount = cost });
Record(day, m, 2, 0, all.Count > 0);
continue;
}
m.Outcome = res.Value ? 1 : 0;
if (pos != null)
var winners = all.Where(x => x.Yes == res.Value).ToList();
if (all.Count > 0 && winners.Count == 0)
{
Positions.Remove(pos);
if (pos.Yes != res.Value)
evs.Add(new PredictEvent { Kind = "lose", Title = m.Title, Market = m, Amount = cost });
Record(day, m, m.Outcome, -cost, true);
}
else if (winners.Count > 0)
{
double payout = winners.Sum(x => x.Shares), profit = payout - cost;
// 内幕持仓按盘口合计(拆到几个钱包里也一样)
if (all.Sum(x => x.Insider) >= Tuning.PredictInsiderShare * m.ShareCap && rng.Chance(Tuning.PredictInsiderCatch))
{
evs.Add(new PredictEvent { Kind = "lose", Title = m.Title, Market = m, Amount = pos.Cost });
Record(day, m, m.Outcome, -pos.Cost, true);
foreach (var pos in winners) Pay(wallet(pos.Wallet), pos, pos.Cost);
evs.Add(new PredictEvent { Kind = "caught", Title = m.Title, Market = m, Amount = profit, Fraud = Math.Min(Tuning.PredictFraudCap, Math.Max(0, profit) * Tuning.PredictFraudShare) });
Record(day, m, m.Outcome, 0, true);
}
else
{
double payout = pos.Shares, profit = payout - pos.Cost;
if (pos.Insider >= Tuning.PredictInsiderShare * m.ShareCap && rng.Chance(Tuning.PredictInsiderCatch))
{
Pay(acct, pos, pos.Cost);
evs.Add(new PredictEvent { Kind = "caught", Title = m.Title, Market = m, Amount = profit, Fraud = Math.Min(Tuning.PredictFraudCap, Math.Max(0, profit) * Tuning.PredictFraudShare) });
Record(day, m, m.Outcome, 0, true);
}
else
{
Pay(acct, pos, payout);
evs.Add(new PredictEvent { Kind = "win", Title = m.Title, Market = m, Amount = payout });
Record(day, m, m.Outcome, profit, true);
}
foreach (var pos in winners) Pay(wallet(pos.Wallet), pos, pos.Shares);
evs.Add(new PredictEvent { Kind = "win", Title = m.Title, Market = m, Amount = payout });
Record(day, m, m.Outcome, profit, true);
}
}
else Record(day, m, m.Outcome, 0, false);