Nouveau Client en Next.js et Silkaj v0.12.1

J’ai développé un nouveau Client destiné uniquement au réseau des épiceries 100% Ğ1 de la Drôme. C’est une appli qui sert de caisse pour chaque épicerie, permet la gestion des comptes portefeuille internes au réseau, et réalise les paiements via la Blockchain en utilisant en back Silkaj v0.12.1.

Résumé

:convenience_store: PMA Guichet v1.5 - Présentation

Application web de caisse pour les épiceries en 100% Ğ1 (blockchain Duniter)

Architecture Technique

Stack moderne : Next.js 14 (App Router) + TypeScript + React 18, base de données MariaDB centralisée avec Prisma ORM, cache Redis distribué, containerisation Docker complète, intégration Silkaj v0.12.1 pour paiements blockchain Ğ1.

Architecture cloud-native : Données critiques (DEWIF, tickets) stockées en base de données centralisée, permettant déploiement multi-instances avec synchronisation automatique, volumes Docker persistants pour relevés CSV et backups.

Fonctions Principales

Paiements Ğ1 : Système de paiement sécurisé via Silkaj avec validation PIN (4 chiffres), génération automatique de fichiers DEWIF (74 octets), double stockage pour compatibilité, failover multi-nœuds blockchain avec basculement automatique.

Gestion de tickets : Création multi-lignes avec support multi-devises (Ğ1 ou DU), conversion automatique, paiement immédiat depuis panier épicier ou interface admin, annulation avec permissions granulaires, historique complet cloud-native.

Dividende Épicerie (DE) : Calcul automatique via formule TRM, distribution équitable entre épiciers actifs, dashboard statistiques temps réel, versement automatique via scripts CRON.

Communication interne : Système de notifications push temps réel, messagerie privée PMA ↔ PMA (interface style WhatsApp), annonces réseau broadcast (SUPERADMIN), documents juridiques versionnés avec vote démocratique (seuil 60%), acceptation traçable avec IP.

Gestion des comptes : Création automatique avec génération clés Ğ1 (Diceware), génération cartes PMA en PDF (EAN-13), scanner codes-barres intégré (3 points), graphique d’évolution de solde (Recharts + CSV Silkaj), gestion multi-épiceries avec relations principales/secondaires.

Administration : 8 pages admin opérationnelles (comptes, épiceries, tickets, paiements, FAQ, configuration, monitoring, administrateurs), système multi-rôles (SUPERADMIN, ADMIN, ÉPICIER, VISITEUR) avec permissions granulaires, authentification JWT sécurisée avec déconnexion automatique, monitoring système complet avec logs Winston et métriques Redis.

Je peux vous en parler plus en détails si besoin mais j’ai une question purement technique concernant le réseau v1.

Pour un compte portefeuille en particulier, j’ai deux soldes différents :

  • Avec Cesium : 1 207,60 Ğ1
  • Avec Silkaj : 1 337.71 Ğ1
Sortie Silkaj

Balance of pubkey : 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf2kDcrL:9YE
Blockchain (unit|relative) │ 1207.6 Ğ1 | 105.19 UD Ğ1
Pending transaction (unit|relative) │ 130.11 Ğ1 | 11.33 UD Ğ1
Total balance (unit|relative) │ 1337.71 Ğ1 | 116.53 UD Ğ1

Ma question : Comment obtenir des détails sur cette transaction en pending ? De qui ? Quand ?

Une petite aide serait la bienvenue, @Moul , merci.

1 Like

Tu peux surement voir les transferts en attente/pas entièrement validés dans la vue historique de Cesium. Elles ne sont pas affichées dans l’historique des transferts de Silkaj.
Après, c’est visible pour pas longtemps.

1 Like

merci de ta réponse rapide @moul , mais non, rien dans l’historique de cesium

Plusieurs heures après, j’ai toujours cette transaction en pending et rien sur cesium

Logs de mon app

[LOGIN] :white_check_mark: PIN validé
[2025-11-06T16:59:05.404Z] [INFO] [API Statuts Documents] Statuts récupérés pour épicier 1 - 1 documents
[2025-11-06T16:59:11.369Z] [INFO] Récupération des informations du compte DE SYSTEME
[2025-11-06T16:59:11.464Z] [INFO] Compte système trouvé: Compte DE System (999999)
[2025-11-06T16:59:11.464Z] [INFO] Récupération solde pour clé: 8jWwwyRZMrRsidfVpsGd…
:magnifying_glass_tilted_left: [getSilkajBalance] Récupération du solde pour: 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf2kDcrL:9YE
:memo: Format legacy détecté (pubkey:checksum), extraction de la clé Base58: 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf2kDcrL
:satellite_antenna: Solde non trouvé en cache, interrogation Silkaj…
:satellite_antenna: Exécution de la commande Silkaj avec failover automatique
[2025-11-06T16:59:11.490Z] [INFO] :white_check_mark: Configuration des nœuds chargée depuis /app/config/silkaj/nodes.json
[2025-11-06T16:59:11.491Z] [INFO] :magnifying_glass_tilted_left: Recherche d’un nœud Ğ1 disponible…
[2025-11-06T16:59:11.705Z] [INFO] :white_check_mark: Nœud g1.duniter.org disponible (HTTPS 200, 214ms)
[2025-11-06T16:59:11.705Z] [INFO] :white_check_mark: Nœud sélectionné: g1.duniter.org (214ms)
[2025-11-06T16:59:11.706Z] [INFO] :satellite_antenna: Tentative 1/3: g1.duniter.org
[2025-11-06T16:59:13.653Z] [INFO] :white_check_mark: Commande Silkaj réussie sur g1.duniter.org
:white_check_mark: Réponse Silkaj (stdout): │─────────────────────────────────────│────────────────────────────────────────│
│ Balance of pubkey │ 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf │
│ │ 2kDcrL:9YE │
│─────────────────────────────────────│────────────────────────────────────────│
│ Blockchain (unit|relative) │ 1207.6 Ğ1 | 105.19 UD Ğ1 │
│─────────────────────────────────────│────────────────────────────────────────│
│ Pending transaction (unit|relative) │ 130.11 Ğ1 | 11.33 UD Ğ1 │
│─────────────────────────────────────│────────────────────────────────────────│
│ Total balance (unit|relative) │ 1337.71 Ğ1 | 116.53 UD Ğ1 │
│─────────────────────────────────────│────────────────────────────────────────│
│ Total relative to M/N │ 0.07 x M/N │
│─────────────────────────────────────│────────────────────────────────────────│
:clipboard: [parseSilkajBalance] Lignes à parser (12): [
‘│─────────────────────────────────────│────────────────────────────────────────│’,
‘│ Balance of pubkey │ 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf │’,
‘│ │ 2kDcrL:9YE │’,
‘│─────────────────────────────────────│────────────────────────────────────────│’,
‘│ Blockchain (unit|relative) │ 1207.6 Ğ1 | 105.19 UD Ğ1 │’,
‘│─────────────────────────────────────│────────────────────────────────────────│’,
‘│ Pending transaction (unit|relative) │ 130.11 Ğ1 | 11.33 UD Ğ1 │’,
‘│─────────────────────────────────────│────────────────────────────────────────│’,
‘│ Total balance (unit|relative) │ 1337.71 Ğ1 | 116.53 UD Ğ1 │’,
‘│─────────────────────────────────────│────────────────────────────────────────│’,
‘│ Total relative to M/N │ 0.07 x M/N │’,
‘│─────────────────────────────────────│────────────────────────────────────────│’
]
:bar_chart: [getSilkajBalance] Résultat parsing: {
amount: 1337.71,
udAmount: 116.53,
status: ‘found’,
outputPreview: ‘│─────────────────────────────────────│────────────────────────────────────────│\n’ +
‘│ Balance of pubkey │ 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf │\n’ +
'│ ’
}
:white_check_mark: Solde sauvegardé dans le cache Redis (TTL: 5 min) - G1: 1337.71, DU: 116.53
[2025-11-06T16:59:13.659Z] [INFO] Solde récupéré: 1337.71 Ğ1, 116.53 DU
[2025-11-06T16:59:17.188Z] [INFO] Récupération des informations du compte DE SYSTEME (forceRefresh=true)
[2025-11-06T16:59:17.280Z] [INFO] Compte système trouvé: Compte DE System (999999)
[2025-11-06T16:59:17.280Z] [INFO] Récupération solde pour clé: 8jWwwyRZMrRsidfVpsGd…
[2025-11-06T16:59:17.281Z] [INFO] :counterclockwise_arrows_button: Force refresh activé - Invalidation des caches…
[2025-11-06T16:59:17.284Z] [INFO] :white_check_mark: Caches invalidés, récupération depuis la blockchain…
:magnifying_glass_tilted_left: [getSilkajBalance] Récupération du solde pour: 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf2kDcrL:9YE
:memo: Format legacy détecté (pubkey:checksum), extraction de la clé Base58: 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf2kDcrL
:satellite_antenna: Solde non trouvé en cache, interrogation Silkaj…
:satellite_antenna: Exécution de la commande Silkaj avec failover automatique
[2025-11-06T16:59:17.288Z] [INFO] :satellite_antenna: Tentative 1/3: g1.duniter.org
[2025-11-06T16:59:19.113Z] [INFO] :white_check_mark: Commande Silkaj réussie sur g1.duniter.org
:white_check_mark: Réponse Silkaj (stdout): │─────────────────────────────────────│────────────────────────────────────────│
│ Balance of pubkey │ 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf │
│ │ 2kDcrL:9YE │
│─────────────────────────────────────│────────────────────────────────────────│
│ Blockchain (unit|relative) │ 1207.6 Ğ1 | 105.19 UD Ğ1 │
│─────────────────────────────────────│────────────────────────────────────────│
│ Pending transaction (unit|relative) │ 130.11 Ğ1 | 11.33 UD Ğ1 │
│─────────────────────────────────────│────────────────────────────────────────│
│ Total balance (unit|relative) │ 1337.71 Ğ1 | 116.53 UD Ğ1 │
│─────────────────────────────────────│────────────────────────────────────────│
│ Total relative to M/N │ 0.07 x M/N │
│─────────────────────────────────────│────────────────────────────────────────│
:clipboard: [parseSilkajBalance] Lignes à parser (12): [
‘│─────────────────────────────────────│────────────────────────────────────────│’,
‘│ Balance of pubkey │ 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf │’,
‘│ │ 2kDcrL:9YE │’,
‘│─────────────────────────────────────│────────────────────────────────────────│’,
‘│ Blockchain (unit|relative) │ 1207.6 Ğ1 | 105.19 UD Ğ1 │’,
‘│─────────────────────────────────────│────────────────────────────────────────│’,
‘│ Pending transaction (unit|relative) │ 130.11 Ğ1 | 11.33 UD Ğ1 │’,
‘│─────────────────────────────────────│────────────────────────────────────────│’,
‘│ Total balance (unit|relative) │ 1337.71 Ğ1 | 116.53 UD Ğ1 │’,
‘│─────────────────────────────────────│────────────────────────────────────────│’,
‘│ Total relative to M/N │ 0.07 x M/N │’,
‘│─────────────────────────────────────│────────────────────────────────────────│’
]
:bar_chart: [getSilkajBalance] Résultat parsing: {
amount: 1337.71,
udAmount: 116.53,
status: ‘found’,
outputPreview: ‘│─────────────────────────────────────│────────────────────────────────────────│\n’ +
‘│ Balance of pubkey │ 8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf │\n’ +
'│ ’
}
:white_check_mark: Solde sauvegardé dans le cache Redis (TTL: 5 min) - G1: 1337.71, DU: 116.53
[2025-11-06T16:59:19.116Z] [INFO] Solde récupéré: 1337.71 Ğ1, 116.53 DU

y a pas un moyen d’interroger la blockchain plus en détails ?

Pour plus de détails, tu peux observer le résultat de :

curl https://g1.duniter.org/tx/history/8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf2kDcrL/pending

Sur mon nœud, il n’y a pas de transferts en attente.

1 Like

j’ai essayé plusieurs noeud et hop !!! Merci @Moul :smiling_face_with_three_hearts: pour la commande, je vais cherché pour trouver d"ou vient cet écart… PS: le Noeud de @kimamila est encore non synchro

curl https://g1.e-is.pro/tx/history/8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf2kDcrL/pending

Résumé

“pending”: [
{
“version”: 10,
“locktime”: 0,
“blockstamp”: “879595-00000019A75AD1F405EAD33E8ED2C1FB73CF4383910876C32FBA150B4621066E”,
“blockstampTime”: 1762370887,
“issuers”: [
“GxWEZrK4KmvLPYtjQpqQgXZ1N3wN3kVdFgakWs3oNW6q”
],
“inputs”: [
“5455:0:T:A8B9E11322ABBE34BE38EEA601144D4388D424A995501C2D39DD087D1C15F681:1”
],
“outputs”: [
“4941:0:SIG(8jWwwyRZMrRsidfVpsGdQGC5zd8pbRDTHrYDwf2kDcrL)”,
“514:0:SIG(GxWEZrK4KmvLPYtjQpqQgXZ1N3wN3kVdFgakWs3oNW6q)”
],
“unlocks”: [
“0:SIG(0)”
],
“signatures”: [
“h4hj8WH3eFIlXBx5foh6oJLblhLfiQAFpb7wb11RFCuoTBTvdMJdxPwSsLA1Jn9MwSqi+OT7Tf/oa7Y2OeuYCA==”
],
“comment”: “”,
“hash”: “2DC94A9C1CDD194B7367C6E2831B9E11E24CE86E2AF2DE10D2AEC157CBBFA57D”,
“time”: 1762371429,
“block_number”: 879597

merci pour votre aide ! :folded_hands:

Effectivement, grâce à ces observations avec curl https://g1.duniter.org/tx/history/.../pending, j’ai pu identifier un bug dans le calcul du solde “pending” de silkaj.

Le problème était que silkaj comptait incorrectement les transactions “sending” (en cours d’envoi) comme faisant partie du solde en attente. Dans mon cas, les transactions “Dividende Epicerie” avec leur change me revenaient gonflaient artificiellement le solde “pending” à 13011 Ğ1, alors qu’en réalité ces transactions sont des paiements sortants non encore confirmés dans la blockchain.

:bug: Bug corrigé dans silkaj v0.20.1

J’ai corrigé le code pour ne compter que les transactions entrantes (“pending” et “receiving”) dans le solde en attente, ce qui est cohérent avec l’API Duniter.

:sparkles: Nouvelles fonctionnalités de silkaj v0.20

Cette version apporte également de nombreuses fonctionnalités supplémentaires :

1. Support de l’authentification par clé privée en ligne de commande :


silkaj --private-key "..." money balance

silkaj --seed "..." checksum

2. Format de sortie JSON (--json) :


silkaj --json money balance <pubkey>

# Retourne du JSON structuré pour usage programmatique

3. Génération de rapports comptables :


# Génère un rapport PDF + JSON pour l'année ou le mois

silkaj money history <pubkey> --compta 2024

silkaj money history <pubkey> --compta 03-2024 --compta-output /path/

4. Vue “primal” pour accéder et parcourir les primo transactions :


silkaj --json money primal <pubkey>

Le code source est disponible ici : Fred / silkaj · GitLab

Encore merci @joss.rendall pour ton aide précieuse à mettre le doigt sur ce bug ! :rocket:

Je n’ai pas du tout interprété le résultat comme toi, ces transactions sont, pour moi, tout simplement en échecs, mais encore inscrite sur quelques noeuds…
Elles ne seront JAMAIS confirmées, et mon CRON a pu relancer celles qui n’étaient pas réellement inscrite en blockchain. De plus, sur mon historique, il n’a eu que 5 ou 6 jours d’echecs de ces versements; donc, env. 5 x 4.86 Ğ1 = 24,30 Ğ1, donc on est loin des 130.11 Ğ1 que je cherche à identifier…
J’ai juste besoin d’un solde conforme à l’API duniter.

@Frederic_Renault , je reviens pour ajouter une remarque tout à fait personnelle :

  • nous travaillons tous pour mettre à disposition des apps en opensource donc faire un fork n’est pas un souci
  • améliorer et corriger ces apps est également vivement apprécié

Mais je ne comprends pas pourquoi :

  • tu ne proposes pas une MR sur le repo existant de Silkja
  • tu conserves le nom de SILKAJ pour ta nouvelle apps

Avoir deux versions d’une app, sur les repos, qui porte le même nom et donc qui s’exécute avec les même commandes de base, ce n’est pas du travail d’équipe.
Libre à toi d’utiliser ton propre SILKAJ sur ton système, mais les autres utilisateurs vont devoir faire un choix : ta version ou celle du repo d’origine… trop compliqué pour l’user lambda de gérer les 2 en même temps.

Donc, pour mon cas personnel, je ne vais même pas essayer tes correctifs, et conserver le repo “officiel” de SILKAJ maintenu par @Moul

j’adapte donc, dans mon code, la pattern à rechercher dans la réponse de SILKAJ pour utiliser ‘Blockchain’ au lieu de ‘Total balance’ :

balance.ts
/**
 * Bibliothèque utilitaire - Récupération de soldes Silkaj
 * 
 * Fonction partagée pour récupérer le solde d'un compte Ğ1
 * Utilisable depuis les API routes sans faire de fetch HTTP interne
 */

import { exec } from 'child_process';
import { promisify } from 'util';
import { 
  getSilkajBalanceFromCache, 
  setSilkajBalanceInCache 
} from '@/lib/redis/silkaj-cache';

const execAsync = promisify(exec);

interface BalanceResult {
  success: boolean;
  publicKey: string;
  balance?: {
    amount: number;
    udAmount: number;
    currency: string;
    status: string;
    lastUpdate: string;
  };
  fromCache?: boolean;
  cacheAge?: number;
  error?: string;
  timestamp: string;
}

/**
 * Récupère le solde d'un compte Ğ1
 * @param publicKey - Clé publique Ğ1 (avec ou sans checksum)
 * @returns Résultat de la requête de solde
 */
export async function getSilkajBalance(publicKey: string): Promise<BalanceResult> {
  try {
    console.log(`🔍 [getSilkajBalance] Récupération du solde pour: ${publicKey}`);

    // Extraire la clé publique Base58 (avant les deux points si présent)
    // Silkaj 0.12.1 accepte uniquement la clé Base58 SANS le checksum
    let pubkeyBase58 = publicKey;
    if (publicKey.includes(':')) {
      const [pubkey] = publicKey.split(':');
      pubkeyBase58 = pubkey;
      console.log(`📝 Format legacy détecté (pubkey:checksum), extraction de la clé Base58: ${pubkeyBase58}`);
    }

    if (!pubkeyBase58 || pubkeyBase58.length < 40) {
      return {
        success: false,
        publicKey,
        error: 'Format de clé publique invalide',
        timestamp: new Date().toISOString()
      };
    }

    // 🚀 Vérifier le cache Redis d'abord
    const cachedBalance = await getSilkajBalanceFromCache(publicKey);
    if (cachedBalance) {
      console.log(`✅ Solde trouvé dans le cache Redis (age: ${Math.round((Date.now() - cachedBalance.lastUpdate) / 1000)}s)`);
      
      return {
        success: true,
        publicKey: publicKey,
        balance: {
          amount: cachedBalance.balance,
          udAmount: cachedBalance.ud || 0,
          currency: 'Ğ1',
          status: 'found',
          lastUpdate: new Date(cachedBalance.lastUpdate).toISOString()
        },
        fromCache: true,
        cacheAge: Math.round((Date.now() - cachedBalance.lastUpdate) / 1000),
        timestamp: new Date().toISOString()
      };
    }

    console.log(`📡 Solde non trouvé en cache, interrogation Silkaj...`);

    // Exécuter la commande Silkaj avec failover automatique
    const baseCommand = `money balance "${pubkeyBase58}"`;
    console.log(`📡 Exécution de la commande Silkaj avec failover automatique`);

    const { executeSilkajWithFailover } = await import('./node-manager');
    const { stdout, stderr } = await executeSilkajWithFailover(baseCommand);

    if (stderr) {
      console.error('Erreur Silkaj (stderr):', stderr);
    }

    if (stdout) {
      console.log('✅ Réponse Silkaj (stdout):', stdout);
    }

    // Parser la réponse de Silkaj
    const balance = parseSilkajBalance(stdout);
    
    // Log de débogage pour le parsing
    console.log(`📊 [getSilkajBalance] Résultat parsing:`, {
      amount: balance.amount,
      udAmount: balance.udAmount,
      status: balance.status,
      outputPreview: stdout?.substring(0, 200)
    });

    // 🚀 Sauvegarder dans le cache Redis si le solde a été trouvé
    if (balance.status === 'found' || balance.status === 'zero_balance') {
      const cached = await setSilkajBalanceInCache(
        publicKey,
        balance.amount,
        balance.udAmount && balance.udAmount > 0 ? balance.udAmount : null
      );
      if (cached) {
        console.log(`✅ Solde sauvegardé dans le cache Redis (TTL: 5 min) - G1: ${balance.amount}, DU: ${balance.udAmount || 'null'}`);
      }
    }

    return {
      success: true,
      publicKey: publicKey,
      balance: balance,
      fromCache: false,
      timestamp: new Date().toISOString()
    };

  } catch (error: any) {
    console.error('❌ Erreur Silkaj:', error);
    
    return {
      success: false,
      error: error.message || 'Erreur inconnue',
      publicKey: publicKey,
      timestamp: new Date().toISOString()
    };
  }
}

/**
 * Parse la réponse de Silkaj pour extraire le solde
 */
function parseSilkajBalance(output: string): any {
  try {
    // Analyser la sortie de Silkaj
    const lines = output.split('\n').filter(line => line.trim());
    
    // Log de débogage pour voir la sortie brute
    console.log(`📋 [parseSilkajBalance] Lignes à parser (${lines.length}):`, lines);
    
    // Chercher les informations de solde dans la sortie
    let balance = {
      amount: 0,
      udAmount: 0,
      currency: 'Ğ1',
      status: 'unknown',
      lastUpdate: new Date().toISOString()
    };

    // Préférence : valeurs confirmées blockchain (sans pending) si présentes
    let blockchainAmount: number | null = null;
    let blockchainDetected = false;

    // Patterns pour extraire les soldes depuis la sortie Silkaj formatée
    // IMPORTANT : Parcourir TOUTES les lignes pour trouver G1 et DU (peuvent être sur lignes séparées)
    for (const line of lines) {
      // Pattern pour la ligne Blockchain (soldes confirmés sans pending)
      const blockchainMatch = line.match(/Blockchain\s*\(unit\|relative\).*?│\s*(\d+\.?\d*)\s*Ğ1\s*\|\s*(\d+\.?\d*)\s*UD\s*Ğ1/i);
      if (blockchainMatch) {
        blockchainAmount = parseFloat(blockchainMatch[1]);
        const udValue = parseFloat(blockchainMatch[2]);
        balance.amount = blockchainAmount;
        balance.udAmount = udValue;
        balance.status = 'found';
        blockchainDetected = true;
        continue;
      }

      const blockchainG1OnlyMatch = line.match(/Blockchain\s*\(unit\|relative\).*?│\s*(\d+\.?\d*)\s*Ğ1/i);
      if (blockchainG1OnlyMatch) {
        blockchainAmount = parseFloat(blockchainG1OnlyMatch[1]);
        balance.amount = blockchainAmount;
        balance.status = 'found';
        blockchainDetected = true;
        // Continuer pour trouver une éventuelle ligne UD
        continue;
      }

      // Pattern pour "Total balance (unit|relative) │ 17115.21 Ğ1 | 1490.87 UD Ğ1" (format tabulaire)
      const balanceMatch = line.match(/Total balance.*?│\s*(\d+\.?\d*)\s*Ğ1\s*\|\s*(\d+\.?\d*)\s*UD\s*Ğ1/i);
      if (balanceMatch) {
        // Ne surcharger la valeur qu'en absence de ligne Blockchain
        const amountValue = parseFloat(balanceMatch[1]);
        const udValue = parseFloat(balanceMatch[2]);
        if (!blockchainDetected) {
          balance.amount = amountValue;
          balance.udAmount = udValue;
          balance.status = 'found';
        }
        continue; // Continuer pour vérifier s'il y a d'autres formats
      }
      
      // Pattern pour "Total balance (unit|relative) │ 17115.21 Ğ1" (sans DU sur même ligne)
      const g1Match = line.match(/Total balance.*?│\s*(\d+\.?\d*)\s*Ğ1/i);
      if (g1Match && !balance.amount) {
        balance.amount = parseFloat(g1Match[1]);
        balance.status = 'found';
        // Ne pas break, continuer pour chercher le DU
      }
      
      // Pattern pour DU sur une ligne séparée : "│ 1490.87 UD Ğ1" ou "1490.87 UD Ğ1"
      const udMatch = line.match(/│\s*(\d+\.?\d*)\s*UD\s*Ğ1|(\d+\.?\d*)\s*UD\s*Ğ1/i);
      if (udMatch) {
        const udValue = parseFloat(udMatch[1] || udMatch[2]);
        if (blockchainDetected) {
          balance.udAmount = udValue;
        } else if (!balance.udAmount) {
          balance.udAmount = udValue;
        }
        if (balance.amount > 0) {
          balance.status = 'found';
        }
      }
      
      // Pattern alternatif pour DU : "X.XX UD" ou "(X.XX UD)"
      const udMatchAlt = line.match(/\((\d+\.?\d*)\s*UD\)|(\d+\.?\d*)\s*UD(?!\s*Ğ1)/i);
      if (udMatchAlt && !balance.udAmount) {
        const udValue = parseFloat(udMatchAlt[1] || udMatchAlt[2]);
        if (blockchainDetected || !balance.udAmount) {
          balance.udAmount = udValue;
        }
      }
      
      // Pattern alternatif pour les montants (ex: "100.00 Ğ1" ou "Balance: 100.00")
      const amountMatch = line.match(/(\d+\.?\d*)\s*Ğ1(?!\s*\|)/i);
      if (amountMatch && !balance.amount) {
        balance.amount = parseFloat(amountMatch[1]);
        balance.status = 'found';
      }
    }

    if (blockchainDetected) {
      console.log(`✅ [parseSilkajBalance] Utilisation du solde Blockchain confirmé (pending exclus)`);
    }

    // Si aucun montant trouvé, vérifier si la clé existe
    if (balance.amount === 0 && balance.status === 'unknown') {
      if (output.toLowerCase().includes('no balance') || output.toLowerCase().includes('zero')) {
        balance.status = 'zero_balance';
      } else if (output.toLowerCase().includes('not found') || output.toLowerCase().includes('invalid')) {
        balance.status = 'key_not_found';
      } else {
        balance.status = 'parsing_error';
      }
    }

    return balance;

  } catch (error) {
    console.error('Erreur lors du parsing du solde:', error);
    return {
      amount: 0,
      udAmount: 0,
      currency: 'Ğ1',
      status: 'parsing_error',
      lastUpdate: new Date().toISOString()
    };
  }
}
1 Like

Bonjour,

Pour répondre à la question…
C’est que l’architecture “UPlanet” repose sur une gestion massive de clés publiques,

Silkaj est pensé comme un client pour un humain, avec des interactions. Nos besoins requièrent un système capable de faire du traitement par lots (batch processing) pour des millions de clés.
Des clefs administratives, celles des identité décentralisée pour chaque parcelle de “territoire” (0.01°), les clefs pour les jetons d’usages (MULTIPASS), d’autres pour les jetons de propriété (ZENCard), et de vote (UPassport)… Toutes convertibles en clef IPFS et NOSTR pour former une structure de données synchronisée selon la règle N² entre les stations P2P formant l’essaim (Astroport.ONE). Tu trouveras des infos dans le code et des articles sur https://www.copylaradio.com

J’avais échangé avec @Moul sur les évolutions nécessaires pour que Silkaj puisse répondre à ce besoin. Malheureusement, ces demandes d’évolution, n’ont jamais reçu de réactions de sa part. La version 0.20.x apporte essentiellement l’usage “one line” avec fichier clef et export json

Sans cela comment le client Next.js dialogue avec silkaj ?

NB: (avec l’aide de @poka et de cursor :robot:) j’ai aussi pu soumettre des proto additifs (clef jumelle nostr & gestion de clef geographique) à GCLI pour anticiper la compatibilité v2

En effet, il y a un problème avec ces transferts “pending” sortants qui sont ajoutés au solde total (temporaire et non sûr). Ils devraient être soustraits.
C’est la partie la plus sensible de Silkaj, utilisée par les commandes balance et transfer (history n’est pas affectée). Tout changement doit être fait avec des pincettes. Le solde pris en compte pour l’émission d’un transfert prends en compte les transferts en attente. Les transferts bloqués en attente est rare dans le cas de l’interaction humaine sauf si un service régulier de transferts est mis en place. Le problème que je vois est que soit considéré que le solde soit plus élevé qu’en réalité. Si quelqu’un souhaite envoyer tout son solde ou presque, Duniter v1 ne va pas permettre un transfert qui consomme plus de sources que celles disponibles. Donc une erreur sera levée.
C’est un cas assez rare, qui a lieu pour les services qui émettent beaucoup de transferts produisant des transferts en attente. Puis, un transfert consommant quasiment ou toutes les sources disponibles, ferait une erreur de la part de Duniter v1.
Ça n’est pas un bug majeur. Du coup, je n’ai pas trop envie de le corriger, étant donné que la migration v2 ne devrait plus trop tarder.

Attention, le champ Blockchain est présent uniquement lorsqu’il y a des transferts temporaires.


Je suis d’accord que ça fait comme si tu publies au nom du projet Silkaj. Je considère que tu es de bonne foi et de bonne intention. Fred a proposé une MR sur le dépôt, donc pas de souci. Étant l’auteur et le mainteneur du projet Silkaj, je possède le droit d’auteur sur cette œuvre. Les licences libres sont fondées sur le droit d’auteur classique. Après, je suppose que dans les licences libres il est stipulé que s’il y a fork officiel, ce dernier doit changer de nom. Ou du moins ça va de soi. Bon après ça n’est pas le cas ici.


En réalité, je ne suis pas fan que le solde soit consommé via le parsing de ce qu’affiche Silkaj. L’interface est faite pour l’humain. Je fournis aucune garantie qu’elle ne change pas. Un petit changement peut casser votre parsing, comme a surement dû expérimenter Fred par le passé. Passer par une sortie json est une bien meilleure approche. Ça ça concerne la v1 où obtenir le solde nécessite de le calculer, ce qui complique la tâche. En v2, il est plus simple d’obtenir le solde via l’API RPC. Je vous conseille de la récupérer directement dans vos apps via cette API.


Durant le dev talk de novembre, tu te plaignais que je n’avais pas pris en compte tes souhaits d’avoir une sortie json sur Silkaj pour tes besoins. Pour ma part, la sortie json ça n’a pas été très haut dans mes priorités. Il y a déjà eu une tentative d’ajouter la sortie json, mais elle n’a pas abouti, car pas portée jusqu’au bout.
On peut y travailler ensemble. Par contre, tu devras faire des efforts. Sinon, je veux bien m’en occuper en échange d’une rémunération.

Concernant ta MR, je ne peux pas l’accepter en l’état. Elle mélange trop de sujets : sortie json, fonctionnalité de --compta pour ton usage personnel, authentification, correctif transferts en attente, release. De plus, il y a beaucoup de contenu généré par LLM dont je ne souhaite pas me taper la lecture.

Je suis en train de bosser pour sortir la v0.20 qui est compatible avec l’écosystème v2.

3 Likes

:heart_on_fire:

2 Likes