@xrplkit/xls26 1.0.0 → 2.0.2
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/package.json +9 -2
- package/readme.md +67 -43
- package/xls26.js +190 -43
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xrplkit/xls26",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.2",
|
|
5
5
|
"main": "xls26.js",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"@xrplkit/toml": "1.0.0"
|
|
8
8
|
},
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
11
|
-
}
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"xrpl",
|
|
14
|
+
"xls-26",
|
|
15
|
+
"parser",
|
|
16
|
+
"metadata",
|
|
17
|
+
"tokens"
|
|
18
|
+
]
|
|
12
19
|
}
|
package/readme.md
CHANGED
|
@@ -1,55 +1,79 @@
|
|
|
1
1
|
# XLS-26 Parsing Library
|
|
2
2
|
|
|
3
3
|
This is an implementation of the [XLS-26 Standard](https://github.com/XRPLF/XRPL-Standards/discussions/71).
|
|
4
|
-
This package exports one single function called `parse` that converts
|
|
4
|
+
This package exports one single function called `parse` that converts the string of a `xrp-ledger.toml` file to a JavaScript object.
|
|
5
|
+
|
|
5
6
|
|
|
6
7
|
## Example
|
|
7
|
-
Assuming you have a file named `xrp-ledger.toml` in the current working directory.
|
|
8
|
-
|
|
9
|
-
import fs from 'fs'
|
|
10
|
-
import { parse } from '@xrplkit/xls26'
|
|
11
|
-
|
|
12
|
-
const tomlString = fs.readFileSync('./xrp-ledger.toml', 'utf-8')
|
|
13
|
-
const xls26Data = parse(tomlString)
|
|
14
|
-
|
|
15
|
-
console.log(xls26Data)
|
|
16
|
-
|
|
17
|
-
### Example Input
|
|
18
|
-
|
|
19
|
-
[[ACCOUNTS]]
|
|
20
|
-
address = "rxworksy7717V3w1nSQhUpaGNqydGYCaS"
|
|
21
|
-
name = "XRPL Works"
|
|
22
|
-
websites = ["https://xrpl.works"]
|
|
23
|
-
description = "XRPL Works is a non-profit organization. Our goal is to simplify the ledger."
|
|
24
|
-
|
|
25
|
-
[[CURRENCIES]]
|
|
26
|
-
code = "58574F524B530000000000000000000000000000"
|
|
27
|
-
issuer = "rxworksy7717V3w1nSQhUpaGNqydGYCaS"
|
|
28
|
-
name = "XWORKS"
|
|
29
|
-
icon = "https://xrpl.works/token/icon.png"
|
|
30
|
-
description = "This token serves the purpose of demonstrating the benefits of the XLS-26 standard."
|
|
31
|
-
|
|
8
|
+
Assuming you have a file named `xrp-ledger.toml` in the current working directory. An example file can be found below.
|
|
32
9
|
|
|
33
|
-
|
|
10
|
+
```javascript
|
|
11
|
+
import fs from 'fs'
|
|
12
|
+
import { parse } from '@xrplkit/xls26'
|
|
13
|
+
|
|
14
|
+
const tomlString = fs.readFileSync('./xrp-ledger.toml', 'utf-8')
|
|
15
|
+
const xls26Data = parse(tomlString)
|
|
16
|
+
|
|
17
|
+
console.log(xls26Data)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Example File
|
|
34
22
|
|
|
23
|
+
```toml
|
|
24
|
+
[[ISSUERS]]
|
|
25
|
+
address = "rCSCManTZ8ME9EoLrSHHYKW8PPwWMgkwr"
|
|
26
|
+
name = "CasinoCoin"
|
|
27
|
+
|
|
28
|
+
[[TOKENS]]
|
|
29
|
+
issuer = "rCSCManTZ8ME9EoLrSHHYKW8PPwWMgkwr"
|
|
30
|
+
currency = "CSC"
|
|
31
|
+
name = "CasinoCoin"
|
|
32
|
+
desc = "CasinoCoin (CSC) is a digital currency, developed specifically for the regulated gaming industry."
|
|
33
|
+
icon = "https://static.xrplmeta.org/icons/csc.png"
|
|
34
|
+
|
|
35
|
+
[[TOKENS.WEBLINKS]]
|
|
36
|
+
url = "https://casinocoin.im"
|
|
37
|
+
type = "website"
|
|
38
|
+
title = "Official Website"
|
|
39
|
+
|
|
40
|
+
[[TOKENS.WEBLINKS]]
|
|
41
|
+
url = "https://twitter.com/CasinoCoin"
|
|
42
|
+
type = "socialmedia"
|
|
35
43
|
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Example Output
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
36
49
|
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
issuers: [
|
|
51
|
+
{
|
|
52
|
+
address: 'rCSCManTZ8ME9EoLrSHHYKW8PPwWMgkwr',
|
|
53
|
+
name: 'CasinoCoin'
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
tokens: [
|
|
57
|
+
{
|
|
58
|
+
currency: 'CSC',
|
|
59
|
+
issuer: 'rCSCManTZ8ME9EoLrSHHYKW8PPwWMgkwr',
|
|
60
|
+
name: 'CasinoCoin',
|
|
61
|
+
desc: 'CasinoCoin (CSC) is a digital currency, developed specifically for the regulated gaming industry.',
|
|
62
|
+
icon: 'https://static.xrplmeta.org/icons/csc.png',
|
|
63
|
+
weblinks: [
|
|
64
|
+
{
|
|
65
|
+
url: 'https://casinocoin.im',
|
|
66
|
+
type: 'website',
|
|
67
|
+
title: 'Official Website'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
url: 'https://twitter.com/CasinoCoin',
|
|
71
|
+
type: 'socialmedia'
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
issues: []
|
|
53
77
|
}
|
|
54
78
|
|
|
55
79
|
```
|
package/xls26.js
CHANGED
|
@@ -1,118 +1,265 @@
|
|
|
1
1
|
// The XLS-26 standard adds additional asset metadata fields to the existing xrp-ledger.toml standard,
|
|
2
2
|
// https://github.com/XRPLF/XRPL-Standards/discussions/71
|
|
3
|
-
// This package provides an implementation
|
|
3
|
+
// This package provides an implementation for a parser according to this standard.
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
import { parse as parseToml } from '@xrplkit/toml'
|
|
7
7
|
|
|
8
|
+
const validWeblinkTypes = [
|
|
9
|
+
'website',
|
|
10
|
+
'socialmedia',
|
|
11
|
+
'support',
|
|
12
|
+
'sourcecode',
|
|
13
|
+
'whitepaper',
|
|
14
|
+
'audit',
|
|
15
|
+
'report'
|
|
16
|
+
]
|
|
8
17
|
|
|
9
|
-
const
|
|
18
|
+
const issuerFields = [
|
|
10
19
|
{
|
|
11
20
|
key: 'address',
|
|
12
|
-
|
|
21
|
+
essential: true,
|
|
22
|
+
validate: v => {
|
|
23
|
+
if(!/^[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{25,35}$/.test(v))
|
|
24
|
+
throw 'is not a valid XRPL address'
|
|
25
|
+
},
|
|
13
26
|
},
|
|
14
27
|
{
|
|
15
28
|
key: 'name',
|
|
16
|
-
validate: v =>
|
|
29
|
+
validate: v => {
|
|
30
|
+
if(typeof v !== 'string' || v.length === 0)
|
|
31
|
+
throw 'has to be a non empty string'
|
|
32
|
+
}
|
|
17
33
|
},
|
|
18
34
|
{
|
|
19
35
|
key: 'description',
|
|
20
36
|
alternativeKeys: ['desc'],
|
|
21
|
-
validate: v =>
|
|
37
|
+
validate: v => {
|
|
38
|
+
if(typeof v !== 'string' || v.length === 0)
|
|
39
|
+
throw 'has to be a non empty string'
|
|
40
|
+
}
|
|
22
41
|
},
|
|
23
42
|
{
|
|
24
43
|
key: 'icon',
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
validate: v => Array.isArray(v) && v.every(v => typeof v === 'string'),
|
|
44
|
+
alternativeKeys: ['avatar'],
|
|
45
|
+
validate: v => {
|
|
46
|
+
if(!/^https?:\/\/.*$/.test(v))
|
|
47
|
+
throw 'has to be a valid HTTP URL that starts with "http"'
|
|
48
|
+
}
|
|
31
49
|
},
|
|
32
50
|
{
|
|
33
|
-
key: '
|
|
34
|
-
validate: v =>
|
|
51
|
+
key: 'trust_level',
|
|
52
|
+
validate: v => {
|
|
53
|
+
if(v !== parseInt(v))
|
|
54
|
+
throw 'has to be a integer'
|
|
55
|
+
|
|
56
|
+
if(v < 0 || v > 3)
|
|
57
|
+
throw 'has to be between 0 and 3'
|
|
58
|
+
}
|
|
35
59
|
}
|
|
36
60
|
]
|
|
37
61
|
|
|
38
|
-
|
|
39
|
-
const currencyFields = [
|
|
62
|
+
const tokenFields = [
|
|
40
63
|
{
|
|
41
|
-
key: '
|
|
42
|
-
|
|
64
|
+
key: 'currency',
|
|
65
|
+
essential: true,
|
|
66
|
+
validate: v => {
|
|
67
|
+
if(typeof v !== 'string' && v.length < 3)
|
|
68
|
+
throw 'is not a valid XRPL currency code'
|
|
69
|
+
}
|
|
43
70
|
},
|
|
44
71
|
{
|
|
45
72
|
key: 'issuer',
|
|
46
|
-
|
|
73
|
+
essential: true,
|
|
74
|
+
validate: v => {
|
|
75
|
+
if(!/^[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{25,35}$/.test(v))
|
|
76
|
+
throw 'is not a valid XRPL address'
|
|
77
|
+
}
|
|
47
78
|
},
|
|
48
79
|
{
|
|
49
80
|
key: 'name',
|
|
50
|
-
validate: v =>
|
|
81
|
+
validate: v => {
|
|
82
|
+
if(typeof v !== 'string' || v.length === 0)
|
|
83
|
+
throw 'has to be a non empty string'
|
|
84
|
+
}
|
|
51
85
|
},
|
|
52
86
|
{
|
|
53
87
|
key: 'description',
|
|
54
88
|
alternativeKeys: ['desc'],
|
|
55
|
-
validate: v =>
|
|
89
|
+
validate: v => {
|
|
90
|
+
if(typeof v !== 'string' || v.length === 0)
|
|
91
|
+
throw 'has to be a non empty string'
|
|
92
|
+
}
|
|
56
93
|
},
|
|
57
94
|
{
|
|
58
95
|
key: 'icon',
|
|
59
|
-
|
|
96
|
+
alternativeKeys: ['avatar'],
|
|
97
|
+
validate: v => {
|
|
98
|
+
if(!/^https?:\/\/.*$/.test(v))
|
|
99
|
+
throw 'has to be a valid HTTP URL that starts with "http"'
|
|
100
|
+
}
|
|
60
101
|
},
|
|
61
102
|
{
|
|
62
|
-
key: '
|
|
63
|
-
|
|
64
|
-
|
|
103
|
+
key: 'trust_level',
|
|
104
|
+
validate: v => {
|
|
105
|
+
if(v !== parseInt(v))
|
|
106
|
+
throw 'has to be a integer'
|
|
107
|
+
|
|
108
|
+
if(v < 0 || v > 3)
|
|
109
|
+
throw 'has to be between 0 and 3'
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
const weblinkFields = [
|
|
115
|
+
{
|
|
116
|
+
key: 'url',
|
|
117
|
+
essential: true,
|
|
118
|
+
validate: v => {
|
|
119
|
+
if(!/^https?:\/\/.*$/.test(v))
|
|
120
|
+
throw 'has to be a valid HTTP URL that starts with "http"'
|
|
121
|
+
}
|
|
65
122
|
},
|
|
66
123
|
{
|
|
67
|
-
key: '
|
|
68
|
-
validate: v =>
|
|
69
|
-
|
|
124
|
+
key: 'type',
|
|
125
|
+
validate: v => {
|
|
126
|
+
if(!validWeblinkTypes.includes(v))
|
|
127
|
+
throw `has to be one of (${validWeblinkTypes.join(', ')})`
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
key: 'title',
|
|
132
|
+
validate: v => {
|
|
133
|
+
if(typeof v !== 'string' || v.length === 0)
|
|
134
|
+
throw 'has to be a non empty string'
|
|
135
|
+
}
|
|
136
|
+
},
|
|
70
137
|
]
|
|
71
138
|
|
|
72
139
|
|
|
73
140
|
export function parse(str){
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
141
|
+
try{
|
|
142
|
+
var toml = parseToml(str)
|
|
143
|
+
}catch(error){
|
|
144
|
+
throw new Error(`Failed to parse .toml: Syntax error at line ${error.line}:${error.column}`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let issuers = []
|
|
148
|
+
let tokens = []
|
|
149
|
+
let issues = []
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if(toml.ISSUERS){
|
|
153
|
+
for(let stanza of toml.ISSUERS){
|
|
154
|
+
let { valid, parsed: issuer, issues: issuerIssues } = parseStanza(stanza, issuerFields)
|
|
155
|
+
|
|
156
|
+
if(valid)
|
|
157
|
+
issuers.push(issuer)
|
|
158
|
+
|
|
159
|
+
issues.push(
|
|
160
|
+
...issuerIssues.map(
|
|
161
|
+
issue => `[[ISSUERS]] ${issue}`
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if(valid && stanza.WEBLINKS){
|
|
166
|
+
for(let substanza of stanza.WEBLINKS){
|
|
167
|
+
let { valid, parsed: weblink, issues: weblinkIssues } = parseStanza(substanza, weblinkFields)
|
|
77
168
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
169
|
+
if(valid){
|
|
170
|
+
issuer.weblinks = [
|
|
171
|
+
...(issuer.weblinks || []),
|
|
172
|
+
weblink
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
issues.push(
|
|
177
|
+
...weblinkIssues.map(
|
|
178
|
+
issue => `[[WEBLINK]] ${issue}`
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
81
184
|
}
|
|
82
185
|
|
|
83
|
-
if(toml.
|
|
84
|
-
|
|
85
|
-
|
|
186
|
+
if(toml.TOKENS){
|
|
187
|
+
for(let stanza of toml.TOKENS){
|
|
188
|
+
let { valid, parsed: token, issues: tokenIssues } = parseStanza(stanza, tokenFields)
|
|
189
|
+
|
|
190
|
+
if(valid)
|
|
191
|
+
tokens.push(token)
|
|
192
|
+
|
|
193
|
+
issues.push(
|
|
194
|
+
...tokenIssues.map(
|
|
195
|
+
issue => `[[TOKENS]] ${issue}`
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if(valid && stanza.WEBLINKS){
|
|
200
|
+
for(let substanza of stanza.WEBLINKS){
|
|
201
|
+
let { valid, parsed: weblink, issues: weblinkIssues } = parseStanza(substanza, weblinkFields)
|
|
202
|
+
|
|
203
|
+
if(valid){
|
|
204
|
+
token.weblinks = [
|
|
205
|
+
...(token.weblinks || []),
|
|
206
|
+
weblink
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
issues.push(
|
|
211
|
+
...weblinkIssues.map(
|
|
212
|
+
issue => `[[WEBLINK]] ${issue}`
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
86
218
|
}
|
|
87
219
|
|
|
220
|
+
|
|
88
221
|
return {
|
|
89
|
-
|
|
90
|
-
|
|
222
|
+
issuers,
|
|
223
|
+
tokens,
|
|
224
|
+
issues
|
|
91
225
|
}
|
|
92
226
|
}
|
|
93
227
|
|
|
94
228
|
function parseStanza(stanza, schemas){
|
|
95
229
|
let parsed = {}
|
|
230
|
+
let issues = []
|
|
231
|
+
let valid = true
|
|
96
232
|
|
|
97
|
-
for(let { key, alternativeKeys, validate } of schemas){
|
|
233
|
+
for(let { key, alternativeKeys, essential, validate } of schemas){
|
|
98
234
|
let keys = [key]
|
|
99
235
|
|
|
100
236
|
if(alternativeKeys)
|
|
101
237
|
keys.push(...alternativeKeys)
|
|
102
238
|
|
|
103
239
|
for(let k of keys){
|
|
104
|
-
if(
|
|
240
|
+
if(stanza[k] === undefined)
|
|
105
241
|
continue
|
|
106
242
|
|
|
107
243
|
let value = stanza[k]
|
|
108
244
|
|
|
109
|
-
if(validate
|
|
110
|
-
|
|
245
|
+
if(validate){
|
|
246
|
+
try{
|
|
247
|
+
validate(value)
|
|
248
|
+
}catch(issue){
|
|
249
|
+
issues.push(`${key} field: ${issue}`)
|
|
250
|
+
break
|
|
251
|
+
}
|
|
252
|
+
}
|
|
111
253
|
|
|
112
254
|
parsed[k] = value
|
|
113
255
|
break
|
|
114
256
|
}
|
|
257
|
+
|
|
258
|
+
if(essential && parsed[key] === undefined){
|
|
259
|
+
issues.push(`${key} field missing: skipping stanza`)
|
|
260
|
+
valid = false
|
|
261
|
+
}
|
|
115
262
|
}
|
|
116
263
|
|
|
117
|
-
return parsed
|
|
264
|
+
return { valid, parsed, issues }
|
|
118
265
|
}
|