txo_parser 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -17
- package/README.md +3 -1
- package/demo/blocktrail.jsonld +10 -0
- package/demo/index.html +62 -0
- package/demo/ledger-history.jsonld +9 -0
- package/demo/lib/bitcoin.js +378 -0
- package/demo/panes/blocktrails-pane.js +673 -0
- package/demo/panes/builder-pane.js +190 -0
- package/demo/panes/faucet-pane.js +137 -0
- package/demo/panes/ledger-pane.js +662 -0
- package/demo/panes/parser-pane.js +171 -0
- package/demo/panes/spec-pane.js +117 -0
- package/demo/panes/voucher-pane.js +693 -0
- package/demo/voucher-data.jsonld +31 -0
- package/demo/webledger.jsonld +9 -0
- package/index.js +29 -8
- package/package.json +2 -2
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { html, render, onUnmount } from 'https://losos.org/losos/html.js'
|
|
2
|
+
import { formatTxoUri, parseTxoUri } from '../../index.js'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
label: 'Build',
|
|
6
|
+
icon: '\uD83D\uDEE0',
|
|
7
|
+
|
|
8
|
+
canHandle(subject, store) {
|
|
9
|
+
var node = store.get(subject.value)
|
|
10
|
+
var type = node && store.type(node)
|
|
11
|
+
return type && (type.includes('TxoDemo') || type.includes('VoucherPool'))
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
render(subject, store, container) {
|
|
15
|
+
var network = 'btc'
|
|
16
|
+
var txid = ''
|
|
17
|
+
var output = '0'
|
|
18
|
+
var amount = ''
|
|
19
|
+
var privkey = ''
|
|
20
|
+
var scriptType = ''
|
|
21
|
+
var result = null
|
|
22
|
+
var error = null
|
|
23
|
+
|
|
24
|
+
var networks = [
|
|
25
|
+
{ code: 'btc', name: 'Bitcoin' },
|
|
26
|
+
{ code: 'tbtc4', name: 'Testnet 4' },
|
|
27
|
+
{ code: 'tbtc3', name: 'Testnet 3' },
|
|
28
|
+
{ code: 'ltc', name: 'Litecoin' },
|
|
29
|
+
{ code: 'liq', name: 'Liquid' }
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
function copyText(text) {
|
|
33
|
+
navigator.clipboard.writeText(text)
|
|
34
|
+
var t = document.createElement('div')
|
|
35
|
+
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'
|
|
36
|
+
t.textContent = 'Copied!'
|
|
37
|
+
document.body.appendChild(t)
|
|
38
|
+
setTimeout(function() { t.remove() }, 1500)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readFields() {
|
|
42
|
+
var el = function(cls) { return container.querySelector('.' + cls) }
|
|
43
|
+
network = (el('b-network') && el('b-network').value) || 'btc'
|
|
44
|
+
txid = (el('b-txid') && el('b-txid').value.trim()) || ''
|
|
45
|
+
output = (el('b-output') && el('b-output').value.trim()) || '0'
|
|
46
|
+
amount = (el('b-amount') && el('b-amount').value.trim()) || ''
|
|
47
|
+
privkey = (el('b-privkey') && el('b-privkey').value.trim()) || ''
|
|
48
|
+
scriptType = (el('b-script') && el('b-script').value.trim()) || ''
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildUri() {
|
|
52
|
+
readFields()
|
|
53
|
+
error = null
|
|
54
|
+
result = null
|
|
55
|
+
|
|
56
|
+
if (!txid) { error = 'TXID is required'; renderApp(); return }
|
|
57
|
+
|
|
58
|
+
var data = {
|
|
59
|
+
network: network,
|
|
60
|
+
txid: txid.toLowerCase(),
|
|
61
|
+
output: parseInt(output) || 0
|
|
62
|
+
}
|
|
63
|
+
if (amount) data.amount = parseFloat(amount) || parseInt(amount) || amount
|
|
64
|
+
if (privkey) data.privkey = privkey
|
|
65
|
+
if (scriptType) data.script_type = scriptType
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
result = formatTxoUri(data)
|
|
69
|
+
error = null
|
|
70
|
+
} catch(e) {
|
|
71
|
+
error = e.message
|
|
72
|
+
result = null
|
|
73
|
+
}
|
|
74
|
+
renderApp()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function randomTxid() {
|
|
78
|
+
var hex = ''
|
|
79
|
+
for (var i = 0; i < 64; i++) hex += '0123456789abcdef'[Math.floor(Math.random() * 16)]
|
|
80
|
+
var el = container.querySelector('.b-txid')
|
|
81
|
+
if (el) el.value = hex
|
|
82
|
+
txid = hex
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function renderApp() {
|
|
86
|
+
render(container, html`
|
|
87
|
+
<style>
|
|
88
|
+
.b-wrap { padding: 0 16px 40px; }
|
|
89
|
+
.b-hero { text-align: center; padding: 40px 0 28px; }
|
|
90
|
+
.b-hero-icon { font-size: 3rem; margin-bottom: 8px; }
|
|
91
|
+
.b-hero-title { font-size: 1.6rem; font-weight: 800; margin-bottom: 4px; }
|
|
92
|
+
.b-hero-sub { font-size: 0.9rem; color: rgba(255,255,255,0.35); }
|
|
93
|
+
.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; }
|
|
94
|
+
.b-card h2 { font-size: 1rem; font-weight: 600; margin: 0 0 16px; }
|
|
95
|
+
.b-field { margin-bottom: 14px; }
|
|
96
|
+
.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; }
|
|
97
|
+
.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; }
|
|
98
|
+
.b-input:focus { border-color: rgba(59,130,246,0.5); }
|
|
99
|
+
.b-input::placeholder { color: rgba(255,255,255,0.2); }
|
|
100
|
+
.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%; }
|
|
101
|
+
.b-row { display: flex; gap: 12px; }
|
|
102
|
+
.b-row > * { flex: 1; }
|
|
103
|
+
.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; }
|
|
104
|
+
.b-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
|
105
|
+
.b-btn-primary { background: linear-gradient(135deg, #3b82f6, #2563eb); border-color: rgba(59,130,246,0.4); color: #fff; }
|
|
106
|
+
.b-btn-sm { padding: 5px 10px; font-size: 0.78rem; border-radius: 6px; }
|
|
107
|
+
.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; }
|
|
108
|
+
.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; }
|
|
109
|
+
.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; }
|
|
110
|
+
.b-hint { font-size: 0.72rem; color: rgba(255,255,255,0.2); margin-top: 4px; }
|
|
111
|
+
@media (max-width: 600px) { .b-row { flex-direction: column; } }
|
|
112
|
+
</style>
|
|
113
|
+
|
|
114
|
+
<div class="b-wrap">
|
|
115
|
+
<div class="b-hero">
|
|
116
|
+
<div class="b-hero-icon">\uD83D\uDEE0</div>
|
|
117
|
+
<div class="b-hero-title">Build TXO URI</div>
|
|
118
|
+
<div class="b-hero-sub">Construct a TXO URI from individual fields</div>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div class="b-card">
|
|
122
|
+
<h2>Fields</h2>
|
|
123
|
+
|
|
124
|
+
<div class="b-row">
|
|
125
|
+
<div class="b-field">
|
|
126
|
+
<div class="b-label">Network</div>
|
|
127
|
+
<select class="b-select b-network" onchange="${buildUri}">
|
|
128
|
+
${networks.map(function(n) {
|
|
129
|
+
return html`<option value="${n.code}" selected="${n.code === network ? true : false}">${n.name} (${n.code})</option>`
|
|
130
|
+
})}
|
|
131
|
+
</select>
|
|
132
|
+
</div>
|
|
133
|
+
<div class="b-field" style="flex:0.4">
|
|
134
|
+
<div class="b-label">Output Index</div>
|
|
135
|
+
<input class="b-input b-output" value="${output}" type="number" min="0"
|
|
136
|
+
oninput="${buildUri}" />
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div class="b-field">
|
|
141
|
+
<div class="b-label" style="display:flex;justify-content:space-between;align-items:center">
|
|
142
|
+
TXID
|
|
143
|
+
<button class="b-btn b-btn-sm" onclick="${function() { randomTxid(); buildUri() }}">\uD83C\uDFB2 Random</button>
|
|
144
|
+
</div>
|
|
145
|
+
<input class="b-input b-txid" placeholder="64-character hex transaction ID" value="${txid}"
|
|
146
|
+
oninput="${buildUri}" />
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<div class="b-row">
|
|
150
|
+
<div class="b-field">
|
|
151
|
+
<div class="b-label">Amount <span style="font-weight:400;text-transform:none">(optional)</span></div>
|
|
152
|
+
<input class="b-input b-amount" placeholder="e.g. 0.75 or 50000" value="${amount}"
|
|
153
|
+
oninput="${buildUri}" />
|
|
154
|
+
</div>
|
|
155
|
+
<div class="b-field">
|
|
156
|
+
<div class="b-label">Script Type <span style="font-weight:400;text-transform:none">(optional)</span></div>
|
|
157
|
+
<input class="b-input b-script" placeholder="e.g. p2tr" value="${scriptType}"
|
|
158
|
+
oninput="${buildUri}" />
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="b-field">
|
|
163
|
+
<div class="b-label">Private Key <span style="font-weight:400;text-transform:none">(optional, keep secure!)</span></div>
|
|
164
|
+
<input class="b-input b-privkey" placeholder="WIF or hex key" value="${privkey}"
|
|
165
|
+
oninput="${buildUri}" />
|
|
166
|
+
<div class="b-hint">\u26A0 Including a private key makes this URI spend-ready. Share only over secure channels.</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<button class="b-btn b-btn-primary" onclick="${buildUri}">\uD83D\uDEE0 Build URI</button>
|
|
170
|
+
|
|
171
|
+
${result ? html`
|
|
172
|
+
<div class="b-result">
|
|
173
|
+
<div class="b-result-uri">${result}</div>
|
|
174
|
+
<div style="display:flex;gap:8px">
|
|
175
|
+
<button class="b-btn b-btn-sm" onclick="${function() { copyText(result) }}">\u2398 Copy</button>
|
|
176
|
+
<button class="b-btn b-btn-sm" onclick="${function() { copyText(JSON.stringify(parseTxoUri(result), null, 2)) }}">\u2398 Copy JSON</button>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
` : null}
|
|
180
|
+
|
|
181
|
+
${error ? html`<div class="b-error">\u2716 ${error}</div>` : null}
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
`)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
renderApp()
|
|
188
|
+
onUnmount(container, function() {})
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { html, render, keyed, onUnmount } from 'https://losos.org/losos/html.js'
|
|
2
|
+
import { parseVoucherFromItem, buildTxoUri } from '../lib/bitcoin.js'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
label: 'Faucet',
|
|
6
|
+
icon: '\uD83D\uDEB0',
|
|
7
|
+
|
|
8
|
+
canHandle(subject, store) {
|
|
9
|
+
var node = store.get(subject.value)
|
|
10
|
+
if (!node) return false
|
|
11
|
+
var type = store.type(node)
|
|
12
|
+
return type && type.includes('VoucherPool')
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
render(subject, lionStore, container, rawData) {
|
|
16
|
+
|
|
17
|
+
// ── Load state ──────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
var data = rawData || {}
|
|
20
|
+
var items = data['schema:itemListElement'] || []
|
|
21
|
+
var allVouchers = items.map(parseVoucherFromItem)
|
|
22
|
+
|
|
23
|
+
// Only show dispensable vouchers: unspent + has key
|
|
24
|
+
var available = allVouchers.filter(function(v) {
|
|
25
|
+
return v.privkey && v.status === 'unspent'
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// ── Helpers ─────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
function toast(msg) {
|
|
31
|
+
var t = document.createElement('div')
|
|
32
|
+
t.style.cssText = 'position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(16,185,129,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'
|
|
33
|
+
t.textContent = msg
|
|
34
|
+
document.body.appendChild(t)
|
|
35
|
+
setTimeout(function() { t.remove() }, 2000)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function copyText(text) {
|
|
39
|
+
navigator.clipboard.writeText(text).then(function() { toast('Copied to clipboard') })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function truncate(s, start, end) {
|
|
43
|
+
start = start || 8; end = end || 6
|
|
44
|
+
if (!s || s.length <= start + end + 3) return s
|
|
45
|
+
return s.slice(0, start) + '\u2026' + s.slice(-end)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function satsBtc(sats) { return (sats / 1e8).toFixed(8) }
|
|
49
|
+
|
|
50
|
+
function faucetLink(v) {
|
|
51
|
+
return location.origin + location.pathname + '?key=' + v.privkey
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Render ──────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
var totalSats = available.reduce(function(s, v) { return s + (v.amount || 0) }, 0)
|
|
57
|
+
|
|
58
|
+
render(container, html`
|
|
59
|
+
<style>
|
|
60
|
+
.f-wrap { padding: 0 16px 40px; }
|
|
61
|
+
.f-hero { text-align: center; padding: 40px 0 32px; }
|
|
62
|
+
.f-hero-icon { font-size: 3rem; margin-bottom: 8px; }
|
|
63
|
+
.f-hero-title { font-size: 1.6rem; font-weight: 800; margin-bottom: 4px; }
|
|
64
|
+
.f-hero-sub { font-size: 0.9rem; color: rgba(255,255,255,0.35); }
|
|
65
|
+
.f-stats { display: flex; gap: 16px; justify-content: center; margin-bottom: 32px; flex-wrap: wrap; }
|
|
66
|
+
.f-stat { background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.06); border-radius: 10px; padding: 14px 24px; text-align: center; }
|
|
67
|
+
.f-stat-val { font-size: 1.5rem; font-weight: 700; color: #10b981; }
|
|
68
|
+
.f-stat-label { font-size: 0.7rem; color: rgba(255,255,255,0.35); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 2px; }
|
|
69
|
+
.f-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; }
|
|
70
|
+
.f-card { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.08); border-radius: 14px; padding: 24px; backdrop-filter: blur(12px); transition: all 0.2s; }
|
|
71
|
+
.f-card:hover { background: rgba(255,255,255,0.08); border-color: rgba(124,58,237,0.3); }
|
|
72
|
+
.f-card-amount { font-size: 1.8rem; font-weight: 800; margin-bottom: 4px; }
|
|
73
|
+
.f-card-amount small { font-size: 0.4em; color: rgba(255,255,255,0.35); }
|
|
74
|
+
.f-card-btc { font-size: 0.82rem; color: rgba(255,255,255,0.3); margin-bottom: 16px; }
|
|
75
|
+
.f-card-txid { font-size: 0.75rem; color: rgba(255,255,255,0.25); font-family: 'SF Mono', 'Fira Code', monospace; margin-bottom: 16px; }
|
|
76
|
+
.f-card-actions { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
77
|
+
.f-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.82rem -apple-system, sans-serif; cursor: pointer; transition: all 0.15s; }
|
|
78
|
+
.f-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
|
|
79
|
+
.f-btn-primary { background: linear-gradient(135deg, #7c3aed, #6d28d9); border-color: rgba(124,58,237,0.4); color: #fff; box-shadow: 0 4px 16px rgba(124,58,237,0.2); flex: 1; justify-content: center; }
|
|
80
|
+
.f-btn-primary:hover { box-shadow: 0 6px 24px rgba(124,58,237,0.3); }
|
|
81
|
+
.f-empty { text-align: center; padding: 48px 24px; color: rgba(255,255,255,0.25); }
|
|
82
|
+
.f-empty-icon { font-size: 3rem; margin-bottom: 12px; }
|
|
83
|
+
.f-empty-text { font-size: 0.95rem; font-style: italic; }
|
|
84
|
+
</style>
|
|
85
|
+
|
|
86
|
+
<div class="f-wrap">
|
|
87
|
+
<div class="f-hero">
|
|
88
|
+
<div class="f-hero-icon">\uD83D\uDEB0</div>
|
|
89
|
+
<div class="f-hero-title">Testnet4 Faucet</div>
|
|
90
|
+
<div class="f-hero-sub">Dispense vouchers for testing</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="f-stats">
|
|
94
|
+
<div class="f-stat">
|
|
95
|
+
<div class="f-stat-val">${String(available.length)}</div>
|
|
96
|
+
<div class="f-stat-label">Available</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="f-stat">
|
|
99
|
+
<div class="f-stat-val">${totalSats.toLocaleString()}</div>
|
|
100
|
+
<div class="f-stat-label">Total sats</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
${available.length === 0 ? html`
|
|
105
|
+
<div class="f-empty">
|
|
106
|
+
<div class="f-empty-icon">\uD83C\uDFDC\uFE0F</div>
|
|
107
|
+
<div class="f-empty-text">No vouchers available to dispense.<br/>Import some in the Vouchers tab first.</div>
|
|
108
|
+
</div>
|
|
109
|
+
` : null}
|
|
110
|
+
|
|
111
|
+
<div class="f-grid">
|
|
112
|
+
${keyed(available, function(v) { return v.id }, function(v) {
|
|
113
|
+
return html`
|
|
114
|
+
<div class="f-card">
|
|
115
|
+
<div class="f-card-amount">${(v.amount || 0).toLocaleString()} ${html`<small>sats</small>`}</div>
|
|
116
|
+
<div class="f-card-btc">${satsBtc(v.amount || 0)} tBTC</div>
|
|
117
|
+
<div class="f-card-txid">${truncate(v.txid)}:${String(v.vout)}</div>
|
|
118
|
+
<div class="f-card-actions">
|
|
119
|
+
<button class="f-btn f-btn-primary"
|
|
120
|
+
onclick="${function() { copyText(faucetLink(v)) }}">
|
|
121
|
+
\uD83D\uDD17 Copy Faucet Link
|
|
122
|
+
</button>
|
|
123
|
+
<button class="f-btn" title="Copy TXO URI"
|
|
124
|
+
onclick="${function() { copyText(buildTxoUri(v)) }}">
|
|
125
|
+
\u2398
|
|
126
|
+
</button>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
`
|
|
130
|
+
})}
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
`)
|
|
134
|
+
|
|
135
|
+
onUnmount(container, function() {})
|
|
136
|
+
}
|
|
137
|
+
}
|