@ledgerhq/coin-xrp 7.2.1-nightly.0 → 7.3.0-nightly.2
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 +18 -0
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.integ.test.js +158 -5
- 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 +6 -8
- package/lib/api/index.test.js.map +1 -1
- package/lib/logic/listOperations.js +1 -6
- package/lib/logic/listOperations.js.map +1 -1
- package/lib/logic/listOperations.test.js +2 -3
- package/lib/logic/listOperations.test.js.map +1 -1
- package/lib/types/model.d.ts +1 -2
- 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 +158 -5
- 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 +6 -8
- package/lib-es/api/index.test.js.map +1 -1
- package/lib-es/logic/listOperations.js +1 -6
- package/lib-es/logic/listOperations.js.map +1 -1
- package/lib-es/logic/listOperations.test.js +2 -3
- package/lib-es/logic/listOperations.test.js.map +1 -1
- package/lib-es/types/model.d.ts +1 -2
- package/lib-es/types/model.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/api/index.integ.test.ts +184 -5
- package/src/api/index.test.ts +6 -8
- package/src/api/index.ts +6 -4
- package/src/logic/listOperations.test.ts +2 -4
- package/src/logic/listOperations.ts +1 -6
- package/src/types/model.ts +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { decode } from "ripple-binary-codec";
|
|
2
2
|
import { createApi } from ".";
|
|
3
|
+
import { Operation } from "@ledgerhq/coin-framework/api/types";
|
|
3
4
|
//import { decode, encodeForSigning } from "ripple-binary-codec";
|
|
4
5
|
//import { sign } from "ripple-keypairs";
|
|
5
6
|
|
|
6
|
-
describe("Xrp Api", () => {
|
|
7
|
+
describe("Xrp Api (testnet)", () => {
|
|
7
8
|
const SENDER = "rh1HPuRVsYYvThxG2Bs1MfjmrVC73S16Fb";
|
|
8
9
|
const api = createApi({ node: "https://s.altnet.rippletest.net:51234" });
|
|
9
10
|
|
|
@@ -33,7 +34,7 @@ describe("Xrp Api", () => {
|
|
|
33
34
|
describe("listOperations", () => {
|
|
34
35
|
it.skip("returns a list regarding address parameter", async () => {
|
|
35
36
|
// When
|
|
36
|
-
const [tx, _] = await api.listOperations(SENDER, { minHeight: 200 });
|
|
37
|
+
const [tx, _] = await api.listOperations(SENDER, { minHeight: 200, order: "asc" });
|
|
37
38
|
|
|
38
39
|
// https://blockexplorer.one/xrp/testnet/address/rh1HPuRVsYYvThxG2Bs1MfjmrVC73S16Fb
|
|
39
40
|
// as of 2025-03-18, the address has 287 transactions
|
|
@@ -51,7 +52,10 @@ describe("Xrp Api", () => {
|
|
|
51
52
|
const SENDER_WITH_TRANSACTIONS = "rUxSkt6hQpWxXQwTNRUCYYRQ7BC2yRA3F8";
|
|
52
53
|
|
|
53
54
|
// When
|
|
54
|
-
const [ops, _] = await api.listOperations(SENDER_WITH_TRANSACTIONS, {
|
|
55
|
+
const [ops, _] = await api.listOperations(SENDER_WITH_TRANSACTIONS, {
|
|
56
|
+
minHeight: 0,
|
|
57
|
+
order: "asc",
|
|
58
|
+
});
|
|
55
59
|
// Then
|
|
56
60
|
const checkSet = new Set(ops.map(elt => elt.tx.hash));
|
|
57
61
|
expect(checkSet.size).toEqual(ops.length);
|
|
@@ -64,6 +68,17 @@ describe("Xrp Api", () => {
|
|
|
64
68
|
// so here we are checking that this limit is bypassed
|
|
65
69
|
expect(ops.length).toBeGreaterThan(200);
|
|
66
70
|
});
|
|
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
|
+
});
|
|
67
82
|
});
|
|
68
83
|
|
|
69
84
|
describe("lastBlock", () => {
|
|
@@ -82,13 +97,13 @@ describe("Xrp Api", () => {
|
|
|
82
97
|
// Account with no transaction (at the time of this writing)
|
|
83
98
|
const SENDER_WITH_NO_TRANSACTION = "rKtXXTVno77jhu6tto1MAXjepyuaKaLcqB";
|
|
84
99
|
|
|
85
|
-
it("returns
|
|
100
|
+
it("returns a balance", async () => {
|
|
86
101
|
// When
|
|
87
102
|
const result = await api.getBalance(SENDER);
|
|
88
103
|
|
|
89
104
|
// Then
|
|
90
105
|
expect(result[0].asset).toEqual({ type: "native" });
|
|
91
|
-
expect(result[0].value).
|
|
106
|
+
expect(result[0].value).toBeGreaterThanOrEqual(BigInt(0));
|
|
92
107
|
});
|
|
93
108
|
|
|
94
109
|
it("returns 0 when address has no transaction", async () => {
|
|
@@ -162,6 +177,170 @@ describe("Xrp Api", () => {
|
|
|
162
177
|
});
|
|
163
178
|
});
|
|
164
179
|
|
|
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
|
+
|
|
165
344
|
// 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`.
|
|
166
345
|
// The value hardcoded here depends on the value filled in the `.env` file.
|
|
167
346
|
/*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, order: "asc" });
|
|
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, order: "asc" });
|
|
126
126
|
|
|
127
127
|
// called 2 times because the second time there is no marker
|
|
128
128
|
expect(mockGetServerInfos).toHaveBeenCalledTimes(2);
|
|
@@ -171,15 +171,13 @@ 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, order: "asc" });
|
|
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;
|
|
183
181
|
// the order is reversed so that the result is always sorted by newest tx first element of the list
|
|
184
182
|
expect(results).toEqual([
|
|
185
183
|
{
|
|
@@ -196,7 +194,7 @@ describe("listOperations", () => {
|
|
|
196
194
|
},
|
|
197
195
|
},
|
|
198
196
|
type: expectedType,
|
|
199
|
-
value:
|
|
197
|
+
value: deliveredAmount,
|
|
200
198
|
senders: [opSender],
|
|
201
199
|
recipients: [opDestination],
|
|
202
200
|
details: {
|
|
@@ -224,7 +222,7 @@ describe("listOperations", () => {
|
|
|
224
222
|
},
|
|
225
223
|
},
|
|
226
224
|
type: expectedType,
|
|
227
|
-
value:
|
|
225
|
+
value: deliveredAmount,
|
|
228
226
|
senders: [opSender],
|
|
229
227
|
recipients: [opDestination],
|
|
230
228
|
details: {
|
|
@@ -247,7 +245,7 @@ describe("listOperations", () => {
|
|
|
247
245
|
},
|
|
248
246
|
},
|
|
249
247
|
type: expectedType,
|
|
250
|
-
value:
|
|
248
|
+
value: deliveredAmount,
|
|
251
249
|
senders: [opSender],
|
|
252
250
|
recipients: [opDestination],
|
|
253
251
|
details: {
|
package/src/api/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
MemoInput,
|
|
28
28
|
} from "../logic";
|
|
29
29
|
import { ListOperationsOptions, XrpMapMemo } from "../types";
|
|
30
|
+
import { Order } from "../types/model";
|
|
30
31
|
|
|
31
32
|
export function createApi(config: XrpConfig): Api<XrpMapMemo> {
|
|
32
33
|
coinConfig.setCoinConfig(() => ({ ...config, status: { type: "active" } }));
|
|
@@ -115,12 +116,13 @@ type PaginationState = {
|
|
|
115
116
|
async function operationsFromHeight(
|
|
116
117
|
address: string,
|
|
117
118
|
minHeight: number,
|
|
119
|
+
order: Order = "asc",
|
|
118
120
|
): Promise<[Operation[], string]> {
|
|
119
121
|
async function fetchNextPage(state: PaginationState): Promise<PaginationState> {
|
|
120
122
|
const options: ListOperationsOptions = {
|
|
121
123
|
limit: state.pageSize,
|
|
122
124
|
minHeight: state.minHeight,
|
|
123
|
-
order:
|
|
125
|
+
order: order,
|
|
124
126
|
};
|
|
125
127
|
if (state.apiNextCursor) {
|
|
126
128
|
options.token = state.apiNextCursor;
|
|
@@ -162,9 +164,9 @@ async function operationsFromHeight(
|
|
|
162
164
|
|
|
163
165
|
// NOTE: double check
|
|
164
166
|
async function operations(address: string, pagination: Pagination): Promise<[Operation[], string]> {
|
|
165
|
-
const { minHeight, lastPagingToken } = pagination;
|
|
167
|
+
const { minHeight, lastPagingToken, order } = pagination;
|
|
166
168
|
if (minHeight) {
|
|
167
|
-
return await operationsFromHeight(address, minHeight);
|
|
169
|
+
return await operationsFromHeight(address, minHeight, order);
|
|
168
170
|
}
|
|
169
171
|
const isInitSync = lastPagingToken === "";
|
|
170
172
|
|
|
@@ -172,5 +174,5 @@ async function operations(address: string, pagination: Pagination): Promise<[Ope
|
|
|
172
174
|
minHeight: isInitSync ? 0 : parseInt(lastPagingToken || "0", 10),
|
|
173
175
|
};
|
|
174
176
|
// TODO token must be implemented properly (waiting ack from the design document)
|
|
175
|
-
return await operationsFromHeight(address, newPagination.minHeight);
|
|
177
|
+
return await operationsFromHeight(address, newPagination.minHeight, order);
|
|
176
178
|
}
|
|
@@ -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, order: "asc" });
|
|
45
45
|
// Then
|
|
46
46
|
expect(mockGetServerInfos).toHaveBeenCalledTimes(1);
|
|
47
47
|
expect(mockNetworkGetTransactions).toHaveBeenCalledTimes(1);
|
|
@@ -215,9 +215,7 @@ describe("listOperations", () => {
|
|
|
215
215
|
// Then
|
|
216
216
|
expect(mockGetServerInfos).toHaveBeenCalledTimes(1);
|
|
217
217
|
expect(mockNetworkGetTransactions).toHaveBeenCalledTimes(1);
|
|
218
|
-
|
|
219
|
-
const expectedValue =
|
|
220
|
-
expectedType === "IN" ? BigInt(deliveredAmount) : BigInt(deliveredAmount + fees);
|
|
218
|
+
const expectedValue = BigInt(deliveredAmount);
|
|
221
219
|
expect(results).toEqual([
|
|
222
220
|
{
|
|
223
221
|
id: "HASH_VALUE",
|
|
@@ -120,17 +120,12 @@ const convertToCoreOperation =
|
|
|
120
120
|
} = operation;
|
|
121
121
|
|
|
122
122
|
const type = Account === address ? "OUT" : "IN";
|
|
123
|
-
|
|
123
|
+
const 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
|
-
}
|
|
134
129
|
|
|
135
130
|
const toEpochDate = (RIPPLE_EPOCH + date) * 1000;
|
|
136
131
|
|
package/src/types/model.ts
CHANGED