@xrplkit/xls26 1.0.0 → 2.0.0

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/readme.md +61 -37
  3. package/xls26.js +188 -42
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xrplkit/xls26",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "2.0.0",
5
5
  "main": "xls26.js",
6
6
  "dependencies": {
7
7
  "@xrplkit/toml": "1.0.0"
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 a string of a `xrp-ledger.toml` file to a JavaScript object.
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. [Example File here](http://xrpl.works/.well-known/xrp-ledger.toml)
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.
9
+
10
+ ```
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
22
+
23
+ ```
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"
43
+ ```
44
+
32
45
 
33
46
  ### Example Output
34
47
 
35
48
  ```
36
49
  {
37
- "accounts": [
50
+ issuers: [
38
51
  {
39
- "address": "rxworksy7717V3w1nSQhUpaGNqydGYCaS",
40
- "name": "XRPL Works",
41
- "description": "XRPL Works is a non-profit organization. Our goal is to simplify the ledger."
52
+ address: 'rCSCManTZ8ME9EoLrSHHYKW8PPwWMgkwr',
53
+ name: 'CasinoCoin'
42
54
  }
43
55
  ],
44
- "currencies": [
56
+ tokens: [
45
57
  {
46
- "code": "58574F524B530000000000000000000000000000",
47
- "issuer": "rxworksy7717V3w1nSQhUpaGNqydGYCaS",
48
- "name": "XWORKS",
49
- "description": "This token serves the purpose of demonstrating the benefits of the XLS-26 standard.",
50
- "icon": "https://xrpl.works/token/icon.png"
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
+ ]
51
74
  }
52
- ]
75
+ ],
76
+ issues: []
53
77
  }
54
78
 
55
79
  ```
package/xls26.js CHANGED
@@ -1,100 +1,235 @@
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 of a parser according to this standard.
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 accountFields = [
18
+ const issuerFields = [
10
19
  {
11
20
  key: 'address',
12
- validate: v => /^[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{25,35}$/.test(v)
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 => typeof v === 'string' && v.length > 0
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 => typeof v === 'string' && v.length > 0
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
- validate: v => /^https?:\/\/.*$/.test(v)
26
- },
27
- {
28
- key: 'links',
29
- type: 'array',
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: 'trusted',
34
- validate: v => typeof v === 'boolean'
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: 'code',
42
- validate: v => /^[0-9A-F]{40}$/
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
- validate: v => /^[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{25,35}$/.test(v)
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 => typeof v === 'string' && v.length > 0
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 => typeof v === 'string' && v.length > 0
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
- validate: v => /^https?:\/\/.*$/.test(v)
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: 'links',
63
- type: 'array',
64
- validate: v => Array.isArray(v) && v.every(v => typeof v === 'string'),
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: 'trusted',
68
- validate: v => typeof v === 'boolean'
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
- let toml = parseToml(str, 'camelCase')
75
- let accounts = []
76
- let currencies = []
141
+ try{
142
+ var toml = parseToml(str, 'camelCase')
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
+ if(toml.issuers){
152
+ for(let stanza of toml.issuers){
153
+ let { valid, parsed: issuer, issues: issuerIssues } = parseStanza(stanza, issuerFields)
154
+
155
+ if(valid)
156
+ issuers.push(issuer)
157
+
158
+ issues.push(
159
+ ...issuerIssues.map(
160
+ issue => `[[ISSUERS]] ${issue}`
161
+ )
162
+ )
163
+
164
+ if(valid && stanza.weblinks){
165
+ for(let substanza of stanza.weblinks){
166
+ let { valid, parsed: weblink, issues: weblinkIssues } = parseStanza(substanza, weblinkFields)
77
167
 
78
- if(toml.accounts){
79
- accounts = toml.accounts
80
- .map(account => parseStanza(account, accountFields))
168
+ if(valid){
169
+ issuer.weblinks = [
170
+ ...(token.weblinks || []),
171
+ weblink
172
+ ]
173
+ }
174
+
175
+ issues.push(
176
+ ...weblinkIssues.map(
177
+ issue => `[[WEBLINK]] ${issue}`
178
+ )
179
+ )
180
+ }
181
+ }
182
+ }
81
183
  }
82
184
 
83
- if(toml.currencies){
84
- currencies = toml.currencies
85
- .map(currency => parseStanza(currency, currencyFields))
185
+ if(toml.tokens){
186
+ for(let stanza of toml.tokens){
187
+ let { valid, parsed: token, issues: tokenIssues } = parseStanza(stanza, tokenFields)
188
+
189
+ if(valid)
190
+ tokens.push(token)
191
+
192
+ issues.push(
193
+ ...tokenIssues.map(
194
+ issue => `[[TOKENS]] ${issue}`
195
+ )
196
+ )
197
+
198
+ if(valid && stanza.weblinks){
199
+ for(let substanza of stanza.weblinks){
200
+ let { valid, parsed: weblink, issues: weblinkIssues } = parseStanza(substanza, weblinkFields)
201
+
202
+ if(valid){
203
+ token.weblinks = [
204
+ ...(token.weblinks || []),
205
+ weblink
206
+ ]
207
+ }
208
+
209
+ issues.push(
210
+ ...weblinkIssues.map(
211
+ issue => `[[WEBLINK]] ${issue}`
212
+ )
213
+ )
214
+ }
215
+ }
216
+ }
86
217
  }
87
218
 
219
+
88
220
  return {
89
- accounts,
90
- currencies
221
+ issuers,
222
+ tokens,
223
+ issues
91
224
  }
92
225
  }
93
226
 
94
227
  function parseStanza(stanza, schemas){
95
228
  let parsed = {}
229
+ let issues = []
230
+ let valid = true
96
231
 
97
- for(let { key, alternativeKeys, validate } of schemas){
232
+ for(let { key, alternativeKeys, essential, validate } of schemas){
98
233
  let keys = [key]
99
234
 
100
235
  if(alternativeKeys)
@@ -106,13 +241,24 @@ function parseStanza(stanza, schemas){
106
241
 
107
242
  let value = stanza[k]
108
243
 
109
- if(validate && !validate(value))
110
- continue
244
+ if(validate){
245
+ try{
246
+ validate(value)
247
+ }catch(issue){
248
+ issues.push(`${key} field: ${issue}`)
249
+ break
250
+ }
251
+ }
111
252
 
112
253
  parsed[k] = value
113
254
  break
114
255
  }
256
+
257
+ if(essential && !parsed.hasOwnProperty(key)){
258
+ issues.push(`${key} field missing: skipping stanza`)
259
+ valid = false
260
+ }
115
261
  }
116
262
 
117
- return parsed
263
+ return { valid, parsed, issues }
118
264
  }