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.
- package/LICENSE +13 -17
- package/README.md +36 -2
- 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 +63 -11
- package/package.json +2 -2
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": {
|
|
3
|
+
"schema": "https://schema.org/",
|
|
4
|
+
"bt": "https://blocktrails.org/ns/"
|
|
5
|
+
},
|
|
6
|
+
"@id": "#this",
|
|
7
|
+
"@type": "VoucherPool",
|
|
8
|
+
"schema:name": "Voucher Pool",
|
|
9
|
+
"schema:description": "Testnet4 voucher management and faucet",
|
|
10
|
+
"schema:hasPart": [
|
|
11
|
+
{
|
|
12
|
+
"@id": "blocktrail.jsonld#this"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"@id": "webledger.jsonld#this"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"@id": "ledger-history.jsonld#this"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"schema:itemListElement": [
|
|
22
|
+
{
|
|
23
|
+
"@type": "schema:ListItem",
|
|
24
|
+
"@id": "mn05mqrf",
|
|
25
|
+
"schema:identifier": "txo:btc:d3a54de54a9df9e89b02dfda4274bbb12180c2ac75cb523917a66f515d01b977:0?amount=416366&key=b8f86cd4e9a6f5db705ebfbcbe3093745b10b2781a7ead82647dc770bd60fb02",
|
|
26
|
+
"schema:address": "tb1pgl8m440er9gmhvxepwuscduanw4xf6p2ccfaetqq48pcxfnhfazsqa22kz",
|
|
27
|
+
"schema:status": "unspent",
|
|
28
|
+
"schema:dateCreated": "2026-03-21T09:57:43.035Z"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Implementation of the TXO URI Specification (v0.1)
|
|
4
4
|
*
|
|
5
5
|
* Format: txo:<network>:<txid>:<output>?key=value&key=value...
|
|
6
|
+
* Legacy Format: txo:<network>:<txid>:<output> [amount] [privkey]
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -16,8 +17,22 @@ export function parseTxoUri (uri) {
|
|
|
16
17
|
throw new Error('Invalid URI: URI must be a non-empty string');
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
//
|
|
20
|
-
const
|
|
20
|
+
// Check if this is using the legacy space-separated format
|
|
21
|
+
const isLegacyFormat = !uri.includes('?') && uri.split(' ').length > 1;
|
|
22
|
+
|
|
23
|
+
// Parse the basic structure first
|
|
24
|
+
let basicParts;
|
|
25
|
+
let extraParts = [];
|
|
26
|
+
|
|
27
|
+
if (isLegacyFormat) {
|
|
28
|
+
const allParts = uri.split(' ');
|
|
29
|
+
basicParts = allParts[0].split(':');
|
|
30
|
+
extraParts = allParts.slice(1);
|
|
31
|
+
} else {
|
|
32
|
+
basicParts = uri.split(':');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const [scheme, network, txid, outputAndRest] = basicParts;
|
|
21
36
|
|
|
22
37
|
// Validate scheme
|
|
23
38
|
if (scheme !== 'txo') {
|
|
@@ -41,7 +56,7 @@ export function parseTxoUri (uri) {
|
|
|
41
56
|
|
|
42
57
|
// Split the output and query string
|
|
43
58
|
let output, queryString;
|
|
44
|
-
if (outputAndRest.includes('?')) {
|
|
59
|
+
if (!isLegacyFormat && outputAndRest.includes('?')) {
|
|
45
60
|
[output, queryString] = outputAndRest.split('?');
|
|
46
61
|
} else {
|
|
47
62
|
output = outputAndRest;
|
|
@@ -64,22 +79,41 @@ export function parseTxoUri (uri) {
|
|
|
64
79
|
// Convert key to lowercase for case-insensitivity
|
|
65
80
|
const normalizedKey = key.toLowerCase();
|
|
66
81
|
|
|
82
|
+
// Normalize aliases to canonical names
|
|
83
|
+
const canonicalKey = normalizedKey === 'key' ? 'privkey' : normalizedKey;
|
|
84
|
+
|
|
67
85
|
// Process specific key types
|
|
68
|
-
if (
|
|
86
|
+
if (canonicalKey === 'amount') {
|
|
69
87
|
// Parse amount as a number
|
|
70
88
|
const amount = parseFloat(value);
|
|
71
89
|
if (!isNaN(amount)) {
|
|
72
|
-
queryParams[
|
|
90
|
+
queryParams[canonicalKey] = amount;
|
|
73
91
|
} else {
|
|
74
|
-
queryParams[
|
|
92
|
+
queryParams[canonicalKey] = value;
|
|
75
93
|
}
|
|
76
94
|
} else {
|
|
77
|
-
queryParams[
|
|
95
|
+
queryParams[canonicalKey] = decodeURIComponent(value);
|
|
78
96
|
}
|
|
79
97
|
}
|
|
80
98
|
});
|
|
81
99
|
}
|
|
82
100
|
|
|
101
|
+
// Handle legacy format with space-separated parameters
|
|
102
|
+
if (isLegacyFormat && extraParts.length > 0) {
|
|
103
|
+
// First extra part is amount
|
|
104
|
+
if (extraParts[0]) {
|
|
105
|
+
const amount = parseFloat(extraParts[0]);
|
|
106
|
+
if (!isNaN(amount)) {
|
|
107
|
+
queryParams.amount = amount;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Second extra part is privkey
|
|
112
|
+
if (extraParts.length > 1 && extraParts[1]) {
|
|
113
|
+
queryParams.privkey = extraParts[1];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
83
117
|
// Build the result object
|
|
84
118
|
const result = {
|
|
85
119
|
network,
|
|
@@ -136,12 +170,30 @@ export function formatTxoUri (data) {
|
|
|
136
170
|
// Build the base URI
|
|
137
171
|
let uri = `txo:${data.network}:${data.txid}:${output}`;
|
|
138
172
|
|
|
139
|
-
// Add query parameters
|
|
173
|
+
// Add query parameters (canonical key names, stable order: amount first, then privkey, then rest)
|
|
174
|
+
const reserved = ['network', 'txid', 'output'];
|
|
175
|
+
const entries = Object.entries(data).filter(([k]) => !reserved.includes(k) && data[k] !== undefined);
|
|
176
|
+
|
|
177
|
+
// Normalize 'key' alias to 'privkey'
|
|
140
178
|
const queryParams = [];
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
for (const [k, v] of entries) {
|
|
181
|
+
const canonical = k.toLowerCase() === 'key' ? 'privkey' : k.toLowerCase();
|
|
182
|
+
if (!seen.has(canonical)) {
|
|
183
|
+
seen.add(canonical);
|
|
184
|
+
queryParams.push(`${canonical}=${encodeURIComponent(v)}`);
|
|
144
185
|
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Stable order: amount first, privkey second, rest alphabetical
|
|
189
|
+
queryParams.sort((a, b) => {
|
|
190
|
+
const keyA = a.split('=')[0];
|
|
191
|
+
const keyB = b.split('=')[0];
|
|
192
|
+
if (keyA === 'amount') return -1;
|
|
193
|
+
if (keyB === 'amount') return 1;
|
|
194
|
+
if (keyA === 'privkey') return -1;
|
|
195
|
+
if (keyB === 'privkey') return 1;
|
|
196
|
+
return keyA.localeCompare(keyB);
|
|
145
197
|
});
|
|
146
198
|
|
|
147
199
|
if (queryParams.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "txo_parser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Parser for TXO URI Specification (v0.1)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"cli"
|
|
24
24
|
],
|
|
25
25
|
"author": "Melvin Carvalho",
|
|
26
|
-
"license": "
|
|
26
|
+
"license": "AGPL-3.0-or-later",
|
|
27
27
|
"bugs": {
|
|
28
28
|
"url": "https://github.com/sandy-mount/txo_parser/issues"
|
|
29
29
|
},
|