Repository navigation
Expand file tree
/
Copy pathstellarErrors.js
More file actions
147 lines (136 loc) · 4.41 KB
/
Copy pathstellarErrors.js
File metadata and controls
147 lines (136 loc) · 4.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
* Map Stellar error codes to user-friendly messages and determine if retryable.
*/
import logger from '../config/logger.js';
const STELLAR_ERROR_MAP = {
// Permanent errors
'op_underfunded': {
userMessage: 'Your account balance was too low to complete this payment.',
retryable: false,
},
'op_no_destination': {
userMessage: 'The recipient account does not exist on the network.',
retryable: false,
},
'op_no_trust': {
userMessage: 'The recipient account does not have a trustline for this asset.',
retryable: false,
},
'op_not_authorized': {
userMessage: 'You are not authorized to send this asset.',
retryable: false,
},
'op_line_full': {
userMessage: 'The recipient account has reached the balance limit for this asset.',
retryable: false,
},
'op_self_not_allowed': {
userMessage: 'You cannot send a payment to yourself.',
retryable: false,
},
'op_low_reserve': {
userMessage: 'This operation would drop your account below the minimum required XLM reserve.',
retryable: false,
},
'op_no_issuer': {
userMessage: 'The asset issuer for this operation does not exist on the network.',
retryable: false,
},
'op_already_exists': {
userMessage: 'A trustline for this asset already exists.',
retryable: false,
},
'op_malformed': {
userMessage: 'The operation was malformed. Please check the details and try again.',
retryable: false,
},
'op_immutable_set': {
userMessage: 'This account has been permanently locked and can no longer be modified.',
retryable: false,
},
'tx_bad_seq': {
userMessage: 'The account sequence number is invalid. Please try again.',
retryable: true,
},
'tx_bad_auth': {
userMessage: 'Transaction authorization failed. Please try again.',
retryable: true,
},
'tx_insufficient_fee': {
userMessage: 'The transaction fee was too low. Please try with a higher fee.',
retryable: true,
},
'tx_too_late': {
userMessage: 'The transaction time bounds have expired. Please try again.',
retryable: true,
},
'tx_too_early': {
userMessage: 'The transaction is too early. Please try again later.',
retryable: true,
},
// Transient errors
'tx_failed': {
userMessage: 'The transaction failed on the network. Please try again.',
retryable: true,
},
'connection_error': {
userMessage: 'Network connection failed. Please check your internet and try again.',
retryable: true,
},
'timeout': {
userMessage: 'The request timed out. Please try again.',
retryable: true,
},
'rate_limit': {
userMessage: 'Too many requests. Please try again in a moment.',
retryable: true,
},
};
/**
* Get a user-friendly error message for a Stellar error code.
* @param {string} errorCode - The error code from Stellar API
* @returns {{ userMessage: string, retryable: boolean }}
*/
export function getStellarErrorInfo(errorCode) {
const info = STELLAR_ERROR_MAP[errorCode];
if (info) return info;
// An unmapped code is a genuinely unknown/new result code. Presenting a
// deterministic on-chain rejection as retryable just encourages pointless
// retries against a failure that will never succeed, so default to
// non-retryable and log a warning so new codes get visibility. See #953.
logger.warn('stellarErrors.unmappedCode', { errorCode });
return {
userMessage:
'This transaction could not be completed due to an unrecognized network error. Please contact support if this persists.',
retryable: false,
};
}
/**
* Extract error code from Horizon API error response.
* @param {Error|object} error - Error object from Horizon API
* @returns {string} Error code
*/
export function extractStellarErrorCode(error) {
if (!error) return 'unknown';
// Check for various error structures
if (error.status === 400 && error.data?.extras?.result_codes) {
const resultCodes = error.data.extras.result_codes;
// Return transaction-level error if available, otherwise operation error
if (resultCodes.transaction) {
return resultCodes.transaction;
}
if (resultCodes.operations && resultCodes.operations.length > 0) {
return resultCodes.operations[0];
}
}
if (error.status >= 500) {
return 'connection_error';
}
if (error.name === 'TimeoutError' || error.code === 'ECONNABORTED') {
return 'timeout';
}
if (error.status === 429) {
return 'rate_limit';
}
return 'tx_failed';
}