@ledgerhq/coin-xrp 7.3.0-nightly.2 → 7.3.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +24 -15
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.integ.test.js +5 -158
- package/lib/api/index.integ.test.js.map +1 -1
- package/lib/api/index.js +5 -5
- package/lib/api/index.js.map +1 -1
- package/lib/api/index.test.js +8 -6
- package/lib/api/index.test.js.map +1 -1
- package/lib/logic/listOperations.js +6 -1
- package/lib/logic/listOperations.js.map +1 -1
- package/lib/logic/listOperations.test.js +3 -2
- package/lib/logic/listOperations.test.js.map +1 -1
- package/lib/types/model.d.ts +2 -1
- package/lib/types/model.d.ts.map +1 -1
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.integ.test.js +5 -158
- package/lib-es/api/index.integ.test.js.map +1 -1
- package/lib-es/api/index.js +5 -5
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/api/index.test.js +8 -6
- package/lib-es/api/index.test.js.map +1 -1
- package/lib-es/logic/listOperations.js +6 -1
- package/lib-es/logic/listOperations.js.map +1 -1
- package/lib-es/logic/listOperations.test.js +3 -2
- package/lib-es/logic/listOperations.test.js.map +1 -1
- package/lib-es/types/model.d.ts +2 -1
- package/lib-es/types/model.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/api/index.integ.test.ts +5 -184
- package/src/api/index.test.ts +8 -6
- package/src/api/index.ts +4 -6
- package/src/logic/listOperations.test.ts +4 -2
- package/src/logic/listOperations.ts +6 -1
- package/src/types/model.ts +1 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { decode } from "ripple-binary-codec";
|
|
2
2
|
import { createApi } from ".";
|
|
3
|
-
import { Operation } from "@ledgerhq/coin-framework/api/types";
|
|
4
3
|
//import { decode, encodeForSigning } from "ripple-binary-codec";
|
|
5
4
|
//import { sign } from "ripple-keypairs";
|
|
6
5
|
|
|
7
|
-
describe("Xrp Api
|
|
6
|
+
describe("Xrp Api", () => {
|
|
8
7
|
const SENDER = "rh1HPuRVsYYvThxG2Bs1MfjmrVC73S16Fb";
|
|
9
8
|
const api = createApi({ node: "https://s.altnet.rippletest.net:51234" });
|
|
10
9
|
|
|
@@ -34,7 +33,7 @@ describe("Xrp Api (testnet)", () => {
|
|
|
34
33
|
describe("listOperations", () => {
|
|
35
34
|
it.skip("returns a list regarding address parameter", async () => {
|
|
36
35
|
// When
|
|
37
|
-
const [tx, _] = await api.listOperations(SENDER, { minHeight: 200
|
|
36
|
+
const [tx, _] = await api.listOperations(SENDER, { minHeight: 200 });
|
|
38
37
|
|
|
39
38
|
// https://blockexplorer.one/xrp/testnet/address/rh1HPuRVsYYvThxG2Bs1MfjmrVC73S16Fb
|
|
40
39
|
// as of 2025-03-18, the address has 287 transactions
|
|
@@ -52,10 +51,7 @@ describe("Xrp Api (testnet)", () => {
|
|
|
52
51
|
const SENDER_WITH_TRANSACTIONS = "rUxSkt6hQpWxXQwTNRUCYYRQ7BC2yRA3F8";
|
|
53
52
|
|
|
54
53
|
// When
|
|
55
|
-
const [ops, _] = await api.listOperations(SENDER_WITH_TRANSACTIONS, {
|
|
56
|
-
minHeight: 0,
|
|
57
|
-
order: "asc",
|
|
58
|
-
});
|
|
54
|
+
const [ops, _] = await api.listOperations(SENDER_WITH_TRANSACTIONS, { minHeight: 0 });
|
|
59
55
|
// Then
|
|
60
56
|
const checkSet = new Set(ops.map(elt => elt.tx.hash));
|
|
61
57
|
expect(checkSet.size).toEqual(ops.length);
|
|
@@ -68,17 +64,6 @@ describe("Xrp Api (testnet)", () => {
|
|
|
68
64
|
// so here we are checking that this limit is bypassed
|
|
69
65
|
expect(ops.length).toBeGreaterThan(200);
|
|
70
66
|
});
|
|
71
|
-
|
|
72
|
-
it("returns operations from latest, but in asc order", async () => {
|
|
73
|
-
// When
|
|
74
|
-
const [txDesc] = await api.listOperations(SENDER, { minHeight: 0, order: "desc" });
|
|
75
|
-
|
|
76
|
-
// Then
|
|
77
|
-
// Check if the result is sorted in ascending order
|
|
78
|
-
expect(txDesc[0].tx.block.height).toBeGreaterThanOrEqual(
|
|
79
|
-
txDesc[txDesc.length - 1].tx.block.height,
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
67
|
});
|
|
83
68
|
|
|
84
69
|
describe("lastBlock", () => {
|
|
@@ -97,13 +82,13 @@ describe("Xrp Api (testnet)", () => {
|
|
|
97
82
|
// Account with no transaction (at the time of this writing)
|
|
98
83
|
const SENDER_WITH_NO_TRANSACTION = "rKtXXTVno77jhu6tto1MAXjepyuaKaLcqB";
|
|
99
84
|
|
|
100
|
-
it("returns
|
|
85
|
+
it("returns an amount above 0 when address has transactions", async () => {
|
|
101
86
|
// When
|
|
102
87
|
const result = await api.getBalance(SENDER);
|
|
103
88
|
|
|
104
89
|
// Then
|
|
105
90
|
expect(result[0].asset).toEqual({ type: "native" });
|
|
106
|
-
expect(result[0].value).
|
|
91
|
+
expect(result[0].value).toBeGreaterThan(BigInt(0));
|
|
107
92
|
});
|
|
108
93
|
|
|
109
94
|
it("returns 0 when address has no transaction", async () => {
|
|
@@ -177,170 +162,6 @@ describe("Xrp Api (testnet)", () => {
|
|
|
177
162
|
});
|
|
178
163
|
});
|
|
179
164
|
|
|
180
|
-
describe("Xrp Api (mainnet)", () => {
|
|
181
|
-
const SENDER = "rn5BQvhksnPfbo277LtFks4iyYStPKGrnJ";
|
|
182
|
-
const api = createApi({ node: "https://xrp.coin.ledger.com" });
|
|
183
|
-
|
|
184
|
-
describe("estimateFees", () => {
|
|
185
|
-
it("returns a default value", async () => {
|
|
186
|
-
// Given
|
|
187
|
-
const amount = BigInt(100);
|
|
188
|
-
|
|
189
|
-
// When
|
|
190
|
-
const result = await api.estimateFees({
|
|
191
|
-
asset: { type: "native" },
|
|
192
|
-
type: "send",
|
|
193
|
-
sender: SENDER,
|
|
194
|
-
amount,
|
|
195
|
-
recipient: "r9m6MwViR4GnUNqoGXGa8eroBrZ9FAPHFS",
|
|
196
|
-
memo: {
|
|
197
|
-
type: "map",
|
|
198
|
-
memos: new Map(),
|
|
199
|
-
},
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Then
|
|
203
|
-
expect(result.value).toEqual(BigInt(10));
|
|
204
|
-
});
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
describe("listOperations", () => {
|
|
208
|
-
let ops: Operation[];
|
|
209
|
-
|
|
210
|
-
beforeAll(async () => {
|
|
211
|
-
const resp = await api.listOperations(SENDER, { minHeight: 0 });
|
|
212
|
-
ops = resp[0];
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("returns operations", async () => {
|
|
216
|
-
// https://xrpscan.com/account/rn5BQvhksnPfbo277LtFks4iyYStPKGrnJ
|
|
217
|
-
// as of 2025-08-29, the address has 398 transactions, 1 op per tx
|
|
218
|
-
expect(ops.length).toBeGreaterThanOrEqual(398);
|
|
219
|
-
const checkSet = new Set(ops.map(elt => elt.tx.hash));
|
|
220
|
-
expect(checkSet.size).toEqual(ops.length);
|
|
221
|
-
ops.forEach(operation => {
|
|
222
|
-
const isSenderOrReceipt =
|
|
223
|
-
operation.senders.includes(SENDER) || operation.recipients.includes(SENDER);
|
|
224
|
-
expect(isSenderOrReceipt).toBeTruthy();
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("returns IN operation", async () => {
|
|
229
|
-
// https://xrpscan.com/tx/8116AC2E4C6FE35C7C2AADA1FE88C32E1FFD0DA8D348855BAC3903B7153F1EFF
|
|
230
|
-
const inTx = {
|
|
231
|
-
hash: "8116AC2E4C6FE35C7C2AADA1FE88C32E1FFD0DA8D348855BAC3903B7153F1EFF",
|
|
232
|
-
amount: 6.713757,
|
|
233
|
-
recipient: SENDER,
|
|
234
|
-
sender: "r9m6MwViR4GnUNqoGXGa8eroBrZ9FAPHFS",
|
|
235
|
-
type: "IN",
|
|
236
|
-
fees: 0.000012,
|
|
237
|
-
};
|
|
238
|
-
const op = ops.find(o => o.tx.hash === inTx.hash) as Operation;
|
|
239
|
-
expect(op.tx.hash).toEqual(inTx.hash);
|
|
240
|
-
expect(op.value).toEqual(BigInt(inTx.amount * 1e6));
|
|
241
|
-
expect(op.recipients).toContain(inTx.recipient);
|
|
242
|
-
expect(op.senders).toContain(inTx.sender);
|
|
243
|
-
expect(op.type).toEqual(inTx.type);
|
|
244
|
-
expect(op.tx.fees).toEqual(BigInt(inTx.fees * 1e6));
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("returns OUT operation", async () => {
|
|
248
|
-
// https://xrpscan.com/tx/F9B0E5CC0A303C6099AFCE3C1DD9D8D80034F8C33041BF1E792C8236D2DF3F79
|
|
249
|
-
const outTx = {
|
|
250
|
-
hash: "F9B0E5CC0A303C6099AFCE3C1DD9D8D80034F8C33041BF1E792C8236D2DF3F79",
|
|
251
|
-
amount: 7.8,
|
|
252
|
-
recipient: "r9m6MwViR4GnUNqoGXGa8eroBrZ9FAPHFS",
|
|
253
|
-
sender: SENDER,
|
|
254
|
-
type: "OUT",
|
|
255
|
-
fees: 0.00001,
|
|
256
|
-
};
|
|
257
|
-
const op = ops.find(o => o.tx.hash === outTx.hash) as Operation;
|
|
258
|
-
expect(op.tx.hash).toEqual(outTx.hash);
|
|
259
|
-
expect(op.value).toEqual(BigInt(outTx.amount * 1e6));
|
|
260
|
-
expect(op.recipients).toContain(outTx.recipient);
|
|
261
|
-
expect(op.senders).toContain(outTx.sender);
|
|
262
|
-
expect(op.type).toEqual(outTx.type);
|
|
263
|
-
expect(op.tx.fees).toEqual(BigInt(outTx.fees * 1e6));
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
describe("lastBlock", () => {
|
|
268
|
-
it("returns last block info", async () => {
|
|
269
|
-
const result = await api.lastBlock();
|
|
270
|
-
expect(result.hash).toBeDefined();
|
|
271
|
-
expect(result.height).toBeDefined();
|
|
272
|
-
expect(result.time).toBeInstanceOf(Date);
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
describe("getBalance", () => {
|
|
277
|
-
it("returns an amount", async () => {
|
|
278
|
-
const result = await api.getBalance(SENDER);
|
|
279
|
-
expect(result[0].asset).toEqual({ type: "native" });
|
|
280
|
-
expect(result[0].value).toBeGreaterThanOrEqual(BigInt(0));
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
describe("craftTransaction", () => {
|
|
285
|
-
const RECIPIENT = "r9m6MwViR4GnUNqoGXGa8eroBrZ9FAPHFS";
|
|
286
|
-
|
|
287
|
-
it("returns a raw transaction", async () => {
|
|
288
|
-
const result = await api.craftTransaction({
|
|
289
|
-
asset: { type: "native" },
|
|
290
|
-
type: "send",
|
|
291
|
-
sender: SENDER,
|
|
292
|
-
recipient: RECIPIENT,
|
|
293
|
-
amount: BigInt(10),
|
|
294
|
-
memo: {
|
|
295
|
-
type: "map",
|
|
296
|
-
memos: new Map([["memos", ["testdata"]]]),
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
|
-
expect(result.length).toEqual(178);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it("should use default fees when user does not provide them for crafting a transaction", async () => {
|
|
303
|
-
const result = await api.craftTransaction({
|
|
304
|
-
asset: { type: "native" },
|
|
305
|
-
type: "send",
|
|
306
|
-
sender: SENDER,
|
|
307
|
-
recipient: RECIPIENT,
|
|
308
|
-
amount: BigInt(10),
|
|
309
|
-
memo: {
|
|
310
|
-
type: "map",
|
|
311
|
-
memos: new Map(),
|
|
312
|
-
},
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
expect(decode(result)).toMatchObject({
|
|
316
|
-
Fee: "10",
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it("should use custom user fees when user provides it for crafting a transaction", async () => {
|
|
321
|
-
const customFees = 99n;
|
|
322
|
-
const result = await api.craftTransaction(
|
|
323
|
-
{
|
|
324
|
-
asset: { type: "native" },
|
|
325
|
-
type: "send",
|
|
326
|
-
sender: SENDER,
|
|
327
|
-
recipient: RECIPIENT,
|
|
328
|
-
amount: BigInt(10),
|
|
329
|
-
memo: {
|
|
330
|
-
type: "map",
|
|
331
|
-
memos: new Map(),
|
|
332
|
-
},
|
|
333
|
-
},
|
|
334
|
-
{ value: customFees },
|
|
335
|
-
);
|
|
336
|
-
|
|
337
|
-
expect(decode(result)).toMatchObject({
|
|
338
|
-
Fee: customFees.toString(),
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
|
|
344
165
|
// To enable this test, you need to fill an `.env` file at the root of this package. Example can be found in `.env.integ.test.example`.
|
|
345
166
|
// The value hardcoded here depends on the value filled in the `.env` file.
|
|
346
167
|
/*describe.skip("combine", () => {
|
package/src/api/index.test.ts
CHANGED
|
@@ -106,7 +106,7 @@ describe("listOperations", () => {
|
|
|
106
106
|
const txs = givenTxs(BigInt(10), BigInt(10), "src", "dest");
|
|
107
107
|
// each time it's called it returns a marker, so in theory it would loop forever
|
|
108
108
|
mockGetTransactions.mockResolvedValue(mockNetworkTxs(txs, defaultMarker));
|
|
109
|
-
const [results, _] = await api.listOperations("src", { minHeight: 0
|
|
109
|
+
const [results, _] = await api.listOperations("src", { minHeight: 0 });
|
|
110
110
|
|
|
111
111
|
// called 10 times because there is a hard limit of 10 iterations in case something goes wrong
|
|
112
112
|
// with interpretation of the token (bug / explorer api changed ...)
|
|
@@ -122,7 +122,7 @@ describe("listOperations", () => {
|
|
|
122
122
|
.mockReturnValueOnce(mockNetworkTxs(txs, defaultMarker))
|
|
123
123
|
.mockReturnValueOnce(mockNetworkTxs(txs, undefined));
|
|
124
124
|
|
|
125
|
-
const [results, _] = await api.listOperations("src", { minHeight: 0
|
|
125
|
+
const [results, _] = await api.listOperations("src", { minHeight: 0 });
|
|
126
126
|
|
|
127
127
|
// called 2 times because the second time there is no marker
|
|
128
128
|
expect(mockGetServerInfos).toHaveBeenCalledTimes(2);
|
|
@@ -171,13 +171,15 @@ describe("listOperations", () => {
|
|
|
171
171
|
mockGetTransactions.mockResolvedValue(mockNetworkTxs([], undefined));
|
|
172
172
|
|
|
173
173
|
// When
|
|
174
|
-
const [results, _] = await api.listOperations(address, { minHeight: 0
|
|
174
|
+
const [results, _] = await api.listOperations(address, { minHeight: 0 });
|
|
175
175
|
|
|
176
176
|
// Then
|
|
177
177
|
// called twice because the marker is set the first time
|
|
178
178
|
expect(mockGetServerInfos).toHaveBeenCalledTimes(2);
|
|
179
179
|
expect(mockGetTransactions).toHaveBeenCalledTimes(2);
|
|
180
180
|
|
|
181
|
+
// if expectedType is "OUT", compute value with fees (i.e. delivered_amount + Fee)
|
|
182
|
+
const expectedValue = expectedType === "IN" ? deliveredAmount : deliveredAmount + fee;
|
|
181
183
|
// the order is reversed so that the result is always sorted by newest tx first element of the list
|
|
182
184
|
expect(results).toEqual([
|
|
183
185
|
{
|
|
@@ -194,7 +196,7 @@ describe("listOperations", () => {
|
|
|
194
196
|
},
|
|
195
197
|
},
|
|
196
198
|
type: expectedType,
|
|
197
|
-
value:
|
|
199
|
+
value: expectedValue,
|
|
198
200
|
senders: [opSender],
|
|
199
201
|
recipients: [opDestination],
|
|
200
202
|
details: {
|
|
@@ -222,7 +224,7 @@ describe("listOperations", () => {
|
|
|
222
224
|
},
|
|
223
225
|
},
|
|
224
226
|
type: expectedType,
|
|
225
|
-
value:
|
|
227
|
+
value: expectedValue,
|
|
226
228
|
senders: [opSender],
|
|
227
229
|
recipients: [opDestination],
|
|
228
230
|
details: {
|
|
@@ -245,7 +247,7 @@ describe("listOperations", () => {
|
|
|
245
247
|
},
|
|
246
248
|
},
|
|
247
249
|
type: expectedType,
|
|
248
|
-
value:
|
|
250
|
+
value: expectedValue,
|
|
249
251
|
senders: [opSender],
|
|
250
252
|
recipients: [opDestination],
|
|
251
253
|
details: {
|
package/src/api/index.ts
CHANGED
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
MemoInput,
|
|
28
28
|
} from "../logic";
|
|
29
29
|
import { ListOperationsOptions, XrpMapMemo } from "../types";
|
|
30
|
-
import { Order } from "../types/model";
|
|
31
30
|
|
|
32
31
|
export function createApi(config: XrpConfig): Api<XrpMapMemo> {
|
|
33
32
|
coinConfig.setCoinConfig(() => ({ ...config, status: { type: "active" } }));
|
|
@@ -116,13 +115,12 @@ type PaginationState = {
|
|
|
116
115
|
async function operationsFromHeight(
|
|
117
116
|
address: string,
|
|
118
117
|
minHeight: number,
|
|
119
|
-
order: Order = "asc",
|
|
120
118
|
): Promise<[Operation[], string]> {
|
|
121
119
|
async function fetchNextPage(state: PaginationState): Promise<PaginationState> {
|
|
122
120
|
const options: ListOperationsOptions = {
|
|
123
121
|
limit: state.pageSize,
|
|
124
122
|
minHeight: state.minHeight,
|
|
125
|
-
order:
|
|
123
|
+
order: "asc",
|
|
126
124
|
};
|
|
127
125
|
if (state.apiNextCursor) {
|
|
128
126
|
options.token = state.apiNextCursor;
|
|
@@ -164,9 +162,9 @@ async function operationsFromHeight(
|
|
|
164
162
|
|
|
165
163
|
// NOTE: double check
|
|
166
164
|
async function operations(address: string, pagination: Pagination): Promise<[Operation[], string]> {
|
|
167
|
-
const { minHeight, lastPagingToken
|
|
165
|
+
const { minHeight, lastPagingToken } = pagination;
|
|
168
166
|
if (minHeight) {
|
|
169
|
-
return await operationsFromHeight(address, minHeight
|
|
167
|
+
return await operationsFromHeight(address, minHeight);
|
|
170
168
|
}
|
|
171
169
|
const isInitSync = lastPagingToken === "";
|
|
172
170
|
|
|
@@ -174,5 +172,5 @@ async function operations(address: string, pagination: Pagination): Promise<[Ope
|
|
|
174
172
|
minHeight: isInitSync ? 0 : parseInt(lastPagingToken || "0", 10),
|
|
175
173
|
};
|
|
176
174
|
// TODO token must be implemented properly (waiting ack from the design document)
|
|
177
|
-
return await operationsFromHeight(address, newPagination.minHeight
|
|
175
|
+
return await operationsFromHeight(address, newPagination.minHeight);
|
|
178
176
|
}
|
|
@@ -41,7 +41,7 @@ describe("listOperations", () => {
|
|
|
41
41
|
// Given
|
|
42
42
|
mockNetworkGetTransactions.mockResolvedValue(mockNetworkTxs([]));
|
|
43
43
|
// When
|
|
44
|
-
const [results, token] = await listOperations("any address", { minHeight: 0
|
|
44
|
+
const [results, token] = await listOperations("any address", { minHeight: 0 });
|
|
45
45
|
// Then
|
|
46
46
|
expect(mockGetServerInfos).toHaveBeenCalledTimes(1);
|
|
47
47
|
expect(mockNetworkGetTransactions).toHaveBeenCalledTimes(1);
|
|
@@ -215,7 +215,9 @@ describe("listOperations", () => {
|
|
|
215
215
|
// Then
|
|
216
216
|
expect(mockGetServerInfos).toHaveBeenCalledTimes(1);
|
|
217
217
|
expect(mockNetworkGetTransactions).toHaveBeenCalledTimes(1);
|
|
218
|
-
|
|
218
|
+
// if expectedType is "OUT", compute value with fees (i.e. delivered_amount + Fee)
|
|
219
|
+
const expectedValue =
|
|
220
|
+
expectedType === "IN" ? BigInt(deliveredAmount) : BigInt(deliveredAmount + fees);
|
|
219
221
|
expect(results).toEqual([
|
|
220
222
|
{
|
|
221
223
|
id: "HASH_VALUE",
|
|
@@ -120,12 +120,17 @@ const convertToCoreOperation =
|
|
|
120
120
|
} = operation;
|
|
121
121
|
|
|
122
122
|
const type = Account === address ? "OUT" : "IN";
|
|
123
|
-
|
|
123
|
+
let value =
|
|
124
124
|
delivered_amount && typeof delivered_amount === "string"
|
|
125
125
|
? BigInt(delivered_amount)
|
|
126
126
|
: BigInt(0);
|
|
127
127
|
|
|
128
128
|
const fees = BigInt(Fee);
|
|
129
|
+
if (type === "OUT") {
|
|
130
|
+
if (!Number.isNaN(fees)) {
|
|
131
|
+
value = value + fees;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
129
134
|
|
|
130
135
|
const toEpochDate = (RIPPLE_EPOCH + date) * 1000;
|
|
131
136
|
|
package/src/types/model.ts
CHANGED