evernode-js-client 0.4.53 → 0.5.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/.eslintrc.json +14 -0
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/clean-pkg.sh +4 -0
- package/npm-readme.md +4 -0
- package/package.json +16 -1
- package/remove-versions.sh +10 -0
- package/src/clients/base-evernode-client.js +567 -0
- package/src/clients/host-client.js +357 -0
- package/src/clients/registry-client.js +52 -0
- package/src/clients/tenant-client.js +264 -0
- package/src/defaults.js +21 -0
- package/src/eccrypto.js +258 -0
- package/src/encryption-helper.js +41 -0
- package/src/event-emitter.js +45 -0
- package/src/evernode-common.js +103 -0
- package/src/evernode-helpers.js +14 -0
- package/src/firestore/firestore-handler.js +309 -0
- package/src/index.js +37 -0
- package/src/state-helpers.js +283 -0
- package/src/transaction-helper.js +62 -0
- package/src/util-helpers.js +48 -0
- package/src/xfl-helpers.js +130 -0
- package/src/xrpl-account.js +473 -0
- package/src/xrpl-api.js +275 -0
- package/src/xrpl-common.js +17 -0
- package/test/package-lock.json +884 -0
- package/test/package.json +9 -0
- package/test/test.js +379 -0
- package/index.js +0 -15166
@@ -0,0 +1,48 @@
|
|
1
|
+
const { Buffer } = require('buffer');
|
2
|
+
const { XflHelpers } = require('./xfl-helpers');
|
3
|
+
const { EvernodeConstants, ErrorReasons } = require('./evernode-common');
|
4
|
+
|
5
|
+
// Utility helper functions.
|
6
|
+
class UtilHelpers {
|
7
|
+
|
8
|
+
static getStateData(states, key) {
|
9
|
+
const state = states.find(s => key === s.key);
|
10
|
+
if (!state)
|
11
|
+
throw { code: ErrorReasons.NO_STATE_KEY, error: `State key '${key}' not found.` };
|
12
|
+
|
13
|
+
return state.data;
|
14
|
+
}
|
15
|
+
|
16
|
+
static readUInt(buf, base = 32, isBE = true) {
|
17
|
+
buf = Buffer.from(buf);
|
18
|
+
switch (base) {
|
19
|
+
case (8):
|
20
|
+
return buf.readUInt8();
|
21
|
+
case (16):
|
22
|
+
return isBE ? buf.readUInt16BE() : buf.readUInt16LE();
|
23
|
+
case (32):
|
24
|
+
return isBE ? buf.readUInt32BE() : buf.readUInt32LE();
|
25
|
+
case (64):
|
26
|
+
return isBE ? Number(buf.readBigUInt64BE()) : Number(buf.readBigUInt64LE());
|
27
|
+
default:
|
28
|
+
throw 'Invalid base value';
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
static decodeLeaseNftUri(hexUri) {
|
33
|
+
// Get the lease index from the nft URI.
|
34
|
+
// <prefix><lease index (uint16)><half of tos hash (16 bytes)><lease amount (uint32)>
|
35
|
+
const prefixLen = EvernodeConstants.LEASE_NFT_PREFIX_HEX.length / 2;
|
36
|
+
const halfToSLen = 16;
|
37
|
+
const uriBuf = Buffer.from(hexUri, 'hex');
|
38
|
+
return {
|
39
|
+
leaseIndex: uriBuf.readUint16BE(prefixLen),
|
40
|
+
halfTos: uriBuf.slice(prefixLen + 2, halfToSLen),
|
41
|
+
leaseAmount: parseFloat(XflHelpers.toString(uriBuf.readBigInt64BE(prefixLen + 2 + halfToSLen)))
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
module.exports = {
|
47
|
+
UtilHelpers
|
48
|
+
}
|
@@ -0,0 +1,130 @@
|
|
1
|
+
const minMantissa = 1000000000000000n
|
2
|
+
const maxMantissa = 9999999999999999n
|
3
|
+
const minExponent = -96
|
4
|
+
const maxExponent = 80
|
5
|
+
|
6
|
+
// Helper class to handle XFL float numbers.
|
7
|
+
class XflHelpers {
|
8
|
+
|
9
|
+
static getExponent(xfl) {
|
10
|
+
if (xfl < 0n)
|
11
|
+
throw "Invalid XFL";
|
12
|
+
if (xfl == 0n)
|
13
|
+
return 0n;
|
14
|
+
return ((xfl >> 54n) & 0xFFn) - 97n;
|
15
|
+
}
|
16
|
+
|
17
|
+
static getMantissa(xfl) {
|
18
|
+
if (xfl < 0n)
|
19
|
+
throw "Invalid XFL";
|
20
|
+
if (xfl == 0n)
|
21
|
+
return 0n;
|
22
|
+
return xfl - ((xfl >> 54n) << 54n);
|
23
|
+
}
|
24
|
+
|
25
|
+
static isNegative(xfl) {
|
26
|
+
if (xfl < 0n)
|
27
|
+
throw "Invalid XFL";
|
28
|
+
if (xfl == 0n)
|
29
|
+
return false;
|
30
|
+
return ((xfl >> 62n) & 1n) == 0n;
|
31
|
+
}
|
32
|
+
|
33
|
+
static toString(xfl) {
|
34
|
+
if (xfl < 0n)
|
35
|
+
throw "Invalid XFL";
|
36
|
+
if (xfl == 0n)
|
37
|
+
return '0';
|
38
|
+
|
39
|
+
const mantissa = this.getMantissa(xfl);
|
40
|
+
const exponent = this.getExponent(xfl);
|
41
|
+
const mantissaStr = mantissa.toString();
|
42
|
+
let finalResult = '';
|
43
|
+
if (exponent > 0n) {
|
44
|
+
finalResult = mantissaStr.padEnd(mantissaStr.length + Number(exponent), '0');
|
45
|
+
} else {
|
46
|
+
const newExponent = Number(exponent) + mantissaStr.length;
|
47
|
+
const cleanedMantissa = mantissaStr.replace(/0+$/, '');
|
48
|
+
if (newExponent == 0) {
|
49
|
+
finalResult = '0.' + cleanedMantissa;
|
50
|
+
} else if (newExponent < 0) {
|
51
|
+
finalResult = '0.' + cleanedMantissa.padStart(newExponent * (-1) + cleanedMantissa.length, '0');
|
52
|
+
} else {
|
53
|
+
finalResult = mantissaStr.substr(0, newExponent) + '.' + mantissaStr.substr(newExponent).replace(/0+$/, '');
|
54
|
+
}
|
55
|
+
}
|
56
|
+
return (this.isNegative(xfl) ? '-' : '') + finalResult.replace(/\.+$/, '');
|
57
|
+
}
|
58
|
+
|
59
|
+
static getXfl(floatStr) {
|
60
|
+
let exponent;
|
61
|
+
let mantissa;
|
62
|
+
floatStr = parseFloat(floatStr).toString();
|
63
|
+
|
64
|
+
if (floatStr === '0') {
|
65
|
+
exponent = BigInt(0);
|
66
|
+
mantissa = BigInt(0);
|
67
|
+
}
|
68
|
+
else if (floatStr.includes('.')) {
|
69
|
+
const parts = floatStr.split('.');
|
70
|
+
exponent = BigInt(-parts[1].length);
|
71
|
+
mantissa = BigInt(parseInt(parts.join('')));
|
72
|
+
}
|
73
|
+
else if (floatStr.endsWith('0')) {
|
74
|
+
const mantissaStr = floatStr.replace(/0+$/g, "");
|
75
|
+
exponent = BigInt(floatStr.length - mantissaStr.length);
|
76
|
+
mantissa = BigInt(parseInt(mantissaStr));
|
77
|
+
}
|
78
|
+
else {
|
79
|
+
exponent = BigInt(0);
|
80
|
+
mantissa = BigInt(parseInt(floatStr));
|
81
|
+
}
|
82
|
+
|
83
|
+
// Convert types as needed.
|
84
|
+
if (typeof (exponent) != 'bigint')
|
85
|
+
exponent = BigInt(exponent);
|
86
|
+
|
87
|
+
if (typeof (mantissa) != 'bigint')
|
88
|
+
mantissa = BigInt(mantissa);
|
89
|
+
|
90
|
+
// Canonical zero.
|
91
|
+
if (mantissa == 0n)
|
92
|
+
return 0n;
|
93
|
+
|
94
|
+
// Normalize.
|
95
|
+
let is_negative = mantissa < 0;
|
96
|
+
if (is_negative)
|
97
|
+
mantissa *= -1n;
|
98
|
+
|
99
|
+
while (mantissa > maxMantissa) {
|
100
|
+
mantissa /= 10n;
|
101
|
+
exponent++;
|
102
|
+
}
|
103
|
+
while (mantissa < minMantissa) {
|
104
|
+
mantissa *= 10n;
|
105
|
+
exponent--;
|
106
|
+
}
|
107
|
+
|
108
|
+
// Canonical zero on mantissa underflow.
|
109
|
+
if (mantissa == 0)
|
110
|
+
return 0n;
|
111
|
+
|
112
|
+
// Under and overflows.
|
113
|
+
if (exponent > maxExponent || exponent < minExponent)
|
114
|
+
return -1; // Note this is an "invalid" XFL used to propagate errors.
|
115
|
+
|
116
|
+
exponent += 97n;
|
117
|
+
|
118
|
+
let xfl = (is_negative ? 0n : 1n);
|
119
|
+
xfl <<= 8n;
|
120
|
+
xfl |= BigInt(exponent);
|
121
|
+
xfl <<= 54n;
|
122
|
+
xfl |= BigInt(mantissa);
|
123
|
+
|
124
|
+
return xfl;
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
module.exports = {
|
129
|
+
XflHelpers
|
130
|
+
}
|
@@ -0,0 +1,473 @@
|
|
1
|
+
const xrpl = require('xrpl');
|
2
|
+
const kp = require('ripple-keypairs');
|
3
|
+
const codec = require('ripple-address-codec');
|
4
|
+
const crypto = require("crypto");
|
5
|
+
const { XrplConstants } = require('./xrpl-common');
|
6
|
+
const { TransactionHelper } = require('./transaction-helper');
|
7
|
+
const { EventEmitter } = require('./event-emitter');
|
8
|
+
const { DefaultValues } = require('./defaults');
|
9
|
+
const xrplCodec = require('xrpl-binary-codec');
|
10
|
+
|
11
|
+
class XrplAccount {
|
12
|
+
|
13
|
+
#events = new EventEmitter();
|
14
|
+
#subscribed = false;
|
15
|
+
#txStreamHandler;
|
16
|
+
|
17
|
+
constructor(address, secret = null, options = {}) {
|
18
|
+
this.xrplApi = options.xrplApi || DefaultValues.xrplApi;
|
19
|
+
|
20
|
+
if (!this.xrplApi)
|
21
|
+
throw "XrplAccount: xrplApi not specified.";
|
22
|
+
|
23
|
+
this.address = address;
|
24
|
+
|
25
|
+
this.secret = secret;
|
26
|
+
if (this.secret)
|
27
|
+
this.wallet = xrpl.Wallet.fromSeed(this.secret);
|
28
|
+
|
29
|
+
this.#txStreamHandler = (eventName, tx, error) => {
|
30
|
+
this.#events.emit(eventName, tx, error);
|
31
|
+
};
|
32
|
+
}
|
33
|
+
|
34
|
+
on(event, handler) {
|
35
|
+
this.#events.on(event, handler);
|
36
|
+
}
|
37
|
+
|
38
|
+
once(event, handler) {
|
39
|
+
this.#events.once(event, handler);
|
40
|
+
}
|
41
|
+
|
42
|
+
off(event, handler = null) {
|
43
|
+
this.#events.off(event, handler);
|
44
|
+
}
|
45
|
+
|
46
|
+
deriveKeypair() {
|
47
|
+
if (!this.secret)
|
48
|
+
throw 'Cannot derive key pair: Account secret is empty.';
|
49
|
+
|
50
|
+
return kp.deriveKeypair(this.secret);
|
51
|
+
}
|
52
|
+
|
53
|
+
async getInfo() {
|
54
|
+
return await this.xrplApi.getAccountInfo(this.address);
|
55
|
+
}
|
56
|
+
|
57
|
+
async getSequence() {
|
58
|
+
return (await this.getInfo())?.Sequence;
|
59
|
+
}
|
60
|
+
|
61
|
+
async getMintedNFTokens() {
|
62
|
+
return ((await this.getInfo())?.MintedNFTokens || 0);
|
63
|
+
}
|
64
|
+
|
65
|
+
async getBurnedNFTokens() {
|
66
|
+
return ((await this.getInfo())?.BurnedNFTokens || 0);
|
67
|
+
}
|
68
|
+
|
69
|
+
async getMessageKey() {
|
70
|
+
return (await this.getInfo())?.MessageKey;
|
71
|
+
}
|
72
|
+
|
73
|
+
async getDomain() {
|
74
|
+
const domain = (await this.getInfo())?.Domain;
|
75
|
+
return domain ? TransactionHelper.hexToASCII(domain) : null;
|
76
|
+
}
|
77
|
+
|
78
|
+
async getTrustLines(currency, issuer) {
|
79
|
+
const lines = await this.xrplApi.getTrustlines(this.address, {
|
80
|
+
limit: 399,
|
81
|
+
peer: issuer
|
82
|
+
});
|
83
|
+
return currency ? lines.filter(l => l.currency === currency) : lines;
|
84
|
+
}
|
85
|
+
|
86
|
+
async getChecks(fromAccount) {
|
87
|
+
return await this.xrplApi.getAccountObjects(fromAccount, { type: "check" });
|
88
|
+
}
|
89
|
+
|
90
|
+
async getNfts() {
|
91
|
+
return await this.xrplApi.getNfts(this.address, {
|
92
|
+
limit: 399
|
93
|
+
});
|
94
|
+
}
|
95
|
+
|
96
|
+
async getOffers() {
|
97
|
+
return await this.xrplApi.getOffers(this.address);
|
98
|
+
}
|
99
|
+
|
100
|
+
async getNftOffers() {
|
101
|
+
return await this.xrplApi.getNftOffers(this.address);
|
102
|
+
}
|
103
|
+
|
104
|
+
async getNftByUri(uri, isHexUri = false) {
|
105
|
+
const nfts = await this.getNfts();
|
106
|
+
const hexUri = isHexUri ? uri : TransactionHelper.asciiToHex(uri).toUpperCase();
|
107
|
+
return nfts.find(n => n.URI == hexUri);
|
108
|
+
}
|
109
|
+
|
110
|
+
async getAccountObjects(options) {
|
111
|
+
return await this.xrplApi.getAccountObjects(this.address, options);
|
112
|
+
}
|
113
|
+
|
114
|
+
async getNamespaceEntries(namespaceId, options = {}) {
|
115
|
+
return await this.xrplApi.getNamespaceEntries(this.address, namespaceId, options);
|
116
|
+
}
|
117
|
+
|
118
|
+
async getFlags() {
|
119
|
+
return xrpl.parseAccountRootFlags((await this.getInfo()).Flags);
|
120
|
+
}
|
121
|
+
|
122
|
+
async getAccountTrx(minLedgerIndex = -1, maxLedgerIndex = -1, isForward = true) {
|
123
|
+
return await this.xrplApi.getAccountTrx(this.address, { ledger_index_min: minLedgerIndex, ledger_index_max: maxLedgerIndex, forward: isForward});
|
124
|
+
}
|
125
|
+
|
126
|
+
setAccountFields(fields, options = {}) {
|
127
|
+
/**
|
128
|
+
* Example for fields
|
129
|
+
*
|
130
|
+
* fields = {
|
131
|
+
* Domain : "www.mydomain.com",
|
132
|
+
* Flags : { asfDefaultRipple: false, asfDisableMaster: true }
|
133
|
+
* }
|
134
|
+
*
|
135
|
+
*/
|
136
|
+
|
137
|
+
if (Object.keys(fields).length === 0)
|
138
|
+
throw "AccountSet fields cannot be empty.";
|
139
|
+
|
140
|
+
const tx = {
|
141
|
+
TransactionType: 'AccountSet',
|
142
|
+
Account: this.address
|
143
|
+
};
|
144
|
+
|
145
|
+
for (const [key, value] of Object.entries(fields)) {
|
146
|
+
|
147
|
+
switch (key) {
|
148
|
+
case 'Domain':
|
149
|
+
tx.Domain = TransactionHelper.asciiToHex(value).toUpperCase();
|
150
|
+
break;
|
151
|
+
|
152
|
+
case 'Flags':
|
153
|
+
for (const [flagKey, flagValue] of Object.entries(value)) {
|
154
|
+
tx[(flagValue) ? 'SetFlag' : 'ClearFlag'] |= xrpl.AccountSetAsfFlags[flagKey];
|
155
|
+
}
|
156
|
+
break;
|
157
|
+
|
158
|
+
default:
|
159
|
+
tx[key] = value;
|
160
|
+
break;
|
161
|
+
}
|
162
|
+
}
|
163
|
+
|
164
|
+
return this.#submitAndVerifyTransaction(tx, options);
|
165
|
+
}
|
166
|
+
|
167
|
+
makePayment(toAddr, amount, currency, issuer = null, memos = null, options = {}) {
|
168
|
+
|
169
|
+
const amountObj = makeAmountObject(amount, currency, issuer);
|
170
|
+
|
171
|
+
return this.#submitAndVerifyTransaction({
|
172
|
+
TransactionType: 'Payment',
|
173
|
+
Account: this.address,
|
174
|
+
Amount: amountObj,
|
175
|
+
Destination: toAddr,
|
176
|
+
Memos: TransactionHelper.formatMemos(memos)
|
177
|
+
}, options);
|
178
|
+
}
|
179
|
+
|
180
|
+
setTrustLine(currency, issuer, limit, allowRippling = false, memos = null, options = {}) {
|
181
|
+
|
182
|
+
if (typeof limit !== 'string')
|
183
|
+
throw "Limit must be a string.";
|
184
|
+
|
185
|
+
let tx = {
|
186
|
+
TransactionType: 'TrustSet',
|
187
|
+
Account: this.address,
|
188
|
+
LimitAmount: {
|
189
|
+
currency: currency,
|
190
|
+
issuer: issuer,
|
191
|
+
value: limit
|
192
|
+
},
|
193
|
+
Memos: TransactionHelper.formatMemos(memos)
|
194
|
+
};
|
195
|
+
|
196
|
+
if (!allowRippling)
|
197
|
+
tx.Flags = 131072; // tfSetNoRipple;
|
198
|
+
|
199
|
+
return this.#submitAndVerifyTransaction(tx, options);
|
200
|
+
}
|
201
|
+
|
202
|
+
setRegularKey(regularKey, memos = null, options = {}) {
|
203
|
+
|
204
|
+
return this.#submitAndVerifyTransaction({
|
205
|
+
TransactionType: 'SetRegularKey',
|
206
|
+
Account: this.address,
|
207
|
+
RegularKey: regularKey,
|
208
|
+
Memos: TransactionHelper.formatMemos(memos)
|
209
|
+
}, options);
|
210
|
+
}
|
211
|
+
|
212
|
+
cashCheck(check, options = {}) {
|
213
|
+
const checkIDhasher = crypto.createHash('sha512')
|
214
|
+
checkIDhasher.update(Buffer.from('0043', 'hex'))
|
215
|
+
checkIDhasher.update(Buffer.from(codec.decodeAccountID(check.Account)))
|
216
|
+
const seqBuf = Buffer.alloc(4)
|
217
|
+
seqBuf.writeUInt32BE(check.Sequence, 0)
|
218
|
+
checkIDhasher.update(seqBuf)
|
219
|
+
const checkID = checkIDhasher.digest('hex').slice(0, 64).toUpperCase()
|
220
|
+
console.log("Calculated checkID:", checkID);
|
221
|
+
|
222
|
+
return this.#submitAndVerifyTransaction({
|
223
|
+
TransactionType: 'CheckCash',
|
224
|
+
Account: this.address,
|
225
|
+
CheckID: checkID,
|
226
|
+
Amount: {
|
227
|
+
currency: check.SendMax.currency,
|
228
|
+
issuer: check.SendMax.issuer,
|
229
|
+
value: check.SendMax.value
|
230
|
+
},
|
231
|
+
}, options);
|
232
|
+
}
|
233
|
+
|
234
|
+
offerSell(sellAmount, sellCurrency, sellIssuer, forAmount, forCurrency, forIssuer = null, expiration = 4294967295, memos = null, options = {}) {
|
235
|
+
|
236
|
+
const sellAmountObj = makeAmountObject(sellAmount, sellCurrency, sellIssuer);
|
237
|
+
const forAmountObj = makeAmountObject(forAmount, forCurrency, forIssuer);
|
238
|
+
|
239
|
+
return this.#submitAndVerifyTransaction({
|
240
|
+
TransactionType: 'OfferCreate',
|
241
|
+
Account: this.address,
|
242
|
+
TakerGets: sellAmountObj,
|
243
|
+
TakerPays: forAmountObj,
|
244
|
+
Expiration: expiration,
|
245
|
+
Memos: TransactionHelper.formatMemos(memos)
|
246
|
+
}, options);
|
247
|
+
}
|
248
|
+
|
249
|
+
offerBuy(buyAmount, buyCurrency, buyIssuer, forAmount, forCurrency, forIssuer = null, expiration = 4294967295, memos = null, options = {}) {
|
250
|
+
|
251
|
+
const buyAmountObj = makeAmountObject(buyAmount, buyCurrency, buyIssuer);
|
252
|
+
const forAmountObj = makeAmountObject(forAmount, forCurrency, forIssuer);
|
253
|
+
|
254
|
+
return this.#submitAndVerifyTransaction({
|
255
|
+
TransactionType: 'OfferCreate',
|
256
|
+
Account: this.address,
|
257
|
+
TakerGets: forAmountObj,
|
258
|
+
TakerPays: buyAmountObj,
|
259
|
+
Expiration: expiration,
|
260
|
+
Memos: TransactionHelper.formatMemos(memos)
|
261
|
+
}, options);
|
262
|
+
}
|
263
|
+
|
264
|
+
cancelOffer(offerSequence, memos = null, options = {}) {
|
265
|
+
return this.#submitAndVerifyTransaction({
|
266
|
+
TransactionType: 'OfferCancel',
|
267
|
+
Account: this.address,
|
268
|
+
OfferSequence: offerSequence,
|
269
|
+
Memos: TransactionHelper.formatMemos(memos)
|
270
|
+
}, options);
|
271
|
+
}
|
272
|
+
|
273
|
+
mintNft(uri, taxon, transferFee, flags = {}, memos = null, options = {}) {
|
274
|
+
return this.#submitAndVerifyTransaction({
|
275
|
+
TransactionType: 'NFTokenMint',
|
276
|
+
Account: this.address,
|
277
|
+
URI: flags.isHexUri ? uri : TransactionHelper.asciiToHex(uri).toUpperCase(),
|
278
|
+
NFTokenTaxon: taxon,
|
279
|
+
TransferFee: transferFee,
|
280
|
+
Flags: (flags.isBurnable ? 1 : 0) | (flags.isOnlyXRP ? 2 : 0) | (flags.isTrustLine ? 4 : 0) | (flags.isTransferable ? 8 : 0),
|
281
|
+
Memos: TransactionHelper.formatMemos(memos)
|
282
|
+
}, options);
|
283
|
+
}
|
284
|
+
|
285
|
+
offerSellNft(nfTokenId, amount, currency, issuer = null, destination = null, expiration = 4294967295, memos = null, options = {}) {
|
286
|
+
|
287
|
+
const amountObj = makeAmountObject(amount, currency, issuer);
|
288
|
+
const tx = {
|
289
|
+
TransactionType: 'NFTokenCreateOffer',
|
290
|
+
Account: this.address,
|
291
|
+
NFTokenID: nfTokenId,
|
292
|
+
Amount: amountObj,
|
293
|
+
Expiration: expiration,
|
294
|
+
Flags: 1, // tfSellToken
|
295
|
+
Memos: TransactionHelper.formatMemos(memos)
|
296
|
+
};
|
297
|
+
|
298
|
+
return this.#submitAndVerifyTransaction(destination ? { ...tx, Destination: destination } : tx, options);
|
299
|
+
}
|
300
|
+
|
301
|
+
offerBuyNft(nfTokenId, owner, amount, currency, issuer = null, expiration = 4294967295, memos = null, options = {}) {
|
302
|
+
|
303
|
+
const amountObj = makeAmountObject(amount, currency, issuer);
|
304
|
+
|
305
|
+
return this.#submitAndVerifyTransaction({
|
306
|
+
TransactionType: 'NFTokenCreateOffer',
|
307
|
+
Account: this.address,
|
308
|
+
NFTokenID: nfTokenId,
|
309
|
+
Owner: owner,
|
310
|
+
Amount: amountObj,
|
311
|
+
Expiration: expiration,
|
312
|
+
Flags: 0, // Buy offer
|
313
|
+
Memos: TransactionHelper.formatMemos(memos)
|
314
|
+
}, options);
|
315
|
+
}
|
316
|
+
|
317
|
+
sellNft(offerId, memos = null, options = {}) {
|
318
|
+
|
319
|
+
return this.#submitAndVerifyTransaction({
|
320
|
+
TransactionType: 'NFTokenAcceptOffer',
|
321
|
+
Account: this.address,
|
322
|
+
NFTokenBuyOffer: offerId,
|
323
|
+
Memos: TransactionHelper.formatMemos(memos)
|
324
|
+
}, options);
|
325
|
+
}
|
326
|
+
|
327
|
+
buyNft(offerId, memos = null, options = {}) {
|
328
|
+
|
329
|
+
return this.#submitAndVerifyTransaction({
|
330
|
+
TransactionType: 'NFTokenAcceptOffer',
|
331
|
+
Account: this.address,
|
332
|
+
NFTokenSellOffer: offerId,
|
333
|
+
Memos: TransactionHelper.formatMemos(memos)
|
334
|
+
}, options);
|
335
|
+
}
|
336
|
+
|
337
|
+
burnNft(nfTokenId, owner = null, memos = null, options = {}) {
|
338
|
+
|
339
|
+
const tx = {
|
340
|
+
TransactionType: 'NFTokenBurn',
|
341
|
+
Account: this.address,
|
342
|
+
NFTokenID: nfTokenId,
|
343
|
+
Memos: TransactionHelper.formatMemos(memos)
|
344
|
+
};
|
345
|
+
|
346
|
+
return this.#submitAndVerifyTransaction(owner ? { ...tx, Owner: owner } : tx, options);
|
347
|
+
}
|
348
|
+
|
349
|
+
async subscribe() {
|
350
|
+
// Subscribe only once. Otherwise event handlers will be duplicated.
|
351
|
+
if (this.#subscribed)
|
352
|
+
return;
|
353
|
+
|
354
|
+
await this.xrplApi.subscribeToAddress(this.address, this.#txStreamHandler);
|
355
|
+
|
356
|
+
this.#subscribed = true;
|
357
|
+
}
|
358
|
+
|
359
|
+
async unsubscribe() {
|
360
|
+
if (!this.#subscribed)
|
361
|
+
return;
|
362
|
+
|
363
|
+
await this.xrplApi.unsubscribeFromAddress(this.address, this.#txStreamHandler);
|
364
|
+
this.#subscribed = false;
|
365
|
+
}
|
366
|
+
|
367
|
+
#submitAndVerifyTransaction(tx, options) {
|
368
|
+
|
369
|
+
if (!this.wallet)
|
370
|
+
throw "no_secret";
|
371
|
+
|
372
|
+
// Returned format.
|
373
|
+
// {
|
374
|
+
// id: txHash, (if signing success)
|
375
|
+
// code: final transaction result code.
|
376
|
+
// details: submission and transaction details, (if signing success)
|
377
|
+
// error: Any error that prevents submission.
|
378
|
+
// }
|
379
|
+
|
380
|
+
return new Promise(async (resolve, reject) => {
|
381
|
+
|
382
|
+
// Attach tx options to the transaction.
|
383
|
+
const txOptions = {
|
384
|
+
LastLedgerSequence: options.maxLedgerIndex || (this.xrplApi.ledgerIndex + XrplConstants.MAX_LEDGER_OFFSET),
|
385
|
+
Sequence: options.sequence || await this.getSequence(),
|
386
|
+
SigningPubKey: '', // This field is required for fee calculation.
|
387
|
+
Fee: '0' // This field is required for fee calculation.
|
388
|
+
}
|
389
|
+
Object.assign(tx, txOptions);
|
390
|
+
const txnBlob = xrplCodec.encode(tx);
|
391
|
+
const fees = await this.xrplApi.getTransactionFee(txnBlob);
|
392
|
+
delete tx['SigningPubKey'];
|
393
|
+
tx.Fee = fees + '';
|
394
|
+
|
395
|
+
try {
|
396
|
+
const submission = await this.xrplApi.submitAndVerify(tx, { wallet: this.wallet });
|
397
|
+
const r = submission?.result;
|
398
|
+
const txResult = {
|
399
|
+
id: r?.hash,
|
400
|
+
code: r?.meta?.TransactionResult,
|
401
|
+
details: r
|
402
|
+
};
|
403
|
+
|
404
|
+
console.log("Transaction result: " + txResult.code);
|
405
|
+
if (txResult.code === "tesSUCCESS")
|
406
|
+
resolve(txResult);
|
407
|
+
else
|
408
|
+
reject(txResult);
|
409
|
+
}
|
410
|
+
catch (err) {
|
411
|
+
console.log("Error submitting transaction:", err);
|
412
|
+
reject({ error: err });
|
413
|
+
}
|
414
|
+
|
415
|
+
});
|
416
|
+
}
|
417
|
+
|
418
|
+
/**
|
419
|
+
* Submit the signed raw transaction.
|
420
|
+
* @param txBlob Signed and encoded transacion as a hex string.
|
421
|
+
*/
|
422
|
+
submitTransactionBlob(txBlob) {
|
423
|
+
|
424
|
+
// Returned format.
|
425
|
+
// {
|
426
|
+
// id: txHash, (if signing success)
|
427
|
+
// code: final transaction result code.
|
428
|
+
// details: submission and transaction details, (if signing success)
|
429
|
+
// error: Any error that prevents submission.
|
430
|
+
// }
|
431
|
+
|
432
|
+
return new Promise(async (resolve, reject) => {
|
433
|
+
try {
|
434
|
+
const submission = await this.xrplApi.submitAndVerify(txBlob);
|
435
|
+
const r = submission?.result;
|
436
|
+
const txResult = {
|
437
|
+
id: r?.hash,
|
438
|
+
code: r?.meta?.TransactionResult,
|
439
|
+
details: r
|
440
|
+
};
|
441
|
+
|
442
|
+
console.log("Transaction result: " + txResult.code);
|
443
|
+
if (txResult.code === "tesSUCCESS")
|
444
|
+
resolve(txResult);
|
445
|
+
else
|
446
|
+
reject(txResult);
|
447
|
+
}
|
448
|
+
catch (err) {
|
449
|
+
console.log("Error submitting transaction:", err);
|
450
|
+
reject({ error: err });
|
451
|
+
}
|
452
|
+
|
453
|
+
});
|
454
|
+
}
|
455
|
+
}
|
456
|
+
|
457
|
+
function makeAmountObject(amount, currency, issuer) {
|
458
|
+
if (typeof amount !== 'string')
|
459
|
+
throw "Amount must be a string.";
|
460
|
+
if (currency !== XrplConstants.XRP && !issuer)
|
461
|
+
throw "Non-XRP currency must have an issuer.";
|
462
|
+
|
463
|
+
const amountObj = (currency == XrplConstants.XRP) ? amount : {
|
464
|
+
currency: currency,
|
465
|
+
issuer: issuer,
|
466
|
+
value: amount
|
467
|
+
}
|
468
|
+
return amountObj;
|
469
|
+
}
|
470
|
+
|
471
|
+
module.exports = {
|
472
|
+
XrplAccount
|
473
|
+
}
|