365) { $errors[] = 'Duration must be between 1 and 365 days.'; } if ($errors) { return '
' . cry01_render_signal_form($association_slug, $access); } $config = cry01_load_config(); $token = $config['node_token'] ?? ''; // 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), ], '_payload' => [ 'g1_pubkey' => $g1_pubkey, 'capacity_description' => $capacity_description, 'g1_denomination' => $g1_denomination, 'duration_days' => $duration_days, 'category_id' => $category_id, 'direction' => $direction, ], ]; $result = cry01_post_to_orchestrator('/cry01/signal', $envelope); if ($result === true) { return '
' . '
Signal registered successfully. ' . 'A Verifiable Credential will be issued and timestamped to Bitcoin.
' . '

← Back to Value Layer

' . '
'; } return '
' . '
Signal could not be registered. ' . 'The orchestrator may be temporarily unavailable. Please try again.
' . '
' . cry01_render_signal_form($association_slug, $access); } // --------------------------------------------------------------------------- // MANAGE POST HANDLER // --------------------------------------------------------------------------- function cry01_handle_manage_post($association_slug) { // Handles operator manage actions. if (!cry01_verify_csrf()) { return cry01_render_error('Invalid form token. Please reload and try again.'); } $action = $_POST['action'] ?? ''; if ($action === 'refresh_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.
'; return '
' . $msg . '
' . cry01_render_manage($association_slug); } return cry01_render_error('Unknown action.'); } // --------------------------------------------------------------------------- // ORCHESTRATOR TRANSPORT // --------------------------------------------------------------------------- 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'] ?? '', '/'); if (!$base) { logger('cry01_spool: receiver_url not configured'); return false; } $url = $base . $path; $body = json_encode($payload); $context = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => "Content-Type: application/json\r\nContent-Length: " . strlen($body) . "\r\n", 'content' => $body, 'timeout' => 10, 'ignore_errors' => true, ], ]); $raw = @file_get_contents($url, false, $context); $headers = $http_response_header ?? []; $status = 0; foreach ($headers as $h) { if (preg_match('/HTTP\/\d\.\d\s+(\d+)/', $h, $m)) { $status = intval($m[1]); } } if ($status === 201) return true; logger('cry01_spool: orchestrator returned status ' . $status . ' for ' . $path); return false; }