155 lines
7.2 KiB
PHP
155 lines
7.2 KiB
PHP
<?php
|
|
|
|
/**
|
|
* g1wallet_renderer.php — All HTML rendering for g1wallet.
|
|
* Knows nothing about network calls or POST handling.
|
|
* Knows nothing about crypto — that lives entirely in g1wallet.js.
|
|
*/
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// ACCESS WALL
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function g1wallet_render_access_wall() {
|
|
$directory_url = g1wallet_h(z_root() . '/channel/theron');
|
|
return '
|
|
<div class="g1wallet-content">
|
|
<div class="alert alert-info" role="alert">
|
|
<strong>SASE verification required to access the Ğ1 Wallet.</strong>
|
|
This wallet is available to verified HOA participants only.
|
|
To participate, you must complete the SASE process.
|
|
Visit <a href="' . $directory_url . '">' . g1wallet_h(App::get_hostname()) . '</a> to begin.
|
|
</div>
|
|
</div>
|
|
';
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// WALLET LANDING
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function g1wallet_render_landing($access) {
|
|
// Wallet landing: shows unlock form or unlocked interface depending on JS session state.
|
|
// JS swaps to the unlocked view on successful derivation.
|
|
|
|
$out = '<div class="g1wallet-content">';
|
|
$out .= '<div class="g1wallet-header mb-3">';
|
|
$out .= '<h2>Ğ1 Wallet</h2>';
|
|
$out .= '<p class="text-muted">Your self-sovereign Ğ1 identity. Keys are derived in your browser and never leave your device.</p>';
|
|
$out .= '</div>';
|
|
|
|
// Optional participation notice.
|
|
$out .= '<p class="text-muted small g1wallet-optional-notice">';
|
|
$out .= 'The Ğ1 Wallet is <strong>optional</strong>. ';
|
|
$out .= 'Ğ1 is a libre currency independent of fiat, used for valuing and bartering surplus capacity among private individuals. ';
|
|
$out .= 'The Civic Infrastructure implements it for participants who choose to engage in that economy. ';
|
|
$out .= 'It is not required for participation in the diagnostic record system. ';
|
|
$out .= 'Future addons such as Barter will require an active wallet — this page is where you manage it.';
|
|
$out .= '</p>';
|
|
|
|
// Locked view — shown by default. JS hides this and shows unlocked-view on successful derivation.
|
|
$out .= '<div id="g1wallet-locked-view">';
|
|
$out .= g1wallet_render_unlock_form();
|
|
$out .= '</div>';
|
|
|
|
// Unlocked view — hidden by default. JS populates and shows this after derivation.
|
|
$out .= '<div id="g1wallet-unlocked-view" style="display:none;">';
|
|
$out .= g1wallet_render_unlocked_placeholder($access);
|
|
$out .= '</div>';
|
|
|
|
$out .= '</div>';
|
|
return $out;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// UNLOCK FORM
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function g1wallet_render_unlock_form() {
|
|
// Renders the wallet unlock form.
|
|
// The form is handled entirely by g1wallet.js — it does NOT POST to the server.
|
|
// The mnemonic never leaves the browser.
|
|
//
|
|
// Derivation (Duniter v2 / Substrate / Ğecko standard):
|
|
// 12-word BIP39 mnemonic
|
|
// → mnemonicToEntropy() → 16 bytes of raw entropy
|
|
// → use entropy directly as 32-byte Ed25519 seed (zero-padded right)
|
|
// → derive Ed25519 keypair (SubtleCrypto, non-extractable private key)
|
|
// → SS58-encode public key with Ğ1 network prefix → g1... address
|
|
|
|
$out = '<div class="g1wallet-unlock-form">';
|
|
$out .= '<h4>Unlock Your Wallet</h4>';
|
|
$out .= '<p class="text-muted small">Enter your 12-word Ğ1 mnemonic phrase. It is used only in your browser to derive your keypair. It is never sent to the server.</p>';
|
|
|
|
$out .= '<div class="mb-3">';
|
|
$out .= '<label class="form-label" for="g1wallet-mnemonic">Mnemonic Phrase <span class="text-danger">*</span></label>';
|
|
$out .= '<textarea class="form-control font-monospace" id="g1wallet-mnemonic" name="g1wallet_mnemonic"
|
|
rows="2" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
|
|
placeholder="word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12"></textarea>';
|
|
$out .= '<div class="form-text">Your 12-word Ğ1 mnemonic — the recovery phrase generated when your Ğ1 account was created. Words are separated by single spaces, lowercase, English wordlist.</div>';
|
|
$out .= '</div>';
|
|
|
|
$out .= '<div id="g1wallet-unlock-error" class="alert alert-danger" style="display:none;" role="alert"></div>';
|
|
|
|
$out .= '<div class="mt-3">';
|
|
$out .= '<button type="button" id="g1wallet-unlock-btn" class="btn btn-primary">Unlock Wallet</button>';
|
|
$out .= '<span id="g1wallet-unlock-spinner" class="ms-2 text-muted small" style="display:none;">Deriving keypair…</span>';
|
|
$out .= '</div>';
|
|
|
|
$out .= '</div>';
|
|
return $out;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// UNLOCKED INTERFACE
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function g1wallet_render_unlocked_placeholder($access) {
|
|
// Unlocked wallet interface.
|
|
// Populated by g1wallet.js after key derivation.
|
|
|
|
$out = '<div class="g1wallet-unlocked">';
|
|
$out .= '<div class="alert alert-success d-flex justify-content-between align-items-center">';
|
|
$out .= '<span><strong>Wallet unlocked.</strong></span>';
|
|
$out .= '<button type="button" id="g1wallet-lock-btn" class="btn btn-sm btn-outline-secondary">Lock</button>';
|
|
$out .= '</div>';
|
|
|
|
$out .= '<div class="mb-3">';
|
|
$out .= '<h5 class="g1wallet-section-label">Public Key (Ğ1 Address)</h5>';
|
|
$out .= '<p id="g1wallet-pubkey-display" class="font-monospace text-muted small">—</p>';
|
|
$out .= '</div>';
|
|
|
|
$out .= '<div class="mb-3">';
|
|
$out .= '<h5 class="g1wallet-section-label">Ğ1 Balance</h5>';
|
|
$out .= '<p id="g1wallet-balance-display" class="text-muted fst-italic">Not yet loaded.</p>';
|
|
$out .= '<button type="button" id="g1wallet-refresh-balance-btn" class="btn btn-sm btn-outline-primary">Refresh Balance</button>';
|
|
$out .= '</div>';
|
|
|
|
// Hidden form to store pubkey in channel settings — posted once after first unlock.
|
|
// g1wallet.js posts to this via fetch() after derivation, not via form submit.
|
|
$out .= '<form id="g1wallet-pubkey-form" style="display:none;">';
|
|
$out .= g1wallet_csrf_token();
|
|
$out .= '<input type="hidden" id="g1wallet-pubkey-input" name="g1_pubkey" value="">';
|
|
$out .= '</form>';
|
|
|
|
if ($access === 'operator') {
|
|
$out .= '<div class="mt-3">';
|
|
$out .= '<p class="text-muted small">You are the operator. Your Ğ1 public key is stored in your channel settings, not in config.</p>';
|
|
$out .= '</div>';
|
|
}
|
|
|
|
$out .= '</div>';
|
|
return $out;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// ERROR
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function g1wallet_render_error($message) {
|
|
// Shows a plain-language error. Never shows a blank page or stack trace.
|
|
return '<div class="g1wallet-content"><div class="alert alert-danger">'
|
|
. g1wallet_h($message)
|
|
. '</div></div>';
|
|
}
|