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>
281 lines
12 KiB
HTML
281 lines
12 KiB
HTML
<!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 & 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>
|