diff --git a/hubzilla/addon/cry01/config.json.template b/hubzilla/addon/cry01/config.json.template index dd79038..1327ef2 100644 --- a/hubzilla/addon/cry01/config.json.template +++ b/hubzilla/addon/cry01/config.json.template @@ -2,8 +2,6 @@ "_note": "Copy to config.json. Do not commit config.json — it contains installation-specific values.", "g1_rpc_endpoint": "REPLACE — Duniter node RPC over Wireguard, e.g. http://10.0.0.105:9944", "orchestrator_url": "REPLACE — orchestrator base URL, e.g. http://10.0.0.105:8700", - "operator_g1_pubkey": "REPLACE — operator Ğ1 public key (not private key — never store private keys)", - "operator_did": "REPLACE — operator did:web identifier, e.g. did:web:directory.diagnostics.kane-il.us", "cache_file": "REPLACE — absolute path to balance cache JSON on the host", "cache_max_age_seconds": 3600, "ots_calendar_url": "https://alice.btc.calendar.opentimestamps.org", diff --git a/hubzilla/addon/cry01/cry01_chain.php b/hubzilla/addon/cry01/cry01_chain.php index 34bf61b..a3c2f75 100644 --- a/hubzilla/addon/cry01/cry01_chain.php +++ b/hubzilla/addon/cry01/cry01_chain.php @@ -122,22 +122,21 @@ function cry01_cache_is_stale() { 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'); +function cry01_refresh_balance_cache($g1_pubkey) { + // Refreshes the balance cache for the given public key. + // The public key comes from the wallet session via the manage POST form — never from config. + // Returns true on success, false on failure. + if (!$g1_pubkey) { + logger('cry01_chain: cry01_refresh_balance_cache called with empty pubkey'); return false; } - $balance = cry01_get_balance($pubkey); + $balance = cry01_get_balance($g1_pubkey); if ($balance === null) return false; $cache = cry01_read_cache(); - $cache['operator_balance'] = $balance; - $cache['operator_g1_pubkey'] = $pubkey; - $cache['refreshed_at'] = date('c'); + $cache['balance'] = $balance; + $cache['g1_pubkey'] = $g1_pubkey; + $cache['refreshed_at'] = date('c'); return cry01_write_cache($cache); } diff --git a/hubzilla/addon/cry01/cry01_renderer.php b/hubzilla/addon/cry01/cry01_renderer.php index eeca381..e1435c9 100644 --- a/hubzilla/addon/cry01/cry01_renderer.php +++ b/hubzilla/addon/cry01/cry01_renderer.php @@ -51,17 +51,18 @@ function cry01_render_landing($association_slug, $access) { // --------------------------------------------------------------------------- function cry01_render_balance_display() { - // Renders the operator Ğ1 balance from cache. Shows staleness if cache is old. - $cache = cry01_read_cache(); - $balance = $cache['operator_balance'] ?? null; - $pubkey = $cache['operator_g1_pubkey'] ?? ''; + // Renders the Ğ1 balance from cache. The cached key belongs to whoever last refreshed. + // Shows staleness if cache is old. + $cache = cry01_read_cache(); + $balance = $cache['balance'] ?? null; + $pubkey = $cache['g1_pubkey'] ?? ''; $refreshed = $cache['refreshed_at'] ?? null; $out = '
'; - $out .= '
Operator Ğ1 Balance
'; + $out .= '
Ğ1 Balance
'; if ($balance === null) { - $out .= '

Balance not yet loaded. Operator: use Manage to refresh.

'; + $out .= '

Balance not yet loaded. Unlock your Ğ1 wallet and use Manage to refresh.

'; } else { $out .= '

' . cry01_h($balance) . '

'; if ($pubkey) { @@ -88,7 +89,6 @@ function cry01_render_signal_board($association_slug, $access) { } // TODO: load signals from orchestrator spool for this association. - // Placeholder until orchestrator query is implemented. $out = '
'; $out .= ''; $out .= '

No signals posted yet.

'; @@ -102,6 +102,7 @@ function cry01_render_signal_board($association_slug, $access) { function cry01_render_signal_form($association_slug, $access) { // Renders the capacity signal registration form. + // The g1_pubkey field is populated by the g1wallet session event when available. $config = cry01_load_config(); $categories = $config['signal_categories'] ?? []; $form_url = z_root() . '/cry01/' . cry01_h($association_slug) . '/signal'; @@ -110,14 +111,18 @@ function cry01_render_signal_form($association_slug, $access) { $out .= '

Register a Capacity Signal

'; $out .= '

Describe what you are offering or seeking, denominated in Ğ1.

'; + // Wallet session note — replaced by g1wallet integration once built. + $out .= '
Your Ğ1 public key will be populated automatically once the Ğ1 Wallet addon is installed. Until then, enter it manually below.
'; + $out .= '
'; $out .= cry01_csrf_token(); - // Ğ1 public key + // g1_pubkey — will be read-only and auto-populated by g1wallet session once available. $out .= '
'; $out .= ''; $out .= ''; + placeholder="G1..." maxlength="64" + data-g1wallet-target="pubkey">'; $out .= '
Your Ğ1 address. This is your payment address for this signal.
'; $out .= '
'; @@ -200,11 +205,11 @@ function cry01_render_g1_candidates($association_slug) { function cry01_render_manage($association_slug) { // Renders the operator manage page: cache refresh and config status. + // The Ğ1 public key comes from the wallet session — not from config. $cache = cry01_read_cache(); $refreshed = $cache['refreshed_at'] ?? null; $config = cry01_load_config(); $has_rpc = !empty($config['g1_rpc_endpoint']); - $has_key = !empty($config['operator_g1_pubkey']); $manage_url = z_root() . '/cry01/' . cry01_h($association_slug) . '/manage'; @@ -215,29 +220,37 @@ function cry01_render_manage($association_slug) { $out .= '
'; $out .= '
Configuration
'; $out .= '

' . ($has_rpc ? '✓' : '✗') . ' Duniter RPC endpoint: '; - $out .= $has_rpc ? 'configured' : 'missing'; - $out .= '

'; - $out .= '

' . ($has_key ? '✓' : '✗') . ' Operator Ğ1 public key: '; - $out .= $has_key ? 'configured' : 'missing'; + $out .= $has_rpc + ? 'configured' + : 'missing'; $out .= '

'; + $out .= '

Your Ğ1 public key comes from your Ğ1 Wallet session — not from config. '; + $out .= 'Unlock your wallet at Ğ1 Wallet, then return here to refresh.

'; $out .= '
'; // Cache status $out .= '
'; $out .= '
Balance Cache
'; if ($refreshed) { - $stale = cry01_cache_is_stale() ? ' (stale)' : ' (current)'; + $cached_key = $cache['g1_pubkey'] ?? ''; + $stale = cry01_cache_is_stale() ? ' (stale)' : ' (current)'; $out .= '

Last refreshed: ' . cry01_h($refreshed) . $stale . '

'; + if ($cached_key) { + $out .= '

Cached key: ' . cry01_h(substr($cached_key, 0, 16) . '...') . '

'; + } } else { $out .= '

Cache has not been populated yet.

'; } $out .= '
'; - // Refresh form + // Refresh form — g1_pubkey comes from wallet session via hidden field. + // g1wallet.js populates data-g1wallet-target="pubkey" fields on unlock. $out .= ''; $out .= cry01_csrf_token(); $out .= ''; + $out .= ''; $out .= ''; + $out .= 'Requires active Ğ1 Wallet session.'; $out .= '
'; $out .= '
'; diff --git a/hubzilla/addon/cry01/cry01_spool.php b/hubzilla/addon/cry01/cry01_spool.php index 92e5ba0..e065290 100644 --- a/hubzilla/addon/cry01/cry01_spool.php +++ b/hubzilla/addon/cry01/cry01_spool.php @@ -12,18 +12,18 @@ function cry01_handle_signal_post($association_slug, $access) { // Validates and submits a capacity signal to the orchestrator spool receiver. - $g1_pubkey = trim($_POST['g1_pubkey'] ?? ''); - $direction = trim($_POST['direction'] ?? ''); - $category_id = trim($_POST['category_id'] ?? ''); + $g1_pubkey = trim($_POST['g1_pubkey'] ?? ''); + $direction = trim($_POST['direction'] ?? ''); + $category_id = trim($_POST['category_id'] ?? ''); $capacity_description = trim($_POST['capacity_description'] ?? ''); - $g1_denomination = trim($_POST['g1_denomination'] ?? ''); - $duration_days = intval($_POST['duration_days'] ?? 0); + $g1_denomination = trim($_POST['g1_denomination'] ?? ''); + $duration_days = intval($_POST['duration_days'] ?? 0); // Validate required fields. $errors = []; - if (!$g1_pubkey) $errors[] = 'Ğ1 public key is required.'; + if (!$g1_pubkey) $errors[] = 'Ğ1 public key is required.'; if (!in_array($direction, ['offer', 'seek'])) $errors[] = 'Direction must be offer or seek.'; - if (!$category_id) $errors[] = 'Category is required.'; + if (!$category_id) $errors[] = 'Category is required.'; if (!$capacity_description) $errors[] = 'Description is required.'; if (!$g1_denomination || !is_numeric($g1_denomination) || floatval($g1_denomination) <= 0) { $errors[] = 'Ğ1 amount must be a positive number.'; @@ -45,11 +45,11 @@ function cry01_handle_signal_post($association_slug, $access) { // Build the spool envelope per contracts/signal-v1.json. $envelope = [ '_header' => [ - 'addon' => 'cry01', - 'association_slug' => $association_slug, - 'participant_xchan' => get_observer_hash(), - 'submitted_at' => date('c'), - 'node_token_hash' => hash('sha256', $token), + 'addon' => 'cry01', + 'association_slug' => $association_slug, + 'participant_xchan' => get_observer_hash(), + 'submitted_at' => date('c'), + 'node_token_hash' => hash('sha256', $token), ], '_payload' => [ 'g1_pubkey' => $g1_pubkey, @@ -83,7 +83,7 @@ function cry01_handle_signal_post($association_slug, $access) { // --------------------------------------------------------------------------- function cry01_handle_manage_post($association_slug) { - // Handles operator manage actions: currently only cache refresh. + // Handles operator manage actions. if (!cry01_verify_csrf()) { return cry01_render_error('Invalid form token. Please reload and try again.'); } @@ -91,10 +91,20 @@ function cry01_handle_manage_post($association_slug) { $action = $_POST['action'] ?? ''; if ($action === 'refresh_cache') { - $ok = cry01_refresh_balance_cache(); + // The public key comes from the wallet session via the hidden form field. + // It is never read from config — the operator is a participant like any other. + $g1_pubkey = trim($_POST['g1_pubkey'] ?? ''); + if (!$g1_pubkey) { + return '
' + . '
No Ğ1 public key received. ' + . 'Unlock your Ğ1 Wallet first, then try again.
' + . '
' + . cry01_render_manage($association_slug); + } + $ok = cry01_refresh_balance_cache($g1_pubkey); $msg = $ok ? '
Balance cache refreshed successfully.
' - : '
Cache refresh failed. Check that the Duniter node is reachable and the operator Ğ1 key is configured.
'; + : '
Cache refresh failed. Check that the Duniter node is reachable.
'; return '
' . $msg . '
' . cry01_render_manage($association_slug); } @@ -108,8 +118,8 @@ function cry01_handle_manage_post($association_slug) { function cry01_post_to_orchestrator($path, $payload) { // POSTs a JSON envelope to the orchestrator spool receiver. // Returns true on success (HTTP 201), false on any failure. - $config = cry01_load_config(); - $base = rtrim($config['receiver_url'] ?? '', '/'); + $config = cry01_load_config(); + $base = rtrim($config['receiver_url'] ?? '', '/'); if (!$base) { logger('cry01_spool: receiver_url not configured'); return false;