@@ -15,6 +15,10 @@ types:
1515 total_peers: Int
1616 reward_rate_per_hour: Float
1717 next_payout_time: Int
18+ current_tier: String
19+ tier_multiplier: Float
20+ stake_amount: Int
21+ total_earned: Int
1822
1923 PeerEconomyInfo:
2024 fields:
@@ -23,42 +27,280 @@ types:
2327 score: Float
2428 triples_stored: Int
2529 rewards_earned: Int
30+ rank: Int
31+ uptime_ratio: Float
2632
2733 DashboardUpdate:
2834 fields:
2935 metrics: EconomyMetrics
3036 leaderboard: List<PeerEconomyInfo>
3137 recent_transactions: List<String>
3238 timestamp: Int
39+ update_id: Int
3340
3441 AlertThreshold:
3542 fields:
3643 min_balance: Int
3744 max_unclaimed_hours: Int
3845 min_rank: Int
46+ alert_on_tier_change: Bool
47+ alert_on_large_payment: Bool
48+ large_payment_threshold: Int
49+
50+ Alert:
51+ fields:
52+ alert_type: String
53+ severity: String
54+ message: String
55+ timestamp: Int
56+ data: String
57+
58+ EarningsProjection:
59+ fields:
60+ period_24h: Int
61+ period_7d: Int
62+ period_30d: Int
63+ based_on_hours: Float
64+ confidence: String
3965
4066behaviors:
4167 - name: get_metrics
42- given: wallet address
68+ given: wallet address and leaderboard
4369 when: requesting dashboard data
4470 then: returns EconomyMetrics with current state
71+ implementation: |
72+ pub fn getMetrics(allocator: Allocator, wallet: Wallet, leaderboard: Leaderboard, contribution: Contribution) !EconomyMetrics {
73+ // Find peer rank
74+ var rank: usize = 0;
75+ var tier: []const u8 = "Basic";
76+ var multiplier: f64 = 1.0;
77+
78+ for (leaderboard.rankings) |r| {
79+ if (std.mem.eql(u8, r.peer_id, wallet.address)) {
80+ rank = r.rank;
81+ tier = r.tier;
82+ multiplier = getTierMultiplier(r.score);
83+ break;
84+ }
85+ }
86+
87+ // Calculate reward rate based on contribution
88+ const hourly_rate = calculateHourlyRate(contribution, multiplier);
89+
90+ // Next payout is every 24 hours from last claim
91+ const next_payout = wallet.last_activity + (24 * 3600);
92+
93+ return .{
94+ .my_wallet_balance = wallet.balance_wei,
95+ .pending_rewards = wallet.pending_rewards,
96+ .current_rank = @intCast(rank),
97+ .total_peers = leaderboard.total_peers,
98+ .reward_rate_per_hour = hourly_rate,
99+ .next_payout_time = next_payout,
100+ .current_tier = try allocator.dupe(u8, tier),
101+ .tier_multiplier = multiplier,
102+ .stake_amount = wallet.stake_amount,
103+ .total_earned = wallet.total_earned,
104+ };
105+ }
106+
107+ fn calculateHourlyRate(contrib: Contribution, multiplier: f64) f64 {
108+ var base: f64 = 0;
109+ // Storage reward per hour
110+ base += @as(f64, @floatFromInt(contrib.triples_stored)) / 1000;
111+ // Retrieval reward per hour
112+ base += @as(f64, @floatFromInt(contrib.triples_retrieved)) / 500;
113+ // Uptime bonus
114+ const uptime_hours = @as(f64, @floatFromInt(contrib.uptime_seconds)) / 3600;
115+ base += uptime_hours * 0.1;
116+ return base * multiplier;
117+ }
118+
119+ fn getTierMultiplier(score: f64) f64 {
120+ return if (score >= 90) 2.0
121+ else if (score >= 75) 1.5
122+ else if (score >= 60) 1.2
123+ else if (score >= 40) 1.0
124+ else 0.8;
125+ }
45126
46127 - name: format_dashboard_update
47128 given: metrics and leaderboard
48129 when: preparing for Swarm Watch display
49130 then: returns formatted DashboardUpdate
131+ implementation: |
132+ pub fn formatDashboardUpdate(allocator: Allocator, metrics: EconomyMetrics, leaderboard: Leaderboard, update_id: i64) !DashboardUpdate {
133+ // Get top 10 peers
134+ const top_n = @min(10, leaderboard.rankings.len);
135+
136+ var peer_infos = try std.ArrayList(PeerEconomyInfo).initCapacity(allocator, top_n);
137+ defer peer_infos.deinit();
138+
139+ for (leaderboard.rankings[0..top_n]) |r| {
140+ try peer_infos.append(.{
141+ .peer_id = r.peer_id,
142+ .tier = r.tier,
143+ .score = r.score,
144+ .triples_stored = 0, // Would come from Contribution data
145+ .rewards_earned = 0, // Would come from wallet data
146+ .rank = r.rank,
147+ .uptime_ratio = 0, // Would come from stats
148+ });
149+ }
150+
151+ return .{
152+ .metrics = metrics,
153+ .leaderboard = try allocator.dupe(PeerEconomyInfo, peer_infos.items),
154+ .recent_transactions = &[_][]const u8{}, // Empty for now
155+ .timestamp = std.time.timestamp(),
156+ .update_id = @intCast(update_id),
157+ };
158+ }
50159
51160 - name: check_alerts
52161 given: metrics and thresholds
53162 when: checking if alert needed
54163 then: returns alert list if thresholds exceeded
164+ implementation: |
165+ pub fn checkAlerts(allocator: Allocator, metrics: EconomyMetrics, thresholds: AlertThreshold) ![]Alert {
166+ var alerts = std.ArrayList(Alert).init(allocator);
167+ defer alerts.deinit();
168+
169+ // Low balance alert
170+ if (metrics.my_wallet_balance < thresholds.min_balance) {
171+ try alerts.append(.{
172+ .alert_type = "low_balance",
173+ .severity = "warning",
174+ .message = try std.fmt.allocPrint(allocator, "Balance low: {d} TRI", .{metrics.my_wallet_balance}),
175+ .timestamp = std.time.timestamp(),
176+ .data = "",
177+ });
178+ }
179+
180+ // Unclaimed rewards alert
181+ const unclaimed_hours = @as(f64, @floatFromInt(metrics.pending_rewards)) / @max(metrics.reward_rate_per_hour, 0.001);
182+ if (unclaimed_hours > @as(f64, @floatFromInt(thresholds.max_unclaimed_hours))) {
183+ try alerts.append(.{
184+ .alert_type = "unclaimed_rewards",
185+ .severity = "info",
186+ .message = try std.fmt.allocPrint(allocator, "{d:.1} hours of unclaimed rewards", .{unclaimed_hours}),
187+ .timestamp = std.time.timestamp(),
188+ .data = "",
189+ });
190+ }
191+
192+ // Rank drop alert
193+ if (metrics.current_rank > thresholds.min_rank) {
194+ try alerts.append(.{
195+ .alert_type = "rank_drop",
196+ .severity = "warning",
197+ .message = try std.fmt.allocPrint(allocator, "Rank dropped to #{d}", .{metrics.current_rank}),
198+ .timestamp = std.time.timestamp(),
199+ .data = "",
200+ });
201+ }
202+
203+ return allocator.dupe(Alert, alerts.items);
204+ }
55205
56206 - name: get_earnings_projection
57- given: current stats
207+ given: current stats and rate
58208 when: projecting future earnings
59209 then: returns estimated rewards for 24h/7d/30d
210+ implementation: |
211+ pub fn getEarningsProjection(hourly_rate: f64, hours_sampled: f64) EarningsProjection {
212+ const confidence = if (hours_sampled >= 168) "high" // 1 week
213+ else if (hours_sampled >= 24) "medium"
214+ else "low";
215+
216+ return .{
217+ .period_24h = @floatToInt(i64, @floor(hourly_rate * 24)),
218+ .period_7d = @floatToInt(i64, @floor(hourly_rate * 24 * 7)),
219+ .period_30d = @floatToInt(i64, @floor(hourly_rate * 24 * 30)),
220+ .based_on_hours = hours_sampled,
221+ .confidence = confidence,
222+ };
223+ }
60224
61225 - name: notify_balance_change
62- given: wallet and amount
226+ given: wallet and amount and thresholds
63227 when: balance changes significantly
64228 then: triggers Telegram notification if enabled
229+ implementation: |
230+ pub fn notifyBalanceChange(allocator: Allocator, wallet: Wallet, amount: i64, thresholds: AlertThreshold) !?[]const u8 {
231+ if (!thresholds.alert_on_large_payment) return null;
232+ if (@abs(amount) < thresholds.large_payment_threshold) return null;
233+
234+ const emoji = if (amount > 0) "🟡" else "🔴";
235+ const action = if (amount > 0) "received" else "sent";
236+
237+ const message = try std.fmt.allocPrint(allocator,
238+ \\{s} Balance Update
239+ \\Amount: {d} TRI {s}
240+ \\New Balance: {d} TRI
241+ \\Tier: {s}
242+ , .{ emoji, @abs(amount), action, wallet.balance_wei, "Basic" });
243+
244+ // Send to Telegram
245+ // TODO: actual Telegram integration
246+ _ = sendTelegramNotification(message);
247+
248+ return message;
249+ }
250+
251+ - name: format_leaderboard_entry
252+ given: peer ranking info
253+ when: formatting single leaderboard entry
254+ then: returns formatted string for display
255+ implementation: |
256+ pub fn formatLeaderboardEntry(allocator: Allocator, info: PeerEconomyInfo) ![]const u8 {
257+ const tier_emoji = getTierEmoji(info.tier);
258+ return try std.fmt.allocPrint(allocator,
259+ "{s} #{d: >3} | {s: <12} | Score: {d: >5.1} | Stored: {d: >6}",
260+ .{ tier_emoji, info.rank, info.peer_id, info.score, info.triples_stored }
261+ );
262+ }
263+
264+ fn getTierEmoji(tier: []const u8) []const u8 {
265+ return if (std.mem.eql(u8, tier, "Elite")) "👑"
266+ else if (std.mem.eql(u8, tier, "Gold")) "🥇"
267+ else if (std.mem.eql(u8, tier, "Silver")) "🥈"
268+ else if (std.mem.eql(u8, tier, "Bronze")) "🥉"
269+ else "⚪";
270+ }
271+
272+ - name: create_dashboard_summary
273+ given: metrics and projection
274+ when: creating summary widget
275+ then: returns formatted multi-line summary
276+ implementation: |
277+ pub fn createDashboardSummary(allocator: Allocator, metrics: EconomyMetrics, projection: EarningsProjection) ![]const u8 {
278+ return try std.fmt.allocPrint(allocator,
279+ \\╔════════════════════════════════════════╗
280+ \\║ TRI ECONOMY DASHBOARD ║
281+ \\╠════════════════════════════════════════╣
282+ \\║ Balance: {d: >15} TRI ║
283+ \\║ Pending: {d: >15} TRI ║
284+ \\║ Rank: #{d: >14} ║
285+ \\║ Tier: {s: <15} ║
286+ \\║ Multiplier: {d: >15.1f}x ║
287+ \\║ Rate/h: {d: >15.2} TRI ║
288+ \\╠────────────────────────────────────────╣
289+ \\║ Projections ({s: >6} confidence): ║
290+ \\║ 24h: +{d: >12} TRI ║
291+ \\║ 7d: +{d: >12} TRI ║
292+ \\║ 30d: +{d: >12} TRI ║
293+ \\╚════════════════════════════════════════╝
294+ , .{
295+ metrics.my_wallet_balance,
296+ metrics.pending_rewards,
297+ metrics.current_rank,
298+ metrics.current_tier,
299+ metrics.tier_multiplier,
300+ metrics.reward_rate_per_hour,
301+ projection.confidence,
302+ projection.period_24h,
303+ projection.period_7d,
304+ projection.period_30d,
305+ });
306+ }
0 commit comments