'2.0', 'id' => 1, 'method' => 'state_getStorage', 'params' => [cry01_balance_storage_key($g1_pubkey)], ]); $result = cry01_rpc_post($rpc, $payload); if ($result === null) return null; return cry01_decode_balance($result); } function cry01_balance_storage_key($g1_pubkey) { // Builds the Substrate storage key for the account balance of the given Ğ1 public key. // The key format is defined by the Duniter v2 runtime (pallet_balances storage map). // Placeholder — requires Substrate storage key encoding (xxHash + Blake2). // TODO: implement full storage key derivation or use duniter-specific RPC method. return '0x' . bin2hex($g1_pubkey); } function cry01_decode_balance($rpc_result) { // Decodes the raw RPC result into a human-readable Ğ1 balance string. // Duniter v2 balances are stored as u64 encoded in SCALE codec. // Returns formatted balance string or null if decoding fails. $hex = $rpc_result['result'] ?? null; if (!$hex || $hex === '0x') return '0'; // Remove 0x prefix and decode little-endian u64. $hex = ltrim($hex, '0x'); $bytes = array_reverse(str_split(str_pad($hex, 16, '0', STR_PAD_LEFT), 2)); $val = 0; foreach ($bytes as $b) { $val = ($val << 8) | hexdec($b); } // Duniter uses centimes (hundredths of Ğ1). Divide by 100. return number_format($val / 100, 2) . ' Ğ1'; } // --------------------------------------------------------------------------- // RPC TRANSPORT // --------------------------------------------------------------------------- function cry01_rpc_post($endpoint, $payload) { // POSTs a JSON-RPC request to the Duniter node. Returns decoded response or null. $context = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => "Content-Type: application/json\r\n", 'content' => $payload, 'timeout' => 5, ], ]); $raw = @file_get_contents($endpoint, false, $context); if ($raw === false) { logger('cry01_chain: RPC call failed to ' . $endpoint); return null; } $data = json_decode($raw, true); if (json_last_error() !== JSON_ERROR_NONE) { logger('cry01_chain: RPC response is not valid JSON'); return null; } return $data; } // --------------------------------------------------------------------------- // CACHE // --------------------------------------------------------------------------- function cry01_read_cache() { // Returns the balance cache array. Returns empty array if cache does not exist or is unreadable. $config = cry01_load_config(); $path = $config['cache_file'] ?? ''; if (!$path) return []; $raw = @file_get_contents($path); if ($raw === false) return []; $data = json_decode($raw, true); return (json_last_error() === JSON_ERROR_NONE) ? $data : []; } function cry01_write_cache($data) { // Writes the balance cache to disk. Returns true on success, false on failure. $config = cry01_load_config(); $path = $config['cache_file'] ?? ''; if (!$path) return false; $tmp = $path . '.tmp'; $ok = @file_put_contents($tmp, json_encode($data, JSON_PRETTY_PRINT)); if ($ok === false) { logger('cry01_chain: could not write cache to ' . $tmp); return false; } return @rename($tmp, $path); } function cry01_cache_is_stale() { // Returns true if the cache is older than cache_max_age_seconds or does not exist. $config = cry01_load_config(); $path = $config['cache_file'] ?? ''; $max_age = intval($config['cache_max_age_seconds'] ?? 3600); if (!$path || !file_exists($path)) return true; return (time() - filemtime($path)) > $max_age; } function cry01_refresh_balance_cache() { // Refreshes the balance cache by querying the Duniter node. // Writes updated cache to disk. Called by the manage route. $config = cry01_load_config(); $pubkey = $config['operator_g1_pubkey'] ?? ''; if (!$pubkey) { logger('cry01_chain: operator_g1_pubkey not set in config'); return false; } $balance = cry01_get_balance($pubkey); if ($balance === null) return false; $cache = cry01_read_cache(); $cache['operator_balance'] = $balance; $cache['operator_g1_pubkey'] = $pubkey; $cache['refreshed_at'] = date('c'); return cry01_write_cache($cache); } // --------------------------------------------------------------------------- // CONFIG LOADER // --------------------------------------------------------------------------- function cry01_load_config() { // Loads cry01 config.json from the addon directory. Returns empty array on failure. static $cfg = null; if ($cfg !== null) return $cfg; $raw = @file_get_contents('addon/cry01/config.json'); if ($raw === false) return []; $cfg = json_decode($raw, true); return (json_last_error() === JSON_ERROR_NONE) ? $cfg : []; }