-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.ts
More file actions
326 lines (303 loc) · 10.1 KB
/
Copy patherrors.ts
File metadata and controls
326 lines (303 loc) · 10.1 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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/**
* Base error class for all ILN SDK errors.
*
* Provides structured error codes, remediation guidance, documentation links, and context.
*/
export class ILNError extends Error {
/** Machine-readable error code (e.g. "INSUFFICIENT_BALANCE"). */
public code: string;
/** Human-readable suggestion for resolving the error. */
public remediation: string;
/** Optional documentation URL for this error code. */
public docsUrl?: string;
/** Optional structured debugging context (never include secrets). */
public context?: Record<string, unknown>;
/** Whether the operation is likely retryable. */
public retryable?: boolean;
/** Preserve original error for debugging. */
public cause?: unknown;
constructor(
message: string,
code: string,
remediation: string,
options?: {
docsUrl?: string;
context?: Record<string, unknown>;
retryable?: boolean;
cause?: unknown;
}
) {
super(message);
Object.setPrototypeOf(this, new.target.prototype);
this.name = this.constructor.name;
this.code = code;
this.remediation = remediation;
if (options?.docsUrl) this.docsUrl = options.docsUrl;
if (options?.context) this.context = options.context;
if (typeof options?.retryable === 'boolean') this.retryable = options.retryable;
if (options && 'cause' in options) this.cause = options.cause;
}
}
const DEFAULT_DOCS_BASE_URL =
'https://github.com/Invoice-Liquidity-Network/Invoice-Liquidity-Network/blob/main/docs/errors.md';
function withDocs(code: string): string {
// Link to an anchor on docs/errors.md for programmatic navigation.
return `${DEFAULT_DOCS_BASE_URL}#${code.toLowerCase()}`;
}
/**
* Thrown when the provided discount rate exceeds protocol limits.
*/
export class InvalidDiscountRateError extends ILNError {
constructor(context?: Record<string, unknown>) {
super(
'Invalid discount rate.',
'INVALID_DISCOUNT_RATE',
'Check `discountRate` is within protocol bounds (see `getProtocolConfig().maxDiscountRate`). If using basis points, ensure value is in bps (e.g., 300 = 3%).',
{
docsUrl: withDocs('INVALID_DISCOUNT_RATE'),
context,
retryable: false,
}
);
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* Thrown when a token mismatch occurs in a transaction.
*/
export class TokenMismatchError extends ILNError {
constructor(context?: Record<string, unknown>) {
super(
'Token mismatch in transaction.',
'TOKEN_MISMATCH',
'Verify that the token contract ID/address used matches the token configured for the invoice/protocol.',
{
docsUrl: withDocs('TOKEN_MISMATCH'),
context,
retryable: false,
}
);
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* Thrown when the payer\'s reputation score is below the protocol minimum.
*/
export class PayerReputationTooLowError extends ILNError {
constructor(context?: Record<string, unknown>) {
super(
'Payer reputation is too low.',
'PAYER_REPUTATION_TOO_LOW',
'The payer does not meet the protocol minimum reputation threshold for this invoice. Check payer score and re-submit with an eligible payer.',
{
docsUrl: withDocs('PAYER_REPUTATION_TOO_LOW'),
context,
retryable: false,
}
);
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* Thrown when the account has insufficient balance for a transaction.
*/
export class InsufficientBalanceError extends ILNError {
constructor(
message = 'Insufficient balance to complete the transaction.',
remediation = 'Ensure the account has enough funds (including transaction fees) before retrying. If you are on testnet, fund the account and re-submit.',
context?: Record<string, unknown>
) {
super(message, 'INSUFFICIENT_BALANCE', remediation, {
docsUrl: withDocs('INSUFFICIENT_BALANCE'),
context,
retryable: true,
});
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* Thrown when a network request to the RPC server fails.
*/
export class NetworkError extends ILNError {
constructor(
message = 'Network request failed.',
remediation = 'Failed to reach the configured Stellar RPC endpoint. Verify your `rpcUrl`, check connectivity, and ensure the RPC server is healthy.',
context?: Record<string, unknown>
) {
super(message, 'NETWORK_ERROR', remediation, {
docsUrl: withDocs('NETWORK_ERROR'),
context,
retryable: true,
});
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* Thrown when a transaction fails to execute on-chain.
*/
export class TransactionFailedError extends ILNError {
constructor(
message = 'Transaction execution failed on-chain.',
remediation = 'The contract rejected the transaction. Review simulation/tx failure reason, verify invoice state, and confirm fee/resource settings.',
context?: Record<string, unknown>
) {
super(message, 'TRANSACTION_FAILED', remediation, {
docsUrl: withDocs('TRANSACTION_FAILED'),
context,
retryable: false,
});
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* Thrown when input validation fails.
*/
export class ValidationError extends ILNError {
constructor(
message = 'Validation failed.',
remediation = 'Check provided input parameters. Use `Validators` to validate fields and inspect which constraint failed.',
context?: Record<string, unknown>
) {
super(message, 'VALIDATION_ERROR', remediation, {
docsUrl: withDocs('VALIDATION_ERROR'),
context,
retryable: false,
});
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* Thrown when a wallet is required but not connected.
*/
export class WalletNotConnectedError extends ILNError {
constructor(
message = 'Wallet is not connected.',
remediation = 'A transaction signer is required for this state-changing operation. Provide a `signer` in the `ILNSdk` configuration or ensure wallet is connected.',
context?: Record<string, unknown>
) {
super(message, 'WALLET_NOT_CONNECTED', remediation, {
docsUrl: withDocs('WALLET_NOT_CONNECTED'),
context,
retryable: false,
});
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* Thrown for generic contract errors that don\'t match specific error types.
*/
export class GenericContractError extends ILNError {
constructor(rawError: string, context?: Record<string, unknown>) {
super(
`Contract error: ${rawError}`,
'CONTRACT_ERROR',
'The contract rejected the transaction. Check invoice/operation parameters and inspect on-chain error details.',
{
docsUrl: withDocs('CONTRACT_ERROR'),
context: {
rawError,
...(context ?? {}),
},
retryable: false,
}
);
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* Thrown when transaction simulation fails.
*/
export class SimulationError extends ILNError {
constructor(
message = 'Transaction simulation failed.',
remediation = 'The SDK could not simulate the transaction successfully. Review transaction parameters and ensure contract state is consistent before retrying.',
context?: Record<string, unknown>
) {
super(message, 'SIMULATION_FAILED', remediation, {
docsUrl: withDocs('SIMULATION_FAILED'),
context,
retryable: false,
});
Object.setPrototypeOf(this, new.target.prototype);
}
}
/**
* Parse a raw contract error into a typed ILNError with detailed debugging context.
*
* @param xdrError - The raw error value from the contract.
* @param signature - Optional function signature or operation name.
* @returns A typed ILNError instance.
*/
export function parseContractError(xdrError: unknown, signature?: string): ILNError {
const errorStr = typeof xdrError === 'string' ? xdrError : JSON.stringify(xdrError);
const baseContext: Record<string, unknown> = {
rawError: xdrError,
rawErrorString: errorStr,
};
if (signature) {
baseContext.matchedSignature = signature;
}
if (errorStr.includes('InvalidDiscountRate')) {
return new InvalidDiscountRateError({ ...baseContext, matchedPattern: 'InvalidDiscountRate' });
}
if (errorStr.includes('TokenMismatch')) {
return new TokenMismatchError({ ...baseContext, matchedPattern: 'TokenMismatch' });
}
if (errorStr.includes('PayerReputationTooLow')) {
return new PayerReputationTooLowError({
...baseContext,
matchedPattern: 'PayerReputationTooLow',
});
}
return new GenericContractError(errorStr, {
...baseContext,
matchedPattern: 'Unknown',
});
}
/**
* Normalizes any caught error or unknown object into a consistent, structured ILNError.
*
* @param err - The unknown error thrown by a function or network call.
* @param fallbackCode - Optional fallback code if err cannot be classified (default: 'UNKNOWN_ERROR').
* @param fallbackMessage - Optional fallback message if err has no message.
* @returns A guaranteed ILNError instance with structured code, remediation, and optional context.
*/
export function normalizeError(
err: unknown,
fallbackCode = 'UNKNOWN_ERROR',
fallbackMessage = 'An unexpected error occurred.'
): ILNError {
if (err instanceof ILNError) {
return err;
}
if (err instanceof Error) {
return new ILNError(
err.message || fallbackMessage,
fallbackCode,
'Review the cause and stack trace for details. Verify input parameters and endpoint connectivity.',
{
docsUrl: withDocs(fallbackCode),
context: { name: err.name, stack: err.stack },
cause: err,
retryable: false,
}
);
}
if (typeof err === 'string') {
return parseContractError(err);
}
const rawStr = typeof err === 'object' && err !== null ? JSON.stringify(err) : String(err);
return new ILNError(
fallbackMessage,
fallbackCode,
'An unclassified error object was thrown. Inspect the raw context object for debugging.',
{
docsUrl: withDocs(fallbackCode),
context: { raw: err, rawStr },
cause: err,
retryable: false,
}
);
}
/** Alias for normalizeError */
export const toILNError = normalizeError;