@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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { BadResponseError } from '@stellar/stellar-sdk'
|
|
5
|
+
import {
|
|
6
|
+
documentationSummaryFromHorizonBody,
|
|
7
|
+
getHorizonErrorBody,
|
|
8
|
+
isHorizonErrorBody,
|
|
9
|
+
} from './horizonErrorBody'
|
|
10
|
+
|
|
11
|
+
describe('isHorizonErrorBody', () => {
|
|
12
|
+
it('accepts RFC 7807 problem bodies with title and status', () => {
|
|
13
|
+
expect(
|
|
14
|
+
isHorizonErrorBody({
|
|
15
|
+
type: 'https://stellar.org/horizon-errors/transaction_malformed',
|
|
16
|
+
title: 'Transaction Malformed',
|
|
17
|
+
status: 400,
|
|
18
|
+
detail: 'Horizon could not decode the transaction envelope in this request.',
|
|
19
|
+
})
|
|
20
|
+
).toBe(true)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('rejects non-problem payloads', () => {
|
|
24
|
+
expect(isHorizonErrorBody({ title: 'Missing status' })).toBe(false)
|
|
25
|
+
expect(isHorizonErrorBody(null)).toBe(false)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('getHorizonErrorBody', () => {
|
|
30
|
+
const body = {
|
|
31
|
+
type: 'https://stellar.org/horizon-errors/bad_request',
|
|
32
|
+
title: 'Bad Request',
|
|
33
|
+
status: 400,
|
|
34
|
+
detail: 'Invalid cursor.',
|
|
35
|
+
extras: { reason: 'cursor is invalid' },
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
it('reads BadResponseError.response', () => {
|
|
39
|
+
const error = new BadResponseError('bad', body)
|
|
40
|
+
expect(getHorizonErrorBody(error)).toEqual(body)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('reads axios-like error.response.data', () => {
|
|
44
|
+
const error = Object.assign(new Error('Request failed with status code 400'), {
|
|
45
|
+
response: { data: body },
|
|
46
|
+
})
|
|
47
|
+
expect(getHorizonErrorBody(error)).toEqual(body)
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('documentationSummaryFromHorizonBody', () => {
|
|
52
|
+
const genericDetail =
|
|
53
|
+
'The transaction failed when submitted to the Stellar network. The `extras.result_codes` field on this response contains further details.'
|
|
54
|
+
|
|
55
|
+
it('prefers operation result code over generic tx_failed', () => {
|
|
56
|
+
expect(
|
|
57
|
+
documentationSummaryFromHorizonBody({
|
|
58
|
+
title: 'Transaction Failed',
|
|
59
|
+
status: 400,
|
|
60
|
+
detail: genericDetail,
|
|
61
|
+
extras: {
|
|
62
|
+
result_codes: {
|
|
63
|
+
transaction: 'tx_failed',
|
|
64
|
+
operations: ['op_underfunded'],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
).toBe(
|
|
69
|
+
'The source account does not have enough balance to send the payment amount while maintaining its minimum reserve.'
|
|
70
|
+
)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('skips op_inner and uses the first failing operation code', () => {
|
|
74
|
+
expect(
|
|
75
|
+
documentationSummaryFromHorizonBody({
|
|
76
|
+
title: 'Transaction Failed',
|
|
77
|
+
status: 400,
|
|
78
|
+
detail: genericDetail,
|
|
79
|
+
extras: {
|
|
80
|
+
result_codes: {
|
|
81
|
+
transaction: 'tx_failed',
|
|
82
|
+
operations: ['op_inner', 'op_underfunded'],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
).toBe(
|
|
87
|
+
'The source account does not have enough balance to send the payment amount while maintaining its minimum reserve.'
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('uses transaction code when it is specific', () => {
|
|
92
|
+
expect(
|
|
93
|
+
documentationSummaryFromHorizonBody({
|
|
94
|
+
title: 'Transaction Failed',
|
|
95
|
+
status: 400,
|
|
96
|
+
detail: genericDetail,
|
|
97
|
+
extras: {
|
|
98
|
+
result_codes: { transaction: 'tx_bad_seq' },
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
).toBe('Sequence number does not match source account.')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('uses Horizon detail when no result codes are present', () => {
|
|
105
|
+
expect(
|
|
106
|
+
documentationSummaryFromHorizonBody({
|
|
107
|
+
title: 'Transaction Malformed',
|
|
108
|
+
status: 400,
|
|
109
|
+
detail: 'Horizon could not decode the transaction envelope in this request.',
|
|
110
|
+
extras: { envelope_xdr: 'AAAA' },
|
|
111
|
+
})
|
|
112
|
+
).toBe('Horizon could not decode the transaction envelope in this request.')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('uses extras.reason for validation errors', () => {
|
|
116
|
+
expect(
|
|
117
|
+
documentationSummaryFromHorizonBody({
|
|
118
|
+
title: 'Bad Request',
|
|
119
|
+
status: 400,
|
|
120
|
+
extras: { invalid_field: 'cursor', reason: 'cursor is invalid' },
|
|
121
|
+
})
|
|
122
|
+
).toBe('cursor is invalid (field: cursor)')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('falls back to title when nothing else is available', () => {
|
|
126
|
+
expect(
|
|
127
|
+
documentationSummaryFromHorizonBody({
|
|
128
|
+
title: 'Rate Limited',
|
|
129
|
+
status: 429,
|
|
130
|
+
})
|
|
131
|
+
).toBe('Rate Limited')
|
|
132
|
+
})
|
|
133
|
+
})
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { BadResponseError } from '@stellar/stellar-sdk'
|
|
5
|
+
|
|
6
|
+
/** Horizon RFC 7807 problem+json body (subset used by coin-stellar). */
|
|
7
|
+
export type HorizonErrorBody = {
|
|
8
|
+
type?: string
|
|
9
|
+
title?: string
|
|
10
|
+
status?: number
|
|
11
|
+
detail?: string
|
|
12
|
+
extras?: {
|
|
13
|
+
envelope_xdr?: string
|
|
14
|
+
result_xdr?: string
|
|
15
|
+
result_codes?: {
|
|
16
|
+
transaction?: string
|
|
17
|
+
operations?: string[]
|
|
18
|
+
}
|
|
19
|
+
invalid_field?: string
|
|
20
|
+
reason?: string
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const GENERIC_TRANSACTION_FAILED_DETAIL =
|
|
25
|
+
/The transaction failed when submitted to the Stellar network/i
|
|
26
|
+
|
|
27
|
+
/** Horizon `extras.result_codes.transaction` → one-line description (Stellar docs). */
|
|
28
|
+
const HORIZON_TRANSACTION_RESULT_CODE_DOCUMENTATION: Record<string, string> = {
|
|
29
|
+
tx_success: 'The transaction succeeded.',
|
|
30
|
+
tx_failed: 'One of the operations failed (none were applied).',
|
|
31
|
+
tx_too_early: 'The ledger closeTime was before the minTime.',
|
|
32
|
+
tx_too_late: 'The ledger closeTime was after the maxTime.',
|
|
33
|
+
tx_missing_operation: 'No operation was specified.',
|
|
34
|
+
tx_bad_seq: 'Sequence number does not match source account.',
|
|
35
|
+
tx_bad_auth: 'Too few valid signatures / wrong network.',
|
|
36
|
+
tx_insufficient_balance: 'Fee would bring account below reserve.',
|
|
37
|
+
tx_no_source_account: 'Source account not found.',
|
|
38
|
+
tx_insufficient_fee: 'Fee is too small.',
|
|
39
|
+
tx_bad_auth_extra: 'Unused signatures attached to transaction.',
|
|
40
|
+
tx_internal_error: 'An unknown error occurred.',
|
|
41
|
+
tx_fee_bump_inner_success: 'Fee bump inner transaction succeeded.',
|
|
42
|
+
tx_fee_bump_inner_failed: 'Fee bump inner transaction failed.',
|
|
43
|
+
tx_not_supported: 'Transaction type not supported.',
|
|
44
|
+
tx_bad_sponsorship: 'Sponsorship is invalid.',
|
|
45
|
+
tx_bad_min_seq_age_or_gap: 'Minimum sequence age or gap precondition failed.',
|
|
46
|
+
tx_malformed: 'Transaction is malformed.',
|
|
47
|
+
tx_soroban_invalid: 'Soroban transaction is invalid.',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Horizon `extras.result_codes.operations[]` → one-line description (Stellar docs). */
|
|
51
|
+
const HORIZON_OPERATION_RESULT_CODE_DOCUMENTATION: Record<string, string> = {
|
|
52
|
+
op_inner: 'The inner object result is valid and the operation was a success.',
|
|
53
|
+
op_bad_auth:
|
|
54
|
+
'There are too few valid signatures, or the transaction was submitted to the wrong network.',
|
|
55
|
+
op_no_source_account: 'The source account was not found.',
|
|
56
|
+
op_no_account: 'The source account was not found.',
|
|
57
|
+
op_not_supported: 'The operation is not supported at this time.',
|
|
58
|
+
op_too_many_subentries: 'Max number of subentries (1000) already reached.',
|
|
59
|
+
op_exceeded_work_limit: 'Operation did too much work.',
|
|
60
|
+
op_malformed: 'The operation input is invalid.',
|
|
61
|
+
op_underfunded:
|
|
62
|
+
'The source account does not have enough balance to send the payment amount while maintaining its minimum reserve.',
|
|
63
|
+
op_src_no_trust:
|
|
64
|
+
'The source account does not have a trustline for the asset it is trying to send.',
|
|
65
|
+
op_src_not_authorized: 'The source account is not authorized to send this asset.',
|
|
66
|
+
op_no_destination: 'The destination account does not exist.',
|
|
67
|
+
op_no_trust: 'The destination account does not have a trustline for the asset being sent.',
|
|
68
|
+
op_not_authorized: 'The destination account is not authorized to hold this asset.',
|
|
69
|
+
op_line_full:
|
|
70
|
+
'The destination account does not have sufficient limits to receive the amount and still satisfy its buying liabilities.',
|
|
71
|
+
op_no_issuer: 'The issuer of the asset does not exist.',
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Operation result codes that mark success for a prior op in a multi-op failure list. */
|
|
75
|
+
const HORIZON_OPERATION_SUCCESS_RESULT_CODES = new Set<string>(['op_inner'])
|
|
76
|
+
|
|
77
|
+
function failingOperationCode(operations: string[] | undefined): string | undefined {
|
|
78
|
+
return operations?.find((code) => !HORIZON_OPERATION_SUCCESS_RESULT_CODES.has(code))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isHorizonErrorBody(data: unknown): data is HorizonErrorBody {
|
|
82
|
+
return !!(
|
|
83
|
+
data &&
|
|
84
|
+
typeof data === 'object' &&
|
|
85
|
+
'title' in data &&
|
|
86
|
+
typeof data.title === 'string' &&
|
|
87
|
+
'status' in data &&
|
|
88
|
+
typeof data.status === 'number'
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Horizon failures are usually {@link BadResponseError} with `response` = problem+json body.
|
|
94
|
+
* Axios rejects first; the SDK forwards it unchanged, so the body lives on `error.response.data`.
|
|
95
|
+
*/
|
|
96
|
+
export function getHorizonErrorBody(error: unknown): HorizonErrorBody | null {
|
|
97
|
+
if (error instanceof BadResponseError && isHorizonErrorBody(error.response)) {
|
|
98
|
+
return error.response
|
|
99
|
+
}
|
|
100
|
+
if (error && typeof error === 'object' && 'response' in error) {
|
|
101
|
+
const data = (error as { response?: { data?: unknown } }).response?.data
|
|
102
|
+
if (isHorizonErrorBody(data)) {
|
|
103
|
+
return data
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function lookupOperationDocumentation(operationCode: string | undefined): string | undefined {
|
|
110
|
+
if (!operationCode) {
|
|
111
|
+
return undefined
|
|
112
|
+
}
|
|
113
|
+
return HORIZON_OPERATION_RESULT_CODE_DOCUMENTATION[operationCode]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function lookupTransactionDocumentation(transactionCode: string | undefined): string | undefined {
|
|
117
|
+
if (!transactionCode) {
|
|
118
|
+
return undefined
|
|
119
|
+
}
|
|
120
|
+
return HORIZON_TRANSACTION_RESULT_CODE_DOCUMENTATION[transactionCode]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function detailDocumentation(body: HorizonErrorBody): string | undefined {
|
|
124
|
+
const detail = body.detail?.trim()
|
|
125
|
+
if (!detail || GENERIC_TRANSACTION_FAILED_DETAIL.test(detail)) {
|
|
126
|
+
return undefined
|
|
127
|
+
}
|
|
128
|
+
return detail
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extrasReasonDocumentation(body: HorizonErrorBody): string | undefined {
|
|
132
|
+
const reason = body.extras?.reason?.trim()
|
|
133
|
+
if (!reason) {
|
|
134
|
+
return undefined
|
|
135
|
+
}
|
|
136
|
+
const invalidField = body.extras?.invalid_field?.trim()
|
|
137
|
+
return invalidField ? `${reason} (field: ${invalidField})` : reason
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Best-effort human-readable summary for logs (`errorExtras.documentationSummary`).
|
|
142
|
+
* Prefers operation result codes, then transaction codes, then Horizon `detail` / `extras.reason`.
|
|
143
|
+
*/
|
|
144
|
+
export function documentationSummaryFromHorizonBody(body: HorizonErrorBody): string {
|
|
145
|
+
const transactionCode = body.extras?.result_codes?.transaction
|
|
146
|
+
const operationCode = failingOperationCode(body.extras?.result_codes?.operations)
|
|
147
|
+
|
|
148
|
+
const operationSummary = lookupOperationDocumentation(operationCode)
|
|
149
|
+
if (operationSummary) {
|
|
150
|
+
return operationSummary
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const transactionSummary = lookupTransactionDocumentation(transactionCode)
|
|
154
|
+
if (transactionSummary && transactionCode !== 'tx_failed') {
|
|
155
|
+
return transactionSummary
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const detail = detailDocumentation(body)
|
|
159
|
+
if (detail) {
|
|
160
|
+
return detail
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const extrasReason = extrasReasonDocumentation(body)
|
|
164
|
+
if (extrasReason) {
|
|
165
|
+
return extrasReason
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (transactionSummary) {
|
|
169
|
+
return transactionSummary
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const title = body.title?.trim()
|
|
173
|
+
if (title) {
|
|
174
|
+
return title
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return 'Unknown Horizon error.'
|
|
178
|
+
}
|
|
@@ -1,112 +1,184 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { LedgerAPI4xx, LedgerAPI5xx, NetworkDown } from '@ledgerhq/errors'
|
|
5
|
+
import { NetworkError, NotFoundError } from '@stellar/stellar-sdk'
|
|
3
6
|
import {
|
|
4
7
|
messageFromHorizonUnknown,
|
|
5
8
|
throwHorizonLedgerOrOperationsError,
|
|
6
|
-
} from
|
|
9
|
+
} from './horizonLedgerErrors'
|
|
10
|
+
|
|
11
|
+
describe('messageFromHorizonUnknown', () => {
|
|
12
|
+
it('returns Horizon documentationSummary when response body is present', () => {
|
|
13
|
+
const error = Object.assign(new Error('Request failed with status code 400'), {
|
|
14
|
+
response: {
|
|
15
|
+
data: {
|
|
16
|
+
title: 'Transaction Malformed',
|
|
17
|
+
status: 400,
|
|
18
|
+
detail: 'Horizon could not decode the transaction envelope in this request.',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
expect(messageFromHorizonUnknown(error)).toBe(
|
|
23
|
+
'Horizon could not decode the transaction envelope in this request.'
|
|
24
|
+
)
|
|
25
|
+
})
|
|
7
26
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
});
|
|
27
|
+
it('returns message for Error', () => {
|
|
28
|
+
expect(messageFromHorizonUnknown(new Error('hello'))).toBe('hello')
|
|
29
|
+
})
|
|
12
30
|
|
|
13
|
-
it(
|
|
14
|
-
expect(messageFromHorizonUnknown(
|
|
15
|
-
})
|
|
31
|
+
it('returns string as-is', () => {
|
|
32
|
+
expect(messageFromHorizonUnknown('plain')).toBe('plain')
|
|
33
|
+
})
|
|
16
34
|
|
|
17
|
-
it(
|
|
18
|
-
expect(messageFromHorizonUnknown({ code: 1 })).toBe(
|
|
19
|
-
expect(messageFromHorizonUnknown(null)).toBe(
|
|
20
|
-
expect(messageFromHorizonUnknown(undefined)).toBe(
|
|
21
|
-
})
|
|
22
|
-
})
|
|
35
|
+
it('returns empty string for non-Error non-string', () => {
|
|
36
|
+
expect(messageFromHorizonUnknown({ code: 1 })).toBe('')
|
|
37
|
+
expect(messageFromHorizonUnknown(null)).toBe('')
|
|
38
|
+
expect(messageFromHorizonUnknown(undefined)).toBe('')
|
|
39
|
+
})
|
|
40
|
+
})
|
|
23
41
|
|
|
24
|
-
describe(
|
|
25
|
-
const notFound =
|
|
42
|
+
describe('throwHorizonLedgerOrOperationsError', () => {
|
|
43
|
+
const notFound = 'ledger not found'
|
|
26
44
|
|
|
27
|
-
it(
|
|
45
|
+
it('throws notFoundMessage for NotFoundError', () => {
|
|
28
46
|
expect(() =>
|
|
29
|
-
throwHorizonLedgerOrOperationsError(new NotFoundError(
|
|
30
|
-
).toThrow(notFound)
|
|
31
|
-
})
|
|
47
|
+
throwHorizonLedgerOrOperationsError(new NotFoundError('gone', {}), notFound)
|
|
48
|
+
).toThrow(notFound)
|
|
49
|
+
})
|
|
32
50
|
|
|
33
|
-
it(
|
|
51
|
+
it('throws notFoundMessage when message contains status code 404', () => {
|
|
34
52
|
expect(() =>
|
|
35
|
-
throwHorizonLedgerOrOperationsError(new Error(
|
|
36
|
-
).toThrow(notFound)
|
|
37
|
-
})
|
|
53
|
+
throwHorizonLedgerOrOperationsError(new Error('Request failed: status code 404'), notFound)
|
|
54
|
+
).toThrow(notFound)
|
|
55
|
+
})
|
|
38
56
|
|
|
39
|
-
it(
|
|
57
|
+
it('throws notFoundMessage when Horizon problem body has status 404', () => {
|
|
58
|
+
const error = Object.assign(new Error('Request failed with status code 404'), {
|
|
59
|
+
response: {
|
|
60
|
+
data: {
|
|
61
|
+
title: 'Resource Missing',
|
|
62
|
+
status: 404,
|
|
63
|
+
detail: 'The resource at the url requested was not found.',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
expect(() => throwHorizonLedgerOrOperationsError(error, notFound)).toThrow(notFound)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('throws LedgerAPI4xx 429 for too many requests in message', () => {
|
|
40
71
|
expect(() =>
|
|
41
|
-
throwHorizonLedgerOrOperationsError(new Error(
|
|
42
|
-
).toThrow(LedgerAPI4xx)
|
|
43
|
-
})
|
|
72
|
+
throwHorizonLedgerOrOperationsError(new Error('Too Many Requests'), notFound)
|
|
73
|
+
).toThrow(LedgerAPI4xx)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('throws LedgerAPI4xx 429 when Horizon problem body has status 429', () => {
|
|
77
|
+
const error = Object.assign(new Error('Request failed with status code 429'), {
|
|
78
|
+
response: {
|
|
79
|
+
data: {
|
|
80
|
+
title: 'Rate Limited',
|
|
81
|
+
status: 429,
|
|
82
|
+
detail: 'The request was rate limited.',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
expect(() => throwHorizonLedgerOrOperationsError(error, notFound)).toThrow(
|
|
87
|
+
expect.objectContaining({ name: 'LedgerAPI4xx', status: 429 })
|
|
88
|
+
)
|
|
89
|
+
})
|
|
44
90
|
|
|
45
|
-
it(
|
|
91
|
+
it('throws LedgerAPI4xx for other 4xx status in message', () => {
|
|
46
92
|
expect(() =>
|
|
47
|
-
throwHorizonLedgerOrOperationsError(new Error(
|
|
48
|
-
).toThrow(LedgerAPI4xx)
|
|
49
|
-
})
|
|
93
|
+
throwHorizonLedgerOrOperationsError(new Error('status code 400'), notFound)
|
|
94
|
+
).toThrow(LedgerAPI4xx)
|
|
95
|
+
})
|
|
50
96
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
97
|
+
it('throws LedgerAPI4xx when Horizon problem body has status 400', () => {
|
|
98
|
+
const error = Object.assign(new Error('Request failed with status code 400'), {
|
|
99
|
+
response: {
|
|
100
|
+
data: {
|
|
101
|
+
title: 'Bad Request',
|
|
102
|
+
status: 400,
|
|
103
|
+
detail: 'The request you sent was invalid in some way.',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
expect(() => throwHorizonLedgerOrOperationsError(error, notFound)).toThrow(LedgerAPI4xx)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test.each([['401'], ['403']])(
|
|
111
|
+
'throws LedgerAPI4xx when message contains status code %s',
|
|
112
|
+
(code) => {
|
|
54
113
|
expect(() =>
|
|
55
114
|
throwHorizonLedgerOrOperationsError(
|
|
56
115
|
new Error(`Request failed: status code ${code}`),
|
|
57
|
-
notFound
|
|
58
|
-
)
|
|
59
|
-
).toThrow(LedgerAPI4xx)
|
|
60
|
-
}
|
|
61
|
-
)
|
|
116
|
+
notFound
|
|
117
|
+
)
|
|
118
|
+
).toThrow(LedgerAPI4xx)
|
|
119
|
+
}
|
|
120
|
+
)
|
|
62
121
|
|
|
63
|
-
it(
|
|
122
|
+
it('throws LedgerAPI5xx for 5xx status in message', () => {
|
|
64
123
|
expect(() =>
|
|
65
|
-
throwHorizonLedgerOrOperationsError(new Error(
|
|
66
|
-
).toThrow(LedgerAPI5xx)
|
|
67
|
-
})
|
|
124
|
+
throwHorizonLedgerOrOperationsError(new Error('status code 503'), notFound)
|
|
125
|
+
).toThrow(LedgerAPI5xx)
|
|
126
|
+
})
|
|
68
127
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
128
|
+
it('throws LedgerAPI5xx when Horizon problem body has status 503', () => {
|
|
129
|
+
const error = Object.assign(new Error('Request failed with status code 503'), {
|
|
130
|
+
response: {
|
|
131
|
+
data: {
|
|
132
|
+
title: 'Internal Server Error',
|
|
133
|
+
status: 503,
|
|
134
|
+
detail: 'An error occurred while processing this request.',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
})
|
|
138
|
+
expect(() => throwHorizonLedgerOrOperationsError(error, notFound)).toThrow(LedgerAPI5xx)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test.each([['502'], ['500']])(
|
|
142
|
+
'throws LedgerAPI5xx when message contains status code %s',
|
|
143
|
+
(code) => {
|
|
72
144
|
expect(() =>
|
|
73
|
-
throwHorizonLedgerOrOperationsError(new Error(`upstream status code ${code}`), notFound)
|
|
74
|
-
).toThrow(LedgerAPI5xx)
|
|
75
|
-
}
|
|
76
|
-
)
|
|
145
|
+
throwHorizonLedgerOrOperationsError(new Error(`upstream status code ${code}`), notFound)
|
|
146
|
+
).toThrow(LedgerAPI5xx)
|
|
147
|
+
}
|
|
148
|
+
)
|
|
77
149
|
|
|
78
|
-
it(
|
|
150
|
+
it('throws NetworkDown for NetworkError', () => {
|
|
79
151
|
expect(() =>
|
|
80
|
-
throwHorizonLedgerOrOperationsError(new NetworkError(
|
|
81
|
-
).toThrow(NetworkDown)
|
|
82
|
-
})
|
|
152
|
+
throwHorizonLedgerOrOperationsError(new NetworkError('net', {}), notFound)
|
|
153
|
+
).toThrow(NetworkDown)
|
|
154
|
+
})
|
|
83
155
|
|
|
84
156
|
test.each([
|
|
85
|
-
[
|
|
86
|
-
[
|
|
87
|
-
[
|
|
88
|
-
[
|
|
89
|
-
[
|
|
90
|
-
])(
|
|
157
|
+
['ECONNRESET', 'ECONNRESET on read'],
|
|
158
|
+
['ECONNREFUSED', 'connect ECONNREFUSED 127.0.0.1:443'],
|
|
159
|
+
['ENOTFOUND', 'getaddrinfo ENOTFOUND horizon.example'],
|
|
160
|
+
['EPIPE', 'write EPIPE broken pipe'],
|
|
161
|
+
['ETIMEDOUT', 'connect ETIMEDOUT 10.0.0.1:443'],
|
|
162
|
+
])('throws NetworkDown when message contains %s', (_label, message) => {
|
|
91
163
|
expect(() => throwHorizonLedgerOrOperationsError(new Error(message), notFound)).toThrow(
|
|
92
|
-
NetworkDown
|
|
93
|
-
)
|
|
94
|
-
})
|
|
164
|
+
NetworkDown
|
|
165
|
+
)
|
|
166
|
+
})
|
|
95
167
|
|
|
96
|
-
it(
|
|
168
|
+
it('throws NetworkDown for undefined is not an object in message', () => {
|
|
97
169
|
expect(() =>
|
|
98
|
-
throwHorizonLedgerOrOperationsError(new Error(
|
|
99
|
-
).toThrow(NetworkDown)
|
|
100
|
-
})
|
|
170
|
+
throwHorizonLedgerOrOperationsError(new Error('undefined is not an object'), notFound)
|
|
171
|
+
).toThrow(NetworkDown)
|
|
172
|
+
})
|
|
101
173
|
|
|
102
|
-
it(
|
|
103
|
-
const o = { custom: true }
|
|
174
|
+
it('rethrows unknown errors', () => {
|
|
175
|
+
const o = { custom: true }
|
|
104
176
|
try {
|
|
105
|
-
throwHorizonLedgerOrOperationsError(o, notFound)
|
|
177
|
+
throwHorizonLedgerOrOperationsError(o, notFound)
|
|
106
178
|
} catch (e) {
|
|
107
|
-
expect(e).toBe(o)
|
|
108
|
-
return
|
|
179
|
+
expect(e).toBe(o)
|
|
180
|
+
return
|
|
109
181
|
}
|
|
110
|
-
throw new Error(
|
|
111
|
-
})
|
|
112
|
-
})
|
|
182
|
+
throw new Error('expected throw')
|
|
183
|
+
})
|
|
184
|
+
})
|
|
@@ -1,45 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { LedgerAPI4xx, LedgerAPI5xx, NetworkDown } from '@ledgerhq/errors'
|
|
5
|
+
import { NetworkError, NotFoundError } from '@stellar/stellar-sdk'
|
|
6
|
+
import { documentationSummaryFromHorizonBody, getHorizonErrorBody } from './horizonErrorBody'
|
|
3
7
|
|
|
4
8
|
export function messageFromHorizonUnknown(e: unknown): string {
|
|
9
|
+
const body = getHorizonErrorBody(e)
|
|
10
|
+
if (body) {
|
|
11
|
+
return documentationSummaryFromHorizonBody(body)
|
|
12
|
+
}
|
|
5
13
|
if (e instanceof Error) {
|
|
6
|
-
return e.message
|
|
14
|
+
return e.message
|
|
7
15
|
}
|
|
8
|
-
if (typeof e ===
|
|
9
|
-
return e
|
|
16
|
+
if (typeof e === 'string') {
|
|
17
|
+
return e
|
|
10
18
|
}
|
|
11
|
-
return
|
|
19
|
+
return ''
|
|
12
20
|
}
|
|
13
21
|
|
|
14
|
-
const HORIZON_STATUS_404 = /status code 404
|
|
15
|
-
const HORIZON_TOO_MANY_REQUESTS = /too many requests/i
|
|
16
|
-
const HORIZON_STATUS_4XX = /status code 4\d{2}
|
|
17
|
-
const HORIZON_STATUS_5XX = /status code 5\d{2}
|
|
18
|
-
const HORIZON_NETWORK_DOWN = /ECONNRESET|ECONNREFUSED|ENOTFOUND|EPIPE|ETIMEDOUT
|
|
19
|
-
const HORIZON_UNDEFINED_OBJECT = /undefined is not an object
|
|
22
|
+
const HORIZON_STATUS_404 = /status code 404/
|
|
23
|
+
const HORIZON_TOO_MANY_REQUESTS = /too many requests/i
|
|
24
|
+
const HORIZON_STATUS_4XX = /status code 4\d{2}/
|
|
25
|
+
const HORIZON_STATUS_5XX = /status code 5\d{2}/
|
|
26
|
+
const HORIZON_NETWORK_DOWN = /ECONNRESET|ECONNREFUSED|ENOTFOUND|EPIPE|ETIMEDOUT/
|
|
27
|
+
const HORIZON_UNDEFINED_OBJECT = /undefined is not an object/
|
|
20
28
|
|
|
21
29
|
export function throwHorizonLedgerOrOperationsError(e: unknown, notFoundMessage: string): never {
|
|
22
|
-
|
|
30
|
+
if (e instanceof NotFoundError) {
|
|
31
|
+
throw new Error(notFoundMessage)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const body = getHorizonErrorBody(e)
|
|
35
|
+
const status = body?.status
|
|
36
|
+
if (status === 404) {
|
|
37
|
+
throw new Error(notFoundMessage)
|
|
38
|
+
}
|
|
39
|
+
if (status === 429) {
|
|
40
|
+
throw new LedgerAPI4xx('status code 4xx', { status: 429, url: undefined, method: 'GET' })
|
|
41
|
+
}
|
|
42
|
+
if (status !== undefined && status >= 400 && status < 500) {
|
|
43
|
+
throw new LedgerAPI4xx()
|
|
44
|
+
}
|
|
45
|
+
if (status !== undefined && status >= 500 && status < 600) {
|
|
46
|
+
throw new LedgerAPI5xx()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const errorMsg = messageFromHorizonUnknown(e)
|
|
23
50
|
|
|
24
|
-
if (
|
|
25
|
-
throw new Error(notFoundMessage)
|
|
51
|
+
if (HORIZON_STATUS_404.exec(errorMsg)) {
|
|
52
|
+
throw new Error(notFoundMessage)
|
|
26
53
|
}
|
|
27
54
|
if (HORIZON_TOO_MANY_REQUESTS.exec(errorMsg)) {
|
|
28
|
-
throw new LedgerAPI4xx(
|
|
55
|
+
throw new LedgerAPI4xx('status code 4xx', { status: 429, url: undefined, method: 'GET' })
|
|
29
56
|
}
|
|
30
57
|
if (HORIZON_STATUS_4XX.exec(errorMsg)) {
|
|
31
|
-
throw new LedgerAPI4xx()
|
|
58
|
+
throw new LedgerAPI4xx()
|
|
32
59
|
}
|
|
33
60
|
if (HORIZON_STATUS_5XX.exec(errorMsg)) {
|
|
34
|
-
throw new LedgerAPI5xx()
|
|
61
|
+
throw new LedgerAPI5xx()
|
|
35
62
|
}
|
|
36
63
|
if (
|
|
37
64
|
e instanceof NetworkError ||
|
|
38
65
|
HORIZON_NETWORK_DOWN.exec(errorMsg) ||
|
|
39
66
|
HORIZON_UNDEFINED_OBJECT.exec(errorMsg)
|
|
40
67
|
) {
|
|
41
|
-
throw new NetworkDown()
|
|
68
|
+
throw new NetworkDown()
|
|
42
69
|
}
|
|
43
70
|
|
|
44
|
-
throw e
|
|
71
|
+
throw e
|
|
45
72
|
}
|
package/src/network/index.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
1
4
|
export {
|
|
2
5
|
broadcastTransaction,
|
|
3
6
|
fetchAccount,
|
|
@@ -13,4 +16,4 @@ export {
|
|
|
13
16
|
getRecipientAccount,
|
|
14
17
|
loadAccount,
|
|
15
18
|
registerHorizonInterceptors,
|
|
16
|
-
} from
|
|
19
|
+
} from './horizon'
|