Skip to content

Commit 104e887

Browse files
authored
Merge pull request #291 from Ejirowebfi/feat/issue-229-trade-analytics
Feat/issue 229 Trade Analytics
2 parents 497d689 + 4c3b25c commit 104e887

2 files changed

Lines changed: 183 additions & 0 deletions

File tree

contract/src/analytics.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,166 @@ pub fn analytics_query(env: &Env, window: TimeWindow) -> AnalyticsResult {
357357
let unique_addresses = load_unique_count(env);
358358
AnalyticsResult { all_time, window: wm, unique_addresses }
359359
}
360+
361+
// ---------------------------------------------------------------------------
362+
// Issue #229 — named query functions
363+
// ---------------------------------------------------------------------------
364+
365+
/// Volume statistics: total trades created, total value locked, and per-window
366+
/// volume for the requested time window.
367+
///
368+
/// Maps to the issue's `get_volume_stats()` requirement.
369+
pub fn get_volume_stats(env: &Env, window: TimeWindow) -> VolumeStats {
370+
let m = load_metrics(env);
371+
let wm = load_or_reset_window(env, &window);
372+
VolumeStats {
373+
trades_created: m.trades_created,
374+
total_value: m.total_volume,
375+
window_volume: wm.volume,
376+
window_trades: wm.trades_created,
377+
}
378+
}
379+
380+
/// Success-rate breakdown: completed / disputed / cancelled counts and the
381+
/// derived rate in basis points.
382+
///
383+
/// Maps to the issue's `get_success_rate()` requirement.
384+
pub fn get_success_rate(env: &Env) -> SuccessRateStats {
385+
let m = load_metrics(env);
386+
let terminal = m.trades_completed
387+
.saturating_add(m.trades_cancelled)
388+
.saturating_add(m.trades_disputed);
389+
let success_rate_bps = if terminal == 0 {
390+
0u32
391+
} else {
392+
((m.trades_completed as u128 * 10_000) / terminal as u128) as u32
393+
};
394+
SuccessRateStats {
395+
completed: m.trades_completed,
396+
disputed: m.trades_disputed,
397+
cancelled: m.trades_cancelled,
398+
success_rate_bps,
399+
}
400+
}
401+
402+
/// Platform usage metrics: active escrows, unique participants, fees collected.
403+
///
404+
/// Maps to the issue's `get_platform_metrics()` requirement.
405+
/// (The existing `get_metrics` / `get_stats` remain unchanged; this adds the
406+
/// named shape the issue specifies.)
407+
pub fn get_platform_usage(env: &Env) -> PlatformUsage {
408+
let m = load_metrics(env);
409+
let active_trades = m.trades_created
410+
.saturating_sub(m.trades_completed)
411+
.saturating_sub(m.trades_cancelled)
412+
.saturating_sub(m.trades_disputed);
413+
PlatformUsage {
414+
active_escrows: active_trades,
415+
unique_participants: load_unique_count(env),
416+
total_fees_collected: m.total_fees_collected,
417+
}
418+
}
419+
420+
/// All metrics scoped to an arbitrary `[start_time, end_time]` range
421+
/// (ledger UTC timestamps in seconds).
422+
///
423+
/// Because Soroban storage cannot be iterated, this function compares the
424+
/// requested range against the stored rolling windows and returns the
425+
/// tightest-fitting window whose `window_start` falls within the range.
426+
/// For ranges that don't align with a preset window the all-time snapshot
427+
/// is returned with `window_matched` set to `false`.
428+
///
429+
/// Maps to the issue's `get_analytics_by_period(start, end)` requirement.
430+
pub fn get_analytics_by_period(env: &Env, start_time: u64, end_time: u64) -> PeriodAnalytics {
431+
let now = env.ledger().timestamp();
432+
let span = end_time.saturating_sub(start_time);
433+
434+
// Pick the smallest preset window that fully covers the requested span.
435+
let window = if span <= 86_400 {
436+
TimeWindow::Last24h
437+
} else if span <= 604_800 {
438+
TimeWindow::Last7d
439+
} else if span <= 2_592_000 {
440+
TimeWindow::Last30d
441+
} else {
442+
TimeWindow::AllTime
443+
};
444+
445+
let wm = load_or_reset_window(env, &window);
446+
// Report whether the stored window actually overlaps the requested range.
447+
let window_matched = wm.window_start >= start_time && wm.window_start <= end_time.min(now);
448+
449+
let m = load_metrics(env);
450+
let stats = compute_stats(m);
451+
452+
PeriodAnalytics {
453+
start_time,
454+
end_time,
455+
window_matched,
456+
volume: wm.volume,
457+
trades_created: wm.trades_created,
458+
trades_completed: wm.trades_completed,
459+
trades_disputed: wm.trades_disputed,
460+
trades_cancelled: wm.trades_cancelled,
461+
success_rate_bps: stats.success_rate_bps,
462+
active_escrows: stats.active_trades,
463+
unique_participants: load_unique_count(env),
464+
total_fees_collected: stats.metrics.total_fees_collected,
465+
}
466+
}
467+
468+
// ---------------------------------------------------------------------------
469+
// New return types for the named query functions
470+
// ---------------------------------------------------------------------------
471+
472+
/// Returned by `get_volume_stats`.
473+
#[contracttype]
474+
#[derive(Clone, Debug, Eq, PartialEq)]
475+
pub struct VolumeStats {
476+
pub trades_created: u64,
477+
/// All-time cumulative value (stroops).
478+
pub total_value: u64,
479+
/// Volume within the requested time window (stroops).
480+
pub window_volume: u64,
481+
/// Trades created within the requested time window.
482+
pub window_trades: u64,
483+
}
484+
485+
/// Returned by `get_success_rate`.
486+
#[contracttype]
487+
#[derive(Clone, Debug, Eq, PartialEq)]
488+
pub struct SuccessRateStats {
489+
pub completed: u64,
490+
pub disputed: u64,
491+
pub cancelled: u64,
492+
/// completed / (completed + disputed + cancelled) * 10_000.
493+
pub success_rate_bps: u32,
494+
}
495+
496+
/// Returned by `get_platform_usage`.
497+
#[contracttype]
498+
#[derive(Clone, Debug, Eq, PartialEq)]
499+
pub struct PlatformUsage {
500+
pub active_escrows: u64,
501+
pub unique_participants: u64,
502+
pub total_fees_collected: u64,
503+
}
504+
505+
/// Returned by `get_analytics_by_period`.
506+
#[contracttype]
507+
#[derive(Clone, Debug, Eq, PartialEq)]
508+
pub struct PeriodAnalytics {
509+
pub start_time: u64,
510+
pub end_time: u64,
511+
/// `true` when a stored window overlaps the requested range.
512+
pub window_matched: bool,
513+
pub volume: u64,
514+
pub trades_created: u64,
515+
pub trades_completed: u64,
516+
pub trades_disputed: u64,
517+
pub trades_cancelled: u64,
518+
pub success_rate_bps: u32,
519+
pub active_escrows: u64,
520+
pub unique_participants: u64,
521+
pub total_fees_collected: u64,
522+
}

contract/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,6 +1874,26 @@ impl StellarEscrowContract {
18741874
analytics::analytics_query(&env, window)
18751875
}
18761876

1877+
/// Volume stats: total trades, total value, and window-scoped volume.
1878+
pub fn get_volume_stats(env: Env, window: analytics::TimeWindow) -> analytics::VolumeStats {
1879+
analytics::get_volume_stats(&env, window)
1880+
}
1881+
1882+
/// Success-rate breakdown: completed / disputed / cancelled counts + rate in bps.
1883+
pub fn get_success_rate(env: Env) -> analytics::SuccessRateStats {
1884+
analytics::get_success_rate(&env)
1885+
}
1886+
1887+
/// Platform usage: active escrows, unique participants, fees collected.
1888+
pub fn get_platform_usage(env: Env) -> analytics::PlatformUsage {
1889+
analytics::get_platform_usage(&env)
1890+
}
1891+
1892+
/// All metrics scoped to an arbitrary ledger-timestamp range [start_time, end_time].
1893+
pub fn get_analytics_by_period(env: Env, start_time: u64, end_time: u64) -> analytics::PeriodAnalytics {
1894+
analytics::get_analytics_by_period(&env, start_time, end_time)
1895+
}
1896+
18771897
// AMM — Automated Market Making
18781898
// -----------------------------------------------------------------------
18791899

0 commit comments

Comments
 (0)