Files
kane-diagnostics/hubzilla/addon/cry01/cry01_chain.php
2026-06-12 15:33:51 -04:00

191 lines
7.2 KiB
PHP

<?php
/**
* cry01_chain.php — Ğ1 application layer on top of cry01_substrate.php.
*
* This file contains Ğ1-specific and cry01-specific logic: what we do with
* account data once decoded — formatting amounts in Ğ1, caching balances,
* refreshing the cache. Generic Substrate protocol mechanics (storage keys,
* SCALE decoding, SS58, RPC transport) live in cry01_substrate.php and are
* required by it.
*/
require_once 'addon/cry01/cry01_substrate.php';
// ----------------------------------------------------------------------------
// ACCOUNT INFO / BALANCE READ
// ----------------------------------------------------------------------------
function cry01_get_account_info($account_id_bytes) {
// Returns the full decoded AccountInfo struct for the given 32-byte raw
// account ID (as returned by cry01_ss58_decode()):
// ['nonce', 'consumers', 'providers', 'sufficients',
// 'free', 'reserved', 'frozen', 'flags']
// All values are base-10 decimal strings (raw on-chain units, not
// formatted Ğ1 amounts).
//
// Returns null on RPC failure. Returns an all-zero struct (not null) if
// the account has never received funds — empty storage at this key is a
// valid "account does not exist yet" response, not an error.
$config = cry01_load_config();
$rpc = $config['g1_rpc_endpoint'] ?? '';
if (!$rpc || !$account_id_bytes) return null;
if (strlen($account_id_bytes) !== 32) {
logger('cry01_chain: cry01_get_account_info called with account_id of length ' . strlen($account_id_bytes) . ', expected 32');
return null;
}
$storage_key = cry01_storage_key('System', 'Account', $account_id_bytes);
$hex = cry01_rpc_state_get_storage($rpc, $storage_key);
if ($hex === null) return null;
if ($hex === '0x') {
// Account has never received funds. All fields zero.
return [
'nonce' => '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 : [];
}