@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.
- package/.oxlintrc.json +7 -0
- package/CHANGELOG.md +6 -0
- package/LICENSE +194 -0
- package/knip.json +12 -0
- package/lib/api/index.d.ts +5 -5
- package/lib/api/index.d.ts.map +1 -1
- package/lib/api/index.js +12 -10
- package/lib/api/index.js.map +1 -1
- package/lib/config.d.ts +1 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +2 -0
- package/lib/config.js.map +1 -1
- package/lib/constants.d.ts.map +1 -1
- package/lib/constants.js +3 -1
- package/lib/constants.js.map +1 -1
- package/lib/logic/broadcast.d.ts.map +1 -1
- package/lib/logic/broadcast.js +2 -0
- package/lib/logic/broadcast.js.map +1 -1
- package/lib/logic/combine.d.ts +1 -1
- package/lib/logic/combine.d.ts.map +1 -1
- package/lib/logic/combine.js +2 -0
- package/lib/logic/combine.js.map +1 -1
- package/lib/logic/common.d.ts +1 -1
- package/lib/logic/common.d.ts.map +1 -1
- package/lib/logic/common.js +4 -2
- package/lib/logic/common.js.map +1 -1
- package/lib/logic/craftTransaction.d.ts +1 -1
- package/lib/logic/craftTransaction.d.ts.map +1 -1
- package/lib/logic/craftTransaction.js +10 -8
- package/lib/logic/craftTransaction.js.map +1 -1
- package/lib/logic/estimateFees.d.ts.map +1 -1
- package/lib/logic/estimateFees.js +2 -0
- package/lib/logic/estimateFees.js.map +1 -1
- package/lib/logic/getBalance.d.ts +1 -1
- package/lib/logic/getBalance.d.ts.map +1 -1
- package/lib/logic/getBalance.js +4 -2
- package/lib/logic/getBalance.js.map +1 -1
- package/lib/logic/getBlock.d.ts +1 -1
- package/lib/logic/getBlock.d.ts.map +1 -1
- package/lib/logic/getBlock.js +39 -34
- package/lib/logic/getBlock.js.map +1 -1
- package/lib/logic/getBlockInfo.d.ts +1 -1
- package/lib/logic/getBlockInfo.d.ts.map +1 -1
- package/lib/logic/getBlockInfo.js +2 -0
- package/lib/logic/getBlockInfo.js.map +1 -1
- package/lib/logic/index.d.ts +12 -12
- package/lib/logic/index.d.ts.map +1 -1
- package/lib/logic/index.js +2 -0
- package/lib/logic/index.js.map +1 -1
- package/lib/logic/lastBlock.d.ts +1 -1
- package/lib/logic/lastBlock.d.ts.map +1 -1
- package/lib/logic/lastBlock.js +2 -0
- package/lib/logic/lastBlock.js.map +1 -1
- package/lib/logic/listOperations.d.ts +1 -1
- package/lib/logic/listOperations.d.ts.map +1 -1
- package/lib/logic/listOperations.js +6 -4
- package/lib/logic/listOperations.js.map +1 -1
- package/lib/logic/sdkWrapper.d.ts +1 -1
- package/lib/logic/sdkWrapper.d.ts.map +1 -1
- package/lib/logic/sdkWrapper.js +2 -0
- package/lib/logic/sdkWrapper.js.map +1 -1
- package/lib/logic/utils.d.ts.map +1 -1
- package/lib/logic/utils.js +4 -2
- package/lib/logic/utils.js.map +1 -1
- package/lib/logic/validateAddress.d.ts +1 -1
- package/lib/logic/validateAddress.d.ts.map +1 -1
- package/lib/logic/validateAddress.js +2 -0
- package/lib/logic/validateAddress.js.map +1 -1
- package/lib/logic/validateIntent.d.ts +2 -2
- package/lib/logic/validateIntent.d.ts.map +1 -1
- package/lib/logic/validateIntent.js +21 -19
- package/lib/logic/validateIntent.js.map +1 -1
- package/lib/logic/validateMemo.d.ts +1 -1
- package/lib/logic/validateMemo.d.ts.map +1 -1
- package/lib/logic/validateMemo.js +7 -5
- package/lib/logic/validateMemo.js.map +1 -1
- package/lib/network/horizon.d.ts +6 -7
- package/lib/network/horizon.d.ts.map +1 -1
- package/lib/network/horizon.js +71 -68
- package/lib/network/horizon.js.map +1 -1
- package/lib/network/horizonLedgerErrors.d.ts.map +1 -1
- package/lib/network/horizonLedgerErrors.js +5 -3
- package/lib/network/horizonLedgerErrors.js.map +1 -1
- package/lib/network/index.d.ts +1 -1
- package/lib/network/index.d.ts.map +1 -1
- package/lib/network/index.js +2 -0
- package/lib/network/index.js.map +1 -1
- package/lib/network/serialization.d.ts +6 -4
- package/lib/network/serialization.d.ts.map +1 -1
- package/lib/network/serialization.js +95 -91
- package/lib/network/serialization.js.map +1 -1
- package/lib/polyfill.d.ts.map +1 -1
- package/lib/polyfill.js +6 -4
- package/lib/polyfill.js.map +1 -1
- package/lib/supportedFeatures.d.ts +1 -1
- package/lib/supportedFeatures.d.ts.map +1 -1
- package/lib/supportedFeatures.js +3 -1
- package/lib/supportedFeatures.js.map +1 -1
- package/lib/types/errors.d.ts.map +1 -1
- package/lib/types/errors.js +14 -12
- package/lib/types/errors.js.map +1 -1
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +2 -0
- package/lib/types/index.js.map +1 -1
- package/lib/types/model.d.ts +6 -6
- package/lib/types/model.d.ts.map +1 -1
- package/lib/types/model.js +7 -5
- package/lib/types/model.js.map +1 -1
- package/lib-es/api/index.d.ts +5 -5
- package/lib-es/api/index.d.ts.map +1 -1
- package/lib-es/api/index.js +20 -18
- package/lib-es/api/index.js.map +1 -1
- package/lib-es/config.d.ts +1 -1
- package/lib-es/config.d.ts.map +1 -1
- package/lib-es/config.js +3 -1
- package/lib-es/config.js.map +1 -1
- package/lib-es/constants.d.ts.map +1 -1
- package/lib-es/constants.js +3 -1
- package/lib-es/constants.js.map +1 -1
- package/lib-es/logic/broadcast.d.ts.map +1 -1
- package/lib-es/logic/broadcast.js +3 -1
- package/lib-es/logic/broadcast.js.map +1 -1
- package/lib-es/logic/combine.d.ts +1 -1
- package/lib-es/logic/combine.d.ts.map +1 -1
- package/lib-es/logic/combine.js +3 -1
- package/lib-es/logic/combine.js.map +1 -1
- package/lib-es/logic/common.d.ts +1 -1
- package/lib-es/logic/common.d.ts.map +1 -1
- package/lib-es/logic/common.js +5 -3
- package/lib-es/logic/common.js.map +1 -1
- package/lib-es/logic/craftTransaction.d.ts +1 -1
- package/lib-es/logic/craftTransaction.d.ts.map +1 -1
- package/lib-es/logic/craftTransaction.js +15 -13
- package/lib-es/logic/craftTransaction.js.map +1 -1
- package/lib-es/logic/estimateFees.d.ts.map +1 -1
- package/lib-es/logic/estimateFees.js +3 -1
- package/lib-es/logic/estimateFees.js.map +1 -1
- package/lib-es/logic/getBalance.d.ts +1 -1
- package/lib-es/logic/getBalance.d.ts.map +1 -1
- package/lib-es/logic/getBalance.js +5 -3
- package/lib-es/logic/getBalance.js.map +1 -1
- package/lib-es/logic/getBlock.d.ts +1 -1
- package/lib-es/logic/getBlock.d.ts.map +1 -1
- package/lib-es/logic/getBlock.js +42 -37
- package/lib-es/logic/getBlock.js.map +1 -1
- package/lib-es/logic/getBlockInfo.d.ts +1 -1
- package/lib-es/logic/getBlockInfo.d.ts.map +1 -1
- package/lib-es/logic/getBlockInfo.js +3 -1
- package/lib-es/logic/getBlockInfo.js.map +1 -1
- package/lib-es/logic/index.d.ts +12 -12
- package/lib-es/logic/index.d.ts.map +1 -1
- package/lib-es/logic/index.js +14 -12
- package/lib-es/logic/index.js.map +1 -1
- package/lib-es/logic/lastBlock.d.ts +1 -1
- package/lib-es/logic/lastBlock.d.ts.map +1 -1
- package/lib-es/logic/lastBlock.js +3 -1
- package/lib-es/logic/lastBlock.js.map +1 -1
- package/lib-es/logic/listOperations.d.ts +1 -1
- package/lib-es/logic/listOperations.d.ts.map +1 -1
- package/lib-es/logic/listOperations.js +7 -5
- package/lib-es/logic/listOperations.js.map +1 -1
- package/lib-es/logic/sdkWrapper.d.ts +1 -1
- package/lib-es/logic/sdkWrapper.d.ts.map +1 -1
- package/lib-es/logic/sdkWrapper.js +4 -2
- package/lib-es/logic/sdkWrapper.js.map +1 -1
- package/lib-es/logic/utils.d.ts.map +1 -1
- package/lib-es/logic/utils.js +6 -4
- package/lib-es/logic/utils.js.map +1 -1
- package/lib-es/logic/validateAddress.d.ts +1 -1
- package/lib-es/logic/validateAddress.d.ts.map +1 -1
- package/lib-es/logic/validateAddress.js +3 -1
- package/lib-es/logic/validateAddress.js.map +1 -1
- package/lib-es/logic/validateIntent.d.ts +2 -2
- package/lib-es/logic/validateIntent.d.ts.map +1 -1
- package/lib-es/logic/validateIntent.js +30 -28
- package/lib-es/logic/validateIntent.js.map +1 -1
- package/lib-es/logic/validateMemo.d.ts +1 -1
- package/lib-es/logic/validateMemo.d.ts.map +1 -1
- package/lib-es/logic/validateMemo.js +8 -6
- package/lib-es/logic/validateMemo.js.map +1 -1
- package/lib-es/network/horizon.d.ts +6 -7
- package/lib-es/network/horizon.d.ts.map +1 -1
- package/lib-es/network/horizon.js +80 -77
- package/lib-es/network/horizon.js.map +1 -1
- package/lib-es/network/horizonLedgerErrors.d.ts.map +1 -1
- package/lib-es/network/horizonLedgerErrors.js +7 -5
- package/lib-es/network/horizonLedgerErrors.js.map +1 -1
- package/lib-es/network/index.d.ts +1 -1
- package/lib-es/network/index.d.ts.map +1 -1
- package/lib-es/network/index.js +3 -1
- package/lib-es/network/index.js.map +1 -1
- package/lib-es/network/serialization.d.ts +6 -4
- package/lib-es/network/serialization.d.ts.map +1 -1
- package/lib-es/network/serialization.js +97 -95
- package/lib-es/network/serialization.js.map +1 -1
- package/lib-es/polyfill.d.ts.map +1 -1
- package/lib-es/polyfill.js +7 -5
- package/lib-es/polyfill.js.map +1 -1
- package/lib-es/supportedFeatures.d.ts +1 -1
- package/lib-es/supportedFeatures.d.ts.map +1 -1
- package/lib-es/supportedFeatures.js +3 -1
- package/lib-es/supportedFeatures.js.map +1 -1
- package/lib-es/types/errors.d.ts.map +1 -1
- package/lib-es/types/errors.js +15 -13
- package/lib-es/types/errors.js.map +1 -1
- package/lib-es/types/index.d.ts +2 -2
- package/lib-es/types/index.d.ts.map +1 -1
- package/lib-es/types/index.js +4 -2
- package/lib-es/types/index.js.map +1 -1
- package/lib-es/types/model.d.ts +6 -6
- package/lib-es/types/model.d.ts.map +1 -1
- package/lib-es/types/model.js +7 -5
- package/lib-es/types/model.js.map +1 -1
- package/package.json +13 -16
- package/src/api/index.integ.test.ts +165 -162
- package/src/api/index.test.ts +106 -103
- package/src/api/index.ts +49 -46
- package/src/api/index_error.test.ts +48 -25
- package/src/config.ts +15 -12
- package/src/constants.ts +4 -1
- package/src/globals.d.ts +7 -5
- package/src/logic/broadcast.integ.test.ts +22 -19
- package/src/logic/broadcast.ts +5 -2
- package/src/logic/combine.test.ts +18 -15
- package/src/logic/combine.ts +8 -5
- package/src/logic/common.ts +9 -6
- package/src/logic/craftTransaction.test.ts +76 -75
- package/src/logic/craftTransaction.ts +46 -43
- package/src/logic/estimateFees.ts +6 -3
- package/src/logic/getBalance.test.ts +31 -28
- package/src/logic/getBalance.ts +13 -10
- package/src/logic/getBlock.integ.test.ts +40 -39
- package/src/logic/getBlock.test.ts +462 -415
- package/src/logic/getBlock.ts +142 -127
- package/src/logic/getBlockInfo.test.ts +72 -69
- package/src/logic/getBlockInfo.ts +15 -12
- package/src/logic/index.ts +15 -12
- package/src/logic/lastBlock.ts +6 -3
- package/src/logic/listOperations.ts +14 -11
- package/src/logic/listOperations.unit.test.ts +212 -209
- package/src/logic/networkWrappers.test.ts +32 -29
- package/src/logic/sdkWrapper.ts +19 -16
- package/src/logic/utils.ts +13 -10
- package/src/logic/validateAddress.test.ts +21 -18
- package/src/logic/validateAddress.ts +7 -4
- package/src/logic/validateIntent.test.ts +62 -59
- package/src/logic/validateIntent.ts +87 -84
- package/src/logic/validateMemo.test.ts +50 -47
- package/src/logic/validateMemo.ts +16 -13
- package/src/network/broadcastTransaction.test.ts +60 -60
- package/src/network/fetchLedgerOperations.unit.test.ts +155 -0
- package/src/network/horizon.integ.test.ts +45 -42
- package/src/network/horizon.test.ts +17 -14
- package/src/network/horizon.ts +279 -281
- package/src/network/horizon.unit.test.ts +238 -237
- package/src/network/horizonLedgerErrors.test.ts +81 -78
- package/src/network/horizonLedgerErrors.ts +22 -19
- package/src/network/index.ts +4 -1
- package/src/network/registerHorizonInterceptors.test.ts +163 -160
- package/src/network/serialization.ts +232 -227
- package/src/network/serialization.unit.test.ts +801 -798
- package/src/polyfill.ts +18 -15
- package/src/supportedFeatures.ts +6 -3
- package/src/types/errors.ts +33 -32
- package/src/types/index.ts +5 -2
- package/src/types/model.ts +55 -52
- package/tsconfig.json +1 -1
- package/.unimportedrc.json +0 -17
- package/LICENSE.txt +0 -21
- 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
|
|
7
|
-
import { formatCurrencyUnit } from
|
|
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
|
|
17
|
-
import BigNumber from
|
|
18
|
-
import { fetchAccountNetworkInfo, getRecipientAccount } from
|
|
19
|
-
import { fetchAccount } from
|
|
20
|
-
import { BASE_RESERVE, MIN_BALANCE } from
|
|
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
|
|
34
|
-
import { isAccountMultiSign, isAddressValid } from
|
|
35
|
-
import { validateMemo } from
|
|
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 !==
|
|
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() ||
|
|
70
|
-
const networkFees = BigInt(networkInfo.fees.toString() ||
|
|
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 ===
|
|
83
|
+
if (transactionIntent.type === 'changeTrust') {
|
|
81
84
|
// Check asset provided
|
|
82
85
|
if (
|
|
83
|
-
transactionIntent.asset.type !==
|
|
84
|
-
((
|
|
85
|
-
(
|
|
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)
|
|
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 ??
|
|
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 ===
|
|
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 !==
|
|
152
|
-
|
|
153
|
+
(b) =>
|
|
154
|
+
b.asset.type !== 'native' &&
|
|
155
|
+
'assetReference' in b.asset &&
|
|
153
156
|
b.asset.assetReference === asset.assetReference &&
|
|
154
|
-
|
|
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
|
-
:
|
|
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 !==
|
|
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
|
-
|
|
1
|
+
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
3
|
|
|
3
|
-
|
|
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
|
-
|
|
10
|
-
|
|
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
|
|
14
|
-
|
|
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,
|
|
17
|
-
}
|
|
18
|
-
)
|
|
19
|
+
expect(validateMemo(memo, 'MEMO_TEXT')).toEqual(false)
|
|
20
|
+
}
|
|
21
|
+
)
|
|
19
22
|
|
|
20
|
-
it.each([
|
|
21
|
-
|
|
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,
|
|
24
|
-
}
|
|
25
|
-
)
|
|
26
|
+
expect(validateMemo(memo, 'MEMO_TEXT')).toEqual(true)
|
|
27
|
+
}
|
|
28
|
+
)
|
|
26
29
|
|
|
27
|
-
it(
|
|
28
|
-
expect(validateMemo(Number.NaN.toString(),
|
|
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([
|
|
32
|
-
|
|
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,
|
|
35
|
-
}
|
|
36
|
-
)
|
|
37
|
+
expect(validateMemo(memo, 'MEMO_ID')).toEqual(true)
|
|
38
|
+
}
|
|
39
|
+
)
|
|
37
40
|
|
|
38
|
-
it.each([
|
|
39
|
-
|
|
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,
|
|
42
|
-
}
|
|
43
|
-
)
|
|
44
|
+
expect(validateMemo(memo, 'MEMO_HASH')).toEqual(false)
|
|
45
|
+
}
|
|
46
|
+
)
|
|
44
47
|
|
|
45
|
-
it(
|
|
46
|
-
const memo =
|
|
47
|
-
expect(validateMemo(memo,
|
|
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([
|
|
51
|
-
|
|
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,
|
|
54
|
-
}
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
it(
|
|
58
|
-
const memo =
|
|
59
|
-
expect(validateMemo(memo,
|
|
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
|
-
|
|
2
|
-
|
|
1
|
+
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
10
|
-
return !memo || memo.length === 0
|
|
11
|
-
case
|
|
12
|
-
return memo.length <= MEMO_TEXT_MAXIMUM_SIZE
|
|
13
|
-
case
|
|
14
|
-
return !new BigNumber(memo.toString()).isNaN()
|
|
15
|
-
case
|
|
16
|
-
case
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
10
|
+
'AAAAAgAAAABRUCgFba+DTbei2ifpyYt5w2Hh0VyZ+X9fayjIDne7YAAAAGQCkDOGAAAABQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAACEIuPfWXgM8WhyqjrpFdIcGV1SUVhMzPUm4YspNHF60QAAAAAAAAAAALkd2QAAAAAAAAABDne7YAAAAEAASzsT/yDIfCfEDstkfnznXjiN7rNd7PkKQEn+rRIFm9EHoirGfHipWoBdYMrc6ixQD/0y0of1piSid8TLiFAB'
|
|
8
11
|
|
|
9
12
|
function horizonTransactionFailedExtras() {
|
|
10
13
|
return {
|
|
11
|
-
type:
|
|
12
|
-
title:
|
|
14
|
+
type: 'https://stellar.org/horizon-errors/transaction_failed',
|
|
15
|
+
title: 'Transaction Failed',
|
|
13
16
|
status: 400,
|
|
14
|
-
detail:
|
|
17
|
+
detail: 'The transaction failed when submitted to the stellar network.',
|
|
15
18
|
extras: {
|
|
16
19
|
result_codes: {
|
|
17
|
-
transaction:
|
|
18
|
-
operations: [
|
|
20
|
+
transaction: 'tx_failed' as const,
|
|
21
|
+
operations: ['op_no_account'] as string[],
|
|
19
22
|
},
|
|
20
|
-
result_xdr:
|
|
23
|
+
result_xdr: 'AAAAAAAAAGT////4AAAAAA==',
|
|
21
24
|
envelope_xdr: tx,
|
|
22
25
|
},
|
|
23
|
-
}
|
|
26
|
+
}
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
describe(
|
|
27
|
-
let submitTransactionSpy: jest.SpiedFunction<Horizon.Server[
|
|
29
|
+
describe('broadcastTransaction', () => {
|
|
30
|
+
let submitTransactionSpy: jest.SpiedFunction<Horizon.Server['submitTransaction']>
|
|
28
31
|
|
|
29
32
|
beforeAll(() => {
|
|
30
33
|
coinConfig.setCoinConfig(() => ({
|
|
31
|
-
explorer: { url:
|
|
32
|
-
status: { type:
|
|
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,
|
|
39
|
-
.mockRejectedValue(new Error(
|
|
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(
|
|
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
|
-
|
|
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:
|
|
57
|
-
horizonTransactionCode:
|
|
56
|
+
name: 'StellarBroadcastFailedError',
|
|
57
|
+
horizonTransactionCode: 'tx_failed',
|
|
58
58
|
cause: expect.any(BadResponseError),
|
|
59
|
-
})
|
|
60
|
-
})
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
61
|
|
|
62
|
-
it(
|
|
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(
|
|
66
|
-
name:
|
|
67
|
-
response: { status: 400, statusText:
|
|
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:
|
|
73
|
-
horizonTransactionCode:
|
|
74
|
-
cause: expect.objectContaining({ name:
|
|
75
|
-
})
|
|
76
|
-
})
|
|
72
|
+
name: 'StellarBroadcastFailedError',
|
|
73
|
+
horizonTransactionCode: 'tx_failed',
|
|
74
|
+
cause: expect.objectContaining({ name: 'AxiosError' }),
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
77
|
|
|
78
|
-
it(
|
|
79
|
-
const original = new Error(
|
|
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(
|
|
86
|
-
const original = Object.assign(new Error(
|
|
87
|
-
name:
|
|
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:
|
|
91
|
-
data: { title:
|
|
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
|
+
})
|