txo_parser 0.0.1 → 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.
@@ -0,0 +1,171 @@
1
+ import { html, render, onUnmount } from 'https://losos.org/losos/html.js'
2
+ import { parseTxoUri, isValidTxoUri, formatTxoUri } from '../../index.js'
3
+
4
+ export default {
5
+ label: 'Parse',
6
+ icon: '\uD83D\uDD0D',
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 input = ''
16
+ var result = null
17
+ var error = null
18
+ var valid = null
19
+
20
+ var examples = [
21
+ 'txo:btc:4e9c1ef9ba5fa3b0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb:0',
22
+ 'txo:btc:4e9c1ef9ba5fa3b0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb:0?amount=0.75',
23
+ 'txo:btc:4e9c1ef9ba5fa3b0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb:0?amount=1000&key=deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef',
24
+ 'txo:tbtc4:4e9c1ef9ba5fa3b0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb:1?amount=50000&privkey=Kx9abc&script_type=p2tr',
25
+ 'txo:btc:4e9c1ef9ba5fa3b0aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabb:0 0.75 Kx9abc'
26
+ ]
27
+
28
+ function doParse() {
29
+ var el = container.querySelector('.txo-input')
30
+ input = el ? el.value.trim() : ''
31
+ if (!input) { result = null; error = null; valid = null; renderApp(); return }
32
+
33
+ valid = isValidTxoUri(input)
34
+ if (valid) {
35
+ try {
36
+ result = parseTxoUri(input)
37
+ error = null
38
+ } catch(e) {
39
+ result = null
40
+ error = e.message
41
+ }
42
+ } else {
43
+ result = null
44
+ error = 'Invalid TXO URI'
45
+ }
46
+ renderApp()
47
+ }
48
+
49
+ function loadExample(uri) {
50
+ var el = container.querySelector('.txo-input')
51
+ if (el) el.value = uri
52
+ input = uri
53
+ doParse()
54
+ }
55
+
56
+ function copyText(text) {
57
+ navigator.clipboard.writeText(text)
58
+ }
59
+
60
+ function renderApp() {
61
+ render(container, html`
62
+ <style>
63
+ .txo-wrap { padding: 0 16px 40px; }
64
+ .txo-hero { text-align: center; padding: 40px 0 28px; }
65
+ .txo-hero-icon { font-size: 3rem; margin-bottom: 8px; }
66
+ .txo-hero-title { font-size: 1.6rem; font-weight: 800; margin-bottom: 4px; }
67
+ .txo-hero-sub { font-size: 0.9rem; color: rgba(255,255,255,0.35); max-width: 500px; margin: 0 auto; }
68
+ .txo-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; backdrop-filter: blur(12px); }
69
+ .txo-card h2 { font-size: 1rem; font-weight: 600; color: rgba(255,255,255,0.9); margin: 0 0 16px; }
70
+ .txo-input { width: 100%; padding: 12px 16px; 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', 'Fira Code', monospace; outline: none; margin-bottom: 12px; }
71
+ .txo-input::placeholder { color: rgba(255,255,255,0.2); font-family: -apple-system, sans-serif; }
72
+ .txo-input:focus { border-color: rgba(59,130,246,0.5); }
73
+ .txo-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; }
74
+ .txo-btn:hover { background: rgba(255,255,255,0.1); color: #fff; }
75
+ .txo-btn-primary { background: linear-gradient(135deg, #3b82f6, #2563eb); border-color: rgba(59,130,246,0.4); color: #fff; }
76
+ .txo-btn-sm { padding: 5px 10px; font-size: 0.75rem; border-radius: 6px; }
77
+ .txo-examples { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 16px; }
78
+ .txo-example { padding: 4px 10px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.06); background: rgba(255,255,255,0.03); color: rgba(255,255,255,0.4); font-size: 0.72rem; cursor: pointer; font-family: 'SF Mono', monospace; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
79
+ .txo-example:hover { background: rgba(59,130,246,0.1); border-color: rgba(59,130,246,0.3); color: rgba(255,255,255,0.7); }
80
+ .txo-result { background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); border-radius: 10px; padding: 16px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 0.85rem; overflow-x: auto; }
81
+ .txo-result-json { color: rgba(255,255,255,0.7); white-space: pre-wrap; line-height: 1.6; }
82
+ .txo-result-key { color: #3b82f6; }
83
+ .txo-result-str { color: #10b981; }
84
+ .txo-result-num { color: #f7931a; }
85
+ .txo-valid { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: 6px; font-size: 0.78rem; font-weight: 600; margin-bottom: 12px; }
86
+ .txo-valid-yes { background: rgba(16,185,129,0.12); border: 1px solid rgba(16,185,129,0.3); color: #10b981; }
87
+ .txo-valid-no { background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.3); color: #ef4444; }
88
+ .txo-anatomy { display: grid; grid-template-columns: auto 1fr; gap: 6px 14px; font-size: 0.82rem; margin-top: 14px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,0.06); }
89
+ .txo-anatomy-label { color: rgba(255,255,255,0.3); font-weight: 600; }
90
+ .txo-anatomy-val { color: rgba(255,255,255,0.7); font-family: 'SF Mono', monospace; word-break: break-all; cursor: pointer; }
91
+ .txo-anatomy-val:hover { color: #fff; }
92
+ </style>
93
+
94
+ <div class="txo-wrap">
95
+ <div class="txo-hero">
96
+ <div class="txo-hero-icon">\uD83D\uDD0D</div>
97
+ <div class="txo-hero-title">Parse TXO URI</div>
98
+ <div class="txo-hero-sub">Paste a TXO URI to parse it into structured JSON. Try an example below.</div>
99
+ </div>
100
+
101
+ <div class="txo-card">
102
+ <h2>Input</h2>
103
+ <div class="txo-examples">
104
+ ${examples.map(function(ex) {
105
+ return html`<div class="txo-example" onclick="${function() { loadExample(ex) }}" title="${ex}">${ex.length > 40 ? ex.slice(0, 37) + '\u2026' : ex}</div>`
106
+ })}
107
+ </div>
108
+ <input class="txo-input" placeholder="txo:btc:txid:vout?amount=N&privkey=K"
109
+ oninput="${doParse}"
110
+ onkeydown="${function(e) { if (e.key === 'Enter') doParse() }}" />
111
+
112
+ ${valid !== null ? html`
113
+ <div class="${'txo-valid ' + (valid ? 'txo-valid-yes' : 'txo-valid-no')}">
114
+ ${valid ? '\u2713 Valid TXO URI' : '\u2716 Invalid'}
115
+ </div>
116
+ ` : null}
117
+ </div>
118
+
119
+ ${result ? html`
120
+ <div class="txo-card">
121
+ <h2>Parsed Result</h2>
122
+ <div class="txo-result">
123
+ <div class="txo-result-json">${formatJson(result)}</div>
124
+ </div>
125
+
126
+ <div class="txo-anatomy">
127
+ <span class="txo-anatomy-label">Network</span>
128
+ <span class="txo-anatomy-val" onclick="${function() { copyText(result.network) }}">${result.network}</span>
129
+ <span class="txo-anatomy-label">TXID</span>
130
+ <span class="txo-anatomy-val" onclick="${function() { copyText(result.txid) }}">${result.txid}</span>
131
+ <span class="txo-anatomy-label">Output</span>
132
+ <span class="txo-anatomy-val">${String(result.output)}</span>
133
+ ${result.amount !== undefined ? html`
134
+ <span class="txo-anatomy-label">Amount</span>
135
+ <span class="txo-anatomy-val">${String(result.amount)}</span>
136
+ ` : null}
137
+ ${result.privkey ? html`
138
+ <span class="txo-anatomy-label">Privkey</span>
139
+ <span class="txo-anatomy-val" onclick="${function() { copyText(result.privkey) }}">${result.privkey.length > 20 ? result.privkey.slice(0, 8) + '\u2026' + result.privkey.slice(-8) : result.privkey}</span>
140
+ ` : null}
141
+ ${result.script_type ? html`
142
+ <span class="txo-anatomy-label">Script Type</span>
143
+ <span class="txo-anatomy-val">${result.script_type}</span>
144
+ ` : null}
145
+ </div>
146
+
147
+ <div style="margin-top:14px;display:flex;gap:8px">
148
+ <button class="txo-btn txo-btn-sm" onclick="${function() { copyText(JSON.stringify(result, null, 2)) }}">\u2398 Copy JSON</button>
149
+ <button class="txo-btn txo-btn-sm" onclick="${function() { copyText(formatTxoUri(result)) }}">\uD83D\uDD17 Copy URI</button>
150
+ </div>
151
+ </div>
152
+ ` : null}
153
+
154
+ ${error && !result ? html`
155
+ <div class="txo-card" style="border-color:rgba(239,68,68,0.2)">
156
+ <div style="color:#ef4444;font-size:0.9rem">\u2716 ${error}</div>
157
+ </div>
158
+ ` : null}
159
+ </div>
160
+ `)
161
+ }
162
+
163
+ function formatJson(obj) {
164
+ var json = JSON.stringify(obj, null, 2)
165
+ return json
166
+ }
167
+
168
+ renderApp()
169
+ onUnmount(container, function() {})
170
+ }
171
+ }
@@ -0,0 +1,117 @@
1
+ import { html, render, onUnmount } from 'https://losos.org/losos/html.js'
2
+
3
+ export default {
4
+ label: 'Spec',
5
+ icon: '\uD83D\uDCC4',
6
+
7
+ canHandle(subject, store) {
8
+ var node = store.get(subject.value)
9
+ var type = node && store.type(node)
10
+ return type && (type.includes('TxoDemo') || type.includes('VoucherPool'))
11
+ },
12
+
13
+ render(subject, store, container) {
14
+ render(container, html`
15
+ <style>
16
+ .sp-wrap { padding: 0 16px 40px; max-width: 800px; margin: 0 auto; }
17
+ .sp-hero { text-align: center; padding: 40px 0 28px; }
18
+ .sp-hero-icon { font-size: 3rem; margin-bottom: 8px; }
19
+ .sp-hero-title { font-size: 1.6rem; font-weight: 800; margin-bottom: 4px; }
20
+ .sp-hero-sub { font-size: 0.9rem; color: rgba(255,255,255,0.35); }
21
+ .sp-section { margin-bottom: 32px; }
22
+ .sp-section h2 { font-size: 1.1rem; font-weight: 700; color: rgba(255,255,255,0.9); margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid rgba(255,255,255,0.06); }
23
+ .sp-section h3 { font-size: 0.95rem; font-weight: 600; color: rgba(255,255,255,0.7); margin: 16px 0 8px; }
24
+ .sp-p { font-size: 0.9rem; color: rgba(255,255,255,0.5); line-height: 1.7; margin-bottom: 10px; }
25
+ .sp-code { background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.06); border-radius: 8px; padding: 14px 18px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 0.85rem; color: #10b981; overflow-x: auto; margin: 10px 0 14px; line-height: 1.5; }
26
+ .sp-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; margin: 10px 0 14px; }
27
+ .sp-table th { text-align: left; padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.08); color: rgba(255,255,255,0.4); font-weight: 600; text-transform: uppercase; font-size: 0.72rem; letter-spacing: 0.05em; }
28
+ .sp-table td { padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.04); color: rgba(255,255,255,0.6); }
29
+ .sp-table td:first-child { font-family: 'SF Mono', monospace; color: #3b82f6; }
30
+ .sp-tag { display: inline-block; padding: 1px 6px; border-radius: 4px; font-size: 0.75rem; font-family: 'SF Mono', monospace; background: rgba(59,130,246,0.1); border: 1px solid rgba(59,130,246,0.2); color: #3b82f6; }
31
+ .sp-warn { background: rgba(251,191,36,0.08); border: 1px solid rgba(251,191,36,0.2); border-radius: 8px; padding: 12px 16px; font-size: 0.82rem; color: #fbbf24; margin: 10px 0; }
32
+ </style>
33
+
34
+ <div class="sp-wrap">
35
+ <div class="sp-hero">
36
+ <div class="sp-hero-icon">\uD83D\uDCC4</div>
37
+ <div class="sp-hero-title">TXO URI Specification</div>
38
+ <div class="sp-hero-sub">v0.2 \u2014 A compact URI scheme for referencing transaction outputs</div>
39
+ </div>
40
+
41
+ <div class="sp-section">
42
+ <h2>1. Format</h2>
43
+ <div class="sp-code">txo:&lt;network&gt;:&lt;txid&gt;:&lt;output&gt;?key=value&amp;key=value\u2026</div>
44
+ <table class="sp-table">
45
+ <tr><th>Segment</th><th>Meaning</th><th>Format</th></tr>
46
+ <tr><td>txo</td><td>URI scheme</td><td>literal, lowercase</td></tr>
47
+ <tr><td>&lt;network&gt;</td><td>Blockchain code</td><td>a\u2013z, 3\u201310 chars (e.g. btc, tbtc4)</td></tr>
48
+ <tr><td>&lt;txid&gt;</td><td>Transaction hash</td><td>64-char lowercase hex</td></tr>
49
+ <tr><td>&lt;output&gt;</td><td>Output index</td><td>0\u20134294967295</td></tr>
50
+ <tr><td>?...</td><td>Query string</td><td>RFC 3986 key=value pairs</td></tr>
51
+ </table>
52
+ </div>
53
+
54
+ <div class="sp-section">
55
+ <h2>2. Networks</h2>
56
+ <table class="sp-table">
57
+ <tr><th>Code</th><th>Name</th><th>Taproot</th></tr>
58
+ <tr><td>btc</td><td>Bitcoin mainnet</td><td>\u2713</td></tr>
59
+ <tr><td>tbtc4</td><td>Bitcoin Testnet 4</td><td>\u2713</td></tr>
60
+ <tr><td>tbtc3</td><td>Bitcoin Testnet 3</td><td>\u2713</td></tr>
61
+ <tr><td>ltc</td><td>Litecoin</td><td>\u2713</td></tr>
62
+ <tr><td>liq</td><td>Liquid Network</td><td>\u2713</td></tr>
63
+ </table>
64
+ </div>
65
+
66
+ <div class="sp-section">
67
+ <h2>3. Query Parameters</h2>
68
+ <table class="sp-table">
69
+ <tr><th>Key</th><th>Type</th><th>Description</th></tr>
70
+ <tr><td>amount</td><td>decimal / integer</td><td>Coin value of output</td></tr>
71
+ <tr><td>privkey</td><td>WIF / hex</td><td>Spending key (canonical)</td></tr>
72
+ <tr><td>key</td><td><em>alias</em></td><td>Alias for privkey; parsers MUST normalize</td></tr>
73
+ <tr><td>script_type</td><td>string</td><td>p2pkh, p2sh, p2wpkh, p2tr</td></tr>
74
+ </table>
75
+ <div class="sp-p">All keys are case-insensitive, SHOULD be lowercase snake_case. Unknown keys MUST be ignored by parsers.</div>
76
+ </div>
77
+
78
+ <div class="sp-section">
79
+ <h2>4. Examples</h2>
80
+ <h3>Minimal reference</h3>
81
+ <div class="sp-code">txo:btc:4e9c\u2026a3b0:0</div>
82
+ <h3>With amount</h3>
83
+ <div class="sp-code">txo:btc:4e9c\u2026a3b0:0?amount=0.75</div>
84
+ <h3>Spend-ready (with key alias)</h3>
85
+ <div class="sp-code">txo:btc:4e9c\u2026a3b0:0?amount=0.75&key=Kx9\u2026</div>
86
+ <h3>Full form</h3>
87
+ <div class="sp-code">txo:btc:4e9c\u2026a3b0:0?amount=0.75&privkey=Kx9\u2026&script_type=p2tr</div>
88
+ <h3>Legacy (space-separated)</h3>
89
+ <div class="sp-code">txo:btc:4e9c\u2026a3b0:0 0.75 Kx9\u2026</div>
90
+ </div>
91
+
92
+ <div class="sp-section">
93
+ <h2>5. Parsing Algorithm</h2>
94
+ <div class="sp-p">1. Split on <span class="sp-tag">:</span> \u2192 [scheme, network, txid, output_and_rest]. Reject if scheme \u2260 "txo".</div>
95
+ <div class="sp-p">2. Split output_and_rest on <span class="sp-tag">?</span> \u2192 output, query_string.</div>
96
+ <div class="sp-p">3. Validate: network \u2208 [a-z]{3,10}, txid \u2208 hex-64, output \u2208 0\u20134294967295.</div>
97
+ <div class="sp-p">4. Decode query_string per RFC 3986.</div>
98
+ <div class="sp-p">5. Normalize aliases: <span class="sp-tag">key</span> \u2192 <span class="sp-tag">privkey</span>.</div>
99
+ <div class="sp-p">6. Output JSON; ignore unknown keys.</div>
100
+ </div>
101
+
102
+ <div class="sp-section">
103
+ <h2>6. Security</h2>
104
+ <div class="sp-warn">\u26A0 Embedding privkey exposes spend authority. Use TLS + QR or secure channels only. URIs are immutable; to replace an output, create a new URI.</div>
105
+ </div>
106
+
107
+ <div class="sp-section">
108
+ <h2>7. Install</h2>
109
+ <div class="sp-code">npm install txo_parser</div>
110
+ <div class="sp-code">import { parseTxoUri, formatTxoUri } from 'txo_parser'</div>
111
+ </div>
112
+ </div>
113
+ `)
114
+
115
+ onUnmount(container, function() {})
116
+ }
117
+ }