forked from StellarRouter/StellarRouter
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.ts
More file actions
132 lines (122 loc) · 4.27 KB
/
Copy patherrors.ts
File metadata and controls
132 lines (122 loc) · 4.27 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
/**
* Error model for `stellar-router-sdk`.
*
* All failures surface as {@link RouterSdkError} instances carrying a stable,
* machine-readable `code` (e.g. `"RouteNotFound"`), mirroring the error names
* returned by the on-chain contracts.
*/
/** @internal contract id label used in error messages. */
const CONTRACT_LABELS: Record<string, string> = {
core: "router-core",
quote: "router-quote",
};
/** Error codes exposed by `router-core` (`RouterError`). */
export const ROUTER_CORE_ERRORS: Readonly<Record<number, string>> = {
1: "AlreadyInitialized",
2: "NotInitialized",
3: "Unauthorized",
4: "RouteNotFound",
5: "RoutePaused",
6: "RouterPaused",
7: "RouteAlreadyExists",
8: "InvalidRouteName",
9: "InvalidMetadata",
};
/** Error codes exposed by `router-quote` (`QuoteError`). */
export const ROUTER_QUOTE_ERRORS: Readonly<Record<number, string>> = {
1: "AlreadyInitialized",
2: "NotInitialized",
3: "Unauthorized",
4: "InvalidAmount",
5: "InvalidFeeBps",
6: "NoQuotesProvided",
7: "RouteNotFound",
8: "InvalidPriceImpactBps",
9: "AmountOverflow",
};
/** Options accepted by {@link RouterSdkError}. */
export interface RouterSdkErrorOptions {
/** The contract the failure originated from (e.g. `"core"`, `"quote"`). */
contract?: "core" | "quote" | string;
/** The on-chain numeric error code, when the failure is a contract error. */
contractErrorCode?: number;
/** The underlying cause, when the failure wrapped a thrown value. */
cause?: unknown;
/** The raw error text from the RPC layer, when available. */
rawError?: string;
}
/**
* The error type thrown by every SDK method.
*
* @example
* try {
* await client.resolve("does-not-exist");
* } catch (err) {
* if (err instanceof RouterSdkError) {
* console.log(err.code); // "RouteNotFound"
* }
* }
*/
export class RouterSdkError extends Error {
readonly code: string;
readonly contract?: string;
readonly contractErrorCode?: number;
override readonly cause?: unknown;
readonly rawError?: string;
constructor(code: string, message: string, options: RouterSdkErrorOptions = {}) {
super(message);
this.name = "RouterSdkError";
this.code = code;
this.contract = options.contract;
this.contractErrorCode = options.contractErrorCode;
this.cause = options.cause;
this.rawError = options.rawError;
}
/** Build an error from a numeric on-chain contract error code. */
static fromContractCode(code: number, options: RouterSdkErrorOptions): RouterSdkError {
const table = codeTableFor(options.contract);
const name = table?.[code];
const contractLabel = CONTRACT_LABELS[options.contract ?? ""] ?? options.contract ?? "contract";
const message = name
? `${contractLabel} rejected the call with ${name} (#${code}).`
: `${contractLabel} rejected the call with an unknown error code #${code}.`;
return new RouterSdkError(name ?? `ContractError${code}`, message, {
...options,
contractErrorCode: code,
});
}
/** Wrap an unexpected thrown value (RPC/network failure) into a RouterSdkError. */
static fromCause(contract: string, cause: unknown): RouterSdkError {
const message = cause instanceof Error ? cause.message : String(cause);
return new RouterSdkError("RpcError", `RPC call failed: ${message}`, {
contract,
cause,
});
}
}
function codeTableFor(contract?: string): Readonly<Record<number, string>> | undefined {
if (contract === "core") return ROUTER_CORE_ERRORS;
if (contract === "quote") return ROUTER_QUOTE_ERRORS;
return undefined;
}
/**
* Extract the on-chain contract error number from a raw RPC simulation error
* string. Soroban renders failed contract errors like
* `Error(Contract, #4)`, where `4` is the numeric `contracterror` discriminant.
*
* @returns the numeric code, or `undefined` when `raw` is not a contract error.
*/
export function parseContractErrorCode(raw: string | null | undefined): number | undefined {
if (!raw) return undefined;
const anchored = raw.match(/\(Contract,\s*#(\d+)\)/);
if (anchored) {
const n = Number(anchored[1]);
if (Number.isInteger(n) && n > 0) return n;
}
const loose = raw.match(/Contract\D*#?(\d+)/);
if (loose) {
const n = Number(loose[1]);
if (Number.isInteger(n) && n > 0) return n;
}
return undefined;
}