feat: afficher l'équivalent DU pour le volume total et la moyenne de transaction
ci/woodpecker/push/woodpecker Pipeline was successful

- SubsquidAdapter : fetchCurrentUD() interroge universalDividends (fallback 11.78 Ğ1)
- DataService : getCurrentUD() avec cache 1h, inclus dans DataResult
- StatsPanel : formatDU() + affichage "≈ X DU" sous le volume total
  et "≈ X Ğ1 / tx · ≈ Y DU / tx" sous le compteur de transactions
- DU actuel Ğ1v2 : 11.78 Ğ1 (bloc 225874)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
syoul
2026-03-23 23:13:00 +01:00
parent bea7cbe60f
commit 45080d83ac
4 changed files with 57 additions and 5 deletions
+18 -2
View File
@@ -12,7 +12,7 @@
* Pour activer : définir VITE_USE_LIVE_API=true dans .env.local
*/
import { fetchTransfers, buildIdentityKeyMap } from './adapters/SubsquidAdapter';
import { fetchTransfers, buildIdentityKeyMap, fetchCurrentUD } from './adapters/SubsquidAdapter';
import { resolveGeoByKeys } from './adapters/CesiumAdapter';
import {
getTransactionsForPeriod,
@@ -22,6 +22,16 @@ import {
const USE_LIVE_API = import.meta.env.VITE_USE_LIVE_API === 'true';
// Cache du DU courant, valide 1 heure (le DU change tous les ~6 mois)
let udCache: { value: number; expiresAt: number } | null = null;
async function getCurrentUD(): Promise<number> {
if (udCache && Date.now() < udCache.expiresAt) return udCache.value;
const value = await fetchCurrentUD();
udCache = { value, expiresAt: Date.now() + 60 * 60 * 1000 };
return value;
}
// Cache de la carte identité SS58→DuniterKey, valide 10 minutes
let keyMapCache: { map: Map<string, string>; expiresAt: number } | null = null;
@@ -106,6 +116,7 @@ export interface DataResult {
transactions: Transaction[]; // uniquement géolocalisées → heatmap
stats: PeriodStats;
source: 'live' | 'mock';
currentUD: number; // valeur du DU courant en Ğ1
}
export async function fetchData(periodDays: number): Promise<DataResult> {
@@ -117,10 +128,14 @@ export async function fetchData(periodDays: number): Promise<DataResult> {
transactions,
stats: { ...base, geoCount: transactions.length },
source: 'mock',
currentUD: 11.78,
};
}
const { geolocated, totalCount, totalVolume } = await fetchLiveTransactions(periodDays);
const [{ geolocated, totalCount, totalVolume }, currentUD] = await Promise.all([
fetchLiveTransactions(periodDays),
getCurrentUD(),
]);
const base = computeStats(geolocated);
return {
@@ -132,5 +147,6 @@ export async function fetchData(periodDays: number): Promise<DataResult> {
topCities: base.topCities,
},
source: 'live',
currentUD,
};
}
+21
View File
@@ -153,6 +153,27 @@ export async function buildIdentityKeyMap(): Promise<Map<string, string>> {
return result;
}
/** Retourne la valeur du DU courant en Ğ1 (ex : 11.78). Fallback hardcodé si indisponible. */
export async function fetchCurrentUD(): Promise<number> {
const UD_FALLBACK = 11.78; // valeur au bloc 225874 — mis à jour si la requête échoue
try {
const response = await fetch(SUBSQUID_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: `{ universalDividends(orderBy: BLOCK_NUMBER_DESC, first: 1) { nodes { amount } } }`,
}),
});
if (!response.ok) return UD_FALLBACK;
const raw = await response.json();
const amountStr: string | undefined = raw?.data?.universalDividends?.nodes?.[0]?.amount;
if (!amountStr) return UD_FALLBACK;
return parseInt(amountStr, 10) / 100;
} catch {
return UD_FALLBACK;
}
}
export interface FetchTransfersResult {
transfers: RawTransfer[];
totalCount: number;