@ledgerhq/coin-stellar 6.26.0 → 6.26.1

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 (271) hide show
  1. package/.oxlintrc.json +7 -0
  2. package/CHANGELOG.md +6 -0
  3. package/LICENSE +194 -0
  4. package/knip.json +12 -0
  5. package/lib/api/index.d.ts +5 -5
  6. package/lib/api/index.d.ts.map +1 -1
  7. package/lib/api/index.js +12 -10
  8. package/lib/api/index.js.map +1 -1
  9. package/lib/config.d.ts +1 -1
  10. package/lib/config.d.ts.map +1 -1
  11. package/lib/config.js +2 -0
  12. package/lib/config.js.map +1 -1
  13. package/lib/constants.d.ts.map +1 -1
  14. package/lib/constants.js +3 -1
  15. package/lib/constants.js.map +1 -1
  16. package/lib/logic/broadcast.d.ts.map +1 -1
  17. package/lib/logic/broadcast.js +2 -0
  18. package/lib/logic/broadcast.js.map +1 -1
  19. package/lib/logic/combine.d.ts +1 -1
  20. package/lib/logic/combine.d.ts.map +1 -1
  21. package/lib/logic/combine.js +2 -0
  22. package/lib/logic/combine.js.map +1 -1
  23. package/lib/logic/common.d.ts +1 -1
  24. package/lib/logic/common.d.ts.map +1 -1
  25. package/lib/logic/common.js +4 -2
  26. package/lib/logic/common.js.map +1 -1
  27. package/lib/logic/craftTransaction.d.ts +1 -1
  28. package/lib/logic/craftTransaction.d.ts.map +1 -1
  29. package/lib/logic/craftTransaction.js +10 -8
  30. package/lib/logic/craftTransaction.js.map +1 -1
  31. package/lib/logic/estimateFees.d.ts.map +1 -1
  32. package/lib/logic/estimateFees.js +2 -0
  33. package/lib/logic/estimateFees.js.map +1 -1
  34. package/lib/logic/getBalance.d.ts +1 -1
  35. package/lib/logic/getBalance.d.ts.map +1 -1
  36. package/lib/logic/getBalance.js +4 -2
  37. package/lib/logic/getBalance.js.map +1 -1
  38. package/lib/logic/getBlock.d.ts +1 -1
  39. package/lib/logic/getBlock.d.ts.map +1 -1
  40. package/lib/logic/getBlock.js +39 -34
  41. package/lib/logic/getBlock.js.map +1 -1
  42. package/lib/logic/getBlockInfo.d.ts +1 -1
  43. package/lib/logic/getBlockInfo.d.ts.map +1 -1
  44. package/lib/logic/getBlockInfo.js +2 -0
  45. package/lib/logic/getBlockInfo.js.map +1 -1
  46. package/lib/logic/index.d.ts +12 -12
  47. package/lib/logic/index.d.ts.map +1 -1
  48. package/lib/logic/index.js +2 -0
  49. package/lib/logic/index.js.map +1 -1
  50. package/lib/logic/lastBlock.d.ts +1 -1
  51. package/lib/logic/lastBlock.d.ts.map +1 -1
  52. package/lib/logic/lastBlock.js +2 -0
  53. package/lib/logic/lastBlock.js.map +1 -1
  54. package/lib/logic/listOperations.d.ts +1 -1
  55. package/lib/logic/listOperations.d.ts.map +1 -1
  56. package/lib/logic/listOperations.js +6 -4
  57. package/lib/logic/listOperations.js.map +1 -1
  58. package/lib/logic/sdkWrapper.d.ts +1 -1
  59. package/lib/logic/sdkWrapper.d.ts.map +1 -1
  60. package/lib/logic/sdkWrapper.js +2 -0
  61. package/lib/logic/sdkWrapper.js.map +1 -1
  62. package/lib/logic/utils.d.ts.map +1 -1
  63. package/lib/logic/utils.js +4 -2
  64. package/lib/logic/utils.js.map +1 -1
  65. package/lib/logic/validateAddress.d.ts +1 -1
  66. package/lib/logic/validateAddress.d.ts.map +1 -1
  67. package/lib/logic/validateAddress.js +2 -0
  68. package/lib/logic/validateAddress.js.map +1 -1
  69. package/lib/logic/validateIntent.d.ts +2 -2
  70. package/lib/logic/validateIntent.d.ts.map +1 -1
  71. package/lib/logic/validateIntent.js +21 -19
  72. package/lib/logic/validateIntent.js.map +1 -1
  73. package/lib/logic/validateMemo.d.ts +1 -1
  74. package/lib/logic/validateMemo.d.ts.map +1 -1
  75. package/lib/logic/validateMemo.js +7 -5
  76. package/lib/logic/validateMemo.js.map +1 -1
  77. package/lib/network/horizon.d.ts +6 -7
  78. package/lib/network/horizon.d.ts.map +1 -1
  79. package/lib/network/horizon.js +71 -68
  80. package/lib/network/horizon.js.map +1 -1
  81. package/lib/network/horizonLedgerErrors.d.ts.map +1 -1
  82. package/lib/network/horizonLedgerErrors.js +5 -3
  83. package/lib/network/horizonLedgerErrors.js.map +1 -1
  84. package/lib/network/index.d.ts +1 -1
  85. package/lib/network/index.d.ts.map +1 -1
  86. package/lib/network/index.js +2 -0
  87. package/lib/network/index.js.map +1 -1
  88. package/lib/network/serialization.d.ts +6 -4
  89. package/lib/network/serialization.d.ts.map +1 -1
  90. package/lib/network/serialization.js +95 -91
  91. package/lib/network/serialization.js.map +1 -1
  92. package/lib/polyfill.d.ts.map +1 -1
  93. package/lib/polyfill.js +6 -4
  94. package/lib/polyfill.js.map +1 -1
  95. package/lib/supportedFeatures.d.ts +1 -1
  96. package/lib/supportedFeatures.d.ts.map +1 -1
  97. package/lib/supportedFeatures.js +3 -1
  98. package/lib/supportedFeatures.js.map +1 -1
  99. package/lib/types/errors.d.ts.map +1 -1
  100. package/lib/types/errors.js +14 -12
  101. package/lib/types/errors.js.map +1 -1
  102. package/lib/types/index.d.ts +2 -2
  103. package/lib/types/index.d.ts.map +1 -1
  104. package/lib/types/index.js +2 -0
  105. package/lib/types/index.js.map +1 -1
  106. package/lib/types/model.d.ts +6 -6
  107. package/lib/types/model.d.ts.map +1 -1
  108. package/lib/types/model.js +7 -5
  109. package/lib/types/model.js.map +1 -1
  110. package/lib-es/api/index.d.ts +5 -5
  111. package/lib-es/api/index.d.ts.map +1 -1
  112. package/lib-es/api/index.js +20 -18
  113. package/lib-es/api/index.js.map +1 -1
  114. package/lib-es/config.d.ts +1 -1
  115. package/lib-es/config.d.ts.map +1 -1
  116. package/lib-es/config.js +3 -1
  117. package/lib-es/config.js.map +1 -1
  118. package/lib-es/constants.d.ts.map +1 -1
  119. package/lib-es/constants.js +3 -1
  120. package/lib-es/constants.js.map +1 -1
  121. package/lib-es/logic/broadcast.d.ts.map +1 -1
  122. package/lib-es/logic/broadcast.js +3 -1
  123. package/lib-es/logic/broadcast.js.map +1 -1
  124. package/lib-es/logic/combine.d.ts +1 -1
  125. package/lib-es/logic/combine.d.ts.map +1 -1
  126. package/lib-es/logic/combine.js +3 -1
  127. package/lib-es/logic/combine.js.map +1 -1
  128. package/lib-es/logic/common.d.ts +1 -1
  129. package/lib-es/logic/common.d.ts.map +1 -1
  130. package/lib-es/logic/common.js +5 -3
  131. package/lib-es/logic/common.js.map +1 -1
  132. package/lib-es/logic/craftTransaction.d.ts +1 -1
  133. package/lib-es/logic/craftTransaction.d.ts.map +1 -1
  134. package/lib-es/logic/craftTransaction.js +15 -13
  135. package/lib-es/logic/craftTransaction.js.map +1 -1
  136. package/lib-es/logic/estimateFees.d.ts.map +1 -1
  137. package/lib-es/logic/estimateFees.js +3 -1
  138. package/lib-es/logic/estimateFees.js.map +1 -1
  139. package/lib-es/logic/getBalance.d.ts +1 -1
  140. package/lib-es/logic/getBalance.d.ts.map +1 -1
  141. package/lib-es/logic/getBalance.js +5 -3
  142. package/lib-es/logic/getBalance.js.map +1 -1
  143. package/lib-es/logic/getBlock.d.ts +1 -1
  144. package/lib-es/logic/getBlock.d.ts.map +1 -1
  145. package/lib-es/logic/getBlock.js +42 -37
  146. package/lib-es/logic/getBlock.js.map +1 -1
  147. package/lib-es/logic/getBlockInfo.d.ts +1 -1
  148. package/lib-es/logic/getBlockInfo.d.ts.map +1 -1
  149. package/lib-es/logic/getBlockInfo.js +3 -1
  150. package/lib-es/logic/getBlockInfo.js.map +1 -1
  151. package/lib-es/logic/index.d.ts +12 -12
  152. package/lib-es/logic/index.d.ts.map +1 -1
  153. package/lib-es/logic/index.js +14 -12
  154. package/lib-es/logic/index.js.map +1 -1
  155. package/lib-es/logic/lastBlock.d.ts +1 -1
  156. package/lib-es/logic/lastBlock.d.ts.map +1 -1
  157. package/lib-es/logic/lastBlock.js +3 -1
  158. package/lib-es/logic/lastBlock.js.map +1 -1
  159. package/lib-es/logic/listOperations.d.ts +1 -1
  160. package/lib-es/logic/listOperations.d.ts.map +1 -1
  161. package/lib-es/logic/listOperations.js +7 -5
  162. package/lib-es/logic/listOperations.js.map +1 -1
  163. package/lib-es/logic/sdkWrapper.d.ts +1 -1
  164. package/lib-es/logic/sdkWrapper.d.ts.map +1 -1
  165. package/lib-es/logic/sdkWrapper.js +4 -2
  166. package/lib-es/logic/sdkWrapper.js.map +1 -1
  167. package/lib-es/logic/utils.d.ts.map +1 -1
  168. package/lib-es/logic/utils.js +6 -4
  169. package/lib-es/logic/utils.js.map +1 -1
  170. package/lib-es/logic/validateAddress.d.ts +1 -1
  171. package/lib-es/logic/validateAddress.d.ts.map +1 -1
  172. package/lib-es/logic/validateAddress.js +3 -1
  173. package/lib-es/logic/validateAddress.js.map +1 -1
  174. package/lib-es/logic/validateIntent.d.ts +2 -2
  175. package/lib-es/logic/validateIntent.d.ts.map +1 -1
  176. package/lib-es/logic/validateIntent.js +30 -28
  177. package/lib-es/logic/validateIntent.js.map +1 -1
  178. package/lib-es/logic/validateMemo.d.ts +1 -1
  179. package/lib-es/logic/validateMemo.d.ts.map +1 -1
  180. package/lib-es/logic/validateMemo.js +8 -6
  181. package/lib-es/logic/validateMemo.js.map +1 -1
  182. package/lib-es/network/horizon.d.ts +6 -7
  183. package/lib-es/network/horizon.d.ts.map +1 -1
  184. package/lib-es/network/horizon.js +80 -77
  185. package/lib-es/network/horizon.js.map +1 -1
  186. package/lib-es/network/horizonLedgerErrors.d.ts.map +1 -1
  187. package/lib-es/network/horizonLedgerErrors.js +7 -5
  188. package/lib-es/network/horizonLedgerErrors.js.map +1 -1
  189. package/lib-es/network/index.d.ts +1 -1
  190. package/lib-es/network/index.d.ts.map +1 -1
  191. package/lib-es/network/index.js +3 -1
  192. package/lib-es/network/index.js.map +1 -1
  193. package/lib-es/network/serialization.d.ts +6 -4
  194. package/lib-es/network/serialization.d.ts.map +1 -1
  195. package/lib-es/network/serialization.js +97 -95
  196. package/lib-es/network/serialization.js.map +1 -1
  197. package/lib-es/polyfill.d.ts.map +1 -1
  198. package/lib-es/polyfill.js +7 -5
  199. package/lib-es/polyfill.js.map +1 -1
  200. package/lib-es/supportedFeatures.d.ts +1 -1
  201. package/lib-es/supportedFeatures.d.ts.map +1 -1
  202. package/lib-es/supportedFeatures.js +3 -1
  203. package/lib-es/supportedFeatures.js.map +1 -1
  204. package/lib-es/types/errors.d.ts.map +1 -1
  205. package/lib-es/types/errors.js +15 -13
  206. package/lib-es/types/errors.js.map +1 -1
  207. package/lib-es/types/index.d.ts +2 -2
  208. package/lib-es/types/index.d.ts.map +1 -1
  209. package/lib-es/types/index.js +4 -2
  210. package/lib-es/types/index.js.map +1 -1
  211. package/lib-es/types/model.d.ts +6 -6
  212. package/lib-es/types/model.d.ts.map +1 -1
  213. package/lib-es/types/model.js +7 -5
  214. package/lib-es/types/model.js.map +1 -1
  215. package/package.json +13 -16
  216. package/src/api/index.integ.test.ts +165 -162
  217. package/src/api/index.test.ts +106 -103
  218. package/src/api/index.ts +49 -46
  219. package/src/api/index_error.test.ts +48 -25
  220. package/src/config.ts +15 -12
  221. package/src/constants.ts +4 -1
  222. package/src/globals.d.ts +7 -5
  223. package/src/logic/broadcast.integ.test.ts +22 -19
  224. package/src/logic/broadcast.ts +5 -2
  225. package/src/logic/combine.test.ts +18 -15
  226. package/src/logic/combine.ts +8 -5
  227. package/src/logic/common.ts +9 -6
  228. package/src/logic/craftTransaction.test.ts +76 -75
  229. package/src/logic/craftTransaction.ts +46 -43
  230. package/src/logic/estimateFees.ts +6 -3
  231. package/src/logic/getBalance.test.ts +31 -28
  232. package/src/logic/getBalance.ts +13 -10
  233. package/src/logic/getBlock.integ.test.ts +40 -39
  234. package/src/logic/getBlock.test.ts +462 -415
  235. package/src/logic/getBlock.ts +142 -127
  236. package/src/logic/getBlockInfo.test.ts +72 -69
  237. package/src/logic/getBlockInfo.ts +15 -12
  238. package/src/logic/index.ts +15 -12
  239. package/src/logic/lastBlock.ts +6 -3
  240. package/src/logic/listOperations.ts +14 -11
  241. package/src/logic/listOperations.unit.test.ts +212 -209
  242. package/src/logic/networkWrappers.test.ts +32 -29
  243. package/src/logic/sdkWrapper.ts +19 -16
  244. package/src/logic/utils.ts +13 -10
  245. package/src/logic/validateAddress.test.ts +21 -18
  246. package/src/logic/validateAddress.ts +7 -4
  247. package/src/logic/validateIntent.test.ts +62 -59
  248. package/src/logic/validateIntent.ts +87 -84
  249. package/src/logic/validateMemo.test.ts +50 -47
  250. package/src/logic/validateMemo.ts +16 -13
  251. package/src/network/broadcastTransaction.test.ts +60 -60
  252. package/src/network/fetchLedgerOperations.unit.test.ts +155 -0
  253. package/src/network/horizon.integ.test.ts +45 -42
  254. package/src/network/horizon.test.ts +17 -14
  255. package/src/network/horizon.ts +279 -281
  256. package/src/network/horizon.unit.test.ts +238 -237
  257. package/src/network/horizonLedgerErrors.test.ts +81 -78
  258. package/src/network/horizonLedgerErrors.ts +22 -19
  259. package/src/network/index.ts +4 -1
  260. package/src/network/registerHorizonInterceptors.test.ts +163 -160
  261. package/src/network/serialization.ts +232 -227
  262. package/src/network/serialization.unit.test.ts +801 -798
  263. package/src/polyfill.ts +18 -15
  264. package/src/supportedFeatures.ts +6 -3
  265. package/src/types/errors.ts +33 -32
  266. package/src/types/index.ts +5 -2
  267. package/src/types/model.ts +55 -52
  268. package/tsconfig.json +1 -1
  269. package/.unimportedrc.json +0 -17
  270. package/LICENSE.txt +0 -21
  271. package/src/network/fetchLedgerOperations.nock.unit.test.ts +0 -156
@@ -1,10 +1,13 @@
1
+ // SPDX-FileCopyrightText: © 2026 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
1
4
  import {
2
5
  TransactionValidation,
3
6
  TransactionIntent,
4
7
  FeeEstimation,
5
8
  Balance,
6
- } from "@ledgerhq/coin-module-framework/api/types";
7
- import { formatCurrencyUnit } from "@ledgerhq/coin-module-framework/currencies/index";
9
+ } from '@ledgerhq/coin-module-framework/api/types'
10
+ import { formatCurrencyUnit } from '@ledgerhq/coin-module-framework/currencies/index'
8
11
  import {
9
12
  AmountRequired,
10
13
  InvalidAddress,
@@ -13,11 +16,11 @@ import {
13
16
  NotEnoughBalanceBecauseDestinationNotCreated,
14
17
  NotEnoughSpendableBalance,
15
18
  RecipientRequired,
16
- } from "@ledgerhq/errors";
17
- import BigNumber from "bignumber.js";
18
- import { fetchAccountNetworkInfo, getRecipientAccount } from "../network";
19
- import { fetchAccount } from "../network/horizon";
20
- import { BASE_RESERVE, MIN_BALANCE } from "../network/serialization";
19
+ } from '@ledgerhq/errors'
20
+ import BigNumber from 'bignumber.js'
21
+ import { fetchAccountNetworkInfo, getRecipientAccount } from '../network'
22
+ import { fetchAccount } from '../network/horizon'
23
+ import { BASE_RESERVE, MIN_BALANCE } from '../network/serialization'
21
24
  import {
22
25
  StellarAssetNotAccepted,
23
26
  StellarAssetNotFound,
@@ -30,108 +33,108 @@ import {
30
33
  StellarNotEnoughNativeBalanceToAddTrustline,
31
34
  StellarSourceHasMultiSign,
32
35
  StellarWrongMemoFormat,
33
- } from "../types";
34
- import { isAccountMultiSign, isAddressValid } from "./utils";
35
- import { validateMemo } from "./validateMemo";
36
+ } from '../types'
37
+ import { isAccountMultiSign, isAddressValid } from './utils'
38
+ import { validateMemo } from './validateMemo'
36
39
 
37
40
  export const validateIntent = async (
38
41
  transactionIntent: TransactionIntent<StellarMemo>,
39
42
  balances: Balance[],
40
- customFees?: FeeEstimation,
43
+ customFees?: FeeEstimation
41
44
  ): Promise<TransactionValidation> => {
42
- const errors: Record<string, Error> = {};
43
- const warnings: Record<string, Error> = {};
44
- const useAllAmount = !!transactionIntent.useAllAmount;
45
+ const errors: Record<string, Error> = {}
46
+ const warnings: Record<string, Error> = {}
47
+ const useAllAmount = !!transactionIntent.useAllAmount
45
48
 
46
- const destinationNotExistMessage = new NotEnoughBalanceBecauseDestinationNotCreated("", {
49
+ const destinationNotExistMessage = new NotEnoughBalanceBecauseDestinationNotCreated('', {
47
50
  minimalAmount: `${MIN_BALANCE} XLM`,
48
- });
51
+ })
49
52
 
50
- const { spendableBalance, balance } = await fetchAccount(transactionIntent.sender);
51
- const networkInfo = await fetchAccountNetworkInfo(transactionIntent.sender);
53
+ const { spendableBalance, balance } = await fetchAccount(transactionIntent.sender)
54
+ const networkInfo = await fetchAccountNetworkInfo(transactionIntent.sender)
52
55
 
53
- const estimatedFees = customFees?.value ?? 0n;
56
+ const estimatedFees = customFees?.value ?? 0n
54
57
  const baseReserve = networkInfo.baseReserve
55
58
  ? BigInt(Math.round(networkInfo.baseReserve.toNumber() * 10)) / 10n
56
- : 0n;
57
- const isAssetPayment = transactionIntent.asset.type !== "native";
58
- const nativeBalance = BigInt(balance.toString());
59
- const nativeAmountAvailable = BigInt(spendableBalance.toString()) - estimatedFees;
60
- let amount = 0n;
61
- let maxAmount = 0n;
62
- let totalSpent = 0n;
59
+ : 0n
60
+ const isAssetPayment = transactionIntent.asset.type !== 'native'
61
+ const nativeBalance = BigInt(balance.toString())
62
+ const nativeAmountAvailable = BigInt(spendableBalance.toString()) - estimatedFees
63
+ let amount = 0n
64
+ let maxAmount = 0n
65
+ let totalSpent = 0n
63
66
 
64
67
  // Enough native balance to cover transaction (with required reserve + fees)
65
68
  if (!errors.amount && nativeAmountAvailable < 0) {
66
- errors.amount = new StellarNotEnoughNativeBalance();
69
+ errors.amount = new StellarNotEnoughNativeBalance()
67
70
  }
68
71
 
69
- const networkInfoBaseFee = BigInt(networkInfo.baseFee.toString() || "0");
70
- const networkFees = BigInt(networkInfo.fees.toString() || "0");
72
+ const networkInfoBaseFee = BigInt(networkInfo.baseFee.toString() || '0')
73
+ const networkFees = BigInt(networkInfo.fees.toString() || '0')
71
74
  // Entered fee is smaller than base fee
72
75
  if (estimatedFees < networkInfoBaseFee) {
73
- errors.transaction = new StellarFeeSmallerThanBase();
76
+ errors.transaction = new StellarFeeSmallerThanBase()
74
77
  // Entered fee is smaller than recommended
75
78
  } else if (estimatedFees < networkFees) {
76
- warnings.transaction = new StellarFeeSmallerThanRecommended();
79
+ warnings.transaction = new StellarFeeSmallerThanRecommended()
77
80
  }
78
81
 
79
82
  // Operation specific checks
80
- if (transactionIntent.type === "changeTrust") {
83
+ if (transactionIntent.type === 'changeTrust') {
81
84
  // Check asset provided
82
85
  if (
83
- transactionIntent.asset.type !== "native" &&
84
- (("assetReference" in transactionIntent.asset && !transactionIntent.asset.assetReference) ||
85
- ("assetOwner" in transactionIntent.asset && !transactionIntent.asset.assetOwner))
86
+ transactionIntent.asset.type !== 'native' &&
87
+ (('assetReference' in transactionIntent.asset && !transactionIntent.asset.assetReference) ||
88
+ ('assetOwner' in transactionIntent.asset && !transactionIntent.asset.assetOwner))
86
89
  ) {
87
90
  // This is unlikely
88
- errors.transaction = new StellarAssetRequired("");
91
+ errors.transaction = new StellarAssetRequired('')
89
92
  }
90
93
 
91
94
  // Has enough native balance to add new trustline
92
95
  // NOTE: need to do this as BASE_RESERVE is 0.5
93
- const SCALE = 10n;
94
- const scaledNative = nativeAmountAvailable * SCALE;
95
- const scaledBaseReserve = BigInt(BASE_RESERVE * 10); // = 5n
96
+ const SCALE = 10n
97
+ const scaledNative = nativeAmountAvailable * SCALE
98
+ const scaledBaseReserve = BigInt(BASE_RESERVE * 10) // = 5n
96
99
 
97
100
  if (scaledNative - scaledBaseReserve < 0n) {
98
- errors.amount = new StellarNotEnoughNativeBalanceToAddTrustline();
101
+ errors.amount = new StellarNotEnoughNativeBalanceToAddTrustline()
99
102
  }
100
103
  } else {
101
104
  // Payment
102
105
  // Check recipient address
103
106
  if (!transactionIntent.recipient) {
104
- errors.recipient = new RecipientRequired("");
107
+ errors.recipient = new RecipientRequired('')
105
108
  } else if (!isAddressValid(transactionIntent.recipient)) {
106
- errors.recipient = new InvalidAddress("", {
107
- currencyName: transactionIntent.asset.name ?? "", // NOTE: before account.currencyName,
108
- });
109
+ errors.recipient = new InvalidAddress('', {
110
+ currencyName: transactionIntent.asset.name ?? '', // NOTE: before account.currencyName,
111
+ })
109
112
  } else if (transactionIntent.sender === transactionIntent.recipient) {
110
- errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource();
113
+ errors.recipient = new InvalidAddressBecauseDestinationIsAlsoSource()
111
114
  }
112
115
 
113
116
  const recipientAccount = await getRecipientAccount({
114
117
  recipient: transactionIntent.recipient,
115
- });
118
+ })
116
119
 
117
120
  // Check recipient account
118
121
  if (!recipientAccount?.id && !errors.recipient && !warnings.recipient) {
119
122
  if (recipientAccount?.isMuxedAccount) {
120
- errors.recipient = new StellarMuxedAccountNotExist();
123
+ errors.recipient = new StellarMuxedAccountNotExist()
121
124
  } else {
122
125
  if (isAssetPayment) {
123
- errors.recipient = destinationNotExistMessage;
126
+ errors.recipient = destinationNotExistMessage
124
127
  } else {
125
- warnings.recipient = destinationNotExistMessage;
128
+ warnings.recipient = destinationNotExistMessage
126
129
  }
127
130
  }
128
131
  }
129
132
 
130
133
  // Asset payment
131
134
  if (isAssetPayment) {
132
- const asset = transactionIntent.asset;
133
- if (asset.type === "native" || (!("assetReference" in asset) && !("assetOwner" in asset))) {
134
- throw new StellarAssetNotFound();
135
+ const asset = transactionIntent.asset
136
+ if (asset.type === 'native' || (!('assetReference' in asset) && !('assetOwner' in asset))) {
137
+ throw new StellarAssetNotFound()
135
138
  }
136
139
 
137
140
  // Check recipient account accepts asset
@@ -141,47 +144,47 @@ export const validateIntent = async (
141
144
  !warnings.recipient &&
142
145
  !recipientAccount.assetIds.includes(`${asset.assetReference}:${asset.assetOwner}`)
143
146
  ) {
144
- errors.recipient = new StellarAssetNotAccepted("", {
147
+ errors.recipient = new StellarAssetNotAccepted('', {
145
148
  assetCode: asset.assetReference,
146
- });
149
+ })
147
150
  }
148
151
 
149
152
  const assetBalance = balances.find(
150
- b =>
151
- b.asset.type !== "native" &&
152
- "assetReference" in b.asset &&
153
+ (b) =>
154
+ b.asset.type !== 'native' &&
155
+ 'assetReference' in b.asset &&
153
156
  b.asset.assetReference === asset.assetReference &&
154
- "assetOwner" in b.asset &&
155
- b.asset.assetOwner === asset.assetOwner,
156
- );
157
+ 'assetOwner' in b.asset &&
158
+ b.asset.assetOwner === asset.assetOwner
159
+ )
157
160
 
158
161
  if (!assetBalance) {
159
162
  // This is unlikely
160
- throw new StellarAssetNotFound();
163
+ throw new StellarAssetNotFound()
161
164
  }
162
- const assetSpendableBalance = assetBalance.value - (assetBalance?.locked || 0n);
165
+ const assetSpendableBalance = assetBalance.value - (assetBalance?.locked || 0n)
163
166
 
164
- maxAmount = assetSpendableBalance || assetBalance.value;
165
- amount = useAllAmount ? maxAmount : transactionIntent.amount;
166
- totalSpent = amount;
167
+ maxAmount = assetSpendableBalance || assetBalance.value
168
+ amount = useAllAmount ? maxAmount : transactionIntent.amount
169
+ totalSpent = amount
167
170
 
168
171
  if (!errors.amount && amount > assetBalance.value) {
169
- errors.amount = new NotEnoughBalance();
172
+ errors.amount = new NotEnoughBalance()
170
173
  }
171
174
  } else {
172
175
  // Native payment
173
- maxAmount = nativeAmountAvailable;
174
- amount = useAllAmount ? maxAmount : (transactionIntent.amount ?? 0n);
176
+ maxAmount = nativeAmountAvailable
177
+ amount = useAllAmount ? maxAmount : (transactionIntent.amount ?? 0n)
175
178
 
176
179
  if (amount > maxAmount) {
177
- errors.amount = new NotEnoughBalance();
180
+ errors.amount = new NotEnoughBalance()
178
181
  }
179
182
 
180
- totalSpent = useAllAmount ? nativeAmountAvailable : amount + estimatedFees;
183
+ totalSpent = useAllAmount ? nativeAmountAvailable : amount + estimatedFees
181
184
 
182
185
  // Need to send at least 1 XLM to create an account
183
186
  if (!errors.recipient && !recipientAccount?.id && !errors.amount && amount < 10000000n) {
184
- errors.amount = destinationNotExistMessage;
187
+ errors.amount = destinationNotExistMessage
185
188
  }
186
189
  if (totalSpent > nativeBalance - baseReserve) {
187
190
  errors.amount = new NotEnoughSpendableBalance(undefined, {
@@ -192,33 +195,33 @@ export const validateIntent = async (
192
195
  {
193
196
  disableRounding: true,
194
197
  showCode: true,
195
- },
198
+ }
196
199
  )
197
- : "Unknown unit",
198
- });
200
+ : 'Unknown unit',
201
+ })
199
202
  }
200
203
 
201
204
  if (!errors.recipient && !errors.amount && (amount < 0n || totalSpent > nativeBalance)) {
202
- errors.amount = new NotEnoughBalance();
203
- totalSpent = 0n;
204
- amount = 0n;
205
+ errors.amount = new NotEnoughBalance()
206
+ totalSpent = 0n
207
+ amount = 0n
205
208
  }
206
209
  }
207
210
 
208
211
  if (amount === 0n) {
209
- errors.amount = new AmountRequired();
212
+ errors.amount = new AmountRequired()
210
213
  }
211
214
  }
212
215
 
213
216
  if (await isAccountMultiSign(transactionIntent.sender)) {
214
- errors.recipient = new StellarSourceHasMultiSign();
217
+ errors.recipient = new StellarSourceHasMultiSign()
215
218
  }
216
219
 
217
220
  if (
218
- transactionIntent?.memo?.type !== "NO_MEMO" &&
221
+ transactionIntent?.memo?.type !== 'NO_MEMO' &&
219
222
  !validateMemo(transactionIntent?.memo?.value, transactionIntent?.memo?.type)
220
223
  ) {
221
- errors.transaction = new StellarWrongMemoFormat();
224
+ errors.transaction = new StellarWrongMemoFormat()
222
225
  }
223
226
 
224
227
  return {
@@ -227,7 +230,7 @@ export const validateIntent = async (
227
230
  estimatedFees,
228
231
  amount,
229
232
  totalSpent,
230
- };
231
- };
233
+ }
234
+ }
232
235
 
233
- export default validateIntent;
236
+ export default validateIntent
@@ -1,61 +1,64 @@
1
- import { MEMO_HASH_FIXED_SIZE, MEMO_TEXT_MAXIMUM_SIZE, validateMemo } from "./validateMemo";
1
+ // SPDX-FileCopyrightText: © 2026 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
2
3
 
3
- describe("validateMemo", () => {
4
- it("should return false when type is NO_MEMO but memo is present", () => {
5
- const memo = "memo";
6
- expect(validateMemo(memo, "NO_MEMO")).toEqual(false);
7
- });
4
+ import { MEMO_HASH_FIXED_SIZE, MEMO_TEXT_MAXIMUM_SIZE, validateMemo } from './validateMemo'
8
5
 
9
- it("should return true when type is NO_MEMO and memo is empty", () => {
10
- expect(validateMemo("", "NO_MEMO")).toEqual(true);
11
- });
6
+ describe('validateMemo', () => {
7
+ it('should return false when type is NO_MEMO but memo is present', () => {
8
+ const memo = 'memo'
9
+ expect(validateMemo(memo, 'NO_MEMO')).toEqual(false)
10
+ })
12
11
 
13
- it.each(["a".repeat(MEMO_TEXT_MAXIMUM_SIZE + 1), "a".repeat(MEMO_TEXT_MAXIMUM_SIZE + 2)])(
14
- "should return false when type is MEMO_TEXT but memo exceed maximum allowed size",
12
+ it('should return true when type is NO_MEMO and memo is empty', () => {
13
+ expect(validateMemo('', 'NO_MEMO')).toEqual(true)
14
+ })
15
+
16
+ it.each(['a'.repeat(MEMO_TEXT_MAXIMUM_SIZE + 1), 'a'.repeat(MEMO_TEXT_MAXIMUM_SIZE + 2)])(
17
+ 'should return false when type is MEMO_TEXT but memo exceed maximum allowed size',
15
18
  (memo: string) => {
16
- expect(validateMemo(memo, "MEMO_TEXT")).toEqual(false);
17
- },
18
- );
19
+ expect(validateMemo(memo, 'MEMO_TEXT')).toEqual(false)
20
+ }
21
+ )
19
22
 
20
- it.each(["memo", "a".repeat(MEMO_TEXT_MAXIMUM_SIZE), "a".repeat(MEMO_TEXT_MAXIMUM_SIZE - 1)])(
21
- "should return true when type is MEMO_TEXT and memo is under maximum allowed size",
23
+ it.each(['memo', 'a'.repeat(MEMO_TEXT_MAXIMUM_SIZE), 'a'.repeat(MEMO_TEXT_MAXIMUM_SIZE - 1)])(
24
+ 'should return true when type is MEMO_TEXT and memo is under maximum allowed size',
22
25
  (memo: string) => {
23
- expect(validateMemo(memo, "MEMO_TEXT")).toEqual(true);
24
- },
25
- );
26
+ expect(validateMemo(memo, 'MEMO_TEXT')).toEqual(true)
27
+ }
28
+ )
26
29
 
27
- it("should return false when type is MEMO_ID and memo is NaN", () => {
28
- expect(validateMemo(Number.NaN.toString(), "MEMO_ID")).toEqual(false);
29
- });
30
+ it('should return false when type is MEMO_ID and memo is NaN', () => {
31
+ expect(validateMemo(Number.NaN.toString(), 'MEMO_ID')).toEqual(false)
32
+ })
30
33
 
31
- it.each(["0", "1", "-1", "123.456"])(
32
- "should return true when type is MEMO_ID and memo is a number",
34
+ it.each(['0', '1', '-1', '123.456'])(
35
+ 'should return true when type is MEMO_ID and memo is a number',
33
36
  (memo: string) => {
34
- expect(validateMemo(memo, "MEMO_ID")).toEqual(true);
35
- },
36
- );
37
+ expect(validateMemo(memo, 'MEMO_ID')).toEqual(true)
38
+ }
39
+ )
37
40
 
38
- it.each(["", "a", "a".repeat(MEMO_HASH_FIXED_SIZE + 1), "a".repeat(MEMO_HASH_FIXED_SIZE - 1)])(
39
- "should return false when type is MEMO_HASH and memo has the wrong size",
41
+ it.each(['', 'a', 'a'.repeat(MEMO_HASH_FIXED_SIZE + 1), 'a'.repeat(MEMO_HASH_FIXED_SIZE - 1)])(
42
+ 'should return false when type is MEMO_HASH and memo has the wrong size',
40
43
  (memo: string) => {
41
- expect(validateMemo(memo, "MEMO_HASH")).toEqual(false);
42
- },
43
- );
44
+ expect(validateMemo(memo, 'MEMO_HASH')).toEqual(false)
45
+ }
46
+ )
44
47
 
45
- it("should return true when type is MEMO_HASH and memo has the wrong size", () => {
46
- const memo = "a".repeat(MEMO_HASH_FIXED_SIZE);
47
- expect(validateMemo(memo, "MEMO_HASH")).toEqual(true);
48
- });
48
+ it('should return true when type is MEMO_HASH and memo has the wrong size', () => {
49
+ const memo = 'a'.repeat(MEMO_HASH_FIXED_SIZE)
50
+ expect(validateMemo(memo, 'MEMO_HASH')).toEqual(true)
51
+ })
49
52
 
50
- it.each(["", "a", "a".repeat(MEMO_HASH_FIXED_SIZE + 1), "a".repeat(MEMO_HASH_FIXED_SIZE - 1)])(
51
- "should return false when type is MEMO_RETURN and memo has the wrong size",
53
+ it.each(['', 'a', 'a'.repeat(MEMO_HASH_FIXED_SIZE + 1), 'a'.repeat(MEMO_HASH_FIXED_SIZE - 1)])(
54
+ 'should return false when type is MEMO_RETURN and memo has the wrong size',
52
55
  (memo: string) => {
53
- expect(validateMemo(memo, "MEMO_RETURN")).toEqual(false);
54
- },
55
- );
56
-
57
- it("should return true when type is MEMO_RETURN and memo has the wrong size", () => {
58
- const memo = "a".repeat(MEMO_HASH_FIXED_SIZE);
59
- expect(validateMemo(memo, "MEMO_RETURN")).toEqual(true);
60
- });
61
- });
56
+ expect(validateMemo(memo, 'MEMO_RETURN')).toEqual(false)
57
+ }
58
+ )
59
+
60
+ it('should return true when type is MEMO_RETURN and memo has the wrong size', () => {
61
+ const memo = 'a'.repeat(MEMO_HASH_FIXED_SIZE)
62
+ expect(validateMemo(memo, 'MEMO_RETURN')).toEqual(true)
63
+ })
64
+ })
@@ -1,19 +1,22 @@
1
- import BigNumber from "bignumber.js";
2
- import { StellarMemoKind } from "../types/model";
1
+ // SPDX-FileCopyrightText: © 2026 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- export const MEMO_TEXT_MAXIMUM_SIZE = 28;
5
- export const MEMO_HASH_FIXED_SIZE = 64;
4
+ import BigNumber from 'bignumber.js'
5
+ import { StellarMemoKind } from '../types/model'
6
+
7
+ export const MEMO_TEXT_MAXIMUM_SIZE = 28
8
+ export const MEMO_HASH_FIXED_SIZE = 64
6
9
 
7
10
  export function validateMemo(memo: string, type: StellarMemoKind): boolean {
8
11
  switch (type) {
9
- case "NO_MEMO":
10
- return !memo || memo.length === 0;
11
- case "MEMO_TEXT":
12
- return memo.length <= MEMO_TEXT_MAXIMUM_SIZE;
13
- case "MEMO_ID":
14
- return !new BigNumber(memo.toString()).isNaN();
15
- case "MEMO_HASH":
16
- case "MEMO_RETURN":
17
- return memo.length === 64;
12
+ case 'NO_MEMO':
13
+ return !memo || memo.length === 0
14
+ case 'MEMO_TEXT':
15
+ return memo.length <= MEMO_TEXT_MAXIMUM_SIZE
16
+ case 'MEMO_ID':
17
+ return !new BigNumber(memo.toString()).isNaN()
18
+ case 'MEMO_HASH':
19
+ case 'MEMO_RETURN':
20
+ return memo.length === 64
18
21
  }
19
22
  }
@@ -1,98 +1,98 @@
1
- import { BadResponseError, Horizon } from "@stellar/stellar-sdk";
2
- import coinConfig from "../config";
3
- import { broadcastTransaction } from "./horizon";
1
+ // SPDX-FileCopyrightText: © 2026 LEDGER SAS
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { BadResponseError, Horizon } from '@stellar/stellar-sdk'
5
+ import coinConfig from '../config'
6
+ import { broadcastTransaction } from './horizon'
4
7
 
5
8
  /** Parses on mainnet; used only so `submitTransaction` is reached (mocked). */
6
9
  const tx =
7
- "AAAAAgAAAABRUCgFba+DTbei2ifpyYt5w2Hh0VyZ+X9fayjIDne7YAAAAGQCkDOGAAAABQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAACEIuPfWXgM8WhyqjrpFdIcGV1SUVhMzPUm4YspNHF60QAAAAAAAAAAALkd2QAAAAAAAAABDne7YAAAAEAASzsT/yDIfCfEDstkfnznXjiN7rNd7PkKQEn+rRIFm9EHoirGfHipWoBdYMrc6ixQD/0y0of1piSid8TLiFAB";
10
+ 'AAAAAgAAAABRUCgFba+DTbei2ifpyYt5w2Hh0VyZ+X9fayjIDne7YAAAAGQCkDOGAAAABQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAACEIuPfWXgM8WhyqjrpFdIcGV1SUVhMzPUm4YspNHF60QAAAAAAAAAAALkd2QAAAAAAAAABDne7YAAAAEAASzsT/yDIfCfEDstkfnznXjiN7rNd7PkKQEn+rRIFm9EHoirGfHipWoBdYMrc6ixQD/0y0of1piSid8TLiFAB'
8
11
 
9
12
  function horizonTransactionFailedExtras() {
10
13
  return {
11
- type: "https://stellar.org/horizon-errors/transaction_failed",
12
- title: "Transaction Failed",
14
+ type: 'https://stellar.org/horizon-errors/transaction_failed',
15
+ title: 'Transaction Failed',
13
16
  status: 400,
14
- detail: "The transaction failed when submitted to the stellar network.",
17
+ detail: 'The transaction failed when submitted to the stellar network.',
15
18
  extras: {
16
19
  result_codes: {
17
- transaction: "tx_failed" as const,
18
- operations: ["op_no_account"] as string[],
20
+ transaction: 'tx_failed' as const,
21
+ operations: ['op_no_account'] as string[],
19
22
  },
20
- result_xdr: "AAAAAAAAAGT////4AAAAAA==",
23
+ result_xdr: 'AAAAAAAAAGT////4AAAAAA==',
21
24
  envelope_xdr: tx,
22
25
  },
23
- };
26
+ }
24
27
  }
25
28
 
26
- describe("broadcastTransaction", () => {
27
- let submitTransactionSpy: jest.SpiedFunction<Horizon.Server["submitTransaction"]>;
29
+ describe('broadcastTransaction', () => {
30
+ let submitTransactionSpy: jest.SpiedFunction<Horizon.Server['submitTransaction']>
28
31
 
29
32
  beforeAll(() => {
30
33
  coinConfig.setCoinConfig(() => ({
31
- explorer: { url: "https://horizon-broadcast-unit-test.invalid/" },
32
- status: { type: "active" },
33
- }));
34
- });
34
+ explorer: { url: 'https://horizon-broadcast-unit-test.invalid/' },
35
+ status: { type: 'active' },
36
+ }))
37
+ })
35
38
 
36
39
  beforeEach(() => {
37
40
  submitTransactionSpy = jest
38
- .spyOn(Horizon.Server.prototype, "submitTransaction")
39
- .mockRejectedValue(new Error("mock not configured"));
40
- });
41
+ .spyOn(Horizon.Server.prototype, 'submitTransaction')
42
+ .mockRejectedValue(new Error('mock not configured'))
43
+ })
41
44
 
42
45
  afterEach(() => {
43
- submitTransactionSpy.mockRestore();
44
- });
46
+ submitTransactionSpy.mockRestore()
47
+ })
45
48
 
46
- it("maps BadResponseError with Horizon body on error.response to StellarBroadcastFailedError", async () => {
47
- const body = horizonTransactionFailedExtras();
49
+ it('maps BadResponseError with Horizon body on error.response to StellarBroadcastFailedError', async () => {
50
+ const body = horizonTransactionFailedExtras()
48
51
  submitTransactionSpy.mockRejectedValue(
49
- new BadResponseError(
50
- "Transaction submission failed. Server responded: 400 Bad Request",
51
- body,
52
- ),
53
- );
52
+ new BadResponseError('Transaction submission failed. Server responded: 400 Bad Request', body)
53
+ )
54
54
 
55
55
  await expect(broadcastTransaction(tx)).rejects.toMatchObject({
56
- name: "StellarBroadcastFailedError",
57
- horizonTransactionCode: "tx_failed",
56
+ name: 'StellarBroadcastFailedError',
57
+ horizonTransactionCode: 'tx_failed',
58
58
  cause: expect.any(BadResponseError),
59
- });
60
- });
59
+ })
60
+ })
61
61
 
62
- it("maps axios-like error with Horizon body on error.response.data to StellarBroadcastFailedError", async () => {
63
- const body = horizonTransactionFailedExtras();
62
+ it('maps axios-like error with Horizon body on error.response.data to StellarBroadcastFailedError', async () => {
63
+ const body = horizonTransactionFailedExtras()
64
64
  submitTransactionSpy.mockRejectedValue(
65
- Object.assign(new Error("Request failed with status code 400"), {
66
- name: "AxiosError",
67
- response: { status: 400, statusText: "Bad Request", data: body },
68
- }),
69
- );
65
+ Object.assign(new Error('Request failed with status code 400'), {
66
+ name: 'AxiosError',
67
+ response: { status: 400, statusText: 'Bad Request', data: body },
68
+ })
69
+ )
70
70
 
71
71
  await expect(broadcastTransaction(tx)).rejects.toMatchObject({
72
- name: "StellarBroadcastFailedError",
73
- horizonTransactionCode: "tx_failed",
74
- cause: expect.objectContaining({ name: "AxiosError" }),
75
- });
76
- });
72
+ name: 'StellarBroadcastFailedError',
73
+ horizonTransactionCode: 'tx_failed',
74
+ cause: expect.objectContaining({ name: 'AxiosError' }),
75
+ })
76
+ })
77
77
 
78
- it("rethrows a plain error with no Horizon payload unchanged (same reference)", async () => {
79
- const original = new Error("ECONNREFUSED");
80
- submitTransactionSpy.mockRejectedValue(original);
78
+ it('rethrows a plain error with no Horizon payload unchanged (same reference)', async () => {
79
+ const original = new Error('ECONNREFUSED')
80
+ submitTransactionSpy.mockRejectedValue(original)
81
81
 
82
- await expect(broadcastTransaction(tx)).rejects.toBe(original);
83
- });
82
+ await expect(broadcastTransaction(tx)).rejects.toBe(original)
83
+ })
84
84
 
85
- it("rethrows an axios-like error when response.data is not a Horizon transaction failure body", async () => {
86
- const original = Object.assign(new Error("Request failed with status code 500"), {
87
- name: "AxiosError",
85
+ it('rethrows an axios-like error when response.data is not a Horizon transaction failure body', async () => {
86
+ const original = Object.assign(new Error('Request failed with status code 500'), {
87
+ name: 'AxiosError',
88
88
  response: {
89
89
  status: 500,
90
- statusText: "Internal Server Error",
91
- data: { title: "Internal Server Error" },
90
+ statusText: 'Internal Server Error',
91
+ data: { title: 'Internal Server Error' },
92
92
  },
93
- });
94
- submitTransactionSpy.mockRejectedValue(original);
93
+ })
94
+ submitTransactionSpy.mockRejectedValue(original)
95
95
 
96
- await expect(broadcastTransaction(tx)).rejects.toBe(original);
97
- });
98
- });
96
+ await expect(broadcastTransaction(tx)).rejects.toBe(original)
97
+ })
98
+ })