@seljs/runtime 1.3.0-beta.27 → 1.3.0-beta.28

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.
@@ -194,10 +194,16 @@ var SELRuntime = class {
194
194
  } else if (typeof error.failedCallIndex === "number") failedCall = collectedCalls[error.failedCallIndex] ?? failedCall;
195
195
  }
196
196
  if (!failedCall) throw error;
197
+ const revertFields = error instanceof require_errors.MulticallBatchError ? {
198
+ revertReason: error.revertReason,
199
+ revertData: error.revertData,
200
+ decodedError: error.decodedError
201
+ } : {};
197
202
  throw new require_errors.SELContractError(`Contract call failed: ${failedCall.contract}.${failedCall.method}`, {
198
203
  cause: error,
199
204
  contractName: failedCall.contract,
200
- methodName: failedCall.method
205
+ methodName: failedCall.method,
206
+ ...revertFields
201
207
  });
202
208
  }
203
209
  return {
@@ -193,10 +193,16 @@ var SELRuntime = class {
193
193
  } else if (typeof error.failedCallIndex === "number") failedCall = collectedCalls[error.failedCallIndex] ?? failedCall;
194
194
  }
195
195
  if (!failedCall) throw error;
196
+ const revertFields = error instanceof MulticallBatchError ? {
197
+ revertReason: error.revertReason,
198
+ revertData: error.revertData,
199
+ decodedError: error.decodedError
200
+ } : {};
196
201
  throw new SELContractError(`Contract call failed: ${failedCall.contract}.${failedCall.method}`, {
197
202
  cause: error,
198
203
  contractName: failedCall.contract,
199
- methodName: failedCall.method
204
+ methodName: failedCall.method,
205
+ ...revertFields
200
206
  });
201
207
  }
202
208
  return {
@@ -4,14 +4,23 @@ let _seljs_common = require("@seljs/common");
4
4
  /**
5
5
  * Thrown when contract validation or execution fails.
6
6
  * Includes optional contract name and method name for context.
7
+ * When the failure is a contract revert, the decoded revert reason and raw
8
+ * revert data are exposed on `revertReason` / `revertData`, and `decodedError`
9
+ * carries any matched custom-error name + args from the contract's ABI.
7
10
  */
8
11
  var SELContractError = class extends _seljs_common.SELError {
9
12
  contractName;
10
13
  methodName;
14
+ revertReason;
15
+ revertData;
16
+ decodedError;
11
17
  constructor(message, options) {
12
18
  super(message, { cause: options?.cause });
13
19
  this.contractName = options?.contractName;
14
20
  this.methodName = options?.methodName;
21
+ this.revertReason = options?.revertReason;
22
+ this.revertData = options?.revertData;
23
+ this.decodedError = options?.decodedError;
15
24
  }
16
25
  };
17
26
  /**
@@ -43,16 +52,25 @@ var ExecutionLimitError = class extends _seljs_common.SELError {
43
52
  /**
44
53
  * Thrown when a Multicall3 batch execution fails.
45
54
  * Includes the failed call index and optional contract name/method name for context.
55
+ * When an individual sub-call reverts, `revertReason` / `revertData` carry the
56
+ * decoded reason text and raw return data, and `decodedError` carries any
57
+ * matched custom-error name + args from the contract's ABI.
46
58
  */
47
59
  var MulticallBatchError = class extends _seljs_common.SELError {
48
60
  failedCallIndex;
49
61
  contractName;
50
62
  methodName;
63
+ revertReason;
64
+ revertData;
65
+ decodedError;
51
66
  constructor(message, options) {
52
67
  super(message, { cause: options?.cause });
53
68
  this.failedCallIndex = options?.failedCallIndex;
54
69
  this.contractName = options?.contractName;
55
70
  this.methodName = options?.methodName;
71
+ this.revertReason = options?.revertReason;
72
+ this.revertData = options?.revertData;
73
+ this.decodedError = options?.decodedError;
56
74
  }
57
75
  };
58
76
  /**
@@ -5,14 +5,29 @@ import { SELError, SELEvaluationError } from "@seljs/common";
5
5
  /**
6
6
  * Thrown when contract validation or execution fails.
7
7
  * Includes optional contract name and method name for context.
8
+ * When the failure is a contract revert, the decoded revert reason and raw
9
+ * revert data are exposed on `revertReason` / `revertData`, and `decodedError`
10
+ * carries any matched custom-error name + args from the contract's ABI.
8
11
  */
9
12
  declare class SELContractError extends SELError {
10
13
  readonly contractName?: string;
11
14
  readonly methodName?: string;
15
+ readonly revertReason?: string;
16
+ readonly revertData?: `0x${string}`;
17
+ readonly decodedError?: {
18
+ name: string;
19
+ args: readonly unknown[];
20
+ };
12
21
  constructor(message: string, options?: {
13
22
  cause?: unknown;
14
23
  contractName?: string;
15
24
  methodName?: string;
25
+ revertReason?: string;
26
+ revertData?: `0x${string}`;
27
+ decodedError?: {
28
+ name: string;
29
+ args: readonly unknown[];
30
+ };
16
31
  });
17
32
  }
18
33
  /**
@@ -44,16 +59,31 @@ declare class ExecutionLimitError extends SELError {
44
59
  /**
45
60
  * Thrown when a Multicall3 batch execution fails.
46
61
  * Includes the failed call index and optional contract name/method name for context.
62
+ * When an individual sub-call reverts, `revertReason` / `revertData` carry the
63
+ * decoded reason text and raw return data, and `decodedError` carries any
64
+ * matched custom-error name + args from the contract's ABI.
47
65
  */
48
66
  declare class MulticallBatchError extends SELError {
49
67
  readonly failedCallIndex?: number;
50
68
  readonly contractName?: string;
51
69
  readonly methodName?: string;
70
+ readonly revertReason?: string;
71
+ readonly revertData?: `0x${string}`;
72
+ readonly decodedError?: {
73
+ name: string;
74
+ args: readonly unknown[];
75
+ };
52
76
  constructor(message: string, options?: {
53
77
  cause?: unknown;
54
78
  failedCallIndex?: number;
55
79
  contractName?: string;
56
80
  methodName?: string;
81
+ revertReason?: string;
82
+ revertData?: `0x${string}`;
83
+ decodedError?: {
84
+ name: string;
85
+ args: readonly unknown[];
86
+ };
57
87
  });
58
88
  }
59
89
  /**
@@ -5,14 +5,29 @@ import { SELError as SELError$1, SELEvaluationError } from "@seljs/common";
5
5
  /**
6
6
  * Thrown when contract validation or execution fails.
7
7
  * Includes optional contract name and method name for context.
8
+ * When the failure is a contract revert, the decoded revert reason and raw
9
+ * revert data are exposed on `revertReason` / `revertData`, and `decodedError`
10
+ * carries any matched custom-error name + args from the contract's ABI.
8
11
  */
9
12
  declare class SELContractError extends SELError$1 {
10
13
  readonly contractName?: string;
11
14
  readonly methodName?: string;
15
+ readonly revertReason?: string;
16
+ readonly revertData?: `0x${string}`;
17
+ readonly decodedError?: {
18
+ name: string;
19
+ args: readonly unknown[];
20
+ };
12
21
  constructor(message: string, options?: {
13
22
  cause?: unknown;
14
23
  contractName?: string;
15
24
  methodName?: string;
25
+ revertReason?: string;
26
+ revertData?: `0x${string}`;
27
+ decodedError?: {
28
+ name: string;
29
+ args: readonly unknown[];
30
+ };
16
31
  });
17
32
  }
18
33
  /**
@@ -44,16 +59,31 @@ declare class ExecutionLimitError extends SELError$1 {
44
59
  /**
45
60
  * Thrown when a Multicall3 batch execution fails.
46
61
  * Includes the failed call index and optional contract name/method name for context.
62
+ * When an individual sub-call reverts, `revertReason` / `revertData` carry the
63
+ * decoded reason text and raw return data, and `decodedError` carries any
64
+ * matched custom-error name + args from the contract's ABI.
47
65
  */
48
66
  declare class MulticallBatchError extends SELError$1 {
49
67
  readonly failedCallIndex?: number;
50
68
  readonly contractName?: string;
51
69
  readonly methodName?: string;
70
+ readonly revertReason?: string;
71
+ readonly revertData?: `0x${string}`;
72
+ readonly decodedError?: {
73
+ name: string;
74
+ args: readonly unknown[];
75
+ };
52
76
  constructor(message: string, options?: {
53
77
  cause?: unknown;
54
78
  failedCallIndex?: number;
55
79
  contractName?: string;
56
80
  methodName?: string;
81
+ revertReason?: string;
82
+ revertData?: `0x${string}`;
83
+ decodedError?: {
84
+ name: string;
85
+ args: readonly unknown[];
86
+ };
57
87
  });
58
88
  }
59
89
  /**
@@ -3,14 +3,23 @@ import { SELError as SELError$1, SELEvaluationError } from "@seljs/common";
3
3
  /**
4
4
  * Thrown when contract validation or execution fails.
5
5
  * Includes optional contract name and method name for context.
6
+ * When the failure is a contract revert, the decoded revert reason and raw
7
+ * revert data are exposed on `revertReason` / `revertData`, and `decodedError`
8
+ * carries any matched custom-error name + args from the contract's ABI.
6
9
  */
7
10
  var SELContractError = class extends SELError$1 {
8
11
  contractName;
9
12
  methodName;
13
+ revertReason;
14
+ revertData;
15
+ decodedError;
10
16
  constructor(message, options) {
11
17
  super(message, { cause: options?.cause });
12
18
  this.contractName = options?.contractName;
13
19
  this.methodName = options?.methodName;
20
+ this.revertReason = options?.revertReason;
21
+ this.revertData = options?.revertData;
22
+ this.decodedError = options?.decodedError;
14
23
  }
15
24
  };
16
25
  /**
@@ -42,16 +51,25 @@ var ExecutionLimitError = class extends SELError$1 {
42
51
  /**
43
52
  * Thrown when a Multicall3 batch execution fails.
44
53
  * Includes the failed call index and optional contract name/method name for context.
54
+ * When an individual sub-call reverts, `revertReason` / `revertData` carry the
55
+ * decoded reason text and raw return data, and `decodedError` carries any
56
+ * matched custom-error name + args from the contract's ABI.
45
57
  */
46
58
  var MulticallBatchError = class extends SELError$1 {
47
59
  failedCallIndex;
48
60
  contractName;
49
61
  methodName;
62
+ revertReason;
63
+ revertData;
64
+ decodedError;
50
65
  constructor(message, options) {
51
66
  super(message, { cause: options?.cause });
52
67
  this.failedCallIndex = options?.failedCallIndex;
53
68
  this.contractName = options?.contractName;
54
69
  this.methodName = options?.methodName;
70
+ this.revertReason = options?.revertReason;
71
+ this.revertData = options?.revertData;
72
+ this.decodedError = options?.decodedError;
55
73
  }
56
74
  };
57
75
  /**
@@ -0,0 +1,67 @@
1
+ require("../_virtual/_rolldown/runtime.cjs");
2
+ let ox = require("ox");
3
+ //#region src/execution/decode-revert.ts
4
+ const ERROR_STRING_SELECTOR = "0x08c379a0";
5
+ const PANIC_SELECTOR = "0x4e487b71";
6
+ const PANIC_CODES = {
7
+ 0: "generic panic",
8
+ 1: "assertion failed",
9
+ 17: "arithmetic overflow or underflow",
10
+ 18: "division or modulo by zero",
11
+ 33: "enum conversion out of bounds",
12
+ 34: "incorrectly encoded storage byte array",
13
+ 49: "pop on empty array",
14
+ 50: "array index out of bounds",
15
+ 65: "excessive memory allocation",
16
+ 81: "zero-initialized internal function type"
17
+ };
18
+ const errorStringAbi = ox.AbiError.from("error Error(string reason)");
19
+ const panicAbi = ox.AbiError.from("error Panic(uint256 code)");
20
+ function decodeRevertData(data, abi) {
21
+ if (data === "0x" || data.length < 10) return { data };
22
+ const selector = data.slice(0, 10).toLowerCase();
23
+ if (selector === ERROR_STRING_SELECTOR) try {
24
+ return {
25
+ data,
26
+ reason: ox.AbiError.decode(errorStringAbi, data)
27
+ };
28
+ } catch {
29
+ return { data };
30
+ }
31
+ if (selector === PANIC_SELECTOR) try {
32
+ const code = ox.AbiError.decode(panicAbi, data);
33
+ const numeric = Number(code);
34
+ return {
35
+ data,
36
+ reason: `Panic: ${PANIC_CODES[numeric] ?? `panic code 0x${numeric.toString(16)}`}`
37
+ };
38
+ } catch {
39
+ return { data };
40
+ }
41
+ try {
42
+ const error = ox.AbiError.fromAbi(abi, data);
43
+ const decoded = ox.AbiError.decode(error, data);
44
+ const args = decoded === void 0 ? [] : Array.isArray(decoded) ? decoded : [decoded];
45
+ const formattedArgs = args.map(formatArg).join(", ");
46
+ return {
47
+ data,
48
+ reason: `${error.name}(${formattedArgs})`,
49
+ decodedError: {
50
+ name: error.name,
51
+ args
52
+ }
53
+ };
54
+ } catch {
55
+ return { data };
56
+ }
57
+ }
58
+ function formatArg(arg) {
59
+ if (typeof arg === "bigint") return arg.toString();
60
+ if (typeof arg === "string") return arg;
61
+ if (typeof arg === "number" || typeof arg === "boolean" || arg === null || arg === void 0) return String(arg);
62
+ if (Array.isArray(arg)) return `[${arg.map(formatArg).join(", ")}]`;
63
+ if (typeof arg === "object") return `{${Object.entries(arg).map(([k, v]) => `${k}: ${formatArg(v)}`).join(", ")}}`;
64
+ return "";
65
+ }
66
+ //#endregion
67
+ exports.decodeRevertData = decodeRevertData;
@@ -0,0 +1,66 @@
1
+ import { AbiError } from "ox";
2
+ //#region src/execution/decode-revert.ts
3
+ const ERROR_STRING_SELECTOR = "0x08c379a0";
4
+ const PANIC_SELECTOR = "0x4e487b71";
5
+ const PANIC_CODES = {
6
+ 0: "generic panic",
7
+ 1: "assertion failed",
8
+ 17: "arithmetic overflow or underflow",
9
+ 18: "division or modulo by zero",
10
+ 33: "enum conversion out of bounds",
11
+ 34: "incorrectly encoded storage byte array",
12
+ 49: "pop on empty array",
13
+ 50: "array index out of bounds",
14
+ 65: "excessive memory allocation",
15
+ 81: "zero-initialized internal function type"
16
+ };
17
+ const errorStringAbi = AbiError.from("error Error(string reason)");
18
+ const panicAbi = AbiError.from("error Panic(uint256 code)");
19
+ function decodeRevertData(data, abi) {
20
+ if (data === "0x" || data.length < 10) return { data };
21
+ const selector = data.slice(0, 10).toLowerCase();
22
+ if (selector === ERROR_STRING_SELECTOR) try {
23
+ return {
24
+ data,
25
+ reason: AbiError.decode(errorStringAbi, data)
26
+ };
27
+ } catch {
28
+ return { data };
29
+ }
30
+ if (selector === PANIC_SELECTOR) try {
31
+ const code = AbiError.decode(panicAbi, data);
32
+ const numeric = Number(code);
33
+ return {
34
+ data,
35
+ reason: `Panic: ${PANIC_CODES[numeric] ?? `panic code 0x${numeric.toString(16)}`}`
36
+ };
37
+ } catch {
38
+ return { data };
39
+ }
40
+ try {
41
+ const error = AbiError.fromAbi(abi, data);
42
+ const decoded = AbiError.decode(error, data);
43
+ const args = decoded === void 0 ? [] : Array.isArray(decoded) ? decoded : [decoded];
44
+ const formattedArgs = args.map(formatArg).join(", ");
45
+ return {
46
+ data,
47
+ reason: `${error.name}(${formattedArgs})`,
48
+ decodedError: {
49
+ name: error.name,
50
+ args
51
+ }
52
+ };
53
+ } catch {
54
+ return { data };
55
+ }
56
+ }
57
+ function formatArg(arg) {
58
+ if (typeof arg === "bigint") return arg.toString();
59
+ if (typeof arg === "string") return arg;
60
+ if (typeof arg === "number" || typeof arg === "boolean" || arg === null || arg === void 0) return String(arg);
61
+ if (Array.isArray(arg)) return `[${arg.map(formatArg).join(", ")}]`;
62
+ if (typeof arg === "object") return `{${Object.entries(arg).map(([k, v]) => `${k}: ${formatArg(v)}`).join(", ")}}`;
63
+ return "";
64
+ }
65
+ //#endregion
66
+ export { decodeRevertData };
@@ -2,6 +2,7 @@ require("../_virtual/_rolldown/runtime.cjs");
2
2
  const require_errors = require("../errors/errors.cjs");
3
3
  require("../errors/index.cjs");
4
4
  const require_debug = require("../debug.cjs");
5
+ const require_decode_revert = require("./decode-revert.cjs");
5
6
  let ox = require("ox");
6
7
  //#region src/execution/round-executor.ts
7
8
  const debug = require_debug.createLogger("execute:round");
@@ -26,7 +27,7 @@ var RoundExecutor = class {
26
27
  const resolvedArgs = this.resolveArgs(call.args, context);
27
28
  encodedCalls.push({
28
29
  target: contract.address,
29
- allowFailure: false,
30
+ allowFailure: true,
30
31
  callData: ox.AbiFunction.encodeData(contract.abi, call.method, resolvedArgs)
31
32
  });
32
33
  }
@@ -45,11 +46,18 @@ var RoundExecutor = class {
45
46
  const contract = this.contracts.get(call.contract);
46
47
  if (!contract) throw new Error(`No ABI registered for contract "${call.contract}"`);
47
48
  const result = results[i];
48
- if (!result?.success) throw new require_errors.MulticallBatchError(`Call failed: ${call.contract}.${call.method}`, {
49
- failedCallIndex: i,
50
- contractName: call.contract,
51
- methodName: call.method
52
- });
49
+ if (!result?.success) {
50
+ const decoded = require_decode_revert.decodeRevertData(result?.returnData ?? "0x", contract.abi);
51
+ const suffix = decoded.reason ? ` — ${decoded.reason}` : "";
52
+ throw new require_errors.MulticallBatchError(`Call failed: ${call.contract}.${call.method}${suffix}`, {
53
+ failedCallIndex: i,
54
+ contractName: call.contract,
55
+ methodName: call.method,
56
+ revertReason: decoded.reason,
57
+ revertData: decoded.data,
58
+ decodedError: decoded.decodedError
59
+ });
60
+ }
53
61
  const decoded = ox.AbiFunction.decodeResult(contract.abi, call.method, result.returnData);
54
62
  debug("decoded %s.%s -> %o", call.contract, call.method, decoded);
55
63
  this.cache.set(call.id, decoded);
@@ -1,6 +1,7 @@
1
1
  import { MulticallBatchError } from "../errors/errors.mjs";
2
2
  import "../errors/index.mjs";
3
3
  import { createLogger } from "../debug.mjs";
4
+ import { decodeRevertData } from "./decode-revert.mjs";
4
5
  import { AbiFunction } from "ox";
5
6
  //#region src/execution/round-executor.ts
6
7
  const debug = createLogger("execute:round");
@@ -25,7 +26,7 @@ var RoundExecutor = class {
25
26
  const resolvedArgs = this.resolveArgs(call.args, context);
26
27
  encodedCalls.push({
27
28
  target: contract.address,
28
- allowFailure: false,
29
+ allowFailure: true,
29
30
  callData: AbiFunction.encodeData(contract.abi, call.method, resolvedArgs)
30
31
  });
31
32
  }
@@ -44,11 +45,18 @@ var RoundExecutor = class {
44
45
  const contract = this.contracts.get(call.contract);
45
46
  if (!contract) throw new Error(`No ABI registered for contract "${call.contract}"`);
46
47
  const result = results[i];
47
- if (!result?.success) throw new MulticallBatchError(`Call failed: ${call.contract}.${call.method}`, {
48
- failedCallIndex: i,
49
- contractName: call.contract,
50
- methodName: call.method
51
- });
48
+ if (!result?.success) {
49
+ const decoded = decodeRevertData(result?.returnData ?? "0x", contract.abi);
50
+ const suffix = decoded.reason ? ` — ${decoded.reason}` : "";
51
+ throw new MulticallBatchError(`Call failed: ${call.contract}.${call.method}${suffix}`, {
52
+ failedCallIndex: i,
53
+ contractName: call.contract,
54
+ methodName: call.method,
55
+ revertReason: decoded.reason,
56
+ revertData: decoded.data,
57
+ decodedError: decoded.decodedError
58
+ });
59
+ }
52
60
  const decoded = AbiFunction.decodeResult(contract.abi, call.method, result.returnData);
53
61
  debug("decoded %s.%s -> %o", call.contract, call.method, decoded);
54
62
  this.cache.set(call.id, decoded);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seljs/runtime",
3
- "version": "1.3.0-beta.27",
3
+ "version": "1.3.0-beta.28",
4
4
  "repository": {
5
5
  "url": "https://github.com/abinnovision/seljs"
6
6
  },
@@ -50,11 +50,11 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@marcbachmann/cel-js": "^7.5.3",
53
- "@seljs/checker": "1.3.0-beta.27",
54
- "@seljs/common": "1.3.0-beta.27",
55
- "@seljs/env": "1.3.0-beta.27",
53
+ "@seljs/checker": "1.3.0-beta.28",
54
+ "@seljs/common": "1.3.0-beta.28",
55
+ "@seljs/env": "1.3.0-beta.28",
56
56
  "@seljs/schema": "1.2.0",
57
- "@seljs/types": "1.3.0-beta.27",
57
+ "@seljs/types": "1.3.0-beta.28",
58
58
  "debug": "^4.4.3",
59
59
  "ox": "^0.14.10"
60
60
  },