ripple-binary-codec 1.7.1 → 1.9.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 (99) hide show
  1. package/dist/enums/definitions.json +250 -1
  2. package/dist/enums/src/enums/definitions.json +250 -1
  3. package/dist/types/index.js +2 -0
  4. package/dist/types/index.js.map +1 -1
  5. package/dist/types/issue.d.ts +39 -0
  6. package/dist/types/issue.js +81 -0
  7. package/dist/types/issue.js.map +1 -0
  8. package/dist/types/xchain-bridge.d.ts +45 -0
  9. package/dist/types/xchain-bridge.js +102 -0
  10. package/dist/types/xchain-bridge.js.map +1 -0
  11. package/package.json +3 -4
  12. package/src/README.md +3 -0
  13. package/src/binary.ts +188 -0
  14. package/src/coretypes.ts +31 -0
  15. package/src/enums/README.md +144 -0
  16. package/src/enums/bytes.ts +75 -0
  17. package/src/enums/constants.ts +4 -0
  18. package/src/enums/definitions.json +2599 -0
  19. package/src/enums/field.ts +85 -0
  20. package/src/enums/index.ts +34 -0
  21. package/src/enums/utils-renumber.ts +134 -0
  22. package/src/enums/xrpl-definitions-base.ts +111 -0
  23. package/src/enums/xrpl-definitions.ts +32 -0
  24. package/src/hash-prefixes.ts +40 -0
  25. package/src/hashes.ts +76 -0
  26. package/src/index.ts +141 -0
  27. package/src/ledger-hashes.ts +187 -0
  28. package/src/quality.ts +39 -0
  29. package/src/serdes/binary-parser.ts +217 -0
  30. package/src/serdes/binary-serializer.ts +166 -0
  31. package/src/shamap.ts +186 -0
  32. package/src/types/account-id.ts +86 -0
  33. package/src/types/amount.ts +256 -0
  34. package/src/types/blob.ts +43 -0
  35. package/src/types/currency.ts +140 -0
  36. package/src/types/hash-128.ts +33 -0
  37. package/src/types/hash-160.ts +20 -0
  38. package/src/types/hash-256.ts +16 -0
  39. package/src/types/hash.ts +81 -0
  40. package/src/types/index.ts +61 -0
  41. package/src/types/issue.ts +96 -0
  42. package/src/types/path-set.ts +290 -0
  43. package/src/types/serialized-type.ts +120 -0
  44. package/src/types/st-array.ts +107 -0
  45. package/src/types/st-object.ts +192 -0
  46. package/src/types/uint-16.ts +49 -0
  47. package/src/types/uint-32.ts +56 -0
  48. package/src/types/uint-64.ts +105 -0
  49. package/src/types/uint-8.ts +49 -0
  50. package/src/types/uint.ts +57 -0
  51. package/src/types/vector-256.ts +84 -0
  52. package/test/amount.test.js +0 -43
  53. package/test/binary-json.test.js +0 -45
  54. package/test/binary-parser.test.js +0 -396
  55. package/test/binary-serializer.test.js +0 -289
  56. package/test/definitions.test.js +0 -160
  57. package/test/fixtures/account-tx-transactions.db +0 -0
  58. package/test/fixtures/codec-fixtures.json +0 -4466
  59. package/test/fixtures/data-driven-tests.json +0 -2919
  60. package/test/fixtures/delivermin-tx-binary.json +0 -1
  61. package/test/fixtures/delivermin-tx.json +0 -98
  62. package/test/fixtures/deposit-preauth-tx-binary.json +0 -1
  63. package/test/fixtures/deposit-preauth-tx-meta-binary.json +0 -1
  64. package/test/fixtures/deposit-preauth-tx.json +0 -58
  65. package/test/fixtures/escrow-cancel-binary.json +0 -1
  66. package/test/fixtures/escrow-cancel-tx.json +0 -6
  67. package/test/fixtures/escrow-create-binary.json +0 -1
  68. package/test/fixtures/escrow-create-tx.json +0 -10
  69. package/test/fixtures/escrow-finish-binary.json +0 -1
  70. package/test/fixtures/escrow-finish-meta-binary.json +0 -1
  71. package/test/fixtures/escrow-finish-tx.json +0 -95
  72. package/test/fixtures/ledger-full-38129.json +0 -1
  73. package/test/fixtures/ledger-full-40000.json +0 -1
  74. package/test/fixtures/negative-unl.json +0 -12
  75. package/test/fixtures/nf-token.json +0 -547
  76. package/test/fixtures/payment-channel-claim-binary.json +0 -1
  77. package/test/fixtures/payment-channel-claim-tx.json +0 -8
  78. package/test/fixtures/payment-channel-create-binary.json +0 -1
  79. package/test/fixtures/payment-channel-create-tx.json +0 -11
  80. package/test/fixtures/payment-channel-fund-binary.json +0 -1
  81. package/test/fixtures/payment-channel-fund-tx.json +0 -7
  82. package/test/fixtures/signerlistset-tx-binary.json +0 -1
  83. package/test/fixtures/signerlistset-tx-meta-binary.json +0 -1
  84. package/test/fixtures/signerlistset-tx.json +0 -94
  85. package/test/fixtures/ticket-create-binary.json +0 -1
  86. package/test/fixtures/ticket-create-tx.json +0 -7
  87. package/test/fixtures/x-codec-fixtures.json +0 -188
  88. package/test/hash.test.js +0 -135
  89. package/test/ledger.test.js +0 -29
  90. package/test/lower-case-hex.test.js +0 -46
  91. package/test/pseudo-transaction.test.js +0 -38
  92. package/test/quality.test.js +0 -15
  93. package/test/shamap.test.js +0 -89
  94. package/test/signing-data-encoding.test.js +0 -242
  95. package/test/tx-encode-decode.test.js +0 -119
  96. package/test/types.test.js +0 -34
  97. package/test/uint.test.js +0 -148
  98. package/test/utils.js +0 -30
  99. package/test/x-address.test.js +0 -181
package/src/shamap.ts ADDED
@@ -0,0 +1,186 @@
1
+ import { strict as assert } from 'assert'
2
+ import { coreTypes } from './types'
3
+ import { HashPrefix } from './hash-prefixes'
4
+ import { Sha512Half } from './hashes'
5
+ import { Hash256 } from './types/hash-256'
6
+ import { BytesList } from './serdes/binary-serializer'
7
+ import { Buffer } from 'buffer/'
8
+
9
+ /**
10
+ * Abstract class describing a SHAMapNode
11
+ */
12
+ abstract class ShaMapNode {
13
+ abstract hashPrefix(): Buffer
14
+ abstract isLeaf(): boolean
15
+ abstract isInner(): boolean
16
+ abstract toBytesSink(list: BytesList): void
17
+ abstract hash(): Hash256
18
+ }
19
+
20
+ /**
21
+ * Class describing a Leaf of SHAMap
22
+ */
23
+ class ShaMapLeaf extends ShaMapNode {
24
+ constructor(public index: Hash256, public item?: ShaMapNode) {
25
+ super()
26
+ }
27
+
28
+ /**
29
+ * @returns true as ShaMapLeaf is a leaf node
30
+ */
31
+ isLeaf(): boolean {
32
+ return true
33
+ }
34
+
35
+ /**
36
+ * @returns false as ShaMapLeaf is not an inner node
37
+ */
38
+ isInner(): boolean {
39
+ return false
40
+ }
41
+
42
+ /**
43
+ * Get the prefix of the this.item
44
+ *
45
+ * @returns The hash prefix, unless this.item is undefined, then it returns an empty Buffer
46
+ */
47
+ hashPrefix(): Buffer {
48
+ return this.item === undefined ? Buffer.alloc(0) : this.item.hashPrefix()
49
+ }
50
+
51
+ /**
52
+ * Hash the bytes representation of this
53
+ *
54
+ * @returns hash of this.item concatenated with this.index
55
+ */
56
+ hash(): Hash256 {
57
+ const hash = Sha512Half.put(this.hashPrefix())
58
+ this.toBytesSink(hash)
59
+ return hash.finish()
60
+ }
61
+
62
+ /**
63
+ * Write the bytes representation of this to a BytesList
64
+ * @param list BytesList to write bytes to
65
+ */
66
+ toBytesSink(list: BytesList): void {
67
+ if (this.item !== undefined) {
68
+ this.item.toBytesSink(list)
69
+ }
70
+ this.index.toBytesSink(list)
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Class defining an Inner Node of a SHAMap
76
+ */
77
+ class ShaMapInner extends ShaMapNode {
78
+ private slotBits = 0
79
+ private branches: Array<ShaMapNode> = Array(16)
80
+
81
+ constructor(private depth: number = 0) {
82
+ super()
83
+ }
84
+
85
+ /**
86
+ * @returns true as ShaMapInner is an inner node
87
+ */
88
+ isInner(): boolean {
89
+ return true
90
+ }
91
+
92
+ /**
93
+ * @returns false as ShaMapInner is not a leaf node
94
+ */
95
+ isLeaf(): boolean {
96
+ return false
97
+ }
98
+
99
+ /**
100
+ * Get the hash prefix for this node
101
+ *
102
+ * @returns hash prefix describing an inner node
103
+ */
104
+ hashPrefix(): Buffer {
105
+ return HashPrefix.innerNode
106
+ }
107
+
108
+ /**
109
+ * Set a branch of this node to be another node
110
+ *
111
+ * @param slot Slot to add branch to this.branches
112
+ * @param branch Branch to add
113
+ */
114
+ setBranch(slot: number, branch: ShaMapNode): void {
115
+ this.slotBits = this.slotBits | (1 << slot)
116
+ this.branches[slot] = branch
117
+ }
118
+
119
+ /**
120
+ * @returns true if node is empty
121
+ */
122
+ empty(): boolean {
123
+ return this.slotBits === 0
124
+ }
125
+
126
+ /**
127
+ * Compute the hash of this node
128
+ *
129
+ * @returns The hash of this node
130
+ */
131
+ hash(): Hash256 {
132
+ if (this.empty()) {
133
+ return (coreTypes.Hash256 as typeof Hash256).ZERO_256
134
+ }
135
+ const hash = Sha512Half.put(this.hashPrefix())
136
+ this.toBytesSink(hash)
137
+ return hash.finish()
138
+ }
139
+
140
+ /**
141
+ * Writes the bytes representation of this node to a BytesList
142
+ *
143
+ * @param list BytesList to write bytes to
144
+ */
145
+ toBytesSink(list: BytesList): void {
146
+ for (let i = 0; i < this.branches.length; i++) {
147
+ const branch = this.branches[i]
148
+ const hash = branch
149
+ ? branch.hash()
150
+ : (coreTypes.Hash256 as typeof Hash256).ZERO_256
151
+ hash.toBytesSink(list)
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Add item to the SHAMap
157
+ *
158
+ * @param index Hash of the index of the item being inserted
159
+ * @param item Item to insert in the map
160
+ * @param leaf Leaf node to insert when branch doesn't exist
161
+ */
162
+ addItem(index?: Hash256, item?: ShaMapNode, leaf?: ShaMapLeaf): void {
163
+ assert.ok(index !== undefined)
164
+ if (index !== undefined) {
165
+ const nibble = index.nibblet(this.depth)
166
+ const existing = this.branches[nibble]
167
+
168
+ if (existing === undefined) {
169
+ this.setBranch(nibble, leaf || new ShaMapLeaf(index, item))
170
+ } else if (existing instanceof ShaMapLeaf) {
171
+ const newInner = new ShaMapInner(this.depth + 1)
172
+ newInner.addItem(existing.index, undefined, existing)
173
+ newInner.addItem(index, item, leaf)
174
+ this.setBranch(nibble, newInner)
175
+ } else if (existing instanceof ShaMapInner) {
176
+ existing.addItem(index, item, leaf)
177
+ } else {
178
+ throw new Error('invalid ShaMap.addItem call')
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ class ShaMap extends ShaMapInner {}
185
+
186
+ export { ShaMap, ShaMapNode, ShaMapLeaf }
@@ -0,0 +1,86 @@
1
+ import {
2
+ decodeAccountID,
3
+ encodeAccountID,
4
+ isValidXAddress,
5
+ xAddressToClassicAddress,
6
+ } from 'ripple-address-codec'
7
+ import { Hash160 } from './hash-160'
8
+ import { Buffer } from 'buffer/'
9
+
10
+ const HEX_REGEX = /^[A-F0-9]{40}$/
11
+
12
+ /**
13
+ * Class defining how to encode and decode an AccountID
14
+ */
15
+ class AccountID extends Hash160 {
16
+ static readonly defaultAccountID: AccountID = new AccountID(Buffer.alloc(20))
17
+
18
+ constructor(bytes?: Buffer) {
19
+ super(bytes ?? AccountID.defaultAccountID.bytes)
20
+ }
21
+
22
+ /**
23
+ * Defines how to construct an AccountID
24
+ *
25
+ * @param value either an existing AccountID, a hex-string, or a base58 r-Address
26
+ * @returns an AccountID object
27
+ */
28
+ static from<T extends Hash160 | string>(value: T): AccountID {
29
+ if (value instanceof AccountID) {
30
+ return value
31
+ }
32
+
33
+ if (typeof value === 'string') {
34
+ if (value === '') {
35
+ return new AccountID()
36
+ }
37
+
38
+ return HEX_REGEX.test(value)
39
+ ? new AccountID(Buffer.from(value, 'hex'))
40
+ : this.fromBase58(value)
41
+ }
42
+
43
+ throw new Error('Cannot construct AccountID from value given')
44
+ }
45
+
46
+ /**
47
+ * Defines how to build an AccountID from a base58 r-Address
48
+ *
49
+ * @param value a base58 r-Address
50
+ * @returns an AccountID object
51
+ */
52
+ static fromBase58(value: string): AccountID {
53
+ if (isValidXAddress(value)) {
54
+ const classic = xAddressToClassicAddress(value)
55
+
56
+ if (classic.tag !== false)
57
+ throw new Error('Only allowed to have tag on Account or Destination')
58
+
59
+ value = classic.classicAddress
60
+ }
61
+
62
+ return new AccountID(Buffer.from(decodeAccountID(value)))
63
+ }
64
+
65
+ /**
66
+ * Overload of toJSON
67
+ *
68
+ * @returns the base58 string for this AccountID
69
+ */
70
+ toJSON(): string {
71
+ return this.toBase58()
72
+ }
73
+
74
+ /**
75
+ * Defines how to encode AccountID into a base58 address
76
+ *
77
+ * @returns the base58 string defined by this.bytes
78
+ */
79
+ toBase58(): string {
80
+ /* eslint-disable @typescript-eslint/no-explicit-any */
81
+ return encodeAccountID(this.bytes as any)
82
+ /* eslint-enable @typescript-eslint/no-explicit-any */
83
+ }
84
+ }
85
+
86
+ export { AccountID }
@@ -0,0 +1,256 @@
1
+ import { Decimal } from 'decimal.js'
2
+
3
+ import { BinaryParser } from '../serdes/binary-parser'
4
+
5
+ import { AccountID } from './account-id'
6
+ import { Currency } from './currency'
7
+ import { JsonObject, SerializedType } from './serialized-type'
8
+ import bigInt = require('big-integer')
9
+ import { Buffer } from 'buffer/'
10
+
11
+ /**
12
+ * Constants for validating amounts
13
+ */
14
+ const MIN_IOU_EXPONENT = -96
15
+ const MAX_IOU_EXPONENT = 80
16
+ const MAX_IOU_PRECISION = 16
17
+ const MAX_DROPS = new Decimal('1e17')
18
+ const MIN_XRP = new Decimal('1e-6')
19
+ const mask = bigInt(0x00000000ffffffff)
20
+
21
+ /**
22
+ * decimal.js configuration for Amount IOUs
23
+ */
24
+ Decimal.config({
25
+ toExpPos: MAX_IOU_EXPONENT + MAX_IOU_PRECISION,
26
+ toExpNeg: MIN_IOU_EXPONENT - MAX_IOU_PRECISION,
27
+ })
28
+
29
+ /**
30
+ * Interface for JSON objects that represent amounts
31
+ */
32
+ interface AmountObject extends JsonObject {
33
+ value: string
34
+ currency: string
35
+ issuer: string
36
+ }
37
+
38
+ /**
39
+ * Type guard for AmountObject
40
+ */
41
+ function isAmountObject(arg): arg is AmountObject {
42
+ const keys = Object.keys(arg).sort()
43
+ return (
44
+ keys.length === 3 &&
45
+ keys[0] === 'currency' &&
46
+ keys[1] === 'issuer' &&
47
+ keys[2] === 'value'
48
+ )
49
+ }
50
+
51
+ /**
52
+ * Class for serializing/Deserializing Amounts
53
+ */
54
+ class Amount extends SerializedType {
55
+ static defaultAmount: Amount = new Amount(
56
+ Buffer.from('4000000000000000', 'hex'),
57
+ )
58
+
59
+ constructor(bytes: Buffer) {
60
+ super(bytes ?? Amount.defaultAmount.bytes)
61
+ }
62
+
63
+ /**
64
+ * Construct an amount from an IOU or string amount
65
+ *
66
+ * @param value An Amount, object representing an IOU, or a string
67
+ * representing an integer amount
68
+ * @returns An Amount object
69
+ */
70
+ static from<T extends Amount | AmountObject | string>(value: T): Amount {
71
+ if (value instanceof Amount) {
72
+ return value
73
+ }
74
+
75
+ let amount = Buffer.alloc(8)
76
+ if (typeof value === 'string') {
77
+ Amount.assertXrpIsValid(value)
78
+
79
+ const number = bigInt(value)
80
+
81
+ const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]
82
+ intBuf[0].writeUInt32BE(Number(number.shiftRight(32)), 0)
83
+ intBuf[1].writeUInt32BE(Number(number.and(mask)), 0)
84
+
85
+ amount = Buffer.concat(intBuf)
86
+
87
+ amount[0] |= 0x40
88
+
89
+ return new Amount(amount)
90
+ }
91
+
92
+ if (isAmountObject(value)) {
93
+ const number = new Decimal(value.value)
94
+ Amount.assertIouIsValid(number)
95
+
96
+ if (number.isZero()) {
97
+ amount[0] |= 0x80
98
+ } else {
99
+ const integerNumberString = number
100
+ .times(`1e${-(number.e - 15)}`)
101
+ .abs()
102
+ .toString()
103
+
104
+ const num = bigInt(integerNumberString)
105
+ const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]
106
+ intBuf[0].writeUInt32BE(Number(num.shiftRight(32)), 0)
107
+ intBuf[1].writeUInt32BE(Number(num.and(mask)), 0)
108
+
109
+ amount = Buffer.concat(intBuf)
110
+
111
+ amount[0] |= 0x80
112
+
113
+ if (number.gt(new Decimal(0))) {
114
+ amount[0] |= 0x40
115
+ }
116
+
117
+ const exponent = number.e - 15
118
+ const exponentByte = 97 + exponent
119
+ amount[0] |= exponentByte >>> 2
120
+ amount[1] |= (exponentByte & 0x03) << 6
121
+ }
122
+
123
+ const currency = Currency.from(value.currency).toBytes()
124
+ const issuer = AccountID.from(value.issuer).toBytes()
125
+ return new Amount(Buffer.concat([amount, currency, issuer]))
126
+ }
127
+
128
+ throw new Error('Invalid type to construct an Amount')
129
+ }
130
+
131
+ /**
132
+ * Read an amount from a BinaryParser
133
+ *
134
+ * @param parser BinaryParser to read the Amount from
135
+ * @returns An Amount object
136
+ */
137
+ static fromParser(parser: BinaryParser): Amount {
138
+ const isXRP = parser.peek() & 0x80
139
+ const numBytes = isXRP ? 48 : 8
140
+ return new Amount(parser.read(numBytes))
141
+ }
142
+
143
+ /**
144
+ * Get the JSON representation of this Amount
145
+ *
146
+ * @returns the JSON interpretation of this.bytes
147
+ */
148
+ toJSON(): AmountObject | string {
149
+ if (this.isNative()) {
150
+ const bytes = this.bytes
151
+ const isPositive = bytes[0] & 0x40
152
+ const sign = isPositive ? '' : '-'
153
+ bytes[0] &= 0x3f
154
+
155
+ const msb = bigInt(bytes.slice(0, 4).readUInt32BE(0))
156
+ const lsb = bigInt(bytes.slice(4).readUInt32BE(0))
157
+ const num = msb.shiftLeft(32).or(lsb)
158
+
159
+ return `${sign}${num.toString()}`
160
+ } else {
161
+ const parser = new BinaryParser(this.toString())
162
+ const mantissa = parser.read(8)
163
+ const currency = Currency.fromParser(parser) as Currency
164
+ const issuer = AccountID.fromParser(parser) as AccountID
165
+
166
+ const b1 = mantissa[0]
167
+ const b2 = mantissa[1]
168
+
169
+ const isPositive = b1 & 0x40
170
+ const sign = isPositive ? '' : '-'
171
+ const exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97
172
+
173
+ mantissa[0] = 0
174
+ mantissa[1] &= 0x3f
175
+ const value = new Decimal(`${sign}0x${mantissa.toString('hex')}`).times(
176
+ `1e${exponent}`,
177
+ )
178
+ Amount.assertIouIsValid(value)
179
+
180
+ return {
181
+ value: value.toString(),
182
+ currency: currency.toJSON(),
183
+ issuer: issuer.toJSON(),
184
+ }
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Validate XRP amount
190
+ *
191
+ * @param amount String representing XRP amount
192
+ * @returns void, but will throw if invalid amount
193
+ */
194
+ private static assertXrpIsValid(amount: string): void {
195
+ if (amount.indexOf('.') !== -1) {
196
+ throw new Error(`${amount.toString()} is an illegal amount`)
197
+ }
198
+
199
+ const decimal = new Decimal(amount)
200
+ if (!decimal.isZero()) {
201
+ if (decimal.lt(MIN_XRP) || decimal.gt(MAX_DROPS)) {
202
+ throw new Error(`${amount.toString()} is an illegal amount`)
203
+ }
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Validate IOU.value amount
209
+ *
210
+ * @param decimal Decimal.js object representing IOU.value
211
+ * @returns void, but will throw if invalid amount
212
+ */
213
+ private static assertIouIsValid(decimal: Decimal): void {
214
+ if (!decimal.isZero()) {
215
+ const p = decimal.precision()
216
+ const e = decimal.e - 15
217
+ if (
218
+ p > MAX_IOU_PRECISION ||
219
+ e > MAX_IOU_EXPONENT ||
220
+ e < MIN_IOU_EXPONENT
221
+ ) {
222
+ throw new Error('Decimal precision out of range')
223
+ }
224
+ this.verifyNoDecimal(decimal)
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Ensure that the value after being multiplied by the exponent does not
230
+ * contain a decimal.
231
+ *
232
+ * @param decimal a Decimal object
233
+ * @returns a string of the object without a decimal
234
+ */
235
+ private static verifyNoDecimal(decimal: Decimal): void {
236
+ const integerNumberString = decimal
237
+ .times(`1e${-(decimal.e - 15)}`)
238
+ .abs()
239
+ .toString()
240
+
241
+ if (integerNumberString.indexOf('.') !== -1) {
242
+ throw new Error('Decimal place found in integerNumberString')
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Test if this amount is in units of Native Currency(XRP)
248
+ *
249
+ * @returns true if Native (XRP)
250
+ */
251
+ private isNative(): boolean {
252
+ return (this.bytes[0] & 0x80) === 0
253
+ }
254
+ }
255
+
256
+ export { Amount, AmountObject }
@@ -0,0 +1,43 @@
1
+ import { SerializedType } from './serialized-type'
2
+ import { BinaryParser } from '../serdes/binary-parser'
3
+ import { Buffer } from 'buffer/'
4
+
5
+ /**
6
+ * Variable length encoded type
7
+ */
8
+ class Blob extends SerializedType {
9
+ constructor(bytes: Buffer) {
10
+ super(bytes)
11
+ }
12
+
13
+ /**
14
+ * Defines how to read a Blob from a BinaryParser
15
+ *
16
+ * @param parser The binary parser to read the Blob from
17
+ * @param hint The length of the blob, computed by readVariableLengthLength() and passed in
18
+ * @returns A Blob object
19
+ */
20
+ static fromParser(parser: BinaryParser, hint: number): Blob {
21
+ return new Blob(parser.read(hint))
22
+ }
23
+
24
+ /**
25
+ * Create a Blob object from a hex-string
26
+ *
27
+ * @param value existing Blob object or a hex-string
28
+ * @returns A Blob object
29
+ */
30
+ static from<T extends Blob | string>(value: T): Blob {
31
+ if (value instanceof Blob) {
32
+ return value
33
+ }
34
+
35
+ if (typeof value === 'string') {
36
+ return new Blob(Buffer.from(value, 'hex'))
37
+ }
38
+
39
+ throw new Error('Cannot construct Blob from value given')
40
+ }
41
+ }
42
+
43
+ export { Blob }
@@ -0,0 +1,140 @@
1
+ import { Hash160 } from './hash-160'
2
+ import { Buffer } from 'buffer/'
3
+
4
+ const XRP_HEX_REGEX = /^0{40}$/
5
+ const ISO_REGEX = /^[A-Z0-9a-z?!@#$%^&*(){}[\]|]{3}$/
6
+ const HEX_REGEX = /^[A-F0-9]{40}$/
7
+ // eslint-disable-next-line no-control-regex
8
+ const STANDARD_FORMAT_HEX_REGEX = /^0{24}[\x00-\x7F]{6}0{10}$/
9
+
10
+ /**
11
+ * Convert an ISO code to a currency bytes representation
12
+ */
13
+ function isoToBytes(iso: string): Buffer {
14
+ const bytes = Buffer.alloc(20)
15
+ if (iso !== 'XRP') {
16
+ const isoBytes = iso.split('').map((c) => c.charCodeAt(0))
17
+ bytes.set(isoBytes, 12)
18
+ }
19
+ return bytes
20
+ }
21
+
22
+ /**
23
+ * Tests if ISO is a valid iso code
24
+ */
25
+ function isIsoCode(iso: string): boolean {
26
+ return ISO_REGEX.test(iso)
27
+ }
28
+
29
+ function isoCodeFromHex(code: Buffer): string | null {
30
+ const iso = code.toString()
31
+ if (iso === 'XRP') {
32
+ return null
33
+ }
34
+ if (isIsoCode(iso)) {
35
+ return iso
36
+ }
37
+ return null
38
+ }
39
+
40
+ /**
41
+ * Tests if hex is a valid hex-string
42
+ */
43
+ function isHex(hex: string): boolean {
44
+ return HEX_REGEX.test(hex)
45
+ }
46
+
47
+ /**
48
+ * Tests if a string is a valid representation of a currency
49
+ */
50
+ function isStringRepresentation(input: string): boolean {
51
+ return input.length === 3 || isHex(input)
52
+ }
53
+
54
+ /**
55
+ * Tests if a Buffer is a valid representation of a currency
56
+ */
57
+ function isBytesArray(bytes: Buffer): boolean {
58
+ return bytes.byteLength === 20
59
+ }
60
+
61
+ /**
62
+ * Ensures that a value is a valid representation of a currency
63
+ */
64
+ function isValidRepresentation(input: Buffer | string): boolean {
65
+ return input instanceof Buffer
66
+ ? isBytesArray(input)
67
+ : isStringRepresentation(input)
68
+ }
69
+
70
+ /**
71
+ * Generate bytes from a string or buffer representation of a currency
72
+ */
73
+ function bytesFromRepresentation(input: string): Buffer {
74
+ if (!isValidRepresentation(input)) {
75
+ throw new Error(`Unsupported Currency representation: ${input}`)
76
+ }
77
+ return input.length === 3 ? isoToBytes(input) : Buffer.from(input, 'hex')
78
+ }
79
+
80
+ /**
81
+ * Class defining how to encode and decode Currencies
82
+ */
83
+ class Currency extends Hash160 {
84
+ static readonly XRP = new Currency(Buffer.alloc(20))
85
+ private readonly _iso: string | null
86
+
87
+ constructor(byteBuf: Buffer) {
88
+ super(byteBuf ?? Currency.XRP.bytes)
89
+ const hex = this.bytes.toString('hex')
90
+
91
+ if (XRP_HEX_REGEX.test(hex)) {
92
+ this._iso = 'XRP'
93
+ } else if (STANDARD_FORMAT_HEX_REGEX.test(hex)) {
94
+ this._iso = isoCodeFromHex(this.bytes.slice(12, 15))
95
+ } else {
96
+ this._iso = null
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Return the ISO code of this currency
102
+ *
103
+ * @returns ISO code if it exists, else null
104
+ */
105
+ iso(): string | null {
106
+ return this._iso
107
+ }
108
+
109
+ /**
110
+ * Constructs a Currency object
111
+ *
112
+ * @param val Currency object or a string representation of a currency
113
+ */
114
+ static from<T extends Hash160 | string>(value: T): Currency {
115
+ if (value instanceof Currency) {
116
+ return value
117
+ }
118
+
119
+ if (typeof value === 'string') {
120
+ return new Currency(bytesFromRepresentation(value))
121
+ }
122
+
123
+ throw new Error('Cannot construct Currency from value given')
124
+ }
125
+
126
+ /**
127
+ * Gets the JSON representation of a currency
128
+ *
129
+ * @returns JSON representation
130
+ */
131
+ toJSON(): string {
132
+ const iso = this.iso()
133
+ if (iso !== null) {
134
+ return iso
135
+ }
136
+ return this.bytes.toString('hex').toUpperCase()
137
+ }
138
+ }
139
+
140
+ export { Currency }