txo_parser
Version:
Parser for TXO URI Specification (v0.2)
191 lines (168 loc) • 9.01 kB
JavaScript
import { html, render, onUnmount } from 'https://losos.org/losos/html.js'
import { formatTxoUri, parseTxoUri } from '../../index.js'
export default {
label: 'Build',
icon: '\uD83D\uDEE0',
canHandle(subject, store) {
var node = store.get(subject.value)
var type = node && store.type(node)
return type && (type.includes('TxoDemo') || type.includes('VoucherPool'))
},
render(subject, store, container) {
var network = 'btc'
var txid = ''
var output = '0'
var amount = ''
var privkey = ''
var scriptType = ''
var result = null
var error = null
var networks = [
{ code: 'btc', name: 'Bitcoin' },
{ code: 'tbtc4', name: 'Testnet 4' },
{ code: 'tbtc3', name: 'Testnet 3' },
{ code: 'ltc', name: 'Litecoin' },
{ code: 'liq', name: 'Liquid' }
]
function copyText(text) {
navigator.clipboard.writeText(text)
var t = document.createElement('div')
t.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(59,130,246,0.9);color:#fff;padding:8px 20px;border-radius:8px;font-size:0.85rem;font-weight:600;z-index:999;animation:v-fade 0.3s'
t.textContent = 'Copied!'
document.body.appendChild(t)
setTimeout(function() { t.remove() }, 1500)
}
function readFields() {
var el = function(cls) { return container.querySelector('.' + cls) }
network = (el('b-network') && el('b-network').value) || 'btc'
txid = (el('b-txid') && el('b-txid').value.trim()) || ''
output = (el('b-output') && el('b-output').value.trim()) || '0'
amount = (el('b-amount') && el('b-amount').value.trim()) || ''
privkey = (el('b-privkey') && el('b-privkey').value.trim()) || ''
scriptType = (el('b-script') && el('b-script').value.trim()) || ''
}
function buildUri() {
readFields()
error = null
result = null
if (!txid) { error = 'TXID is required'; renderApp(); return }
var data = {
network: network,
txid: txid.toLowerCase(),
output: parseInt(output) || 0
}
if (amount) data.amount = parseFloat(amount) || parseInt(amount) || amount
if (privkey) data.privkey = privkey
if (scriptType) data.script_type = scriptType
try {
result = formatTxoUri(data)
error = null
} catch(e) {
error = e.message
result = null
}
renderApp()
}
function randomTxid() {
var hex = ''
for (var i = 0; i < 64; i++) hex += '0123456789abcdef'[Math.floor(Math.random() * 16)]
var el = container.querySelector('.b-txid')
if (el) el.value = hex
txid = hex
}
function renderApp() {
render(container, html`
<style>
.b-wrap { padding: 0 16px 40px; }
.b-hero { text-align: center; padding: 40px 0 28px; }
.b-hero-icon { font-size: 3rem; margin-bottom: 8px; }
.b-hero-title { font-size: 1.6rem; font-weight: 800; margin-bottom: 4px; }
.b-hero-sub { font-size: 0.9rem; color: rgba(255,255,255,0.35); }
.b-card { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); border-radius: 14px; padding: 24px; margin-bottom: 20px; }
.b-card h2 { font-size: 1rem; font-weight: 600; margin: 0 0 16px; }
.b-field { margin-bottom: 14px; }
.b-label { font-size: 0.78rem; color: rgba(255,255,255,0.4); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px; }
.b-input { width: 100%; padding: 10px 14px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.08); background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.9); font: 0.88rem 'SF Mono', monospace; outline: none; }
.b-input:focus { border-color: rgba(59,130,246,0.5); }
.b-input::placeholder { color: rgba(255,255,255,0.2); }
.b-select { padding: 10px 14px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.08); background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.9); font: 0.88rem -apple-system, sans-serif; outline: none; width: 100%; }
.b-row { display: flex; gap: 12px; }
.b-row > * { flex: 1; }
.b-btn { display: inline-flex; align-items: center; gap: 6px; padding: 10px 20px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.08); background: rgba(255,255,255,0.06); color: rgba(255,255,255,0.8); font: 600 0.85rem -apple-system, sans-serif; cursor: pointer; transition: all 0.15s; }
.b-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
.b-btn-primary { background: linear-gradient(135deg, #3b82f6, #2563eb); border-color: rgba(59,130,246,0.4); color: #fff; }
.b-btn-sm { padding: 5px 10px; font-size: 0.78rem; border-radius: 6px; }
.b-result { background: rgba(16,185,129,0.06); border: 1px solid rgba(16,185,129,0.2); border-radius: 10px; padding: 16px; margin-top: 16px; }
.b-result-uri { font-family: 'SF Mono', monospace; font-size: 0.85rem; color: #10b981; word-break: break-all; line-height: 1.5; margin-bottom: 10px; }
.b-error { background: rgba(239,68,68,0.08); border: 1px solid rgba(239,68,68,0.2); border-radius: 10px; padding: 14px; color: #ef4444; font-size: 0.85rem; margin-top: 16px; }
.b-hint { font-size: 0.72rem; color: rgba(255,255,255,0.2); margin-top: 4px; }
@media (max-width: 600px) { .b-row { flex-direction: column; } }
</style>
<div class="b-wrap">
<div class="b-hero">
<div class="b-hero-icon">\uD83D\uDEE0</div>
<div class="b-hero-title">Build TXO URI</div>
<div class="b-hero-sub">Construct a TXO URI from individual fields</div>
</div>
<div class="b-card">
<h2>Fields</h2>
<div class="b-row">
<div class="b-field">
<div class="b-label">Network</div>
<select class="b-select b-network" onchange="${buildUri}">
${networks.map(function(n) {
return html`<option value="${n.code}" selected="${n.code === network ? true : false}">${n.name} (${n.code})</option>`
})}
</select>
</div>
<div class="b-field" style="flex:0.4">
<div class="b-label">Output Index</div>
<input class="b-input b-output" value="${output}" type="number" min="0"
oninput="${buildUri}" />
</div>
</div>
<div class="b-field">
<div class="b-label" style="display:flex;justify-content:space-between;align-items:center">
TXID
<button class="b-btn b-btn-sm" onclick="${function() { randomTxid(); buildUri() }}">\uD83C\uDFB2 Random</button>
</div>
<input class="b-input b-txid" placeholder="64-character hex transaction ID" value="${txid}"
oninput="${buildUri}" />
</div>
<div class="b-row">
<div class="b-field">
<div class="b-label">Amount <span style="font-weight:400;text-transform:none">(optional)</span></div>
<input class="b-input b-amount" placeholder="e.g. 0.75 or 50000" value="${amount}"
oninput="${buildUri}" />
</div>
<div class="b-field">
<div class="b-label">Script Type <span style="font-weight:400;text-transform:none">(optional)</span></div>
<input class="b-input b-script" placeholder="e.g. p2tr" value="${scriptType}"
oninput="${buildUri}" />
</div>
</div>
<div class="b-field">
<div class="b-label">Private Key <span style="font-weight:400;text-transform:none">(optional, keep secure!)</span></div>
<input class="b-input b-privkey" placeholder="WIF or hex key" value="${privkey}"
oninput="${buildUri}" />
<div class="b-hint">\u26A0 Including a private key makes this URI spend-ready. Share only over secure channels.</div>
</div>
<button class="b-btn b-btn-primary" onclick="${buildUri}">\uD83D\uDEE0 Build URI</button>
${result ? html`
<div class="b-result">
<div class="b-result-uri">${result}</div>
<div style="display:flex;gap:8px">
<button class="b-btn b-btn-sm" onclick="${function() { copyText(result) }}">\u2398 Copy</button>
<button class="b-btn b-btn-sm" onclick="${function() { copyText(JSON.stringify(parseTxoUri(result), null, 2)) }}">\u2398 Copy JSON</button>
</div>
</div>
` : null}
${error ? html`<div class="b-error">\u2716 ${error}</div>` : null}
</div>
</div>
`)
}
renderApp()
onUnmount(container, function() {})
}
}