Initial push
This commit is contained in:
@@ -37,18 +37,18 @@ function cry01_load_pdl(&$b) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// HELPERS
|
// HELPERS
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_h($value) {
|
function cry01_h($value) {
|
||||||
// HTML-escapes a value for safe output.
|
// HTML-escapes a value for safe output.
|
||||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// ACCESS
|
// ACCESS
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_access_state($association_slug = '') {
|
function cry01_access_state($association_slug = '') {
|
||||||
// Returns operator, participant, or public. Does not call local_channel() for group checks.
|
// Returns operator, participant, or public. Does not call local_channel() for group checks.
|
||||||
@@ -109,9 +109,9 @@ function cry01_access_wall($association_slug = '') {
|
|||||||
';
|
';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// CONTENT ROUTER
|
// CONTENT ROUTER
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_content() {
|
function cry01_content() {
|
||||||
if (function_exists('head_add_css')) {
|
if (function_exists('head_add_css')) {
|
||||||
@@ -160,14 +160,63 @@ function cry01_content() {
|
|||||||
}
|
}
|
||||||
return cry01_render_manage($association_slug);
|
return cry01_render_manage($association_slug);
|
||||||
|
|
||||||
|
case 'lookup':
|
||||||
|
// Public Ğ1 balance lookup. No SASE gate, no wallet session, no
|
||||||
|
// storage of any kind. Address in, balance out.
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
if (!cry01_verify_csrf()) {
|
||||||
|
return cry01_render_error('Invalid form token. Please reload and try again.');
|
||||||
|
}
|
||||||
|
return cry01_handle_lookup_post($association_slug, $access);
|
||||||
|
}
|
||||||
|
return cry01_render_landing($association_slug, $access);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return cry01_render_landing($association_slug, $access);
|
return cry01_render_landing($association_slug, $access);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
// LOOKUP HANDLER
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function cry01_handle_lookup_post($association_slug, $access) {
|
||||||
|
// Public balance lookup: decode the pasted Ğ1 address, query the chain,
|
||||||
|
// re-render the landing page with the result (or an error) inline.
|
||||||
|
// No data is stored anywhere — this is a pure read.
|
||||||
|
$address = trim($_POST['g1_lookup_address'] ?? '');
|
||||||
|
|
||||||
|
if (!$address) {
|
||||||
|
return cry01_render_landing($association_slug, $access, [
|
||||||
|
'lookup_error' => 'Please enter a Ğ1 address.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$account_id = cry01_ss58_decode($address);
|
||||||
|
if ($account_id === null) {
|
||||||
|
return cry01_render_landing($association_slug, $access, [
|
||||||
|
'lookup_error' => 'That doesn\'t look like a valid Ğ1 address. Check for typos and try again.',
|
||||||
|
'lookup_address' => $address,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$balance = cry01_get_balance($account_id);
|
||||||
|
if ($balance === null) {
|
||||||
|
return cry01_render_landing($association_slug, $access, [
|
||||||
|
'lookup_error' => 'Could not reach the Ğ1 network right now. Please try again shortly.',
|
||||||
|
'lookup_address' => $address,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cry01_render_landing($association_slug, $access, [
|
||||||
|
'lookup_address' => $address,
|
||||||
|
'lookup_balance' => $balance,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
// RENDER — INDEX
|
// RENDER — INDEX
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_render_index() {
|
function cry01_render_index() {
|
||||||
// Lists all registered associations with links to their value layer pages.
|
// Lists all registered associations with links to their value layer pages.
|
||||||
@@ -191,9 +240,9 @@ function cry01_render_index() {
|
|||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// RENDER — NOT FOUND / ERROR
|
// RENDER — NOT FOUND / ERROR
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_render_not_found() {
|
function cry01_render_not_found() {
|
||||||
return '<div class="cry01-content"><div class="alert alert-warning">Association not found.</div></div>';
|
return '<div class="cry01-content"><div class="alert alert-warning">Association not found.</div></div>';
|
||||||
@@ -204,9 +253,9 @@ function cry01_render_error($message) {
|
|||||||
return '<div class="cry01-content"><div class="alert alert-danger">' . cry01_h($message) . '</div></div>';
|
return '<div class="cry01-content"><div class="alert alert-danger">' . cry01_h($message) . '</div></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// CSRF
|
// CSRF
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_csrf_token() {
|
function cry01_csrf_token() {
|
||||||
// Generates and stores a CSRF token for the current session.
|
// Generates and stores a CSRF token for the current session.
|
||||||
|
|||||||
@@ -8,12 +8,18 @@
|
|||||||
* If the node infrastructure changes, only this file changes.
|
* If the node infrastructure changes, only this file changes.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
require_once 'addon/cry01/vendor/Blake2b.php';
|
||||||
|
|
||||||
|
use deemru\Blake2b;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
// BALANCE READ
|
// BALANCE READ
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_get_balance($g1_pubkey) {
|
function cry01_get_balance($g1_pubkey) {
|
||||||
// Returns the current Ğ1 balance for the given public key.
|
// Returns the current Ğ1 balance for the given public key.
|
||||||
|
// $g1_pubkey is a 32-byte raw account ID (NOT an SS58 address string —
|
||||||
|
// callers must decode the address first via cry01_ss58_decode()).
|
||||||
// Queries the local Duniter node RPC. Returns null on failure.
|
// Queries the local Duniter node RPC. Returns null on failure.
|
||||||
$config = cry01_load_config();
|
$config = cry01_load_config();
|
||||||
$rpc = $config['g1_rpc_endpoint'] ?? '';
|
$rpc = $config['g1_rpc_endpoint'] ?? '';
|
||||||
@@ -32,34 +38,302 @@ function cry01_get_balance($g1_pubkey) {
|
|||||||
return cry01_decode_balance($result);
|
return cry01_decode_balance($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cry01_balance_storage_key($g1_pubkey) {
|
function cry01_balance_storage_key($account_id_bytes) {
|
||||||
// Builds the Substrate storage key for the account balance of the given Ğ1 public key.
|
// Builds the Substrate storage key for System.Account(account_id_bytes).
|
||||||
// The key format is defined by the Duniter v2 runtime (pallet_balances storage map).
|
//
|
||||||
// Placeholder — requires Substrate storage key encoding (xxHash + Blake2).
|
// Storage key = xxh128("System") . xxh128("Account") . Blake2_128Concat(account_id_bytes)
|
||||||
// TODO: implement full storage key derivation or use duniter-specific RPC method.
|
// = xxh128("System") . xxh128("Account") . blake2b_128(account_id_bytes) . account_id_bytes
|
||||||
return '0x' . bin2hex($g1_pubkey);
|
//
|
||||||
|
// xxh128 is PHP's native hash('xxh128', ..., true) — confirmed correct
|
||||||
|
// against the published test vector (16c27099bd855aff3b3efe27980515ad
|
||||||
|
// for "php.watch").
|
||||||
|
//
|
||||||
|
// blake2b_128 is the vendored deemru/Blake2b pure-PHP implementation,
|
||||||
|
// confirmed correct against RFC 7693 parameterized test vectors:
|
||||||
|
// blake2b-128("") => cae66941d9efbd404e4d88758ea67670
|
||||||
|
// blake2b-128("abc") => cf4ab791c62b8d2b2109c90275287816
|
||||||
|
//
|
||||||
|
// $account_id_bytes must be exactly 32 raw bytes.
|
||||||
|
if (strlen($account_id_bytes) !== 32) {
|
||||||
|
logger('cry01_chain: cry01_balance_storage_key called with account_id of length ' . strlen($account_id_bytes) . ', expected 32');
|
||||||
|
return '0x';
|
||||||
|
}
|
||||||
|
|
||||||
|
$prefix_system = hash('xxh128', 'System', true);
|
||||||
|
$prefix_account = hash('xxh128', 'Account', true);
|
||||||
|
|
||||||
|
$b2b128 = new Blake2b(16);
|
||||||
|
$key_hash = $b2b128->hash($account_id_bytes);
|
||||||
|
|
||||||
|
$storage_key = $prefix_system . $prefix_account . $key_hash . $account_id_bytes;
|
||||||
|
|
||||||
|
return '0x' . bin2hex($storage_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
function cry01_decode_balance($rpc_result) {
|
function cry01_decode_balance($rpc_result) {
|
||||||
// Decodes the raw RPC result into a human-readable Ğ1 balance string.
|
// 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.
|
// Duniter v2 System.Account storage value is an AccountInfo struct:
|
||||||
|
// nonce: u32, consumers: u32, providers: u32, sufficients: u32,
|
||||||
|
// data: AccountData { free: u128, reserved: u128, frozen: u128, flags: u128 }
|
||||||
|
//
|
||||||
|
// All fields are SCALE little-endian fixed-width integers, concatenated
|
||||||
|
// with no separators:
|
||||||
|
// nonce 4 bytes
|
||||||
|
// consumers 4 bytes
|
||||||
|
// providers 4 bytes
|
||||||
|
// sufficients 4 bytes
|
||||||
|
// free 16 bytes <- this is the balance we want
|
||||||
|
// reserved 16 bytes
|
||||||
|
// frozen 16 bytes
|
||||||
|
// flags 16 bytes
|
||||||
|
//
|
||||||
|
// 'free' starts at byte offset 16 (4+4+4+4) and is 16 bytes (u128),
|
||||||
|
// little-endian.
|
||||||
|
//
|
||||||
|
// Returns formatted balance string or null if decoding fails or the
|
||||||
|
// account does not exist (empty storage = no AccountInfo = zero balance).
|
||||||
$hex = $rpc_result['result'] ?? null;
|
$hex = $rpc_result['result'] ?? null;
|
||||||
if (!$hex || $hex === '0x') return '0';
|
if (!$hex || $hex === '0x') {
|
||||||
// Remove 0x prefix and decode little-endian u64.
|
// Empty storage means the account has never received funds —
|
||||||
$hex = ltrim($hex, '0x');
|
// a valid result, not an error. Balance is zero.
|
||||||
$bytes = array_reverse(str_split(str_pad($hex, 16, '0', STR_PAD_LEFT), 2));
|
return '0';
|
||||||
$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';
|
$hex = ltrim($hex, '0x');
|
||||||
|
if (substr($rpc_result['result'], 0, 2) === '0x') {
|
||||||
|
$hex = substr($rpc_result['result'], 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytes = hex2bin($hex);
|
||||||
|
if ($bytes === false || strlen($bytes) < 32) {
|
||||||
|
logger('cry01_chain: cry01_decode_balance got unexpected payload length ' . strlen((string) $bytes));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'free' balance: bytes 16..31 (16 bytes, u128, little-endian).
|
||||||
|
$free_bytes = substr($bytes, 16, 16);
|
||||||
|
|
||||||
|
// PHP integers are 64-bit; u128 can exceed that. Decode as little-endian
|
||||||
|
// and accumulate using string-based big-integer arithmetic (base 10
|
||||||
|
// string accumulation) to avoid overflow for very large balances.
|
||||||
|
$value = cry01_le_bytes_to_decimal_string($free_bytes);
|
||||||
|
|
||||||
|
return cry01_format_g1_amount($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
function cry01_le_bytes_to_decimal_string($bytes) {
|
||||||
|
// Converts a little-endian byte string to a base-10 decimal string,
|
||||||
|
// using only integer string arithmetic (no bcmath/gmp dependency).
|
||||||
|
// Suitable for u128 values (up to 16 bytes).
|
||||||
|
$result = '0';
|
||||||
|
$multiplier = '1';
|
||||||
|
|
||||||
|
$len = strlen($bytes);
|
||||||
|
for ($i = 0; $i < $len; $i++) {
|
||||||
|
$byteVal = ord($bytes[$i]);
|
||||||
|
if ($byteVal !== 0) {
|
||||||
|
$term = cry01_decimal_string_multiply($multiplier, (string) $byteVal);
|
||||||
|
$result = cry01_decimal_string_add($result, $term);
|
||||||
|
}
|
||||||
|
if ($i < $len - 1) {
|
||||||
|
$multiplier = cry01_decimal_string_multiply($multiplier, '256');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cry01_decimal_string_add($a, $b) {
|
||||||
|
// Adds two non-negative base-10 decimal strings, returns the sum as a string.
|
||||||
|
$a = strrev($a);
|
||||||
|
$b = strrev($b);
|
||||||
|
$len = max(strlen($a), strlen($b));
|
||||||
|
$a = str_pad($a, $len, '0');
|
||||||
|
$b = str_pad($b, $len, '0');
|
||||||
|
|
||||||
|
$result = '';
|
||||||
|
$carry = 0;
|
||||||
|
for ($i = 0; $i < $len; $i++) {
|
||||||
|
$sum = intval($a[$i]) + intval($b[$i]) + $carry;
|
||||||
|
$carry = intdiv($sum, 10);
|
||||||
|
$result .= ($sum % 10);
|
||||||
|
}
|
||||||
|
if ($carry) {
|
||||||
|
$result .= $carry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ltrim(strrev($result), '0') ?: '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
function cry01_decimal_string_multiply($a, $b) {
|
||||||
|
// Multiplies two non-negative base-10 decimal strings, returns the product as a string.
|
||||||
|
if ($a === '0' || $b === '0') return '0';
|
||||||
|
|
||||||
|
$a = strrev($a);
|
||||||
|
$b = strrev($b);
|
||||||
|
$result = array_fill(0, strlen($a) + strlen($b), 0);
|
||||||
|
|
||||||
|
for ($i = 0; $i < strlen($a); $i++) {
|
||||||
|
for ($j = 0; $j < strlen($b); $j++) {
|
||||||
|
$result[$i + $j] += intval($a[$i]) * intval($b[$j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carry propagation.
|
||||||
|
for ($k = 0; $k < count($result) - 1; $k++) {
|
||||||
|
$result[$k + 1] += intdiv($result[$k], 10);
|
||||||
|
$result[$k] = $result[$k] % 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
$str = implode('', array_reverse($result));
|
||||||
|
return ltrim($str, '0') ?: '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// Less than 1 Ğ1 — pad to at least 3 digits so we can split cents.
|
||||||
|
$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';
|
||||||
|
|
||||||
|
// Add thousands separators to the whole part.
|
||||||
|
$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';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// SS58 / BASE58 ADDRESS DECODING
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function cry01_base58_decode($input) {
|
||||||
|
// Generic Base58 decoder (Bitcoin/IPFS alphabet, same as SS58).
|
||||||
|
// Pure PHP, no bcmath/gmp dependency. Returns raw decoded bytes,
|
||||||
|
// including any version/checksum bytes still attached.
|
||||||
|
// Throws InvalidArgumentException on invalid characters.
|
||||||
|
static $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
||||||
|
|
||||||
|
$input = trim($input);
|
||||||
|
if ($input === '') return '';
|
||||||
|
|
||||||
|
$map = [];
|
||||||
|
for ($i = 0; $i < strlen($alphabet); $i++) {
|
||||||
|
$map[$alphabet[$i]] = $i;
|
||||||
|
}
|
||||||
|
|
||||||
|
$leadingZeros = 0;
|
||||||
|
for ($i = 0; $i < strlen($input); $i++) {
|
||||||
|
if ($input[$i] === '1') {
|
||||||
|
$leadingZeros++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$bytes = [0];
|
||||||
|
|
||||||
|
for ($i = 0; $i < strlen($input); $i++) {
|
||||||
|
$c = $input[$i];
|
||||||
|
if (!isset($map[$c])) {
|
||||||
|
throw new \InvalidArgumentException("Invalid base58 character: '$c'");
|
||||||
|
}
|
||||||
|
$value = $map[$c];
|
||||||
|
|
||||||
|
$carry = $value;
|
||||||
|
for ($j = count($bytes) - 1; $j >= 0; $j--) {
|
||||||
|
$carry += $bytes[$j] * 58;
|
||||||
|
$bytes[$j] = $carry & 0xff;
|
||||||
|
$carry >>= 8;
|
||||||
|
}
|
||||||
|
while ($carry > 0) {
|
||||||
|
array_unshift($bytes, $carry & 0xff);
|
||||||
|
$carry >>= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = '';
|
||||||
|
$started = false;
|
||||||
|
foreach ($bytes as $b) {
|
||||||
|
if (!$started && $b === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$started = true;
|
||||||
|
$result .= chr($b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_repeat("\x00", $leadingZeros) . $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cry01_ss58_decode($address) {
|
||||||
|
// Decodes an SS58-encoded address (Ğ1/Substrate) to its raw 32-byte
|
||||||
|
// account ID, verifying the checksum.
|
||||||
|
//
|
||||||
|
// Confirmed format for Ğ1 addresses (e.g. "g1LvTpYXkKEASMiBYLp8RQmSN5kZyXtoHX8XE2FqQ9hDjqp5B"):
|
||||||
|
// 36 bytes total = 2-byte network prefix (0x5891) + 32-byte account ID + 2-byte checksum
|
||||||
|
//
|
||||||
|
// Checksum = first 2 bytes of Blake2b-512("SS58PRE" + prefix + account_id)
|
||||||
|
//
|
||||||
|
// Returns the raw 32-byte account ID on success, or null on any
|
||||||
|
// validation failure (invalid base58, wrong length, bad checksum).
|
||||||
|
try {
|
||||||
|
$decoded = cry01_base58_decode($address);
|
||||||
|
} catch (\InvalidArgumentException $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$len = strlen($decoded);
|
||||||
|
|
||||||
|
if ($len === 36) {
|
||||||
|
$prefix = substr($decoded, 0, 2);
|
||||||
|
$account = substr($decoded, 2, 32);
|
||||||
|
$checksum = substr($decoded, 34, 2);
|
||||||
|
} elseif ($len === 35) {
|
||||||
|
$prefix = substr($decoded, 0, 1);
|
||||||
|
$account = substr($decoded, 1, 32);
|
||||||
|
$checksum = substr($decoded, 33, 2);
|
||||||
|
} else {
|
||||||
|
logger('cry01_chain: cry01_ss58_decode got unexpected decoded length ' . $len);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$b2b512 = new Blake2b(64);
|
||||||
|
$hash = $b2b512->hash('SS58PRE' . $prefix . $account);
|
||||||
|
$expected_checksum = substr($hash, 0, 2);
|
||||||
|
|
||||||
|
if ($expected_checksum !== $checksum) {
|
||||||
|
logger('cry01_chain: cry01_ss58_decode checksum mismatch for address');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
// RPC TRANSPORT
|
// RPC TRANSPORT
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_rpc_post($endpoint, $payload) {
|
function cry01_rpc_post($endpoint, $payload) {
|
||||||
// POSTs a JSON-RPC request to the Duniter node. Returns decoded response or null.
|
// POSTs a JSON-RPC request to the Duniter node. Returns decoded response or null.
|
||||||
@@ -84,9 +358,9 @@ function cry01_rpc_post($endpoint, $payload) {
|
|||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// CACHE
|
// CACHE
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_read_cache() {
|
function cry01_read_cache() {
|
||||||
// Returns the balance cache array. Returns empty array if cache does not exist or is unreadable.
|
// Returns the balance cache array. Returns empty array if cache does not exist or is unreadable.
|
||||||
@@ -140,9 +414,9 @@ function cry01_refresh_balance_cache($g1_pubkey) {
|
|||||||
return cry01_write_cache($cache);
|
return cry01_write_cache($cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// CONFIG LOADER
|
// CONFIG LOADER
|
||||||
// ---------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_load_config() {
|
function cry01_load_config() {
|
||||||
// Loads cry01 config.json from the addon directory. Returns empty array on failure.
|
// Loads cry01 config.json from the addon directory. Returns empty array on failure.
|
||||||
|
|||||||
@@ -10,8 +10,10 @@
|
|||||||
// ASSOCIATION LANDING
|
// ASSOCIATION LANDING
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_render_landing($association_slug, $access) {
|
function cry01_render_landing($association_slug, $access, $lookup = []) {
|
||||||
// Renders the main cry01 page: balance display + signal board.
|
// Renders the main cry01 page: balance display + signal board + public lookup.
|
||||||
|
// $lookup may contain: lookup_error, lookup_address, lookup_balance —
|
||||||
|
// populated when this page is rendered as the result of a lookup POST.
|
||||||
$raw = @file_get_contents('addon/vs01/config.json');
|
$raw = @file_get_contents('addon/vs01/config.json');
|
||||||
$cfg = $raw ? json_decode($raw, true) : [];
|
$cfg = $raw ? json_decode($raw, true) : [];
|
||||||
$assoc = $cfg['associations'][$association_slug] ?? [];
|
$assoc = $cfg['associations'][$association_slug] ?? [];
|
||||||
@@ -32,6 +34,7 @@ function cry01_render_landing($association_slug, $access) {
|
|||||||
$out .= '</p>';
|
$out .= '</p>';
|
||||||
|
|
||||||
$out .= cry01_render_balance_display();
|
$out .= cry01_render_balance_display();
|
||||||
|
$out .= cry01_render_lookup_form($association_slug, $lookup);
|
||||||
$out .= cry01_render_signal_board($association_slug, $access);
|
$out .= cry01_render_signal_board($association_slug, $access);
|
||||||
|
|
||||||
if ($access === 'participant') {
|
if ($access === 'participant') {
|
||||||
@@ -55,7 +58,7 @@ function cry01_render_landing($association_slug, $access) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// BALANCE DISPLAY
|
// BALANCE DISPLAY (operator/cached)
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_render_balance_display() {
|
function cry01_render_balance_display() {
|
||||||
@@ -86,6 +89,48 @@ function cry01_render_balance_display() {
|
|||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// PUBLIC BALANCE LOOKUP
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function cry01_render_lookup_form($association_slug, $lookup = []) {
|
||||||
|
// Renders the public Ğ1 address balance lookup form.
|
||||||
|
// Anyone can use this — no SASE, no wallet session, nothing stored.
|
||||||
|
// Paste an address, see its current balance, read directly from the
|
||||||
|
// Civic Infrastructure's own Duniter mirror node.
|
||||||
|
$lookup_url = z_root() . '/cry01/' . cry01_h($association_slug) . '/lookup';
|
||||||
|
|
||||||
|
$error = $lookup['lookup_error'] ?? '';
|
||||||
|
$entered_address = $lookup['lookup_address'] ?? '';
|
||||||
|
$balance = $lookup['lookup_balance'] ?? null;
|
||||||
|
|
||||||
|
$out = '<div class="cry01-lookup mb-4">';
|
||||||
|
$out .= '<h5 class="cry01-section-label">Ğ1 Address Lookup</h5>';
|
||||||
|
$out .= '<p class="text-muted small">Paste any Ğ1 wallet address to check its current balance — read directly from this node\'s Duniter mirror. Nothing is stored.</p>';
|
||||||
|
|
||||||
|
$out .= '<form method="post" action="' . $lookup_url . '" class="cry01-lookup-form">';
|
||||||
|
$out .= cry01_csrf_token();
|
||||||
|
$out .= '<div class="input-group">';
|
||||||
|
$out .= '<input type="text" class="form-control font-monospace" name="g1_lookup_address"
|
||||||
|
placeholder="g1..." maxlength="64"
|
||||||
|
value="' . cry01_h($entered_address) . '">';
|
||||||
|
$out .= '<button type="submit" class="btn btn-outline-primary">Check Balance</button>';
|
||||||
|
$out .= '</div>';
|
||||||
|
$out .= '</form>';
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
$out .= '<div class="alert alert-danger mt-2">' . cry01_h($error) . '</div>';
|
||||||
|
} elseif ($balance !== null) {
|
||||||
|
$out .= '<div class="alert alert-success mt-2">';
|
||||||
|
$out .= '<span class="font-monospace small">' . cry01_h(substr($entered_address, 0, 16) . '...') . '</span><br>';
|
||||||
|
$out .= '<strong>' . cry01_h($balance) . '</strong>';
|
||||||
|
$out .= '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$out .= '</div>';
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// SIGNAL BOARD
|
// SIGNAL BOARD
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user