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