Updated
This commit is contained in:
@@ -2,8 +2,6 @@
|
|||||||
"_note": "Copy to config.json. Do not commit config.json — it contains installation-specific values.",
|
"_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",
|
"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",
|
"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_file": "REPLACE — absolute path to balance cache JSON on the host",
|
||||||
"cache_max_age_seconds": 3600,
|
"cache_max_age_seconds": 3600,
|
||||||
"ots_calendar_url": "https://alice.btc.calendar.opentimestamps.org",
|
"ots_calendar_url": "https://alice.btc.calendar.opentimestamps.org",
|
||||||
|
|||||||
@@ -122,22 +122,21 @@ function cry01_cache_is_stale() {
|
|||||||
return (time() - filemtime($path)) > $max_age;
|
return (time() - filemtime($path)) > $max_age;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cry01_refresh_balance_cache() {
|
function cry01_refresh_balance_cache($g1_pubkey) {
|
||||||
// Refreshes the balance cache by querying the Duniter node.
|
// Refreshes the balance cache for the given public key.
|
||||||
// Writes updated cache to disk. Called by the manage route.
|
// The public key comes from the wallet session via the manage POST form — never from config.
|
||||||
$config = cry01_load_config();
|
// Returns true on success, false on failure.
|
||||||
$pubkey = $config['operator_g1_pubkey'] ?? '';
|
if (!$g1_pubkey) {
|
||||||
if (!$pubkey) {
|
logger('cry01_chain: cry01_refresh_balance_cache called with empty pubkey');
|
||||||
logger('cry01_chain: operator_g1_pubkey not set in config');
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$balance = cry01_get_balance($pubkey);
|
$balance = cry01_get_balance($g1_pubkey);
|
||||||
if ($balance === null) return false;
|
if ($balance === null) return false;
|
||||||
|
|
||||||
$cache = cry01_read_cache();
|
$cache = cry01_read_cache();
|
||||||
$cache['operator_balance'] = $balance;
|
$cache['balance'] = $balance;
|
||||||
$cache['operator_g1_pubkey'] = $pubkey;
|
$cache['g1_pubkey'] = $g1_pubkey;
|
||||||
$cache['refreshed_at'] = date('c');
|
$cache['refreshed_at'] = date('c');
|
||||||
return cry01_write_cache($cache);
|
return cry01_write_cache($cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,17 +51,18 @@ function cry01_render_landing($association_slug, $access) {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_render_balance_display() {
|
function cry01_render_balance_display() {
|
||||||
// Renders the operator Ğ1 balance from cache. Shows staleness if cache is old.
|
// Renders the Ğ1 balance from cache. The cached key belongs to whoever last refreshed.
|
||||||
$cache = cry01_read_cache();
|
// Shows staleness if cache is old.
|
||||||
$balance = $cache['operator_balance'] ?? null;
|
$cache = cry01_read_cache();
|
||||||
$pubkey = $cache['operator_g1_pubkey'] ?? '';
|
$balance = $cache['balance'] ?? null;
|
||||||
|
$pubkey = $cache['g1_pubkey'] ?? '';
|
||||||
$refreshed = $cache['refreshed_at'] ?? null;
|
$refreshed = $cache['refreshed_at'] ?? null;
|
||||||
|
|
||||||
$out = '<div class="cry01-balance-display mb-4">';
|
$out = '<div class="cry01-balance-display mb-4">';
|
||||||
$out .= '<h5 class="cry01-section-label">Operator Ğ1 Balance</h5>';
|
$out .= '<h5 class="cry01-section-label">Ğ1 Balance</h5>';
|
||||||
|
|
||||||
if ($balance === null) {
|
if ($balance === null) {
|
||||||
$out .= '<p class="text-muted fst-italic">Balance not yet loaded. Operator: use Manage to refresh.</p>';
|
$out .= '<p class="text-muted fst-italic">Balance not yet loaded. Unlock your Ğ1 wallet and use Manage to refresh.</p>';
|
||||||
} else {
|
} else {
|
||||||
$out .= '<p class="cry01-balance-amount">' . cry01_h($balance) . '</p>';
|
$out .= '<p class="cry01-balance-amount">' . cry01_h($balance) . '</p>';
|
||||||
if ($pubkey) {
|
if ($pubkey) {
|
||||||
@@ -88,7 +89,6 @@ function cry01_render_signal_board($association_slug, $access) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: load signals from orchestrator spool for this association.
|
// TODO: load signals from orchestrator spool for this association.
|
||||||
// Placeholder until orchestrator query is implemented.
|
|
||||||
$out = '<div class="cry01-signal-board mb-4">';
|
$out = '<div class="cry01-signal-board mb-4">';
|
||||||
$out .= '<h5 class="cry01-section-label">Capacity Signals</h5>';
|
$out .= '<h5 class="cry01-section-label">Capacity Signals</h5>';
|
||||||
$out .= '<p class="text-muted fst-italic">No signals posted yet.</p>';
|
$out .= '<p class="text-muted fst-italic">No signals posted yet.</p>';
|
||||||
@@ -102,6 +102,7 @@ function cry01_render_signal_board($association_slug, $access) {
|
|||||||
|
|
||||||
function cry01_render_signal_form($association_slug, $access) {
|
function cry01_render_signal_form($association_slug, $access) {
|
||||||
// Renders the capacity signal registration form.
|
// Renders the capacity signal registration form.
|
||||||
|
// The g1_pubkey field is populated by the g1wallet session event when available.
|
||||||
$config = cry01_load_config();
|
$config = cry01_load_config();
|
||||||
$categories = $config['signal_categories'] ?? [];
|
$categories = $config['signal_categories'] ?? [];
|
||||||
$form_url = z_root() . '/cry01/' . cry01_h($association_slug) . '/signal';
|
$form_url = z_root() . '/cry01/' . cry01_h($association_slug) . '/signal';
|
||||||
@@ -110,14 +111,18 @@ function cry01_render_signal_form($association_slug, $access) {
|
|||||||
$out .= '<h3>Register a Capacity Signal</h3>';
|
$out .= '<h3>Register a Capacity Signal</h3>';
|
||||||
$out .= '<p class="text-muted">Describe what you are offering or seeking, denominated in Ğ1.</p>';
|
$out .= '<p class="text-muted">Describe what you are offering or seeking, denominated in Ğ1.</p>';
|
||||||
|
|
||||||
|
// Wallet session note — replaced by g1wallet integration once built.
|
||||||
|
$out .= '<div class="alert alert-info small">Your Ğ1 public key will be populated automatically once the Ğ1 Wallet addon is installed. Until then, enter it manually below.</div>';
|
||||||
|
|
||||||
$out .= '<form method="post" action="' . $form_url . '" class="cry01-form" novalidate>';
|
$out .= '<form method="post" action="' . $form_url . '" class="cry01-form" novalidate>';
|
||||||
$out .= cry01_csrf_token();
|
$out .= cry01_csrf_token();
|
||||||
|
|
||||||
// Ğ1 public key
|
// g1_pubkey — will be read-only and auto-populated by g1wallet session once available.
|
||||||
$out .= '<div class="mb-3">';
|
$out .= '<div class="mb-3">';
|
||||||
$out .= '<label class="form-label" for="g1_pubkey">Your Ğ1 Public Key <span class="text-danger">*</span></label>';
|
$out .= '<label class="form-label" for="g1_pubkey">Your Ğ1 Public Key <span class="text-danger">*</span></label>';
|
||||||
$out .= '<input type="text" class="form-control" id="g1_pubkey" name="g1_pubkey" required
|
$out .= '<input type="text" class="form-control" id="g1_pubkey" name="g1_pubkey" required
|
||||||
placeholder="G1..." maxlength="64">';
|
placeholder="G1..." maxlength="64"
|
||||||
|
data-g1wallet-target="pubkey">';
|
||||||
$out .= '<div class="form-text">Your Ğ1 address. This is your payment address for this signal.</div>';
|
$out .= '<div class="form-text">Your Ğ1 address. This is your payment address for this signal.</div>';
|
||||||
$out .= '</div>';
|
$out .= '</div>';
|
||||||
|
|
||||||
@@ -200,11 +205,11 @@ function cry01_render_g1_candidates($association_slug) {
|
|||||||
|
|
||||||
function cry01_render_manage($association_slug) {
|
function cry01_render_manage($association_slug) {
|
||||||
// Renders the operator manage page: cache refresh and config status.
|
// 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();
|
$cache = cry01_read_cache();
|
||||||
$refreshed = $cache['refreshed_at'] ?? null;
|
$refreshed = $cache['refreshed_at'] ?? null;
|
||||||
$config = cry01_load_config();
|
$config = cry01_load_config();
|
||||||
$has_rpc = !empty($config['g1_rpc_endpoint']);
|
$has_rpc = !empty($config['g1_rpc_endpoint']);
|
||||||
$has_key = !empty($config['operator_g1_pubkey']);
|
|
||||||
|
|
||||||
$manage_url = z_root() . '/cry01/' . cry01_h($association_slug) . '/manage';
|
$manage_url = z_root() . '/cry01/' . cry01_h($association_slug) . '/manage';
|
||||||
|
|
||||||
@@ -215,29 +220,37 @@ function cry01_render_manage($association_slug) {
|
|||||||
$out .= '<div class="mb-3">';
|
$out .= '<div class="mb-3">';
|
||||||
$out .= '<h5>Configuration</h5>';
|
$out .= '<h5>Configuration</h5>';
|
||||||
$out .= '<p>' . ($has_rpc ? '✓' : '✗') . ' Duniter RPC endpoint: ';
|
$out .= '<p>' . ($has_rpc ? '✓' : '✗') . ' Duniter RPC endpoint: ';
|
||||||
$out .= $has_rpc ? '<span class="text-success">configured</span>' : '<span class="text-danger">missing</span>';
|
$out .= $has_rpc
|
||||||
$out .= '</p>';
|
? '<span class="text-success">configured</span>'
|
||||||
$out .= '<p>' . ($has_key ? '✓' : '✗') . ' Operator Ğ1 public key: ';
|
: '<span class="text-danger">missing</span>';
|
||||||
$out .= $has_key ? '<span class="text-success">configured</span>' : '<span class="text-danger">missing</span>';
|
|
||||||
$out .= '</p>';
|
$out .= '</p>';
|
||||||
|
$out .= '<p class="text-muted small">Your Ğ1 public key comes from your Ğ1 Wallet session — not from config. ';
|
||||||
|
$out .= 'Unlock your wallet at <a href="' . z_root() . '/g1wallet">Ğ1 Wallet</a>, then return here to refresh.</p>';
|
||||||
$out .= '</div>';
|
$out .= '</div>';
|
||||||
|
|
||||||
// Cache status
|
// Cache status
|
||||||
$out .= '<div class="mb-3">';
|
$out .= '<div class="mb-3">';
|
||||||
$out .= '<h5>Balance Cache</h5>';
|
$out .= '<h5>Balance Cache</h5>';
|
||||||
if ($refreshed) {
|
if ($refreshed) {
|
||||||
$stale = cry01_cache_is_stale() ? ' (stale)' : ' (current)';
|
$cached_key = $cache['g1_pubkey'] ?? '';
|
||||||
|
$stale = cry01_cache_is_stale() ? ' (stale)' : ' (current)';
|
||||||
$out .= '<p>Last refreshed: ' . cry01_h($refreshed) . $stale . '</p>';
|
$out .= '<p>Last refreshed: ' . cry01_h($refreshed) . $stale . '</p>';
|
||||||
|
if ($cached_key) {
|
||||||
|
$out .= '<p class="text-muted small">Cached key: ' . cry01_h(substr($cached_key, 0, 16) . '...') . '</p>';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$out .= '<p class="text-muted">Cache has not been populated yet.</p>';
|
$out .= '<p class="text-muted">Cache has not been populated yet.</p>';
|
||||||
}
|
}
|
||||||
$out .= '</div>';
|
$out .= '</div>';
|
||||||
|
|
||||||
// Refresh form
|
// Refresh form — g1_pubkey comes from wallet session via hidden field.
|
||||||
|
// g1wallet.js populates data-g1wallet-target="pubkey" fields on unlock.
|
||||||
$out .= '<form method="post" action="' . $manage_url . '">';
|
$out .= '<form method="post" action="' . $manage_url . '">';
|
||||||
$out .= cry01_csrf_token();
|
$out .= cry01_csrf_token();
|
||||||
$out .= '<input type="hidden" name="action" value="refresh_cache">';
|
$out .= '<input type="hidden" name="action" value="refresh_cache">';
|
||||||
|
$out .= '<input type="hidden" name="g1_pubkey" id="manage_g1_pubkey" data-g1wallet-target="pubkey">';
|
||||||
$out .= '<button type="submit" class="btn btn-sm btn-outline-primary">Refresh Balance Cache</button>';
|
$out .= '<button type="submit" class="btn btn-sm btn-outline-primary">Refresh Balance Cache</button>';
|
||||||
|
$out .= '<span class="text-muted small ms-2">Requires active Ğ1 Wallet session.</span>';
|
||||||
$out .= '</form>';
|
$out .= '</form>';
|
||||||
|
|
||||||
$out .= '</div>';
|
$out .= '</div>';
|
||||||
|
|||||||
@@ -12,18 +12,18 @@
|
|||||||
|
|
||||||
function cry01_handle_signal_post($association_slug, $access) {
|
function cry01_handle_signal_post($association_slug, $access) {
|
||||||
// Validates and submits a capacity signal to the orchestrator spool receiver.
|
// Validates and submits a capacity signal to the orchestrator spool receiver.
|
||||||
$g1_pubkey = trim($_POST['g1_pubkey'] ?? '');
|
$g1_pubkey = trim($_POST['g1_pubkey'] ?? '');
|
||||||
$direction = trim($_POST['direction'] ?? '');
|
$direction = trim($_POST['direction'] ?? '');
|
||||||
$category_id = trim($_POST['category_id'] ?? '');
|
$category_id = trim($_POST['category_id'] ?? '');
|
||||||
$capacity_description = trim($_POST['capacity_description'] ?? '');
|
$capacity_description = trim($_POST['capacity_description'] ?? '');
|
||||||
$g1_denomination = trim($_POST['g1_denomination'] ?? '');
|
$g1_denomination = trim($_POST['g1_denomination'] ?? '');
|
||||||
$duration_days = intval($_POST['duration_days'] ?? 0);
|
$duration_days = intval($_POST['duration_days'] ?? 0);
|
||||||
|
|
||||||
// Validate required fields.
|
// Validate required fields.
|
||||||
$errors = [];
|
$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 (!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 (!$capacity_description) $errors[] = 'Description is required.';
|
||||||
if (!$g1_denomination || !is_numeric($g1_denomination) || floatval($g1_denomination) <= 0) {
|
if (!$g1_denomination || !is_numeric($g1_denomination) || floatval($g1_denomination) <= 0) {
|
||||||
$errors[] = 'Ğ1 amount must be a positive number.';
|
$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.
|
// Build the spool envelope per contracts/signal-v1.json.
|
||||||
$envelope = [
|
$envelope = [
|
||||||
'_header' => [
|
'_header' => [
|
||||||
'addon' => 'cry01',
|
'addon' => 'cry01',
|
||||||
'association_slug' => $association_slug,
|
'association_slug' => $association_slug,
|
||||||
'participant_xchan' => get_observer_hash(),
|
'participant_xchan' => get_observer_hash(),
|
||||||
'submitted_at' => date('c'),
|
'submitted_at' => date('c'),
|
||||||
'node_token_hash' => hash('sha256', $token),
|
'node_token_hash' => hash('sha256', $token),
|
||||||
],
|
],
|
||||||
'_payload' => [
|
'_payload' => [
|
||||||
'g1_pubkey' => $g1_pubkey,
|
'g1_pubkey' => $g1_pubkey,
|
||||||
@@ -83,7 +83,7 @@ function cry01_handle_signal_post($association_slug, $access) {
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function cry01_handle_manage_post($association_slug) {
|
function cry01_handle_manage_post($association_slug) {
|
||||||
// Handles operator manage actions: currently only cache refresh.
|
// Handles operator manage actions.
|
||||||
if (!cry01_verify_csrf()) {
|
if (!cry01_verify_csrf()) {
|
||||||
return cry01_render_error('Invalid form token. Please reload and try again.');
|
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'] ?? '';
|
$action = $_POST['action'] ?? '';
|
||||||
|
|
||||||
if ($action === 'refresh_cache') {
|
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 '<div class="cry01-content">'
|
||||||
|
. '<div class="alert alert-warning">No Ğ1 public key received. '
|
||||||
|
. 'Unlock your <a href="' . z_root() . '/g1wallet">Ğ1 Wallet</a> first, then try again.</div>'
|
||||||
|
. '</div>'
|
||||||
|
. cry01_render_manage($association_slug);
|
||||||
|
}
|
||||||
|
$ok = cry01_refresh_balance_cache($g1_pubkey);
|
||||||
$msg = $ok
|
$msg = $ok
|
||||||
? '<div class="alert alert-success">Balance cache refreshed successfully.</div>'
|
? '<div class="alert alert-success">Balance cache refreshed successfully.</div>'
|
||||||
: '<div class="alert alert-warning">Cache refresh failed. Check that the Duniter node is reachable and the operator Ğ1 key is configured.</div>';
|
: '<div class="alert alert-warning">Cache refresh failed. Check that the Duniter node is reachable.</div>';
|
||||||
return '<div class="cry01-content">' . $msg . '</div>' . cry01_render_manage($association_slug);
|
return '<div class="cry01-content">' . $msg . '</div>' . cry01_render_manage($association_slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,8 +118,8 @@ function cry01_handle_manage_post($association_slug) {
|
|||||||
function cry01_post_to_orchestrator($path, $payload) {
|
function cry01_post_to_orchestrator($path, $payload) {
|
||||||
// POSTs a JSON envelope to the orchestrator spool receiver.
|
// POSTs a JSON envelope to the orchestrator spool receiver.
|
||||||
// Returns true on success (HTTP 201), false on any failure.
|
// Returns true on success (HTTP 201), false on any failure.
|
||||||
$config = cry01_load_config();
|
$config = cry01_load_config();
|
||||||
$base = rtrim($config['receiver_url'] ?? '', '/');
|
$base = rtrim($config['receiver_url'] ?? '', '/');
|
||||||
if (!$base) {
|
if (!$base) {
|
||||||
logger('cry01_spool: receiver_url not configured');
|
logger('cry01_spool: receiver_url not configured');
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user