'0', 'consumers' => '0', 'providers' => '0', 'sufficients' => '0', 'free' => '0', 'reserved' => '0', 'frozen' => '0', 'flags' => '0', ]; } $bytes = hex2bin(substr($hex, 0, 2) === '0x' ? substr($hex, 2) : $hex); if ($bytes === false) { logger('cry01_chain: cry01_get_account_info got non-hex storage value'); return null; } return cry01_decode_account_info($bytes); } function cry01_get_balance($account_id_bytes) { // Returns the current Ğ1 balance for the given 32-byte raw account ID, // formatted as a human-readable string (e.g. "1.00 Ğ1"). Thin wrapper // around cry01_get_account_info() — extracts and formats 'free'. // Returns null on failure. $info = cry01_get_account_info($account_id_bytes); if ($info === null) return null; return cry01_format_g1_amount($info['free']); } // ---------------------------------------------------------------------------- // Ğ1 AMOUNT FORMATTING // ---------------------------------------------------------------------------- function cry01_format_g1_amount($decimal_string) { // Formats a raw on-chain u128 amount (in the smallest unit) as a // human-readable Ğ1 balance string. // // Duniter v2 uses centimes (hundredths of Ğ1) as the smallest unit, // same as Duniter v1. Divide by 100 and format with 2 decimal places. // // $decimal_string is a base-10 string (may be arbitrarily long for u128). $len = strlen($decimal_string); if ($len <= 2) { $decimal_string = str_pad($decimal_string, 3, '0', STR_PAD_LEFT); $len = strlen($decimal_string); } $whole = substr($decimal_string, 0, $len - 2); $cents = substr($decimal_string, $len - 2); $whole = ltrim($whole, '0') ?: '0'; $whole_formatted = ''; $rev = strrev($whole); for ($i = 0; $i < strlen($rev); $i++) { if ($i > 0 && $i % 3 === 0) { $whole_formatted .= ','; } $whole_formatted .= $rev[$i]; } $whole_formatted = strrev($whole_formatted); return $whole_formatted . '.' . $cents . ' Ğ1'; } // ---------------------------------------------------------------------------- // 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($g1_address) { // Refreshes the balance cache for the given Ğ1 address (SS58 string, // e.g. "g1LvTpY..." — the format provided by g1wallet's // data-g1wallet-target="pubkey" fields and entered by the operator). // Decodes the address internally, consistent with // cry01_handle_lookup_post(). Returns true on success, false on failure. // // Cache stores the account ID as hex, not raw binary — raw binary bytes // are not valid UTF-8 and cause json_encode() to fail silently (returns // false), which previously resulted in an empty (0-byte) cache file // despite this function reporting success. if (!$g1_address) { logger('cry01_chain: cry01_refresh_balance_cache called with empty address'); return false; } $account_id = cry01_ss58_decode($g1_address); if ($account_id === null) { logger('cry01_chain: cry01_refresh_balance_cache could not decode address'); return false; } $balance = cry01_get_balance($account_id); if ($balance === null) return false; $cache = cry01_read_cache(); $cache['balance'] = $balance; $cache['g1_pubkey'] = bin2hex($account_id); $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 : []; }