@ledgerhq/coin-xrp 6.1.3-nightly.1 → 6.2.0-next.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.
Files changed (191) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.unimportedrc.json +3 -1
  3. package/CHANGELOG.md +45 -8
  4. package/jest.config.js +2 -5
  5. package/lib/api/index.d.ts +2 -11
  6. package/lib/api/index.d.ts.map +1 -1
  7. package/lib/api/index.integ.test.js +24 -7
  8. package/lib/api/index.integ.test.js.map +1 -1
  9. package/lib/api/index.js +15 -5
  10. package/lib/api/index.js.map +1 -1
  11. package/lib/api/index.test.js +18 -9
  12. package/lib/api/index.test.js.map +1 -1
  13. package/lib/index.d.ts +0 -1
  14. package/lib/index.d.ts.map +1 -1
  15. package/lib/index.js +0 -3
  16. package/lib/index.js.map +1 -1
  17. package/lib/logic/combine.d.ts.map +1 -1
  18. package/lib/logic/combine.js +8 -3
  19. package/lib/logic/combine.js.map +1 -1
  20. package/lib/logic/getBalance.d.ts.map +1 -1
  21. package/lib/logic/getBalance.js +13 -1
  22. package/lib/logic/getBalance.js.map +1 -1
  23. package/lib/logic/getBalance.test.js +14 -1
  24. package/lib/logic/getBalance.test.js.map +1 -1
  25. package/lib/logic/getTransactionStatus.d.ts +3 -0
  26. package/lib/logic/getTransactionStatus.d.ts.map +1 -0
  27. package/lib/{bridge → logic}/getTransactionStatus.js +15 -19
  28. package/lib/logic/getTransactionStatus.js.map +1 -0
  29. package/lib/logic/getTransactionStatus.test.d.ts +2 -0
  30. package/lib/logic/getTransactionStatus.test.d.ts.map +1 -0
  31. package/lib/logic/getTransactionStatus.test.js +184 -0
  32. package/lib/logic/getTransactionStatus.test.js.map +1 -0
  33. package/lib/logic/index.d.ts +2 -1
  34. package/lib/logic/index.d.ts.map +1 -1
  35. package/lib/logic/index.js +3 -2
  36. package/lib/logic/index.js.map +1 -1
  37. package/lib/logic/utils.d.ts +0 -1
  38. package/lib/logic/utils.d.ts.map +1 -1
  39. package/lib/logic/utils.js +14 -10
  40. package/lib/logic/utils.js.map +1 -1
  41. package/lib/test/bridgeDatasetTest.d.ts.map +1 -1
  42. package/lib/test/bridgeDatasetTest.js +7 -7
  43. package/lib/test/bridgeDatasetTest.js.map +1 -1
  44. package/lib/{bridge/transaction.d.ts → transaction.d.ts} +1 -1
  45. package/lib/transaction.d.ts.map +1 -0
  46. package/lib/transaction.js.map +1 -0
  47. package/lib/types/model.d.ts +7 -0
  48. package/lib/types/model.d.ts.map +1 -1
  49. package/lib-es/api/index.d.ts +2 -11
  50. package/lib-es/api/index.d.ts.map +1 -1
  51. package/lib-es/api/index.integ.test.js +24 -7
  52. package/lib-es/api/index.integ.test.js.map +1 -1
  53. package/lib-es/api/index.js +16 -6
  54. package/lib-es/api/index.js.map +1 -1
  55. package/lib-es/api/index.test.js +18 -9
  56. package/lib-es/api/index.test.js.map +1 -1
  57. package/lib-es/index.d.ts +0 -1
  58. package/lib-es/index.d.ts.map +1 -1
  59. package/lib-es/index.js +0 -1
  60. package/lib-es/index.js.map +1 -1
  61. package/lib-es/logic/combine.d.ts.map +1 -1
  62. package/lib-es/logic/combine.js +8 -3
  63. package/lib-es/logic/combine.js.map +1 -1
  64. package/lib-es/logic/getBalance.d.ts.map +1 -1
  65. package/lib-es/logic/getBalance.js +14 -2
  66. package/lib-es/logic/getBalance.js.map +1 -1
  67. package/lib-es/logic/getBalance.test.js +14 -1
  68. package/lib-es/logic/getBalance.test.js.map +1 -1
  69. package/lib-es/logic/getTransactionStatus.d.ts +3 -0
  70. package/lib-es/logic/getTransactionStatus.d.ts.map +1 -0
  71. package/lib-es/{bridge → logic}/getTransactionStatus.js +13 -14
  72. package/lib-es/logic/getTransactionStatus.js.map +1 -0
  73. package/lib-es/logic/getTransactionStatus.test.d.ts +2 -0
  74. package/lib-es/logic/getTransactionStatus.test.d.ts.map +1 -0
  75. package/lib-es/logic/getTransactionStatus.test.js +159 -0
  76. package/lib-es/logic/getTransactionStatus.test.js.map +1 -0
  77. package/lib-es/logic/index.d.ts +2 -1
  78. package/lib-es/logic/index.d.ts.map +1 -1
  79. package/lib-es/logic/index.js +2 -1
  80. package/lib-es/logic/index.js.map +1 -1
  81. package/lib-es/logic/utils.d.ts +0 -1
  82. package/lib-es/logic/utils.d.ts.map +1 -1
  83. package/lib-es/logic/utils.js +13 -8
  84. package/lib-es/logic/utils.js.map +1 -1
  85. package/lib-es/test/bridgeDatasetTest.d.ts.map +1 -1
  86. package/lib-es/test/bridgeDatasetTest.js +7 -7
  87. package/lib-es/test/bridgeDatasetTest.js.map +1 -1
  88. package/lib-es/{bridge/transaction.d.ts → transaction.d.ts} +1 -1
  89. package/lib-es/transaction.d.ts.map +1 -0
  90. package/lib-es/transaction.js.map +1 -0
  91. package/lib-es/types/model.d.ts +7 -0
  92. package/lib-es/types/model.d.ts.map +1 -1
  93. package/package.json +10 -11
  94. package/src/api/index.integ.test.ts +24 -8
  95. package/src/api/index.test.ts +23 -22
  96. package/src/api/index.ts +28 -19
  97. package/src/index.ts +0 -1
  98. package/src/logic/combine.ts +10 -3
  99. package/src/logic/getBalance.test.ts +14 -1
  100. package/src/logic/getBalance.ts +18 -2
  101. package/src/logic/getTransactionStatus.test.ts +215 -0
  102. package/src/{bridge → logic}/getTransactionStatus.ts +18 -21
  103. package/src/logic/index.ts +2 -6
  104. package/src/logic/utils.ts +24 -8
  105. package/src/test/bridgeDatasetTest.ts +8 -7
  106. package/src/{bridge/transaction.ts → transaction.ts} +1 -1
  107. package/src/types/model.ts +11 -0
  108. package/lib/bridge/broadcast.d.ts +0 -4
  109. package/lib/bridge/broadcast.d.ts.map +0 -1
  110. package/lib/bridge/broadcast.js +0 -11
  111. package/lib/bridge/broadcast.js.map +0 -1
  112. package/lib/bridge/createTransaction.d.ts +0 -4
  113. package/lib/bridge/createTransaction.d.ts.map +0 -1
  114. package/lib/bridge/createTransaction.js +0 -18
  115. package/lib/bridge/createTransaction.js.map +0 -1
  116. package/lib/bridge/estimateMaxSpendable.d.ts +0 -4
  117. package/lib/bridge/estimateMaxSpendable.d.ts.map +0 -1
  118. package/lib/bridge/estimateMaxSpendable.js +0 -26
  119. package/lib/bridge/estimateMaxSpendable.js.map +0 -1
  120. package/lib/bridge/getTransactionStatus.d.ts +0 -4
  121. package/lib/bridge/getTransactionStatus.d.ts.map +0 -1
  122. package/lib/bridge/getTransactionStatus.js.map +0 -1
  123. package/lib/bridge/index.d.ts +0 -11
  124. package/lib/bridge/index.d.ts.map +0 -1
  125. package/lib/bridge/index.js +0 -47
  126. package/lib/bridge/index.js.map +0 -1
  127. package/lib/bridge/prepareTransaction.d.ts +0 -4
  128. package/lib/bridge/prepareTransaction.d.ts.map +0 -1
  129. package/lib/bridge/prepareTransaction.js +0 -14
  130. package/lib/bridge/prepareTransaction.js.map +0 -1
  131. package/lib/bridge/signOperation.d.ts +0 -5
  132. package/lib/bridge/signOperation.d.ts.map +0 -1
  133. package/lib/bridge/signOperation.js +0 -76
  134. package/lib/bridge/signOperation.js.map +0 -1
  135. package/lib/bridge/synchronization.d.ts +0 -3
  136. package/lib/bridge/synchronization.d.ts.map +0 -1
  137. package/lib/bridge/synchronization.js +0 -85
  138. package/lib/bridge/synchronization.js.map +0 -1
  139. package/lib/bridge/synchronization.test.d.ts +0 -2
  140. package/lib/bridge/synchronization.test.d.ts.map +0 -1
  141. package/lib/bridge/synchronization.test.js +0 -140
  142. package/lib/bridge/synchronization.test.js.map +0 -1
  143. package/lib/bridge/transaction.d.ts.map +0 -1
  144. package/lib/bridge/transaction.js.map +0 -1
  145. package/lib-es/bridge/broadcast.d.ts +0 -4
  146. package/lib-es/bridge/broadcast.d.ts.map +0 -1
  147. package/lib-es/bridge/broadcast.js +0 -7
  148. package/lib-es/bridge/broadcast.js.map +0 -1
  149. package/lib-es/bridge/createTransaction.d.ts +0 -4
  150. package/lib-es/bridge/createTransaction.d.ts.map +0 -1
  151. package/lib-es/bridge/createTransaction.js +0 -11
  152. package/lib-es/bridge/createTransaction.js.map +0 -1
  153. package/lib-es/bridge/estimateMaxSpendable.d.ts +0 -4
  154. package/lib-es/bridge/estimateMaxSpendable.d.ts.map +0 -1
  155. package/lib-es/bridge/estimateMaxSpendable.js +0 -19
  156. package/lib-es/bridge/estimateMaxSpendable.js.map +0 -1
  157. package/lib-es/bridge/getTransactionStatus.d.ts +0 -4
  158. package/lib-es/bridge/getTransactionStatus.d.ts.map +0 -1
  159. package/lib-es/bridge/getTransactionStatus.js.map +0 -1
  160. package/lib-es/bridge/index.d.ts +0 -11
  161. package/lib-es/bridge/index.d.ts.map +0 -1
  162. package/lib-es/bridge/index.js +0 -41
  163. package/lib-es/bridge/index.js.map +0 -1
  164. package/lib-es/bridge/prepareTransaction.d.ts +0 -4
  165. package/lib-es/bridge/prepareTransaction.d.ts.map +0 -1
  166. package/lib-es/bridge/prepareTransaction.js +0 -10
  167. package/lib-es/bridge/prepareTransaction.js.map +0 -1
  168. package/lib-es/bridge/signOperation.d.ts +0 -5
  169. package/lib-es/bridge/signOperation.d.ts.map +0 -1
  170. package/lib-es/bridge/signOperation.js +0 -72
  171. package/lib-es/bridge/signOperation.js.map +0 -1
  172. package/lib-es/bridge/synchronization.d.ts +0 -3
  173. package/lib-es/bridge/synchronization.d.ts.map +0 -1
  174. package/lib-es/bridge/synchronization.js +0 -78
  175. package/lib-es/bridge/synchronization.js.map +0 -1
  176. package/lib-es/bridge/synchronization.test.d.ts +0 -2
  177. package/lib-es/bridge/synchronization.test.d.ts.map +0 -1
  178. package/lib-es/bridge/synchronization.test.js +0 -135
  179. package/lib-es/bridge/synchronization.test.js.map +0 -1
  180. package/lib-es/bridge/transaction.d.ts.map +0 -1
  181. package/lib-es/bridge/transaction.js.map +0 -1
  182. package/src/bridge/broadcast.ts +0 -11
  183. package/src/bridge/createTransaction.ts +0 -13
  184. package/src/bridge/estimateMaxSpendable.ts +0 -25
  185. package/src/bridge/index.ts +0 -59
  186. package/src/bridge/prepareTransaction.ts +0 -18
  187. package/src/bridge/signOperation.ts +0 -100
  188. package/src/bridge/synchronization.test.ts +0 -153
  189. package/src/bridge/synchronization.ts +0 -108
  190. /package/lib/{bridge/transaction.js → transaction.js} +0 -0
  191. /package/lib-es/{bridge/transaction.js → transaction.js} +0 -0
package/src/api/index.ts CHANGED
@@ -16,11 +16,11 @@ import {
16
16
  getNextValidSequence,
17
17
  lastBlock,
18
18
  listOperations,
19
- MemoInput,
19
+ getTransactionStatus,
20
20
  } from "../logic";
21
- import { ListOperationsOptions, XrpAsset } from "../types";
21
+ import { ListOperationsOptions, XrpAsset, XrpMapMemo } from "../types";
22
22
 
23
- export function createApi(config: XrpConfig): Api<XrpAsset, TransactionIntentExtra, XrpSender> {
23
+ export function createApi(config: XrpConfig): Api<XrpAsset, XrpMapMemo> {
24
24
  coinConfig.setCoinConfig(() => ({ ...config, status: { type: "active" } }));
25
25
 
26
26
  return {
@@ -31,36 +31,45 @@ export function createApi(config: XrpConfig): Api<XrpAsset, TransactionIntentExt
31
31
  getBalance,
32
32
  lastBlock,
33
33
  listOperations: operations,
34
+ validateIntent: getTransactionStatus,
34
35
  };
35
36
  }
36
37
 
37
- export type TransactionIntentExtra = {
38
- destinationTag?: number | null | undefined;
39
- memos?: MemoInput[];
40
- };
41
-
42
- export type XrpSender = {
43
- address: string;
44
- publicKey?: string;
45
- };
46
-
47
38
  async function craft(
48
- transactionIntent: TransactionIntent<XrpAsset, TransactionIntentExtra, XrpSender>,
39
+ transactionIntent: TransactionIntent<XrpAsset, XrpMapMemo>,
49
40
  customFees?: bigint,
50
41
  ): Promise<string> {
51
- const nextSequenceNumber = await getNextValidSequence(transactionIntent.sender.address);
42
+ const nextSequenceNumber = await getNextValidSequence(transactionIntent.sender);
52
43
  const estimatedFees = customFees !== undefined ? customFees : (await estimateFees()).fee;
44
+
45
+ const memosMap =
46
+ transactionIntent.memo?.type === "map" ? transactionIntent.memo.memos : new Map();
47
+
48
+ const destinationTagValue = memosMap.get("destinationTag");
49
+ const destinationTag =
50
+ typeof destinationTagValue === "string" ? Number(destinationTagValue) : undefined;
51
+
52
+ const memoStrings = memosMap.get("memos") as string[] | undefined;
53
+
54
+ let memoEntries: { type: string; data: string }[] = [];
55
+
56
+ if (Array.isArray(memoStrings) && memoStrings.length > 0) {
57
+ memoEntries = memoStrings.map(value => ({ type: "memo", data: value }));
58
+ }
59
+
53
60
  const tx = await craftTransaction(
54
- { address: transactionIntent.sender.address, nextSequenceNumber },
61
+ { address: transactionIntent.sender, nextSequenceNumber },
55
62
  {
56
63
  recipient: transactionIntent.recipient,
57
64
  amount: transactionIntent.amount,
58
65
  fee: estimatedFees,
59
- destinationTag: transactionIntent.destinationTag,
60
- memos: transactionIntent.memos,
66
+ destinationTag,
67
+ // NOTE: double check before/after here
68
+ memos: memoEntries,
61
69
  },
62
- transactionIntent.sender.publicKey,
70
+ transactionIntent.senderPublicKey,
63
71
  );
72
+
64
73
  return tx.serializedTransaction;
65
74
  }
66
75
 
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
1
  export * from "./types";
2
2
 
3
- export { createBridges } from "./bridge/index";
4
3
  export type { XrpCoinConfig } from "./config";
@@ -8,11 +8,18 @@ type XRPTransaction = JsonObject & {
8
8
 
9
9
  export function combine(transaction: string, signature: string, publicKey?: string): string {
10
10
  const xrplTransaction: JsonObject = decode(transaction);
11
- let transactionWithSignature: XRPTransaction = { ...xrplTransaction, TxnSignature: signature };
11
+
12
+ let transactionWithSignature: XRPTransaction = { ...xrplTransaction } as any;
12
13
 
13
14
  if (publicKey) {
14
- transactionWithSignature = { ...transactionWithSignature, SigningPubKey: publicKey };
15
+ transactionWithSignature = {
16
+ ...transactionWithSignature,
17
+ SigningPubKey: publicKey,
18
+ };
15
19
  }
16
20
 
17
- return encode(transactionWithSignature);
21
+ transactionWithSignature = { ...transactionWithSignature, TxnSignature: signature };
22
+
23
+ const encoded = encode(transactionWithSignature).toUpperCase();
24
+ return encoded;
18
25
  }
@@ -2,21 +2,33 @@ import { faker } from "@faker-js/faker";
2
2
  import { getBalance } from "./getBalance";
3
3
 
4
4
  const mockGetAccountInfo = jest.fn();
5
+ const mockGetServerInfos = jest.fn();
5
6
  jest.mock("../network", () => ({
6
7
  getAccountInfo: (address: string) => mockGetAccountInfo(address),
8
+ getServerInfos: () => mockGetServerInfos(),
7
9
  }));
8
10
 
9
11
  describe("getBalance", () => {
10
12
  afterEach(() => {
11
13
  mockGetAccountInfo.mockClear();
14
+ mockGetServerInfos.mockClear();
12
15
  });
13
16
 
14
17
  it("returns the balance from Explorer", async () => {
18
+ mockGetServerInfos.mockResolvedValue({
19
+ info: {
20
+ validated_ledger: {
21
+ reserve_base_xrp: 23,
22
+ reserve_inc_xrp: 5,
23
+ },
24
+ },
25
+ });
15
26
  // Given
16
27
  const balance = faker.number.bigInt(100_000_000);
17
28
  const address = "ACCOUNT_ADDRESS";
18
29
  mockGetAccountInfo.mockResolvedValue({
19
30
  balance,
31
+ ownerCount: 0,
20
32
  });
21
33
 
22
34
  // When
@@ -24,7 +36,8 @@ describe("getBalance", () => {
24
36
 
25
37
  // Then
26
38
  expect(mockGetAccountInfo).toHaveBeenCalledTimes(1);
39
+ expect(mockGetServerInfos).toHaveBeenCalledTimes(1);
27
40
  expect(mockGetAccountInfo.mock.lastCall[0]).toEqual(address);
28
- expect(result).toEqual([{ value: balance, asset: { type: "native" } }]);
41
+ expect(result).toEqual([{ value: balance, asset: { type: "native" }, locked: 23000000n }]);
29
42
  });
30
43
  });
@@ -1,8 +1,24 @@
1
1
  import { Balance } from "@ledgerhq/coin-framework/api/types";
2
- import { getAccountInfo } from "../network";
2
+ import { getAccountInfo, getServerInfos } from "../network";
3
3
  import { XrpAsset } from "../types";
4
+ import { parseAPIValue } from "./common";
4
5
 
5
6
  export async function getBalance(address: string): Promise<Balance<XrpAsset>[]> {
6
7
  const accountInfo = await getAccountInfo(address);
7
- return [{ value: BigInt(accountInfo.balance), asset: { type: "native" } }];
8
+ const serverInfo = await getServerInfos();
9
+
10
+ const reserveMinXRP = parseAPIValue(serverInfo.info.validated_ledger.reserve_base_xrp.toString());
11
+ const reservePerTrustline = parseAPIValue(
12
+ serverInfo.info.validated_ledger.reserve_inc_xrp.toString(),
13
+ );
14
+ const trustlines = accountInfo.ownerCount;
15
+
16
+ const locked = reserveMinXRP.plus(reservePerTrustline.times(trustlines));
17
+ return [
18
+ {
19
+ value: BigInt(accountInfo.balance),
20
+ asset: { type: "native" },
21
+ locked: BigInt(locked.toString()),
22
+ },
23
+ ];
8
24
  }
@@ -0,0 +1,215 @@
1
+ import { getTransactionStatus } from "./getTransactionStatus";
2
+ import * as logic from "./index";
3
+
4
+ const mockGetServerInfos = jest.fn();
5
+ const mockCachedRecipientIsNew = jest.fn();
6
+
7
+ jest.mock("../network", () => ({
8
+ getServerInfos: () => mockGetServerInfos(),
9
+ }));
10
+
11
+ jest.spyOn(logic, "cachedRecipientIsNew").mockImplementation(addr => {
12
+ if (addr === RECIPIENT_NEW) {
13
+ return Promise.resolve(true);
14
+ }
15
+ return Promise.resolve(false);
16
+ });
17
+
18
+ const reserveBase = 10_000_000n; // 10 XRP (drops)
19
+
20
+ const SENDER = "rPSCfmnX3t9jQJG5RNcZtSaP5UhExZDue4";
21
+ const RECIPIENT = "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe";
22
+ const RECIPIENT_NEW = "rDKsbvy9uaNpPtvVFraJyNGfjvTw8xivgK";
23
+
24
+ const account = {
25
+ address: SENDER,
26
+ balance: 50_000_000n,
27
+ currencyUnit: {
28
+ code: "XRP",
29
+ magnitude: 6,
30
+ name: "XRP",
31
+ symbol: "XRP",
32
+ },
33
+ currencyName: "XRP",
34
+ };
35
+
36
+ describe("getTransactionStatus", () => {
37
+ afterEach(() => {
38
+ mockGetServerInfos.mockReset();
39
+ mockCachedRecipientIsNew.mockReset();
40
+ });
41
+
42
+ it("returns no errors on valid transaction", async () => {
43
+ mockGetServerInfos.mockResolvedValue({
44
+ info: {
45
+ validated_ledger: {
46
+ reserve_base_xrp: reserveBase / 1_000_000n, // XRP value, not drops
47
+ },
48
+ },
49
+ });
50
+
51
+ mockCachedRecipientIsNew.mockResolvedValue(false);
52
+
53
+ const result = await getTransactionStatus(
54
+ account as any,
55
+ {
56
+ amount: 20_000_000n,
57
+ fee: 10_000n,
58
+ recipient: RECIPIENT,
59
+ } as any,
60
+ );
61
+
62
+ expect(result.errors).toEqual({});
63
+ expect(result.warnings).toEqual({});
64
+ expect(result.totalSpent).toBe(20_010_000n);
65
+ });
66
+
67
+ it("throws FeeTooHigh warning when fee is >10% of amount", async () => {
68
+ mockGetServerInfos.mockResolvedValue({
69
+ info: {
70
+ validated_ledger: {
71
+ reserve_base_xrp: reserveBase / 1_000_000n,
72
+ },
73
+ },
74
+ });
75
+
76
+ const result = await getTransactionStatus(
77
+ account as any,
78
+ {
79
+ amount: 1_000_000n,
80
+ fee: 200_000n, // 20%
81
+ recipient: RECIPIENT,
82
+ } as any,
83
+ );
84
+
85
+ expect(result.warnings.feeTooHigh).toBeInstanceOf(Error);
86
+ expect(result.errors).toEqual({});
87
+ });
88
+
89
+ it("errors when fee is missing", async () => {
90
+ mockGetServerInfos.mockResolvedValue({
91
+ info: {
92
+ validated_ledger: {
93
+ reserve_base_xrp: reserveBase / 1_000_000n,
94
+ },
95
+ },
96
+ });
97
+
98
+ const result = await getTransactionStatus(
99
+ account as any,
100
+ {
101
+ amount: 10_000_000n,
102
+ recipient: RECIPIENT,
103
+ } as any,
104
+ );
105
+
106
+ expect(result.errors.fee?.name).toBe("FeeNotLoaded");
107
+ });
108
+
109
+ it("errors if recipient is same as sender", async () => {
110
+ mockGetServerInfos.mockResolvedValue({
111
+ info: {
112
+ validated_ledger: {
113
+ reserve_base_xrp: reserveBase / 1_000_000n,
114
+ },
115
+ },
116
+ });
117
+
118
+ const result = await getTransactionStatus(
119
+ account as any,
120
+ {
121
+ amount: 10_000_000n,
122
+ fee: 10_000n,
123
+ recipient: SENDER,
124
+ } as any,
125
+ );
126
+
127
+ expect(result.errors.recipient?.name).toBe("InvalidAddressBecauseDestinationIsAlsoSource");
128
+ });
129
+
130
+ it("errors if recipient is new and amount is too low", async () => {
131
+ mockGetServerInfos.mockResolvedValue({
132
+ info: {
133
+ validated_ledger: {
134
+ reserve_base_xrp: reserveBase / 1_000_000n,
135
+ },
136
+ },
137
+ });
138
+
139
+ mockCachedRecipientIsNew.mockResolvedValue(true);
140
+
141
+ const result = await getTransactionStatus(
142
+ account as any,
143
+ {
144
+ amount: 5_000_000n,
145
+ fee: 10_000n,
146
+ recipient: RECIPIENT_NEW,
147
+ } as any,
148
+ );
149
+
150
+ expect(result.errors.amount?.name).toBe("NotEnoughBalanceBecauseDestinationNotCreated");
151
+ });
152
+
153
+ it("errors if amount is zero", async () => {
154
+ mockGetServerInfos.mockResolvedValue({
155
+ info: {
156
+ validated_ledger: {
157
+ reserve_base_xrp: reserveBase / 1_000_000n,
158
+ },
159
+ },
160
+ });
161
+
162
+ const result = await getTransactionStatus(
163
+ account as any,
164
+ {
165
+ amount: 0n,
166
+ fee: 10_000n,
167
+ recipient: RECIPIENT,
168
+ } as any,
169
+ );
170
+
171
+ expect(result.errors.amount?.name).toBe("AmountRequired");
172
+ });
173
+
174
+ it("errors if recipient is invalid", async () => {
175
+ mockGetServerInfos.mockResolvedValue({
176
+ info: {
177
+ validated_ledger: {
178
+ reserve_base_xrp: reserveBase / 1_000_000n,
179
+ },
180
+ },
181
+ });
182
+
183
+ const result = await getTransactionStatus(
184
+ account as any,
185
+ {
186
+ amount: 1_000_000n,
187
+ fee: 10_000n,
188
+ recipient: "not-an-address",
189
+ } as any,
190
+ );
191
+
192
+ expect(result.errors.recipient?.name).toBe("InvalidAddress");
193
+ });
194
+
195
+ it("errors if recipient is missing", async () => {
196
+ mockGetServerInfos.mockResolvedValue({
197
+ info: {
198
+ validated_ledger: {
199
+ reserve_base_xrp: reserveBase / 1_000_000n,
200
+ },
201
+ },
202
+ });
203
+
204
+ const result = await getTransactionStatus(
205
+ account as any,
206
+ {
207
+ amount: 1_000_000n,
208
+ fee: 10_000n,
209
+ recipient: "",
210
+ } as any,
211
+ );
212
+
213
+ expect(result.errors.recipient?.name).toBe("RecipientRequired");
214
+ });
215
+ });
@@ -9,40 +9,37 @@ import {
9
9
  NotEnoughSpendableBalance,
10
10
  RecipientRequired,
11
11
  } from "@ledgerhq/errors";
12
- import BigNumber from "bignumber.js";
13
12
  import { isValidClassicAddress } from "ripple-address-codec";
14
- import { Account, AccountBridge } from "@ledgerhq/types-live";
15
13
  import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
16
14
  import { getServerInfos } from "../network";
17
- import { cachedRecipientIsNew, parseAPIValue } from "../logic";
18
- import { Transaction, TransactionStatus } from "../types";
15
+ import { cachedRecipientIsNew, parseAPIValue } from ".";
16
+ import { Transaction, TransactionValidation, Account } from "@ledgerhq/coin-framework/api/types";
19
17
 
20
- export const getTransactionStatus: AccountBridge<
21
- Transaction,
22
- Account,
23
- TransactionStatus
24
- >["getTransactionStatus"] = async (account, transaction) => {
18
+ export const getTransactionStatus = async (
19
+ account: Account,
20
+ transaction: Transaction,
21
+ ): Promise<TransactionValidation> => {
25
22
  const errors: Record<string, Error> = {};
26
23
  const warnings: Record<string, Error> = {};
27
24
  const serverInfos = await getServerInfos();
28
25
  const reserveBaseXRP = parseAPIValue(
29
26
  serverInfos.info.validated_ledger.reserve_base_xrp.toString(),
30
27
  );
31
- const estimatedFees = new BigNumber(transaction.fee || 0);
32
- const totalSpent = new BigNumber(transaction.amount).plus(estimatedFees);
33
- const amount = new BigNumber(transaction.amount);
28
+ const estimatedFees = transaction.fee || 0n;
29
+ const totalSpent = transaction.amount + estimatedFees;
30
+ const amount = transaction.amount;
34
31
 
35
- if (amount.gt(0) && estimatedFees.times(10).gt(amount)) {
32
+ if (amount > 0 && estimatedFees * 10n > amount) {
36
33
  warnings.feeTooHigh = new FeeTooHigh();
37
34
  }
38
35
 
39
36
  if (!transaction.fee) {
40
37
  errors.fee = new FeeNotLoaded();
41
- } else if (transaction.fee.eq(0)) {
38
+ } else if (transaction.fee == 0n) {
42
39
  errors.fee = new FeeRequired();
43
- } else if (totalSpent.gt(account.balance.minus(reserveBaseXRP))) {
40
+ } else if (totalSpent > account.balance - BigInt(reserveBaseXRP.toString())) {
44
41
  errors.amount = new NotEnoughSpendableBalance("", {
45
- minimumAmount: formatCurrencyUnit(account.currency.units[0], reserveBaseXRP, {
42
+ minimumAmount: formatCurrencyUnit(account.currencyUnit, reserveBaseXRP, {
46
43
  disableRounding: true,
47
44
  useGrouping: false,
48
45
  showCode: true,
@@ -51,10 +48,10 @@ export const getTransactionStatus: AccountBridge<
51
48
  } else if (
52
49
  transaction.recipient &&
53
50
  (await cachedRecipientIsNew(transaction.recipient)) &&
54
- transaction.amount.lt(reserveBaseXRP)
51
+ transaction.amount < BigInt(reserveBaseXRP.toString())
55
52
  ) {
56
53
  errors.amount = new NotEnoughBalanceBecauseDestinationNotCreated("", {
57
- minimalAmount: formatCurrencyUnit(account.currency.units[0], reserveBaseXRP, {
54
+ minimalAmount: formatCurrencyUnit(account.currencyUnit, reserveBaseXRP, {
58
55
  disableRounding: true,
59
56
  useGrouping: false,
60
57
  showCode: true,
@@ -64,15 +61,15 @@ export const getTransactionStatus: AccountBridge<
64
61
 
65
62
  if (!transaction.recipient) {
66
63
  errors.recipient = new RecipientRequired("");
67
- } else if (account.freshAddress === transaction.recipient) {
64
+ } else if (account.address === transaction.recipient) {
68
65
  errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();
69
66
  } else if (!isValidClassicAddress(transaction.recipient)) {
70
67
  errors.recipient = new InvalidAddress("", {
71
- currencyName: account.currency.name,
68
+ currencyName: account.currencyName,
72
69
  });
73
70
  }
74
71
 
75
- if (!errors.amount && amount.eq(0)) {
72
+ if (!errors.amount && amount == 0n) {
76
73
  errors.amount = new AmountRequired();
77
74
  }
78
75
 
@@ -6,11 +6,7 @@ export { estimateFees } from "./estimateFees";
6
6
  export { getBalance } from "./getBalance";
7
7
  export { lastBlock } from "./lastBlock";
8
8
  export { listOperations } from "./listOperations";
9
- export {
10
- RIPPLE_EPOCH,
11
- cachedRecipientIsNew,
12
- getNextValidSequence,
13
- removeCachedRecipientIsNew,
14
- } from "./utils";
9
+ export { getTransactionStatus } from "./getTransactionStatus";
10
+ export { RIPPLE_EPOCH, cachedRecipientIsNew, getNextValidSequence } from "./utils";
15
11
 
16
12
  export { parseAPIValue } from "./common";
@@ -22,6 +22,15 @@ function isRecipientValid(recipient: string): boolean {
22
22
  return isValidClassicAddress(recipient);
23
23
  }
24
24
 
25
+ // --- 10-Seconds Cache Implementation ---
26
+ type CacheEntry = {
27
+ value: boolean;
28
+ expiresAt: number;
29
+ };
30
+
31
+ const recipientCache = new Map<string, CacheEntry>();
32
+ const TTL = 10 * 1000; // 10 seconds
33
+
25
34
  const recipientIsNew = async (recipient: string): Promise<boolean> => {
26
35
  if (!isRecipientValid(recipient)) return false;
27
36
 
@@ -29,12 +38,19 @@ const recipientIsNew = async (recipient: string): Promise<boolean> => {
29
38
  return info.isNewAccount;
30
39
  };
31
40
 
32
- const cacheRecipientsNew: Record<string, boolean> = {};
33
- export const cachedRecipientIsNew = async (recipient: string) => {
34
- if (recipient in cacheRecipientsNew) return cacheRecipientsNew[recipient];
35
- cacheRecipientsNew[recipient] = await recipientIsNew(recipient);
36
- return cacheRecipientsNew[recipient];
37
- };
38
- export const removeCachedRecipientIsNew = (recipient: string) => {
39
- delete cacheRecipientsNew[recipient];
41
+ export const cachedRecipientIsNew = async (recipient: string): Promise<boolean> => {
42
+ const now = Date.now();
43
+ const cached = recipientCache.get(recipient);
44
+
45
+ if (cached && now < cached.expiresAt) {
46
+ return cached.value;
47
+ }
48
+
49
+ const isNew = await recipientIsNew(recipient);
50
+ recipientCache.set(recipient, {
51
+ value: isNew,
52
+ expiresAt: now + TTL,
53
+ });
54
+
55
+ return isNew;
40
56
  };
@@ -1,7 +1,7 @@
1
1
  import BigNumber from "bignumber.js";
2
2
  import { DatasetTest } from "@ledgerhq/types-live";
3
3
  import { InvalidAddressBecauseDestinationIsAlsoSource } from "@ledgerhq/errors";
4
- import { fromTransactionRaw } from "../bridge/transaction";
4
+ import { fromTransactionRaw } from "../transaction";
5
5
  import { Transaction } from "../types";
6
6
 
7
7
  export const newAddress1 = "rZvBc5e2YR1A9otS3r9DyGh3NDP8XLLp4";
@@ -97,18 +97,19 @@ export const dataset: DatasetTest<Transaction> = {
97
97
  recipient: "rageXHB6Q4VbvvWdTzKANwjeCT4HXFCKX7",
98
98
  amount: "10000000",
99
99
  tag: null,
100
- fee: "1",
100
+ fee: "10", // NOTE: fee is not customizable, this field is ignored
101
101
  feeCustomUnit: null,
102
102
  networkInfo: null,
103
103
  }),
104
104
  expectedStatus: {
105
105
  amount: new BigNumber("10000000"),
106
- estimatedFees: new BigNumber("1"),
106
+ estimatedFees: new BigNumber("10"), // NOTE: hardcoded fee
107
+
107
108
  errors: {
108
109
  recipient: new InvalidAddressBecauseDestinationIsAlsoSource(),
109
110
  },
110
111
  warnings: {},
111
- totalSpent: new BigNumber("10000001"),
112
+ totalSpent: new BigNumber("10000010"), // NOTE: amount + hardcoded fee
112
113
  },
113
114
  },
114
115
  {
@@ -118,16 +119,16 @@ export const dataset: DatasetTest<Transaction> = {
118
119
  recipient: "rB6pwovsyrFWhPYUsjj9V3CHck985QjiXi",
119
120
  amount: "10000000",
120
121
  tag: 12345,
121
- fee: "1",
122
+ fee: "10", // NOTE: fee is not customizable, this field is ignored
122
123
  feeCustomUnit: null,
123
124
  networkInfo: null,
124
125
  }),
125
126
  expectedStatus: {
126
127
  amount: new BigNumber("10000000"),
127
- estimatedFees: new BigNumber("1"),
128
+ estimatedFees: new BigNumber("10"), // NOTE: hardcoded fee
128
129
  errors: {},
129
130
  warnings: {},
130
- totalSpent: new BigNumber("10000001"),
131
+ totalSpent: new BigNumber("10000010"), // NOTE: amount + hardcoded fee
131
132
  },
132
133
  },
133
134
  ],
@@ -1,5 +1,5 @@
1
1
  import { BigNumber } from "bignumber.js";
2
- import type { Transaction, TransactionRaw } from "../types";
2
+ import type { Transaction, TransactionRaw } from "./types";
3
3
  import { formatTransactionStatus } from "@ledgerhq/coin-framework/formatters";
4
4
  import {
5
5
  fromTransactionCommonRaw,
@@ -1,3 +1,5 @@
1
+ import { TypedMapMemo } from "@ledgerhq/coin-framework/api/types";
2
+
1
3
  export type AccountInfo = {
2
4
  isNewAccount: boolean;
3
5
  balance: string;
@@ -11,7 +13,16 @@ export type XrpMemo = {
11
13
  type?: string;
12
14
  };
13
15
 
16
+ export type XrpMemoKind = "destinationTag" | "memo";
17
+
18
+ export type XrpMemoValueMap = {
19
+ destinationTag: string;
20
+ memos: string[];
21
+ };
22
+ export type XrpMapMemo = TypedMapMemo<XrpMemoValueMap>;
23
+
14
24
  type Order = "asc" | "desc";
25
+
15
26
  export type ListOperationsOptions = {
16
27
  // pagination:
17
28
  limit?: number;
@@ -1,4 +0,0 @@
1
- import { AccountBridge } from "@ledgerhq/types-live";
2
- import { Transaction } from "../types";
3
- export declare const broadcast: AccountBridge<Transaction>["broadcast"];
4
- //# sourceMappingURL=broadcast.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"broadcast.d.ts","sourceRoot":"","sources":["../../src/bridge/broadcast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,eAAO,MAAM,SAAS,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,WAAW,CAK7D,CAAC"}
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.broadcast = void 0;
4
- const operation_1 = require("@ledgerhq/coin-framework/operation");
5
- const logic_1 = require("../logic");
6
- const broadcast = async ({ signedOperation: { signature, operation }, }) => {
7
- const hash = await (0, logic_1.broadcast)(signature);
8
- return (0, operation_1.patchOperationWithHash)(operation, hash);
9
- };
10
- exports.broadcast = broadcast;
11
- //# sourceMappingURL=broadcast.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../../src/bridge/broadcast.ts"],"names":[],"mappings":";;;AACA,kEAA4E;AAC5E,oCAAuD;AAGhD,MAAM,SAAS,GAA4C,KAAK,EAAE,EACvE,eAAe,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,GAC1C,EAAE,EAAE;IACH,MAAM,IAAI,GAAG,MAAM,IAAA,iBAAc,EAAC,SAAS,CAAC,CAAC;IAC7C,OAAO,IAAA,kCAAsB,EAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC,CAAC;AALW,QAAA,SAAS,aAKpB"}
@@ -1,4 +0,0 @@
1
- import { Transaction } from "../types";
2
- import { AccountBridge } from "@ledgerhq/types-live";
3
- export declare const createTransaction: AccountBridge<Transaction>["createTransaction"];
4
- //# sourceMappingURL=createTransaction.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createTransaction.d.ts","sourceRoot":"","sources":["../../src/bridge/createTransaction.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,WAAW,CAAC,CAAC,mBAAmB,CAQ5E,CAAC"}
@@ -1,18 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createTransaction = void 0;
7
- const bignumber_js_1 = __importDefault(require("bignumber.js"));
8
- const createTransaction = () => ({
9
- family: "xrp",
10
- amount: new bignumber_js_1.default(0),
11
- recipient: "",
12
- fee: null,
13
- tag: undefined,
14
- networkInfo: null,
15
- feeCustomUnit: null,
16
- });
17
- exports.createTransaction = createTransaction;
18
- //# sourceMappingURL=createTransaction.js.map