deviant/public/index.html
walerikus f8568e7691 feat(onramp): BCH Buy/On-ramp in Apps→Discover (ADR-0036)
Third-party fiat/crypto → BCH acquisition surfaced as external,
transparency-ranked, non-endorsed link-outs. Non-custodial (BCH to the
user's own address); read-only integration depth (Tier 1 referral handoff
+ Tier 2 read-only quote, no Tier-3 custody surface).

Core (src/onramp/): OnrampProvider contract + four launch adapters
(Guardarian, Trocador, SideShift, Changelly); pure transparency ranking
(objective facts > kycnot.me reputation > local per-device rating, never
merged); per-jurisdiction availability gating; key-gated kycnot.me
read-only reputation client.

Wiring: web API handlers + routes (/api/onramp[/handoff|/quote|/rate],
?journey=&country=); referral IDs + API keys via env, never influencing
ranking. UI: "Buy BCH" Discover tab (public/) rendering the three signals
separately, regulator/region warnings, local star ratings, external-only
handoff.

Live-API verified (2026-05-31): corrected kycnot.me (kycLevel 0-4,
key-gated) and Trocador (/anonpay/ handoff, key-gated quote). Some
deep-link param names UNCONFIRMED (commented) pending a live re-check;
jurisdiction lists seeded `global` pending real coverage data.

Tests: 46 onramp unit + 6 web-route (real listener); full suite 460 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:44:07 +02:00

281 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Deviant Wallet</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<!-- Loading splash: the CosmoGenesis opening, shown on first paint, fades out once the app is ready. -->
<div id="splash" class="splash" role="status" aria-label="Loading Deviant Wallet">
<div class="splash-inner">
<div class="splash-wordmark">Deviant</div>
<div class="splash-label">A response to a default trajectory</div>
<p class="splash-opening">When the institutional, technical, economic, and rhetorical forces all point in the same direction, the outcome they point toward becomes the default trajectory. <span class="splash-deviation">Deviation</span> from the trajectory requires active resistance. In a <span class="splash-rot" id="splash-rot">captured</span> information environment with limited resistance capacity, the default trajectory is what occurs.</p>
<p class="splash-response"><strong>Deviant</strong> exists to enable that resistance — by giving users on-chain experience that <em>intermediation cannot mediate</em>.</p>
<div class="splash-slogan">Power Your Ideas.</div>
<div class="splash-foot">Version 0.1 · CosmoGenesis · on Bitcoin Cash</div>
</div>
</div>
<header>
<h1>Deviant <span class="muted">Wallet</span></h1>
<div id="wallet-meta" class="meta">connecting…</div>
</header>
<nav class="topnav" id="topnav">
<button data-view="home" class="navbtn active">Home</button>
<button data-view="assets" class="navbtn">My Assets</button>
<button data-view="actions" class="navbtn">Actions</button>
<button data-view="asm" class="navbtn">ASM · DEAs</button>
<button data-view="buy" class="navbtn">Buy BCH</button>
</nav>
<main>
<!-- ============ Home ============ -->
<section id="view-home" class="view">
<div class="card">
<div class="balance-hero">
<div class="hero-label muted">Bitcoin Cash</div>
<div class="hero-bch" id="home-native">…</div>
<div class="hero-total" id="home-total"></div>
</div>
</div>
<div class="card">
<div class="card-head">
<h2>Favorites</h2>
<button id="refresh-home" class="ghost">Refresh</button>
</div>
<div id="favorites">Loading…</div>
</div>
</section>
<!-- ============ My Assets ============ -->
<section id="view-assets" class="view" hidden>
<div class="card" id="balance-card">
<div class="card-head">
<h2>My Assets</h2>
<button id="refresh" class="ghost">Refresh</button>
</div>
<p class="hint muted">Tap an asset for actions · ★ to favorite.</p>
<div id="balance">Loading…</div>
</div>
</section>
<!-- ============ Actions ============ -->
<section id="view-actions" class="view" hidden>
<div class="card">
<div class="card-head"><h2 id="action-title">Action</h2></div>
<div class="tabs" id="tabs">
<button data-op="claim" class="tab active">Claim</button>
<button data-op="redeem" class="tab">Redeem</button>
<button data-op="swap" class="tab">Swap</button>
<button data-op="create" class="tab">Create</button>
<button data-op="register" class="tab">Register</button>
</div>
<form id="action-form" autocomplete="off">
<div class="field" data-for="token">
<label>Token (ticker or id)</label>
<input name="token" placeholder="ASM" />
</div>
<div class="field" data-for="source" hidden>
<label>Source token</label>
<input name="source" placeholder="ASM" />
</div>
<div class="field" data-for="target" hidden>
<label>Target token</label>
<input name="target" placeholder="ASM2" />
</div>
<div class="field" data-for="units">
<label><span id="units-label">Units</span> <span class="muted" id="units-max-hint"></span></label>
<div class="input-row">
<input name="units" inputmode="numeric" placeholder="1000" />
<button type="button" class="mini" id="max-btn" hidden>Max</button>
</div>
</div>
<div class="field" data-for="to">
<label><span id="to-label">Recipient</span> <span class="muted" id="to-hint"></span></label>
<input name="to" placeholder="leave blank to send to yourself" />
</div>
<!-- Create (DEA genesis) fields -->
<div class="field" data-for="cname" hidden>
<label>Asset name</label>
<input name="cname" placeholder="My Asset" />
</div>
<div class="field" data-for="cticker" hidden>
<label>Ticker</label>
<input name="cticker" placeholder="MYA" />
</div>
<div class="field" data-for="crate" hidden>
<label>Rate <span class="muted">(sats per unit — FIXED forever)</span></label>
<input name="crate" inputmode="numeric" placeholder="1000" />
</div>
<div class="field" data-for="cfee" hidden>
<label>Founder fee <span class="muted">(bps, 0–500)</span></label>
<input name="cfee" inputmode="numeric" placeholder="100" />
</div>
<div class="field" data-for="creservoir" hidden>
<label>Reservoir supply <span class="muted">(blank = 10^18)</span></label>
<input name="creservoir" inputmode="numeric" placeholder="1000000000000000000" />
</div>
<div class="field" data-for="cmint" hidden>
<label>Initial units for me <span class="muted">(optional — claimed at your fixed rate, backed; not a free premine)</span></label>
<input name="cmint" inputmode="numeric" placeholder="100" />
</div>
<!-- Register: guided (default) vs advanced paste -->
<div class="field" data-for="regmode" hidden>
<div class="seg" id="regmode-seg">
<button type="button" class="segbtn active" data-regmode="guided">New draft</button>
<button type="button" class="segbtn" data-regmode="paste">Add real token</button>
</div>
<p class="hint muted" id="reg-hint"></p>
</div>
<div class="field" data-for="rname" hidden>
<label>Name</label>
<input name="rname" placeholder="My Token" />
</div>
<div class="field" data-for="rticker" hidden>
<label>Ticker</label>
<input name="rticker" placeholder="MTK" />
</div>
<div class="field" data-for="rrate" hidden>
<label>Rate <span class="muted">(sats per unit)</span></label>
<input name="rrate" inputmode="numeric" placeholder="1000" />
</div>
<div class="field" data-for="raddr" hidden>
<label>Your address <span class="muted">(where these coins live)</span></label>
<input name="raddr" placeholder="bchtest:q…" />
</div>
<div class="field" data-for="definition" hidden>
<label>Token definition <span class="muted">(paste portable or chipnet JSON)</span></label>
<textarea name="definition" rows="8" spellcheck="false"
placeholder='{ "version": 1, "tier": "A", ... }'></textarea>
</div>
<div class="actions">
<button type="submit" class="primary" id="submit-btn">Preview</button>
</div>
</form>
<div id="preview" class="preview" hidden>
<pre id="preview-text"></pre>
<div class="actions">
<button id="broadcast" class="danger">Confirm &amp; Broadcast</button>
<button id="cancel" class="ghost">Cancel</button>
</div>
</div>
<div id="result" class="result" hidden></div>
</div>
</section>
<!-- ============ ASM (DEAs against BCH) ============ -->
<section id="view-asm" class="view" hidden>
<div class="card">
<div class="card-head">
<h2>ASM · Orbital Network</h2>
<button id="refresh-asm" class="ghost">Refresh</button>
</div>
<p class="hint muted">
ASM-eligible assets priced against BCH. These swap counterparty-free
(DEA / DRA both ways; PLA until its reservoir depletes).
</p>
<div id="asm-list">Loading…</div>
</div>
</section>
<!-- ============ Buy / On-ramp (ADR-0036) ============ -->
<section id="view-buy" class="view" hidden>
<div class="card">
<div class="card-head">
<h2>Buy / On-ramp</h2>
<button id="refresh-buy" class="ghost">Refresh</button>
</div>
<p class="hint muted">
Third-party ways to get BCH sent to <em>your own</em> address. Ranked by
transparency — KYC posture · external reputation (kycnot.me) · your own
ratings. <strong>Not</strong> Deviant endorsements; each is an independent
service you leave the wallet to use.
</p>
<div class="buy-controls">
<select id="buy-journey" aria-label="What do you have?">
<option value="">All routes</option>
<option value="fiat-to-bch">I have fiat (card / bank)</option>
<option value="crypto-to-bch">I have crypto</option>
</select>
<input id="buy-amount" inputmode="decimal" placeholder="amount (optional)" />
<input id="buy-from" placeholder="from (EUR / BTC…)" />
<input id="buy-country" maxlength="2" placeholder="country (e.g. US)" />
</div>
<div id="buy-list">Loading…</div>
</div>
</section>
</main>
<footer class="muted">
Two-phase by design: Preview never broadcasts. Nothing hits the chain until you confirm.
</footer>
<!-- Global primary actions -->
<nav class="bottombar">
<button class="primary" id="btn-send">Send</button>
<button id="btn-receive">Receive</button>
</nav>
<!-- Receive sheet -->
<div id="receive-modal" class="modal" hidden>
<div class="modal-card">
<div class="card-head"><h2>Receive</h2><button id="receive-close" class="ghost">Close</button></div>
<div id="receive-body"></div>
</div>
</div>
<!-- Send sheet: pick what to send -->
<div id="send-modal" class="modal" hidden>
<div class="modal-card">
<div class="card-head"><h2>Send</h2><button id="send-close" class="ghost">Close</button></div>
<p class="hint muted">What do you want to send?</p>
<div class="actions send-choices">
<button class="primary" id="send-token">CashToken (real, on-chain)</button>
<button id="send-local">Draft / wallet-local</button>
</div>
</div>
</div>
<script src="/app.js"></script>
<script>
// CosmoGenesis loading splash: rotate the word (captured -> compromised -> constrained),
// then dismiss on window-load with a minimum on-screen time and a hard safety cap.
(function () {
var splash = document.getElementById('splash');
if (!splash) return;
var rotTimer = null, rot = document.getElementById('splash-rot');
if (rot && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
var words = ['captured', 'compromised', 'constrained'], i = 0;
rotTimer = setInterval(function () {
rot.style.opacity = '0';
setTimeout(function () { i = (i + 1) % words.length; rot.textContent = words[i]; rot.style.opacity = '1'; }, 500);
}, 2600);
}
var start = Date.now(), MIN = 2400, FADE = 650, CAP = 8000, done = false;
function dismiss() {
if (done) return; done = true;
var wait = Math.max(0, MIN - (Date.now() - start));
setTimeout(function () {
if (rotTimer) clearInterval(rotTimer);
splash.classList.add('hide');
setTimeout(function () { if (splash.parentNode) splash.parentNode.removeChild(splash); }, FADE);
}, wait);
}
window.addEventListener('load', dismiss);
setTimeout(dismiss, CAP);
})();
</script>
</body>
</html>