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.
- package/dist/enums/definitions.json +250 -1
- package/dist/enums/src/enums/definitions.json +250 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/issue.d.ts +39 -0
- package/dist/types/issue.js +81 -0
- package/dist/types/issue.js.map +1 -0
- package/dist/types/xchain-bridge.d.ts +45 -0
- package/dist/types/xchain-bridge.js +102 -0
- package/dist/types/xchain-bridge.js.map +1 -0
- package/package.json +3 -4
- package/src/README.md +3 -0
- package/src/binary.ts +188 -0
- package/src/coretypes.ts +31 -0
- package/src/enums/README.md +144 -0
- package/src/enums/bytes.ts +75 -0
- package/src/enums/constants.ts +4 -0
- package/src/enums/definitions.json +2599 -0
- package/src/enums/field.ts +85 -0
- package/src/enums/index.ts +34 -0
- package/src/enums/utils-renumber.ts +134 -0
- package/src/enums/xrpl-definitions-base.ts +111 -0
- package/src/enums/xrpl-definitions.ts +32 -0
- package/src/hash-prefixes.ts +40 -0
- package/src/hashes.ts +76 -0
- package/src/index.ts +141 -0
- package/src/ledger-hashes.ts +187 -0
- package/src/quality.ts +39 -0
- package/src/serdes/binary-parser.ts +217 -0
- package/src/serdes/binary-serializer.ts +166 -0
- package/src/shamap.ts +186 -0
- package/src/types/account-id.ts +86 -0
- package/src/types/amount.ts +256 -0
- package/src/types/blob.ts +43 -0
- package/src/types/currency.ts +140 -0
- package/src/types/hash-128.ts +33 -0
- package/src/types/hash-160.ts +20 -0
- package/src/types/hash-256.ts +16 -0
- package/src/types/hash.ts +81 -0
- package/src/types/index.ts +61 -0
- package/src/types/issue.ts +96 -0
- package/src/types/path-set.ts +290 -0
- package/src/types/serialized-type.ts +120 -0
- package/src/types/st-array.ts +107 -0
- package/src/types/st-object.ts +192 -0
- package/src/types/uint-16.ts +49 -0
- package/src/types/uint-32.ts +56 -0
- package/src/types/uint-64.ts +105 -0
- package/src/types/uint-8.ts +49 -0
- package/src/types/uint.ts +57 -0
- package/src/types/vector-256.ts +84 -0
- package/test/amount.test.js +0 -43
- package/test/binary-json.test.js +0 -45
- package/test/binary-parser.test.js +0 -396
- package/test/binary-serializer.test.js +0 -289
- package/test/definitions.test.js +0 -160
- package/test/fixtures/account-tx-transactions.db +0 -0
- package/test/fixtures/codec-fixtures.json +0 -4466
- package/test/fixtures/data-driven-tests.json +0 -2919
- package/test/fixtures/delivermin-tx-binary.json +0 -1
- package/test/fixtures/delivermin-tx.json +0 -98
- package/test/fixtures/deposit-preauth-tx-binary.json +0 -1
- package/test/fixtures/deposit-preauth-tx-meta-binary.json +0 -1
- package/test/fixtures/deposit-preauth-tx.json +0 -58
- package/test/fixtures/escrow-cancel-binary.json +0 -1
- package/test/fixtures/escrow-cancel-tx.json +0 -6
- package/test/fixtures/escrow-create-binary.json +0 -1
- package/test/fixtures/escrow-create-tx.json +0 -10
- package/test/fixtures/escrow-finish-binary.json +0 -1
- package/test/fixtures/escrow-finish-meta-binary.json +0 -1
- package/test/fixtures/escrow-finish-tx.json +0 -95
- package/test/fixtures/ledger-full-38129.json +0 -1
- package/test/fixtures/ledger-full-40000.json +0 -1
- package/test/fixtures/negative-unl.json +0 -12
- package/test/fixtures/nf-token.json +0 -547
- package/test/fixtures/payment-channel-claim-binary.json +0 -1
- package/test/fixtures/payment-channel-claim-tx.json +0 -8
- package/test/fixtures/payment-channel-create-binary.json +0 -1
- package/test/fixtures/payment-channel-create-tx.json +0 -11
- package/test/fixtures/payment-channel-fund-binary.json +0 -1
- package/test/fixtures/payment-channel-fund-tx.json +0 -7
- package/test/fixtures/signerlistset-tx-binary.json +0 -1
- package/test/fixtures/signerlistset-tx-meta-binary.json +0 -1
- package/test/fixtures/signerlistset-tx.json +0 -94
- package/test/fixtures/ticket-create-binary.json +0 -1
- package/test/fixtures/ticket-create-tx.json +0 -7
- package/test/fixtures/x-codec-fixtures.json +0 -188
- package/test/hash.test.js +0 -135
- package/test/ledger.test.js +0 -29
- package/test/lower-case-hex.test.js +0 -46
- package/test/pseudo-transaction.test.js +0 -38
- package/test/quality.test.js +0 -15
- package/test/shamap.test.js +0 -89
- package/test/signing-data-encoding.test.js +0 -242
- package/test/tx-encode-decode.test.js +0 -119
- package/test/types.test.js +0 -34
- package/test/uint.test.js +0 -148
- package/test/utils.js +0 -30
- 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 }
|