@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.
@@ -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 (testnet)", () => {
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, order: "asc" });
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 a balance", async () => {
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).toBeGreaterThanOrEqual(BigInt(0));
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", () => {
@@ -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, order: "asc" });
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, order: "asc" });
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, order: "asc" });
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: deliveredAmount,
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: deliveredAmount,
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: deliveredAmount,
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: 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, order } = pagination;
165
+ const { minHeight, lastPagingToken } = pagination;
168
166
  if (minHeight) {
169
- return await operationsFromHeight(address, minHeight, order);
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, order);
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, order: "asc" });
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
- const expectedValue = BigInt(deliveredAmount);
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
- const value =
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
 
@@ -21,7 +21,7 @@ export type XrpMemoValueMap = {
21
21
  };
22
22
  export type XrpMapMemo = TypedMapMemo<XrpMemoValueMap>;
23
23
 
24
- export type Order = "asc" | "desc";
24
+ type Order = "asc" | "desc";
25
25
 
26
26
  export type ListOperationsOptions = {
27
27
  // pagination: