1- // SPDX-License-Identifier: MPL-2.0
1+ // SPDX-License-Identifier: MPL-2.0
22#include " pch.h"
33
44#include " WalletPage.h"
1111#include < chrono>
1212#include < cmath>
1313#include < cstdio>
14+ #include < cstdlib>
1415#include < cwchar>
1516#include < iterator>
17+ #include < string_view>
1618
1719#include " Log.h"
1820#include " MainWindow.xaml.h"
@@ -191,6 +193,167 @@ UIElement BuildReliabilityChart(std::vector<double> weights, std::vector<double>
191193 return host;
192194}
193195
196+ // ---- --preview-ui sample data ----------------------------------------------
197+ //
198+ // WHY THIS EXISTS. --preview-ui deliberately makes no API call (Startup.h): with
199+ // no token every request would be an unauthenticated hit on the production API,
200+ // so every panel on this destination renders EMPTY. That is worth looking at and
201+ // it is not the populated one - and a wallet card, a payouts table, a points
202+ // breakdown, a reliability chart and a leaderboard row are exactly the things
203+ // where "reads correct" has never been evidence on this project. Without this
204+ // they could ship having never been drawn.
205+ //
206+ // So: with --preview-ui ALREADY on, URNETWORK_PREVIEW_SAMPLE=1 pushes obviously
207+ // synthetic rows through the SAME Apply* functions the API path uses. Two gates,
208+ // no network, and a warning in the log every time, because data on screen that
209+ // did not come from the server is the one thing a screenshot cannot show you.
210+ //
211+ // The values are deliberately not plausible as anybody's account: the wallet
212+ // addresses spell what they are.
213+ constexpr const char * kSampleOwnNetworkId = " sample-network-self" ;
214+
215+ bool PreviewSample () {
216+ static const bool on = [] {
217+ size_t len = 0 ;
218+ char value[16 ]{};
219+ if (getenv_s (&len, value, sizeof (value), " URNETWORK_PREVIEW_SAMPLE" ) != 0 || len == 0 ) {
220+ return false ;
221+ }
222+ const bool enabled = std::string_view (value) == " 1" ;
223+ if (enabled) {
224+ urnw::LogWarn (
225+ " preview-sample: rendering SYNTHETIC wallet/leaderboard rows - none of "
226+ " this came from the api" );
227+ }
228+ return enabled;
229+ }();
230+ return on;
231+ }
232+
233+ std::vector<urnet::AccountWallet> SampleWallets () {
234+ urnet::AccountWallet sol;
235+ sol.wallet_id = " 5a3e0000-0000-4000-8000-00000000501a" ;
236+ sol.blockchain = urnet::SOL ;
237+ sol.wallet_address = " SAMPLEwa11etADDRESSnotREALsolana000000SAMPLE" ;
238+ sol.default_token_type = " USDC" ;
239+ sol.active = true ;
240+ sol.has_seeker_token = true ;
241+
242+ urnet::AccountWallet matic;
243+ matic.wallet_id = " 5a3e0000-0000-4000-8000-0000000a71c0" ;
244+ matic.blockchain = urnet::MATIC ;
245+ matic.wallet_address = " 0xSAMPLEwa11etADDRESSnotREALpolygonSAMPLE" ;
246+ matic.default_token_type = " USDC" ;
247+ matic.active = true ;
248+
249+ urnet::AccountWallet tao;
250+ tao.wallet_id = " 5a3e0000-0000-4000-8000-00000000007a" ;
251+ tao.blockchain = urnet::TAO ;
252+ tao.wallet_address = " 5SAMPLEwa11etADDRESSnotREALbittensorSAMPLE" ;
253+ tao.default_token_type = " USDC" ;
254+ tao.active = true ;
255+ return {sol, matic, tao};
256+ }
257+
258+ std::vector<urnet::AccountPayment> SamplePayments () {
259+ auto make = [](const char * id, const char * wallet, const char * chain, double amount,
260+ const char * when, const char * tx, bool completed) {
261+ urnet::AccountPayment p;
262+ p.payment_id = id;
263+ p.wallet_id = wallet;
264+ p.blockchain = chain;
265+ p.token_type = " USDC" ;
266+ p.token_amount = amount;
267+ p.complete_time = completed ? std::optional<std::string>(when) : std::nullopt ;
268+ p.create_time = when;
269+ p.completed = completed;
270+ p.wallet_address = " SAMPLEwa11etADDRESSnotREALsolana000000SAMPLE" ;
271+ if (tx) p.tx_hash = tx;
272+ p.payout_byte_count = 8'123'456'789 ;
273+ return p;
274+ };
275+ return {
276+ make (" 5a3e0000-0000-4000-8000-0000000000a1" , " 5a3e0000-0000-4000-8000-00000000501a" , urnet::SOL , 0 , " 2026-08-02T00:00:00Z" , nullptr ,
277+ false ),
278+ make (" 5a3e0000-0000-4000-8000-0000000000a2" , " 5a3e0000-0000-4000-8000-00000000501a" , urnet::SOL , 12.34 , " 2026-07-26T00:00:00Z" ,
279+ " SAMPLEtxHASHnotREAL2222222222222222" , true ),
280+ make (" 5a3e0000-0000-4000-8000-0000000000a3" , " 5a3e0000-0000-4000-8000-0000000a71c0" , urnet::MATIC , 8.90 , " 2026-07-19T00:00:00Z" ,
281+ " 0xSAMPLEtxHASHnotREAL33333333333333" , true ),
282+ };
283+ }
284+
285+ std::vector<urnet::AccountPoint> SamplePoints () {
286+ auto make = [](const char * event, int64_t nanoPoints, const char * paymentId) {
287+ urnet::AccountPoint p;
288+ p.event = event;
289+ p.point_value = nanoPoints;
290+ p.account_payment_id = paymentId;
291+ return p;
292+ };
293+ const int64_t nano = 1'000'000'000 ;
294+ return {
295+ make (kEventPayout , 12'340 * nano, " 5a3e0000-0000-4000-8000-0000000000a2" ),
296+ make (kEventReferral , 2'100 * nano, " 5a3e0000-0000-4000-8000-0000000000a2" ),
297+ make (kEventReliability , 860 * nano, " 5a3e0000-0000-4000-8000-0000000000a2" ),
298+ make (kEventMultiplier , 12'340 * nano, " 5a3e0000-0000-4000-8000-0000000000a2" ),
299+ make (kEventPayout , 8'900 * nano, " 5a3e0000-0000-4000-8000-0000000000a3" ),
300+ make (kEventReliability , 415 * nano, " 5a3e0000-0000-4000-8000-0000000000a3" ),
301+ };
302+ }
303+
304+ urnet::ReliabilityWindow SampleReliability () {
305+ urnet::ReliabilityWindow rw;
306+ rw.mean_reliability_weight = 0.62 ;
307+ rw.max_total_client_count = 18 ;
308+ rw.max_client_count = 11 ;
309+ rw.bucket_duration_seconds = 3600 ;
310+ std::vector<double > weights;
311+ std::vector<int64_t > totals;
312+ for (int i = 0 ; i < 24 ; ++i) {
313+ weights.push_back (0.35 + 0.45 * std::sin (i * 0.5 ) * std::sin (i * 0.5 ) + 0.05 * (i % 3 ));
314+ totals.push_back (4 + (i * 7 ) % 15 );
315+ }
316+ rw.reliability_weights = weights;
317+ rw.total_client_counts = totals;
318+ rw.client_counts = totals;
319+
320+ urnet::CountryMultiplierList countries;
321+ auto country = [](const char * name, const char * code, double multiplier) {
322+ urnet::CountryMultiplier cm;
323+ cm.country_location_id = std::string (" sample-" ) + code;
324+ cm.country = name;
325+ cm.country_code = code;
326+ cm.reliability_multiplier = multiplier;
327+ return cm;
328+ };
329+ countries.push_back (country (" Sample Republic" , " SR" , 3.25 ));
330+ countries.push_back (country (" Sampleland" , " SL" , 2.00 ));
331+ countries.push_back (country (" Samplia" , " SA" , 1.40 ));
332+ countries.push_back (country (" Not Multiplied" , " NM" , 1.00 )); // filtered out
333+ rw.country_multipliers = countries;
334+ return rw;
335+ }
336+
337+ urnet::LeaderboardEarnersList SampleEarners () {
338+ auto make = [](const char * id, const char * name, float mib, bool isPublic,
339+ bool profanity = false ) {
340+ urnet::LeaderboardEarner e;
341+ e.network_id = id;
342+ e.network_name = name;
343+ e.net_mib_count = mib;
344+ e.is_public = isPublic;
345+ e.contains_profanity = profanity;
346+ return e;
347+ };
348+ return {
349+ make (" sample-net-1" , " sample-alpha" , 4'194'304 .0f , true ),
350+ make (" sample-net-2" , " hidden-should-not-render" , 2'097'152 .0f , /* isPublic=*/ false ),
351+ make (kSampleOwnNetworkId , " sample-my-network" , 786'432 .0f , true ),
352+ make (" sample-net-4" , " profane-should-not-render" , 524'288 .0f , true , /* profanity=*/ true ),
353+ make (" sample-net-5" , " sample-epsilon" , 131'072 .0f , true ),
354+ };
355+ }
356+
194357} // namespace
195358
196359WalletPage::WalletPage (winrt::URnetwork::implementation::MainWindow& window)
@@ -519,10 +682,10 @@ winrt::fire_and_forget WalletPage::ShowWalletDetail(urnet::AccountWallet wallet)
519682 [weak] {
520683 if (auto w = weak.get ()) w->wallet ().RefreshAfterWalletChange ();
521684 },
522- [weak](hstring message, bool ok) {
685+ [weak](hstring message) {
686+ // success only: the sheet is gone by the time this runs
523687 if (auto w = weak.get ()) {
524- w->wallet ().snackbar_ .Show (message, ok ? InfoBarSeverity::Success
525- : InfoBarSeverity::Error);
688+ w->wallet ().snackbar_ .Show (message, InfoBarSeverity::Success);
526689 }
527690 });
528691 co_await self->wallet ().walletSheet_ ->Dialog ().ShowAsync ();
@@ -571,9 +734,11 @@ void WalletPage::ApplyPoints(std::vector<urnet::AccountPoint> const& points, Fet
571734 w_.AccountPointsStatusText ().Text (Loc (" something_went_wrong" ));
572735 w_.AccountPointsStatusText ().Visibility (Visibility::Visible);
573736 w_.AccountPointsPanel ().Children ().Clear ();
737+ w_.AccountPointsCard ().Visibility (Visibility::Collapsed);
574738 return ;
575739 }
576740 w_.AccountPointsStatusText ().Visibility (Visibility::Collapsed);
741+ w_.AccountPointsCard ().Visibility (Visibility::Visible);
577742 RebuildPointsCard ();
578743 RebuildWalletCards (); // the per-wallet totals do not move, but the seeker row does
579744}
@@ -736,6 +901,8 @@ void WalletPage::ApplyReliability(std::optional<urnet::ReliabilityWindow> window
736901 reliability_ = window;
737902 auto panel = w_.ReliabilityPanel ();
738903 panel.Children ().Clear ();
904+ // an empty card is a bar of nothing sitting under its own status line
905+ w_.ReliabilityCard ().Visibility (Visibility::Collapsed);
739906
740907 if (state == Fetch::Failed) {
741908 w_.ReliabilityStatusText ().Text (Loc (" something_went_wrong" ));
@@ -748,6 +915,7 @@ void WalletPage::ApplyReliability(std::optional<urnet::ReliabilityWindow> window
748915 return ;
749916 }
750917 w_.ReliabilityStatusText ().Visibility (Visibility::Collapsed);
918+ w_.ReliabilityCard ().Visibility (Visibility::Visible);
751919 auto const & rw = *reliability_;
752920
753921 // the two figures the window is summarised by
@@ -856,7 +1024,7 @@ void WalletPage::ApplySeekerState() {
8561024// Claim the 2x multiplier by proving the wallet holds the Seeker / Saga token
8571025// (android SettingsScreen.signAndVerifySeekerHolder). The wallet signs a
8581026// timestamped challenge through the ur.io/wallet-connect browser bridge and the
859- // signed triple goes to Api.verifySeekerHolder — the address alone proves
1027+ // signed triple goes to Api.verifySeekerHolder — the address alone proves
8601028// nothing, so there is no shortcut past the signature.
8611029//
8621030// android puts up its wallet picker through Mobile Wallet Adapter; the browser
@@ -949,7 +1117,7 @@ void WalletPage::ApplySeekerResult(bool ok, std::string const& serverError) {
9491117// ---- connect wallet (external wallet, by address) -------------------------
9501118// Paste an address; the server validates it per chain and the first chain that
9511119// accepts it wins. Bittensor connects by address only (no signature), matching
952- // apple/android — the signed bridge flow is sign-in, not wallet connect.
1120+ // apple/android — the signed bridge flow is sign-in, not wallet connect.
9531121
9541122void WalletPage::OnWalletAddressChanged (IInspectable const &, TextChangedEventArgs const &) {
9551123 walletValidation_ = {};
@@ -1063,6 +1231,17 @@ void WalletPage::ShowPreviewSnackbar() {
10631231// otherwise sit on "Loading..." forever - which is exactly what a hang looks
10641232// like. Settle them all on their empty state instead.
10651233void WalletPage::ShowPreviewWalletState () {
1234+ if (PreviewSample ()) {
1235+ ApplyWallets (SampleWallets (), Fetch::Ready);
1236+ ApplyPayoutWalletId (" 5a3e0000-0000-4000-8000-00000000501a" );
1237+ ApplyTransferStats (41'237'481'984 , /* ok=*/ true );
1238+ ApplyWalletBalance (1'234'500'000'000 , /* ok=*/ true );
1239+ ApplyReferrals (7 , /* ok=*/ true );
1240+ ApplyPayments (SamplePayments (), Fetch::Ready);
1241+ ApplyPoints (SamplePoints (), Fetch::Ready);
1242+ ApplyReliability (SampleReliability (), Fetch::Ready);
1243+ return ;
1244+ }
10661245 ApplyWallets ({}, Fetch::Ready);
10671246 ApplyTransferStats (0 , /* ok=*/ false );
10681247 ApplyWalletBalance (0 , /* ok=*/ false );
@@ -1075,6 +1254,16 @@ void WalletPage::ShowPreviewWalletState() {
10751254// ---- leaderboard ---------------------------------------------------------
10761255
10771256void WalletPage::ShowPreviewLeaderboardState () {
1257+ if (PreviewSample ()) {
1258+ ownNetworkId_ = kSampleOwnNetworkId ;
1259+ urnet::NetworkRanking ranking;
1260+ ranking.leaderboard_rank = 42 ;
1261+ ranking.net_mib_count = 786432 .0f ; // 768 GiB
1262+ ranking.leaderboard_public = true ;
1263+ ApplyRanking (ranking, /* ok=*/ true );
1264+ ApplyLeaderboard (SampleEarners (), Fetch::Ready);
1265+ return ;
1266+ }
10781267 ApplyRanking ({}, /* ok=*/ false );
10791268 ApplyLeaderboard ({}, Fetch::Ready);
10801269}
0 commit comments