@ledgerhq/coin-stellar 6.2.0 → 6.3.0-nightly.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 (79) hide show
  1. package/.eslintrc.js +4 -0
  2. package/CHANGELOG.md +14 -0
  3. package/lib/api/index.d.ts.map +1 -1
  4. package/lib/api/index.js +3 -5
  5. package/lib/api/index.js.map +1 -1
  6. package/lib/logic/estimateFees.d.ts +1 -1
  7. package/lib/logic/estimateFees.d.ts.map +1 -1
  8. package/lib/logic/estimateFees.js +3 -3
  9. package/lib/logic/estimateFees.js.map +1 -1
  10. package/lib/logic/getBalance.test.js +111 -15
  11. package/lib/logic/getBalance.test.js.map +1 -1
  12. package/lib/logic/listOperations.unit.test.js +41 -12
  13. package/lib/logic/listOperations.unit.test.js.map +1 -1
  14. package/lib/logic/validateIntent.d.ts +2 -2
  15. package/lib/logic/validateIntent.d.ts.map +1 -1
  16. package/lib/logic/validateIntent.js +6 -5
  17. package/lib/logic/validateIntent.js.map +1 -1
  18. package/lib/network/horizon.d.ts +2 -4
  19. package/lib/network/horizon.d.ts.map +1 -1
  20. package/lib/network/horizon.js +24 -15
  21. package/lib/network/horizon.js.map +1 -1
  22. package/lib/network/index.d.ts +1 -1
  23. package/lib/network/index.d.ts.map +1 -1
  24. package/lib/network/index.js +1 -4
  25. package/lib/network/index.js.map +1 -1
  26. package/lib/network/serialization.d.ts +2 -1
  27. package/lib/network/serialization.d.ts.map +1 -1
  28. package/lib/network/serialization.js +7 -13
  29. package/lib/network/serialization.js.map +1 -1
  30. package/lib/polyfill.d.ts.map +1 -1
  31. package/lib/polyfill.js +4 -1
  32. package/lib/polyfill.js.map +1 -1
  33. package/lib/types/bridge.d.ts +2 -0
  34. package/lib/types/bridge.d.ts.map +1 -1
  35. package/lib/types/bridge.js.map +1 -1
  36. package/lib-es/api/index.d.ts.map +1 -1
  37. package/lib-es/api/index.js +3 -5
  38. package/lib-es/api/index.js.map +1 -1
  39. package/lib-es/logic/estimateFees.d.ts +1 -1
  40. package/lib-es/logic/estimateFees.d.ts.map +1 -1
  41. package/lib-es/logic/estimateFees.js +4 -4
  42. package/lib-es/logic/estimateFees.js.map +1 -1
  43. package/lib-es/logic/getBalance.test.js +111 -12
  44. package/lib-es/logic/getBalance.test.js.map +1 -1
  45. package/lib-es/logic/listOperations.unit.test.js +41 -12
  46. package/lib-es/logic/listOperations.unit.test.js.map +1 -1
  47. package/lib-es/logic/validateIntent.d.ts +2 -2
  48. package/lib-es/logic/validateIntent.d.ts.map +1 -1
  49. package/lib-es/logic/validateIntent.js +5 -4
  50. package/lib-es/logic/validateIntent.js.map +1 -1
  51. package/lib-es/network/horizon.d.ts +2 -4
  52. package/lib-es/network/horizon.d.ts.map +1 -1
  53. package/lib-es/network/horizon.js +22 -14
  54. package/lib-es/network/horizon.js.map +1 -1
  55. package/lib-es/network/index.d.ts +1 -1
  56. package/lib-es/network/index.d.ts.map +1 -1
  57. package/lib-es/network/index.js +1 -1
  58. package/lib-es/network/index.js.map +1 -1
  59. package/lib-es/network/serialization.d.ts +2 -1
  60. package/lib-es/network/serialization.d.ts.map +1 -1
  61. package/lib-es/network/serialization.js +4 -9
  62. package/lib-es/network/serialization.js.map +1 -1
  63. package/lib-es/polyfill.d.ts.map +1 -1
  64. package/lib-es/polyfill.js +4 -1
  65. package/lib-es/polyfill.js.map +1 -1
  66. package/lib-es/types/bridge.d.ts +2 -0
  67. package/lib-es/types/bridge.d.ts.map +1 -1
  68. package/lib-es/types/bridge.js.map +1 -1
  69. package/package.json +10 -10
  70. package/src/api/index.ts +3 -6
  71. package/src/logic/estimateFees.ts +4 -4
  72. package/src/logic/getBalance.test.ts +128 -17
  73. package/src/logic/listOperations.unit.test.ts +45 -12
  74. package/src/logic/validateIntent.ts +10 -9
  75. package/src/network/horizon.ts +27 -19
  76. package/src/network/index.ts +0 -3
  77. package/src/network/serialization.ts +4 -13
  78. package/src/polyfill.ts +4 -1
  79. package/src/types/bridge.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ledgerhq/coin-stellar",
3
- "version": "6.2.0",
3
+ "version": "6.3.0-nightly.0",
4
4
  "description": "Ledger Stellar Coin integration",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -102,20 +102,20 @@
102
102
  "./package.json": "./package.json"
103
103
  },
104
104
  "dependencies": {
105
- "@stellar/stellar-sdk": "^11.3.0",
105
+ "@stellar/stellar-sdk": "^14.0.0",
106
106
  "bignumber.js": "^9.1.2",
107
107
  "expect": "^27.4.6",
108
108
  "invariant": "^2.2.2",
109
109
  "rxjs": "^7.8.1",
110
110
  "@exodus/patch-broken-hermes-typed-arrays": "1.0.0-alpha.1",
111
- "@ledgerhq/coin-framework": "^6.2.0",
112
- "@ledgerhq/cryptoassets": "^13.26.0",
113
- "@ledgerhq/devices": "8.5.0",
114
- "@ledgerhq/errors": "^6.24.0",
115
- "@ledgerhq/live-env": "^2.14.0",
116
- "@ledgerhq/live-network": "^2.0.15",
111
+ "@ledgerhq/coin-framework": "^6.2.1-nightly.0",
112
+ "@ledgerhq/cryptoassets": "^13.27.0-nightly.0",
113
+ "@ledgerhq/devices": "8.5.1-nightly.0",
114
+ "@ledgerhq/errors": "^6.25.0-nightly.0",
115
+ "@ledgerhq/live-env": "^2.15.0-nightly.1",
116
+ "@ledgerhq/live-network": "^2.0.16-nightly.1",
117
117
  "@ledgerhq/logs": "^6.13.0",
118
- "@ledgerhq/types-cryptoassets": "^7.25.0",
118
+ "@ledgerhq/types-cryptoassets": "^7.26.0-nightly.1",
119
119
  "@ledgerhq/types-live": "^6.82.0"
120
120
  },
121
121
  "devDependencies": {
@@ -132,7 +132,7 @@
132
132
  "scripts": {
133
133
  "clean": "rimraf lib lib-es",
134
134
  "build": "tsc && tsc -m esnext --moduleResolution bundler --outDir lib-es",
135
- "coverage": "jest --coverage || true",
135
+ "coverage": "jest --coverage",
136
136
  "prewatch": "pnpm build",
137
137
  "watch": "tsc --watch",
138
138
  "watch:es": "tsc --watch -m esnext --moduleResolution bundler --outDir lib-es",
package/src/api/index.ts CHANGED
@@ -77,8 +77,7 @@ async function craft(
77
77
  transactionIntent: TransactionIntent<StellarMemo>,
78
78
  customFees?: FeeEstimation,
79
79
  ): Promise<string> {
80
- const fees =
81
- customFees?.value || transactionIntent.fees || (await estimateFees(transactionIntent.sender));
80
+ const fees = customFees?.value || (await estimateFees());
82
81
 
83
82
  // NOTE: check how many memos, throw if more than one?
84
83
  // if (transactionIntent.memos && transactionIntent.memos.length > 1) {
@@ -116,10 +115,8 @@ function compose(tx: string, signature: string, pubkey?: string): string {
116
115
  return combine(envelopeFromAnyXDR(tx, "base64"), signature, pubkey);
117
116
  }
118
117
 
119
- async function estimate(transactionIntent: TransactionIntent): Promise<FeeEstimation> {
120
- const value = transactionIntent?.fees
121
- ? transactionIntent?.fees
122
- : await estimateFees(transactionIntent.sender);
118
+ async function estimate(_transactionIntent: TransactionIntent): Promise<FeeEstimation> {
119
+ const value = await estimateFees();
123
120
  return { value };
124
121
  }
125
122
 
@@ -1,10 +1,10 @@
1
- import { fetchAccountNetworkInfo } from "../network";
1
+ import { fetchBaseFee } from "../network";
2
2
 
3
3
  /**
4
4
  * Estimate the fees for one transaction
5
5
  * @see {@link https://developers.stellar.org/docs/learn/fundamentals/fees-resource-limits-metering#inclusion-fee}
6
6
  */
7
- export async function estimateFees(account: string): Promise<bigint> {
8
- const { fees } = await fetchAccountNetworkInfo(account);
9
- return BigInt(fees.toString());
7
+ export async function estimateFees(): Promise<bigint> {
8
+ const baseFee = await fetchBaseFee();
9
+ return BigInt(baseFee.recommendedFee);
10
10
  }
@@ -1,26 +1,137 @@
1
- import coinConfig, { StellarCoinConfig } from "../config";
2
1
  import { getBalance } from "./getBalance";
3
- import { AccountCallBuilder } from "@stellar/stellar-sdk/lib/horizon/account_call_builder";
2
+ import { fetchAccount } from "../network";
3
+ import { findTokenById } from "@ledgerhq/cryptoassets/tokens";
4
+ import { getAssetIdFromAsset } from "./utils";
5
+ import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies/parseCurrencyUnit";
6
+
7
+ jest.mock("../network", () => ({
8
+ fetchAccount: jest.fn(),
9
+ }));
10
+
11
+ jest.mock("@ledgerhq/cryptoassets/tokens", () => ({
12
+ findTokenById: jest.fn(),
13
+ }));
14
+
15
+ jest.mock("./utils", () => ({
16
+ getAssetIdFromAsset: jest.fn(),
17
+ }));
18
+
19
+ jest.mock("@ledgerhq/coin-framework/currencies/parseCurrencyUnit", () => ({
20
+ parseCurrencyUnit: jest.fn(),
21
+ }));
4
22
 
5
23
  describe("getBalance", () => {
6
- coinConfig.setCoinConfig(
7
- () =>
8
- ({
9
- status: { type: "active" },
10
- explorer: { url: "https://stellar.explorer.com" },
11
- }) as unknown as StellarCoinConfig,
12
- );
13
- it("gets the balance of a Stellar account", async () => {
14
- jest.spyOn(AccountCallBuilder.prototype, "accountId").mockReturnValue({
15
- call: () => Promise.resolve({ balances: [{ asset_type: "native", balance: "50" }] }),
16
- } as any);
17
-
18
- expect(await getBalance("GC65CUPW2IMTJJY6CII7F3OBPVG4YGASEPBBLM4V3LBKX62P6LA24OFV")).toEqual([
24
+ it("returns native balance when no assets are present", async () => {
25
+ (fetchAccount as jest.Mock).mockResolvedValue({
26
+ balance: 100000000,
27
+ spendableBalance: 99999990,
28
+ assets: [],
29
+ });
30
+
31
+ const result = await getBalance("test-address");
32
+
33
+ expect(result).toEqual([
34
+ {
35
+ value: BigInt(100000000),
36
+ asset: { type: "native" },
37
+ locked: BigInt(10),
38
+ },
39
+ ]);
40
+ });
41
+
42
+ it("returns native and asset balances when assets are present", async () => {
43
+ (fetchAccount as jest.Mock).mockResolvedValue({
44
+ balance: 100000000,
45
+ spendableBalance: 99999990,
46
+ assets: [
47
+ {
48
+ asset_type: "credit_alphanum4",
49
+ asset_code: "USDC",
50
+ asset_issuer: "issuer-address",
51
+ balance: "50.1234567",
52
+ },
53
+ ],
54
+ });
55
+
56
+ (getAssetIdFromAsset as jest.Mock).mockReturnValue("USDC-issuer-address");
57
+ (findTokenById as jest.Mock).mockReturnValue({
58
+ units: [{ code: "USDC", magnitude: 7 }],
59
+ });
60
+ (parseCurrencyUnit as jest.Mock).mockReturnValue({
61
+ toString: () => "50.1234567",
62
+ });
63
+
64
+ const result = await getBalance("test-address");
65
+
66
+ expect(result).toEqual([
19
67
  {
20
- value: BigInt(500000000),
68
+ value: BigInt(100000000),
21
69
  asset: { type: "native" },
22
- locked: BigInt(500000000 - 499999900),
70
+ locked: BigInt(10),
71
+ },
72
+ {
73
+ value: BigInt(50),
74
+ asset: {
75
+ type: "credit_alphanum4",
76
+ assetReference: "USDC",
77
+ assetOwner: "issuer-address",
78
+ },
23
79
  },
24
80
  ]);
81
+
82
+ expect(fetchAccount).toHaveBeenCalledWith("test-address");
83
+ expect(getAssetIdFromAsset).toHaveBeenCalledWith({
84
+ asset_type: "credit_alphanum4",
85
+ asset_code: "USDC",
86
+ asset_issuer: "issuer-address",
87
+ balance: "50.1234567",
88
+ });
89
+ expect(findTokenById).toHaveBeenCalledWith("stellar/asset/USDC-issuer-address");
90
+ expect(parseCurrencyUnit).toHaveBeenCalledWith({ code: "USDC", magnitude: 7 }, "50.1234567");
91
+ });
92
+
93
+ it("handles assets without matching tokens gracefully", async () => {
94
+ (fetchAccount as jest.Mock).mockResolvedValue({
95
+ balance: 100000000,
96
+ spendableBalance: 99999990,
97
+ assets: [
98
+ {
99
+ asset_type: "credit_alphanum4",
100
+ asset_code: "USDT",
101
+ asset_issuer: "issuer-address",
102
+ balance: "25.0000000",
103
+ },
104
+ ],
105
+ });
106
+
107
+ (getAssetIdFromAsset as jest.Mock).mockReturnValue("USDT-issuer-address");
108
+ (findTokenById as jest.Mock).mockReturnValue(null);
109
+
110
+ const result = await getBalance("test-address");
111
+
112
+ expect(result).toEqual([
113
+ {
114
+ value: BigInt(100000000),
115
+ asset: { type: "native" },
116
+ locked: BigInt(10),
117
+ },
118
+ {
119
+ value: BigInt(0),
120
+ asset: {
121
+ type: "credit_alphanum4",
122
+ assetReference: "USDT",
123
+ assetOwner: "issuer-address",
124
+ },
125
+ },
126
+ ]);
127
+
128
+ expect(fetchAccount).toHaveBeenCalledWith("test-address");
129
+ expect(getAssetIdFromAsset).toHaveBeenCalledWith({
130
+ asset_type: "credit_alphanum4",
131
+ asset_code: "USDT",
132
+ asset_issuer: "issuer-address",
133
+ balance: "25.0000000",
134
+ });
135
+ expect(findTokenById).toHaveBeenCalledWith("stellar/asset/USDT-issuer-address");
25
136
  });
26
137
  });
@@ -1,7 +1,8 @@
1
1
  import { listOperations } from "./listOperations";
2
2
  import coinConfig from "../config";
3
- import { OperationCallBuilder } from "@stellar/stellar-sdk/lib/horizon/operation_call_builder";
4
- import { HorizonApi } from "@stellar/stellar-sdk/lib/horizon";
3
+ import { Horizon } from "@stellar/stellar-sdk";
4
+
5
+ jest.mock("@stellar/stellar-sdk");
5
6
 
6
7
  describe("listOperations", () => {
7
8
  coinConfig.setCoinConfig(() => ({
@@ -9,8 +10,34 @@ describe("listOperations", () => {
9
10
  explorer: { url: "https://stellar.coin.ledger.com" },
10
11
  }));
11
12
 
13
+ const mockCall = jest.fn();
14
+
15
+ beforeEach(() => {
16
+ (Horizon.Server as jest.Mock).mockImplementation(() => ({
17
+ operations: () => ({
18
+ forAccount: () => ({
19
+ limit: () => ({
20
+ order: () => ({
21
+ cursor: () => ({
22
+ includeFailed: () => ({
23
+ join: () => ({
24
+ call: mockCall,
25
+ }),
26
+ }),
27
+ }),
28
+ }),
29
+ }),
30
+ }),
31
+ }),
32
+ }));
33
+ });
34
+
35
+ afterEach(() => {
36
+ jest.clearAllMocks();
37
+ });
38
+
12
39
  it("lists operations associated with an address", async () => {
13
- jest.spyOn(OperationCallBuilder.prototype, "call").mockResolvedValue({
40
+ mockCall.mockResolvedValueOnce({
14
41
  records: [
15
42
  {
16
43
  id: "operation_id1",
@@ -20,7 +47,7 @@ describe("listOperations", () => {
20
47
  from: "address",
21
48
  to: "receiver1",
22
49
  amount: "46.0600000",
23
- type: HorizonApi.OperationResponseType.payment,
50
+ type: Horizon.HorizonApi.OperationResponseType.payment,
24
51
  transaction_successful: true,
25
52
  created_at: "2025-01-01",
26
53
  transaction: () => ({
@@ -40,7 +67,7 @@ describe("listOperations", () => {
40
67
  from: "address",
41
68
  to: "receiver2",
42
69
  amount: "666.0000000",
43
- type: HorizonApi.OperationResponseType.payment,
70
+ type: Horizon.HorizonApi.OperationResponseType.payment,
44
71
  transaction_successful: false,
45
72
  created_at: "2025-01-01",
46
73
  transaction: () => ({
@@ -60,7 +87,7 @@ describe("listOperations", () => {
60
87
  from: "sender",
61
88
  to: "address",
62
89
  amount: "50.5000000",
63
- type: HorizonApi.OperationResponseType.payment,
90
+ type: Horizon.HorizonApi.OperationResponseType.payment,
64
91
  transaction_successful: true,
65
92
  created_at: "2025-01-01",
66
93
  transaction: () => ({
@@ -85,6 +112,7 @@ describe("listOperations", () => {
85
112
  ledgerOpType: "OUT",
86
113
  sequence: undefined,
87
114
  memo: undefined,
115
+ status: "success",
88
116
  },
89
117
  senders: ["address"],
90
118
  recipients: ["receiver1"],
@@ -105,6 +133,7 @@ describe("listOperations", () => {
105
133
  ledgerOpType: "OUT",
106
134
  sequence: undefined,
107
135
  memo: undefined,
136
+ status: "failed",
108
137
  },
109
138
  senders: ["address"],
110
139
  recipients: ["receiver2"],
@@ -125,6 +154,7 @@ describe("listOperations", () => {
125
154
  ledgerOpType: "IN",
126
155
  sequence: undefined,
127
156
  memo: undefined,
157
+ status: "success",
128
158
  },
129
159
  senders: ["sender"],
130
160
  recipients: ["address"],
@@ -143,7 +173,7 @@ describe("listOperations", () => {
143
173
  });
144
174
 
145
175
  it("stops at minHeight", async () => {
146
- jest.spyOn(OperationCallBuilder.prototype, "call").mockResolvedValue({
176
+ mockCall.mockResolvedValueOnce({
147
177
  records: [
148
178
  {
149
179
  id: "operation_id1",
@@ -153,7 +183,7 @@ describe("listOperations", () => {
153
183
  from: "address",
154
184
  to: "receiver1",
155
185
  amount: "46.0600000",
156
- type: HorizonApi.OperationResponseType.payment,
186
+ type: Horizon.HorizonApi.OperationResponseType.payment,
157
187
  transaction_successful: true,
158
188
  created_at: "2025-01-01",
159
189
  transaction: () => ({
@@ -173,7 +203,7 @@ describe("listOperations", () => {
173
203
  from: "address",
174
204
  to: "receiver2",
175
205
  amount: "666.0000000",
176
- type: HorizonApi.OperationResponseType.payment,
206
+ type: Horizon.HorizonApi.OperationResponseType.payment,
177
207
  transaction_successful: false,
178
208
  created_at: "2025-01-01",
179
209
  transaction: () => ({
@@ -193,7 +223,7 @@ describe("listOperations", () => {
193
223
  from: "sender",
194
224
  to: "address",
195
225
  amount: "50.5000000",
196
- type: HorizonApi.OperationResponseType.payment,
226
+ type: Horizon.HorizonApi.OperationResponseType.payment,
197
227
  transaction_successful: true,
198
228
  created_at: "2025-01-01",
199
229
  transaction: () => ({
@@ -218,6 +248,7 @@ describe("listOperations", () => {
218
248
  ledgerOpType: "OUT",
219
249
  sequence: undefined,
220
250
  memo: undefined,
251
+ status: "success",
221
252
  },
222
253
  senders: ["address"],
223
254
  recipients: ["receiver1"],
@@ -238,6 +269,7 @@ describe("listOperations", () => {
238
269
  ledgerOpType: "OUT",
239
270
  sequence: undefined,
240
271
  memo: undefined,
272
+ status: "failed",
241
273
  },
242
274
  senders: ["address"],
243
275
  recipients: ["receiver2"],
@@ -256,7 +288,7 @@ describe("listOperations", () => {
256
288
  });
257
289
 
258
290
  it("should return memo if set", async () => {
259
- jest.spyOn(OperationCallBuilder.prototype, "call").mockResolvedValue({
291
+ mockCall.mockResolvedValueOnce({
260
292
  records: [
261
293
  {
262
294
  id: "operation_id1",
@@ -266,7 +298,7 @@ describe("listOperations", () => {
266
298
  from: "address",
267
299
  to: "receiver1",
268
300
  amount: "46.0600000",
269
- type: HorizonApi.OperationResponseType.payment,
301
+ type: Horizon.HorizonApi.OperationResponseType.payment,
270
302
  transaction_successful: true,
271
303
  created_at: "2025-01-01",
272
304
  transaction: () => ({
@@ -306,6 +338,7 @@ describe("listOperations", () => {
306
338
  type: "MEMO_TEXT",
307
339
  value: "momo",
308
340
  },
341
+ status: "success",
309
342
  },
310
343
  },
311
344
  ],
@@ -8,14 +8,13 @@ import {
8
8
  InvalidAddress,
9
9
  } from "@ledgerhq/errors";
10
10
  import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index";
11
- import { TransactionValidation, TransactionIntent } from "@ledgerhq/coin-framework/api/types";
12
- import { isAddressValid, isAccountMultiSign, isMemoValid } from "./utils";
13
11
  import {
14
- BASE_RESERVE,
15
- MIN_BALANCE,
16
- fetchAccountNetworkInfo,
17
- getRecipientAccount,
18
- } from "../network";
12
+ TransactionValidation,
13
+ TransactionIntent,
14
+ FeeEstimation,
15
+ } from "@ledgerhq/coin-framework/api/types";
16
+ import { isAddressValid, isAccountMultiSign, isMemoValid } from "./utils";
17
+ import { fetchAccountNetworkInfo, getRecipientAccount } from "../network";
19
18
  import {
20
19
  StellarWrongMemoFormat,
21
20
  StellarAssetRequired,
@@ -32,9 +31,11 @@ import {
32
31
  import BigNumber from "bignumber.js";
33
32
  import { getBalance } from "./getBalance";
34
33
  import { fetchAccount } from "../network/horizon";
34
+ import { BASE_RESERVE, MIN_BALANCE } from "../network/serialization";
35
35
 
36
36
  export const validateIntent = async (
37
37
  transactionIntent: TransactionIntent<StellarMemo>,
38
+ customFees?: FeeEstimation,
38
39
  ): Promise<TransactionValidation> => {
39
40
  const errors: Record<string, Error> = {};
40
41
  const warnings: Record<string, Error> = {};
@@ -47,7 +48,7 @@ export const validateIntent = async (
47
48
  const { spendableBalance, balance } = await fetchAccount(transactionIntent.sender);
48
49
  const networkInfo = await fetchAccountNetworkInfo(transactionIntent.sender);
49
50
 
50
- const estimatedFees = transactionIntent.fees ?? 0n;
51
+ const estimatedFees = customFees?.value ?? 0n;
51
52
  const baseReserve = networkInfo.baseReserve
52
53
  ? BigInt(Math.round(networkInfo.baseReserve.toNumber() * 10)) / 10n
53
54
  : 0n;
@@ -203,7 +204,7 @@ export const validateIntent = async (
203
204
  }
204
205
  }
205
206
 
206
- if (!errors.amount && amount === 0n) {
207
+ if (amount === 0n) {
207
208
  errors.amount = new AmountRequired();
208
209
  }
209
210
  }
@@ -26,11 +26,7 @@ import {
26
26
  NetworkCongestionLevel,
27
27
  StellarOperation,
28
28
  } from "../types";
29
- import {
30
- getAccountSpendableBalance,
31
- getReservedBalance,
32
- rawOperationsToOperations,
33
- } from "./serialization";
29
+ import { getReservedBalance, rawOperationsToOperations } from "./serialization";
34
30
  import { patchHermesTypedArraysIfNeeded, unpatchHermesTypedArrays } from "../polyfill";
35
31
 
36
32
  const FALLBACK_BASE_FEE = 100;
@@ -52,11 +48,6 @@ function getServer(): Horizon.Server {
52
48
  return server;
53
49
  }
54
50
 
55
- // Constants
56
- export const BASE_RESERVE = 0.5;
57
- export const BASE_RESERVE_MIN_COUNT = 2;
58
- export const MIN_BALANCE = 1;
59
-
60
51
  // Due to the inconsistency between the axios version (1.6.5) used by `stellar-sdk`
61
52
  // and the version (0.26.1) used by `@ledgerhq/live-network/network`, it is not possible to use the interceptors
62
53
  // provided by `@ledgerhq/live-network/network`.
@@ -78,6 +69,19 @@ function useConfigHost(url: string): string {
78
69
  return u.toString();
79
70
  }
80
71
 
72
+ const getMinimumBalance = (account: Horizon.ServerApi.AccountRecord): BigNumber => {
73
+ return parseCurrencyUnit(currency.units[0], getReservedBalance(account).toString());
74
+ };
75
+
76
+ export async function getAccountSpendableBalance(
77
+ balance: BigNumber,
78
+ account: Horizon.ServerApi.AccountRecord,
79
+ ): Promise<BigNumber> {
80
+ const minimumBalance = getMinimumBalance(account);
81
+ const { recommendedFee } = await fetchBaseFee();
82
+ return BigNumber.max(balance.minus(minimumBalance).minus(recommendedFee), 0);
83
+ }
84
+
81
85
  Horizon.AxiosClient.interceptors.response.use(response => {
82
86
  if (coinConfig.getCoinConfig().enableNetworkLogs) {
83
87
  const { url, method } = response.config;
@@ -323,7 +327,7 @@ export async function fetchOperations({
323
327
 
324
328
  // in this context, if we have filtered out operations it means those operations were < minHeight, so we are done
325
329
  const nextCursor =
326
- filteredOps.length == rawOps.length ? rawOps[rawOps.length - 1].paging_token : "";
330
+ filteredOps.length === rawOps.length ? rawOps[rawOps.length - 1].paging_token : "";
327
331
 
328
332
  return [filteredOps, nextCursor];
329
333
  } catch (e: unknown) {
@@ -396,14 +400,18 @@ export async function fetchSigners(account: string): Promise<Signer[]> {
396
400
  }
397
401
 
398
402
  export async function broadcastTransaction(signedTransaction: string): Promise<string> {
399
- patchHermesTypedArraysIfNeeded();
400
- const transaction = new StellarSdkTransaction(signedTransaction, Networks.PUBLIC);
401
- // Immediately restore
402
- unpatchHermesTypedArrays();
403
- const res = await getServer().submitTransaction(transaction, {
404
- skipMemoRequiredCheck: true,
405
- });
406
- return res.hash;
403
+ try {
404
+ patchHermesTypedArraysIfNeeded();
405
+ const transaction = new StellarSdkTransaction(signedTransaction, Networks.PUBLIC);
406
+
407
+ const res = await getServer().submitTransaction(transaction, {
408
+ skipMemoRequiredCheck: true,
409
+ });
410
+ return res.hash;
411
+ } finally {
412
+ // Restore
413
+ unpatchHermesTypedArrays();
414
+ }
407
415
  }
408
416
 
409
417
  export async function loadAccount(addr: string): Promise<AccountRecord | null> {
@@ -10,7 +10,4 @@ export {
10
10
  getLastBlock,
11
11
  getRecipientAccount,
12
12
  loadAccount,
13
- BASE_RESERVE,
14
- BASE_RESERVE_MIN_COUNT,
15
- MIN_BALANCE,
16
13
  } from "./horizon";
@@ -7,24 +7,15 @@ import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
7
7
  import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies";
8
8
  import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies/parseCurrencyUnit";
9
9
  import { Horizon } from "@stellar/stellar-sdk";
10
- import { BASE_RESERVE, BASE_RESERVE_MIN_COUNT, fetchBaseFee } from "./horizon";
11
10
  import type { BalanceAsset, RawOperation, StellarMemo, StellarOperation } from "../types";
12
11
  import BigNumber from "bignumber.js";
13
12
 
14
13
  const currency = getCryptoCurrencyById("stellar");
15
14
 
16
- export async function getAccountSpendableBalance(
17
- balance: BigNumber,
18
- account: Horizon.ServerApi.AccountRecord,
19
- ): Promise<BigNumber> {
20
- const minimumBalance = getMinimumBalance(account);
21
- const { recommendedFee } = await fetchBaseFee();
22
- return BigNumber.max(balance.minus(minimumBalance).minus(recommendedFee), 0);
23
- }
24
-
25
- const getMinimumBalance = (account: Horizon.ServerApi.AccountRecord): BigNumber => {
26
- return parseCurrencyUnit(currency.units[0], getReservedBalance(account).toString());
27
- };
15
+ // Constants
16
+ const BASE_RESERVE_MIN_COUNT = 2;
17
+ export const BASE_RESERVE = 0.5;
18
+ export const MIN_BALANCE = 1;
28
19
 
29
20
  export function getReservedBalance(account: Horizon.ServerApi.AccountRecord): BigNumber {
30
21
  const numOfSponsoringEntries = Number(account.num_sponsoring);
package/src/polyfill.ts CHANGED
@@ -22,7 +22,10 @@ export function patchHermesTypedArraysIfNeeded() {
22
22
  if (global.HermesInternal && !hermesTypedArrayPatched) {
23
23
  try {
24
24
  log("coin:stellar", "🔵 Patching TypedArray because of Hermes bug");
25
- require("@exodus/patch-broken-hermes-typed-arrays");
25
+ // Only require the module when we're actually running on Hermes
26
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
27
+ const fixHermesTypedArrayBug = require("@exodus/patch-broken-hermes-typed-arrays");
28
+ fixHermesTypedArrayBug();
26
29
  hermesTypedArrayPatched = true;
27
30
  } catch (e) {
28
31
  log("coin:stellar", "Failed to patch typed arrays");
@@ -9,6 +9,7 @@ import type {
9
9
  TransactionStatusCommon,
10
10
  TransactionStatusCommonRaw,
11
11
  } from "@ledgerhq/types-live";
12
+ import { FeeEstimation } from "@ledgerhq/coin-framework/api/types";
12
13
 
13
14
  export type NetworkInfo = {
14
15
  family: "stellar";
@@ -52,6 +53,7 @@ export type StellarTransactionMode = "send" | "changeTrust";
52
53
  export type Transaction = TransactionCommon & {
53
54
  family: "stellar";
54
55
  networkInfo?: NetworkInfo | null | undefined;
56
+ customFees?: FeeEstimation;
55
57
  fees?: BigNumber | null;
56
58
  baseReserve?: BigNumber | null;
57
59
  memoType?: string | null;