@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
package/src/network/horizon.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import type { Operation, Page } from '@ledgerhq/coin-module-framework/api/types'
|
|
5
|
+
import type { CacheRes } from '@ledgerhq/live-network/cache'
|
|
6
|
+
import { LedgerAPI4xx, LedgerAPI5xx, NetworkDown } from '@ledgerhq/errors'
|
|
7
|
+
import { makeLRUCache } from '@ledgerhq/live-network/cache'
|
|
8
|
+
import { log } from '@ledgerhq/logs'
|
|
6
9
|
import {
|
|
7
10
|
BadResponseError,
|
|
8
11
|
BASE_FEE,
|
|
@@ -14,79 +17,79 @@ import {
|
|
|
14
17
|
Transaction as StellarSdkTransaction,
|
|
15
18
|
StrKey,
|
|
16
19
|
xdr,
|
|
17
|
-
} from
|
|
18
|
-
import { BigNumber } from
|
|
19
|
-
import coinConfig from
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
20
|
+
} from '@stellar/stellar-sdk'
|
|
21
|
+
import { BigNumber } from 'bignumber.js'
|
|
22
|
+
import coinConfig from '../config'
|
|
23
|
+
import { parseAPIValue } from '../logic/common'
|
|
24
|
+
import { patchHermesTypedArraysIfNeeded, unpatchHermesTypedArrays } from '../polyfill'
|
|
22
25
|
import {
|
|
23
26
|
type BalanceAsset,
|
|
24
27
|
type NetworkInfo,
|
|
25
28
|
type RawOperation,
|
|
26
29
|
type Signer,
|
|
27
30
|
NetworkCongestionLevel,
|
|
28
|
-
} from
|
|
29
|
-
import {
|
|
30
|
-
import { throwHorizonLedgerOrOperationsError } from
|
|
31
|
-
import {
|
|
31
|
+
} from '../types'
|
|
32
|
+
import { StellarBroadcastFailedError, type StellarDecodedResultXdr } from '../types/errors'
|
|
33
|
+
import { throwHorizonLedgerOrOperationsError } from './horizonLedgerErrors'
|
|
34
|
+
import { getReservedBalance, rawOperationsToOperations } from './serialization'
|
|
32
35
|
|
|
33
|
-
const FALLBACK_BASE_FEE = 100
|
|
34
|
-
const TRESHOLD_LOW = 0.5
|
|
35
|
-
const TRESHOLD_MEDIUM = 0.75
|
|
36
|
-
const FETCH_LIMIT = 100
|
|
36
|
+
const FALLBACK_BASE_FEE = 100
|
|
37
|
+
const TRESHOLD_LOW = 0.5
|
|
38
|
+
const TRESHOLD_MEDIUM = 0.75
|
|
39
|
+
const FETCH_LIMIT = 100
|
|
37
40
|
|
|
38
41
|
const STELLAR_TX_RESULT_CODES_DOC_URL =
|
|
39
|
-
|
|
42
|
+
'https://developers.stellar.org/docs/data/apis/horizon/api-reference/errors/result-codes/transactions'
|
|
40
43
|
|
|
41
44
|
/** Horizon `extras.result_codes.transaction` → one-line description (Stellar docs). */
|
|
42
45
|
const HORIZON_TRANSACTION_RESULT_CODE_DOCUMENTATION: Record<string, string> = {
|
|
43
|
-
tx_success:
|
|
44
|
-
tx_failed:
|
|
45
|
-
tx_too_early:
|
|
46
|
-
tx_too_late:
|
|
47
|
-
tx_missing_operation:
|
|
48
|
-
tx_bad_seq:
|
|
49
|
-
tx_bad_auth:
|
|
50
|
-
tx_insufficient_balance:
|
|
51
|
-
tx_no_source_account:
|
|
52
|
-
tx_insufficient_fee:
|
|
53
|
-
tx_bad_auth_extra:
|
|
54
|
-
tx_internal_error:
|
|
55
|
-
tx_fee_bump_inner_success:
|
|
56
|
-
tx_fee_bump_inner_failed:
|
|
57
|
-
tx_not_supported:
|
|
58
|
-
tx_bad_sponsorship:
|
|
59
|
-
tx_bad_min_seq_age_or_gap:
|
|
60
|
-
tx_malformed:
|
|
61
|
-
tx_soroban_invalid:
|
|
62
|
-
}
|
|
46
|
+
tx_success: 'The transaction succeeded.',
|
|
47
|
+
tx_failed: 'One of the operations failed (none were applied).',
|
|
48
|
+
tx_too_early: 'The ledger closeTime was before the minTime.',
|
|
49
|
+
tx_too_late: 'The ledger closeTime was after the maxTime.',
|
|
50
|
+
tx_missing_operation: 'No operation was specified.',
|
|
51
|
+
tx_bad_seq: 'Sequence number does not match source account.',
|
|
52
|
+
tx_bad_auth: 'Too few valid signatures / wrong network.',
|
|
53
|
+
tx_insufficient_balance: 'Fee would bring account below reserve.',
|
|
54
|
+
tx_no_source_account: 'Source account not found.',
|
|
55
|
+
tx_insufficient_fee: 'Fee is too small.',
|
|
56
|
+
tx_bad_auth_extra: 'Unused signatures attached to transaction.',
|
|
57
|
+
tx_internal_error: 'An unknown error occurred.',
|
|
58
|
+
tx_fee_bump_inner_success: 'Fee bump inner transaction succeeded.',
|
|
59
|
+
tx_fee_bump_inner_failed: 'Fee bump inner transaction failed.',
|
|
60
|
+
tx_not_supported: 'Transaction type not supported.',
|
|
61
|
+
tx_bad_sponsorship: 'Sponsorship is invalid.',
|
|
62
|
+
tx_bad_min_seq_age_or_gap: 'Minimum sequence age or gap precondition failed.',
|
|
63
|
+
tx_malformed: 'Transaction is malformed.',
|
|
64
|
+
tx_soroban_invalid: 'Soroban transaction is invalid.',
|
|
65
|
+
}
|
|
63
66
|
|
|
64
67
|
type HorizonSubmitErrorBody = {
|
|
65
|
-
type?: string
|
|
66
|
-
title?: string
|
|
67
|
-
status?: number
|
|
68
|
-
detail?: string
|
|
68
|
+
type?: string
|
|
69
|
+
title?: string
|
|
70
|
+
status?: number
|
|
71
|
+
detail?: string
|
|
69
72
|
extras?: {
|
|
70
|
-
envelope_xdr?: string
|
|
71
|
-
result_xdr?: string
|
|
73
|
+
envelope_xdr?: string
|
|
74
|
+
result_xdr?: string
|
|
72
75
|
result_codes?: {
|
|
73
|
-
transaction?: string
|
|
74
|
-
operations?: string[]
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
76
|
+
transaction?: string
|
|
77
|
+
operations?: string[]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
78
81
|
|
|
79
82
|
function isHorizonSubmitErrorBody(data: unknown): data is HorizonSubmitErrorBody {
|
|
80
83
|
return !!(
|
|
81
84
|
data &&
|
|
82
|
-
typeof data ===
|
|
83
|
-
|
|
84
|
-
typeof data.extras ===
|
|
85
|
+
typeof data === 'object' &&
|
|
86
|
+
'extras' in data &&
|
|
87
|
+
typeof data.extras === 'object' &&
|
|
85
88
|
data.extras !== null &&
|
|
86
|
-
|
|
89
|
+
'result_codes' in data.extras &&
|
|
87
90
|
data.extras.result_codes !== undefined &&
|
|
88
|
-
typeof data.extras.result_codes ===
|
|
89
|
-
)
|
|
91
|
+
typeof data.extras.result_codes === 'object'
|
|
92
|
+
)
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
/**
|
|
@@ -96,58 +99,58 @@ function isHorizonSubmitErrorBody(data: unknown): data is HorizonSubmitErrorBody
|
|
|
96
99
|
*/
|
|
97
100
|
function getHorizonBodyFromSubmitFailure(error: unknown): HorizonSubmitErrorBody | null {
|
|
98
101
|
if (error instanceof BadResponseError && isHorizonSubmitErrorBody(error.response)) {
|
|
99
|
-
return error.response
|
|
102
|
+
return error.response
|
|
100
103
|
}
|
|
101
|
-
if (error && typeof error ===
|
|
102
|
-
const data = (error as { response?: { data?: unknown } }).response?.data
|
|
104
|
+
if (error && typeof error === 'object' && 'response' in error) {
|
|
105
|
+
const data = (error as { response?: { data?: unknown } }).response?.data
|
|
103
106
|
if (isHorizonSubmitErrorBody(data)) {
|
|
104
|
-
return data
|
|
107
|
+
return data
|
|
105
108
|
}
|
|
106
109
|
}
|
|
107
|
-
return null
|
|
110
|
+
return null
|
|
108
111
|
}
|
|
109
112
|
|
|
110
113
|
function decodeTransactionResultFields(resultXdrBase64: string | undefined):
|
|
111
114
|
| {
|
|
112
|
-
feeChargedStroops?: string
|
|
113
|
-
resultXdrSwitchName?: string
|
|
114
|
-
decodedResultXdr: StellarDecodedResultXdr
|
|
115
|
+
feeChargedStroops?: string
|
|
116
|
+
resultXdrSwitchName?: string
|
|
117
|
+
decodedResultXdr: StellarDecodedResultXdr
|
|
115
118
|
}
|
|
116
119
|
| undefined {
|
|
117
120
|
if (!resultXdrBase64) {
|
|
118
|
-
return undefined
|
|
121
|
+
return undefined
|
|
119
122
|
}
|
|
120
123
|
try {
|
|
121
|
-
const tr = xdr.TransactionResult.fromXDR(resultXdrBase64,
|
|
122
|
-
const feeChargedStroops = tr.feeCharged().toString()
|
|
123
|
-
const resultXdrSwitchName = tr.result().switch().name
|
|
124
|
+
const tr = xdr.TransactionResult.fromXDR(resultXdrBase64, 'base64')
|
|
125
|
+
const feeChargedStroops = tr.feeCharged().toString()
|
|
126
|
+
const resultXdrSwitchName = tr.result().switch().name
|
|
124
127
|
const decodedResultXdr: StellarDecodedResultXdr = {
|
|
125
128
|
feeChargedStroops,
|
|
126
129
|
resultSwitch: resultXdrSwitchName,
|
|
127
|
-
}
|
|
128
|
-
return { feeChargedStroops, resultXdrSwitchName, decodedResultXdr }
|
|
130
|
+
}
|
|
131
|
+
return { feeChargedStroops, resultXdrSwitchName, decodedResultXdr }
|
|
129
132
|
} catch {
|
|
130
133
|
return {
|
|
131
134
|
decodedResultXdr: { decodeFailed: true, rawResultXdrBase64: resultXdrBase64 },
|
|
132
|
-
}
|
|
135
|
+
}
|
|
133
136
|
}
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
function makeBroadcastFailedError(body: HorizonSubmitErrorBody, cause: Error): Error {
|
|
137
|
-
const extras = body.extras
|
|
138
|
-
const horizonTransactionCode = extras?.result_codes?.transaction ??
|
|
139
|
-
const horizonOperationCodes = extras?.result_codes?.operations
|
|
140
|
+
const extras = body.extras
|
|
141
|
+
const horizonTransactionCode = extras?.result_codes?.transaction ?? ''
|
|
142
|
+
const horizonOperationCodes = extras?.result_codes?.operations
|
|
140
143
|
const documentationSummary =
|
|
141
144
|
(horizonTransactionCode &&
|
|
142
145
|
HORIZON_TRANSACTION_RESULT_CODE_DOCUMENTATION[horizonTransactionCode]) ||
|
|
143
|
-
|
|
144
|
-
const decodedResult = decodeTransactionResultFields(extras?.result_xdr)
|
|
145
|
-
const feeChargedStroops = decodedResult?.feeChargedStroops
|
|
146
|
-
const resultXdrSwitchName = decodedResult?.resultXdrSwitchName
|
|
147
|
-
const decodedResultXdr = decodedResult?.decodedResultXdr
|
|
148
|
-
const envelopeXdr = extras?.envelope_xdr ??
|
|
146
|
+
'Unknown transaction result code.'
|
|
147
|
+
const decodedResult = decodeTransactionResultFields(extras?.result_xdr)
|
|
148
|
+
const feeChargedStroops = decodedResult?.feeChargedStroops
|
|
149
|
+
const resultXdrSwitchName = decodedResult?.resultXdrSwitchName
|
|
150
|
+
const decodedResultXdr = decodedResult?.decodedResultXdr
|
|
151
|
+
const envelopeXdr = extras?.envelope_xdr ?? ''
|
|
149
152
|
const message =
|
|
150
|
-
body.detail || body.title || `Transaction submission failed (${body.status ??
|
|
153
|
+
body.detail || body.title || `Transaction submission failed (${body.status ?? 'unknown'}).`
|
|
151
154
|
|
|
152
155
|
return new StellarBroadcastFailedError(
|
|
153
156
|
message,
|
|
@@ -161,28 +164,28 @@ function makeBroadcastFailedError(body: HorizonSubmitErrorBody, cause: Error): E
|
|
|
161
164
|
decodedResultXdr,
|
|
162
165
|
envelopeXdr,
|
|
163
166
|
},
|
|
164
|
-
{ cause }
|
|
165
|
-
)
|
|
167
|
+
{ cause }
|
|
168
|
+
)
|
|
166
169
|
}
|
|
167
170
|
|
|
168
171
|
// Horizon client instance is cached to avoid costly rebuild at every request
|
|
169
172
|
// Watch out: cache key is the URL, coin module can be instantiated several times with different URLs
|
|
170
|
-
const servers = new Map<string, Horizon.Server>()
|
|
173
|
+
const servers = new Map<string, Horizon.Server>()
|
|
171
174
|
function getServer(): Horizon.Server {
|
|
172
|
-
const url = coinConfig.getCoinConfig().explorer.url
|
|
173
|
-
let server = servers.get(url)
|
|
175
|
+
const url = coinConfig.getCoinConfig().explorer.url
|
|
176
|
+
let server = servers.get(url)
|
|
174
177
|
if (server === undefined) {
|
|
175
|
-
server = new Horizon.Server(url)
|
|
176
|
-
servers.set(url, server)
|
|
178
|
+
server = new Horizon.Server(url)
|
|
179
|
+
servers.set(url, server)
|
|
177
180
|
}
|
|
178
|
-
return server
|
|
181
|
+
return server
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
// Tracks request start times for response-duration logging.
|
|
182
185
|
// WeakMap avoids mutating the feaxios request config (whose type doesn't include `metadata`).
|
|
183
|
-
const requestStartTimes = new WeakMap<object, number>()
|
|
186
|
+
const requestStartTimes = new WeakMap<object, number>()
|
|
184
187
|
|
|
185
|
-
let interceptorsRegistered = false
|
|
188
|
+
let interceptorsRegistered = false
|
|
186
189
|
|
|
187
190
|
/**
|
|
188
191
|
* Register logging and URL-fix interceptors on Horizon.AxiosClient.
|
|
@@ -193,83 +196,83 @@ let interceptorsRegistered = false;
|
|
|
193
196
|
* InterceptorManager types rather than importing live-network's axios-typed interceptors.
|
|
194
197
|
*/
|
|
195
198
|
export function registerHorizonInterceptors(): void {
|
|
196
|
-
if (interceptorsRegistered) return
|
|
197
|
-
interceptorsRegistered = true
|
|
199
|
+
if (interceptorsRegistered) return
|
|
200
|
+
interceptorsRegistered = true
|
|
198
201
|
|
|
199
|
-
Horizon.AxiosClient.interceptors.request.use(config => {
|
|
200
|
-
let enableNetworkLogs = false
|
|
202
|
+
Horizon.AxiosClient.interceptors.request.use((config) => {
|
|
203
|
+
let enableNetworkLogs = false
|
|
201
204
|
try {
|
|
202
|
-
enableNetworkLogs = !!coinConfig.getCoinConfig().enableNetworkLogs
|
|
205
|
+
enableNetworkLogs = !!coinConfig.getCoinConfig().enableNetworkLogs
|
|
203
206
|
} catch {
|
|
204
|
-
return config
|
|
207
|
+
return config
|
|
205
208
|
}
|
|
206
209
|
if (enableNetworkLogs) {
|
|
207
|
-
const { baseURL, url, method =
|
|
208
|
-
log(
|
|
209
|
-
requestStartTimes.set(config, Date.now())
|
|
210
|
+
const { baseURL, url, method = '', data } = config
|
|
211
|
+
log('network', `${method} ${baseURL ?? ''}${url}`, { data })
|
|
212
|
+
requestStartTimes.set(config, Date.now())
|
|
210
213
|
}
|
|
211
|
-
return config
|
|
212
|
-
})
|
|
214
|
+
return config
|
|
215
|
+
})
|
|
213
216
|
|
|
214
|
-
Horizon.AxiosClient.interceptors.response.use(response => {
|
|
215
|
-
let enableNetworkLogs = false
|
|
217
|
+
Horizon.AxiosClient.interceptors.response.use((response) => {
|
|
218
|
+
let enableNetworkLogs = false
|
|
216
219
|
try {
|
|
217
|
-
enableNetworkLogs = !!coinConfig.getCoinConfig().enableNetworkLogs
|
|
220
|
+
enableNetworkLogs = !!coinConfig.getCoinConfig().enableNetworkLogs
|
|
218
221
|
} catch {
|
|
219
222
|
// coinConfig not initialised on this instance; skip logging and leave URLs untouched
|
|
220
223
|
}
|
|
221
224
|
if (enableNetworkLogs) {
|
|
222
225
|
// response.config is typed as `any` in stellar-sdk's HttpClientResponse
|
|
223
|
-
const startTime = requestStartTimes.get(response.config) ?? 0
|
|
224
|
-
requestStartTimes.delete(response.config)
|
|
226
|
+
const startTime = requestStartTimes.get(response.config) ?? 0
|
|
227
|
+
requestStartTimes.delete(response.config)
|
|
225
228
|
log(
|
|
226
|
-
|
|
227
|
-
`${response.status} ${response.config?.method ??
|
|
228
|
-
{ data: response.data }
|
|
229
|
-
)
|
|
229
|
+
'network-success',
|
|
230
|
+
`${response.status} ${response.config?.method ?? ''} ${response.config?.baseURL ?? ''}${response.config?.url ?? ''} (${(Date.now() - startTime).toFixed(0)}ms)`,
|
|
231
|
+
{ data: response.data }
|
|
232
|
+
)
|
|
230
233
|
}
|
|
231
234
|
|
|
232
235
|
// FIXME: workaround for the Stellar SDK not using the correct URL: the "next" URL
|
|
233
236
|
// included in server responses points to the node itself instead of our reverse proxy...
|
|
234
237
|
// (https://github.com/stellar/js-stellar-sdk/issues/637)
|
|
235
|
-
const next_href = response?.data?._links?.next?.href
|
|
238
|
+
const next_href = response?.data?._links?.next?.href
|
|
236
239
|
if (next_href) {
|
|
237
|
-
response.data._links.next.href = useConfigHostAndProtocol(next_href)
|
|
240
|
+
response.data._links.next.href = useConfigHostAndProtocol(next_href)
|
|
238
241
|
}
|
|
239
242
|
response?.data?._embedded?.records?.forEach((r: any) => {
|
|
240
|
-
const href = r.transaction?._links?.ledger?.href
|
|
241
|
-
if (href) r.transaction._links.ledger.href = useConfigHostAndProtocol(href)
|
|
242
|
-
})
|
|
243
|
+
const href = r.transaction?._links?.ledger?.href
|
|
244
|
+
if (href) r.transaction._links.ledger.href = useConfigHostAndProtocol(href)
|
|
245
|
+
})
|
|
243
246
|
|
|
244
|
-
return response
|
|
245
|
-
})
|
|
247
|
+
return response
|
|
248
|
+
})
|
|
246
249
|
}
|
|
247
250
|
|
|
248
251
|
// It replaces the host and the protocol of the URL returned with the original ones.
|
|
249
252
|
export function useConfigHostAndProtocol(url: string): string {
|
|
250
|
-
const originalUrl = new URL(coinConfig.getCoinConfig().explorer.url)
|
|
253
|
+
const originalUrl = new URL(coinConfig.getCoinConfig().explorer.url)
|
|
251
254
|
// URL.protocol setter silently fails when changing between special (https://) and
|
|
252
255
|
// non-special (injected://) schemes, so reconstruct via string replacement instead.
|
|
253
|
-
return url.replace(/^[^:]+:\/\/[^/]+/, `${originalUrl.protocol}//${originalUrl.host}`)
|
|
256
|
+
return url.replace(/^[^:]+:\/\/[^/]+/, `${originalUrl.protocol}//${originalUrl.host}`)
|
|
254
257
|
}
|
|
255
258
|
|
|
256
259
|
const getMinimumBalance = (account: Horizon.ServerApi.AccountRecord): BigNumber => {
|
|
257
|
-
return parseAPIValue(getReservedBalance(account).toString())
|
|
258
|
-
}
|
|
260
|
+
return parseAPIValue(getReservedBalance(account).toString())
|
|
261
|
+
}
|
|
259
262
|
|
|
260
263
|
export async function getAccountSpendableBalance(
|
|
261
264
|
balance: BigNumber,
|
|
262
|
-
account: Horizon.ServerApi.AccountRecord
|
|
265
|
+
account: Horizon.ServerApi.AccountRecord
|
|
263
266
|
): Promise<BigNumber> {
|
|
264
|
-
const minimumBalance = getMinimumBalance(account)
|
|
265
|
-
const { recommendedFee } = await fetchBaseFee()
|
|
266
|
-
return BigNumber.max(balance.minus(minimumBalance).minus(recommendedFee), 0)
|
|
267
|
+
const minimumBalance = getMinimumBalance(account)
|
|
268
|
+
const { recommendedFee } = await fetchBaseFee()
|
|
269
|
+
return BigNumber.max(balance.minus(minimumBalance).minus(recommendedFee), 0)
|
|
267
270
|
}
|
|
268
271
|
|
|
269
272
|
export async function fetchBaseFee(): Promise<{
|
|
270
|
-
baseFee: number
|
|
271
|
-
recommendedFee: number
|
|
272
|
-
networkCongestionLevel: NetworkCongestionLevel
|
|
273
|
+
baseFee: number
|
|
274
|
+
recommendedFee: number
|
|
275
|
+
networkCongestionLevel: NetworkCongestionLevel
|
|
273
276
|
}> {
|
|
274
277
|
// For tests
|
|
275
278
|
if (coinConfig.getCoinConfig().useStaticFees) {
|
|
@@ -277,27 +280,27 @@ export async function fetchBaseFee(): Promise<{
|
|
|
277
280
|
baseFee: 100,
|
|
278
281
|
recommendedFee: 100,
|
|
279
282
|
networkCongestionLevel: NetworkCongestionLevel.LOW,
|
|
280
|
-
}
|
|
283
|
+
}
|
|
281
284
|
}
|
|
282
285
|
|
|
283
|
-
const baseFee = new BigNumber(BASE_FEE).toNumber() || FALLBACK_BASE_FEE
|
|
284
|
-
let recommendedFee = baseFee
|
|
285
|
-
let networkCongestionLevel = NetworkCongestionLevel.MEDIUM
|
|
286
|
+
const baseFee = new BigNumber(BASE_FEE).toNumber() || FALLBACK_BASE_FEE
|
|
287
|
+
let recommendedFee = baseFee
|
|
288
|
+
let networkCongestionLevel = NetworkCongestionLevel.MEDIUM
|
|
286
289
|
|
|
287
290
|
try {
|
|
288
|
-
const feeStats = await getServer().feeStats()
|
|
289
|
-
const ledgerCapacityUsage = feeStats.ledger_capacity_usage
|
|
290
|
-
recommendedFee = new BigNumber(feeStats.fee_charged.mode).toNumber()
|
|
291
|
+
const feeStats = await getServer().feeStats()
|
|
292
|
+
const ledgerCapacityUsage = feeStats.ledger_capacity_usage
|
|
293
|
+
recommendedFee = new BigNumber(feeStats.fee_charged.mode).toNumber()
|
|
291
294
|
|
|
292
295
|
if (
|
|
293
296
|
new BigNumber(ledgerCapacityUsage).toNumber() > TRESHOLD_LOW &&
|
|
294
297
|
new BigNumber(ledgerCapacityUsage).toNumber() <= TRESHOLD_MEDIUM
|
|
295
298
|
) {
|
|
296
|
-
networkCongestionLevel = NetworkCongestionLevel.MEDIUM
|
|
299
|
+
networkCongestionLevel = NetworkCongestionLevel.MEDIUM
|
|
297
300
|
} else if (new BigNumber(ledgerCapacityUsage).toNumber() > TRESHOLD_MEDIUM) {
|
|
298
|
-
networkCongestionLevel = NetworkCongestionLevel.HIGH
|
|
301
|
+
networkCongestionLevel = NetworkCongestionLevel.HIGH
|
|
299
302
|
} else {
|
|
300
|
-
networkCongestionLevel = NetworkCongestionLevel.LOW
|
|
303
|
+
networkCongestionLevel = NetworkCongestionLevel.LOW
|
|
301
304
|
}
|
|
302
305
|
} catch {
|
|
303
306
|
// do nothing, will use defaults
|
|
@@ -307,7 +310,7 @@ export async function fetchBaseFee(): Promise<{
|
|
|
307
310
|
baseFee,
|
|
308
311
|
recommendedFee,
|
|
309
312
|
networkCongestionLevel,
|
|
310
|
-
}
|
|
313
|
+
}
|
|
311
314
|
}
|
|
312
315
|
|
|
313
316
|
/**
|
|
@@ -317,39 +320,39 @@ export async function fetchBaseFee(): Promise<{
|
|
|
317
320
|
* @param {string} addr
|
|
318
321
|
*/
|
|
319
322
|
export async function fetchAccount(addr: string): Promise<{
|
|
320
|
-
blockHeight: number
|
|
321
|
-
balance: BigNumber
|
|
322
|
-
spendableBalance: BigNumber
|
|
323
|
-
assets: BalanceAsset[]
|
|
323
|
+
blockHeight: number
|
|
324
|
+
balance: BigNumber
|
|
325
|
+
spendableBalance: BigNumber
|
|
326
|
+
assets: BalanceAsset[]
|
|
324
327
|
}> {
|
|
325
|
-
let account: Horizon.ServerApi.AccountRecord = {} as Horizon.ServerApi.AccountRecord
|
|
326
|
-
let assets: BalanceAsset[] = []
|
|
327
|
-
let balance =
|
|
328
|
+
let account: Horizon.ServerApi.AccountRecord = {} as Horizon.ServerApi.AccountRecord
|
|
329
|
+
let assets: BalanceAsset[] = []
|
|
330
|
+
let balance = '0'
|
|
328
331
|
|
|
329
332
|
try {
|
|
330
|
-
account = await getServer().accounts().accountId(addr).call()
|
|
333
|
+
account = await getServer().accounts().accountId(addr).call()
|
|
331
334
|
balance =
|
|
332
|
-
account.balances?.find(balance => {
|
|
333
|
-
return balance.asset_type ===
|
|
334
|
-
})?.balance ||
|
|
335
|
+
account.balances?.find((balance) => {
|
|
336
|
+
return balance.asset_type === 'native'
|
|
337
|
+
})?.balance || '0'
|
|
335
338
|
// Getting all non-native (XLM) assets on the account
|
|
336
|
-
assets = account.balances?.filter(balance => {
|
|
337
|
-
return balance.asset_type !==
|
|
338
|
-
}) as BalanceAsset[]
|
|
339
|
+
assets = account.balances?.filter((balance) => {
|
|
340
|
+
return balance.asset_type !== 'native'
|
|
341
|
+
}) as BalanceAsset[]
|
|
339
342
|
} catch {
|
|
340
|
-
balance =
|
|
343
|
+
balance = '0'
|
|
341
344
|
}
|
|
342
345
|
|
|
343
|
-
const formattedBalance = parseAPIValue(balance)
|
|
346
|
+
const formattedBalance = parseAPIValue(balance)
|
|
344
347
|
|
|
345
|
-
const spendableBalance = await getAccountSpendableBalance(formattedBalance, account)
|
|
348
|
+
const spendableBalance = await getAccountSpendableBalance(formattedBalance, account)
|
|
346
349
|
|
|
347
350
|
return {
|
|
348
351
|
blockHeight: account.sequence ? new BigNumber(account.sequence).toNumber() : 0,
|
|
349
352
|
balance: formattedBalance,
|
|
350
353
|
spendableBalance,
|
|
351
354
|
assets,
|
|
352
|
-
}
|
|
355
|
+
}
|
|
353
356
|
}
|
|
354
357
|
|
|
355
358
|
/**
|
|
@@ -366,17 +369,17 @@ export async function fetchAccount(addr: string): Promise<{
|
|
|
366
369
|
export async function fetchAllOperations(
|
|
367
370
|
accountId: string,
|
|
368
371
|
addr: string,
|
|
369
|
-
order:
|
|
370
|
-
cursor: string =
|
|
371
|
-
maxOperations?: number
|
|
372
|
+
order: 'asc' | 'desc',
|
|
373
|
+
cursor: string = '',
|
|
374
|
+
maxOperations?: number
|
|
372
375
|
): Promise<Operation[]> {
|
|
373
376
|
if (!addr) {
|
|
374
|
-
return []
|
|
377
|
+
return []
|
|
375
378
|
}
|
|
376
379
|
|
|
377
|
-
const limit = coinConfig.getCoinConfig().explorer.fetchLimit ?? FETCH_LIMIT
|
|
378
|
-
let operations: Operation[] = []
|
|
379
|
-
let fetchedOpsCount = limit
|
|
380
|
+
const limit = coinConfig.getCoinConfig().explorer.fetchLimit ?? FETCH_LIMIT
|
|
381
|
+
let operations: Operation[] = []
|
|
382
|
+
let fetchedOpsCount = limit
|
|
380
383
|
|
|
381
384
|
try {
|
|
382
385
|
let rawOperations = await getServer()
|
|
@@ -386,50 +389,45 @@ export async function fetchAllOperations(
|
|
|
386
389
|
.order(order)
|
|
387
390
|
.cursor(cursor)
|
|
388
391
|
.includeFailed(true)
|
|
389
|
-
.join(
|
|
390
|
-
.call()
|
|
392
|
+
.join('transactions')
|
|
393
|
+
.call()
|
|
391
394
|
|
|
392
395
|
if (!rawOperations || !rawOperations.records.length) {
|
|
393
|
-
return []
|
|
396
|
+
return []
|
|
394
397
|
}
|
|
395
398
|
|
|
396
399
|
operations = operations.concat(
|
|
397
|
-
await rawOperationsToOperations(rawOperations.records as RawOperation[], addr, accountId, 0)
|
|
398
|
-
)
|
|
400
|
+
await rawOperationsToOperations(rawOperations.records as RawOperation[], addr, accountId, 0)
|
|
401
|
+
)
|
|
399
402
|
|
|
400
403
|
while (rawOperations.records.length > 0) {
|
|
401
404
|
if (maxOperations && fetchedOpsCount >= maxOperations) {
|
|
402
|
-
break
|
|
405
|
+
break
|
|
403
406
|
}
|
|
404
|
-
fetchedOpsCount += limit
|
|
407
|
+
fetchedOpsCount += limit
|
|
405
408
|
|
|
406
|
-
rawOperations = await rawOperations.next()
|
|
409
|
+
rawOperations = await rawOperations.next()
|
|
407
410
|
operations = operations.concat(
|
|
408
|
-
await rawOperationsToOperations(
|
|
409
|
-
|
|
410
|
-
addr,
|
|
411
|
-
accountId,
|
|
412
|
-
0,
|
|
413
|
-
),
|
|
414
|
-
);
|
|
411
|
+
await rawOperationsToOperations(rawOperations.records as RawOperation[], addr, accountId, 0)
|
|
412
|
+
)
|
|
415
413
|
}
|
|
416
414
|
|
|
417
|
-
return operations
|
|
415
|
+
return operations
|
|
418
416
|
} catch (e: unknown) {
|
|
419
417
|
// FIXME: terrible hacks, because Stellar SDK fails to cast network failures to typed errors in react-native...
|
|
420
418
|
// (https://github.com/stellar/js-stellar-sdk/issues/638)
|
|
421
|
-
const errorMsg = e ? String(e) :
|
|
419
|
+
const errorMsg = e ? String(e) : ''
|
|
422
420
|
|
|
423
421
|
if (e instanceof NotFoundError || errorMsg.match(/status code 404/)) {
|
|
424
|
-
return []
|
|
422
|
+
return []
|
|
425
423
|
}
|
|
426
424
|
|
|
427
425
|
if (errorMsg.match(/status code 4[0-9]{2}/)) {
|
|
428
|
-
throw new LedgerAPI4xx()
|
|
426
|
+
throw new LedgerAPI4xx()
|
|
429
427
|
}
|
|
430
428
|
|
|
431
429
|
if (errorMsg.match(/status code 5[0-9]{2}/)) {
|
|
432
|
-
throw new LedgerAPI5xx()
|
|
430
|
+
throw new LedgerAPI5xx()
|
|
433
431
|
}
|
|
434
432
|
|
|
435
433
|
if (
|
|
@@ -437,10 +435,10 @@ export async function fetchAllOperations(
|
|
|
437
435
|
errorMsg.match(/ECONNRESET|ECONNREFUSED|ENOTFOUND|EPIPE|ETIMEDOUT/) ||
|
|
438
436
|
errorMsg.match(/undefined is not an object/)
|
|
439
437
|
) {
|
|
440
|
-
throw new NetworkDown()
|
|
438
|
+
throw new NetworkDown()
|
|
441
439
|
}
|
|
442
440
|
|
|
443
|
-
throw e
|
|
441
|
+
throw e
|
|
444
442
|
}
|
|
445
443
|
}
|
|
446
444
|
|
|
@@ -453,19 +451,19 @@ export async function fetchOperations({
|
|
|
453
451
|
cursor,
|
|
454
452
|
limit,
|
|
455
453
|
}: {
|
|
456
|
-
accountId: string
|
|
457
|
-
addr: string
|
|
458
|
-
minHeight: number
|
|
459
|
-
order:
|
|
460
|
-
cursor: string | undefined
|
|
461
|
-
limit?: number | undefined
|
|
454
|
+
accountId: string
|
|
455
|
+
addr: string
|
|
456
|
+
minHeight: number
|
|
457
|
+
order: 'asc' | 'desc'
|
|
458
|
+
cursor: string | undefined
|
|
459
|
+
limit?: number | undefined
|
|
462
460
|
}): Promise<Page<Operation>> {
|
|
463
|
-
const noResult: Page<Operation> = { items: [], next:
|
|
461
|
+
const noResult: Page<Operation> = { items: [], next: '' }
|
|
464
462
|
if (!addr) {
|
|
465
|
-
return noResult
|
|
463
|
+
return noResult
|
|
466
464
|
}
|
|
467
465
|
|
|
468
|
-
const requestedLimit = limit ?? coinConfig.getCoinConfig().explorer.fetchLimit ?? FETCH_LIMIT
|
|
466
|
+
const requestedLimit = limit ?? coinConfig.getCoinConfig().explorer.fetchLimit ?? FETCH_LIMIT
|
|
469
467
|
|
|
470
468
|
try {
|
|
471
469
|
const rawOperations = await getServer()
|
|
@@ -473,41 +471,41 @@ export async function fetchOperations({
|
|
|
473
471
|
.forAccount(addr)
|
|
474
472
|
.limit(requestedLimit)
|
|
475
473
|
.order(order)
|
|
476
|
-
.cursor(cursor ??
|
|
474
|
+
.cursor(cursor ?? '')
|
|
477
475
|
.includeFailed(true)
|
|
478
|
-
.join(
|
|
479
|
-
.call()
|
|
476
|
+
.join('transactions')
|
|
477
|
+
.call()
|
|
480
478
|
|
|
481
479
|
if (!rawOperations || !rawOperations.records.length) {
|
|
482
|
-
return noResult
|
|
480
|
+
return noResult
|
|
483
481
|
}
|
|
484
482
|
|
|
485
|
-
const rawOps = rawOperations.records as RawOperation[]
|
|
486
|
-
const filteredOps = await rawOperationsToOperations(rawOps, addr, accountId, minHeight)
|
|
483
|
+
const rawOps = rawOperations.records as RawOperation[]
|
|
484
|
+
const filteredOps = await rawOperationsToOperations(rawOps, addr, accountId, minHeight)
|
|
487
485
|
|
|
488
486
|
// Always return the cursor so the caller can continue pagination,
|
|
489
487
|
// even when filteredOps is empty (e.g. page of unsupported types).
|
|
490
|
-
const nextCursor = rawOps.length < requestedLimit ?
|
|
488
|
+
const nextCursor = rawOps.length < requestedLimit ? '' : rawOps[rawOps.length - 1].paging_token
|
|
491
489
|
|
|
492
|
-
return { items: filteredOps, next: nextCursor }
|
|
490
|
+
return { items: filteredOps, next: nextCursor }
|
|
493
491
|
} catch (e: unknown) {
|
|
494
492
|
// FIXME: terrible hacks, because Stellar SDK fails to cast network failures to typed errors in react-native...
|
|
495
493
|
// (https://github.com/stellar/js-stellar-sdk/issues/638)
|
|
496
494
|
// update 2025-04-01: in case of NetworkError, the error.response fields are undefined. Hence we cannot rely on status code
|
|
497
495
|
// the only way to check is the errror message, which may break at some point
|
|
498
|
-
const errorMsg = e ? String(e) :
|
|
496
|
+
const errorMsg = e ? String(e) : ''
|
|
499
497
|
|
|
500
498
|
if (e instanceof NotFoundError || errorMsg.match(/status code 404/)) {
|
|
501
|
-
return noResult
|
|
499
|
+
return noResult
|
|
502
500
|
}
|
|
503
501
|
if (errorMsg.match(/too many requests/i)) {
|
|
504
|
-
throw new LedgerAPI4xx(
|
|
502
|
+
throw new LedgerAPI4xx('status code 4xx', { status: 429, url: undefined, method: 'GET' })
|
|
505
503
|
}
|
|
506
504
|
if (errorMsg.match(/status code 4[0-9]{2}/)) {
|
|
507
|
-
throw new LedgerAPI4xx()
|
|
505
|
+
throw new LedgerAPI4xx()
|
|
508
506
|
}
|
|
509
507
|
if (errorMsg.match(/status code 5[0-9]{2}/)) {
|
|
510
|
-
throw new LedgerAPI5xx()
|
|
508
|
+
throw new LedgerAPI5xx()
|
|
511
509
|
}
|
|
512
510
|
|
|
513
511
|
if (
|
|
@@ -515,101 +513,101 @@ export async function fetchOperations({
|
|
|
515
513
|
errorMsg.match(/ECONNRESET|ECONNREFUSED|ENOTFOUND|EPIPE|ETIMEDOUT/) ||
|
|
516
514
|
errorMsg.match(/undefined is not an object/)
|
|
517
515
|
) {
|
|
518
|
-
throw new NetworkDown()
|
|
516
|
+
throw new NetworkDown()
|
|
519
517
|
}
|
|
520
518
|
|
|
521
|
-
throw e
|
|
519
|
+
throw e
|
|
522
520
|
}
|
|
523
521
|
}
|
|
524
522
|
|
|
525
523
|
export async function fetchAccountNetworkInfo(account: string): Promise<NetworkInfo> {
|
|
526
524
|
try {
|
|
527
|
-
const extendedAccount = await getServer().accounts().accountId(account).call()
|
|
528
|
-
const baseReserve = getReservedBalance(extendedAccount)
|
|
529
|
-
const { recommendedFee, networkCongestionLevel, baseFee } = await fetchBaseFee()
|
|
525
|
+
const extendedAccount = await getServer().accounts().accountId(account).call()
|
|
526
|
+
const baseReserve = getReservedBalance(extendedAccount)
|
|
527
|
+
const { recommendedFee, networkCongestionLevel, baseFee } = await fetchBaseFee()
|
|
530
528
|
|
|
531
529
|
return {
|
|
532
|
-
family:
|
|
530
|
+
family: 'stellar',
|
|
533
531
|
fees: new BigNumber(recommendedFee.toString()),
|
|
534
532
|
baseFee: new BigNumber(baseFee.toString()),
|
|
535
533
|
baseReserve,
|
|
536
534
|
networkCongestionLevel,
|
|
537
|
-
}
|
|
535
|
+
}
|
|
538
536
|
} catch {
|
|
539
537
|
return {
|
|
540
|
-
family:
|
|
538
|
+
family: 'stellar',
|
|
541
539
|
fees: new BigNumber(0),
|
|
542
540
|
baseFee: new BigNumber(100),
|
|
543
541
|
baseReserve: new BigNumber(0),
|
|
544
|
-
}
|
|
542
|
+
}
|
|
545
543
|
}
|
|
546
544
|
}
|
|
547
545
|
|
|
548
546
|
export async function fetchSequence(address: string): Promise<BigNumber> {
|
|
549
|
-
const extendedAccount = await loadAccount(address)
|
|
550
|
-
return extendedAccount ? new BigNumber(extendedAccount.sequence) : new BigNumber(0)
|
|
547
|
+
const extendedAccount = await loadAccount(address)
|
|
548
|
+
return extendedAccount ? new BigNumber(extendedAccount.sequence) : new BigNumber(0)
|
|
551
549
|
}
|
|
552
550
|
|
|
553
551
|
export async function fetchSigners(account: string): Promise<Signer[]> {
|
|
554
552
|
try {
|
|
555
|
-
const extendedAccount = await getServer().accounts().accountId(account).call()
|
|
556
|
-
return extendedAccount.signers
|
|
553
|
+
const extendedAccount = await getServer().accounts().accountId(account).call()
|
|
554
|
+
return extendedAccount.signers
|
|
557
555
|
} catch {
|
|
558
|
-
return []
|
|
556
|
+
return []
|
|
559
557
|
}
|
|
560
558
|
}
|
|
561
559
|
|
|
562
560
|
function interpretedError(error: unknown): unknown {
|
|
563
|
-
const body = getHorizonBodyFromSubmitFailure(error)
|
|
561
|
+
const body = getHorizonBodyFromSubmitFailure(error)
|
|
564
562
|
if (body) {
|
|
565
|
-
const cause = error instanceof Error ? error : new Error(String(error))
|
|
566
|
-
return makeBroadcastFailedError(body, cause)
|
|
563
|
+
const cause = error instanceof Error ? error : new Error(String(error))
|
|
564
|
+
return makeBroadcastFailedError(body, cause)
|
|
567
565
|
}
|
|
568
|
-
return error
|
|
566
|
+
return error
|
|
569
567
|
}
|
|
570
568
|
|
|
571
569
|
export async function broadcastTransaction(signedTransaction: string): Promise<string> {
|
|
572
570
|
try {
|
|
573
|
-
patchHermesTypedArraysIfNeeded()
|
|
574
|
-
const transaction = new StellarSdkTransaction(signedTransaction, Networks.PUBLIC)
|
|
571
|
+
patchHermesTypedArraysIfNeeded()
|
|
572
|
+
const transaction = new StellarSdkTransaction(signedTransaction, Networks.PUBLIC)
|
|
575
573
|
|
|
576
574
|
try {
|
|
577
575
|
const res = await getServer().submitTransaction(transaction, {
|
|
578
576
|
skipMemoRequiredCheck: true,
|
|
579
|
-
})
|
|
580
|
-
return res.hash
|
|
577
|
+
})
|
|
578
|
+
return res.hash
|
|
581
579
|
} catch (error: unknown) {
|
|
582
|
-
throw interpretedError(error)
|
|
580
|
+
throw interpretedError(error)
|
|
583
581
|
}
|
|
584
582
|
} finally {
|
|
585
583
|
// Restore
|
|
586
|
-
unpatchHermesTypedArrays()
|
|
584
|
+
unpatchHermesTypedArrays()
|
|
587
585
|
}
|
|
588
586
|
}
|
|
589
587
|
|
|
590
588
|
export async function loadAccount(addr: string): Promise<Horizon.AccountResponse | null> {
|
|
591
589
|
if (!addr || !addr.length) {
|
|
592
|
-
return null
|
|
590
|
+
return null
|
|
593
591
|
}
|
|
594
592
|
|
|
595
593
|
try {
|
|
596
|
-
return await getServer().loadAccount(addr)
|
|
594
|
+
return await getServer().loadAccount(addr)
|
|
597
595
|
} catch {
|
|
598
|
-
return null
|
|
596
|
+
return null
|
|
599
597
|
}
|
|
600
598
|
}
|
|
601
599
|
|
|
602
600
|
export async function getLastBlock(): Promise<{
|
|
603
|
-
height: number
|
|
604
|
-
hash: string
|
|
605
|
-
time: Date
|
|
601
|
+
height: number
|
|
602
|
+
hash: string
|
|
603
|
+
time: Date
|
|
606
604
|
}> {
|
|
607
|
-
const ledger = await getServer().ledgers().order(
|
|
605
|
+
const ledger = await getServer().ledgers().order('desc').limit(1).call()
|
|
608
606
|
return {
|
|
609
607
|
height: ledger.records[0].sequence,
|
|
610
608
|
hash: ledger.records[0].hash,
|
|
611
609
|
time: new Date(ledger.records[0].closed_at),
|
|
612
|
-
}
|
|
610
|
+
}
|
|
613
611
|
}
|
|
614
612
|
|
|
615
613
|
/**
|
|
@@ -622,11 +620,11 @@ export async function getLastBlock(): Promise<{
|
|
|
622
620
|
*/
|
|
623
621
|
export async function fetchLedgerRecord(sequence: number): Promise<Horizon.ServerApi.LedgerRecord> {
|
|
624
622
|
try {
|
|
625
|
-
const record = await getServer().ledgers().ledger(sequence).call()
|
|
623
|
+
const record = await getServer().ledgers().ledger(sequence).call()
|
|
626
624
|
// Horizon returns a single ledger; SDK typings still use CollectionPage for this builder.
|
|
627
|
-
return record as unknown as Horizon.ServerApi.LedgerRecord
|
|
625
|
+
return record as unknown as Horizon.ServerApi.LedgerRecord
|
|
628
626
|
} catch (e: unknown) {
|
|
629
|
-
throwHorizonLedgerOrOperationsError(e, `Stellar ledger ${sequence} not found`)
|
|
627
|
+
throwHorizonLedgerOrOperationsError(e, `Stellar ledger ${sequence} not found`)
|
|
630
628
|
}
|
|
631
629
|
}
|
|
632
630
|
|
|
@@ -637,95 +635,95 @@ export async function fetchLedgerRecord(sequence: number): Promise<Horizon.Serve
|
|
|
637
635
|
* @throws Error when the ledger does not exist (Horizon 404).
|
|
638
636
|
*/
|
|
639
637
|
export async function fetchAllLedgerOperations(ledgerSequence: number): Promise<RawOperation[]> {
|
|
640
|
-
const limit = coinConfig.getCoinConfig().explorer.fetchLimit ?? FETCH_LIMIT
|
|
638
|
+
const limit = coinConfig.getCoinConfig().explorer.fetchLimit ?? FETCH_LIMIT
|
|
641
639
|
|
|
642
640
|
try {
|
|
643
641
|
let response = await getServer()
|
|
644
642
|
.operations()
|
|
645
643
|
.forLedger(ledgerSequence)
|
|
646
644
|
.includeFailed(true)
|
|
647
|
-
.join(
|
|
648
|
-
.order(
|
|
645
|
+
.join('transactions')
|
|
646
|
+
.order('asc')
|
|
649
647
|
.limit(limit)
|
|
650
|
-
.call()
|
|
648
|
+
.call()
|
|
651
649
|
|
|
652
|
-
const records: RawOperation[] = [...(response.records as RawOperation[])]
|
|
650
|
+
const records: RawOperation[] = [...(response.records as RawOperation[])]
|
|
653
651
|
|
|
654
652
|
while (response.records.length === limit) {
|
|
655
|
-
response = await response.next()
|
|
656
|
-
records.push(...(response.records as RawOperation[]))
|
|
653
|
+
response = await response.next()
|
|
654
|
+
records.push(...(response.records as RawOperation[]))
|
|
657
655
|
if (response.records.length === 0) {
|
|
658
|
-
break
|
|
656
|
+
break
|
|
659
657
|
}
|
|
660
658
|
}
|
|
661
659
|
|
|
662
|
-
return records
|
|
660
|
+
return records
|
|
663
661
|
} catch (e: unknown) {
|
|
664
|
-
throwHorizonLedgerOrOperationsError(e, `Stellar ledger ${ledgerSequence} not found`)
|
|
662
|
+
throwHorizonLedgerOrOperationsError(e, `Stellar ledger ${ledgerSequence} not found`)
|
|
665
663
|
}
|
|
666
664
|
}
|
|
667
665
|
|
|
668
666
|
export const getRecipientAccount: CacheRes<
|
|
669
667
|
Array<{
|
|
670
|
-
recipient: string
|
|
668
|
+
recipient: string
|
|
671
669
|
}>,
|
|
672
670
|
{
|
|
673
|
-
id: string | null
|
|
674
|
-
isMuxedAccount: boolean
|
|
675
|
-
assetIds: string[]
|
|
671
|
+
id: string | null
|
|
672
|
+
isMuxedAccount: boolean
|
|
673
|
+
assetIds: string[]
|
|
676
674
|
} | null
|
|
677
675
|
> = makeLRUCache(
|
|
678
676
|
async ({ recipient }) => await recipientAccount(recipient),
|
|
679
|
-
extract => extract.recipient,
|
|
677
|
+
(extract) => extract.recipient,
|
|
680
678
|
{
|
|
681
679
|
max: 300,
|
|
682
680
|
ttl: 5 * 60,
|
|
683
|
-
}
|
|
684
|
-
)
|
|
681
|
+
} // 5 minutes
|
|
682
|
+
)
|
|
685
683
|
|
|
686
684
|
async function recipientAccount(address?: string): Promise<{
|
|
687
|
-
id: string | null
|
|
688
|
-
isMuxedAccount: boolean
|
|
689
|
-
assetIds: string[]
|
|
685
|
+
id: string | null
|
|
686
|
+
isMuxedAccount: boolean
|
|
687
|
+
assetIds: string[]
|
|
690
688
|
} | null> {
|
|
691
689
|
if (!address) {
|
|
692
|
-
return null
|
|
690
|
+
return null
|
|
693
691
|
}
|
|
694
692
|
|
|
695
|
-
let accountAddress = address
|
|
693
|
+
let accountAddress = address
|
|
696
694
|
|
|
697
|
-
const isMuxedAccount = StrKey.isValidMed25519PublicKey(address)
|
|
695
|
+
const isMuxedAccount = StrKey.isValidMed25519PublicKey(address)
|
|
698
696
|
|
|
699
697
|
if (isMuxedAccount) {
|
|
700
|
-
const muxedAccount = MuxedAccount.fromAddress(address,
|
|
701
|
-
accountAddress = muxedAccount.baseAccount().accountId()
|
|
698
|
+
const muxedAccount = MuxedAccount.fromAddress(address, '0')
|
|
699
|
+
accountAddress = muxedAccount.baseAccount().accountId()
|
|
702
700
|
}
|
|
703
701
|
|
|
704
|
-
const account: Horizon.AccountResponse | null = await loadAccount(accountAddress)
|
|
702
|
+
const account: Horizon.AccountResponse | null = await loadAccount(accountAddress)
|
|
705
703
|
|
|
706
704
|
if (!account) {
|
|
707
|
-
return null
|
|
705
|
+
return null
|
|
708
706
|
}
|
|
709
707
|
|
|
710
708
|
return {
|
|
711
709
|
id: account.id,
|
|
712
710
|
isMuxedAccount,
|
|
713
711
|
assetIds: account.balances.reduce((allAssets: any[], balance: any) => {
|
|
714
|
-
return [...allAssets, getBalanceId(balance)]
|
|
712
|
+
return [...allAssets, getBalanceId(balance)]
|
|
715
713
|
}, []),
|
|
716
|
-
}
|
|
714
|
+
}
|
|
717
715
|
}
|
|
718
716
|
|
|
719
717
|
function getBalanceId(balance: BalanceAsset): string | null {
|
|
720
718
|
switch (balance.asset_type) {
|
|
721
|
-
case
|
|
722
|
-
return
|
|
723
|
-
case
|
|
724
|
-
return balance.liquidity_pool_id || null
|
|
725
|
-
case
|
|
726
|
-
case
|
|
727
|
-
return `${balance.asset_code}:${balance.asset_issuer}
|
|
719
|
+
case 'native':
|
|
720
|
+
return 'native'
|
|
721
|
+
case 'liquidity_pool_shares':
|
|
722
|
+
return balance.liquidity_pool_id || null
|
|
723
|
+
case 'credit_alphanum4':
|
|
724
|
+
case 'credit_alphanum12':
|
|
725
|
+
return `${balance.asset_code}:${balance.asset_issuer}`
|
|
728
726
|
default:
|
|
729
|
-
return null
|
|
727
|
+
return null
|
|
730
728
|
}
|
|
731
729
|
}
|